blob: 92ea16e57d50c764c240d5a1c615ccca08680695 [file] [log] [blame]
Austin Schuh8f99c822024-05-05 22:43:40 -07001load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES")
Adam Snaider0967b812023-11-02 15:29:43 -07002load("@org_frc971//tools/rust:defs.bzl", "rust_library")
Brian Silvermanc270a4d2022-07-23 16:08:06 -07003load("@rules_cc//cc:find_cc_toolchain.bzl", "find_cc_toolchain")
Brian Silvermanc270a4d2022-07-23 16:08:06 -07004
5def _cc_toolchain_flags(ctx, cc_toolchain):
6 feature_configuration = cc_common.configure_features(
7 ctx = ctx,
8 cc_toolchain = cc_toolchain,
9 requested_features = ctx.features,
10 unsupported_features = ctx.disabled_features,
11 )
James Kuszmaul126dcff2022-08-12 16:30:05 -070012 preprocessor_defines = []
13 for lib in ctx.attr.libs:
14 preprocessor_defines.append(lib[CcInfo].compilation_context.defines)
Brian Silvermanc270a4d2022-07-23 16:08:06 -070015 compile_variables = cc_common.create_compile_variables(
16 feature_configuration = feature_configuration,
17 cc_toolchain = cc_toolchain,
18 user_compile_flags = ctx.fragments.cpp.copts + ctx.fragments.cpp.cxxopts,
James Kuszmaul126dcff2022-08-12 16:30:05 -070019 preprocessor_defines = depset(transitive = preprocessor_defines),
Brian Silvermanc270a4d2022-07-23 16:08:06 -070020 )
21 command_line = cc_common.get_memory_inefficient_command_line(
22 feature_configuration = feature_configuration,
23 action_name = ACTION_NAMES.cpp_compile,
24 variables = compile_variables,
25 )
26 env = cc_common.get_environment_variables(
27 feature_configuration = feature_configuration,
28 action_name = ACTION_NAMES.cpp_compile,
29 variables = compile_variables,
30 )
31 return command_line, env
32
33# All the stuff about AUTOCXX_RS_FILE and--fix-rs-include-name only applies when
34# using --gen-rs-include. We use --gen-rs-archive so none of that matters.
35#
36# The generated .rs file uses `include!` on the top-level C++ headers imported
37# via `#include` in `include_cpp!`. This always operates relative to the source
38# file (I don't see any way to change it), nor does autocxx have a way to change
39# the path. There are headers involved which use `#pragma once`, so just copying
40# them is a bad idea. Instead, we generate forwarding headers.
41def _autocxx_library_gen_impl(ctx):
Brian Silvermanc270a4d2022-07-23 16:08:06 -070042 # TODO(Brian): Provide some way to override this globally in WORKSPACE? Need
43 # a real strategy for coordinating toolchains and flags, see the TODO below
44 # where cc_command_line is used for details.
45 if ctx.attr.override_cc_toolchain:
46 cc_toolchain = ctx.attr.override_cc_toolchain[cc_common.CcToolchainInfo]
47 else:
48 cc_toolchain = find_cc_toolchain(ctx)
49
50 # The directory where we generate files. Needs to be unique and in our package.
51 gendir = ctx.label.name + "__dir"
52
53 cc_command_line, cc_env = _cc_toolchain_flags(ctx, cc_toolchain)
54
55 includes = []
56 in_headers = []
57 forwarding_headers = []
58 for lib in ctx.attr.libs:
59 compilation = lib[CcInfo].compilation_context
60
61 # TODO(Brian): How should we be juggling includes, quote_includes, and system_includes?
62 includes.append(compilation.includes)
63 includes.append(compilation.quote_includes)
64 includes.append(compilation.system_includes)
65 in_headers.append(compilation.headers)
66 for header in compilation.direct_public_headers:
67 # TODO(Brian): This doesn't work if it's being `#include`ed (via
68 # `include_cpp!`) using one of the includes paths. Maybe we should
69 # add each `includes` prefixed with `gendir` to solve that?
70 forwarding = ctx.actions.declare_file("%s/%s" % (gendir, header.short_path))
71 forwarding_headers.append(forwarding)
72 ctx.actions.write(forwarding, '#include "%s"' % header.short_path)
73 includes = depset(transitive = includes)
74 action_inputs = depset(
75 direct = ctx.files.srcs + ctx.files.cxxbridge_srcs,
76 transitive = in_headers + [cc_toolchain.all_files],
77 )
78
79 # This is always the name with --gen-rs-archive, regardless of other flags.
80 out_rs_json = ctx.actions.declare_file("%s/gen.rs.json" % gendir)
81 out_env_file = ctx.actions.declare_file("%s/rustc_env" % gendir)
82 ctx.actions.write(
83 output = out_env_file,
Brian Silverman9809c5f2022-07-23 16:12:23 -070084 # The first path is valid for rust_library/rust_binary/rust_test/etc, the second one
85 # is valid for rust_doc_test due to working directory differences.
86 content = "AUTOCXX_RS_JSON_ARCHIVE=%s:%s" % (out_rs_json.path, out_rs_json.short_path),
Brian Silvermanc270a4d2022-07-23 16:08:06 -070087 )
88
89 out_h = ctx.actions.declare_file("%s_cxxgen.h" % ctx.label.name.rstrip("__gen"))
90 out_h_guard = out_h.short_path.replace("/", "_").replace(".", "_")
91 out_h_contents = [
92 "#ifndef %s" % out_h_guard,
93 "#define %s" % out_h_guard,
94 "// GENERATED FILE, DO NOT EDIT",
95 "//",
96 "// #includes all of the declarations exported to C++ from %s" % ctx.label,
97 ]
98 out_cc = []
99
100 # See `gen --help` for details on the naming of these outputs.
101 for cc_index in range(ctx.attr.sections_to_generate):
102 out_cc.append(ctx.actions.declare_file("%s/gen%d.cc" % (gendir, cc_index)))
103 gen_h = ctx.actions.declare_file("%s/gen%d.h" % (gendir, cc_index))
104 out_cc.append(gen_h)
105 out_h_contents.append("#include \"%s\"" % gen_h.short_path)
106 autocxxgen_h = ctx.actions.declare_file("%s/autocxxgen%d.h" % (gendir, cc_index))
107 out_cc.append(autocxxgen_h)
108 out_h_contents.append("#include \"%s\"" % autocxxgen_h.short_path)
109
110 cxxbridge_cc_srcs = []
111 for src in ctx.files.cxxbridge_srcs:
112 cxxbridge_cc = ctx.actions.declare_file("%s/cxxbridge.cc" % gendir)
113 cxxbridge_cc_srcs.append(cxxbridge_cc)
114 cxxbridge_h = ctx.actions.declare_file("%s/cxxbridge.h" % gendir)
115 cxxbridge_cc_srcs.append(cxxbridge_h)
116 out_h_contents.append("#include \"%s\"" % cxxbridge_h.short_path)
117 ctx.actions.run(
118 mnemonic = "CxxCodegen",
119 executable = ctx.executable._cxx_codegen,
120 inputs = [src],
121 outputs = [cxxbridge_cc, cxxbridge_h],
122 arguments = [src.path, "--output", cxxbridge_h.path, "--output", cxxbridge_cc.path],
123 )
124
125 out_h_contents.append("#endif // %s" % out_h_guard)
126 ctx.actions.write(
127 output = out_h,
128 content = "\n".join(out_h_contents),
129 )
130
131 gen_rs = ctx.actions.args()
132 gen_rs.add_all(["--outdir", out_rs_json.dirname])
133 gen_rs.add("--gen-rs-archive")
134 gen_rs.add("--gen-cpp")
Austin Schuh50e3dca2023-07-23 14:34:27 -0700135 #gen_rs.add("--auto-allowlist")
Brian Silvermanc270a4d2022-07-23 16:08:06 -0700136
137 gen_rs.add_all(["--generate-exact", ctx.attr.sections_to_generate])
138
139 gen_rs.add_all(ctx.files.srcs)
140 gen_rs.add_all(ctx.files.cxxbridge_srcs)
141
142 # TODO: Do these go before or after the --? They're partially redundant with
143 # cc_command_line too.
144 gen_rs.add_all(includes, before_each = "-I")
145 gen_rs.add("--")
146
147 # TODO: These are flags for the cc_toolchain, not the libclang they're being passed to.
148 # Figure out how to handle that nicely. Maybe just require they're compatible, and direct
149 # people to overriding the toolchain in use instead?
150 gen_rs.add_all(cc_command_line)
151
152 gen_rs.add("-Wno-unused-private-field")
153 env = dict(cc_env)
154 env.update(
155 LIBCLANG_PATH = ctx.file._libclang.path,
156 )
157 if ctx.attr.gen_debug:
158 env.update(
159 RUST_BACKTRACE = "full",
160 RUST_LOG = "autocxx_engine=info",
161 )
162 ctx.actions.run(
163 arguments = [gen_rs],
164 outputs = [out_rs_json] + out_cc,
165 tools = [ctx.file._libclang],
166 inputs = action_inputs,
167 env = env,
168 executable = ctx.executable._autocxx_gen,
169 mnemonic = "AutocxxGen",
170 )
171
172 return [
173 OutputGroupInfo(
174 cc_srcs = out_cc + cxxbridge_cc_srcs,
175 hdr_srcs = [out_h],
176 compile_data = forwarding_headers + [out_rs_json],
177 env_files = [out_env_file],
178 ),
179 ]
180
181_autocxx_library_gen = rule(
182 implementation = _autocxx_library_gen_impl,
183 attrs = {
184 "libs": attr.label_list(
185 mandatory = True,
186 providers = [CcInfo],
187 doc = "C++ libraries to let Rust use headers from",
188 ),
189 "srcs": attr.label_list(
190 allow_files = [".rs"],
191 mandatory = False,
192 doc = "Rust sources with `include_cpp!` macros",
193 default = [],
194 ),
195 # TODO(Brian): Do we need to support this? Or just throw them in srcs?
196 "cxxbridge_srcs": attr.label_list(
197 allow_files = [".rs"],
198 mandatory = False,
199 doc = "Rust sources with only [cxx::bridge] annotations",
200 default = [],
201 ),
202 "sections_to_generate": attr.int(
203 default = 20,
204 doc = (
205 "The number of `cxx::bridge` sections to support," +
206 " including ones created by `autocxx::include_cpp!`." +
207 " The default is sufficient for most use cases." +
208 " Setting this too large has a small performance impact, setting it" +
209 " too low will result in a build failure."
210 ),
211 ),
212 "gen_debug": attr.bool(
213 default = False,
214 doc = "Print (lots of) debug info about autocxx's codegen at build time.",
215 ),
216 "_autocxx_gen": attr.label(
217 executable = True,
218 default = Label("@//third_party/autocxx/gen/cmd:gen"),
219 cfg = "exec",
220 ),
221 "_cxx_codegen": attr.label(
222 executable = True,
Adam Snaider770b97b2023-08-04 21:07:48 -0700223 default = Label("@cxxbridge-cmd//:cxxbridge-cmd"),
Brian Silvermanc270a4d2022-07-23 16:08:06 -0700224 cfg = "exec",
225 ),
226 "_libclang": attr.label(
227 cfg = "exec",
228 default = Label("@llvm_k8//:libclang"),
229 allow_single_file = True,
230 ),
231 "override_cc_toolchain": attr.label(mandatory = False, providers = [cc_common.CcToolchainInfo]),
232 "_cc_toolchain": attr.label(default = Label("@bazel_tools//tools/cpp:current_cc_toolchain")),
233 },
234 toolchains = [
235 "@rules_rust//rust:toolchain",
236 "@bazel_tools//tools/cpp:toolchain_type",
237 ],
238 fragments = ["cpp"],
239)
240
241def autocxx_library(
242 name,
243 visibility = None,
Adam Snaider0967b812023-11-02 15:29:43 -0700244 target_compatible_with = ["//tools/platforms/rust:has_support"],
Brian Silvermanc270a4d2022-07-23 16:08:06 -0700245 libs = [],
246 srcs = [],
247 cxxbridge_srcs = [],
248 override_cc_toolchain = None,
249 deps = [],
250 rs_deps = [],
251 testonly = None,
252 crate_features = None,
253 crate_name = None,
Adam Snaider5f000672023-11-02 16:04:30 -0700254 gen_debug = None,
Adam Snaiderf560ae92023-11-07 17:06:21 -0800255 **kwargs):
Brian Silvermanc270a4d2022-07-23 16:08:06 -0700256 """A macro to generate Rust <-> C++ interop code with autocxx.
257
258 Creates the following rules:
259 * A rust_library with the given name, which includes the given srcs. Note that it will not
260 include them directly due to how autocxx works, instead they will be copied into a
261 generated file along with changes from autocxx.
262 * A cc_library with the given name + `_cc`. This is for C++ code that wants to use APIs
263 from the given Rust code. Rust dependencies should _not_ depend on this. The header for C++
264 to #include will be named by the given name + `_cxxgen.h`.
265
266 `deps` is for other `autocxx_library` rules. `rs_deps` is for dependencies of the Rust code.
267 """
268 library_gen_name = "%s__gen" % name
269 _autocxx_library_gen(
270 name = library_gen_name,
271 visibility = ["//visibility:private"],
272 target_compatible_with = target_compatible_with,
273 testonly = testonly,
274 libs = libs,
275 srcs = srcs,
276 cxxbridge_srcs = cxxbridge_srcs,
277 override_cc_toolchain = override_cc_toolchain,
278 gen_debug = gen_debug,
279 )
280 gen_cc_srcs_name = "%s__cc_srcs" % name
281 native.filegroup(
282 name = gen_cc_srcs_name,
283 visibility = ["//visibility:private"],
284 target_compatible_with = target_compatible_with,
285 testonly = testonly,
286 srcs = [library_gen_name],
287 output_group = "cc_srcs",
288 )
289 gen_hdr_srcs_name = "%s__hdr_srcs" % name
290 native.filegroup(
291 name = gen_hdr_srcs_name,
292 visibility = ["//visibility:private"],
293 target_compatible_with = target_compatible_with,
294 testonly = testonly,
295 srcs = [library_gen_name],
296 output_group = "hdr_srcs",
297 )
298 gen_compile_data_name = "%s__compile_data" % name
299 native.filegroup(
300 name = gen_compile_data_name,
301 visibility = ["//visibility:private"],
302 target_compatible_with = target_compatible_with,
303 testonly = testonly,
304 srcs = [library_gen_name],
305 output_group = "compile_data",
306 )
307 gen_env_files_name = "%s__env_files" % name
308 native.filegroup(
309 name = gen_env_files_name,
310 visibility = ["//visibility:private"],
311 target_compatible_with = target_compatible_with,
312 testonly = testonly,
313 srcs = [library_gen_name],
314 output_group = "env_files",
315 )
316 cc_library_name = "%s__cc" % name
317 native.cc_library(
318 name = cc_library_name,
319 visibility = visibility,
320 target_compatible_with = target_compatible_with,
321 testonly = testonly,
322 deps = deps + libs + [
Adam Snaider770b97b2023-08-04 21:07:48 -0700323 "@crate_index//:cxx_cc",
Brian Silvermanc270a4d2022-07-23 16:08:06 -0700324 ],
325 srcs = [gen_cc_srcs_name],
326 hdrs = [gen_hdr_srcs_name],
327 )
328
329 rust_library(
330 name = name,
331 visibility = visibility,
332 target_compatible_with = target_compatible_with,
333 testonly = testonly,
334 srcs = srcs + cxxbridge_srcs,
335 proc_macro_deps = [
Adam Snaider770b97b2023-08-04 21:07:48 -0700336 "@crate_index//:cxxbridge-macro",
Brian Silvermanc270a4d2022-07-23 16:08:06 -0700337 ],
338 crate_features = crate_features,
339 crate_name = crate_name,
340 deps = deps + rs_deps + [
341 cc_library_name,
Adam Snaider770b97b2023-08-04 21:07:48 -0700342 "@crate_index//:cxx",
Brian Silvermanc270a4d2022-07-23 16:08:06 -0700343 "//third_party/autocxx",
344 ],
345 compile_data = [gen_compile_data_name],
346 rustc_env_files = [gen_env_files_name],
Adam Snaiderf560ae92023-11-07 17:06:21 -0800347 **kwargs
Brian Silvermanc270a4d2022-07-23 16:08:06 -0700348 )