aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build
diff options
context:
space:
mode:
authorGravatar gregce <gregce@google.com>2017-09-20 00:35:36 +0200
committerGravatar László Csomor <laszlocsomor@google.com>2017-09-20 09:04:07 +0200
commit9dbe48b678b0aa53456f6d44e2de1b4e15f6c634 (patch)
tree45ad94db99380c5078a1bbce6300ac8a288a6a72 /src/main/java/com/google/devtools/build
parent0bc9b3e14f305706d72180371f73a98d6bfcdf35 (diff)
Move ConfiguredTargetFunction dynamic config selection to its own class.
Also clarify the interfaces *TransitionResolver* - which determines what transition to apply to an input configuration and *ConfigurationResolver* - which determines the output configuration from that transition. PiperOrigin-RevId: 169311986
Diffstat (limited to 'src/main/java/com/google/devtools/build')
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/DependencyResolver.java18
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationResolver.java595
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/config/TransitionResolver.java211
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java472
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/PostConfiguredTargetFunction.java5
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java5
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/TransitiveTargetValue.java2
7 files changed, 683 insertions, 625 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/DependencyResolver.java b/src/main/java/com/google/devtools/build/lib/analysis/DependencyResolver.java
index 855c985486..41323b6892 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/DependencyResolver.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/DependencyResolver.java
@@ -22,11 +22,11 @@ import com.google.devtools.build.lib.analysis.AspectCollection.AspectCycleOnPath
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.ConfigurationResolver;
import com.google.devtools.build.lib.analysis.config.DynamicTransitionMapper;
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.analysis.config.TransitionResolver;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.events.Location;
@@ -62,10 +62,10 @@ import javax.annotation.Nullable;
* <p>Includes logic to derive the right configurations depending on transition type.
*/
public abstract class DependencyResolver {
- private final ConfigurationResolver configResolver;
+ private final TransitionResolver transitionResolver;
protected DependencyResolver(DynamicTransitionMapper transitionMapper) {
- this.configResolver = new ConfigurationResolver(transitionMapper);
+ this.transitionResolver = new TransitionResolver(transitionMapper);
}
/**
@@ -374,11 +374,11 @@ public abstract class DependencyResolver {
getSplitOptions(depResolver.rule, attribute, ruleConfig);
if (!splitOptions.isEmpty() && !ruleConfig.isHostConfiguration()) {
// Late-bound attribute with a split transition:
- // Since we want to get the same results as ConfigurationResolver.evaluateTransition (but
+ // Since we want to get the same results as TransitionResolver.evaluateTransition (but
// skip it since we've already applied the split), we want to make sure this logic
- // doesn't do anything differently. ConfigurationResolver.evaluateTransition has additional
+ // doesn't do anything differently. TransitionResolver.evaluateTransition has additional
// logic for host configs. So when we're in the host configuration we fall back to the
- // non-split branch, which calls ConfigurationResolver.evaluateTransition, which returns its
+ // non-split branch, which calls TransitionResolver.evaluateTransition, which returns its
// "host mode" result without ever looking at the split.
Iterable<BuildConfiguration> splitConfigs =
getConfigurations(ruleConfig.fragmentClasses(), splitOptions);
@@ -714,7 +714,7 @@ public abstract class DependencyResolver {
if (toTarget == null) {
return; // Skip this round: we still need to Skyframe-evaluate the dep's target.
}
- Attribute.Transition transition = configResolver.evaluateTransition(
+ Attribute.Transition transition = transitionResolver.evaluateTransition(
ruleConfig, rule, attributeAndOwner.attribute, toTarget);
outgoingEdges.put(
attributeAndOwner.attribute,
@@ -728,7 +728,7 @@ public abstract class DependencyResolver {
* Resolves the given dep for the given attribute using a pre-prepared configuration.
*
* <p>Use this method with care: it skips Bazel's standard config transition semantics ({@link
- * ConfigurationResolver#evaluateTransition}). That means attributes passed through here won't
+ * TransitionResolver#evaluateTransition}). That means attributes passed through here won't
* obey standard rules on which configurations apply to their deps. This should only be done for
* special circumstances that really justify the difference. When in doubt, use {@link
* #resolveDep(AttributeAndOwner, Label)}.
@@ -741,7 +741,7 @@ public abstract class DependencyResolver {
}
outgoingEdges.put(
attributeAndOwner.attribute,
- configResolver.usesNullConfiguration(toTarget)
+ transitionResolver.usesNullConfiguration(toTarget)
? Dependency.withNullConfiguration(depLabel)
: Dependency.withTransitionAndAspects(depLabel, new FixedTransition(
config.getOptions()), requiredAspects(attributeAndOwner, toTarget)));
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationResolver.java b/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationResolver.java
index 2da77b0129..02916941ef 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationResolver.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationResolver.java
@@ -15,193 +15,502 @@
package com.google.devtools.build.lib.analysis.config;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
+import com.google.common.base.Verify;
+import com.google.common.base.VerifyException;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+import com.google.devtools.build.lib.analysis.Dependency;
+import com.google.devtools.build.lib.analysis.TargetAndConfiguration;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.concurrent.ThreadSafety;
+import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.packages.Attribute;
-import com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition;
-import com.google.devtools.build.lib.packages.Attribute.SplitTransition;
-import com.google.devtools.build.lib.packages.Attribute.Transition;
-import com.google.devtools.build.lib.packages.InputFile;
-import com.google.devtools.build.lib.packages.PackageGroup;
-import com.google.devtools.build.lib.packages.Rule;
-import com.google.devtools.build.lib.packages.RuleTransitionFactory;
-import com.google.devtools.build.lib.packages.Target;
-import com.google.devtools.build.lib.util.Preconditions;
+import com.google.devtools.build.lib.packages.RuleClassProvider;
+import com.google.devtools.build.lib.skyframe.BuildConfigurationValue;
+import com.google.devtools.build.lib.skyframe.ConfiguredTargetFunction;
+import com.google.devtools.build.lib.skyframe.TransitiveTargetValue;
+import com.google.devtools.build.lib.util.OrderedSetMultimap;
+import com.google.devtools.build.skyframe.SkyFunction;
+import com.google.devtools.build.skyframe.SkyKey;
+import com.google.devtools.build.skyframe.ValueOrException;
+
+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.Objects;
+import java.util.Set;
+import javax.annotation.Nullable;
/**
- * Determines which configurations targets should take.
+ * Turns configuration transition requests into actual configurations.
+ *
+ * <p>This involves:
+ * <ol>
+ * <li>Patching a source configuration's options with the transition
+ * <li>If {@link BuildConfiguration#trimConfigurations} is true, trimming configuration fragments
+ * to only those needed by the destination target and its transitive dependencies
+ * <li>Getting the destination configuration from Skyframe
+ * </li>
+ * </ol>
*
- * <p>This is the "generic engine" for configuration selection. It doesn't know anything about
- * specific rules or their requirements. Rule writers decide those with appropriately placed
- * {@link PatchTransition} declarations. This class then processes those declarations to determine
- * final configurations.
+ * <p>For the work of determining the transition requests themselves, see
+ * {@link TransitionResolver}.
*/
public final class ConfigurationResolver {
- private final DynamicTransitionMapper transitionMapper;
-
/**
- * Instantiates this resolver with a helper class that maps non-{@link PatchTransition}s to
- * {@link PatchTransition}s.
+ * Translates a set of {@link Dependency} objects with configuration transition requests to the
+ * same objects with resolved configurations.
+ *
+ * <p>If {@link BuildConfiguration.Options#trimConfigurations()} is true, these configurations
+ * only contain the fragments needed by the dep and its transitive closure. Else they
+ * unconditionally include all fragments.
+ *
+ * <p>This method is heavily performance-optimized. Because {@link ConfiguredTargetFunction} calls
+ * it 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 configuration of the source target
+ * @param originalDeps the transition requests for each dep under this target's attributes
+ * @param hostConfiguration the host configuration
+ * @param ruleClassProvider provider for determining the right configuration fragments for deps
+ *
+ * @return a mapping from each attribute in the source target to the {@link BuildConfiguration}s
+ * and {@link Label}s for the deps under that attribute. Returns null if not all Skyframe
+ * dependencies are available.
*/
- public ConfigurationResolver(DynamicTransitionMapper transitionMapper) {
- this.transitionMapper = transitionMapper;
+ @Nullable
+ public static OrderedSetMultimap<Attribute, Dependency> resolveConfigurations(
+ SkyFunction.Environment env,
+ TargetAndConfiguration ctgValue,
+ OrderedSetMultimap<Attribute, Dependency> originalDeps,
+ BuildConfiguration hostConfiguration,
+ RuleClassProvider ruleClassProvider)
+ throws ConfiguredTargetFunction.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 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 configuration-resolved 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 resolved 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> resolvedDeps = 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
+ // when preparing final results. Note that null-configured deps are received with
+ // NullConfigurationDependency instead of
+ // 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.hasExplicitConfiguration()) {
+ 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 trimmed 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(
+ resolvedDeps,
+ 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(
+ resolvedDeps,
+ 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 = applyTransition(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(
+ resolvedDeps,
+ 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()) {
+ resolvedDeps.put(attr, resolvedDep);
+ } else {
+ putOnlyEntry(resolvedDeps, attr, resolvedDep);
+ }
+ }
+ }
+ } catch (InvalidConfigurationException e) {
+ throw new ConfiguredTargetFunction.DependencyEvaluationException(e);
+ }
+
+ return sortResolvedDeps(originalDeps, resolvedDeps, attributesAndLabels);
}
/**
- * Given a parent rule and configuration depending on a child through an attribute, determines
- * the configuration the child should take.
- *
- * @param fromConfig the parent rule's configuration
- * @param fromRule the parent rule
- * @param attribute the attribute creating the dependency (e.g. "srcs")
- * @param toTarget the child target (which may or may not be a rule)
- *
- * @return the child's configuration, expressed as a diff from the parent's configuration. This
- * is usually a {@PatchTransition} but exceptions apply (e.g.
- * {@link Attribute.ConfigurationTransition}).
+ * Encapsulates a set of config fragments and a config transition. This can be used to determine
+ * the exact build options needed to set a configuration.
*/
- public Transition evaluateTransition(BuildConfiguration fromConfig, final Rule fromRule,
- final Attribute attribute, final Target toTarget) {
+ @ThreadSafety.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;
- // I. Input files and package groups have no configurations. We don't want to duplicate them.
- if (usesNullConfiguration(toTarget)) {
- return Attribute.ConfigurationTransition.NULL;
+ FragmentsAndTransition(Set<Class<? extends BuildConfiguration.Fragment>> fragments,
+ Attribute.Transition transition) {
+ this.fragments = fragments;
+ this.transition = transition;
+ hashCode = Objects.hash(this.fragments, this.transition);
}
- // II. Host configurations never switch to another. All prerequisites of host targets have the
- // same host configuration.
- if (fromConfig.isHostConfiguration()) {
- return Attribute.ConfigurationTransition.NONE;
+ @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);
+ }
}
- // Make sure config_setting dependencies are resolved in the referencing rule's configuration,
- // unconditionally. For example, given:
- //
- // genrule(
- // name = 'myrule',
- // tools = select({ '//a:condition': [':sometool'] })
- //
- // all labels in "tools" get resolved in the host configuration (since the "tools" attribute
- // declares a host configuration transition). We want to explicitly exclude configuration labels
- // from these transitions, since their *purpose* is to do computation on the owning
- // rule's configuration.
- // TODO(bazel-team): don't require special casing here. This is far too hackish.
- if (toTarget instanceof Rule && ((Rule) toTarget).getRuleClassObject().isConfigMatcher()) {
- // TODO(gregce): see if this actually gets called
- return Attribute.ConfigurationTransition.NONE;
- }
-
- // The current transition to apply. When multiple transitions are requested, this is a
- // ComposingSplitTransition, which encapsulates them into a single object so calling code
- // doesn't need special logic for combinations.
- Transition currentTransition = Attribute.ConfigurationTransition.NONE;
-
- // Apply the parent rule's outgoing transition if it has one.
- RuleTransitionFactory transitionFactory =
- fromRule.getRuleClassObject().getOutgoingTransitionFactory();
- if (transitionFactory != null) {
- Transition transition = transitionFactory.buildTransitionFor(toTarget.getAssociatedRule());
- if (transition != null) {
- currentTransition = composeTransitions(currentTransition, transition);
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+ }
+
+ /**
+ * Encapsulates an <attribute, label> pair that can be used to map from an input dependency to a
+ * trimmed dependency.
+ */
+ @ThreadSafety.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);
}
- // TODO(gregce): make the below transitions composable (i.e. take away the "else" clauses).
- // The "else" is a legacy restriction from static configurations.
- if (attribute.hasSplitConfigurationTransition()) {
- currentTransition = split(currentTransition,
- (SplitTransition<BuildOptions>) attribute.getSplitTransition(fromRule));
- } else {
- // III. Attributes determine configurations. The configuration of a prerequisite is determined
- // by the attribute.
- currentTransition = composeTransitions(currentTransition,
- attribute.getConfigurationTransition());
+ @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;
}
+ }
- return applyConfigurationHook(currentTransition, toTarget);
+ /**
+ * Variation of {@link Multimap#put} that triggers an exception if a value already exists.
+ */
+ @VisibleForTesting
+ public 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);
}
+
/**
- * Returns true if the given target should have a null configuration. This method is the
- * "source of truth" for this determination.
+ * 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
*/
- public static boolean usesNullConfiguration(Target target) {
- return target instanceof InputFile || target instanceof PackageGroup;
+ @Nullable
+ private static Set<Class<? extends BuildConfiguration.Fragment>> getTransitiveFragments(
+ SkyFunction.Environment env, Label dep, BuildConfiguration parentConfig)
+ throws InterruptedException {
+ 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();
}
/**
- * Composes two transitions together efficiently.
+ * Applies a 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).
*/
@VisibleForTesting
- public Transition composeTransitions(Transition transition1, Transition transition2) {
- if (isFinal(transition1)) {
- return transition1;
- } else if (transition2 == Attribute.ConfigurationTransition.NONE) {
- return transition1;
- } else if (transition2 == Attribute.ConfigurationTransition.NULL) {
- // A NULL transition can just replace earlier transitions: no need to cfpose them.
- return Attribute.ConfigurationTransition.NULL;
- } else if (transition2 == Attribute.ConfigurationTransition.HOST) {
- // A HOST transition can just replace earlier transitions: no need to compose them.
- // But it also improves performance: host transitions are common, and
- // ConfiguredTargetFunction has special optimized logic to handle them. If they were buried
- // in the last segment of a ComposingSplitTransition, those optimizations wouldn't trigger.
- return HostTransition.INSTANCE;
- }
-
- // TODO(gregce): remove the below conversion when all transitions are patch transitions.
- Transition dynamicTransition = transitionMapper.map(transition2);
- return transition1 == Attribute.ConfigurationTransition.NONE
- ? dynamicTransition
- : new ComposingSplitTransition(transition1, dynamicTransition);
+ public static List<BuildOptions> applyTransition(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.
+ // So return the original options.
+ result = ImmutableList.<BuildOptions>of(fromOptions);
+ } else {
+ result = toOptions;
+ }
+ } else {
+ throw new IllegalStateException(String.format(
+ "unsupported config 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();
+ }
}
/**
- * Returns true if once the given transition is applied to a dep no followup transitions should
- * be composed after it.
+ * 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 boolean isFinal(Transition transition) {
- return (transition == Attribute.ConfigurationTransition.NULL
- || transition == HostTransition.INSTANCE);
+ private static void checkForMissingFragments(SkyFunction.Environment env,
+ TargetAndConfiguration ctgValue, String attribute, Dependency dep,
+ Set<Class<? extends BuildConfiguration.Fragment>> expectedDepFragments)
+ throws ConfiguredTargetFunction.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 ConfiguredTargetFunction.DependencyEvaluationException(
+ new InvalidConfigurationException(msg));
+ }
}
/**
- * Applies the given split and composes it after an existing transition.
+ * Determines the output ordering of each <attribute, depLabel> ->
+ * [dep<config1>, dep<config2>, ...] collection produced by a split transition.
*/
- private static Transition split(Transition currentTransition,
- SplitTransition<BuildOptions> split) {
- Preconditions.checkState(currentTransition != Attribute.ConfigurationTransition.NULL,
- "cannot apply splits after null transitions (null transitions are expected to be final)");
- Preconditions.checkState(currentTransition != HostTransition.INSTANCE,
- "cannot apply splits after host transitions (host transitions are expected to be final)");
- return currentTransition == Attribute.ConfigurationTransition.NONE
- ? split
- : new ComposingSplitTransition(currentTransition, split);
- }
+ @VisibleForTesting
+ public static final Comparator<Dependency> SPLIT_DEP_ORDERING =
+ new Comparator<Dependency>() {
+ @Override
+ public int compare(Dependency d1, Dependency d2) {
+ return d1.getConfiguration().getMnemonic().compareTo(d2.getConfiguration().getMnemonic());
+ }
+ };
/**
- * Applies any configuration hooks associated with the dep target, composes their transitions
- * after an existing transition, and returns the composed result.
+ * 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 resolvedDeps 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 #resolveConfigurations#attributesAndLabels} for details.
*/
- private Transition applyConfigurationHook(Transition currentTransition, Target toTarget) {
- if (isFinal(currentTransition)) {
- return currentTransition;
- }
- Rule associatedRule = toTarget.getAssociatedRule();
- RuleTransitionFactory transitionFactory =
- associatedRule.getRuleClassObject().getTransitionFactory();
- if (transitionFactory != null) {
- // transitionMapper is only needed because of Attribute.ConfigurationTransition.DATA: this is
- // C++-specific but non-C++ rules declare it. So they can't directly provide the C++-specific
- // patch transition that implements it.
- PatchTransition ruleClassTransition = (PatchTransition)
- transitionMapper.map(transitionFactory.buildTransitionFor(associatedRule));
- if (ruleClassTransition != null) {
- if (currentTransition == ConfigurationTransition.NONE) {
- return ruleClassTransition;
- } else {
- return new ComposingSplitTransition(currentTransition, ruleClassTransition);
+ private static OrderedSetMultimap<Attribute, Dependency> sortResolvedDeps(
+ OrderedSetMultimap<Attribute, Dependency> originalDeps,
+ Multimap<AttributeAndLabel, Dependency> resolvedDeps,
+ 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().hasExplicitConfiguration()) {
+ result.put(attrAndLabel.attribute, depsEntry.getValue());
+ } else {
+ Collection<Dependency> resolvedDepWithSplit = resolvedDeps.get(attrAndLabel);
+ Verify.verify(!resolvedDepWithSplit.isEmpty());
+ if (resolvedDepWithSplit.size() > 1) {
+ List<Dependency> sortedSplitList = new ArrayList<>(resolvedDepWithSplit);
+ Collections.sort(sortedSplitList, SPLIT_DEP_ORDERING);
+ resolvedDepWithSplit = sortedSplitList;
}
+ result.putAll(depsEntry.getKey(), resolvedDepWithSplit);
}
}
- return currentTransition;
+ return result;
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/TransitionResolver.java b/src/main/java/com/google/devtools/build/lib/analysis/config/TransitionResolver.java
new file mode 100644
index 0000000000..ebcefb6eb2
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/TransitionResolver.java
@@ -0,0 +1,211 @@
+// Copyright 2017 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.analysis.config;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.devtools.build.lib.packages.Attribute;
+import com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition;
+import com.google.devtools.build.lib.packages.Attribute.SplitTransition;
+import com.google.devtools.build.lib.packages.Attribute.Transition;
+import com.google.devtools.build.lib.packages.InputFile;
+import com.google.devtools.build.lib.packages.PackageGroup;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.packages.RuleTransitionFactory;
+import com.google.devtools.build.lib.packages.Target;
+import com.google.devtools.build.lib.util.Preconditions;
+
+/**
+ * Determines the {@link Attribute.Transition}s dependencies should apply to their parents'
+ * configurations.
+ *
+ * <p>For the work of turning these transitions into actual configurations, see
+ * {@link ConfigurationResolver}.
+ *
+ * <p>This is the "generic engine" for configuration selection. It doesn't know anything about
+ * specific rules or their requirements. Rule writers decide those with appropriately placed
+ * {@link PatchTransition} declarations. This class then processes those declarations to determine
+ * final transitions.
+ */
+public final class TransitionResolver {
+ private final DynamicTransitionMapper transitionMapper;
+
+ /**
+ * Instantiates this resolver with a helper class that maps non-{@link PatchTransition}s to
+ * {@link PatchTransition}s.
+ */
+ public TransitionResolver(DynamicTransitionMapper transitionMapper) {
+ this.transitionMapper = transitionMapper;
+ }
+
+ /**
+ * Given a parent rule and configuration depending on a child through an attribute, determines
+ * the configuration the child should take.
+ *
+ * @param fromConfig the parent rule's configuration
+ * @param fromRule the parent rule
+ * @param attribute the attribute creating the dependency (e.g. "srcs")
+ * @param toTarget the child target (which may or may not be a rule)
+ *
+ * @return the child's configuration, expressed as a diff from the parent's configuration. This
+ * is usually a {@PatchTransition} but exceptions apply (e.g.
+ * {@link Attribute.ConfigurationTransition}).
+ */
+ public Transition evaluateTransition(BuildConfiguration fromConfig, final Rule fromRule,
+ final Attribute attribute, final Target toTarget) {
+
+ // I. Input files and package groups have no configurations. We don't want to duplicate them.
+ if (usesNullConfiguration(toTarget)) {
+ return Attribute.ConfigurationTransition.NULL;
+ }
+
+ // II. Host configurations never switch to another. All prerequisites of host targets have the
+ // same host configuration.
+ if (fromConfig.isHostConfiguration()) {
+ return Attribute.ConfigurationTransition.NONE;
+ }
+
+ // Make sure config_setting dependencies are resolved in the referencing rule's configuration,
+ // unconditionally. For example, given:
+ //
+ // genrule(
+ // name = 'myrule',
+ // tools = select({ '//a:condition': [':sometool'] })
+ //
+ // all labels in "tools" get resolved in the host configuration (since the "tools" attribute
+ // declares a host configuration transition). We want to explicitly exclude configuration labels
+ // from these transitions, since their *purpose* is to do computation on the owning
+ // rule's configuration.
+ // TODO(bazel-team): don't require special casing here. This is far too hackish.
+ if (toTarget instanceof Rule && ((Rule) toTarget).getRuleClassObject().isConfigMatcher()) {
+ // TODO(gregce): see if this actually gets called
+ return Attribute.ConfigurationTransition.NONE;
+ }
+
+ // The current transition to apply. When multiple transitions are requested, this is a
+ // ComposingSplitTransition, which encapsulates them into a single object so calling code
+ // doesn't need special logic for combinations.
+ Transition currentTransition = Attribute.ConfigurationTransition.NONE;
+
+ // Apply the parent rule's outgoing transition if it has one.
+ RuleTransitionFactory transitionFactory =
+ fromRule.getRuleClassObject().getOutgoingTransitionFactory();
+ if (transitionFactory != null) {
+ Transition transition = transitionFactory.buildTransitionFor(toTarget.getAssociatedRule());
+ if (transition != null) {
+ currentTransition = composeTransitions(currentTransition, transition);
+ }
+ }
+
+ // TODO(gregce): make the below transitions composable (i.e. take away the "else" clauses).
+ // The "else" is a legacy restriction from static configurations.
+ if (attribute.hasSplitConfigurationTransition()) {
+ currentTransition = split(currentTransition,
+ (SplitTransition<BuildOptions>) attribute.getSplitTransition(fromRule));
+ } else {
+ // III. Attributes determine configurations. The configuration of a prerequisite is determined
+ // by the attribute.
+ currentTransition = composeTransitions(currentTransition,
+ attribute.getConfigurationTransition());
+ }
+
+ return applyConfigurationHook(currentTransition, toTarget);
+ }
+
+ /**
+ * Returns true if the given target should have a null configuration. This method is the
+ * "source of truth" for this determination.
+ */
+ public static boolean usesNullConfiguration(Target target) {
+ return target instanceof InputFile || target instanceof PackageGroup;
+ }
+
+ /**
+ * Composes two transitions together efficiently.
+ */
+ @VisibleForTesting
+ public Transition composeTransitions(Transition transition1, Transition transition2) {
+ if (isFinal(transition1)) {
+ return transition1;
+ } else if (transition2 == Attribute.ConfigurationTransition.NONE) {
+ return transition1;
+ } else if (transition2 == Attribute.ConfigurationTransition.NULL) {
+ // A NULL transition can just replace earlier transitions: no need to compose them.
+ return Attribute.ConfigurationTransition.NULL;
+ } else if (transition2 == Attribute.ConfigurationTransition.HOST) {
+ // A HOST transition can just replace earlier transitions: no need to compose them.
+ // But it also improves performance: host transitions are common, and
+ // ConfiguredTargetFunction has special optimized logic to handle them. If they were buried
+ // in the last segment of a ComposingSplitTransition, those optimizations wouldn't trigger.
+ return HostTransition.INSTANCE;
+ }
+
+ // TODO(gregce): remove the below conversion when all transitions are patch transitions.
+ Transition dynamicTransition = transitionMapper.map(transition2);
+ return transition1 == Attribute.ConfigurationTransition.NONE
+ ? dynamicTransition
+ : new ComposingSplitTransition(transition1, dynamicTransition);
+ }
+
+ /**
+ * Returns true if once the given transition is applied to a dep no followup transitions should
+ * be composed after it.
+ */
+ private static boolean isFinal(Transition transition) {
+ return (transition == Attribute.ConfigurationTransition.NULL
+ || transition == HostTransition.INSTANCE);
+ }
+
+ /**
+ * Applies the given split and composes it after an existing transition.
+ */
+ private static Transition split(Transition currentTransition,
+ SplitTransition<BuildOptions> split) {
+ Preconditions.checkState(currentTransition != Attribute.ConfigurationTransition.NULL,
+ "cannot apply splits after null transitions (null transitions are expected to be final)");
+ Preconditions.checkState(currentTransition != HostTransition.INSTANCE,
+ "cannot apply splits after host transitions (host transitions are expected to be final)");
+ return currentTransition == Attribute.ConfigurationTransition.NONE
+ ? split
+ : new ComposingSplitTransition(currentTransition, split);
+ }
+
+ /**
+ * Applies any configuration hooks associated with the dep target, composes their transitions
+ * after an existing transition, and returns the composed result.
+ */
+ private Transition applyConfigurationHook(Transition currentTransition, Target toTarget) {
+ if (isFinal(currentTransition)) {
+ return currentTransition;
+ }
+ Rule associatedRule = toTarget.getAssociatedRule();
+ RuleTransitionFactory transitionFactory =
+ associatedRule.getRuleClassObject().getTransitionFactory();
+ if (transitionFactory != null) {
+ // transitionMapper is only needed because of Attribute.ConfigurationTransition.DATA: this is
+ // C++-specific but non-C++ rules declare it. So they can't directly provide the C++-specific
+ // patch transition that implements it.
+ PatchTransition ruleClassTransition = (PatchTransition)
+ transitionMapper.map(transitionFactory.buildTransitionFor(associatedRule));
+ if (ruleClassTransition != null) {
+ if (currentTransition == ConfigurationTransition.NONE) {
+ return ruleClassTransition;
+ } else {
+ return new ComposingSplitTransition(currentTransition, ruleClassTransition);
+ }
+ }
+ }
+ return currentTransition;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java
index 715ecf23d9..c157351ba2 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java
@@ -13,20 +13,12 @@
// limitations under the License.
package com.google.devtools.build.lib.skyframe;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Joiner;
import com.google.common.base.Supplier;
-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.ImmutableSet;
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.Actions;
import com.google.devtools.build.lib.actions.Actions.GeneratingActions;
import com.google.devtools.build.lib.actions.MutableActionGraph.ActionConflictException;
@@ -42,18 +34,15 @@ import com.google.devtools.build.lib.analysis.LabelAndConfiguration;
import com.google.devtools.build.lib.analysis.TargetAndConfiguration;
import com.google.devtools.build.lib.analysis.ToolchainContext;
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.ConfigurationResolver;
import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
-import com.google.devtools.build.lib.analysis.config.PatchTransition;
import com.google.devtools.build.lib.analysis.configuredtargets.MergedConfiguredTarget;
import com.google.devtools.build.lib.analysis.configuredtargets.MergedConfiguredTarget.DuplicateException;
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;
@@ -81,17 +70,12 @@ 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.HashMap;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.LinkedHashMap;
-import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Semaphore;
import javax.annotation.Nullable;
@@ -401,8 +385,8 @@ public final class ConfiguredTargetFunction implements SkyFunction {
// Trim each dep's configuration so it only includes the fragments needed by its transitive
// closure.
if (ctgValue.getConfiguration() != null) {
- depValueNames = getDynamicConfigurations(env, ctgValue, depValueNames, hostConfiguration,
- ruleClassProvider);
+ depValueNames = ConfigurationResolver.resolveConfigurations(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.
@@ -438,454 +422,6 @@ public final class ConfiguredTargetFunction implements SkyFunction {
}
/**
- * 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#hasExplicitConfiguration()} == 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.hasExplicitConfiguration()) {
- 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 {
- 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.
- // So return the original options.
- 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().hasExplicitConfiguration()) {
- 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
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PostConfiguredTargetFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/PostConfiguredTargetFunction.java
index 0a6d6af9c8..897c0fc177 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PostConfiguredTargetFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PostConfiguredTargetFunction.java
@@ -24,6 +24,7 @@ import com.google.devtools.build.lib.analysis.LabelAndConfiguration;
import com.google.devtools.build.lib.analysis.TargetAndConfiguration;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider;
+import com.google.devtools.build.lib.analysis.config.ConfigurationResolver;
import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.packages.Attribute;
@@ -113,8 +114,8 @@ public class PostConfiguredTargetFunction implements SkyFunction {
configConditions,
/*toolchainContext=*/ null);
if (ct.getConfiguration() != null) {
- deps = ConfiguredTargetFunction.getDynamicConfigurations(env, ctgValue, deps,
- hostConfiguration, ruleClassProvider);
+ deps = ConfigurationResolver.resolveConfigurations(env, ctgValue, deps, hostConfiguration,
+ ruleClassProvider);
}
} catch (EvalException | ConfiguredTargetFunction.DependencyEvaluationException
| InvalidConfigurationException | InconsistentAspectOrderException e) {
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
index 42709cc8d8..1cbb82743b 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
@@ -68,6 +68,7 @@ import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.analysis.config.BuildConfigurationCollection;
import com.google.devtools.build.lib.analysis.config.BuildOptions;
import com.google.devtools.build.lib.analysis.config.ConfigurationFragmentFactory;
+import com.google.devtools.build.lib.analysis.config.ConfigurationResolver;
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;
@@ -1454,7 +1455,7 @@ public abstract class SkyframeExecutor implements WalkableGraphFactory {
Set<Class<? extends BuildConfiguration.Fragment>> depFragments =
fragmentsMap.get(key.getLabel());
if (depFragments != null) {
- for (BuildOptions toOptions : ConfiguredTargetFunction.getDynamicTransitionOptions(
+ for (BuildOptions toOptions : ConfigurationResolver.applyTransition(
fromOptions, key.getTransition(), depFragments, ruleClassProvider, true)) {
configSkyKeys.add(BuildConfigurationValue.key(depFragments, toOptions));
}
@@ -1469,7 +1470,7 @@ public abstract class SkyframeExecutor implements WalkableGraphFactory {
Set<Class<? extends BuildConfiguration.Fragment>> depFragments =
fragmentsMap.get(key.getLabel());
if (depFragments != null) {
- for (BuildOptions toOptions : ConfiguredTargetFunction.getDynamicTransitionOptions(
+ for (BuildOptions toOptions : ConfigurationResolver.applyTransition(
fromOptions, key.getTransition(), depFragments, ruleClassProvider, true)) {
SkyKey configKey = BuildConfigurationValue.key(depFragments, toOptions);
BuildConfigurationValue configValue =
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/TransitiveTargetValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/TransitiveTargetValue.java
index 843c531b3d..531d2152f0 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/TransitiveTargetValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/TransitiveTargetValue.java
@@ -126,7 +126,7 @@ public class TransitiveTargetValue implements SkyValue {
* <p>See
* {@link com.google.devtools.build.lib.packages.RuleClass.Builder#requiresConfigurationFragments}
*/
- NestedSet<Class<? extends BuildConfiguration.Fragment>> getTransitiveConfigFragments() {
+ public NestedSet<Class<? extends BuildConfiguration.Fragment>> getTransitiveConfigFragments() {
return transitiveConfigFragments;
}