xref: /aosp_15_r20/external/cronet/build/rust/rs_bindings_from_cc.gni (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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.
4
5import("//build/config/clang/clang.gni")
6import("//build/config/rust.gni")
7import("//build/config/sysroot.gni")
8import("//build/rust/mixed_static_library.gni")
9
10# Template to generate and build Rust bindings for a set of C++ headers using
11# Crubit's `rs_bindings_from_cc` tool.
12#
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).
16#
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).
20#
21# Parameters:
22#
23#   bindings_target:
24#     The C++ target (e.g. a `source_set`) that Rust bindings should be
25#     generated for.
26#
27#   public_headers:
28#     The .h files to generate bindings for.
29#
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.
33#
34#     TODO(crbug.com/1329611): Internal headers should still to be included in
35#     the targets_and_args metadata...
36#
37#   deps:
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
43#     `goat_rs_api`).
44#
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
48#     `public_deps`:
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).
54#
55# Usage example:
56#
57#   BUILD.gn:
58#       import("//build/rust/rs_bindings_from_cc.gni")
59#       import("//build/rust/rust_executable.gni")
60#
61#       rust_executable("my_target") {
62#         crate_root = "main.rs"
63#         sources = [ "main.rs" ]
64#         deps = [ ":teleport_rs_api" ]
65#       ]
66#
67#       # This will generate "teleport_rs_api" target that provides Rust
68#       # bindings for the "teleport.h" header from the ":teleport" source
69#       # set.
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".
74#       }
75#
76#       source_set("teleport") {
77#         sources = [ "teleport.h", ... ]
78#         public_deps = [ ":goat" ]
79#       }
80#
81#       rs_bindings_from_cc("goat_rs_api") {
82#         bindings_target = ":goat"
83#         public_headers = ["goat.h"]
84#       }
85#       source_set("goat") {
86#         sources = [ "goat.h", ... ]
87#       }
88#
89#   teleport.h:
90#     #include "goat.h"
91#     void TeleportGoat(const Goat& goat_to_teleport);
92#
93#   goat.h:
94#     struct Goat { ... };
95#
96#   main.rs:
97#     fn main() {
98#       let g: goat_rs_api::Goat = ...;
99#       teleport_rs_api::TeleportGoat(&g);
100#     }
101#
102# Debugging and implementation notes:
103#
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`).
108#
109template("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
114
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`")
120
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) ]
127  }
128
129  # Optional parameter: testonly.
130  _testonly = false
131  if (defined(invoker.testonly)) {
132    _testonly = invoker.testonly
133  }
134
135  # Optional parameter: visibility.
136  if (defined(invoker.visibility)) {
137    _visibility = invoker.visibility
138  }
139
140  # Optional parameter: deps.
141  #
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) {
145  # assert something }
146  _deps = []
147  if (defined(invoker.deps)) {
148    _deps = invoker.deps
149  }
150
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"
159
160  # Calculating the --targets_and_args snippet for the *current* target
161  # and putting it into GN's `metadata`.
162  group(_metadata_target_name) {
163    testonly = _testonly
164    visibility = [
165      ":${_gen_metadata_target_name}",
166      ":${_lib_target_name}",
167    ]
168    deps = []
169
170    metadata = {
171      # The data below corresponds to a single-target entry inside
172      # `--targets_and_args` cmdline argument of `rs_bindings_from_cc`.
173      crubit_target_and_args = [
174        {
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
178          # crate.
179          t = get_label_info(":${_lib_target_name}", "label_no_toolchain")
180          h = _rebased_public_headers
181        },
182      ]
183    }
184  }
185
186  # Gathering --targets-and-args data from *all* transitive dependencies and
187  # putting them into the file at `_metadata_path`.
188  generated_file(_gen_metadata_target_name) {
189    testonly = _testonly
190    visibility = [ ":${_gen_bindings_target_name}" ]
191
192    deps = [ ":${_metadata_target_name}" ]
193    deps += _deps
194
195    testonly = _testonly
196    outputs = [ _metadata_path ]
197    output_conversion = "json"
198    data_keys = [ "crubit_target_and_args" ]
199
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" ]
205  }
206
207  # Exposing the generated Rust bindings.
208  mixed_static_library(_lib_target_name) {
209    testonly = _testonly
210    if (defined(_visibility)) {
211      visibility = _visibility
212    }
213
214    sources = [ _cc_out_path ]
215    deps = _deps
216    deps += [
217      ":${_gen_bindings_target_name}",
218      ":${_metadata_target_name}",
219      "//third_party/crubit:deps_of_rs_api_impl",
220      _bindings_target,
221    ]
222
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 ]
228
229    rs_sources = [ _rs_out_path ]
230    rs_crate_name = _lib_target_name
231    rs_crate_root = _rs_out_path
232    rs_deps = _deps
233    rs_deps += [
234      ":${_gen_bindings_target_name}",
235      "//third_party/crubit:deps_of_rs_api",
236    ]
237
238    metadata = {
239      crubit_metadata_deps = _deps + [ ":${_metadata_target_name}" ]
240    }
241  }
242
243  # Invoking Crubit's `rs_bindings_from_cc` tool to generate Rust bindings.
244  action(_gen_bindings_target_name) {
245    testonly = _testonly
246    if (defined(_visibility)) {
247      visibility = _visibility
248    }
249
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
253    outputs = [
254      _rs_out_path,
255      _cc_out_path,
256    ]
257
258    deps = [ ":${_gen_metadata_target_name}" ]
259    args = [
260      # Target-specific outputs:
261      "--rs_out",
262      rebase_path(_rs_out_path),
263      "--cc_out",
264      rebase_path(_cc_out_path),
265
266      # Target-specific inputs:
267      "--public_headers",
268      string_join(",", _rebased_public_headers),
269      "--targets_and_args_from_gn",
270      rebase_path(_metadata_path),
271    ]
272
273    # Several important compiler flags come from default_compiler_configs
274    configs = default_compiler_configs
275    if (defined(invoker.configs)) {
276      configs += invoker.configs
277    }
278    args += [
279      "--",
280      "{{defines}}",
281      "{{include_dirs}}",
282      "{{cflags}}",
283
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.
287      #
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",
291              root_build_dir),
292
293      # Passes C comments through as rustdoc attributes.
294      "-fparse-all-comments",
295    ]
296  }
297}
298