[bazel] Remove tabs from assembly outputs

Signed-off-by: Miguel Young de la Sota <mcyoung@google.com>
diff --git a/rules/cc_side_outputs.bzl b/rules/cc_side_outputs.bzl
index e363fdb..c0caecd 100644
--- a/rules/cc_side_outputs.bzl
+++ b/rules/cc_side_outputs.bzl
@@ -29,7 +29,12 @@
         if src.files.to_list()[0].extension in [
             # We only use .cc, but to avoid nasty surprises we support all five
             # C++ file extensions.
-            "c", "cc", "cpp", "cxx", "c++", "C",
+            "c",
+            "cc",
+            "cpp",
+            "cxx",
+            "c++",
+            "C",
         ]
     ]
 
@@ -73,18 +78,20 @@
         output_file = ctx.actions.declare_file(
             # Some source files in the repo are currently pulled in by multiple
             # rules, and this is allowed in general, although not a good idea.
-            # 
+            #
             # Adding the rule name in front of the file name helps mitigate this
             # issue.
             "{}.{}.{}".format(target.label.name, source_file.basename, extension),
         )
         outputs.append(output_file)
 
+        #output_file_tmp = ctx.actions.declare_file(output_file.basename + ".tmp")
+
         # C files are treated specially, and have different flags applied
         # (e.g. --std=c11).
         opts = ctx.fragments.cpp.copts + ctx.rule.attr.copts
         if source_file.extension == "c":
-            opts += ctx.fragments.cpp.conlyopts + ctx.rule.attr.conlyopts
+            opts += ctx.fragments.cpp.conlyopts
         else:
             opts += ctx.fragments.cpp.cxxopts + ctx.rule.attr.cxxopts
 
@@ -92,7 +99,6 @@
             feature_configuration = feature_configuration,
             cc_toolchain = cc_toolchain,
             source_file = source_file.path,
-            output_file = output_file.path,
             user_compile_flags = opts + flags,
             include_directories = depset(
                 direct = [src.dirname for src in cc_compile_ctx.headers.to_list()],
@@ -118,9 +124,15 @@
             variables = c_compile_variables,
         )
 
-        ctx.actions.run(
-            executable = c_compiler_path,
-            arguments = command_line,
+        ctx.actions.run_shell(
+            tools = [
+                ctx.file._cleanup_script,
+            ],
+            arguments = [
+                c_compiler_path,
+                ctx.file._cleanup_script.path,
+                output_file.path,
+            ] + command_line,
             env = env,
             inputs = depset(
                 [source_file],
@@ -130,6 +142,12 @@
                 ],
             ),
             outputs = [output_file],
+            command = """
+                CC=$1; shift
+                UNTAB=$1; shift
+                OUT=$1; shift
+                $CC -o - $@ | $UNTAB > $OUT
+            """,
         )
 
     return [CcSideProductInfo(files = depset(
@@ -148,6 +166,10 @@
         all of its dependencies.
     """,
     attrs = {
+        "_cleanup_script": attr.label(
+            allow_single_file = True,
+            default = Label("//rules/scripts:expand_tabs.sh"),
+        ),
         "_cc_toolchain": attr.label(
             default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
         ),
@@ -176,6 +198,10 @@
         If the current compiler does not appear to be clang, it outputs nothing instead.
     """,
     attrs = {
+        "_cleanup_script": attr.label(
+            allow_single_file = True,
+            default = Label("//rules/scripts:expand_tabs.sh"),
+        ),
         "_cc_toolchain": attr.label(
             default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
         ),
diff --git a/rules/opentitan.bzl b/rules/opentitan.bzl
index 1759329..4eb18c3 100644
--- a/rules/opentitan.bzl
+++ b/rules/opentitan.bzl
@@ -126,25 +126,28 @@
         disassembly = ctx.actions.declare_file("{}.elf.s".format(src.basename))
         outputs.append(disassembly)
         ctx.actions.run_shell(
+            tools = [ctx.file._cleanup_script],
             outputs = [disassembly],
             inputs = [src] + cc_toolchain.all_files.to_list(),
             arguments = [
                 cc_toolchain.objdump_executable,
                 src.path,
+                ctx.file._cleanup_script.path,
                 disassembly.path,
             ],
-            command = "$1 --disassemble --headers --line-numbers --source $2 > $3",
+            command = "$1 --disassemble --headers --line-numbers --source $2 | $3 > $4",
         )
-        return [DefaultInfo(
-            files = depset(outputs),
-            data_runfiles = ctx.runfiles(files = outputs),
-        )]
+        return [DefaultInfo(files = depset(outputs), data_runfiles = ctx.runfiles(files = outputs))]
 
 elf_to_disassembly = rv_rule(
     implementation = _elf_to_disassembly_impl,
     attrs = {
         "srcs": attr.label_list(allow_files = True),
         "platform": attr.string(default = OPENTITAN_PLATFORM),
+        "_cleanup_script": attr.label(
+            allow_single_file = True,
+            default = Label("//rules/scripts:expand_tabs.sh"),
+        ),
         "_cc_toolchain": attr.label(default = Label("@bazel_tools//tools/cpp:current_cc_toolchain")),
     },
     toolchains = ["@rules_cc//cc:toolchain_type"],
diff --git a/rules/otbn.bzl b/rules/otbn.bzl
index f787b65..be19780 100644
--- a/rules/otbn.bzl
+++ b/rules/otbn.bzl
@@ -154,7 +154,6 @@
         "srcs": attr.label_list(allow_files = True),
         "_cc_toolchain": attr.label(default = Label("@bazel_tools//tools/cpp:current_cc_toolchain")),
         "_otbn_as": attr.label(default = "//hw/ip/otbn/util:otbn-as", allow_single_file = True),
-
     },
     fragments = ["cpp"],
     toolchains = ["@rules_cc//cc:toolchain_type"],
diff --git a/rules/scripts/expand_tabs.sh b/rules/scripts/expand_tabs.sh
new file mode 100755
index 0000000..7de1bf6
--- /dev/null
+++ b/rules/scripts/expand_tabs.sh
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+#
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+# Helper script that reads a file from stdin and replaces every tab character
+# with at most eight spaces (i.e. what smart tabs would render it as at an
+# eight-space tab.
+# 
+# This is useful for cleaning up the output of GCC tools that are excited about
+# mixing tabs and spaces.
+
+set -e
+
+# A simple perl program that matches every tab in every line
+# and replaces it with `8 - n % 8` spaces, where `n` is the
+# index of the tab. Although the array $@ holds this
+# information, it does not account for previous tab
+# replacements, so we need to maintain a running total of
+# how many new spaces we've added, which we do with `$acc`.
+perl -pe '
+    my $acc = 0;
+    s/\t/
+        my $sp = 8 - ($acc + $-[0]) % 8;
+        $acc += $sp - 1;
+        " " x $sp
+    /eg'
\ No newline at end of file