diff options
author | 2016-08-09 22:36:51 +0000 | |
---|---|---|
committer | 2016-08-10 08:38:45 +0000 | |
commit | 373e3e28274cca5b87f48abe33884edb84016dd3 (patch) | |
tree | 61744785eadf07f70d0a9a3264c00cf992441190 /src | |
parent | eff8b365c172b7e904ac6f7bba0c893fed7c91a8 (diff) |
Implements dynamic split transitions on latebound attributes.
With this change, dynamic configs are at full feature parity for
split transitions (minus some small differences in composed
transitions from BuildConfigurationCollection.configurationHook).
--
MOS_MIGRATED_REVID=129802414
Diffstat (limited to 'src')
10 files changed, 344 insertions, 41 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java b/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java index dde2d0684e..55bcf81851 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java @@ -38,7 +38,9 @@ import com.google.devtools.build.lib.analysis.ExtraActionArtifactsProvider.Extra import com.google.devtools.build.lib.analysis.config.BinTools; 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.ConfigMatchingProvider; +import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.cmdline.PackageIdentifier; import com.google.devtools.build.lib.collect.nestedset.NestedSet; @@ -81,6 +83,7 @@ import com.google.devtools.build.skyframe.SkyKey; import com.google.devtools.build.skyframe.WalkableGraph; import com.google.devtools.common.options.Option; import com.google.devtools.common.options.OptionsBase; + import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -865,7 +868,7 @@ public class BuildView { @VisibleForTesting public Iterable<ConfiguredTarget> getDirectPrerequisitesForTesting( EventHandler eventHandler, ConfiguredTarget ct, BuildConfigurationCollection configurations) - throws EvalException, InterruptedException { + throws EvalException, InvalidConfigurationException, InterruptedException { return skyframeExecutor.getConfiguredTargets( eventHandler, ct.getConfiguration(), ImmutableSet.copyOf( @@ -875,8 +878,9 @@ public class BuildView { @VisibleForTesting public ListMultimap<Attribute, Dependency> getDirectPrerequisiteDependenciesForTesting( - final EventHandler eventHandler, ConfiguredTarget ct, - BuildConfigurationCollection configurations) throws EvalException, InterruptedException { + final EventHandler eventHandler, final ConfiguredTarget ct, + BuildConfigurationCollection configurations) + throws EvalException, InvalidConfigurationException, InterruptedException { if (!(ct.getTarget() instanceof Rule)) { return ArrayListMultimap.create(); } @@ -908,6 +912,24 @@ public class BuildView { throw new IllegalStateException(e); } } + + @Override + protected List<BuildConfiguration> getConfigurations( + Set<Class<? extends BuildConfiguration.Fragment>> fragments, + Iterable<BuildOptions> buildOptions) { + Preconditions.checkArgument(ct.getConfiguration().fragmentClasses().equals(fragments)); + Dependency asDep = Dependency.withTransitionAndAspects(ct.getLabel(), + Attribute.ConfigurationTransition.NONE, ImmutableSet.<AspectDescriptor>of()); + ImmutableList.Builder<BuildConfiguration> builder = ImmutableList.builder(); + for (BuildOptions options : buildOptions) { + builder.add(Iterables.getOnlyElement( + skyframeExecutor + .getConfigurations(eventHandler, options, ImmutableList.<Dependency>of(asDep)) + .values() + )); + } + return builder.build(); + } } DependencyResolver dependencyResolver = new SilentDependencyResolver(); @@ -945,7 +967,8 @@ public class BuildView { private ListMultimap<Attribute, ConfiguredTarget> getPrerequisiteMapForTesting( final EventHandler eventHandler, ConfiguredTarget target, - BuildConfigurationCollection configurations) throws EvalException, InterruptedException { + BuildConfigurationCollection configurations) + throws EvalException, InvalidConfigurationException, InterruptedException { ListMultimap<Attribute, Dependency> depNodeNames = getDirectPrerequisiteDependenciesForTesting( eventHandler, target, configurations); @@ -978,7 +1001,7 @@ public class BuildView { public RuleContext getRuleContextForTesting( ConfiguredTarget target, StoredEventHandler eventHandler, BuildConfigurationCollection configurations, BinTools binTools) - throws EvalException, InterruptedException { + throws EvalException, InvalidConfigurationException, InterruptedException { BuildConfiguration targetConfig = target.getConfiguration(); CachingAnalysisEnvironment env = new CachingAnalysisEnvironment(getArtifactFactory(), @@ -995,7 +1018,7 @@ public class BuildView { @VisibleForTesting public RuleContext getRuleContextForTesting(EventHandler eventHandler, ConfiguredTarget target, AnalysisEnvironment env, BuildConfigurationCollection configurations) - throws EvalException, InterruptedException { + throws EvalException, InvalidConfigurationException, InterruptedException { BuildConfiguration targetConfig = target.getConfiguration(); return new RuleContext.Builder( env, @@ -1023,7 +1046,7 @@ public class BuildView { public ConfiguredTarget getPrerequisiteConfiguredTargetForTesting( EventHandler eventHandler, ConfiguredTarget dependentTarget, Label desiredTarget, BuildConfigurationCollection configurations) - throws EvalException, InterruptedException { + throws EvalException, InvalidConfigurationException, InterruptedException { Collection<ConfiguredTarget> configuredTargets = getPrerequisiteMapForTesting(eventHandler, dependentTarget, configurations).values(); for (ConfiguredTarget ct : configuredTargets) { 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 66360f4828..3956037516 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,7 +22,10 @@ import com.google.common.collect.Iterables; import com.google.common.collect.ListMultimap; import com.google.common.collect.Sets; 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.InvalidConfigurationException; +import com.google.devtools.build.lib.analysis.config.PatchTransition; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; import com.google.devtools.build.lib.packages.Aspect; @@ -94,7 +97,7 @@ public abstract class DependencyResolver { BuildConfiguration hostConfig, @Nullable Aspect aspect, ImmutableMap<Label, ConfigMatchingProvider> configConditions) - throws EvalException, InterruptedException { + throws EvalException, InvalidConfigurationException, InterruptedException { NestedSetBuilder<Label> rootCauses = NestedSetBuilder.<Label>stableOrder(); ListMultimap<Attribute, Dependency> outgoingEdges = dependentNodeMap( node, hostConfig, aspect, configConditions, rootCauses); @@ -138,7 +141,7 @@ public abstract class DependencyResolver { @Nullable Aspect aspect, ImmutableMap<Label, ConfigMatchingProvider> configConditions, NestedSetBuilder<Label> rootCauses) - throws EvalException, InterruptedException { + throws EvalException, InvalidConfigurationException, InterruptedException { Target target = node.getTarget(); BuildConfiguration config = node.getConfiguration(); ListMultimap<Attribute, Dependency> outgoingEdges = ArrayListMultimap.create(); @@ -168,7 +171,7 @@ public abstract class DependencyResolver { ImmutableMap<Label, ConfigMatchingProvider> configConditions, NestedSetBuilder<Label> rootCauses, ListMultimap<Attribute, Dependency> outgoingEdges) - throws EvalException, InterruptedException { + throws EvalException, InvalidConfigurationException, InterruptedException { Preconditions.checkArgument(node.getTarget() instanceof Rule); BuildConfiguration ruleConfig = Preconditions.checkNotNull(node.getConfiguration()); Rule rule = (Rule) node.getTarget(); @@ -316,7 +319,7 @@ public abstract class DependencyResolver { RuleResolver depResolver, BuildConfiguration ruleConfig, BuildConfiguration hostConfig) - throws EvalException, InterruptedException { + throws EvalException, InvalidConfigurationException, InterruptedException { ConfiguredAttributeMapper attributeMap = depResolver.attributeMap; for (Attribute attribute : depResolver.attributes) { if (!attribute.isLateBound() || !attribute.getCondition().apply(attributeMap)) { @@ -327,7 +330,9 @@ public abstract class DependencyResolver { LateBoundDefault<BuildConfiguration> lateBoundDefault = (LateBoundDefault<BuildConfiguration>) attribute.getLateBoundDefault(); - if (attribute.hasSplitConfigurationTransition()) { + Collection<BuildOptions> splitOptions = + getSplitOptions(depResolver.rule, attribute, ruleConfig); + if (!splitOptions.isEmpty()) { // Late-bound attribute with a split transition: // Since we want to get the same results as BuildConfiguration.evaluateTransition (but // skip it since we've already applied the split), we want to make sure this logic @@ -336,9 +341,18 @@ public abstract class DependencyResolver { // of those apply, in the name of keeping the fork as simple as possible. Verify.verify(attribute.getConfigurator() == null); Verify.verify(!lateBoundDefault.useHostConfiguration()); - for (BuildConfiguration splitConfig : ruleConfig.getSplitConfigurations( - attribute.getSplitTransition(depResolver.rule))) { - // TODO(gregce): support dynamic split transitions + + Iterable<BuildConfiguration> splitConfigs; + if (!ruleConfig.useDynamicConfigurations()) { + splitConfigs = ruleConfig + .getSplitConfigurations(attribute.getSplitTransition(depResolver.rule)); + } else { + splitConfigs = getConfigurations(ruleConfig.fragmentClasses(), splitOptions); + if (splitConfigs == null) { + continue; // Need Skyframe deps. + } + } + for (BuildConfiguration splitConfig : splitConfigs) { for (Label dep : resolveLateBoundAttribute( depResolver.rule, attribute, splitConfig, attributeMap)) { // Skip the normal config transition pipeline and directly feed the split config. This @@ -360,6 +374,23 @@ public abstract class DependencyResolver { } /** + * Returns true if the rule's attribute triggers a split in this configuration. + * + * <p>Even though the attribute may have a split, splits don't have to apply in every + * configuration (see {@link Attribute.SplitTransition#split}). + */ + private static Collection<BuildOptions> getSplitOptions(Rule rule, Attribute attribute, + BuildConfiguration ruleConfig) { + if (!attribute.hasSplitConfigurationTransition()) { + return ImmutableList.<BuildOptions>of(); + } + @SuppressWarnings("unchecked") // Attribute.java doesn't have the BuildOptions symbol. + Attribute.SplitTransition<BuildOptions> transition = + (Attribute.SplitTransition<BuildOptions>) attribute.getSplitTransition(rule); + return transition.split(ruleConfig.getOptions()); + } + + /** * Returns the label dependencies for the given late-bound attribute in this rule. * * @param rule the rule being evaluated @@ -586,8 +617,6 @@ public abstract class DependencyResolver { * configurations to apply to it. */ void resolveDep(Attribute attribute, Label depLabel) { - // Late-bound split attributes are separately handled (see LateBoundSplitResolver). - Verify.verify(!(attribute.isLateBound() && attribute.hasSplitConfigurationTransition())); Target toTarget = getTarget(rule, depLabel, rootCauses); if (toTarget == null) { return; // Skip this round: we still need to Skyframe-evaluate the dep's target. @@ -625,10 +654,9 @@ public abstract class DependencyResolver { ImmutableSet<AspectDescriptor> aspects = requiredAspects(aspect, attribute, toTarget, rule); Dependency dep; if (config.useDynamicConfigurations() && !applyNullTransition) { - // Since we feed a pre-prepared configuration directly to the dep, it won't get trimmed to - // the dep's fragments. - // TODO(gregce): properly trim this configuration, too. - dep = Dependency.withConfigurationAndAspects(depLabel, config, aspects); + // Pass a transition rather than directly feeding the configuration so deps get trimmed. + dep = Dependency.withTransitionAndAspects( + depLabel, new FixedTransition(config.getOptions()), aspects); } else { dep = Iterables.getOnlyElement(transitionApplier.getDependencies(depLabel, aspects)); } @@ -637,6 +665,27 @@ public abstract class DependencyResolver { } } + /** + * A patch transition that returns a fixed set of options regardless of the input. + */ + private static class FixedTransition implements PatchTransition { + private final BuildOptions toOptions; + + FixedTransition(BuildOptions toOptions) { + this.toOptions = toOptions; + } + + @Override + public BuildOptions apply(BuildOptions options) { + return toOptions; + } + + @Override + public boolean defaultsToSelf() { + return false; + } + } + private void visitTargetVisibility(TargetAndConfiguration node, NestedSetBuilder<Label> rootCauses, Collection<Dependency> outgoingEdges) { Target target = node.getTarget(); @@ -696,4 +745,18 @@ public abstract class DependencyResolver { */ @Nullable protected abstract Target getTarget(Target from, Label label, NestedSetBuilder<Label> rootCauses); + + /** + * Returns the build configurations with the given options and fragments, in the same order as + * the input options. + * + * <p>Returns null if any configurations aren't ready to be returned at this moment. If + * getConfigurations returns null once or more during a {@link #dependentNodeMap} call, the + * results of that call will be incomplete. For use within Skyframe, where several iterations may + * be needed to discover all dependencies. + */ + @Nullable + protected abstract List<BuildConfiguration> getConfigurations( + Set<Class<? extends BuildConfiguration.Fragment>> fragments, + Iterable<BuildOptions> buildOptions) throws InvalidConfigurationException; } 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 27b0d093d1..f97ea94f5a 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 @@ -1102,7 +1102,12 @@ public final class BuildConfiguration { public boolean equalsOrIsSupersetOf(BuildConfiguration other) { return this.equals(other) || (other != null - && outputRoots.equals(other.outputRoots) + // TODO(gregce): add back in output root checking. This requires a better approach to + // configuration-safe output paths. If the parent config has a fragment the child config + // doesn't, it may inject $(FOO) into the output roots. So the child bindir might be + // "bazel-out/arm-linux-fastbuild/bin" while the parent bindir is + // "bazel-out/android-arm-linux-fastbuild/bin". That's pretty awkward to check here. + // && outputRoots.equals(other.outputRoots) && actionsEnabled == other.actionsEnabled && fragments.values().containsAll(other.fragments.values()) && buildOptions.getOptions().containsAll(other.buildOptions.getOptions())); diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelConfigurationCollection.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelConfigurationCollection.java index 6fc4476782..4db1a92b53 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelConfigurationCollection.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelConfigurationCollection.java @@ -37,7 +37,6 @@ import com.google.devtools.build.lib.packages.Attribute.Transition; import com.google.devtools.build.lib.rules.cpp.CppRuleClasses.LipoTransition; import java.util.LinkedHashSet; -import java.util.List; import java.util.Map; import java.util.Set; @@ -76,19 +75,7 @@ public class BazelConfigurationCollection implements ConfigurationCollectionFact ListMultimap<SplitTransition<?>, BuildConfiguration> splitTransitionsTable = ArrayListMultimap.create(); for (SplitTransition<BuildOptions> transition : buildOptions.getPotentialSplitTransitions()) { - List<BuildOptions> splitOptionsList = transition.split(buildOptions); - - // While it'd be clearer to condition the below on "if (!splitOptionsList.empty())", - // IosExtension.ExtensionSplitArchTransition defaults to a single-value split. If we failed - // that case then no builds would work, whether or not they're iOS builds (since iOS - // configurations are unconditionally loaded). Once we have dynamic configuraiton support - // for split transitions, this will all go away. - if (splitOptionsList.size() > 1 && targetConfiguration.useDynamicConfigurations()) { - throw new InvalidConfigurationException( - "dynamic configurations don't yet support split transitions"); - } - - for (BuildOptions splitOptions : splitOptionsList) { + for (BuildOptions splitOptions : transition.split(buildOptions)) { BuildConfiguration splitConfig = configurationFactory.getConfiguration( packageProvider, splitOptions, false, cache); splitTransitionsTable.put(transition, splitConfig); 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 1e4d11466e..b09fc1dbe4 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 @@ -296,6 +296,8 @@ final class ConfiguredTargetFunction implements SkyFunction { env.getListener().handle(Event.error(e.getLocation(), e.getMessage())); throw new DependencyEvaluationException( new ConfiguredValueCreationException(e.print(), ctgValue.getLabel())); + } catch (InvalidConfigurationException e) { + throw new DependencyEvaluationException(e); } // Trim each dep's configuration so it only includes the fragments needed by its transitive @@ -456,7 +458,7 @@ final class ConfiguredTargetFunction implements SkyFunction { new AttributeAndLabel(depsEntry.getKey(), dep.getLabel()); if (dep.hasStaticConfiguration()) { - // Certain targets (like output files and late-bound splits) trivially pass their + // Certain targets (like output files) trivially pass their // configurations to their deps. So no need to transform them in any way. trimmedDeps.put(attributeAndLabel, dep); continue; @@ -561,7 +563,7 @@ final class ConfiguredTargetFunction implements SkyFunction { AttributeAndLabel attr = new AttributeAndLabel(info.getKey(), originalDep.getLabel()); Dependency resolvedDep = Dependency.withConfigurationAndAspects(originalDep.getLabel(), trimmedConfig.getConfiguration(), originalDep.getAspects()); - if (originalDep.getTransition() instanceof Attribute.SplitTransition) { + if (attr.attribute.hasSplitConfigurationTransition()) { trimmedDeps.put(attr, resolvedDep); } else { putOnlyEntry(trimmedDeps, attr, resolvedDep); 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 d342bb66a4..92b2678d13 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.InvalidConfigurationException; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.packages.Attribute; import com.google.devtools.build.lib.packages.BuildType; @@ -116,6 +117,8 @@ public class PostConfiguredTargetFunction implements SkyFunction { throw new PostConfiguredTargetFunctionException(e); } catch (ConfiguredTargetFunction.DependencyEvaluationException e) { throw new PostConfiguredTargetFunctionException(e); + } catch (InvalidConfigurationException e) { + throw new PostConfiguredTargetFunctionException(e); } env.getValues(Iterables.transform(deps.values(), TO_KEYS)); diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeDependencyResolver.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeDependencyResolver.java index 4e4c603dff..09fc0f2176 100644 --- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeDependencyResolver.java +++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeDependencyResolver.java @@ -13,8 +13,12 @@ // limitations under the License. package com.google.devtools.build.lib.skyframe; +import com.google.common.collect.ImmutableList; import com.google.devtools.build.lib.analysis.DependencyResolver; 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.BuildOptions; +import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; import com.google.devtools.build.lib.events.Event; @@ -26,6 +30,12 @@ import com.google.devtools.build.lib.packages.Target; import com.google.devtools.build.lib.packages.TargetUtils; import com.google.devtools.build.skyframe.SkyFunction.Environment; import com.google.devtools.build.skyframe.SkyKey; +import com.google.devtools.build.skyframe.ValueOrException; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; import javax.annotation.Nullable; @@ -110,4 +120,25 @@ public final class SkyframeDependencyResolver extends DependencyResolver { return null; } } + + @Nullable + @Override + protected List<BuildConfiguration> getConfigurations( + Set<Class<? extends BuildConfiguration.Fragment>> fragments, + Iterable<BuildOptions> buildOptions) throws InvalidConfigurationException { + List<SkyKey> keys = new ArrayList<>(); + for (BuildOptions options : buildOptions) { + keys.add(BuildConfigurationValue.key(fragments, options)); + } + Map<SkyKey, ValueOrException<InvalidConfigurationException>> configValues = + env.getValuesOrThrow(keys, InvalidConfigurationException.class); + if (env.valuesMissing()) { + return null; + } + ImmutableList.Builder<BuildConfiguration> result = ImmutableList.builder(); + for (SkyKey key : keys) { + result.add(((BuildConfigurationValue) configValues.get(key).get()).getConfiguration()); + } + return result.build(); + } } diff --git a/src/test/java/com/google/devtools/build/lib/analysis/BuildViewTest.java b/src/test/java/com/google/devtools/build/lib/analysis/BuildViewTest.java index b1539f8766..d48756ad1b 100644 --- a/src/test/java/com/google/devtools/build/lib/analysis/BuildViewTest.java +++ b/src/test/java/com/google/devtools/build/lib/analysis/BuildViewTest.java @@ -37,6 +37,7 @@ import com.google.devtools.build.lib.actions.Actions; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.actions.FailAction; import com.google.devtools.build.lib.analysis.BuildView.AnalysisResult; +import com.google.devtools.build.lib.analysis.config.ConfigurationFactory; import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException; import com.google.devtools.build.lib.analysis.util.AnalysisMock; import com.google.devtools.build.lib.analysis.util.BuildViewTestBase; @@ -58,6 +59,7 @@ import com.google.devtools.build.skyframe.NotifyingHelper.Listener; import com.google.devtools.build.skyframe.NotifyingHelper.Order; import com.google.devtools.build.skyframe.SkyKey; import com.google.devtools.build.skyframe.TrackingAwaiter; + import java.util.Collection; import java.util.LinkedHashSet; import java.util.concurrent.CountDownLatch; @@ -1252,6 +1254,25 @@ public class BuildViewTest extends BuildViewTestBase { + "required config fragments: Jvm"); } + @Test + public void lateBoundSplitAttributeConfigs() throws Exception { + useRuleClassProvider(LateBoundSplitUtil.getRuleClassProvider()); + // Register the latebound split fragment with the config creation environment. + useConfigurationFactory(new ConfigurationFactory( + ruleClassProvider.getConfigurationCollectionFactory(), + ruleClassProvider.getConfigurationFragments())); + + scratch.file("foo/BUILD", + "rule_with_latebound_split(", + " name = 'foo')", + "sh_binary(", + " name = 'latebound_dep',", + " srcs = ['latebound_dep.sh'])"); + update("//foo:foo"); + assertNotNull(getConfiguredTarget("//foo:foo")); + // TODO(bazel-team): also check that the dep is created in each expected configuration. + } + /** Runs the same test with the reduced loading phase. */ @TestSpec(size = Suite.SMALL_TESTS) @RunWith(JUnit4.class) diff --git a/src/test/java/com/google/devtools/build/lib/analysis/DependencyResolverTest.java b/src/test/java/com/google/devtools/build/lib/analysis/DependencyResolverTest.java index 239e2f12ad..7f1538464d 100644 --- a/src/test/java/com/google/devtools/build/lib/analysis/DependencyResolverTest.java +++ b/src/test/java/com/google/devtools/build/lib/analysis/DependencyResolverTest.java @@ -18,6 +18,8 @@ import static org.junit.Assert.assertNotNull; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ListMultimap; +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.util.AnalysisTestCase; import com.google.devtools.build.lib.analysis.util.TestAspects; @@ -31,14 +33,14 @@ import com.google.devtools.build.lib.packages.NoSuchPackageException; import com.google.devtools.build.lib.packages.NoSuchTargetException; import com.google.devtools.build.lib.packages.NoSuchThingException; import com.google.devtools.build.lib.packages.Target; - +import java.util.List; +import java.util.Set; +import javax.annotation.Nullable; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import javax.annotation.Nullable; - /** * Tests for {@link DependencyResolver}. * @@ -80,6 +82,15 @@ public class DependencyResolverTest extends AnalysisTestCase { throw new IllegalStateException(e); } } + + @Nullable + @Override + protected List<BuildConfiguration> getConfigurations( + Set<Class<? extends BuildConfiguration.Fragment>> fragments, + Iterable<BuildOptions> buildOptions) { + throw new UnsupportedOperationException( + "this functionality is covered by analysis-phase integration tests"); + } }; } diff --git a/src/test/java/com/google/devtools/build/lib/analysis/LateBoundSplitUtil.java b/src/test/java/com/google/devtools/build/lib/analysis/LateBoundSplitUtil.java new file mode 100644 index 0000000000..a6a2480786 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/analysis/LateBoundSplitUtil.java @@ -0,0 +1,157 @@ +// Copyright 2016 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; + +import static com.google.devtools.build.lib.packages.Attribute.attr; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +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.ConfigurationEnvironment; +import com.google.devtools.build.lib.analysis.config.ConfigurationFragmentFactory; +import com.google.devtools.build.lib.analysis.config.FragmentOptions; +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; +import com.google.devtools.build.lib.packages.AttributeMap; +import com.google.devtools.build.lib.packages.BuildType; +import com.google.devtools.build.lib.packages.Rule; +import com.google.devtools.build.lib.packages.RuleClass; +import com.google.devtools.build.lib.testutil.TestRuleClassProvider; +import com.google.devtools.build.lib.testutil.UnknownRuleConfiguredTarget; +import com.google.devtools.build.lib.util.FileTypeSet; +import com.google.devtools.common.options.Option; + +import java.util.List; + +/** + * Rule and configuration class definitions for testing late-bound split attributes. + */ +public class LateBoundSplitUtil { + /** + * A custom {@link FragmentOptions} with the option to be split. + */ + public static class Options extends FragmentOptions { // public for options loader + @Option( + name = "foo", + defaultValue = "", + category = "undocumented" + ) + public String fooFlag; + + @Override + public List<Attribute.SplitTransition<BuildOptions>> getPotentialSplitTransitions() { + return ImmutableList.<Attribute.SplitTransition<BuildOptions>>of(SIMPLE_SPLIT); + } + } + + /** + * The split. + */ + private static final Attribute.SplitTransition<BuildOptions> SIMPLE_SPLIT = + new Attribute.SplitTransition<BuildOptions>() { + @Override + public List<BuildOptions> split(BuildOptions buildOptions) { + BuildOptions split1 = buildOptions.clone(); + split1.get(Options.class).fooFlag = "one"; + BuildOptions split2 = buildOptions.clone(); + split2.get(Options.class).fooFlag = "two"; + return ImmutableList.<BuildOptions>of(split1, split2); + } + + @Override + public boolean defaultsToSelf() { + return false; + } + }; + + /** + * The {@link BuildConfiguration.Fragment} that contains the options. + */ + private static class Fragment extends BuildConfiguration.Fragment { + } + + /** + * The fragment's loader. + */ + static class FragmentLoader implements ConfigurationFragmentFactory { + @Override + public BuildConfiguration.Fragment create(ConfigurationEnvironment env, + BuildOptions buildOptions) + throws InvalidConfigurationException { + return new Fragment(); + } + + @Override + public Class<? extends BuildConfiguration.Fragment> creates() { + return Fragment.class; + } + + @Override + public ImmutableSet<Class<? extends FragmentOptions>> requiredOptions() { + return ImmutableSet.<Class<? extends FragmentOptions>>of(Options.class); + } + } + + /** + * The resolver that chooses the late-bound attribute's value. + */ + private static final Attribute.LateBoundLabel<BuildConfiguration> SIMPLE_LATEBOUND_RESOLVER = + new Attribute.LateBoundLabel<BuildConfiguration>() { + @Override + public Label resolve(Rule rule, AttributeMap attributes, BuildConfiguration configuration) { + return Label.parseAbsoluteUnchecked("//foo:latebound_dep"); + } + }; + + /** + * A custom rule that applies a late-bound split attribute. + */ + static class RuleWithLateBoundSplitAttribute implements RuleDefinition { + @Override + public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment environment) { + return builder + .add(attr(":latebound_split_attr", BuildType.LABEL) + .allowedFileTypes(FileTypeSet.ANY_FILE) + .allowedRuleClasses(Attribute.ANY_RULE) + .cfg(SIMPLE_SPLIT) + .value(SIMPLE_LATEBOUND_RESOLVER)) + .requiresConfigurationFragments(Fragment.class) + .build(); + } + + @Override + public Metadata getMetadata() { + return RuleDefinition.Metadata.builder() + .name("rule_with_latebound_split") + .ancestors(BaseRuleClasses.RuleBase.class) + .factoryClass(UnknownRuleConfiguredTarget.class) + .build(); + } + } + + /** + * Returns a rule class provider with standard test setup plus the above rules/configs. + */ + static ConfiguredRuleClassProvider getRuleClassProvider() { + ConfiguredRuleClassProvider.Builder builder = new ConfiguredRuleClassProvider.Builder(); + TestRuleClassProvider.addStandardRules(builder); + builder.addRuleDefinition(new RuleWithLateBoundSplitAttribute()); + builder.addConfigurationFragment(new FragmentLoader()); + builder.addConfigurationOptions(Options.class); + return builder.build(); + } +}
\ No newline at end of file |