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/mixed_static_library.gni") 9 10# Template to generate and build Rust bindings for a set of C++ headers using 11# Crubit's `rs_bindings_from_cc` tool. 12# 13# This template expands to a `mixed_static_library` named "<target>_rs_api" and 14# containing the Rust side of the bindings (as well as internal C++ thunks 15# needed to support the bindings). 16# 17# The generated out/.../gen/.../<target>_rs_api.rs is machine-generated, but 18# should be fairly readable (inspecting it might be useful to discover the 19# imported bindings and their shape). 20# 21# Parameters: 22# 23# bindings_target: 24# The C++ target (e.g. a `source_set`) that Rust bindings should be 25# generated for. 26# 27# public_headers: 28# The .h files to generate bindings for. 29# 30# Implementation note: This doesn't just take *all* the headers of the 31# `bindings_target`, because typically only a *subset* of headers provides 32# the *public* API that bindings are needed for. 33# 34# TODO(crbug.com/1329611): Internal headers should still to be included in 35# the targets_and_args metadata... 36# 37# deps: 38# Other `rs_bindings_from_cc` targets that the bindings need to depend on 39# (e.g. because APIs in the `public_headers` refer to `struct`s declared in 40# those other targets. Note how in the usage example below bindings for 41# `struct Goat` are provided by `goat_rs_api`, and that therefore the 42# bindings for the `TeleportGoat` provided by `teleport_rs_api` depend on 43# `goat_rs_api`). 44# 45# Oftentimes `deps` can be a copy of the `public_deps` of the 46# `bindings_target`, but depending on targets with the suffix "_rs_api". 47# Still, there are scenarios where `deps` don't parallel *all* entries from 48# `public_deps`: 49# * `public_deps` that don't expose Rust APIs (i.e. there are no 50# "..._rs_api" targets to depend on). 51# * `public_deps` that Crubit bindings don't depend on (dependencies that 52# don't provide re-exportable C++ APIs, or that only provide items 53# that are ignored by Crubit - e.g. `#define`s). 54# 55# Usage example: 56# 57# BUILD.gn: 58# import("//build/rust/rs_bindings_from_cc.gni") 59# import("//build/rust/rust_executable.gni") 60# 61# rust_executable("my_target") { 62# crate_root = "main.rs" 63# sources = [ "main.rs" ] 64# deps = [ ":teleport_rs_api" ] 65# ] 66# 67# # This will generate "teleport_rs_api" target that provides Rust 68# # bindings for the "teleport.h" header from the ":teleport" source 69# # set. 70# rs_bindings_from_cc("teleport_rs_api") { 71# bindings_target = ":teleport" 72# public_headers = ["teleport.h"] 73# deps = [ ":goat_rs_api" ] # Parallel's `public_deps` of ":teleport". 74# } 75# 76# source_set("teleport") { 77# sources = [ "teleport.h", ... ] 78# public_deps = [ ":goat" ] 79# } 80# 81# rs_bindings_from_cc("goat_rs_api") { 82# bindings_target = ":goat" 83# public_headers = ["goat.h"] 84# } 85# source_set("goat") { 86# sources = [ "goat.h", ... ] 87# } 88# 89# teleport.h: 90# #include "goat.h" 91# void TeleportGoat(const Goat& goat_to_teleport); 92# 93# goat.h: 94# struct Goat { ... }; 95# 96# main.rs: 97# fn main() { 98# let g: goat_rs_api::Goat = ...; 99# teleport_rs_api::TeleportGoat(&g); 100# } 101# 102# Debugging and implementation notes: 103# 104# - Consider running the build while CRUBIT_DEBUG environment variable is set. 105# This will generate additional `.ir` file and log extra information from 106# the `run_rs_bindings_from_cc.py` script (e.g. full cmdlines for invoking 107# `rs_bindings_from_cc`). 108# 109template("rs_bindings_from_cc") { 110 # Mandatory parameter: bindings_target. 111 assert(defined(invoker.bindings_target), 112 "Must specify the C target to make bindings for.") 113 _bindings_target = invoker.bindings_target 114 115 # Mandatory/unavoidable parameter: target_name 116 _lib_target_name = target_name 117 _base_target_name = get_label_info(_bindings_target, "name") 118 assert(_lib_target_name == "${_base_target_name}_rs_api", 119 "The convention is that bindings for `foo` are named `foo_rs_api`") 120 121 # Mandatory parameter: public_headers. 122 assert(defined(invoker.public_headers), 123 "Must specify the public C headers to make bindings for.") 124 _rebased_public_headers = [] 125 foreach(hdr, invoker.public_headers) { 126 _rebased_public_headers += [ rebase_path(hdr) ] 127 } 128 129 # Optional parameter: testonly. 130 _testonly = false 131 if (defined(invoker.testonly)) { 132 _testonly = invoker.testonly 133 } 134 135 # Optional parameter: visibility. 136 if (defined(invoker.visibility)) { 137 _visibility = invoker.visibility 138 } 139 140 # Optional parameter: deps. 141 # 142 # TODO(crbug.com/1329611): Can we somehow assert that `_deps` only contains 143 # some "..._rs_api" targets crated via 144 # `mixed_static_library($_lib_target_name)` below? foreach(dep, _deps) { 145 # assert something } 146 _deps = [] 147 if (defined(invoker.deps)) { 148 _deps = invoker.deps 149 } 150 151 # Various names and paths that are shared across multiple targets defined 152 # in the template here. 153 _gen_bindings_target_name = "${_lib_target_name}_gen_bindings" 154 _gen_metadata_target_name = "${_lib_target_name}_gen_metadata" 155 _metadata_target_name = "${_lib_target_name}_metadata" 156 _metadata_path = "${target_gen_dir}/${_lib_target_name}_meta.json" 157 _rs_out_path = "${target_gen_dir}/${_lib_target_name}.rs" 158 _cc_out_path = "${target_gen_dir}/${_lib_target_name}_impl.cc" 159 160 # Calculating the --targets_and_args snippet for the *current* target 161 # and putting it into GN's `metadata`. 162 group(_metadata_target_name) { 163 testonly = _testonly 164 visibility = [ 165 ":${_gen_metadata_target_name}", 166 ":${_lib_target_name}", 167 ] 168 deps = [] 169 170 metadata = { 171 # The data below corresponds to a single-target entry inside 172 # `--targets_and_args` cmdline argument of `rs_bindings_from_cc`. 173 crubit_target_and_args = [ 174 { 175 # The `get_label_info` call below expands ":foo_rs_api" into 176 # something like "//dir/bar/baz:foo_rs_api". Crubit assumes that 177 # there is a colon + uses the after-colon-suffix as the name of the 178 # crate. 179 t = get_label_info(":${_lib_target_name}", "label_no_toolchain") 180 h = _rebased_public_headers 181 }, 182 ] 183 } 184 } 185 186 # Gathering --targets-and-args data from *all* transitive dependencies and 187 # putting them into the file at `_metadata_path`. 188 generated_file(_gen_metadata_target_name) { 189 testonly = _testonly 190 visibility = [ ":${_gen_bindings_target_name}" ] 191 192 deps = [ ":${_metadata_target_name}" ] 193 deps += _deps 194 195 testonly = _testonly 196 outputs = [ _metadata_path ] 197 output_conversion = "json" 198 data_keys = [ "crubit_target_and_args" ] 199 200 # `walk_keys` are used to limit how deep the transitive dependency goes. 201 # This is important, because Crubit doesn't care about all the `deps` or 202 # `public_deps` of the `_bindings_target`. (See also the doc comment about 203 # `rs_bindings_from_cc.deps` parameter at the top of this file.) 204 walk_keys = [ "crubit_metadata_deps" ] 205 } 206 207 # Exposing the generated Rust bindings. 208 mixed_static_library(_lib_target_name) { 209 testonly = _testonly 210 if (defined(_visibility)) { 211 visibility = _visibility 212 } 213 214 sources = [ _cc_out_path ] 215 deps = _deps 216 deps += [ 217 ":${_gen_bindings_target_name}", 218 ":${_metadata_target_name}", 219 "//third_party/crubit:deps_of_rs_api_impl", 220 _bindings_target, 221 ] 222 223 # Chromium already covers `chromium/src/` and `out/Release/gen` in the 224 # include path, but we need to explicitly add `out/Release` below. This 225 # is needed, because `--public_headers` passed to Crubit use paths relative 226 # to the `out/Release` directory. See also b/239238801. 227 include_dirs = [ root_build_dir ] 228 229 rs_sources = [ _rs_out_path ] 230 rs_crate_name = _lib_target_name 231 rs_crate_root = _rs_out_path 232 rs_deps = _deps 233 rs_deps += [ 234 ":${_gen_bindings_target_name}", 235 "//third_party/crubit:deps_of_rs_api", 236 ] 237 238 metadata = { 239 crubit_metadata_deps = _deps + [ ":${_metadata_target_name}" ] 240 } 241 } 242 243 # Invoking Crubit's `rs_bindings_from_cc` tool to generate Rust bindings. 244 action(_gen_bindings_target_name) { 245 testonly = _testonly 246 if (defined(_visibility)) { 247 visibility = _visibility 248 } 249 250 script = "//build/rust/run_rs_bindings_from_cc.py" 251 inputs = [ "//third_party/rust-toolchain/bin/rs_bindings_from_cc" ] 252 sources = invoker.public_headers 253 outputs = [ 254 _rs_out_path, 255 _cc_out_path, 256 ] 257 258 deps = [ ":${_gen_metadata_target_name}" ] 259 args = [ 260 # Target-specific outputs: 261 "--rs_out", 262 rebase_path(_rs_out_path), 263 "--cc_out", 264 rebase_path(_cc_out_path), 265 266 # Target-specific inputs: 267 "--public_headers", 268 string_join(",", _rebased_public_headers), 269 "--targets_and_args_from_gn", 270 rebase_path(_metadata_path), 271 ] 272 273 # Several important compiler flags come from default_compiler_configs 274 configs = default_compiler_configs 275 if (defined(invoker.configs)) { 276 configs += invoker.configs 277 } 278 args += [ 279 "--", 280 "{{defines}}", 281 "{{include_dirs}}", 282 "{{cflags}}", 283 284 # This path contains important C headers (e.g. stddef.h) and {{cflags}} 285 # does not include it. Normally this path is implicitly added by clang but 286 # it does not happen for libclang. 287 # 288 # Add it last so includes from deps and configs take precedence. 289 "-isystem" + rebase_path( 290 clang_base_path + "/lib/clang/" + clang_version + "/include", 291 root_build_dir), 292 293 # Passes C comments through as rustdoc attributes. 294 "-fparse-all-comments", 295 ] 296 } 297} 298