// 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.Predicate; import com.google.common.base.Splitter; 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.Interner; import com.google.common.collect.Interners; import com.google.common.collect.Iterables; import com.google.common.collect.ListMultimap; import com.google.common.collect.Lists; 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.AspectCollection; 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.actions.FileWriteAction; import com.google.devtools.build.lib.analysis.config.BuildConfigurationCollection.Transitions; import com.google.devtools.build.lib.buildeventstream.BuildEvent; import com.google.devtools.build.lib.buildeventstream.BuildEventConverters; import com.google.devtools.build.lib.buildeventstream.BuildEventId; import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos; import com.google.devtools.build.lib.buildeventstream.GenericBuildEvent; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.cmdline.LabelSyntaxException; import com.google.devtools.build.lib.cmdline.RepositoryName; 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.ConfigurationTransition; 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.RuleTransitionFactory; 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.skylarkinterface.SkylarkModuleCategory; 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.Pair; 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.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.OptionsParser.OptionUsageRestrictions; import com.google.devtools.common.options.OptionsParsingException; import com.google.devtools.common.options.TriState; 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.LinkedHashMap; 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. * * <p>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. * * <p>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. * * <p>Instances of BuildConfiguration are canonical: * <pre>c1.equals(c2) <=> c1==c2.</pre> */ @SkylarkModule(name = "configuration", category = SkylarkModuleCategory.BUILTIN, 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 implements BuildEvent { /** * An interface for language-specific configurations. * * <p>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<String, String> globalMakeEnvBuilder) { } /** * Returns a fragment of the output directory name for this configuration. The output * directory for the whole configuration contains all the short names by all fragments. */ @Nullable public String getOutputDirectoryName() { return null; } /** * The platform name is a concatenation of fragment platform names. */ public String getPlatformName() { return ""; } /** * Add items to the shell environment. */ @SuppressWarnings("unused") public void setupShellEnvironment(ImmutableMap.Builder<String, String> builder) { } /** * Returns the shell to be used. * * <p>Each configuration instance must have at most one fragment that returns non-null. */ @SuppressWarnings("unused") public PathFragment getShellExecutable() { return null; } /** * Returns { 'option name': 'alternative default' } entries for options where the * "real default" should be something besides the default specified in the {@link Option} * declaration. */ public Map<String, Object> lateBoundOptionDefaults() { return ImmutableMap.of(); } /** * Return set of features enabled by this configuration. */ public ImmutableSet<String> configurationEnabledFeatures(RuleContext ruleContext) { return ImmutableSet.of(); } /** * @return false if a Fragment understands that it won't be able to work with a given strategy, * or true otherwise. */ public boolean compatibleWithStrategy(String strategyName) { return true; } /** * Returns an extra transition that should apply to top-level targets in this * configuration. Returns null if no transition is needed. * * <p>Overriders should not change {@link FragmentOptions} not associated with their fragment. * * <p>If multiple fragments specify a transition, they're composed together in a * deterministic but undocumented order (so don't write code expecting a specific order). */ @Nullable public PatchTransition topLevelConfigurationHook(Target toTarget) { return null; } } private static final Label convertLabel(String input) throws OptionsParsingException { try { // Check if the input starts with '/'. We don't check for "//" so that // we get a better error message if the user accidentally tries to use // an absolute path (starting with '/') for a label. if (!input.startsWith("/") && !input.startsWith("@")) { input = "//" + input; } return Label.parseAbsolute(input); } catch (LabelSyntaxException e) { throw new OptionsParsingException(e.getMessage()); } } /** * A converter from strings to Labels. */ public static class LabelConverter implements Converter<Label> { @Override public Label convert(String input) throws OptionsParsingException { return convertLabel(input); } @Override public String getTypeDescription() { return "a build target label"; } } /** A converter from comma-separated strings to Label lists. */ public static class LabelListConverter implements Converter<List<Label>> { @Override public List<Label> convert(String input) throws OptionsParsingException { ImmutableList.Builder result = ImmutableList.builder(); for (String label : Splitter.on(",").omitEmptyStrings().split(input)) { result.add(convertLabel(label)); } return result.build(); } @Override public String getTypeDescription() { return "a build target label"; } } /** * A converter that returns null if the input string is empty, otherwise it converts * the input to a label. */ public static class EmptyToNullLabelConverter implements Converter<Label> { @Override public Label convert(String input) throws OptionsParsingException { return input.isEmpty() ? null : convertLabel(input); } @Override public String getTypeDescription() { return "a build target label"; } } /** * A label converter that returns a default value if the input string is empty. */ public static class DefaultLabelConverter implements Converter<Label> { private final Label defaultValue; protected DefaultLabelConverter(String defaultValue) { this.defaultValue = defaultValue.equals("null") ? null : Label.parseAbsoluteUnchecked(defaultValue); } @Override public Label convert(String input) throws OptionsParsingException { return input.isEmpty() ? defaultValue : convertLabel(input); } @Override public String getTypeDescription() { return "a build target label"; } } /** Flag converter for a map of unique keys with optional labels as values. */ public static class LabelMapConverter implements Converter<Map<String, Label>> { @Override public Map<String, Label> convert(String input) throws OptionsParsingException { // Use LinkedHashMap so we can report duplicate keys more easily while preserving order Map<String, Label> result = new LinkedHashMap<>(); for (String entry : Splitter.on(",").omitEmptyStrings().trimResults().split(input)) { String key; Label label; int sepIndex = entry.indexOf('='); if (sepIndex < 0) { key = entry; label = null; } else { key = entry.substring(0, sepIndex); String value = entry.substring(sepIndex + 1); label = value.isEmpty() ? null : convertLabel(value); } if (result.containsKey(key)) { throw new OptionsParsingException("Key '" + key + "' appears twice"); } result.put(key, label); } return Collections.unmodifiableMap(result); } @Override public String getTypeDescription() { return "a comma-separated list of keys optionally followed by '=' and a label"; } } /** TODO(bazel-team): document this */ public static class PluginOptionConverter implements Converter<Map.Entry<String, String>> { @Override public Map.Entry<String, String> convert(String input) throws OptionsParsingException { int index = input.indexOf('='); if (index == -1) { throw new OptionsParsingException("Plugin option not in the plugin=option format"); } String option = input.substring(0, index); String value = input.substring(index + 1); return Maps.immutableEntry(option, value); } @Override public String getTypeDescription() { return "An option for a plugin"; } } /** TODO(bazel-team): document this */ public static class RunsPerTestConverter extends PerLabelOptions.PerLabelOptionsConverter { @Override public PerLabelOptions convert(String input) throws OptionsParsingException { try { return parseAsInteger(input); } catch (NumberFormatException ignored) { return parseAsRegex(input); } } private PerLabelOptions parseAsInteger(String input) throws NumberFormatException, OptionsParsingException { int numericValue = Integer.parseInt(input); if (numericValue <= 0) { throw new OptionsParsingException("'" + input + "' should be >= 1"); } else { RegexFilter catchAll = new RegexFilter(Collections.singletonList(".*"), Collections.<String>emptyList()); return new PerLabelOptions(catchAll, Collections.singletonList(input)); } } private PerLabelOptions parseAsRegex(String input) throws OptionsParsingException { PerLabelOptions testRegexps = super.convert(input); if (testRegexps.getOptions().size() != 1) { throw new OptionsParsingException( "'" + input + "' has multiple runs for a single pattern"); } String runsPerTest = Iterables.getOnlyElement(testRegexps.getOptions()); try { int numericRunsPerTest = Integer.parseInt(runsPerTest); if (numericRunsPerTest <= 0) { throw new OptionsParsingException("'" + input + "' has a value < 1"); } } catch (NumberFormatException e) { throw new OptionsParsingException("'" + input + "' has a non-numeric value", e); } return testRegexps; } @Override public String getTypeDescription() { return "a positive integer or test_regex@runs. This flag may be passed more than once"; } } /** * Values for the --strict_*_deps option */ public static enum StrictDepsMode { /** Silently allow referencing transitive dependencies. */ OFF, /** Warn about transitive dependencies being used directly. */ WARN, /** Fail the build when transitive dependencies are used directly. */ ERROR, /** Transition to strict by default. */ STRICT, /** When no flag value is specified on the command line. */ DEFAULT } /** * Converter for the --strict_*_deps option. */ public static class StrictDepsConverter extends EnumConverter<StrictDepsMode> { public StrictDepsConverter() { super(StrictDepsMode.class, "strict dependency checking level"); } } /** * Converter to auto-detect the cpu of the machine on which Bazel runs. * * <p>If the compilation happens remotely then the cpu of the remote machine might be different * from the auto-detected one and the --cpu and --host_cpu options must be set explicitly. */ public static class AutoCpuConverter implements Converter<String> { @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"; default: // We only support x64 Windows for now. return "unknown"; } case LINUX: switch (CPU.getCurrent()) { case X86_32: return "piii"; case X86_64: return "k8"; case PPC: return "ppc"; case ARM: return "arm"; case S390X: return "s390x"; default: return "unknown"; } default: return "unknown"; } } return input; } @Override public String getTypeDescription() { return "a string"; } } /** * Options that affect the value of a BuildConfiguration instance. * * <p>(Note: any client that creates a view will also need to declare * BuildView.Options, which affect the <i>mechanism</i> of view construction, * even if they don't affect the value of the BuildConfiguration instances.) * * <p>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}. * * <p>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 { @Option( name = "define", converter = Converters.AssignmentConverter.class, defaultValue = "", category = "semantics", allowMultiple = true, help = "Each --define option specifies an assignment for a build variable." ) public List<Map.Entry<String, String>> commandLineBuildVariables; @Option( name = "cpu", defaultValue = "", category = "semantics", converter = AutoCpuConverter.class, help = "The target CPU." ) public String cpu; /** * Allows a configuration to record if --experimental_multi_cpu was used to set a cpu value. * This is necessary to ensure that a configuration transition that sets cpu does not erase the * difference between a pair of configurations created by --experimental_multi_cpu, leading to a * crash when the configurations are treated as the same. * * <p>TODO(b/33780512): Remove once dynamic configurations are used. */ @Option( name = "experimental multi cpu distinguisher", defaultValue = "", optionUsageRestrictions = OptionUsageRestrictions.INTERNAL ) public String experimentalMultiCpuDistinguisher; @Option( name = "min_param_file_size", defaultValue = "32768", optionUsageRestrictions = OptionUsageRestrictions.UNDOCUMENTED, help = "Minimum command line length before creating a parameter file." ) public int minParamFileSize; @Option( name = "experimental_extended_sanity_checks", defaultValue = "false", optionUsageRestrictions = OptionUsageRestrictions.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", optionUsageRestrictions = OptionUsageRestrictions.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<Label> pluginList; @Option( name = "plugin_copt", converter = PluginOptionConverter.class, allowMultiple = true, category = "flags", defaultValue = ":", help = "Plugin options" ) public List<Map.Entry<String, String>> pluginCoptList; @Option( name = "stamp", defaultValue = "false", category = "semantics", help = "Stamp binaries with the date, username, hostname, workspace information, etc." ) public boolean stampBinaries; // This default value is always overwritten in the case of "bazel coverage" by // CoverageCommand.setDefaultInstrumentationFilter(). @Option( name = "instrumentation_filter", converter = RegexFilter.RegexFilterConverter.class, defaultValue = "-/javatests[/:]", category = "semantics", help = "When coverage is enabled, only rules with names included by the " + "specified regex-based filter will be instrumented. Rules prefixed " + "with '-' are excluded instead. Note that only non-test rules are " + "instrumented unless --instrument_test_targets is enabled." ) public RegexFilter instrumentationFilter; @Option( name = "instrument_test_targets", defaultValue = "false", category = "semantics", help = "When coverage is enabled, specifies whether to consider instrumenting test rules. " + "When set, test rules included by --instrumentation_filter are instrumented. " + "Otherwise, test rules are always excluded from coverage instrumentation." ) public boolean instrumentTestTargets; @Option( name = "host_cpu", defaultValue = "", category = "semantics", converter = AutoCpuConverter.class, help = "The host CPU." ) public String hostCpu; @Option( name = "compilation_mode", abbrev = 'c', converter = CompilationMode.Converter.class, defaultValue = "fastbuild", category = "semantics", // Should this be "flags"? help = "Specify the mode the binary will be built in. " + "Values: 'fastbuild', 'dbg', 'opt'." ) public CompilationMode compilationMode; /** * This option is used internally to set output directory name of the <i>host</i> configuration * to a constant, so that the output files for the host are completely independent of those for * the target, no matter what options are in force (k8/piii, opt/dbg, etc). */ @Option( name = "output directory name", defaultValue = "null", optionUsageRestrictions = OptionUsageRestrictions.INTERNAL ) public String outputDirectoryName; @Option( name = "platform_suffix", defaultValue = "null", category = "misc", help = "Specifies a suffix to be added to the configuration directory." ) public String platformSuffix; // TODO(bazel-team): The test environment is actually computed in BlazeRuntime and this option // is not read anywhere else. Thus, it should be in a different options class, preferably one // specific to the "test" command or maybe in its own configuration fragment. // BlazeRuntime, though. @Option( name = "test_env", converter = Converters.OptionalAssignmentConverter.class, allowMultiple = true, defaultValue = "", category = "testing", help = "Specifies additional environment variables to be injected into the test runner " + "environment. Variables can be either specified by name, in which case its value " + "will be read from the Bazel client environment, or by the name=value pair. " + "This option can be used multiple times to specify several variables. " + "Used only by the 'bazel test' command." ) public List<Map.Entry<String, String>> testEnvironment; // TODO(bazel-team): The set of available variables from the client environment for actions // is computed independently in CommandEnvironment to inject a more restricted client // environment to skyframe. @Option( name = "action_env", converter = Converters.OptionalAssignmentConverter.class, allowMultiple = true, defaultValue = "", category = "semantics", help = "Specifies the set of environment variables available to actions. " + "Variables can be either specified by name, in which case the value will be " + "taken from the invocation environment, or by the name=value pair which sets " + "the value independent of the invocation environment. This option can be used " + "multiple times; for options given for the same variable, the latest wins, options " + "for different variables accumulate." ) public List<Map.Entry<String, String>> actionEnvironment; @Option( name = "collect_code_coverage", defaultValue = "false", category = "testing", help = "If specified, Bazel will instrument code (using offline instrumentation where " + "possible) and will collect coverage information during tests. Only targets that " + " match --instrumentation_filter will be affected. Usually this option should " + " not be specified directly - 'bazel coverage' command should be used instead." ) public boolean collectCodeCoverage; @Option( name = "coverage_support", converter = LabelConverter.class, defaultValue = "@bazel_tools//tools/test:coverage_support", category = "testing", help = "Location of support files that are required on the inputs of every test action " + "that collects code coverage. Defaults to '//tools/test:coverage_support'." ) public Label coverageSupport; @Option( name = "coverage_report_generator", converter = LabelConverter.class, defaultValue = "@bazel_tools//tools/test:coverage_report_generator", category = "testing", help = "Location of the binary that is used to generate coverage reports. This must " + "currently be a filegroup that contains a single file, the binary. Defaults to " + "'//tools/test:coverage_report_generator'." ) public Label coverageReportGenerator; @Option( name = "experimental_use_llvm_covmap", defaultValue = "false", category = "experimental", help = "If specified, Bazel will generate llvm-cov coverage map information rather than " + "gcov when collect_code_coverage is enabled." ) public boolean useLLVMCoverageMapFormat; @Option( name = "cache_test_results", defaultValue = "auto", category = "testing", abbrev = 't', // it's useful to toggle this on/off quickly help = "If 'auto', Bazel will only rerun a test if any of the following conditions apply: " + "(1) Bazel detects changes in the test or its dependencies " + "(2) the test is marked as external " + "(3) multiple test runs were requested with --runs_per_test " + "(4) the test failed " + "If 'yes', the caching behavior will be the same as 'auto' except that " + "it may cache test failures and test runs with --runs_per_test. " + "If 'no', all tests will be always executed." ) public TriState cacheTestResults; @Deprecated @Option( name = "test_result_expiration", defaultValue = "-1", // No expiration by defualt. category = "testing", help = "This option is deprecated and has no effect." ) public int testResultExpiration; @Option( name = "test_sharding_strategy", defaultValue = "explicit", category = "testing", converter = TestActionBuilder.ShardingStrategyConverter.class, help = "Specify strategy for test sharding: " + "'explicit' to only use sharding if the 'shard_count' BUILD attribute is present. " + "'disabled' to never use test sharding. " + "'experimental_heuristic' to enable sharding on remotely executed tests without an " + "explicit 'shard_count' attribute which link in a supported framework. Considered " + "experimental." ) public TestActionBuilder.TestShardingStrategy testShardingStrategy; @Option( name = "runs_per_test", allowMultiple = true, defaultValue = "1", category = "testing", converter = RunsPerTestConverter.class, help = "Specifies number of times to run each test. If any of those attempts " + "fail for any reason, the whole test would be considered failed. " + "Normally the value specified is just an integer. Example: --runs_per_test=3 " + "will run all tests 3 times. " + "Alternate syntax: regex_filter@runs_per_test. Where runs_per_test stands for " + "an integer value and regex_filter stands " + "for a list of include and exclude regular expression patterns (Also see " + "--instrumentation_filter). Example: " + "--runs_per_test=//foo/.*,-//foo/bar/.*@3 runs all tests in //foo/ " + "except those under foo/bar three times. " + "This option can be passed multiple times. " ) public List<PerLabelOptions> runsPerTest; @Option( name = "build_runfile_links", defaultValue = "true", category = "strategy", help = "If true, build runfiles symlink forests for all targets. " + "If false, write only manifests when possible." ) public boolean buildRunfiles; @Option( name = "legacy_external_runfiles", defaultValue = "true", category = "strategy", help = "If true, build runfiles symlink forests for external repositories under " + ".runfiles/wsname/external/repo (in addition to .runfiles/repo)." ) public boolean legacyExternalRunfiles; @Option( name = "test_arg", allowMultiple = true, defaultValue = "", category = "testing", help = "Specifies additional options and arguments that should be passed to the test " + "executable. Can be used multiple times to specify several arguments. " + "If multiple tests are executed, each of them will receive identical arguments. " + "Used only by the 'bazel test' command." ) public List<String> testArguments; @Option( name = "test_filter", allowMultiple = false, defaultValue = "null", category = "testing", help = "Specifies a filter to forward to the test framework. Used to limit " + "the tests run. Note that this does not affect which targets are built." ) public String testFilter; @Option( name = "check_fileset_dependencies_recursively", defaultValue = "true", category = "semantics", help = "If false, fileset targets will, whenever possible, create " + "symlinks to directories instead of creating one symlink for each " + "file inside the directory. Disabling this will significantly " + "speed up fileset builds, but targets that depend on filesets will " + "not be rebuilt if files are added, removed or modified in a " + "subdirectory which has not been traversed." ) public boolean checkFilesetDependenciesRecursively; @Option( name = "experimental_skyframe_native_filesets", defaultValue = "false", category = "experimental", help = "If true, Blaze will use the skyframe-native implementation of the Fileset rule." + " This offers improved performance in incremental builds of Filesets as well as" + " correct incremental behavior, but is not yet stable. The default is false," + " meaning Blaze uses the legacy impelementation of Fileset." ) public boolean skyframeNativeFileset; @Option( name = "run_under", category = "run", defaultValue = "null", converter = RunUnderConverter.class, help = "Prefix to insert in front of command before running. " + "Examples:\n" + "\t--run_under=valgrind\n" + "\t--run_under=strace\n" + "\t--run_under='strace -c'\n" + "\t--run_under='valgrind --quiet --num-callers=20'\n" + "\t--run_under=//package:target\n" + "\t--run_under='//package:target --options'\n" ) public RunUnder runUnder; @Option( name = "distinct_host_configuration", defaultValue = "true", category = "strategy", help = "Build all the tools used during the build for a distinct configuration from that used " + "for the target program. When this is disabled, the same configuration is used for " + "host and target programs. This may cause undesirable rebuilds of tools such as " + "the protocol compiler (and then everything downstream) whenever a minor change " + "is made to the target configuration, such as setting the linker options. When " + "this is enabled (the default), a distinct configuration will be used to build the " + "tools, preventing undesired rebuilds. However, certain libraries will then need " + "to be compiled twice, once for each configuration, which may cause some builds " + "to be slower. As a rule of thumb, this option is likely to benefit users that " + "make frequent changes in configuration (e.g. opt/dbg). " + "Please read the user manual for the full explanation." ) public boolean useDistinctHostConfiguration; @Option( name = "check_visibility", defaultValue = "true", category = "checking", help = "If disabled, visibility errors are demoted to warnings." ) public boolean checkVisibility; // Moved from viewOptions to here because license information is very expensive to serialize. // Having it here allows us to skip computation of transitive license information completely // when the setting is disabled. @Option( name = "check_licenses", defaultValue = "false", category = "checking", help = "Check that licensing constraints imposed by dependent packages " + "do not conflict with distribution modes of the targets being built. " + "By default, licenses are not checked." ) public boolean checkLicenses; @Option( name = "enforce_constraints", defaultValue = "true", optionUsageRestrictions = OptionUsageRestrictions.UNDOCUMENTED, help = "Checks the environments each target is compatible with and reports errors if any " + "target has dependencies that don't support the same environments", oldName = "experimental_enforce_constraints" ) public boolean enforceConstraints; @Option( name = "experimental_action_listener", allowMultiple = true, defaultValue = "", category = "experimental", converter = LabelConverter.class, help = "Use action_listener to attach an extra_action to existing build actions." ) public List<Label> actionListeners; // TODO(bazel-team): Either remove this flag once transparent compression is shown to not // noticeably affect running time, or keep this flag and move it into a new configuration // fragment. @Option( name = "experimental_transparent_compression", defaultValue = "true", optionUsageRestrictions = OptionUsageRestrictions.UNDOCUMENTED, help = "Enables gzip compression for the contents of FileWriteActions, which reduces " + "memory usage in the analysis phase at the expense of additional time overhead." ) public boolean transparentCompression; @Option( name = "is host configuration", defaultValue = "false", optionUsageRestrictions = OptionUsageRestrictions.INTERNAL, help = "Shows whether these options are set for host configuration." ) public boolean isHost; @Option( name = "features", allowMultiple = true, defaultValue = "", category = "flags", help = "The given features will be enabled or disabled by default for all packages. " + "Specifying -<feature> will disable the feature globally. " + "Negative features always override positive ones. " + "This flag is used to enable rolling out default feature changes without a " + "Blaze release." ) public List<String> defaultFeatures; @Option( name = "target_environment", converter = LabelConverter.class, allowMultiple = true, defaultValue = "", category = "flags", help = "Declares this build's target environment. Must be a label reference to an " + "\"environment\" rule. If specified, all top-level targets must be " + "compatible with this environment." ) public List<Label> targetEnvironments; /** * Values for --experimental_dynamic_configs. */ public enum DynamicConfigsMode { /** Don't use dynamic configurations. */ OFF, /** Use dynamic configurations, including only the fragments each rule needs. */ ON, /** Use dynamic configurations, always including all fragments known to Blaze. */ NOTRIM, /** * Use untrimmed dynamic configurations unless an {@link Options} fragment needs static * configurations. This is used to exempt features that don't yet work with dynamic configs. */ // TODO(gregce): make this mode unnecesary by making everything compatible with dynamic // configs. b/23280991 tracks the effort (LIPO is the main culprit). NOTRIM_PARTIAL } /** * Converter for --experimental_dynamic_configs. */ public static class DynamicConfigsConverter extends EnumConverter<DynamicConfigsMode> { public DynamicConfigsConverter() { super(DynamicConfigsMode.class, "dynamic configurations mode"); } } @Option( name = "experimental_dynamic_configs", defaultValue = "notrim_partial", optionUsageRestrictions = OptionUsageRestrictions.UNDOCUMENTED, converter = DynamicConfigsConverter.class, help = "Dynamically instantiates build configurations instead of using the default " + "static globally defined ones" ) public DynamicConfigsMode useDynamicConfigurations; @Option( name = "experimental_enable_runfiles", defaultValue = "auto", optionUsageRestrictions = OptionUsageRestrictions.UNDOCUMENTED, help = "Enable runfiles; off on Windows, on on other platforms" ) public TriState enableRunfiles; @Option( name = "build_python_zip", defaultValue = "auto", optionUsageRestrictions = OptionUsageRestrictions.UNDOCUMENTED, help = "Build python executable zip; on on Windows, off on other platforms" ) public TriState buildPythonZip; @Override public FragmentOptions getHost(boolean fallback) { Options host = (Options) getDefault(); host.outputDirectoryName = "host"; host.compilationMode = CompilationMode.OPT; host.isHost = true; host.useDynamicConfigurations = useDynamicConfigurations; host.commandLineBuildVariables = commandLineBuildVariables; host.enforceConstraints = enforceConstraints; if (fallback) { // In the fallback case, we have already tried the target options and they didn't work, so // now we try the default options; the hostCpu field has the default value, because we use // getDefault() above. host.cpu = host.hostCpu; } else { host.cpu = hostCpu; } // === Runfiles === // Ideally we could force this the other way, and skip runfiles construction // for host tools which are never run locally, but that's probably a very // small optimization. host.buildRunfiles = true; // === Linkstamping === // Disable all link stamping for the host configuration, to improve action // cache hit rates for tools. host.stampBinaries = false; // === Visibility === host.checkVisibility = checkVisibility; // === Licenses === host.checkLicenses = checkLicenses; // === Fileset === host.skyframeNativeFileset = skyframeNativeFileset; // === Allow runtime_deps to depend on neverlink Java libraries. host.allowRuntimeDepsOnNeverLink = allowRuntimeDepsOnNeverLink; // === Pass on C++ compiler features. host.defaultFeatures = ImmutableList.copyOf(defaultFeatures); return host; } @Override public void addAllLabels(Multimap<String, Label> labelMap) { labelMap.putAll("action_listener", actionListeners); labelMap.putAll("plugins", pluginList); if ((runUnder != null) && (runUnder.getLabel() != null)) { labelMap.put("RunUnder", runUnder.getLabel()); } } @Override public Map<String, Set<Label>> getDefaultsLabels(BuildConfiguration.Options commonOptions) { return ImmutableMap.<String, Set<Label>>of( "coverage_support", ImmutableSet.of(coverageSupport), "coverage_report_generator", ImmutableSet.of(coverageReportGenerator)); } } private final String checksum; private Transitions transitions; private Set<BuildConfiguration> allReachableConfigurations; private final ImmutableMap<Class<? extends Fragment>, Fragment> fragments; private final ImmutableMap<String, Class<? extends Fragment>> skylarkVisibleFragments; /** * Directories in the output tree. * * <p>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. * * <p>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. * * <p>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 <i>first</i> time you rebuild with a different * setting, but will result in less work if you regularly switch * back and forth between different settings. * * <p>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. * * <p>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 enum OutputDirectory { BIN("bin"), GENFILES("genfiles"), MIDDLEMAN(true), TESTLOGS("testlogs"), COVERAGE("coverage-metadata"), INCLUDE(BlazeDirectories.RELATIVE_INCLUDE_DIR), OUTPUT(false); private final PathFragment nameFragment; private final boolean middleman; /** * This constructor is for roots without suffixes, e.g., * [[execroot/repo]/bazel-out/local-fastbuild]. * @param isMiddleman whether the root should be a middleman root or a "normal" derived root. */ OutputDirectory(boolean isMiddleman) { this.nameFragment = PathFragment.EMPTY_FRAGMENT; this.middleman = isMiddleman; } OutputDirectory(String name) { this.nameFragment = PathFragment.create(name); this.middleman = false; } Root getRoot( RepositoryName repositoryName, String outputDirName, BlazeDirectories directories) { // e.g., execroot/repo1 Path execRoot = directories.getExecRoot(); // e.g., execroot/repo1/bazel-out/config/bin Path outputDir = execRoot.getRelative(directories.getRelativeOutputPath()) .getRelative(outputDirName); if (middleman) { return INTERNER.intern(Root.middlemanRoot(execRoot, outputDir, repositoryName.isMain())); } // e.g., [[execroot/repo1]/bazel-out/config/bin] return INTERNER.intern( Root.asDerivedRoot( execRoot, outputDir.getRelative(nameFragment), repositoryName.isMain())); } } private final BlazeDirectories directories; private final String outputDirName; // We intern the roots for non-main repositories, so we don't keep around thousands of copies of // the same root. private static Interner<Root> INTERNER = Interners.newWeakInterner(); // We precompute the roots for the main repository, since that's the common case. private final Root outputDirectoryForMainRepository; private final Root binDirectoryForMainRepository; private final Root includeDirectoryForMainRepository; private final Root genfilesDirectoryForMainRepository; private final Root coverageDirectoryForMainRepository; private final Root testlogsDirectoryForMainRepository; private final Root middlemanDirectoryForMainRepository; // Cache this value for quicker access. We don't cache it inside BuildOptions because BuildOptions // is mutable, so a cached value there could fall out of date when it's updated. private final boolean actionsEnabled; // TODO(bazel-team): Move this to a configuration fragment. private final PathFragment shellExecutable; /** * The global "make variables" such as "$(TARGET_CPU)"; these get applied to all rules analyzed in * this configuration. */ private final ImmutableMap<String, String> globalMakeEnv; private final ImmutableMap<String, String> localShellEnvironment; private final ImmutableSet<String> envVariables; private final BuildOptions buildOptions; private final Options options; private final String mnemonic; private final String platformName; private final ImmutableMap<String, String> testEnvironment; private final ImmutableMap<String, String> commandLineBuildVariables; private final int hashCode; // We can precompute the hash code as all its inputs are immutable. /** Data for introspecting the options used by this configuration. */ private final TransitiveOptionDetails transitiveOptionDetails; /** * Returns true if this configuration is semantically equal to the other, with * the possible exception that the other has fewer fragments. * * <p>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 // TODO(gregce): add back in output root checking. This requires a better approach to // configuration-safe output paths. If the parent config has a fragment the child config // doesn't, it may inject $(FOO) into the output roots. So the child bindir might be // "bazel-out/arm-linux-fastbuild/bin" while the parent bindir is // "bazel-out/android-arm-linux-fastbuild/bin". That's pretty awkward to check here. // && outputRoots.equals(other.outputRoots) && actionsEnabled == other.actionsEnabled && fragments.values().containsAll(other.fragments.values()) && buildOptions.getOptions().containsAll(other.buildOptions.getOptions())); } /** * Returns {@code true} if this configuration is semantically equal to the other, including * checking that both have the same sets of fragments and options. */ @Override public boolean equals(Object other) { if (this == other) { return true; } if (!useDynamicConfigurations()) { // Static configurations aren't safe for value equality because they include transition // references to other configurations (see setConfigurationTransitions). For example, imagine // in one build target config A has a reference to data config B. Now imagine a second build // where target config C has a reference to data config D. If A and B are value-equal, that // means a call to ConfiguredTargetKey("//foo", C) might return the SkyKey for ("//foo", A). // This is not just possible but *likely* due to SkyKey interning (see // SkyKey.SKY_KEY_INTERNER). This means a data transition on that config could incorrectly // return B, which is not safe because B is not necessarily value-equal to D. // // This becomes safe with dynamic configurations: transitions are completely triggered by // external logic and configs have no awareness of them at all. return false; } if (!(other instanceof BuildConfiguration)) { return false; } BuildConfiguration otherConfig = (BuildConfiguration) other; return actionsEnabled == otherConfig.actionsEnabled && fragments.values().equals(otherConfig.fragments.values()) && buildOptions.getOptions().equals(otherConfig.buildOptions.getOptions()); } private int computeHashCode() { return Objects.hash(isActionsEnabled(), fragments, buildOptions.getOptions()); } @Override public int hashCode() { if (!useDynamicConfigurations()) { return BuildConfiguration.super.hashCode(); } return hashCode; } /** * Returns map of all the fragments for this configuration. */ public ImmutableMap<Class<? extends Fragment>, 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<String> 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<String, String> 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 (trimConfigurations() && !options.useDistinctHostConfiguration) { reporter.handle(Event.error( "--nodistinct_host_configuration does not currently work with dynamic configurations")); } } /** * @return false if any of the fragments don't work well with the supplied strategy. */ public boolean compatibleWithStrategy(final String strategyName) { return Iterables.all( fragments.values(), new Predicate<Fragment>() { @Override public boolean apply(@Nullable Fragment fragment) { return fragment.compatibleWithStrategy(strategyName); } }); } /** * Compute the shell environment, which, at configuration level, is a pair consisting of the * statically set environment variables with their values and the set of environment variables to * be inherited from the client environment. */ private Pair<ImmutableMap<String, String>, ImmutableSet<String>> setupShellEnvironment() { ImmutableMap.Builder<String, String> builder = new ImmutableMap.Builder<>(); for (Fragment fragment : fragments.values()) { fragment.setupShellEnvironment(builder); } // Shell environment variables specified via options take precedence over the // ones inherited from the fragments. In the long run, these fragments will // be replaced by appropriate default rc files anyway. Map<String, String> shellEnv = new TreeMap<>(builder.build()); for (Map.Entry<String, String> entry : options.actionEnvironment) { shellEnv.put(entry.getKey(), entry.getValue()); } Map<String, String> fixedShellEnv = new TreeMap<>(shellEnv); Set<String> variableShellEnv = new HashSet<>(); for (Map.Entry<String, String> entry : shellEnv.entrySet()) { if (entry.getValue() == null) { String key = entry.getKey(); fixedShellEnv.remove(key); variableShellEnv.add(key); } } return Pair.of(ImmutableMap.copyOf(fixedShellEnv), ImmutableSet.copyOf(variableShellEnv)); } /** * Sorts fragments by class name. This produces a stable order which, e.g., facilitates * consistent output from buildMneumonic. */ private static final Comparator lexicalFragmentSorter = new Comparator<Class<? extends Fragment>>() { @Override public int compare(Class<? extends Fragment> o1, Class<? extends Fragment> o2) { return o1.getName().compareTo(o2.getName()); } }; /** * Constructs a new BuildConfiguration instance. */ public BuildConfiguration(BlazeDirectories directories, Map<Class<? extends Fragment>, Fragment> fragmentsMap, BuildOptions buildOptions) { this.directories = directories; this.fragments = ImmutableSortedMap.copyOf(fragmentsMap, lexicalFragmentSorter); this.skylarkVisibleFragments = buildIndexOfSkylarkVisibleFragments(); this.buildOptions = buildOptions.clone(); this.actionsEnabled = buildOptions.enableActions(); this.options = buildOptions.get(Options.class); Map<String, String> testEnv = new TreeMap<>(); for (Map.Entry<String, String> entry : this.options.testEnvironment) { if (entry.getValue() != null) { testEnv.put(entry.getKey(), entry.getValue()); } } this.testEnvironment = ImmutableMap.copyOf(testEnv); // We can't use an ImmutableMap.Builder here; we need the ability to add entries with keys that // are already in the map so that the same define can be specified on the command line twice, // and ImmutableMap.Builder does not support that. Map<String, String> commandLineDefinesBuilder = new TreeMap<>(); for (Map.Entry<String, String> define : options.commandLineBuildVariables) { commandLineDefinesBuilder.put(define.getKey(), define.getValue()); } commandLineBuildVariables = ImmutableMap.copyOf(commandLineDefinesBuilder); this.mnemonic = buildMnemonic(); this.outputDirName = (options.outputDirectoryName != null) ? options.outputDirectoryName : mnemonic; this.outputDirectoryForMainRepository = OutputDirectory.OUTPUT.getRoot(RepositoryName.MAIN, outputDirName, directories); this.binDirectoryForMainRepository = OutputDirectory.BIN.getRoot(RepositoryName.MAIN, outputDirName, directories); this.includeDirectoryForMainRepository = OutputDirectory.INCLUDE.getRoot(RepositoryName.MAIN, outputDirName, directories); this.genfilesDirectoryForMainRepository = OutputDirectory.GENFILES.getRoot(RepositoryName.MAIN, outputDirName, directories); this.coverageDirectoryForMainRepository = OutputDirectory.COVERAGE.getRoot(RepositoryName.MAIN, outputDirName, directories); this.testlogsDirectoryForMainRepository = OutputDirectory.TESTLOGS.getRoot(RepositoryName.MAIN, outputDirName, directories); this.middlemanDirectoryForMainRepository = OutputDirectory.MIDDLEMAN.getRoot(RepositoryName.MAIN, outputDirName, directories); this.platformName = buildPlatformName(); this.shellExecutable = computeShellExecutable(); Pair<ImmutableMap<String, String>, ImmutableSet<String>> shellEnvironment = setupShellEnvironment(); this.localShellEnvironment = shellEnvironment.getFirst(); this.envVariables = shellEnvironment.getSecond(); this.transitiveOptionDetails = computeOptionsMap(buildOptions, fragments.values()); ImmutableMap.Builder<String, String> globalMakeEnvBuilder = ImmutableMap.builder(); for (Fragment fragment : fragments.values()) { fragment.addGlobalMakeVariables(globalMakeEnvBuilder); } globalMakeEnvBuilder.put("COMPILATION_MODE", options.compilationMode.toString()); /* * Attention! Document these in the build-encyclopedia */ // the bin directory and the genfiles directory // These variables will be used on Windows as well, so we need to make sure // that paths use the correct system file-separator. globalMakeEnvBuilder.put("BINDIR", getBinDirectory().getExecPath().getPathString()); globalMakeEnvBuilder.put("GENDIR", getGenfilesDirectory().getExecPath().getPathString()); globalMakeEnv = globalMakeEnvBuilder.build(); checksum = Fingerprint.md5Digest(buildOptions.computeCacheKey()); hashCode = computeHashCode(); } /** * Returns a copy of this configuration only including the given fragments (which the current * configuration is assumed to have). */ public BuildConfiguration clone( Set<Class<? extends BuildConfiguration.Fragment>> fragmentClasses, RuleClassProvider ruleClassProvider) { ClassToInstanceMap<Fragment> fragmentsMap = MutableClassToInstanceMap.create(); for (Fragment fragment : fragments.values()) { if (fragmentClasses.contains(fragment.getClass())) { fragmentsMap.put(fragment.getClass(), fragment); } } BuildOptions options = buildOptions.trim( getOptionsClasses(fragmentsMap.keySet(), ruleClassProvider)); BuildConfiguration newConfig = new BuildConfiguration(directories, fragmentsMap, options); newConfig.setConfigurationTransitions(this.transitions); return newConfig; } /** * Returns the config fragment options classes used by the given fragment types. */ public static Set<Class<? extends FragmentOptions>> getOptionsClasses( Iterable<Class<? extends Fragment>> fragmentClasses, RuleClassProvider ruleClassProvider) { Multimap<Class<? extends BuildConfiguration.Fragment>, Class<? extends FragmentOptions>> fragmentToRequiredOptions = ArrayListMultimap.create(); for (ConfigurationFragmentFactory fragmentLoader : ((ConfiguredRuleClassProvider) ruleClassProvider).getConfigurationFragments()) { fragmentToRequiredOptions.putAll(fragmentLoader.creates(), fragmentLoader.requiredOptions()); } Set<Class<? extends FragmentOptions>> options = new HashSet<>(); for (Class<? extends BuildConfiguration.Fragment> fragmentClass : fragmentClasses) { options.addAll(fragmentToRequiredOptions.get(fragmentClass)); } return options; } private ImmutableMap<String, Class<? extends Fragment>> buildIndexOfSkylarkVisibleFragments() { ImmutableMap.Builder<String, Class<? extends Fragment>> builder = ImmutableMap.builder(); for (Class<? extends Fragment> fragmentClass : fragments.keySet()) { String name = SkylarkModule.Resolver.resolveName(fragmentClass); if (name != null) { builder.put(name, fragmentClass); } } return builder.build(); } /** * Retrieves the {@link TransitiveOptionDetails} containing data on this configuration's options. * * @see BuildConfigurationOptionDetails */ TransitiveOptionDetails getTransitiveOptionDetails() { return transitiveOptionDetails; } /** Computes and returns the {@link TransitiveOptionDetails} for this configuration. */ private static TransitiveOptionDetails computeOptionsMap( BuildOptions buildOptions, Iterable<Fragment> fragments) { // Collect from our fragments "alternative defaults" for options where the default // should be something other than what's specified in Option.defaultValue. Map<String, Object> lateBoundDefaults = Maps.newHashMap(); for (Fragment fragment : fragments) { lateBoundDefaults.putAll(fragment.lateBoundOptionDefaults()); } return TransitiveOptionDetails.forOptionsWithDefaults( buildOptions.getOptions(), lateBoundDefaults); } private String buildMnemonic() { // See explanation at declaration for outputRoots. String platformSuffix = (options.platformSuffix != null) ? options.platformSuffix : ""; ArrayList<String> nameParts = new ArrayList<>(); for (Fragment fragment : fragments.values()) { nameParts.add(fragment.getOutputDirectoryName()); } nameParts.add(getCompilationMode() + platformSuffix); return Joiner.on('-').skipNulls().join(nameParts); } private String buildPlatformName() { StringBuilder platformNameBuilder = new StringBuilder(); for (Fragment fragment : fragments.values()) { platformNameBuilder.append(fragment.getPlatformName()); } return platformNameBuilder.toString(); } /** * Set the outgoing configuration transitions. During the lifetime of a given build configuration, * this must happen exactly once, shortly after the configuration is created. */ public void setConfigurationTransitions(Transitions transitions) { // TODO(bazel-team): This method makes the object mutable - get rid of it. Dynamic // configurations should eventually make this obsolete. Preconditions.checkNotNull(transitions); Preconditions.checkState(this.transitions == null); this.transitions = transitions; } public Transitions getTransitions() { return transitions; } /** * Returns all configurations that can be reached from this configuration through any kind of * configuration transition. */ public synchronized Collection<BuildConfiguration> getAllReachableConfigurations() { if (allReachableConfigurations == null) { // This is needed for every configured target in skyframe m2, so we cache it. // We could alternatively make the corresponding dependencies into a skyframe node. this.allReachableConfigurations = computeAllReachableConfigurations(); } return allReachableConfigurations; } /** * Returns all configurations that can be reached from this configuration through any kind of * configuration transition. */ private Set<BuildConfiguration> computeAllReachableConfigurations() { Set<BuildConfiguration> result = new LinkedHashSet<>(); Queue<BuildConfiguration> queue = new LinkedList<>(); queue.add(this); while (!queue.isEmpty()) { BuildConfiguration config = queue.remove(); if (!result.add(config)) { continue; } config.getTransitions().addDirectlyReachableConfigurations(queue); } return result; } /** * Returns the new configuration after traversing a dependency edge with a given configuration * transition. * * @param transition the configuration transition * @return the new configuration * @throws IllegalArgumentException if the transition is a {@link SplitTransition} * * TODO(bazel-team): remove this as part of the static -> dynamic configuration migration */ public BuildConfiguration getConfiguration(Transition transition) { Preconditions.checkArgument(!(transition instanceof SplitTransition)); // The below call precondition-checks we're indeed using static configurations. return transitions.getStaticConfiguration(transition); } /** * Returns the new configurations after traversing a dependency edge with a given split * transition. * * @param transition the split configuration transition * @return the new configurations */ public List<BuildConfiguration> getSplitConfigurations(SplitTransition<?> transition) { return transitions.getSplitConfigurations(transition); } /** * A common interface for static vs. dynamic configuration implementations that allows * common configuration and transition-selection logic to seamlessly work with either. * * <p>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<BuildOptions> 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(Configurator<BuildOptions> configurator); /** * Calls {@link Transitions#configurationHook} on the current configuration(s) represent by * this instance. */ void applyConfigurationHook(Rule fromRule, Attribute attribute, Target toTarget); /** * 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<Dependency> getDependencies(Label label, AspectCollection aspects); } /** * Transition applier for static configurations. This implementation populates * {@link com.google.devtools.build.lib.analysis.Dependency} objects with * actual configurations. * * TODO(bazel-team): remove this when dynamic configurations are fully production-ready. */ private static class StaticTransitionApplier implements TransitionApplier { // The configuration(s) this applier applies to dep rules. Plural because of split transitions. // May change multiple times: the ultimate transition might be a sequence of intermediate // transitions. List<BuildConfiguration> toConfigurations; private StaticTransitionApplier(BuildConfiguration originalConfiguration) { this.toConfigurations = ImmutableList.<BuildConfiguration>of(originalConfiguration); } @Override public TransitionApplier create(BuildConfiguration configuration) { return new StaticTransitionApplier(configuration); } @Override public void applyTransition(Transition transition) { if (transition == Attribute.ConfigurationTransition.NULL) { toConfigurations = Lists.<BuildConfiguration>asList(null, new BuildConfiguration[0]); } else { ImmutableList.Builder<BuildConfiguration> newConfigs = ImmutableList.builder(); for (BuildConfiguration currentConfig : toConfigurations) { newConfigs.add(currentConfig.getTransitions().getStaticConfiguration(transition)); } toConfigurations = newConfigs.build(); } } @Override public void split(SplitTransition<BuildOptions> splitTransition) { // Split transitions can't be nested, so if we're splitting we must be doing it over // a single config. toConfigurations = Iterables.getOnlyElement(toConfigurations).getSplitConfigurations(splitTransition); } @Override public boolean isNull() { return toConfigurations.size() == 1 ? Iterables.getOnlyElement(toConfigurations) == null : false; } @Override public void applyAttributeConfigurator(Configurator<BuildOptions> configurator) { // There should only be one output configuration at this point: splits don't occur down // attributes with attribute configurators. We can lift this restriction later if desired. BuildOptions toOptions = Iterables.getOnlyElement(toConfigurations).getOptions(); applyTransition(configurator.apply(toOptions)); } @Override public void applyConfigurationHook(Rule fromRule, Attribute attribute, Target toTarget) { ImmutableList.Builder<BuildConfiguration> toConfigs = ImmutableList.builder(); for (BuildConfiguration currentConfig : toConfigurations) { // BuildConfigurationCollection.configurationHook can apply further transitions. We want // those transitions to only affect currentConfig (not everything in toConfigurations), so // we use a delegate bound to only that config. StaticTransitionApplier delegate = new StaticTransitionApplier(currentConfig); currentConfig.getTransitions().configurationHook(fromRule, attribute, toTarget, delegate); currentConfig = Iterables.getOnlyElement(delegate.toConfigurations); // Allow rule classes to override their own configurations. Rule associatedRule = toTarget.getAssociatedRule(); if (associatedRule != null) { @SuppressWarnings("unchecked") RuleClass.Configurator<BuildConfiguration, Rule> func = associatedRule.getRuleClassObject().<BuildConfiguration, Rule>getConfigurator(); currentConfig = func.apply(associatedRule, currentConfig); } toConfigs.add(currentConfig); } toConfigurations = toConfigs.build(); } @Override public Iterable<Dependency> getDependencies( Label label, AspectCollection aspects) { ImmutableList.Builder<Dependency> deps = ImmutableList.builder(); for (BuildConfiguration config : toConfigurations) { deps.add(config != null ? Dependency.withConfigurationAndAspects(label, config, aspects) : Dependency.withNullConfiguration(label)); } return deps.build(); } } /** * Transition applier for dynamic configurations. This implementation populates * {@link com.google.devtools.build.lib.analysis.Dependency} objects with * transitions that the caller subsequently creates configurations from. */ private static class DynamicTransitionApplier implements TransitionApplier { private final BuildOptions originalOptions; private final Transitions transitionsManager; private boolean splitApplied = false; // The transition this applier applies to dep rules. When multiple transitions are requested, // this is a ComposingSplitTransition, which encapsulates the sequence into a single instance // so calling code doesn't need special logic to support combinations. private Transition currentTransition = Attribute.ConfigurationTransition.NONE; private DynamicTransitionApplier(BuildConfiguration originalConfiguration) { this.originalOptions = originalConfiguration.getOptions(); this.transitionsManager = originalConfiguration.getTransitions(); } @Override public TransitionApplier create(BuildConfiguration configuration) { return new DynamicTransitionApplier(configuration); } /** * Returns true if the given transition should not be modifiable by subsequent ones, i.e. * once this transition is applied it's the final word on the output configuration. */ private static boolean isFinal(Transition transition) { return (transition == Attribute.ConfigurationTransition.NULL || transition == HostTransition.INSTANCE); } @Override public void applyTransition(Transition transitionToApply) { currentTransition = composeTransitions(currentTransition, transitionToApply); } /** * Composes two transitions together efficiently. */ private Transition composeTransitions(Transition transition1, Transition transition2) { if (isFinal(transition1)) { return transition1; } else if (transition2 == Attribute.ConfigurationTransition.NONE) { return transition1; } else if (transition2 == Attribute.ConfigurationTransition.NULL) { // A NULL transition can just replace earlier transitions: no need to cfpose them. return Attribute.ConfigurationTransition.NULL; } else if (transition2 == Attribute.ConfigurationTransition.HOST) { // A HOST transition can just replace earlier transitions: no need to compose them. // But it also improves performance: host transitions are common, and // ConfiguredTargetFunction has special optimized logic to handle them. If they were buried // in the last segment of a ComposingSplitTransition, those optimizations wouldn't trigger. return HostTransition.INSTANCE; } // TODO(gregce): remove this dynamic transition mapping when static configs are removed. Transition dynamicTransition = (transition2 instanceof PatchTransition) ? transition2 : transitionsManager.getDynamicTransition(transition2); return transition1 == Attribute.ConfigurationTransition.NONE ? dynamicTransition : new ComposingSplitTransition(transition1, dynamicTransition); } @Override // TODO(gregce): fold this into applyTransition during the static config code removal cleanup public void split(SplitTransition<BuildOptions> splitTransition) { // This "single split" check doesn't come from any design restriction. Its purpose is to // protect against runaway graph explosion, e.g. applying split[1,2,3] -> split[4,5,6] -> ... // and getting 3^n versions of a dep. So it's fine to loosen or lift this restriction // for a principled use case. Preconditions.checkState(!splitApplied, "dependency edges may apply at most one split transition"); Preconditions.checkState(currentTransition != Attribute.ConfigurationTransition.NULL, "cannot apply splits after null transitions (null transitions are expected to be final)"); Preconditions.checkState(currentTransition != HostTransition.INSTANCE, "cannot apply splits after host transitions (host transitions are expected to be final)"); currentTransition = currentTransition == Attribute.ConfigurationTransition.NONE ? splitTransition : new ComposingSplitTransition(currentTransition, splitTransition); splitApplied = true; } @Override public boolean isNull() { return currentTransition == Attribute.ConfigurationTransition.NULL; } /** * A {@link PatchTransition} that applies an attribute configurator over some input options * to determine which transition to use, then applies that transition over those options * for the final output. */ private static final class AttributeConfiguratorTransition implements PatchTransition { private final Configurator<BuildOptions> configurator; AttributeConfiguratorTransition(Configurator<BuildOptions> configurator) { this.configurator = configurator; } @Override public BuildOptions apply(BuildOptions options) { return Iterables.getOnlyElement( ComposingSplitTransition.apply(options, configurator.apply(options))); } @Override public boolean defaultsToSelf() { return false; } } /** * Unlike the static config version, this one can be composed with arbitrary transitions * (including splits). */ @Override public void applyAttributeConfigurator(Configurator<BuildOptions> configurator) { if (isFinal(currentTransition)) { return; } currentTransition = composeTransitions(currentTransition, new AttributeConfiguratorTransition(configurator)); } @Override public void applyConfigurationHook(Rule fromRule, Attribute attribute, Target toTarget) { if (isFinal(currentTransition)) { return; } transitionsManager.configurationHook(fromRule, attribute, toTarget, this); Rule associatedRule = toTarget.getAssociatedRule(); RuleTransitionFactory transitionFactory = associatedRule.getRuleClassObject().getTransitionFactory(); if (transitionFactory != null) { PatchTransition ruleClassTransition = (PatchTransition) transitionFactory.buildTransitionFor(associatedRule); if (ruleClassTransition != null) { if (currentTransition == ConfigurationTransition.NONE) { currentTransition = ruleClassTransition; } else { currentTransition = new ComposingSplitTransition(ruleClassTransition, currentTransition); } } } // We don't support rule class configurators (which may need intermediate configurations to // apply). 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. if (associatedRule != null) { @SuppressWarnings("unchecked") RuleClass.Configurator<?, ?> func = associatedRule.getRuleClassObject().getConfigurator(); Verify.verify(func == RuleClass.NO_CHANGE || func.getCategory().equals("lipo")); } } @Override public Iterable<Dependency> getDependencies( Label label, AspectCollection aspects) { return ImmutableList.of( isNull() // We can trivially set the final value for null-configured targets now. This saves // us from having to recreate a new Dependency object for the final value later. Since // there are lots of null-configured targets (e.g. all source files), this can add up // over the course of a build. ? Dependency.withNullConfiguration(label) : Dependency.withTransitionAndAspects(label, currentTransition, aspects)); } } /** * Returns the {@link TransitionApplier} that should be passed to {#evaluateTransition} calls. */ public TransitionApplier getTransitionApplier() { return useDynamicConfigurations() ? new DynamicTransitionApplier(this) : new StaticTransitionApplier(this); } /** * 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).getRuleClassObject().isConfigMatcher()) { transitionApplier.applyTransition(Attribute.ConfigurationTransition.NONE); // Unnecessary. return; } // TODO(gregce): make the below transitions composable (i.e. take away the "else" clauses) once // the static config code path is removed. They can be mixed freely with dynamic configurations. if (attribute.hasSplitConfigurationTransition()) { Preconditions.checkState(attribute.getConfigurator() == null); transitionApplier.split( (SplitTransition<BuildOptions>) attribute.getSplitTransition(fromRule)); } else { // III. Attributes determine configurations. The configuration of a prerequisite is determined // by the attribute. @SuppressWarnings("unchecked") Configurator<BuildOptions> configurator = (Configurator<BuildOptions>) attribute.getConfigurator(); if (configurator != null) { // TODO(gregce): remove this branch when static config logic is removed. Attribute // configurators can just be implemented as standard attribute transitions, via // applyTransition. transitionApplier.applyAttributeConfigurator(configurator); } else { transitionApplier.applyTransition(attribute.getConfigurationTransition()); } } transitionApplier.applyConfigurationHook(fromRule, attribute, toTarget); } /** * 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(RepositoryName repositoryName) { return repositoryName.equals(RepositoryName.MAIN) ? outputDirectoryForMainRepository : OutputDirectory.OUTPUT.getRoot(repositoryName, outputDirName, directories); } /** * Returns the bin directory for this build configuration. */ @SkylarkCallable(name = "bin_dir", structField = true, documented = false) @Deprecated public Root getBinDirectory() { return getBinDirectory(RepositoryName.MAIN); } /** * TODO(kchodorow): This (and the other get*Directory functions) won't work with external * repositories without changes to how ArtifactFactory resolves derived roots. This is not an * issue right now because it only effects Blaze's include scanning (internal) and Bazel's * repositories (external) but will need to be fixed. */ public Root getBinDirectory(RepositoryName repositoryName) { return repositoryName.equals(RepositoryName.MAIN) ? binDirectoryForMainRepository : OutputDirectory.BIN.getRoot(repositoryName, outputDirName, directories); } /** * 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(RepositoryName repositoryName) { return repositoryName.equals(RepositoryName.MAIN) ? includeDirectoryForMainRepository : OutputDirectory.INCLUDE.getRoot(repositoryName, outputDirName, directories); } /** * Returns the genfiles directory for this build configuration. */ @SkylarkCallable(name = "genfiles_dir", structField = true, documented = false) @Deprecated public Root getGenfilesDirectory() { return getGenfilesDirectory(RepositoryName.MAIN); } public Root getGenfilesDirectory(RepositoryName repositoryName) { return repositoryName.equals(RepositoryName.MAIN) ? genfilesDirectoryForMainRepository : OutputDirectory.GENFILES.getRoot(repositoryName, outputDirName, directories); } /** * 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(RepositoryName repositoryName) { return repositoryName.equals(RepositoryName.MAIN) ? coverageDirectoryForMainRepository : OutputDirectory.COVERAGE.getRoot(repositoryName, outputDirName, directories); } /** * Returns the testlogs directory for this build configuration. */ public Root getTestLogsDirectory(RepositoryName repositoryName) { return repositoryName.equals(RepositoryName.MAIN) ? testlogsDirectoryForMainRepository : OutputDirectory.TESTLOGS.getRoot(repositoryName, outputDirName, directories); } /** * 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(RepositoryName repositoryName) { return repositoryName.equals(RepositoryName.MAIN) ? middlemanDirectoryForMainRepository : OutputDirectory.MIDDLEMAN.getRoot(repositoryName, outputDirName, directories); } public boolean getAllowRuntimeDepsOnNeverLink() { return options.allowRuntimeDepsOnNeverLink; } public boolean isStrictFilesets() { return options.strictFilesets; } public List<Label> getPlugins() { return options.pluginList; } /** * Returns the configuration-dependent string for this configuration. This is also the name of the * configuration's base output directory unless {@link Options#outputDirectoryName} overrides it. */ public String getMnemonic() { return mnemonic; } @Override public String toString() { return checksum(); } @SkylarkCallable( name = "default_shell_env", structField = true, doc = "A dictionary representing the static local shell environment. It maps variables " + "to their values (strings)." ) /** * Return the "fixed" part of the actions' environment variables. * * <p>An action's full set of environment variables consist of a "fixed" part and of a "variable" * part. The "fixed" variables are independent of the Bazel client's own environment, and are * returned by this function. The "variable" ones are inherited from the Bazel client's own * environment, and are returned by {@link getVariableShellEnvironment}. * * <p>Since values of the "fixed" variables are already known at analysis phase, it is returned * here as a map. */ public ImmutableMap<String, String> getLocalShellEnvironment() { return localShellEnvironment; } /** * Return the "variable" part of the actions' environment variables. * * <p>An action's full set of environment variables consist of a "fixed" part and of a "variable" * part. The "fixed" variables are independent of the Bazel client's own environment, and are * returned by {@link #getLocalShellEnvironment}. The "variable" ones are inherited from the Bazel * client's own environment, and are returned by this function. * * <p>The values of the "variable" variables are tracked in Skyframe via the {@link * com.google.devtools.build.lib.skyframe.SkyFunctions.CLIENT_ENVIRONMENT_VARIABLE} skyfunction. * This method only returns the names of those variables to be inherited, if set in the client's * environment. (Variables where the name is not returned in this set should not be taken from the * client environment.) */ public ImmutableSet<String> getVariableShellEnvironment() { return envVariables; } /** * Returns the path to sh. */ public PathFragment getShellExecutable() { return shellExecutable; } /** * Returns a regex-based instrumentation filter instance that used to match label * names to identify targets to be instrumented in the coverage mode. */ public RegexFilter getInstrumentationFilter() { return options.instrumentationFilter; } /** * Returns a boolean of whether to include targets created by *_test rules in the set of targets * matched by --instrumentation_filter. If this is false, all test targets are excluded from * instrumentation. */ public boolean shouldInstrumentTestTargets() { return options.instrumentTestTargets; } /** * Returns a new, unordered mapping of names to values of "Make" variables defined by this * configuration. * * <p>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. * * <p>Command-line definitions of make enviroments override variables defined by * {@code Fragment.addGlobalMakeVariables()}. */ public Map<String, String> getMakeEnvironment() { Map<String, String> makeEnvironment = new HashMap<>(); makeEnvironment.putAll(globalMakeEnv); makeEnvironment.putAll(commandLineBuildVariables); 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<String, String> getCommandLineBuildVariables() { return commandLineBuildVariables; } /** * Returns the global defaults for this configuration for the Make environment. */ public Map<String, String> getGlobalMakeEnvironment() { return globalMakeEnv; } /** * 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 extends Fragment> T getFragment(Class<T> clazz) { return clazz.cast(fragments.get(clazz)); } /** * Returns true if the requested configuration fragment is present. */ public <T extends Fragment> boolean hasFragment(Class<T> clazz) { return getFragment(clazz) != null; } /** * Returns true if all requested configuration fragment are present (this may be slow). */ public boolean hasAllFragments(Set<Class<?>> fragmentClasses) { for (Class<?> fragmentClass : fragmentClasses) { if (!hasFragment(fragmentClass.asSubclass(Fragment.class))) { return false; } } return true; } /** * Which fragments does this configuration contain? */ public Set<Class<? extends Fragment>> 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; } /** * Returns if we are building external runfiles symlinks using the old-style structure. */ public boolean legacyExternalRunfiles() { return options.legacyExternalRunfiles; } public boolean getCheckFilesetDependenciesRecursively() { return options.checkFilesetDependenciesRecursively; } public boolean getSkyframeNativeFileset() { return options.skyframeNativeFileset; } public List<String> 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. */ @SkylarkCallable( name = "test_env", structField = true, doc = "A dictionary containing user-specified test environment variables and their values, " + "as set by the --test_env options." ) public ImmutableMap<String, String> 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 for this run. Note that this " + "does not compute whether a specific rule should be instrumented for code coverage " + "data collection. For that, see the <a href=\"ctx.html#coverage_instrumented\"><code>" + "ctx.coverage_instrumented</code></a> function.") public boolean isCodeCoverageEnabled() { return options.collectCodeCoverage; } public boolean isLLVMCoverageMapFormatEnabled() { return options.useLLVMCoverageMapFormat; } /** If false, AnalysisEnviroment doesn't register any actions created by the ConfiguredTarget. */ 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<Label> getActionListeners() { return isActionsEnabled() ? options.actionListeners : ImmutableList.<Label>of(); } /** * Returns whether FileWriteAction may transparently compress its contents in the analysis phase * to save memory. Semantics are not affected. */ public FileWriteAction.Compression transparentCompression() { return FileWriteAction.Compression.fromBoolean(options.transparentCompression); } /** * Returns whether we should use dynamically instantiated build configurations vs. static * configurations (e.g. predefined in {@link * com.google.devtools.build.lib.analysis.ConfigurationCollectionFactory}). */ public boolean useDynamicConfigurations() { return options.useDynamicConfigurations != Options.DynamicConfigsMode.OFF; } /** * Returns whether we should trim dynamic configurations to only include the fragments needed * to correctly analyze a rule. */ public boolean trimConfigurations() { return options.useDynamicConfigurations == Options.DynamicConfigsMode.ON; } /** * Returns compilation mode. */ public CompilationMode getCompilationMode() { return options.compilationMode; } /** Returns the cache key of the build options used to create this configuration. */ public final String checksum() { return checksum; } /** Returns a copy of the build configuration options for this configuration. */ public BuildOptions cloneOptions() { BuildOptions clone = buildOptions.clone(); return clone; } /** * Returns the actual options reference used by this configuration. * * <p><b>Be very careful using this method.</b> 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. * * <p>Do not use this method without careful review with other Bazel developers.. */ public BuildOptions getOptions() { return buildOptions; } public ListMultimap<String, Label> getAllLabels() { return buildOptions.getAllLabels(); } public String getCpu() { return options.cpu; } @VisibleForTesting public String getHostCpu() { return options.hostCpu; } public boolean runfilesEnabled() { switch (options.enableRunfiles) { case YES: return true; case NO: return false; default: return OS.getCurrent() != OS.WINDOWS; } } public boolean buildPythonZip() { switch (options.buildPythonZip) { case YES: return true; case NO: return false; default: return OS.getCurrent() == OS.WINDOWS; } } /** * Collects executables defined by fragments. */ private PathFragment computeShellExecutable() { PathFragment result = null; for (Fragment fragment : fragments.values()) { if (fragment.getShellExecutable() != null) { Verify.verify(result == null); result = fragment.getShellExecutable(); } } return result; } /** * See {@code BuildConfigurationCollection.Transitions.getArtifactOwnerConfiguration()}. */ public BuildConfiguration getArtifactOwnerConfiguration() { // Dynamic configurations inherit transitions objects from other configurations exclusively // for use of Transitions.getDynamicTransition. 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 the list of default features used for all packages. */ public List<String> 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<Label> getTargetEnvironments() { return options.targetEnvironments; } public Class<? extends Fragment> getSkylarkFragmentByName(String name) { return skylarkVisibleFragments.get(name); } public ImmutableCollection<String> getSkylarkFragmentNames() { return skylarkVisibleFragments.keySet(); } /** * Returns an extra transition that should apply to top-level targets in this * configuration. Returns null if no transition is needed. */ @Nullable public PatchTransition topLevelConfigurationHook(Target toTarget) { PatchTransition currentTransition = null; for (Fragment fragment : fragments.values()) { PatchTransition fragmentTransition = fragment.topLevelConfigurationHook(toTarget); if (fragmentTransition == null) { continue; } else if (currentTransition == null) { currentTransition = fragmentTransition; } else { currentTransition = new ComposingPatchTransition(currentTransition, fragmentTransition); } } return currentTransition; } @Override public BuildEventId getEventId() { return BuildEventId.configurationId(checksum()); } @Override public Collection<BuildEventId> getChildrenEvents() { return ImmutableList.of(); } @Override public BuildEventStreamProtos.BuildEvent asStreamProto(BuildEventConverters converters) { return GenericBuildEvent.protoChaining(this) .setConfiguration( BuildEventStreamProtos.Configuration.newBuilder() .setMnemonic(getMnemonic()) .setPlatformName(getPlatformName()) .setCpu(getCpu()) .build()) .build(); } }