Replace android.WriteFile rule with android.WriteFileRule
The android.WriteFile rule takes careful escaping to produce the
right contents. Wrap it in an android.WriteFileRule that handles
the escaping.
Test: compare all android.WriteFile outputs
Change-Id: If71a5843af47a37ca61714e1a1ebb32d08536c31
diff --git a/android/apex.go b/android/apex.go
index e70ec4f..276f7a4 100644
--- a/android/apex.go
+++ b/android/apex.go
@@ -598,36 +598,22 @@
var fullContent strings.Builder
var flatContent strings.Builder
- fmt.Fprintf(&fullContent, "%s(minSdkVersion:%s):\\n", ctx.ModuleName(), minSdkVersion)
+ fmt.Fprintf(&fullContent, "%s(minSdkVersion:%s):\n", ctx.ModuleName(), minSdkVersion)
for _, key := range FirstUniqueStrings(SortedStringKeys(depInfos)) {
info := depInfos[key]
toName := fmt.Sprintf("%s(minSdkVersion:%s)", info.To, info.MinSdkVersion)
if info.IsExternal {
toName = toName + " (external)"
}
- fmt.Fprintf(&fullContent, " %s <- %s\\n", toName, strings.Join(SortedUniqueStrings(info.From), ", "))
- fmt.Fprintf(&flatContent, "%s\\n", toName)
+ fmt.Fprintf(&fullContent, " %s <- %s\n", toName, strings.Join(SortedUniqueStrings(info.From), ", "))
+ fmt.Fprintf(&flatContent, "%s\n", toName)
}
d.fullListPath = PathForModuleOut(ctx, "depsinfo", "fulllist.txt").OutputPath
- ctx.Build(pctx, BuildParams{
- Rule: WriteFile,
- Description: "Full Dependency Info",
- Output: d.fullListPath,
- Args: map[string]string{
- "content": fullContent.String(),
- },
- })
+ WriteFileRule(ctx, d.fullListPath, fullContent.String())
d.flatListPath = PathForModuleOut(ctx, "depsinfo", "flatlist.txt").OutputPath
- ctx.Build(pctx, BuildParams{
- Rule: WriteFile,
- Description: "Flat Dependency Info",
- Output: d.flatListPath,
- Args: map[string]string{
- "content": flatContent.String(),
- },
- })
+ WriteFileRule(ctx, d.flatListPath, flatContent.String())
}
// TODO(b/158059172): remove minSdkVersion allowlist
diff --git a/android/api_levels.go b/android/api_levels.go
index bace3d4..08328e1 100644
--- a/android/api_levels.go
+++ b/android/api_levels.go
@@ -225,14 +225,7 @@
ctx.Errorf(err.Error())
}
- ctx.Build(pctx, BuildParams{
- Rule: WriteFile,
- Description: "generate " + file.Base(),
- Output: file,
- Args: map[string]string{
- "content": string(jsonStr[:]),
- },
- })
+ WriteFileRule(ctx, file, string(jsonStr))
}
func GetApiLevelsJson(ctx PathContext) WritablePath {
diff --git a/android/defs.go b/android/defs.go
index 2b1bd85..631dfe8 100644
--- a/android/defs.go
+++ b/android/defs.go
@@ -15,8 +15,12 @@
package android
import (
+ "strings"
+ "testing"
+
"github.com/google/blueprint"
_ "github.com/google/blueprint/bootstrap"
+ "github.com/google/blueprint/proptools"
)
var (
@@ -91,9 +95,9 @@
// ubuntu 14.04 offcially use dash for /bin/sh, and its builtin echo command
// doesn't support -e option. Therefore we force to use /bin/bash when writing out
// content to file.
- WriteFile = pctx.AndroidStaticRule("WriteFile",
+ writeFile = pctx.AndroidStaticRule("writeFile",
blueprint.RuleParams{
- Command: "/bin/bash -c 'echo -e $$0 > $out' '$content'",
+ Command: `/bin/bash -c 'echo -e "$$0" > $out' $content`,
Description: "writing file $out",
},
"content")
@@ -111,3 +115,64 @@
func init() {
pctx.Import("github.com/google/blueprint/bootstrap")
}
+
+var (
+ // echoEscaper escapes a string such that passing it to "echo -e" will produce the input value.
+ echoEscaper = strings.NewReplacer(
+ `\`, `\\`, // First escape existing backslashes so they aren't interpreted by `echo -e`.
+ "\n", `\n`, // Then replace newlines with \n
+ )
+
+ // echoEscaper reverses echoEscaper.
+ echoUnescaper = strings.NewReplacer(
+ `\n`, "\n",
+ `\\`, `\`,
+ )
+
+ // shellUnescaper reverses the replacer in proptools.ShellEscape
+ shellUnescaper = strings.NewReplacer(`'\''`, `'`)
+)
+
+// WriteFileRule creates a ninja rule to write contents to a file. The contents will be escaped
+// so that the file contains exactly the contents passed to the function, plus a trailing newline.
+func WriteFileRule(ctx BuilderContext, outputFile WritablePath, content string) {
+ content = echoEscaper.Replace(content)
+ content = proptools.ShellEscape(content)
+ if content == "" {
+ content = "''"
+ }
+ ctx.Build(pctx, BuildParams{
+ Rule: writeFile,
+ Output: outputFile,
+ Description: "write " + outputFile.Base(),
+ Args: map[string]string{
+ "content": content,
+ },
+ })
+}
+
+// shellUnescape reverses proptools.ShellEscape
+func shellUnescape(s string) string {
+ // Remove leading and trailing quotes if present
+ if len(s) >= 2 && s[0] == '\'' {
+ s = s[1 : len(s)-1]
+ }
+ s = shellUnescaper.Replace(s)
+ return s
+}
+
+// ContentFromFileRuleForTests returns the content that was passed to a WriteFileRule for use
+// in tests.
+func ContentFromFileRuleForTests(t *testing.T, params TestingBuildParams) string {
+ t.Helper()
+ if g, w := params.Rule, writeFile; g != w {
+ t.Errorf("expected params.Rule to be %q, was %q", w, g)
+ return ""
+ }
+
+ content := params.Args["content"]
+ content = shellUnescape(content)
+ content = echoUnescaper.Replace(content)
+
+ return content
+}
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 532a7aa..321883f 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -5574,7 +5574,7 @@
}
`, withManifestPackageNameOverrides([]string{"AppFoo:com.android.foo"}))
- bundleConfigRule := ctx.ModuleForTests("myapex", "android_common_myapex_image").Description("Bundle Config")
+ bundleConfigRule := ctx.ModuleForTests("myapex", "android_common_myapex_image").Output("bundle_config.json")
content := bundleConfigRule.Args["content"]
ensureContains(t, content, `"compression":{"uncompressed_glob":["apex_payload.img","apex_manifest.*"]}`)
@@ -5600,7 +5600,7 @@
set: "AppSet.apks",
}`)
mod := ctx.ModuleForTests("myapex", "android_common_myapex_image")
- bundleConfigRule := mod.Description("Bundle Config")
+ bundleConfigRule := mod.Output("bundle_config.json")
content := bundleConfigRule.Args["content"]
ensureContains(t, content, `"compression":{"uncompressed_glob":["apex_payload.img","apex_manifest.*"]}`)
s := mod.Rule("apexRule").Args["copy_commands"]
diff --git a/apex/builder.go b/apex/builder.go
index ad673d6..acfb8c5 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -373,14 +373,7 @@
panic(fmt.Errorf("error while marshalling to %q: %#v", output, err))
}
- ctx.Build(pctx, android.BuildParams{
- Rule: android.WriteFile,
- Output: output,
- Description: "Bundle Config " + output.String(),
- Args: map[string]string{
- "content": string(j),
- },
- })
+ android.WriteFileRule(ctx, output, string(j))
return output.OutputPath
}
diff --git a/apex/key.go b/apex/key.go
index d2d5786..43764da 100644
--- a/apex/key.go
+++ b/apex/key.go
@@ -116,7 +116,7 @@
partition string
}
toString := func(e apexKeyEntry) string {
- format := "name=%q public_key=%q private_key=%q container_certificate=%q container_private_key=%q partition=%q\\n"
+ format := "name=%q public_key=%q private_key=%q container_certificate=%q container_private_key=%q partition=%q\n"
if e.presigned {
return fmt.Sprintf(format, e.name, "PRESIGNED", "PRESIGNED", "PRESIGNED", "PRESIGNED", e.partition)
} else {
@@ -173,17 +173,9 @@
var filecontent strings.Builder
for _, name := range moduleNames {
- fmt.Fprintf(&filecontent, "%s", toString(apexKeyMap[name]))
+ filecontent.WriteString(toString(apexKeyMap[name]))
}
-
- ctx.Build(pctx, android.BuildParams{
- Rule: android.WriteFile,
- Description: "apexkeys.txt",
- Output: s.output,
- Args: map[string]string{
- "content": filecontent.String(),
- },
- })
+ android.WriteFileRule(ctx, s.output, filecontent.String())
}
func apexKeysTextFactory() android.Singleton {
diff --git a/cc/cc_test.go b/cc/cc_test.go
index b803cba..f616cf3 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -296,8 +296,8 @@
func checkWriteFileOutput(t *testing.T, params android.TestingBuildParams, expected []string) {
t.Helper()
- assertString(t, params.Rule.String(), android.WriteFile.String())
- actual := strings.FieldsFunc(strings.ReplaceAll(params.Args["content"], "\\n", "\n"), func(r rune) bool { return r == '\n' })
+ content := android.ContentFromFileRuleForTests(t, params)
+ actual := strings.FieldsFunc(content, func(r rune) bool { return r == '\n' })
assertArrayString(t, actual, expected)
}
diff --git a/cc/fuzz.go b/cc/fuzz.go
index fe3c12b..dddfe94 100644
--- a/cc/fuzz.go
+++ b/cc/fuzz.go
@@ -278,14 +278,7 @@
if fuzz.Properties.Fuzz_config != nil {
configPath := android.PathForModuleOut(ctx, "config").Join(ctx, "config.json")
- ctx.Build(pctx, android.BuildParams{
- Rule: android.WriteFile,
- Description: "fuzzer infrastructure configuration",
- Output: configPath,
- Args: map[string]string{
- "content": fuzz.Properties.Fuzz_config.String(),
- },
- })
+ android.WriteFileRule(ctx, configPath, fuzz.Properties.Fuzz_config.String())
fuzz.config = configPath
}
diff --git a/cc/snapshot_utils.go b/cc/snapshot_utils.go
index 238508d..05c06ac 100644
--- a/cc/snapshot_utils.go
+++ b/cc/snapshot_utils.go
@@ -93,13 +93,6 @@
func writeStringToFile(ctx android.SingletonContext, content, out string) android.OutputPath {
outPath := android.PathForOutput(ctx, out)
- ctx.Build(pctx, android.BuildParams{
- Rule: android.WriteFile,
- Output: outPath,
- Description: "WriteFile " + out,
- Args: map[string]string{
- "content": content,
- },
- })
+ android.WriteFileRule(ctx, outPath, content)
return outPath
}
diff --git a/cc/vndk.go b/cc/vndk.go
index 2cac03c..4a005f3 100644
--- a/cc/vndk.go
+++ b/cc/vndk.go
@@ -487,14 +487,7 @@
}
txt.outputFile = android.PathForModuleOut(ctx, filename).OutputPath
- ctx.Build(pctx, android.BuildParams{
- Rule: android.WriteFile,
- Output: txt.outputFile,
- Description: "Writing " + txt.outputFile.String(),
- Args: map[string]string{
- "content": strings.Join(list, "\\n"),
- },
- })
+ android.WriteFileRule(ctx, txt.outputFile, strings.Join(list, "\n"))
installPath := android.PathForModuleInstall(ctx, "etc")
ctx.InstallFile(installPath, filename, txt.outputFile)
@@ -825,14 +818,7 @@
merged = append(merged, addPrefix(filterOutLibClangRt(vndkcore), "VNDK-core: ")...)
merged = append(merged, addPrefix(vndkprivate, "VNDK-private: ")...)
c.vndkLibrariesFile = android.PathForOutput(ctx, "vndk", "vndk.libraries.txt")
- ctx.Build(pctx, android.BuildParams{
- Rule: android.WriteFile,
- Output: c.vndkLibrariesFile,
- Description: "Writing " + c.vndkLibrariesFile.String(),
- Args: map[string]string{
- "content": strings.Join(merged, "\\n"),
- },
- })
+ android.WriteFileRule(ctx, c.vndkLibrariesFile, strings.Join(merged, "\n"))
}
func (c *vndkSnapshotSingleton) MakeVars(ctx android.MakeVarsContext) {
diff --git a/dexpreopt/config.go b/dexpreopt/config.go
index 03accc8..f77246e 100644
--- a/dexpreopt/config.go
+++ b/dexpreopt/config.go
@@ -488,13 +488,7 @@
return
}
- ctx.Build(pctx, android.BuildParams{
- Rule: android.WriteFile,
- Output: android.PathForOutput(ctx, "dexpreopt_soong.config"),
- Args: map[string]string{
- "content": string(data),
- },
- })
+ android.WriteFileRule(ctx, android.PathForOutput(ctx, "dexpreopt_soong.config"), string(data))
}
func (s *globalSoongConfigSingleton) MakeVars(ctx android.MakeVarsContext) {
diff --git a/java/builder.go b/java/builder.go
index 3043e46..cd35245 100644
--- a/java/builder.go
+++ b/java/builder.go
@@ -572,14 +572,7 @@
}
func GenerateMainClassManifest(ctx android.ModuleContext, outputFile android.WritablePath, mainClass string) {
- ctx.Build(pctx, android.BuildParams{
- Rule: android.WriteFile,
- Description: "manifest",
- Output: outputFile,
- Args: map[string]string{
- "content": "Main-Class: " + mainClass + "\n",
- },
- })
+ android.WriteFileRule(ctx, outputFile, "Main-Class: "+mainClass+"\n")
}
func TransformZipAlign(ctx android.ModuleContext, outputFile android.WritablePath, inputFile android.Path) {
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index 629d34f..f9975ba 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -651,14 +651,8 @@
updatableBcpPackagesName := "updatable-bcp-packages.txt"
updatableBcpPackages := image.dir.Join(ctx, updatableBcpPackagesName)
- ctx.Build(pctx, android.BuildParams{
- Rule: android.WriteFile,
- Output: updatableBcpPackages,
- Args: map[string]string{
- // WriteFile automatically adds the last end-of-line.
- "content": strings.Join(updatablePackages, "\\n"),
- },
- })
+ // WriteFileRule automatically adds the last end-of-line.
+ android.WriteFileRule(ctx, updatableBcpPackages, strings.Join(updatablePackages, "\n"))
rule := android.NewRuleBuilder()
rule.MissingDeps(missingDeps)
@@ -720,13 +714,7 @@
func writeGlobalConfigForMake(ctx android.SingletonContext, path android.WritablePath) {
data := dexpreopt.GetGlobalConfigRawData(ctx)
- ctx.Build(pctx, android.BuildParams{
- Rule: android.WriteFile,
- Output: path,
- Args: map[string]string{
- "content": string(data),
- },
- })
+ android.WriteFileRule(ctx, path, string(data))
}
// Export paths for default boot image to Make