blob: fea74aaf7b3ed707e480473fb8a688074707a3c5 [file] [log] [blame]
# 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])},
)