| // Copyright 2015 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.common.collect.MoreCollectors.onlyElement; | |
| import static com.google.common.truth.Truth.assertThat; | |
| import static com.google.devtools.build.lib.analysis.BaseRuleClasses.ACTION_LISTENER; | |
| import static com.google.devtools.build.lib.packages.Attribute.attr; | |
| import static com.google.devtools.build.lib.packages.BuildType.LABEL; | |
| import static com.google.devtools.build.lib.packages.BuildType.LABEL_LIST; | |
| import static org.junit.Assert.assertThrows; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.collect.ImmutableMap; | |
| import com.google.common.collect.Iterables; | |
| import com.google.common.eventbus.EventBus; | |
| import com.google.devtools.build.lib.actions.Artifact; | |
| import com.google.devtools.build.lib.actions.MutableActionGraph.ActionConflictException; | |
| import com.google.devtools.build.lib.actions.util.ActionsTestUtil; | |
| import com.google.devtools.build.lib.actions.util.ActionsTestUtil.NullAction; | |
| import com.google.devtools.build.lib.analysis.config.ExecutionTransitionFactory; | |
| import com.google.devtools.build.lib.analysis.test.InstrumentedFilesInfo; | |
| import com.google.devtools.build.lib.analysis.util.AnalysisTestCase; | |
| import com.google.devtools.build.lib.analysis.util.MockRule; | |
| import com.google.devtools.build.lib.analysis.util.TestAspects; | |
| import com.google.devtools.build.lib.analysis.util.TestAspects.AspectApplyingToFiles; | |
| import com.google.devtools.build.lib.analysis.util.TestAspects.AspectInfo; | |
| import com.google.devtools.build.lib.analysis.util.TestAspects.DummyRuleFactory; | |
| import com.google.devtools.build.lib.analysis.util.TestAspects.ExtraAttributeAspect; | |
| import com.google.devtools.build.lib.analysis.util.TestAspects.RuleInfo; | |
| import com.google.devtools.build.lib.cmdline.Label; | |
| import com.google.devtools.build.lib.cmdline.LabelSyntaxException; | |
| import com.google.devtools.build.lib.cmdline.RepositoryName; | |
| 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.events.EventKind; | |
| import com.google.devtools.build.lib.events.OutputFilter.RegexOutputFilter; | |
| import com.google.devtools.build.lib.packages.AspectDefinition; | |
| import com.google.devtools.build.lib.packages.AspectParameters; | |
| import com.google.devtools.build.lib.packages.Attribute.LateBoundDefault; | |
| import com.google.devtools.build.lib.packages.NativeAspectClass; | |
| import com.google.devtools.build.lib.packages.Provider; | |
| import com.google.devtools.build.lib.packages.StarlarkProvider; | |
| import com.google.devtools.build.lib.packages.StructImpl; | |
| import com.google.devtools.build.lib.skyframe.AspectKeyCreator.AspectKey; | |
| import com.google.devtools.build.lib.testutil.MoreAsserts; | |
| import com.google.devtools.build.lib.testutil.TestConstants; | |
| import com.google.devtools.build.lib.vfs.ModifiedFileSet; | |
| import com.google.devtools.build.lib.vfs.Root; | |
| import java.util.ArrayList; | |
| import java.util.List; | |
| import java.util.Map; | |
| import java.util.regex.Pattern; | |
| import net.starlark.java.eval.StarlarkInt; | |
| import org.junit.Test; | |
| import org.junit.runner.RunWith; | |
| import org.junit.runners.JUnit4; | |
| /** | |
| * Tests for aspect creation and merging with configured targets. | |
| * | |
| * <p>Uses the complete analysis machinery and depends on custom rules so that behaviors related to | |
| * aspects can be tested even if they aren't used by regular rules. | |
| */ | |
| @RunWith(JUnit4.class) | |
| public class AspectTest extends AnalysisTestCase { | |
| private void pkg(String name, String... contents) throws Exception { | |
| scratch.file(name + "/BUILD", contents); | |
| } | |
| @Test | |
| public void testAspectAppliedToAliasWithSelect() throws Exception { | |
| setRulesAvailableInTests(TestAspects.BASE_RULE, TestAspects.ASPECT_REQUIRING_RULE); | |
| pkg("a", | |
| "aspect(name='a', foo=[':b'])", | |
| "alias(name='b', actual=select({'//conditions:default': ':c'}))", | |
| "base(name='c')"); | |
| ConfiguredTarget a = getConfiguredTarget("//a:a"); | |
| assertThat(a.getProvider(RuleInfo.class).getData().toList()) | |
| .containsExactly("aspect //a:c", "rule //a:a"); | |
| } | |
| @Test | |
| public void testAspectAppliedToChainedAliases() throws Exception { | |
| setRulesAvailableInTests(TestAspects.BASE_RULE, TestAspects.ASPECT_REQUIRING_RULE); | |
| pkg("a", | |
| "aspect(name='a', foo=[':b'])", | |
| "alias(name='b', actual=':c')", | |
| "alias(name='c', actual=':d')", | |
| "alias(name='d', actual=':e')", | |
| "base(name='e')"); | |
| ConfiguredTarget a = getConfiguredTarget("//a:a"); | |
| assertThat(a.getProvider(RuleInfo.class).getData().toList()) | |
| .containsExactly("aspect //a:e", "rule //a:a"); | |
| } | |
| @Test | |
| public void testAspectAppliedToChainedAliasesAndSelect() throws Exception { | |
| setRulesAvailableInTests(TestAspects.BASE_RULE, TestAspects.ASPECT_REQUIRING_RULE); | |
| pkg("a", | |
| "aspect(name='a', foo=[':b'])", | |
| "alias(name='b', actual=select({'//conditions:default': ':c'}))", | |
| "alias(name='c', actual=select({'//conditions:default': ':d'}))", | |
| "base(name='d')"); | |
| ConfiguredTarget a = getConfiguredTarget("//a:a"); | |
| assertThat(a.getProvider(RuleInfo.class).getData().toList()) | |
| .containsExactly("aspect //a:d", "rule //a:a"); | |
| } | |
| @Test | |
| public void providersOfAspectAreMergedIntoDependency() throws Exception { | |
| setRulesAvailableInTests(TestAspects.BASE_RULE, TestAspects.ASPECT_REQUIRING_RULE); | |
| pkg("a", | |
| "aspect(name='a', foo=[':b'])", | |
| "aspect(name='b', foo=[])"); | |
| ConfiguredTarget a = getConfiguredTarget("//a:a"); | |
| assertThat(a.getProvider(RuleInfo.class).getData().toList()) | |
| .containsExactly("aspect //a:b", "rule //a:a"); | |
| } | |
| @Test | |
| public void aspectIsNotCreatedIfAdvertisedProviderIsNotPresent() throws Exception { | |
| setRulesAvailableInTests(TestAspects.BASE_RULE, TestAspects.LIAR_RULE, | |
| TestAspects.ASPECT_REQUIRING_PROVIDER_RULE); | |
| pkg("a", | |
| "aspect_requiring_provider(name='a', foo=[':b'])", | |
| "liar(name='b', foo=[])"); | |
| ConfiguredTarget a = getConfiguredTarget("//a:a"); | |
| assertThat(a.getProvider(RuleInfo.class).getData().toList()).containsExactly("rule //a:a"); | |
| } | |
| @Test | |
| public void aspectIsNotCreatedIfAdvertisedProviderIsNotPresentWithAlias() throws Exception { | |
| setRulesAvailableInTests(TestAspects.BASE_RULE, TestAspects.LIAR_RULE, | |
| TestAspects.ASPECT_REQUIRING_PROVIDER_RULE); | |
| pkg("a", | |
| "aspect_requiring_provider(name='a', foo=[':b'])", | |
| "alias(name = 'b_alias', actual = ':b')", | |
| "liar(name='b', foo=[])"); | |
| ConfiguredTarget a = getConfiguredTarget("//a:a"); | |
| assertThat(a.getProvider(RuleInfo.class).getData().toList()).containsExactly("rule //a:a"); | |
| } | |
| @Test | |
| public void aspectIsNotPropagatedThroughLiars() throws Exception { | |
| setRulesAvailableInTests(TestAspects.BASE_RULE, TestAspects.LIAR_RULE, | |
| TestAspects.HONEST_RULE, TestAspects.ASPECT_REQUIRING_PROVIDER_RULE); | |
| pkg("a", | |
| "aspect_requiring_provider(name='a', foo=[':b_alias'])", | |
| "alias(name = 'b_alias', actual = ':b')", | |
| "liar(name='b', foo=[':c'])", | |
| "honest(name = 'c', foo = [])" | |
| ); | |
| ConfiguredTarget a = getConfiguredTarget("//a:a"); | |
| assertThat(a.getProvider(RuleInfo.class).getData().toList()).containsExactly("rule //a:a"); | |
| } | |
| @Test | |
| public void aspectPropagatedThroughAliasRule() throws Exception { | |
| setRulesAvailableInTests(TestAspects.BASE_RULE, TestAspects.HONEST_RULE, | |
| TestAspects.ASPECT_REQUIRING_PROVIDER_RULE); | |
| pkg("a", | |
| "aspect_requiring_provider(name='a', foo=[':b_alias'])", | |
| "alias(name = 'b_alias', actual = ':b')", | |
| "honest(name='b', foo=[])"); | |
| ConfiguredTarget a = getConfiguredTarget("//a:a"); | |
| assertThat(a.getProvider(RuleInfo.class).getData().toList()) | |
| .containsExactly("rule //a:a", "aspect //a:b"); | |
| } | |
| @Test | |
| public void aspectPropagatedThroughAliasRuleAndHonestRules() throws Exception { | |
| setRulesAvailableInTests(TestAspects.BASE_RULE, TestAspects.HONEST_RULE, | |
| TestAspects.ASPECT_REQUIRING_PROVIDER_RULE); | |
| pkg("a", | |
| "aspect_requiring_provider(name='a', foo=[':b'])", | |
| "alias(name = 'b_alias', actual = ':b')", | |
| "honest(name='b', foo=[':c'])", | |
| "honest(name='c', foo=[])" | |
| ); | |
| ConfiguredTarget a = getConfiguredTarget("//a:a"); | |
| assertThat(a.getProvider(RuleInfo.class).getData().toList()) | |
| .containsExactly("rule //a:a", "aspect //a:b", "aspect //a:c"); | |
| } | |
| @Test | |
| public void aspectCreationWorksThroughBind() throws Exception { | |
| if (getInternalTestExecutionMode() != TestConstants.InternalTestExecutionMode.NORMAL) { | |
| // TODO(b/67651960): fix or justify disabling. | |
| return; | |
| } | |
| setRulesAvailableInTests(TestAspects.BASE_RULE, TestAspects.HONEST_RULE, | |
| TestAspects.ASPECT_REQUIRING_PROVIDER_RULE); | |
| pkg("a", | |
| "aspect_requiring_provider(name='a', foo=['//external:b'])", | |
| "honest(name='b', foo=[])"); | |
| scratch.overwriteFile("WORKSPACE", | |
| new ImmutableList.Builder<String>() | |
| .addAll(analysisMock.getWorkspaceContents(mockToolsConfig)) | |
| .add("bind(name='b', actual='//a:b')") | |
| .build()); | |
| skyframeExecutor.invalidateFilesUnderPathForTesting( | |
| reporter, ModifiedFileSet.EVERYTHING_MODIFIED, Root.fromPath(rootDirectory)); | |
| ConfiguredTarget a = getConfiguredTarget("//a:a"); | |
| assertThat(a.getProvider(RuleInfo.class).getData().toList()) | |
| .containsExactly("rule //a:a", "aspect //a:b"); | |
| } | |
| @Test | |
| public void aspectCreatedIfAdvertisedProviderIsPresent() throws Exception { | |
| setRulesAvailableInTests(TestAspects.BASE_RULE, TestAspects.HONEST_RULE, | |
| TestAspects.ASPECT_REQUIRING_PROVIDER_RULE); | |
| pkg("a", | |
| "aspect_requiring_provider(name='a', foo=[':b'])", | |
| "honest(name='b', foo=[])"); | |
| ConfiguredTarget a = getConfiguredTarget("//a:a"); | |
| assertThat(a.getProvider(RuleInfo.class).getData().toList()) | |
| .containsExactly("rule //a:a", "aspect //a:b"); | |
| } | |
| @Test | |
| public void aspectCreatedIfAtLeastOneSetOfAdvertisedProvidersArePresent() throws Exception { | |
| setRulesAvailableInTests(TestAspects.BASE_RULE, TestAspects.HONEST_RULE, | |
| TestAspects.HONEST_RULE_2, TestAspects.ASPECT_REQUIRING_PROVIDER_SETS_RULE); | |
| pkg("a", | |
| "aspect_requiring_provider_sets(name='a', foo=[':b', ':c'])", | |
| "honest(name='b', foo=[])", | |
| "honest2(name='c', foo=[])"); | |
| ConfiguredTarget a = getConfiguredTarget("//a:a"); | |
| assertThat(a.getProvider(RuleInfo.class).getData().toList()) | |
| .containsExactly("rule //a:a", "aspect //a:b", "aspect //a:c"); | |
| } | |
| @Test | |
| public void aspectWithParametrizedDefinition() throws Exception { | |
| setRulesAvailableInTests( | |
| TestAspects.BASE_RULE, | |
| TestAspects.HONEST_RULE, | |
| TestAspects.PARAMETERIZED_DEFINITION_ASPECT_RULE); | |
| pkg( | |
| "a", | |
| "honest(name='q', foo=[])", | |
| "parametrized_definition_aspect(name='a', foo=[':b'], baz='//a:q')", | |
| "honest(name='c', foo=[])", | |
| "honest(name='b', foo=[':c'])"); | |
| ConfiguredTarget a = getConfiguredTarget("//a:a"); | |
| assertThat(a.getProvider(TestAspects.RuleInfo.class).getData().toList()) | |
| .containsExactly( | |
| "rule //a:a", | |
| "aspect //a:b data //a:q $dep:[ //a:q]", | |
| "aspect //a:c data //a:q $dep:[ //a:q]"); | |
| } | |
| @Test | |
| public void aspectInError() throws Exception { | |
| setRulesAvailableInTests(TestAspects.BASE_RULE, TestAspects.ERROR_ASPECT_RULE, | |
| TestAspects.SIMPLE_RULE); | |
| pkg("a", | |
| "simple(name='a', foo=[':b'])", | |
| "error_aspect(name='b', foo=[':c'])", | |
| "simple(name='c')"); | |
| reporter.removeHandler(failFastHandler); | |
| // getConfiguredTarget() uses a separate code path that does not hit | |
| // SkyframeBuildView#configureTargets | |
| assertThrows(ViewCreationFailedException.class, () -> update("//a:a")); | |
| assertContainsEvent("Aspect error"); | |
| } | |
| @Test | |
| public void transitiveAspectInError() throws Exception { | |
| setRulesAvailableInTests(TestAspects.BASE_RULE, TestAspects.ERROR_ASPECT_RULE, | |
| TestAspects.SIMPLE_RULE); | |
| pkg("a", | |
| "error_aspect(name='a', foo=[':b'])", | |
| "error_aspect(name='b', bar=[':c'])", | |
| "error_aspect(name='c', bar=[':d'])", | |
| "error_aspect(name='d')"); | |
| reporter.removeHandler(failFastHandler); | |
| // getConfiguredTarget() uses a separate code path that does not hit | |
| // SkyframeBuildView#configureTargets | |
| assertThrows(ViewCreationFailedException.class, () -> update("//a:a")); | |
| assertContainsEvent("Aspect error"); | |
| } | |
| @Test | |
| public void aspectDependenciesDontShowDeprecationWarnings() throws Exception { | |
| setRulesAvailableInTests(TestAspects.BASE_RULE, TestAspects.EXTRA_ATTRIBUTE_ASPECT_RULE); | |
| pkg("extra", "base(name='extra', deprecation='bad aspect')"); | |
| pkg("a", | |
| "rule_with_extra_deps_aspect(name='a', foo=[':b'])", | |
| "base(name='b')"); | |
| getConfiguredTarget("//a:a"); | |
| assertContainsEventWithFrequency("bad aspect", 0); | |
| } | |
| @Test | |
| public void aspectDependsOnPackageGroup() throws Exception { | |
| setRulesAvailableInTests( | |
| TestAspects.BASE_RULE, TestAspects.PACKAGE_GROUP_ATTRIBUTE_ASPECT_RULE); | |
| pkg("extra", "package_group(name='extra')"); | |
| pkg("a", "rule_with_package_group_deps_aspect(name='a', foo=[':b'])", "base(name='b')"); | |
| getConfiguredTarget("//a:a"); | |
| assertContainsEventWithFrequency("bad aspect", 0); | |
| } | |
| @Test | |
| public void aspectWithComputedAttribute() throws Exception { | |
| setRulesAvailableInTests(TestAspects.BASE_RULE, TestAspects.COMPUTED_ATTRIBUTE_ASPECT_RULE); | |
| pkg("a", "rule_with_computed_deps_aspect(name='a', foo=[':b'])", "base(name='b')"); | |
| getConfiguredTarget("//a:a"); | |
| } | |
| @Test | |
| public void ruleDependencyDeprecationWarningsAbsentDuringAspectEvaluations() throws Exception { | |
| setRulesAvailableInTests(TestAspects.BASE_RULE, TestAspects.ASPECT_REQUIRING_RULE); | |
| pkg("a", "aspect(name='a', foo=['//b:b'])"); | |
| pkg("b", "aspect(name='b', bar=['//d:d'])"); | |
| pkg("d", "base(name='d', deprecation='bad rule')"); | |
| getConfiguredTarget("//a:a"); | |
| assertContainsEventWithFrequency("bad rule", 1); | |
| } | |
| @Test | |
| public void aspectWarningsFilteredByOutputFiltersForAssociatedRules() throws Exception { | |
| if (getInternalTestExecutionMode() != TestConstants.InternalTestExecutionMode.NORMAL) { | |
| // TODO(b/67651960): fix or justify disabling. | |
| return; | |
| } | |
| setRulesAvailableInTests(TestAspects.BASE_RULE, TestAspects.WARNING_ASPECT_RULE); | |
| pkg("a", "warning_aspect(name='a', foo=['//b:b', '//c:c'])"); | |
| pkg("b", "base(name='b')"); | |
| pkg("c", "base(name='c')"); | |
| reporter.setOutputFilter(RegexOutputFilter.forPattern(Pattern.compile("^//b:"))); | |
| getConfiguredTarget("//a:a"); | |
| assertContainsEventWithFrequency("Aspect warning on //b:b", 1); | |
| assertContainsEventWithFrequency("Aspect warning on //c:c", 0); | |
| } | |
| @Test | |
| public void sameTargetInDifferentAttributes() throws Exception { | |
| setRulesAvailableInTests(TestAspects.BASE_RULE, TestAspects.ASPECT_REQUIRING_RULE, | |
| TestAspects.SIMPLE_RULE); | |
| pkg("a", | |
| "aspect(name='a', foo=[':b'], bar=[':b'])", | |
| "aspect(name='b', foo=[])"); | |
| ConfiguredTarget a = getConfiguredTarget("//a:a"); | |
| assertThat(a.getProvider(RuleInfo.class).getData().toList()) | |
| .containsExactly("aspect //a:b", "rule //a:a"); | |
| } | |
| @Test | |
| public void sameTargetInDifferentAttributesWithDifferentAspects() throws Exception { | |
| setRulesAvailableInTests(TestAspects.BASE_RULE, TestAspects.MULTI_ASPECT_RULE, | |
| TestAspects.SIMPLE_RULE); | |
| pkg("a", | |
| "multi_aspect(name='a', foo=':b', bar=':b')", | |
| "simple(name='b')"); | |
| ConfiguredTarget a = getConfiguredTarget("//a:a"); | |
| assertThat(a.getProvider(RuleInfo.class).getData().toList()).containsExactly("foo", "bar"); | |
| } | |
| @Test | |
| public void informationFromBaseRulePassedToAspect() throws Exception { | |
| setRulesAvailableInTests(TestAspects.BASE_RULE, TestAspects.HONEST_RULE, | |
| TestAspects.ASPECT_REQUIRING_PROVIDER_RULE); | |
| pkg("a", | |
| "aspect_requiring_provider(name='a', foo=[':b'], baz='hello')", | |
| "honest(name='b', foo=[])"); | |
| ConfiguredTarget a = getConfiguredTarget("//a:a"); | |
| assertThat(a.getProvider(RuleInfo.class).getData().toList()) | |
| .containsExactly("rule //a:a", "aspect //a:b data hello"); | |
| } | |
| /** | |
| * Rule definitions to be used in emptyAspectAttributesAreAvailableInRuleContext(). | |
| */ | |
| public static class EmptyAspectAttributesAreAvailableInRuleContext { | |
| public static final MockRule TEST_RULE = | |
| () -> | |
| MockRule.ancestor(TestAspects.BASE_RULE.getClass()) | |
| .factory(DummyRuleFactory.class) | |
| .define( | |
| "testrule", | |
| (builder, env) -> | |
| builder.add( | |
| attr("foo", LABEL_LIST) | |
| .legacyAllowAnyFileType() | |
| .aspect(AspectWithEmptyLateBoundAttribute.INSTANCE))); | |
| public static class AspectWithEmptyLateBoundAttribute extends NativeAspectClass | |
| implements ConfiguredAspectFactory { | |
| static final AspectWithEmptyLateBoundAttribute INSTANCE = | |
| new AspectWithEmptyLateBoundAttribute(); | |
| private AspectWithEmptyLateBoundAttribute() {} | |
| @Override | |
| public AspectDefinition getDefinition(AspectParameters params) { | |
| return new AspectDefinition.Builder(this) | |
| .add(attr(":late", LABEL).value(LateBoundDefault.alwaysNull())) | |
| .build(); | |
| } | |
| @Override | |
| public ConfiguredAspect create( | |
| Label targetLabel, | |
| ConfiguredTarget ct, | |
| RuleContext ruleContext, | |
| AspectParameters parameters, | |
| RepositoryName toolsRepository) | |
| throws InterruptedException, ActionConflictException { | |
| Object lateBoundPrereq = ruleContext.getPrerequisite(":late"); | |
| return new ConfiguredAspect.Builder(ruleContext) | |
| .addProvider( | |
| AspectInfo.class, | |
| new AspectInfo( | |
| NestedSetBuilder.create( | |
| Order.STABLE_ORDER, lateBoundPrereq != null ? "non-empty" : "empty"))) | |
| .build(); | |
| } | |
| } | |
| } | |
| /** | |
| * An Aspect has a late-bound attribute with no value (that is, a LateBoundDefault whose | |
| * getDefault() returns `null`). Test that this attribute is available in the RuleContext which is | |
| * provided to the Aspect's `create()` method. | |
| */ | |
| @Test | |
| public void emptyAspectAttributesAreAvailableInRuleContext() throws Exception { | |
| setRulesAndAspectsAvailableInTests( | |
| ImmutableList.of( | |
| TestAspects.SIMPLE_ASPECT, | |
| EmptyAspectAttributesAreAvailableInRuleContext.AspectWithEmptyLateBoundAttribute | |
| .INSTANCE), | |
| ImmutableList.of( | |
| TestAspects.BASE_RULE, EmptyAspectAttributesAreAvailableInRuleContext.TEST_RULE)); | |
| pkg("a", | |
| "testrule(name='a', foo=[':b'])", | |
| "testrule(name='b')"); | |
| ConfiguredTarget a = getConfiguredTarget("//a:a"); | |
| assertThat(a.getProvider(RuleInfo.class).getData().toList()).contains("empty"); | |
| } | |
| /** | |
| * Rule definitions to be used in extraActionsAreEmitted(). | |
| */ | |
| public static class ExtraActionsAreEmitted { | |
| public static final MockRule TEST_RULE = | |
| () -> | |
| MockRule.ancestor(TestAspects.BASE_RULE.getClass()) | |
| .factory(DummyRuleFactory.class) | |
| .define( | |
| "testrule", | |
| (builder, env) -> | |
| builder | |
| .add( | |
| attr("foo", LABEL_LIST) | |
| .legacyAllowAnyFileType() | |
| .aspect(AspectThatRegistersAction.INSTANCE)) | |
| .add( | |
| attr(":action_listener", LABEL_LIST) | |
| .cfg(ExecutionTransitionFactory.create()) | |
| .value(ACTION_LISTENER))); | |
| public static class AspectThatRegistersAction extends NativeAspectClass | |
| implements ConfiguredAspectFactory { | |
| static final AspectThatRegistersAction INSTANCE = new AspectThatRegistersAction(); | |
| private AspectThatRegistersAction() {} | |
| @Override | |
| public AspectDefinition getDefinition(AspectParameters params) { | |
| return new AspectDefinition.Builder(this).build(); | |
| } | |
| @Override | |
| public ConfiguredAspect create( | |
| Label targetLabel, | |
| ConfiguredTarget ct, | |
| RuleContext ruleContext, | |
| AspectParameters parameters, | |
| RepositoryName toolsRepository) | |
| throws InterruptedException, ActionConflictException { | |
| ruleContext.registerAction(new NullAction(ruleContext.createOutputArtifact())); | |
| return new ConfiguredAspect.Builder(ruleContext).build(); | |
| } | |
| } | |
| } | |
| /** | |
| * Test that actions registered in an Aspect are reported as extra-actions on the attached rule. | |
| * AspectThatRegistersAction registers a NullAction, whose mnemonic is "Null". We have an | |
| * action_listener that targets that mnemonic, which makes sure the Aspect machinery will expose | |
| * an ExtraActionArtifactsProvider. | |
| * The rule //a:a doesn't have an aspect, so the only action we get is the one on //a:b | |
| * (which does have an aspect). | |
| */ | |
| @Test | |
| public void extraActionsAreEmitted() throws Exception { | |
| setRulesAndAspectsAvailableInTests( | |
| ImmutableList.of( | |
| TestAspects.SIMPLE_ASPECT, ExtraActionsAreEmitted.AspectThatRegistersAction.INSTANCE), | |
| ImmutableList.of(TestAspects.BASE_RULE, ExtraActionsAreEmitted.TEST_RULE)); | |
| useConfiguration("--experimental_action_listener=//extra_actions:listener"); | |
| scratch.file( | |
| "extra_actions/BUILD", | |
| "extra_action(name='xa', cmd='echo dont-care')", | |
| "action_listener(name='listener', mnemonics=['Null'], extra_actions=[':xa'])"); | |
| pkg("a", | |
| "testrule(name='a', foo=[':b'])", | |
| "testrule(name='b')"); | |
| update(); | |
| ConfiguredTarget a = getConfiguredTarget("//a:a"); | |
| NestedSet<Artifact.DerivedArtifact> extraActionArtifacts = | |
| a.getProvider(ExtraActionArtifactsProvider.class).getTransitiveExtraActionArtifacts(); | |
| for (Artifact artifact : extraActionArtifacts.toList()) { | |
| assertThat(artifact.getOwnerLabel()).isEqualTo(Label.create("@//a", "b")); | |
| } | |
| } | |
| @Test | |
| public void aspectPropagatesToAllAttributes() throws Exception { | |
| setRulesAvailableInTests(TestAspects.BASE_RULE, TestAspects.SIMPLE_RULE, | |
| TestAspects.ALL_ATTRIBUTES_ASPECT_RULE); | |
| pkg("a", | |
| "simple(name='a', foo=[':b'], foo1=':c', txt='some text')", | |
| "simple(name='b', foo=[], txt='some text')", | |
| "simple(name='c', foo=[], txt='more text')", | |
| "all_attributes_aspect(name='x', foo=[':a'])"); | |
| ConfiguredTarget a = getConfiguredTarget("//a:x"); | |
| assertThat(a.getProvider(RuleInfo.class).getData().toList()) | |
| .containsExactly("aspect //a:a", "aspect //a:b", "aspect //a:c", "rule //a:x"); | |
| } | |
| /** | |
| * Tests that when --experimental_extra_action_top_level_only, Blaze reports extra-actions for | |
| * actions registered by Aspects injected by a top-level rule. Because we can't know whether an | |
| * aspect was injected by a top-level target or one of its children, we approximate it by only | |
| * reporting extra-actions from Aspects that the top-level target could have injected. | |
| * | |
| * <p>Here, injector1() and injector2() inject aspects into their children. null_rule() just | |
| * passes the aspects to its children. The test makes sure that actions registered by aspect1 | |
| * (injected by injector1()) are reported to the extra-action mechanism. Actions registered by | |
| * aspect2 (from injector2) are not reported, because the target under test (//x:a) doesn't inject | |
| * aspect2. | |
| */ | |
| @Test | |
| public void extraActionsAreEmitted_topLevel() throws Exception { | |
| useConfiguration( | |
| "--experimental_action_listener=//pkg1:listener", | |
| "--experimental_extra_action_top_level_only"); | |
| scratch.file( | |
| "x/BUILD", | |
| "load(':extension.bzl', 'injector1', 'injector2', 'null_rule')", | |
| "injector1(name='a', deps=[':b'])", | |
| "null_rule(name='b', deps=[':c'])", | |
| "null_rule(name='c', deps=[':d'])", | |
| "injector2(name = 'd', extra_deps=[':e'])", | |
| "null_rule(name = 'e')"); | |
| scratch.file( | |
| "x/extension.bzl", | |
| "def _aspect_impl(target, ctx):", | |
| " ctx.actions.do_nothing(mnemonic='Mnemonic')", | |
| " return []", | |
| "aspect1 = aspect(_aspect_impl, attr_aspects=['deps'])", | |
| "aspect2 = aspect(_aspect_impl, attr_aspects=['extra_deps'])", | |
| "def _rule_impl(ctx):", | |
| " return []", | |
| "injector1 = rule(_rule_impl, attrs = { 'deps' : attr.label_list(aspects = [aspect1]) })", | |
| "null_rule = rule(_rule_impl, attrs = { 'deps' : attr.label_list() })", | |
| "injector2 = rule(", | |
| " _rule_impl, attrs = { 'extra_deps' : attr.label_list(aspects = [aspect2]) })"); | |
| scratch.file( | |
| "pkg1/BUILD", | |
| "extra_action(name='xa', cmd='echo dont-care')", | |
| "action_listener(name='listener', mnemonics=['Mnemonic'], extra_actions=[':xa'])"); | |
| // Check: //x:d injects an aspect which produces some extra-action. | |
| { | |
| AnalysisResult analysisResult = update("//x:d"); | |
| // Get owners of all extra-action artifacts. | |
| List<Label> extraArtifactOwners = new ArrayList<>(); | |
| for (Artifact artifact : analysisResult.getArtifactsToBuild()) { | |
| if (artifact.getRootRelativePathString().endsWith(".xa")) { | |
| extraArtifactOwners.add(artifact.getOwnerLabel()); | |
| } | |
| } | |
| assertThat(extraArtifactOwners).containsExactly(Label.create("@//x", "e")); | |
| } | |
| // Actual test: //x:a reports actions registered by the aspect it injects. | |
| { | |
| AnalysisResult analysisResult = update("//x:a"); | |
| // Get owners of all extra-action artifacts. | |
| List<Label> extraArtifactOwners = new ArrayList<>(); | |
| for (Artifact artifact : analysisResult.getArtifactsToBuild()) { | |
| if (artifact.getRootRelativePathString().endsWith(".xa")) { | |
| extraArtifactOwners.add(artifact.getOwnerLabel()); | |
| } | |
| } | |
| assertThat(extraArtifactOwners) | |
| .containsExactly( | |
| Label.create("@//x", "b"), Label.create("@//x", "c"), Label.create("@//x", "d")); | |
| } | |
| } | |
| @Test | |
| public void extraActionsFromDifferentAspectsDontConflict() throws Exception { | |
| useConfiguration( | |
| "--experimental_action_listener=//pkg1:listener", | |
| "--experimental_extra_action_top_level_only"); | |
| scratch.file( | |
| "x/BUILD", | |
| "load(':extension.bzl', 'injector1', 'injector2', 'null_rule')", | |
| "injector2(name='i2_a', deps = [':i1_a'])", | |
| "injector1(name='i1_a', deps=[':n'], param = 'a')", | |
| "injector1(name='i1_b', deps=[':n'], param = 'b')", | |
| "injector2(name='i2', deps=[':n'])", | |
| "null_rule(name = 'n')"); | |
| scratch.file( | |
| "x/extension.bzl", | |
| "def _aspect_impl(target, ctx):", | |
| " ctx.actions.do_nothing(mnemonic='Mnemonic')", | |
| " return []", | |
| "aspect1 = aspect(_aspect_impl, attr_aspects=['deps'], attrs =", | |
| " {'param': attr.string(values = ['a', 'b'])})", | |
| "aspect2 = aspect(_aspect_impl, attr_aspects=['deps'])", | |
| "def _rule_impl(ctx):", | |
| " return []", | |
| "injector1 = rule(_rule_impl, attrs =", | |
| " { 'deps' : attr.label_list(aspects = [aspect1]), 'param' : attr.string() })", | |
| "injector2 = rule(_rule_impl, attrs = { 'deps' : attr.label_list(aspects = [aspect2]) })", | |
| "null_rule = rule(_rule_impl, attrs = { 'deps' : attr.label_list() })"); | |
| scratch.file( | |
| "pkg1/BUILD", | |
| "extra_action(name='xa', cmd='echo dont-care')", | |
| "action_listener(name='listener', mnemonics=['Mnemonic'], extra_actions=[':xa'])"); | |
| update("//x:i1_a", "//x:i1_b", "//x:i2", "//x:i2_a"); | |
| // Implicitly check that update() didn't throw an exception because of two actions producing | |
| // the same outputs. | |
| } | |
| @Test | |
| public void sharedArtifactsInAspect() throws Exception { | |
| scratch.file( | |
| "foo/shared_aspect.bzl", | |
| "def _shared_aspect_impl(target, ctx):", | |
| " shared_file = ctx.actions.declare_file('shared_file')", | |
| " ctx.actions.write(output=shared_file, content='Shared content')", | |
| " lib = ctx.rule.attr.lib", | |
| " if lib:", | |
| " result = depset([shared_file], transitive=[ctx.rule.attr.lib.prov])", | |
| " else:", | |
| " result = depset([shared_file])", | |
| " return struct(prov=result)", | |
| "", | |
| "shared_aspect = aspect(implementation = _shared_aspect_impl,", | |
| " attr_aspects = ['lib'])", | |
| "", | |
| "def _rule_impl(ctx):", | |
| " pass", | |
| "", | |
| "simple_rule = rule(", | |
| " implementation=_rule_impl,", | |
| " attrs = {'lib': attr.label(providers = ['prov'],", | |
| " aspects=[shared_aspect])})"); | |
| scratch.file( | |
| "foo/BUILD", | |
| "load(':shared_aspect.bzl', 'shared_aspect', 'simple_rule')", | |
| "", | |
| "simple_rule(name = 'top_rule', lib = ':first_dep')", | |
| "simple_rule(name = 'first_dep', lib = ':second_dep')", | |
| "simple_rule(name = 'second_dep')"); | |
| // Confirm that load is successful and doesn't crash. | |
| update("//foo:top_rule"); | |
| } | |
| @Test | |
| public void aspectPropagatesToAllAttributesImplicit() throws Exception { | |
| setRulesAvailableInTests(TestAspects.BASE_RULE, TestAspects.SIMPLE_RULE, | |
| TestAspects.IMPLICIT_DEP_RULE, TestAspects.ALL_ATTRIBUTES_ASPECT_RULE); | |
| scratch.file( | |
| "extra/BUILD", | |
| "simple(name ='extra')" | |
| ); | |
| pkg("a", | |
| "simple(name='a', foo=[':b'], foo1=':c', txt='some text')", | |
| "simple(name='b', foo=[], txt='some text')", | |
| "implicit_dep(name='c')", | |
| "all_attributes_aspect(name='x', foo=[':a'])"); | |
| update(); | |
| ConfiguredTarget a = getConfiguredTarget("//a:x"); | |
| assertThat(a.getProvider(RuleInfo.class).getData().toList()) | |
| .containsExactly( | |
| "aspect //a:a", "aspect //a:b", "aspect //a:c", "aspect //extra:extra", "rule //a:x"); | |
| } | |
| @Test | |
| public void aspectPropagatesToAllAttributesLateBound() throws Exception { | |
| setRulesAvailableInTests(TestAspects.BASE_RULE, TestAspects.SIMPLE_RULE, | |
| TestAspects.LATE_BOUND_DEP_RULE, TestAspects.ALL_ATTRIBUTES_ASPECT_RULE); | |
| scratch.file( | |
| "extra/BUILD", | |
| "simple(name ='extra')" | |
| ); | |
| pkg("a", | |
| "simple(name='a', foo=[':b'], foo1=':c', txt='some text')", | |
| "simple(name='b', foo=[], txt='some text')", | |
| "late_bound_dep(name='c')", | |
| "all_attributes_aspect(name='x', foo=[':a'])"); | |
| useConfiguration("--plugin=//extra:extra"); | |
| update(); | |
| ConfiguredTarget a = getConfiguredTarget("//a:x"); | |
| assertThat(a.getProvider(RuleInfo.class).getData().toList()) | |
| .containsExactly( | |
| "aspect //a:a", "aspect //a:b", "aspect //a:c", "aspect //extra:extra", "rule //a:x"); | |
| } | |
| /** | |
| * Ensures an aspect with attr = '*' doesn't try to propagate to its own implicit attributes. | |
| * Doing so leads to a dependency cycle. | |
| */ | |
| @Test | |
| public void aspectWithAllAttributesDoesNotPropagateToOwnImplicitAttributes() throws Exception { | |
| setRulesAvailableInTests(TestAspects.BASE_RULE, TestAspects.SIMPLE_RULE, | |
| TestAspects.ALL_ATTRIBUTES_WITH_TOOL_ASPECT_RULE); | |
| pkg( | |
| "a", | |
| "simple(name='tool')", | |
| "simple(name='a')", | |
| "all_attributes_with_tool_aspect(name='x', foo=[':a'])"); | |
| ConfiguredTarget a = getConfiguredTarget("//a:x"); | |
| assertThat(a.getProvider(RuleInfo.class).getData().toList()) | |
| .containsExactly("aspect //a:a", "rule //a:x"); | |
| } | |
| /** | |
| * Makes sure the aspect *will* propagate to its implicit attributes if there is a "regular" | |
| * dependency path to it (i.e. not through its own implicit attributes). | |
| */ | |
| @Test | |
| public void aspectWithAllAttributesPropagatesToItsToolIfThereIsPath() throws Exception { | |
| setRulesAvailableInTests(TestAspects.BASE_RULE, TestAspects.SIMPLE_RULE, | |
| TestAspects.ALL_ATTRIBUTES_WITH_TOOL_ASPECT_RULE); | |
| pkg( | |
| "a", | |
| "simple(name='tool')", | |
| "simple(name='a', foo=[':b'], foo1=':c', txt='some text')", | |
| "simple(name='b', foo=[], txt='some text')", | |
| "simple(name='c', foo=[':tool'], txt='more text')", | |
| "all_attributes_with_tool_aspect(name='x', foo=[':a'])"); | |
| ConfiguredTarget a = getConfiguredTarget("//a:x"); | |
| assertThat(a.getProvider(RuleInfo.class).getData().toList()) | |
| .containsExactly( | |
| "aspect //a:a", "aspect //a:b", "aspect //a:c", "aspect //a:tool", "rule //a:x"); | |
| } | |
| @Test | |
| public void aspectTruthInAdvertisement() throws Exception { | |
| reporter.removeHandler(failFastHandler); // expect errors | |
| setRulesAvailableInTests(TestAspects.BASE_RULE, TestAspects.SIMPLE_RULE, | |
| TestAspects.FALSE_ADVERTISEMENT_ASPECT_RULE); | |
| pkg( | |
| "a", | |
| "simple(name = 's')", | |
| "false_advertisement_aspect(name = 'x', deps = [':s'])" | |
| ); | |
| try { | |
| update("//a:x"); | |
| } catch (ViewCreationFailedException e) { | |
| // expected. | |
| } | |
| assertContainsEvent( | |
| "Aspect 'FalseAdvertisementAspect', applied to '//a:s'," | |
| + " does not provide advertised provider 'RequiredProvider'"); | |
| assertContainsEvent( | |
| "Aspect 'FalseAdvertisementAspect', applied to '//a:s'," | |
| + " does not provide advertised provider 'advertised_provider'"); | |
| } | |
| @Test | |
| public void aspectApplyingToFiles() throws Exception { | |
| AspectApplyingToFiles aspectApplyingToFiles = new AspectApplyingToFiles(); | |
| setRulesAndAspectsAvailableInTests(ImmutableList.of(aspectApplyingToFiles), ImmutableList.of()); | |
| pkg( | |
| "a", | |
| "java_binary(name = 'x', main_class = 'x.FooBar', srcs = ['x.java'])" | |
| ); | |
| AnalysisResult analysisResult = update(new EventBus(), defaultFlags(), | |
| ImmutableList.of(aspectApplyingToFiles.getName()), | |
| "//a:x_deploy.jar"); | |
| ConfiguredAspect aspect = Iterables.getOnlyElement(analysisResult.getAspectsMap().values()); | |
| AspectApplyingToFiles.Provider provider = | |
| aspect.getProvider(AspectApplyingToFiles.Provider.class); | |
| assertThat(provider.getLabel()) | |
| .isEqualTo(Label.parseAbsoluteUnchecked("//a:x_deploy.jar")); | |
| } | |
| @Test | |
| public void aspectApplyingToSourceFilesIgnored() throws Exception { | |
| AspectApplyingToFiles aspectApplyingToFiles = new AspectApplyingToFiles(); | |
| setRulesAndAspectsAvailableInTests(ImmutableList.of(aspectApplyingToFiles), ImmutableList.of()); | |
| pkg( | |
| "a", | |
| "java_binary(name = 'x', main_class = 'x.FooBar', srcs = ['x.java'])" | |
| ); | |
| scratch.file("a/x.java", ""); | |
| AnalysisResult analysisResult = update(new EventBus(), defaultFlags(), | |
| ImmutableList.of(aspectApplyingToFiles.getName()), | |
| "//a:x.java"); | |
| ConfiguredAspect aspect = Iterables.getOnlyElement(analysisResult.getAspectsMap().values()); | |
| assertThat(aspect.getProvider(AspectApplyingToFiles.Provider.class)).isNull(); | |
| } | |
| @Test | |
| public void aspectApplyingToPackageGroupIgnored() throws Exception { | |
| AspectApplyingToFiles aspectApplyingToFiles = new AspectApplyingToFiles(); | |
| setRulesAndAspectsAvailableInTests(ImmutableList.of(aspectApplyingToFiles), ImmutableList.of()); | |
| pkg("b"); | |
| pkg( | |
| "a", | |
| "package_group(name = 'group', packages = ['//b'])", | |
| "java_binary(name = 'x', main_class = 'x.F', srcs = ['x.java'], visibility = [':group'])"); | |
| scratch.file("a/x.java", ""); | |
| // This exercises a code path that crashes if the PackageGroup is matched as an aspect provider. | |
| AnalysisResult analysisResult = | |
| update( | |
| new EventBus(), | |
| defaultFlags(), | |
| ImmutableList.of(aspectApplyingToFiles.getName()), | |
| "//a:group"); | |
| assertThat(analysisResult.getAspectsMap()).hasSize(1); | |
| } | |
| @Test | |
| public void duplicateTopLevelAspects_duplicateAspectsNotAllowed() throws Exception { | |
| AspectApplyingToFiles aspectApplyingToFiles = new AspectApplyingToFiles(); | |
| setRulesAndAspectsAvailableInTests(ImmutableList.of(aspectApplyingToFiles), ImmutableList.of()); | |
| pkg("a", "java_binary(name = 'x', main_class = 'x.FooBar', srcs = ['x.java'])"); | |
| reporter.removeHandler(failFastHandler); | |
| assertThrows( | |
| ViewCreationFailedException.class, | |
| () -> | |
| update( | |
| new EventBus(), | |
| defaultFlags(), | |
| ImmutableList.of(aspectApplyingToFiles.getName(), aspectApplyingToFiles.getName()), | |
| "//a:x_deploy.jar")); | |
| assertContainsEvent("Aspect AspectApplyingToFiles has already been added"); | |
| } | |
| @Test | |
| public void aspectWithExtraAttribute_ignoredForOutputFile() throws Exception { | |
| ExtraAttributeAspect aspect = | |
| new ExtraAttributeAspect("//nonexistent", /*applyToFiles=*/ false); | |
| setRulesAndAspectsAvailableInTests(ImmutableList.of(aspect), ImmutableList.of()); | |
| scratch.file("a/BUILD", "genrule(name='gen_a', outs=['a'], cmd='touch $@')"); | |
| AnalysisResult analysisResult = | |
| update(new EventBus(), defaultFlags(), ImmutableList.of(aspect.getName()), "//a"); | |
| ConfiguredAspect configuredAspect = | |
| Iterables.getOnlyElement(analysisResult.getAspectsMap().values()); | |
| assertThat(configuredAspect.getProvider(ExtraAttributeAspect.Provider.class)).isNull(); | |
| } | |
| @Test | |
| public void aspectWithExtraAttributeApplyToFiles_outputFile_hasResolvedAttribute() | |
| throws Exception { | |
| ExtraAttributeAspect aspect = new ExtraAttributeAspect("//extra", /*applyToFiles=*/ true); | |
| setRulesAndAspectsAvailableInTests( | |
| ImmutableList.of(aspect), ImmutableList.of(TestAspects.BASE_RULE, TestAspects.SIMPLE_RULE)); | |
| scratch.file("extra/BUILD", "simple(name='extra')"); | |
| scratch.file("a/BUILD", "genrule(name='gen_a', outs=['a'], cmd='touch $@')"); | |
| AnalysisResult analysisResult = | |
| update(new EventBus(), defaultFlags(), ImmutableList.of(aspect.getName()), "//a"); | |
| ConfiguredAspect configuredAspect = | |
| Iterables.getOnlyElement(analysisResult.getAspectsMap().values()); | |
| ExtraAttributeAspect.Provider provider = | |
| configuredAspect.getProvider(ExtraAttributeAspect.Provider.class); | |
| assertThat(provider.label()).isEqualTo("//extra:extra"); | |
| } | |
| @Test | |
| public void aspectWithExtraAttributeApplyToFiles_ignoredForSourceFile() throws Exception { | |
| ExtraAttributeAspect aspect = new ExtraAttributeAspect("//nonexistent", /*applyToFiles=*/ true); | |
| setRulesAndAspectsAvailableInTests(ImmutableList.of(aspect), ImmutableList.of()); | |
| scratch.file("a/BUILD", "exports_files(['a.txt'])"); | |
| scratch.file("a/a.txt", "hello"); | |
| AnalysisResult analysisResult = | |
| update(new EventBus(), defaultFlags(), ImmutableList.of(aspect.getName()), "//a:a.txt"); | |
| ConfiguredAspect configuredAspect = | |
| Iterables.getOnlyElement(analysisResult.getAspectsMap().values()); | |
| assertThat(configuredAspect.getProvider(ExtraAttributeAspect.Provider.class)).isNull(); | |
| } | |
| @Test | |
| public void aspectWithExtraAttributeApplyToFilesAndNot_outputFile_onlyApplyToFilesIsResolved() | |
| throws Exception { | |
| ExtraAttributeAspect aspectApplies = | |
| new ExtraAttributeAspect("//extra", /*applyToFiles=*/ true); | |
| ExtraAttributeAspect aspectDoesNotApply = | |
| new ExtraAttributeAspect("//nonexistent", /*applyToFiles=*/ false); | |
| setRulesAndAspectsAvailableInTests( | |
| ImmutableList.of(aspectApplies, aspectDoesNotApply), | |
| ImmutableList.of(TestAspects.BASE_RULE, TestAspects.SIMPLE_RULE)); | |
| scratch.file("extra/BUILD", "simple(name='extra')"); | |
| scratch.file("a/BUILD", "genrule(name='gen_a', outs=['a'], cmd='touch $@')"); | |
| AnalysisResult analysisResult = | |
| update( | |
| new EventBus(), | |
| defaultFlags(), | |
| ImmutableList.of(aspectApplies.getName(), aspectDoesNotApply.getName()), | |
| "//a"); | |
| assertThat(analysisResult.getAspectsMap()).hasSize(2); | |
| ExtraAttributeAspect.Provider provider = | |
| getAspectByName(analysisResult.getAspectsMap(), aspectApplies.getName()) | |
| .getProvider(ExtraAttributeAspect.Provider.class); | |
| assertThat(provider.label()).isEqualTo("//extra:extra"); | |
| assertThat( | |
| getAspectByName(analysisResult.getAspectsMap(), aspectDoesNotApply.getName()) | |
| .getProviders() | |
| .getProviderCount()) | |
| .isEqualTo(0); | |
| } | |
| @Test | |
| public void aspectWithExtraAttributeDependsOnNotApplicable_usesAttributeFromDependentAspect() | |
| throws Exception { | |
| ExtraAttributeAspect aspectApplies = | |
| new ExtraAttributeAspect( | |
| "//extra", /*applyToFiles=*/ true, ExtraAttributeAspect.Provider.class); | |
| ExtraAttributeAspect aspectDoesNotApply = | |
| new ExtraAttributeAspect("//extra:extra2", /*applyToFiles=*/ false); | |
| setRulesAndAspectsAvailableInTests( | |
| ImmutableList.of(aspectApplies, aspectDoesNotApply), | |
| ImmutableList.of(TestAspects.BASE_RULE, TestAspects.SIMPLE_RULE)); | |
| scratch.file("extra/BUILD", "simple(name='extra')", "simple(name='extra2')"); | |
| scratch.file("a/BUILD", "genrule(name='gen_a', outs=['a'], cmd='touch $@')"); | |
| AnalysisResult analysisResult = | |
| update( | |
| new EventBus(), | |
| defaultFlags(), | |
| ImmutableList.of(aspectDoesNotApply.getName(), aspectApplies.getName()), | |
| "//a"); | |
| assertThat(analysisResult.getAspectsMap()).hasSize(2); | |
| ExtraAttributeAspect.Provider provider = | |
| getAspectByName(analysisResult.getAspectsMap(), aspectApplies.getName()) | |
| .getProvider(ExtraAttributeAspect.Provider.class); | |
| assertThat(provider.label()).isEqualTo("//extra:extra2"); | |
| assertThat( | |
| getAspectByName(analysisResult.getAspectsMap(), aspectDoesNotApply.getName()) | |
| .getProviders() | |
| .getProviderCount()) | |
| .isEqualTo(0); | |
| } | |
| @Test | |
| public void sameConfiguredAttributeOnAspectAndRule() throws Exception { | |
| scratch.file( | |
| "a/a.bzl", | |
| "def _a_impl(t, ctx):", | |
| " return [DefaultInfo()]", | |
| "def _r_impl(ctx):", | |
| " return [DefaultInfo()]", | |
| "a = aspect(", | |
| " implementation = _a_impl,", | |
| " attrs = {'_f': attr.label(", | |
| " default = configuration_field(", | |
| " fragment = 'cpp', name = 'cc_toolchain'))})", | |
| "r = rule(", | |
| " implementation = _r_impl,", | |
| " attrs = {'_f': attr.label(", | |
| " default = configuration_field(", | |
| " fragment = 'cpp', name = 'cc_toolchain')),", | |
| " 'dep': attr.label(aspects=[a])})"); | |
| scratch.file("a/BUILD", "load(':a.bzl', 'r')", "r(name='r')"); | |
| setRulesAndAspectsAvailableInTests(ImmutableList.of(), ImmutableList.of()); | |
| getConfiguredTarget("//a:r"); | |
| } | |
| @Test | |
| public void topLevelConflictDetected() throws Exception { | |
| String bzlFileTemplate = | |
| String.join( | |
| "\n", | |
| "def _aspect1_impl(target, ctx):", | |
| " outfile = ctx.actions.declare_file('aspect.out')", | |
| " ctx.actions.run_shell(", | |
| " outputs = [outfile],", | |
| " progress_message = 'Action for aspect 1',", | |
| " command = 'echo \"1\" > ' + outfile.path,", | |
| " )", | |
| " return [OutputGroupInfo(files = [outfile])]", | |
| "def _aspect2_impl(target, ctx):", | |
| " outfile = ctx.actions.declare_file('aspect.out')", | |
| " ctx.actions.run_shell(", | |
| " outputs = [outfile],", | |
| " progress_message = 'Action for aspect 2',", | |
| " command = 'echo \"%s\" > ' + outfile.path,", | |
| " )", | |
| " return [OutputGroupInfo(files = [outfile])]", | |
| "aspect1 = aspect(implementation = _aspect1_impl)", | |
| "aspect2 = aspect(implementation = _aspect2_impl)"); | |
| scratch.file("foo/aspect.bzl", String.format(bzlFileTemplate, "2")); | |
| scratch.file("foo/BUILD", "sh_library(name = 'foo', srcs = ['foo.sh'])"); | |
| // Expect errors. | |
| reporter.removeHandler(failFastHandler); | |
| ViewCreationFailedException exception = | |
| assertThrows( | |
| ViewCreationFailedException.class, | |
| () -> | |
| update( | |
| new EventBus(), | |
| defaultFlags(), | |
| ImmutableList.of("//foo:aspect.bzl%aspect1", "//foo:aspect.bzl%aspect2"), | |
| "//foo:foo")); | |
| assertThat(exception) | |
| .hasMessageThat() | |
| .containsMatch( | |
| "ConflictException: for foo/aspect.out, previous action: action 'Action for aspect .'," | |
| + " attempted action: action 'Action for aspect .'"); | |
| MoreAsserts.assertContainsEvent( | |
| eventCollector, | |
| Pattern.compile( | |
| "Aspects: \\[//foo:aspect.bzl%aspect[12]], \\[//foo:aspect.bzl%aspect[12]]"), | |
| EventKind.ERROR); | |
| // Fix bzl file so actions are shared: analysis should succeed now. | |
| scratch.overwriteFile("foo/aspect.bzl", String.format(bzlFileTemplate, "1")); | |
| reporter.addHandler(failFastHandler); | |
| AnalysisResult result = | |
| update( | |
| new EventBus(), | |
| defaultFlags(), | |
| ImmutableList.of("//foo:aspect.bzl%aspect1", "//foo:aspect.bzl%aspect2"), | |
| "//foo:foo"); | |
| assertThat(result.getAspectsMap()).hasSize(2); | |
| // Break bzl file again: we should notice. | |
| scratch.overwriteFile("foo/aspect.bzl", String.format(bzlFileTemplate, "2")); | |
| // Expect errors. | |
| reporter.removeHandler(failFastHandler); | |
| exception = | |
| assertThrows( | |
| ViewCreationFailedException.class, | |
| () -> | |
| update( | |
| new EventBus(), | |
| defaultFlags(), | |
| ImmutableList.of("//foo:aspect.bzl%aspect1", "//foo:aspect.bzl%aspect2"), | |
| "//foo:foo")); | |
| assertThat(exception) | |
| .hasMessageThat() | |
| .containsMatch( | |
| "ConflictException: for foo/aspect.out, previous action: action 'Action for aspect .'," | |
| + " attempted action: action 'Action for aspect .'"); | |
| } | |
| @Test | |
| public void conflictBetweenTargetAndAspect() throws Exception { | |
| scratch.file( | |
| "foo/aspect.bzl", | |
| "def _aspect_impl(target, ctx):", | |
| " outfile = ctx.actions.declare_file('conflict.out')", | |
| " ctx.actions.run_shell(", | |
| " outputs = [outfile],", | |
| " progress_message = 'Action for aspect',", | |
| " command = 'echo \"aspect\" > ' + outfile.path,", | |
| " )", | |
| " return [OutputGroupInfo(files = [outfile])]", | |
| "", | |
| "def _rule_impl(ctx):", | |
| " outfile = ctx.actions.declare_file('conflict.out')", | |
| " ctx.actions.run_shell(", | |
| " outputs = [outfile],", | |
| " progress_message = 'Action for target',", | |
| " command = 'echo \"target\" > ' + outfile.path,", | |
| " )", | |
| " return [DefaultInfo(files = depset([outfile]))]", | |
| "my_aspect = aspect(implementation = _aspect_impl)", | |
| "my_rule = rule(implementation = _rule_impl, attrs = {'deps' : attr.label_list(aspects =" | |
| + " [my_aspect])})"); | |
| scratch.file( | |
| "foo/BUILD", | |
| "load('//foo:aspect.bzl', 'my_aspect', 'my_rule')", | |
| "my_rule(name = 'foo', deps = [':dep'])", | |
| "sh_library(name = 'dep', srcs = ['dep.sh'])"); | |
| // Expect errors. | |
| reporter.removeHandler(failFastHandler); | |
| ViewCreationFailedException exception = | |
| assertThrows(ViewCreationFailedException.class, () -> update("//foo:foo")); | |
| assertThat(exception).hasMessageThat().containsMatch("ConflictException: for foo/conflict.out"); | |
| MoreAsserts.assertContainsEvent( | |
| eventCollector, | |
| Pattern.compile( | |
| "Aspects: (\\[], \\[//foo:aspect.bzl%my_aspect]|\\[//foo:aspect.bzl%my_aspect], \\[])"), | |
| EventKind.ERROR); | |
| } | |
| @Test | |
| public void aspectDuplicatesRuleProviderError() throws Exception { | |
| setRulesAndAspectsAvailableInTests(ImmutableList.of(), ImmutableList.of()); | |
| scratch.file( | |
| "aspect/build_defs.bzl", | |
| "def _aspect_impl(target, ctx):", | |
| " return [DefaultInfo()]", | |
| "", | |
| "returns_default_info_aspect = aspect(implementation = _aspect_impl)", | |
| "", | |
| "def _rule_impl(ctx):", | |
| " pass", | |
| "", | |
| "duplicate_provider_aspect_applying_rule = rule(", | |
| " implementation = _rule_impl,", | |
| " attrs = {'to': attr.label(aspects = [returns_default_info_aspect])},", | |
| ")"); | |
| scratch.file( | |
| "aspect/BUILD", | |
| "load('build_defs.bzl', 'duplicate_provider_aspect_applying_rule')", | |
| "cc_library(name = 'rule_target')", | |
| "duplicate_provider_aspect_applying_rule(name='applies_aspect', to=':rule_target')"); | |
| assertThat( | |
| assertThrows( | |
| AssertionError.class, () -> getConfiguredTarget("//aspect:applies_aspect"))) | |
| .hasMessageThat() | |
| .contains("Provider DefaultInfo provided twice"); | |
| } | |
| @Test | |
| public void instrumentedFilesInfoFromBaseRuleAndAspectUsesAspect() throws Exception { | |
| scratch.file( | |
| "aspect/build_defs.bzl", | |
| "def _instrumented_files_info_aspect_impl(target, ctx):", | |
| " return [coverage_common.instrumented_files_info(ctx, source_attributes=['a'])]", | |
| "", | |
| "instrumented_files_info_aspect = aspect(", | |
| " implementation = _instrumented_files_info_aspect_impl,", | |
| ")", | |
| "", | |
| "def _no_instrumented_files_info_aspect_impl(target, ctx):", | |
| " return []", | |
| "", | |
| "no_instrumented_files_info_aspect = aspect(", | |
| " implementation = _no_instrumented_files_info_aspect_impl,", | |
| ")", | |
| "", | |
| "def _applies_aspect_impl(ctx):", | |
| " return coverage_common.instrumented_files_info(ctx, dependency_attributes=['to'])", | |
| "", | |
| "instrumented_files_info_aspect_rule = rule(", | |
| " implementation = _applies_aspect_impl,", | |
| " attrs = {'to': attr.label(aspects = [instrumented_files_info_aspect])},", | |
| ")", | |
| "", | |
| "no_instrumented_files_info_aspect_rule = rule(", | |
| " implementation = _applies_aspect_impl,", | |
| " attrs = {'to': attr.label(aspects = [no_instrumented_files_info_aspect])},", | |
| ")", | |
| "", | |
| "def _base_rule_impl(ctx):", | |
| " return [coverage_common.instrumented_files_info(ctx, source_attributes=['b'])]", | |
| "", | |
| "base_rule = rule(", | |
| " implementation = _base_rule_impl,", | |
| " attrs = {'a': attr.label(allow_files=True), 'b': attr.label(allow_files=True)},", | |
| ")", | |
| "", | |
| "def _base_rule_no_coverage_impl(ctx):", | |
| " return []", | |
| "", | |
| "base_rule_no_coverage = rule(", | |
| " implementation = _base_rule_no_coverage_impl,", | |
| " attrs = {'a': attr.label(allow_files=True), 'b': attr.label(allow_files=True)},", | |
| ")"); | |
| scratch.file( | |
| "aspect/BUILD", | |
| "load(", | |
| " 'build_defs.bzl',", | |
| " 'base_rule',", | |
| " 'base_rule_no_coverage',", | |
| " 'instrumented_files_info_aspect_rule',", | |
| " 'no_instrumented_files_info_aspect_rule',", | |
| ")", | |
| "", | |
| "base_rule(", | |
| " name = 'rule_target',", | |
| // Ends up in coverage sources when instrumented_files_info_aspect is applied | |
| " a = 'a',", | |
| // Ends up in coverage sources for the base rule's InstrumentedFilesInfo is used | |
| " b = 'b',", | |
| ")", | |
| "", | |
| "instrumented_files_info_aspect_rule(", | |
| " name='duplicate_instrumented_file_info',", | |
| " to=':rule_target',", | |
| ")", | |
| "", | |
| "no_instrumented_files_info_aspect_rule(", | |
| " name='instrumented_file_info_from_base_target',", | |
| " to=':rule_target',", | |
| ")", | |
| "", | |
| "base_rule_no_coverage(", | |
| " name = 'rule_target_no_coverage',", | |
| // Ends up in coverage sources when instrumented_files_info_aspect is applied | |
| " a = 'a',", | |
| // Ends up in coverage sources never | |
| " b = 'b',", | |
| ")", | |
| "", | |
| "instrumented_files_info_aspect_rule(", | |
| " name='instrumented_files_info_only_from_aspect',", | |
| " to=':rule_target_no_coverage',", | |
| ")", | |
| "", | |
| "no_instrumented_files_info_aspect_rule(", | |
| " name='no_instrumented_files_info',", | |
| " to=':rule_target_no_coverage',", | |
| ")"); | |
| useConfiguration("--collect_code_coverage", "--instrumentation_filter=.*"); | |
| update(); | |
| assertThat(getInstrumentedFiles("//aspect:rule_target")).containsExactly("b"); | |
| assertThat(getInstrumentedFiles("//aspect:duplicate_instrumented_file_info")) | |
| .containsExactly("a"); | |
| assertThat(getInstrumentedFiles("//aspect:instrumented_file_info_from_base_target")) | |
| .containsExactly("b"); | |
| assertThat(getInstrumentedFiles("//aspect:rule_target_no_coverage")).isEmpty(); | |
| assertThat(getInstrumentedFiles("//aspect:instrumented_files_info_only_from_aspect")) | |
| .containsExactly("a"); | |
| assertThat(getInstrumentedFiles("//aspect:no_instrumented_files_info")).isEmpty(); | |
| } | |
| private List<String> getInstrumentedFiles(String label) throws InterruptedException { | |
| return ActionsTestUtil.baseArtifactNames( | |
| getConfiguredTarget(label) | |
| .get(InstrumentedFilesInfo.STARLARK_CONSTRUCTOR) | |
| .getInstrumentedFiles()); | |
| } | |
| @Test | |
| public void aspectSeesAspectHintsAttributeOnNativeRule() throws Exception { | |
| useConfiguration("--experimental_enable_aspect_hints"); | |
| setupAspectHints(); | |
| scratch.file( | |
| "aspect_hints/BUILD", | |
| "load('//aspect_hints:hints_counter.bzl', 'count_hints')", | |
| "load('//aspect_hints:hints.bzl', 'hint')", | |
| "", | |
| "hint(name = 'my_hint', hints_cnt = 3)", | |
| "cc_library(name = 'lib1', deps = [':lib2'])", | |
| "cc_library(name = 'lib2', aspect_hints = [':my_hint'])", | |
| "count_hints(name = 'cnt', deps = [':lib1'])"); | |
| update(); | |
| ConfiguredTarget a = getConfiguredTarget("//aspect_hints:cnt"); | |
| StarlarkInt info = (StarlarkInt) getHintsCntInfo(a).getValue("cnt"); | |
| assertThat(info.truncateToInt()).isEqualTo(3); | |
| } | |
| @Test | |
| public void aspectSeesAspectHintsAttributeOnStarlarkRule() throws Exception { | |
| useConfiguration("--experimental_enable_aspect_hints"); | |
| setupAspectHints(); | |
| setupStarlarkRule(); | |
| scratch.file( | |
| "aspect_hints/BUILD", | |
| "load('//aspect_hints:hints_counter.bzl', 'count_hints')", | |
| "load('//aspect_hints:custom_rule.bzl', 'custom_rule')", | |
| "load('//aspect_hints:hints.bzl', 'hint')", | |
| "", | |
| "hint(name = 'my_hint', hints_cnt = 2)", | |
| "custom_rule(name = 'lib1', deps = [':lib2'])", | |
| "custom_rule(name = 'lib2', aspect_hints = [':my_hint'])", | |
| "count_hints(name = 'cnt', deps = [':lib1'])"); | |
| update(); | |
| ConfiguredTarget a = getConfiguredTarget("//aspect_hints:cnt"); | |
| StarlarkInt info = (StarlarkInt) getHintsCntInfo(a).getValue("cnt"); | |
| assertThat(info.truncateToInt()).isEqualTo(2); | |
| } | |
| private void setupAspectHints() throws Exception { | |
| scratch.file( | |
| "aspect_hints/hints.bzl", | |
| "HintInfo = provider(fields = ['hints_cnt'])", | |
| "def _hint_impl(ctx):", | |
| " return [HintInfo(hints_cnt = ctx.attr.hints_cnt)]", | |
| "", | |
| "hint = rule(", | |
| " implementation = _hint_impl,", | |
| " attrs = {'hints_cnt': attr.int(default=0)},", | |
| ")"); | |
| scratch.file( | |
| "aspect_hints/hints_counter.bzl", | |
| "load('//aspect_hints:hints.bzl', 'HintInfo')", | |
| "HintsCntInfo = provider(fields = ['cnt'])", | |
| "", | |
| "def _my_aspect_impl(target, ctx):", | |
| " transitive_hints = 0", | |
| " for dep in ctx.rule.attr.deps:", | |
| " transitive_hints = transitive_hints + dep[HintsCntInfo].cnt", | |
| "", | |
| " hints = 0", | |
| " for hint in ctx.rule.attr.aspect_hints:", | |
| " hints = hints + hint[HintInfo].hints_cnt", | |
| "", | |
| " return [HintsCntInfo(cnt = hints + transitive_hints)]", | |
| "", | |
| "my_aspect = aspect(", | |
| " implementation = _my_aspect_impl,", | |
| " attr_aspects = ['deps'],", | |
| ")", | |
| "", | |
| "def _count_hints_impl(ctx):", | |
| " hints = 0", | |
| " for dep in ctx.attr.deps:", | |
| " hints = hints + dep[HintsCntInfo].cnt", | |
| " return [HintsCntInfo(cnt = hints)]", | |
| "", | |
| "count_hints = rule(", | |
| " implementation = _count_hints_impl,", | |
| " attrs = {", | |
| " 'deps': attr.label_list(aspects = [my_aspect])", | |
| " }", | |
| ")"); | |
| } | |
| private void setupStarlarkRule() throws Exception { | |
| scratch.file( | |
| "aspect_hints/custom_rule.bzl", | |
| "def _custom_rule_impl(ctx):", | |
| " return []", | |
| "", | |
| "custom_rule = rule(", | |
| " implementation = _custom_rule_impl,", | |
| " attrs = {", | |
| " 'deps': attr.label_list(),", | |
| " },", | |
| ")"); | |
| } | |
| private static StructImpl getHintsCntInfo(ConfiguredTarget configuredTarget) | |
| throws LabelSyntaxException { | |
| Provider.Key key = | |
| new StarlarkProvider.Key( | |
| Label.parseCanonical("//aspect_hints:hints_counter.bzl"), "HintsCntInfo"); | |
| return (StructImpl) configuredTarget.get(key); | |
| } | |
| private static ConfiguredAspect getAspectByName( | |
| ImmutableMap<AspectKey, ConfiguredAspect> aspectMap, String name) { | |
| return aspectMap.entrySet().stream() | |
| .filter(e -> e.getKey().getAspectName().equals(name)) | |
| .map(Map.Entry::getValue) | |
| .collect(onlyElement()); | |
| } | |
| } |