blob: 36e14926ba97a94c8794e9137b1de88acbd176dc [file] [log] [blame]
# Copyright 2018 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/config/clang/clang.gni")
import("//build/fidl/toolchain.gni")
import("//build/rust/config.gni") # for rust_config
import("//build/rust/toolchain.gni")
import("//build/sdk/sdk_atom.gni")
import("//build/testing/test_spec.gni")
# Copies a binary or shared object file, linking to it in the .build-id
# directory if it is an ELF file. Strips fuchsia binaries and shared objects.
#
# Parameters
#
# sources
# Required: A single source file to copy. This is only a list for
# consistency with other target types. This file should NOT have been
# copied prior to this target, or we may waste space.
#
# outputs
# Required: A single destination to copy the file to. This is only a list
# for consistency with other target types.
#
# override_os:
# Optional: Override the OS that the file is assumed to be built for. By
# default, we use the current_os.
template("copy_with_build_id") {
forward_variables_from(invoker,
[
"visibility",
"override_os",
])
srcs = invoker.sources
outs = invoker.outputs
assert(srcs == [ srcs[0] ], "Must specify exactly one source")
assert(outs == [ outs[0] ], "Must specify exactly one output")
os = current_os
if (defined(override_os)) {
os = override_os
}
use_strip = os == "fuchsia"
group_deps = []
objcopy_target_name = "${target_name}_objcopy"
group_deps += [ ":${objcopy_target_name}" ]
action(objcopy_target_name) {
forward_variables_from(invoker,
"*",
[
"visibility",
"sources",
"outputs",
])
stamp_file = outs[0] + ".build-id.stamp"
script = "//build/rust/stamp.sh"
args = [ rebase_path(stamp_file) ]
sources = srcs
outputs = [
stamp_file,
]
# Link all ELF files into .build-id.
# TODO(tmandry): Enable for linux when ELF note bug is fixed.
if (os == "fuchsia") {
args += [
rebase_path("${clang_prefix}/llvm-objcopy", "", root_build_dir),
"--build-id-link-dir=.build-id",
]
if (use_strip) {
args += [
# TODO(jakehehrlich): Make this use --strip-sections.
"--strip-all",
"--build-id-link-input=.debug",
"--build-id-link-output=",
rebase_path(sources[0]),
rebase_path(outs[0]),
]
outputs += [ outs[0] ]
} else {
# This invocation replaces the input file with (what should be) an
# identical copy of itself. This is the reason that the file should not
# have been copied (i.e. hard linked) before, or we will have duplicate
# data in the build directory.
#
# Ideally, we would have a tool that only does .build-id linking without
# making a copy, but this works okay for now.
args += [
"--build-id-link-output=.debug",
rebase_path(sources[0]),
]
}
}
}
# If we did not produce a stripped copy above, we should copy the source to
# the destination.
if (!use_strip) {
copy_target_name = "${target_name}_copy"
group_deps += [ ":${copy_target_name}" ]
copy(copy_target_name) {
forward_variables_from(invoker, "*", [ "visibility" ])
# sources, outputs forwarded
}
}
group(target_name) {
forward_variables_from(invoker, [ "testonly" ])
public_deps = group_deps
}
}
template("rustc_third_party_artifact") {
forward_variables_from(invoker, [ "visibility" ])
# Dummy build target to match the one in rustc_artifact
build_target_name = "${target_name}_build"
action(build_target_name) {
script = "//build/rust/write_3p_crate_dep_info.py"
forward_variables_from(invoker,
[
"crate_name",
"testonly",
])
pool = "//build/rust:pool($default_toolchain)"
out_info_path = "${target_out_dir}/${target_name}_info.json"
args = [
# This is a slight lie to the end user-- the crate name that
# users provide and that appears in Cargo.toml is the package name,
# which may be different than the crate name (the thing given to
# --extern). One example of this is the rust-crypto crate,
# whose real crate name is crypto, but which is published under
# the package name rust-crypto.
"--package-name",
crate_name,
"--output",
rebase_path(out_info_path),
]
outputs = [
out_info_path,
]
}
group(target_name) {
forward_variables_from(invoker, [ "testonly" ])
public_deps = [
":${build_target_name}",
]
}
}
# Defines a Rust artifact to be built directly with rustc (rather than using cargo)
#
# Only for internal use, supporting rustc_library and rustc_binary.
#
# The arguments are the same as for rustc_library and rustc_binary, with the exception
# of `type`, which must be one of bin/lib/staticlib/proc-macro. This is used to determine
# the kind of artifact that is produced by rustc.
#
template("rustc_artifact") {
forward_variables_from(invoker, [ "visibility" ])
assert(defined(invoker.type),
"Must specify an artifact type (bin/lib/staticlib/proc-macro)")
type = invoker.type
# bin: executable binary application
# lib: intermediate artifact to be used in other Rust programs
# staticlib: a statically-linked system library, generally used for linking Rust into C
# proc-macro: a procedural macro (such as a custom-derive)
assert(
type == "bin" || type == "lib" || type == "staticlib" ||
type == "proc-macro",
"Artifact type must be one of: \"bin\", \"lib\", \"staticlib\", or \"proc-macro\"")
if (type == "lib") {
# For now, lib == rlib, but this could change in future versions of rustc.
# If/when this changes, we will likely want to transition manually rather
# than being automatically changed as a result of a toolchain upgrade.
type = "rlib"
}
if (defined(invoker.name)) {
package_name = invoker.name
} else {
package_name = target_name
}
crate_name = string_replace(package_name, "-", "_")
if (defined(invoker.version)) {
version = invoker.version
} else {
version = "0.1.0"
}
assert(defined(invoker.edition), "Must specify an edition. Preferably 2018")
edition = invoker.edition
group_deps = []
if (type == "bin") {
if (defined(invoker.with_lto)) {
with_lto = invoker.with_lto
} else if (rust_lto != "unset") {
with_lto = rust_lto
} else if (is_debug) {
with_lto = "none"
} else {
# Release builds default to "thin" lto
with_lto = "thin"
}
} else {
with_lto = "none"
}
assert(with_lto == "none" || with_lto == "thin" || with_lto == "fat",
"with_lto was neither none, thin, or fat")
# Determine the prefix and extension for the output file based on the crate type
if (type == "bin") {
prefix = ""
extension = ""
root_file = "src/main.rs"
} else if (type == "rlib") {
prefix = "lib"
extension = ".rlib"
root_file = "src/lib.rs"
} else if (type == "staticlib") {
prefix = "staticlib"
extension = ".a"
root_file = "src/lib.rs"
} else if (type == "proc-macro") {
prefix = "lib"
root_file = "src/lib.rs"
}
third_party_build = "//third_party/rust_crates:build-third-party"
third_party_deps_data =
"${root_out_dir}/rust_third_party_crates/deps_data.json"
first_party_crate_root = "${root_out_dir}/rust_crates"
if (defined(invoker.output_file_override)) {
output_filename = invoker.output_file_override
} else {
output_filename = "${prefix}${crate_name}${extension}"
}
output_file = "${first_party_crate_root}/$output_filename"
output_depfile = "${first_party_crate_root}/${prefix}${crate_name}.d"
build_target_name = "${target_name}_build"
group_deps += [ ":${build_target_name}" ]
with_unit_tests = defined(invoker.with_unit_tests) && invoker.with_unit_tests
if (with_unit_tests) {
test_filename = "${crate_name}_${invoker.type}_test"
test_output_file = "${root_out_dir}/exe.unstripped/${test_filename}"
test_depfile = "${test_output_file}.d"
build_test_target_name = "${target_name}_test_build"
test_group_deps = [ ":${build_test_target_name}" ]
}
# Test specs are used for linux and mac tests to record metadata for testing
# instruction; this happens within package.gni for fuchsia tests.
test_spec_target_name = "${target_name}_spec"
if (with_unit_tests && (is_linux || is_mac)) {
test_spec(test_spec_target_name) {
name = invoker.target_name
location = test_output_file
deps = []
if (defined(invoker.deps)) {
deps += invoker.deps
}
if (defined(invoker.non_rust_deps)) {
deps += invoker.non_rust_deps
}
if (defined(invoker.test_environments)) {
environments = invoker.test_environments
}
}
} else {
not_needed([ "test_spec_target_name" ])
if (defined(invoker.test_environments)) {
assert(with_unit_tests,
"test_environments may only be set when with_unit_tests is true")
not_needed(invoker, [ "test_environments" ])
}
}
# Iterate through the deps collecting a list of the outputs
# of their build targets, which will be passed to rustc as
# `--extern` crates.
dep_info_paths = []
if (defined(invoker.deps)) {
foreach(dep, invoker.deps) {
dep_target_name = get_label_info(dep, "name")
dep_dir = get_label_info(dep, "dir")
dep_build_target = "${dep_dir}:${dep_target_name}_build"
dep_out_dir = get_label_info(dep_build_target, "target_out_dir")
dep_info_path = "${dep_out_dir}/${dep_target_name}_build_info.json"
dep_info_paths += [
"--dep-data",
rebase_path(dep_info_path),
]
}
}
if (defined(invoker.source_root)) {
root_file = invoker.source_root
}
root_file = rebase_path(root_file)
cargo_toml_dir = "$target_gen_dir/$target_name"
# Collect the lib_dirs we should be getting from //zircon/public/lib deps.
zircon_lib_dirs_file = "$target_gen_dir/${build_target_name}_zircon_libs"
extra_args = [
"--lib-dir-file",
rebase_path(zircon_lib_dirs_file),
]
extra_deps = [ ":${build_target_name}_zircon_libs" ]
extra_inputs = [ zircon_lib_dirs_file ]
generated_file("${build_target_name}_zircon_libs") {
forward_variables_from(invoker, [ "testonly" ])
# Clear possibly inherited visibility before setting our own.
visibility = []
visibility = [ ":$build_target_name" ]
if (with_unit_tests) {
visibility += [ ":$build_test_target_name" ]
}
deps = [
third_party_build,
]
if (defined(invoker.deps)) {
deps += invoker.deps
}
if (defined(invoker.non_rust_deps)) {
deps += invoker.non_rust_deps
}
outputs = [
zircon_lib_dirs_file,
]
data_keys = [ "zircon_lib_dirs" ]
output_conversion = "list lines"
}
# Declare the action targets that perform the build itself
rustc_target_script = "//build/rust/build_rustc_target.py"
rustc_target_pool = "//build/rust:pool($default_toolchain)"
rustc_target_deps = []
if (defined(invoker.deps)) {
rustc_target_deps += invoker.deps
}
rustc_target_deps += [ third_party_build ]
rustc_target_inputs = [ root_file ]
rustc_target_public_deps = []
if (defined(invoker.non_rust_deps)) {
rustc_target_public_deps += invoker.non_rust_deps
}
# The set of unstable features permitted as per
# https://fuchsia.googlesource.com/fuchsia/+/refs/heads/master/docs/development/languages/rust/unstable.md
unstable_rust_features = [
"async_await",
"await_macro",
"futures_api",
]
# Specifically enable specialization for use in recovery_netstack.
# This flag is not allowed to be introduced in new code.
if (defined(
invoker.__unstable_recovery_netstack_only_specialization_bypass)) {
unstable_rust_features +=
invoker.__unstable_recovery_netstack_only_specialization_bypass
}
# Make builds independent of absolute file path. The file names
# embedded in debugging information will be expressed as relative to
# the build directory, e.g. "../.." for an "out/subdir" under //.
absolute_path = rebase_path("//.")
relative_path = rebase_path("//.", root_build_dir)
rustc_target_args = [
"--crate-root",
rebase_path(root_file),
"--crate-type",
type,
"--crate-name",
crate_name,
"--package-name",
package_name,
"--root-out-dir",
rebase_path(root_out_dir),
"--first-party-crate-root",
rebase_path(first_party_crate_root),
"--third-party-deps-data",
rebase_path(third_party_deps_data),
"--edition",
edition,
"--warnings",
rust_warnings,
"--remap-path-prefix",
"$absolute_path=$relative_path",
]
if (defined(invoker.features)) {
foreach(feature, invoker.features) {
rustc_target_args += [
"--feature",
feature,
]
}
}
foreach(unstable_rust_feature, unstable_rust_features) {
rustc_target_args += [
"--unstable-rust-feature",
unstable_rust_feature,
]
}
rustc_target_args += rust_build_args + extra_args
rustc_target_deps += rust_build_deps + extra_deps
rustc_target_inputs += rust_build_inputs + extra_inputs
if (with_lto != "none") {
rustc_target_args += [
"--lto",
with_lto,
]
}
if (defined(invoker.non_rust_deps)) {
foreach(dep, invoker.non_rust_deps) {
rustc_target_args += [
"--lib-dir",
rebase_path(get_label_info(dep, "target_out_dir")),
]
}
}
rustc_target_args += dep_info_paths
action(build_target_name) {
forward_variables_from(invoker, [ "testonly" ])
script = rustc_target_script
pool = rustc_target_pool
out_info_path = "${target_out_dir}/${target_name}_info.json"
args = rustc_target_args
args += [
"--cargo-toml-dir",
rebase_path(cargo_toml_dir),
"--version",
version,
"--out-info",
rebase_path(out_info_path),
"--output-file",
rebase_path(output_file),
"--depfile",
rebase_path(output_depfile),
]
inputs = rustc_target_inputs
outputs = [
output_file,
out_info_path,
]
depfile = output_depfile
deps = rustc_target_deps
public_deps = rustc_target_public_deps
}
if (with_unit_tests) {
action(build_test_target_name) {
forward_variables_from(invoker, [ "testonly" ])
script = rustc_target_script
pool = rustc_target_pool
args = rustc_target_args
args += [
"--test",
"--output-file",
rebase_path(test_output_file),
"--depfile",
rebase_path(test_depfile),
]
inputs = rustc_target_inputs
outputs = [
test_output_file,
]
# TODO(tmandry): Add `testonly = true` here once tests only depend on the test group.
depfile = test_depfile
deps = rustc_target_deps
if (is_linux || is_mac) {
deps += [ ":$test_spec_target_name" ]
}
public_deps = rustc_target_public_deps
}
}
if (type == "staticlib") {
output_path = "${root_out_dir}/${output_filename}"
# Copy staticlibs out of rust_crates/* into the root dir
# TODO(cramertj) remove and replace with writing directly to the root
# dir once all existing build rules have moved off of using rust_crates/*
copy_target_name = "${target_name}_copy"
group_deps += [ ":${copy_target_name}" ]
copy(copy_target_name) {
forward_variables_from(invoker, [ "testonly" ])
deps = [
":${build_target_name}",
]
sources = [
output_file,
]
outputs = [
output_path,
]
}
}
if (type == "bin") {
output_path_unstripped = "${root_out_dir}/exe.unstripped/${output_filename}"
output_path = "${root_out_dir}/${output_filename}"
# Copy binaries out of rust_crates/* into the root dir
# TODO(cramertj) remove and replace with writing directly to the root
# dir once all existing build rules have moved off of using rust_crates/*
copy_target_name = "${target_name}_copy" # depended upon below
group_deps += [ ":${copy_target_name}" ]
copy(copy_target_name) {
forward_variables_from(invoker, [ "testonly" ])
deps = [
":${build_target_name}",
]
sources = [
output_file,
]
outputs = [
output_path_unstripped,
]
}
strip_target_name = "${target_name}_strip"
group_deps += [ ":${strip_target_name}" ]
copy_with_build_id(strip_target_name) {
forward_variables_from(invoker, [ "testonly" ])
deps = [
":${copy_target_name}",
]
sources = [
output_path_unstripped,
]
outputs = [
output_path,
]
}
# if appropriate, create an SDK atom for the binary that we just stripped
if (defined(invoker.sdk_category) && invoker.sdk_category != "excluded" &&
!is_fuchsia && !(defined(invoker.test) && invoker.test)) {
output_name = target_name
file_base = "tools/$output_name"
sdk_atom("${target_name}_sdk") {
id = "sdk://tools/${output_name}"
category = invoker.sdk_category
meta = {
dest = "${file_base}-meta.json"
schema = "host_tool"
value = {
type = "host_tool"
name = output_name
root = "tools"
files = [ file_base ]
}
}
files = [
{
source = output_path
dest = file_base
},
]
if (defined(invoker.sdk_deps)) {
deps = invoker.sdk_deps
}
non_sdk_deps = [ ":$strip_target_name" ]
}
}
}
if (with_unit_tests) {
strip_test_target_name = "${target_name}_test_strip"
test_group_deps += [ ":${strip_test_target_name}" ]
test_output_path = "${root_out_dir}/${test_filename}"
copy_with_build_id(strip_test_target_name) {
forward_variables_from(invoker, [ "testonly" ])
deps = [
":${build_test_target_name}",
]
sources = [
test_output_file,
]
outputs = [
test_output_path,
]
}
}
# proc macros are stripped and copied inside `rustc_macro.gni`.
cargo_toml_target_name = "${target_name}_cargo"
group_deps += [ ":${cargo_toml_target_name}" ]
action(cargo_toml_target_name) {
script = "//build/rust/write_cargo_toml.py"
forward_variables_from(invoker,
[
"deps",
"testonly",
])
if (!defined(deps)) {
deps = []
}
deps += [ third_party_build ]
if (defined(invoker.non_rust_deps)) {
public_deps = invoker.non_rust_deps
}
args = [
"--out-dir",
rebase_path(cargo_toml_dir),
"--source-root",
root_file,
"--package-name",
package_name,
"--crate-name",
crate_name,
"--crate-type",
type,
"--version",
version,
"--edition",
edition,
"--third-party-deps-data",
rebase_path(third_party_deps_data),
]
if (defined(invoker.features)) {
foreach(feature, invoker.features) {
args += [
"--feature",
feature,
]
}
}
if (with_lto != "none") {
args += [
"--lto",
with_lto,
]
}
# list of paths to info about crate dependencies
args += dep_info_paths
outputs = [
"${cargo_toml_dir}/Cargo.toml",
]
}
if (with_unit_tests) {
test_target_name = "${target_name}_test"
group(test_target_name) {
forward_variables_from(invoker, [ "testonly" ])
public_deps = test_group_deps
}
# TODO(tmandry): Remove this once we have everyone depending on the right group.
group_deps += [ ":${test_target_name}" ]
}
group(target_name) {
forward_variables_from(invoker, [ "testonly" ])
public_deps = group_deps
}
}