blob: 1a2bf1db549182a5fbd18f3ac3f54bfca8a2fae3 [file] [log] [blame]
# 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/rust.gni")
import("//build/rust/analyze.gni")
import("//build/rust/rust_unit_test.gni")
# The //build directory is re-used for non-Chromium products. We do not support
# cxx bindings in such contexts, because //third_party may be missing.
if (build_with_chromium) {
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.
#
# 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") {
# Only one of `crate_root` or `generate_crate_root` can be specified, or
# neither.
assert(!defined(invoker.crate_root) ||
!(defined(invoker.generate_crate_root) && invoker.generate_crate_root))
_target_name = target_name
_crate_name = target_name
if (defined(invoker.crate_name)) {
_crate_name = invoker.crate_name
}
if (defined(invoker.output_dir) && invoker.output_dir != "") {
# This is where the build target (.exe, .rlib, etc) goes.
_output_dir = invoker.output_dir
}
# 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
}
if (defined(invoker.generate_crate_root) && invoker.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};",
"",
]
}
}
_crate_root =
string_join("", get_target_outputs(":${_target_name}_crate_root"))
} 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
}
_use_local_std = use_local_std_by_default
if (defined(invoker.use_local_std)) {
_use_local_std = invoker.use_local_std
}
_rustflags = []
if (defined(invoker.rustflags)) {
_rustflags += invoker.rustflags
}
if (defined(invoker.features)) {
foreach(i, invoker.features) {
_rustflags += [ "--cfg=feature=\"${i}\"" ]
}
}
_edition = "2021"
if (defined(invoker.edition)) {
_edition = invoker.edition
}
_configs = [ "//build/rust:edition_${_edition}" ]
_test_configs = []
if (invoker.target_type == "executable") {
if (defined(invoker.executable_configs)) {
_configs += invoker.executable_configs
}
} else if (invoker.target_type == "rust_proc_macro") {
if (defined(invoker.proc_macro_configs)) {
_configs += invoker.proc_macro_configs
_test_configs += [ "//build/rust:proc_macro_extern" ]
}
} else {
if (defined(invoker.library_configs)) {
_configs += invoker.library_configs
}
}
_forward_to_host_toolchain = false
if (invoker.target_type == "rust_proc_macro") {
if (current_toolchain != host_toolchain_no_sanitizers) {
_forward_to_host_toolchain = true
}
_main_target_suffix = "${target_name}__proc_macro"
} else {
_main_target_suffix = "__rlib"
}
_deps = []
if (defined(invoker.deps)) {
_deps += invoker.deps
}
_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 = {
}
}
_is_data_dep = defined(invoker.is_data_dep) && invoker.is_data_dep
_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 = []
if (defined(invoker.cxx_bindings)) {
assert(build_with_chromium,
"cxx bindings are not supported when building rust targets " +
"outside the Chromium build.")
_cxx_bindings = invoker.cxx_bindings
}
_rustenv = [ "OUT_DIR=" + rebase_path(_env_out_dir) ]
if (defined(invoker.rustenv)) {
_rustenv += invoker.rustenv
}
# TODO(danakj): This could be a hash generated from the input crate, such as
# from its path, in which case the BUILD.gn would not need to specify
# anything. But GN doesn't give us a hash function to make that easy.
_metadata = "0"
if (defined(invoker.epoch)) {
_metadata = invoker.epoch
}
# 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/1256930) - verify this is correct
assert(defined(invoker.sources), "sources must be listed")
if (_forward_to_host_toolchain) {
# Redirect to the host toolchain.
group(_target_name) {
testonly = _testonly
if (defined(_visibility)) {
visibility = _visibility
}
public_deps = [
":${_target_name}${_main_target_suffix}($host_toolchain_no_sanitizers)",
]
}
not_needed(invoker, "*")
not_needed([
"_allow_unsafe",
"_build_unit_tests",
"_crate_root",
"_crate_name",
"_cxx_bindings",
"_deps",
"_aliased_deps",
"_is_data_dep",
"_metadata",
"_out_dir",
"_public_deps",
"_rustenv",
"_rustflags",
"_support_use_from_cpp",
"_testonly",
"_use_local_std",
"_visibility",
])
} else {
group(_target_name) {
testonly = _testonly
if (defined(_visibility)) {
visibility = _visibility
}
# Both the C++ bindings (if present) and the Rust crate should be treated
# like direct dependencies, so we expose them both in public_deps.
public_deps = [ ":${_target_name}${_main_target_suffix}" ]
# TODO(danakj): This would not be needed if we stopped forwarding through
# a group in the common (non-procmacro) case.
if (_is_data_dep) {
data_deps = [ ":${_target_name}${_main_target_suffix}" ]
}
if (_cxx_bindings != []) {
public_deps += [ ":${_target_name}_cxx_generated" ]
# Additionally, C++ bindings generated by Cxx can include C++ types
# that come from the Cxx library, such as `rust::Str`. So any C++
# target that depends on a rust target directly may need access to Cxx
# as well, which means it must appear in public_deps.
public_deps += [ "//build/rust:cxx_cppdeps" ]
# cxx_cppdeps pulls in the default libstd, so make sure the default was
# not overridden.
assert(
_use_local_std == use_local_std_by_default,
"Rust targets with cxx bindings cannot override the default libstd")
} else if (!defined(invoker.no_std) || !invoker.no_std) {
# If C++ depends on and links in the library, we need to make sure C++
# links in the Rust stdlib. This is orthogonal to if the library exports
# bindings for C++ to use.
if (_use_local_std) {
deps = [ "//build/rust/std:link_local_std" ]
} else {
assert(prebuilt_libstd_supported,
"Prebuilt Rust stdlib is not available for this target")
deps = [ "//build/rust/std:link_prebuilt_std" ]
}
}
}
_rust_deps = _deps
_rust_aliased_deps = _aliased_deps
_rust_public_deps = _public_deps
_cxx_deps = _deps
# The Rust target (and unit tests) need the Cxx crate when using it to
# generate bindings.
if (_cxx_bindings != []) {
_rust_deps += [ "//build/rust:cxx_rustdeps" ]
# C++ targets can depend on the Rust target from the BUILD.gn file to
# access the headers generated from it
_rust_public_deps += [ ":${_target_name}_cxx_generated" ]
}
if (!defined(invoker.no_std) || !invoker.no_std) {
if (_use_local_std) {
_rust_deps += [ "//build/rust/std:local_std_for_rustc" ]
} else {
_rust_deps += [ "//build/rust/std:prebuilt_std_for_rustc" ]
}
} else {
not_needed([ "_use_local_std" ])
}
# You must go through the groups above to get to these targets.
_visibility = []
_visibility = [ ":${_target_name}" ]
target(invoker.target_type, "${_target_name}${_main_target_suffix}") {
forward_variables_from(invoker,
"*",
TESTONLY_AND_VISIBILITY + [
"features",
"deps",
"aliased_deps",
"public_deps",
"rustflags",
"rustenv",
"configs",
"unit_test_output_dir",
"unit_test_target",
"test_inputs",
])
testonly = _testonly
visibility = _visibility
crate_name = _crate_name
crate_root = _crate_root
configs = []
configs = _configs
deps = _rust_deps
aliased_deps = _rust_aliased_deps
public_deps = _rust_public_deps
rustflags = _rustflags
rustflags += [ "-Cmetadata=${_metadata}" ]
rustenv = _rustenv
# The Rust tool() declarations, like C++ ones, use the output_name and
# output_dir, so that GN targets can override these if needed. Here we
# give them their default values, or allow them to be overridden.
if (defined(_output_dir)) {
output_dir = _output_dir
}
if (!defined(output_name) || output_name == "") {
output_name = crate_name
}
if (compute_inputs_for_analyze) {
deps += [ ":${_target_name}_analyze" ]
}
if (!_allow_unsafe) {
configs += [ "//build/rust:forbid_unsafe" ]
}
}
if (compute_inputs_for_analyze) {
# Find and depend on all rust files in the crate for the purpose of `gn
# analyze`.
analyze_rust("${_target_name}_analyze") {
forward_variables_from(invoker, "*", [ "crate_root" ])
crate_root = _crate_root
}
}
if (_cxx_bindings != []) {
rust_cxx("${_target_name}_cxx_generated") {
testonly = _testonly
visibility = [ ":${_target_name}${_main_target_suffix}" ]
if (defined(_visibility)) {
visibility += _visibility
}
sources = _cxx_bindings
deps = _cxx_deps + _public_deps
if (is_component_build) {
# 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 = true
} else if (invoker.target_type == "shared_library") {
export_symbols = true
} else {
export_symbols = false
}
}
} else {
not_needed([ "_cxx_deps" ])
}
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) {
forward_variables_from(invoker, [ "sources" ])
testonly = true
crate_root = _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(invoker.test_inputs)) {
inputs += invoker.test_inputs
}
if (defined(invoker.executable_configs)) {
configs = []
configs = invoker.executable_configs
} else if (!defined(configs)) {
configs = []
}
configs += _test_configs
rustenv = _rustenv
if (!_allow_unsafe) {
configs += [ "//build/rust:forbid_unsafe" ]
}
}
} else {
not_needed([
"_crate_root",
"_crate_name",
"_metadata",
"_test_configs",
])
}
}
}
set_defaults("rust_target") {
executable_configs = default_executable_configs
library_configs = default_compiler_configs
proc_macro_configs = default_rust_proc_macro_configs
}