Add an actions provider for testing Skylark rules.
The new provider gathers actions generated by any Skylark-based RuleConfiguredTarget, so long as the rule definition has _skylark_test=True set. For the moment this flag is under the user's control, but the intention is that it will be set by a test runner.
--
MOS_MIGRATED_REVID=134687396
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ActionsProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/ActionsProvider.java
new file mode 100644
index 0000000..ea3e5d3
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ActionsProvider.java
@@ -0,0 +1,47 @@
+// Copyright 2016 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;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.packages.SkylarkClassObject;
+import com.google.devtools.build.lib.packages.SkylarkClassObjectConstructor;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This provides a view over the actions that were created during the analysis of a rule
+ * (not including actions for its transitive dependencies).
+ */
+public final class ActionsProvider{
+
+ public static final SkylarkClassObjectConstructor ACTIONS_PROVIDER =
+ SkylarkClassObjectConstructor.createNative("Actions");
+
+ public static SkylarkClassObject create(Iterable<ActionAnalysisMetadata> actions) {
+ Map<Artifact, ActionAnalysisMetadata> map = new HashMap<>();
+ for (ActionAnalysisMetadata action : actions) {
+ for (Artifact artifact : action.getOutputs()) {
+ // In the case that two actions generated the same artifact, the first wins. They
+ // ought to be equal anyway.
+ if (!map.containsKey(artifact)) {
+ map.put(artifact, action);
+ }
+ }
+ }
+ ImmutableMap<String, Object> fields = ImmutableMap.of("by_file", map);
+ return new SkylarkClassObject(ACTIONS_PROVIDER, fields);
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/SkylarkProviderValidationUtil.java b/src/main/java/com/google/devtools/build/lib/analysis/SkylarkProviderValidationUtil.java
index 869a2b0..ee88f97 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/SkylarkProviderValidationUtil.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/SkylarkProviderValidationUtil.java
@@ -17,6 +17,7 @@
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.events.Location;
@@ -105,6 +106,7 @@
|| type.equals(Integer.class)
|| type.equals(Boolean.class)
|| Artifact.class.isAssignableFrom(type)
+ || ActionAnalysisMetadata.class.isAssignableFrom(type)
|| type.equals(Label.class)
|| type.equals(com.google.devtools.build.lib.syntax.Runtime.NoneType.class);
}
diff --git a/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java b/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
index 53aabfa..9fcb2c4 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
@@ -476,6 +476,7 @@
private String name;
private final RuleClassType type;
private final boolean skylark;
+ private boolean skylarkTestable = false;
private boolean documented;
private boolean publicByDefault = false;
private boolean binaryOutput = true;
@@ -589,6 +590,7 @@
name,
skylark,
skylarkExecutable,
+ skylarkTestable,
documented,
publicByDefault,
binaryOutput,
@@ -662,6 +664,12 @@
return this;
}
+ public Builder setSkylarkTestable() {
+ Preconditions.checkState(skylark, "Cannot set skylarkTestable on a non-Skylark rule");
+ skylarkTestable = true;
+ return this;
+ }
+
/**
* Sets the policy for the case where the configuration is missing required fragments (see
* {@link #requiresConfigurationFragments}).
@@ -953,6 +961,7 @@
private final boolean isSkylark;
private final boolean skylarkExecutable;
+ private final boolean skylarkTestable;
private final boolean documented;
private final boolean publicByDefault;
private final boolean binaryOutput;
@@ -1063,6 +1072,7 @@
String name,
boolean isSkylark,
boolean skylarkExecutable,
+ boolean skylarkTestable,
boolean documented,
boolean publicByDefault,
boolean binaryOutput,
@@ -1086,6 +1096,7 @@
this.isSkylark = isSkylark;
this.targetKind = name + " rule";
this.skylarkExecutable = skylarkExecutable;
+ this.skylarkTestable = skylarkTestable;
this.documented = documented;
this.publicByDefault = publicByDefault;
this.binaryOutput = binaryOutput;
@@ -1886,7 +1897,7 @@
return ruleDefinitionEnvironmentHashCode;
}
- /** Returns true if this RuleClass is a skylark-defined RuleClass. */
+ /** Returns true if this RuleClass is a Skylark-defined RuleClass. */
public boolean isSkylark() {
return isSkylark;
}
@@ -1900,6 +1911,14 @@
}
/**
+ * Returns true if this RuleClass is Skylark-defined and is subject to analysis-time
+ * tests.
+ */
+ public boolean isSkylarkTestable() {
+ return skylarkTestable;
+ }
+
+ /**
* Returns true if this rule class outputs a default executable for every rule.
*/
public boolean outputsDefaultExecutable() {
diff --git a/src/main/java/com/google/devtools/build/lib/packages/SkylarkClassObjectConstructor.java b/src/main/java/com/google/devtools/build/lib/packages/SkylarkClassObjectConstructor.java
index 7666e2c..0854822 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/SkylarkClassObjectConstructor.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/SkylarkClassObjectConstructor.java
@@ -60,14 +60,14 @@
}
/**
- * Create a native Declared Provider({@link SkylarkClassObject} constructor)
+ * Create a native Declared Provider ({@link SkylarkClassObject} constructor)
*/
public static SkylarkClassObjectConstructor createNative(String name) {
return new SkylarkClassObjectConstructor(name);
}
/**
- * Create a Skylark-defined Declared Provider({@link SkylarkClassObject} constructor)
+ * Create a Skylark-defined Declared Provider ({@link SkylarkClassObject} constructor)
*
* Needs to be exported later.
*/
diff --git a/src/main/java/com/google/devtools/build/lib/packages/SkylarkNativeModule.java b/src/main/java/com/google/devtools/build/lib/packages/SkylarkNativeModule.java
index 190a46b..776dfa6 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/SkylarkNativeModule.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/SkylarkNativeModule.java
@@ -43,7 +43,6 @@
+ "The following functions are also available:")
public class SkylarkNativeModule {
- // TODO(bazel-team): shouldn't we return a SkylarkList instead?
@SkylarkSignature(
name = "glob",
objectType = SkylarkNativeModule.class,
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 4144ea6..95db141 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
@@ -35,6 +35,7 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.ActionsProvider;
import com.google.devtools.build.lib.analysis.BaseRuleClasses;
import com.google.devtools.build.lib.analysis.OutputGroupProvider;
import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
@@ -200,6 +201,18 @@
private static final SkylarkClassObjectConstructor struct =
SkylarkClassObjectConstructor.STRUCT;
+ // TODO(bazel-team): Move to a "testing" namespace module. Normally we'd pass an objectType
+ // to @SkylarkSignature to do this, but that doesn't work here because we're exposing an already-
+ // configured BaseFunction, rather than defining a new BuiltinFunction. This should wait for
+ // better support from the Skylark/Java interface, or perhaps support for first-class modules.
+ @SkylarkSignature(name = "Actions", returnType = SkylarkClassObjectConstructor.class, doc =
+ "Provides access to the actions generated by a rule. This is designed for testing rules, "
+ + "and should not be accessed outside of test logic. This provider is only available for "
+ + "targets generated by rules marked with <pre class=\"language-python\">"
+ + "_skylark_testable=True</pre>."
+ )
+ private static final SkylarkClassObjectConstructor actions = ActionsProvider.ACTIONS_PROVIDER;
+
@SkylarkSignature(name = "provider", returnType = SkylarkClassObjectConstructor.class, doc =
"Creates a declared provider 'constructor'. The return value of this"
+ "function can be used to create \"struct-like\" values. Example:<br>"
@@ -267,14 +280,21 @@
@Param(name = "host_fragments", type = SkylarkList.class, generic1 = String.class,
defaultValue = "[]",
doc = "List of names of configuration fragments that the rule requires "
- + "in host configuration.")},
+ + "in host configuration."),
+ @Param(name = "_skylark_testable", type = Boolean.class, defaultValue = "False",
+ 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 "
+ + "rules. This flag may be removed in the future.")},
useAst = true, useEnvironment = true)
private static final BuiltinFunction rule = new BuiltinFunction("rule") {
@SuppressWarnings({"rawtypes", "unchecked"}) // castMap produces
// an Attribute.Builder instead of a Attribute.Builder<?> but it's OK.
public BaseFunction invoke(BaseFunction implementation, Boolean test, Object attrs,
Object implicitOutputs, Boolean executable, Boolean outputToGenfiles, SkylarkList fragments,
- SkylarkList hostFragments, FuncallExpression ast, Environment funcallEnv)
+ SkylarkList hostFragments, Boolean skylarkTestable, FuncallExpression ast,
+ Environment funcallEnv)
throws EvalException, ConversionException {
funcallEnv.checkLoadingOrWorkspacePhase("rule", ast.getLocation());
RuleClassType type = test ? RuleClassType.TEST : RuleClassType.NORMAL;
@@ -285,6 +305,11 @@
RuleClass.Builder builder = new RuleClass.Builder("", type, true, parent);
ImmutableList<Pair<String, SkylarkAttr.Descriptor>> attributes =
attrObjectToAttributesList(attrs, ast);
+
+ if (skylarkTestable) {
+ builder.setSkylarkTestable();
+ }
+
if (executable || test) {
addAttribute(
ast.getLocation(),
@@ -872,7 +897,7 @@
)
}
)
- public static final BuiltinFunction output_group = new BuiltinFunction("output_group") {
+ private static final BuiltinFunction output_group = new BuiltinFunction("output_group") {
public SkylarkNestedSet invoke(TransitiveInfoCollection self, String group) {
OutputGroupProvider provider = self.getProvider(OutputGroupProvider.class);
NestedSet<Artifact> result = provider != null
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java
index 0645548..50133da 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java
@@ -16,6 +16,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.ActionsProvider;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
import com.google.devtools.build.lib.analysis.RuleContext;
@@ -156,7 +157,8 @@
filesToBuild.add(executable);
}
builder.setFilesToBuild(filesToBuild.build());
- return addStructFields(ruleContext, builder, target, executable, registeredProviderTypes);
+ return addStructFieldsAndBuild(
+ ruleContext, builder, target, executable, registeredProviderTypes);
}
private static Artifact getExecutable(RuleContext ruleContext, Object target)
@@ -220,7 +222,7 @@
}
}
- private static ConfiguredTarget addStructFields(
+ private static ConfiguredTarget addStructFieldsAndBuild(
RuleContext ruleContext,
RuleConfiguredTargetBuilder builder,
Object target,
@@ -343,6 +345,13 @@
? null : RunfilesSupport.withExecutable(ruleContext, computedDefaultRunfiles, executable);
builder.setRunfilesSupport(runfilesSupport, executable);
}
+
+ if (ruleContext.getRule().getRuleClassObject().isSkylarkTestable()) {
+ SkylarkClassObject actions = ActionsProvider.create(
+ ruleContext.getAnalysisEnvironment().getRegisteredActions());
+ builder.addSkylarkDeclaredProvider(actions, loc);
+ }
+
try {
return builder.build();
} catch (IllegalArgumentException e) {
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 d3d8cfc..dfe738c 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
@@ -307,7 +307,7 @@
}
filesBuilder.put(
skyname, ruleContext.getPrerequisiteArtifacts(a.getName(), Mode.DONT_CHECK).list());
- List<?> allPrereq = ruleContext.getPrerequisites(a.getName(), Mode.DONT_CHECK);
+
if (type == BuildType.LABEL && !a.hasSplitConfigurationTransition()) {
Object prereq = ruleContext.getPrerequisite(a.getName(), Mode.DONT_CHECK);
if (prereq == null) {
@@ -316,6 +316,7 @@
attrBuilder.put(skyname, prereq);
} else {
// Type.LABEL_LIST
+ List<?> allPrereq = ruleContext.getPrerequisites(a.getName(), Mode.DONT_CHECK);
attrBuilder.put(skyname, SkylarkList.createImmutable(allPrereq));
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java b/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java
index 0245ea6..2782bc89 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java
@@ -114,7 +114,7 @@
callable.parameters().length == 0 || !callable.structField(),
"Method "
+ method
- + " was annotated with both structField amd parameters.");
+ + " was annotated with both structField and parameters.");
if (callable.parameters().length > 0 || callable.mandatoryPositionals() >= 0) {
int nbArgs =
callable.parameters().length