Rollback of commit c182908910a370b490e7e027b867e11f9f2fb086.
*** Reason for rollback ***
--
MOS_MIGRATED_REVID=140687884
diff --git a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
index 72ecf59..ef13e49 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
@@ -1059,7 +1059,9 @@
Collection<Target> targets = context.pkgBuilder.getTargets();
Location loc = ast.getLocation();
- SkylarkDict<String, SkylarkDict<String, Object>> rules = SkylarkDict.of(env);
+ // Sort by name.
+ SkylarkDict<String, SkylarkDict<String, Object>> rules =
+ SkylarkDict.<String, SkylarkDict<String, Object>>of(env);
for (Target t : targets) {
if (t instanceof Rule) {
SkylarkDict<String, Object> m = targetDict(t, loc, env);
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 c14424a..9aa6b26 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
@@ -91,8 +91,6 @@
import com.google.devtools.build.lib.util.Pair;
import com.google.devtools.build.lib.util.Preconditions;
import com.google.protobuf.TextFormat;
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -104,20 +102,20 @@
public class SkylarkRuleClassFunctions {
@SkylarkSignature(
- name = "DATA_CFG",
- returnType = ConfigurationTransition.class,
- doc =
- "Deprecated. Use string \"data\" instead. "
- + "Specifies a transition to the data configuration."
+ name = "DATA_CFG",
+ returnType = ConfigurationTransition.class,
+ doc =
+ "Deprecated. Use string \"data\" instead. "
+ + "Specifies a transition to the data configuration."
)
private static final Object dataTransition = ConfigurationTransition.DATA;
@SkylarkSignature(
- name = "HOST_CFG",
- returnType = ConfigurationTransition.class,
- doc =
- "Deprecated. Use string \"host\" instead. "
- + "Specifies a transition to the host configuration."
+ name = "HOST_CFG",
+ returnType = ConfigurationTransition.class,
+ doc =
+ "Deprecated. Use string \"host\" instead. "
+ + "Specifies a transition to the host configuration."
)
private static final Object hostTransition = ConfigurationTransition.HOST;
@@ -127,15 +125,15 @@
// (except for transition phase) it's probably OK.
private static final LoadingCache<String, Label> labelCache =
CacheBuilder.newBuilder().build(new CacheLoader<String, Label>() {
- @Override
- public Label load(String from) throws Exception {
- try {
- return Label.parseAbsolute(from, false);
- } catch (LabelSyntaxException e) {
- throw new Exception(from);
- }
- }
- });
+ @Override
+ public Label load(String from) throws Exception {
+ try {
+ return Label.parseAbsolute(from, false);
+ } catch (LabelSyntaxException e) {
+ throw new Exception(from);
+ }
+ }
+ });
// TODO(bazel-team): Remove the code duplication (BaseRuleClasses and this class).
/** Parent rule class for non-executable non-test Skylark rules. */
@@ -243,119 +241,114 @@
// TODO(bazel-team): implement attribute copy and other rule properties
@SkylarkSignature(
- name = "rule",
- doc =
- "Creates a new rule. Store it in a global value, so that it can be loaded and called "
- + "from BUILD files.",
- returnType = BaseFunction.class,
- parameters = {
- @Param(
- name = "implementation",
- type = BaseFunction.class,
- doc =
- "the function implementing this rule, must have exactly one parameter: "
- + "<a href=\"ctx.html\">ctx</a>. The function is called during the analysis "
- + "phase for each instance of the rule. It can access the attributes "
- + "provided by the user. It must create actions to generate all the declared "
- + "outputs."
- ),
- @Param(
- name = "test",
- type = Boolean.class,
- defaultValue = "False",
- doc =
- "Whether this rule is a test rule. "
- + "If True, the rule must end with <code>_test</code> (otherwise it must "
- + "not), and there must be an action that generates "
- + "<code>ctx.outputs.executable</code>."
- ),
- @Param(
- name = "attrs",
- type = SkylarkDict.class,
- noneable = true,
- defaultValue = "None",
- doc =
- "dictionary to declare all the attributes of the rule. It maps from an attribute "
- + "name to an attribute object (see <a href=\"attr.html\">attr</a> module). "
- + "Attributes starting with <code>_</code> are private, and can be used to "
- + "add an implicit dependency on a label. The attribute <code>name</code> is "
- + "implicitly added and must not be specified. Attributes "
- + "<code>visibility</code>, <code>deprecation</code>, <code>tags</code>, "
- + "<code>testonly</code>, and <code>features</code> are implicitly added and "
- + "might be overriden."
- ),
- // TODO(bazel-team): need to give the types of these builtin attributes
- @Param(
- name = "outputs",
- type = SkylarkDict.class,
- callbackEnabled = true,
- noneable = true,
- defaultValue = "None",
- doc =
- "outputs of this rule. "
- + "It is a dictionary mapping from string to a template name. "
- + "For example: <code>{\"ext\": \"%{name}.ext\"}</code>. <br>"
- + "The dictionary key becomes an attribute in <code>ctx.outputs</code>. "
- + "Similar to computed dependency rule attributes, you can also specify the "
- + "name of a function that returns the dictionary. This function can access "
- + "all rule attributes that are listed as parameters in its function "
- + "signature. For example, <code>outputs = _my_func<code> with "
- + "<code>def _my_func(srcs, deps):</code> has access to the attributes "
- + "'srcs' and 'deps' (if defined)."
- ),
- @Param(
- name = "executable",
- type = Boolean.class,
- defaultValue = "False",
- doc =
- "whether this rule is marked as executable or not. If True, "
- + "there must be an action that generates "
- + "<code>ctx.outputs.executable</code>."
- ),
- @Param(
- name = "output_to_genfiles",
- type = Boolean.class,
- defaultValue = "False",
- doc =
- "If true, the files will be generated in the genfiles directory instead of the "
- + "bin directory. Unless you need it for compatibility with existing rules "
- + "(e.g. when generating header files for C++), do not set this flag."
- ),
- @Param(
- name = "fragments",
- type = SkylarkList.class,
- generic1 = String.class,
- defaultValue = "[]",
- doc =
- "List of names of configuration fragments that the rule requires "
- + "in target configuration."
- ),
- @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."
- ),
- @Param(
- name = "_skylark_testable",
- type = Boolean.class,
- defaultValue = "False",
- doc =
- "<i>(Experimental)</i><br/><br/>"
- + "If true, this rule will expose its actions for inspection by rules that "
- + "depend on it via an <a href=\"globals.html#Actions\">Actions</a> "
- + "provider. The provider is also available to the rule itself by calling "
- + "<a href=\"ctx.html#created_actions\">ctx.created_actions()</a>."
- + "<br/><br/>"
- + "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
+ name = "rule",
+ doc =
+ "Creates a new rule. Store it in a global value, so that it can be loaded and called "
+ + "from BUILD files.",
+ returnType = BaseFunction.class,
+ parameters = {
+ @Param(
+ name = "implementation",
+ type = BaseFunction.class,
+ doc =
+ "the function implementing this rule, must have exactly one parameter: "
+ + "<a href=\"ctx.html\">ctx</a>. The function is called during the analysis phase "
+ + "for each instance of the rule. It can access the attributes provided by the "
+ + "user. It must create actions to generate all the declared outputs."
+ ),
+ @Param(
+ name = "test",
+ type = Boolean.class,
+ defaultValue = "False",
+ doc =
+ "Whether this rule is a test rule. "
+ + "If True, the rule must end with <code>_test</code> (otherwise it must not), "
+ + "and there must be an action that generates <code>ctx.outputs.executable</code>."
+ ),
+ @Param(
+ name = "attrs",
+ type = SkylarkDict.class,
+ noneable = true,
+ defaultValue = "None",
+ doc =
+ "dictionary to declare all the attributes of the rule. It maps from an attribute name "
+ + "to an attribute object (see <a href=\"attr.html\">attr</a> module). "
+ + "Attributes starting with <code>_</code> are private, and can be used to add "
+ + "an implicit dependency on a label. The attribute <code>name</code> is "
+ + "implicitly added and must not be specified. Attributes <code>visibility</code>, "
+ + "<code>deprecation</code>, <code>tags</code>, <code>testonly</code>, and "
+ + "<code>features</code> are implicitly added and might be overriden."
+ ),
+ // TODO(bazel-team): need to give the types of these builtin attributes
+ @Param(
+ name = "outputs",
+ type = SkylarkDict.class,
+ callbackEnabled = true,
+ noneable = true,
+ defaultValue = "None",
+ doc =
+ "outputs of this rule. "
+ + "It is a dictionary mapping from string to a template name. "
+ + "For example: <code>{\"ext\": \"%{name}.ext\"}</code>. <br>"
+ + "The dictionary key becomes an attribute in <code>ctx.outputs</code>. "
+ + "Similar to computed dependency rule attributes, you can also specify the name "
+ + "of a function that returns the dictionary. This function can access all rule "
+ + "attributes that are listed as parameters in its function signature. "
+ + "For example, <code>outputs = _my_func<code> with <code>def _my_func(srcs, deps):"
+ + "</code> has access to the attributes 'srcs' and 'deps' (if defined)."
+ ),
+ @Param(
+ name = "executable",
+ type = Boolean.class,
+ defaultValue = "False",
+ doc =
+ "whether this rule is marked as executable or not. If True, "
+ + "there must be an action that generates <code>ctx.outputs.executable</code>."
+ ),
+ @Param(
+ name = "output_to_genfiles",
+ type = Boolean.class,
+ defaultValue = "False",
+ doc =
+ "If true, the files will be generated in the genfiles directory instead of the "
+ + "bin directory. Unless you need it for compatibility with existing rules "
+ + "(e.g. when generating header files for C++), do not set this flag."
+ ),
+ @Param(
+ name = "fragments",
+ type = SkylarkList.class,
+ generic1 = String.class,
+ defaultValue = "[]",
+ doc =
+ "List of names of configuration fragments that the rule requires "
+ + "in target configuration."
+ ),
+ @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."
+ ),
+ @Param(
+ name = "_skylark_testable",
+ type = Boolean.class,
+ defaultValue = "False",
+ doc =
+ "<i>(Experimental)</i><br/><br/>"
+ + "If true, this rule will expose its actions for inspection by rules that depend "
+ + "on it via an <a href=\"globals.html#Actions\">Actions</a> provider. "
+ + "The provider is also available to the rule itself by calling "
+ + "<a href=\"ctx.html#created_actions\">ctx.created_actions()</a>."
+ + "<br/><br/>"
+ + "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") {
@@ -460,57 +453,56 @@
@SkylarkSignature(name = "aspect", doc =
- "Creates a new aspect. The result of this function must be stored in a global value. "
- + "Please see the <a href=\"../aspects.md\">introduction to Aspects</a> for more "
- + "details.",
- returnType = SkylarkAspect.class,
- parameters = {
- @Param(name = "implementation", type = BaseFunction.class,
- doc = "the function implementing this aspect. Must have two parameters: "
- + "<a href=\"Target.html\">Target</a> (the target to which the aspect is "
- + "applied) and <a href=\"ctx.html\">ctx</a>. Attributes of the target are "
- + "available via ctx.rule field. The function is called during the analysis "
- + "phase for each application of an aspect to a target."
- ),
- @Param(name = "attr_aspects", type = SkylarkList.class, generic1 = String.class,
- defaultValue = "[]",
- doc = "List of attribute names. The aspect propagates along dependencies specified "
- + "by attributes of a target with this name. The list can also contain a single "
- + "string '*': in that case aspect propagates along all dependencies of a target."
- ),
- @Param(name = "attrs", type = SkylarkDict.class, noneable = true, defaultValue = "None",
- doc = "dictionary to declare all the attributes of the aspect. "
- + "It maps from an attribute name to an attribute object "
- + "(see <a href=\"attr.html\">attr</a> module). "
- + "Aspect attributes are available to implementation function as fields of ctx "
- + "parameter. Implicit attributes starting with <code>_</code> must have default "
- + "values, and have type <code>label</code> or <code>label_list</code>. "
- + "Explicit attributes must have type <code>string</code>, and must use the "
- + "<code>values</code> restriction. If explicit attributes are present, the "
- + "aspect can only be used with rules that have attributes of the same name and "
- + "type, with valid values."
- ),
- @Param(
- name = "fragments",
- type = SkylarkList.class,
- generic1 = String.class,
- defaultValue = "[]",
- doc =
- "List of names of configuration fragments that the aspect requires "
- + "in target configuration."
- ),
- @Param(
- name = "host_fragments",
- type = SkylarkList.class,
- generic1 = String.class,
- defaultValue = "[]",
- doc =
- "List of names of configuration fragments that the aspect requires "
- + "in host configuration."
- )
- },
- useEnvironment = true,
- useAst = true
+ "Creates a new aspect. The result of this function must be stored in a global value. "
+ + "Please see the <a href=\"../aspects.md\">introduction to Aspects</a> for more details.",
+ returnType = SkylarkAspect.class,
+ parameters = {
+ @Param(name = "implementation", type = BaseFunction.class,
+ doc = "the function implementing this aspect. Must have two parameters: "
+ + "<a href=\"Target.html\">Target</a> (the target to which the aspect is applied) and "
+ + "<a href=\"ctx.html\">ctx</a>. Attributes of the target are available via ctx.rule "
+ + " field. The function is called during the analysis phase for each application of "
+ + "an aspect to a target."
+ ),
+ @Param(name = "attr_aspects", type = SkylarkList.class, generic1 = String.class,
+ defaultValue = "[]",
+ doc = "List of attribute names. The aspect propagates along dependencies specified by "
+ + " attributes of a target with this name. The list can also contain a single string '*':"
+ + " in that case aspect propagates along all dependencies of a target."
+ ),
+ @Param(name = "attrs", type = SkylarkDict.class, noneable = true, defaultValue = "None",
+ doc = "dictionary to declare all the attributes of the aspect. "
+ + "It maps from an attribute name to an attribute object "
+ + "(see <a href=\"attr.html\">attr</a> module). "
+ + "Aspect attributes are available to implementation function as fields of ctx parameter. "
+ + "Implicit attributes starting with <code>_</code> must have default values, and have "
+ + "type <code>label</code> or <code>label_list</code>. "
+ + "Explicit attributes must have type <code>string</code>, and must use the "
+ + "<code>values</code> restriction. If explicit attributes are present, the aspect can "
+ + "only be used with rules that have attributes of the same name and type, with valid "
+ + "values. "
+ ),
+ @Param(
+ name = "fragments",
+ type = SkylarkList.class,
+ generic1 = String.class,
+ defaultValue = "[]",
+ doc =
+ "List of names of configuration fragments that the aspect requires "
+ + "in target configuration."
+ ),
+ @Param(
+ name = "host_fragments",
+ type = SkylarkList.class,
+ generic1 = String.class,
+ defaultValue = "[]",
+ doc =
+ "List of names of configuration fragments that the aspect requires "
+ + "in host configuration."
+ )
+ },
+ useEnvironment = true,
+ useAst = true
)
private static final BuiltinFunction aspect =
new BuiltinFunction("aspect") {
@@ -564,7 +556,7 @@
ast.getLocation(),
String.format(
"Aspect parameter attribute '%s' must have type 'string' and use the "
- + "'values' restriction.",
+ + "'values' restriction.",
nativeName));
}
if (!hasDefault) {
@@ -637,7 +629,7 @@
// TODO(dslomov): If a Skylark parameter extractor is specified for this aspect, its
// attributes may not be required.
for (Map.Entry<String, ImmutableSet<String>> attrRequirements :
- attribute.getRequiredAspectParameters().entrySet()) {
+ attribute.getRequiredAspectParameters().entrySet()) {
for (String required : attrRequirements.getValue()) {
if (!ruleClass.hasAttr(required, Type.STRING)) {
throw new EvalException(definitionLocation, String.format(
@@ -657,7 +649,7 @@
if (pkgContext == null) {
throw new EvalException(ast.getLocation(),
"Cannot instantiate a rule when loading a .bzl file. Rules can only be called from "
- + "a BUILD file (possibly via a macro).");
+ + "a BUILD file (possibly via a macro).");
}
return RuleFactory.createAndAddRule(
pkgContext,
@@ -746,39 +738,39 @@
objectType = Label.class,
parameters = {@Param(name = "label_string", type = String.class,
doc = "the label string"),
- @Param(
- name = "relative_to_caller_repository",
- type = Boolean.class,
- defaultValue = "False",
- named = true,
- positional = false,
- doc = "whether the label should be resolved relative to the label of the file this "
- + "function is called from.")},
+ @Param(
+ name = "relative_to_caller_repository",
+ type = Boolean.class,
+ defaultValue = "False",
+ named = true,
+ positional = false,
+ doc = "whether the label should be resolved relative to the label of the file this "
+ + "function is called from.")},
useLocation = true,
useEnvironment = true)
private static final BuiltinFunction label = new BuiltinFunction("Label") {
- @SuppressWarnings({"unchecked", "unused"})
- public Label invoke(
- String labelString, Boolean relativeToCallerRepository, Location loc, Environment env)
- throws EvalException {
- Label parentLabel = null;
- if (relativeToCallerRepository) {
- parentLabel = env.getCallerLabel();
- } else {
- parentLabel = env.getGlobals().label();
- }
- try {
- if (parentLabel != null) {
- LabelValidator.parseAbsoluteLabel(labelString);
- labelString = parentLabel.getRelative(labelString)
- .getUnambiguousCanonicalForm();
+ @SuppressWarnings({"unchecked", "unused"})
+ public Label invoke(
+ String labelString, Boolean relativeToCallerRepository, Location loc, Environment env)
+ throws EvalException {
+ Label parentLabel = null;
+ if (relativeToCallerRepository) {
+ parentLabel = env.getCallerLabel();
+ } else {
+ parentLabel = env.getGlobals().label();
}
- return labelCache.get(labelString);
- } catch (LabelValidator.BadLabelException | LabelSyntaxException | ExecutionException e) {
- throw new EvalException(loc, "Illegal absolute label syntax: " + labelString);
+ try {
+ if (parentLabel != null) {
+ LabelValidator.parseAbsoluteLabel(labelString);
+ labelString = parentLabel.getRelative(labelString)
+ .getUnambiguousCanonicalForm();
+ }
+ return labelCache.get(labelString);
+ } catch (LabelValidator.BadLabelException | LabelSyntaxException | ExecutionException e) {
+ throw new EvalException(loc, "Illegal absolute label syntax: " + labelString);
+ }
}
- }
- };
+ };
// We want the Label ctor to show up under the Label documentation, but to be a "global
// function." Thus, we create a global Label object here, which just points to the Skylark
@@ -789,18 +781,18 @@
@SkylarkSignature(name = "FileType",
doc = "Deprecated. Creates a file filter from a list of strings. For example, to match "
- + "files ending with .cc or .cpp, use: "
- + "<pre class=language-python>FileType([\".cc\", \".cpp\"])</pre>",
+ + "files ending with .cc or .cpp, use: "
+ + "<pre class=language-python>FileType([\".cc\", \".cpp\"])</pre>",
returnType = SkylarkFileType.class,
objectType = SkylarkFileType.class,
parameters = {
- @Param(name = "types", type = SkylarkList.class, generic1 = String.class,
- defaultValue = "[]", doc = "a list of the accepted file extensions")})
+ @Param(name = "types", type = SkylarkList.class, generic1 = String.class, defaultValue = "[]",
+ doc = "a list of the accepted file extensions")})
private static final BuiltinFunction fileType = new BuiltinFunction("FileType") {
- public SkylarkFileType invoke(SkylarkList types) throws EvalException {
- return SkylarkFileType.of(types.getContents(String.class, "types"));
- }
- };
+ public SkylarkFileType invoke(SkylarkList types) throws EvalException {
+ return SkylarkFileType.of(types.getContents(String.class, "types"));
+ }
+ };
// We want the FileType ctor to show up under the FileType documentation, but to be a "global
// function." Thus, we create a global FileType object here, which just points to the Skylark
@@ -813,7 +805,6 @@
doc = "Creates a text message from the struct parameter. This method only works if all "
+ "struct elements (recursively) are strings, ints, booleans, other structs or a "
+ "list of these types. Quotes and new lines in strings are escaped. "
- + "Keys are iterated in the sorted order. "
+ "Examples:<br><pre class=language-python>"
+ "struct(key=123).to_proto()\n# key: 123\n\n"
+ "struct(key=True).to_proto()\n# key: true\n\n"
@@ -827,62 +818,59 @@
+ "# key {\n# inner_key {\n# inner_inner_key: \"text\"\n# }\n# }\n</pre>",
objectType = SkylarkClassObject.class, returnType = String.class,
parameters = {
- // TODO(bazel-team): shouldn't we accept any ClassObject?
- @Param(name = "self", type = SkylarkClassObject.class,
- doc = "this struct")},
+ // TODO(bazel-team): shouldn't we accept any ClassObject?
+ @Param(name = "self", type = SkylarkClassObject.class,
+ doc = "this struct")},
useLocation = true)
private static final BuiltinFunction toProto = new BuiltinFunction("to_proto") {
- public String invoke(SkylarkClassObject self, Location loc) throws EvalException {
- StringBuilder sb = new StringBuilder();
- printProtoTextMessage(self, sb, 0, loc);
- return sb.toString();
- }
-
- private void printProtoTextMessage(
- ClassObject object, StringBuilder sb, int indent, Location loc) throws EvalException {
- // For determinism sort the keys alphabetically
- List<String> keys = new ArrayList<>(object.getKeys());
- Collections.sort(keys);
- for (String key : keys) {
- printProtoTextMessage(key, object.getValue(key), sb, indent, loc);
+ public String invoke(SkylarkClassObject self, Location loc) throws EvalException {
+ StringBuilder sb = new StringBuilder();
+ printProtoTextMessage(self, sb, 0, loc);
+ return sb.toString();
}
- }
- private void printProtoTextMessage(String key, Object value, StringBuilder sb,
- int indent, Location loc, String container) throws EvalException {
- if (value instanceof ClassObject) {
- print(sb, key + " {", indent);
- printProtoTextMessage((ClassObject) value, sb, indent + 1, loc);
- print(sb, "}", indent);
- } else if (value instanceof String) {
- print(sb,
- key + ": \"" + escapeDoubleQuotesAndBackslashesAndNewlines((String) value) + "\"",
- indent);
- } else if (value instanceof Integer) {
- print(sb, key + ": " + value, indent);
- } else if (value instanceof Boolean) {
- // We're relying on the fact that Java converts Booleans to Strings in the same way
- // as the protocol buffers do.
- print(sb, key + ": " + value, indent);
- } else {
- throw new EvalException(loc,
- "Invalid text format, expected a struct, a string, a bool, or an int but got a "
- + EvalUtils.getDataTypeName(value) + " for " + container + " '" + key + "'");
- }
- }
-
- private void printProtoTextMessage(String key, Object value, StringBuilder sb,
- int indent, Location loc) throws EvalException {
- if (value instanceof SkylarkList) {
- for (Object item : ((SkylarkList) value)) {
- // TODO(bazel-team): There should be some constraint on the fields of the structs
- // in the same list but we ignore that for now.
- printProtoTextMessage(key, item, sb, indent, loc, "list element in struct field");
+ private void printProtoTextMessage(ClassObject object, StringBuilder sb,
+ int indent, Location loc) throws EvalException {
+ for (String key : object.getKeys()) {
+ printProtoTextMessage(key, object.getValue(key), sb, indent, loc);
}
- } else {
- printProtoTextMessage(key, value, sb, indent, loc, "struct field");
}
- }
+
+ private void printProtoTextMessage(String key, Object value, StringBuilder sb,
+ int indent, Location loc, String container) throws EvalException {
+ if (value instanceof ClassObject) {
+ print(sb, key + " {", indent);
+ printProtoTextMessage((ClassObject) value, sb, indent + 1, loc);
+ print(sb, "}", indent);
+ } else if (value instanceof String) {
+ print(sb,
+ key + ": \"" + escapeDoubleQuotesAndBackslashesAndNewlines((String) value) + "\"",
+ indent);
+ } else if (value instanceof Integer) {
+ print(sb, key + ": " + value, indent);
+ } else if (value instanceof Boolean) {
+ // We're relying on the fact that Java converts Booleans to Strings in the same way
+ // as the protocol buffers do.
+ print(sb, key + ": " + value, indent);
+ } else {
+ throw new EvalException(loc,
+ "Invalid text format, expected a struct, a string, a bool, or an int but got a "
+ + EvalUtils.getDataTypeName(value) + " for " + container + " '" + key + "'");
+ }
+ }
+
+ private void printProtoTextMessage(String key, Object value, StringBuilder sb,
+ int indent, Location loc) throws EvalException {
+ if (value instanceof SkylarkList) {
+ for (Object item : ((SkylarkList) value)) {
+ // TODO(bazel-team): There should be some constraint on the fields of the structs
+ // in the same list but we ignore that for now.
+ printProtoTextMessage(key, item, sb, indent, loc, "list element in struct field");
+ }
+ } else {
+ printProtoTextMessage(key, value, sb, indent, loc, "struct field");
+ }
+ }
private void print(StringBuilder sb, String text, int indent) {
for (int i = 0; i < indent; i++) {
@@ -890,8 +878,8 @@
}
sb.append(text);
sb.append("\n");
- }
- };
+ }
+ };
/**
* Escapes the given string for use in proto/JSON string.
@@ -992,13 +980,13 @@
}
)
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
- ? provider.getOutputGroup(group)
- : NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER);
- return SkylarkNestedSet.of(Artifact.class, result);
- }
+ public SkylarkNestedSet invoke(TransitiveInfoCollection self, String group) {
+ OutputGroupProvider provider = self.getProvider(OutputGroupProvider.class);
+ NestedSet<Artifact> result = provider != null
+ ? provider.getOutputGroup(group)
+ : NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER);
+ return SkylarkNestedSet.of(Artifact.class, result);
+ }
};
static {
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java b/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java
index c95df57..c83ed98 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java
@@ -16,7 +16,6 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
@@ -73,9 +72,6 @@
o1 = SkylarkType.convertToSkylark(o1, /*env=*/ null);
o2 = SkylarkType.convertToSkylark(o2, /*env=*/ null);
- if (o1 instanceof ClassObject && o2 instanceof ClassObject) {
- throw new ComparisonException("Cannot compare structs");
- }
if (o1 instanceof SkylarkNestedSet && o2 instanceof SkylarkNestedSet) {
throw new ComparisonException("Cannot compare sets");
}
@@ -316,15 +312,6 @@
return ((SkylarkList) o).getImmutableList();
} else if (o instanceof Map) {
// For dictionaries we iterate through the keys only
- if (o instanceof SkylarkDict) {
- // SkylarkDicts handle ordering themselves
- SkylarkDict<?, ?> dict = (SkylarkDict) o;
- List<Object> list = Lists.newArrayListWithCapacity(dict.size());
- for (Map.Entry<?, ?> entries : dict.entrySet()) {
- list.add(entries.getKey());
- }
- return ImmutableList.copyOf(list);
- }
// For determinism, we sort the keys.
try {
return SKYLARK_COMPARATOR.sortedCopy(((Map<?, ?>) o).keySet());
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 7ca5793..5748f63 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
@@ -801,7 +801,7 @@
ImmutableList.Builder<Object> posargs = new ImmutableList.Builder<>();
// We copy this into an ImmutableMap in the end, but we can't use an ImmutableMap.Builder, or
// we'd still have to have a HashMap on the side for the sake of properly handling duplicates.
- Map<String, Object> kwargs = new LinkedHashMap<>();
+ Map<String, Object> kwargs = new HashMap<>();
BaseFunction function = checkCallable(funcValue, getLocation());
evalArguments(posargs, kwargs, env);
return function.call(posargs.build(), ImmutableMap.copyOf(kwargs), this, env);
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java b/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java
index 52da289..b2a33c5 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java
@@ -1432,9 +1432,9 @@
objectType = SkylarkDict.class,
returnType = MutableList.class,
doc =
- "Returns the list of values:"
+ "Returns the list of values. Dictionaries are always sorted by their keys:"
+ "<pre class=\"language-python\">"
- + "{2: \"a\", 4: \"b\", 1: \"c\"}.values() == [\"a\", \"b\", \"c\"]</pre>\n",
+ + "{2: \"a\", 4: \"b\", 1: \"c\"}.values() == [\"c\", \"a\", \"b\"]</pre>\n",
parameters = {@Param(name = "self", type = SkylarkDict.class, doc = "This dict.")},
useEnvironment = true
)
@@ -1450,9 +1450,9 @@
objectType = SkylarkDict.class,
returnType = MutableList.class,
doc =
- "Returns the list of key-value tuples:"
+ "Returns the list of key-value tuples. Dictionaries are always sorted by their keys:"
+ "<pre class=\"language-python\">"
- + "{2: \"a\", 4: \"b\", 1: \"c\"}.items() == [(2, \"a\"), (4, \"b\"), (1, \"c\")]"
+ + "{2: \"a\", 4: \"b\", 1: \"c\"}.items() == [(1, \"c\"), (2, \"a\"), (4, \"b\")]"
+ "</pre>\n",
parameters = {@Param(name = "self", type = SkylarkDict.class, doc = "This dict.")},
useEnvironment = true
@@ -1470,8 +1470,8 @@
@SkylarkSignature(name = "keys", objectType = SkylarkDict.class,
returnType = MutableList.class,
- doc = "Returns the list of keys:"
- + "<pre class=\"language-python\">{2: \"a\", 4: \"b\", 1: \"c\"}.keys() == [2, 4, 1]"
+ doc = "Returns the list of keys. Dictionaries are always sorted by their keys:"
+ + "<pre class=\"language-python\">{2: \"a\", 4: \"b\", 1: \"c\"}.keys() == [1, 2, 4]"
+ "</pre>\n",
parameters = {
@Param(name = "self", type = SkylarkDict.class, doc = "This dict.")},
@@ -1482,11 +1482,9 @@
@SuppressWarnings("unchecked")
public MutableList<?> invoke(SkylarkDict<?, ?> self,
Environment env) throws EvalException {
- List<Object> list = Lists.newArrayListWithCapacity(self.size());
- for (Map.Entry<?, ?> entries : self.entrySet()) {
- list.add(entries.getKey());
- }
- return new MutableList(list, env);
+ return new MutableList(
+ Ordering.natural().sortedCopy((Set<Comparable<?>>) (Set<?>) self.keySet()),
+ env);
}
};
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkDict.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkDict.java
index af75d43..6ae2ae5 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkDict.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkDict.java
@@ -18,8 +18,8 @@
import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory;
import com.google.devtools.build.lib.syntax.SkylarkMutable.MutableMap;
-import java.util.LinkedHashMap;
import java.util.Map;
+import java.util.TreeMap;
import javax.annotation.Nullable;
/**
@@ -38,7 +38,7 @@
+ "d = {\"a\" : 1} + {\"b\" : 2} # d == {\"a\" : 1, \"b\" : 2}\n"
+ "d += {\"c\" : 3} # d == {\"a\" : 1, \"b\" : 2, \"c\" : 3}\n"
+ "d = d + {\"c\" : 5} # d == {\"a\" : 1, \"b\" : 2, \"c\" : 5}</pre>"
- + "Iterating on a dict is equivalent to iterating on its keys (order is not specified).<br>"
+ + "Iterating on a dict is equivalent to iterating on its keys (in sorted order).<br>"
+ "Dicts support the <code>in</code> operator, testing membership in the keyset of the dict. "
+ "Example:<br>"
+ "<pre class=\"language-python\">\"a\" in {\"a\" : 2, \"b\" : 5} # evaluates as True"
@@ -46,7 +46,7 @@
public final class SkylarkDict<K, V>
extends MutableMap<K, V> implements Map<K, V>, SkylarkIndexable {
- private final LinkedHashMap<K, V> contents = new LinkedHashMap<>();
+ private final TreeMap<K, V> contents = new TreeMap<>(EvalUtils.SKYLARK_COMPARATOR);
private final Mutability mutability;
@@ -139,7 +139,7 @@
/** @return the first key in the dict */
K firstKey() {
- return contents.entrySet().iterator().next().getKey();
+ return contents.firstKey();
}
/**
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 066c544..17f5484 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
@@ -657,11 +657,6 @@
}
@Test
- public void testProtoFieldsOrder() throws Exception {
- checkTextMessage("struct(d=4, b=2, c=3, a=1).to_proto()", "a: 1", "b: 2", "c: 3", "d: 4");
- }
-
- @Test
public void testTextMessageEscapes() throws Exception {
checkTextMessage("struct(name='a\"b').to_proto()", "name: \"a\\\"b\"");
checkTextMessage("struct(name='a\\'b').to_proto()", "name: \"a'b\"");
@@ -815,14 +810,6 @@
}
@Test
- public void testStructIncomparability() throws Exception {
- checkErrorContains("Cannot compare structs", "struct(a = 1) < struct(a = 2)");
- checkErrorContains("Cannot compare structs", "struct(a = 1) > struct(a = 2)");
- checkErrorContains("Cannot compare structs", "struct(a = 1) <= struct(a = 2)");
- checkErrorContains("Cannot compare structs", "struct(a = 1) >= struct(a = 2)");
- }
-
- @Test
public void testStructAccessingFieldsFromSkylark() throws Exception {
eval("x = struct(a = 1, b = 2)", "x1 = x.a", "x2 = x.b");
assertThat(lookup("x1")).isEqualTo(1);
@@ -947,18 +934,6 @@
}
@Test
- public void testStructsInDicts() throws Exception {
- eval("d = {struct(a = 1): 'aa', struct(b = 2): 'bb'}");
- assertThat(eval("d[struct(a = 1)]")).isEqualTo("aa");
- assertThat(eval("d[struct(b = 2)]")).isEqualTo("bb");
- assertThat(eval("str([d[k] for k in d])")).isEqualTo("[\"aa\", \"bb\"]");
-
- checkErrorContains(
- "unhashable type: 'struct'",
- "{struct(a = []): 'foo'}");
- }
-
- @Test
public void testStructMembersAreImmutable() throws Exception {
checkErrorContains(
"cannot assign to 's.x'",
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 a36fc74..b60dace 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
@@ -547,27 +547,27 @@
assertEquals(
SkylarkList.createImmutable(
ImmutableList.<String>of(
- "name",
- "visibility",
- "tags",
- "generator_name",
+ "cmd",
+ "compatible_with",
+ "executable",
+ "features",
"generator_function",
"generator_location",
- "features",
- "compatible_with",
- "restricted_to",
- "srcs",
- "tools",
- "toolchains",
- "outs",
- "cmd",
- "output_to_bindir",
+ "generator_name",
+ "heuristic_label_expansion",
+ "kind",
"local",
"message",
- "executable",
+ "name",
+ "output_to_bindir",
+ "outs",
+ "restricted_to",
+ "srcs",
"stamp",
- "heuristic_label_expansion",
- "kind")),
+ "tags",
+ "toolchains",
+ "tools",
+ "visibility")),
result);
}
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java b/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java
index 9bdaa15..fa72014 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java
@@ -146,11 +146,7 @@
.testStatement("'hello' == 'bye'", false)
.testStatement("None == None", true)
.testStatement("[1, 2] == [1, 2]", true)
- .testStatement("[1, 2] == [2, 1]", false)
- .testStatement("{'a': 1, 'b': 2} == {'b': 2, 'a': 1}", true)
- .testStatement("{'a': 1, 'b': 2} == {'a': 1}", false)
- .testStatement("{'a': 1, 'b': 2} == {'a': 1, 'b': 2, 'c': 3}", false)
- .testStatement("{'a': 1, 'b': 2} == {'a': 1, 'b': 3}", false);
+ .testStatement("[1, 2] == [2, 1]", false);
}
@Test
@@ -161,11 +157,7 @@
.testStatement("'hello' != 'hel' + 'lo'", false)
.testStatement("'hello' != 'bye'", true)
.testStatement("[1, 2] != [1, 2]", false)
- .testStatement("[1, 2] != [2, 1]", true)
- .testStatement("{'a': 1, 'b': 2} != {'b': 2, 'a': 1}", false)
- .testStatement("{'a': 1, 'b': 2} != {'a': 1}", true)
- .testStatement("{'a': 1, 'b': 2} != {'a': 1, 'b': 2, 'c': 3}", true)
- .testStatement("{'a': 1, 'b': 2} != {'a': 1, 'b': 3}", true);
+ .testStatement("[1, 2] != [2, 1]", true);
}
@Test
@@ -280,10 +272,7 @@
.update(kwargs.getName(), kwargs)
.testEval(
"kwargs(foo=1, bar='bar', wiz=[1,2,3]).items()",
- "[('foo', 1), ('bar', 'bar'), ('wiz', [1, 2, 3])]")
- .testEval(
- "kwargs(wiz=[1,2,3], bar='bar', foo=1).items()",
- "[('wiz', [1, 2, 3]), ('bar', 'bar'), ('foo', 1)]");
+ "[('bar', 'bar'), ('foo', 1), ('wiz', [1, 2, 3])]");
}
@Test
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java b/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java
index a001f3f..ea81adb 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java
@@ -1311,8 +1311,8 @@
new BothModesTest()
.testEval("{1: 'foo'}.values()", "['foo']")
.testEval("{}.values()", "[]")
- .testEval("{True: 3, False: 5}.values()", "[3, 5]")
- .testEval("{'a': 5, 'c': 2, 'b': 4, 'd': 3}.values()", "[5, 2, 4, 3]");
+ .testEval("{True: 3, False: 5}.values()", "[5, 3]")
+ .testEval("{'a': 5, 'c': 2, 'b': 4, 'd': 3}.values()", "[5, 4, 2, 3]");
// sorted by keys
}
@@ -1321,9 +1321,9 @@
new BothModesTest()
.testEval("{1: 'foo'}.keys()", "[1]")
.testEval("{}.keys()", "[]")
- .testEval("{True: 3, False: 5}.keys()", "[True, False]")
+ .testEval("{True: 3, False: 5}.keys()", "[False, True]")
.testEval(
- "{1:'a', 2:'b', 6:'c', 0:'d', 5:'e', 4:'f', 3:'g'}.keys()", "[1, 2, 6, 0, 5, 4, 3]");
+ "{1:'a', 2:'b', 6:'c', 0:'d', 5:'e', 4:'f', 3:'g'}.keys()", "[0, 1, 2, 3, 4, 5, 6]");
}
@Test
@@ -1342,7 +1342,7 @@
.testEval("{'a': 'foo'}.items()", "[('a', 'foo')]")
.testEval("{}.items()", "[]")
.testEval("{1: 3, 2: 5}.items()", "[(1, 3), (2, 5)]")
- .testEval("{'a': 5, 'c': 2, 'b': 4}.items()", "[('a', 5), ('c', 2), ('b', 4)]");
+ .testEval("{'a': 5, 'c': 2, 'b': 4}.items()", "[('a', 5), ('b', 4), ('c', 2)]");
}
@Test
@@ -1378,9 +1378,9 @@
"popitem(): dictionary is empty",
"d = {2: 'bar', 3: 'baz', 1: 'foo'}\n"
+ "if len(d) != 3: fail('popitem 0')\n"
+ + "if d.popitem() != (1, 'foo'): fail('popitem 1')\n"
+ "if d.popitem() != (2, 'bar'): fail('popitem 2')\n"
+ "if d.popitem() != (3, 'baz'): fail('popitem 3')\n"
- + "if d.popitem() != (1, 'foo'): fail('popitem 1')\n"
+ "if d != {}: fail('popitem 4')\n"
+ "d.popitem()");
}
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
index e949290..dcb927f 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
@@ -919,7 +919,7 @@
" for a in d:",
" s += a",
" return s",
- "s = foo()").testLookup("s", "cab");
+ "s = foo()").testLookup("s", "abc");
}
@Test