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