xref: /aosp_15_r20/external/cronet/build/rust/rust_bindgen.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/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# This template expands to an action that generates the Rust side of the
31# bindings. Add it as a dependency and then incorporate
32# `get_target_outputs(target_name)[0]` into your Rust crate.
33#
34# Parameters:
35#
36# header:
37#   The .h file to generate bindings for.
38#
39# deps: (optional)
40#   C targets on which the headers depend in order to build successfully.
41#
42# configs: (optional)
43#   C compilation targets determine the correct list of -D and -I flags based
44#   on their dependencies and any configs applied. The same applies here. Set
45#   any configs here as if this were a C target.
46#
47# bindgen_flags: (optional)
48#   The additional bindgen flags which are passed to the executable
49#
50# wrap_static_fns: (optional)
51#   If set to true, enables binding `static` and `static inline` functions in
52#   the header. Setting this causes the template to emit a source_set target
53#   named "${target_name}_static_fns", which must be incorporated into the
54#   build. Additionally, `get_target_outputs` will return both the Rust file and
55#   a generated C file, but callers can rely on the Rust file being first.
56#
57# Rust targets depending on the output must include! the generated file.
58#
59template("rust_bindgen") {
60  assert(defined(invoker.header),
61         "Must specify the C header file to make bindings for.")
62  wrap_static_fns = defined(invoker.wrap_static_fns) && invoker.wrap_static_fns
63
64  bindgen_target_name = target_name
65  action(bindgen_target_name) {
66    # bindgen relies on knowing the {{defines}} and {{include_dirs}} required
67    # to build the C++ headers which it's parsing. These are passed to the
68    # script's args and are populated using deps and configs.
69    forward_variables_from(invoker,
70                           TESTONLY_AND_VISIBILITY + [
71                                 "deps",
72                                 "configs",
73                               ])
74
75    sources = [ invoker.header ]
76
77    if (!defined(configs)) {
78      configs = []
79    }
80
81    # Several important compiler flags come from default_compiler_configs
82    configs += default_compiler_configs
83
84    output_dir = "$target_gen_dir"
85    out_gen_rs = "$output_dir/${target_name}.rs"
86
87    script = rebase_path("//build/rust/run_bindgen.py")
88    inputs = [ _bindgen_path ]
89
90    depfile = "$target_out_dir/${target_name}.d"
91    outputs = [ out_gen_rs ]
92
93    args = [
94      "--exe",
95      rebase_path(_bindgen_path, root_build_dir),
96      "--header",
97      rebase_path(invoker.header, root_build_dir),
98      "--depfile",
99      rebase_path(depfile, root_build_dir),
100      "--output",
101      rebase_path(out_gen_rs, root_build_dir),
102      "--libclang-path",
103      rebase_path(_libclang_path, root_build_dir),
104    ]
105
106    if (wrap_static_fns) {
107      out_gen_c = "$output_dir/${target_name}.c"
108      outputs += [ out_gen_c ]
109      args += [
110        "--wrap-static-fns",
111        rebase_path(out_gen_c, root_build_dir),
112      ]
113    }
114
115    if (is_linux) {
116      # Linux clang, and clang libs, use a shared libstdc++, which we must
117      # point to.
118      args += [
119        "--ld-library-path",
120        rebase_path(clang_base_path + "/lib", root_build_dir),
121      ]
122    }
123
124    if (defined(invoker.bindgen_flags)) {
125      args += [ "--bindgen-flags" ]
126      foreach(flag, invoker.bindgen_flags) {
127        args += [ flag ]
128      }
129    }
130
131    args += [
132      "--",
133      "{{defines}}",
134      "{{include_dirs}}",
135      "{{cflags}}",
136      "{{cflags_c}}",
137    ]
138
139    # libclang will run the system `clang` to find the "resource dir" which it
140    # places before the directory specified in `-isysroot`.
141    # https://github.com/llvm/llvm-project/blob/699e0bed4bfead826e210025bf33e5a1997c018b/clang/lib/Tooling/Tooling.cpp#L499-L510
142    #
143    # This means include files are pulled from the wrong place if the `clang`
144    # says the wrong thing. We point it to our clang's resource dir which will
145    # make it behave consistently with our other command line flags and allows
146    # system headers to be found.
147    clang_resource_dir =
148        rebase_path(clang_base_path + "/lib/clang/" + clang_version,
149                    root_build_dir)
150    args += [
151      "-resource-dir",
152      clang_resource_dir,
153    ]
154
155    # The `--sysroot` flag is not working as expected and gets ignored (we don't
156    # fully understand why, see b/328510249). But we add `-isystem` to point at
157    # the headers in the sysroot which are otherwise not found.
158    if (is_win) {
159      args += [ "-I" + rebase_path(sysroot + "/usr/include/", root_build_dir) ]
160    } else {
161      args += [
162        "-isystem",
163        rebase_path(sysroot + "/usr/include/", root_build_dir),
164      ]
165    }
166
167    if (is_win) {
168      # On Windows we fall back to using system headers from a sysroot from
169      # depot_tools. This is negotiated by python scripts and the result is
170      # available in //build/toolchain/win/win_toolchain_data.gni. From there
171      # we get the `include_flags_imsvc` which point to the system headers.
172      if (host_cpu == "x86") {
173        win_toolchain_data = win_toolchain_data_x86
174      } else if (host_cpu == "x64") {
175        win_toolchain_data = win_toolchain_data_x64
176      } else if (host_cpu == "arm64") {
177        win_toolchain_data = win_toolchain_data_arm64
178      } else {
179        error("Unsupported host_cpu, add it to win_toolchain_data.gni")
180      }
181      args += win_toolchain_data.include_flags_imsvc_list
182    }
183
184    # Passes C comments through as rustdoc attributes.
185    if (is_win) {
186      args += [ "/clang:-fparse-all-comments" ]
187    } else {
188      args += [ "-fparse-all-comments" ]
189    }
190
191    # Default configs include "-fvisibility=hidden", and for some reason this
192    # causes bindgen not to emit function bindings. Override it.
193    if (!is_win) {
194      args += [ "-fvisibility=default" ]
195    }
196
197    if (is_win) {
198      # We pass MSVC style flags to clang on Windows, and libclang needs to be
199      # told explicitly to accept them.
200      args += [ "--driver-mode=cl" ]
201
202      # On Windows, libclang adds arguments that it then fails to understand.
203      # -fno-spell-checking
204      # -fallow-editor-placeholders
205      # These should not cause bindgen to fail.
206      args += [ "-Wno-unknown-argument" ]
207
208      # Replace these two arguments with a version that clang-cl can parse.
209      args += [
210        "/clang:-fno-spell-checking",
211        "/clang:-fallow-editor-placeholders",
212      ]
213    }
214
215    if (is_cfi) {
216      # LLVM searches for a default CFI ignorelist at (exactly)
217      # $(cwd)/lib/clang/$(llvm_version)/share/cfi_ignorelist.txt
218      # Even if we provide a custom -fsanitize-ignorelist, the absence
219      # of this default file will cause a fatal error. clang finds
220      # it within third_party/llvm-build, but for bindgen our cwd
221      # is the $out_dir. We _could_ create this file at the right
222      # location within the outdir using a "copy" target, but as
223      # we don't actually generate code within bindgen, the easier
224      # option is to tell bindgen to ignore all CFI ignorelists.
225      args += [ "-fno-sanitize-ignorelist" ]
226    }
227  }
228
229  if (wrap_static_fns) {
230    source_set("${target_name}_static_fns") {
231      forward_variables_from(invoker,
232                             TESTONLY_AND_VISIBILITY + [
233                                   "deps",
234                                   "configs",
235                                 ])
236      bindgen_output = get_target_outputs(":${bindgen_target_name}")
237
238      sources = filter_include(bindgen_output, [ "*.c" ])
239      deps += [ ":${bindgen_target_name}" ]
240
241      # bindgen generates a C file whose include is relative to the directory it
242      # runs from.
243      include_dirs = [ root_build_dir ]
244    }
245  }
246}
247