// Copyright 2014 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.analysis.config; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; import com.google.common.base.Verify; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ClassToInstanceMap; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.Iterables; import com.google.common.collect.ListMultimap; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.MutableClassToInstanceMap; import com.google.devtools.build.lib.actions.Root; import com.google.devtools.build.lib.analysis.AspectDescriptor; import com.google.devtools.build.lib.analysis.BlazeDirectories; import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider; import com.google.devtools.build.lib.analysis.Dependency; import com.google.devtools.build.lib.analysis.RuleContext; import com.google.devtools.build.lib.analysis.config.BuildConfigurationCollection.Transitions; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.cmdline.LabelSyntaxException; import com.google.devtools.build.lib.events.Event; import com.google.devtools.build.lib.events.EventHandler; import com.google.devtools.build.lib.packages.Attribute; import com.google.devtools.build.lib.packages.Attribute.Configurator; import com.google.devtools.build.lib.packages.Attribute.SplitTransition; import com.google.devtools.build.lib.packages.Attribute.Transition; import com.google.devtools.build.lib.packages.InputFile; import com.google.devtools.build.lib.packages.PackageGroup; import com.google.devtools.build.lib.packages.Rule; import com.google.devtools.build.lib.packages.RuleClass; import com.google.devtools.build.lib.packages.RuleClassProvider; import com.google.devtools.build.lib.packages.Target; import com.google.devtools.build.lib.rules.test.TestActionBuilder; import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable; import com.google.devtools.build.lib.skylarkinterface.SkylarkModule; import com.google.devtools.build.lib.util.CPU; import com.google.devtools.build.lib.util.Fingerprint; import com.google.devtools.build.lib.util.OS; import com.google.devtools.build.lib.util.Preconditions; import com.google.devtools.build.lib.util.RegexFilter; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.vfs.PathFragment; import com.google.devtools.build.skyframe.SkyFunction; import com.google.devtools.build.skyframe.SkyFunction.Environment; import com.google.devtools.common.options.Converter; import com.google.devtools.common.options.Converters; import com.google.devtools.common.options.EnumConverter; import com.google.devtools.common.options.Option; import com.google.devtools.common.options.OptionsBase; import com.google.devtools.common.options.OptionsParsingException; import com.google.devtools.common.options.TriState; import java.io.Serializable; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Queue; import java.util.Set; import java.util.TreeMap; import javax.annotation.Nullable; /** * Instances of BuildConfiguration represent a collection of context * information which may affect a build (for example: the target platform for * compilation, or whether or not debug tables are required). In fact, all * "environmental" information (e.g. from the tool's command-line, as opposed * to the BUILD file) that can affect the output of any build tool should be * explicitly represented in the BuildConfiguration instance. * *

A single build may require building tools to run on a variety of * platforms: when compiling a server application for production, we must build * the build tools (like compilers) to run on the host platform, but cross-compile * the application for the production environment. * *

There is always at least one BuildConfiguration instance in any build: * the one representing the host platform. Additional instances may be created, * in a cross-compilation build, for example. * *

Instances of BuildConfiguration are canonical: *

c1.equals(c2) <=> c1==c2.
*/ @SkylarkModule(name = "configuration", doc = "Data required for the analysis of a target that comes from targets that " + "depend on it and not targets that it depends on.") public final class BuildConfiguration { /** * An interface for language-specific configurations. * *

All implementations must be immutable and communicate this as clearly as possible * (e.g. declare {@link ImmutableList} signatures on their interfaces vs. {@link List}). * This is because fragment instances may be shared across configurations. */ public abstract static class Fragment { /** * Validates the options for this Fragment. Issues warnings for the * use of deprecated options, and warnings or errors for any option settings * that conflict. */ @SuppressWarnings("unused") public void reportInvalidOptions(EventHandler reporter, BuildOptions buildOptions) { } /** * Adds mapping of names to values of "Make" variables defined by this configuration. */ @SuppressWarnings("unused") public void addGlobalMakeVariables(ImmutableMap.Builder globalMakeEnvBuilder) { } /** * Collects all labels that should be implicitly loaded from labels that were specified as * options, keyed by the name to be displayed to the user if something goes wrong. * The resulting set only contains labels that were derived from command-line options; the * intention is that it can be used to sanity-check that the command-line options actually * contain these in their transitive closure. * *

This functionality only exists for legacy configuration fragments that compute labels from * command-line option values. Don't do that! Instead, use a rule that specifies the mapping * explicitly. */ @SuppressWarnings("unused") protected void addImplicitLabels(Multimap implicitLabels) { } /** * Returns a multimap of all labels that should be implicitly loaded from labels that were * specified as options, keyed by the name to be displayed to the user if something goes wrong. * The returned set only contains labels that were derived from command-line options; the * intention is that it can be used to sanity-check that the command-line options actually * contain these in their transitive closure. */ public final ListMultimap getImplicitLabels() { ListMultimap implicitLabels = ArrayListMultimap.create(); addImplicitLabels(implicitLabels); return implicitLabels; } /** * Adds all the roots from this fragment. */ @SuppressWarnings("unused") public void addRoots(List roots) { } /** * Returns a (key, value) mapping to insert into the subcommand environment for coverage. */ public Map getCoverageEnvironment() { return ImmutableMap.of(); } /* * Returns the command-line "Make" variable overrides. */ public ImmutableMap getCommandLineDefines() { return ImmutableMap.of(); } /** * Returns the labels required to run coverage for the fragment. */ public ImmutableList

This detects the host cpu of the Blaze's server but if the compilation happens in a * compilation cluster then the host cpu of the compilation cluster might be different than * the auto-detected one and the --host_cpu option must then be set explicitly. */ public static class HostCpuConverter implements Converter { @Override public String convert(String input) throws OptionsParsingException { if (input.isEmpty()) { // TODO(philwo) - replace these deprecated names with more logical ones (e.g. k8 becomes // linux-x86_64, darwin includes the CPU architecture, ...). switch (OS.getCurrent()) { case DARWIN: return "darwin"; case FREEBSD: return "freebsd"; case WINDOWS: switch (CPU.getCurrent()) { case X86_64: return "x64_windows"; } break; // We only support x64 Windows for now. case LINUX: switch (CPU.getCurrent()) { case X86_32: return "piii"; case X86_64: return "k8"; case PPC: return "ppc"; case ARM: return "arm"; } } return "unknown"; } return input; } @Override public String getTypeDescription() { return "a string"; } } /** * Options that affect the value of a BuildConfiguration instance. * *

(Note: any client that creates a view will also need to declare * BuildView.Options, which affect the mechanism of view construction, * even if they don't affect the value of the BuildConfiguration instances.) * *

IMPORTANT: when adding new options, be sure to consider whether those * values should be propagated to the host configuration or not (see * {@link ConfigurationFactory#getConfiguration}. * *

ALSO IMPORTANT: all option types MUST define a toString method that * gives identical results for semantically identical option values. The * simplest way to ensure that is to return the input string. */ public static class Options extends FragmentOptions implements Cloneable { public String getCpu() { return cpu; } @Option(name = "cpu", defaultValue = "null", category = "semantics", help = "The target CPU.") public String cpu; @Option(name = "min_param_file_size", defaultValue = "32768", category = "undocumented", help = "Minimum command line length before creating a parameter file.") public int minParamFileSize; @Option(name = "experimental_extended_sanity_checks", defaultValue = "false", category = "undocumented", help = "Enables internal validation checks to make sure that configured target " + "implementations only access things they should. Causes a performance hit.") public boolean extendedSanityChecks; @Option(name = "experimental_allow_runtime_deps_on_neverlink", defaultValue = "true", category = "undocumented", help = "Flag to help transition from allowing to disallowing runtime_deps on neverlink" + " Java archives. The depot needs to be cleaned up to roll this out by default.") public boolean allowRuntimeDepsOnNeverLink; @Option(name = "strict_filesets", defaultValue = "false", category = "semantics", help = "If this option is enabled, filesets crossing package boundaries are reported " + "as errors. It does not work when check_fileset_dependencies_recursively is " + "disabled.") public boolean strictFilesets; // Plugins are build using the host config. To avoid cycles we just don't propagate // this option to the host config. If one day we decide to use plugins when building // host tools, we can improve this by (for example) creating a compiler configuration that is // used only for building plugins. @Option(name = "plugin", converter = LabelConverter.class, allowMultiple = true, defaultValue = "", category = "flags", help = "Plugins to use in the build. Currently works with java_plugin.") public List

The computation of the output directory should be a non-injective mapping from * BuildConfiguration instances to strings. The result should identify the aspects of the * configuration that should be reflected in the output file names. Furthermore the * returned string must not contain shell metacharacters. * *

For configuration settings which are NOT part of the output directory name, * rebuilding with a different value of such a setting will build in * the same output directory. This means that any actions whose * keys (see Action.getKey()) have changed will be rerun. That * may result in a lot of recompilation. * *

For configuration settings which ARE part of the output directory name, * rebuilding with a different value of such a setting will rebuild * in a different output directory; this will result in higher disk * usage and more work the first time you rebuild with a different * setting, but will result in less work if you regularly switch * back and forth between different settings. * *

With one important exception, it's sound to choose any subset of the * config's components for this string, it just alters the dimensionality * of the cache. In other words, it's a trade-off on the "injectiveness" * scale: at one extreme (output directory name contains all data in the config, and is * thus injective) you get extremely precise caching (no competition for the * same output-file locations) but you have to rebuild for even the * slightest change in configuration. At the other extreme (the output * (directory name is a constant) you have very high competition for * output-file locations, but if a slight change in configuration doesn't * affect a particular build step, you're guaranteed not to have to * rebuild it. The important exception has to do with multiple configurations: every * configuration in the build must have a different output directory name so that * their artifacts do not conflict. * *

The host configuration is special-cased: in order to guarantee that its output directory * is always separate from that of the target configuration, we simply pin it to "host". We do * this so that the build works even if the two configurations are too close (which is common) * and so that the path of artifacts in the host configuration is a bit more readable. */ private final OutputRoots outputRoots; /** If false, AnalysisEnviroment doesn't register any actions created by the ConfiguredTarget. */ private final boolean actionsEnabled; private final ImmutableSet

This can be used to: *

    *
  1. Find an option's (parsed) value given its command-line name
  2. *
  3. Parse alternative values for the option.
  4. *
* *

This map is "transitive" in that it includes *all* options recognizable by this * configuration, including those defined in child fragments. */ private final Map transitiveOptionsMap; /** * Returns true if this configuration is semantically equal to the other, with * the possible exception that the other has fewer fragments. * *

This is useful for dynamic configurations - as the same configuration gets "trimmed" while * going down a dependency chain, it's still the same configuration but loses some of its * fragments. So we need a more nuanced concept of "equality" than simple reference equality. */ public boolean equalsOrIsSupersetOf(BuildConfiguration other) { return this.equals(other) || (other != null && outputRoots.equals(other.outputRoots) && actionsEnabled == other.actionsEnabled && fragments.values().containsAll(other.fragments.values()) && buildOptions.getOptions().containsAll(other.buildOptions.getOptions())); } /** * Returns map of all the fragments for this configuration. */ public ImmutableMap, Fragment> getAllFragments() { return fragments; } /** * Validates the options for this BuildConfiguration. Issues warnings for the * use of deprecated options, and warnings or errors for any option settings * that conflict. */ public void reportInvalidOptions(EventHandler reporter) { for (Fragment fragment : fragments.values()) { fragment.reportInvalidOptions(reporter, this.buildOptions); } Set plugins = new HashSet<>(); for (Label plugin : options.pluginList) { String name = plugin.getName(); if (plugins.contains(name)) { reporter.handle(Event.error("A build cannot have two plugins with the same name")); } plugins.add(name); } for (Map.Entry opt : options.pluginCoptList) { if (!plugins.contains(opt.getKey())) { reporter.handle(Event.error("A plugin_copt must refer to an existing plugin")); } } if (options.outputDirectoryName != null) { reporter.handle(Event.error( "The internal '--output directory name' option cannot be used on the command line")); } if (options.testShardingStrategy == TestActionBuilder.TestShardingStrategy.EXPERIMENTAL_HEURISTIC) { reporter.handle(Event.warn( "Heuristic sharding is intended as a one-off experimentation tool for determing the " + "benefit from sharding certain tests. Please don't keep this option in your " + ".blazerc or continuous build")); } if (options.useDynamicConfigurations && !options.useDistinctHostConfiguration) { reporter.handle(Event.error( "--nodistinct_host_configuration does not currently work with dynamic configurations")); } } private ImmutableMap setupShellEnvironment() { ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); for (Fragment fragment : fragments.values()) { fragment.setupShellEnvironment(builder); } return builder.build(); } /** * Sorts fragments by class name. This produces a stable order which, e.g., facilitates * consistent output from buildMneumonic. */ private final static Comparator lexicalFragmentSorter = new Comparator>() { @Override public int compare(Class o1, Class o2) { return o1.getName().compareTo(o2.getName()); } }; /** * Constructs a new BuildConfiguration instance. */ public BuildConfiguration(BlazeDirectories directories, Map, Fragment> fragmentsMap, BuildOptions buildOptions, boolean actionsDisabled) { this(null, directories, fragmentsMap, buildOptions, actionsDisabled); } /** * Constructor variation that uses the passed in output roots if non-null, else computes them * from the directories. */ public BuildConfiguration(@Nullable OutputRoots outputRoots, @Nullable BlazeDirectories directories, Map, Fragment> fragmentsMap, BuildOptions buildOptions, boolean actionsDisabled) { Preconditions.checkState(outputRoots == null ^ directories == null); this.actionsEnabled = !actionsDisabled; this.fragments = ImmutableSortedMap.copyOf(fragmentsMap, lexicalFragmentSorter); this.skylarkVisibleFragments = buildIndexOfSkylarkVisibleFragments(); this.buildOptions = buildOptions; this.options = buildOptions.get(Options.class); Map testEnv = new TreeMap<>(); for (Map.Entry entry : this.options.testEnvironment) { if (entry.getValue() != null) { testEnv.put(entry.getKey(), entry.getValue()); } } this.testEnvironment = ImmutableMap.copyOf(testEnv); this.mnemonic = buildMnemonic(); String outputDirName = (options.outputDirectoryName != null) ? options.outputDirectoryName : mnemonic; this.platformName = buildPlatformName(); this.shExecutable = collectExecutables().get("sh"); this.outputRoots = outputRoots != null ? outputRoots : new OutputRoots(directories, outputDirName); ImmutableSet.Builder

The basic role of this interface is to "accept" a desired transition and produce * an actual configuration change from it in an implementation-appropriate way. */ public interface TransitionApplier { /** * Creates a new instance of this transition applier bound to the specified source * configuration. */ TransitionApplier create(BuildConfiguration config); /** * Accepts the given configuration transition. The implementation decides how to turn * this into an actual configuration. This may be called multiple times (representing a * request for a sequence of transitions). */ void applyTransition(Transition transition); /** * Accepts the given split transition. The implementation decides how to turn this into * actual configurations. */ void split(SplitTransition splitTransition); /** * Returns whether or not all configuration(s) represented by the current state of this * instance are null. */ boolean isNull(); /** * Applies the given attribute configurator to the current configuration(s). */ void applyAttributeConfigurator(Attribute attribute, Rule fromRule, Target toTarget); /** * Calls {@link Transitions#configurationHook} on the current configuration(s) represent by * this instance. */ void applyConfigurationHook(Rule fromRule, Attribute attribute, Target toTarget); /** * Returns the underlying {@Transitions} object for this instance's current configuration. * Does not work for split configurations. */ Transitions getCurrentTransitions(); /** * Populates a {@link com.google.devtools.build.lib.analysis.Dependency} * for each configuration represented by this instance. * TODO(bazel-team): this is a really ugly reverse dependency: factor this away. */ Iterable getDependencies(Label label, ImmutableSet aspects); } /** * Transition applier for static configurations. This implementation populates * {@link com.google.devtools.build.lib.analysis.Dependency} objects with * actual configurations. * *

Does not support split transitions (see {@link SplittableTransitionApplier}). * TODO(bazel-team): remove this when dynamic configurations are fully production-ready. */ private static class StaticTransitionApplier implements TransitionApplier { BuildConfiguration currentConfiguration; private StaticTransitionApplier(BuildConfiguration originalConfiguration) { this.currentConfiguration = originalConfiguration; } @Override public TransitionApplier create(BuildConfiguration configuration) { return new StaticTransitionApplier(configuration); } @Override public void applyTransition(Transition transition) { if (transition == Attribute.ConfigurationTransition.NULL) { currentConfiguration = null; } else { currentConfiguration = currentConfiguration.getTransitions().getStaticConfiguration(transition); } } @Override public void split(SplitTransition splitTransition) { throw new UnsupportedOperationException("This only works with SplittableTransitionApplier"); } @Override public boolean isNull() { return currentConfiguration == null; } @Override public void applyAttributeConfigurator(Attribute attribute, Rule fromRule, Target toTarget) { @SuppressWarnings("unchecked") Configurator configurator = (Configurator) attribute.getConfigurator(); Verify.verifyNotNull(configurator); currentConfiguration = configurator.apply(fromRule, currentConfiguration, attribute, toTarget); } @Override public void applyConfigurationHook(Rule fromRule, Attribute attribute, Target toTarget) { currentConfiguration.getTransitions().configurationHook(fromRule, attribute, toTarget, this); // Allow rule classes to override their own configurations. Rule associatedRule = toTarget.getAssociatedRule(); if (associatedRule != null) { @SuppressWarnings("unchecked") RuleClass.Configurator func = associatedRule.getRuleClassObject().getConfigurator(); currentConfiguration = func.apply(associatedRule, currentConfiguration); } } @Override public Transitions getCurrentTransitions() { return currentConfiguration.getTransitions(); } @Override public Iterable getDependencies( Label label, ImmutableSet aspects) { return ImmutableList.of( currentConfiguration != null ? Dependency.withConfigurationAndAspects(label, currentConfiguration, aspects) : Dependency.withNullConfiguration(label)); } } /** * Transition applier for dynamic configurations. This implementation populates * {@link com.google.devtools.build.lib.analysis.Dependency} objects with * transition definitions that the caller subsequently creates configurations out of. * *

Does not support split transitions (see {@link SplittableTransitionApplier}). */ private static class DynamicTransitionApplier implements TransitionApplier { private final BuildConfiguration originalConfiguration; private Transition transition = Attribute.ConfigurationTransition.NONE; private DynamicTransitionApplier(BuildConfiguration originalConfiguration) { this.originalConfiguration = originalConfiguration; } @Override public TransitionApplier create(BuildConfiguration configuration) { return new DynamicTransitionApplier(configuration); } @Override public void applyTransition(Transition transition) { if (transition == Attribute.ConfigurationTransition.NONE) { return; } else if (this.transition != HostTransition.INSTANCE) { // We don't currently support composed transitions (e.g. applyTransitions shouldn't be // called multiple times). We can add support for this if needed by simply storing a list of // transitions instead of a single transition. But we only want to do that if really // necessary - if we can simplify BuildConfiguration's transition logic to not require // scenarios like that, it's better to keep this simpler interface. // // The HostTransition exemption is because of limited cases where composition can // occur. See relevant comments beginning with "BuildConfiguration.applyTransition NOTE" // in the transition logic code if available. // Ensure we don't already have any mutating transitions registered. // Note that for dynamic configurations, LipoDataTransition is equivalent to NONE. That's // because dynamic transitions don't work with LIPO, so there's no LIPO context to change. Verify.verify(this.transition == Attribute.ConfigurationTransition.NONE || this.transition.toString().contains("LipoDataTransition")); this.transition = getCurrentTransitions().getDynamicTransition(transition); } } @Override public void split(SplitTransition splitTransition) { throw new UnsupportedOperationException("This only works with SplittableTransitionApplier"); } @Override public boolean isNull() { return transition == Attribute.ConfigurationTransition.NULL; } @Override public void applyAttributeConfigurator(Attribute attribute, Rule fromRule, Target toTarget) { // We don't support meaningful attribute configurators (since they produce configurations, // and we're only interested in generating transitions so the calling code can realize // configurations from them). So just check that the configurator is just a no-op. @SuppressWarnings("unchecked") Configurator configurator = (Configurator) attribute.getConfigurator(); Verify.verifyNotNull(configurator); BuildConfiguration toConfiguration = configurator.apply(fromRule, originalConfiguration, attribute, toTarget); Verify.verify(toConfiguration == originalConfiguration); } @Override public void applyConfigurationHook(Rule fromRule, Attribute attribute, Target toTarget) { if (isNull()) { return; } getCurrentTransitions().configurationHook(fromRule, attribute, toTarget, this); // We don't support rule class configurators (which might imply composed transitions). // The only current use of that is LIPO, which can't currently be invoked with dynamic // configurations (e.g. this code can never get called for LIPO builds). So check that // if there is a configurator, it's for LIPO, in which case we can ignore it. Rule associatedRule = toTarget.getAssociatedRule(); if (associatedRule != null) { @SuppressWarnings("unchecked") RuleClass.Configurator func = associatedRule.getRuleClassObject().getConfigurator(); Verify.verify(func == RuleClass.NO_CHANGE || func.getCategory().equals("lipo")); } } @Override public Transitions getCurrentTransitions() { return originalConfiguration.getTransitions(); } @Override public Iterable getDependencies( Label label, ImmutableSet aspects) { return ImmutableList.of( Dependency.withTransitionAndAspects(label, transition, aspects)); } } /** * Transition applier that wraps an underlying implementation with added support for * split transitions. All external calls into BuildConfiguration should use this applier. */ private static class SplittableTransitionApplier implements TransitionApplier { private List appliers; private SplittableTransitionApplier(TransitionApplier original) { appliers = ImmutableList.of(original); } @Override public TransitionApplier create(BuildConfiguration configuration) { throw new UnsupportedOperationException("Not intended to be wrapped under another applier"); } @Override public void applyTransition(Transition transition) { for (TransitionApplier applier : appliers) { applier.applyTransition(transition); } } @Override public void split(SplitTransition splitTransition) { TransitionApplier originalApplier = Iterables.getOnlyElement(appliers); ImmutableList.Builder splitAppliers = ImmutableList.builder(); for (BuildConfiguration splitConfig : originalApplier.getCurrentTransitions().getSplitConfigurations(splitTransition)) { splitAppliers.add(originalApplier.create(splitConfig)); } appliers = splitAppliers.build(); } @Override public boolean isNull() { throw new UnsupportedOperationException("Only for use from a Transitions instance"); } @Override public void applyAttributeConfigurator(Attribute attribute, Rule fromRule, Target toTarget) { for (TransitionApplier applier : appliers) { applier.applyAttributeConfigurator(attribute, fromRule, toTarget); } } @Override public void applyConfigurationHook(Rule fromRule, Attribute attribute, Target toTarget) { for (TransitionApplier applier : appliers) { applier.applyConfigurationHook(fromRule, attribute, toTarget); } } @Override public Transitions getCurrentTransitions() { throw new UnsupportedOperationException("Only for use from a Transitions instance"); } @Override public Iterable getDependencies( Label label, ImmutableSet aspects) { ImmutableList.Builder builder = ImmutableList.builder(); for (TransitionApplier applier : appliers) { builder.addAll(applier.getDependencies(label, aspects)); } return builder.build(); } } /** * Returns the {@link TransitionApplier} that should be passed to {#evaluateTransition} calls. */ public TransitionApplier getTransitionApplier() { TransitionApplier applier = useDynamicConfigurations() ? new DynamicTransitionApplier(this) : new StaticTransitionApplier(this); return new SplittableTransitionApplier(applier); } /** * Returns true if the given target uses a null configuration, false otherwise. Consider * this method the "source of truth" for determining this. */ public static boolean usesNullConfiguration(Target target) { return target instanceof InputFile || target instanceof PackageGroup; } /** * Calculates the configurations of a direct dependency. If a rule in some BUILD file refers * to a target (like another rule or a source file) using a label attribute, that target needs * to have a configuration, too. This method figures out the proper configuration for the * dependency. * * @param fromRule the rule that's depending on some target * @param attribute the attribute using which the rule depends on that target (eg. "srcs") * @param toTarget the target that's dependeded on * @param transitionApplier the transition applier to accept transitions requests */ public void evaluateTransition(final Rule fromRule, final Attribute attribute, final Target toTarget, TransitionApplier transitionApplier) { // Fantastic configurations and where to find them: // I. Input files and package groups have no configurations. We don't want to duplicate them. if (usesNullConfiguration(toTarget)) { transitionApplier.applyTransition(Attribute.ConfigurationTransition.NULL); return; } // II. Host configurations never switch to another. All prerequisites of host targets have the // same host configuration. if (isHostConfiguration()) { transitionApplier.applyTransition(Attribute.ConfigurationTransition.NONE); return; } // Make sure config_setting dependencies are resolved in the referencing rule's configuration, // unconditionally. For example, given: // // genrule( // name = 'myrule', // tools = select({ '//a:condition': [':sometool'] }) // // all labels in "tools" get resolved in the host configuration (since the "tools" attribute // declares a host configuration transition). We want to explicitly exclude configuration labels // from these transitions, since their *purpose* is to do computation on the owning // rule's configuration. // TODO(bazel-team): don't require special casing here. This is far too hackish. if (toTarget instanceof Rule && ((Rule) toTarget).getRuleClass().equals(ConfigRuleClasses.ConfigSettingRule.RULE_NAME)) { transitionApplier.applyTransition(Attribute.ConfigurationTransition.NONE); // Unnecessary. return; } if (attribute.hasSplitConfigurationTransition()) { Preconditions.checkState(attribute.getConfigurator() == null); transitionApplier.split(attribute.getSplitTransition(fromRule)); } else { // III. Attributes determine configurations. The configuration of a prerequisite is determined // by the attribute. @SuppressWarnings("unchecked") Configurator configurator = (Configurator) attribute.getConfigurator(); if (configurator != null) { transitionApplier.applyAttributeConfigurator(attribute, fromRule, toTarget); } else { transitionApplier.applyTransition(attribute.getConfigurationTransition()); } } transitionApplier.applyConfigurationHook(fromRule, attribute, toTarget); } /** * For an given environment, returns a subset containing all * variables in the given list if they are defined in the given * environment. */ @VisibleForTesting static Map getMapping(List variables, Map environment) { Map result = new HashMap<>(); for (String var : variables) { if (environment.containsKey(var)) { result.put(var, environment.get(var)); } } return result; } /** * Returns the {@link Option} class the defines the given option, null if the * option isn't recognized. * *

optionName is the name of the option as it appears on the command line * e.g. {@link Option#name}). */ Class getOptionClass(String optionName) { OptionDetails optionData = transitiveOptionsMap.get(optionName); return optionData == null ? null : optionData.optionsClass; } /** * Returns the value of the specified option for this configuration or null if the * option isn't recognized. Since an option's legitimate value could be null, use * {@link #getOptionClass} to distinguish between that and an unknown option. * *

optionName is the name of the option as it appears on the command line * e.g. {@link Option#name}). */ Object getOptionValue(String optionName) { OptionDetails optionData = transitiveOptionsMap.get(optionName); return (optionData == null) ? null : optionData.value; } /** * Returns whether or not the given option supports multiple values at the command line (e.g. * "--myoption value1 --myOption value2 ..."). Returns false for unrecognized options. Use * {@link #getOptionClass} to distinguish between those and legitimate single-value options. * *

As declared in {@link Option#allowMultiple}, multi-value options are expected to be * of type {@code List}. */ boolean allowsMultipleValues(String optionName) { OptionDetails optionData = transitiveOptionsMap.get(optionName); return (optionData == null) ? false : optionData.allowsMultiple; } /** * The platform string, suitable for use as a key into a MakeEnvironment. */ public String getPlatformName() { return platformName; } /** * Returns the output directory for this build configuration. */ public Root getOutputDirectory() { return outputRoots.outputDirectory; } /** * Returns the bin directory for this build configuration. */ @SkylarkCallable(name = "bin_dir", structField = true, doc = "The root corresponding to bin directory.") public Root getBinDirectory() { return outputRoots.binDirectory; } /** * Returns a relative path to the bin directory at execution time. */ public PathFragment getBinFragment() { return getBinDirectory().getExecPath(); } /** * Returns the include directory for this build configuration. */ public Root getIncludeDirectory() { return outputRoots.includeDirectory; } /** * Returns the genfiles directory for this build configuration. */ @SkylarkCallable(name = "genfiles_dir", structField = true, doc = "The root corresponding to genfiles directory.") public Root getGenfilesDirectory() { return outputRoots.genfilesDirectory; } /** * Returns the directory where coverage-related artifacts and metadata files * should be stored. This includes for example uninstrumented class files * needed for Jacoco's coverage reporting tools. */ public Root getCoverageMetadataDirectory() { return outputRoots.coverageMetadataDirectory; } /** * Returns the testlogs directory for this build configuration. */ public Root getTestLogsDirectory() { return outputRoots.testLogsDirectory; } /** * Returns a relative path to the genfiles directory at execution time. */ public PathFragment getGenfilesFragment() { return getGenfilesDirectory().getExecPath(); } /** * Returns the path separator for the host platform. This is basically the same as {@link * java.io.File#pathSeparator}, except that that returns the value for this JVM, which may or may * not match the host platform. You should only use this when invoking tools that are known to use * the native path separator, i.e., the path separator for the machine that they run on. */ @SkylarkCallable(name = "host_path_separator", structField = true, doc = "Returns the separator for PATH environment variable, which is ':' on Unix.") public String getHostPathSeparator() { // TODO(bazel-team): Maybe do this in the constructor instead? This isn't serialization-safe. return OS.getCurrent() == OS.WINDOWS ? ";" : ":"; } /** * Returns the internal directory (used for middlemen) for this build configuration. */ public Root getMiddlemanDirectory() { return outputRoots.middlemanDirectory; } public boolean getAllowRuntimeDepsOnNeverLink() { return options.allowRuntimeDepsOnNeverLink; } public boolean isStrictFilesets() { return options.strictFilesets; } public List

This does *not* include package-defined overrides (e.g. vardef) * and so should not be used by the build logic. This is used only for * the 'info' command. * *

Command-line definitions of make enviroments override variables defined by * {@code Fragment.addGlobalMakeVariables()}. */ public Map getMakeEnvironment() { Map makeEnvironment = new HashMap<>(); makeEnvironment.putAll(globalMakeEnv); for (Fragment fragment : fragments.values()) { makeEnvironment.putAll(fragment.getCommandLineDefines()); } return ImmutableMap.copyOf(makeEnvironment); } /** * Returns a new, unordered mapping of names that are set through the command lines. * (Fragments, in particular the Google C++ support, can set variables through the * command line.) */ public Map getCommandLineDefines() { ImmutableMap.Builder builder = ImmutableMap.builder(); for (Fragment fragment : fragments.values()) { builder.putAll(fragment.getCommandLineDefines()); } return builder.build(); } /** * Returns the global defaults for this configuration for the Make environment. */ public Map getGlobalMakeEnvironment() { return globalMakeEnv; } /** * Returns a (key, value) mapping to insert into the subcommand environment for coverage * actions. */ public Map getCoverageEnvironment() { Map env = new HashMap<>(); for (Fragment fragment : fragments.values()) { env.putAll(fragment.getCoverageEnvironment()); } return env; } /** * Returns the default value for the specified "Make" variable for this * configuration. Returns null if no value was found. */ public String getMakeVariableDefault(String var) { return globalMakeEnv.get(var); } /** * Returns a configuration fragment instances of the given class. */ public T getFragment(Class clazz) { return clazz.cast(fragments.get(clazz)); } /** * Returns true if the requested configuration fragment is present. */ public boolean hasFragment(Class clazz) { return getFragment(clazz) != null; } /** * Returns true if all requested configuration fragment are present (this may be slow). */ public boolean hasAllFragments(Set> fragmentClasses) { for (Class fragmentClass : fragmentClasses) { if (!hasFragment(fragmentClass.asSubclass(Fragment.class))) { return false; } } return true; } /** * Which fragments does this configuration contain? */ public Set> fragmentClasses() { return fragments.keySet(); } /** * Returns true if non-functional build stamps are enabled. */ public boolean stampBinaries() { return options.stampBinaries; } /** * Returns true if extended sanity checks should be enabled. */ public boolean extendedSanityChecks() { return options.extendedSanityChecks; } /** * Returns true if we are building runfiles symlinks for this configuration. */ public boolean buildRunfiles() { return options.buildRunfiles; } public boolean getCheckFilesetDependenciesRecursively() { return options.checkFilesetDependenciesRecursively; } public boolean getSkyframeNativeFileset() { return options.skyframeNativeFileset; } public List getTestArguments() { return options.testArguments; } public String getTestFilter() { return options.testFilter; } /** * Returns user-specified test environment variables and their values, as * set by the --test_env options. */ public ImmutableMap getTestEnv() { return testEnvironment; } public TriState cacheTestResults() { return options.cacheTestResults; } public int getMinParamFileSize() { return options.minParamFileSize; } @SkylarkCallable(name = "coverage_enabled", structField = true, doc = "A boolean that tells whether code coverage is enabled.") public boolean isCodeCoverageEnabled() { return options.collectCodeCoverage; } public boolean isMicroCoverageEnabled() { return options.collectMicroCoverage; } public boolean isActionsEnabled() { return actionsEnabled; } public TestActionBuilder.TestShardingStrategy testShardingStrategy() { return options.testShardingStrategy; } /** * @return number of times the given test should run. * If the test doesn't match any of the filters, runs it once. */ public int getRunsPerTestForLabel(Label label) { for (PerLabelOptions perLabelRuns : options.runsPerTest) { if (perLabelRuns.isIncluded(label)) { return Integer.parseInt(Iterables.getOnlyElement(perLabelRuns.getOptions())); } } return 1; } public RunUnder getRunUnder() { return options.runUnder; } /** * Returns true if this is a host configuration. */ public boolean isHostConfiguration() { return options.isHost; } public boolean checkVisibility() { return options.checkVisibility; } public boolean checkLicenses() { return options.checkLicenses; } public boolean enforceConstraints() { return options.enforceConstraints; } public List

Be very careful using this method. Options classes are mutable - no caller * should ever call this method if there's any change the reference might be written to. * This method only exists because {@link #cloneOptions} can be expensive when applied to * every edge in a dependency graph, which becomes possible with dynamic configurations. * *

Do not use this method without careful review with other Bazel developers.. */ public BuildOptions getOptions() { return buildOptions; } /** * Declares dependencies on any relevant Skyframe values (for example, relevant FileValues). */ public void declareSkyframeDependencies(SkyFunction.Environment env) { for (Fragment fragment : fragments.values()) { fragment.declareSkyframeDependencies(env); } } /** * Returns all the roots for this configuration. */ public List getRoots() { List roots = new ArrayList<>(); // Configuration-specific roots. roots.add(getBinDirectory()); roots.add(getGenfilesDirectory()); roots.add(getIncludeDirectory()); roots.add(getMiddlemanDirectory()); roots.add(getTestLogsDirectory()); // Fragment-defined roots for (Fragment fragment : fragments.values()) { fragment.addRoots(roots); } return ImmutableList.copyOf(roots); } public ListMultimap getAllLabels() { return buildOptions.getAllLabels(); } public String getCpu() { return options.cpu; } /** * Returns true if the configuration performs static linking. */ public boolean performsStaticLink() { for (Fragment fragment : fragments.values()) { if (fragment.performsStaticLink()) { return true; } } return false; } /** * Collects executables defined by fragments. */ private ImmutableMap collectExecutables() { ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); for (Fragment fragment : fragments.values()) { fragment.defineExecutables(builder); } return builder.build(); } /** * See {@code BuildConfigurationCollection.Transitions.getArtifactOwnerConfiguration()}. */ public BuildConfiguration getArtifactOwnerConfiguration() { // Dynamic configurations inherit transitions objects from other configurations exclusively // for use of Transitions.getDynamicTransitions. No other calls to transitions should be // made for dynamic configurations. // TODO(bazel-team): enforce the above automatically (without having to explicitly check // for dynamic configuration mode). return useDynamicConfigurations() ? this : transitions.getArtifactOwnerConfiguration(); } /** * @return whether proto header modules should be built. */ public boolean getProtoHeaderModules() { return options.protoHeaderModules; } /** * @return the list of default features used for all packages. */ public List getDefaultFeatures() { return options.defaultFeatures; } /** * Returns the "top-level" environment space, i.e. the set of environments all top-level * targets must be compatible with. An empty value implies no restrictions. */ public List