// 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.OutputGroupProvider.INTERNAL_SUFFIX; import static org.junit.Assert.fail; import com.google.common.base.Function; 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.OutputGroupProvider; 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.SkylarkClassObject; import com.google.devtools.build.lib.packages.SkylarkClassObjectConstructor.SkylarkKey; import com.google.devtools.build.lib.rules.cpp.CppConfiguration; import com.google.devtools.build.lib.rules.java.Jvm; 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 javax.annotation.Nullable; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * Tests for Skylark aspects */ @RunWith(JUnit4.class) public class SkylarkAspectsTest extends AnalysisTestCase { protected boolean keepGoing() { return false; } private static final String LINE_SEPARATOR = System.lineSeparator(); @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).getConstructor().getKey()) .isEqualTo(fooKey); assertThat(configuredAspect.get(barKey).getConstructor().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).getConstructor().getKey()) .isEqualTo(fooKey); assertThat(configuredAspect.get(barKey).getConstructor().getKey()) .isEqualTo(barKey); } private Iterable<String> getAspectDescriptions(AnalysisResult analysisResult) { return transform( analysisResult.getAspects(), new Function<AspectValue, String>() { @Nullable @Override public String apply(AspectValue aspectValue) { return 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(), new Function<ConfiguredTarget, String>() { @Nullable @Override public String apply(ConfiguredTarget configuredTarget) { return 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(), new Function<Object, String>() { @Nullable @Override public String apply(Object 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', '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(), new Function<Object, String>() { @Nullable @Override public String apply(Object 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', 'my_rule')", "my_rule(name = 'xxx',)", "my_rule(name = 'yyy', dep = ':xxx')"); AnalysisResult analysisResult = update("//test:yyy"); ConfiguredTarget target = Iterables.getOnlyElement(analysisResult.getTargetsToBuild()); SkylarkClassObject 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(); OutputGroupProvider outputGroupProvider = OutputGroupProvider.get( aspectValue.getConfiguredAspect()); assertThat(outputGroupProvider).isNotNull(); NestedSet<Artifact> names = outputGroupProvider.getOutputGroup("my_result"); assertThat(names).isNotEmpty(); NestedSet<Artifact> expectedSet = OutputGroupProvider.get(getConfiguredTarget("//test:xxx")) .getOutputGroup(OutputGroupProvider.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(); OutputGroupProvider outputGroupProvider = OutputGroupProvider.get( aspectValue.getConfiguredAspect()); assertThat(outputGroupProvider).isNotNull(); NestedSet<Artifact> names = outputGroupProvider.getOutputGroup("my_result"); assertThat(names).isNotEmpty(); NestedSet<Artifact> expectedSet = OutputGroupProvider.get(getConfiguredTarget("//test:xxx")) .getOutputGroup(OutputGroupProvider.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(), new Function<ConfiguredTarget, String>() { @Nullable @Override public String apply(ConfiguredTarget configuredTarget) { return configuredTarget.getLabel().toString(); } })) .containsExactly("//test:xxx"); AspectValue aspectValue = analysisResult.getAspects().iterator().next(); OutputGroupProvider outputGroupProvider = OutputGroupProvider.get(aspectValue.getConfiguredAspect()); assertThat(outputGroupProvider).isNotNull(); NestedSet<Artifact> names = outputGroupProvider.getOutputGroup("my_result"); assertThat(names).isNotEmpty(); NestedSet<Artifact> expectedSet = OutputGroupProvider.get(getConfiguredTarget("//test:xxx")) .getOutputGroup(OutputGroupProvider.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(), new Function<ConfiguredTarget, String>() { @Nullable @Override public String apply(ConfiguredTarget configuredTarget) { return configuredTarget.getLabel().toString(); } })) .containsExactly("//test:xxx"); AspectValue aspectValue = analysisResult.getAspects().iterator().next(); OutputGroupProvider outputGroupProvider = OutputGroupProvider.get(aspectValue.getConfiguredAspect()); assertThat(outputGroupProvider).isNotNull(); NestedSet<Artifact> names = outputGroupProvider.getOutputGroup("my_result"); assertThat(names).isNotEmpty(); NestedSet<Artifact> expectedSet = OutputGroupProvider.get(getConfiguredTarget("//test:xxx")) .getOutputGroup(OutputGroupProvider.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', '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(), new Function<Object, String>() { @Nullable @Override public String apply(Object 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', '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', '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.new_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.new_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.new_file('g.txt')", " ctx.file_action(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.new_file(target.label.name + '_a1.txt')", " ctx.file_action(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"); OutputGroupProvider outputGroupProvider = OutputGroupProvider.get(Iterables.getOnlyElement(analysisResult.getTargetsToBuild())); assertThat(getOutputGroupContents(outputGroupProvider, "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.new_file(target.label.name + '_a1.txt')", " ctx.file_action(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"); OutputGroupProvider outputGroupProvider = OutputGroupProvider.get(Iterables.getOnlyElement(analysisResult.getTargetsToBuild())); assertThat(getOutputGroupContents(outputGroupProvider, "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.new_file(target.label.name + '_a1.txt')", " ctx.file_action(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.new_file(target.label.name + '_a2.txt')", " ctx.file_action(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"); OutputGroupProvider outputGroupProvider = OutputGroupProvider.get(Iterables.getOnlyElement(analysisResult.getTargetsToBuild())); assertThat(getOutputGroupContents(outputGroupProvider, "a1_group")) .containsExactly("test/base_a1.txt"); assertThat(getOutputGroupContents(outputGroupProvider, "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.new_file(target.label.name + '_a1.txt')", " ctx.file_action(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.new_file(target.label.name + '_a2.txt')", " ctx.file_action(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"); OutputGroupProvider outputGroupProvider = OutputGroupProvider.get(Iterables.getOnlyElement(analysisResult.getTargetsToBuild())); assertThat(getOutputGroupContents(outputGroupProvider, "a1_group")) .containsExactly("test/base_a1.txt"); assertThat(getOutputGroupContents(outputGroupProvider, "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.new_file(target.label.name + '_a1.txt')", " ctx.file_action(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.new_file(target.label.name + '_a2.txt')", " ctx.file_action(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(OutputGroupProvider outputGroupProvider, String groupName) { return Iterables.transform(outputGroupProvider.getOutputGroup(groupName), new Function<Artifact, String>() { @Override public String apply(Artifact artifact) { return 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 from //test:aspect.bzl is not an aspect"); } @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', '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', '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', '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.new_file('aspect-output-' + target.label.name)", " ctx.file_action(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', '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, new Function<Artifact, String>() { @Override public String apply(Artifact artifact) { return 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 sharedAttributeDefintionWithAspects() 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.new_file('dummy.txt')", " ctx.action(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(), new Function<Artifact, String>() { @Override public String apply(Artifact artifact) { return 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(), new Function<Object, String>() { @Nullable @Override public String apply(Object 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.new_file('a3.out')", " ctx.file_action(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"); } /** SkylarkAspectTest with "keep going" flag */ @RunWith(JUnit4.class) public static final class WithKeepGoing extends SkylarkAspectsTest { @Override protected FlagBuilder defaultFlags() { return new FlagBuilder().with(Flag.KEEP_GOING); } @Override protected boolean keepGoing() { return true; } } }