| # 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("$zx/public/gn/config/standard.gni") |
| import("$zx/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)) { |
| assert(os != "", "empty `os` in environment_redirect()") |
| } else { |
| if (base_environment == "host") { |
| assert(host_os != "", "empty `host_os` in environment_redirect()") |
| os = host_os |
| } else { |
| os = "fuchsia" |
| } |
| } |
| |
| 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)) { |
| # Name construction logic must match environment.gni. |
| toolchain_base_name = "${environment_label}-${cpu}" |
| |
| # For host environments include the OS to distinguish one from another. |
| # For other environments, the OS is implicit (i.e. "fuchsia" modulo EFI). |
| if (base_environment == "host") { |
| toolchain_base_name += "-${os}" |
| } else { |
| not_needed([ "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 |
| } |
| } |
| } |