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