blob: ab610b4cd4a37745714de732fdbd4f7215949387 [file] [log] [blame]
// Copyright 2025 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.starlark;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
import static com.google.devtools.build.lib.packages.Attribute.attr;
import static com.google.devtools.build.lib.packages.BuildType.LABEL;
import static java.util.stream.Collectors.joining;
import static org.junit.Assert.assertThrows;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.analysis.ConfiguredAspect;
import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
import com.google.devtools.build.lib.analysis.RuleDefinition;
import com.google.devtools.build.lib.analysis.ViewCreationFailedException;
import com.google.devtools.build.lib.analysis.config.BuildOptions;
import com.google.devtools.build.lib.analysis.config.Fragment;
import com.google.devtools.build.lib.analysis.config.FragmentOptions;
import com.google.devtools.build.lib.analysis.config.RequiresOptions;
import com.google.devtools.build.lib.analysis.util.AnalysisTestCase;
import com.google.devtools.build.lib.analysis.util.DummyTestFragment;
import com.google.devtools.build.lib.analysis.util.DummyTestFragment.DummyTestOptions;
import com.google.devtools.build.lib.analysis.util.MockRule;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.packages.AspectClass;
import com.google.devtools.build.lib.packages.Attribute.LabelLateBoundDefault;
import com.google.devtools.build.lib.packages.StarlarkAspectClass;
import com.google.devtools.build.lib.skyframe.AspectKeyCreator.AspectKey;
import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
import java.util.Map;
import net.starlark.java.eval.Sequence;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Tests for Starlark aspects {@code attr_aspects} function. */
@RunWith(JUnit4.class)
public final class StarlarkAspectAttrAspectsFunctionTest extends AnalysisTestCase {
private void createTestDefs(String propagationAttrsFunction, String propagationPredicateFunction)
throws Exception {
scratch.file("test/BUILD");
scratch.file(
"test/defs.bzl",
String.format(
"""
AspectInfo = provider()
def _rule_impl(ctx):
pass
simple_rule = rule(
implementation = _rule_impl,
attrs = {
'deps_1': attr.label_list(),
'deps_2': attr.label_list(),
'_tool': attr.label(default = Label('//pkg1:rule_tool'))},
)
tool_rule = rule(
implementation = _rule_impl,
)
%s
%s
def _aspect_impl(target, ctx):
res = ['cmdline_aspect on ' + str(target.label)]
rule_attr = ctx.rule.attr
for dep in (getattr(rule_attr, 'deps_1', []) + getattr(rule_attr, 'deps_2', [])):
if AspectInfo in dep:
res.extend(dep[AspectInfo].res)
if hasattr(rule_attr, '_tool') and AspectInfo in rule_attr._tool:
res.extend(rule_attr._tool[AspectInfo].res)
return [AspectInfo(res = res)]
cmdline_aspect = aspect(
implementation = _aspect_impl,
attr_aspects = _propagation_attrs,
propagation_predicate = _propagation_predicate,
)
""",
propagationAttrsFunction, propagationPredicateFunction));
}
private void createTestDefs(String propagationAttrsFunction) throws Exception {
createTestDefs(
propagationAttrsFunction,
"""
def _propagation_predicate(ctx):
return True
""");
}
private void createTestPackages() throws Exception {
scratch.file(
"config_setting/BUILD", "config_setting(name='defines', values={'define': 'foo=1'})");
scratch.file(
"pkg1/BUILD",
"""
load('//test:defs.bzl', 'simple_rule', 'tool_rule')
simple_rule(name = 't1', deps_1 = [':d1'], deps_2 = [':d2'])
simple_rule(name = 'd1', deps_1 = [':d11'], deps_2 = [':d12'])
simple_rule(name = 'd2', deps_2 = [':d22'])
simple_rule(name = 'd11')
simple_rule(name = 'd12')
simple_rule(name = 'd22')
tool_rule(name = 'rule_tool')
simple_rule(
name = 'with_selects',
deps_1 = select({
'//config_setting:defines': [':d1'],
'//conditions:default': ['d2'],
})
)
simple_rule(name = 't2', deps_1 = [':d1', ':d11'], deps_2 = [':d2'])
""");
}
@Test
public void selectedAttrsReturned_aspectPropagatesAlongReturnedAttrs() throws Exception {
createTestDefs(
"""
def _propagation_attrs(ctx):
if ctx.rule.label == Label('//pkg1:t1'):
attr_aspects = []
for attr_name in dir(ctx.rule.attr):
attr_value = getattr(ctx.rule.attr, attr_name).value
if type(attr_value) == type(Label("//foo")):
attr_aspects.append(attr_name)
if type(attr_value) == type([]):
if len(attr_value) > 0 and type(attr_value[0]) == type(Label("//foo")):
attr_aspects.append(attr_name)
return attr_aspects
else:
return []
""");
createTestPackages();
var analysisResult = update(ImmutableList.of("//test:defs.bzl%cmdline_aspect"), "//pkg1:t1");
assertThat(getFormattedAspectKeys("cmdline_aspect"))
.containsExactly(
"cmdline_aspect on //pkg1:t1",
"cmdline_aspect on //pkg1:d1",
"cmdline_aspect on //pkg1:d2",
"cmdline_aspect on //pkg1:rule_tool");
var aspectResult = getAspectResult(analysisResult.getAspectsMap(), "cmdline_aspect");
assertThat(aspectResult)
.containsExactly(
"cmdline_aspect on @@//pkg1:t1",
"cmdline_aspect on @@//pkg1:d1",
"cmdline_aspect on @@//pkg1:d2",
"cmdline_aspect on @@//pkg1:rule_tool");
}
@Test
public void wildCardAttrReturned_buildFails() throws Exception {
createTestDefs(
"""
def _propagation_attrs(ctx):
return ['*']
""");
createTestPackages();
reporter.removeHandler(failFastHandler);
assertThrows(
ViewCreationFailedException.class,
() -> update(ImmutableList.of("//test:defs.bzl%cmdline_aspect"), "//pkg1:t1"));
assertContainsEvent("'*' is not allowed in 'attr_aspects' list");
}
@Test
public void invalidReturnValue_buildFails() throws Exception {
createTestDefs(
"""
def _propagation_attrs(ctx):
return [44, 'foo']
""");
createTestPackages();
reporter.removeHandler(failFastHandler);
assertThrows(
ViewCreationFailedException.class,
() -> update(ImmutableList.of("//test:defs.bzl%cmdline_aspect"), "//pkg1:t1"));
assertContainsEvent("at index 0 of attr_aspects, got element of type int, want string");
}
@Test
public void withPropagationPredicate_aspectPropagatedToSatisfyingTargets() throws Exception {
createTestDefs(
"""
def _propagation_attrs(ctx):
return ['deps_1']
""",
"""
def _propagation_predicate(ctx):
if ctx.rule.label == Label('//pkg1:d1'):
return False
return True
""");
createTestPackages();
var analysisResult = update(ImmutableList.of("//test:defs.bzl%cmdline_aspect"), "//pkg1:t2");
assertThat(getFormattedAspectKeys("cmdline_aspect"))
.containsExactly("cmdline_aspect on //pkg1:t2", "cmdline_aspect on //pkg1:d11");
var aspectAResult = getAspectResult(analysisResult.getAspectsMap(), "cmdline_aspect");
assertThat(aspectAResult)
.containsExactly("cmdline_aspect on @@//pkg1:t2", "cmdline_aspect on @@//pkg1:d11");
}
@Test
public void aspectApplyToGeneratingRule_attrAspectsFuncRunOnGeneratingRule() throws Exception {
scratch.file("test/BUILD");
scratch.file(
"test/defs.bzl",
"""
AspectInfo = provider()
def _propagation_attrs(ctx):
if ctx.rule.label == Label('//pkg1:target_with_output'):
if ctx.rule.attr.out.value == Label('//pkg1:my_out.txt'):
return ['deps']
return []
def _aspect_impl(target, ctx):
res = ['generating_rule_aspect on ' + str(target.label)]
rule_attr = ctx.rule.attr
for dep in getattr(rule_attr, 'deps', []):
if AspectInfo in dep:
res.extend(dep[AspectInfo].res)
return [AspectInfo(res = res)]
generating_rule_aspect = aspect(
implementation = _aspect_impl,
attr_aspects = _propagation_attrs,
apply_to_generating_rules = True,
)
def _out_rule_impl(ctx):
if ctx.outputs.out:
ctx.actions.write(ctx.outputs.out, 'hi')
return []
out_rule = rule(
implementation = _out_rule_impl,
attrs = {'deps': attr.label_list(), 'out': attr.output()},
)
""");
scratch.file(
"pkg1/BUILD",
"""
load('//test:defs.bzl', 'out_rule')
out_rule(name = 'target_with_output', out = 'my_out.txt', deps = [':d1'])
out_rule(name = 'd1')
""");
var analysisResult =
update(ImmutableList.of("//test:defs.bzl%generating_rule_aspect"), "//pkg1:my_out.txt");
assertThat(getFormattedAspectKeys("generating_rule_aspect"))
.containsExactly(
"generating_rule_aspect on //pkg1:my_out.txt",
"generating_rule_aspect on //pkg1:target_with_output",
"generating_rule_aspect on //pkg1:d1");
var aspectResult = getAspectResult(analysisResult.getAspectsMap(), "generating_rule_aspect");
assertThat(aspectResult)
.containsExactly(
"generating_rule_aspect on @@//pkg1:target_with_output",
"generating_rule_aspect on @@//pkg1:d1");
}
@Test
public void attrWithSelect_availableToAttrAspectsFunc() throws Exception {
createTestDefs(
"""
def _propagation_attrs(ctx):
deps_1 = ctx.rule.attr.deps_1.value
if len(deps_1) == 1 and deps_1[0] == Label('//pkg1:d1'):
return ['deps_1']
return []
""");
createTestPackages();
useConfiguration("--define=foo=1");
var analysisResult =
update(ImmutableList.of("//test:defs.bzl%cmdline_aspect"), "//pkg1:with_selects");
assertThat(getFormattedAspectKeys("cmdline_aspect"))
.containsExactly("cmdline_aspect on //pkg1:with_selects", "cmdline_aspect on //pkg1:d1");
var aspectResult = getAspectResult(analysisResult.getAspectsMap(), "cmdline_aspect");
assertThat(aspectResult)
.containsExactly(
"cmdline_aspect on @@//pkg1:with_selects", "cmdline_aspect on @@//pkg1:d1");
}
@Test
public void computedDefaultAttr_availableToAttrAspectsFunc() throws Exception {
scratch.file("test/BUILD");
scratch.file(
"test/defs.bzl",
"""
AspectInfo = provider()
def _propagation_attrs(ctx):
if hasattr(ctx.rule.attr, '_computed'):
cmp = ctx.rule.attr._computed.value
if Label('//pkg1:my_prefix01') in cmp and Label('//pkg1:my_prefix02') in cmp:
return ['_computed']
return []
def _aspect_impl(target, ctx):
res = ['cmdline_aspect on ' + str(target.label)]
rule_attr = ctx.rule.attr
for dep in getattr(rule_attr, '_computed', []):
if AspectInfo in dep:
res.extend(dep[AspectInfo].res)
return [AspectInfo(res = res)]
cmdline_aspect = aspect(
implementation = _aspect_impl,
attr_aspects = _propagation_attrs,
)
def _compute_attr(prefix):
return [
Label("//pkg1:" + prefix + "01"),
Label("//pkg1:" + prefix + "02"),
]
def _rule_impl(ctx):
pass
computed_default_rule = rule(
implementation = _rule_impl,
attrs = {
"prefix": attr.string(default = "default_prefix"),
"_computed": attr.label_list(default = _compute_attr),
},
)
simple_rule = rule(
implementation = _rule_impl,
)
""");
scratch.file(
"pkg1/BUILD",
"""
load('//test:defs.bzl', 'computed_default_rule', 'simple_rule')
computed_default_rule(name = 'computed_default_target', prefix = 'my_prefix')
simple_rule(name = 'my_prefix01')
simple_rule(name = 'my_prefix02')
""");
var analysisResult =
update(
ImmutableList.of("//test:defs.bzl%cmdline_aspect"), "//pkg1:computed_default_target");
assertThat(getFormattedAspectKeys("cmdline_aspect"))
.containsExactly(
"cmdline_aspect on //pkg1:computed_default_target",
"cmdline_aspect on //pkg1:my_prefix01",
"cmdline_aspect on //pkg1:my_prefix02");
var aspectResult = getAspectResult(analysisResult.getAspectsMap(), "cmdline_aspect");
assertThat(aspectResult)
.containsExactly(
"cmdline_aspect on @@//pkg1:computed_default_target",
"cmdline_aspect on @@//pkg1:my_prefix01",
"cmdline_aspect on @@//pkg1:my_prefix02");
}
/** A custom {@link FragmentOptions} for the latebound attribute test. */
public static class TestOptions extends FragmentOptions {}
/** The {@link Fragment} that contains the options. */
@RequiresOptions(options = {TestOptions.class})
public static final class TestFragment extends Fragment {
private final BuildOptions buildOptions;
public TestFragment(BuildOptions buildOptions) {
this.buildOptions = buildOptions;
}
// Getter required to satisfy AutoCodec.
public BuildOptions getBuildOptions() {
return buildOptions;
}
public Label getDep() {
return Label.parseCanonicalUnchecked("//pkg1:latebound_dep");
}
}
@Test
public void lateBoundAttributes_availableToAttrAspectsFunc() throws Exception {
RuleDefinition lateBoundDepRule =
(MockRule)
() ->
MockRule.define(
"rule_with_latebound_attr",
(builder, env) ->
builder
.add(
attr(":latebound_attr", LABEL)
.value(
LabelLateBoundDefault.fromTargetConfiguration(
TestFragment.class,
null,
(rule, attributes, testConfig) -> testConfig.getDep())))
.requiresConfigurationFragments(TestFragment.class));
ConfiguredRuleClassProvider.Builder builder = new ConfiguredRuleClassProvider.Builder();
TestRuleClassProvider.addStandardRules(builder);
builder.addRuleDefinition(lateBoundDepRule);
builder.addConfigurationFragment(TestFragment.class);
useRuleClassProvider(builder.build());
scratch.file("test/BUILD");
scratch.file(
"test/defs.bzl",
"""
AspectInfo = provider()
def _propagation_attrs(ctx):
if hasattr(ctx.rule.attr, '_latebound_attr'):
if ctx.rule.attr._latebound_attr.value == Label('//pkg1:latebound_dep'):
return ['_latebound_attr']
return []
def _aspect_impl(target, ctx):
res = ['cmdline_aspect on ' + str(target.label)]
rule_attr = ctx.rule.attr
if hasattr(rule_attr, '_latebound_attr'):
if AspectInfo in rule_attr._latebound_attr:
res.extend(rule_attr._latebound_attr[AspectInfo].res)
return [AspectInfo(res = res)]
cmdline_aspect = aspect(
implementation = _aspect_impl,
attr_aspects = _propagation_attrs
)
def _rule_impl(ctx):
pass
simple_rule = rule(
implementation = _rule_impl,
)
""");
scratch.file(
"pkg1/BUILD",
"""
load('//test:defs.bzl', 'simple_rule')
rule_with_latebound_attr(name = 'latebound_target')
simple_rule(name = 'latebound_dep')
""");
var analysisResult =
update(ImmutableList.of("//test:defs.bzl%cmdline_aspect"), "//pkg1:latebound_target");
assertThat(getFormattedAspectKeys("cmdline_aspect"))
.containsExactly(
"cmdline_aspect on //pkg1:latebound_target", "cmdline_aspect on //pkg1:latebound_dep");
var aspectResult = getAspectResult(analysisResult.getAspectsMap(), "cmdline_aspect");
assertThat(aspectResult)
.containsExactly(
"cmdline_aspect on @@//pkg1:latebound_target",
"cmdline_aspect on @@//pkg1:latebound_dep");
}
@Test
public void toolAttr_correctlyIdentified() throws Exception {
scratch.file("test/BUILD");
scratch.file(
"test/defs.bzl",
"""
AspectInfo = provider()
def _propagation_attrs(ctx):
attr_aspects = []
for attr_name in dir(ctx.rule.attr):
if getattr(ctx.rule.attr, attr_name).is_tool:
attr_aspects.append(attr_name)
return attr_aspects
def _aspect_impl(target, ctx):
res = ['cmdline_aspect on ' + str(target.label)]
rule_attr = ctx.rule.attr
if hasattr(rule_attr, '_tool_1') and AspectInfo in rule_attr._tool_1:
res.extend(rule_attr._tool_1[AspectInfo].res)
if hasattr(rule_attr, '_tool_2') and AspectInfo in rule_attr._tool_2:
res.extend(rule_attr._tool_2[AspectInfo].res)
if hasattr(rule_attr, '_non_tool') and AspectInfo in rule_attr._non_tool:
res.extend(rule_attr._non_tool[AspectInfo].res)
return [AspectInfo(res = res)]
cmdline_aspect = aspect(
implementation = _aspect_impl,
attr_aspects = _propagation_attrs
)
def _rule_impl(ctx):
pass
rule_with_tool = rule(
implementation = _rule_impl,
attrs = {
'_tool_1': attr.label(
default = Label('//pkg1:tool_1'), flags = ['IS_TOOL_DEPENDENCY']),
'_tool_2': attr.label(default = Label('//pkg1:tool_2'), cfg = 'exec'),
'_non_tool': attr.label(default = Label('//pkg1:non_tool')),
},
)
simple_rule = rule(
implementation = _rule_impl,
)
""");
scratch.file(
"pkg1/BUILD",
"""
load('//test:defs.bzl', 'rule_with_tool', 'simple_rule')
rule_with_tool(name = 'main_target')
simple_rule(name = 'tool_1')
simple_rule(name = 'tool_2')
simple_rule(name = 'non_tool')
""");
var analysisResult =
update(ImmutableList.of("//test:defs.bzl%cmdline_aspect"), "//pkg1:main_target");
assertThat(getFormattedAspectKeys("cmdline_aspect"))
.containsExactly(
"cmdline_aspect on //pkg1:main_target",
"cmdline_aspect on //pkg1:tool_1",
"cmdline_aspect on //pkg1:tool_2");
var aspectResult = getAspectResult(analysisResult.getAspectsMap(), "cmdline_aspect");
assertThat(aspectResult)
.containsExactly(
"cmdline_aspect on @@//pkg1:main_target",
"cmdline_aspect on @@//pkg1:tool_1",
"cmdline_aspect on @@//pkg1:tool_2");
}
@Test
public void attrInitializer_availableToAttrAspectsFunc() throws Exception {
scratch.file("test/BUILD");
scratch.file(
"test/defs.bzl",
"""
AspectInfo = provider()
def _propagation_attrs(ctx):
if hasattr(ctx.rule.attr, 'deps'):
deps = ctx.rule.attr.deps.value
if Label('//pkg1:added') in deps and Label('//pkg1:initial') in deps:
return ['deps']
return []
def _aspect_impl(target, ctx):
res = ['cmdline_aspect on ' + str(target.label)]
rule_attr = ctx.rule.attr
for dep in getattr(rule_attr, 'deps', []):
if AspectInfo in dep:
res.extend(dep[AspectInfo].res)
return [AspectInfo(res = res)]
cmdline_aspect = aspect(
implementation = _aspect_impl,
attr_aspects = _propagation_attrs
)
def _rule_impl(ctx):
pass
def _initializer(name, deps = []):
return {"deps": deps + ["//pkg1:added"]}
rule_with_initializer = rule(
implementation = _rule_impl,
initializer = _initializer,
attrs = {
"deps": attr.label_list(),
},
)
simple_rule = rule(
implementation = _rule_impl,
)
""");
scratch.file(
"pkg1/BUILD",
"""
load('//test:defs.bzl', 'rule_with_initializer', 'simple_rule')
rule_with_initializer(name = 'main_target', deps = [":initial"])
simple_rule(name = 'initial')
simple_rule(name = 'added')
""");
useConfiguration("--experimental_rule_extension_api");
var analysisResult =
update(ImmutableList.of("//test:defs.bzl%cmdline_aspect"), "//pkg1:main_target");
assertThat(getFormattedAspectKeys("cmdline_aspect"))
.containsExactly(
"cmdline_aspect on //pkg1:main_target",
"cmdline_aspect on //pkg1:added",
"cmdline_aspect on //pkg1:initial");
var aspectResult = getAspectResult(analysisResult.getAspectsMap(), "cmdline_aspect");
assertThat(aspectResult)
.containsExactly(
"cmdline_aspect on @@//pkg1:main_target",
"cmdline_aspect on @@//pkg1:added",
"cmdline_aspect on @@//pkg1:initial");
}
@Test
public void aspectOnAspect_eachAspectPropagatesSeparately() throws Exception {
scratch.file("test/BUILD");
scratch.file(
"test/defs.bzl",
"""
AProv = provider()
BProv = provider()
CProv = provider()
def _a_propagation_attrs(ctx):
return ['dep_1', 'dep_2']
def b_propagation_attrs(ctx):
return ['dep_1']
def _c_propagation_attrs(ctx):
return ['dep_2']
def _aspect_a_impl(target, ctx):
current_res = 'aspect_a on %s' % target.label
if BProv in target:
current_res += ' with BProv'
if CProv in target:
current_res += ' with CProv'
res = [current_res]
if ctx.rule.attr.dep_1 and AProv in ctx.rule.attr.dep_1:
res.extend(ctx.rule.attr.dep_1[AProv].res)
if ctx.rule.attr.dep_2 and AProv in ctx.rule.attr.dep_2:
res.extend(ctx.rule.attr.dep_2[AProv].res)
return [AProv(res = res)]
def _aspect_b_impl(target, ctx):
return [BProv(res = 'aspect_b on %s' % target.label)]
def _aspect_c_impl(target, ctx):
return [CProv(res = 'aspect_c on %s' % target.label)]
aspect_a = aspect(
implementation = _aspect_a_impl,
attr_aspects = _a_propagation_attrs,
required_aspect_providers = [[BProv], [CProv]],
)
aspect_b = aspect(
implementation = _aspect_b_impl,
attr_aspects = b_propagation_attrs,
provides = [BProv],
)
aspect_c = aspect(
implementation = _aspect_c_impl,
attr_aspects = _c_propagation_attrs,
provides = [CProv],
)
def _rule_impl(ctx):
return []
my_rule = rule(
implementation = _rule_impl,
attrs = {
"dep_1": attr.label(),
"dep_2": attr.label(),
},
)
""");
scratch.file(
"pkg1/BUILD",
"""
load('//test:defs.bzl', 'my_rule')
my_rule(name = 'main_target', dep_1 = ':dep_1', dep_2 = ':dep_2')
my_rule(name = 'dep_1')
my_rule(name = 'dep_2')
""");
var analysisResult =
update(
ImmutableList.of(
"//test:defs.bzl%aspect_c", "//test:defs.bzl%aspect_b", "//test:defs.bzl%aspect_a"),
"//pkg1:main_target");
assertThat(getFormattedAspectKeys("aspect_a"))
.containsExactly(
"aspect_a on //pkg1:main_target with base aspects: aspect_b,aspect_c",
"aspect_a on //pkg1:dep_1 with base aspects: aspect_b",
"aspect_a on //pkg1:dep_2 with base aspects: aspect_c");
assertThat(getFormattedAspectKeys("aspect_b"))
.containsExactly("aspect_b on //pkg1:main_target", "aspect_b on //pkg1:dep_1");
assertThat(getFormattedAspectKeys("aspect_c"))
.containsExactly("aspect_c on //pkg1:main_target", "aspect_c on //pkg1:dep_2");
var aspectAResult =
getAspectResult(analysisResult.getAspectsMap(), "aspect_a", "//pkg1:main_target", "AProv");
assertThat(aspectAResult)
.containsExactly(
"aspect_a on @@//pkg1:main_target with BProv with CProv",
"aspect_a on @@//pkg1:dep_1 with BProv",
"aspect_a on @@//pkg1:dep_2 with CProv");
}
@Test
public void requiredAspect_propagatesWithMainAspect() throws Exception {
scratch.file("test/BUILD");
scratch.file(
"test/defs.bzl",
"""
AProv = provider()
BProv = provider()
def _a_propagation_attrs(ctx):
return ['dep_1', 'dep_2']
def _aspect_a_impl(target, ctx):
current_res = 'aspect_a on %s' % target.label
if BProv in target:
current_res += ' with BProv'
res = [current_res]
if ctx.rule.attr.dep_1 and AProv in ctx.rule.attr.dep_1:
res.extend(ctx.rule.attr.dep_1[AProv].res)
if ctx.rule.attr.dep_2 and AProv in ctx.rule.attr.dep_2:
res.extend(ctx.rule.attr.dep_2[AProv].res)
return [AProv(res = res)]
def _aspect_b_impl(target, ctx):
return [BProv(res = 'aspect_b on %s' % target.label)]
aspect_b = aspect(
implementation = _aspect_b_impl,
attr_aspects = [],
provides = [BProv],
)
aspect_a = aspect(
implementation = _aspect_a_impl,
attr_aspects = _a_propagation_attrs,
requires = [aspect_b],
)
def _rule_impl(ctx):
return []
my_rule = rule(
implementation = _rule_impl,
attrs = {
"dep_1": attr.label(),
"dep_2": attr.label(),
},
)
""");
scratch.file(
"pkg1/BUILD",
"""
load('//test:defs.bzl', 'my_rule')
my_rule(name = 'main_target', dep_1 = ':dep_1', dep_2 = ':dep_2')
my_rule(name = 'dep_1')
my_rule(name = 'dep_2')
""");
var analysisResult = update(ImmutableList.of("//test:defs.bzl%aspect_a"), "//pkg1:main_target");
assertThat(getFormattedAspectKeys("aspect_a"))
.containsExactly(
"aspect_a on //pkg1:main_target with base aspects: aspect_b",
"aspect_a on //pkg1:dep_1 with base aspects: aspect_b",
"aspect_a on //pkg1:dep_2 with base aspects: aspect_b");
assertThat(getFormattedAspectKeys("aspect_b"))
.containsExactly(
"aspect_b on //pkg1:main_target",
"aspect_b on //pkg1:dep_1",
"aspect_b on //pkg1:dep_2");
var aspectAResult =
getAspectResult(analysisResult.getAspectsMap(), "aspect_a", "//pkg1:main_target", "AProv");
assertThat(aspectAResult)
.containsExactly(
"aspect_a on @@//pkg1:main_target with BProv",
"aspect_a on @@//pkg1:dep_1 with BProv",
"aspect_a on @@//pkg1:dep_2 with BProv");
}
private void createDormantDepsTest(String aspectDef) throws Exception {
scratch.file("test/BUILD");
scratch.file(
"test/defs.bzl",
String.format(
"""
ComponentInfo = provider(fields = ["components"])
def _component_impl(ctx):
current = struct(label=ctx.label, impl = ctx.attr.impl)
transitive = [d[ComponentInfo].components for d in ctx.attr.deps]
return [
ComponentInfo(components = depset(direct = [current], transitive = transitive)),
]
component = rule(
implementation = _component_impl,
attrs = {
"deps": attr.label_list(providers = [ComponentInfo]),
"impl": attr.dormant_label(),
},
provides = [ComponentInfo],
dependency_resolution_rule = True,
)
def _binary_impl(ctx):
return [DefaultInfo(files=depset(ctx.files._impls))]
def _materializer(ctx):
all = depset(transitive = [d[ComponentInfo].components for d in ctx.attr.components])
selected = [c.impl for c in all.to_list() if "yes" in str(c.label)]
return selected
binary = rule(
implementation = _binary_impl,
attrs = {
"components": attr.label_list(providers = [ComponentInfo], for_dependency_resolution = True),
"_impls": attr.label_list(materializer = _materializer),
"regular_deps": attr.label_list(),
})
def _rule_impl(ctx):
pass
simple_rule = rule(
implementation = _rule_impl,
)
%s
""",
aspectDef));
scratch.file(
"pkg1/BUILD",
"""
load("//test:defs.bzl", "component", "binary", "simple_rule")
component(name="a_yes", impl=":a_impl")
component(name="b_no", deps = [":c_yes", ":d_no"], impl=":b_impl")
component(name="c_yes", impl=":c_impl")
component(name="d_no", impl=":d_impl")
binary(name="bin", components=[":a_yes", ":b_no"], regular_deps = [":dep_1"])
[filegroup(name=x + "_impl", srcs=[x]) for x in ["a", "b", "c", "d"]]
simple_rule(name = 'dep_1')
""");
}
@Test
public void aspectOnMaterializingTarget_attrAspectsFuncUsed() throws Exception {
createDormantDepsTest(
"""
AspectInfo = provider()
def _propagation_attrs(ctx):
if ctx.rule.qualified_kind.rule_name == 'binary':
# the value of _impls should be None as it is not materialized yet
if ctx.rule.attr._impls.value != None:
fail("'_impls' should be None")
# the value of regular_deps should be available
if Label('//pkg1:dep_1') not in ctx.rule.attr.regular_deps.value:
fail("regular_deps should be available")
# the value of components should be available
components = ctx.rule.attr.components.value
if len(components) != 2 or Label('//pkg1:a_yes') not in components or Label('//pkg1:b_no') not in components:
fail("components should be available")
return ['_impls', 'components', 'regular_deps']
return []
def _aspect_impl(target, ctx):
res = ['cmdline_aspect on %s' % target.label]
deps = []
deps.extend(getattr(ctx.rule.attr, '_impls', []))
deps.extend(getattr(ctx.rule.attr, 'components', []))
deps.extend(getattr(ctx.rule.attr, 'regular_deps', []))
for dep in deps:
if AspectInfo in dep:
res += dep[AspectInfo].res
return [AspectInfo(res = res)]
cmdline_aspect = aspect(
implementation = _aspect_impl,
attr_aspects = _propagation_attrs,
)
""");
useConfiguration("--experimental_dormant_deps");
var analysisResult = update(ImmutableList.of("//test:defs.bzl%cmdline_aspect"), "//pkg1:bin");
assertThat(getFormattedAspectKeys("cmdline_aspect"))
.containsExactly(
"cmdline_aspect on //pkg1:bin",
"cmdline_aspect on //pkg1:dep_1",
"cmdline_aspect on //pkg1:a_yes",
"cmdline_aspect on //pkg1:b_no",
"cmdline_aspect on //pkg1:a_impl",
"cmdline_aspect on //pkg1:c_impl");
var aspectAResult =
getAspectResult(
analysisResult.getAspectsMap(), "cmdline_aspect", "//pkg1:bin", "AspectInfo");
assertThat(aspectAResult)
.containsExactly(
"cmdline_aspect on @@//pkg1:bin",
"cmdline_aspect on @@//pkg1:dep_1",
"cmdline_aspect on @@//pkg1:a_yes",
"cmdline_aspect on @@//pkg1:b_no",
"cmdline_aspect on @@//pkg1:a_impl",
"cmdline_aspect on @@//pkg1:c_impl");
}
@Test
public void aspectOnDependencyResolutionTargets_attrAspectsFuncUsed() throws Exception {
createDormantDepsTest(
"""
AspectInfo = provider()
def _propagation_attrs(ctx):
if ctx.rule.qualified_kind.rule_name == 'component':
# the value of impl should be available
impl_name = ctx.rule.label.name.split('_')[0] + '_impl'
impl_val = ctx.rule.attr.impl.value
if type(impl_val) != 'DormantDependency' or impl_val.label.name != impl_name:
fail("'impl' should be available")
# the value of deps should be available
if ctx.rule.label == Label('//pkg1:b_no'):
deps = ctx.rule.attr.deps.value
if Label('//pkg1:c_yes') not in deps or Label('//pkg1:d_no') not in deps:
fail("deps should be available")
elif len(ctx.rule.attr.deps.value) != 0:
fail("deps should be empty")
return ['deps', 'impl']
return ['components']
def _aspect_impl(target, ctx):
res = ['cmdline_aspect on %s' % target.label]
if hasattr(ctx.rule.attr, 'impl') and type(ctx.rule.attr.impl) != 'DormantDependency':
fail("impl should be a dormant dependency")
deps = []
deps.extend(getattr(ctx.rule.attr, 'deps', []))
deps.extend(getattr(ctx.rule.attr, 'components', []))
for dep in deps:
if AspectInfo in dep:
res += dep[AspectInfo].res
return [AspectInfo(res = res)]
cmdline_aspect = aspect(
implementation = _aspect_impl,
attr_aspects = _propagation_attrs,
)
""");
useConfiguration("--experimental_dormant_deps");
var analysisResult = update(ImmutableList.of("//test:defs.bzl%cmdline_aspect"), "//pkg1:bin");
assertThat(getFormattedAspectKeys("cmdline_aspect"))
.containsExactly(
"cmdline_aspect on //pkg1:bin",
"cmdline_aspect on //pkg1:a_yes",
"cmdline_aspect on //pkg1:b_no",
"cmdline_aspect on //pkg1:c_yes",
"cmdline_aspect on //pkg1:d_no");
var aspectResult =
getAspectResult(
analysisResult.getAspectsMap(), "cmdline_aspect", "//pkg1:bin", "AspectInfo");
assertThat(aspectResult)
.containsExactly(
"cmdline_aspect on @@//pkg1:bin",
"cmdline_aspect on @@//pkg1:a_yes",
"cmdline_aspect on @@//pkg1:b_no",
"cmdline_aspect on @@//pkg1:c_yes",
"cmdline_aspect on @@//pkg1:d_no");
}
@Test
public void allRuleAttributesAreAvailableInAttrAspectsFunc() throws Exception {
scratch.file("test/BUILD");
scratch.file(
"test/defs.bzl",
"""
attr_map = {
'bool': True,
'int': 100,
'int_list': [1, 2, 3],
'label': Label('//pkg1:dep_1'),
'label_keyed_string_dict': {Label('//pkg1:dep_1'): 'key1', Label('//pkg1:dep_2'): 'key2'},
'label_list': [Label('//pkg1:dep_1'), Label('//pkg1:dep_2')],
'output': Label('//pkg1:output.txt'),
'output_list': [Label('//pkg1:output_2.txt'), Label('//pkg1:output_3.txt')],
'string': 'string_value',
'string_dict': {'key1': 'value1', 'key2': 'value2'},
'string_keyed_label_dict': {'key1': Label('//pkg1:dep_1'), 'key2': Label('//pkg1:dep_2')},
'string_list': ['string_value_1', 'string_value_2'],
'string_list_dict': {'key1': ['string_value_1', 'string_value_2'], 'key2': ['string_value_3', 'string_value_4']},
}
def _propagation_attrs(ctx):
for attr_name, expected_val in attr_map.items():
if not hasattr(ctx.rule.attr, attr_name):
fail("'%s' is not an attribute of the rule" % attr_name)
actual_val = getattr(ctx.rule.attr, attr_name).value
if expected_val != actual_val:
fail("'%s' has wrong value: expected %s, got %s" % (attr_name, expected_val, actual_val))
return []
def _aspect_impl(target, ctx):
return []
cmdline_aspect = aspect(
implementation = _aspect_impl,
attr_aspects = _propagation_attrs,
)
def _rule_impl(ctx):
if ctx.outputs.output:
ctx.actions.write(ctx.outputs.output, 'hi')
if ctx.outputs.output_list:
ctx.actions.write(ctx.outputs.output_list[0], 'hi')
ctx.actions.write(ctx.outputs.output_list[1], 'hi')
return []
my_rule = rule(
implementation = _rule_impl,
attrs = {
"bool": attr.bool(),
"int": attr.int(),
"int_list": attr.int_list(),
"label": attr.label(),
"label_keyed_string_dict": attr.label_keyed_string_dict(),
"label_list": attr.label_list(),
"output": attr.output(),
"output_list": attr.output_list(),
"string": attr.string(),
"string_dict": attr.string_dict(),
"string_keyed_label_dict": attr.string_keyed_label_dict(),
"string_list": attr.string_list(),
"string_list_dict": attr.string_list_dict(),
})
""");
scratch.file(
"pkg1/BUILD",
"""
load("//test:defs.bzl", "my_rule")
my_rule(
name = 'main_target',
bool = True,
int = 100,
int_list = [1, 2, 3],
label = ':dep_1',
label_keyed_string_dict = {'//pkg1:dep_1': 'key1', '//pkg1:dep_2': 'key2'},
label_list = [':dep_1', ':dep_2'],
output = 'output.txt',
output_list = ['output_2.txt', 'output_3.txt'],
string = 'string_value',
string_dict = {'key1': 'value1', 'key2': 'value2'},
string_keyed_label_dict = {'key1': ':dep_1', 'key2': ':dep_2'},
string_list = ['string_value_1', 'string_value_2'],
string_list_dict = {'key1': ['string_value_1', 'string_value_2'], 'key2': ['string_value_3', 'string_value_4']},
)
my_rule(name = 'dep_1')
my_rule(name = 'dep_2')
""");
var unused = update(ImmutableList.of("//test:defs.bzl%cmdline_aspect"), "//pkg1:main_target");
assertThat(getFormattedAspectKeys("cmdline_aspect"))
.containsExactly("cmdline_aspect on //pkg1:main_target");
}
@Test
public void attributeWithTransition_availableInAttrAspectsFunc() throws Exception {
scratch.file("test/BUILD");
scratch.file(
"test/defs.bzl",
"""
def _propagation_attrs(ctx):
if ctx.rule.label.name != 'main_target':
return []
dep = ctx.rule.attr.dep.value
if not dep or type(dep) != type([]) or dep[0] != Label('//pkg1:dep_1'):
fail("dep is not available")
return ['dep']
def _aspect_impl(target, ctx):
return []
cmdline_aspect = aspect(
implementation = _aspect_impl,
attr_aspects = _propagation_attrs,
)
def _transition_impl(settings, attr):
return {
"t1": {"//command_line_option:foo" : "v1"},
"t2": {"//command_line_option:foo" : "v2"}
}
simple_transition = transition(
implementation = _transition_impl,
inputs = [],
outputs = ["//command_line_option:foo"]
)
def _rule_impl(ctx):
pass
my_rule = rule(
implementation = _rule_impl,
attrs = {
"dep": attr.label(cfg = simple_transition),
},
)
""");
scratch.file(
"pkg1/BUILD",
"""
load("//test:defs.bzl", "my_rule")
my_rule(name = 'main_target', dep = ':dep_1')
my_rule(name = 'dep_1')
""");
ConfiguredRuleClassProvider.Builder builder = new ConfiguredRuleClassProvider.Builder();
TestRuleClassProvider.addStandardRules(builder);
builder.addConfigurationFragment(DummyTestFragment.class);
useRuleClassProvider(builder.build());
useConfiguration("--foo=default");
var unused = update(ImmutableList.of("//test:defs.bzl%cmdline_aspect"), "//pkg1:main_target");
var aspectKeys =
getAspectKeys("cmdline_aspect").stream()
.map(
k ->
k.getLabel()
+ " with foo = "
+ k.getConfigurationKey().getOptions().get(DummyTestOptions.class).foo);
assertThat(aspectKeys)
.containsExactly(
"//pkg1:main_target with foo = default",
"//pkg1:dep_1 with foo = v1",
"//pkg1:dep_1 with foo = v2");
}
private ImmutableList<AspectKey> getAspectKeys(String aspectName) {
return skyframeExecutor.getEvaluator().getDoneValues().entrySet().stream()
.filter(
entry ->
entry.getKey() instanceof AspectKey
&& ((AspectKey) entry.getKey())
.getAspectClass()
.toString()
.equals("//test:defs.bzl%" + aspectName))
.map(e -> (AspectKey) e.getKey())
.collect(toImmutableList());
}
private String formatAspectKey(AspectKey aspectKey) {
if (aspectKey.getBaseKeys().isEmpty()) {
return Splitter.on("%").splitToList(aspectKey.getAspectClass().toString()).get(1)
+ " on "
+ aspectKey.getLabel();
}
String baseAspects =
aspectKey.getBaseKeys().stream()
.map(k -> Splitter.on("%").splitToList(k.getAspectClass().toString()).get(1))
.collect(joining(","));
return Splitter.on("%").splitToList(aspectKey.getAspectClass().toString()).get(1)
+ " on "
+ aspectKey.getLabel()
+ " with base aspects: "
+ baseAspects;
}
private ImmutableList<String> getFormattedAspectKeys(String aspectName) {
return skyframeExecutor.getEvaluator().getDoneValues().entrySet().stream()
.filter(
entry ->
entry.getKey() instanceof AspectKey
&& ((AspectKey) entry.getKey())
.getAspectClass()
.toString()
.equals("//test:defs.bzl%" + aspectName))
.map(e -> formatAspectKey((AspectKey) e.getKey()))
.collect(toImmutableList());
}
private Sequence<?> getAspectResult(
Map<AspectKey, ConfiguredAspect> aspectsMap, String aspectName) throws Exception {
return getAspectResult(aspectsMap, aspectName, null, "AspectInfo");
}
private Sequence<?> getAspectResult(
Map<AspectKey, ConfiguredAspect> aspectsMap,
String aspectName,
String targetLabel,
String providerName)
throws Exception {
for (Map.Entry<AspectKey, ConfiguredAspect> entry : aspectsMap.entrySet()) {
AspectClass aspectClass = entry.getKey().getAspectClass();
if (aspectClass instanceof StarlarkAspectClass starlarkAspectClass) {
String aspectExportedName = starlarkAspectClass.getExportedName();
if (aspectExportedName.equals(aspectName)
&& (targetLabel == null || entry.getKey().getLabel().toString().equals(targetLabel))) {
return getStarlarkProvider(entry.getValue(), "//test:defs.bzl", providerName)
.getValue("res", Sequence.class);
}
}
}
throw new AssertionError("Aspect result not found for aspect: " + aspectName);
}
}