blob: 165328f365981a5709a2a4bf632ee661d52aed43 [file] [log] [blame]
// Copyright 2023 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.lib.analysis.starlark;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
import static com.google.devtools.build.lib.analysis.starlark.StarlarkSubrule.getRuleAttrName;
import static com.google.devtools.build.lib.skyframe.BzlLoadValue.keyForBuild;
import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.actions.Action;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.AspectValue;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.FilesToRunProvider;
import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
import com.google.devtools.build.lib.analysis.config.transitions.StarlarkExposedRuleTransitionFactory;
import com.google.devtools.build.lib.analysis.platform.ToolchainInfo;
import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
import com.google.devtools.build.lib.packages.Attribute;
import com.google.devtools.build.lib.packages.AttributeValueSource;
import com.google.devtools.build.lib.packages.Provider;
import com.google.devtools.build.lib.packages.StarlarkInfo;
import com.google.devtools.build.lib.packages.StarlarkProvider;
import com.google.devtools.build.lib.packages.StructImpl;
import com.google.devtools.build.lib.rules.java.JavaToolchainProvider;
import com.google.devtools.build.lib.starlark.util.BazelEvaluationTestCase;
import com.google.devtools.build.lib.starlarkbuildapi.StarlarkSubruleApi;
import com.google.devtools.build.lib.starlarkbuildapi.cpp.CppConfigurationApi;
import com.google.devtools.build.lib.testutil.TestConstants;
import net.starlark.java.eval.BuiltinFunction;
import net.starlark.java.eval.Sequence;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class StarlarkSubruleTest extends BuildViewTestCase {
private final BazelEvaluationTestCase ev = new BazelEvaluationTestCase("//subrule_testing:label");
private final BazelEvaluationTestCase evOutsideAllowlist =
new BazelEvaluationTestCase("//foo:bar");
@Test
public void testSubruleFunctionSymbol_notVisibleInBUILD() throws Exception {
scratch.file("foo/BUILD", "subrule");
checkLoadingPhaseError("//foo", "'subrule' is not defined");
}
@Test
// checks that 'subrule' symbol visibility in bzl files, not whether it's callable
public void testSubruleFunctionSymbol_isVisibleInBzl() throws Exception {
Object subruleFunction = ev.eval("subrule");
assertNoEvents();
assertThat(subruleFunction).isNotNull();
assertThat(subruleFunction).isInstanceOf(BuiltinFunction.class);
}
@Test
public void testSubruleInstantiation_inAllowlistedPackage_succeeds() throws Exception {
Object subrule = ev.eval("subrule(implementation = lambda : 0 )");
assertThat(subrule).isNotNull();
assertThat(subrule).isInstanceOf(StarlarkSubruleApi.class);
}
@Test
public void testSubrule_isCallableOnlyFromRuleOrAspectImplementation() throws Exception {
ev.execAndExport("x = subrule(implementation = lambda : 'dummy result')");
ev.checkEvalErrorContains("x can only be called from a rule or aspect implementation", "x()");
}
@Test
public void testSubrule_isCallableOnlyAfterExport() throws Exception {
ev.checkEvalErrorContains(
"Invalid subrule hasn't been exported by a bzl file",
"unexported = [subrule(implementation = lambda: None)]",
"unexported[0]()");
}
@Test
public void testSubrule_ruleMustDeclareSubrule() throws Exception {
scratch.file(
"subrule_testing/myrule.bzl",
"""
_my_subrule = subrule(implementation = lambda: "")
def _rule_impl(ctx):
_my_subrule()
my_rule = rule(implementation = _rule_impl)
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
AssertionError error =
assertThrows(AssertionError.class, () -> getConfiguredTarget("//subrule_testing:foo"));
assertThat(error)
.hasMessageThat()
.contains(
"Error in _my_subrule: rule 'my_rule' must declare '_my_subrule' in" + " 'subrules'");
}
@Test
public void testSubrule_childAndParentSubrules() throws Exception {
scratch.file(
"subrule_testing/parent.bzl",
"_parent_subrule = subrule(implementation = lambda ctx: None)",
"def _rule_impl(ctx):",
" _parent_subrule()",
"parent_rule = rule(implementation = _rule_impl, extendable = True, subrules ="
+ " [_parent_subrule])");
scratch.file(
"subrule_testing/child.bzl",
"load('parent.bzl', 'parent_rule')",
"_child_subrule = subrule(implementation = lambda ctx: None)",
"def _rule_impl(ctx):",
" ctx.super()",
" _child_subrule()",
"child_rule = rule(implementation = _rule_impl, parent = parent_rule, subrules ="
+ " [_child_subrule])");
scratch.file(
"subrule_testing/BUILD",
"""
load("child.bzl", "child_rule")
child_rule(name = "foo")
""");
getConfiguredTarget("//subrule_testing:foo");
assertNoEvents();
}
@Test
public void testSubrule_childAndParent_canUseTheSameSubrule() throws Exception {
scratch.file(
"subrule_testing/parent.bzl",
"parent_subrule = subrule(",
" implementation = lambda ctx, _tool: None,",
" attrs = {'_tool': attr.label(default = ':tool')}",
")",
"def _rule_impl(ctx):",
" parent_subrule()",
"parent_rule = rule(implementation = _rule_impl, extendable = True, subrules ="
+ " [parent_subrule])");
scratch.file(
"subrule_testing/child.bzl",
"load('parent.bzl', 'parent_rule', 'parent_subrule')",
"def _rule_impl(ctx):",
" ctx.super()",
" parent_subrule()",
"child_rule = rule(implementation = _rule_impl, parent = parent_rule, subrules ="
+ " [parent_subrule])");
scratch.file(
"subrule_testing/BUILD",
"""
load("child.bzl", "child_rule")
child_rule(name = "foo")
filegroup(name = "tool")
""");
getConfiguredTarget("//subrule_testing:foo");
assertNoEvents();
}
@Test
public void testSubrule_childCantUseParentsSubrule() throws Exception {
scratch.file(
"subrule_testing/parent.bzl",
"parent_subrule = subrule(implementation = lambda ctx: None)",
"def _rule_impl(ctx):",
" parent_subrule()",
"parent_rule = rule(implementation = _rule_impl, extendable = True, subrules ="
+ " [parent_subrule])");
scratch.file(
"subrule_testing/child.bzl",
"""
load("parent.bzl", "parent_rule", "parent_subrule")
_child_subrule = subrule(implementation = lambda ctx: None)
def _rule_impl(ctx):
ctx.super()
parent_subrule()
child_rule = rule(implementation = _rule_impl, parent = parent_rule)
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("child.bzl", "child_rule")
child_rule(name = "foo")
""");
AssertionError error =
assertThrows(AssertionError.class, () -> getConfiguredTarget("//subrule_testing:foo"));
assertThat(error)
.hasMessageThat()
.contains(
"Error in parent_subrule: rule 'child_rule' must declare 'parent_subrule' in"
+ " 'subrules'");
}
@Test
public void testSubrule_parentCantUseChildsSubrule() throws Exception {
scratch.file(
"subrule_testing/parent.bzl",
"""
my_subrule = subrule(implementation = lambda ctx: None)
def _rule_impl(ctx):
my_subrule()
parent_rule = rule(implementation = _rule_impl, extendable = True)
""");
scratch.file(
"subrule_testing/child.bzl",
"load('parent.bzl', 'parent_rule', 'my_subrule')",
"def _rule_impl(ctx):",
" ctx.super()",
" my_subrule()",
"child_rule = rule(implementation = _rule_impl, parent = parent_rule, subrules ="
+ " [my_subrule])");
scratch.file(
"subrule_testing/BUILD",
"""
load("child.bzl", "child_rule")
child_rule(name = "foo")
""");
AssertionError error =
assertThrows(AssertionError.class, () -> getConfiguredTarget("//subrule_testing:foo"));
assertThat(error)
.hasMessageThat()
.contains(
"Error in my_subrule: rule 'parent_rule' must declare 'my_subrule' in 'subrules'");
}
@Test
public void testSubrule_aspectMustDeclareSubrule() throws Exception {
scratch.file(
"subrule_testing/myrule.bzl",
"""
_my_subrule = subrule(implementation = lambda ctx: "dummy aspect result")
def _aspect_impl(ctx, target):
res = _my_subrule()
_my_aspect = aspect(implementation = _aspect_impl)
my_rule = rule(
implementation = lambda ctx: [],
attrs = {"dep": attr.label(mandatory = True, aspects = [_my_aspect])},
)
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
java_library(name = "bar")
my_rule(
name = "foo",
dep = "bar",
)
""");
AssertionError error =
assertThrows(AssertionError.class, () -> getConfiguredTarget("//subrule_testing:foo"));
assertThat(error)
.hasMessageThat()
.contains(
"Error in _my_subrule: aspect '//subrule_testing:myrule.bzl%_my_aspect' must"
+ " declare '_my_subrule' in 'subrules'");
}
@Test
public void testSubruleCallingUndeclaredSibling_fails() throws Exception {
scratch.file(
"subrule_testing/myrule.bzl",
"""
def _subrule1_impl(ctx):
return "result from subrule1"
_my_subrule1 = subrule(implementation = _subrule1_impl)
def _subrule2_impl(ctx):
return _my_subrule1()
_my_subrule2 = subrule(implementation = _subrule2_impl)
MyInfo = provider()
def _rule_impl(ctx):
res = _my_subrule2()
return [MyInfo(result = res)]
my_rule = rule(_rule_impl, subrules = [_my_subrule2, _my_subrule1])
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
AssertionError error =
assertThrows(AssertionError.class, () -> getConfiguredTarget("//subrule_testing:foo"));
assertThat(error).isNotNull();
assertThat(error)
.hasMessageThat()
.contains("subrule _my_subrule2 must declare _my_subrule1 in 'subrules'");
}
@Test
public void testSubrule_implementationMustAcceptSubruleContext() throws Exception {
scratch.file(
"subrule_testing/myrule.bzl",
"""
_my_subrule = subrule(implementation = lambda: "")
def _rule_impl(ctx):
_my_subrule()
my_rule = rule(implementation = _rule_impl, subrules = [_my_subrule])
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
AssertionError error =
assertThrows(AssertionError.class, () -> getConfiguredTarget("//subrule_testing:foo"));
assertThat(error)
.hasMessageThat()
.contains("Error: lambda() does not accept positional arguments, but got 1");
}
@Test
public void testSubrule_isCallableFromRule() throws Exception {
scratch.file(
"subrule_testing/myrule.bzl",
"""
_my_subrule = subrule(implementation = lambda ctx: "dummy rule result")
MyInfo = provider()
def _rule_impl(ctx):
res = _my_subrule()
return MyInfo(result = res)
my_rule = rule(implementation = _rule_impl, subrules = [_my_subrule])
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
StructImpl provider =
getProvider("//subrule_testing:foo", "//subrule_testing:myrule.bzl", "MyInfo");
assertThat(provider).isNotNull();
assertThat(provider.getValue("result")).isEqualTo("dummy rule result");
}
@Test
public void testSubrule_isCallableFromAspect() throws Exception {
scratch.file(
"subrule_testing/myrule.bzl",
"""
_my_subrule = subrule(implementation = lambda ctx: "dummy aspect result")
MyInfo = provider()
def _aspect_impl(ctx, target):
res = _my_subrule()
return MyInfo(result = res)
_my_aspect = aspect(implementation = _aspect_impl, subrules = [_my_subrule])
my_rule = rule(
implementation = lambda ctx: [ctx.attr.dep[MyInfo]],
attrs = {"dep": attr.label(mandatory = True, aspects = [_my_aspect])},
)
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
java_library(name = "bar")
my_rule(
name = "foo",
dep = "bar",
)
""");
StructImpl provider =
getProvider("//subrule_testing:foo", "//subrule_testing:myrule.bzl", "MyInfo");
assertThat(provider).isNotNull();
assertThat(provider.getValue("result")).isEqualTo("dummy aspect result");
}
@Test
public void testSubrule_subruleContextExposesRuleLabel() throws Exception {
scratch.file(
"subrule_testing/myrule.bzl",
"""
def _subrule_impl(ctx):
return "called in: " + str(ctx.label)
_my_subrule = subrule(implementation = _subrule_impl)
MyInfo = provider()
def _rule_impl(ctx):
res = _my_subrule()
return MyInfo(result = res)
my_rule = rule(implementation = _rule_impl, subrules = [_my_subrule])
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
StructImpl provider =
getProvider("//subrule_testing:foo", "//subrule_testing:myrule.bzl", "MyInfo");
assertThat(provider).isNotNull();
assertThat(provider.getValue("result")).isEqualTo("called in: @@//subrule_testing:foo");
}
@Test
public void testSubrule_subruleContextExposesActionsApi() throws Exception {
scratch.file(
"subrule_testing/myrule.bzl",
"""
def _subrule_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name + ".out")
ctx.actions.write(out, "subrule file content")
return out
_my_subrule = subrule(implementation = _subrule_impl)
MyInfo = provider()
def _rule_impl(ctx):
res = _my_subrule()
return MyInfo(result = res)
my_rule = rule(implementation = _rule_impl, subrules = [_my_subrule])
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
Artifact artifact =
(Artifact)
getProvider("//subrule_testing:foo", "//subrule_testing:myrule.bzl", "MyInfo")
.getValue("result");
assertThat(artifact).isNotNull();
assertThat(artifact.getFilename()).isEqualTo("foo.out");
assertThat(((FileWriteAction) getGeneratingAction(artifact)).getFileContents())
.isEqualTo("subrule file content");
}
@Test
public void testSubruleActions_run_doesNotAllowSettingToolchain() throws Exception {
scratch.file(
"subrule_testing/myrule.bzl",
"""
def _subrule_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name + ".out")
ctx.actions.run(toolchain = "foo", executable = "/path/to/tool", outputs = [out])
_my_subrule = subrule(implementation = _subrule_impl)
MyInfo = provider()
def _rule_impl(ctx):
_my_subrule()
my_rule = rule(implementation = _rule_impl, subrules = [_my_subrule])
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
AssertionError error =
assertThrows(AssertionError.class, () -> getConfiguredTarget("//subrule_testing:foo"));
assertThat(error).hasMessageThat().contains("'toolchain' may not be specified in subrules");
}
@Test
public void testSubruleActions_run_doesNotAllowSettingExecGroup() throws Exception {
scratch.file(
"subrule_testing/myrule.bzl",
"""
def _subrule_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name + ".out")
ctx.actions.run(exec_group = "foo", executable = "/path/to/tool", outputs = [out])
_my_subrule = subrule(implementation = _subrule_impl)
MyInfo = provider()
def _rule_impl(ctx):
_my_subrule()
my_rule = rule(implementation = _rule_impl, subrules = [_my_subrule])
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
AssertionError error =
assertThrows(AssertionError.class, () -> getConfiguredTarget("//subrule_testing:foo"));
assertThat(error).hasMessageThat().contains("'exec_group' may not be specified in subrules");
}
@Test
public void testSubruleContext_cannotBeUsedOutsideImplementationFunction() throws Exception {
scratch.file(
"subrule_testing/myrule.bzl",
"""
def _subrule_impl(ctx):
return ctx
_my_subrule = subrule(implementation = _subrule_impl)
def _rule_impl(ctx):
subrule_ctx = _my_subrule()
subrule_ctx.label
my_rule = rule(implementation = _rule_impl, subrules = [_my_subrule])
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
AssertionError error =
assertThrows(AssertionError.class, () -> getConfiguredTarget("//subrule_testing:foo"));
assertThat(error)
.hasMessageThat()
.contains(
"Error: cannot access field or method 'label' of subrule context outside of its own"
+ " implementation function");
}
@Test
public void testRuleContext_cannotBeUsedInSubruleImplementation() throws Exception {
scratch.file(
"subrule_testing/myrule.bzl",
"""
def _subrule_impl(ctx, rule_ctx):
rule_ctx.label
_my_subrule = subrule(implementation = _subrule_impl)
def _rule_impl(ctx):
subrule_ctx = _my_subrule(ctx)
my_rule = rule(implementation = _rule_impl, subrules = [_my_subrule])
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
AssertionError error =
assertThrows(AssertionError.class, () -> getConfiguredTarget("//subrule_testing:foo"));
assertThat(error)
.hasMessageThat()
.contains(
"Error: cannot access field or method 'label' of rule context for"
+ " '//subrule_testing:foo' outside of its own rule implementation function");
}
@Test
public void testSubruleAttrs_publicAttributesAreNotPermitted() throws Exception {
ev.checkEvalErrorContains(
"illegal attribute name 'foo': subrules may only define private attributes",
"subrule(implementation = lambda: None, attrs = {'foo': attr.string()})");
}
@Test
public void testSubruleAttrs_computedDefaultsAreNotPermitted() throws Exception {
ev.checkEvalErrorContains(
"for attribute '_foo': subrules cannot define computed defaults.",
"subrule(",
" implementation = lambda: None,",
" attrs = {'_foo': attr.label(default = lambda: '')}",
")");
}
@Test
public void testSubruleAttrs_onlyLabelsOrLabelListsPermitted() throws Exception {
ev.checkEvalErrorContains(
"bad type for attribute '_foo': subrule attributes may only be label or lists of labels.",
"subrule(",
" implementation = lambda: None,",
" attrs = {'_foo': attr.int()}",
")");
}
@Test
public void testSubruleAttrs_attributeMustHaveDefaultValue() throws Exception {
ev.checkEvalErrorContains(
"for attribute '_foo': no default value specified",
"subrule(",
" implementation = lambda: None,",
" attrs = {'_foo': attr.label()}",
")");
}
@Test
public void testSubruleAttrs_cannotHaveStarlarkTransitions() throws Exception {
ev.checkEvalErrorContains(
"bad cfg for attribute '_foo': subrules may only have target/exec attributes.",
"my_transition = transition(implementation = lambda: None, inputs = [], outputs = [])",
"_my_subrule = subrule(",
" implementation = lambda: None,",
" attrs = {'_foo': attr.label(cfg = my_transition)}",
")");
}
@Test
public void testSubruleAttrs_cannotHaveNativeTransitions() throws Exception {
ev.update("native_transition", (StarlarkExposedRuleTransitionFactory) data -> null);
ev.checkEvalErrorContains(
"bad cfg for attribute '_foo': subrules may only have target/exec attributes.",
"_my_subrule = subrule(",
" implementation = lambda: None,",
" attrs = {'_foo': attr.label(cfg = native_transition)}",
")");
}
@Test
public void testSubruleAttrs_notVisibleInRuleCtx() throws Exception {
scratch.file("default/BUILD", "genrule(name = 'default', outs = ['a'], cmd = '')");
scratch.file(
"subrule_testing/myrule.bzl",
"""
def _subrule_impl(ctx):
return
_my_subrule = subrule(
implementation = _subrule_impl,
attrs = {"_foo": attr.label(default = "//default")},
)
MyInfo = provider()
def _rule_impl(ctx):
res = dir(ctx.attr)
return MyInfo(result = res)
my_rule = rule(implementation = _rule_impl, subrules = [_my_subrule])
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
ImmutableList<String> ruleClassAttributes =
getRuleContext(getConfiguredTarget("//subrule_testing:foo"))
.getRule()
.getRuleClassObject()
.getAttributes()
.stream()
.map(Attribute::getName)
.collect(toImmutableList());
ImmutableList<String> attributesVisibleToStarlark =
Sequence.cast(
getProvider("//subrule_testing:foo", "//subrule_testing:myrule.bzl", "MyInfo")
.getValue("result"),
String.class,
"")
.getImmutableList();
String ruleAttrName =
getRuleAttrName(
Label.parseCanonical("//subrule_testing:myrule.bzl"),
"_my_subrule",
"_foo",
AttributeValueSource.DIRECT);
assertThat(ruleClassAttributes).contains(ruleAttrName);
assertThat(attributesVisibleToStarlark).doesNotContain(ruleAttrName);
}
@Test
public void testSubruleAttrs_notVisibleInAspectCtx() throws Exception {
scratch.file("default/BUILD", "genrule(name = 'default', outs = ['a'], cmd = '')");
scratch.file(
"subrule_testing/myrule.bzl",
"""
_my_subrule = subrule(
implementation = lambda: None,
attrs = {"_foo": attr.label(default = "//default")},
)
MyInfo = provider()
def _aspect_impl(target, ctx):
res = dir(ctx.attr)
return MyInfo(result = res)
my_aspect = aspect(implementation = _aspect_impl, subrules = [_my_subrule])
def _rule_impl(ctx):
return ctx.attr.dep[MyInfo]
my_rule = rule(
implementation = _rule_impl,
attrs = {"dep": attr.label(aspects = [my_aspect])},
)
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(
name = "foo",
dep = "//default",
)
""");
ImmutableList<String> attributesVisibleToStarlark =
Sequence.cast(
getProvider("//subrule_testing:foo", "//subrule_testing:myrule.bzl", "MyInfo")
.getValue("result"),
String.class,
"")
.getImmutableList();
ImmutableMap<String, Attribute> aspectAttributes =
((AspectValue) getAspect("//subrule_testing:myrule.bzl%my_aspect"))
.getAspect()
.getDefinition()
.getAttributes();
String ruleAttrName =
getRuleAttrName(
Label.parseCanonical("//subrule_testing:myrule.bzl"),
"_my_subrule",
"_foo",
AttributeValueSource.DIRECT);
assertThat(aspectAttributes).containsKey(ruleAttrName);
assertThat(attributesVisibleToStarlark).doesNotContain(ruleAttrName);
}
@Test
public void testSubruleAttrs_overridingImplicitAttributeValueFails() throws Exception {
scratch.file("default/BUILD", "genrule(name = 'default', outs = ['a'], cmd = '')");
scratch.file(
"subrule_testing/myrule.bzl",
"""
def _subrule_impl(ctx, _foo):
return
_my_subrule = subrule(
implementation = _subrule_impl,
attrs = {"_foo": attr.label(default = "//default")},
)
def _rule_impl(ctx):
res = _my_subrule(_foo = "//override")
return []
my_rule = rule(implementation = _rule_impl, subrules = [_my_subrule])
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
AssertionError error =
assertThrows(AssertionError.class, () -> getConfiguredTarget("//subrule_testing:foo"));
assertThat(error)
.hasMessageThat()
.contains(
"Error in _my_subrule: got invalid named argument: '_foo' is an implicit dependency and"
+ " cannot be overridden");
}
@Test
public void testSubruleAttrs_implicitLabelDepsAreResolvedToTargets() throws Exception {
scratch.file(
"some/pkg/BUILD",
//
"genrule(name = 'tool', cmd = '', outs = ['tool.exe'])");
scratch.file(
"subrule_testing/myrule.bzl",
"""
def _subrule_impl(ctx, _tool):
return _tool
_my_subrule = subrule(
implementation = _subrule_impl,
attrs = {"_tool": attr.label(default = "//some/pkg:tool")},
)
MyInfo = provider()
def _rule_impl(ctx):
res = _my_subrule()
return MyInfo(result = res)
my_rule = rule(implementation = _rule_impl, subrules = [_my_subrule])
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
StructImpl provider =
getProvider("//subrule_testing:foo", "//subrule_testing:myrule.bzl", "MyInfo");
assertThat(provider).isNotNull();
Object value = provider.getValue("result");
assertThat(value).isInstanceOf(ConfiguredTarget.class);
assertThat(((ConfiguredTarget) value).getLabel().toString()).isEqualTo("//some/pkg:tool");
}
@Test
public void testSubruleAttrs_singleFileLabelAttributesAreResolvedToFile() throws Exception {
scratch.file(
"some/pkg/BUILD",
//
"genrule(name = 'tool', cmd = '', outs = ['tool.exe'])");
scratch.file(
"subrule_testing/myrule.bzl",
"""
def _subrule_impl(ctx, _tool):
return _tool
_my_subrule = subrule(
implementation = _subrule_impl,
attrs = {"_tool": attr.label(allow_single_file = True, default = "//some/pkg:tool")},
)
MyInfo = provider()
def _rule_impl(ctx):
res = _my_subrule()
return MyInfo(result = res)
my_rule = rule(implementation = _rule_impl, subrules = [_my_subrule])
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
StructImpl provider =
getProvider("//subrule_testing:foo", "//subrule_testing:myrule.bzl", "MyInfo");
assertThat(provider).isNotNull();
Object value = provider.getValue("result");
assertThat(value).isInstanceOf(Artifact.class);
assertThat(((Artifact) value).getRootRelativePathString()).isEqualTo("some/pkg/tool.exe");
}
@Test
public void testSubruleAttr_executableAttrIsPassedAsFilesToRun() throws Exception {
scratch.file(
"my/BUILD",
//
"cc_binary(name = 'tool')");
scratch.file(
"subrule_testing/myrule.bzl",
"""
def _subrule_impl(ctx, _tool):
return _tool
_my_subrule = subrule(
implementation = _subrule_impl,
attrs = {"_tool": attr.label(default = "//my:tool", executable = True, cfg = "exec")},
)
MyInfo = provider()
def _rule_impl(ctx):
res = _my_subrule()
return MyInfo(result = res)
my_rule = rule(implementation = _rule_impl, subrules = [_my_subrule])
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
Object result =
getProvider("//subrule_testing:foo", "//subrule_testing:myrule.bzl", "MyInfo")
.getValue("result");
assertThat(result).isInstanceOf(FilesToRunProvider.class);
assertThat(((FilesToRunProvider) result).getExecutable().getRootRelativePathString())
.isEqualTo("my/tool");
}
@Test
public void testSubruleAction_executableMustBeFilesToRunProvider() throws Exception {
scratch.file(
"my/BUILD",
//
"cc_binary(name = 'tool')");
scratch.file(
"subrule_testing/myrule.bzl",
"""
def _subrule_impl(ctx, _tool):
out = ctx.actions.declare_file(ctx.label.name + ".out")
ctx.actions.run(executable = _tool.executable, outputs = [out])
return out
_my_subrule = subrule(
implementation = _subrule_impl,
attrs = {"_tool": attr.label(default = "//my:tool", executable = True, cfg = "exec")},
)
MyInfo = provider()
def _rule_impl(ctx):
res = _my_subrule()
return MyInfo(result = res)
my_rule = rule(implementation = _rule_impl, subrules = [_my_subrule])
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
AssertionError error =
assertThrows(AssertionError.class, () -> getConfiguredTarget("//subrule_testing:foo"));
assertThat(error)
.hasMessageThat()
.contains("Error in run: for 'executable', expected FilesToRunProvider, got File");
}
@Test
public void testSubruleAttrs_lateBoundDefaultsAreResolved() throws Exception {
scratch.file(
"my/BUILD",
//
"cc_binary(name = 'tool')");
scratch.file(
"subrule_testing/myrule.bzl",
"""
def _subrule_impl(ctx, _tool):
return _tool
_my_subrule = subrule(
implementation = _subrule_impl,
attrs = {"_tool": attr.label(
default = configuration_field(fragment = "coverage", name = "output_generator"),
)},
)
MyInfo = provider()
def _rule_impl(ctx):
res = _my_subrule()
return MyInfo(result = res)
my_rule = rule(implementation = _rule_impl, subrules = [_my_subrule])
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
// TODO: b/293304174 - use a custom fragment instead of coverage
useConfiguration("--collect_code_coverage", "--coverage_output_generator=//my:tool");
StructImpl provider =
getProvider("//subrule_testing:foo", "//subrule_testing:myrule.bzl", "MyInfo");
assertThat(provider).isNotNull();
Object value = provider.getValue("result");
assertThat(value).isInstanceOf(ConfiguredTarget.class);
assertThat(((ConfiguredTarget) value).getLabel().toString()).isEqualTo("//my:tool");
}
@Test
public void testSubruleToolchains_cannotRequireMoreThanOne() throws Exception {
ev.checkEvalErrorContains(
"subrules may require at most 1 toolchain",
"_my_subrule = subrule(",
" implementation = lambda: None,",
" toolchains = ['//t1', '//t2'],",
")");
}
@Test
public void testSubruleToolchains_cannotAccessUnrequestedToolchain() throws Exception {
useConfiguration("--incompatible_auto_exec_groups");
scratch.file(
"subrule_testing/myrule.bzl",
"def _subrule_impl(ctx):",
" ctx.toolchains['" + TestConstants.JAVA_TOOLCHAIN_TYPE + "']",
"_my_subrule = subrule(",
" implementation = _subrule_impl,",
")",
"",
"def _rule_impl(ctx):",
" _my_subrule()",
"",
"my_rule = rule(",
" implementation = _rule_impl,",
" subrules = [_my_subrule],",
")");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
assertThrows(
TestConstants.JAVA_TOOLCHAIN_TYPE + " was requested but only types [] are configured",
AssertionError.class,
() -> getConfiguredTarget("//subrule_testing:foo"));
}
@Test
public void testSubruleToolchains_cannotAccessToolchainFromRule() throws Exception {
useConfiguration("--incompatible_auto_exec_groups");
scratch.file(
"subrule_testing/myrule.bzl",
"def _subrule_impl(ctx):",
" ctx.toolchains['" + TestConstants.JAVA_TOOLCHAIN_TYPE + "']",
"_my_subrule = subrule(",
" implementation = _subrule_impl,",
")",
"",
"def _rule_impl(ctx):",
" _my_subrule()",
"",
"my_rule = rule(",
" implementation = _rule_impl,",
" subrules = [_my_subrule],",
" toolchains = ['" + TestConstants.JAVA_TOOLCHAIN_TYPE + "'],",
")");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
assertThrows(
TestConstants.JAVA_TOOLCHAIN_TYPE + " was requested but only types [] are configured",
AssertionError.class,
() -> getConfiguredTarget("//subrule_testing:foo"));
}
@Test
public void testSubruleToolchains_requestedToolchainIsResolved_inRule() throws Exception {
useConfiguration("--incompatible_auto_exec_groups");
scratch.file(
"subrule_testing/myrule.bzl",
"def _subrule_impl(ctx):",
" return ctx.toolchains['" + TestConstants.JAVA_TOOLCHAIN_TYPE + "']",
"_my_subrule = subrule(",
" implementation = _subrule_impl,",
" toolchains = ['" + TestConstants.JAVA_TOOLCHAIN_TYPE + "'],",
")",
"MyInfo = provider()",
"def _rule_impl(ctx):",
" return [MyInfo(result = _my_subrule())]",
"",
"my_rule = rule(",
" implementation = _rule_impl,",
" subrules = [_my_subrule],",
")");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
ToolchainInfo toolchainInfo =
getProvider("//subrule_testing:foo", "//subrule_testing:myrule.bzl", "MyInfo")
.getValue("result", ToolchainInfo.class);
assertThat(toolchainInfo).isNotNull();
assertThat(toolchainInfo.getValue("java", StarlarkInfo.class).getProvider().getKey())
.isEqualTo(JavaToolchainProvider.PROVIDER.getKey());
}
@Test
public void testSubruleToolchains_requstedToolchainIsResolved_inAspect() throws Exception {
useConfiguration("--incompatible_auto_exec_groups");
scratch.file("default/BUILD", "genrule(name = 'default', outs = ['a'], cmd = '')");
scratch.file(
"subrule_testing/myrule.bzl",
"def _subrule_impl(ctx):",
" return ctx.toolchains['" + TestConstants.JAVA_TOOLCHAIN_TYPE + "']",
"_my_subrule = subrule(",
" implementation = _subrule_impl,",
" toolchains = ['" + TestConstants.JAVA_TOOLCHAIN_TYPE + "'],",
")",
"MyInfo=provider()",
"def _aspect_impl(target, ctx):",
" return MyInfo(result = _my_subrule())",
"my_aspect = aspect(implementation = _aspect_impl, subrules = [_my_subrule])",
"def _rule_impl(ctx):",
" return ctx.attr.dep[MyInfo]",
"my_rule = rule(",
" implementation = _rule_impl,",
" attrs = {'dep' : attr.label(aspects = [my_aspect])}",
")");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(
name = "foo",
dep = "//default",
)
""");
ToolchainInfo toolchainInfo =
getProvider("//subrule_testing:foo", "//subrule_testing:myrule.bzl", "MyInfo")
.getValue("result", ToolchainInfo.class);
assertThat(toolchainInfo).isNotNull();
assertThat(toolchainInfo.getValue("java", StarlarkInfo.class).getProvider().getKey())
.isEqualTo(JavaToolchainProvider.PROVIDER.getKey());
}
@Test
public void testSubruleToolchains_noToolchainIsSuppliedToAction() throws Exception {
useConfiguration("--incompatible_auto_exec_groups");
scratch.file(
"subrule_testing/myrule.bzl",
"""
def _subrule_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name + ".out")
ctx.actions.run(outputs = [out], executable = "/bin/ls", tools = [depset()])
return out
_my_subrule = subrule(
implementation = _subrule_impl,
)
def _rule_impl(ctx):
return [DefaultInfo(files = depset([_my_subrule()]))]
my_rule = rule(
implementation = _rule_impl,
subrules = [_my_subrule],
)
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
ConfiguredTarget target = getConfiguredTarget("//subrule_testing:foo");
Action action = getGeneratingAction(target, "subrule_testing/foo.out");
assertThat(action).isNotNull();
assertThat(action.getOwner()).isEqualTo(getRuleContext(target).getActionOwner());
}
@Test
public void testSubruleToolchains_requestedToolchainIsSuppliedToAction() throws Exception {
useConfiguration("--incompatible_auto_exec_groups");
scratch.file(
"subrule_testing/myrule.bzl",
"def _subrule_impl(ctx):",
" out = ctx.actions.declare_file(ctx.label.name + '.out')",
" ctx.actions.run(outputs = [out], executable = '/bin/ls', tools = [depset()])",
" return out",
"_my_subrule = subrule(",
" implementation = _subrule_impl,",
" toolchains = ['" + TestConstants.JAVA_TOOLCHAIN_TYPE + "'],",
")",
"def _rule_impl(ctx):",
" return [DefaultInfo(files = depset([_my_subrule()]))]",
"",
"my_rule = rule(",
" implementation = _rule_impl,",
" subrules = [_my_subrule],",
")");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
ConfiguredTarget target = getConfiguredTarget("//subrule_testing:foo");
Action action = getGeneratingAction(target, "subrule_testing/foo.out");
assertThat(action).isNotNull();
assertThat(action.getOwner())
.isEqualTo(getRuleContext(target).getActionOwner(TestConstants.JAVA_TOOLCHAIN_TYPE));
}
@Test
public void testSubruleFragments_errorForInvalidFragments() throws Exception {
scratch.file(
"subrule_testing/myrule.bzl",
"""
def _subrule_impl(ctx):
return ctx.fragments.foobar
_my_subrule = subrule(
implementation = _subrule_impl,
fragments = ["java", "cpp"],
)
MyInfo = provider()
def _rule_impl(ctx):
res = _my_subrule()
return MyInfo(result = res)
my_rule = rule(_rule_impl, subrules = [_my_subrule])
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
AssertionError assertionError =
assertThrows(AssertionError.class, () -> getConfiguredTarget("//subrule_testing:foo"));
assertThat(assertionError)
.hasMessageThat()
.contains(
"There is no configuration fragment named 'foobar'. Available fragments: 'java',"
+ " 'cpp'");
}
@Test
public void testSubruleFragments_onlyDeclaredFragmentsAreVisible() throws Exception {
scratch.file(
"subrule_testing/myrule.bzl",
"""
def _subrule_impl(ctx):
return dir(ctx.fragments)
_my_subrule = subrule(
implementation = _subrule_impl,
fragments = ["cpp", "python"],
)
MyInfo = provider()
def _rule_impl(ctx):
res = _my_subrule()
return MyInfo(result = res)
my_rule = rule(_rule_impl, subrules = [_my_subrule], fragments = ["java"])
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
Sequence<String> fragments =
Sequence.cast(
getProvider("//subrule_testing:foo", "//subrule_testing:myrule.bzl", "MyInfo")
.getValue("result"),
String.class,
"ctx.fragments");
assertThat(fragments).isNotNull();
assertThat(fragments).containsExactly("cpp", "python");
}
@Test
public void testSubruleFragments_ruleCannotAccessSubruleFragments() throws Exception {
scratch.file(
"subrule_testing/myrule.bzl",
"""
def _subrule_impl(ctx):
pass
_my_subrule = subrule(
implementation = _subrule_impl,
fragments = ["cpp"],
)
def _rule_impl(ctx):
ctx.fragments.cpp
return []
my_rule = rule(_rule_impl, subrules = [_my_subrule])
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
AssertionError assertionError =
assertThrows(AssertionError.class, () -> getConfiguredTarget("//subrule_testing:foo"));
assertThat(assertionError)
.hasMessageThat()
.contains("my_rule has to declare 'cpp' as a required fragment in order to access it");
}
@Test
public void testSubruleFragments_canAccessDeclaredFragments() throws Exception {
scratch.file(
"subrule_testing/myrule.bzl",
"""
def _subrule_impl(ctx):
return ctx.fragments.cpp
_my_subrule = subrule(
implementation = _subrule_impl,
fragments = ["cpp"],
)
MyInfo = provider()
def _rule_impl(ctx):
res = _my_subrule()
return MyInfo(result = res)
my_rule = rule(_rule_impl, subrules = [_my_subrule], fragments = ["java"])
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
CppConfigurationApi<?> fragment =
getProvider("//subrule_testing:foo", "//subrule_testing:myrule.bzl", "MyInfo")
.getValue("result", CppConfigurationApi.class);
assertThat(fragment).isNotNull();
}
@Test
public void testSubruleFragments_mustDeclareFragmentsIfAccessed() throws Exception {
scratch.file(
"subrule_testing/myrule.bzl",
"""
def _subrule_impl(ctx):
ctx.fragments.java
_my_subrule = subrule(
implementation = _subrule_impl,
fragments = ["cpp", "python"],
)
def _rule_impl(ctx):
res = _my_subrule()
return []
my_rule = rule(_rule_impl, subrules = [_my_subrule], fragments = ["java"])
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
AssertionError assertionError =
assertThrows(AssertionError.class, () -> getConfiguredTarget("//subrule_testing:foo"));
assertThat(assertionError)
.hasMessageThat()
.contains("_my_subrule has to declare 'java' as a required fragment in order to access it");
}
@Test
public void testTransitiveSubrules_subruleMustDeclareCalledSubrule() throws Exception {
scratch.file(
"subrule_testing/myrule.bzl",
"""
def _A_impl(ctx):
return "from subruleA"
_A = subrule(implementation = _A_impl)
def _B_impl(ctx):
return _A()
_B = subrule(implementation = _B_impl)
def _rule_impl(ctx):
res = _B()
my_rule = rule(_rule_impl, subrules = [_B])
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
AssertionError assertionError =
assertThrows(AssertionError.class, () -> getConfiguredTarget("//subrule_testing:foo"));
assertThat(assertionError)
.hasMessageThat()
.contains("Error in _A: subrule _B must declare _A in 'subrules'");
}
@Test
public void testTransitiveSubrules_ruleCannotCallUndeclaredTransitiveSubrule() throws Exception {
scratch.file(
"subrule_testing/myrule.bzl",
"""
def _A_impl(ctx):
return "from subruleA"
_A = subrule(implementation = _A_impl)
def _B_impl(ctx):
return _A()
_B = subrule(implementation = _B_impl, subrules = [_A])
def _rule_impl(ctx):
_A()
my_rule = rule(_rule_impl, subrules = [_B])
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
AssertionError assertionError =
assertThrows(AssertionError.class, () -> getConfiguredTarget("//subrule_testing:foo"));
assertThat(assertionError)
.hasMessageThat()
.contains("rule 'my_rule' must declare '_A' in 'subrules'");
}
@Test
public void testTransitiveSubrules_subruleCanCallDeclaredSubrule() throws Exception {
scratch.file(
"subrule_testing/myrule.bzl",
"""
def _A_impl(ctx):
return "from subruleA"
_A = subrule(implementation = _A_impl)
def _B_impl(ctx):
return _A()
_B = subrule(implementation = _B_impl, subrules = [_A])
MyInfo = provider()
def _rule_impl(ctx):
res = _B()
return MyInfo(result = res)
my_rule = rule(_rule_impl, subrules = [_B])
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
String result =
getProvider("//subrule_testing:foo", "//subrule_testing:myrule.bzl", "MyInfo")
.getValue("result", String.class);
assertThat(result).isEqualTo("from subruleA");
}
@Test
public void testTransitiveSubrules_arbitrarilyLongTransitiveChainsAreResolved() throws Exception {
scratch.file("a/BUILD", "genrule(name = 'tool', cmd = '', outs = ['tool.out'])");
scratch.file(
"subrule_testing/myrule.bzl",
"""
def _A_impl(ctx, _tool):
return "tool name: " + _tool.label.name
_A = subrule(implementation = _A_impl, attrs = {"_tool": attr.label(default = "//a:tool")})
_B = subrule(implementation = lambda ctx: _A(), subrules = [_A])
_C = subrule(implementation = lambda ctx: _B(), subrules = [_B])
_D = subrule(implementation = lambda ctx: _C(), subrules = [_C])
MyInfo = provider()
def _rule_impl(ctx):
return MyInfo(result = _D())
my_rule = rule(_rule_impl, subrules = [_D])
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
String result =
getProvider("//subrule_testing:foo", "//subrule_testing:myrule.bzl", "MyInfo")
.getValue("result", String.class);
assertThat(result).isEqualTo("tool name: tool");
}
@Test
public void testTransitiveSubrules_ruleAndSubruleCanHaveCommonSubruleDependency()
throws Exception {
scratch.file(
"subrule_testing/myrule.bzl",
"""
def _A_impl(ctx):
return "from subruleA"
_A = subrule(implementation = _A_impl)
def _B_impl(ctx):
_A()
return "from subruleB"
_B = subrule(implementation = _B_impl, subrules = [_A])
MyInfo = provider()
def _rule_impl(ctx):
resA = _A()
resB = _B()
return MyInfo(resA = resA, resB = resB)
my_rule = rule(_rule_impl, subrules = [_A, _B])
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
String resA =
getProvider("//subrule_testing:foo", "//subrule_testing:myrule.bzl", "MyInfo")
.getValue("resA", String.class);
String resB =
getProvider("//subrule_testing:foo", "//subrule_testing:myrule.bzl", "MyInfo")
.getValue("resB", String.class);
assertThat(resA).isEqualTo("from subruleA");
assertThat(resB).isEqualTo("from subruleB");
}
@Test
public void testTransitiveSubrules_callerSubruleCtxIsLocked() throws Exception {
scratch.file(
"subrule_testing/myrule.bzl",
"""
def _A_impl(ctx, ctxB):
return ctxB.label
_A = subrule(implementation = _A_impl)
def _B_impl(ctx):
return _A(ctx)
_B = subrule(implementation = _B_impl, subrules = [_A])
MyInfo = provider()
def _rule_impl(ctx):
res = _B()
return MyInfo(result = res)
my_rule = rule(_rule_impl, subrules = [_B])
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
AssertionError error =
assertThrows(AssertionError.class, () -> getConfiguredTarget("//subrule_testing:foo"));
assertThat(error)
.hasMessageThat()
.contains(
"cannot access field or method 'label' of subrule context outside of its own"
+ " implementation function");
}
@Test
public void testTransitiveSubrules_callerSubruleCtxIsUnlockedUponResumption() throws Exception {
scratch.file(
"subrule_testing/myrule.bzl",
"""
def _A_impl(ctx):
return "from A"
_A = subrule(implementation = _A_impl)
def _B_impl(ctx):
_A()
return "from B: " + ctx.label.name
_B = subrule(implementation = _B_impl, subrules = [_A])
MyInfo = provider()
def _rule_impl(ctx):
res = _B()
return MyInfo(result = res)
my_rule = rule(_rule_impl, subrules = [_B])
""");
scratch.file(
"subrule_testing/BUILD",
"""
load("myrule.bzl", "my_rule")
my_rule(name = "foo")
""");
String result =
getProvider("//subrule_testing:foo", "//subrule_testing:myrule.bzl", "MyInfo")
.getValue("result", String.class);
assertThat(result).isEqualTo("from B: foo");
}
@Test
public void testSubruleInstantiation_outsideAllowlist_failsWithPrivateAPIError()
throws Exception {
evOutsideAllowlist.checkEvalErrorContains(
"'//foo:bar' cannot use private API", "subrule(implementation = lambda: 0 )");
}
@Test
public void testSubrulesParamForRule_isPrivateAPI() throws Exception {
evOutsideAllowlist.checkEvalErrorContains(
"'//foo:bar' cannot use private API", "rule(implementation = lambda: 0, subrules = [1])");
}
@Test
public void testSubrulesParamForAspect_isPrivateAPI() throws Exception {
evOutsideAllowlist.checkEvalErrorContains(
"'//foo:bar' cannot use private API", "aspect(implementation = lambda: 0, subrules = [1])");
}
private StructImpl getProvider(String targetLabel, String providerLabel, String providerName)
throws LabelSyntaxException {
ConfiguredTarget target = getConfiguredTarget(targetLabel);
Provider.Key key =
new StarlarkProvider.Key(keyForBuild(Label.parseCanonical(providerLabel)), providerName);
return (StructImpl) target.get(key);
}
}