xref: /aosp_15_r20/external/angle/build/rust/rust_bindgen_generator.gni (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
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/rust_static_library.gni")
9
10if (is_win) {
11  import("//build/toolchain/win/win_toolchain_data.gni")
12}
13
14_bindgen_path = "${rust_bindgen_root}/bin/bindgen"
15if (host_os == "win") {
16  _bindgen_path = "${_bindgen_path}.exe"
17}
18
19# On Windows, the libclang.dll is beside the bindgen.exe, otherwise it is in
20# ../lib.
21_libclang_path = rust_bindgen_root
22if (host_os == "win") {
23  _libclang_path += "/bin"
24} else {
25  _libclang_path += "/lib"
26}
27
28# Template to build Rust/C bindings with bindgen.
29#
30# If you're developing first-party code then consider using `rust_bindgen`
31# instead of `rust_bindgen_generator` to simplify your build targets.
32#
33# This template expands to an action that generates the Rust side of the
34# bindings. Add it as a dependency through `bindgen_deps` on your
35# rust_target then incorporate it into your library through
36#
37# mod bindings {
38#    include!(concat!(env!("OUT_DIR"), "/{rust_bindgen_generator_target_name}.rs"));
39# }
40#
41# Parameters:
42#
43# header:
44#   The .h file to generate bindings for.
45#
46# deps: (optional)
47#   C targets on which the headers depend in order to build successfully.
48#
49# configs: (optional)
50#   C compilation targets determine the correct list of -D and -I flags based
51#   on their dependencies and any configs applied. The same applies here. Set
52#   any configs here as if this were a C target.
53#
54# cpp: (optional)
55#   Use C++ mode to consume the header instead of C mode (the default).
56#
57# bindgen_flags: (optional)
58#   The additional bindgen flags which are passed to the executable. A `--` will
59#   be prepended to each flag. So use `bindgen_flags = [ "foo" ]` to pass
60#   `--foo` to bindgen.
61#
62# wrap_static_fns: (optional)
63#   If set to true, enables binding `static` and `static inline` functions in
64#   the header. Setting this causes the template to emit a source_set target
65#   named "${target_name}_static_fns", which must be incorporated into the
66#   build. Additionally, `get_target_outputs` will return both the Rust file and
67#   a generated C file, but callers can rely on the Rust file being first.
68template("rust_bindgen_generator") {
69  assert(defined(invoker.header),
70         "Must specify the C/C++ header file to make bindings for.")
71  _wrap_static_fns = defined(invoker.wrap_static_fns) && invoker.wrap_static_fns
72
73  if (!defined(invoker.cpp)) {
74    _cpp = false
75  } else {
76    _cpp = invoker.cpp
77  }
78
79  # This is only created if the bindgen has wrap_static_fns to true
80  _static_fn_target_name = target_name + "_static_fns"
81  if (defined(invoker.library_name)) {
82    _library_name = invoker.library_name
83  }
84
85  _bindgen_target_name = target_name
86  action(target_name) {
87    # bindgen relies on knowing the {{defines}} and {{include_dirs}} required
88    # to build the C++ headers which it's parsing. These are passed to the
89    # script's args and are populated using deps and configs.
90    forward_variables_from(invoker,
91                           TESTONLY_AND_VISIBILITY + [
92                                 "configs",
93                                 "deps",
94                                 "public_configs",
95                                 "public_deps",
96                                 "output_name",
97                               ])
98    sources = [ invoker.header ]
99    if (!defined(configs)) {
100      configs = []
101    }
102
103    # Get rid of the visibility, the user should not control visibility.
104    # We have to do it that way otherwise we might get "visibility not used"
105    # errors.
106    visibility = []
107
108    # This should only be allowed to be used by third_party code.
109    # First-party code should use `rust_bindgen` template.
110    # We're intentionally not allowing visibility to be forwarded
111    # to prevent people from doing visibility = ["*"] which will
112    # allow a rust_bindgen_generator to be used by 1P folks.
113    visibility = [ "//third_party/*" ]
114    if (defined(_library_name)) {
115      # Allow the copy target to be able to depend on the generated file.
116      visibility += [ ":${_library_name}_${_bindgen_target_name}_copy" ]
117    }
118
119    if (_wrap_static_fns) {
120      visibility += [ ":$_static_fn_target_name" ]
121    }
122
123    # Several important compiler flags come from default_compiler_configs
124    configs += default_compiler_configs
125
126    output_dir = "${target_gen_dir}/${target_name}"
127    if (!defined(output_name)) {
128      output_name = target_name
129    }
130
131    output_file = "$output_dir/${output_name}.rs"
132
133    script = rebase_path("//build/rust/run_bindgen.py")
134    inputs = [
135      _bindgen_path,
136      "//build/action_helpers.py",
137      "//build/gn_helpers.py",
138      "//build/rust/filter_clang_args.py",
139    ]
140
141    depfile = "$target_out_dir/${target_name}.d"
142    outputs = [ output_file ]
143
144    args = [
145      "--exe",
146      rebase_path(_bindgen_path, root_build_dir),
147      "--header",
148      rebase_path(invoker.header, root_build_dir),
149      "--depfile",
150      rebase_path(depfile, root_build_dir),
151      "--output",
152      rebase_path(output_file, root_build_dir),
153      "--libclang-path",
154      rebase_path(_libclang_path, root_build_dir),
155    ]
156
157    if (_wrap_static_fns) {
158      if (_cpp) {
159        out_gen_c = "$output_dir/${target_name}.cc"
160      } else {
161        out_gen_c = "$output_dir/${target_name}.c"
162      }
163      outputs += [ out_gen_c ]
164      args += [
165        "--wrap-static-fns",
166        rebase_path(out_gen_c, root_build_dir),
167      ]
168    }
169
170    if (is_linux) {
171      # Linux clang, and clang libs, use a shared libstdc++, which we must
172      # point to.
173      args += [
174        "--ld-library-path",
175        rebase_path(clang_base_path + "/lib", root_build_dir),
176      ]
177    }
178
179    args += [ "--bindgen-flags" ]
180    if (defined(invoker.bindgen_flags)) {
181      foreach(flag, invoker.bindgen_flags) {
182        args += [ flag ]
183      }
184    }
185    if (_cpp) {
186      args += [ "enable-cxx-namespaces" ]
187    }
188
189    # Everything below is passed through to libclang.
190    args += [ "--" ]
191
192    if (_cpp) {
193      args += [
194        "-x",
195        "c++",
196      ]
197    }
198
199    args += [
200      "{{defines}}",
201      "{{include_dirs}}",
202      "{{cflags}}",
203    ]
204    if (_cpp) {
205      args += [ "{{cflags_cc}}" ]
206    } else {
207      args += [ "{{cflags_c}}" ]
208    }
209
210    # libclang will run the system `clang` to find the "resource dir" which it
211    # places before the directory specified in `-isysroot`.
212    # https://github.com/llvm/llvm-project/blob/699e0bed4bfead826e210025bf33e5a1997c018b/clang/lib/Tooling/Tooling.cpp#L499-L510
213    #
214    # This means include files are pulled from the wrong place if the `clang`
215    # says the wrong thing. We point it to our clang's resource dir which will
216    # make it behave consistently with our other command line flags and allows
217    # system headers to be found.
218    clang_resource_dir =
219        rebase_path(clang_base_path + "/lib/clang/" + clang_version,
220                    root_build_dir)
221    args += [
222      "-resource-dir",
223      clang_resource_dir,
224    ]
225
226    # The `--sysroot` flag is not working as expected and gets ignored (we don't
227    # fully understand why, see b/328510249). But we add `-isystem` to point at
228    # the headers in the sysroot which are otherwise not found.
229    if (sysroot != "") {
230      if (is_win) {
231        args +=
232            [ "-I" + rebase_path(sysroot + "/usr/include/", root_build_dir) ]
233      } else {
234        args += [
235          "-isystem",
236          rebase_path(sysroot + "/usr/include/", root_build_dir),
237        ]
238      }
239    }
240
241    if (is_win) {
242      # On Windows we fall back to using system headers from a sysroot from
243      # depot_tools. This is negotiated by python scripts and the result is
244      # available in //build/toolchain/win/win_toolchain_data.gni. From there
245      # we get the `include_flags_imsvc` which point to the system headers.
246      if (host_cpu == "x86") {
247        win_toolchain_data = win_toolchain_data_x86
248      } else if (host_cpu == "x64") {
249        win_toolchain_data = win_toolchain_data_x64
250      } else if (host_cpu == "arm64") {
251        win_toolchain_data = win_toolchain_data_arm64
252      } else {
253        error("Unsupported host_cpu, add it to win_toolchain_data.gni")
254      }
255      args += win_toolchain_data.include_flags_imsvc_list
256    }
257
258    # Passes C comments through as rustdoc attributes.
259    if (is_win) {
260      args += [ "/clang:-fparse-all-comments" ]
261    } else {
262      args += [ "-fparse-all-comments" ]
263    }
264
265    # Default configs include "-fvisibility=hidden", and for some reason this
266    # causes bindgen not to emit function bindings. Override it.
267    if (!is_win) {
268      args += [ "-fvisibility=default" ]
269    }
270
271    if (is_win) {
272      # We pass MSVC style flags to clang on Windows, and libclang needs to be
273      # told explicitly to accept them.
274      args += [ "--driver-mode=cl" ]
275
276      # On Windows, libclang adds arguments that it then fails to understand.
277      # -fno-spell-checking
278      # -fallow-editor-placeholders
279      # These should not cause bindgen to fail.
280      args += [ "-Wno-unknown-argument" ]
281
282      # C++ mode makes bindgen pass /TP to libclang (to parse as C++) which
283      # then libclang says is unused.
284      args += [ "-Wno-unused-command-line-argument" ]
285
286      # Replace these two arguments with a version that clang-cl can parse.
287      args += [
288        "/clang:-fno-spell-checking",
289        "/clang:-fallow-editor-placeholders",
290      ]
291    }
292
293    if (is_cfi) {
294      # LLVM searches for a default CFI ignorelist at (exactly)
295      # $(cwd)/lib/clang/$(llvm_version)/share/cfi_ignorelist.txt
296      # Even if we provide a custom -fsanitize-ignorelist, the absence
297      # of this default file will cause a fatal error. clang finds
298      # it within third_party/llvm-build, but for bindgen our cwd
299      # is the $out_dir. We _could_ create this file at the right
300      # location within the outdir using a "copy" target, but as
301      # we don't actually generate code within bindgen, the easier
302      # option is to tell bindgen to ignore all CFI ignorelists.
303      args += [ "-fno-sanitize-ignorelist" ]
304    }
305    if (!defined(_library_name)) {
306      not_needed([ _bindgen_target_name ])
307    }
308  }
309
310  if (_wrap_static_fns) {
311    source_set(_static_fn_target_name) {
312      forward_variables_from(invoker,
313                             TESTONLY_AND_VISIBILITY + [
314                                   "deps",
315                                   "configs",
316                                   "public_configs",
317                                   "public_deps",
318                                 ])
319      bindgen_output = get_target_outputs(":${_bindgen_target_name}")
320      if (!defined(deps)) {
321        deps = []
322      }
323      deps += [ ":${_bindgen_target_name}" ]
324
325      if (_cpp) {
326        sources = filter_include(bindgen_output, [ "*.cc" ])
327      } else {
328        sources = filter_include(bindgen_output, [ "*.c" ])
329      }
330
331      # bindgen generates a C file whose include is relative to the directory it
332      # runs from.
333      include_dirs = [ root_build_dir ]
334
335      # Get rid of the visibility, the user should not control visibility.
336      # We have to do it that way otherwise we might get "visibility not used"
337      # errors.
338      visibility = []
339      if (defined(_library_name)) {
340        # Allow both the rust target declared through `rust_bindgen` to depend
341        # on this library.
342        visibility = [ ":${_library_name}" ]
343      } else {
344        # This should only be allowed to be used by third_party code.
345        # First-party code should use `rust_bindgen` template.
346        # We're intentionally not allowing visibility to be forwarded
347        # to prevent people from doing visibility = ["*"] which will
348        # allow a rust_bindgen_generator to be used by 1P folks.
349        visibility = [ "//third_party/*" ]
350      }
351    }
352  } else {
353    not_needed([ _static_fn_target_name ])
354  }
355}
356