com.google.devtools.build.lib.skyframe.ConfiguredTargetFunction.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.build.lib.skyframe.ConfiguredTargetFunction.java

Source

// 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.Predicate;
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.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.AspectDescriptor;
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.SkylarkProviderIdentifier;
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.Collections;
import java.util.Comparator;
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);
            // It's important that we don't use "if (env.missingValues()) { return null }" here (or
            // in the following lines). See the comments in getDynamicConfigurations' Skyframe call
            // for explanation.
            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
     * analysis time. Keep this in mind when making modifications and performance-test any changes you
     * make.
     *
     * @param env Skyframe evaluation environment
     * @param ctgValue the label and the configuration of the node
     * @param originalDeps the set of configuration transition requests for this target's attributes
     * @param hostConfiguration the host configuration
     * @param ruleClassProvider the rule class provider for determining the right configuration
     *    fragments to apply to deps
     *
     * @return a mapping from each attribute to the {@link BuildConfiguration}s and {@link Label}s
     *    to use for that attribute's deps. Returns null if not all Skyframe dependencies are
     *    available yet.
     */
    @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 dynamically configured 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 dynamic 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.
        //
        // For split transitions, originaldeps.get("data") = [":a", ":b"] can produce the output
        // [":a"<config1>, ":a"<config2>, ..., ":b"<config1>, ":b"<config2>, ...]. All instances of ":a"
        // still appear before all instances of ":b". But the [":a"<config1>, ":a"<config2>"] subset may
        // be in any (deterministic) order. In particular, this may not be the same order as
        // SplitTransition.split. If needed, this code can be modified to use that order, but that
        // involves more runtime in performance-critical code, so we won't make that change without a
        // clear need.
        //
        // This map is used heavily by all builds. Inserts and gets should be as fast as possible.
        Multimap<AttributeAndLabel, Dependency> dynamicDeps = 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(dynamicDeps, 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(dynamicDeps, 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) {
                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(dynamicDeps, 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 from Skyframe. While not every value might be available,
        // we don't call env.valuesMissing() here because that could be true from the earlier
        // resolver.dependentNodeMap call in computeDependencies, which also calls Skyframe. This method
        // doesn't need those missing values, but it still has to be called after
        // resolver.dependentNodeMap because it consumes that method's output. The reason the missing
        // values don't matter is because resolver.dependentNodeMap still returns "partial" results
        // and this method runs over whatever's available.
        //
        // While there would be no *correctness* harm in nulling out early, there's significant
        // *performance* harm. Profiling shows that putting "if (env.valuesMissing()) { return null; }"
        // here (or even after resolver.dependentNodeMap) produces a ~30% performance hit on the
        // analysis phase. That's because resolveConfiguredTargetDependencies and
        // resolveAspectDependencies don't get a chance to make their own Skyframe requests before
        // bailing out of this ConfiguredTargetFunction call. Ideally we could batch all requests
        // from all methods into a single Skyframe call, but there are enough subtle data flow
        // dependencies in ConfiguredTargetFucntion to make that impractical.
        Map<SkyKey, ValueOrException<InvalidConfigurationException>> depConfigValues = env
                .getValuesOrThrow(keysToEntries.keySet(), InvalidConfigurationException.class);

        // 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();
                ValueOrException<InvalidConfigurationException> valueOrException = entry.getValue();
                if (valueOrException.get() == null) {
                    // Instead of env.missingValues(), check for missing values here. This guarantees we only
                    // null out on missing values from *this specific Skyframe request*.
                    return null;
                }
                BuildConfigurationValue trimmedConfig = (BuildConfigurationValue) valueOrException.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()) {
                        dynamicDeps.put(attr, resolvedDep);
                    } else {
                        putOnlyEntry(dynamicDeps, attr, resolvedDep);
                    }
                }
            }
        } catch (InvalidConfigurationException e) {
            throw new DependencyEvaluationException(e);
        }

        return sortDynamicallyConfiguredDeps(originalDeps, dynamicDeps, attributesAndLabels);
    }

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

    /**
     * Determines the output ordering of each <attribute, depLabel> ->
     * [dep<config1>, dep<config2>, ...] collection produced by a split transition.
     */
    @VisibleForTesting
    static final Comparator<Dependency> DYNAMIC_SPLIT_DEP_ORDERING = new Comparator<Dependency>() {
        @Override
        public int compare(Dependency d1, Dependency d2) {
            return d1.getConfiguration().getMnemonic().compareTo(d2.getConfiguration().getMnemonic());
        }
    };

    /**
     * Helper method for {@link #getDynamicConfigurations}: returns a copy of the output deps
     * using the same key and value ordering as the input deps.
     *
     * @param originalDeps the input deps with the ordering to preserve
     * @param dynamicDeps the unordered output deps
     * @param attributesAndLabels collection of <attribute, depLabel> pairs guaranteed to match
     *   the ordering of originalDeps.entries(). This is a performance optimization: see
     *   {@link #getDynamicConfigurations#attributesAndLabels} for details.
     */
    private static OrderedSetMultimap<Attribute, Dependency> sortDynamicallyConfiguredDeps(
            OrderedSetMultimap<Attribute, Dependency> originalDeps,
            Multimap<AttributeAndLabel, Dependency> dynamicDeps, ArrayList<AttributeAndLabel> attributesAndLabels) {
        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> dynamicAttrDeps = dynamicDeps.get(attrAndLabel);
                Verify.verify(!dynamicAttrDeps.isEmpty());
                if (dynamicAttrDeps.size() > 1) {
                    List<Dependency> sortedSplitList = new ArrayList<>(dynamicAttrDeps);
                    Collections.sort(sortedSplitList, DYNAMIC_SPLIT_DEP_ORDERING);
                    dynamicAttrDeps = sortedSplitList;
                }
                result.putAll(depsEntry.getKey(), dynamicAttrDeps);
            }
        }
        return result;
    }

    /**
     * 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(final ConfiguredTarget dep, Aspect aspect) {
        return aspect.getDefinition().getRequiredProviders().isSatisfiedBy(new Predicate<Class<?>>() {
            @Override
            public boolean apply(Class<?> provider) {
                return dep.getProvider(provider.asSubclass(TransitiveInfoProvider.class)) != null;
            }
        }, new Predicate<SkylarkProviderIdentifier>() {
            @Override
            public boolean apply(SkylarkProviderIdentifier skylarkProviderIdentifier) {
                return dep.get(skylarkProviderIdentifier) != null;
            }
        });
    }

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