# Copyright 2021 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/cronet/config.gni")
import("//build/config/rust.gni")
import("//build/rust/gni_impl/cpp_api_from_rust.gni")
import("//build/rust/rust_unit_test.gni")
# The //build directory is re-used for non-Chromium projects. Do not support
# cxx bindings in such contexts by default, because //third_party may be
# missing. Projects that wish to use cxx bindings must explicitly set the
# enable_rust_cxx GN arg to true.
if (enable_rust_cxx) {
import("//third_party/rust/cxx/chromium_integration/rust_cxx.gni")
}
# Creates a Rust target (rlib, executable, proc macro etc.) with ability to
# understand some handy variables such as "edition" and "features" and also to
# build any associated unit tests.
#
# `bindgen_deps` field exists only for 3P cargo sys crates that uses
# `rust_bindgen_generator` templates. For 1P code, `rust_bindgen` should
# be used and should go directly in the `deps` field.
#
# Normally, you should not use this directly. Use either
# - cargo_crate.gni - for 3p crates only
# - rust_static_library.gni - for 1p Rust code
#
# Because the common use of this is rust_static_library, all the documentation
# for the supported options is given in rust_static_library.gni. Please refer
# over there.
#
# If you're using rust_target directly, you will also need to specify:
# target_type executable, rust_library etc. per GN norms
#
# There is one area where this differs from `rust_static_library`: configs.
# Here, you must specify `executable_configs` or `library_configs` depending on
# the type of thing you're generating. This is so that different defaults can
# be provided.
template("rust_target") {
assert(enable_rust)
_target_name = target_name
# NOTE: TargetName=>CrateName mangling algorithm should be updated
# simultaneously in 3 places: here, //build/rust/rust_static_library.gni,
# //build/rust/chromium_prelude/import_attribute.rs
if (defined(invoker.crate_name)) {
_crate_name = invoker.crate_name
} else {
_dir_hash = string_hash(get_label_info(":${_target_name}", "dir"))
_crate_name = "${_target_name}_${_dir_hash}"
}
_generate_crate_root =
defined(invoker.generate_crate_root) && invoker.generate_crate_root
# Only one of `crate_root` or `generate_crate_root` can be specified, or
# neither.
assert(!defined(invoker.crate_root) || !_generate_crate_root)
# This is where the OUT_DIR environment variable points to when running a
# build script and when compiling the build target, for consuming generated
# files.
_env_out_dir = "$target_gen_dir/$_target_name"
_allow_unsafe = false
if (defined(invoker.allow_unsafe)) {
_allow_unsafe = invoker.allow_unsafe
}
# Default to the `args.gn`-wide setting, but allow individual targets to
# opt-out by setting `no_clippy = true`.
_enable_clippy_for_this_target = enable_rust_clippy
if (defined(invoker.no_clippy) && invoker.no_clippy) {
_enable_clippy_for_this_target = false
}
_enable_cpp_api_from_rust_for_this_target = false
if (defined(invoker.cpp_api_from_rust)) {
_cpp_api_from_rust_attributes = invoker.cpp_api_from_rust
if (!enable_cpp_api_from_rust) {
not_needed(_cpp_api_from_rust_attributes, "*")
} else {
_enable_cpp_api_from_rust_for_this_target = true
assert(defined(_cpp_api_from_rust_attributes.target_name))
_cpp_api_from_rust_target_name = _cpp_api_from_rust_attributes.target_name
# The logic below is based on the thinking captured in section
# "Decision: namespace attr _mandatory_ for 1st-party" in
# https://docs.google.com/document/d/1I5uRfjC4xghqMgc8C0Jynt7ZmN9Dbn9qcIHfO_wpucA/edit?usp=sharing
if (!defined(_cpp_api_from_rust_attributes.cpp_namespace)) {
if (defined(invoker.crate_name)) {
_cpp_api_from_rust_attributes.cpp_namespace = invoker.crate_name
} else {
assert(false,
"Please specify `cpp_namespace` for `cpp_api_from_rust`")
}
}
}
}
if (_generate_crate_root) {
generated_file("${_target_name}_crate_root") {
outputs = [ "${target_gen_dir}/${target_name}.rs" ]
contents = [
"// Generated crate root for ${_target_name}.",
"// @generated",
"",
]
foreach(rs, invoker.sources) {
rs_path_from_root = rebase_path(rs, target_gen_dir)
contents += [ "#[path = \"${rs_path_from_root}\"]" ]
# Drop the file extension from the module name.
rs_modname = string_replace(rs, ".rs", "")
# Replace invalid "/" chars in the source file path.
rs_modname = string_replace(rs_modname, "/", "_")
# Since source files are specified relative to the BUILD.gn they may
# also have ".." path components.
rs_modname = string_replace(rs_modname, "..", "dotdot")
contents += [
"mod ${rs_modname};",
"",
]
}
}
_generated_crate_root = get_target_outputs(":${_target_name}_crate_root")
_crate_root = _generated_crate_root[0]
} else if (defined(invoker.crate_root)) {
_crate_root = invoker.crate_root
} else if (invoker.target_type == "executable") {
_crate_root = "src/main.rs"
} else {
_crate_root = "src/lib.rs"
}
_testonly = false
if (defined(invoker.testonly)) {
_testonly = invoker.testonly
}
if (defined(invoker.visibility)) {
_visibility = invoker.visibility
}
_rustflags = []
if (defined(invoker.rustflags)) {
_rustflags += invoker.rustflags
}
if (defined(invoker.features)) {
foreach(i, invoker.features) {
_rustflags += [ "--cfg=feature=\"${i}\"" ]
}
}
assert(!defined(configs))
_configs = []
# Allow the `invoker` to override Rust language edition. See also doc
# comments for `edition` at the top of
# `//build/rust/rust_static_library.gni`.
if (defined(invoker.edition)) {
_configs += [ "//build/rust:edition_${invoker.edition}" ]
} else {
_configs += [ "//build/rust:default_edition" ]
}
_test_configs = []
if (invoker.target_type == "executable") {
_configs += invoker.executable_configs
} else if (invoker.target_type == "rust_proc_macro") {
_configs += invoker.proc_macro_configs
_test_configs += [ "//build/rust:proc_macro_extern" ]
} else if (invoker.target_type == "shared_library") {
_configs += invoker.shared_library_configs
} else {
_configs += invoker.library_configs
}
if (invoker.target_type == "rust_proc_macro") {
_main_target_suffix = "__proc_macro"
} else if (invoker.target_type == "shared_library") {
_main_target_suffix = "__shared_library"
} else {
_main_target_suffix = ""
}
_main_target_name = "${_target_name}${_main_target_suffix}"
_deps = []
if (defined(invoker.deps)) {
_deps += invoker.deps
}
if (defined(invoker.bindgen_deps)) {
_bindgen_inputs = []
# This iteration assumes that no targets have the same name which is
# very rare to happen and if it does. An error will be thrown as we
# try to create two targets with the same name, the error might not
# be descriptive enough so maybe adding a check action would be better.
foreach(bindgen_dep, invoker.bindgen_deps) {
_copy_target_name =
target_name + "_" + get_label_info(bindgen_dep, "name") + "_copy"
copy(_copy_target_name) {
_bindgen_output_files = get_target_outputs(bindgen_dep)
if (defined(invoker.testonly)) {
testonly = invoker.testonly
}
# `rust_bindgen_generator` promises that the first output file is always .rs.
sources = [ _bindgen_output_files[0] ]
outputs = [ "$_env_out_dir/{{source_name_part}}.rs" ]
deps = [ bindgen_dep ]
}
# The bindgen-generated rs files are inputs to this library for the library
# to `include!` them.
# The output of the copy action is always a single file so just copy everything.
_bindgen_inputs += get_target_outputs(":$_copy_target_name")
# Depend on the bindgen generation to make the above `_bindgen_inputs`.
_deps += [ ":$_copy_target_name" ]
}
}
_public_deps = []
if (defined(invoker.public_deps)) {
_public_deps += invoker.public_deps
}
if (defined(invoker.aliased_deps)) {
_aliased_deps = invoker.aliased_deps
} else {
_aliased_deps = {
}
}
_build_unit_tests = false
if (defined(invoker.build_native_rust_unit_tests)) {
_build_unit_tests =
invoker.build_native_rust_unit_tests && can_build_rust_unit_tests
}
# Declares that the Rust crate generates bindings between C++ and Rust via the
# Cxx crate. It may generate C++ headers and/or use the cxx crate macros to
# generate Rust code internally, depending on what bindings are declared. If
# set, it's a set of rust files that include Cxx bindings declarations.
_cxx_bindings = []
assert(!defined(invoker.cxx_bindings) || enable_rust_cxx,
"cxx bindings are not supported when building rust targets " +
"outside the Chromium build.")
if (defined(invoker.cxx_bindings)) {
_cxx_bindings = invoker.cxx_bindings
}
_rustenv = [ "OUT_DIR=" + rebase_path(_env_out_dir, root_build_dir) ]
if (defined(invoker.rustenv)) {
_rustenv += invoker.rustenv
}
# We require that all source files are listed, even though this is
# not a requirement for rustc. The reason is to ensure that tools
# such as `gn deps` give the correct answer, and thus we trigger
# the right test suites etc. on code change.
# TODO(crbug.com/40200431) - verify this is correct
assert(defined(invoker.sources), "sources must be listed")
if (invoker.target_type == "rust_proc_macro" &&
!toolchain_for_rust_host_build_tools) {
# Redirect to the proc macro toolchain, which uses prebuilt stdlib libraries
# that are not built with panic=abort.
group(_target_name) {
testonly = _testonly
if (defined(_visibility)) {
visibility = _visibility
}
public_deps = [ ":${_main_target_name}($rust_macro_toolchain)" ]
}
not_needed(invoker, "*")
not_needed([
"_aliased_deps",
"_allow_unsafe",
"_build_unit_tests",
"_crate_root",
"_crate_name",
"_cxx_bindings",
"_deps",
"_enable_clippy_for_this_target",
"_enable_cpp_api_from_rust_for_this_target",
"_rustc_metadata",
"_out_dir",
"_public_deps",
"_rustenv",
"_rustflags",
"_support_use_from_cpp",
"_testonly",
])
} else {
# These are dependencies that must be included into the C++ target that
# depends on this Rust one, and into the Rust target itself, respectively.
#
# For an rlib or exe, it's enough to add all these as dependencies of the
# Rust target alone, and they will get included into the final link step.
#
# But when then Rust target is a shared library, the C++ target needs to
# link the C++ thunks that are used to call the cxx bridge functions. And
# Cxx library itself needs to be in both.
_cxx_generated_deps_for_cpp = []
_cxx_generated_deps_for_rust = []
if (_cxx_bindings != []) {
_cxx_generated_deps_for_cpp += [
# The Cxx-generated thunks, which have the public C++ names and bounce
# over to the Rust code.
":${_target_name}_cxx_generated",
# Additionally, C++ bindings generated by Cxx can include C++ types
# that come from the Cxx library, such as `rust::Str`. The header and
# implementation of these types are provided in the cxx_cppdeps target.
# The C++ targets depending on this Rust target need the headers, while
# the Rust target needs the implementation.
"//build/rust:cxx_cppdeps",
]
_cxx_generated_deps_for_rust = [
# The implementation of the Cxx library needs to be in the Rust target.
"//build/rust:cxx_cppdeps",
]
}
# Proc macros and shared libraries have a group for the target name and
# redirect to a suffixed target for the actual library.
if (_main_target_suffix != "") {
group(_target_name) {
testonly = _testonly
if (defined(_visibility)) {
visibility = _visibility
}
public_deps = [ ":${_main_target_name}" ]
public_deps += _cxx_generated_deps_for_cpp
}
}
_rustc_metadata = ""
if (defined(invoker.rustc_metadata)) {
_rustc_metadata = invoker.rustc_metadata
}
_rust_deps = _deps
_rust_aliased_deps = _aliased_deps
_rust_public_deps = _public_deps
_cxx_deps = _deps
# Include the `chromium` crate in all first-party code. Third-party code
# (and the `chromium` crate itself) opts out by setting
# `no_chromium_prelude`.
if (!defined(invoker.no_chromium_prelude) || !invoker.no_chromium_prelude) {
if (enable_chromium_prelude) {
_rust_deps += [ "//build/rust/chromium_prelude" ]
}
}
if (_cxx_bindings != []) {
# The Rust target (and unit tests) need the Cxx crate when using it to
# generate bindings.
_rust_deps += [ "//build/rust:cxx_rustdeps" ]
}
if (!defined(invoker.no_std) || !invoker.no_std) {
_rust_deps += [ "//build/rust/std" ]
}
# When Cronet is built in the Android repo, it builds differently (e.g., it
# uses stdlib from Android, which includes its own allocator. If we depend
# on Chromium's allocator crate, its shims, and libstd's will conflict on
# each other and fail the build (see https://crbug.com/469501106 for more
# details).
# Ideally, we should do the same when building Cronet within Chromium, for
# consistency. However, this is currently incompatible with how Chromium
# builds rust dependencies (https://crbug.com/469501106 documents the errors
# encountered when trying this).
if ((!defined(invoker.no_allocator_crate) || !invoker.no_allocator_crate) &&
!is_cronet_for_aosp_build) {
_rust_deps += [ "//build/rust/allocator" ]
}
if (_build_unit_tests) {
_unit_test_target = "${_target_name}_unittests"
if (defined(invoker.unit_test_target)) {
_unit_test_target = invoker.unit_test_target
}
rust_unit_test(_unit_test_target) {
testonly = true
crate_name = _unit_test_target
crate_root = _crate_root
sources = invoker.sources + [ crate_root ]
rustflags = _rustflags
env_out_dir = _env_out_dir
if (defined(invoker.unit_test_output_dir)) {
output_dir = invoker.unit_test_output_dir
}
deps = _rust_deps + _public_deps
aliased_deps = _rust_aliased_deps
public_deps = [ ":${_target_name}" ]
if (defined(invoker.test_deps)) {
deps += invoker.test_deps
}
inputs = []
if (defined(invoker.inputs)) {
inputs += invoker.inputs
}
if (defined(_bindgen_inputs)) {
inputs += _bindgen_inputs
}
if (defined(invoker.test_inputs)) {
inputs += invoker.test_inputs
}
if (defined(invoker.executable_configs)) {
configs = []
configs += invoker.executable_configs
}
configs += _test_configs
rustenv = _rustenv
if (!_allow_unsafe) {
configs += [ "//build/rust:forbid_unsafe" ]
}
}
} else {
not_needed([
"_crate_root",
"_crate_name",
"_rustc_metadata",
"_test_configs",
])
not_needed(invoker, [ "executable_configs" ])
}
if (_enable_clippy_for_this_target) {
_clippy_target_name = "${_target_name}_clippy"
}
_rust_sources = invoker.sources
if (_generate_crate_root) {
_rust_deps += [ ":${_target_name}_crate_root" ]
_rust_sources += [ _crate_root ]
}
_rust_inputs = []
if (defined(invoker.inputs)) {
_rust_inputs += invoker.inputs
}
if (defined(_bindgen_inputs)) {
_rust_inputs += _bindgen_inputs
}
target(invoker.target_type, "${_main_target_name}") {
forward_variables_from(invoker,
"*",
TESTONLY_AND_VISIBILITY + [
"aliased_deps",
"configs",
"deps",
"features",
"inputs",
"no_clippy",
"public_deps",
"rustenv",
"rustflags",
"sources",
"test_inputs",
"unit_test_output_dir",
"unit_test_target",
])
if (_main_target_suffix != "") {
# There's a group that depends on this target, and dependencies must
# be through that group.
visibility = [ ":$_target_name" ]
not_needed([ "_visibility" ])
} else if (defined(_visibility)) {
visibility = _visibility
}
if (_enable_clippy_for_this_target && defined(visibility)) {
visibility += [ ":${_clippy_target_name}" ]
}
testonly = _testonly
crate_name = _crate_name
crate_root = _crate_root
configs = []
configs = _configs
deps = _rust_deps + _cxx_generated_deps_for_rust
aliased_deps = _rust_aliased_deps
public_deps = _rust_public_deps
if (_main_target_suffix == "") {
# When these are not provided by a wrapper group target, they are added
# to the Rust target itself.
public_deps += _cxx_generated_deps_for_cpp
}
rustflags = _rustflags
if (_rustc_metadata != "") {
rustflags += [ "-Cmetadata=${_rustc_metadata}" ]
}
rustenv = _rustenv
sources = _rust_sources
inputs = _rust_inputs
if (!defined(output_name)) {
# Note that file names of libraries must start with the crate name in
# order for the compiler to find transitive dependencies in the
# directory search paths (since they are not all explicitly specified).
#
# For bin targets, we expect the target name to be unique, and the name
# of the exe should not add magic stuff to it. And bin crates can not be
# transitive dependencies.
if (invoker.target_type == "executable") {
output_name = _target_name
} else {
# TODO(danakj): Since the crate name includes the whole path for 1p
# libraries, we could move the output_dir to `root_out_dir` here for
# them, which would make for shorter file paths. But we need to not
# do the same for 3p crates or those with a `crate_name` set
# explicitly.
output_name = _crate_name
}
}
if (!_allow_unsafe) {
configs += [ "//build/rust:forbid_unsafe" ]
}
if (_enable_clippy_for_this_target && enable_rust_clippy_eager) {
# We want to make `_clippy_target_name` to execute any time
# `_main_target_name` is built, but libraries that depend on
# `_main_target_name` should not have to wait until Clippy finishes.
# GN `validations` are a special dependency kind that helps with this.
validations = [ ":${_clippy_target_name}" ]
}
}
if (_cxx_bindings != []) {
rust_cxx("${_target_name}_cxx_generated") {
testonly = _testonly
visibility = [ ":${_target_name}" ]
if (defined(_visibility)) {
visibility += _visibility
}
sources = _cxx_bindings
deps = _cxx_deps + _public_deps
configs = _configs
forward_variables_from(invoker, [ "allow_circular_includes_from" ])
# In a component_build the cxx bindings may be linked into a shared
# library at any point up the dependency tree, so always export.
export_symbols = is_component_build
}
} else {
not_needed([ "_cxx_deps" ])
}
if (_enable_clippy_for_this_target ||
_enable_cpp_api_from_rust_for_this_target) {
# Using `filter_include(get_target_outputs(..., ["*.rustflags.json"]))`
# would have been cleaner, but `get_target_outputs` doesn't work for
# library nor executable targets...
_rustc_env_and_flags =
"${target_out_dir}/${_main_target_name}.rustflags.json"
_rustc_rsp = "${target_out_dir}/${_main_target_name}.rsp.rust"
# All targets (e.g. Clippy and Crubit) that act as a secondary Rust
# compiler (i.e. consume `.rustflags.json` generated by
# `rustc_wrapper.py`) require the same set of shared, baseline
# attributes. These attributes are gathered below to support reuse.
_SECONDARY_RUST_COMPILER_ATTR_NAMES = [
"sources",
"deps",
"testonly",
]
_secondary_rust_compiler_attrs = {
# Consumed by the "compiler-like" tool:
sources = _rust_sources + _rust_inputs
# Consumed by the `..._wrapper.py` script:
sources += [
_rustc_env_and_flags,
_rustc_rsp,
]
# For `_rustc_env_and_flags` and `_rustc_rsp`
# (both produced as a side-effect of running `rustc_wrapper.py`).
deps = [ ":${_main_target_name}" ]
# For `.rlib`s, build-script-generated files, etc.
deps += _rust_deps
testonly = _testonly
}
if (_enable_clippy_for_this_target) {
_clippy_stamp_file = "${target_out_dir}/${_target_name}.clippy"
action("${_clippy_target_name}") {
forward_variables_from(_secondary_rust_compiler_attrs,
_SECONDARY_RUST_COMPILER_ATTR_NAMES)
script = "//build/rust/gni_impl/clippy_wrapper.py"
# `import`ed for `LoadRustEnvAndFlags`, etc.
inputs = [ "//build/rust/gni_impl/rustc_wrapper.py" ]
args = [
"--clippy-driver",
rebase_path("//third_party/rust-toolchain/bin/clippy-driver",
root_build_dir),
"--rustc-env-and-flags",
rebase_path(_rustc_env_and_flags, root_build_dir),
"--build-stamp-file",
rebase_path(_clippy_stamp_file, root_build_dir),
]
outputs = [ _clippy_stamp_file ]
}
}
if (_enable_cpp_api_from_rust_for_this_target) {
cpp_api_from_rust(_cpp_api_from_rust_target_name) {
forward_variables_from(_secondary_rust_compiler_attrs,
_SECONDARY_RUST_COMPILER_ATTR_NAMES)
rust_target = ":${_main_target_name}"
rustc_env_and_flags = _rustc_env_and_flags
original_attributes = _cpp_api_from_rust_attributes
crate_name = _crate_name
}
}
}
}
}