blob: 5fcff4b824709e9918c72124ce4d9c8a6bdd9f0b [file] [log] [blame]
Miguel Young de la Sota4bfbba32022-03-24 16:12:14 -04001# 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
7load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES", "C_COMPILE_ACTION_NAME")
Miguel Young de la Sota4b01c7a2022-04-21 17:25:52 -04008load("@rules_cc//cc:find_cc_toolchain.bzl", "find_cc_toolchain")
Miguel Young de la Sota4bfbba32022-03-24 16:12:14 -04009load("//rules:rv.bzl", "rv_rule")
10
11CcSideProductInfo = provider(fields = ["files"])
12
Miguel Young de la Sota7bb7fe72022-04-01 16:40:02 -040013def _is_c_or_cc(file):
14 return file.extension in [
15 "c",
16 # We only use .cc, but to avoid nasty surprises we support all five
17 # C++ file extensions.
18 "cc",
19 "cpp",
20 "cxx",
21 "c++",
22 "C",
23 ]
24
25def _cc_compile_different_output(name, target, ctx, extension, flags, process_all_files = False):
Miguel Young de la Sota4bfbba32022-03-24 16:12:14 -040026 """
27 Helper macro for implementing the .s and .ll outputting libraries.
28
29 In addition to the usual target and ctx inputs for an aspect, it also
30 takes an extension to add to each source file of the rule being analyzed,
31 and flags to add to the compiler arguments to get the output we want.
32 """
33 if ctx.rule.kind not in ["cc_library", "cc_binary", "cc_test"]:
34 return [CcSideProductInfo(files = depset([]))]
35
36 # This filters out both headers and assembly source inputs, neither of which
37 # make sense to generate an .s output for.
38 translation_units = [
39 src.files.to_list()[0]
40 for src in ctx.rule.attr.srcs
Miguel Young de la Sota7bb7fe72022-04-01 16:40:02 -040041 if process_all_files or _is_c_or_cc(src.files.to_list()[0])
Miguel Young de la Sota4bfbba32022-03-24 16:12:14 -040042 ]
43
44 transitive = []
45 for dep in ctx.rule.attr.deps:
46 if CcSideProductInfo in dep:
47 transitive.append(dep[CcSideProductInfo].files)
48
49 # Libraries consisting of just headers or assembly files have nothing
50 # useful to contribute to the output.
51 if len(translation_units) == 0:
52 return [CcSideProductInfo(files = depset(
53 transitive = transitive,
54 ))]
55
56 if CcInfo in target:
57 cc_info = target[CcInfo]
58 else:
59 # Some rules, like cc_binary, do NOT produce a CcInfo provider. Therefore,
60 # we need to build one from its dependencies.
61 cc_info = cc_common.merge_cc_infos(
62 direct_cc_infos = [dep[CcInfo] for dep in ctx.rule.attr.deps if CcInfo in dep],
63 )
64 cc_compile_ctx = cc_info.compilation_context
65
Miguel Young de la Sota4b01c7a2022-04-21 17:25:52 -040066 cc_toolchain = find_cc_toolchain(ctx).cc
Miguel Young de la Sota4bfbba32022-03-24 16:12:14 -040067 feature_configuration = cc_common.configure_features(
68 ctx = ctx,
69 cc_toolchain = cc_toolchain,
70 requested_features = ctx.features,
71 unsupported_features = ctx.disabled_features,
72 )
73
74 c_compiler_path = cc_common.get_tool_for_action(
75 feature_configuration = feature_configuration,
76 action_name = ACTION_NAMES.c_compile,
77 )
78
79 outputs = []
80 for source_file in translation_units:
81 output_file = ctx.actions.declare_file(
82 # Some source files in the repo are currently pulled in by multiple
83 # rules, and this is allowed in general, although not a good idea.
Miguel Young de la Sota9fd48832022-03-25 17:35:06 -040084 #
Miguel Young de la Sota4bfbba32022-03-24 16:12:14 -040085 # Adding the rule name in front of the file name helps mitigate this
86 # issue.
87 "{}.{}.{}".format(target.label.name, source_file.basename, extension),
88 )
89 outputs.append(output_file)
90
91 # C files are treated specially, and have different flags applied
92 # (e.g. --std=c11).
Miguel Young de la Sota98cfa222022-04-06 15:31:06 -040093 #
Miguel Young de la Sota7bb7fe72022-04-01 16:40:02 -040094 # Things that are neither C or C++ TU files don't get any flags.
Miguel Young de la Sota4bfbba32022-03-24 16:12:14 -040095 opts = ctx.fragments.cpp.copts + ctx.rule.attr.copts
96 if source_file.extension == "c":
Miguel Young de la Sota9fd48832022-03-25 17:35:06 -040097 opts += ctx.fragments.cpp.conlyopts
Miguel Young de la Sota7bb7fe72022-04-01 16:40:02 -040098 elif _is_c_or_cc(source_file):
Miguel Young de la Sota4bfbba32022-03-24 16:12:14 -040099 opts += ctx.fragments.cpp.cxxopts + ctx.rule.attr.cxxopts
100
101 c_compile_variables = cc_common.create_compile_variables(
102 feature_configuration = feature_configuration,
103 cc_toolchain = cc_toolchain,
104 source_file = source_file.path,
Miguel Young de la Sota4bfbba32022-03-24 16:12:14 -0400105 user_compile_flags = opts + flags,
106 include_directories = depset(
107 direct = [src.dirname for src in cc_compile_ctx.headers.to_list()],
108 transitive = [cc_compile_ctx.includes],
109 ),
110 quote_include_directories = cc_compile_ctx.quote_includes,
111 system_include_directories = cc_compile_ctx.system_includes,
112 framework_include_directories = cc_compile_ctx.framework_includes,
113 preprocessor_defines = depset(
114 direct = ctx.rule.attr.local_defines,
115 transitive = [cc_compile_ctx.defines],
116 ),
117 )
118
119 command_line = cc_common.get_memory_inefficient_command_line(
120 feature_configuration = feature_configuration,
121 action_name = ACTION_NAMES.c_compile,
122 variables = c_compile_variables,
123 )
124 env = cc_common.get_environment_variables(
125 feature_configuration = feature_configuration,
126 action_name = ACTION_NAMES.c_compile,
127 variables = c_compile_variables,
128 )
129
Miguel Young de la Sota7bb7fe72022-04-01 16:40:02 -0400130 if hasattr(ctx.file, "_clang_format") and source_file.extension != "S":
131 oldenv = env
132 env = {"CLANG_FORMAT": ctx.file._clang_format.path}
133 for k, v in oldenv.items():
134 env[k] = v
135
Miguel Young de la Sota9fd48832022-03-25 17:35:06 -0400136 ctx.actions.run_shell(
Miguel Young de la Sota7bb7fe72022-04-01 16:40:02 -0400137 mnemonic = name,
Miguel Young de la Sota9fd48832022-03-25 17:35:06 -0400138 tools = [
139 ctx.file._cleanup_script,
140 ],
141 arguments = [
142 c_compiler_path,
143 ctx.file._cleanup_script.path,
144 output_file.path,
145 ] + command_line,
Miguel Young de la Sota4bfbba32022-03-24 16:12:14 -0400146 env = env,
147 inputs = depset(
148 [source_file],
149 transitive = [
150 cc_toolchain.all_files,
151 cc_compile_ctx.headers,
152 ],
153 ),
154 outputs = [output_file],
Miguel Young de la Sota9fd48832022-03-25 17:35:06 -0400155 command = """
156 CC=$1; shift
Miguel Young de la Sota7bb7fe72022-04-01 16:40:02 -0400157 CLEANUP=$1; shift
Miguel Young de la Sota9fd48832022-03-25 17:35:06 -0400158 OUT=$1; shift
Miguel Young de la Sota7bb7fe72022-04-01 16:40:02 -0400159 $CC -o - $@ 2> /dev/null | $CLEANUP > $OUT
Miguel Young de la Sota9fd48832022-03-25 17:35:06 -0400160 """,
Miguel Young de la Sota4bfbba32022-03-24 16:12:14 -0400161 )
162
163 return [CcSideProductInfo(files = depset(
164 direct = outputs,
165 transitive = transitive,
166 ))]
167
Miguel Young de la Sota7bb7fe72022-04-01 16:40:02 -0400168def _cc_preprocess_aspect_impl(target, ctx):
169 return _cc_compile_different_output("Preprocess", target, ctx, "i", ["-E"], process_all_files = True)
170
171cc_preprocess_aspect = aspect(
172 implementation = _cc_preprocess_aspect_impl,
173 doc = """
174 An aspect that provides a CcSideProductInfo containing the preprocessed outputs
175 of every C/C++ translation unit in the sources of the rule it is applied to and
176 all of its dependencies.
177 """,
178 attrs = {
179 "_cleanup_script": attr.label(
180 allow_single_file = True,
181 default = Label("//rules/scripts:clean_up_cpp_output.sh"),
182 ),
183 "_clang_format": attr.label(
Timothy Trippel41b46fe2022-08-17 23:01:25 -0700184 default = "@lowrisc_rv32imcb_files//:bin/clang-format",
Miguel Young de la Sota7bb7fe72022-04-01 16:40:02 -0400185 allow_single_file = True,
186 cfg = "host",
187 executable = True,
188 ),
189 "_cc_toolchain": attr.label(
190 default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
191 ),
192 },
193 attr_aspects = ["deps"],
194 provides = [CcSideProductInfo],
195 toolchains = ["@rules_cc//cc:toolchain_type"],
196 incompatible_use_toolchain_transition = True,
197 fragments = ["cpp"],
198 host_fragments = ["cpp"],
199)
200
Miguel Young de la Sota4bfbba32022-03-24 16:12:14 -0400201def _cc_assembly_aspect_impl(target, ctx):
Miguel Young de la Sota7bb7fe72022-04-01 16:40:02 -0400202 return _cc_compile_different_output("AsmOutput", target, ctx, "s", ["-S"])
Miguel Young de la Sota4bfbba32022-03-24 16:12:14 -0400203
204cc_asm_aspect = aspect(
205 implementation = _cc_assembly_aspect_impl,
206 doc = """
207 An aspect that provides a CcSideProductInfo containing the assembly file outputs
208 of every C/C++ translation unit in the sources of the rule it is applied to and
209 all of its dependencies.
210 """,
211 attrs = {
Miguel Young de la Sota9fd48832022-03-25 17:35:06 -0400212 "_cleanup_script": attr.label(
213 allow_single_file = True,
214 default = Label("//rules/scripts:expand_tabs.sh"),
215 ),
Miguel Young de la Sota4bfbba32022-03-24 16:12:14 -0400216 "_cc_toolchain": attr.label(
217 default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
218 ),
219 },
220 attr_aspects = ["deps"],
221 provides = [CcSideProductInfo],
222 toolchains = ["@rules_cc//cc:toolchain_type"],
223 incompatible_use_toolchain_transition = True,
224 fragments = ["cpp"],
225 host_fragments = ["cpp"],
226)
227
228def _cc_llvm_aspect_impl(target, ctx):
Miguel Young de la Sota4b01c7a2022-04-21 17:25:52 -0400229 cc_toolchain = find_cc_toolchain(ctx).cc
Miguel Young de la Sota4bfbba32022-03-24 16:12:14 -0400230 if cc_toolchain.compiler.find("clang") == -1:
231 return CcSideProductInfo(files = depset())
Miguel Young de la Sota7bb7fe72022-04-01 16:40:02 -0400232 return _cc_compile_different_output("LLVMOutput", target, ctx, "ll", ["-S", "-emit-llvm"])
Miguel Young de la Sota4bfbba32022-03-24 16:12:14 -0400233
234cc_llvm_aspect = aspect(
235 implementation = _cc_llvm_aspect_impl,
236 doc = """
237 An aspect that provides a CcSideProductInfo containing the LLVM IR file outputs
238 of every C/C++ translation unit in the sources of the rule it is applied to and
239 all of its dependencies.
240
241 If the current compiler does not appear to be clang, it outputs nothing instead.
242 """,
243 attrs = {
Miguel Young de la Sota9fd48832022-03-25 17:35:06 -0400244 "_cleanup_script": attr.label(
245 allow_single_file = True,
246 default = Label("//rules/scripts:expand_tabs.sh"),
247 ),
Miguel Young de la Sota4bfbba32022-03-24 16:12:14 -0400248 "_cc_toolchain": attr.label(
249 default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
250 ),
251 },
252 attr_aspects = ["deps"],
253 provides = [CcSideProductInfo],
254 toolchains = ["@rules_cc//cc:toolchain_type"],
255 incompatible_use_toolchain_transition = True,
256 fragments = ["cpp"],
257 host_fragments = ["cpp"],
258)
259
260MapFile = provider(fields = ["map_file"])
261
262def _cc_relink_with_linkmap_aspect_impl(target, ctx):
263 # As mentioned above, there is no CcInfo in a cc_binary, so we're forced
264 # to rumage around the target's actions for its link action, and then
265 # re-run it, but capturing the map file as a side-product.
266 link_action = None
267 for action in target.actions:
268 if action.mnemonic == "CppLink":
269 link_action = action
270 break
271 if not link_action:
272 return [MapFile(map_file = None)]
273
274 output_file = ctx.actions.declare_file(target.label.name + ".ldmap")
275
Miguel Young de la Sota4b01c7a2022-04-21 17:25:52 -0400276 cc_toolchain = find_cc_toolchain(ctx).cc
Miguel Young de la Sota4bfbba32022-03-24 16:12:14 -0400277 feature_configuration = cc_common.configure_features(
278 ctx = ctx,
279 cc_toolchain = cc_toolchain,
280 requested_features = ctx.features,
281 unsupported_features = ctx.disabled_features,
282 )
283 linker_path = cc_common.get_tool_for_action(
284 feature_configuration = feature_configuration,
285 action_name = ACTION_NAMES.cpp_link_executable,
286 )
287
288 ctx.actions.run(
Miguel Young de la Sota7bb7fe72022-04-01 16:40:02 -0400289 mnemonic = "LinkMapFile",
Miguel Young de la Sota4bfbba32022-03-24 16:12:14 -0400290 executable = linker_path,
291 arguments = link_action.argv[1:] + ["-Wl,-Map=" + output_file.path],
292 env = link_action.env,
293 inputs = link_action.inputs,
294 outputs = [output_file],
295 )
296
297 return [MapFile(map_file = output_file)]
298
299cc_relink_with_linkmap_aspect = aspect(
300 implementation = _cc_relink_with_linkmap_aspect_impl,
301 doc = """
302 An aspect to apply to a cc_binary rule that rebuilds its linker invocation and
303 re-runs it, capturing the map file as an output.
304
305 This needs to exist because cc_binary currently does not allow us to specify that
306 it may emit multiple outputs.
307 """,
308 attrs = {
309 "_cc_toolchain": attr.label(
310 default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
311 ),
312 },
313 provides = [MapFile],
314 toolchains = ["@rules_cc//cc:toolchain_type"],
315 incompatible_use_toolchain_transition = True,
316 fragments = ["cpp"],
317 host_fragments = ["cpp"],
318)
319
Miguel Young de la Sota7bb7fe72022-04-01 16:40:02 -0400320def _rv_preprocess_impl(ctx):
321 return [DefaultInfo(
322 files = ctx.attr.target[CcSideProductInfo].files,
323 data_runfiles = ctx.runfiles(transitive_files = ctx.attr.target[CcSideProductInfo].files),
324 )]
325
326rv_preprocess = rv_rule(
327 implementation = _rv_preprocess_impl,
328 attrs = {"target": attr.label(aspects = [cc_preprocess_aspect])},
329)
330
Miguel Young de la Sota4bfbba32022-03-24 16:12:14 -0400331def _rv_asm_impl(ctx):
332 return [DefaultInfo(
333 files = ctx.attr.target[CcSideProductInfo].files,
334 data_runfiles = ctx.runfiles(transitive_files = ctx.attr.target[CcSideProductInfo].files),
335 )]
336
337rv_asm = rv_rule(
338 implementation = _rv_asm_impl,
339 attrs = {"target": attr.label(aspects = [cc_asm_aspect])},
340)
341
342def _llvm_ir_impl(ctx):
343 return [DefaultInfo(
344 files = ctx.attr.target[CcSideProductInfo].files,
345 data_runfiles = ctx.runfiles(transitive_files = ctx.attr.target[CcSideProductInfo].files),
346 )]
347
348rv_llvm_ir = rv_rule(
349 implementation = _rv_asm_impl,
350 attrs = {"target": attr.label(aspects = [cc_llvm_aspect])},
351)
352
353def _cc_relink_with_linkmap_impl(ctx):
354 return [DefaultInfo(
355 files = depset([ctx.attr.target[MapFile].map_file]),
356 data_runfiles = ctx.runfiles(files = [ctx.attr.target[MapFile].map_file]),
357 )]
358
359rv_relink_with_linkmap = rv_rule(
360 implementation = _cc_relink_with_linkmap_impl,
361 attrs = {"target": attr.label(aspects = [cc_relink_with_linkmap_aspect])},
362)