blob: 7426a792242a4bad05859d182f3913c888154de9 [file] [log] [blame]
# Copyright 2021 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//build/compiled_action.gni")
import("//build/rust/rustc_library.gni")
import("//build/rust/rustc_staticlib.gni")
# Defines a Rust library with a C++ FFI generated using the 'cxx' crate.
# See https://docs.rs/cxx/ for general docs for the 'cxx' crate.
#
# This template provides the build machinery to use the 'cxx' crate to generate a Rust/C++ FFI when
# you want to use a Rust library from a C++ codebase or, vice versa, to define a Rust interface for
# a C++ library.
#
# The template produces a source_set that includes the generated FFI header and implementation,
# plus any specified 'sources', and that depends on a Rust static library built from the specified
# 'rust_sources' and 'rust_deps'.
#
# For example, to create a C++ API for a Rust library "foo", you could create a "src/lib.rs" file
# containing a #[cxx::bridge] module and accompanying implementations (following the 'cxx' crate
# docs), a "ffi.h" with the user-facing C++ API you want, a "ffi.cc" implementation that includes
# the generated "lib.rs.h" header, and the following build targets:
#
# rustc_library("foo") {
# ...
# }
#
# rust_cxx_ffi_library("foo-ffi") {
# rust_sources = [
# "src/lib.rs",
# "src/other.rs",
# ]
# bridge_sources = [ "src/lib.rs" ]
# rust_deps = [ ":foo" ]
# sources = [
# "ffi.h",
# "ffi.c"
# ]
# }
#
# Then you can depend on the source_set from other Rust or C++ targets:
#
# test("foo-ffi-test") {
# sources = [ "ffi-test.cc" ]
# deps = [
# ":foo-ffi",
# ]
# }
#
# rustc_library("uses-foo") {
# sources = [ "src/lib.rs" ]
# deps = [
# ":foo-ffi",
# ]
# }
#
# For a real example, see //src/lib/process_builder/ffi
#
# Parameters
#
# name (optional)
# Name of the crate as defined in its manifest file. Unspecified if not set explicitly.
#
# rust_sources (required)
# List of Rust source files which the Rust library is allowed to compile. The Rust compiler
# discovers source files by following `mod` declarations starting at the `rust_source_root`. The
# discovered source files must match this list.
# Type: list of paths
#
# bridge_sources (required)
# The subset of `rust_sources` which contain a `#[cxx::bridge]` module.
# Type: list of paths
#
# rust_source_root (optional)
# Location of the crate root. Defaults to `./src/lib.rs`.
# Type: path
#
# rust_deps (optional)
# List of dependencies required by the Rust static library target. Technically optional, but at
# minimum you'll need "//third_party/rust_crates:cxx".
# Type: list of paths
# Default: []
#
# cpp_sources (optional)
# List of C++ source files to compile into the final source_set.
# Type: list of paths
#
# deps (optional)
# public_deps (optional)
# testonly (optional)
# visibility (optional)
# These all have their usual GN meaning and are applied to the final source_set.
#
template("rust_cxx_ffi_library") {
assert(defined(invoker.rust_sources),
"rust_sources must be defined for $target_name")
rust_lib_target = "${target_name}_rustc_library"
generated_rs_target = "${target_name}_staticlib_source_root_rs"
rust_staticlib_target = "${target_name}_rustc_staticlib"
config_target = "${target_name}_config"
# compiled_action's default assumption is that the executable output_name == target_name, which
# is true for generated 3P Rust executable targets.
cxxbridge_cmd_tool = "//third_party/rust_crates:cxxbridge"
if (defined(invoker.rust_source_root)) {
rust_source_root = invoker.rust_source_root
} else {
rust_source_root = "src/lib.rs"
}
source_gen_dir = get_path_info(rust_source_root, "gen_dir")
generation_targets = []
generated_sources = []
foreach(src, invoker.bridge_sources) {
assert(invoker.rust_sources + [ src ] - [ src ] != invoker.rust_sources,
"rust_sources must contain all bridge_sources, including $src")
target_prefix = "${target_name}_" + string_replace(src, "/", "_")
gen_target = "${target_prefix}_cxxbridge"
generation_targets += [ ":$gen_target" ]
include_stem = get_path_info(get_path_info(src, "dir"), "abspath")
source_file = get_path_info(src, "file")
generated_header = "$source_gen_dir/$include_stem/$source_file.h"
generated_impl = "$source_gen_dir/$include_stem/$source_file.cc"
generated_sources += [
generated_header,
generated_impl,
]
compiled_action(gen_target) {
tool = cxxbridge_cmd_tool
sources = [ src ]
outputs = [
generated_header,
generated_impl,
]
args = [
"-o",
rebase_path(generated_header, root_build_dir),
"-o",
rebase_path(generated_impl, root_build_dir),
rebase_path(src, root_build_dir),
]
visibility = [ ":*" ]
}
}
if (defined(invoker.name)) {
package_name = invoker.name
} else {
package_name = target_name
}
rust_deps = [ "//third_party/rust_crates:cxx" ]
if (defined(invoker.rust_deps)) {
rust_deps += invoker.rust_deps
}
# This allows other Rust libraries or even other rust_cxx_ffi_library targets to depend on this,
# so that multiple CXX FFI bridges can be composed, where the rustc_staticlib target alone does
# not.
rustc_library(rust_lib_target) {
forward_variables_from(invoker,
[
"testonly",
"visibility",
])
name = package_name
edition = "2018"
source_root = rust_source_root
sources = invoker.rust_sources
# Depending on the generation targets above avoids noisy build failures if there's an issue
# in the cxxbridge section, since then this will definitely fail too.
deps = generation_targets + rust_deps
}
crate_name = string_replace(package_name, "-", "_")
generated_file(generated_rs_target) {
outputs = [ "$source_gen_dir/staticlib_source_root.rs" ]
contents = "pub use " + crate_name + "::*;"
}
# Create a staticlib from the rustc_library above. This uses a generated source_root that just
# re-exports all of the library's symbols.
rustc_staticlib(rust_staticlib_target) {
forward_variables_from(invoker,
[
"testonly",
"visibility",
])
edition = "2018"
_generated_outputs = get_target_outputs(":$generated_rs_target")
source_root = _generated_outputs[0]
sources = [ source_root ]
deps = [
":$generated_rs_target",
":$rust_lib_target",
]
}
config(config_target) {
include_dirs = [ source_gen_dir ]
}
source_set(target_name) {
forward_variables_from(invoker,
[
"deps",
"public_deps",
"testonly",
"visibility",
])
sources = []
if (defined(invoker.cpp_sources)) {
sources += invoker.cpp_sources
}
sources += generated_sources
if (!defined(deps)) {
deps = []
}
deps += generation_targets
deps += [ ":$rust_staticlib_target" ]
if (!defined(public_deps)) {
public_deps = []
}
public_deps += [
":$rust_lib_target",
"//src/lib/fuchsia-cxx:cxx_lib",
]
if (!defined(public_configs)) {
public_configs = []
}
public_configs += [ ":$config_target" ]
}
}