aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Greg Estren <gregce@google.com>2016-08-05 21:07:02 +0000
committerGravatar Yue Gan <yueg@google.com>2016-08-08 08:08:13 +0000
commit66cadd3d1c6014799616ba22f9d1381a82006c48 (patch)
treeb12a01b53e88aeb239ad29c1c37f990e7704a322
parent57472638ce94796deabc3d3db4ba851a1d0d600f (diff)
Implements dynamic split transitions (minus latebound attribute splits).
With the prereq work behind this, this is surprisingly straightforward. The main change is to eliminate BuildConfiguration.SplittableTransitionApplier, make both DynamicTransitionApplier and StaticTransitionApplier split-aware, and add awareness of this to ConfiguredTargetFunction.trimConfigurations. Latebound splits will follow next. -- MOS_MIGRATED_REVID=129480309
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java22
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java220
-rw-r--r--src/main/java/com/google/devtools/build/lib/packages/Attribute.java4
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java98
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java19
5 files changed, 188 insertions, 175 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
index b429606b48..1bd77b5719 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
@@ -19,6 +19,7 @@ import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
+import com.google.common.base.Verify;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
@@ -43,6 +44,7 @@ import com.google.devtools.build.lib.analysis.actions.ActionConstructionContext;
import com.google.devtools.build.lib.analysis.buildinfo.BuildInfoFactory.BuildInfoKey;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration.Fragment;
+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.FragmentCollection;
import com.google.devtools.build.lib.cmdline.Label;
@@ -704,26 +706,28 @@ public final class RuleContext extends TargetContext
checkAttribute(attributeName, Mode.SPLIT);
Attribute attributeDefinition = getAttribute(attributeName);
- SplitTransition<?> transition = attributeDefinition.getSplitTransition(rule);
- List<BuildConfiguration> configurations =
- getConfiguration().getTransitions().getSplitConfigurationsNoSelf(transition);
- if (configurations.isEmpty()) {
+ @SuppressWarnings("unchecked") // Attribute.java doesn't have the BuildOptions symbol.
+ SplitTransition<BuildOptions> transition =
+ (SplitTransition<BuildOptions>) attributeDefinition.getSplitTransition(rule);
+ List<ConfiguredTarget> deps = targetMap.get(attributeName);
+
+ List<BuildOptions> splitOptions = transition.split(getConfiguration().getOptions());
+ if (splitOptions.isEmpty()) {
// The split transition is not active. Defer the decision on which CPU to use.
- return ImmutableMap.of(Optional.<String>absent(), targetMap.get(attributeName));
+ return ImmutableMap.of(Optional.<String>absent(), deps);
}
Set<String> cpus = new HashSet<>();
- for (BuildConfiguration config : configurations) {
+ for (BuildOptions options : splitOptions) {
// This method should only be called when the split config is enabled on the command line, in
// which case this cpu can't be null.
- Preconditions.checkNotNull(config.getCpu());
- cpus.add(config.getCpu());
+ cpus.add(Verify.verifyNotNull(options.get(BuildConfiguration.Options.class).getCpu()));
}
// Use an ImmutableListMultimap.Builder here to preserve ordering.
ImmutableListMultimap.Builder<Optional<String>, TransitiveInfoCollection> result =
ImmutableListMultimap.builder();
- for (TransitiveInfoCollection t : targetMap.get(attributeName)) {
+ for (TransitiveInfoCollection t : deps) {
if (t.getConfiguration() != null) {
result.put(Optional.of(t.getConfiguration().getCpu()), t);
} else {
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java
index d5836c6d42..27b0d093d1 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java
@@ -26,6 +26,7 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.MutableClassToInstanceMap;
@@ -1499,12 +1500,6 @@ public final class BuildConfiguration {
void applyConfigurationHook(Rule fromRule, Attribute attribute, Target toTarget);
/**
- * Returns the underlying {@Transitions} object for this instance's current configuration.
- * Does not work for split configurations.
- */
- Transitions getCurrentTransitions();
-
- /**
* Populates a {@link com.google.devtools.build.lib.analysis.Dependency}
* for each configuration represented by this instance.
* TODO(bazel-team): this is a really ugly reverse dependency: factor this away.
@@ -1517,14 +1512,16 @@ public final class BuildConfiguration {
* {@link com.google.devtools.build.lib.analysis.Dependency} objects with
* actual configurations.
*
- * <p>Does not support split transitions (see {@link SplittableTransitionApplier}).
* TODO(bazel-team): remove this when dynamic configurations are fully production-ready.
*/
private static class StaticTransitionApplier implements TransitionApplier {
- BuildConfiguration currentConfiguration;
+ // The configuration(s) this applier applies to dep rules. Plural because of split transitions.
+ // May change multiple times: the ultimate transition might be a sequence of intermediate
+ // transitions.
+ List<BuildConfiguration> toConfigurations;
private StaticTransitionApplier(BuildConfiguration originalConfiguration) {
- this.currentConfiguration = originalConfiguration;
+ this.toConfigurations = ImmutableList.<BuildConfiguration>of(originalConfiguration);
}
@Override
@@ -1535,72 +1532,95 @@ public final class BuildConfiguration {
@Override
public void applyTransition(Transition transition) {
if (transition == Attribute.ConfigurationTransition.NULL) {
- currentConfiguration = null;
+ toConfigurations = Lists.<BuildConfiguration>asList(null, new BuildConfiguration[0]);
} else {
- currentConfiguration =
- currentConfiguration.getTransitions().getStaticConfiguration(transition);
+ ImmutableList.Builder<BuildConfiguration> newConfigs = ImmutableList.builder();
+ for (BuildConfiguration currentConfig : toConfigurations) {
+ newConfigs.add(currentConfig.getTransitions().getStaticConfiguration(transition));
+ }
+ toConfigurations = newConfigs.build();
}
}
@Override
public void split(SplitTransition<?> splitTransition) {
- throw new UnsupportedOperationException("This only works with SplittableTransitionApplier");
+ // Split transitions can't be nested, so if we're splitting we must be doing it over
+ // a single config.
+ toConfigurations =
+ Iterables.getOnlyElement(toConfigurations).getSplitConfigurations(splitTransition);
}
@Override
public boolean isNull() {
- return currentConfiguration == null;
+ return toConfigurations.size() == 1
+ ? Iterables.getOnlyElement(toConfigurations) == null
+ : false;
}
@Override
public void applyAttributeConfigurator(Attribute attribute, Rule fromRule, Target toTarget) {
+ // Checks that evaluateTransition never applies an attribute configurator and split
+ // transition in the same call.
+ Verify.verify(toConfigurations.size() == 1);
@SuppressWarnings("unchecked")
Configurator<BuildConfiguration, Rule> configurator =
(Configurator<BuildConfiguration, Rule>) attribute.getConfigurator();
Verify.verifyNotNull(configurator);
- currentConfiguration =
- configurator.apply(fromRule, currentConfiguration, attribute, toTarget);
+ toConfigurations = ImmutableList.<BuildConfiguration>of(
+ configurator.apply(fromRule, Iterables.getOnlyElement(toConfigurations), attribute,
+ toTarget));
}
@Override
public void applyConfigurationHook(Rule fromRule, Attribute attribute, Target toTarget) {
- currentConfiguration.getTransitions().configurationHook(fromRule, attribute, toTarget, this);
+ ImmutableList.Builder<BuildConfiguration> toConfigs = ImmutableList.builder();
+ for (BuildConfiguration currentConfig : toConfigurations) {
+ // BuildConfigurationCollection.configurationHook can apply further transitions. We want
+ // those transitions to only affect currentConfig (not everything in toConfigurations), so
+ // we use a delegate bound to only that config.
+ StaticTransitionApplier delegate = new StaticTransitionApplier(currentConfig);
+ currentConfig.getTransitions().configurationHook(fromRule, attribute, toTarget, delegate);
+ currentConfig = Iterables.getOnlyElement(delegate.toConfigurations);
+
+ // Allow rule classes to override their own configurations.
+ Rule associatedRule = toTarget.getAssociatedRule();
+ if (associatedRule != null) {
+ @SuppressWarnings("unchecked")
+ RuleClass.Configurator<BuildConfiguration, Rule> func =
+ associatedRule.getRuleClassObject().<BuildConfiguration, Rule>getConfigurator();
+ currentConfig = func.apply(associatedRule, currentConfig);
+ }
- // Allow rule classes to override their own configurations.
- Rule associatedRule = toTarget.getAssociatedRule();
- if (associatedRule != null) {
- @SuppressWarnings("unchecked")
- RuleClass.Configurator<BuildConfiguration, Rule> func =
- associatedRule.getRuleClassObject().<BuildConfiguration, Rule>getConfigurator();
- currentConfiguration = func.apply(associatedRule, currentConfiguration);
+ toConfigs.add(currentConfig);
}
- }
-
- @Override
- public Transitions getCurrentTransitions() {
- return currentConfiguration.getTransitions();
+ toConfigurations = toConfigs.build();
}
@Override
public Iterable<Dependency> getDependencies(
Label label, ImmutableSet<AspectDescriptor> aspects) {
- return ImmutableList.of(
- currentConfiguration != null
- ? Dependency.withConfigurationAndAspects(label, currentConfiguration, aspects)
- : Dependency.withNullConfiguration(label));
+ ImmutableList.Builder<Dependency> deps = ImmutableList.builder();
+ for (BuildConfiguration config : toConfigurations) {
+ deps.add(config != null
+ ? Dependency.withConfigurationAndAspects(label, config, aspects)
+ : Dependency.withNullConfiguration(label));
+ }
+ return deps.build();
}
}
/**
* Transition applier for dynamic configurations. This implementation populates
* {@link com.google.devtools.build.lib.analysis.Dependency} objects with
- * transition definitions that the caller subsequently creates configurations out of.
- *
- * <p>Does not support split transitions (see {@link SplittableTransitionApplier}).
+ * transitions that the caller subsequently creates configurations from.
*/
private static class DynamicTransitionApplier implements TransitionApplier {
private final BuildConfiguration originalConfiguration;
- private Transition transition = Attribute.ConfigurationTransition.NONE;
+ // The transition this applier applies to dep rules. May change multiple times. However,
+ // composed transitions (e.g. fromConfig -> FooTransition -> BarTransition) are not currently
+ // supported, in the name of keeping the model simple. We can always revisit that assumption
+ // if needed.
+ private Transition currentTransition = Attribute.ConfigurationTransition.NONE;
private DynamicTransitionApplier(BuildConfiguration originalConfiguration) {
this.originalConfiguration = originalConfiguration;
@@ -1612,37 +1632,42 @@ public final class BuildConfiguration {
}
@Override
- public void applyTransition(Transition transition) {
- if (transition == Attribute.ConfigurationTransition.NONE) {
+ public void applyTransition(Transition transitionToApply) {
+ if (transitionToApply == Attribute.ConfigurationTransition.NONE
+ // Outside of LIPO, data transitions are a no-op. Since dynamic configs don't yet support
+ // LIPO, just return fast as a no-op. This isn't just convenient: evaluateTransition
+ // calls configurationHook after standard attribute transitions. If configurationHook
+ // triggers a data transition, that undoes the earlier transitions (because of lack of
+ // composed transition support). That's dangerous and especially pointless for non-LIPO
+ // builds. Hence this check.
+ // TODO(gregce): add LIPO support and/or make this special case unnecessary.
+ || transitionToApply == Attribute.ConfigurationTransition.DATA
+ // This means it's not possible to transition back out of a host transition. We may
+ // need to revise this when we properly support multiple host configurations.
+ || currentTransition == HostTransition.INSTANCE) {
return;
- } else if (this.transition != HostTransition.INSTANCE) {
- // We don't currently support composed transitions (e.g. applyTransitions shouldn't be
- // called multiple times). We can add support for this if needed by simply storing a list of
- // transitions instead of a single transition. But we only want to do that if really
- // necessary - if we can simplify BuildConfiguration's transition logic to not require
- // scenarios like that, it's better to keep this simpler interface.
- //
- // The HostTransition exemption is because of limited cases where composition can
- // occur. See relevant comments beginning with "BuildConfiguration.applyTransition NOTE"
- // in the transition logic code if available.
-
- // Ensure we don't already have any mutating transitions registered.
- // Note that for dynamic configurations, LipoDataTransition is equivalent to NONE. That's
- // because dynamic transitions don't work with LIPO, so there's no LIPO context to change.
- Verify.verify(this.transition == Attribute.ConfigurationTransition.NONE
- || this.transition.toString().contains("LipoDataTransition"));
- this.transition = getCurrentTransitions().getDynamicTransition(transition);
}
+
+ // Since we don't support composed transitions, we need to be careful applying a transition
+ // when another transition has already been applied (the latter will simply overwrite the
+ // former). All allowed cases should be explicitly asserted here.
+ Verify.verify(currentTransition == Attribute.ConfigurationTransition.NONE
+ // LIPO transitions are okay because they're no-ops outside LIPO builds. And dynamic
+ // configs don't yet support LIPO builds.
+ || currentTransition.toString().contains("LipoDataTransition"));
+ currentTransition = getCurrentTransitions().getDynamicTransition(transitionToApply);
}
@Override
public void split(SplitTransition<?> splitTransition) {
- throw new UnsupportedOperationException("This only works with SplittableTransitionApplier");
+ Verify.verify(currentTransition == Attribute.ConfigurationTransition.NONE,
+ "split transitions aren't expected to mix with other transitions");
+ currentTransition = splitTransition;
}
@Override
public boolean isNull() {
- return transition == Attribute.ConfigurationTransition.NULL;
+ return currentTransition == Attribute.ConfigurationTransition.NULL;
}
@Override
@@ -1679,8 +1704,7 @@ public final class BuildConfiguration {
}
}
- @Override
- public Transitions getCurrentTransitions() {
+ private Transitions getCurrentTransitions() {
return originalConfiguration.getTransitions();
}
@@ -1688,78 +1712,7 @@ public final class BuildConfiguration {
public Iterable<Dependency> getDependencies(
Label label, ImmutableSet<AspectDescriptor> aspects) {
return ImmutableList.of(
- Dependency.withTransitionAndAspects(label, transition, aspects));
- }
- }
-
- /**
- * Transition applier that wraps an underlying implementation with added support for
- * split transitions. All external calls into BuildConfiguration should use this applier.
- */
- private static class SplittableTransitionApplier implements TransitionApplier {
- private List<TransitionApplier> appliers;
-
- private SplittableTransitionApplier(TransitionApplier original) {
- appliers = ImmutableList.of(original);
- }
-
- @Override
- public TransitionApplier create(BuildConfiguration configuration) {
- throw new UnsupportedOperationException("Not intended to be wrapped under another applier");
- }
-
- @Override
- public void applyTransition(Transition transition) {
- for (TransitionApplier applier : appliers) {
- applier.applyTransition(transition);
- }
- }
-
- @Override
- public void split(SplitTransition<?> splitTransition) {
- TransitionApplier originalApplier = Iterables.getOnlyElement(appliers);
- ImmutableList.Builder<TransitionApplier> splitAppliers = ImmutableList.builder();
- for (BuildConfiguration splitConfig :
- originalApplier.getCurrentTransitions().getSplitConfigurations(splitTransition)) {
- splitAppliers.add(originalApplier.create(splitConfig));
- }
- appliers = splitAppliers.build();
- }
-
- @Override
- public boolean isNull() {
- throw new UnsupportedOperationException("Only for use from a Transitions instance");
- }
-
-
- @Override
- public void applyAttributeConfigurator(Attribute attribute, Rule fromRule, Target toTarget) {
- for (TransitionApplier applier : appliers) {
- applier.applyAttributeConfigurator(attribute, fromRule, toTarget);
- }
- }
-
- @Override
- public void applyConfigurationHook(Rule fromRule, Attribute attribute, Target toTarget) {
- for (TransitionApplier applier : appliers) {
- applier.applyConfigurationHook(fromRule, attribute, toTarget);
- }
- }
-
- @Override
- public Transitions getCurrentTransitions() {
- throw new UnsupportedOperationException("Only for use from a Transitions instance");
- }
-
-
- @Override
- public Iterable<Dependency> getDependencies(
- Label label, ImmutableSet<AspectDescriptor> aspects) {
- ImmutableList.Builder<Dependency> builder = ImmutableList.builder();
- for (TransitionApplier applier : appliers) {
- builder.addAll(applier.getDependencies(label, aspects));
- }
- return builder.build();
+ Dependency.withTransitionAndAspects(label, currentTransition, aspects));
}
}
@@ -1767,10 +1720,9 @@ public final class BuildConfiguration {
* Returns the {@link TransitionApplier} that should be passed to {#evaluateTransition} calls.
*/
public TransitionApplier getTransitionApplier() {
- TransitionApplier applier = useDynamicConfigurations()
+ return useDynamicConfigurations()
? new DynamicTransitionApplier(this)
: new StaticTransitionApplier(this);
- return new SplittableTransitionApplier(applier);
}
/**
diff --git a/src/main/java/com/google/devtools/build/lib/packages/Attribute.java b/src/main/java/com/google/devtools/build/lib/packages/Attribute.java
index 9088840bd5..f2fb3355e9 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/Attribute.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/Attribute.java
@@ -149,6 +149,9 @@ public final class Attribute implements Comparable<Attribute> {
* A configuration split transition; this should be used to transition to multiple configurations
* simultaneously. Note that the corresponding rule implementations must have special support to
* handle this.
+ *
+ * <p>{@code T} must always be {@code BuildOptions}, but it can't be defined that way because
+ * the symbol isn't available here.
*/
// TODO(bazel-team): Serializability constraints?
public interface SplitTransition<T> extends Transition {
@@ -1476,6 +1479,7 @@ public final class Attribute implements Comparable<Attribute> {
* Returns the split configuration transition for this attribute.
*
* @param rule the originating {@link Rule} which owns this attribute
+ * @return a SplitTransition<BuildOptions> object
* @throws IllegalStateException if {@link #hasSplitConfigurationTransition} is not true
*/
public SplitTransition<?> getSplitTransition(Rule rule) {
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 b9f839c977..1e4d11466e 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
@@ -77,9 +77,9 @@ import com.google.devtools.build.skyframe.ValueOrException;
import com.google.devtools.build.skyframe.ValueOrException2;
import java.util.Collection;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
@@ -396,11 +396,12 @@ final class ConfiguredTargetFunction implements SkyFunction {
}
/**
- * Variation of {@link Map#put} that triggers an exception if another value already exists.
+ * Variation of {@link Multimap#put} that triggers an exception if a value already exists.
*/
- private static <K, V> void putOnlyEntry(Map<K, V> map, K key, V value) {
- Verify.verify(map.put(key, value) == null,
+ private static <K, V> void putOnlyEntry(Multimap<K, V> map, K key, V value) {
+ Verify.verify(!map.containsKey(key),
"couldn't insert %s: map already has key %s", value.toString(), key.toString());
+ map.put(key, value);
}
/**
@@ -432,7 +433,9 @@ final class ConfiguredTargetFunction implements SkyFunction {
// 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.
- Map<FragmentsAndTransition, BuildOptions> transitionsMap = new HashMap<>();
+ //
+ // 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 =
@@ -445,7 +448,7 @@ final class ConfiguredTargetFunction implements SkyFunction {
// 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.
- Map<AttributeAndLabel, Dependency> trimmedDeps = new HashMap<>();
+ Multimap<AttributeAndLabel, Dependency> trimmedDeps = ArrayListMultimap.create();
for (Map.Entry<Attribute, Dependency> depsEntry : originalDeps.entries()) {
Dependency dep = depsEntry.getValue();
@@ -455,7 +458,7 @@ final class ConfiguredTargetFunction implements SkyFunction {
if (dep.hasStaticConfiguration()) {
// Certain targets (like output files and late-bound splits) trivially pass their
// configurations to their deps. So no need to transform them in any way.
- putOnlyEntry(trimmedDeps, attributeAndLabel, dep);
+ trimmedDeps.put(attributeAndLabel, dep);
continue;
} else if (dep.getTransition() == Attribute.ConfigurationTransition.NULL) {
putOnlyEntry(
@@ -508,26 +511,24 @@ final class ConfiguredTargetFunction implements SkyFunction {
// Apply the transition or use the cached result if it was already applied.
FragmentsAndTransition transitionKey = new FragmentsAndTransition(depFragments, transition);
- BuildOptions toOptions = transitionsMap.get(transitionKey);
+ List<BuildOptions> toOptions = transitionsMap.get(transitionKey);
if (toOptions == null) {
- Verify.verify(transition == Attribute.ConfigurationTransition.NONE
- || transition instanceof PatchTransition);
- BuildOptions fromOptions = ctgOptions;
- // TODO(bazel-team): safety-check that the below call never mutates fromOptions.
- toOptions = transition == Attribute.ConfigurationTransition.NONE
- ? fromOptions
- : ((PatchTransition) transition).apply(fromOptions);
- if (!sameFragments) {
- // TODO(bazel-team): pre-compute getOptionsClasses in the constructor.
- toOptions = toOptions.trim(BuildConfiguration.getOptionsClasses(
- transitiveDepInfo.getTransitiveConfigFragments(), ruleClassProvider));
+ ImmutableList.Builder<BuildOptions> toOptionsBuilder = ImmutableList.builder();
+ for (BuildOptions options : getDynamicTransitionOptions(ctgOptions, transition)) {
+ if (!sameFragments) {
+ options = options.trim(BuildConfiguration.getOptionsClasses(
+ transitiveDepInfo.getTransitiveConfigFragments(), ruleClassProvider));
+ }
+ toOptionsBuilder.add(options);
}
+ toOptions = toOptionsBuilder.build();
transitionsMap.put(transitionKey, toOptions);
}
// If the transition doesn't change the configuration, trivially re-use the original
// configuration.
- if (sameFragments && toOptions.equals(ctgOptions)) {
+ if (sameFragments && toOptions.size() == 1
+ && Iterables.getOnlyElement(toOptions).equals(ctgOptions)) {
putOnlyEntry(
trimmedDeps,
attributeAndLabel,
@@ -537,7 +538,9 @@ final class ConfiguredTargetFunction implements SkyFunction {
}
// If we get here, we have to get the configuration from Skyframe.
- keysToEntries.put(BuildConfigurationValue.key(depFragments, toOptions), depsEntry);
+ for (BuildOptions options : toOptions) {
+ keysToEntries.put(BuildConfigurationValue.key(depFragments, options), depsEntry);
+ }
}
// Get all BuildConfigurations we need to get from Skyframe.
@@ -555,11 +558,14 @@ final class ConfiguredTargetFunction implements SkyFunction {
BuildConfigurationValue trimmedConfig = (BuildConfigurationValue) entry.getValue().get();
for (Map.Entry<Attribute, Dependency> info : keysToEntries.get(key)) {
Dependency originalDep = info.getValue();
- putOnlyEntry(trimmedDeps, new AttributeAndLabel(info.getKey(), originalDep.getLabel()),
- Dependency.withConfigurationAndAspects(
- originalDep.getLabel(),
- trimmedConfig.getConfiguration(),
- originalDep.getAspects()));
+ AttributeAndLabel attr = new AttributeAndLabel(info.getKey(), originalDep.getLabel());
+ Dependency resolvedDep = Dependency.withConfigurationAndAspects(originalDep.getLabel(),
+ trimmedConfig.getConfiguration(), originalDep.getAspects());
+ if (originalDep.getTransition() instanceof Attribute.SplitTransition) {
+ trimmedDeps.put(attr, resolvedDep);
+ } else {
+ putOnlyEntry(trimmedDeps, attr, resolvedDep);
+ }
}
}
} catch (InvalidConfigurationException e) {
@@ -570,15 +576,47 @@ final class ConfiguredTargetFunction implements SkyFunction {
// appear in the same order) as the input.
ListMultimap<Attribute, Dependency> result = ArrayListMultimap.create();
for (Map.Entry<Attribute, Dependency> depsEntry : originalDeps.entries()) {
- Dependency trimmedDep = Verify.verifyNotNull(
- trimmedDeps.get(
- new AttributeAndLabel(depsEntry.getKey(), depsEntry.getValue().getLabel())));
- result.put(depsEntry.getKey(), trimmedDep);
+ Collection<Dependency> trimmedAttrDeps = trimmedDeps.get(
+ new AttributeAndLabel(depsEntry.getKey(), depsEntry.getValue().getLabel()));
+ Verify.verify(!trimmedAttrDeps.isEmpty());
+ result.putAll(depsEntry.getKey(), trimmedAttrDeps);
}
return result;
}
/**
+ * Applies a dynamic configuration transition over a set of build options.
+ *
+ * @return the build options for the transitioned configuration. Contains the same fragment
+ * options as the input.
+ */
+ private static Collection<BuildOptions> getDynamicTransitionOptions(BuildOptions fromOptions,
+ Attribute.Transition transition) {
+ if (transition == Attribute.ConfigurationTransition.NONE) {
+ return ImmutableList.<BuildOptions>of(fromOptions);
+ } else if (transition instanceof PatchTransition) {
+ // TODO(bazel-team): safety-check that this never mutates fromOptions.
+ return 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());
+ return ImmutableList.<BuildOptions>of(fromOptions);
+ } else {
+ return toOptions;
+ }
+ } else {
+ throw new IllegalStateException(String.format(
+ "unsupported dynamic transition type: %s", transition.getClass().getName()));
+ }
+ }
+
+
+ /**
* 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.
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java b/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java
index bb630a3c55..4fdde18aab 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java
@@ -173,6 +173,7 @@ public abstract class BuildViewTestCase extends FoundationTestCase {
protected BuildConfigurationCollection masterConfig;
protected BuildConfiguration targetConfig; // "target" or "build" config
private List<String> configurationArgs;
+ private boolean useDynamicConfigs;
protected OptionsParser optionsParser;
private PackageCacheOptions packageCacheOptions;
@@ -381,13 +382,27 @@ public abstract class BuildViewTestCase extends FoundationTestCase {
* @throws IllegalArgumentException
*/
protected final void useConfiguration(String... args) throws Exception {
- masterConfig = createConfigurations(args);
+ String[] actualArgs;
+ if (useDynamicConfigs) {
+ actualArgs = Arrays.copyOf(args, args.length + 1);
+ actualArgs[args.length] = "--experimental_dynamic_configs";
+ } else {
+ actualArgs = args;
+ }
+ masterConfig = createConfigurations(actualArgs);
targetConfig = getTargetConfiguration();
- configurationArgs = Arrays.asList(args);
+ configurationArgs = Arrays.asList(actualArgs);
createBuildView();
}
/**
+ * Makes subsequent {@link #useConfiguration} calls automatically enable dynamic configurations.
+ */
+ protected final void useDynamicConfigurations() {
+ useDynamicConfigs = true;
+ }
+
+ /**
* Creates BuildView using current hostConfig/targetConfig values.
* Ensures that hostConfig is either identical to the targetConfig or has
* 'host' short name.