blob: c0caecd2ee38cdd72c5f2506402421de08eee7c0 [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")
8load("//rules:bugfix.bzl", "find_cc_toolchain")
9load("//rules:rv.bzl", "rv_rule")
10
11CcSideProductInfo = provider(fields = ["files"])
12
13def _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 Sota9fd48832022-03-25 17:35:06 -040032 "c",
33 "cc",
34 "cpp",
35 "cxx",
36 "c++",
37 "C",
Miguel Young de la Sota4bfbba32022-03-24 16:12:14 -040038 ]
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 Sota9fd48832022-03-25 17:35:06 -040081 #
Miguel Young de la Sota4bfbba32022-03-24 16:12:14 -040082 # 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 Sota9fd48832022-03-25 17:35:06 -040088 #output_file_tmp = ctx.actions.declare_file(output_file.basename + ".tmp")
89
Miguel Young de la Sota4bfbba32022-03-24 16:12:14 -040090 # 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 Sota9fd48832022-03-25 17:35:06 -040094 opts += ctx.fragments.cpp.conlyopts
Miguel Young de la Sota4bfbba32022-03-24 16:12:14 -040095 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 Sota4bfbba32022-03-24 16:12:14 -0400102 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 Sota9fd48832022-03-25 17:35:06 -0400127 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 Sota4bfbba32022-03-24 16:12:14 -0400136 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 Sota9fd48832022-03-25 17:35:06 -0400145 command = """
146 CC=$1; shift
147 UNTAB=$1; shift
148 OUT=$1; shift
149 $CC -o - $@ | $UNTAB > $OUT
150 """,
Miguel Young de la Sota4bfbba32022-03-24 16:12:14 -0400151 )
152
153 return [CcSideProductInfo(files = depset(
154 direct = outputs,
155 transitive = transitive,
156 ))]
157
158def _cc_assembly_aspect_impl(target, ctx):
159 return _cc_compile_different_output(target, ctx, "s", ["-S"])
160
161cc_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 Sota9fd48832022-03-25 17:35:06 -0400169 "_cleanup_script": attr.label(
170 allow_single_file = True,
171 default = Label("//rules/scripts:expand_tabs.sh"),
172 ),
Miguel Young de la Sota4bfbba32022-03-24 16:12:14 -0400173 "_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
185def _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
191cc_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 Sota9fd48832022-03-25 17:35:06 -0400201 "_cleanup_script": attr.label(
202 allow_single_file = True,
203 default = Label("//rules/scripts:expand_tabs.sh"),
204 ),
Miguel Young de la Sota4bfbba32022-03-24 16:12:14 -0400205 "_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
217MapFile = provider(fields = ["map_file"])
218
219def _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
255cc_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
276def _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
282rv_asm = rv_rule(
283 implementation = _rv_asm_impl,
284 attrs = {"target": attr.label(aspects = [cc_asm_aspect])},
285)
286
287def _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
293rv_llvm_ir = rv_rule(
294 implementation = _rv_asm_impl,
295 attrs = {"target": attr.label(aspects = [cc_llvm_aspect])},
296)
297
298def _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
304rv_relink_with_linkmap = rv_rule(
305 implementation = _cc_relink_with_linkmap_impl,
306 attrs = {"target": attr.label(aspects = [cc_relink_with_linkmap_aspect])},
307)