Miguel Young de la Sota | 4bfbba3 | 2022-03-24 16:12:14 -0400 | [diff] [blame] | 1 | # Copyright lowRISC contributors. |
| 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| 3 | # SPDX-License-Identifier: Apache-2.0 |
| 4 | |
| 5 | """Aspects and rules for making cc_* libraries emit more outputs.""" |
| 6 | |
| 7 | load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES", "C_COMPILE_ACTION_NAME") |
| 8 | load("//rules:bugfix.bzl", "find_cc_toolchain") |
| 9 | load("//rules:rv.bzl", "rv_rule") |
| 10 | |
| 11 | CcSideProductInfo = provider(fields = ["files"]) |
| 12 | |
| 13 | def _cc_compile_different_output(target, ctx, extension, flags): |
| 14 | """ |
| 15 | Helper macro for implementing the .s and .ll outputting libraries. |
| 16 | |
| 17 | In addition to the usual target and ctx inputs for an aspect, it also |
| 18 | takes an extension to add to each source file of the rule being analyzed, |
| 19 | and flags to add to the compiler arguments to get the output we want. |
| 20 | """ |
| 21 | if ctx.rule.kind not in ["cc_library", "cc_binary", "cc_test"]: |
| 22 | return [CcSideProductInfo(files = depset([]))] |
| 23 | |
| 24 | # This filters out both headers and assembly source inputs, neither of which |
| 25 | # make sense to generate an .s output for. |
| 26 | translation_units = [ |
| 27 | src.files.to_list()[0] |
| 28 | for src in ctx.rule.attr.srcs |
| 29 | if src.files.to_list()[0].extension in [ |
| 30 | # We only use .cc, but to avoid nasty surprises we support all five |
| 31 | # C++ file extensions. |
Miguel Young de la Sota | 9fd4883 | 2022-03-25 17:35:06 -0400 | [diff] [blame^] | 32 | "c", |
| 33 | "cc", |
| 34 | "cpp", |
| 35 | "cxx", |
| 36 | "c++", |
| 37 | "C", |
Miguel Young de la Sota | 4bfbba3 | 2022-03-24 16:12:14 -0400 | [diff] [blame] | 38 | ] |
| 39 | ] |
| 40 | |
| 41 | transitive = [] |
| 42 | for dep in ctx.rule.attr.deps: |
| 43 | if CcSideProductInfo in dep: |
| 44 | transitive.append(dep[CcSideProductInfo].files) |
| 45 | |
| 46 | # Libraries consisting of just headers or assembly files have nothing |
| 47 | # useful to contribute to the output. |
| 48 | if len(translation_units) == 0: |
| 49 | return [CcSideProductInfo(files = depset( |
| 50 | transitive = transitive, |
| 51 | ))] |
| 52 | |
| 53 | if CcInfo in target: |
| 54 | cc_info = target[CcInfo] |
| 55 | else: |
| 56 | # Some rules, like cc_binary, do NOT produce a CcInfo provider. Therefore, |
| 57 | # we need to build one from its dependencies. |
| 58 | cc_info = cc_common.merge_cc_infos( |
| 59 | direct_cc_infos = [dep[CcInfo] for dep in ctx.rule.attr.deps if CcInfo in dep], |
| 60 | ) |
| 61 | cc_compile_ctx = cc_info.compilation_context |
| 62 | |
| 63 | cc_toolchain = find_cc_toolchain(ctx) |
| 64 | feature_configuration = cc_common.configure_features( |
| 65 | ctx = ctx, |
| 66 | cc_toolchain = cc_toolchain, |
| 67 | requested_features = ctx.features, |
| 68 | unsupported_features = ctx.disabled_features, |
| 69 | ) |
| 70 | |
| 71 | c_compiler_path = cc_common.get_tool_for_action( |
| 72 | feature_configuration = feature_configuration, |
| 73 | action_name = ACTION_NAMES.c_compile, |
| 74 | ) |
| 75 | |
| 76 | outputs = [] |
| 77 | for source_file in translation_units: |
| 78 | output_file = ctx.actions.declare_file( |
| 79 | # Some source files in the repo are currently pulled in by multiple |
| 80 | # rules, and this is allowed in general, although not a good idea. |
Miguel Young de la Sota | 9fd4883 | 2022-03-25 17:35:06 -0400 | [diff] [blame^] | 81 | # |
Miguel Young de la Sota | 4bfbba3 | 2022-03-24 16:12:14 -0400 | [diff] [blame] | 82 | # Adding the rule name in front of the file name helps mitigate this |
| 83 | # issue. |
| 84 | "{}.{}.{}".format(target.label.name, source_file.basename, extension), |
| 85 | ) |
| 86 | outputs.append(output_file) |
| 87 | |
Miguel Young de la Sota | 9fd4883 | 2022-03-25 17:35:06 -0400 | [diff] [blame^] | 88 | #output_file_tmp = ctx.actions.declare_file(output_file.basename + ".tmp") |
| 89 | |
Miguel Young de la Sota | 4bfbba3 | 2022-03-24 16:12:14 -0400 | [diff] [blame] | 90 | # C files are treated specially, and have different flags applied |
| 91 | # (e.g. --std=c11). |
| 92 | opts = ctx.fragments.cpp.copts + ctx.rule.attr.copts |
| 93 | if source_file.extension == "c": |
Miguel Young de la Sota | 9fd4883 | 2022-03-25 17:35:06 -0400 | [diff] [blame^] | 94 | opts += ctx.fragments.cpp.conlyopts |
Miguel Young de la Sota | 4bfbba3 | 2022-03-24 16:12:14 -0400 | [diff] [blame] | 95 | else: |
| 96 | opts += ctx.fragments.cpp.cxxopts + ctx.rule.attr.cxxopts |
| 97 | |
| 98 | c_compile_variables = cc_common.create_compile_variables( |
| 99 | feature_configuration = feature_configuration, |
| 100 | cc_toolchain = cc_toolchain, |
| 101 | source_file = source_file.path, |
Miguel Young de la Sota | 4bfbba3 | 2022-03-24 16:12:14 -0400 | [diff] [blame] | 102 | user_compile_flags = opts + flags, |
| 103 | include_directories = depset( |
| 104 | direct = [src.dirname for src in cc_compile_ctx.headers.to_list()], |
| 105 | transitive = [cc_compile_ctx.includes], |
| 106 | ), |
| 107 | quote_include_directories = cc_compile_ctx.quote_includes, |
| 108 | system_include_directories = cc_compile_ctx.system_includes, |
| 109 | framework_include_directories = cc_compile_ctx.framework_includes, |
| 110 | preprocessor_defines = depset( |
| 111 | direct = ctx.rule.attr.local_defines, |
| 112 | transitive = [cc_compile_ctx.defines], |
| 113 | ), |
| 114 | ) |
| 115 | |
| 116 | command_line = cc_common.get_memory_inefficient_command_line( |
| 117 | feature_configuration = feature_configuration, |
| 118 | action_name = ACTION_NAMES.c_compile, |
| 119 | variables = c_compile_variables, |
| 120 | ) |
| 121 | env = cc_common.get_environment_variables( |
| 122 | feature_configuration = feature_configuration, |
| 123 | action_name = ACTION_NAMES.c_compile, |
| 124 | variables = c_compile_variables, |
| 125 | ) |
| 126 | |
Miguel Young de la Sota | 9fd4883 | 2022-03-25 17:35:06 -0400 | [diff] [blame^] | 127 | ctx.actions.run_shell( |
| 128 | tools = [ |
| 129 | ctx.file._cleanup_script, |
| 130 | ], |
| 131 | arguments = [ |
| 132 | c_compiler_path, |
| 133 | ctx.file._cleanup_script.path, |
| 134 | output_file.path, |
| 135 | ] + command_line, |
Miguel Young de la Sota | 4bfbba3 | 2022-03-24 16:12:14 -0400 | [diff] [blame] | 136 | env = env, |
| 137 | inputs = depset( |
| 138 | [source_file], |
| 139 | transitive = [ |
| 140 | cc_toolchain.all_files, |
| 141 | cc_compile_ctx.headers, |
| 142 | ], |
| 143 | ), |
| 144 | outputs = [output_file], |
Miguel Young de la Sota | 9fd4883 | 2022-03-25 17:35:06 -0400 | [diff] [blame^] | 145 | command = """ |
| 146 | CC=$1; shift |
| 147 | UNTAB=$1; shift |
| 148 | OUT=$1; shift |
| 149 | $CC -o - $@ | $UNTAB > $OUT |
| 150 | """, |
Miguel Young de la Sota | 4bfbba3 | 2022-03-24 16:12:14 -0400 | [diff] [blame] | 151 | ) |
| 152 | |
| 153 | return [CcSideProductInfo(files = depset( |
| 154 | direct = outputs, |
| 155 | transitive = transitive, |
| 156 | ))] |
| 157 | |
| 158 | def _cc_assembly_aspect_impl(target, ctx): |
| 159 | return _cc_compile_different_output(target, ctx, "s", ["-S"]) |
| 160 | |
| 161 | cc_asm_aspect = aspect( |
| 162 | implementation = _cc_assembly_aspect_impl, |
| 163 | doc = """ |
| 164 | An aspect that provides a CcSideProductInfo containing the assembly file outputs |
| 165 | of every C/C++ translation unit in the sources of the rule it is applied to and |
| 166 | all of its dependencies. |
| 167 | """, |
| 168 | attrs = { |
Miguel Young de la Sota | 9fd4883 | 2022-03-25 17:35:06 -0400 | [diff] [blame^] | 169 | "_cleanup_script": attr.label( |
| 170 | allow_single_file = True, |
| 171 | default = Label("//rules/scripts:expand_tabs.sh"), |
| 172 | ), |
Miguel Young de la Sota | 4bfbba3 | 2022-03-24 16:12:14 -0400 | [diff] [blame] | 173 | "_cc_toolchain": attr.label( |
| 174 | default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"), |
| 175 | ), |
| 176 | }, |
| 177 | attr_aspects = ["deps"], |
| 178 | provides = [CcSideProductInfo], |
| 179 | toolchains = ["@rules_cc//cc:toolchain_type"], |
| 180 | incompatible_use_toolchain_transition = True, |
| 181 | fragments = ["cpp"], |
| 182 | host_fragments = ["cpp"], |
| 183 | ) |
| 184 | |
| 185 | def _cc_llvm_aspect_impl(target, ctx): |
| 186 | cc_toolchain = find_cc_toolchain(ctx) |
| 187 | if cc_toolchain.compiler.find("clang") == -1: |
| 188 | return CcSideProductInfo(files = depset()) |
| 189 | return _cc_compile_different_output(target, ctx, "ll", ["-S", "-emit-llvm"]) |
| 190 | |
| 191 | cc_llvm_aspect = aspect( |
| 192 | implementation = _cc_llvm_aspect_impl, |
| 193 | doc = """ |
| 194 | An aspect that provides a CcSideProductInfo containing the LLVM IR file outputs |
| 195 | of every C/C++ translation unit in the sources of the rule it is applied to and |
| 196 | all of its dependencies. |
| 197 | |
| 198 | If the current compiler does not appear to be clang, it outputs nothing instead. |
| 199 | """, |
| 200 | attrs = { |
Miguel Young de la Sota | 9fd4883 | 2022-03-25 17:35:06 -0400 | [diff] [blame^] | 201 | "_cleanup_script": attr.label( |
| 202 | allow_single_file = True, |
| 203 | default = Label("//rules/scripts:expand_tabs.sh"), |
| 204 | ), |
Miguel Young de la Sota | 4bfbba3 | 2022-03-24 16:12:14 -0400 | [diff] [blame] | 205 | "_cc_toolchain": attr.label( |
| 206 | default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"), |
| 207 | ), |
| 208 | }, |
| 209 | attr_aspects = ["deps"], |
| 210 | provides = [CcSideProductInfo], |
| 211 | toolchains = ["@rules_cc//cc:toolchain_type"], |
| 212 | incompatible_use_toolchain_transition = True, |
| 213 | fragments = ["cpp"], |
| 214 | host_fragments = ["cpp"], |
| 215 | ) |
| 216 | |
| 217 | MapFile = provider(fields = ["map_file"]) |
| 218 | |
| 219 | def _cc_relink_with_linkmap_aspect_impl(target, ctx): |
| 220 | # As mentioned above, there is no CcInfo in a cc_binary, so we're forced |
| 221 | # to rumage around the target's actions for its link action, and then |
| 222 | # re-run it, but capturing the map file as a side-product. |
| 223 | link_action = None |
| 224 | for action in target.actions: |
| 225 | if action.mnemonic == "CppLink": |
| 226 | link_action = action |
| 227 | break |
| 228 | if not link_action: |
| 229 | return [MapFile(map_file = None)] |
| 230 | |
| 231 | output_file = ctx.actions.declare_file(target.label.name + ".ldmap") |
| 232 | |
| 233 | cc_toolchain = find_cc_toolchain(ctx) |
| 234 | feature_configuration = cc_common.configure_features( |
| 235 | ctx = ctx, |
| 236 | cc_toolchain = cc_toolchain, |
| 237 | requested_features = ctx.features, |
| 238 | unsupported_features = ctx.disabled_features, |
| 239 | ) |
| 240 | linker_path = cc_common.get_tool_for_action( |
| 241 | feature_configuration = feature_configuration, |
| 242 | action_name = ACTION_NAMES.cpp_link_executable, |
| 243 | ) |
| 244 | |
| 245 | ctx.actions.run( |
| 246 | executable = linker_path, |
| 247 | arguments = link_action.argv[1:] + ["-Wl,-Map=" + output_file.path], |
| 248 | env = link_action.env, |
| 249 | inputs = link_action.inputs, |
| 250 | outputs = [output_file], |
| 251 | ) |
| 252 | |
| 253 | return [MapFile(map_file = output_file)] |
| 254 | |
| 255 | cc_relink_with_linkmap_aspect = aspect( |
| 256 | implementation = _cc_relink_with_linkmap_aspect_impl, |
| 257 | doc = """ |
| 258 | An aspect to apply to a cc_binary rule that rebuilds its linker invocation and |
| 259 | re-runs it, capturing the map file as an output. |
| 260 | |
| 261 | This needs to exist because cc_binary currently does not allow us to specify that |
| 262 | it may emit multiple outputs. |
| 263 | """, |
| 264 | attrs = { |
| 265 | "_cc_toolchain": attr.label( |
| 266 | default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"), |
| 267 | ), |
| 268 | }, |
| 269 | provides = [MapFile], |
| 270 | toolchains = ["@rules_cc//cc:toolchain_type"], |
| 271 | incompatible_use_toolchain_transition = True, |
| 272 | fragments = ["cpp"], |
| 273 | host_fragments = ["cpp"], |
| 274 | ) |
| 275 | |
| 276 | def _rv_asm_impl(ctx): |
| 277 | return [DefaultInfo( |
| 278 | files = ctx.attr.target[CcSideProductInfo].files, |
| 279 | data_runfiles = ctx.runfiles(transitive_files = ctx.attr.target[CcSideProductInfo].files), |
| 280 | )] |
| 281 | |
| 282 | rv_asm = rv_rule( |
| 283 | implementation = _rv_asm_impl, |
| 284 | attrs = {"target": attr.label(aspects = [cc_asm_aspect])}, |
| 285 | ) |
| 286 | |
| 287 | def _llvm_ir_impl(ctx): |
| 288 | return [DefaultInfo( |
| 289 | files = ctx.attr.target[CcSideProductInfo].files, |
| 290 | data_runfiles = ctx.runfiles(transitive_files = ctx.attr.target[CcSideProductInfo].files), |
| 291 | )] |
| 292 | |
| 293 | rv_llvm_ir = rv_rule( |
| 294 | implementation = _rv_asm_impl, |
| 295 | attrs = {"target": attr.label(aspects = [cc_llvm_aspect])}, |
| 296 | ) |
| 297 | |
| 298 | def _cc_relink_with_linkmap_impl(ctx): |
| 299 | return [DefaultInfo( |
| 300 | files = depset([ctx.attr.target[MapFile].map_file]), |
| 301 | data_runfiles = ctx.runfiles(files = [ctx.attr.target[MapFile].map_file]), |
| 302 | )] |
| 303 | |
| 304 | rv_relink_with_linkmap = rv_rule( |
| 305 | implementation = _cc_relink_with_linkmap_impl, |
| 306 | attrs = {"target": attr.label(aspects = [cc_relink_with_linkmap_aspect])}, |
| 307 | ) |