blob: 135e1fa667bbd8ffef3b8dc3daf9c957662f6a99 [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/rust/rust_executable.gni")
import("//build/rust/rust_macro.gni")
import("//build/rust/rust_static_library.gni")
# This template allows for building Cargo crates within gn.
#
# It is intended for use with pre-existing (third party) code and
# is none too efficient. (It will stall the build pipeline whilst
# it runs build scripts to work out what flags are needed). First
# party code should directly use first-class gn targets, such as
# //build/rust/rust_static_library.gni or similar.
#
# Because it's intended for third-party code, it automatically
# defaults to //build/config/compiler:no_chromium_code which
# suppresses some warnings. If you *do* use this for first party
# code, you should remove that config and add the equivalent
# //build/config/compiler:chromium_code config.
#
# Arguments:
# sources
# crate_root
# deps
# aliased_deps
# features
# build_native_rust_unit_tests
# edition
# crate_name
# All just as in rust_static_library.gni
# library_configs/executable_configs
# All just as in rust_target.gni
#
# epoch (optional)
# The major version of the library, which is used to differentiate between
# multiple versions of the same library name. This includes all leading 0s
# and the first non-zero value in the crate's version. This should be left
# as the default, which is "0", for first-party code unless there are
# multiple versions of a crate present. For third-party code, the version
# epoch (matching the directory it is found in) should be specified.
#
# Examples:
# 1.0.2 => epoch = "1"
# 4.2.0 => epoch = "4"
# 0.2.7 => epoch = "0.2"
# 0.0.3 => epoch = "0.0.3"
#
# dev_deps
# Same meaning as test_deps in rust_static_library.gni, but called
# dev_deps to match Cargo.toml better.
#
# build_root (optional)
# Filename of build.rs build script.
#
# build_deps (optional)
# Build script dependencies
#
# build_sources (optional)
# List of sources for build script. Must be specified if
# build_root is specified.
#
# build_script_outputs (optional)
# List of .rs files generated by the build script, if any.
# Fine to leave undefined even if you have a build script.
# This doesn't directly correspond to any Cargo variable,
# but unfortunately is necessary for gn to build its dependency
# trees automatically.
# Many build scripts just output --cfg directives, in which case
# no source code is generated and this can remain empty.
#
# build_script_inputs (optional)
# If the build script reads any files generated by build_deps,
# as opposed to merely linking against them, add a list of such
# files here. Again, this doesn't correspond to a Cargo variable
# but is necessary for gn.
#
# crate_type "bin", "proc-macro" or "rlib" (optional)
# Whether to build an executable. The default is "rlib".
# At present others are not supported.
#
# cargo_pkg_authors
# cargo_pkg_version
# cargo_pkg_name
# cargo_pkg_description
# Strings as found within 'version' and similar fields within Cargo.toml.
# Converted to environment variables passed to rustc, in case the crate
# uses clap `crate_version!` or `crate_authors!` macros (fairly common in
# command line tool help)
template("cargo_crate") {
_orig_target_name = target_name
_crate_name = _orig_target_name
if (defined(invoker.crate_name)) {
_crate_name = invoker.crate_name
}
# Construct metadata from the crate epoch or an explicitly provided metadata
# field.
_rustc_metadata = ""
if (defined(invoker.rustc_metadata)) {
_rustc_metadata = invoker.rustc_metadata
} else if (defined(invoker.epoch)) {
_rustc_metadata = "${_crate_name}-${invoker.epoch}"
}
# Executables need to have unique names. Work out a prefix.
if (defined(invoker.build_root)) {
_epochlabel = "vunknown"
if (defined(invoker.epoch)) {
_tempepoch = string_replace(invoker.epoch, ".", "_")
_epochlabel = "v${_tempepoch}"
}
# This name includes the target name to ensure it's unique for each possible
# build target in the same BUILD.gn file.
_build_script_name =
"${_crate_name}_${target_name}_${_epochlabel}_build_script"
# Where the OUT_DIR will point when running the build script exe, and
# compiling the crate library/binaries. This directory must include the
# target name to avoid collisions between multiple GN targets that exist
# in the same BUILD.gn.
_build_script_env_out_dir = "$target_gen_dir/$target_name"
}
_rustenv = []
if (defined(invoker.rustenv)) {
_rustenv = invoker.rustenv
}
if (defined(invoker.cargo_pkg_authors)) {
_rustenv += [ "CARGO_PKG_AUTHORS=${invoker.cargo_pkg_authors}" ]
}
if (defined(invoker.cargo_pkg_version)) {
_rustenv += [ "CARGO_PKG_VERSION=${invoker.cargo_pkg_version}" ]
}
if (defined(invoker.cargo_pkg_name)) {
_rustenv += [ "CARGO_PKG_NAME=${invoker.cargo_pkg_name}" ]
}
if (defined(invoker.cargo_pkg_description)) {
_rustenv += [ "CARGO_PKG_DESCRIPTION=${invoker.cargo_pkg_description}" ]
}
# Try to determine the CARGO_MANIFEST_DIR, preferring the directory
# with build.rs and otherwise assuming that the target contains a
# `crate/` subdirectory.
if (defined(invoker.build_root)) {
manifest_dir = "."
} else {
build_gn_dir = get_label_info(target_name, "dir")
manifest_dir = rebase_path(build_gn_dir + "/crate", root_build_dir)
}
_rustenv += [ "CARGO_MANIFEST_DIR=${manifest_dir}" ]
# cargo_crate() should set library_configs, executable_configs,
# proc_macro_configs. Not configs.
assert(!defined(invoker.configs))
# Work out what we're building.
_crate_type = "rlib"
if (defined(invoker.crate_type)) {
_crate_type = invoker.crate_type
}
if (_crate_type == "cdylib") {
# Crates are rarely cdylibs. The example encountered so far aims
# to expose a C API to other code. In a Chromium context, we don't
# want to build that as a dylib for a couple of reasons:
# * rust_shared_library does not work on Mac. rustc does not know
# how to export the __llvm_profile_raw_version symbol.
# * even if it did work, this might require us to distribute extra
# binaries (.so/.dylib etc.)
# For the only case we've had so far, it makes more sense to build
# the code as a static library which we can then link into downstream
# binaries.
_crate_type = "rlib"
}
if (_crate_type == "bin") {
_target_type = "rust_executable"
assert(!defined(invoker.epoch))
if (defined(invoker.executable_configs)) {
_configs = invoker.executable_configs
}
} else if (_crate_type == "proc-macro") {
_target_type = "rust_macro"
if (defined(invoker.proc_macro_configs)) {
_configs = invoker.proc_macro_configs
}
} else {
assert(_crate_type == "rlib")
_target_type = "rust_static_library"
if (defined(invoker.library_configs)) {
_configs = invoker.library_configs
}
}
if (defined(invoker.output_name)) {
_output_name = invoker.output_name
} else if (_crate_type != "bin") {
# 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.
_output_name = "${_crate_name}_${_orig_target_name}"
}
_testonly = false
if (defined(invoker.testonly)) {
_testonly = invoker.testonly
}
# The main target, either a Rust source set or an executable.
target(_target_type, target_name) {
forward_variables_from(invoker,
"*",
TESTONLY_AND_VISIBILITY + [
"build_root",
"build_deps",
"build_sources",
"build_script_inputs",
"build_script_outputs",
"epoch",
"unit_test_target",
"configs",
"executable_configs",
"library_configs",
"proc_macro_configs",
"rustenv",
"dev_deps",
])
testonly = _testonly
if (defined(invoker.visibility)) {
visibility = invoker.visibility
}
if (defined(crate_type) && crate_type == "cdylib") {
# See comments above about cdylib.
crate_type = "rlib"
}
crate_name = _crate_name
if (defined(_output_name)) {
output_name = _output_name
}
# Don't import the `chromium` crate into third-party code.
no_chromium_prelude = true
rustc_metadata = _rustc_metadata
# TODO(crbug.com/40259764): don't default to true. This requires changes to
# third_party.toml and gnrt when generating third-party build targets.
allow_unsafe = true
configs = []
if (defined(_configs)) {
configs += _configs
}
if (_crate_type == "rlib") {
# Forward configs for unit tests.
if (defined(invoker.executable_configs)) {
executable_configs = invoker.executable_configs
}
}
if (!defined(rustflags)) {
rustflags = []
}
rustenv = _rustenv
if (!defined(build_native_rust_unit_tests)) {
build_native_rust_unit_tests = _crate_type != "proc-macro"
}
if (build_native_rust_unit_tests) {
# Unit tests in a proc-macro crate type don't make sense, you can't
# compile executables against the `proc_macro` crate.
assert(_crate_type != "proc-macro")
}
# The unit tests for each target, if generated, should be unique as well.
# a) It needs to be unique even if multiple build targets have the same
# `crate_name`, but different target names.
# b) It needs to be unique even if multiple build targets have the same
# `crate_name` and target name, but different epochs.
_unit_test_unique_target_name = ""
if (_crate_name != _orig_target_name) {
_unit_test_unique_target_name = "${_orig_target_name}_"
}
_unit_test_unique_epoch = ""
if (defined(invoker.epoch)) {
_epoch_str = string_replace(invoker.epoch, ".", "_")
_unit_test_unique_epoch = "v${_epoch_str}_"
}
if (defined(output_dir) && output_dir != "") {
unit_test_output_dir = output_dir
}
unit_test_target = "${_unit_test_unique_target_name}${_crate_name}_${_unit_test_unique_epoch}unittests"
if ((!defined(output_dir) || output_dir == "") && _crate_type == "rlib") {
# Cargo crate rlibs can be compiled differently for tests, and must not
# collide with the production outputs. This does *not* override the
# unit_test_output_dir, which is set above, as that target is not an rlib.
output_dir = "$target_out_dir/$_orig_target_name"
}
if (defined(invoker.dev_deps)) {
test_deps = invoker.dev_deps
}
if (defined(invoker.build_root)) {
# Uh-oh, we have a build script
if (!defined(deps)) {
deps = []
}
if (!defined(sources)) {
sources = []
}
if (!defined(inputs)) {
inputs = []
}
# This... is a bit weird. We generate a file called cargo_flags.rs which
# does not actually contain Rust code, but instead some flags to add
# to the rustc command line. We need it to end in a .rs extension so that
# we can include it in the 'sources' line and thus have dependency
# calculation done correctly. data_deps won't work because targets don't
# require them to be present until runtime.
flags_file = "$_build_script_env_out_dir/cargo_flags.rs"
rustflags += [ "@" + rebase_path(flags_file, root_build_dir) ]
sources += [ flags_file ]
if (defined(invoker.build_script_outputs)) {
# Build scripts may output arbitrary files. They are usually included in
# the main Rust target using include! or include_str! and therefore the
# filename may be .rs or may be arbitrary. We want to educate ninja
# about the dependency either way.
foreach(extra_source,
filter_include(invoker.build_script_outputs, [ "*.rs" ])) {
sources += [ "$_build_script_env_out_dir/$extra_source" ]
}
foreach(extra_source,
filter_exclude(invoker.build_script_outputs, [ "*.rs" ])) {
inputs += [ "$_build_script_env_out_dir/$extra_source" ]
}
}
deps += [ ":${_build_script_name}_output" ]
}
}
if (defined(invoker.build_root)) {
# Extra targets required to make build script work
action("${_build_script_name}_output") {
script = rebase_path("//build/rust/run_build_script.py")
build_script_target = ":${_build_script_name}($rust_macro_toolchain)"
deps = [ build_script_target ]
testonly = _testonly
if (defined(invoker.visibility)) {
visibility = invoker.visibility
}
# The build script may be built with a different toolchain when
# cross-compiling (the host toolchain) so we must find the path relative
# to that.
_build_script_root_out_dir =
get_label_info(build_script_target, "root_out_dir")
_build_script_exe = "$_build_script_root_out_dir/$_build_script_name"
# The executable is always built with the `rust_macro_toolchain` which
# targets the `host_os`. The rule here is on the `target_toolchain` which
# can be different (e.g. compiling on Linux, targeting Windows).
if (host_os == "win") {
_build_script_exe = "${_build_script_exe}.exe"
}
_flags_file = "$_build_script_env_out_dir/cargo_flags.rs"
inputs = [ _build_script_exe ]
outputs = [ _flags_file ]
args = [
"--build-script",
rebase_path(_build_script_exe, root_build_dir),
"--output",
rebase_path(_flags_file, root_build_dir),
"--rust-prefix",
rebase_path("${rust_sysroot}/bin", root_build_dir),
"--out-dir",
rebase_path(_build_script_env_out_dir, root_build_dir),
"--src-dir",
rebase_path(get_path_info(invoker.build_root, "dir"), root_build_dir),
]
if (defined(rust_abi_target) && rust_abi_target != "") {
args += [
"--target",
rust_abi_target,
]
}
if (defined(invoker.features)) {
args += [ "--features" ]
args += invoker.features
}
if (defined(invoker.build_script_outputs)) {
args += [ "--generated-files" ]
args += invoker.build_script_outputs
foreach(generated_file, invoker.build_script_outputs) {
outputs += [ "$_build_script_env_out_dir/$generated_file" ]
}
}
if (_rustenv != []) {
args += [ "--env" ]
args += _rustenv
}
if (defined(invoker.build_script_inputs)) {
inputs += invoker.build_script_inputs
}
}
if (toolchain_for_rust_host_build_tools) {
# The build script is only available to be built on the host, and we use
# the rust_macro_toolchain for it to unblock building them while the
# Chromium stdlib is still being compiled.
rust_executable(_build_script_name) {
crate_name = _build_script_name
sources = invoker.build_sources
crate_root = invoker.build_root
testonly = _testonly
if (defined(invoker.visibility)) {
visibility = invoker.visibility
}
if (defined(invoker.build_deps)) {
deps = invoker.build_deps
}
if (defined(invoker.build_script_inputs)) {
inputs = invoker.build_script_inputs
}
# Don't import the `chromium` crate into third-party code.
no_chromium_prelude = true
# The ${_build_script_name}_output target looks for the exe in this
# location. Due to how the Windows component build works, this has to
# be $root_out_dir for all EXEs. In component build, C++ links to the
# CRT as a DLL, and if Rust does not match, we can't link mixed target
# Rust EXE/DLLs, as the headers in C++ said something different than
# what Rust links. Since the CRT DLL is placed in the $root_out_dir,
# an EXE can find it if it's also placed in that dir.
output_dir = root_out_dir
rustenv = _rustenv
forward_variables_from(invoker,
[
"features",
"edition",
"rustflags",
])
configs -= [
"//build/config/compiler:chromium_code",
# Avoid generating profiling data for build scripts.
#
# TODO(crbug.com/40261306): determine for sure whether to remove this
# config. I'm not sure of the overlap between PGO instrumentation and
# code coverage instrumentation, but we definitely don't want build
# script coverage for PGO, while we might for test coverage metrics.
#
# If we do include build script output in test metrics, it could be
# misleading: exercising some code from a build script doesn't give us
# the same signal as an actual test.
"//build/config/coverage:default_coverage",
]
configs += [ "//build/config/compiler:no_chromium_code" ]
}
} else {
not_needed(invoker,
[
"build_sources",
"build_deps",
"build_root",
"build_script_inputs",
"build_script_outputs",
])
}
} else {
not_needed([
"_name_specific_output_dir",
"_orig_target_name",
])
}
}
set_defaults("cargo_crate") {
library_configs = default_compiler_configs
executable_configs = default_executable_configs
proc_macro_configs = default_rust_proc_macro_configs
}