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