diff options
3 files changed, 97 insertions, 12 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java index 292f123e38..92470017c7 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java +++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java @@ -285,7 +285,10 @@ public class SkylarkRuleClassFunctions { doc = "<i>(Experimental)</i> " + "If true, this rule will expose its actions for inspection by rules that depend " + "on it via an <a href=\"ActionsSkylarkApiProvider.html\">actions</a> provider." - + "This should only be used for testing the analysis-time behavior of Skylark " + + "The provider is also available to the rule itself by calling " + + "<code>ctx.created_actions()</code>." + + "" + + "<p>This should only be used for testing the analysis-time behavior of Skylark " + "rules. This flag may be removed in the future.")}, useAst = true, useEnvironment = true) private static final BuiltinFunction rule = new BuiltinFunction("rule") { diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java index 665a15067a..594ace937e 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java +++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java @@ -22,6 +22,7 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.actions.Root; +import com.google.devtools.build.lib.analysis.ActionsProvider; import com.google.devtools.build.lib.analysis.AnalysisUtils; import com.google.devtools.build.lib.analysis.ConfigurationMakeVariableContext; import com.google.devtools.build.lib.analysis.FilesToRunProvider; @@ -164,8 +165,8 @@ public final class SkylarkRuleContext { public SkylarkRuleContext(RuleContext ruleContext, Kind kind) throws EvalException, InterruptedException { this.ruleContext = Preconditions.checkNotNull(ruleContext); - fragments = new FragmentCollection(ruleContext, ConfigurationTransition.NONE); - hostFragments = new FragmentCollection(ruleContext, ConfigurationTransition.HOST); + this.fragments = new FragmentCollection(ruleContext, ConfigurationTransition.NONE); + this.hostFragments = new FragmentCollection(ruleContext, ConfigurationTransition.HOST); if (kind == Kind.RULE) { Collection<Attribute> attributes = ruleContext.getRule().getAttributes(); @@ -419,6 +420,24 @@ public final class SkylarkRuleContext { return ruleContext; } + @SkylarkCallable(name = "created_actions", + doc = "For rules marked <code>_skylark_testable=True</code>, this returns an " + + "<a href=\"ActionsSkylarkApiProvider.html\">actions</a> provider representing all " + + "actions created so far for the current rule. For all other rules, returns None. " + + "Note that the provider is not updated when subsequent actions are created, so you " + + "will have to call this function again if you wish to inspect them. " + + "" + + "<p>This is intended to help test rule-implementation helper functions that take in a " + + "<a href=\"ctx.html\">ctx</a> object and create actions for it.") + public Object createdActions() { + if (ruleContext.getRule().getRuleClassObject().isSkylarkTestable()) { + return ActionsProvider.create( + ruleContext.getAnalysisEnvironment().getRegisteredActions()); + } else { + return Runtime.NONE; + } + } + @SkylarkCallable(name = "attr", structField = true, doc = ATTR_DOC) public SkylarkClassObject getAttr() { return attributesCollection.getAttr(); diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleContextTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleContextTest.java index 01a267e9a9..41a857577d 100644 --- a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleContextTest.java +++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleContextTest.java @@ -1029,7 +1029,7 @@ public class SkylarkRuleContextTest extends SkylarkTestCase { // The common structure of the following actions tests is a rule under test depended upon by // a testing rule, where the rule under test has one output and one caller-supplied action. - private String getSimpleUnderTestDefinition(String actionLine) { + private String getSimpleUnderTestDefinition(String actionLine, boolean withSkylarkTestable) { return linesAsString( "def _undertest_impl(ctx):", " out = ctx.outputs.out", @@ -1037,10 +1037,18 @@ public class SkylarkRuleContextTest extends SkylarkTestCase { "undertest_rule = rule(", " implementation = _undertest_impl,", " outputs = {'out': '%{name}.txt'},", - " _skylark_testable = True,", + withSkylarkTestable ? " _skylark_testable = True," : "", ")"); } + private String getSimpleUnderTestDefinition(String actionLine) { + return getSimpleUnderTestDefinition(actionLine, true); + } + + private String getSimpleNontestableUnderTestDefinition(String actionLine) { + return getSimpleUnderTestDefinition(actionLine, false); + } + private final String testingRuleDefinition = linesAsString( "def _testing_impl(ctx):", @@ -1089,13 +1097,8 @@ public class SkylarkRuleContextTest extends SkylarkTestCase { public void testNoAccessToDependencyActionsWithoutSkylarkTest() throws Exception { reporter.removeHandler(failFastHandler); scratch.file("test/rules.bzl", - "def _undertest_impl(ctx):", - " out = ctx.outputs.out", - " ctx.action(outputs=[out], command='echo foo123 > ' + out.path)", - "undertest_rule = rule(", - " implementation = _undertest_impl,", - " outputs = {'out': '%{name}.txt'},", - ")", + getSimpleNontestableUnderTestDefinition( + "ctx.action(outputs=[out], command='echo foo123 > ' + out.path)"), testingRuleDefinition); scratch.file("test/BUILD", simpleBuildDefinition); @@ -1148,6 +1151,66 @@ public class SkylarkRuleContextTest extends SkylarkTestCase { assertThat(eval("list(action2.outputs)")).isEqualTo(eval("[file2]")); } + // For created_actions() tests, the "undertest" rule represents both the code under test and the + // Skylark user test code itself. + + @Test + public void testCreatedActions() throws Exception { + // createRuleContext() gives us the context for a rule upon entry into its analysis function. + // But we need to inspect the result of calling created_actions() after the rule context has + // been modified by creating actions. So we'll call created_actions() from within the analysis + // function and pass it along as a provider. + scratch.file("test/rules.bzl", + "def _undertest_impl(ctx):", + " out1 = ctx.outputs.out1", + " out2 = ctx.outputs.out2", + " ctx.action(outputs=[out1], command='echo foo123 > ' + out1.path,", + " mnemonic='foo')", + " v = ctx.created_actions().by_file", + " ctx.action(outputs=[out2], command='echo bar123 > ' + out2.path)", + " return struct(v=v, out1=out1, out2=out2)", + "undertest_rule = rule(", + " implementation = _undertest_impl,", + " outputs = {'out1': '%{name}1.txt',", + " 'out2': '%{name}2.txt'},", + " _skylark_testable = True,", + ")", + testingRuleDefinition + ); + scratch.file("test/BUILD", + simpleBuildDefinition); + SkylarkRuleContext ruleContext = createRuleContext("//test:testing"); + + Object mapUnchecked = evalRuleContextCode(ruleContext, "ruleContext.attr.dep.v"); + assertThat(mapUnchecked).isInstanceOf(SkylarkDict.class); + SkylarkDict<?, ?> map = (SkylarkDict<?, ?>) mapUnchecked; + // Should only have the first action because created_actions() was called + // before the second action was created. + Object file = eval("ruleContext.attr.dep.out1"); + assertThat(map).hasSize(1); + assertThat(map).containsKey(file); + Object actionUnchecked = map.get(file); + assertThat(actionUnchecked).isInstanceOf(ActionAnalysisMetadata.class); + assertThat(((ActionAnalysisMetadata) actionUnchecked).getMnemonic()).isEqualTo("foo"); + } + + @Test + public void testNoAccessToCreatedActionsWithoutSkylarkTest() throws Exception { + scratch.file("test/rules.bzl", + getSimpleNontestableUnderTestDefinition( + "ctx.action(outputs=[out], command='echo foo123 > ' + out.path)") + ); + scratch.file("test/BUILD", + "load(':rules.bzl', 'undertest_rule')", + "undertest_rule(", + " name = 'undertest',", + ")"); + SkylarkRuleContext ruleContext = createRuleContext("//test:undertest"); + + Object result = evalRuleContextCode(ruleContext, "ruleContext.created_actions()"); + assertThat(result).isEqualTo(Runtime.NONE); + } + @Test public void testSpawnActionInterface() throws Exception { scratch.file("test/rules.bzl", |