blob: 50e2d657d378793b19dd5b2c4003d2f0e1bd2340 [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.
import("//zircon/public/gn/config/standard.gni")
import("//zircon/public/gn/toolchain/variants.gni")
# Redirect dependents to dependencies in another environment (toolchain).
#
# An environment_redirect() acts like a group() with `public_deps` set (which
# we call a "redirect" target): Depending on this target means to depend on
# its dependencies. The special feature of environment_redirect() is that it
# selects an appropriate toolchain for those dependencies based on the other
# parameters described below.
#
# A complexity arises when redirecting to a different environment, cpu, or os.
# The information necessary to determine what variant will be selected is only
# available inside a toolchain context that is *some* variant of the same
# (environment, cpu, os) tuple. Because of this, environment_redirect() has
# to first redirect to *some* extant variant of the right environment, cpu,
# and os and only from there can it then redirect to the correct variant. To
# do this, it must redirect to the selfsame environment_redirect() in that
# other toolchain so that the instantiation in that toolchain can run the code
# to redirect to the correct variant. An unfortunate side effect of this is
# that the entire BUILD.gn file containing the environment_redirect() target
# will then be instantiated in that other environment. This can become
# problematic because not every BUILD.gn file expects to be instantiated in
# every different environment. In fact, there are sometimes very good reasons
# for a BUILD.gn file to have an assert() that it's not instantiated in the
# "wrong" toolchain.
#
# There are two opposing cases with regard to this subtlety:
#
# 1. $deps leads to library() or other "non-terminal" targets.
# Hence it's crucial to actually select the "right" variant.
# There is no way to avoid the "self-reference in other toolchain".
# We call this "indirect mode" because it bounces to itself in another
# toolchain before going to $deps.
#
# 2. $deps leads only to "terminal" targets like executable() or host_tool().
# (Intermediate group() targets leading to those are not a problem.)
# Since each of those targets does its own variant selection, it doesn't
# really matter which variant toolchain environment_redirect() redirects to.
# If we know this is the case, then there is no need for the "self-reference
# in other toolchain". This lets us avoid instantiating the invoker's
# build file in the target environment, which might be a problem.
# We call this "direct mode" because it goes directly to $deps in
# another toolchain.
#
# There is no way environment_redirect() can figure out which of these cases
# the $deps fall into. So the invoker must indicate. The $direct parameter
# requests "direct mode", so that the invoker need not worry about their
# BUILD.gn file being instantiated in the target toolchain. The default is
# "indirect mode", which is more "safe" in the sense that it will never go to
# $deps in the wrong variant.
#
# Parameters
#
# cpu
# Optional: Required if $current_cpu is "" (default toolchain).
# Defaults to $host_cpu in "host" or "host.*" environments.
#
# os
# Optional: Required if $current_os is "" (default toolchain).
# Defaults to $host_os in "host" or "host.*" environments.
#
# environment_label
# Optional: Required if ${toolchain.environment_label} is undefined
# (default toolchain). This is the label of an execution environment
# defined with environment(). The $deps will be resolved in one of the
# toolchains defined by that environment() invocation.
# Default: ${toolchain.environment_label}
#
# deps
# Required: These must be labels without toolchain suffix. The
# environment_redirect() target redirects its dependents to instead depend
# on this list of labels, but in the toolchain selected by the other
# parameters.
# Type: list(label_no_toolchain)
#
# direct
# Optional: $deps reaches only "terminal" targets that do their own variant
# selection. See discussion above.
# Type: bool
# Default: false
#
# shlib
# Optional: Go directly to the environment's ${toolchain.shlib} toolchain.
# Type: bool
# Default: false
#
# exclude_variant_tags
# Optional: Never select a variant that has one of these tags.
# This has no effect if $variant is set.
# Type: list(string)
#
# variant
# Optional: Specific variant toolchain to select. If omitted, one will be
# chosen from $default_variants with the expectation that the
# environment() named by $environment_label used a `.variants` list
# including $default_variants (as is preset).
# Type: string
#
template("environment_redirect") {
forward_variables_from(invoker,
[
"cpu",
"environment_label",
"os",
"variant",
])
if (!defined(environment_label)) {
assert(defined(toolchain.environment_label),
"environment_redirect() needs `environment_label`" +
" in $current_toolchain")
environment_label = toolchain.environment_label
}
environment_label = get_label_info(environment_label, "label_no_toolchain")
environment = get_label_info(environment_label, "name")
# For, e.g., "host.fuzz", the base is "host".
# This is not always needed depending on the cpu, os, and variant parameters.
base_environment = get_path_info(environment, "name")
not_needed([ "base_environment" ])
if (defined(cpu)) {
assert(cpu != "", "empty `cpu` in environment_redirect()")
} else {
if (base_environment == "host") {
assert(host_cpu != "",
"`cpu` required with empty `host_cpu` in environment_redirect()")
cpu = host_cpu
} else {
assert(
current_cpu != "",
"`cpu` required with empty `current_cpu` in environment_redirect()")
cpu = current_cpu
}
}
if (!defined(os)) {
if (base_environment == "host") {
assert(host_os != "", "empty `host_os` in environment_redirect()")
os = host_os
} else {
os = "fuchsia"
}
}
assert(os != "", "empty `os` in environment_redirect()")
redirect_deps = invoker.deps
foreach(label, redirect_deps) {
# The original string should not contain a "(toolchain)" suffix.
# We can't easily tell if it does, but we can tell if it has any
# suffix other than the expansion of "($current_toolchain)".
full_label = get_label_info(label, "label_with_toolchain")
bare_label = get_label_info(label, "label_no_toolchain")
assert(full_label == "$bare_label($current_toolchain)",
"environment_redirect() `deps` cannot have toolchain suffix")
}
if (!defined(variant) && (!defined(toolchain.environment_label) ||
toolchain.environment_label != environment_label ||
current_cpu != cpu || current_os != os)) {
# If the caller didn't request a specific variant, then use some variant
# known to exist. Then redirect to this target in that toolchain, which
# will have enough information to use _variant_target() below to do the
# final redirect. We could try here to find the default most likely to be
# selected for an arbitrary target in the selected environment in hopes of
# reducing the likelihood that a second redirect will actually be needed
# (and thus reducing the number of extra toolchains in which any targets
# might need to be instantiated that will never be built). Most
# environments select from `variants + default_variants`, so we could
# guess based on that list. However, we can't be sure that the target
# environment actually uses $variants at all, so we could select a variant
# from a catch-all selector in $variants that doesn't exist at all in the
# target environment (let alone it being the actual correct default for
# that environment, which we can't know). But any such guessing would
# never be perfect--if we could determine that correctly with the
# information available to us, then we'd never need a second redirect in
# the first place. So rather than using $variants, we just take the first
# plausible selector from $default_variants. In the common environments
# and when $variants is not set, this will be the right default. In case
# the target environment didn't even include $default_variants in its
# selector list at all, environment() has special-case code to add a dummy
# toolchain named for the "plausible" variant this logic will select.
# **NOTE:** See the comments there that mention redirects.
foreach(default, default_variants) {
if (!defined(variant)) {
if (default == "$default") {
foreach(shorthand, variant_shorthands) {
if (default != "" &&
(default == shorthand.name ||
get_path_info(default, "dir") == shorthand.name)) {
default = ""
}
}
if (default != "") {
if (get_path_info(default, "file") == default) {
# Ignore "variant/output_name" shorthand selectors.
default = {
variant = default
}
} else {
default = ""
}
}
}
if (!defined(variant) && default != "" &&
(!defined(default.environment) || default.environment == [] ||
default.environment + [ environment ] - [ environment ] !=
default.environment ||
default.environment + [ base_environment ] -
[ base_environment ] != default.environment)) {
variant = default.variant
if (!defined(invoker.direct) || !invoker.direct) {
redirect_deps = []
redirect_deps = [ ":$target_name" ]
}
if (defined(invoker.exclude_variant_tags)) {
not_needed(invoker, [ "exclude_variant_tags" ])
}
}
}
}
} else if (defined(invoker.direct)) {
not_needed(invoker, [ "direct" ])
}
if (defined(variant)) {
assert(variant != "")
if (redirect_deps != []) {
# Name construction logic must match environment.gni.
toolchain_base_name = "${environment_label}-${cpu}"
# For all host environments include the OS to distinguish one from
# another. For other environments, it's implicit when it's "fuchsia"
# and when it exactly matches the base environment name (e.g. "efi").
if (base_environment == "host" ||
(os != "fuchsia" && os != base_environment)) {
toolchain_base_name += "-${os}"
}
toolchain_name = "${toolchain_base_name}-${variant}"
if (defined(invoker.shlib) && invoker.shlib) {
toolchain_name += ".shlib"
}
}
group(target_name) {
forward_variables_from(invoker,
[
"assert_no_deps",
"metadata",
"visibility",
"testonly",
])
if (defined(visibility)) {
visibility += [ ":$target_name" ]
}
public_deps = []
foreach(label, redirect_deps) {
label = get_label_info(label, "label_no_toolchain")
public_deps += [ "$label($toolchain_name)" ]
}
}
} else {
# This does variant selection as if it were an executable called "",
# so it should get to the configured default variant.
_variant_target(target_name) {
forward_variables_from(invoker,
[
"assert_no_deps",
"metadata",
"visibility",
"testonly",
])
if (defined(visibility)) {
visibility += [ ":$target_name" ]
}
target = {
type = "group"
match = "executable"
output_name = ""
variant_suffix_target = false
forward_variables_from(invoker,
[
"exclude_variant_tags",
"shlib",
])
}
no_implicit_deps = true
public_deps = invoker.deps
}
}
}