1 # Copyright 2022 The Chromium Authors
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 import("//build/config/clang/clang.gni")
6 import("//build/config/rust.gni")
7 import("//build/config/sysroot.gni")
8 import("//build/rust/mixed_static_library.gni")
10 # Template to generate and build Rust bindings for a set of C++ headers using
11 # Crubit's `rs_bindings_from_cc` tool.
13 # This template expands to a `mixed_static_library` named "<target>_rs_api" and
14 # containing the Rust side of the bindings (as well as internal C++ thunks
15 # needed to support the bindings).
17 # The generated out/.../gen/.../<target>_rs_api.rs is machine-generated, but
18 # should be fairly readable (inspecting it might be useful to discover the
19 # imported bindings and their shape).
24 # The C++ target (e.g. a `source_set`) that Rust bindings should be
28 # The .h files to generate bindings for.
30 # Implementation note: This doesn't just take *all* the headers of the
31 # `bindings_target`, because typically only a *subset* of headers provides
32 # the *public* API that bindings are needed for.
34 # TODO(crbug.com/1329611): Internal headers should still to be included in
35 # the targets_and_headers metadata...
38 # Other `rs_bindings_from_cc` targets that the bindings need to depend on
39 # (e.g. because APIs in the `public_headers` refer to `struct`s declared in
40 # those other targets. Note how in the usage example below bindings for
41 # `struct Goat` are provided by `goat_rs_api`, and that therefore the
42 # bindings for the `TeleportGoat` provided by `teleport_rs_api` depend on
45 # Oftentimes `deps` can be a copy of the `public_deps` of the
46 # `bindings_target`, but depending on targets with the suffix "_rs_api".
47 # Still, there are scenarios where `deps` don't parallel *all* entries from
49 # * `public_deps` that don't expose Rust APIs (i.e. there are no
50 # "..._rs_api" targets to depend on).
51 # * `public_deps` that Crubit bindings don't depend on (dependencies that
52 # don't provide re-exportable C++ APIs, or that only provide items
53 # that are ignored by Crubit - e.g. `#define`s).
58 # import("//build/rust/rs_bindings_from_cc.gni")
59 # import("//build/rust/rust_executable.gni")
61 # rust_executable("my_target") {
62 # crate_root = "main.rs"
63 # sources = [ "main.rs" ]
64 # deps = [ ":teleport_rs_api" ]
67 # # This will generate "teleport_rs_api" target that provides Rust
68 # # bindings for the "teleport.h" header from the ":teleport" source
70 # rs_bindings_from_cc("teleport_rs_api") {
71 # bindings_target = ":teleport"
72 # public_headers = ["teleport.h"]
73 # deps = [ ":goat_rs_api" ] # Parallel's `public_deps` of ":teleport".
76 # source_set("teleport") {
77 # sources = [ "teleport.h", ... ]
78 # public_deps = [ ":goat" ]
81 # rs_bindings_from_cc("goat_rs_api") {
82 # bindings_target = ":goat"
83 # public_headers = ["goat.h"]
85 # source_set("goat") {
86 # sources = [ "goat.h", ... ]
91 # void TeleportGoat(const Goat& goat_to_teleport);
94 # struct Goat { ... };
98 # let g: goat_rs_api::Goat = ...;
99 # teleport_rs_api::TeleportGoat(&g);
102 # Debugging and implementation notes:
104 # - Consider running the build while CRUBIT_DEBUG environment variable is set.
105 # This will generate additional `.ir` file and log extra information from
106 # the `run_rs_bindings_from_cc.py` script (e.g. full cmdlines for invoking
107 # `rs_bindings_from_cc`).
109 template("rs_bindings_from_cc") {
110 # Mandatory parameter: bindings_target.
111 assert(defined(invoker.bindings_target),
112 "Must specify the C target to make bindings for.")
113 _bindings_target = invoker.bindings_target
115 # Mandatory/unavoidable parameter: target_name
116 _lib_target_name = target_name
117 _base_target_name = get_label_info(_bindings_target, "name")
118 assert(_lib_target_name == "${_base_target_name}_rs_api",
119 "The convention is that bindings for `foo` are named `foo_rs_api`")
121 # Mandatory parameter: public_headers.
122 assert(defined(invoker.public_headers),
123 "Must specify the public C headers to make bindings for.")
124 _rebased_public_headers = []
125 foreach(hdr, invoker.public_headers) {
126 _rebased_public_headers += [ rebase_path(hdr) ]
129 # Optional parameter: testonly.
131 if (defined(invoker.testonly)) {
132 _testonly = invoker.testonly
135 # Optional parameter: visibility.
136 if (defined(invoker.visibility)) {
137 _visibility = invoker.visibility
140 # Optional parameter: deps.
142 # TODO(crbug.com/1329611): Can we somehow assert that `_deps` only contains
143 # some "..._rs_api" targets crated via
144 # `mixed_static_library($_lib_target_name)` below? foreach(dep, _deps) {
147 if (defined(invoker.deps)) {
151 # Various names and paths that are shared across multiple targets defined
152 # in the template here.
153 _gen_bindings_target_name = "${_lib_target_name}_gen_bindings"
154 _gen_metadata_target_name = "${_lib_target_name}_gen_metadata"
155 _metadata_target_name = "${_lib_target_name}_metadata"
156 _metadata_path = "${target_gen_dir}/${_lib_target_name}_meta.json"
157 _rs_out_path = "${target_gen_dir}/${_lib_target_name}.rs"
158 _cc_out_path = "${target_gen_dir}/${_lib_target_name}_impl.cc"
160 # Calculating the --targets_and_headers snippet for the *current* target
161 # and putting it into GN's `metadata`.
162 group(_metadata_target_name) {
165 ":${_gen_metadata_target_name}",
166 ":${_lib_target_name}",
171 # The data below corresponds to a single-target entry inside
172 # `--targets_and_headers` cmdline argument of `rs_bindings_from_cc`.
173 crubit_target_and_headers = [
175 # The `get_label_info` call below expands ":foo_rs_api" into
176 # something like "//dir/bar/baz:foo_rs_api". Crubit assumes that
177 # there is a colon + uses the after-colon-suffix as the name of the
179 t = get_label_info(":${_lib_target_name}", "label_no_toolchain")
180 h = _rebased_public_headers
186 # Gathering --targets-and-headers data from *all* transitive dependencies and
187 # putting them into the file at `_metadata_path`.
188 generated_file(_gen_metadata_target_name) {
190 visibility = [ ":${_gen_bindings_target_name}" ]
192 deps = [ ":${_metadata_target_name}" ]
196 outputs = [ _metadata_path ]
197 output_conversion = "json"
198 data_keys = [ "crubit_target_and_headers" ]
200 # `walk_keys` are used to limit how deep the transitive dependency goes.
201 # This is important, because Crubit doesn't care about all the `deps` or
202 # `public_deps` of the `_bindings_target`. (See also the doc comment about
203 # `rs_bindings_from_cc.deps` parameter at the top of this file.)
204 walk_keys = [ "crubit_metadata_deps" ]
207 # Exposing the generated Rust bindings.
208 mixed_static_library(_lib_target_name) {
210 if (defined(_visibility)) {
211 visibility = _visibility
214 sources = [ _cc_out_path ]
217 ":${_gen_bindings_target_name}",
218 ":${_metadata_target_name}",
219 "//third_party/crubit:deps_of_rs_api_impl",
223 # Chromium already covers `chromium/src/` and `out/Release/gen` in the
224 # include path, but we need to explicitly add `out/Release` below. This
225 # is needed, because `--public_headers` passed to Crubit use paths relative
226 # to the `out/Release` directory. See also b/239238801.
227 include_dirs = [ root_build_dir ]
229 rs_sources = [ _rs_out_path ]
230 rs_crate_name = _lib_target_name
231 rs_crate_root = _rs_out_path
234 ":${_gen_bindings_target_name}",
235 "//third_party/crubit:deps_of_rs_api",
239 crubit_metadata_deps = _deps + [ ":${_metadata_target_name}" ]
243 # Invoking Crubit's `rs_bindings_from_cc` tool to generate Rust bindings.
244 action(_gen_bindings_target_name) {
246 if (defined(_visibility)) {
247 visibility = _visibility
250 script = "//build/rust/run_rs_bindings_from_cc.py"
251 inputs = [ "//third_party/rust-toolchain/bin/rs_bindings_from_cc" ]
252 sources = invoker.public_headers
258 deps = [ ":${_gen_metadata_target_name}" ]
260 # Target-specific outputs:
262 rebase_path(_rs_out_path),
264 rebase_path(_cc_out_path),
266 # Target-specific inputs:
268 string_join(",", _rebased_public_headers),
269 "--targets_and_headers_from_gn",
270 rebase_path(_metadata_path),
273 # Several important compiler flags come from default_compiler_configs
274 configs = default_compiler_configs
275 if (defined(invoker.configs)) {
276 configs += invoker.configs
284 # This path contains important C headers (e.g. stddef.h) and {{cflags}}
285 # does not include it. Normally this path is implicitly added by clang but
286 # it does not happen for libclang.
288 # Add it last so includes from deps and configs take precedence.
289 "-isystem" + rebase_path(
290 clang_base_path + "/lib/clang/" + clang_version + "/include",
293 # Passes C comments through as rustdoc attributes.
294 "-fparse-all-comments",