# 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/mixed_static_library.gni") # Template to generate and build Rust bindings for a set of C++ headers using # Crubit's `rs_bindings_from_cc` tool. # # This template expands to a `mixed_static_library` named "_rs_api" and # containing the Rust side of the bindings (as well as internal C++ thunks # needed to support the bindings). # # The generated out/.../gen/.../_rs_api.rs is machine-generated, but # should be fairly readable (inspecting it might be useful to discover the # imported bindings and their shape). # # Parameters: # # bindings_target: # The C++ target (e.g. a `source_set`) that Rust bindings should be # generated for. # # public_headers: # The .h files to generate bindings for. # # Implementation note: This doesn't just take *all* the headers of the # `bindings_target`, because typically only a *subset* of headers provides # the *public* API that bindings are needed for. # # TODO(crbug.com/1329611): Internal headers should still to be included in # the targets_and_args metadata... # # deps: # Other `rs_bindings_from_cc` targets that the bindings need to depend on # (e.g. because APIs in the `public_headers` refer to `struct`s declared in # those other targets. Note how in the usage example below bindings for # `struct Goat` are provided by `goat_rs_api`, and that therefore the # bindings for the `TeleportGoat` provided by `teleport_rs_api` depend on # `goat_rs_api`). # # Oftentimes `deps` can be a copy of the `public_deps` of the # `bindings_target`, but depending on targets with the suffix "_rs_api". # Still, there are scenarios where `deps` don't parallel *all* entries from # `public_deps`: # * `public_deps` that don't expose Rust APIs (i.e. there are no # "..._rs_api" targets to depend on). # * `public_deps` that Crubit bindings don't depend on (dependencies that # don't provide re-exportable C++ APIs, or that only provide items # that are ignored by Crubit - e.g. `#define`s). # # Usage example: # # BUILD.gn: # import("//build/rust/rs_bindings_from_cc.gni") # import("//build/rust/rust_executable.gni") # # rust_executable("my_target") { # crate_root = "main.rs" # sources = [ "main.rs" ] # deps = [ ":teleport_rs_api" ] # ] # # # This will generate "teleport_rs_api" target that provides Rust # # bindings for the "teleport.h" header from the ":teleport" source # # set. # rs_bindings_from_cc("teleport_rs_api") { # bindings_target = ":teleport" # public_headers = ["teleport.h"] # deps = [ ":goat_rs_api" ] # Parallel's `public_deps` of ":teleport". # } # # source_set("teleport") { # sources = [ "teleport.h", ... ] # public_deps = [ ":goat" ] # } # # rs_bindings_from_cc("goat_rs_api") { # bindings_target = ":goat" # public_headers = ["goat.h"] # } # source_set("goat") { # sources = [ "goat.h", ... ] # } # # teleport.h: # #include "goat.h" # void TeleportGoat(const Goat& goat_to_teleport); # # goat.h: # struct Goat { ... }; # # main.rs: # fn main() { # let g: goat_rs_api::Goat = ...; # teleport_rs_api::TeleportGoat(&g); # } # # Debugging and implementation notes: # # - Consider running the build while CRUBIT_DEBUG environment variable is set. # This will generate additional `.ir` file and log extra information from # the `run_rs_bindings_from_cc.py` script (e.g. full cmdlines for invoking # `rs_bindings_from_cc`). # template("rs_bindings_from_cc") { # Mandatory parameter: bindings_target. assert(defined(invoker.bindings_target), "Must specify the C target to make bindings for.") _bindings_target = invoker.bindings_target # Mandatory/unavoidable parameter: target_name _lib_target_name = target_name _base_target_name = get_label_info(_bindings_target, "name") assert(_lib_target_name == "${_base_target_name}_rs_api", "The convention is that bindings for `foo` are named `foo_rs_api`") # Mandatory parameter: public_headers. assert(defined(invoker.public_headers), "Must specify the public C headers to make bindings for.") _rebased_public_headers = [] foreach(hdr, invoker.public_headers) { _rebased_public_headers += [ rebase_path(hdr) ] } # Optional parameter: testonly. _testonly = false if (defined(invoker.testonly)) { _testonly = invoker.testonly } # Optional parameter: visibility. if (defined(invoker.visibility)) { _visibility = invoker.visibility } # Optional parameter: deps. # # TODO(crbug.com/1329611): Can we somehow assert that `_deps` only contains # some "..._rs_api" targets crated via # `mixed_static_library($_lib_target_name)` below? foreach(dep, _deps) { # assert something } _deps = [] if (defined(invoker.deps)) { _deps = invoker.deps } # Various names and paths that are shared across multiple targets defined # in the template here. _gen_bindings_target_name = "${_lib_target_name}_gen_bindings" _gen_metadata_target_name = "${_lib_target_name}_gen_metadata" _metadata_target_name = "${_lib_target_name}_metadata" _metadata_path = "${target_gen_dir}/${_lib_target_name}_meta.json" _rs_out_path = "${target_gen_dir}/${_lib_target_name}.rs" _cc_out_path = "${target_gen_dir}/${_lib_target_name}_impl.cc" # Calculating the --targets_and_args snippet for the *current* target # and putting it into GN's `metadata`. group(_metadata_target_name) { testonly = _testonly visibility = [ ":${_gen_metadata_target_name}", ":${_lib_target_name}", ] deps = [] metadata = { # The data below corresponds to a single-target entry inside # `--targets_and_args` cmdline argument of `rs_bindings_from_cc`. crubit_target_and_args = [ { # The `get_label_info` call below expands ":foo_rs_api" into # something like "//dir/bar/baz:foo_rs_api". Crubit assumes that # there is a colon + uses the after-colon-suffix as the name of the # crate. t = get_label_info(":${_lib_target_name}", "label_no_toolchain") h = _rebased_public_headers }, ] } } # Gathering --targets-and-args data from *all* transitive dependencies and # putting them into the file at `_metadata_path`. generated_file(_gen_metadata_target_name) { testonly = _testonly visibility = [ ":${_gen_bindings_target_name}" ] deps = [ ":${_metadata_target_name}" ] deps += _deps testonly = _testonly outputs = [ _metadata_path ] output_conversion = "json" data_keys = [ "crubit_target_and_args" ] # `walk_keys` are used to limit how deep the transitive dependency goes. # This is important, because Crubit doesn't care about all the `deps` or # `public_deps` of the `_bindings_target`. (See also the doc comment about # `rs_bindings_from_cc.deps` parameter at the top of this file.) walk_keys = [ "crubit_metadata_deps" ] } # Exposing the generated Rust bindings. mixed_static_library(_lib_target_name) { testonly = _testonly if (defined(_visibility)) { visibility = _visibility } sources = [ _cc_out_path ] deps = _deps deps += [ ":${_gen_bindings_target_name}", ":${_metadata_target_name}", "//third_party/crubit:deps_of_rs_api_impl", _bindings_target, ] # Chromium already covers `chromium/src/` and `out/Release/gen` in the # include path, but we need to explicitly add `out/Release` below. This # is needed, because `--public_headers` passed to Crubit use paths relative # to the `out/Release` directory. See also b/239238801. include_dirs = [ root_build_dir ] rs_sources = [ _rs_out_path ] rs_crate_name = _lib_target_name rs_crate_root = _rs_out_path rs_deps = _deps rs_deps += [ ":${_gen_bindings_target_name}", "//third_party/crubit:deps_of_rs_api", ] metadata = { crubit_metadata_deps = _deps + [ ":${_metadata_target_name}" ] } } # Invoking Crubit's `rs_bindings_from_cc` tool to generate Rust bindings. action(_gen_bindings_target_name) { testonly = _testonly if (defined(_visibility)) { visibility = _visibility } script = "//build/rust/run_rs_bindings_from_cc.py" inputs = [ "//third_party/rust-toolchain/bin/rs_bindings_from_cc" ] sources = invoker.public_headers outputs = [ _rs_out_path, _cc_out_path, ] deps = [ ":${_gen_metadata_target_name}" ] args = [ # Target-specific outputs: "--rs_out", rebase_path(_rs_out_path), "--cc_out", rebase_path(_cc_out_path), # Target-specific inputs: "--public_headers", string_join(",", _rebased_public_headers), "--targets_and_args_from_gn", rebase_path(_metadata_path), ] # Several important compiler flags come from default_compiler_configs configs = default_compiler_configs if (defined(invoker.configs)) { configs += invoker.configs } args += [ "--", "{{defines}}", "{{include_dirs}}", "{{cflags}}", # This path contains important C headers (e.g. stddef.h) and {{cflags}} # does not include it. Normally this path is implicitly added by clang but # it does not happen for libclang. # # Add it last so includes from deps and configs take precedence. "-isystem" + rebase_path( clang_base_path + "/lib/clang/" + clang_version + "/include", root_build_dir), # Passes C comments through as rustdoc attributes. "-fparse-all-comments", ] } }