Write raw files to disk instead of the ninja file

Writing raw files as rules in the ninja file unnecessarily bloats
the ninja file.  Write files immediately to disk instead to files
based on the hash of the contents, and then emit ninja rules to
copy the files into place during the build.  Delete obsolete files
in a singleton at the end of analysis.

Bug: 306029038
Test: Run: m libc_musl_version.h
           touch build/soong/Android.bp
           m libc_musl_version.h
      libc_musl_version.h/genrule.sbox.textproto is not recopied.
Test: Run: lunch aosp_cf_x86_64_phone-userdebug
           m libc_musl_version.h
	   lunch aosp_x86_64-userdebug
	   m libc_musl_version.h
	   lunch aosp_cf_x86_64_phone-userdebug
	   m libc_musl_version.h
      libc_musl_version.h/genrule.sbox.textproto is recopied but restat prevents rerunning the genrule.
Test: Run: touch out/soong/raw-aosp_cf_x86_64_phone/00/foo
           touch build/soong/Android.bp
	   m nothing
      out/soong/raw-aosp_cf_x86_64_phone/00/foo is removed.
Change-Id: I172869c4d49565504794c051e2e8c1f7cf46486e
This commit is contained in:
Colin Cross
2023-11-02 16:57:08 -07:00
parent 51428c451a
commit 31a674571e
12 changed files with 340 additions and 144 deletions

View File

@@ -15,13 +15,8 @@
package android
import (
"fmt"
"strings"
"testing"
"github.com/google/blueprint"
"github.com/google/blueprint/bootstrap"
"github.com/google/blueprint/proptools"
)
var (
@@ -72,8 +67,7 @@ var (
Command: "if ! cmp -s $in $out; then cp $in $out; fi",
Description: "cp if changed $out",
Restat: true,
},
"cpFlags")
})
CpExecutable = pctx.AndroidStaticRule("CpExecutable",
blueprint.RuleParams{
@@ -146,106 +140,6 @@ func BazelCcToolchainVars(config Config) string {
return BazelToolchainVars(config, exportedVars)
}
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(`'\''`, `'`)
)
func buildWriteFileRule(ctx BuilderContext, outputFile WritablePath, content string) {
content = echoEscaper.Replace(content)
content = proptools.NinjaEscape(proptools.ShellEscapeIncludingSpaces(content))
if content == "" {
content = "''"
}
ctx.Build(pctx, BuildParams{
Rule: writeFile,
Output: outputFile,
Description: "write " + outputFile.Base(),
Args: map[string]string{
"content": content,
},
})
}
// 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) {
WriteFileRuleVerbatim(ctx, outputFile, content+"\n")
}
// WriteFileRuleVerbatim 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.
func WriteFileRuleVerbatim(ctx BuilderContext, outputFile WritablePath, content string) {
// This is MAX_ARG_STRLEN subtracted with some safety to account for shell escapes
const SHARD_SIZE = 131072 - 10000
if len(content) > SHARD_SIZE {
var chunks WritablePaths
for i, c := range ShardString(content, SHARD_SIZE) {
tempPath := outputFile.ReplaceExtension(ctx, fmt.Sprintf("%s.%d", outputFile.Ext(), i))
buildWriteFileRule(ctx, tempPath, c)
chunks = append(chunks, tempPath)
}
ctx.Build(pctx, BuildParams{
Rule: Cat,
Inputs: chunks.Paths(),
Output: outputFile,
Description: "Merging to " + outputFile.Base(),
})
return
}
buildWriteFileRule(ctx, outputFile, content)
}
// WriteExecutableFileRuleVerbatim is the same as WriteFileRuleVerbatim, but runs chmod +x on the result
func WriteExecutableFileRuleVerbatim(ctx BuilderContext, outputFile WritablePath, content string) {
intermediate := PathForIntermediates(ctx, "write_executable_file_intermediates").Join(ctx, outputFile.String())
WriteFileRuleVerbatim(ctx, intermediate, content)
ctx.Build(pctx, BuildParams{
Rule: CpExecutable,
Output: outputFile,
Input: intermediate,
})
}
// 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, ctx *TestContext, 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
}
// GlobToListFileRule creates a rule that writes a list of files matching a pattern to a file.
func GlobToListFileRule(ctx ModuleContext, pattern string, excludes []string, file WritablePath) {
bootstrap.GlobFile(ctx.blueprintModuleContext(), pattern, excludes, file.String())