| # Copyright lowRISC contributors. |
| # Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| # SPDX-License-Identifier: Apache-2.0 |
| |
| """Aspects and rules for making cc_* libraries emit more outputs.""" |
| |
| load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES", "C_COMPILE_ACTION_NAME") |
| load("@rules_cc//cc:find_cc_toolchain.bzl", "find_cc_toolchain") |
| load("//rules:rv.bzl", "rv_rule") |
| |
| CcSideProductInfo = provider(fields = ["files"]) |
| |
| def _is_c_or_cc(file): |
| return file.extension in [ |
| "c", |
| # We only use .cc, but to avoid nasty surprises we support all five |
| # C++ file extensions. |
| "cc", |
| "cpp", |
| "cxx", |
| "c++", |
| "C", |
| ] |
| |
| def _cc_compile_different_output(name, target, ctx, extension, flags, process_all_files = False): |
| """ |
| Helper macro for implementing the .s and .ll outputting libraries. |
| |
| In addition to the usual target and ctx inputs for an aspect, it also |
| takes an extension to add to each source file of the rule being analyzed, |
| and flags to add to the compiler arguments to get the output we want. |
| """ |
| if ctx.rule.kind not in ["cc_library", "cc_binary", "cc_test"]: |
| return [CcSideProductInfo(files = depset([]))] |
| |
| # This filters out both headers and assembly source inputs, neither of which |
| # make sense to generate an .s output for. |
| translation_units = [ |
| src.files.to_list()[0] |
| for src in ctx.rule.attr.srcs |
| if process_all_files or _is_c_or_cc(src.files.to_list()[0]) |
| ] |
| |
| transitive = [] |
| for dep in ctx.rule.attr.deps: |
| if CcSideProductInfo in dep: |
| transitive.append(dep[CcSideProductInfo].files) |
| |
| # Libraries consisting of just headers or assembly files have nothing |
| # useful to contribute to the output. |
| if len(translation_units) == 0: |
| return [CcSideProductInfo(files = depset( |
| transitive = transitive, |
| ))] |
| |
| if CcInfo in target: |
| cc_info = target[CcInfo] |
| else: |
| # Some rules, like cc_binary, do NOT produce a CcInfo provider. Therefore, |
| # we need to build one from its dependencies. |
| cc_info = cc_common.merge_cc_infos( |
| direct_cc_infos = [dep[CcInfo] for dep in ctx.rule.attr.deps if CcInfo in dep], |
| ) |
| cc_compile_ctx = cc_info.compilation_context |
| |
| cc_toolchain = find_cc_toolchain(ctx).cc |
| feature_configuration = cc_common.configure_features( |
| ctx = ctx, |
| cc_toolchain = cc_toolchain, |
| requested_features = ctx.features, |
| unsupported_features = ctx.disabled_features, |
| ) |
| |
| c_compiler_path = cc_common.get_tool_for_action( |
| feature_configuration = feature_configuration, |
| action_name = ACTION_NAMES.c_compile, |
| ) |
| |
| outputs = [] |
| for source_file in translation_units: |
| 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) |
| |
| # C files are treated specially, and have different flags applied |
| # (e.g. --std=c11). |
| # |
| # Things that are neither C or C++ TU files don't get any flags. |
| opts = ctx.fragments.cpp.copts + ctx.rule.attr.copts |
| if source_file.extension == "c": |
| opts += ctx.fragments.cpp.conlyopts |
| elif _is_c_or_cc(source_file): |
| opts += ctx.fragments.cpp.cxxopts |
| if hasattr(ctx.rule.attr, "cxxopts"): |
| opts += ctx.rule.attr.cxxopts |
| |
| c_compile_variables = cc_common.create_compile_variables( |
| feature_configuration = feature_configuration, |
| cc_toolchain = cc_toolchain, |
| source_file = source_file.path, |
| user_compile_flags = opts + flags, |
| include_directories = depset( |
| direct = [src.dirname for src in cc_compile_ctx.headers.to_list()], |
| transitive = [cc_compile_ctx.includes], |
| ), |
| quote_include_directories = cc_compile_ctx.quote_includes, |
| system_include_directories = cc_compile_ctx.system_includes, |
| framework_include_directories = cc_compile_ctx.framework_includes, |
| preprocessor_defines = depset( |
| direct = ctx.rule.attr.local_defines, |
| transitive = [cc_compile_ctx.defines], |
| ), |
| ) |
| |
| command_line = cc_common.get_memory_inefficient_command_line( |
| feature_configuration = feature_configuration, |
| action_name = ACTION_NAMES.c_compile, |
| variables = c_compile_variables, |
| ) |
| env = cc_common.get_environment_variables( |
| feature_configuration = feature_configuration, |
| action_name = ACTION_NAMES.c_compile, |
| variables = c_compile_variables, |
| ) |
| |
| if hasattr(ctx.file, "_clang_format") and source_file.extension != "S": |
| oldenv = env |
| env = {"CLANG_FORMAT": ctx.file._clang_format.path} |
| for k, v in oldenv.items(): |
| env[k] = v |
| |
| ctx.actions.run_shell( |
| mnemonic = name, |
| 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], |
| transitive = [ |
| cc_toolchain.all_files, |
| cc_compile_ctx.headers, |
| ], |
| ), |
| outputs = [output_file], |
| command = """ |
| CC=$1; shift |
| CLEANUP=$1; shift |
| OUT=$1; shift |
| $CC -o - $@ 2> /dev/null | $CLEANUP > $OUT |
| """, |
| ) |
| |
| return [CcSideProductInfo(files = depset( |
| direct = outputs, |
| transitive = transitive, |
| ))] |
| |
| def _cc_preprocess_aspect_impl(target, ctx): |
| return _cc_compile_different_output("Preprocess", target, ctx, "i", ["-E"], process_all_files = True) |
| |
| cc_preprocess_aspect = aspect( |
| implementation = _cc_preprocess_aspect_impl, |
| doc = """ |
| An aspect that provides a CcSideProductInfo containing the preprocessed outputs |
| of every C/C++ translation unit in the sources of the rule it is applied to and |
| all of its dependencies. |
| """, |
| attrs = { |
| "_cleanup_script": attr.label( |
| allow_single_file = True, |
| default = Label("//rules/scripts:clean_up_cpp_output.sh"), |
| ), |
| "_clang_format": attr.label( |
| default = "@lowrisc_rv32imcb_files//:bin/clang-format", |
| allow_single_file = True, |
| cfg = "host", |
| executable = True, |
| ), |
| "_cc_toolchain": attr.label( |
| default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"), |
| ), |
| }, |
| attr_aspects = ["deps"], |
| provides = [CcSideProductInfo], |
| toolchains = ["@rules_cc//cc:toolchain_type"], |
| incompatible_use_toolchain_transition = True, |
| fragments = ["cpp"], |
| host_fragments = ["cpp"], |
| ) |
| |
| def _cc_assembly_aspect_impl(target, ctx): |
| return _cc_compile_different_output("AsmOutput", target, ctx, "s", ["-S"]) |
| |
| cc_asm_aspect = aspect( |
| implementation = _cc_assembly_aspect_impl, |
| doc = """ |
| An aspect that provides a CcSideProductInfo containing the assembly file outputs |
| of every C/C++ translation unit in the sources of the rule it is applied to and |
| 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"), |
| ), |
| }, |
| attr_aspects = ["deps"], |
| provides = [CcSideProductInfo], |
| toolchains = ["@rules_cc//cc:toolchain_type"], |
| incompatible_use_toolchain_transition = True, |
| fragments = ["cpp"], |
| host_fragments = ["cpp"], |
| ) |
| |
| def _cc_llvm_aspect_impl(target, ctx): |
| cc_toolchain = find_cc_toolchain(ctx).cc |
| if cc_toolchain.compiler.find("clang") == -1: |
| return CcSideProductInfo(files = depset()) |
| return _cc_compile_different_output("LLVMOutput", target, ctx, "ll", ["-S", "-emit-llvm"]) |
| |
| cc_llvm_aspect = aspect( |
| implementation = _cc_llvm_aspect_impl, |
| doc = """ |
| An aspect that provides a CcSideProductInfo containing the LLVM IR file outputs |
| of every C/C++ translation unit in the sources of the rule it is applied to and |
| all of its dependencies. |
| |
| 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"), |
| ), |
| }, |
| attr_aspects = ["deps"], |
| provides = [CcSideProductInfo], |
| toolchains = ["@rules_cc//cc:toolchain_type"], |
| incompatible_use_toolchain_transition = True, |
| fragments = ["cpp"], |
| host_fragments = ["cpp"], |
| ) |
| |
| MapFile = provider(fields = ["map_file"]) |
| |
| def _cc_relink_with_linkmap_aspect_impl(target, ctx): |
| # As mentioned above, there is no CcInfo in a cc_binary, so we're forced |
| # to rumage around the target's actions for its link action, and then |
| # re-run it, but capturing the map file as a side-product. |
| link_action = None |
| for action in target.actions: |
| if action.mnemonic == "CppLink": |
| link_action = action |
| break |
| if not link_action: |
| return [MapFile(map_file = None)] |
| |
| output_file = ctx.actions.declare_file(target.label.name + ".ldmap") |
| |
| cc_toolchain = find_cc_toolchain(ctx).cc |
| feature_configuration = cc_common.configure_features( |
| ctx = ctx, |
| cc_toolchain = cc_toolchain, |
| requested_features = ctx.features, |
| unsupported_features = ctx.disabled_features, |
| ) |
| linker_path = cc_common.get_tool_for_action( |
| feature_configuration = feature_configuration, |
| action_name = ACTION_NAMES.cpp_link_executable, |
| ) |
| |
| ctx.actions.run( |
| mnemonic = "LinkMapFile", |
| executable = linker_path, |
| arguments = link_action.argv[1:] + ["-Wl,-Map=" + output_file.path], |
| env = link_action.env, |
| inputs = link_action.inputs, |
| outputs = [output_file], |
| ) |
| |
| return [MapFile(map_file = output_file)] |
| |
| cc_relink_with_linkmap_aspect = aspect( |
| implementation = _cc_relink_with_linkmap_aspect_impl, |
| doc = """ |
| An aspect to apply to a cc_binary rule that rebuilds its linker invocation and |
| re-runs it, capturing the map file as an output. |
| |
| This needs to exist because cc_binary currently does not allow us to specify that |
| it may emit multiple outputs. |
| """, |
| attrs = { |
| "_cc_toolchain": attr.label( |
| default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"), |
| ), |
| }, |
| provides = [MapFile], |
| toolchains = ["@rules_cc//cc:toolchain_type"], |
| incompatible_use_toolchain_transition = True, |
| fragments = ["cpp"], |
| host_fragments = ["cpp"], |
| ) |
| |
| def _rv_preprocess_impl(ctx): |
| return [DefaultInfo( |
| files = ctx.attr.target[CcSideProductInfo].files, |
| data_runfiles = ctx.runfiles(transitive_files = ctx.attr.target[CcSideProductInfo].files), |
| )] |
| |
| rv_preprocess = rv_rule( |
| implementation = _rv_preprocess_impl, |
| attrs = {"target": attr.label(aspects = [cc_preprocess_aspect])}, |
| ) |
| |
| def _rv_asm_impl(ctx): |
| return [DefaultInfo( |
| files = ctx.attr.target[CcSideProductInfo].files, |
| data_runfiles = ctx.runfiles(transitive_files = ctx.attr.target[CcSideProductInfo].files), |
| )] |
| |
| rv_asm = rv_rule( |
| implementation = _rv_asm_impl, |
| attrs = {"target": attr.label(aspects = [cc_asm_aspect])}, |
| ) |
| |
| def _llvm_ir_impl(ctx): |
| return [DefaultInfo( |
| files = ctx.attr.target[CcSideProductInfo].files, |
| data_runfiles = ctx.runfiles(transitive_files = ctx.attr.target[CcSideProductInfo].files), |
| )] |
| |
| rv_llvm_ir = rv_rule( |
| implementation = _rv_asm_impl, |
| attrs = {"target": attr.label(aspects = [cc_llvm_aspect])}, |
| ) |
| |
| def _cc_relink_with_linkmap_impl(ctx): |
| return [DefaultInfo( |
| files = depset([ctx.attr.target[MapFile].map_file]), |
| data_runfiles = ctx.runfiles(files = [ctx.attr.target[MapFile].map_file]), |
| )] |
| |
| rv_relink_with_linkmap = rv_rule( |
| implementation = _cc_relink_with_linkmap_impl, |
| attrs = {"target": attr.label(aspects = [cc_relink_with_linkmap_aspect])}, |
| ) |