blob: 1fc45ccb00af1c9fd946b4b43ad422378eac63d9 [file] [log] [blame]
# Copyright 2019 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.
assert(!defined(current_toolchain), "only for buildconfig context")
declare_args() {
# *This must never be set as a build argument*.
#
# "$zx/" is the prefix for GN "source-absolute" paths in the Zircon
# build. When Zircon is built standalone, the Zircon repository is the
# root of the build (where `.gn` is found) so "$zx/" becomes "//". When
# Zircon is part of a larger unified build, there is a higher-level `.gn`
# file that uses `default_args` to set "$zx/" to "//zircon/".
zx = "/"
}
declare_args() {
# *This must never be set as a build argument.*
# It exists only to be set via define_toolchain().
# See define_environment() for more information.
toolchain = {
configs = []
environment = "stub"
label = "$zx/public/gn/toolchain:stub"
globals = {
}
}
}
# The default toolchain is a very boring one. It can only be used for
# action and copy rules where current_cpu et al don't matter. Doing
# anything else happens in another toolchain.
set_default_toolchain(toolchain.label)
# The rest of this file pertains to what happens in other toolchains
# defined by define_environment().
# The toolchain can specify pervasive globals.
forward_variables_from(toolchain.globals, "*")
# This is the name for $current_cpu that's used in Zircon file names.
if (current_cpu == "x64") {
zircon_cpu = "x86"
} else if (current_cpu != "") {
zircon_cpu = current_cpu
}
# Shorthand for `current_os == "fuchsia"`.
is_fuchsia = current_os == "fuchsia"
# Shorthand for `current_os == "linux"`.
is_linux = current_os == "linux"
# Shorthand for `current_os == "mac"`.
is_mac = current_os == "mac"
if (!defined(is_gcc)) {
# True iff $current_toolchain builds with GCC rather than Clang.
is_gcc = false
}
if (!defined(is_host)) {
# True iff $current_toolchain builds for a host platform rather than for
# the Fuchsia system being built. This doesn't mean that it builds for
# $host_cpu and $host_os--it could be a cross-compilation toolchain for
# building host tools for a different host.
is_host = false
}
if (!defined(is_kernel)) {
# True iff $current_toolchain builds kernel-like code.
is_kernel = false
}
###
### config() revamp
###
# Assert in a template that its invoker doesn't use legacy configs.
#
# Always invoked: `assert_no_legacy_configs(target_name) { type = "..." }`
#
# Parameters
#
# invoker
# Required: Implicitly set in every template() scope.
#
# type
# Required: The name of the calling template().
#
template("assert_no_legacy_configs") {
label = get_label_info(":$target_name", "label_no_toolchain")
target_invoker = invoker.invoker
type = invoker.type
assert(!defined(target_invoker.all_dependent_configs),
"all_dependent_configs is not supported in this build")
assert(!defined(target_invoker.public_configs),
"use public_deps instead of public_configs in $label $type()")
not_needed([ "target_invoker" ])
}
# This is used exclusively by the config() template, below.
template("_raw_config") {
config(target_name) {
forward_variables_from(invoker, "*")
}
}
# Propagate switch settings and dependencies to dependents.
#
# This template replaces GN's built-in config() with enhanced and
# simplified semantics. A config() is a target that acts like a group()
# target in all respects. But it can also set all the switches and have
# `inputs` and `libs` like legacy config().
# By convention, a config() is usually referred to in `configs`, but in
# fact all the compiling target types are templates that just merge
# `configs` into `deps` and putting a config() label into `deps` works just
# the same. The main reason for the convention is just that `configs` is
# usually predefined with a default list via set_defaults() so `configs -=
# ...` can remove default elements, while `deps` is not.
#
# A config() itself never sets `configs` or `public_configs` like in legacy
# config(). Instead use `public_deps` for another config() that should be
# propagated to users of this config().
#
# Parameters
#
# compiler_flags
# Optional: This is appended to all of $asmflags, $cflags, and $ldflags.
# It's a convenient shorthand for flags that the compiler driver treats
# the same way for assembling, compiling, and linking tasks.
#
# data_deps
# deps
# Optional: As for group(). This is *not* the way to refer to another
# config() so as to propagate its switch settings; instead use
# `public_deps` for that. Note that potentially each individual file
# compiled with the switches of this config() will also have a
# Ninja dependency arc to each target in `data_deps` or `deps`. Hence
# this should be used sparingly.
#
# public_deps
# Optional: As for group(). This is the way to refer to other
# config() targets. Every dependent of this target will also use
# the switches of the config() targets in `public_deps`.
#
template("config") {
_config_name = target_name
_config_label = get_label_info(":$target_name", "label_no_toolchain")
# The old ways are now taboo.
assert(!defined(invoker.configs),
"use public_deps instead of configs in $_config_label config()")
assert(!defined(invoker.public_configs),
"use public_deps instead of public_configs in $_config_label config()")
# The underlying config() holds everything but the deps.
_raw_config("_config.$_config_name") {
visibility = [ ":$_config_name" ]
asmflags = []
cflags = []
ldflags = []
forward_variables_from(invoker,
"*",
[
"compiler_flags",
"data_deps",
"deps",
"metadata",
"public_deps",
"testonly",
"visibility",
])
if (defined(invoker.compiler_flags)) {
asmflags += invoker.compiler_flags
cflags += invoker.compiler_flags
ldflags += invoker.compiler_flags
}
}
group(_config_name) {
forward_variables_from(invoker,
[
"data_deps",
"deps",
"metadata",
"public_deps",
"testonly",
"visibility",
])
public_configs = [ ":_config.$_config_name" ]
metadata = {
forward_variables_from(invoker,
[
"ldflags",
"lib_dirs",
"libs",
])
if (defined(invoker.compiler_flags)) {
if (!defined(ldflags)) {
ldflags = []
}
ldflags += invoker.compiler_flags
}
}
}
}
###
### "Non-terminal" (library and source_set) target types.
### These are compiling targets that never do variant selection.
###
# Just wrap these to inject assert_no_legacy_configs()
# and translate configs to deps.
foreach(target_type,
[
"source_set",
"static_library",
]) {
template(target_type) {
assert_no_legacy_configs(target_name) {
type = target_type
}
target(target_type, target_name) {
forward_variables_from(invoker,
"*",
[
"configs",
"deps",
"visibility",
])
forward_variables_from(invoker, [ "visibility" ])
deps = invoker.configs
if (defined(invoker.deps)) {
deps += invoker.deps
}
}
}
}
# This is for doing the actual shared_library() or loadable_module()
# target in a toolchain that supports them directly. That is, either
# ${toolchain.label} == ${toolchain.shlib} or this is a toolchain that
# was defined with `solink = true` so there is no ${toolchain.shlib}.
template("_shlib_toolchain_target") {
assert_no_legacy_configs(target_name) {
type = invoker.target_type
}
assert(!is_kernel,
"${invoker.target_type}() targets don't work in kernel toolchains")
target(invoker.target_type, target_name) {
# visibility and data_deps get special treatment in ${toolchain.shlib}.
if (defined(toolchain.shlib)) {
if (defined(invoker.visibility)) {
# Make sure we're visible to the redirector groups defined below.
visibility = invoker.visibility + [ ":$target_name" ]
}
if (defined(invoker.data_deps)) {
# Redirect data_deps to the non-shlib toolchain.
data_deps = []
foreach(label, invoker.data_deps) {
if (get_label_info(label, "toolchain") == current_toolchain) {
label += "(${toolchain.label})"
}
data_deps += [ label ]
}
}
} else {
forward_variables_from(invoker,
[
"data_deps",
"visibility",
])
}
# Everything else is passed through (everything in solink toolchains).
# But also apply configs->deps.
forward_variables_from(invoker,
"*",
[
"configs",
"deps",
"data_deps",
"target_type",
"visibility",
])
deps = invoker.configs
if (defined(invoker.deps)) {
deps += invoker.deps
}
}
}
template("shared_library") {
if (!defined(toolchain.shlib) || current_toolchain == toolchain.shlib) {
# This is the toolchain that actually builds the libraries.
_shlib_toolchain_target(target_name) {
target_type = "shared_library"
forward_variables_from(invoker,
"*",
[
"metadata",
"target_type",
"visibility",
])
forward_variables_from(invoker, [ "visibility" ])
# Elaborate the toolchain's defaults to compute the output file name.
if (!defined(output_name)) {
output_name = target_name
}
output_name += toolchain.output_name_suffix
if (!defined(output_extension)) {
output_extension = "so"
}
output_file = "$target_out_dir/lib$output_name"
if (output_extension != "") {
output_file += ".$output_extension"
}
metadata = {
if (defined(invoker.metadata)) {
forward_variables_from(invoker.metadata, "*")
}
# Every terminal target provides these metadata keys. The first is
# used the data key for the output of the link, as a file name
# relative to $root_build_dir appropriate for command-line
# contexts. The second is used as a walk key to provide a
# dependency barrier against e.g. shared_library() deps or other
# executable() data_deps.
link_output = [ rebase_path(output_file + toolchain.link_output_suffix,
root_build_dir) ]
link_barrier = []
if (current_os != "mac" && current_os != "win") {
elf_link_output = link_output
}
# Each shared_library() that's not on the whitelist gets a poison
# pill to flag it if it appears in the dependency graph from a
# driver() target.
if (!defined(driver_blacklist)) {
driver_blacklist = [ true ]
}
}
}
} else {
# In the main toolchain, just redirect to the shlib toolchain.
group(target_name) {
forward_variables_from(invoker,
[
"testonly",
"visibility",
])
not_needed(invoker, "*")
public_deps = [
":$target_name(${toolchain.shlib})",
]
}
}
}
template("library") {
_library_name = target_name
host = defined(invoker.host) && invoker.host
kernel = defined(invoker.kernel) && invoker.kernel
shared = defined(invoker.shared) && invoker.shared
if (defined(invoker.static)) {
static = invoker.static
} else {
static = !kernel
}
if (defined(invoker.sdk)) {
sdk = invoker.sdk
sdk_headers = invoker.sdk_headers
} else {
sdk = false
sdk_headers = []
}
_library_params = [
"kernel",
"host",
"sdk",
"sdk_headers",
"shared",
"static",
]
assert(host || kernel || static || shared,
"library(\"$target_name\") must build somewhere!")
# Not all of these will be referenced in all toolchains.
not_needed(_library_params)
# A specialized toolchain might not support shared libraries.
shared = shared && defined(toolchain.shlib)
# Empty libraries are useless, so do a source set instead.
# TODO(crbug.com/gn/16): Empty library works OK and the source_set
# case tickles a GN bug. Remove `&& false` when the bug is fixed.
if (invoker.sources == [] && false) {
static_library = "source_set"
} else {
static_library = "static_library"
}
not_needed([ "static_library" ])
targets = false
if (is_kernel) {
if (kernel) {
targets = true
source_set(_library_name) {
forward_variables_from(invoker, "*", _library_params)
if (!defined(public_deps)) {
public_deps = []
}
public_deps += [ ":${_library_name}.headers" ]
}
}
if (sdk != false && !static && !shared && toolchain.tags == []) {
data_deps = [
":$_library_name-$current_cpu.pkg(${toolchain.label})",
]
}
} else if (is_host) {
if (host) {
targets = true
target(static_library, _library_name) {
if (static_library == "static_library") {
complete_static_lib = true
}
forward_variables_from(invoker, "*", _library_params)
if (!defined(public_deps)) {
public_deps = []
}
public_deps += [ ":${_library_name}.headers" ]
}
}
} else if (static || shared) {
targets = true
source_set("${_library_name}._sources") {
visibility = [
":${_library_name}.static",
":${_library_name}.shared",
]
forward_variables_from(invoker,
"*",
_library_params + [
"data_deps",
"install_path",
"public_deps",
"visibility",
])
if (!defined(deps)) {
deps = []
}
deps += [ ":${_library_name}.headers" ]
if (defined(invoker.data_deps)) {
# Redirect data_deps to the non-shlib toolchain.
data_deps = []
foreach(label, invoker.data_deps) {
if (get_label_info(label, "toolchain") == current_toolchain) {
label += "(${toolchain.label})"
}
data_deps += [ label ]
}
}
}
if (static) {
target(static_library, "${_library_name}.static") {
if (static_library == "static_library") {
complete_static_lib = true
}
output_name = _library_name
forward_variables_from(invoker,
[
"configs",
"data_deps",
"public_deps",
"testonly",
"visibility",
])
if (!defined(public_deps)) {
public_deps = []
}
public_deps += [ ":${_library_name}.headers" ]
deps = [
":${_library_name}._sources",
]
}
}
if (shared) {
if (defined(invoker.install_path)) {
install_path = invoker.install_path
} else {
install_path = "lib/${toolchain.libprefix}lib${_library_name}.so"
}
shared_library("${_library_name}.shared") {
output_name = _library_name
forward_variables_from(invoker,
[
"configs",
"ldflags",
"libs",
"lib_dirs",
"testonly",
"visibility",
])
# Everything that depends on the library gets the headers.
# It also gets the explicit `public_deps`, which includes
# any header dependencies or public config()s.
public_deps = [
":${_library_name}.headers",
]
if (defined(invoker.public_deps)) {
public_deps += invoker.public_deps
}
# The library depends on the source_set(). It also depends on the
# `deps` from the source_set() so as to get any config()
# dependencies that affect linking rather than just compilation.
# Other dependencies are redundant since the source_set() already
# has them, but they don't hurt.
deps = [
":${_library_name}._sources",
]
if (defined(invoker.deps)) {
deps += invoker.deps
}
# An explicit `install_path = false` means this DSO is not installed.
if (install_path != false) {
metadata = {
manifest_inputs = [ "$target_out_dir/lib${output_name}.so" ]
manifest_lines = [ "${install_path}=" +
rebase_path(manifest_inputs[0], root_build_dir) ]
}
}
}
}
group(_library_name) {
forward_variables_from(invoker,
[
"testonly",
"visibility",
])
if (shared) {
public_deps = [
":${_library_name}.shared",
]
} else {
public_deps = [
":${_library_name}.static",
]
}
if (sdk != false && toolchain.environment == "user" &&
toolchain.tags == []) {
if (sdk == "shared") {
data_deps = [
":$_library_name-$current_cpu.pkg(${toolchain.shlib})",
]
} else {
data_deps = [
":$_library_name-$current_cpu.pkg(${toolchain.label})",
]
}
}
}
}
if (!targets) {
# In this toolchain there are no actual targets, only the headers.
not_needed(invoker, "*")
group(_library_name) {
forward_variables_from(invoker,
[
"testonly",
"visibility",
])
public_deps = [
":${_library_name}.headers",
]
}
}
config("${_library_name}.headers") {
include_dirs = [ "include" ]
# The public_deps here represent header dependencies.
forward_variables_from(invoker,
[
"public_deps",
"testonly",
"visibility",
])
if (defined(visibility)) {
visibility += [ ":$_library_name" ]
if (!is_kernel) {
visibility += [
":${_library_name}.sources",
":${_library_name}.static",
":${_library_name}.shared",
]
}
}
}
# If this library is the main target for the directory, then give its
# auxiliary targets aliases `dir:headers`, `dir:static`, `dir:shared`.
if (get_label_info(":$_library_name", "name") ==
get_path_info(get_label_info(":$_library_name", "dir"), "file")) {
group("headers") {
forward_variables_from(invoker,
[
"testonly",
"visibility",
])
public_deps = [
":${_library_name}.headers",
]
}
if (!is_kernel && !is_host) {
if (static) {
group("static") {
forward_variables_from(invoker,
[
"testonly",
"visibility",
])
public_deps = [
":${_library_name}.static",
]
}
}
if (shared) {
group("shared") {
forward_variables_from(invoker,
[
"testonly",
"visibility",
])
public_deps = [
":${_library_name}.shared",
]
}
}
}
}
if (sdk != false &&
(toolchain.environment == "user" || (!shared && !static))) {
if (sdk == "static") {
assert(static,
"$target_name must have static=true to have sdk=\"static\"")
} else if (sdk == "shared") {
assert(shared || !defined(toolchain.shlib),
"$target_name must have shared=true to have sdk=\"shared\"")
} else {
assert(sdk == "source",
"$target_name sdk=\"$sdk\" not static, shared, or source")
}
if (toolchain.tags != [] ||
(defined(toolchain.shlib) &&
((sdk == "shared" && current_toolchain != toolchain.shlib) ||
(sdk != "shared" && current_toolchain == toolchain.shlib)))) {
sdk = false
}
if (sdk != false) {
import("$zx/public/gn/pkg.gni")
pkg_export("$target_name-$current_cpu.pkg") {
contents = [
"[package]",
"name=$_library_name",
"type=lib",
]
if (sdk == "source") {
contents += [
"arch=src",
"[src]",
]
foreach(file, invoker.sources) {
contents += [ rebase_path(file, ".") + "=SOURCE/" +
rebase_path(file, "//") ]
}
} else {
contents += [
"arch=$zircon_cpu",
"[lib]",
]
if (sdk == "static") {
if (defined(invoker.output_prefix_override) &&
invoker.output_prefix_override) {
contents += [ "lib/${_library_name}.a=BUILD/" +
rebase_path("$target_out_dir/${_library_name}.a",
root_build_dir) ]
} else {
contents += [ "lib/lib${_library_name}.a=BUILD/" +
rebase_path("$target_out_dir/lib${_library_name}.a",
root_build_dir) ]
}
} else {
soname = "lib${_library_name}.so"
contents += [
"debug/$soname=BUILD/" +
rebase_path("$target_out_dir/$soname.debug", root_build_dir),
"lib/$soname=BUILD/" +
rebase_path("$target_out_dir/$soname.debug", root_build_dir),
]
if (defined(invoker.install_path)) {
install_path = invoker.install_path
} else {
install_path = "lib/${toolchain.libprefix}lib${_library_name}.so"
}
if (install_path != false) {
contents +=
[ "dist/$install_path=BUILD/" +
rebase_path("$target_out_dir/$soname", root_build_dir) ]
}
}
}
contents += [ "[includes]" ]
foreach(file, invoker.sdk_headers) {
contents += [ rebase_path(file, ".") + "=SOURCE/" +
rebase_path(file, "//", "include") ]
}
sdk_deps = []
if (defined(invoker.public_deps)) {
sdk_deps += invoker.public_deps
}
if (sdk == "source" && defined(invoker.deps)) {
sdk_deps += invoker.deps
}
if (sdk_deps != []) {
banjo_deps = []
fidl_deps = []
lib_deps = []
foreach(label, sdk_deps) {
if (get_path_info(get_label_info(label, "dir"), "dir") ==
"$zx/system/banjo") {
banjo_deps += [ get_label_info(label, "name") ]
} else if (get_label_info(label, "name") == "c.headers" ||
(get_label_info(label, "name") == "c" &&
get_path_info(get_label_info(label, "dir"), "dir") ==
"$zx/system/fidl")) {
assert(get_path_info(get_label_info(label, "dir"), "dir") ==
"$zx/system/fidl")
fidl_deps +=
[ get_path_info(get_label_info(label, "dir"), "name") ]
} else if (get_label_info(label, "name") !=
"enable_driver_tracing" &&
get_label_info(label, "name") !=
"generated-public-headers") {
assert(true || get_label_info(label, "name") == "headers" ||
get_path_info(get_label_info(label, "name"),
"extension") == "headers",
"$label is not a library() headers target")
lib_deps +=
[ get_path_info(get_label_info(label, "dir"), "name") ]
}
}
lib_deps += [ "zxcpp" ]
lib_deps -= [ "zxcpp" ]
if (lib_deps != []) {
contents += [ "[deps]" ] + lib_deps
}
if (banjo_deps != []) {
contents += [ "[banjo-deps]" ] + banjo_deps
}
if (fidl_deps != []) {
contents += [ "[fidl-deps]" ] + fidl_deps
}
}
}
}
}
}
###
### "Terminal" (executable and loadable_module) target types.
### These are the targets that do variant selection.
###
template("_variant_target") {
target = {
main_metadata = {
}
variant_metadata = {
}
forward_variables_from(invoker.target, "*")
if (!defined(match)) {
match = type
}
}
assert_no_legacy_configs(target_name) {
type = target.match
}
main_target_name = target_name
# Elaborate the default to simplify deriving names later.
if (defined(invoker.output_name)) {
output_name = invoker.output_name
} else {
output_name = target_name
}
if (toolchain.variant_selectors == []) {
# This toolchain does not participate in variant selection.
# Each target is just what it seems.
target(target.type, main_target_name) {
forward_variables_from(invoker, "*", [ "visibility" ])
forward_variables_from(invoker, [ "visibility" ])
}
} else {
# Most toolchains defined with define_environment() do variant selection.
# The ${toolchain.variant_selectors} list (usually ultimately from the
# $variants build argument and some defaults) controls which sibling
# toolchain among all the offspring of the same define_environment() is
# the usual builder for each target. The first matching selector in the
# list wins.
builder_toolchain = false
foreach(selector, toolchain.variant_selectors) {
# An empty selector always matches, so start with the flag set.
# Each `if` block below applies each inclusion criterion: if it's
# present and it does not include this target, then clear the flag.
match = true
if (selector.cpu != []) {
if (selector.cpu + [ current_cpu ] - [ current_cpu ] == selector.cpu) {
match = false
}
}
if (selector.dir != []) {
dir = get_label_info(":$target_name", "dir")
if (selector.dir + [ dir ] - [ dir ] == selector.dir) {
match = false
}
}
if (selector.environment != []) {
if (selector.environment + [ toolchain.environment ] -
[ toolchain.environment ] == selector.environment &&
selector.environment + [ toolchain.base_environment ] -
[ toolchain.base_environment ] == selector.environment) {
match = false
}
}
if (selector.label != []) {
label = get_label_info(":$target_name", "label_no_toolchain")
if (selector.label + [ label ] - [ label ] == selector.label) {
match = false
}
}
if (selector.name != []) {
name = get_name_info(":$target_name", "name_no_toolchain")
if (selector.name + [ name ] - [ name ] == selector.name) {
match = false
}
}
if (selector.os != []) {
if (selector.os + [ current_os ] - [ current_os ] == selector.os) {
match = false
}
}
if (selector.output_name != []) {
if (selector.output_name + [ output_name ] - [ output_name ] ==
selector.output_name) {
match = false
}
}
if (selector.target_type != []) {
if (selector.target_type + [ target.match ] - [ target.match ] ==
selector.target_type) {
match = false
}
}
if (defined(selector.host) && selector.host != is_host) {
match = false
}
if (defined(selector.kernel) && selector.kernel != is_kernel) {
match = false
}
# If the flag stayed set, this is the winner. Since GN's foreach()
# has nothing like a `break` command to bail out of the loop early,
# the best we can do is ignore later matches when builder_toolchain
# is already set.
if (match && builder_toolchain == false) {
if (defined(target.shlib) && target.shlib &&
defined(selector.shlib_toolchain)) {
builder_toolchain = selector.shlib_toolchain
} else {
builder_toolchain = selector.toolchain
}
}
}
# Validation of the selector list in define_environment()
# should have made sure there was a catch-all selector.
assert(builder_toolchain != false)
# So that's handy for choosing a non-default variant for a target based on
# build-time configuration (i.e. build arguments like $variants). But
# another handy feature is building both the default target and one or
# more separate variant builds of the same target under a different name
# (i.e. the $output_name gets a per-variant suffix) just by putting the
# suffixed target label into the dependency graph. That is, just use:
# `deps += [ "//some/dir:foobin.foovariant" ]` without worrying about the
# exact toolchain name. Only "//some/dir:foobin" is defined directly.
# When that target name is used, the variant chosen via the selector list
# is installed as "foobin". But there's also a "foobin.$variant" target
# defined that will install the given variant of that binary as
# "foobin.$variant" so that multiple variants can exist side by side in
# the same install directory.
variant_target_name = main_target_name + toolchain.variant_suffix
# To get this ease of use, a lot more goes on under the covers. Firstly,
# each toolchain in the environment must define a "$target_name.$variant"
# target for each *other* variant that just redirects to that toolchain.
extra_visibility = []
foreach(other, toolchain.other_variants) {
other_target_name = main_target_name + other.suffix
extra_visibility += [ ":other_target_name" ]
group(other_target_name) {
forward_variables_from(invoker,
[
"testonly",
"visibility",
])
public_deps = [
":$other_target_name(${other.label})",
]
}
}
# Since the variant-suffixed targets exist and so might be in the
# dependency graph, every toolchain needs to define an actual linking
# target to build this binary. In the chosen builder_toolchain, that same
# binary needs be installed under a different name as well. There's no
# reason to link or compile the same thing twice, so we don't want two
# compiling targets. Instead, one can simply refer to the other and
# change the installed name. That is, either a copy() target to
# physically copy the binary to a different $output_name or a group() that
# simply provides metadata that says where to place the binary in a
# filesystem image. In either case the (physical or virtual) copy target
# has the "real" compiling target in `deps`.
#
# The fly in the ointment with that scheme is the metadata. The metadata
# to build up a filesystem image (e.g. `manifest_lines`) means that if a
# target is in the dependency graph then it will wind up in the image.
# So, say we were to define "foobin.foovariant" as the real compiling
# target, with metadata to install it as "foobin.foovariant"; and "foobin"
# as a group() with `deps = [ ":foobin.foovariant" ]` and metadata to
# install that same binary as "foobin". Now, if the intent is to include
# only "foobin.foovariant" as so only "foobin.foovariant" is in the
# dependency graph, it works out fine. However, more often the intent is
# instead to include only "foobin" in the dependency graph and have only
# "foobin" installed. In that case, since "foobin" depends on
# "foobin.foovariant", the metadata collection finds both and the binary
# winds up installed under both names when that was not requested.
#
# One approach is to use a dependency barrier protocol with the
# get_metadata() calls done for filesystem image construction. That is,
# those calls use `manifest_barrier` as a walk key. Then "foobin" has
# `manifest_barrier = []` in its `metadata` so that the collection picks
# up the metadata to install as "foobin" but stops there. If and only if
# the dependency graph in the get_metadata() call separately reaches
# "foobin.foovariant" will the metadata to install as "foobin.foovariant"
# be seen by that walk. The trouble with this scheme is that it also cuts
# off the `deps` and `data_deps` of "foobin.foovariant" from the metadata
# walk so the runtime files the binary requires (shared libraries,
# resources) are omitted from the filesystem image. Perhaps that could be
# mitigated by putting all those `deps` and `data_deps` from the real
# builder target (expanded to "label_with_toolchain" to get what they
# would be in the builder) into the `manifest_barrier` list (and thus they
# would have to be in the group's `data_deps` too).
#
# Instead, we use an approach that is not specific to any particular
# metadata protocol. The real linking target is yet a third target, an
# internal target called "foobin._build" that's defined *without* the
# `metadata` set by the invoker. Both "foobin" and "foobin.foovariant"
# are just group() targets with `deps = [ ":foobin._build" ]`, but each
# has different metadata.
#
# This internal target is only really required in the chosen builder
# toolchain, since in other toolchains the variant-suffixed target is the
# only one that causes something to be installed rather than just being a
# pure redirect to builder_toolchain. But for consistency and (relative)
# simplicity of this code, we always define it.
builder_target_name = "${main_target_name}._build"
# TODO(docs): NOTE!!! NOTE!!! This whole scheme means that visibility
# lists of deps of terminal targets can't really list individual targets,
# only directory wildcards like ":*". `visibility = [ ":foobin" ]` would
# not allow the "foobin.foovariant" target (or the internal
# "foobin._build" target) to have that dependency.
extra_visibility += [ ":$main_target_name" ]
if (current_toolchain == builder_toolchain) {
# In the chosen builder toolchain, the main target redirects
# to the builder target but also adds the metadata.
group(main_target_name) {
forward_variables_from(invoker,
[
"testonly",
"visibility",
])
public_deps = [
":$builder_target_name",
]
metadata = {
if (defined(invoker.metadata)) {
forward_variables_from(invoker.metadata, "*")
assert(!defined(manifest_inputs) && !defined(manifest_lines),
"the `manifest_inputs` and `manifest_lines` metadata keys" +
" are reserved for standard templates; use resource()")
}
forward_variables_from(target.main_metadata, "*")
}
}
} else {
# In all other toolchains, the main target just redirects
# to the builder_toolchain (where the metadata lives).
group(main_target_name) {
forward_variables_from(invoker,
[
"testonly",
"visibility",
])
public_deps = [
":$main_target_name($builder_toolchain)",
]
}
}
# The metadata-defining targets must be defined in the shlib toolchain so
# that their $root_out_dir-based expansions are correct. Both targets in
# the main toolchain just redirect to the shlib toolchain.
if (defined(target.shlib) && target.shlib && defined(toolchain.shlib) &&
current_toolchain != toolchain.shlib) {
group(variant_target_name) {
forward_variables_from(invoker,
[
"testonly",
"visibility",
])
public_deps = [
":$variant_target_name(${toolchain.shlib})",
]
}
not_needed(invoker, "*")
not_needed([
"builder_target_name",
"output_name",
])
} else {
# The variant-suffixed target redirects to the builder target but also
# adds the metadata--different metadata than the main target above.
extra_visibility += [ ":$variant_target_name" ]
group(variant_target_name) {
forward_variables_from(invoker,
[
"testonly",
"visibility",
])
public_deps = [
":$builder_target_name",
]
metadata = {
if (defined(invoker.metadata)) {
forward_variables_from(invoker.metadata, "*")
assert(!defined(manifest_inputs) && !defined(manifest_lines),
"the `manifest_inputs` and `manifest_lines` metadata keys" +
" are reserved for standard templates; use resource()")
}
forward_variables_from(target.variant_metadata, "*")
}
}
# Finally, define the actual builder target--with no metadata.
target(target.type, builder_target_name) {
forward_variables_from(invoker,
"*",
[
"metadata",
"output_name",
"target",
"visibility",
])
output_name = output_name
if (defined(invoker.visibility)) {
# Make sure we're visible to the redirector groups.
visibility = invoker.visibility + extra_visibility
}
}
}
}
}
template("_basic_executable") {
assert(current_toolchain != default_toolchain)
assert_no_legacy_configs(target_name) {
type = invoker.target_type
}
_variant_target(target_name) {
target = {
match = invoker.target_type
type = "executable"
forward_variables_from(invoker,
[
"variant_suffix_metadata",
"variant_suffix_outputs",
])
}
forward_variables_from(invoker,
"*",
[
"configs",
"deps",
"metadata",
"target",
"visibility",
])
forward_variables_from(invoker, [ "visibility" ])
deps = invoker.configs
if (defined(invoker.deps)) {
deps += invoker.deps
}
# Elaborate the toolchain's defaults to compute the output file name.
if (!defined(output_name)) {
output_name = target_name
}
output_name += toolchain.output_name_suffix
if (!defined(output_extension)) {
output_extension = toolchain.executable_extension
}
output_file = "$target_out_dir/$output_name"
if (output_extension != "") {
output_file += ".$output_extension"
}
metadata = {
if (defined(invoker.metadata)) {
forward_variables_from(invoker.metadata, "*")
}
# Every terminal target provides these metadata keys. The first is
# used the data key for the output of the link, as a file name
# relative to $root_build_dir appropriate for command-line
# contexts. The second is used as a walk key to provide a
# dependency barrier against e.g. shared_library() deps or other
# executable() data_deps.
link_output = [ rebase_path(output_file + toolchain.link_output_suffix,
root_build_dir) ]
link_barrier = []
if (current_os != "mac" && current_os != "win") {
elf_link_output = link_output
}
}
if (!is_host && !is_kernel) {
# An explicit `install_path = false` means this binary is not installed.
if (!defined(install_path)) {
install_path = "bin/" + get_path_info(output_file, "file")
}
if (install_path != false) {
target.main_metadata = {
manifest_inputs = [ output_file ]
manifest_lines =
[ "${install_path}=" + rebase_path(output_file, root_build_dir) ]
}
# Also define an alias with the variant suffix. _variant_target will
# make this redirect to the specific variant toolchain chosen for
# this target. In only that toolchain, the metadata will give the
# binary a second install path with the variant suffix.
target.variant_metadata = {
manifest_inputs = [ output_file ]
manifest_lines = [ "${install_path}${toolchain.variant_suffix}=" +
rebase_path(output_file, root_build_dir) ]
}
}
}
}
}
template("loadable_module") {
assert(!is_kernel)
assert(!is_host)
_variant_target(target_name) {
# This is for _variant_target().
target = {
shlib = true
match = "loadable_module"
type = "_shlib_toolchain_target"
}
# This is for _shlib_toolchain_target().
target_type = "loadable_module"
forward_variables_from(invoker,
"*",
[
"metadata",
"target_type",
"visibility",
])
forward_variables_from(invoker, [ "visibility" ])
# Elaborate the toolchain's defaults to compute the output file name.
if (!defined(output_name)) {
output_name = target_name
}
output_name += toolchain.output_name_suffix
if (!defined(output_extension)) {
output_extension = "so"
}
output_file = "$target_out_dir/$output_name"
if (output_extension != "") {
output_file += ".$output_extension"
}
metadata = {
if (defined(invoker.metadata)) {
forward_variables_from(invoker.metadata, "*")
}
# Every terminal target provides these metadata keys. The first is
# used the data key for the output of the link, as a file name
# relative to $root_build_dir appropriate for command-line
# contexts. The second is used as a walk key to provide a
# dependency barrier against e.g. shared_library() deps or other
# executable() data_deps.
link_output = [ rebase_path(output_file + toolchain.link_output_suffix,
root_build_dir) ]
link_barrier = []
if (current_os != "mac" && current_os != "win") {
elf_link_output = link_output
}
}
if (defined(invoker.install_path)) {
target.main_metadata = {
manifest_inputs = [ output_file ]
manifest_lines =
[ "${install_path}=" + rebase_path(output_file, root_build_dir) ]
}
# Also define an alias with the variant suffix. _variant_target will
# make this redirect to the specific variant toolchain chosen for
# this target. In only that toolchain, the metadata will give the
# binary a second install path with the variant suffix.
extension = get_path_info(invoker.install_path, "extension")
if (extension != "") {
extension = ".$extension"
}
target.variant_metadata = {
manifest_inputs = [ output_file ]
manifest_lines = [ get_path_info(invoker.install_path, "dir") + "/" +
get_path_info(invoker.install_path, "name") +
toolchain.variant_suffix + extension + "=" +
rebase_path(output_file, root_build_dir) ]
}
}
}
}
template("driver") {
loadable_module(target_name) {
data_deps = []
deps = []
forward_variables_from(invoker, "*", [ "visibility" ])
forward_variables_from(invoker, [ "visibility" ])
# All drivers implicitly get the driver ABI.
deps += [ "$zx/system/ulib/driver" ]
# Drivers that use C++ library facilities cannot use the libc++
# shared library (it's not in the whitelist). So always use the
# hermetic static library. This has no effect if no C++ library
# symbols are used.
configs += [ "$zx/public/gn/config:static-libc++" ]
# It's an error to link against any shared library that's not on the
# whitelist. Those libraries set `driver_blackist=[]` while others (by
# default, see shared_library() above) set `driver_blacklist=[true]`.
if (false) { # TODO(get_metadata): add assert_no_metadata feature
assert_no_metadata([ "" ],
[ "driver_blacklist" ],
[ "driver_blacklist_barrier" ],
"driver() targets cannot depend on shared libraries")
}
# Elaborate the defaults to compute the output file name.
if (!defined(output_name)) {
output_name = target_name
}
# Set the standard install_path.
if (!defined(install_path)) {
install_path = "driver/${output_name}.so"
}
}
}
template("test_driver") {
driver(target_name) {
testonly = true
forward_variables_from(invoker,
"*",
[
"install_path",
"visibility",
])
forward_variables_from(invoker, [ "visibility" ])
# Elaborate the defaults to compute the output file name.
if (!defined(output_name)) {
output_name = target_name
}
install_path = "driver/test/${output_name}.so"
}
}
template("executable") {
_basic_executable(target_name) {
target_type = "executable"
forward_variables_from(invoker,
"*",
[
"target_type",
"visibility",
])
forward_variables_from(invoker, [ "visibility" ])
}
}
template("test") {
if (is_kernel) {
not_needed(invoker, "*")
not_needed([ "target_name" ])
} else {
_basic_executable(target_name) {
target_type = "test"
testonly = true
forward_variables_from(invoker,
"*",
[
"target_type",
"test_group",
"testonly",
"visibility",
])
forward_variables_from(invoker, [ "visibility" ])
if (!defined(output_name)) {
output_name = "${target_name}-test"
}
output_name += toolchain.output_name_suffix
if (!defined(output_extension)) {
output_extension = toolchain.executable_extension
}
if (!is_host && !defined(install_path)) {
if (defined(invoker.test_group)) {
test_group = invoker.test_group
} else {
test_group = "sys"
}
install_path = "test/$test_group/$output_name"
if (output_extension != "") {
install_path += ".$output_extension"
}
}
if (is_host) {
metadata = { # TODO: host test metadata
}
# TODO: variant_suffix_metadata = { ... }
}
}
}
}
template("host_tool") {
assert(!is_kernel, "host_tool() targets don't work in kernel toolchains")
_basic_executable(target_name) {
target_type = "host_tool"
forward_variables_from(invoker,
"*",
[
"target_type",
"visibility",
])
forward_variables_from(invoker, [ "visibility" ])
if (!defined(output_name)) {
output_name = target_name
}
output_name += toolchain.output_name_suffix
if (!defined(output_extension)) {
output_extension = toolchain.executable_extension
}
if (is_host) {
tool_executable = "$target_out_dir/$output_name"
tool_extension = ""
if (output_extension != "") {
tool_extension = ".$output_extension"
}
metadata = {
# List of tool binaries that could be presented to users' command line.
tool_executables = [ rebase_path(
get_path_info(tool_executable, "dir") + "/" +
get_path_info(tool_executable, "name") + tool_extension,
root_build_dir) ]
# See host_tool_action().
host_tool_barrier = []
host_tool_rspfile = tool_executables + [ "--" ]
if (defined(toolchain.host_run_env)) { # TODO: sanitizer case
host_tool_rspfile += toolchain.host_run_env
}
host_tool_rspfile += tool_executables
}
variant_suffix_outputs = [ tool_executable + tool_extension ]
variant_suffix_metadata = {
tool_executables = variant_suffix_outputs
}
}
if (is_host && current_os == host_os && current_cpu == host_cpu) {
if (!defined(data_deps)) {
data_deps = []
}
data_deps += [ ":$target_name.pkg" ]
}
}
# TODO(mcgrathr): Temporary hacks for integrating with the legacy
# Fuchsia GN build.
if (is_host && current_os == host_os && current_cpu == host_cpu) {
import("$zx/public/gn/pkg.gni")
assert(!defined(invoker.output_name))
tool_name = target_name
pkg_export("$target_name.pkg") {
contents = [
"[package]",
"name=$tool_name",
"type=tool",
"arch=host",
"[bin]",
"$tool_name=BUILD/" +
rebase_path("$target_out_dir/$tool_name", root_build_dir),
]
}
}
}
# These are all the target types (both stock GN and Fuchsia templates) that
# compile `sources` with the $current_toolchain tools. All these get a
# default `configs` below, so targets use `configs +=` and `configs -=`.
_compile_target_types = [
"executable",
"host_tool",
"library",
"loadable_module",
"shared_library",
"source_set",
"static_library",
"test",
]
if (toolchain.environment == "user") {
_compile_target_types += [
"driver",
"test_driver",
]
}
# ${toolchain.configs} gives the initial `configs` set for every compile
# target in this toolchain. Targets use `+=` rather than `=` unless they
# are explicitly doing `= []` to remove all the toolchain defaults; they
# can use `-=` to remove specific members from the default set. In the
# Fuchsia build, `configs` winds up being used as `deps`, so things listed
# in ${toolchain.configs} can actually be any target type, not just config().
foreach(target_type, _compile_target_types) {
set_defaults(target_type) {
configs = []
foreach(config, toolchain.configs) {
# Either it's an absolute label string or it's a scope with filters
# and mutators. See define_environment().
if (config == "$config") {
configs += [ config ]
} else if (!defined(config.types) || config.types + [ target_type ] -
[ target_type ] != config.types) {
configs += config.add
configs -= config.remove
}
}
}
}