Add 'provider()' function.
--
MOS_MIGRATED_REVID=129889793
diff --git a/src/main/java/com/google/devtools/build/lib/packages/SkylarkAspect.java b/src/main/java/com/google/devtools/build/lib/packages/SkylarkAspect.java
index 1a78509..be74013 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/SkylarkAspect.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/SkylarkAspect.java
@@ -20,14 +20,12 @@
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory;
-import com.google.devtools.build.lib.skylarkinterface.SkylarkValue;
import com.google.devtools.build.lib.syntax.BaseFunction;
import com.google.devtools.build.lib.syntax.Environment;
import com.google.devtools.build.lib.syntax.Printer;
import com.google.devtools.build.lib.syntax.Type;
import com.google.devtools.build.lib.util.Pair;
import com.google.devtools.build.lib.util.Preconditions;
-
import javax.annotation.Nullable;
/** A Skylark value that is a result of an 'aspect(..)' function call. */
@@ -39,7 +37,7 @@
+ "documentation of the aspect function</a> or the "
+ "<a href=\"../aspects.md\">introduction to Aspects</a>."
)
-public class SkylarkAspect implements SkylarkValue {
+public class SkylarkAspect implements SkylarkExportable {
private final BaseFunction implementation;
private final ImmutableList<String> attributeAspects;
private final ImmutableList<Attribute> attributes;
@@ -107,6 +105,7 @@
return paramAttributes;
}
+ @Override
public void export(Label extensionLabel, String name) {
Preconditions.checkArgument(!isExported());
this.aspectClass = new SkylarkAspectClass(extensionLabel, name);
@@ -137,6 +136,7 @@
return builder.build();
}
+ @Override
public boolean isExported() {
return aspectClass != null;
}
diff --git a/src/main/java/com/google/devtools/build/lib/packages/SkylarkClassObject.java b/src/main/java/com/google/devtools/build/lib/packages/SkylarkClassObject.java
index f519096..5e5183c 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/SkylarkClassObject.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/SkylarkClassObject.java
@@ -124,6 +124,10 @@
return StructConcatter.INSTANCE;
}
+ public SkylarkClassObjectConstructor getConstructor() {
+ return constructor;
+ }
+
private static class StructConcatter implements Concatter {
private static final StructConcatter INSTANCE = new StructConcatter();
@@ -134,6 +138,12 @@
Concatable left, Concatable right, Location loc) throws EvalException {
SkylarkClassObject lval = (SkylarkClassObject) left;
SkylarkClassObject rval = (SkylarkClassObject) right;
+ if (!lval.constructor.equals(rval.constructor)) {
+ throw new EvalException(loc,
+ String.format("Cannot concat %s with %s",
+ lval.constructor.getPrintableName(),
+ rval.constructor.getPrintableName()));
+ }
SetView<String> commonFields = Sets
.intersection(lval.values.keySet(), rval.values.keySet());
if (!commonFields.isEmpty()) {
@@ -174,7 +184,7 @@
@Override
public void write(Appendable buffer, char quotationMark) {
boolean first = true;
- Printer.append(buffer, constructor.getName());
+ Printer.append(buffer, constructor.getPrintableName());
Printer.append(buffer, "(");
// Sort by key to ensure deterministic output.
for (String key : Ordering.natural().sortedCopy(values.keySet())) {
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 b451efe..d090c95 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
@@ -14,22 +14,29 @@
package com.google.devtools.build.lib.packages;
+import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
import com.google.devtools.build.lib.syntax.BaseFunction;
import com.google.devtools.build.lib.syntax.Environment;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.syntax.FuncallExpression;
import com.google.devtools.build.lib.syntax.FunctionSignature;
-import com.google.devtools.build.lib.syntax.FunctionSignature.WithValues;
import com.google.devtools.build.lib.syntax.SkylarkType;
-import com.google.devtools.build.lib.syntax.Type.ConversionException;
+import com.google.devtools.build.lib.util.Preconditions;
import java.util.Map;
+import java.util.Objects;
import javax.annotation.Nullable;
/**
* A constructor for {@link SkylarkClassObject}.
*/
-public final class SkylarkClassObjectConstructor extends BaseFunction {
+@SkylarkModule(name = "provider",
+ doc = "A constructor for simple value objects. "
+ + "See the global <a href=\"globals.html#provider\">provider</a> function "
+ + "for more details."
+)
+public final class SkylarkClassObjectConstructor extends BaseFunction implements SkylarkExportable {
/**
* "struct" function.
*/
@@ -38,7 +45,10 @@
private static final FunctionSignature.WithValues<Object, SkylarkType> SIGNATURE =
- WithValues.create(FunctionSignature.KWARGS);
+ FunctionSignature.WithValues.create(FunctionSignature.KWARGS);
+
+ @Nullable
+ private Key key;
public SkylarkClassObjectConstructor(String name, Location location) {
super(name, SIGNATURE, location);
@@ -50,7 +60,7 @@
@Override
protected Object call(Object[] args, @Nullable FuncallExpression ast, @Nullable Environment env)
- throws EvalException, ConversionException, InterruptedException {
+ throws EvalException, InterruptedException {
@SuppressWarnings("unchecked")
Map<String, Object> kwargs = (Map<String, Object>) args[0];
return new SkylarkClassObject(this, kwargs, ast != null ? ast.getLocation() : Location.BUILTIN);
@@ -65,6 +75,26 @@
}
@Override
+ public boolean isExported() {
+ return key != null;
+ }
+
+ @Nullable
+ public Key getKey() {
+ return key;
+ }
+
+ public String getPrintableName() {
+ return key != null ? key.exportedName : getName();
+ }
+
+ @Override
+ public void export(Label extensionLabel, String exportedName) {
+ Preconditions.checkState(!isExported());
+ this.key = new Key(extensionLabel, exportedName);
+ }
+
+ @Override
public int hashCode() {
return System.identityHashCode(this);
}
@@ -73,4 +103,46 @@
public boolean equals(@Nullable Object other) {
return other == this;
}
+
+ /**
+ * A serializable representation of {@link SkylarkClassObjectConstructor}
+ * that uniquely identifies all {@link SkylarkClassObjectConstructor}s that
+ * are exposed to SkyFrame.
+ */
+ public static class Key {
+ private final Label extensionLabel;
+ private final String exportedName;
+
+ public Key(Label extensionLabel, String exportedName) {
+ this.extensionLabel = Preconditions.checkNotNull(extensionLabel);
+ this.exportedName = Preconditions.checkNotNull(exportedName);
+ }
+
+ public Label getExtensionLabel() {
+ return extensionLabel;
+ }
+
+ public String getExportedName() {
+ return exportedName;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(extensionLabel, exportedName);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof Key)) {
+ return false;
+ }
+ Key other = (Key) obj;
+ return Objects.equals(this.extensionLabel, other.extensionLabel)
+ && Objects.equals(this.exportedName, other.exportedName);
+ }
+ }
}
diff --git a/src/main/java/com/google/devtools/build/lib/packages/SkylarkExportable.java b/src/main/java/com/google/devtools/build/lib/packages/SkylarkExportable.java
new file mode 100644
index 0000000..003baa9
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/packages/SkylarkExportable.java
@@ -0,0 +1,38 @@
+// 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.packages;
+
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.skylarkinterface.SkylarkValue;
+import com.google.devtools.build.lib.syntax.EvalException;
+
+/**
+ * {@link SkylarkValue}s that need special handling when they are
+ * exported from an extension file. For example, rule definitions
+ * receive their name at the end of the execution of the .bzl file.
+ */
+public interface SkylarkExportable extends SkylarkValue {
+
+ /**
+ * Is this value already exported?
+ */
+ boolean isExported();
+
+ /**
+ * Notify the value that it is exported from {@code extensionLabel}
+ * extension with name {@code exportedName}.
+ */
+ void export(Label extensionLabel, String exportedName) throws EvalException;
+}
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 8b6f052..734ae0b 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
@@ -64,6 +64,7 @@
import com.google.devtools.build.lib.packages.SkylarkAspect;
import com.google.devtools.build.lib.packages.SkylarkClassObject;
import com.google.devtools.build.lib.packages.SkylarkClassObjectConstructor;
+import com.google.devtools.build.lib.packages.SkylarkExportable;
import com.google.devtools.build.lib.packages.TargetUtils;
import com.google.devtools.build.lib.packages.TestSize;
import com.google.devtools.build.lib.rules.SkylarkAttr.Descriptor;
@@ -194,7 +195,7 @@
@SkylarkSignature(name = "struct", returnType = SkylarkClassObject.class, doc =
"Creates an immutable struct using the keyword arguments as attributes. It is used to group "
- + "multiple values together.Example:<br>"
+ + "multiple values together. Example:<br>"
+ "<pre class=\"language-python\">s = struct(x = 2, y = 3)\n"
+ "return s.x + getattr(s, \"y\") # returns 5</pre>",
extraKeywords = @Param(name = "kwargs", doc = "the struct attributes"),
@@ -202,6 +203,23 @@
private static final SkylarkClassObjectConstructor struct =
SkylarkClassObjectConstructor.STRUCT;
+ @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>"
+ + "<pre class=\"language-python\">data = provider()\n"
+ + "d = data(x = 2, y = 3)"
+ + "return d.x + d.y # returns 5</pre>",
+ useLocation = true
+ )
+ private static final BuiltinFunction provider =
+ new BuiltinFunction("provider") {
+ public SkylarkClassObjectConstructor invoke(Location location) {
+ return new SkylarkClassObjectConstructor(
+ "<no name>", // name is set on export.
+ location);
+ }
+ };
+
// TODO(bazel-team): implement attribute copy and other rule properties
@SkylarkSignature(name = "rule", doc =
@@ -473,7 +491,7 @@
/** The implementation for the magic function "rule" that creates Skylark rule classes */
- public static final class RuleFunction extends BaseFunction {
+ public static final class RuleFunction extends BaseFunction implements SkylarkExportable {
private RuleClass.Builder builder;
private RuleClass ruleClass;
@@ -544,7 +562,7 @@
/**
* Export a RuleFunction from a Skylark file with a given name.
*/
- void export(Label skylarkLabel, String ruleClassName) throws EvalException {
+ public void export(Label skylarkLabel, String ruleClassName) throws EvalException {
Preconditions.checkState(ruleClass == null && builder != null);
this.skylarkLabel = skylarkLabel;
if (type == RuleClassType.TEST != TargetUtils.isTestRuleName(ruleClassName)) {
@@ -569,35 +587,40 @@
Preconditions.checkState(ruleClass != null && builder == null);
return ruleClass;
}
+
+ @Override
+ public boolean isExported() {
+ return skylarkLabel != null;
+ }
}
+ /**
+ * All classes of values that need special processing after they are exported
+ * from an extension file.
+ *
+ * Order in list list is significant: all {@link }SkylarkAspect}s need to be exported
+ * before {@link RuleFunction}s etc.
+ */
+ private static final List<Class<? extends SkylarkExportable>> EXPORTABLES =
+ ImmutableList.of(
+ SkylarkClassObjectConstructor.class,
+ SkylarkAspect.class,
+ RuleFunction.class);
+
public static void exportRuleFunctionsAndAspects(Environment env, Label skylarkLabel)
throws EvalException {
Set<String> globalNames = env.getGlobals().getDirectVariableNames();
- // Export aspects first since rules can depend on aspects.
- for (String name : globalNames) {
- Object value = env.lookup(name);
- if (name == null) {
- throw new AssertionError(String.format("No such variable: '%s'", name));
- }
- if (value instanceof SkylarkAspect) {
- SkylarkAspect skylarkAspect = (SkylarkAspect) value;
- if (!skylarkAspect.isExported()) {
- skylarkAspect.export(skylarkLabel, name);
+ for (Class<? extends SkylarkExportable> exportable : EXPORTABLES) {
+ for (String name : globalNames) {
+ Object value = env.lookup(name);
+ if (value == null) {
+ throw new AssertionError(String.format("No such variable: '%s'", name));
}
- }
- }
-
- for (String name : globalNames) {
- Object value = env.lookup(name);
- if (value == null) {
- throw new AssertionError(String.format("No such variable: '%s'", name));
- }
- if (value instanceof RuleFunction) {
- RuleFunction function = (RuleFunction) value;
- if (function.skylarkLabel == null) {
- function.export(skylarkLabel, name);
+ if (exportable.isInstance(value)) {
+ if (!exportable.cast(value).isExported()) {
+ exportable.cast(value).export(skylarkLabel, name);
+ }
}
}
}
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java
index 9edfe35..f8823b9 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java
@@ -960,7 +960,6 @@
return MutableList.<Object>of(env, 1, 2, 3);
}
-
@Test
public void testStructMutabilityDeep() throws Exception {
assertTrue(EvalUtils.isImmutable(Tuple.<Object>of(makeList(null))));
@@ -972,4 +971,56 @@
assertFalse(EvalUtils.isImmutable(makeBigStruct(ev.getEnvironment())));
}
+ @Test
+ public void declaredProviders() throws Exception {
+ evalAndExport(
+ "data = provider()",
+ "d = data(x = 1, y ='abc')",
+ "d_x = d.x",
+ "d_y = d.y"
+ );
+ assertThat(lookup("d_x")).isEqualTo(1);
+ assertThat(lookup("d_y")).isEqualTo("abc");
+ SkylarkClassObjectConstructor dataConstructor = (SkylarkClassObjectConstructor) lookup("data");
+ SkylarkClassObject data = (SkylarkClassObject) lookup("d");
+ assertThat(data.getConstructor()).isEqualTo(dataConstructor);
+ assertThat(dataConstructor.isExported()).isTrue();
+ assertThat(dataConstructor.getPrintableName()).isEqualTo("data");
+ assertThat(dataConstructor.getKey()).isEqualTo(
+ new SkylarkClassObjectConstructor.Key(FAKE_LABEL, "data")
+ );
+ }
+
+ @Test
+ public void declaredProvidersConcatSuccess() throws Exception {
+ evalAndExport(
+ "data = provider()",
+ "dx = data(x = 1)",
+ "dy = data(y = 'abc')",
+ "dxy = dx + dy",
+ "x = dxy.x",
+ "y = dxy.y"
+ );
+ assertThat(lookup("x")).isEqualTo(1);
+ assertThat(lookup("y")).isEqualTo("abc");
+ SkylarkClassObjectConstructor dataConstructor = (SkylarkClassObjectConstructor) lookup("data");
+ SkylarkClassObject dx = (SkylarkClassObject) lookup("dx");
+ assertThat(dx.getConstructor()).isEqualTo(dataConstructor);
+ SkylarkClassObject dy = (SkylarkClassObject) lookup("dy");
+ assertThat(dy.getConstructor()).isEqualTo(dataConstructor);
+ }
+
+ @Test
+ public void declaredProvidersConcatError() throws Exception {
+ evalAndExport(
+ "data1 = provider()",
+ "data2 = provider()"
+ );
+
+ checkEvalError("Cannot concat data1 with data2",
+ "d1 = data1(x = 1)",
+ "d2 = data2(y = 2)",
+ "d = d1 + d2"
+ );
+ }
}