export Java variables to Bazel
Test: build/bazel/bp2build.sh
Change-Id: Ia06f9265c9f96e6add6edbd0cee925fe37b430d3
diff --git a/android/config_bp2build.go b/android/config_bp2build.go
index 80b09fc..748be62 100644
--- a/android/config_bp2build.go
+++ b/android/config_bp2build.go
@@ -101,6 +101,17 @@
ev.exportedStringVars.set(name, value)
}
+// ExportVariableFuncVariable declares a variable whose value is evaluated at
+// runtime via a function and exports it to Bazel's toolchain.
+func (ev ExportedVariables) ExportVariableFuncVariable(name string, f func() string) {
+ ev.exportedConfigDependingVars.set(name, func(config Config) string {
+ return f()
+ })
+ ev.pctx.VariableFunc(name, func(PackageVarContext) string {
+ return f()
+ })
+}
+
// ExportString only exports a variable to Bazel, but does not declare it in Soong
func (ev ExportedVariables) ExportString(name string, value string) {
ev.exportedStringVars.set(name, value)
@@ -403,7 +414,8 @@
return ret, nil
}
var ret []string
- for _, v := range strings.Split(toExpand, " ") {
+ stringFields := splitStringKeepingQuotedSubstring(toExpand, ' ')
+ for _, v := range stringFields {
val, err := expandVarInternal(v, map[string]bool{})
if err != nil {
return ret, err
@@ -414,6 +426,46 @@
return ret, nil
}
+// splitStringKeepingQuotedSubstring splits a string on a provided separator,
+// but it will not split substrings inside unescaped double quotes. If the double
+// quotes are escaped, then the returned string will only include the quote, and
+// not the escape.
+func splitStringKeepingQuotedSubstring(s string, delimiter byte) []string {
+ var ret []string
+ quote := byte('"')
+
+ var substring []byte
+ quoted := false
+ escaped := false
+
+ for i := range s {
+ if !quoted && s[i] == delimiter {
+ ret = append(ret, string(substring))
+ substring = []byte{}
+ continue
+ }
+
+ characterIsEscape := i < len(s)-1 && s[i] == '\\' && s[i+1] == quote
+ if characterIsEscape {
+ escaped = true
+ continue
+ }
+
+ if s[i] == quote {
+ if !escaped {
+ quoted = !quoted
+ }
+ escaped = false
+ }
+
+ substring = append(substring, s[i])
+ }
+
+ ret = append(ret, string(substring))
+
+ return ret
+}
+
func validateVariableMethod(name string, methodValue reflect.Value) {
methodType := methodValue.Type()
if methodType.Kind() != reflect.Func {
diff --git a/android/config_bp2build_test.go b/android/config_bp2build_test.go
index 05a1798..1a0ba7b 100644
--- a/android/config_bp2build_test.go
+++ b/android/config_bp2build_test.go
@@ -321,3 +321,134 @@
})
}
}
+
+func TestSplitStringKeepingQuotedSubstring(t *testing.T) {
+ testCases := []struct {
+ description string
+ s string
+ delimiter byte
+ split []string
+ }{
+ {
+ description: "empty string returns single empty string",
+ s: "",
+ delimiter: ' ',
+ split: []string{
+ "",
+ },
+ },
+ {
+ description: "string with single space returns two empty strings",
+ s: " ",
+ delimiter: ' ',
+ split: []string{
+ "",
+ "",
+ },
+ },
+ {
+ description: "string with two spaces returns three empty strings",
+ s: " ",
+ delimiter: ' ',
+ split: []string{
+ "",
+ "",
+ "",
+ },
+ },
+ {
+ description: "string with four words returns four word string",
+ s: "hello world with words",
+ delimiter: ' ',
+ split: []string{
+ "hello",
+ "world",
+ "with",
+ "words",
+ },
+ },
+ {
+ description: "string with words and nested quote returns word strings and quote string",
+ s: `hello "world with" words`,
+ delimiter: ' ',
+ split: []string{
+ "hello",
+ `"world with"`,
+ "words",
+ },
+ },
+ {
+ description: "string with escaped quote inside real quotes",
+ s: `hello \"world "with\" words"`,
+ delimiter: ' ',
+ split: []string{
+ "hello",
+ `"world`,
+ `"with" words"`,
+ },
+ },
+ {
+ description: "string with words and escaped quotes returns word strings",
+ s: `hello \"world with\" words`,
+ delimiter: ' ',
+ split: []string{
+ "hello",
+ `"world`,
+ `with"`,
+ "words",
+ },
+ },
+ {
+ description: "string which is single quoted substring returns only substring",
+ s: `"hello world with words"`,
+ delimiter: ' ',
+ split: []string{
+ `"hello world with words"`,
+ },
+ },
+ {
+ description: "string starting with quote returns quoted string",
+ s: `"hello world with" words`,
+ delimiter: ' ',
+ split: []string{
+ `"hello world with"`,
+ "words",
+ },
+ },
+ {
+ description: "string with starting quote and no ending quote returns quote to end of string",
+ s: `hello "world with words`,
+ delimiter: ' ',
+ split: []string{
+ "hello",
+ `"world with words`,
+ },
+ },
+ {
+ description: "quoted string is treated as a single \"word\" unless separated by delimiter",
+ s: `hello "world"with words`,
+ delimiter: ' ',
+ split: []string{
+ "hello",
+ `"world"with`,
+ "words",
+ },
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.description, func(t *testing.T) {
+ split := splitStringKeepingQuotedSubstring(tc.s, tc.delimiter)
+ if len(split) != len(tc.split) {
+ t.Fatalf("number of split string elements (%d) differs from expected (%d): split string (%v), expected (%v)",
+ len(split), len(tc.split), split, tc.split,
+ )
+ }
+ for i := range split {
+ if split[i] != tc.split[i] {
+ t.Errorf("split string element (%d), %v, differs from expected, %v", i, split[i], tc.split[i])
+ }
+ }
+ })
+ }
+}