blob: b1f43efc1c9e77dcbeb0a6e03a951d370f1a578f [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/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")
# BEGIN constants {{{
# file locations
first_party_crate_root = "${root_out_dir}/rust_crates"
third_party_build = "//third_party/rust_crates:build-third-party"
third_party_search_path_config = "//third_party/rust_crates:search_path_config"
third_party_deps_data = "${root_out_dir}/rust_third_party_crates/deps_data.json"
third_party_crate_root = "${root_out_dir}/rust_third_party_crates"
if (host_os == "mac") {
proc_macro_ext = ".dylib"
} else {
proc_macro_ext = ".so"
}
# }}} END constants
# Create a target that resembles a rustc_artifact invocation and
# can be depended on in the `deps` of other rustc_artifact invocations,
# but is in fact backed by a Cargo-built third-party crate.
#
# Parameters
#
# crate_name
# crate_type
# package_name
# visibility
# testonly
template("rustc_third_party_artifact") {
forward_variables_from(invoker,
[
"visibility",
"crate_name",
"crate_type",
"package_name",
"testonly",
])
if (crate_type == "lib") {
ext = ".rlib"
} else if (crate_type == "proc-macro") {
ext = proc_macro_ext
} else {
assert(false, "unsupported third-party crate type: " + crate_type)
}
# Build target to match the one in rustc_artifact.
# Rather than being a rust_library target, however, this is actually a
# `config` which adds `--extern` flags to `rustflags` in order to
# make the given third-party dependency available to the target that
# is depending on this rule.
build_target_name = "${target_name}_build"
config(build_target_name) {
externs = [
{
crate_name = crate_name
path = "$third_party_crate_root/lib${crate_name}-${package_name}${ext}"
},
]
}
# Dummy target to write target info
info_target_name = "${target_name}_info"
generated_file(info_target_name) {
outputs = [ "${target_out_dir}/${info_target_name}.json" ]
contents = {
crate_name = crate_name
crate_type = crate_type
package_name = package_name
third_party = true
}
output_conversion = "json"
}
group(target_name) {
forward_variables_from(invoker, [ "testonly" ])
public_deps = [
":${info_target_name}",
third_party_build,
]
public_configs = [ ":${build_target_name}" ]
# Everything that depends on a third-party library, even transitively,
# needs to include the third-party -Ldependency search paths.
all_dependent_configs = [ third_party_search_path_config ]
}
}
# Defines a Rust artifact to be built directly with rustc (rather than using cargo)
#
# Only for internal use, supporting rustc_{binary,library,macro,staticlib,test}.
#
# 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.
#
# Parameters
#
# name
# type
# version
# visibility
# edition
# force_opt
# with_lto
# output_filename_suffix
# only_unit_tests
# with_unit_tests
# target_name
# deps
# non_rust_deps (deprecated)
# test_environments
# source_root
# __unstable_recovery_netstack_only_specialization_bypass
# features
# testonly
# test_deps
# sdk_category
# sdk_deps
# test
template("rustc_artifact") {
forward_variables_from(invoker, [ "visibility" ])
# BEGIN invoker.type -> type {{{
assert(defined(invoker.type),
"Must specify an artifact type (bin/lib/staticlib/cdylib/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 == "cdylib" || type == "proc-macro",
"Artifact type must be one of: \"bin\", \"lib\", \"cdylib\", \"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"
}
# }}} END invoker.type -> type
# BEGIN invoker.name -> package_name, crate_name {{{
if (defined(invoker.name)) {
package_name = invoker.name
} else {
package_name = target_name
}
crate_name = string_replace(package_name, "-", "_")
# }}} END invoker.name -> package_name, crate_name
# BEGIN invoker.version -> version {{{
if (defined(invoker.version)) {
version = invoker.version
} else {
version = "0.1.0"
}
# }}} END invoker.version -> version
# BEGIN invoker.edition -> edition {{{
assert(defined(invoker.edition), "Must specify an edition. Preferably 2018")
# }}} END invoker.edition -> edition
# BEGIN calculate rust_opt_level {{{
if (rust_override_opt != "") {
rust_opt_level = rust_override_opt
if (defined(invoker.force_opt)) {
not_needed(invoker, [ "force_opt" ])
}
} else if (defined(invoker.force_opt)) {
rust_opt_level = invoker.force_opt
} else {
if (is_debug) {
rust_opt_level = "0"
} else {
rust_opt_level = "z"
}
}
rust_opt_level_arg = "-Copt-level=$rust_opt_level"
# }}} END calculate rust_opt_level
# BEGIN calculate with_lto, rust_lto_args {{{
if (type == "bin" || type == "staticlib") {
if (rust_override_lto != "") {
with_lto = rust_override_lto
if (defined(invoker.with_lto)) {
not_needed(invoker, [ "with_lto" ])
}
} else if (defined(invoker.with_lto)) {
with_lto = invoker.with_lto
} else if (rust_lto != "") {
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")
rust_lto_args = []
if (with_lto != "none") {
rust_lto_args += [ "-Clto=$with_lto" ]
}
# }}} END calculate with_lto, rust_lto_args
# BEGIN calculate rust_panic_args {{{
rust_panic_args = []
if (rust_panic == "abort") {
rust_panic_args = [
"-Cpanic=abort",
"-Zpanic_abort_tests",
]
}
# END calculate rust_panic_args {{{
# BEGIN calculate output_file, root_file {{{
# Determine the prefix and extension for the output file based on the crate type
if (type == "bin") {
prefix = ""
extension = ""
root_file_default = "src/main.rs"
} else if (type == "rlib") {
prefix = "lib"
extension = ".rlib"
root_file_default = "src/lib.rs"
} else if (type == "cdylib") {
prefix = "lib"
extension = ".so"
root_file_default = "src/lib.rs"
} else if (type == "staticlib") {
prefix = "lib"
extension = ".a"
root_file_default = "src/lib.rs"
} else if (type == "proc-macro") {
prefix = "lib"
extension = proc_macro_ext
root_file_default = "src/lib.rs"
}
only_unit_tests = defined(invoker.only_unit_tests) && invoker.only_unit_tests
if (defined(invoker.output_filename_suffix)) {
output_filename = "${crate_name}${invoker.output_filename_suffix}"
} else {
output_filename = crate_name
}
if (type == "bin") {
output_file_dir = root_out_dir
} else if (type == "staticlib") {
output_file_dir = root_out_dir
} else if (type == "cdylib") {
output_file_dir = root_out_dir
} else {
output_file_dir = first_party_crate_root
}
output_file = "${output_file_dir}/${prefix}${output_filename}${extension}"
if (defined(invoker.source_root)) {
not_needed([ "root_file_default" ])
root_file = rebase_path(invoker.source_root)
} else {
root_file = rebase_path(root_file_default)
}
# }}} END calculate output_file, root_file
group_deps = []
if (only_unit_tests) {
not_needed([
"output_filename",
"output_file",
])
} else {
build_target_name = "${target_name}_build"
group_deps += [ ":${build_target_name}" ]
}
with_unit_tests = only_unit_tests || (defined(invoker.with_unit_tests) &&
invoker.with_unit_tests)
# BEGIN calculate test_output_file, build_test_target_name {{{
if (with_unit_tests) {
if (only_unit_tests) {
test_filename = crate_name
} else {
test_filename = "${crate_name}_${invoker.type}_test"
}
test_output_file = "${root_out_dir}/${test_filename}"
not_needed([ "test_output_file" ])
build_test_target_name = "${target_name}_test_build"
test_group_deps = [ ":${build_test_target_name}" ]
}
# }}} END calculate test_output_file, build_test_target_name
# BEGIN test_spec {{{
# 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) {
forward_variables_from(invoker, [ "testonly" ])
name = invoker.target_name
target = invoker.target_name
path = 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" ])
}
}
# }}} END test_spec
# BEGIN calculate unstable_rust_features {{{
# 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 = []
# Specifically enable specialization for use in netstack3.
# This flag is not allowed to be introduced in new code.
if (defined(invoker.__unstable_netstack3_only_specialization_bypass)) {
unstable_rust_features +=
invoker.__unstable_netstack3_only_specialization_bypass
}
rust_unstable_feature_arg = "-Zallow-features="
foreach(unstable_rust_feature, unstable_rust_features) {
rust_unstable_feature_arg += unstable_rust_feature + ","
}
# }}} END calculate unstable_rust_features
# BEGIN calculate cfg_feature_args {{{
cfg_feature_args = []
if (defined(invoker.features)) {
foreach(feature, invoker.features) {
cfg_feature_args += [
"--cfg",
"feature=\"" + feature + "\"",
]
}
}
# }}} END calculate cfg_feature_args
cargo_toml_dir = "$target_gen_dir/$target_name"
# BEGIN write info.json {{{
info_target_name = "${target_name}_info"
generated_file(info_target_name) {
# Write out static information about the target to be used by
# reverse-dependencies to determine the content of their Cargo.toml
# files and --extern invocations.
outputs = [ "${target_out_dir}/${target_name}.json" ]
contents = {
cargo_toml_dir = rebase_path(cargo_toml_dir)
crate_name = crate_name
lib_path = rebase_path(output_file)
package_name = package_name
third_party = false
version = version
}
output_conversion = "json"
}
group_deps += [ ":${info_target_name}" ]
# }}} END write info.json
build_target_deps = []
if (defined(invoker.deps)) {
build_target_deps += invoker.deps
}
if (defined(invoker.non_rust_deps)) {
build_target_deps += invoker.non_rust_deps
}
# from build/rust/config.gni
build_target_deps += std_deps
build_target_deps += sysroot_deps
shared_rflags = [
rust_opt_level_arg,
rust_unstable_feature_arg,
"--edition=${invoker.edition}",
# from build/rust/config.gni
"--target",
target,
"-Cdebuginfo=" + symbol_level,
"--cap-lints",
rust_cap_lints,
] + rust_lto_args + rust_panic_args + cfg_feature_args
if (is_mac) {
# For mac_sdk_min:
import("//build/config/mac/mac_sdk.gni")
shared_rflags += [ "-Clink-arg=-mmacosx-version-min=" + mac_sdk_min ]
}
if (is_fuchsia) {
shared_rflags += [
"-L",
sysroot + "/lib",
"-Clinker=" + clang_prefix_rebased + "/lld",
"-Clink-arg=--pack-dyn-relocs=relr",
"-Clink-arg=--sysroot=" + sysroot,
"-Clink-arg=-L" + sysroot + "/lib",
"-Clink-arg=-L" + clang_resource_dir + "/" + target + "/lib",
"-Clink-arg=--threads",
"-Clink-arg=-dynamic-linker=ld.so.1",
"-Clink-arg=--icf=all",
]
if (current_cpu == "arm64") {
shared_rflags += [ "-Clink-arg=--fix-cortex-a53-843419" ]
}
} else {
shared_rflags += [ "-Clinker=" + clang_prefix_rebased + "/clang" ]
if (current_cpu == "arm64") {
shared_rflags += [ "-Clink-arg=-Wl,--fix-cortex-a53-843419" ]
}
if (current_os == "linux") {
shared_rflags += [ "-Clink-arg=-Wl,--build-id" ]
}
if (current_os != "mac") {
shared_rflags += [
"-Clink-arg=-Wl,--threads",
"-Clink-arg=-Wl,--icf=all",
]
}
}
if (defined(invoker.allow_deprecated) && invoker.allow_deprecated) {
shared_rflags += [
"-A",
"deprecated",
]
}
# BEGIN build target {{{
if (!only_unit_tests) {
rflags = shared_rflags
if (current_toolchain == fidl_toolchain) {
# If rustc is unset in the current toolchain, we need to make
# a fake output so that our target still compiles
not_needed([
"root_file",
"rflags",
])
generated_file(build_target_name) {
forward_variables_from(invoker, [ "testonly" ])
outputs = [ output_file ]
deps = build_target_deps
contents = "dummy rust file for toolchains without rustc"
}
} else if (type == "bin") {
executable(build_target_name) {
forward_variables_from(invoker, [ "testonly" ])
crate_root = root_file
crate_name = crate_name
deps = build_target_deps
output_name = output_filename
rustflags = rflags
}
} else if (type == "rlib") {
rust_library(build_target_name) {
forward_variables_from(invoker, [ "testonly" ])
crate_root = root_file
crate_name = crate_name
deps = build_target_deps
output_name = output_filename
rustflags = rflags
}
} else if (type == "proc-macro") {
rust_proc_macro(build_target_name) {
forward_variables_from(invoker, [ "testonly" ])
crate_root = root_file
crate_name = crate_name
deps = build_target_deps
output_name = output_filename
rustflags = rflags
}
} else if (type == "staticlib") {
static_library(build_target_name) {
forward_variables_from(invoker, [ "testonly" ])
crate_root = root_file
crate_name = crate_name
deps = build_target_deps
output_name = output_filename
rustflags = rflags
complete_static_lib = true
}
} else if (type == "cdylib") {
shared_library(build_target_name) {
forward_variables_from(invoker, [ "testonly" ])
crate_root = root_file
crate_name = crate_name
crate_type = "cdylib"
deps = build_target_deps
output_name = output_filename
public_deps = non_rust_deps
rflags += [
# needs DT_SONAME inserted into elf file which
# rust doesn't do automatically
"-Clink-arg=--soname=lib${crate_name}.so",
]
rustflags = rflags
}
} else {
assert(
false,
"unsupported crate type, should've been caught above-- report this issue to the toolchain team")
}
}
# }}} END buildtarget
# BEGIN test target {{{
if (with_unit_tests) {
if (current_toolchain == fidl_toolchain) {
# If rustc is unset in the current toolchain, we need to make
# a fake output so that our target still compiles
not_needed([
"rust_opt_level_arg",
"rust_unstable_feature_arg",
"cfg_feature_args",
"rust_lto_args",
])
generated_file(build_test_target_name) {
outputs = [ test_output_file ]
deps = build_target_deps
contents = "dummy rust file for toolchains without rustc"
testonly = true
}
} else {
test_target_deps = []
if (defined(invoker.test_deps)) {
test_target_deps += invoker.test_deps
}
executable(build_test_target_name) {
crate_root = root_file
crate_name = crate_name
testonly = true
output_name = test_filename
rustflags = shared_rflags + [
"--test",
"--crate-type",
type,
]
deps = build_target_deps + test_target_deps
if (is_linux || is_mac) {
deps += [ ":$test_spec_target_name" ]
}
if (defined(invoker.test_deps)) {
deps += invoker.test_deps
}
}
}
}
# }}} END test target
# BEGIN strip binary and create sdk atom {{{
if (type == "bin" && !only_unit_tests) {
group_deps += [ ":${build_target_name}" ]
# 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"
# TODO(fxb/42999): remove extra atom
if (current_cpu == host_cpu) {
sdk_atom("${target_name}_sdk_legacy") {
id = "sdk://$file_base"
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 = "$root_out_dir/$output_name"
dest = file_base
},
]
if (defined(invoker.sdk_deps)) {
deps = invoker.sdk_deps
}
non_sdk_deps = [ ":$build_target_name" ]
}
}
if (host_os == "linux" || host_os == "mac") {
file_base = "tools/$current_cpu/$output_name"
}
sdk_atom("${target_name}_sdk") {
id = "sdk://$file_base"
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 = "$root_out_dir/$output_name"
dest = file_base
},
]
if (defined(invoker.sdk_deps)) {
deps = invoker.sdk_deps
}
non_sdk_deps = [ ":$build_target_name" ]
}
}
}
# }}} END strip binary and create sdk atom
# BEGIN generate Cargo.toml {{{
cargo_toml_target_name = "${target_name}_cargo"
group_deps += [ ":${cargo_toml_target_name}" ]
# Iterate through the deps collecting a list of the outputs
# of their build targets to use for calculating the dep data needed
# to produce Cargo.toml files.
dep_info_paths = []
dep_info_sources = []
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}_info.json"
dep_info_paths += [
"--dep-data",
rebase_path(dep_info_path),
]
dep_info_sources += [ rebase_path(dep_info_path) ]
}
}
test_dep_info_paths = []
test_dep_info_sources = []
if (defined(invoker.test_deps)) {
foreach(dep, invoker.test_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}_info.json"
test_dep_info_paths += [
"--test-only-dep-data",
rebase_path(dep_info_path),
]
dep_info_sources += [ rebase_path(dep_info_path) ]
}
}
action(cargo_toml_target_name) {
script = "//build/rust/write_cargo_toml.py"
forward_variables_from(invoker, [ "testonly" ])
deps = [ third_party_build ]
if (defined(invoker.deps)) {
foreach(dep, invoker.deps) {
dep_info_name = get_label_info(dep, "label_no_toolchain") + "_info"
dep_info_name += "(" + get_label_info(dep, "toolchain") + ")"
deps += [ dep_info_name ]
}
}
if (defined(invoker.test_deps)) {
foreach(test_dep, invoker.test_deps) {
dep_info_name = get_label_info(test_dep, "label_no_toolchain") + "_info"
dep_info_name += "(" + get_label_info(test_dep, "toolchain") + ")"
deps += [ dep_info_name ]
}
}
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",
invoker.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
args += test_dep_info_paths
sources = dep_info_sources
sources += test_dep_info_sources
outputs = [ "${cargo_toml_dir}/Cargo.toml" ]
}
# }}} END generate Cargo.toml
# BEGIN declare test_target_name group {{{
if (with_unit_tests) {
test_target_name = "${target_name}_test"
group(test_target_name) {
testonly = true
public_deps = test_group_deps
deps = [ "//build/test:rust_test_metadata" ]
metadata = {
test_barrier = [ "//build/test:rust_test_metadata" ]
}
}
if (only_unit_tests) {
group_deps += [ ":${test_target_name}" ]
}
}
# }}} END test_target_name group
group(target_name) {
forward_variables_from(invoker, [ "testonly" ])
public_deps = group_deps
}
}