// 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.skyframe;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Verify;
import com.google.common.base.VerifyException;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
import com.google.devtools.build.lib.actions.Actions;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.MutableActionGraph.ActionConflictException;
import com.google.devtools.build.lib.analysis.AspectDescriptor;
import com.google.devtools.build.lib.analysis.CachingAnalysisEnvironment;
import com.google.devtools.build.lib.analysis.ConfiguredAspect;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.Dependency;
import com.google.devtools.build.lib.analysis.LabelAndConfiguration;
import com.google.devtools.build.lib.analysis.MergedConfiguredTarget;
import com.google.devtools.build.lib.analysis.MergedConfiguredTarget.DuplicateException;
import com.google.devtools.build.lib.analysis.TargetAndConfiguration;
import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.analysis.config.BuildOptions;
import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider;
import com.google.devtools.build.lib.analysis.config.HostTransition;
import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
import com.google.devtools.build.lib.analysis.config.PatchTransition;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.collect.nestedset.Order;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.StoredEventHandler;
import com.google.devtools.build.lib.packages.Aspect;
import com.google.devtools.build.lib.packages.AspectDefinition;
import com.google.devtools.build.lib.packages.Attribute;
import com.google.devtools.build.lib.packages.BuildType;
import com.google.devtools.build.lib.packages.NoSuchTargetException;
import com.google.devtools.build.lib.packages.NoSuchThingException;
import com.google.devtools.build.lib.packages.Package;
import com.google.devtools.build.lib.packages.RawAttributeMapper;
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.packages.RuleClassProvider;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.packages.TargetUtils;
import com.google.devtools.build.lib.skyframe.AspectFunction.AspectCreationException;
import com.google.devtools.build.lib.skyframe.AspectValue.AspectKey;
import com.google.devtools.build.lib.skyframe.SkyframeExecutor.BuildViewProvider;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.util.OrderedSetMultimap;
import com.google.devtools.build.lib.util.Preconditions;
import com.google.devtools.build.skyframe.SkyFunction;
import com.google.devtools.build.skyframe.SkyFunctionException;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
import com.google.devtools.build.skyframe.ValueOrException;
import com.google.devtools.build.skyframe.ValueOrException2;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Semaphore;
import javax.annotation.Nullable;

/**
 * SkyFunction for {@link ConfiguredTargetValue}s.
 *
 * This class, together with {@link AspectFunction} drives the analysis phase. For more information,
 * see {@link com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory}.
 *
 * @see com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory
 */
final class ConfiguredTargetFunction implements SkyFunction {
  // This construction is a bit funky, but guarantees that the Object reference here is globally
  // unique.
  static final ImmutableMap<Label, ConfigMatchingProvider> NO_CONFIG_CONDITIONS =
      ImmutableMap.<Label, ConfigMatchingProvider>of();

  /**
   * Exception class that signals an error during the evaluation of a dependency.
   */
  public static class DependencyEvaluationException extends Exception {
    public DependencyEvaluationException(InvalidConfigurationException cause) {
      super(cause);
    }

    public DependencyEvaluationException(ConfiguredValueCreationException cause) {
      super(cause);
    }

    @Override
    public synchronized Exception getCause() {
      return (Exception) super.getCause();
    }
  }

  private static final Function<Dependency, SkyKey> TO_KEYS =
      new Function<Dependency, SkyKey>() {
    @Override
    public SkyKey apply(Dependency input) {
      return ConfiguredTargetValue.key(input.getLabel(), input.getConfiguration());
    }
  };

  private final BuildViewProvider buildViewProvider;
  private final RuleClassProvider ruleClassProvider;
  private final Semaphore cpuBoundSemaphore;

  ConfiguredTargetFunction(
      BuildViewProvider buildViewProvider,
      RuleClassProvider ruleClassProvider,
      Semaphore cpuBoundSemaphore) {
    this.buildViewProvider = buildViewProvider;
    this.ruleClassProvider = ruleClassProvider;
    this.cpuBoundSemaphore = cpuBoundSemaphore;
  }

  private static boolean useDynamicConfigurations(BuildConfiguration config) {
    return config != null && config.useDynamicConfigurations();
  }

  @Override
  public SkyValue compute(SkyKey key, Environment env) throws ConfiguredTargetFunctionException,
      InterruptedException {
    SkyframeBuildView view = buildViewProvider.getSkyframeBuildView();
    NestedSetBuilder<Package> transitivePackages = NestedSetBuilder.stableOrder();
    NestedSetBuilder<Label> transitiveLoadingRootCauses = NestedSetBuilder.stableOrder();
    ConfiguredTargetKey configuredTargetKey = (ConfiguredTargetKey) key.argument();
    LabelAndConfiguration lc = LabelAndConfiguration.of(
        configuredTargetKey.getLabel(), configuredTargetKey.getConfiguration());

    BuildConfiguration configuration = lc.getConfiguration();

    PackageValue packageValue =
        (PackageValue) env.getValue(PackageValue.key(lc.getLabel().getPackageIdentifier()));
    if (packageValue == null) {
      return null;
    }

    // TODO(ulfjack): This tries to match the logic in TransitiveTargetFunction /
    // TargetMarkerFunction. Maybe we can merge the two?
    Package pkg = packageValue.getPackage();
    Target target;
    try {
      target = pkg.getTarget(lc.getLabel().getName());
    } catch (NoSuchTargetException e) {
      throw new ConfiguredTargetFunctionException(
          new ConfiguredValueCreationException(e.getMessage()));
    }
    if (pkg.containsErrors()) {
      transitiveLoadingRootCauses.add(lc.getLabel());
    }
    transitivePackages.add(pkg);
    // TODO(bazel-team): This is problematic - we create the right key, but then end up with a value
    // that doesn't match; we can even have the same value multiple times. However, I think it's
    // only triggered in tests (i.e., in normal operation, the configuration passed in is already
    // null).
    if (!target.isConfigurable()) {
      configuration = null;
    }

    // This line is only needed for accurate error messaging. Say this target has a circular
    // dependency with one of its deps. With this line, loading this target fails so Bazel
    // associates the corresponding error with this target, as expected. Without this line,
    // the first TransitiveTargetValue call happens on its dep (in trimConfigurations), so Bazel
    // associates the error with the dep, which is misleading.
    if (useDynamicConfigurations(configuration) && configuration.trimConfigurations()
        && env.getValue(TransitiveTargetValue.key(lc.getLabel())) == null) {
      return null;
    }

    TargetAndConfiguration ctgValue = new TargetAndConfiguration(target, configuration);

    SkyframeDependencyResolver resolver = view.createDependencyResolver(env);

    // TODO(janakr): this acquire() call may tie up this thread indefinitely, reducing the
    // parallelism of Skyframe. This is a strict improvement over the prior state of the code, in
    // which we ran with #processors threads, but ideally we would call #tryAcquire here, and if we
    // failed, would exit this SkyFunction and restart it when permits were available.
    cpuBoundSemaphore.acquire();
    try {
      // Get the configuration targets that trigger this rule's configurable attributes.
      ImmutableMap<Label, ConfigMatchingProvider> configConditions = getConfigConditions(
          ctgValue.getTarget(), env, resolver, ctgValue, transitivePackages,
          transitiveLoadingRootCauses);
      if (env.valuesMissing()) {
        return null;
      }
      // TODO(ulfjack): ConfiguredAttributeMapper (indirectly used from computeDependencies) isn't
      // safe to use if there are missing config conditions, so we stop here, but only if there are
      // config conditions - though note that we can't check if configConditions is non-empty - it
      // may be empty for other reasons. It would be better to continue here so that we can collect
      // more root causes during computeDependencies.
      // Note that this doesn't apply to AspectFunction, because aspects can't have configurable
      // attributes.
      if (!transitiveLoadingRootCauses.isEmpty() && configConditions != NO_CONFIG_CONDITIONS) {
        throw new ConfiguredTargetFunctionException(
            new ConfiguredValueCreationException(transitiveLoadingRootCauses.build()));
      }

      OrderedSetMultimap<Attribute, ConfiguredTarget> depValueMap =
          computeDependencies(
              env,
              resolver,
              ctgValue,
              ImmutableList.<Aspect>of(),
              configConditions,
              ruleClassProvider,
              view.getHostConfiguration(configuration),
              transitivePackages,
              transitiveLoadingRootCauses);
      if (env.valuesMissing()) {
        return null;
      }
      if (!transitiveLoadingRootCauses.isEmpty()) {
        throw new ConfiguredTargetFunctionException(
            new ConfiguredValueCreationException(transitiveLoadingRootCauses.build()));
      }
      Preconditions.checkNotNull(depValueMap);
      ConfiguredTargetValue ans = createConfiguredTarget(
          view, env, target, configuration, depValueMap, configConditions, transitivePackages);
      return ans;
    } catch (DependencyEvaluationException e) {
      if (e.getCause() instanceof ConfiguredValueCreationException) {
        throw new ConfiguredTargetFunctionException(
            (ConfiguredValueCreationException) e.getCause());
      } else {
        // Cast to InvalidConfigurationException as a consistency check. If you add any
        // DependencyEvaluationException constructors, you may need to change this code, too.
        InvalidConfigurationException cause = (InvalidConfigurationException) e.getCause();
        throw new ConfiguredTargetFunctionException(
            new ConfiguredValueCreationException(cause.getMessage(), target.getLabel()));
      }
    } catch (AspectCreationException e) {
      // getAnalysisRootCause may be null if the analysis of the aspect itself failed.
      Label analysisRootCause = target.getLabel();
      if (e.getAnalysisRootCause() != null) {
        analysisRootCause = e.getAnalysisRootCause();
      }
      throw new ConfiguredTargetFunctionException(
          new ConfiguredValueCreationException(e.getMessage(), analysisRootCause));
    } finally {
      cpuBoundSemaphore.release();
    }
  }

  /**
   * Computes the direct dependencies of a node in the configured target graph (a configured
   * target or an aspects).
   *
   * <p>Returns null if Skyframe hasn't evaluated the required dependencies yet. In this case, the
   * caller should also return null to Skyframe.
   *  @param env the Skyframe environment
   * @param resolver The dependency resolver
   * @param ctgValue The label and the configuration of the node
   * @param aspects
   * @param configConditions the configuration conditions for evaluating the attributes of the node
   * @param ruleClassProvider rule class provider for determining the right configuration fragments
   *   to apply to deps
   * @param hostConfiguration the host configuration. There's a noticeable performance hit from
   *     instantiating this on demand for every dependency that wants it, so it's best to compute
   *     the host configuration as early as possible and pass this reference to all consumers
   * */
  @Nullable
  static OrderedSetMultimap<Attribute, ConfiguredTarget> computeDependencies(
      Environment env,
      SkyframeDependencyResolver resolver,
      TargetAndConfiguration ctgValue,
      Iterable<Aspect> aspects,
      ImmutableMap<Label, ConfigMatchingProvider> configConditions,
      RuleClassProvider ruleClassProvider,
      BuildConfiguration hostConfiguration,
      NestedSetBuilder<Package> transitivePackages,
      NestedSetBuilder<Label> transitiveLoadingRootCauses)
      throws DependencyEvaluationException, ConfiguredTargetFunctionException,
      AspectCreationException, InterruptedException {
    // Create the map from attributes to set of (target, configuration) pairs.
    OrderedSetMultimap<Attribute, Dependency> depValueNames;
    try {
      depValueNames = resolver.dependentNodeMap(
          ctgValue, hostConfiguration, aspects, configConditions, transitiveLoadingRootCauses);
    } catch (EvalException e) {
      // EvalException can only be thrown by computed Skylark attributes in the current rule.
      env.getListener().handle(Event.error(e.getLocation(), e.getMessage()));
      throw new DependencyEvaluationException(
          new ConfiguredValueCreationException(e.print(), ctgValue.getLabel()));
    } catch (InvalidConfigurationException e) {
      throw new DependencyEvaluationException(e);
    }

    // Trim each dep's configuration so it only includes the fragments needed by its transitive
    // closure (only dynamic configurations support this).
    if (useDynamicConfigurations(ctgValue.getConfiguration())) {
      depValueNames = getDynamicConfigurations(env, ctgValue, depValueNames, hostConfiguration,
          ruleClassProvider);
      if (depValueNames == null) {
        return null;
      }
    }

    // Resolve configured target dependencies and handle errors.
    Map<SkyKey, ConfiguredTarget> depValues = resolveConfiguredTargetDependencies(env,
        depValueNames.values(), transitivePackages, transitiveLoadingRootCauses);
    if (depValues == null) {
      return null;
    }

    // Resolve required aspects.
    OrderedSetMultimap<SkyKey, ConfiguredAspect> depAspects = resolveAspectDependencies(
        env, depValues, depValueNames.values(), transitivePackages);
    if (depAspects == null) {
      return null;
    }

    // Merge the dependent configured targets and aspects into a single map.
    try {
      return mergeAspects(depValueNames, depValues, depAspects);
    } catch (DuplicateException e) {
      env.getListener().handle(
          Event.error(ctgValue.getTarget().getLocation(), e.getMessage()));

      throw new ConfiguredTargetFunctionException(
          new ConfiguredValueCreationException(e.getMessage(), ctgValue.getLabel()));
    }
  }

  /**
   * Helper class for {@link #getDynamicConfigurations} - encapsulates a set of config fragments and
   * a dynamic transition. This can be used to determine the exact build options needed to
   * set a dynamic configuration.
   */
  @Immutable
  private static final class FragmentsAndTransition {
    // Treat this as immutable. The only reason this isn't an ImmutableSet is because it
    // gets bound to a NestedSet.toSet() reference, which returns a Set interface.
    final Set<Class<? extends BuildConfiguration.Fragment>> fragments;
    final Attribute.Transition transition;
    private final int hashCode;

    FragmentsAndTransition(Set<Class<? extends BuildConfiguration.Fragment>> fragments,
        Attribute.Transition transition) {
      this.fragments = fragments;
      this.transition = transition;
      hashCode = Objects.hash(this.fragments, this.transition);
    }

    @Override
    public boolean equals(Object o) {
      if (o == this) {
        return true;
      } else if (o == null) {
        return false;
      } else {
        FragmentsAndTransition other = (FragmentsAndTransition) o;
        return other.transition.equals(transition) && other.fragments.equals(fragments);
      }
    }

    @Override
    public int hashCode() {
      return hashCode;
    }
  }

  /**
   * Helper class for {@link #getDynamicConfigurations} - encapsulates an <attribute, label> pair
   * that can be used to map from an input dependency to a trimmed dependency.
   */
  @Immutable
  private static final class AttributeAndLabel {
    final Attribute attribute;
    final Label label;
    Integer hashCode;

    AttributeAndLabel(Attribute attribute, Label label) {
      this.attribute = attribute;
      this.label = label;
    }

    @Override
    public boolean equals(Object o) {
      if (!(o instanceof AttributeAndLabel)) {
        return false;
      }
      AttributeAndLabel other = (AttributeAndLabel) o;
      return Objects.equals(other.attribute, attribute) && other.label.equals(label);
    }

    @Override
    public int hashCode() {
      if (hashCode == null) {
        // Not every <Attribute, Label> pair gets hashed. So only evaluate for the instances that
        // need it. This can significantly reduce the number of evaluations.
        hashCode = Objects.hash(this.attribute, this.label);
      }
      return hashCode;
    }
  }

  /**
   * Variation of {@link Multimap#put} that triggers an exception if a value already exists.
   */
  @VisibleForTesting
  static <K, V> void putOnlyEntry(Multimap<K, V> map, K key, V value) {
    // Performance note: while "Verify.verify(!map.containsKey(key, value), String.format(...)))"
    // is simpler code, profiling shows a substantial performance penalty to that approach
    // (~10% extra analysis phase time on a simple cc_binary). Most of that is from the cost of
    // evaluating value.toString() on every call. This approach essentially eliminates the overhead.
    if (map.containsKey(key)) {
      throw new VerifyException(
          String.format("couldn't insert %s: map already has key %s",
              value.toString(), key.toString()));
    }
    map.put(key, value);
  }

  /**
   * Creates a dynamic configuration for each dep that's custom-fitted specifically for that dep.
   *
   * <p>More specifically: given a set of {@link Dependency} instances holding dynamic config
   * transition requests (e.g. {@link Dependency#hasStaticConfiguration()} == false}), returns
   * equivalent dependencies containing dynamically created configurations applying those
   * transitions. If {@link BuildConfiguration.Options#trimConfigurations()} is true, these
   * configurations only contain the fragments needed by the dep and its transitive closure. Else
   * the configurations unconditionally include all fragments.
   *
   * <p>This method is heavily performance-optimized. Because it, in aggregate, reads over every
   * edge in the configured target graph, small inefficiencies can have observable impact on build
   * analysis time. Keep this in mind when making modifications and performance-test any changes you
   * make.
   */
  @Nullable
  static OrderedSetMultimap<Attribute, Dependency> getDynamicConfigurations(
      Environment env,
      TargetAndConfiguration ctgValue,
      OrderedSetMultimap<Attribute, Dependency> originalDeps,
      BuildConfiguration hostConfiguration,
      RuleClassProvider ruleClassProvider)
      throws DependencyEvaluationException, InterruptedException {

    // Maps each Skyframe-evaluated BuildConfiguration to the dependencies that need that
    // configuration. For cases where Skyframe isn't needed to get the configuration (e.g. when
    // we just re-used the original rule's configuration), we should skip this outright.
    Multimap<SkyKey, Map.Entry<Attribute, Dependency>> keysToEntries = LinkedListMultimap.create();

    // Stores the result of applying a dynamic transition to the current configuration using a
    // particular subset of fragments. By caching this, we save from redundantly computing the
    // same transition for every dependency edge that requests that transition. This can have
    // real effect on analysis time for commonly triggered transitions.
    //
    // Split transitions may map to multiple values. All other transitions map to one.
    Map<FragmentsAndTransition, List<BuildOptions>> transitionsMap = new LinkedHashMap<>();

    // The fragments used by the current target's configuration.
    Set<Class<? extends BuildConfiguration.Fragment>> ctgFragments =
        ctgValue.getConfiguration().fragmentClasses();
    BuildOptions ctgOptions = ctgValue.getConfiguration().getOptions();

    // Stores the trimmed versions of each dependency. This method must preserve the original label
    // ordering of each attribute. For example, if originalDeps.get("data") is [":a", ":b"], the
    // trimmed variant must also be [":a", ":b"] in the same order. Because we may not actualize
    // the results in order (some results need Skyframe-evaluated configurations while others can
    // be computed trivially), we dump them all into this map, then as a final step iterate through
    // the original list and pluck out values from here for the final value.
    //
    // This map is used heavily by all builds. Inserts and gets should be as fast as possible.
    Multimap<AttributeAndLabel, Dependency> trimmedDeps = LinkedHashMultimap.create();

    // Performance optimization: This method iterates over originalDeps twice. By storing
    // AttributeAndLabel instances in this list, we avoid having to recreate them the second time
    // (particularly avoid recomputing their hash codes). Profiling shows this shaves 25% off this
    // method's execution time (at the time of this comment).
    ArrayList<AttributeAndLabel> attributesAndLabels = new ArrayList<>(originalDeps.size());

    for (Map.Entry<Attribute, Dependency> depsEntry : originalDeps.entries()) {
      Dependency dep = depsEntry.getValue();
      AttributeAndLabel attributeAndLabel =
          new AttributeAndLabel(depsEntry.getKey(), dep.getLabel());
      attributesAndLabels.add(attributeAndLabel);
      // Certain targets (like output files) trivially re-use their input configuration. Likewise,
      // deps with null configurations (e.g. source files), can be trivially computed. So we skip
      // all logic in this method for these cases and just reinsert their original configurations
      // back at the end (note that null-configured targets will have a static
      // NullConfigurationDependency instead of dynamic
      // Dependency(label, transition=Attribute.Configuration.Transition.NULL)).
      //
      // A *lot* of targets have null deps, so this produces real savings. Profiling tests over a
      // simple cc_binary show this saves ~1% of total analysis phase time.
      if (dep.hasStaticConfiguration()) {
        continue;
      }

      // Figure out the required fragments for this dep and its transitive closure.
      Set<Class<? extends BuildConfiguration.Fragment>> depFragments =
          getTransitiveFragments(env, dep.getLabel(), ctgValue.getConfiguration());
      if (depFragments == null) {
        return null;
      }
      // TODO(gregce): remove the below call once we have confidence dynamic configurations always
      // provide needed fragments. This unnecessarily drags performance on the critical path (up
      // to 0.5% of total analysis time as profiled over a simple cc_binary).
      if (ctgValue.getConfiguration().trimConfigurations()) {
        checkForMissingFragments(env, ctgValue, attributeAndLabel.attribute.getName(), dep,
            depFragments);
      }

      boolean sameFragments = depFragments.equals(ctgFragments);
      Attribute.Transition transition = dep.getTransition();

      if (sameFragments) {
        if (transition == Attribute.ConfigurationTransition.NONE) {
          // The dep uses the same exact configuration.
          putOnlyEntry(
              trimmedDeps,
              attributeAndLabel,
              Dependency.withConfigurationAndAspects(
                  dep.getLabel(), ctgValue.getConfiguration(), dep.getAspects()));
          continue;
        } else if (transition == HostTransition.INSTANCE) {
          // The current rule's host configuration can also be used for the dep. We short-circuit
          // the standard transition logic for host transitions because these transitions are
          // uniquely frequent. It's possible, e.g., for every node in the configured target graph
          // to incur multiple host transitions. So we aggressively optimize to avoid hurting
          // analysis time.
          putOnlyEntry(
              trimmedDeps,
              attributeAndLabel,
              Dependency.withConfigurationAndAspects(
                  dep.getLabel(), hostConfiguration, dep.getAspects()));
          continue;
        }
      }

      // Apply the transition or use the cached result if it was already applied.
      FragmentsAndTransition transitionKey = new FragmentsAndTransition(depFragments, transition);
      List<BuildOptions> toOptions = transitionsMap.get(transitionKey);
      if (toOptions == null) {
        ImmutableList.Builder<BuildOptions> toOptionsBuilder = ImmutableList.builder();
        toOptions = getDynamicTransitionOptions(ctgOptions, transition, depFragments,
            ruleClassProvider, !sameFragments);
        transitionsMap.put(transitionKey, toOptions);
      }

      // If the transition doesn't change the configuration, trivially re-use the original
      // configuration.
      if (sameFragments && toOptions.size() == 1
          && Iterables.getOnlyElement(toOptions).equals(ctgOptions)) {
        putOnlyEntry(
            trimmedDeps,
            attributeAndLabel,
            Dependency.withConfigurationAndAspects(
                dep.getLabel(), ctgValue.getConfiguration(), dep.getAspects()));
        continue;
      }

      // If we get here, we have to get the configuration from Skyframe.
      for (BuildOptions options : toOptions) {
        keysToEntries.put(BuildConfigurationValue.key(depFragments, options), depsEntry);
      }
    }

    // Get all BuildConfigurations we need to get from Skyframe.
    Map<SkyKey, ValueOrException<InvalidConfigurationException>> depConfigValues =
        env.getValuesOrThrow(keysToEntries.keySet(), InvalidConfigurationException.class);
    if (env.valuesMissing()) {
      return null;
    }

    // Now fill in the remaining unresolved deps with the now-resolved configurations.
    try {
      for (Map.Entry<SkyKey, ValueOrException<InvalidConfigurationException>> entry :
          depConfigValues.entrySet()) {
        SkyKey key = entry.getKey();
        BuildConfigurationValue trimmedConfig = (BuildConfigurationValue) entry.getValue().get();
        for (Map.Entry<Attribute, Dependency> info : keysToEntries.get(key)) {
          Dependency originalDep = info.getValue();
          AttributeAndLabel attr = new AttributeAndLabel(info.getKey(), originalDep.getLabel());
          Dependency resolvedDep = Dependency.withConfigurationAndAspects(originalDep.getLabel(),
              trimmedConfig.getConfiguration(), originalDep.getAspects());
          if (attr.attribute.hasSplitConfigurationTransition()) {
            trimmedDeps.put(attr, resolvedDep);
          } else {
            putOnlyEntry(trimmedDeps, attr, resolvedDep);
          }
        }
      }
    } catch (InvalidConfigurationException e) {
      throw new DependencyEvaluationException(e);
    }

    // Re-assemble the output map with the same value ordering (e.g. each attribute's dep labels
    // appear in the same order) as the input.
    Iterator<AttributeAndLabel> iterator = attributesAndLabels.iterator();
    OrderedSetMultimap<Attribute, Dependency> result = OrderedSetMultimap.create();
    for (Map.Entry<Attribute, Dependency> depsEntry : originalDeps.entries()) {
      AttributeAndLabel attrAndLabel = iterator.next();
      if (depsEntry.getValue().hasStaticConfiguration()) {
        result.put(attrAndLabel.attribute, depsEntry.getValue());
      } else {
        Collection<Dependency> trimmedAttrDeps = trimmedDeps.get(attrAndLabel);
        Verify.verify(!trimmedAttrDeps.isEmpty());
        result.putAll(depsEntry.getKey(), trimmedAttrDeps);
      }
    }
    return result;
  }

  /**
   * Returns the configuration fragments required by a dep and its transitive closure.
   * Returns null if Skyframe dependencies aren't yet available.
   *
   * @param env Skyframe evaluation environment
   * @param dep label of the dep to check
   * @param parentConfig configuration of the rule depending on the dep
   */
  @Nullable
  private static Set<Class<? extends BuildConfiguration.Fragment>> getTransitiveFragments(
      Environment env, Label dep, BuildConfiguration parentConfig) throws InterruptedException {
    Preconditions.checkArgument(parentConfig.useDynamicConfigurations());
    if (!parentConfig.trimConfigurations()) {
      return parentConfig.getAllFragments().keySet();
    }
    SkyKey fragmentsKey = TransitiveTargetValue.key(dep);
    TransitiveTargetValue transitiveDepInfo = (TransitiveTargetValue) env.getValue(fragmentsKey);
    if (transitiveDepInfo == null) {
      // This should only be possible for tests. In actual runs, this was already called
      // as a routine part of the loading phase.
      // TODO(bazel-team): check this only occurs in a test context.
      return null;
    }
    return transitiveDepInfo.getTransitiveConfigFragments().toSet();
  }

  /**
   * Applies a dynamic configuration transition over a set of build options.
   *
   * @return the build options for the transitioned configuration. If trimResults is true,
   *     only options needed by the required fragments are included. Else the same options as the
   *     original input are included (with different possible values, of course).
   */
  static List<BuildOptions> getDynamicTransitionOptions(BuildOptions fromOptions,
      Attribute.Transition transition,
      Iterable<Class<? extends BuildConfiguration.Fragment>> requiredFragments,
      RuleClassProvider ruleClassProvider, boolean trimResults) {
    List<BuildOptions> result;
    if (transition == Attribute.ConfigurationTransition.NONE) {
      result = ImmutableList.<BuildOptions>of(fromOptions);
    } else if (transition instanceof PatchTransition) {
      // TODO(bazel-team): safety-check that this never mutates fromOptions.
      result = ImmutableList.<BuildOptions>of(((PatchTransition) transition).apply(fromOptions));
    } else if (transition instanceof Attribute.SplitTransition) {
      @SuppressWarnings("unchecked") // Attribute.java doesn't have the BuildOptions symbol.
      List<BuildOptions> toOptions =
          ((Attribute.SplitTransition<BuildOptions>) transition).split(fromOptions);
      if (toOptions.isEmpty()) {
        // When the split returns an empty list, it's signaling it doesn't apply to this instance.
        // Check that it's safe to skip the transition and return the original options.
        Verify.verify(transition.defaultsToSelf());
        result = ImmutableList.<BuildOptions>of(fromOptions);
      } else {
        result = toOptions;
      }
    } else {
      throw new IllegalStateException(String.format(
          "unsupported dynamic transition type: %s", transition.getClass().getName()));
    }

    if (!trimResults) {
      return result;
    } else {
      ImmutableList.Builder<BuildOptions> trimmedOptions = ImmutableList.builder();
      for (BuildOptions toOptions : result) {
        trimmedOptions.add(toOptions.trim(
            BuildConfiguration.getOptionsClasses(requiredFragments, ruleClassProvider)));
      }
      return trimmedOptions.build();
    }
  }

  /**
   * Diagnostic helper method for dynamic configurations: checks the config fragments required by
   * a dep against the fragments in its actual configuration. If any are missing, triggers a
   * descriptive "missing fragments" error.
   */
  private static void checkForMissingFragments(Environment env, TargetAndConfiguration ctgValue,
      String attribute, Dependency dep,
      Set<Class<? extends BuildConfiguration.Fragment>> expectedDepFragments)
      throws DependencyEvaluationException {
    Set<String> ctgFragmentNames = new HashSet<>();
    for (BuildConfiguration.Fragment fragment :
        ctgValue.getConfiguration().getAllFragments().values()) {
      ctgFragmentNames.add(fragment.getClass().getSimpleName());
    }
    Set<String> depFragmentNames = new HashSet<>();
    for (Class<? extends BuildConfiguration.Fragment> fragmentClass : expectedDepFragments) {
     depFragmentNames.add(fragmentClass.getSimpleName());
    }
    Set<String> missing = Sets.difference(depFragmentNames, ctgFragmentNames);
    if (!missing.isEmpty()) {
      String msg = String.format(
          "%s: dependency %s from attribute \"%s\" is missing required config fragments: %s",
          ctgValue.getLabel(), dep.getLabel(), attribute, Joiner.on(", ").join(missing));
      env.getListener().handle(Event.error(msg));
      throw new DependencyEvaluationException(new InvalidConfigurationException(msg));
    }
  }


  /**
   * Merges the each direct dependency configured target with the aspects associated with it.
   *
   * <p>Note that the combination of a configured target and its associated aspects are not
   * represented by a Skyframe node. This is because there can possibly be many different
   * combinations of aspects for a particular configured target, so it would result in a
   * combinatiorial explosion of Skyframe nodes.
   */
  private static OrderedSetMultimap<Attribute, ConfiguredTarget> mergeAspects(
      OrderedSetMultimap<Attribute, Dependency> depValueNames,
      Map<SkyKey, ConfiguredTarget> depConfiguredTargetMap,
      OrderedSetMultimap<SkyKey, ConfiguredAspect> depAspectMap)
      throws DuplicateException  {
    OrderedSetMultimap<Attribute, ConfiguredTarget> result = OrderedSetMultimap.create();

    for (Map.Entry<Attribute, Dependency> entry : depValueNames.entries()) {
      Dependency dep = entry.getValue();
      SkyKey depKey = TO_KEYS.apply(dep);
      ConfiguredTarget depConfiguredTarget = depConfiguredTargetMap.get(depKey);

      result.put(entry.getKey(),
          MergedConfiguredTarget.of(depConfiguredTarget, depAspectMap.get(depKey)));
    }

    return result;
  }

  /**
   * Given a list of {@link Dependency} objects, returns a multimap from the {@link SkyKey} of the
   * dependency to the {@link ConfiguredAspect} instances that should be merged into it.
   *
   * <p>Returns null if the required aspects are not computed yet.
   */
  @Nullable
  private static OrderedSetMultimap<SkyKey, ConfiguredAspect> resolveAspectDependencies(
      Environment env,
      Map<SkyKey, ConfiguredTarget> configuredTargetMap,
      Iterable<Dependency> deps,
      NestedSetBuilder<Package> transitivePackages)
      throws AspectCreationException, InterruptedException {
    OrderedSetMultimap<SkyKey, ConfiguredAspect> result = OrderedSetMultimap.create();
    Set<SkyKey> aspectKeys = new HashSet<>();
    for (Dependency dep : deps) {
      AspectKey key = null;
      for (Entry<AspectDescriptor, BuildConfiguration> depAspect
          : dep.getAspectConfigurations().entrySet()) {
        key = getNextAspectKey(key, dep, depAspect);
        aspectKeys.add(key.getSkyKey());
      }
    }

    Map<SkyKey, ValueOrException2<AspectCreationException, NoSuchThingException>> depAspects =
        env.getValuesOrThrow(aspectKeys, AspectCreationException.class, NoSuchThingException.class);

    for (Dependency dep : deps) {
      SkyKey depKey = TO_KEYS.apply(dep);
      // If the same target was declared in different attributes of rule, we should not process it
      // twice.
      if (result.containsKey(depKey)) {
        continue;
      }
      AspectKey key = null;
      ConfiguredTarget depConfiguredTarget = configuredTargetMap.get(depKey);
      for (Entry<AspectDescriptor, BuildConfiguration> depAspect
          : dep.getAspectConfigurations().entrySet()) {
        key = getNextAspectKey(key, dep, depAspect);
        SkyKey aspectKey = key.getSkyKey();
        AspectValue aspectValue;
        try {
          // TODO(ulfjack): Catch all thrown AspectCreationException and NoSuchThingException
          // instances and merge them into a single Exception to get full root cause data.
          aspectValue = (AspectValue) depAspects.get(aspectKey).get();
        } catch (NoSuchThingException e) {
          throw new AspectCreationException(
              String.format(
                  "Evaluation of aspect %s on %s failed: %s",
                  depAspect.getKey().getAspectClass().getName(),
                  dep.getLabel(),
                  e.toString()));
        }

        if (aspectValue == null) {
          // Dependent aspect has either not been computed yet or is in error.
          return null;
        }
        if (!aspectMatchesConfiguredTarget(depConfiguredTarget, aspectValue.getAspect())) {
          continue;
        }

        result.put(depKey, aspectValue.getConfiguredAspect());
        transitivePackages.addTransitive(aspectValue.getTransitivePackages());
      }
    }
    return result;
  }

  private static AspectKey getNextAspectKey(AspectKey key, Dependency dep,
      Entry<AspectDescriptor, BuildConfiguration> depAspect) {
    if (key == null) {
      key = AspectValue.createAspectKey(dep.getLabel(),
          dep.getConfiguration(), depAspect.getKey(), depAspect.getValue()
      );
    } else {
      key = AspectValue.createAspectKey(key, depAspect.getKey(), depAspect.getValue());
    }
    return key;
  }

  private static boolean aspectMatchesConfiguredTarget(ConfiguredTarget dep, Aspect aspect) {
    AspectDefinition aspectDefinition = aspect.getDefinition();
    for (Class<?> provider : aspectDefinition.getRequiredProviders()) {
      if (dep.getProvider(provider.asSubclass(TransitiveInfoProvider.class)) == null) {
        return false;
      }
    }
    return true;
  }

  /**
   * Returns the set of {@link ConfigMatchingProvider}s that key the configurable attributes used by
   * this rule.
   *
   * <p>>If the configured targets supplying those providers aren't yet resolved by the dependency
   * resolver, returns null.
   */
  @Nullable
  static ImmutableMap<Label, ConfigMatchingProvider> getConfigConditions(
      Target target,
      Environment env,
      SkyframeDependencyResolver resolver,
      TargetAndConfiguration ctgValue,
      NestedSetBuilder<Package> transitivePackages,
      NestedSetBuilder<Label> transitiveLoadingRootCauses)
      throws DependencyEvaluationException, InterruptedException {
    if (!(target instanceof Rule)) {
      return NO_CONFIG_CONDITIONS;
    }

    Map<Label, ConfigMatchingProvider> configConditions = new LinkedHashMap<>();

    // Collect the labels of the configured targets we need to resolve.
    OrderedSetMultimap<Attribute, Label> configLabelMap = OrderedSetMultimap.create();
    RawAttributeMapper attributeMap = RawAttributeMapper.of(((Rule) target));
    for (Attribute a : ((Rule) target).getAttributes()) {
      for (Label configLabel : attributeMap.getConfigurabilityKeys(a.getName(), a.getType())) {
        if (!BuildType.Selector.isReservedLabel(configLabel)) {
          configLabelMap.put(a, target.getLabel().resolveRepositoryRelative(configLabel));
        }
      }
    }
    if (configLabelMap.isEmpty()) {
      return NO_CONFIG_CONDITIONS;
    }

    // Collect the corresponding Skyframe configured target values. Abort early if they haven't
    // been computed yet.
    Collection<Dependency> configValueNames = resolver.resolveRuleLabels(
        ctgValue, configLabelMap, transitiveLoadingRootCauses);
    if (env.valuesMissing()) {
      return null;
    }


    // No need to get new configs from Skyframe - config_setting rules always use the current
    // target's config.
    // TODO(bazel-team): remove the need for this special transformation. We can probably do this by
    // simply passing this through trimConfigurations.
    BuildConfiguration targetConfig = ctgValue.getConfiguration();
    if (useDynamicConfigurations(targetConfig)) {
      ImmutableList.Builder<Dependency> staticConfigs = ImmutableList.builder();
      for (Dependency dep : configValueNames) {
        staticConfigs.add(
            Dependency.withConfigurationAndAspects(dep.getLabel(), targetConfig, dep.getAspects()));
      }
      configValueNames = staticConfigs.build();
    }

    Map<SkyKey, ConfiguredTarget> configValues = resolveConfiguredTargetDependencies(
        env, configValueNames, transitivePackages, transitiveLoadingRootCauses);
    if (configValues == null) {
      return null;
    }

    // Get the configured targets as ConfigMatchingProvider interfaces.
    for (Dependency entry : configValueNames) {
      ConfiguredTarget value = configValues.get(TO_KEYS.apply(entry));
      // The code above guarantees that value is non-null here.
      ConfigMatchingProvider provider = value.getProvider(ConfigMatchingProvider.class);
      if (provider != null) {
        configConditions.put(entry.getLabel(), provider);
      } else {
        // Not a valid provider for configuration conditions.
        String message =
            entry.getLabel() + " is not a valid configuration key for " + target.getLabel();
        env.getListener().handle(Event.error(TargetUtils.getLocationMaybe(target), message));
        throw new DependencyEvaluationException(new ConfiguredValueCreationException(
            message, target.getLabel()));
      }
    }

    return ImmutableMap.copyOf(configConditions);
  }

  /**
   * * Resolves the targets referenced in depValueNames and returns their ConfiguredTarget
   * instances.
   *
   * <p>Returns null if not all instances are available yet.
   */
  @Nullable
  private static Map<SkyKey, ConfiguredTarget> resolveConfiguredTargetDependencies(
      Environment env,
      Collection<Dependency> deps,
      NestedSetBuilder<Package> transitivePackages,
      NestedSetBuilder<Label> transitiveLoadingRootCauses)
      throws DependencyEvaluationException, InterruptedException {
    boolean missedValues = env.valuesMissing();
    boolean failed = false;
    Iterable<SkyKey> depKeys = Iterables.transform(deps, TO_KEYS);
    Map<SkyKey, ValueOrException<ConfiguredValueCreationException>> depValuesOrExceptions =
        env.getValuesOrThrow(depKeys, ConfiguredValueCreationException.class);
    Map<SkyKey, ConfiguredTarget> result =
        Maps.newHashMapWithExpectedSize(depValuesOrExceptions.size());
    for (Map.Entry<SkyKey, ValueOrException<ConfiguredValueCreationException>> entry
        : depValuesOrExceptions.entrySet()) {
      try {
        ConfiguredTargetValue depValue = (ConfiguredTargetValue) entry.getValue().get();
        if (depValue == null) {
          missedValues = true;
        } else {
          result.put(entry.getKey(), depValue.getConfiguredTarget());
          transitivePackages.addTransitive(depValue.getTransitivePackages());
        }
      } catch (ConfiguredValueCreationException e) {
        // TODO(ulfjack): If there is an analysis root cause, we drop all loading root causes.
        if (e.getAnalysisRootCause() != null) {
          throw new DependencyEvaluationException(e);
        }
        transitiveLoadingRootCauses.addTransitive(e.loadingRootCauses);
        failed = true;
      }
    }
    if (missedValues) {
      return null;
    } else if (failed) {
      throw new DependencyEvaluationException(
          new ConfiguredValueCreationException(transitiveLoadingRootCauses.build()));
    } else {
      return result;
    }
  }


  @Override
  public String extractTag(SkyKey skyKey) {
    return Label.print(((ConfiguredTargetKey) skyKey.argument()).getLabel());
  }

  @Nullable
  private ConfiguredTargetValue createConfiguredTarget(SkyframeBuildView view,
      Environment env, Target target, BuildConfiguration configuration,
      OrderedSetMultimap<Attribute, ConfiguredTarget> depValueMap,
      ImmutableMap<Label, ConfigMatchingProvider> configConditions,
      NestedSetBuilder<Package> transitivePackages)
      throws ConfiguredTargetFunctionException, InterruptedException {
    StoredEventHandler events = new StoredEventHandler();
    BuildConfiguration ownerConfig = (configuration == null)
        ? null : configuration.getArtifactOwnerConfiguration();
    CachingAnalysisEnvironment analysisEnvironment = view.createAnalysisEnvironment(
        new ConfiguredTargetKey(target.getLabel(), ownerConfig), false,
        events, env, configuration);
    if (env.valuesMissing()) {
      return null;
    }

    Preconditions.checkNotNull(depValueMap);
    ConfiguredTarget configuredTarget = view.createConfiguredTarget(target, configuration,
        analysisEnvironment, depValueMap, configConditions);

    events.replayOn(env.getListener());
    if (events.hasErrors()) {
      analysisEnvironment.disable(target);
      throw new ConfiguredTargetFunctionException(new ConfiguredValueCreationException(
          "Analysis of target '" + target.getLabel() + "' failed; build aborted",
          target.getLabel()));
    }
    Preconditions.checkState(!analysisEnvironment.hasErrors(),
        "Analysis environment hasError() but no errors reported");
    if (env.valuesMissing()) {
      return null;
    }

    analysisEnvironment.disable(target);
    Preconditions.checkNotNull(configuredTarget, target);

    ImmutableMap<Artifact, ActionAnalysisMetadata> generatingActions;
    // Check for conflicting actions within this configured target (that indicates a bug in the
    // rule implementation).
    try {
      generatingActions = Actions.filterSharedActionsAndThrowActionConflict(
          analysisEnvironment.getRegisteredActions());
    } catch (ActionConflictException e) {
      throw new ConfiguredTargetFunctionException(e);
    }
    return new ConfiguredTargetValue(
        configuredTarget, generatingActions, transitivePackages.build());
  }

  /**
   * An exception indicating that there was a problem during the construction of
   * a ConfiguredTargetValue.
   */
  public static final class ConfiguredValueCreationException extends Exception {
    private final NestedSet<Label> loadingRootCauses;
    // TODO(ulfjack): Collect all analysis root causes, not just the first one.
    @Nullable private final Label analysisRootCause;

    public ConfiguredValueCreationException(String message, Label currentTarget) {
      super(message);
      this.loadingRootCauses = NestedSetBuilder.<Label>emptySet(Order.STABLE_ORDER);
      this.analysisRootCause = Preconditions.checkNotNull(currentTarget);
    }

    public ConfiguredValueCreationException(String message, NestedSet<Label> rootCauses) {
      super(message);
      this.loadingRootCauses = rootCauses;
      this.analysisRootCause = null;
    }

    public ConfiguredValueCreationException(NestedSet<Label> rootCauses) {
      this("Loading failed", rootCauses);
    }

    public ConfiguredValueCreationException(String message) {
      this(message, NestedSetBuilder.<Label>emptySet(Order.STABLE_ORDER));
    }

    public NestedSet<Label> getRootCauses() {
      return loadingRootCauses;
    }

    public Label getAnalysisRootCause() {
      return analysisRootCause;
    }
  }

  /**
   * Used to declare all the exception types that can be wrapped in the exception thrown by
   * {@link ConfiguredTargetFunction#compute}.
   */
  public static final class ConfiguredTargetFunctionException extends SkyFunctionException {
    public ConfiguredTargetFunctionException(NoSuchThingException e) {
      super(e, Transience.PERSISTENT);
    }

    private ConfiguredTargetFunctionException(ConfiguredValueCreationException e) {
      super(e, Transience.PERSISTENT);
    }

    private ConfiguredTargetFunctionException(ActionConflictException e) {
      super(e, Transience.PERSISTENT);
    }
  }
}
