| // 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.Iterables.getOnlyElement; |
| 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.common.eventbus.Subscribe; |
| import com.google.devtools.build.lib.actions.ActionConflictException; |
| import com.google.devtools.build.lib.actions.Artifact; |
| 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.buildeventstream.BuildEventIdUtil; |
| import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildEventId; |
| 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.StarlarkInfo; |
| import com.google.devtools.build.lib.packages.StarlarkProvider; |
| import com.google.devtools.build.lib.packages.StarlarkProviderIdentifier; |
| 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.createFactory()) |
| .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", |
| extra_actions = [":xa"], |
| mnemonics = ["Null"], |
| ) |
| """); |
| 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", |
| extra_actions = [":xa"], |
| mnemonics = ["Mnemonic"], |
| ) |
| """); |
| |
| // 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", |
| param = "a", |
| deps = [":n"], |
| ) |
| |
| injector1( |
| name = "i1_b", |
| param = "b", |
| deps = [":n"], |
| ) |
| |
| 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", |
| extra_actions = [":xa"], |
| mnemonics = ["Mnemonic"], |
| ) |
| """); |
| |
| 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'])" |
| ); |
| |
| var collector = new AspectConfiguredCollector(); |
| eventBus.register(collector); |
| |
| AnalysisResult analysisResult = |
| update( |
| 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); |
| Label label = Label.parseCanonicalUnchecked("//a:x_deploy.jar"); |
| assertThat(provider.getLabel()).isEqualTo(label); |
| |
| // Verifies that the AspectConfiguredEvent declares the corresponding AspectCompleteEvent. |
| AspectConfiguredEvent configuredEvent = getOnlyElement(collector.events); |
| BuildEventId targetCompletedId = getOnlyElement(configuredEvent.getChildrenEvents()); |
| AspectKey key = getOnlyElement(analysisResult.getAspectsMap().keySet()); |
| assertThat(targetCompletedId) |
| .isEqualTo( |
| BuildEventIdUtil.aspectCompleted( |
| label, |
| BuildEventIdUtil.configurationId(key.getConfigurationKey()), |
| "AspectApplyingToFiles")); |
| } |
| |
| @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.get(ExtraAttributeAspect.PROVIDER.getKey())).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()); |
| StarlarkInfo provider = |
| (StarlarkInfo) configuredAspect.get(ExtraAttributeAspect.PROVIDER.getKey()); |
| assertThat(provider.getValue("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.get(ExtraAttributeAspect.PROVIDER.getKey())).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); |
| StarlarkInfo provider = |
| (StarlarkInfo) |
| getAspectByName(analysisResult.getAspectsMap(), aspectApplies.getName()) |
| .get(ExtraAttributeAspect.PROVIDER.getKey()); |
| assertThat(provider.getValue("label")).isEqualTo("//extra:extra"); |
| assertThat( |
| getAspectByName(analysisResult.getAspectsMap(), aspectDoesNotApply.getName()) |
| .getProviders() |
| .getProviderCount()) |
| .isEqualTo(0); |
| } |
| |
| @Test |
| public void aspectWithExtraAttributeDependsOnNotApplicable_usesItsOwnAttribute() |
| throws Exception { |
| ExtraAttributeAspect aspectApplies = |
| new ExtraAttributeAspect( |
| "//extra", |
| /* applyToFiles= */ true, |
| StarlarkProviderIdentifier.forKey(ExtraAttributeAspect.PROVIDER.getKey())); |
| 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); |
| StarlarkInfo provider = |
| (StarlarkInfo) |
| getAspectByName(analysisResult.getAspectsMap(), aspectApplies.getName()) |
| .get(ExtraAttributeAspect.PROVIDER.getKey()); |
| assertThat(provider.getValue("label")).isEqualTo("//extra:extra"); |
| 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("file 'foo/aspect.out' is generated by these conflicting actions:"); |
| 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); |
| assertThrows( |
| ViewCreationFailedException.class, |
| () -> |
| update( |
| new EventBus(), |
| defaultFlags(), |
| ImmutableList.of("//foo:aspect.bzl%aspect1", "//foo:aspect.bzl%aspect2"), |
| "//foo:foo")); |
| MoreAsserts.assertContainsEvent( |
| eventCollector, |
| Pattern.compile( |
| "Aspects: \\[//foo:aspect.bzl%aspect[12]], \\[//foo:aspect.bzl%aspect[12]]"), |
| EventKind.ERROR); |
| } |
| |
| @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("file 'foo/conflict.out' is generated by these conflicting actions"); |
| 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 { |
| setupAspectHints(); |
| scratch.file( |
| "aspect_hints/BUILD", |
| """ |
| load("//aspect_hints:hints.bzl", "hint") |
| load("//aspect_hints:hints_counter.bzl", "count_hints") |
| |
| 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 { |
| setupAspectHints(); |
| setupStarlarkRule(); |
| scratch.file( |
| "aspect_hints/BUILD", |
| """ |
| load("//aspect_hints:custom_rule.bzl", "custom_rule") |
| load("//aspect_hints:hints.bzl", "hint") |
| load("//aspect_hints:hints_counter.bzl", "count_hints") |
| |
| 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); |
| } |
| |
| @Test |
| public void ruleDepsVisibilityNotAffectNativeAspect() throws Exception { |
| setRulesAndAspectsAvailableInTests( |
| ImmutableList.of(TestAspects.ALL_ATTRIBUTES_ASPECT), ImmutableList.of()); |
| scratch.file("defs/BUILD"); |
| scratch.file( |
| "defs/build_defs.bzl", |
| """ |
| def _rule_impl(ctx): |
| pass |
| |
| implicit_dep_rule = rule( |
| implementation = _rule_impl, |
| attrs = { |
| "_tool": attr.label(default = "//tool:tool"), |
| "deps": attr.label_list(), |
| }, |
| ) |
| """); |
| scratch.file("tool/BUILD", "sh_library(name='tool', visibility = ['//defs:__pkg__'])"); |
| scratch.file( |
| "pkg/BUILD", |
| """ |
| load("//defs:build_defs.bzl", "implicit_dep_rule") |
| |
| implicit_dep_rule(name = "y") |
| |
| implicit_dep_rule( |
| name = "x", |
| deps = [":y"], |
| ) |
| """); |
| |
| AnalysisResult result = |
| update( |
| new EventBus(), |
| defaultFlags(), |
| ImmutableList.of(TestAspects.ALL_ATTRIBUTES_ASPECT.getName()), |
| "//pkg:x"); |
| |
| assertThat(result.hasError()).isFalse(); |
| } |
| |
| @Test |
| public void nativeAspectFailIfDepsNotVisible() throws Exception { |
| scratch.file("tool/BUILD", "sh_library(name='tool', visibility = ['//visibility:private'])"); |
| ExtraAttributeAspect extraAttributeAspect = new ExtraAttributeAspect("//tool:tool", false); |
| setRulesAndAspectsAvailableInTests(ImmutableList.of(extraAttributeAspect), ImmutableList.of()); |
| scratch.file( |
| "pkg/build_defs.bzl", |
| """ |
| def _rule_impl(ctx): |
| pass |
| |
| simple_rule = rule( |
| implementation = _rule_impl, |
| ) |
| """); |
| scratch.file( |
| "pkg/BUILD", |
| """ |
| load("//pkg:build_defs.bzl", "simple_rule") |
| |
| simple_rule(name = "x") |
| """); |
| reporter.removeHandler(failFastHandler); |
| |
| assertThrows( |
| ViewCreationFailedException.class, |
| () -> |
| update( |
| new EventBus(), |
| defaultFlags(), |
| ImmutableList.of(extraAttributeAspect.getName()), |
| "//pkg:x")); |
| assertContainsEvent( |
| "ExtraAttributeAspect_//tool:tool_false aspect on simple_rule rule //pkg:x: " |
| + "Visibility error:\n" |
| + "target '//tool:tool' is not visible from\n" |
| + "target '//pkg:x'"); |
| } |
| |
| 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()); |
| } |
| |
| private static class AspectConfiguredCollector { |
| private final ArrayList<AspectConfiguredEvent> events = new ArrayList<>(); |
| |
| @Subscribe |
| public void configuredEvent(AspectConfiguredEvent event) { |
| events.add(event); |
| } |
| } |
| } |