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