aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java5
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java23
-rw-r--r--src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleContextTest.java81
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",