aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/test/java/com/google/devtools/build/lib/skylark/SkylarkDefinedAspectsTest.java
diff options
context:
space:
mode:
authorGravatar cparsons <cparsons@google.com>2017-12-20 14:49:13 -0800
committerGravatar Copybara-Service <copybara-piper@google.com>2017-12-20 14:50:47 -0800
commit0d55f4c3c754392a07e72f7146c484a8fb492d16 (patch)
treed4aaa848454014db64b1af7f7532f841dca693d9 /src/test/java/com/google/devtools/build/lib/skylark/SkylarkDefinedAspectsTest.java
parent104cd41297856e168e252f3a6ee78fb29d4bae86 (diff)
Expose ObjcProtoAspect to Skylark.
RELNOTES: None. PiperOrigin-RevId: 179737025
Diffstat (limited to 'src/test/java/com/google/devtools/build/lib/skylark/SkylarkDefinedAspectsTest.java')
-rw-r--r--src/test/java/com/google/devtools/build/lib/skylark/SkylarkDefinedAspectsTest.java2573
1 files changed, 2573 insertions, 0 deletions
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkDefinedAspectsTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkDefinedAspectsTest.java
new file mode 100644
index 0000000000..a5353e1df2
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkDefinedAspectsTest.java
@@ -0,0 +1,2573 @@
+// 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.skylark;
+
+import static com.google.common.collect.Iterables.transform;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.devtools.build.lib.analysis.OutputGroupInfo.INTERNAL_SUFFIX;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.BuildView.AnalysisResult;
+import com.google.devtools.build.lib.analysis.ConfiguredAspect;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.OutputGroupInfo;
+import com.google.devtools.build.lib.analysis.ViewCreationFailedException;
+import com.google.devtools.build.lib.analysis.util.AnalysisTestCase;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.cmdline.TargetParsingException;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.packages.AspectDefinition;
+import com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition;
+import com.google.devtools.build.lib.packages.Info;
+import com.google.devtools.build.lib.packages.SkylarkProvider.SkylarkKey;
+import com.google.devtools.build.lib.packages.util.MockObjcSupport;
+import com.google.devtools.build.lib.packages.util.MockProtoSupport;
+import com.google.devtools.build.lib.rules.cpp.CppConfiguration;
+import com.google.devtools.build.lib.rules.java.Jvm;
+import com.google.devtools.build.lib.rules.objc.ObjcProtoProvider;
+import com.google.devtools.build.lib.skyframe.AspectValue;
+import com.google.devtools.build.lib.syntax.SkylarkList;
+import com.google.devtools.build.lib.syntax.SkylarkNestedSet;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import java.util.Arrays;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for Skylark aspects */
+@RunWith(JUnit4.class)
+public class SkylarkDefinedAspectsTest extends AnalysisTestCase {
+ protected boolean keepGoing() {
+ return false;
+ }
+
+ private static final String LINE_SEPARATOR = System.lineSeparator();
+
+ @Before
+ public final void initializeToolsConfigMock() throws Exception {
+ // Required for tests including the objc_library rule.
+ MockObjcSupport.setup(mockToolsConfig);
+ // Required for tests including the proto_library rule.
+ MockProtoSupport.setup(mockToolsConfig);
+ }
+
+ @Test
+ public void simpleAspect() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " print('This aspect does nothing')",
+ " return struct()",
+ "MyAspect = aspect(implementation=_impl)");
+ scratch.file("test/BUILD", "java_library(name = 'xxx',)");
+
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
+ assertThat(getAspectDescriptions(analysisResult))
+ .containsExactly("//test:aspect.bzl%MyAspect(//test:xxx)");
+ }
+
+ @Test
+ public void aspectWithDeclaredProviders() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "foo = provider()",
+ "bar = provider()",
+ "def _impl(target, ctx):",
+ " return [foo(), bar()]",
+ "MyAspect = aspect(implementation=_impl)");
+ scratch.file("test/BUILD", "java_library(name = 'xxx',)");
+
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
+ assertThat(getAspectDescriptions(analysisResult))
+ .containsExactly("//test:aspect.bzl%MyAspect(//test:xxx)");
+ ConfiguredAspect configuredAspect = Iterables.getOnlyElement(analysisResult.getAspects())
+ .getConfiguredAspect();
+
+ SkylarkKey fooKey = new SkylarkKey(Label.parseAbsolute("//test:aspect.bzl"), "foo");
+ SkylarkKey barKey = new SkylarkKey(Label.parseAbsolute("//test:aspect.bzl"), "bar");
+
+ assertThat(configuredAspect.get(fooKey).getProvider().getKey()).isEqualTo(fooKey);
+ assertThat(configuredAspect.get(barKey).getProvider().getKey()).isEqualTo(barKey);
+ }
+
+ @Test
+ public void aspectWithDeclaredProvidersInAStruct() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "foo = provider()",
+ "bar = provider()",
+ "def _impl(target, ctx):",
+ " return struct(foobar='foobar', providers=[foo(), bar()])",
+ "MyAspect = aspect(implementation=_impl)");
+ scratch.file("test/BUILD", "java_library(name = 'xxx',)");
+
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
+ assertThat(getAspectDescriptions(analysisResult))
+ .containsExactly("//test:aspect.bzl%MyAspect(//test:xxx)");
+
+ ConfiguredAspect configuredAspect = Iterables.getOnlyElement(analysisResult.getAspects())
+ .getConfiguredAspect();
+
+ SkylarkKey fooKey = new SkylarkKey(Label.parseAbsolute("//test:aspect.bzl"), "foo");
+ SkylarkKey barKey = new SkylarkKey(Label.parseAbsolute("//test:aspect.bzl"), "bar");
+
+ assertThat(configuredAspect.get(fooKey).getProvider().getKey()).isEqualTo(fooKey);
+ assertThat(configuredAspect.get(barKey).getProvider().getKey()).isEqualTo(barKey);
+ }
+
+ private Iterable<String> getAspectDescriptions(AnalysisResult analysisResult) {
+ return transform(
+ analysisResult.getAspects(),
+ aspectValue ->
+ String.format(
+ "%s(%s)",
+ aspectValue.getConfiguredAspect().getName(), aspectValue.getLabel().toString()));
+ }
+
+ @Test
+ public void aspectCommandLineLabel() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " print('This aspect does nothing')",
+ " return struct()",
+ "MyAspect = aspect(implementation=_impl)");
+ scratch.file("test/BUILD", "java_library(name = 'xxx',)");
+
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("//test:aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
+ assertThat(getAspectDescriptions(analysisResult))
+ .containsExactly("//test:aspect.bzl%MyAspect(//test:xxx)");
+ }
+
+ @Test
+ public void aspectCommandLineRepoLabel() throws Exception {
+ scratch.overwriteFile(
+ "WORKSPACE",
+ scratch.readFile("WORKSPACE"),
+ "local_repository(name='local', path='local/repo')"
+ );
+ scratch.file("local/repo/WORKSPACE");
+ scratch.file(
+ "local/repo/aspect.bzl",
+ "def _impl(target, ctx):",
+ " print('This aspect does nothing')",
+ " return struct()",
+ "MyAspect = aspect(implementation=_impl)");
+ scratch.file("local/repo/BUILD");
+
+ scratch.file("test/BUILD", "java_library(name = 'xxx',)");
+
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("@local//:aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
+ assertThat(getAspectDescriptions(analysisResult))
+ .containsExactly("@local//:aspect.bzl%MyAspect(//test:xxx)");
+ }
+
+ private Iterable<String> getLabelsToBuild(AnalysisResult analysisResult) {
+ return transform(
+ analysisResult.getTargetsToBuild(),
+ configuredTarget -> configuredTarget.getLabel().toString());
+ }
+
+
+ @Test
+ public void aspectAllowsFragmentsToBeSpecified() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " print('This aspect does nothing')",
+ " return struct()",
+ "MyAspect = aspect(implementation=_impl, fragments=['jvm'], host_fragments=['cpp'])");
+ scratch.file("test/BUILD", "java_library(name = 'xxx',)");
+
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+ AspectValue aspectValue = Iterables.getOnlyElement(analysisResult.getAspects());
+ AspectDefinition aspectDefinition = aspectValue.getAspect().getDefinition();
+ assertThat(
+ aspectDefinition.getConfigurationFragmentPolicy()
+ .isLegalConfigurationFragment(Jvm.class, ConfigurationTransition.NONE))
+ .isTrue();
+ assertThat(
+ aspectDefinition.getConfigurationFragmentPolicy()
+ .isLegalConfigurationFragment(Jvm.class, ConfigurationTransition.HOST))
+ .isFalse();
+ assertThat(
+ aspectDefinition.getConfigurationFragmentPolicy()
+ .isLegalConfigurationFragment(CppConfiguration.class, ConfigurationTransition.NONE))
+ .isFalse();
+ assertThat(
+ aspectDefinition.getConfigurationFragmentPolicy()
+ .isLegalConfigurationFragment(CppConfiguration.class, ConfigurationTransition.HOST))
+ .isTrue();
+ }
+
+ @Test
+ public void aspectPropagating() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " s = depset([target.label])",
+ " c = depset([ctx.rule.kind])",
+ " for i in ctx.rule.attr.deps:",
+ " s += i.target_labels",
+ " c += i.rule_kinds",
+ " return struct(target_labels = s, rule_kinds = c)",
+ "",
+ "MyAspect = aspect(",
+ " implementation=_impl,",
+ " attr_aspects=['deps'],",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "java_library(",
+ " name = 'yyy',",
+ ")",
+ "java_library(",
+ " name = 'xxx',",
+ " srcs = ['A.java'],",
+ " deps = [':yyy'],",
+ ")");
+
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
+ AspectValue aspectValue = analysisResult.getAspects().iterator().next();
+ ConfiguredAspect configuredAspect = aspectValue.getConfiguredAspect();
+ assertThat(configuredAspect).isNotNull();
+ Object names = configuredAspect.get("target_labels");
+ assertThat(names).isInstanceOf(SkylarkNestedSet.class);
+ assertThat(
+ transform(
+ ((SkylarkNestedSet) names).toCollection(),
+ o -> {
+ assertThat(o).isInstanceOf(Label.class);
+ return o.toString();
+ }))
+ .containsExactly("//test:xxx", "//test:yyy");
+ Object ruleKinds = configuredAspect.get("rule_kinds");
+ assertThat(ruleKinds).isInstanceOf(SkylarkNestedSet.class);
+ assertThat(((SkylarkNestedSet) ruleKinds).toCollection()).containsExactly("java_library");
+ }
+
+ @Test
+ public void aspectsPropagatingForDefaultAndImplicit() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " s = depset([target.label])",
+ " c = depset([ctx.rule.kind])",
+ " a = ctx.rule.attr",
+ " if hasattr(a, '_stl') and a._stl:",
+ " s += a._stl.target_labels",
+ " c += a._stl.rule_kinds",
+ " if hasattr(a, '_stl_default') and a._stl_default:",
+ " s += a._stl_default.target_labels",
+ " c += a._stl_default.rule_kinds",
+ " return struct(target_labels = s, rule_kinds = c)",
+ "",
+ "def _rule_impl(ctx):",
+ " pass",
+ "",
+ "my_rule = rule(implementation = _rule_impl,",
+ " attrs = { '_stl' : attr.label(default = Label('//test:xxx')) },",
+ ")",
+ "MyAspect = aspect(",
+ " implementation=_impl,",
+ " attr_aspects=['_stl', '_stl_default'],",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:aspect.bzl', 'my_rule')",
+ "cc_library(",
+ " name = 'xxx',",
+ ")",
+ "my_rule(",
+ " name = 'yyy',",
+ ")"
+ );
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:yyy");
+ AspectValue aspectValue = analysisResult.getAspects().iterator().next();
+ ConfiguredAspect configuredAspect = aspectValue.getConfiguredAspect();
+ assertThat(configuredAspect).isNotNull();
+ Object names = configuredAspect.get("target_labels");
+ assertThat(names).isInstanceOf(SkylarkNestedSet.class);
+ assertThat(
+ transform(
+ ((SkylarkNestedSet) names).toCollection(),
+ o -> {
+ assertThat(o).isInstanceOf(Label.class);
+ return ((Label) o).getName();
+ }))
+ .containsExactly("stl", "xxx", "yyy");
+ }
+
+ @Test
+ public void aspectsDirOnMergedTargets() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " return struct(aspect_provider = 'data')",
+ "",
+ "p = provider()",
+ "MyAspect = aspect(implementation=_impl)",
+ "def _rule_impl(ctx):",
+ " if ctx.attr.dep:",
+ " return [p(dir = dir(ctx.attr.dep))]",
+ " return [p()]",
+ "",
+ "my_rule = rule(implementation = _rule_impl,",
+ " attrs = { 'dep' : attr.label(aspects = [MyAspect]) },",
+ ")");
+ SkylarkKey providerKey = new SkylarkKey(Label.parseAbsoluteUnchecked("//test:aspect.bzl"), "p");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:aspect.bzl', 'my_rule')",
+ "my_rule(name = 'xxx',)",
+ "my_rule(name = 'yyy', dep = ':xxx')");
+ AnalysisResult analysisResult = update("//test:yyy");
+ ConfiguredTarget target = Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
+
+ Info names = target.get(providerKey);
+ assertThat((Iterable<?>) names.getValue("dir"))
+ .containsExactly(
+ "aspect_provider",
+ "data_runfiles",
+ "default_runfiles",
+ "files",
+ "files_to_run",
+ "label",
+ "output_group",
+ "output_groups");
+ }
+
+ @Test
+ public void aspectWithOutputGroups() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " f = target.output_group('_hidden_top_level" + INTERNAL_SUFFIX + "')",
+ " return struct(output_groups = { 'my_result' : f })",
+ "",
+ "MyAspect = aspect(",
+ " implementation=_impl,",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "java_library(",
+ " name = 'xxx',",
+ " srcs = ['A.java'],",
+ ")");
+
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
+ AspectValue aspectValue = analysisResult.getAspects().iterator().next();
+ OutputGroupInfo outputGroupInfo = OutputGroupInfo.get(
+ aspectValue.getConfiguredAspect());
+
+ assertThat(outputGroupInfo).isNotNull();
+ NestedSet<Artifact> names = outputGroupInfo.getOutputGroup("my_result");
+ assertThat(names).isNotEmpty();
+ NestedSet<Artifact> expectedSet = OutputGroupInfo.get(getConfiguredTarget("//test:xxx"))
+ .getOutputGroup(OutputGroupInfo.HIDDEN_TOP_LEVEL);
+ assertThat(names).containsExactlyElementsIn(expectedSet);
+ }
+
+ @Test
+ public void aspectWithOutputGroupsDeclaredProvider() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " f = target[OutputGroupInfo]._hidden_top_level" + INTERNAL_SUFFIX,
+ " return [OutputGroupInfo(my_result = f)]",
+ "",
+ "MyAspect = aspect(",
+ " implementation=_impl,",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "java_library(",
+ " name = 'xxx',",
+ " srcs = ['A.java'],",
+ ")");
+
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
+ AspectValue aspectValue = analysisResult.getAspects().iterator().next();
+ OutputGroupInfo outputGroupInfo = OutputGroupInfo.get(
+ aspectValue.getConfiguredAspect());
+
+ assertThat(outputGroupInfo).isNotNull();
+ NestedSet<Artifact> names = outputGroupInfo.getOutputGroup("my_result");
+ assertThat(names).isNotEmpty();
+ NestedSet<Artifact> expectedSet = OutputGroupInfo.get(getConfiguredTarget("//test:xxx"))
+ .getOutputGroup(OutputGroupInfo.HIDDEN_TOP_LEVEL);
+ assertThat(names).containsExactlyElementsIn(expectedSet);
+ }
+
+ @Test
+ public void aspectWithOutputGroupsAsList() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " g = target.output_group('_hidden_top_level" + INTERNAL_SUFFIX + "')",
+ " return struct(output_groups = { 'my_result' : [ f for f in g] })",
+ "",
+ "MyAspect = aspect(",
+ " implementation=_impl,",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "java_library(",
+ " name = 'xxx',",
+ " srcs = ['A.java'],",
+ ")");
+
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(
+ transform(
+ analysisResult.getTargetsToBuild(),
+ configuredTarget -> configuredTarget.getLabel().toString()))
+ .containsExactly("//test:xxx");
+ AspectValue aspectValue = analysisResult.getAspects().iterator().next();
+ OutputGroupInfo outputGroupInfo =
+ OutputGroupInfo.get(aspectValue.getConfiguredAspect());
+ assertThat(outputGroupInfo).isNotNull();
+ NestedSet<Artifact> names = outputGroupInfo.getOutputGroup("my_result");
+ assertThat(names).isNotEmpty();
+ NestedSet<Artifact> expectedSet = OutputGroupInfo.get(getConfiguredTarget("//test:xxx"))
+ .getOutputGroup(OutputGroupInfo.HIDDEN_TOP_LEVEL);
+ assertThat(names).containsExactlyElementsIn(expectedSet);
+ }
+
+ @Test
+ public void aspectWithOutputGroupsAsListDeclaredProvider() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " g = target[OutputGroupInfo]._hidden_top_level" + INTERNAL_SUFFIX,
+ " return [OutputGroupInfo(my_result= [ f for f in g])]",
+ "",
+ "MyAspect = aspect(",
+ " implementation=_impl,",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "java_library(",
+ " name = 'xxx',",
+ " srcs = ['A.java'],",
+ ")");
+
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(
+ transform(
+ analysisResult.getTargetsToBuild(),
+ configuredTarget -> configuredTarget.getLabel().toString()))
+ .containsExactly("//test:xxx");
+ AspectValue aspectValue = analysisResult.getAspects().iterator().next();
+ OutputGroupInfo outputGroupInfo =
+ OutputGroupInfo.get(aspectValue.getConfiguredAspect());
+ assertThat(outputGroupInfo).isNotNull();
+ NestedSet<Artifact> names = outputGroupInfo.getOutputGroup("my_result");
+ assertThat(names).isNotEmpty();
+ NestedSet<Artifact> expectedSet = OutputGroupInfo.get(getConfiguredTarget("//test:xxx"))
+ .getOutputGroup(OutputGroupInfo.HIDDEN_TOP_LEVEL);
+ assertThat(names).containsExactlyElementsIn(expectedSet);
+ }
+
+
+ @Test
+ public void aspectsFromSkylarkRules() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _aspect_impl(target, ctx):",
+ " s = depset([target.label])",
+ " for i in ctx.rule.attr.deps:",
+ " s += i.target_labels",
+ " return struct(target_labels = s)",
+ "",
+ "def _rule_impl(ctx):",
+ " s = depset([])",
+ " for i in ctx.attr.attr:",
+ " s += i.target_labels",
+ " return struct(rule_deps = s)",
+ "",
+ "MyAspect = aspect(",
+ " implementation=_aspect_impl,",
+ " attr_aspects=['deps'],",
+ ")",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'attr' : ",
+ " attr.label_list(mandatory=True, allow_files=True, aspects = [MyAspect]) },",
+ ")");
+
+ scratch.file(
+ "test/BUILD",
+ "load('//test:aspect.bzl', 'my_rule')",
+ "java_library(",
+ " name = 'yyy',",
+ ")",
+ "my_rule(",
+ " name = 'xxx',",
+ " attr = [':yyy'],",
+ ")");
+
+ AnalysisResult analysisResult = update("//test:xxx");
+ assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
+ ConfiguredTarget target = analysisResult.getTargetsToBuild().iterator().next();
+ Object names = target.get("rule_deps");
+ assertThat(names).isInstanceOf(SkylarkNestedSet.class);
+ assertThat(
+ transform(
+ ((SkylarkNestedSet) names).toCollection(),
+ o -> {
+ assertThat(o).isInstanceOf(Label.class);
+ return o.toString();
+ }))
+ .containsExactly("//test:yyy");
+ }
+
+ @Test
+ public void aspectsNonExported() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _aspect_impl(target, ctx):",
+ " return []",
+ "",
+ "def _rule_impl(ctx):",
+ " pass",
+ "",
+ "def mk_aspect():",
+ " return aspect(implementation=_aspect_impl)",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'attr' : attr.label_list(aspects = [mk_aspect()]) },",
+ ")");
+
+ scratch.file(
+ "test/BUILD",
+ "load('//test:aspect.bzl', 'my_rule')",
+ "java_library(",
+ " name = 'yyy',",
+ ")",
+ "my_rule(",
+ " name = 'xxx',",
+ " attr = [':yyy'],",
+ ")");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult analysisResult = update("//test:xxx");
+ assertThat(keepGoing()).isTrue();
+ assertThat(analysisResult.hasError()).isTrue();
+ } catch (ViewCreationFailedException | TargetParsingException e) {
+ // expected
+ }
+
+ assertContainsEvent("ERROR /workspace/test/aspect.bzl:11:23");
+ assertContainsEvent("Aspects should be top-level values in extension files that define them.");
+ }
+
+ @Test
+ public void providerNonExported() throws Exception {
+ scratch.file(
+ "test/rule.bzl",
+ "def mk_provider():",
+ " return provider()",
+ "def _rule_impl(ctx):",
+ " pass",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'attr' : attr.label_list(providers = [mk_provider()]) },",
+ ")");
+
+ scratch.file(
+ "test/BUILD",
+ "load('//test:rule.bzl', 'my_rule')",
+ "java_library(",
+ " name = 'yyy',",
+ ")",
+ "my_rule(",
+ " name = 'xxx',",
+ " attr = [':yyy'],",
+ ")");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult analysisResult = update("//test:xxx");
+ assertThat(keepGoing()).isTrue();
+ assertThat(analysisResult.hasError()).isTrue();
+ } catch (ViewCreationFailedException | TargetParsingException e) {
+ // expected
+ }
+
+ assertContainsEvent("ERROR /workspace/test/rule.bzl:7:23");
+ assertContainsEvent(
+ "Providers should be top-level values in extension files that define them.");
+ }
+
+
+ @Test
+ public void aspectOnLabelAttr() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _aspect_impl(target, ctx):",
+ " return struct(aspect_data='foo')",
+ "",
+ "def _rule_impl(ctx):",
+ " return struct(data=ctx.attr.attr.aspect_data)",
+ "",
+ "MyAspect = aspect(",
+ " implementation=_aspect_impl,",
+ ")",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'attr' : ",
+ " attr.label(aspects = [MyAspect]) },",
+ ")");
+
+ scratch.file(
+ "test/BUILD",
+ "load('//test:aspect.bzl', 'my_rule')",
+ "java_library(",
+ " name = 'yyy',",
+ ")",
+ "my_rule(",
+ " name = 'xxx',",
+ " attr = ':yyy',",
+ ")");
+
+ AnalysisResult analysisResult = update("//test:xxx");
+ ConfiguredTarget target = analysisResult.getTargetsToBuild().iterator().next();
+ Object value = target.get("data");
+ assertThat(value).isEqualTo("foo");
+ }
+
+ @Test
+ public void labelKeyedStringDictAllowsAspects() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _aspect_impl(target, ctx):",
+ " return struct(aspect_data=target.label.name)",
+ "",
+ "def _rule_impl(ctx):",
+ " return struct(",
+ " data=','.join(['{}:{}'.format(dep.aspect_data, val)",
+ " for dep, val in ctx.attr.attr.items()]))",
+ "",
+ "MyAspect = aspect(",
+ " implementation=_aspect_impl,",
+ ")",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'attr' : ",
+ " attr.label_keyed_string_dict(aspects = [MyAspect]) },",
+ ")");
+
+ scratch.file(
+ "test/BUILD",
+ "load('//test:aspect.bzl', 'my_rule')",
+ "java_library(",
+ " name = 'yyy',",
+ ")",
+ "my_rule(",
+ " name = 'xxx',",
+ " attr = {':yyy': 'zzz'},",
+ ")");
+
+ AnalysisResult analysisResult = update("//test:xxx");
+ ConfiguredTarget target = analysisResult.getTargetsToBuild().iterator().next();
+ Object value = target.get("data");
+ assertThat(value).isEqualTo("yyy:zzz");
+ }
+
+ @Test
+ public void aspectsDoNotAttachToFiles() throws Exception {
+ FileSystemUtils.appendIsoLatin1(scratch.resolve("WORKSPACE"),
+ "bind(name = 'yyy', actual = '//test:zzz.jar')");
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " return struct()",
+ "",
+ "MyAspect = aspect(",
+ " implementation=_impl,",
+ " attr_aspects=['deps'],",
+ ")");
+ scratch.file("test/zzz.jar");
+ scratch.file(
+ "test/BUILD",
+ "exports_files(['zzz.jar'])",
+ "java_library(",
+ " name = 'xxx',",
+ " srcs = ['A.java'],",
+ " deps = ['//external:yyy'],",
+ ")");
+
+ AnalysisResult result = update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(result.hasError()).isFalse();
+ }
+
+ @Test
+ public void aspectsDoNotAttachToTopLevelFiles() throws Exception {
+ FileSystemUtils.appendIsoLatin1(scratch.resolve("WORKSPACE"),
+ "bind(name = 'yyy', actual = '//test:zzz.jar')");
+ scratch.file(
+ "test/aspect.bzl",
+ "p = provider()",
+ "def _impl(target, ctx):",
+ " return [p()]",
+ "",
+ "MyAspect = aspect(",
+ " implementation=_impl,",
+ " attr_aspects=['deps'],",
+ ")");
+ scratch.file("test/zzz.jar");
+ scratch.file(
+ "test/BUILD",
+ "exports_files(['zzz.jar'])",
+ "java_library(",
+ " name = 'xxx',",
+ " srcs = ['A.java'],",
+ " deps = ['//external:yyy'],",
+ ")");
+
+ AnalysisResult result = update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:zzz.jar");
+ assertThat(result.hasError()).isFalse();
+ assertThat(
+ Iterables.getOnlyElement(result.getAspects())
+ .getConfiguredAspect()
+ .getProviders()
+ .getProviderCount())
+ .isEqualTo(0);
+ }
+
+
+ @Test
+ public void aspectFailingExecution() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " return 1/0",
+ "",
+ "MyAspect = aspect(implementation=_impl)");
+ scratch.file("test/BUILD", "java_library(name = 'xxx',)");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult result = update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(keepGoing()).isTrue();
+ assertThat(result.hasError()).isTrue();
+ } catch (ViewCreationFailedException e) {
+ // expect to fail.
+ }
+ assertContainsEvent(
+ "ERROR /workspace/test/BUILD:1:1: in "
+ + "//test:aspect.bzl%MyAspect aspect on java_library rule //test:xxx: \n"
+ + "Traceback (most recent call last):"
+ + LINE_SEPARATOR
+ + "\tFile \"/workspace/test/BUILD\", line 1"
+ + LINE_SEPARATOR
+ + "\t\t//test:aspect.bzl%MyAspect(...)"
+ + LINE_SEPARATOR
+ + "\tFile \"/workspace/test/aspect.bzl\", line 2, in _impl"
+ + LINE_SEPARATOR
+ + "\t\t1 / 0"
+ + LINE_SEPARATOR
+ + "integer division by zero");
+ }
+
+ @Test
+ public void aspectFailingReturnsNotAStruct() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " return 0",
+ "",
+ "MyAspect = aspect(implementation=_impl)");
+ scratch.file("test/BUILD", "java_library(name = 'xxx',)");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult result = update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(keepGoing()).isTrue();
+ assertThat(result.hasError()).isTrue();
+ } catch (ViewCreationFailedException e) {
+ // expect to fail.
+ }
+ assertContainsEvent("Aspect implementation should return a struct or a list, but got int");
+ }
+
+ @Test
+ public void aspectFailingOrphanArtifacts() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " ctx.actions.declare_file('missing_in_action.txt')",
+ " return struct()",
+ "",
+ "MyAspect = aspect(implementation=_impl)");
+ scratch.file("test/BUILD", "java_library(name = 'xxx',)");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult result = update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(keepGoing()).isTrue();
+ assertThat(result.hasError()).isTrue();
+ } catch (ViewCreationFailedException e) {
+ // expect to fail.
+ }
+ assertContainsEvent(
+ "ERROR /workspace/test/BUILD:1:1: in "
+ + "//test:aspect.bzl%MyAspect aspect on java_library rule //test:xxx: \n"
+ + "\n"
+ + "\n"
+ + "The following files have no generating action:\n"
+ + "test/missing_in_action.txt\n");
+ }
+
+ @Test
+ public void topLevelAspectIsNotAnAspect() throws Exception {
+ scratch.file("test/aspect.bzl", "MyAspect = 4");
+ scratch.file("test/BUILD", "java_library(name = 'xxx')");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult result = update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(keepGoing()).isTrue();
+ assertThat(result.hasError()).isTrue();
+ } catch (ViewCreationFailedException e) {
+ // expect to fail.
+ }
+ assertContainsEvent("MyAspect from //test:aspect.bzl is not an aspect");
+ }
+
+ @Test
+ public void duplicateOutputGroups() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " f = ctx.actions.declare_file('f.txt')",
+ " ctx.file_action(f, 'f')",
+ " return struct(output_groups = { 'duplicate' : depset([f]) })",
+ "",
+ "MyAspect = aspect(implementation=_impl)",
+ "def _rule_impl(ctx):",
+ " g = ctx.actions.declare_file('g.txt')",
+ " ctx.actions.write(g, 'g')",
+ " return struct(output_groups = { 'duplicate' : depset([g]) })",
+ "my_rule = rule(_rule_impl)",
+ "def _noop(ctx):",
+ " pass",
+ "rbase = rule(_noop, attrs = { 'dep' : attr.label(aspects = [MyAspect]) })");
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'my_rule', 'rbase')",
+ "my_rule(name = 'xxx')",
+ "rbase(name = 'yyy', dep = ':xxx')"
+ );
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult result = update("//test:yyy");
+ assertThat(keepGoing()).isTrue();
+ assertThat(result.hasError()).isTrue();
+ } catch (ViewCreationFailedException e) {
+ // expect to fail.
+ }
+ assertContainsEvent("ERROR /workspace/test/BUILD:3:1: Output group duplicate provided twice");
+ }
+
+ @Test
+ public void outputGroupsFromOneAspect() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _a1_impl(target, ctx):",
+ " f = ctx.actions.declare_file(target.label.name + '_a1.txt')",
+ " ctx.actions.write(f, 'f')",
+ " return struct(output_groups = { 'a1_group' : depset([f]) })",
+ "",
+ "a1 = aspect(implementation=_a1_impl, attr_aspects = ['dep'])",
+ "def _rule_impl(ctx):",
+ " if not ctx.attr.dep:",
+ " return struct()",
+ " og = {k:ctx.attr.dep.output_groups[k] for k in ctx.attr.dep.output_groups}",
+ " return struct(output_groups = og)",
+ "my_rule1 = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [a1]) })"
+ );
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'my_rule1')",
+ "my_rule1(name = 'base')",
+ "my_rule1(name = 'xxx', dep = ':base')"
+ );
+
+
+ AnalysisResult analysisResult = update("//test:xxx");
+ OutputGroupInfo outputGroupInfo =
+ OutputGroupInfo.get(Iterables.getOnlyElement(analysisResult.getTargetsToBuild()));
+ assertThat(getOutputGroupContents(outputGroupInfo, "a1_group"))
+ .containsExactly("test/base_a1.txt");
+ }
+
+ @Test
+ public void outputGroupsDeclaredProviderFromOneAspect() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _a1_impl(target, ctx):",
+ " f = ctx.actions.declare_file(target.label.name + '_a1.txt')",
+ " ctx.actions.write(f, 'f')",
+ " return [OutputGroupInfo(a1_group = depset([f]))]",
+ "",
+ "a1 = aspect(implementation=_a1_impl, attr_aspects = ['dep'])",
+ "def _rule_impl(ctx):",
+ " if not ctx.attr.dep:",
+ " return struct()",
+ " return [OutputGroupInfo(a1_group = ctx.attr.dep[OutputGroupInfo].a1_group)]",
+ "my_rule1 = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [a1]) })"
+ );
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'my_rule1')",
+ "my_rule1(name = 'base')",
+ "my_rule1(name = 'xxx', dep = ':base')"
+ );
+
+
+ AnalysisResult analysisResult = update("//test:xxx");
+ OutputGroupInfo outputGroupInfo =
+ OutputGroupInfo.get(Iterables.getOnlyElement(analysisResult.getTargetsToBuild()));
+ assertThat(getOutputGroupContents(outputGroupInfo, "a1_group"))
+ .containsExactly("test/base_a1.txt");
+ }
+
+ @Test
+ public void outputGroupsFromTwoAspects() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _a1_impl(target, ctx):",
+ " f = ctx.actions.declare_file(target.label.name + '_a1.txt')",
+ " ctx.actions.write(f, 'f')",
+ " return struct(output_groups = { 'a1_group' : depset([f]) })",
+ "",
+ "a1 = aspect(implementation=_a1_impl, attr_aspects = ['dep'])",
+ "def _rule_impl(ctx):",
+ " if not ctx.attr.dep:",
+ " return struct()",
+ " og = {k:ctx.attr.dep.output_groups[k] for k in ctx.attr.dep.output_groups}",
+ " return struct(output_groups = og)",
+ "my_rule1 = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [a1]) })",
+ "def _a2_impl(target, ctx):",
+ " g = ctx.actions.declare_file(target.label.name + '_a2.txt')",
+ " ctx.actions.write(g, 'f')",
+ " return struct(output_groups = { 'a2_group' : depset([g]) })",
+ "",
+ "a2 = aspect(implementation=_a2_impl, attr_aspects = ['dep'])",
+ "my_rule2 = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [a2]) })");
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'my_rule1', 'my_rule2')",
+ "my_rule1(name = 'base')",
+ "my_rule1(name = 'xxx', dep = ':base')",
+ "my_rule2(name = 'yyy', dep = ':xxx')"
+ );
+
+
+ AnalysisResult analysisResult = update("//test:yyy");
+ OutputGroupInfo outputGroupInfo =
+ OutputGroupInfo.get(Iterables.getOnlyElement(analysisResult.getTargetsToBuild()));
+ assertThat(getOutputGroupContents(outputGroupInfo, "a1_group"))
+ .containsExactly("test/base_a1.txt");
+ assertThat(getOutputGroupContents(outputGroupInfo, "a2_group"))
+ .containsExactly("test/xxx_a2.txt");
+ }
+
+ @Test
+ public void outputGroupsDeclaredProvidersFromTwoAspects() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _a1_impl(target, ctx):",
+ " f = ctx.actions.declare_file(target.label.name + '_a1.txt')",
+ " ctx.actions.write(f, 'f')",
+ " return [OutputGroupInfo(a1_group = depset([f]))]",
+ "",
+ "a1 = aspect(implementation=_a1_impl, attr_aspects = ['dep'])",
+ "def _rule_impl(ctx):",
+ " if not ctx.attr.dep:",
+ " return struct()",
+ " og = dict()",
+ " dep_og = ctx.attr.dep[OutputGroupInfo]",
+ " if hasattr(dep_og, 'a1_group'):",
+ " og['a1_group'] = dep_og.a1_group",
+ " if hasattr(dep_og, 'a2_group'):",
+ " og['a2_group'] = dep_og.a2_group",
+ " return [OutputGroupInfo(**og)]",
+ "my_rule1 = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [a1]) })",
+ "def _a2_impl(target, ctx):",
+ " g = ctx.actions.declare_file(target.label.name + '_a2.txt')",
+ " ctx.actions.write(g, 'f')",
+ " return [OutputGroupInfo(a2_group = depset([g]))]",
+ "",
+ "a2 = aspect(implementation=_a2_impl, attr_aspects = ['dep'])",
+ "my_rule2 = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [a2]) })");
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'my_rule1', 'my_rule2')",
+ "my_rule1(name = 'base')",
+ "my_rule1(name = 'xxx', dep = ':base')",
+ "my_rule2(name = 'yyy', dep = ':xxx')"
+ );
+
+
+ AnalysisResult analysisResult = update("//test:yyy");
+ OutputGroupInfo outputGroupInfo =
+ OutputGroupInfo.get(Iterables.getOnlyElement(analysisResult.getTargetsToBuild()));
+ assertThat(getOutputGroupContents(outputGroupInfo, "a1_group"))
+ .containsExactly("test/base_a1.txt");
+ assertThat(getOutputGroupContents(outputGroupInfo, "a2_group"))
+ .containsExactly("test/xxx_a2.txt");
+ }
+
+
+ @Test
+ public void duplicateOutputGroupsFromTwoAspects() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _a1_impl(target, ctx):",
+ " f = ctx.actions.declare_file(target.label.name + '_a1.txt')",
+ " ctx.actions.write(f, 'f')",
+ " return struct(output_groups = { 'a1_group' : depset([f]) })",
+ "",
+ "a1 = aspect(implementation=_a1_impl, attr_aspects = ['dep'])",
+ "def _rule_impl(ctx):",
+ " if not ctx.attr.dep:",
+ " return struct()",
+ " og = {k:ctx.attr.dep.output_groups[k] for k in ctx.attr.dep.output_groups}",
+ " return struct(output_groups = og)",
+ "my_rule1 = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [a1]) })",
+ "def _a2_impl(target, ctx):",
+ " g = ctx.actions.declare_file(target.label.name + '_a2.txt')",
+ " ctx.actions.write(g, 'f')",
+ " return struct(output_groups = { 'a1_group' : depset([g]) })",
+ "",
+ "a2 = aspect(implementation=_a2_impl, attr_aspects = ['dep'])",
+ "my_rule2 = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [a2]) })");
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'my_rule1', 'my_rule2')",
+ "my_rule1(name = 'base')",
+ "my_rule1(name = 'xxx', dep = ':base')",
+ "my_rule2(name = 'yyy', dep = ':xxx')"
+ );
+
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult analysisResult = update("//test:yyy");
+ assertThat(analysisResult.hasError()).isTrue();
+ assertThat(keepGoing()).isTrue();
+ } catch (ViewCreationFailedException e) {
+ // expected.
+ }
+ assertContainsEvent("ERROR /workspace/test/BUILD:3:1: Output group a1_group provided twice");
+ }
+
+
+ private static Iterable<String> getOutputGroupContents(OutputGroupInfo outputGroupInfo,
+ String groupName) {
+ return Iterables.transform(
+ outputGroupInfo.getOutputGroup(groupName), Artifact::getRootRelativePathString);
+ }
+
+
+ @Test
+ public void duplicateSkylarkProviders() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " return struct(duplicate = 'x')",
+ "",
+ "MyAspect = aspect(implementation=_impl)",
+ "def _rule_impl(ctx):",
+ " return struct(duplicate = 'y')",
+ "my_rule = rule(_rule_impl)",
+ "def _noop(ctx):",
+ " pass",
+ "rbase = rule(_noop, attrs = { 'dep' : attr.label(aspects = [MyAspect]) })"
+ );
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'my_rule', 'rbase')",
+ "my_rule(name = 'xxx')",
+ "rbase(name = 'yyy', dep = ':xxx')"
+ );
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult result = update("//test:yyy");
+ assertThat(keepGoing()).isTrue();
+ assertThat(result.hasError()).isTrue();
+ } catch (ViewCreationFailedException e) {
+ // expect to fail.
+ }
+ assertContainsEvent("ERROR /workspace/test/BUILD:3:1: Provider duplicate provided twice");
+ }
+
+
+ @Test
+ public void topLevelAspectDoesNotExist() throws Exception {
+ scratch.file("test/aspect.bzl", "");
+ scratch.file("test/BUILD", "java_library(name = 'xxx')");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult result = update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(keepGoing()).isTrue();
+ assertThat(result.hasError()).isTrue();
+ } catch (ViewCreationFailedException e) {
+ // expect to fail.
+ }
+ assertContainsEvent("MyAspect is not exported from //test:aspect.bzl");
+ }
+
+ @Test
+ public void topLevelAspectDoesNotExist2() throws Exception {
+ scratch.file("test/BUILD", "java_library(name = 'xxx')");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult result = update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(keepGoing()).isTrue();
+ assertThat(result.hasError()).isTrue();
+ } catch (ViewCreationFailedException e) {
+ // expect to fail.
+ }
+ assertContainsEvent(
+ "Extension file not found. Unable to load file '//test:aspect.bzl': "
+ + "file doesn't exist or isn't a file");
+ }
+
+ @Test
+ public void topLevelAspectDoesNotExistNoBuildFile() throws Exception {
+ scratch.file("test/BUILD", "java_library(name = 'xxx')");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult result = update(ImmutableList.of("foo/aspect.bzl%MyAspect"), "//test:xxx");
+ assertThat(keepGoing()).isTrue();
+ assertThat(result.hasError()).isTrue();
+ } catch (ViewCreationFailedException e) {
+ // expect to fail.
+ }
+ assertContainsEvent(
+ "Every .bzl file must have a corresponding package, but 'foo' does not have one. "
+ + "Please create a BUILD file in the same or any parent directory. "
+ + "Note that this BUILD file does not need to do anything except exist.");
+ }
+
+ @Test
+ public void aspectParametersUncovered() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " return struct()",
+ "def _rule_impl(ctx):",
+ " return struct()",
+ "MyAspectUncovered = aspect(",
+ " implementation=_impl,",
+ " attrs = { 'my_attr' : attr.string(values=['aaa']) },",
+ ")",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'deps' : attr.label_list(aspects=[MyAspectUncovered]) },",
+ ")");
+ scratch.file("test/BUILD",
+ "load('//test:aspect.bzl', 'my_rule')",
+ "my_rule(name = 'xxx')");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult result = update(ImmutableList.<String>of(), "//test:xxx");
+ assertThat(keepGoing()).isTrue();
+ assertThat(result.hasError()).isTrue();
+ } catch (Exception e) {
+ // expect to fail.
+ }
+ assertContainsEvent( // "ERROR /workspace/test/aspect.bzl:9:11: "
+ "Aspect //test:aspect.bzl%MyAspectUncovered requires rule my_rule to specify attribute "
+ + "'my_attr' with type string.");
+ }
+
+ @Test
+ public void aspectParametersTypeMismatch() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " return struct()",
+ "def _rule_impl(ctx):",
+ " return struct()",
+ "MyAspectMismatch = aspect(",
+ " implementation=_impl,",
+ " attrs = { 'my_attr' : attr.string(values=['aaa']) },",
+ ")",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'deps' : attr.label_list(aspects=[MyAspectMismatch]),",
+ " 'my_attr' : attr.int() },",
+ ")");
+ scratch.file("test/BUILD",
+ "load('//test:aspect.bzl', 'my_rule')",
+ "my_rule(name = 'xxx', my_attr = 4)");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult result = update(ImmutableList.<String>of(), "//test:xxx");
+ assertThat(keepGoing()).isTrue();
+ assertThat(result.hasError()).isTrue();
+ } catch (Exception e) {
+ // expect to fail.
+ }
+ assertContainsEvent(
+ "Aspect //test:aspect.bzl%MyAspectMismatch requires rule my_rule to specify attribute "
+ + "'my_attr' with type string.");
+ }
+
+ @Test
+ public void aspectParametersBadDefault() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " return struct()",
+ "def _rule_impl(ctx):",
+ " return struct()",
+ "MyAspectBadDefault = aspect(",
+ " implementation=_impl,",
+ " attrs = { 'my_attr' : attr.string(values=['a'], default='b') },",
+ ")",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'deps' : attr.label_list(aspects=[MyAspectBadDefault]) },",
+ ")");
+ scratch.file("test/BUILD",
+ "load('//test:aspect.bzl', 'my_rule')",
+ "my_rule(name = 'xxx')");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult result = update(ImmutableList.<String>of(), "//test:xxx");
+ assertThat(keepGoing()).isTrue();
+ assertThat(result.hasError()).isTrue();
+ } catch (Exception e) {
+ // expect to fail.
+ }
+ assertContainsEvent("ERROR /workspace/test/aspect.bzl:5:22: "
+ + "Aspect parameter attribute 'my_attr' has a bad default value: has to be one of 'a' "
+ + "instead of 'b'");
+ }
+
+ @Test
+ public void aspectParametersBadValue() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " return struct()",
+ "def _rule_impl(ctx):",
+ " return struct()",
+ "MyAspectBadValue = aspect(",
+ " implementation=_impl,",
+ " attrs = { 'my_attr' : attr.string(values=['a']) },",
+ ")",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'deps' : attr.label_list(aspects=[MyAspectBadValue]),",
+ " 'my_attr' : attr.string() },",
+ ")");
+ scratch.file("test/BUILD",
+ "load('//test:aspect.bzl', 'my_rule')",
+ "my_rule(name = 'xxx', my_attr='b')");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult result = update(ImmutableList.<String>of(), "//test:xxx");
+ assertThat(keepGoing()).isTrue();
+ assertThat(result.hasError()).isTrue();
+ } catch (Exception e) {
+ // expect to fail.
+ }
+ assertContainsEvent("ERROR /workspace/test/BUILD:2:1: //test:xxx: invalid value in 'my_attr' "
+ + "attribute: has to be one of 'a' instead of 'b'");
+ }
+
+ @Test
+ public void aspectParameters() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " return struct()",
+ "def _rule_impl(ctx):",
+ " return struct()",
+ "MyAspect = aspect(",
+ " implementation=_impl,",
+ " attrs = { 'my_attr' : attr.string(values=['aaa']) },",
+ ")",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'deps' : attr.label_list(aspects=[MyAspect]),",
+ " 'my_attr' : attr.string() },",
+ ")");
+ scratch.file("test/BUILD",
+ "load('//test:aspect.bzl', 'my_rule')",
+ "my_rule(name = 'xxx', my_attr = 'aaa')");
+
+ AnalysisResult result = update(ImmutableList.<String>of(), "//test:xxx");
+ assertThat(result.hasError()).isFalse();
+ }
+
+ @Test
+ public void aspectParametersOptional() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " return struct()",
+ "def _rule_impl(ctx):",
+ " return struct()",
+ "MyAspectOptParam = aspect(",
+ " implementation=_impl,",
+ " attrs = { 'my_attr' : attr.string(values=['aaa'], default='aaa') },",
+ ")",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'deps' : attr.label_list(aspects=[MyAspectOptParam]) },",
+ ")");
+ scratch.file("test/BUILD",
+ "load('//test:aspect.bzl', 'my_rule')",
+ "my_rule(name = 'xxx')");
+
+ AnalysisResult result = update(ImmutableList.<String>of(), "//test:xxx");
+ assertThat(result.hasError()).isFalse();
+ }
+
+ @Test
+ public void aspectParametersOptionalOverride() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " if (ctx.attr.my_attr == 'a'):",
+ " fail('Rule is not overriding default, still has value ' + ctx.attr.my_attr)",
+ " return struct()",
+ "def _rule_impl(ctx):",
+ " return struct()",
+ "MyAspectOptOverride = aspect(",
+ " implementation=_impl,",
+ " attrs = { 'my_attr' : attr.string(values=['a', 'b'], default='a') },",
+ ")",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'deps' : attr.label_list(aspects=[MyAspectOptOverride]),",
+ " 'my_attr' : attr.string() },",
+ ")");
+ scratch.file("test/BUILD",
+ "load('//test:aspect.bzl', 'my_rule')",
+ "my_rule(name = 'xxx', my_attr = 'b')");
+
+ AnalysisResult result = update(ImmutableList.<String>of(), "//test:xxx");
+ assertThat(result.hasError()).isFalse();
+ }
+
+ @Test
+ public void testMultipleExecutablesInTarget() throws Exception {
+ scratch.file("foo/extension.bzl",
+ "def _aspect_impl(target, ctx):",
+ " return struct()",
+ "my_aspect = aspect(_aspect_impl)",
+ "def _main_rule_impl(ctx):",
+ " pass",
+ "my_rule = rule(_main_rule_impl,",
+ " attrs = { ",
+ " 'exe1' : attr.label(executable = True, allow_files = True, cfg = 'host'),",
+ " 'exe2' : attr.label(executable = True, allow_files = True, cfg = 'host'),",
+ " },",
+ ")"
+ );
+
+ scratch.file("foo/tool.sh", "#!/bin/bash");
+ scratch.file("foo/BUILD",
+ "load(':extension.bzl', 'my_rule')",
+ "my_rule(name = 'main', exe1 = ':tool.sh', exe2 = ':tool.sh')"
+ );
+ AnalysisResult analysisResultOfRule =
+ update(ImmutableList.<String>of(), "//foo:main");
+ assertThat(analysisResultOfRule.hasError()).isFalse();
+
+ AnalysisResult analysisResultOfAspect =
+ update(ImmutableList.<String>of("/foo/extension.bzl%my_aspect"), "//foo:main");
+ assertThat(analysisResultOfAspect.hasError()).isFalse();
+ }
+
+ @Test
+ public void aspectFragmentAccessSuccess() throws Exception {
+ getConfiguredTargetForAspectFragment(
+ "ctx.fragments.cpp.compiler", "'cpp'", "", "", "");
+ assertNoEvents();
+ }
+
+ @Test
+ public void aspectHostFragmentAccessSuccess() throws Exception {
+ getConfiguredTargetForAspectFragment(
+ "ctx.host_fragments.cpp.compiler", "", "'cpp'", "", "");
+ assertNoEvents();
+ }
+
+ @Test
+ public void aspectFragmentAccessError() throws Exception {
+ reporter.removeHandler(failFastHandler);
+ try {
+ getConfiguredTargetForAspectFragment(
+ "ctx.fragments.cpp.compiler", "'java'", "'cpp'", "'cpp'", "");
+ fail("update() should have failed");
+ } catch (ViewCreationFailedException e) {
+ // expected
+ }
+ assertContainsEvent(
+ "//test:aspect.bzl%MyAspect aspect on my_rule has to declare 'cpp' as a "
+ + "required fragment in target configuration in order to access it. Please update the "
+ + "'fragments' argument of the rule definition "
+ + "(for example: fragments = [\"cpp\"])");
+ }
+
+ @Test
+ public void aspectHostFragmentAccessError() throws Exception {
+ reporter.removeHandler(failFastHandler);
+ try {
+ getConfiguredTargetForAspectFragment(
+ "ctx.host_fragments.cpp.compiler", "'cpp'", "'java'", "", "'cpp'");
+ fail("update() should have failed");
+ } catch (ViewCreationFailedException e) {
+ // expected
+ }
+ assertContainsEvent(
+ "//test:aspect.bzl%MyAspect aspect on my_rule has to declare 'cpp' as a "
+ + "required fragment in host configuration in order to access it. Please update the "
+ + "'host_fragments' argument of the rule definition "
+ + "(for example: host_fragments = [\"cpp\"])");
+ }
+
+ private ConfiguredTarget getConfiguredTargetForAspectFragment(
+ String fullFieldName,
+ String fragments,
+ String hostFragments,
+ String ruleFragments,
+ String ruleHostFragments)
+ throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _aspect_impl(target, ctx):",
+ " return struct(result = str(" + fullFieldName + "))",
+ "",
+ "def _rule_impl(ctx):",
+ " return struct(stuff = '...')",
+ "",
+ "MyAspect = aspect(",
+ " implementation=_aspect_impl,",
+ " attr_aspects=['deps'],",
+ " fragments=[" + fragments + "],",
+ " host_fragments=[" + hostFragments + "],",
+ ")",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'attr' : ",
+ " attr.label_list(mandatory=True, allow_files=True, aspects = [MyAspect]) },",
+ " fragments=[" + ruleFragments + "],",
+ " host_fragments=[" + ruleHostFragments + "],",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:aspect.bzl', 'my_rule')",
+ "exports_files(['zzz'])",
+ "my_rule(",
+ " name = 'yyy',",
+ " attr = ['zzz'],",
+ ")",
+ "my_rule(",
+ " name = 'xxx',",
+ " attr = ['yyy'],",
+ ")");
+
+ AnalysisResult result = update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+ if (result.hasError()) {
+ assertThat(keepGoing()).isTrue();
+ throw new ViewCreationFailedException("Analysis failed");
+ }
+
+ return getConfiguredTarget("//test:xxx");
+ }
+
+ @Test
+ public void invalidateAspectOnBzlFileChange() throws Exception {
+ scratch.file("test/build_defs.bzl", aspectBzlFile("'deps'"));
+ scratch.file(
+ "test/BUILD",
+ "load(':build_defs.bzl', 'repro', 'repro_no_aspect')",
+ "repro_no_aspect(name = 'r0')",
+ "repro_no_aspect(name = 'r1', deps = [':r0'])",
+ "repro(name = 'r2', deps = [':r1'])");
+ buildTargetAndCheckRuleInfo("//test:r0", "//test:r1");
+
+ // Make aspect propagation list empty.
+ scratch.overwriteFile("test/build_defs.bzl", aspectBzlFile(""));
+
+ // The aspect should not propagate to //test:r0 anymore.
+ buildTargetAndCheckRuleInfo("//test:r1");
+ }
+
+ private void buildTargetAndCheckRuleInfo(String... expectedLabels) throws Exception {
+ AnalysisResult result = update(ImmutableList.<String>of(), "//test:r2");
+ ConfiguredTarget configuredTarget = result.getTargetsToBuild().iterator().next();
+ SkylarkNestedSet ruleInfoValue =
+ (SkylarkNestedSet)
+ configuredTarget.get("rule_info");
+ assertThat(ruleInfoValue.getSet(String.class))
+ .containsExactlyElementsIn(Arrays.asList(expectedLabels));
+ }
+
+ private String[] aspectBzlFile(String attrAspects) {
+ return new String[] {
+ "def _repro_aspect_impl(target, ctx):",
+ " s = depset([str(target.label)])",
+ " for d in ctx.rule.attr.deps:",
+ " if hasattr(d, 'aspect_info'):",
+ " s = s | d.aspect_info",
+ " return struct(aspect_info = s)",
+ "",
+ "_repro_aspect = aspect(",
+ " _repro_aspect_impl,",
+ " attr_aspects = [" + attrAspects + "],",
+ ")",
+ "",
+ "def repro_impl(ctx):",
+ " s = depset()",
+ " for d in ctx.attr.deps:",
+ " if hasattr(d, 'aspect_info'):",
+ " s = s | d.aspect_info",
+ " return struct(rule_info = s)",
+ "",
+ "def repro_no_aspect_impl(ctx):",
+ " pass",
+ "",
+ "repro_no_aspect = rule(implementation = repro_no_aspect_impl,",
+ " attrs = {",
+ " 'deps': attr.label_list(",
+ " allow_files = True,",
+ " )",
+ " },",
+ ")",
+ "",
+ "repro = rule(implementation = repro_impl,",
+ " attrs = {",
+ " 'deps': attr.label_list(",
+ " allow_files = True,",
+ " aspects = [_repro_aspect],",
+ " )",
+ " },",
+ ")"
+ };
+ }
+
+ @Test
+ public void aspectOutputsToBinDirectory() throws Exception {
+ scratch.file(
+ "foo/extension.bzl",
+ "def _aspect_impl(target, ctx):",
+ " file = ctx.actions.declare_file('aspect-output-' + target.label.name)",
+ " ctx.actions.write(file, 'data')",
+ " return struct(aspect_file = file)",
+ "my_aspect = aspect(_aspect_impl)",
+ "def _rule_impl(ctx):",
+ " pass",
+ "rule_bin_out = rule(_rule_impl, output_to_genfiles=False)",
+ "rule_gen_out = rule(_rule_impl, output_to_genfiles=True)",
+ "def _main_rule_impl(ctx):",
+ " s = depset()",
+ " for d in ctx.attr.deps:",
+ " s = s | depset([d.aspect_file])",
+ " return struct(aspect_files = s)",
+ "main_rule = rule(_main_rule_impl,",
+ " attrs = { 'deps' : attr.label_list(aspects = [my_aspect]) },",
+ ")");
+
+ scratch.file("foo/BUILD",
+ "load(':extension.bzl', 'rule_bin_out', 'rule_gen_out', 'main_rule')",
+ "rule_bin_out(name = 'rbin')",
+ "rule_gen_out(name = 'rgen')",
+ "main_rule(name = 'main', deps = [':rbin', ':rgen'])"
+ );
+ AnalysisResult analysisResult = update(ImmutableList.<String>of(), "//foo:main");
+ ConfiguredTarget target = analysisResult.getTargetsToBuild().iterator().next();
+ NestedSet<Artifact> aspectFiles =
+ ((SkylarkNestedSet) target.get("aspect_files")).getSet(Artifact.class);
+ assertThat(transform(aspectFiles, Artifact::getFilename))
+ .containsExactly("aspect-output-rbin", "aspect-output-rgen");
+ for (Artifact aspectFile : aspectFiles) {
+ String rootPath = aspectFile.getRoot().getExecPath().toString();
+ assertWithMessage("Artifact %s should not be in genfiles", aspectFile)
+ .that(rootPath).doesNotContain("genfiles");
+ assertWithMessage("Artifact %s should be in bin", aspectFile)
+ .that(rootPath).endsWith("bin");
+ }
+ }
+
+ @Test
+ public void toplevelAspectOnFile() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " print('This aspect does nothing')",
+ " return struct()",
+ "MyAspect = aspect(implementation=_impl)");
+ scratch.file("test/BUILD", "exports_files(['file.txt'])");
+ scratch.file("test/file.txt", "");
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:file.txt");
+ assertThat(analysisResult.hasError()).isFalse();
+ assertThat(
+ Iterables.getOnlyElement(analysisResult.getAspects())
+ .getConfiguredAspect()
+ .getProviders()
+ .getProviderCount())
+ .isEqualTo(0);
+ }
+
+ @Test
+ public void sharedAttributeDefinitionWithAspects() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _aspect_impl(target,ctx):",
+ " return struct()",
+ "my_aspect = aspect(implementation = _aspect_impl)",
+ "_ATTR = { 'deps' : attr.label_list(aspects = [my_aspect]) }",
+ "def _dummy_impl(ctx):",
+ " pass",
+ "r1 = rule(_dummy_impl, attrs = _ATTR)",
+ "r2 = rule(_dummy_impl, attrs = _ATTR)"
+ );
+
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'r1', 'r2')",
+ "r1(name = 't1')",
+ "r2(name = 't2', deps = [':t1'])"
+ );
+ AnalysisResult analysisResult = update("//test:t2");
+ assertThat(analysisResult.hasError()).isFalse();
+ }
+
+ @Test
+ public void multipleAspects() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _aspect_impl(target,ctx):",
+ " return struct()",
+ "my_aspect = aspect(implementation = _aspect_impl)",
+ "def _dummy_impl(ctx):",
+ " pass",
+ "r1 = rule(_dummy_impl, ",
+ " attrs = { 'deps' : attr.label_list(aspects = [my_aspect, my_aspect]) })"
+ );
+
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'r1')",
+ "r1(name = 't1')"
+ );
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult result = update("//test:r1");
+ assertThat(keepGoing()).isTrue();
+ assertThat(result.hasError()).isTrue();
+ } catch (TargetParsingException | ViewCreationFailedException expected) {
+ // expected.
+ }
+ assertContainsEvent("aspect //test:aspect.bzl%my_aspect added more than once");
+ }
+
+ @Test
+ public void topLevelAspectsAndExtraActions() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _aspect_impl(target,ctx):",
+ " f = ctx.actions.declare_file('dummy.txt')",
+ " ctx.actions.run_shell(outputs = [f], command='echo xxx > $(location f)',",
+ " mnemonic='AspectAction')",
+ " return struct()",
+ "my_aspect = aspect(implementation = _aspect_impl)"
+ );
+ scratch.file(
+ "test/BUILD",
+ "extra_action(",
+ " name = 'xa',",
+ " cmd = 'echo $(EXTRA_ACTION_FILE) > $(output file.xa)',",
+ " out_templates = ['file.xa'],",
+ ")",
+ "action_listener(",
+ " name = 'al',",
+ " mnemonics = [ 'AspectAction' ],",
+ " extra_actions = [ ':xa' ])",
+ "java_library(name = 'xxx')"
+ );
+ useConfiguration("--experimental_action_listener=//test:al");
+ AnalysisResult analysisResult = update(
+ ImmutableList.<String>of("test/aspect.bzl%my_aspect"),
+ "//test:xxx");
+ assertThat(
+ Iterables.transform(
+ analysisResult.getAdditionalArtifactsToBuild(), Artifact::getFilename))
+ .contains("file.xa");
+ }
+
+ @Test
+ public void aspectsPropagatingToAllAttributes() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " s = depset([target.label])",
+ " if hasattr(ctx.rule.attr, 'runtime_deps'):",
+ " for i in ctx.rule.attr.runtime_deps:",
+ " s += i.target_labels",
+ " return struct(target_labels = s)",
+ "",
+ "MyAspect = aspect(",
+ " implementation=_impl,",
+ " attrs = { '_tool' : attr.label(default = Label('//test:tool')) },",
+ " attr_aspects=['*'],",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "java_library(",
+ " name = 'tool',",
+ ")",
+ "java_library(",
+ " name = 'bar',",
+ " runtime_deps = [':tool'],",
+ ")",
+ "java_library(",
+ " name = 'foo',",
+ " runtime_deps = [':bar'],",
+ ")");
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:foo");
+ AspectValue aspectValue = analysisResult.getAspects().iterator().next();
+ ConfiguredAspect configuredAspect = aspectValue.getConfiguredAspect();
+ assertThat(configuredAspect).isNotNull();
+ Object names = configuredAspect.get("target_labels");
+ assertThat(names).isInstanceOf(SkylarkNestedSet.class);
+ assertThat(
+ transform(
+ ((SkylarkNestedSet) names).toCollection(),
+ o -> {
+ assertThat(o).isInstanceOf(Label.class);
+ return ((Label) o).getName();
+ }))
+ .containsExactly("foo", "bar", "tool");
+ }
+
+ /**
+ * Simple straightforward linear aspects-on-aspects.
+ */
+ @Test
+ public void aspectOnAspectLinear() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "a1p = provider()",
+ "def _a1_impl(target,ctx):",
+ " return struct(a1p = a1p(text = 'random'))",
+ "a1 = aspect(_a1_impl, attr_aspects = ['dep'], provides = ['a1p'])",
+ "a2p = provider()",
+ "def _a2_impl(target,ctx):",
+ " value = []",
+ " if hasattr(ctx.rule.attr.dep, 'a2p'):",
+ " value += ctx.rule.attr.dep.a2p.value",
+ " if hasattr(target, 'a1p'):",
+ " value.append(str(target.label) + str(ctx.aspect_ids) + '=yes')",
+ " else:",
+ " value.append(str(target.label) + str(ctx.aspect_ids) + '=no')",
+ " return struct(a2p = a2p(value = value))",
+ "a2 = aspect(_a2_impl, attr_aspects = ['dep'], required_aspect_providers = ['a1p'])",
+ "def _r1_impl(ctx):",
+ " pass",
+ "def _r2_impl(ctx):",
+ " return struct(result = ctx.attr.dep.a2p.value)",
+ "r1 = rule(_r1_impl, attrs = { 'dep' : attr.label(aspects = [a1])})",
+ "r2 = rule(_r2_impl, attrs = { 'dep' : attr.label(aspects = [a2])})"
+ );
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'r1', 'r2')",
+ "r1(name = 'r0')",
+ "r1(name = 'r1', dep = ':r0')",
+ "r2(name = 'r2', dep = ':r1')"
+ );
+ AnalysisResult analysisResult = update("//test:r2");
+ ConfiguredTarget target = Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
+ SkylarkList result = (SkylarkList) target.get("result");
+
+ // "yes" means that aspect a2 sees a1's providers.
+ assertThat(result).containsExactly(
+ "//test:r0[\"//test:aspect.bzl%a1\", \"//test:aspect.bzl%a2\"]=yes",
+ "//test:r1[\"//test:aspect.bzl%a2\"]=no");
+ }
+
+ /**
+ * Diamond case.
+ * rule r1 depends or r0 with aspect a1.
+ * rule r2 depends or r0 with aspect a2.
+ * rule rcollect depends on r1, r2 with aspect a3.
+ *
+ * Aspect a3 should be applied twice to target r0: once in [a1, a3] sequence
+ * and once in [a2, a3] sequence.
+ */
+ @Test
+ public void aspectOnAspectDiamond() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _a1_impl(target,ctx):",
+ " return struct(a1p = 'text from a1')",
+ "a1 = aspect(_a1_impl, attr_aspects = ['deps'], provides = ['a1p'])",
+ "",
+ "def _a2_impl(target,ctx):",
+ " return struct(a2p = 'text from a2')",
+ "a2 = aspect(_a2_impl, attr_aspects = ['deps'], provides = ['a2p'])",
+ "",
+ "def _a3_impl(target,ctx):",
+ " value = []",
+ " f = ctx.actions.declare_file('a3.out')",
+ " ctx.actions.write(f, 'text')",
+ " for dep in ctx.rule.attr.deps:",
+ " if hasattr(dep, 'a3p'):",
+ " value += dep.a3p",
+ " s = str(target.label) + str(ctx.aspect_ids) + '='",
+ " if hasattr(target, 'a1p'):",
+ " s += 'a1p'",
+ " if hasattr(target, 'a2p'):",
+ " s += 'a2p'",
+ " value.append(s)",
+ " return struct(a3p = value)",
+ "a3 = aspect(_a3_impl, attr_aspects = ['deps'],",
+ " required_aspect_providers = [['a1p'], ['a2p']])",
+ "def _r1_impl(ctx):",
+ " pass",
+ "def _rcollect_impl(ctx):",
+ " value = []",
+ " for dep in ctx.attr.deps:",
+ " if hasattr(dep, 'a3p'):",
+ " value += dep.a3p",
+ " return struct(result = value)",
+ "r1 = rule(_r1_impl, attrs = { 'deps' : attr.label_list(aspects = [a1])})",
+ "r2 = rule(_r1_impl, attrs = { 'deps' : attr.label_list(aspects = [a2])})",
+ "rcollect = rule(_rcollect_impl, attrs = { 'deps' : attr.label_list(aspects = [a3])})"
+ );
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'r1', 'r2', 'rcollect')",
+ "r1(name = 'r0')",
+ "r1(name = 'r1', deps = [':r0'])",
+ "r2(name = 'r2', deps = [':r0'])",
+ "rcollect(name = 'rcollect', deps = [':r1', ':r2'])"
+ );
+ AnalysisResult analysisResult = update("//test:rcollect");
+ ConfiguredTarget target = Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
+ SkylarkList result = (SkylarkList) target.get("result");
+ assertThat(result).containsExactly(
+ "//test:r0[\"//test:aspect.bzl%a1\", \"//test:aspect.bzl%a3\"]=a1p",
+ "//test:r1[\"//test:aspect.bzl%a3\"]=",
+ "//test:r0[\"//test:aspect.bzl%a2\", \"//test:aspect.bzl%a3\"]=a2p",
+ "//test:r2[\"//test:aspect.bzl%a3\"]=");
+ }
+
+ /**
+ * Linear with duplicates.
+ * r2_1 depends on r0 with aspect a2.
+ * r1 depends on r2_1 with aspect a1.
+ * r2 depends on r1 with aspect a2.
+ *
+ * a2 is not interested in a1.
+ * There should be just one instance of aspect a2 on r0, and is should *not* see a1.
+ */
+ @Test
+ public void aspectOnAspectLinearDuplicates() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "a1p = provider()",
+ "def _a1_impl(target,ctx):",
+ " return struct(a1p = 'a1p')",
+ "a1 = aspect(_a1_impl, attr_aspects = ['dep'], provides = ['a1p'])",
+ "a2p = provider()",
+ "def _a2_impl(target,ctx):",
+ " value = []",
+ " if hasattr(ctx.rule.attr.dep, 'a2p'):",
+ " value += ctx.rule.attr.dep.a2p.value",
+ " if hasattr(target, 'a1p'):",
+ " value.append(str(target.label) + str(ctx.aspect_ids) + '=yes')",
+ " else:",
+ " value.append(str(target.label) + str(ctx.aspect_ids) + '=no')",
+ " return struct(a2p = a2p(value = value))",
+ "a2 = aspect(_a2_impl, attr_aspects = ['dep'], required_aspect_providers = [])",
+ "def _r1_impl(ctx):",
+ " pass",
+ "def _r2_impl(ctx):",
+ " return struct(result = ctx.attr.dep.a2p.value)",
+ "r1 = rule(_r1_impl, attrs = { 'dep' : attr.label(aspects = [a1])})",
+ "r2 = rule(_r2_impl, attrs = { 'dep' : attr.label(aspects = [a2])})"
+ );
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'r1', 'r2')",
+ "r1(name = 'r0')",
+ "r2(name = 'r2_1', dep = ':r0')",
+ "r1(name = 'r1', dep = ':r2_1')",
+ "r2(name = 'r2', dep = ':r1')"
+ );
+ AnalysisResult analysisResult = update("//test:r2");
+ ConfiguredTarget target = Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
+ SkylarkList result = (SkylarkList) target.get("result");
+ // "yes" means that aspect a2 sees a1's providers.
+ assertThat(result).containsExactly(
+ "//test:r0[\"//test:aspect.bzl%a2\"]=no",
+ "//test:r1[\"//test:aspect.bzl%a2\"]=no",
+ "//test:r2_1[\"//test:aspect.bzl%a2\"]=no");
+ }
+
+ /**
+ * Linear aspects-on-aspects with alias rule.
+ */
+ @Test
+ public void aspectOnAspectLinearAlias() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "a1p = provider()",
+ "def _a1_impl(target,ctx):",
+ " return struct(a1p = a1p(text = 'random'))",
+ "a1 = aspect(_a1_impl, attr_aspects = ['dep'], provides = ['a1p'])",
+ "a2p = provider()",
+ "def _a2_impl(target,ctx):",
+ " value = []",
+ " if hasattr(ctx.rule.attr.dep, 'a2p'):",
+ " value += ctx.rule.attr.dep.a2p.value",
+ " if hasattr(target, 'a1p'):",
+ " value.append(str(target.label) + str(ctx.aspect_ids) + '=yes')",
+ " else:",
+ " value.append(str(target.label) + str(ctx.aspect_ids) + '=no')",
+ " return struct(a2p = a2p(value = value))",
+ "a2 = aspect(_a2_impl, attr_aspects = ['dep'], required_aspect_providers = ['a1p'])",
+ "def _r1_impl(ctx):",
+ " pass",
+ "def _r2_impl(ctx):",
+ " return struct(result = ctx.attr.dep.a2p.value)",
+ "r1 = rule(_r1_impl, attrs = { 'dep' : attr.label(aspects = [a1])})",
+ "r2 = rule(_r2_impl, attrs = { 'dep' : attr.label(aspects = [a2])})"
+ );
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'r1', 'r2')",
+ "r1(name = 'r0')",
+ "alias(name = 'a0', actual = ':r0')",
+ "r1(name = 'r1', dep = ':a0')",
+ "r2(name = 'r2', dep = ':r1')"
+ );
+ AnalysisResult analysisResult = update("//test:r2");
+ ConfiguredTarget target = Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
+ SkylarkList<?> result = (SkylarkList<?>) target.get("result");
+
+ // "yes" means that aspect a2 sees a1's providers.
+ assertThat(result).containsExactly(
+ "//test:r0[\"//test:aspect.bzl%a1\", \"//test:aspect.bzl%a2\"]=yes",
+ "//test:r1[\"//test:aspect.bzl%a2\"]=no");
+ }
+
+ @Test
+ public void aspectDescriptions() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _a_impl(target,ctx):",
+ " s = str(target.label) + str(ctx.aspect_ids) + '='",
+ " value = []",
+ " if ctx.rule.attr.dep:",
+ " d = ctx.rule.attr.dep",
+ " this_id = ctx.aspect_ids[len(ctx.aspect_ids) - 1]",
+ " s += str(d.label) + str(d.my_ids) + ',' + str(this_id in d.my_ids)",
+ " value += ctx.rule.attr.dep.ap",
+ " else:",
+ " s += 'None'",
+ " value.append(s)",
+ " return struct(ap = value, my_ids = ctx.aspect_ids)",
+ "a = aspect(_a_impl, attr_aspects = ['dep'])",
+ "def _r_impl(ctx):",
+ " if not ctx.attr.dep:",
+ " return struct(result = [])",
+ " return struct(result = ctx.attr.dep.ap)",
+ "r = rule(_r_impl, attrs = { 'dep' : attr.label(aspects = [a])})"
+ );
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'r')",
+ "r(name = 'r0')",
+ "r(name = 'r1', dep = ':r0')",
+ "r(name = 'r2', dep = ':r1')"
+ );
+ AnalysisResult analysisResult = update("//test:r2");
+ ConfiguredTarget target = Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
+ SkylarkList<?> result = (SkylarkList<?>) target.get("result");
+
+ assertThat(result).containsExactly(
+ "//test:r0[\"//test:aspect.bzl%a\"]=None",
+ "//test:r1[\"//test:aspect.bzl%a\"]=//test:r0[\"//test:aspect.bzl%a\"],True");
+ }
+
+
+ @Test
+ public void attributesWithAspectsReused() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " return struct()",
+ "my_aspect = aspect(_impl)",
+ "a_dict = { 'foo' : attr.label_list(aspects = [my_aspect]) }"
+ );
+
+ scratch.file(
+ "test/r1.bzl",
+ "load(':aspect.bzl', 'my_aspect', 'a_dict')",
+ "def _rule_impl(ctx):",
+ " pass",
+ "r1 = rule(_rule_impl, attrs = a_dict)"
+ );
+
+ scratch.file(
+ "test/r2.bzl",
+ "load(':aspect.bzl', 'my_aspect', 'a_dict')",
+ "def _rule_impl(ctx):",
+ " pass",
+ "r2 = rule(_rule_impl, attrs = a_dict)"
+ );
+
+ scratch.file(
+ "test/BUILD",
+ "load(':r1.bzl', 'r1')",
+ "load(':r2.bzl', 'r2')",
+ "r1(name = 'x1')",
+ "r2(name = 'x2', foo = [':x1'])"
+ );
+ AnalysisResult analysisResult = update("//test:x2");
+ assertThat(analysisResult.hasError()).isFalse();
+ }
+
+ @Test
+ public void aspectAdvertisingProviders() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " return struct()",
+ "my_aspect = aspect(_impl, provides = ['foo'])",
+ "a_dict = { 'foo' : attr.label_list(aspects = [my_aspect]) }"
+ );
+ scratch.file("test/BUILD", "java_library(name = 'xxx',)");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ AnalysisResult analysisResult = update(
+ ImmutableList.of("//test:aspect.bzl%my_aspect"),
+ "//test:xxx");
+ assertThat(keepGoing()).isTrue();
+ assertThat(analysisResult.hasError()).isTrue();
+ } catch (ViewCreationFailedException e) {
+ // expect exception
+ }
+ assertContainsEvent(
+ "Aspect '//test:aspect.bzl%my_aspect', applied to '//test:xxx', "
+ + "does not provide advertised provider 'foo'");
+ }
+
+ @Test
+ public void aspectOnAspectInconsistentVisibility() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "a1p = provider()",
+ "def _a1_impl(target,ctx):",
+ " return struct(a1p = a1p(text = 'random'))",
+ "a1 = aspect(_a1_impl, attr_aspects = ['dep'], provides = ['a1p'])",
+ "a2p = provider()",
+ "def _a2_impl(target,ctx):",
+ " return struct(a2p = a2p(value = 'random'))",
+ "a2 = aspect(_a2_impl, attr_aspects = ['dep'], required_aspect_providers = ['a1p'])",
+ "def _r1_impl(ctx):",
+ " pass",
+ "def _r2_impl(ctx):",
+ " return struct(result = ctx.attr.dep.a2p.value)",
+ "r1 = rule(_r1_impl, attrs = { 'dep' : attr.label(aspects = [a1])})",
+ "r2 = rule(_r2_impl, attrs = { 'dep' : attr.label(aspects = [a2])})"
+ );
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'r1', 'r2')",
+ "r1(name = 'r0')",
+ "r1(name = 'r1', dep = ':r0')",
+ "r2(name = 'r2', dep = ':r1')",
+ "r1(name = 'r1_1', dep = ':r2')",
+ "r2(name = 'r2_1', dep = ':r1_1')"
+
+ );
+ reporter.removeHandler(failFastHandler);
+
+ try {
+ AnalysisResult analysisResult = update("//test:r2_1");
+ assertThat(analysisResult.hasError()).isTrue();
+ assertThat(keepGoing()).isTrue();
+ } catch (ViewCreationFailedException e) {
+ // expected
+ }
+ assertContainsEvent("ERROR /workspace/test/BUILD:4:1: Aspect //test:aspect.bzl%a2 is"
+ + " applied twice, both before and after aspect //test:aspect.bzl%a1 "
+ + "(when propagating from //test:r2 to //test:r1 via attribute dep)");
+ }
+
+ @Test
+ public void aspectOnAspectInconsistentVisibilityIndirect() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "a1p = provider()",
+ "def _a1_impl(target,ctx):",
+ " return struct(a1p = a1p(text = 'random'))",
+ "a1 = aspect(_a1_impl, attr_aspects = ['dep'], provides = ['a1p'])",
+ "a2p = provider()",
+ "def _a2_impl(target,ctx):",
+ " return struct(a2p = a2p(value = 'random'))",
+ "a2 = aspect(_a2_impl, attr_aspects = ['dep'], required_aspect_providers = ['a1p'])",
+ "def _r1_impl(ctx):",
+ " pass",
+ "def _r2_impl(ctx):",
+ " return struct(result = ctx.attr.dep.a2p.value)",
+ "r1 = rule(_r1_impl, attrs = { 'dep' : attr.label(aspects = [a1])})",
+ "r2 = rule(_r2_impl, attrs = { 'dep' : attr.label(aspects = [a2])})",
+ "def _r0_impl(ctx):",
+ " pass",
+ "r0 = rule(_r0_impl, attrs = { 'dep' : attr.label()})"
+ );
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'r0', 'r1', 'r2')",
+ "r0(name = 'r0')",
+ "r1(name = 'r1', dep = ':r0')",
+ "r2(name = 'r2', dep = ':r1')",
+ "r1(name = 'r1_1', dep = ':r2')",
+ "r2(name = 'r2_1', dep = ':r1_1')",
+ "r0(name = 'r0_2', dep = ':r2_1')"
+ );
+ reporter.removeHandler(failFastHandler);
+
+ try {
+ AnalysisResult analysisResult = update("//test:r0_2");
+ assertThat(analysisResult.hasError()).isTrue();
+ assertThat(keepGoing()).isTrue();
+ } catch (ViewCreationFailedException e) {
+ // expected
+ }
+ assertContainsEvent("ERROR /workspace/test/BUILD:4:1: Aspect //test:aspect.bzl%a2 is"
+ + " applied twice, both before and after aspect //test:aspect.bzl%a1 "
+ + "(when propagating from //test:r2 to //test:r1 via attribute dep)");
+ }
+
+ /**
+ * Aspect a3 sees aspect a2, aspect a2 sees aspect a1, but a3 does not see a1.
+ * All three aspects should still propagate together.
+ */
+ @Test
+ public void aspectOnAspectOnAspect() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "p1 = provider()",
+ "def _a1_impl(target, ctx):",
+ " return [p1()]",
+ "a1 = aspect(_a1_impl, attr_aspects = ['dep'], provides = [p1])",
+ "p2 = provider()",
+ "def _a2_impl(target, ctx):",
+ " value = True if p1 in target else False",
+ " return [p2(has_p1 = value)]",
+ "a2 = aspect(_a2_impl, attr_aspects = ['dep'],",
+ " required_aspect_providers = [p1], provides = [p2])",
+ "p3 = provider()",
+ "def _a3_impl(target, ctx):",
+ " list = []",
+ " if ctx.rule.attr.dep:",
+ " list = ctx.rule.attr.dep[p3].value",
+ " my_value = str(target.label) +'=' + str(target[p2].has_p1 if p2 in target else False)",
+ " return [p3(value = list + [my_value])]",
+ "a3 = aspect(_a3_impl, attr_aspects = ['dep'],",
+ " required_aspect_providers = [p2])",
+ "def _r0_impl(ctx):",
+ " pass",
+ "r0 = rule(_r0_impl, attrs = { 'dep' : attr.label()})",
+ "def _r1_impl(ctx):",
+ " pass",
+ "def _r2_impl(ctx):",
+ " pass",
+ "r1 = rule(_r1_impl, attrs = { 'dep' : attr.label(aspects = [a1])})",
+ "r2 = rule(_r2_impl, attrs = { 'dep' : attr.label(aspects = [a2])})"
+ );
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'r0', 'r1', 'r2')",
+ "r0(name = 'r0_1')",
+ "r0(name = 'r0_2', dep = ':r0_1')",
+ "r0(name = 'r0_3', dep = ':r0_2')",
+ "r1(name = 'r1_1', dep = ':r0_3')",
+ "r2(name = 'r2_1', dep = ':r1_1')"
+ );
+
+ AnalysisResult analysisResult = update(ImmutableList.of("//test:aspect.bzl%a3"), "//test:r2_1");
+ ConfiguredAspect configuredAspect = Iterables.getOnlyElement(analysisResult.getAspects())
+ .getConfiguredAspect();
+ SkylarkKey p3 = new SkylarkKey(Label.parseAbsolute("//test:aspect.bzl"), "p3");
+ assertThat((SkylarkList<?>) configuredAspect.get(p3).getValue("value"))
+ .containsExactly(
+ "//test:r0_1=True",
+ "//test:r0_2=True",
+ "//test:r0_3=True",
+ "//test:r1_1=False",
+ "//test:r2_1=False");
+ }
+
+ /**
+ * r0 is a dependency of r1 via two attributes, dep1 and dep2.
+ * r1 sends an aspect 'a' along dep1 but not along dep2.
+ *
+ * rcollect depends upon r1 and sends another aspect, 'collector',
+ * along its dep dependency. 'collector' wants to see aspect 'a' and propagates along
+ * dep1 and dep2. It should be applied both to r0 and to r0+a.
+ */
+ @Test
+ public void multipleDepsDifferentAspects() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "PAspect = provider()",
+ "PCollector = provider()",
+ "def _aspect_impl(target, ctx):",
+ " return [PAspect()]",
+ "a = aspect(_aspect_impl, attr_aspects = ['dep'], provides = [PAspect])",
+ "def _collector_impl(target, ctx):",
+ " suffix = '+PAspect' if PAspect in target else ''",
+ " result = [str(target.label)+suffix]",
+ " for a in ['dep', 'dep1', 'dep2']:",
+ " if hasattr(ctx.rule.attr, a):",
+ " result += getattr(ctx.rule.attr, a)[PCollector].result",
+ " return [PCollector(result=result)]",
+ "collector = aspect(_collector_impl, attr_aspects = ['*'], ",
+ " required_aspect_providers = [PAspect])",
+ "def _rimpl(ctx):",
+ " pass",
+ "r0 = rule(_rimpl)",
+ "r1 = rule(_rimpl, ",
+ " attrs = {",
+ " 'dep1' : attr.label(),",
+ " 'dep2' : attr.label(aspects = [a]),",
+ " },",
+ ")",
+ "def _rcollect_impl(ctx):",
+ " return [ctx.attr.dep[PCollector]]",
+ "rcollect = rule(_rcollect_impl,",
+ " attrs = {",
+ " 'dep' : attr.label(aspects = [collector]),",
+ " })"
+ );
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'r0', 'r1', 'rcollect')",
+ "r0(name = 'r0')",
+ "r1(name = 'r1', dep1 = ':r0', dep2 = ':r0')",
+ "rcollect(name = 'rcollect', dep = ':r1')"
+ );
+
+ AnalysisResult analysisResult = update(ImmutableList.of(), "//test:rcollect");
+ ConfiguredTarget configuredTarget =
+ Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
+ SkylarkKey pCollector = new SkylarkKey(Label.parseAbsolute("//test:aspect.bzl"), "PCollector");
+ assertThat((SkylarkList<?>) configuredTarget.get(pCollector).getValue("result"))
+ .containsExactly(
+ "//test:r1",
+ "//test:r0",
+ "//test:r0+PAspect");
+ }
+
+ @Test
+ public void aspectSeesOtherAspectAttributes() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "PAspect = provider(fields = [])",
+ "PCollector = provider(fields = ['aspect_attr'])",
+ "def _a_impl(target, ctx):",
+ " return [PAspect()]",
+ "a = aspect(_a_impl, ",
+ " provides = [PAspect],",
+ " attrs = {'_a_attr' : attr.label(default = '//test:foo')})",
+ "def _rcollect(target, ctx):",
+ " if hasattr(ctx.rule.attr, '_a_attr'):",
+ " return [PCollector(aspect_attr = ctx.rule.attr._a_attr.label)]",
+ " if hasattr(ctx.rule.attr, 'dep'):",
+ " return [ctx.rule.attr.dep[PCollector]]",
+ " return [PCollector()]",
+ "acollect = aspect(_rcollect, attr_aspects = ['*'], required_aspect_providers = [PAspect])",
+ "def _rimpl(ctx):",
+ " pass",
+ "r0 = rule(_rimpl)",
+ "r = rule(_rimpl, attrs = { 'dep' : attr.label(aspects = [a]) })");
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'r0', 'r')",
+ "r0(name = 'foo')",
+ "r0(name = 'bar')",
+ "r(name = 'baz', dep = ':bar')");
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("//test:aspect.bzl%acollect"), "//test:baz");
+ ConfiguredAspect configuredAspect =
+ Iterables.getOnlyElement(analysisResult.getAspects()).getConfiguredAspect();
+ SkylarkKey pCollector = new SkylarkKey(Label.parseAbsolute("//test:aspect.bzl"), "PCollector");
+ Info collector = configuredAspect.get(pCollector);
+ assertThat(collector.getValue("aspect_attr")).isEqualTo(Label.parseAbsolute("//test:foo"));
+ }
+
+ @Test
+ public void ruleAttributesWinOverAspects() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "PAspect = provider(fields = [])",
+ "PCollector = provider(fields = ['attr_value'])",
+ "def _a_impl(target, ctx):",
+ " return [PAspect()]",
+ "a = aspect(_a_impl, ",
+ " provides = [PAspect],",
+ " attrs = {'_same_attr' : attr.int(default = 239)})",
+ "def _rcollect(target, ctx):",
+ " if hasattr(ctx.rule.attr, '_same_attr'):",
+ " return [PCollector(attr_value = ctx.rule.attr._same_attr)]",
+ " if hasattr(ctx.rule.attr, 'dep'):",
+ " return [ctx.rule.attr.dep[PCollector]]",
+ " return [PCollector()]",
+ "acollect = aspect(_rcollect, attr_aspects = ['*'], required_aspect_providers = [PAspect])",
+ "def _rimpl(ctx):",
+ " pass",
+ "r0 = rule(_rimpl)",
+ "r = rule(_rimpl, ",
+ " attrs = { ",
+ " 'dep' : attr.label(aspects = [a]), ",
+ " '_same_attr' : attr.int(default = 30)",
+ " })");
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'r0', 'r')",
+ "r0(name = 'foo')",
+ "r0(name = 'bar')",
+ "r(name = 'baz', dep = ':bar')");
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("//test:aspect.bzl%acollect"), "//test:baz");
+ ConfiguredAspect configuredAspect =
+ Iterables.getOnlyElement(analysisResult.getAspects()).getConfiguredAspect();
+ SkylarkKey pCollector = new SkylarkKey(Label.parseAbsolute("//test:aspect.bzl"), "PCollector");
+ Info collector = configuredAspect.get(pCollector);
+ assertThat(collector.getValue("attr_value")).isEqualTo(30);
+ }
+
+ @Test
+ public void earlyAspectAttributesWin() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "PAspect1 = provider(fields = [])",
+ "PAspect2 = provider(fields = [])",
+ "PCollector = provider(fields = ['attr_value'])",
+ "def _a1_impl(target, ctx):",
+ " return [PAspect1()]",
+ "def _a2_impl(target, ctx):",
+ " return [PAspect2()]",
+ "a1 = aspect(_a1_impl, ",
+ " provides = [PAspect1],",
+ " attrs = {'_same_attr' : attr.int(default = 30)})",
+ "a2 = aspect(_a2_impl, ",
+ " provides = [PAspect2],",
+ " attrs = {'_same_attr' : attr.int(default = 239)})",
+ "def _rcollect(target, ctx):",
+ " if hasattr(ctx.rule.attr, 'dep'):",
+ " return [ctx.rule.attr.dep[PCollector]]",
+ " if hasattr(ctx.rule.attr, '_same_attr'):",
+ " return [PCollector(attr_value = ctx.rule.attr._same_attr)]",
+ " fail('???')",
+ " return [PCollector()]",
+ "acollect = aspect(_rcollect, attr_aspects = ['*'], ",
+ " required_aspect_providers = [[PAspect1], [PAspect2]])",
+ "def _rimpl(ctx):",
+ " pass",
+ "r0 = rule(_rimpl)",
+ "r1 = rule(_rimpl, ",
+ " attrs = { ",
+ " 'dep' : attr.label(aspects = [a1]), ",
+ " })",
+ "r2 = rule(_rimpl, ",
+ " attrs = { ",
+ " 'dep' : attr.label(aspects = [a2]), ",
+ " })"
+ );
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'r0', 'r1', 'r2')",
+ "r0(name = 'bar')",
+ "r1(name = 'baz', dep = ':bar')",
+ "r2(name = 'quux', dep = ':baz')"
+ );
+
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("//test:aspect.bzl%acollect"), "//test:quux");
+ ConfiguredAspect configuredAspect =
+ Iterables.getOnlyElement(analysisResult.getAspects()).getConfiguredAspect();
+ SkylarkKey pCollector = new SkylarkKey(Label.parseAbsolute("//test:aspect.bzl"), "PCollector");
+ Info collector = configuredAspect.get(pCollector);
+ assertThat(collector.getValue("attr_value")).isEqualTo(30);
+ }
+
+
+ @Test
+ public void aspectPropagatesOverOtherAspectAttributes() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "PAspect = provider(fields = [])",
+ "PCollector = provider(fields = ['visited'])",
+ "def _a_impl(target, ctx):",
+ " return [PAspect()]",
+ "a = aspect(_a_impl, ",
+ " provides = [PAspect],",
+ " attrs = {'_a_attr' : attr.label(default = '//test:referenced_from_aspect_only')})",
+ "def _rcollect(target, ctx):",
+ " transitive = []",
+ " if hasattr(ctx.rule.attr, 'dep') and ctx.rule.attr.dep:",
+ " transitive += [ctx.rule.attr.dep[PCollector].visited]",
+ " if hasattr(ctx.rule.attr, '_a_attr') and ctx.rule.attr._a_attr:",
+ " transitive += [ctx.rule.attr._a_attr[PCollector].visited] ",
+ " visited = depset([target.label], transitive = transitive, )",
+ " return [PCollector(visited = visited)]",
+ "acollect = aspect(_rcollect, attr_aspects = ['*'], required_aspect_providers = [PAspect])",
+ "def _rimpl(ctx):",
+ " pass",
+ "r0 = rule(_rimpl)",
+ "r = rule(_rimpl, attrs = { 'dep' : attr.label(aspects = [a]) })");
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'r0', 'r')",
+ "r0(name = 'referenced_from_aspect_only')",
+ "r0(name = 'bar')",
+ "r(name = 'baz', dep = ':bar')");
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("//test:aspect.bzl%acollect"), "//test:baz");
+ ConfiguredAspect configuredAspect =
+ Iterables.getOnlyElement(analysisResult.getAspects()).getConfiguredAspect();
+ SkylarkKey pCollector = new SkylarkKey(Label.parseAbsolute("//test:aspect.bzl"), "PCollector");
+ Info collector = configuredAspect.get(pCollector);
+ assertThat(((SkylarkNestedSet) collector.getValue("visited")).toCollection())
+ .containsExactly(
+ Label.parseAbsolute("//test:referenced_from_aspect_only"),
+ Label.parseAbsolute("//test:bar"),
+ Label.parseAbsolute("//test:baz"));
+ }
+
+ @Test
+ // This test verifies that aspects which are defined natively and exported for use in skylark
+ // can be referenced at the top level using the --aspects flag. For ease of testing,
+ // apple_common.objc_proto_aspect is used as an example.
+ public void testTopLevelSkylarkObjcProtoAspect() throws Exception {
+ scratch.file("test_skylark/BUILD");
+ scratch.file(
+ "test_skylark/top_level_stub.bzl",
+ "top_level_aspect = apple_common.objc_proto_aspect",
+ "",
+ "def top_level_stub_impl(ctx):",
+ " return struct()",
+ "top_level_stub = rule(",
+ " top_level_stub_impl,",
+ " attrs = {",
+ " 'deps': attr.label_list(),",
+ " },",
+ " fragments = ['apple'],",
+ ")");
+
+ scratch.file(
+ "x/BUILD",
+ "proto_library(",
+ " name = 'protos',",
+ " srcs = ['data.proto'],",
+ ")",
+ "objc_proto_library(",
+ " name = 'x',",
+ " deps = [':protos'],",
+ " portable_proto_filters = ['data_filter.pbascii'],",
+ ")");
+
+ scratch.file(
+ "bin/BUILD",
+ "load('//test_skylark:top_level_stub.bzl', 'top_level_stub')",
+ "top_level_stub(",
+ " name = 'link_target',",
+ " deps = ['//x:x'],",
+ ")");
+
+ useConfiguration(MockObjcSupport.requiredObjcCrosstoolFlags().toArray(new String[1]));
+ AnalysisResult analysisResult =
+ update(
+ ImmutableList.of("test_skylark/top_level_stub.bzl%top_level_aspect"),
+ "//bin:link_target");
+ ConfiguredAspect configuredAspect =
+ Iterables.getOnlyElement(analysisResult.getAspects()).getConfiguredAspect();
+
+ ObjcProtoProvider objcProtoProvider =
+ (ObjcProtoProvider) configuredAspect.get(ObjcProtoProvider.SKYLARK_CONSTRUCTOR.getKey());
+ assertThat(objcProtoProvider).isNotNull();
+ }
+
+ /** SkylarkAspectTest with "keep going" flag */
+ @RunWith(JUnit4.class)
+ public static final class WithKeepGoing extends SkylarkDefinedAspectsTest {
+ @Override
+ protected FlagBuilder defaultFlags() {
+ return new FlagBuilder().with(Flag.KEEP_GOING);
+ }
+
+ @Override
+ protected boolean keepGoing() {
+ return true;
+ }
+ }
+}