// 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.Verify;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ClassToInstanceMap;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.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.AspectDescriptor;
import com.google.devtools.build.lib.analysis.BlazeDirectories;
import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
import com.google.devtools.build.lib.analysis.Dependency;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.config.BuildConfigurationCollection.Transitions;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
import com.google.devtools.build.lib.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.Configurator;
import com.google.devtools.build.lib.packages.Attribute.SplitTransition;
import com.google.devtools.build.lib.packages.Attribute.Transition;
import com.google.devtools.build.lib.packages.InputFile;
import com.google.devtools.build.lib.packages.PackageGroup;
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.packages.RuleClass;
import com.google.devtools.build.lib.packages.RuleClassProvider;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.rules.test.TestActionBuilder;
import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
import com.google.devtools.build.lib.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.OptionsBase;
import com.google.devtools.common.options.OptionsParsingException;
import com.google.devtools.common.options.TriState;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.Set;
import java.util.TreeMap;
import javax.annotation.Nullable;

/**
 * Instances of BuildConfiguration represent a collection of context
 * information which may affect a build (for example: the target platform for
 * compilation, or whether or not debug tables are required).  In fact, all
 * "environmental" information (e.g. from the tool's command-line, as opposed
 * to the BUILD file) that can affect the output of any build tool should be
 * explicitly represented in the BuildConfiguration instance.
 *
 * <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 {
  /**
   * 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();
    }
  }

  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";
    }
  }

  /** 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;

    @Option(name = "min_param_file_size",
        defaultValue = "32768",
        category = "undocumented",
        help = "Minimum command line length before creating a parameter file.")
    public int minParamFileSize;

    @Option(name = "experimental_extended_sanity_checks",
        defaultValue = "false",
        category = "undocumented",
        help  = "Enables internal validation checks to make sure that configured target "
            + "implementations only access things they should. Causes a performance hit.")
    public boolean extendedSanityChecks;

    @Option(name = "experimental_allow_runtime_deps_on_neverlink",
        defaultValue = "true",
        category = "undocumented",
        help = "Flag to help transition from allowing to disallowing runtime_deps on neverlink"
            + " Java archives. The depot needs to be cleaned up to roll this out by default.")
    public boolean allowRuntimeDepsOnNeverLink;

    @Option(name = "strict_filesets",
        defaultValue = "false",
        category = "semantics",
        help = "If this option is enabled, filesets crossing package boundaries are reported "
            + "as errors. It does not work when check_fileset_dependencies_recursively is "
            + "disabled.")
    public boolean strictFilesets;

    // Plugins are build using the host config. To avoid cycles we just don't propagate
    // this option to the host config. If one day we decide to use plugins when building
    // host tools, we can improve this by (for example) creating a compiler configuration that is
    // used only for building plugins.
    @Option(name = "plugin",
        converter = LabelConverter.class,
        allowMultiple = true,
        defaultValue = "",
        category = "flags",
        help = "Plugins to use in the build. Currently works with java_plugin.")
    public List<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", // (Spaces => can't be specified on command line.)
        defaultValue = "null",
        category = "undocumented")
    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 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 = "microcoverage",
        defaultValue = "false",
        category = "testing",
        help = "If specified with coverage, Blaze will collect microcoverage (per test method "
            + "coverage) information during tests. Only targets that match "
            + "--instrumentation_filter will be affected. Usually this option should not be "
            + "specified directly - 'blaze coverage --microcoverage' command should be used "
            + "instead."
    )
    public boolean collectMicroCoverage;

    @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 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",
        category = "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;

    @Option(name = "is host configuration",
        defaultValue = "false",
        category = "undocumented",
        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 static 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,
    }

    /**
     * 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 = "off",
        category = "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",
        category = "undocumented",
        help = "Enable runfiles; off on Windows, on on other platforms"
    )
    public TriState enableRunfiles;

    @Option(
        name = "build_python_zip",
        defaultValue = "auto",
        category = "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));
    }
  }

  /**
   * All the output directories pertinent to a configuration.
   */
  private static final class OutputRoots implements Serializable {
    private final Root outputDirectory; // the configuration-specific output directory.
    private final Root binDirectory;
    private final Root genfilesDirectory;
    private final Root coverageMetadataDirectory; // for coverage-related metadata, artifacts, etc.
    private final Root testLogsDirectory;
    private final Root includeDirectory;
    private final Root middlemanDirectory;

    private OutputRoots(BlazeDirectories directories, String outputDirName) {
      Path execRoot = directories.getExecRoot();
      // configuration-specific output tree
      Path outputDir = directories.getOutputPath().getRelative(outputDirName);
      this.outputDirectory = Root.asDerivedRoot(execRoot, outputDir, true);

      // specific subdirs under outputDirectory
      this.binDirectory = Root
          .asDerivedRoot(execRoot, outputDir.getRelative("bin"), true);
      this.genfilesDirectory = Root.asDerivedRoot(
          execRoot, outputDir.getRelative("genfiles"), true);
      this.coverageMetadataDirectory = Root.asDerivedRoot(execRoot,
          outputDir.getRelative("coverage-metadata"), true);
      this.testLogsDirectory = Root.asDerivedRoot(
          execRoot, outputDir.getRelative("testlogs"), true);
      this.includeDirectory = Root.asDerivedRoot(
          execRoot, outputDir.getRelative(BlazeDirectories.RELATIVE_INCLUDE_DIR), true);
      this.middlemanDirectory = Root.middlemanRoot(execRoot, outputDir, true);
    }

    @Override
    public boolean equals(Object o) {
      if (o == this) {
        return true;
      }
      if (!(o instanceof OutputRoots)) {
        return false;
      }
      OutputRoots other = (OutputRoots) o;
      return outputDirectory.equals(other.outputDirectory)
          && binDirectory.equals(other.binDirectory)
          && genfilesDirectory.equals(other.genfilesDirectory)
          && coverageMetadataDirectory.equals(other.coverageMetadataDirectory)
          && testLogsDirectory.equals(other.testLogsDirectory)
          && includeDirectory.equals(other.includeDirectory);
    }

    @Override
    public int hashCode() {
      return Objects.hash(outputDirectory, binDirectory, genfilesDirectory,
          coverageMetadataDirectory, testLogsDirectory, includeDirectory);
    }
  }

  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 final OutputRoots outputRoots;

  /** If false, AnalysisEnviroment doesn't register any actions created by the ConfiguredTarget. */
  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;

  /**
   * Helper container for {@link #transitiveOptionsMap} below.
   */
  private static class OptionDetails implements Serializable {
    private OptionDetails(Class<? extends OptionsBase> optionsClass, Object value,
        boolean allowsMultiple) {
      this.optionsClass = optionsClass;
      this.value = value;
      this.allowsMultiple = allowsMultiple;
    }

    /** The {@link FragmentOptions} class that defines this option. */
    private final Class<? extends OptionsBase> optionsClass;

    /**
     * The value of the given option (either explicitly defined or default). May be null.
     */
    private final Object value;

    /** Whether or not this option supports multiple values. */
    private final boolean allowsMultiple;
  }

  /**
   * Maps option names to the {@link OptionDetails} the option takes for this configuration.
   *
   * <p>This can be used to:
   * <ol>
   *   <li>Find an option's (parsed) value given its command-line name</li>
   *   <li>Parse alternative values for the option.</li>
   * </ol>
   *
   * <p>This map is "transitive" in that it includes *all* options recognizable by this
   * configuration, including those defined in child fragments.
   */
  private final Map<String, OptionDetails> transitiveOptionsMap;

  /**
   * 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 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"));
    }
  }

  /**
   * 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 final static 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,
      boolean actionsDisabled) {
    this(null, directories, fragmentsMap, buildOptions, actionsDisabled);
  }

  /**
   * Constructor variation that uses the passed in output roots if non-null, else computes them
   * from the directories.
   */
  public BuildConfiguration(@Nullable OutputRoots outputRoots,
      @Nullable BlazeDirectories directories,
      Map<Class<? extends Fragment>, Fragment> fragmentsMap,
      BuildOptions buildOptions,
      boolean actionsDisabled) {
    Preconditions.checkState(outputRoots == null ^ directories == null);
    this.actionsEnabled = !actionsDisabled;
    this.fragments = ImmutableSortedMap.copyOf(fragmentsMap, lexicalFragmentSorter);

    this.skylarkVisibleFragments = buildIndexOfSkylarkVisibleFragments();

    this.buildOptions = buildOptions;
    this.options = buildOptions.get(Options.class);

    Map<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();
    String outputDirName = (options.outputDirectoryName != null)
        ? options.outputDirectoryName : mnemonic;
    this.platformName = buildPlatformName();

    this.shellExecutable = computeShellExecutable();

    this.outputRoots = outputRoots != null
        ? outputRoots
        : new OutputRoots(directories, outputDirName);

    Pair<ImmutableMap<String, String>, ImmutableSet<String>> shellEnvironment =
        setupShellEnvironment();
    this.localShellEnvironment = shellEnvironment.getFirst();
    this.envVariables = shellEnvironment.getSecond();

    this.transitiveOptionsMap = 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());
  }

  /**
   * 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(outputRoots, null, fragmentsMap, options, !actionsEnabled);
    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();
  }

  /**
   * Computes and returns the transitive optionName -> "option info" map for
   * this configuration.
   */
  private static Map<String, OptionDetails> 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());
    }

    ImmutableMap.Builder<String, OptionDetails> map = ImmutableMap.builder();
    try {
      for (FragmentOptions options : buildOptions.getOptions()) {
        for (Field field : options.getClass().getFields()) {
          if (field.isAnnotationPresent(Option.class)) {
            Option option = field.getAnnotation(Option.class);
            Object value = field.get(options);
            if (value == null) {
              if (lateBoundDefaults.containsKey(option.name())) {
                value = lateBoundDefaults.get(option.name());
              } else if (!option.defaultValue().equals("null")) {
                // See {@link Option#defaultValue} for an explanation of default "null" strings.
                value = option.defaultValue();
              }
            }
            map.put(option.name(),
                new OptionDetails(options.getClass(), value, option.allowMultiple()));
          }
        }
      }
    } catch (IllegalAccessException e) {
      throw new IllegalStateException(
          "Unexpected illegal access trying to create this configuration's options map: ", e);
    }
    return map.build();
  }

  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<?> splitTransition);

    /**
     * Returns whether or not all configuration(s) represented by the current state of this
     * instance are null.
     */
    boolean isNull();

    /**
     * Applies the given attribute configurator to the current configuration(s).
     */
    void applyAttributeConfigurator(Attribute attribute, Rule fromRule, Target toTarget);

    /**
     * Calls {@link Transitions#configurationHook} on the current configuration(s) represent by
     * this instance.
     */
    void applyConfigurationHook(Rule fromRule, Attribute attribute, Target toTarget);

    /**
     * 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, ImmutableSet<AspectDescriptor> 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<?> 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(Attribute attribute, Rule fromRule, Target toTarget) {
      // Checks that evaluateTransition never applies an attribute configurator and split
      // transition in the same call.
      Verify.verify(toConfigurations.size() == 1);
      @SuppressWarnings("unchecked")
      Configurator<BuildConfiguration, Rule> configurator =
          (Configurator<BuildConfiguration, Rule>) attribute.getConfigurator();
      Verify.verifyNotNull(configurator);
      toConfigurations = ImmutableList.<BuildConfiguration>of(
          configurator.apply(fromRule, Iterables.getOnlyElement(toConfigurations), attribute,
              toTarget));
    }

    @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, ImmutableSet<AspectDescriptor> 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 BuildConfiguration originalConfiguration;
    // The transition this applier applies to dep rules. May change multiple times. However,
    // composed transitions (e.g. fromConfig -> FooTransition -> BarTransition) are not currently
    // supported, in the name of keeping the model simple. We can always revisit that assumption
    // if needed.
    private Transition currentTransition = Attribute.ConfigurationTransition.NONE;

    private DynamicTransitionApplier(BuildConfiguration originalConfiguration) {
      this.originalConfiguration = originalConfiguration;
    }

    @Override
    public TransitionApplier create(BuildConfiguration configuration) {
      return new DynamicTransitionApplier(configuration);
    }

    @Override
    public void applyTransition(Transition transitionToApply) {
      if (transitionToApply == Attribute.ConfigurationTransition.NONE
          // Outside of LIPO, data transitions are a no-op. Since dynamic configs don't yet support
          // LIPO, just return fast as a no-op. This isn't just convenient: evaluateTransition
          // calls configurationHook after standard attribute transitions. If configurationHook
          // triggers a data transition, that undoes the earlier transitions (because of lack of
          // composed transition support). That's dangerous and especially pointless for non-LIPO
          // builds. Hence this check.
          // TODO(gregce): add LIPO support and/or make this special case unnecessary.
          || transitionToApply == Attribute.ConfigurationTransition.DATA
          // This means it's not possible to transition back out of a host transition. We may
          // need to revise this when we properly support multiple host configurations.
          || currentTransition == HostTransition.INSTANCE) {
        return;
      }

      // Since we don't support composed transitions, we need to be careful applying a transition
      // when another transition has already been applied (the latter will simply overwrite the
      // former). All allowed cases should be explicitly asserted here.
      Verify.verify(currentTransition == Attribute.ConfigurationTransition.NONE
          // LIPO transitions are okay because they're no-ops outside LIPO builds. And dynamic
          // configs don't yet support LIPO builds.
          || currentTransition.toString().contains("LipoDataTransition"));
      currentTransition = getCurrentTransitions().getDynamicTransition(transitionToApply);
    }

    @Override
    public void split(SplitTransition<?> splitTransition) {
      Verify.verify(currentTransition == Attribute.ConfigurationTransition.NONE,
          "split transitions aren't expected to mix with other transitions");
      currentTransition = splitTransition;
    }

    @Override
    public boolean isNull() {
      return currentTransition == Attribute.ConfigurationTransition.NULL;
    }

    @Override
    public void applyAttributeConfigurator(Attribute attribute, Rule fromRule, Target toTarget) {
      // We don't support meaningful attribute configurators (since they produce configurations,
      // and we're only interested in generating transitions so the calling code can realize
      // configurations from them). So just check that the configurator is just a no-op.
      @SuppressWarnings("unchecked")
      Configurator<BuildConfiguration, Rule> configurator =
          (Configurator<BuildConfiguration, Rule>) attribute.getConfigurator();
      Verify.verifyNotNull(configurator);
      BuildConfiguration toConfiguration =
          configurator.apply(fromRule, originalConfiguration, attribute, toTarget);
      Verify.verify(toConfiguration == originalConfiguration);
    }

    @Override
    public void applyConfigurationHook(Rule fromRule, Attribute attribute, Target toTarget) {
      if (isNull()) {
        return;
      }
      getCurrentTransitions().configurationHook(fromRule, attribute, toTarget, this);

      // We don't support rule class configurators (which might imply composed transitions).
      // The only current use of that is LIPO, which can't currently be invoked with dynamic
      // configurations (e.g. this code can never get called for LIPO builds). So check that
      // if there is a configurator, it's for LIPO, in which case we can ignore it.
      Rule associatedRule = toTarget.getAssociatedRule();
      if (associatedRule != null) {
        @SuppressWarnings("unchecked")
        RuleClass.Configurator<?, ?> func =
            associatedRule.getRuleClassObject().getConfigurator();
        Verify.verify(func == RuleClass.NO_CHANGE || func.getCategory().equals("lipo"));
      }
    }

    private Transitions getCurrentTransitions() {
      return originalConfiguration.getTransitions();
    }

    @Override
    public Iterable<Dependency> getDependencies(
        Label label, ImmutableSet<AspectDescriptor> 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).getRuleClass().equals(ConfigRuleClasses.ConfigSettingRule.RULE_NAME)) {
      transitionApplier.applyTransition(Attribute.ConfigurationTransition.NONE); // Unnecessary.
      return;
    }

    if (attribute.hasSplitConfigurationTransition()) {
      Preconditions.checkState(attribute.getConfigurator() == null);
      transitionApplier.split(attribute.getSplitTransition(fromRule));
    } else {
      // III. Attributes determine configurations. The configuration of a prerequisite is determined
      // by the attribute.
      @SuppressWarnings("unchecked")
      Configurator<BuildConfiguration, Rule> configurator =
          (Configurator<BuildConfiguration, Rule>) attribute.getConfigurator();
      if (configurator != null) {
        transitionApplier.applyAttributeConfigurator(attribute, fromRule, toTarget);
      } else {
        transitionApplier.applyTransition(attribute.getConfigurationTransition());
      }
    }

    transitionApplier.applyConfigurationHook(fromRule, attribute, toTarget);
  }

  /**
   * For an given environment, returns a subset containing all
   * variables in the given list if they are defined in the given
   * environment.
   */
  @VisibleForTesting
  static Map<String, String> getMapping(List<String> variables,
      Map<String, String> environment) {
    Map<String, String> result = new HashMap<>();
    for (String var : variables) {
      if (environment.containsKey(var)) {
        result.put(var, environment.get(var));
      }
    }
    return result;
  }

  /**
   * Returns the {@link Option} class the defines the given option, null if the
   * option isn't recognized.
   *
   * <p>optionName is the name of the option as it appears on the command line
   * e.g. {@link Option#name}).
   */
  Class<? extends OptionsBase> getOptionClass(String optionName) {
    OptionDetails optionData = transitiveOptionsMap.get(optionName);
    return optionData == null ? null : optionData.optionsClass;
  }

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

  /**
   * Returns whether or not the given option supports multiple values at the command line (e.g.
   * "--myoption value1 --myOption value2 ..."). Returns false for unrecognized options. Use
   * {@link #getOptionClass} to distinguish between those and legitimate single-value options.
   *
   * <p>As declared in {@link Option#allowMultiple}, multi-value options are expected to be
   * of type {@code List<T>}.
   */
  boolean allowsMultipleValues(String optionName) {
    OptionDetails optionData = transitiveOptionsMap.get(optionName);
    return (optionData == null) ? false : optionData.allowsMultiple;
  }

  /**
   * The platform string, suitable for use as a key into a MakeEnvironment.
   */
  public String getPlatformName() {
    return platformName;
  }

  /**
   * Returns the output directory for this build configuration.
   */
  public Root getOutputDirectory(RepositoryName repositoryName) {
    return outputRoots.outputDirectory;
  }

  /**
   * Returns the bin directory for this build configuration.
   */
  @SkylarkCallable(name = "bin_dir", structField = true, documented = false)
  @Deprecated
  public Root getBinDirectory() {
    return outputRoots.binDirectory;
  }

  /**
   * 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.
   * TODO(kchodorow): Use the repository name to derive the bin directory.
   */
  @SuppressWarnings("unused")
  public Root getBinDirectory(RepositoryName repositoryName) {
    return getBinDirectory();
  }

  /**
   * 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.
   * TODO(kchodorow): Use the repository name to derive the include directory.
   */
  @SuppressWarnings("unused")
  public Root getIncludeDirectory(RepositoryName repositoryName) {
    return outputRoots.includeDirectory;
  }

  /**
   * Returns the genfiles directory for this build configuration.
   */
  @SkylarkCallable(name = "genfiles_dir", structField = true, documented = false)
  @Deprecated
  public Root getGenfilesDirectory() {
    return outputRoots.genfilesDirectory;
  }

  // TODO(kchodorow): Use the repository name to derive the genfiles directory.
  @SuppressWarnings("unused")
  public Root getGenfilesDirectory(RepositoryName repositoryName) {
    return getGenfilesDirectory();
  }

  /**
   * 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.
   * TODO(kchodorow): Use the repository name to derive the coverage directory.
   */
  @SuppressWarnings("unused")
  public Root getCoverageMetadataDirectory(RepositoryName repositoryName) {
    return outputRoots.coverageMetadataDirectory;
  }

  /**
   * Returns the testlogs directory for this build configuration.
   * TODO(kchodorow): Use the repository name to derive the test directory.
   */
  @SuppressWarnings("unused")
  public Root getTestLogsDirectory(RepositoryName repositoryName) {
    return outputRoots.testLogsDirectory;
  }

  /**
   * Returns a relative path to the genfiles directory at execution time.
   */
  public PathFragment getGenfilesFragment() {
    return getGenfilesDirectory().getExecPath();
  }

  /**
   * Returns the path separator for the host platform. This is basically the same as {@link
   * java.io.File#pathSeparator}, except that that returns the value for this JVM, which may or may
   * not match the host platform. You should only use this when invoking tools that are known to use
   * the native path separator, i.e., the path separator for the machine that they run on.
   */
  @SkylarkCallable(name = "host_path_separator", structField = true,
      doc = "Returns the separator for PATH environment variable, which is ':' on Unix.")
  public String getHostPathSeparator() {
    // TODO(bazel-team): Maybe do this in the constructor instead? This isn't serialization-safe.
    return OS.getCurrent() == OS.WINDOWS ? ";" : ":";
  }

  /**
   * Returns the internal directory (used for middlemen) for this build configuration.
   * TODO(kchodorow): Use the repository name to derive the middleman directory.
   */
  @SuppressWarnings("unused")
  public Root getMiddlemanDirectory(RepositoryName repositoryName) {
    return outputRoots.middlemanDirectory;
  }

  public boolean getAllowRuntimeDepsOnNeverLink() {
    return options.allowRuntimeDepsOnNeverLink;
  }

  public boolean isStrictFilesets() {
    return options.strictFilesets;
  }

  public List<Label> getPlugins() {
    return options.pluginList;
  }

  public List<Map.Entry<String, String>> getPluginCopts() {
    return options.pluginCoptList;
  }

  /**
   * 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)."
  )
  public ImmutableMap<String, String> getLocalShellEnvironment() {
    return localShellEnvironment;
  }

  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 isMicroCoverageEnabled() {
    return options.collectMicroCoverage;
  }

  public boolean isLLVMCoverageMapFormatEnabled() {
    return options.useLLVMCoverageMapFormat;
  }

  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 actionsEnabled ? options.actionListeners : ImmutableList.<Label>of();
  }

  /**
   * 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;
  }

  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.getDynamicTransitions. No other calls to transitions should be
    // made for dynamic configurations.
    // TODO(bazel-team): enforce the above automatically (without having to explicitly check
    // for dynamic configuration mode).
    return useDynamicConfigurations() ? this : transitions.getArtifactOwnerConfiguration();
  }

  /**
   * @return 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();
  }
}
