RELNOTES: struct.to_proto() converts dict into proto3 text message (map<,>).
Fixes #7896
PiperOrigin-RevId: 243331636
diff --git a/src/main/java/com/google/devtools/build/lib/packages/StructImpl.java b/src/main/java/com/google/devtools/build/lib/packages/StructImpl.java
index 9cf24b0..509a834 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/StructImpl.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/StructImpl.java
@@ -210,7 +210,13 @@
private void printProtoTextMessage(
String key, Object value, StringBuilder sb, int indent, Location loc, String container)
throws EvalException {
- if (value instanceof ClassObject) {
+ if (value instanceof Map.Entry) {
+ Map.Entry<?, ?> entry = (Map.Entry<?, ?>) value;
+ print(sb, key + " {", indent);
+ printProtoTextMessage("key", entry.getKey(), sb, indent + 1, loc);
+ printProtoTextMessage("value", entry.getValue(), sb, indent + 1, loc);
+ print(sb, "}", indent);
+ } else if (value instanceof ClassObject) {
print(sb, key + " {", indent);
printProtoTextMessage((ClassObject) value, sb, indent + 1, loc);
print(sb, "}", indent);
@@ -228,7 +234,7 @@
} else {
throw new EvalException(
loc,
- "Invalid text format, expected a struct, a string, a bool, or an int but got a "
+ "Invalid text format, expected a struct, a dict, a string, a bool, or an int but got a "
+ EvalUtils.getDataTypeName(value)
+ " for "
+ container
@@ -246,6 +252,10 @@
// in the same list but we ignore that for now.
printProtoTextMessage(key, item, sb, indent, loc, "list element in struct field");
}
+ } else if (value instanceof SkylarkDict) {
+ for (Map.Entry<?, ?> entry : ((SkylarkDict<?, ?>) value).entrySet()) {
+ printProtoTextMessage(key, entry, sb, indent, loc, "entry of dictionary");
+ }
} else {
printProtoTextMessage(key, value, sb, indent, loc, "struct field");
}
diff --git a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/StructApi.java b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/StructApi.java
index ac79139..10c8b15 100644
--- a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/StructApi.java
+++ b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/StructApi.java
@@ -38,9 +38,10 @@
name = "to_proto",
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. "
+ + "struct elements (recursively) are strings, ints, booleans, "
+ + "other structs or dicts or lists of these types. "
+ + "Quotes and new lines in strings are escaped. "
+ + "Struct 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"
@@ -51,7 +52,17 @@
+ "struct(key=[struct(inner_key=1), struct(inner_key=2)]).to_proto()\n"
+ "# key {\n# inner_key: 1\n# }\n# key {\n# inner_key: 2\n# }\n\n"
+ "struct(key=struct(inner_key=struct(inner_inner_key='text'))).to_proto()\n"
- + "# key {\n# inner_key {\n# inner_inner_key: \"text\"\n# }\n# }\n</pre>",
+ + "# key {\n# inner_key {\n# inner_inner_key: \"text\"\n# }\n# }\n\n"
+ + "struct(foo={4: 3, 2: 1}).to_proto()\n"
+ + "# foo: {\n"
+ + "# key: 4\n"
+ + "# value: 3\n"
+ + "# }\n"
+ + "# foo: {\n"
+ + "# key: 2\n"
+ + "# value: 1\n"
+ + "# }\n"
+ + "</pre>",
useLocation = true)
public String toProto(Location loc) throws EvalException;
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 005d025..8c70a04 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
@@ -860,8 +860,13 @@
}
private void checkTextMessage(String from, String... lines) throws Exception {
+ String[] strings = lines.clone();
Object result = evalRuleClassCode(from);
- assertThat(result).isEqualTo(Joiner.on("\n").join(lines) + "\n");
+ String expect = "";
+ if (strings.length > 0) {
+ expect = Joiner.on("\n").join(lines) + "\n";
+ }
+ assertThat(result).isEqualTo(expect);
}
@Test
@@ -884,6 +889,7 @@
@Test
public void testSimpleTextMessages() throws Exception {
checkTextMessage("struct(name='value').to_proto()", "name: \"value\"");
+ checkTextMessage("struct(name=[]).to_proto()"); // empty lines
checkTextMessage("struct(name=['a', 'b']).to_proto()", "name: \"a\"", "name: \"b\"");
checkTextMessage("struct(name=123).to_proto()", "name: 123");
checkTextMessage("struct(name=[1, 2, 3]).to_proto()", "name: 1", "name: 2", "name: 3");
@@ -898,6 +904,53 @@
"}");
checkTextMessage(
"struct(a=struct(b=struct(c='c'))).to_proto()", "a {", " b {", " c: \"c\"", " }", "}");
+ // dict to_proto tests
+ checkTextMessage("struct(name={}).to_proto()"); // empty lines
+ checkTextMessage(
+ "struct(name={'a': 'b'}).to_proto()", "name {", " key: \"a\"", " value: \"b\"", "}");
+ checkTextMessage(
+ "struct(name={'c': 'd', 'a': 'b'}).to_proto()",
+ "name {",
+ " key: \"c\"",
+ " value: \"d\"",
+ "}",
+ "name {",
+ " key: \"a\"",
+ " value: \"b\"",
+ "}");
+ checkTextMessage(
+ "struct(x=struct(y={'a': 1})).to_proto()",
+ "x {",
+ " y {",
+ " key: \"a\"",
+ " value: 1",
+ " }",
+ "}");
+ checkTextMessage(
+ "struct(name={'a': struct(b=1, c=2)}).to_proto()",
+ "name {",
+ " key: \"a\"",
+ " value {",
+ " b: 1",
+ " c: 2",
+ " }",
+ "}");
+ checkTextMessage(
+ "struct(name={'a': struct(b={4: 'z', 3: 'y'}, c=2)}).to_proto()",
+ "name {",
+ " key: \"a\"",
+ " value {",
+ " b {",
+ " key: 4",
+ " value: \"z\"",
+ " }",
+ " b {",
+ " key: 3",
+ " value: \"y\"",
+ " }",
+ " c: 2",
+ " }",
+ "}");
}
@Test
@@ -918,7 +971,7 @@
@Test
public void testTextMessageInvalidElementInListStructure() throws Exception {
checkErrorContains(
- "Invalid text format, expected a struct, a string, a bool, or "
+ "Invalid text format, expected a struct, a dict, a string, a bool, or "
+ "an int but got a list for list element in struct field 'a'",
"struct(a=[['b']]).to_proto()");
}
@@ -926,7 +979,7 @@
@Test
public void testTextMessageInvalidStructure() throws Exception {
checkErrorContains(
- "Invalid text format, expected a struct, a string, a bool, or an int "
+ "Invalid text format, expected a struct, a dict, a string, a bool, or an int "
+ "but got a function for struct field 'a'",
"struct(a=rule).to_proto()");
}