// 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.Splitter; import com.google.common.base.Suppliers; 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.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.MutableClassToInstanceMap; import com.google.devtools.build.lib.actions.ActionEnvironment; import com.google.devtools.build.lib.actions.ArtifactRoot; import com.google.devtools.build.lib.actions.BuildConfigurationEvent; import com.google.devtools.build.lib.actions.CommandLines.CommandLineLimits; import com.google.devtools.build.lib.analysis.BlazeDirectories; import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider; import com.google.devtools.build.lib.analysis.actions.FileWriteAction; import com.google.devtools.build.lib.buildeventstream.BuildEventId; import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos; 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.concurrent.BlazeInterners; import com.google.devtools.build.lib.events.Event; import com.google.devtools.build.lib.events.EventHandler; import com.google.devtools.build.lib.packages.RuleClassProvider; import com.google.devtools.build.lib.packages.TestTimeout; import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec; import com.google.devtools.build.lib.skylarkbuildapi.BuildConfigurationApi; import com.google.devtools.build.lib.skylarkinterface.SkylarkInterfaceUtils; import com.google.devtools.build.lib.skylarkinterface.SkylarkModule; import com.google.devtools.build.lib.util.OS; 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.OptionDocumentationCategory; import com.google.devtools.common.options.OptionEffectTag; import com.google.devtools.common.options.OptionMetadataTag; import com.google.devtools.common.options.OptionsParsingException; import com.google.devtools.common.options.TriState; import java.time.Duration; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.TreeMap; import java.util.function.Supplier; import javax.annotation.Nullable; /** * Instances of BuildConfiguration represent a collection of context information which may affect a * build (for example: the target platform for compilation, or whether or not debug tables are * required). In fact, all "environmental" information (e.g. from the tool's command-line, as * opposed to the BUILD file) that can affect the output of any build tool should be explicitly * represented in the BuildConfiguration instance. * *

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

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

Instances of BuildConfiguration are canonical: * *

c1.equals(c2) <=> c1==c2.
*/ // TODO(janakr): If overhead of fragments class names is too high, add constructor that just takes // fragments and gets names from them. @AutoCodec public class BuildConfiguration implements BuildConfigurationApi { /** * Sorts fragments by class name. This produces a stable order which, e.g., facilitates consistent * output from buildMnemonic. */ @AutoCodec public static final Comparator> lexicalFragmentSorter = Comparator.comparing(Class::getName); private static final Interner, Fragment>> fragmentsInterner = BlazeInterners.newWeakInterner(); /** Compute the default shell environment for actions from the command line options. */ public interface ActionEnvironmentProvider { ActionEnvironment getActionEnvironment(BuildOptions options); } /** * An interface for language-specific configurations. * *

All implementations must be immutable and communicate this as clearly as possible (e.g. * declare {@link ImmutableList} signatures on their interfaces vs. {@link List}). This is because * fragment instances may be shared across configurations. */ public abstract static class Fragment { /** * Validates the options for this Fragment. Issues warnings for the * use of deprecated options, and warnings or errors for any option settings * that conflict. */ @SuppressWarnings("unused") public void reportInvalidOptions(EventHandler reporter, BuildOptions buildOptions) { } /** * Adds mapping of names to values of "Make" variables defined by this configuration. */ @SuppressWarnings("unused") public void addGlobalMakeVariables(ImmutableMap.Builder globalMakeEnvBuilder) { } /** * 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; } /** * 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 lateBoundOptionDefaults() { return ImmutableMap.of(); } } public static final Label convertOptionsLabel(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, ImmutableMap.of()); } catch (LabelSyntaxException e) { throw new OptionsParsingException(e.getMessage()); } } /** * A converter from strings to Labels. */ public static class LabelConverter implements Converter

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

IMPORTANT: when adding new options, be sure to consider whether those values should be * propagated to the host configuration or not. * *

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 = "experimental_separate_genfiles_directory", defaultValue = "true", documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, effectTags = {OptionEffectTag.AFFECTS_OUTPUTS}, metadataTags = {OptionMetadataTag.EXPERIMENTAL}, help = "Whether to have a separate genfiles directory or fold it into the bin directory" ) public boolean separateGenfilesDirectory; @Option( name = "define", converter = Converters.AssignmentConverter.class, defaultValue = "", allowMultiple = true, documentationCategory = OptionDocumentationCategory.OUTPUT_PARAMETERS, effectTags = {OptionEffectTag.CHANGES_INPUTS, OptionEffectTag.AFFECTS_OUTPUTS}, help = "Each --define option specifies an assignment for a build variable." ) public List> commandLineBuildVariables; @Option( name = "cpu", defaultValue = "", converter = AutoCpuConverter.class, documentationCategory = OptionDocumentationCategory.OUTPUT_PARAMETERS, effectTags = {OptionEffectTag.CHANGES_INPUTS, OptionEffectTag.AFFECTS_OUTPUTS}, help = "The target CPU." ) public String cpu; @Option( name = "min_param_file_size", defaultValue = "32768", documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, effectTags = { OptionEffectTag.LOADING_AND_ANALYSIS, OptionEffectTag.EXECUTION, OptionEffectTag.ACTION_COMMAND_LINES}, help = "Minimum command line length before creating a parameter file." ) public int minParamFileSize; @Option( name = "defer_param_files", defaultValue = "true", documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, effectTags = { OptionEffectTag.LOADING_AND_ANALYSIS, OptionEffectTag.EXECUTION, OptionEffectTag.ACTION_COMMAND_LINES }, help = "Whether to use deferred param files. When set, param files will not be " + "added to the action graph. Instead, they will be added as virtual action inputs " + "and written at the same time as the action executes.") public boolean deferParamFiles; @Option( name = "experimental_extended_sanity_checks", defaultValue = "false", documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, effectTags = { OptionEffectTag.BAZEL_INTERNAL_CONFIGURATION }, metadataTags = { OptionMetadataTag.EXPERIMENTAL }, 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 = "strict_filesets", defaultValue = "false", documentationCategory = OptionDocumentationCategory.INPUT_STRICTNESS, effectTags = {OptionEffectTag.BUILD_FILE_SEMANTICS, OptionEffectTag.EAGERNESS_TO_EXIT}, 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; @Option( name = "experimental_strict_fileset_output", defaultValue = "false", documentationCategory = OptionDocumentationCategory.EXECUTION_STRATEGY, effectTags = {OptionEffectTag.EXECUTION}, help = "If this option is enabled, filesets will treat all output artifacts as regular files. " + "They will not traverse directories or be sensitive to symlinks." ) public boolean strictFilesetOutput; @Option( name = "stamp", defaultValue = "false", documentationCategory = OptionDocumentationCategory.OUTPUT_PARAMETERS, effectTags = {OptionEffectTag.AFFECTS_OUTPUTS}, 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 // a value returned by InstrumentationFilterSupport.computeInstrumentationFilter. @Option( name = "instrumentation_filter", converter = RegexFilter.RegexFilterConverter.class, defaultValue = "-/javatests[/:],-/test/java[/:]", documentationCategory = OptionDocumentationCategory.OUTPUT_PARAMETERS, effectTags = {OptionEffectTag.AFFECTS_OUTPUTS}, 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", documentationCategory = OptionDocumentationCategory.OUTPUT_PARAMETERS, effectTags = {OptionEffectTag.AFFECTS_OUTPUTS}, 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 = "", converter = AutoCpuConverter.class, documentationCategory = OptionDocumentationCategory.OUTPUT_PARAMETERS, effectTags = {OptionEffectTag.CHANGES_INPUTS, OptionEffectTag.AFFECTS_OUTPUTS}, help = "The host CPU." ) public String hostCpu; @Option( name = "compilation_mode", abbrev = 'c', converter = CompilationMode.Converter.class, defaultValue = "fastbuild", documentationCategory = OptionDocumentationCategory.OUTPUT_PARAMETERS, effectTags = { OptionEffectTag.AFFECTS_OUTPUTS, OptionEffectTag.ACTION_COMMAND_LINES }, help = "Specify the mode the binary will be built in. Values: 'fastbuild', 'dbg', 'opt'." ) public CompilationMode compilationMode; @Option( name = "host_compilation_mode", converter = CompilationMode.Converter.class, defaultValue = "opt", documentationCategory = OptionDocumentationCategory.OUTPUT_PARAMETERS, effectTags = { OptionEffectTag.AFFECTS_OUTPUTS, OptionEffectTag.ACTION_COMMAND_LINES }, help = "Specify the mode the tools used during the build will be built in. Values: " + "'fastbuild', 'dbg', 'opt'." ) public CompilationMode hostCompilationMode; /** * This option is used internally to set output directory name of the host 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", documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, effectTags = { OptionEffectTag.LOSES_INCREMENTAL_STATE, OptionEffectTag.AFFECTS_OUTPUTS, OptionEffectTag.LOADING_AND_ANALYSIS }, metadataTags = { OptionMetadataTag.INTERNAL } ) public String outputDirectoryName; @Option( name = "platform_suffix", defaultValue = "null", documentationCategory = OptionDocumentationCategory.OUTPUT_PARAMETERS, effectTags = { OptionEffectTag.LOSES_INCREMENTAL_STATE, OptionEffectTag.AFFECTS_OUTPUTS, OptionEffectTag.LOADING_AND_ANALYSIS }, 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. @Option( name = "test_env", converter = Converters.OptionalAssignmentConverter.class, allowMultiple = true, defaultValue = "", documentationCategory = OptionDocumentationCategory.TESTING, effectTags = {OptionEffectTag.TEST_RUNNER}, 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> testEnvironment; @Option( name = "test_timeout", defaultValue = "-1", converter = TestTimeout.TestTimeoutConverter.class, documentationCategory = OptionDocumentationCategory.TESTING, effectTags = {OptionEffectTag.UNKNOWN}, help = "Override the default test timeout values for test timeouts (in secs). If a single " + "positive integer value is specified it will override all categories. If 4 " + "comma-separated integers are specified, they will override the timeouts for " + "short, moderate, long and eternal (in that order). In either form, a value of " + "-1 tells blaze to use its default timeouts for that category.") public Map testTimeout; // 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 = "", documentationCategory = OptionDocumentationCategory.OUTPUT_PARAMETERS, effectTags = {OptionEffectTag.ACTION_COMMAND_LINES}, 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> actionEnvironment; @Option( name = "collect_code_coverage", defaultValue = "false", documentationCategory = OptionDocumentationCategory.OUTPUT_PARAMETERS, effectTags = {OptionEffectTag.AFFECTS_OUTPUTS}, 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 = "experimental_java_coverage", defaultValue = "false", documentationCategory = OptionDocumentationCategory.OUTPUT_PARAMETERS, effectTags = {OptionEffectTag.AFFECTS_OUTPUTS}, help = "If true Bazel will use a new way of computing code coverage for java targets." ) public boolean experimentalJavaCoverage; @Option( name = "build_runfile_manifests", defaultValue = "true", documentationCategory = OptionDocumentationCategory.OUTPUT_SELECTION, effectTags = {OptionEffectTag.AFFECTS_OUTPUTS}, help = "If true, write runfiles manifests for all targets. " + "If false, omit them." ) public boolean buildRunfilesManifests; @Option( name = "build_runfile_links", defaultValue = "true", documentationCategory = OptionDocumentationCategory.OUTPUT_SELECTION, effectTags = {OptionEffectTag.AFFECTS_OUTPUTS}, 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", documentationCategory = OptionDocumentationCategory.OUTPUT_SELECTION, effectTags = {OptionEffectTag.AFFECTS_OUTPUTS}, 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 = "check_fileset_dependencies_recursively", defaultValue = "true", documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, deprecationWarning = "This flag is a no-op and fileset dependencies are always checked " + "to ensure correctness of builds.", effectTags = {OptionEffectTag.AFFECTS_OUTPUTS} ) public boolean checkFilesetDependenciesRecursively; @Option( name = "experimental_skyframe_native_filesets", defaultValue = "true", documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, effectTags = {OptionEffectTag.BAZEL_INTERNAL_CONFIGURATION}, deprecationWarning = "This flag is a no-op and skyframe-native-filesets is always true." ) public boolean skyframeNativeFileset; @Option( name = "run_under", defaultValue = "null", converter = RunUnderConverter.class, documentationCategory = OptionDocumentationCategory.OUTPUT_PARAMETERS, effectTags = {OptionEffectTag.ACTION_COMMAND_LINES}, 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", documentationCategory = OptionDocumentationCategory.BUILD_TIME_OPTIMIZATION, effectTags = { OptionEffectTag.LOSES_INCREMENTAL_STATE, OptionEffectTag.BAZEL_INTERNAL_CONFIGURATION, OptionEffectTag.LOADING_AND_ANALYSIS, }, 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", documentationCategory = OptionDocumentationCategory.INPUT_STRICTNESS, effectTags = {OptionEffectTag.BUILD_FILE_SEMANTICS}, 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", documentationCategory = OptionDocumentationCategory.INPUT_STRICTNESS, effectTags = {OptionEffectTag.BUILD_FILE_SEMANTICS}, 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", documentationCategory = OptionDocumentationCategory.INPUT_STRICTNESS, effectTags = { OptionEffectTag.BUILD_FILE_SEMANTICS }, 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 = "", converter = LabelListConverter.class, documentationCategory = OptionDocumentationCategory.OUTPUT_PARAMETERS, effectTags = {OptionEffectTag.EXECUTION}, metadataTags = {OptionMetadataTag.EXPERIMENTAL}, help = "Use action_listener to attach an extra_action to existing build actions." ) public List

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

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

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

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

The host configuration is special-cased: in order to guarantee that its output directory is * always separate from that of the target configuration, we simply pin it to "host". We do this * so that the build works even if the two configurations are too close (which is common) and so * that the path of artifacts in the host configuration is a bit more readable. */ @AutoCodec.VisibleForSerialization public 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; } @AutoCodec.VisibleForSerialization public ArtifactRoot getRoot( String outputDirName, BlazeDirectories directories, RepositoryName mainRepositoryName) { // e.g., execroot/repo1 Path execRoot = directories.getExecRoot(mainRepositoryName.strippedName()); // e.g., execroot/repo1/bazel-out/config/bin Path outputDir = execRoot.getRelative(directories.getRelativeOutputPath()) .getRelative(outputDirName); if (middleman) { return INTERNER.intern(ArtifactRoot.middlemanRoot(execRoot, outputDir)); } // e.g., [[execroot/repo1]/bazel-out/config/bin] return INTERNER.intern( ArtifactRoot.asDerivedRoot(execRoot, outputDir.getRelative(nameFragment))); } } 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 INTERNER = Interners.newWeakInterner(); // We precompute the roots for the main repository, since that's the common case. private final ArtifactRoot outputDirectoryForMainRepository; private final ArtifactRoot binDirectoryForMainRepository; private final ArtifactRoot includeDirectoryForMainRepository; private final ArtifactRoot genfilesDirectoryForMainRepository; private final ArtifactRoot coverageDirectoryForMainRepository; private final ArtifactRoot testlogsDirectoryForMainRepository; private final ArtifactRoot middlemanDirectoryForMainRepository; private final boolean separateGenfilesDirectory; /** * The global "make variables" such as "$(TARGET_CPU)"; these get applied to all rules analyzed in * this configuration. */ private final ImmutableMap globalMakeEnv; private final ActionEnvironment actionEnv; private final ActionEnvironment testEnv; private final ImmutableMap testTimeout; private final BuildOptions buildOptions; private final BuildOptions.OptionsDiffForReconstruction buildOptionsDiff; private final Options options; private final String mnemonic; private final ImmutableMap 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; private final Supplier buildEventSupplier; /** * Returns true if this configuration is semantically equal to the other, with * the possible exception that the other has fewer fragments. * *

This is useful for trimming: 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) && 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 (!(other instanceof BuildConfiguration)) { return false; } BuildConfiguration otherConfig = (BuildConfiguration) other; return fragments.values().equals(otherConfig.fragments.values()) && buildOptions.equals(otherConfig.buildOptions); } private int computeHashCode() { return Objects.hash(fragments, buildOptions.getOptions()); } public void describe(StringBuilder sb) { for (Fragment fragment : fragments.values()) { sb.append(fragment.getClass().getName()).append('\n'); } for (String s : buildOptions.toString().split(" ")) { sb.append(s).append('\n'); } } @Override public int hashCode() { return hashCode; } /** Returns map of all the fragments for this configuration. */ public ImmutableMap, Fragment> getFragmentsMap() { 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); } if (OS.getCurrent() == OS.WINDOWS && runfilesEnabled()) { reporter.handle(Event.error("building runfiles is not supported on Windows")); } if (options.outputDirectoryName != null) { reporter.handle(Event.error( "The internal '--output directory name' option cannot be used on the command line")); } } /** * Compute the test 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 ActionEnvironment setupTestEnvironment() { // We make a copy first to remove duplicate entries; last one wins. Map testEnv = new HashMap<>(); for (Map.Entry entry : options.testEnvironment) { testEnv.put(entry.getKey(), entry.getValue()); } return ActionEnvironment.split(testEnv); } private static ImmutableSortedMap, Fragment> makeFragmentsMap( Map, Fragment> fragmentsMap) { return fragmentsInterner.intern(ImmutableSortedMap.copyOf(fragmentsMap, lexicalFragmentSorter)); } /** Constructs a new BuildConfiguration instance. */ public BuildConfiguration( BlazeDirectories directories, Map, Fragment> fragmentsMap, BuildOptions buildOptions, BuildOptions.OptionsDiffForReconstruction buildOptionsDiff, ImmutableSet reservedActionMnemonics, ActionEnvironment actionEnvironment, String repositoryName) { this( directories, fragmentsMap, buildOptions, buildOptionsDiff, reservedActionMnemonics, actionEnvironment, RepositoryName.createFromValidStrippedName(repositoryName)); } @AutoCodec.VisibleForSerialization @AutoCodec.Instantiator BuildConfiguration( BlazeDirectories directories, Map, Fragment> fragmentsMap, BuildOptions buildOptions, BuildOptions.OptionsDiffForReconstruction buildOptionsDiff, ImmutableSet reservedActionMnemonics, ActionEnvironment actionEnvironment, RepositoryName mainRepositoryName) { this.directories = directories; this.fragments = makeFragmentsMap(fragmentsMap); this.fragmentClassSet = FragmentClassSet.of(this.fragments.keySet()); this.skylarkVisibleFragments = buildIndexOfSkylarkVisibleFragments(); this.buildOptions = buildOptions.clone(); this.buildOptionsDiff = buildOptionsDiff; this.options = buildOptions.get(Options.class); this.separateGenfilesDirectory = options.separateGenfilesDirectory; this.mainRepositoryName = mainRepositoryName; // 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 commandLineDefinesBuilder = new TreeMap<>(); for (Map.Entry 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(outputDirName, directories, mainRepositoryName); this.binDirectoryForMainRepository = OutputDirectory.BIN.getRoot(outputDirName, directories, mainRepositoryName); this.includeDirectoryForMainRepository = OutputDirectory.INCLUDE.getRoot(outputDirName, directories, mainRepositoryName); this.genfilesDirectoryForMainRepository = OutputDirectory.GENFILES.getRoot(outputDirName, directories, mainRepositoryName); this.coverageDirectoryForMainRepository = OutputDirectory.COVERAGE.getRoot(outputDirName, directories, mainRepositoryName); this.testlogsDirectoryForMainRepository = OutputDirectory.TESTLOGS.getRoot(outputDirName, directories, mainRepositoryName); this.middlemanDirectoryForMainRepository = OutputDirectory.MIDDLEMAN.getRoot(outputDirName, directories, mainRepositoryName); this.actionEnv = actionEnvironment; this.testEnv = setupTestEnvironment(); this.testTimeout = ImmutableMap.copyOf(options.testTimeout); this.transitiveOptionDetails = computeOptionsMap(buildOptions, fragments.values(), options.useLateBoundOptionDefaults); ImmutableMap.Builder globalMakeEnvBuilder = ImmutableMap.builder(); for (Fragment fragment : fragments.values()) { fragment.addGlobalMakeVariables(globalMakeEnvBuilder); } // TODO(configurability-team): Deprecate TARGET_CPU in favor of platforms. globalMakeEnvBuilder.put("TARGET_CPU", options.cpu); 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 = buildOptions.computeChecksum(); hashCode = computeHashCode(); this.reservedActionMnemonics = reservedActionMnemonics; this.buildEventSupplier = Suppliers.memoize(this::createBuildEvent); this.commandLineLimits = new CommandLineLimits(options.minParamFileSize); } /** * Returns a copy of this configuration only including the given fragments (which the current * configuration is assumed to have). */ public BuildConfiguration clone( FragmentClassSet fragmentClasses, RuleClassProvider ruleClassProvider, BuildOptions defaultBuildOptions) { ClassToInstanceMap fragmentsMap = MutableClassToInstanceMap.create(); for (Fragment fragment : fragments.values()) { if (fragmentClasses.fragmentClasses().contains(fragment.getClass())) { fragmentsMap.put(fragment.getClass(), fragment); } } BuildOptions options = buildOptions.trim( getOptionsClasses(fragmentsMap.keySet(), ruleClassProvider)); BuildConfiguration newConfig = new BuildConfiguration( directories, fragmentsMap, options, BuildOptions.diffForReconstruction(defaultBuildOptions, options), reservedActionMnemonics, actionEnv, mainRepositoryName.strippedName()); return newConfig; } /** * Returns the config fragment options classes used by the given fragment types. */ public static Set> getOptionsClasses( Iterable> fragmentClasses, RuleClassProvider ruleClassProvider) { Multimap, Class> fragmentToRequiredOptions = ArrayListMultimap.create(); for (ConfigurationFragmentFactory fragmentLoader : ((ConfiguredRuleClassProvider) ruleClassProvider).getConfigurationFragments()) { fragmentToRequiredOptions.putAll(fragmentLoader.creates(), fragmentLoader.requiredOptions()); } Set> options = new HashSet<>(); for (Class fragmentClass : fragmentClasses) { options.addAll(fragmentToRequiredOptions.get(fragmentClass)); } return options; } private ImmutableMap> buildIndexOfSkylarkVisibleFragments() { ImmutableMap.Builder> builder = ImmutableMap.builder(); for (Class fragmentClass : fragments.keySet()) { SkylarkModule module = SkylarkInterfaceUtils.getSkylarkModule(fragmentClass); if (module != null) { builder.put(module.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 fragments, boolean useLateBoundOptionDefaults) { // Collect from our fragments "alternative defaults" for options where the default // should be something other than what's specified in Option.defaultValue. Map lateBoundDefaults = Maps.newHashMap(); if (useLateBoundOptionDefaults) { 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 nameParts = new ArrayList<>(); for (Fragment fragment : fragments.values()) { nameParts.add(fragment.getOutputDirectoryName()); } nameParts.add(getCompilationMode() + platformSuffix); return Joiner.on('-').skipNulls().join(nameParts); } /** Returns the output directory for this build configuration. */ public ArtifactRoot getOutputDirectory(RepositoryName repositoryName) { return repositoryName.isMain() || repositoryName.equals(mainRepositoryName) ? outputDirectoryForMainRepository : OutputDirectory.OUTPUT.getRoot(outputDirName, directories, mainRepositoryName); } @Override public ArtifactRoot getBinDir() { return getBinDirectory(RepositoryName.MAIN); } /** Returns the bin directory for this build configuration. */ public ArtifactRoot 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 ArtifactRoot getBinDirectory(RepositoryName repositoryName) { return repositoryName.isMain() || repositoryName.equals(mainRepositoryName) ? binDirectoryForMainRepository : OutputDirectory.BIN.getRoot(outputDirName, directories, mainRepositoryName); } /** * 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 ArtifactRoot getIncludeDirectory(RepositoryName repositoryName) { return repositoryName.isMain() || repositoryName.equals(mainRepositoryName) ? includeDirectoryForMainRepository : OutputDirectory.INCLUDE.getRoot(outputDirName, directories, mainRepositoryName); } @Override public ArtifactRoot getGenfilesDir() { return getGenfilesDirectory(RepositoryName.MAIN); } /** Returns the genfiles directory for this build configuration. */ public ArtifactRoot getGenfilesDirectory() { return getGenfilesDirectory(RepositoryName.MAIN); } public ArtifactRoot getGenfilesDirectory(RepositoryName repositoryName) { if (!separateGenfilesDirectory) { return getBinDirectory(repositoryName); } return repositoryName.isMain() || repositoryName.equals(mainRepositoryName) ? genfilesDirectoryForMainRepository : OutputDirectory.GENFILES.getRoot(outputDirName, directories, mainRepositoryName); } /** * 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 ArtifactRoot getCoverageMetadataDirectory(RepositoryName repositoryName) { return repositoryName.isMain() || repositoryName.equals(mainRepositoryName) ? coverageDirectoryForMainRepository : OutputDirectory.COVERAGE.getRoot(outputDirName, directories, mainRepositoryName); } /** Returns the testlogs directory for this build configuration. */ public ArtifactRoot getTestLogsDirectory(RepositoryName repositoryName) { return repositoryName.isMain() || repositoryName.equals(mainRepositoryName) ? testlogsDirectoryForMainRepository : OutputDirectory.TESTLOGS.getRoot(outputDirName, directories, mainRepositoryName); } /** * 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. */ @Override 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 ArtifactRoot getMiddlemanDirectory(RepositoryName repositoryName) { return repositoryName.isMain() || repositoryName.equals(mainRepositoryName) ? middlemanDirectoryForMainRepository : OutputDirectory.MIDDLEMAN.getRoot(outputDirName, directories, mainRepositoryName); } public boolean isStrictFilesets() { return options.strictFilesets; } public boolean isStrictFilesetOutput() { return options.strictFilesetOutput; } public String getMainRepositoryName() { return mainRepositoryName.strippedName(); } /** * 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(); } public ActionEnvironment getActionEnvironment() { return actionEnv; } /** * Return the "fixed" part of the actions' environment variables. * *

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}. * *

Since values of the "fixed" variables are already known at analysis phase, it is returned * here as a map. */ @Override public ImmutableMap getLocalShellEnvironment() { return actionEnv.getFixedEnv(); } /** * Return the "variable" part of the actions' environment variables. * *

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. * *

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.) */ @Deprecated // Use getActionEnvironment instead. public ImmutableSet getVariableShellEnvironment() { return actionEnv.getInheritedEnv(); } /** * 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. * *

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

Command-line definitions of make enviroments override variables defined by * {@code Fragment.addGlobalMakeVariables()}. */ public Map getMakeEnvironment() { Map makeEnvironment = new HashMap<>(); makeEnvironment.putAll(globalMakeEnv); 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 ImmutableMap getCommandLineBuildVariables() { return commandLineBuildVariables; } /** * Returns the global defaults for this configuration for the Make environment. */ public ImmutableMap 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 getFragment(Class clazz) { return clazz.cast(fragments.get(clazz)); } /** * Returns true if the requested configuration fragment is present. */ public boolean hasFragment(Class clazz) { return getFragment(clazz) != null; } /** * Returns true if all requested configuration fragment are present (this may be slow). */ public boolean hasAllFragments(Set> fragmentClasses) { for (Class fragmentClass : fragmentClasses) { if (!hasFragment(fragmentClass.asSubclass(Fragment.class))) { return false; } } return true; } /** Which fragments does this configuration contain? */ public FragmentClassSet fragmentClasses() { return fragmentClassSet; } /** * 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 manifests for this configuration. */ public boolean buildRunfilesManifests() { return options.buildRunfilesManifests; } /** * 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; } /** * Returns user-specified test environment variables and their values, as set by the --test_env * options. */ @Override public ImmutableMap getTestEnv() { return testEnv.getFixedEnv(); } /** Returns test timeout mapping as set by --test_timeout options. */ public ImmutableMap getTestTimeout() { return testTimeout; } /** * Returns user-specified test environment variables and their values, as set by the * {@code --test_env} options. It is incomplete in that it is not a superset of the * {@link #getActionEnvironment}, but both have to be applied, with this one being applied after * the other, such that {@code --test_env} settings can override {@code --action_env} settings. */ // TODO(ulfjack): Just return the merged action and test action environment here? public ActionEnvironment getTestActionEnvironment() { return testEnv; } public CommandLineLimits getCommandLineLimits() { return commandLineLimits; } public boolean deferParamFiles() { return options.deferParamFiles; } @Override public boolean isCodeCoverageEnabled() { return options.collectCodeCoverage; } public boolean isExperimentalJavaCoverage() { return options.experimentalJavaCoverage; } public RunUnder getRunUnder() { return options.runUnder; } /** * Returns true if this is a host configuration. */ public boolean isHostConfiguration() { return options.isHost; } public boolean checkVisibility() { return options.checkVisibility; } public boolean checkLicenses() { return options.checkLicenses; } public boolean enforceConstraints() { return options.enforceConstraints; } public List

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

Do not use this method without careful review with other Bazel developers. */ public BuildOptions getOptions() { return buildOptions; } public BuildOptions.OptionsDiffForReconstruction getBuildOptionsDiff() { return buildOptionsDiff; } 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 enableWindowsExeLauncher() { return options.windowsExeLauncher; } /** * @return the list of default features used for all packages. */ public List getDefaultFeatures() { return options.defaultFeatures; } /** * Returns the "top-level" environment space, i.e. the set of environments all top-level * targets must be compatible with. An empty value implies no restrictions. */ public List