Merge "Write raw files to disk instead of the ninja file" into main
This commit is contained in:
@@ -81,6 +81,7 @@ bootstrap_go_package {
|
|||||||
"prebuilt_build_tool.go",
|
"prebuilt_build_tool.go",
|
||||||
"proto.go",
|
"proto.go",
|
||||||
"provider.go",
|
"provider.go",
|
||||||
|
"raw_files.go",
|
||||||
"register.go",
|
"register.go",
|
||||||
"rule_builder.go",
|
"rule_builder.go",
|
||||||
"sandbox.go",
|
"sandbox.go",
|
||||||
|
@@ -18,6 +18,7 @@ package android
|
|||||||
// product variables necessary for soong_build's operation.
|
// product variables necessary for soong_build's operation.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"android/soong/shared"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@@ -118,6 +119,11 @@ func (c Config) SoongOutDir() string {
|
|||||||
return c.soongOutDir
|
return c.soongOutDir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tempDir returns the path to out/soong/.temp, which is cleared at the beginning of every build.
|
||||||
|
func (c Config) tempDir() string {
|
||||||
|
return shared.TempDirForOutDir(c.soongOutDir)
|
||||||
|
}
|
||||||
|
|
||||||
func (c Config) OutDir() string {
|
func (c Config) OutDir() string {
|
||||||
return c.outDir
|
return c.outDir
|
||||||
}
|
}
|
||||||
|
108
android/defs.go
108
android/defs.go
@@ -15,13 +15,8 @@
|
|||||||
package android
|
package android
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/google/blueprint"
|
"github.com/google/blueprint"
|
||||||
"github.com/google/blueprint/bootstrap"
|
"github.com/google/blueprint/bootstrap"
|
||||||
"github.com/google/blueprint/proptools"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -72,8 +67,7 @@ var (
|
|||||||
Command: "if ! cmp -s $in $out; then cp $in $out; fi",
|
Command: "if ! cmp -s $in $out; then cp $in $out; fi",
|
||||||
Description: "cp if changed $out",
|
Description: "cp if changed $out",
|
||||||
Restat: true,
|
Restat: true,
|
||||||
},
|
})
|
||||||
"cpFlags")
|
|
||||||
|
|
||||||
CpExecutable = pctx.AndroidStaticRule("CpExecutable",
|
CpExecutable = pctx.AndroidStaticRule("CpExecutable",
|
||||||
blueprint.RuleParams{
|
blueprint.RuleParams{
|
||||||
@@ -146,106 +140,6 @@ func BazelCcToolchainVars(config Config) string {
|
|||||||
return BazelToolchainVars(config, exportedVars)
|
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.
|
// 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) {
|
func GlobToListFileRule(ctx ModuleContext, pattern string, excludes []string, file WritablePath) {
|
||||||
bootstrap.GlobFile(ctx.blueprintModuleContext(), pattern, excludes, file.String())
|
bootstrap.GlobFile(ctx.blueprintModuleContext(), pattern, excludes, file.String())
|
||||||
|
279
android/raw_files.go
Normal file
279
android/raw_files.go
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
// Copyright 2023 Google Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package android
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"github.com/google/blueprint"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/blueprint/proptools"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WriteFileRule creates a ninja rule to write contents to a file by immediately writing the
|
||||||
|
// contents, plus a trailing newline, to a file in out/soong/raw-${TARGET_PRODUCT}, and then creating
|
||||||
|
// a ninja rule to copy the file into place.
|
||||||
|
func WriteFileRule(ctx BuilderContext, outputFile WritablePath, content string) {
|
||||||
|
writeFileRule(ctx, outputFile, content, true, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteFileRuleVerbatim creates a ninja rule to write contents to a file by immediately writing the
|
||||||
|
// contents to a file in out/soong/raw-${TARGET_PRODUCT}, and then creating a ninja rule to copy the file into place.
|
||||||
|
func WriteFileRuleVerbatim(ctx BuilderContext, outputFile WritablePath, content string) {
|
||||||
|
writeFileRule(ctx, outputFile, content, false, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteExecutableFileRuleVerbatim is the same as WriteFileRuleVerbatim, but runs chmod +x on the result
|
||||||
|
func WriteExecutableFileRuleVerbatim(ctx BuilderContext, outputFile WritablePath, content string) {
|
||||||
|
writeFileRule(ctx, outputFile, content, false, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// tempFile provides a testable wrapper around a file in out/soong/.temp. It writes to a temporary file when
|
||||||
|
// not in tests, but writes to a buffer in memory when used in tests.
|
||||||
|
type tempFile struct {
|
||||||
|
// tempFile contains wraps an io.Writer, which will be file if testMode is false, or testBuf if it is true.
|
||||||
|
io.Writer
|
||||||
|
|
||||||
|
file *os.File
|
||||||
|
testBuf *strings.Builder
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTempFile(ctx BuilderContext, pattern string, testMode bool) *tempFile {
|
||||||
|
if testMode {
|
||||||
|
testBuf := &strings.Builder{}
|
||||||
|
return &tempFile{
|
||||||
|
Writer: testBuf,
|
||||||
|
testBuf: testBuf,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
f, err := os.CreateTemp(absolutePath(ctx.Config().tempDir()), pattern)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("failed to open temporary raw file: %w", err))
|
||||||
|
}
|
||||||
|
return &tempFile{
|
||||||
|
Writer: f,
|
||||||
|
file: f,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tempFile) close() error {
|
||||||
|
if t.file != nil {
|
||||||
|
return t.file.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tempFile) name() string {
|
||||||
|
if t.file != nil {
|
||||||
|
return t.file.Name()
|
||||||
|
}
|
||||||
|
return "temp_file_in_test"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tempFile) rename(to string) {
|
||||||
|
if t.file != nil {
|
||||||
|
os.MkdirAll(filepath.Dir(to), 0777)
|
||||||
|
err := os.Rename(t.file.Name(), to)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("failed to rename %s to %s: %w", t.file.Name(), to, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tempFile) remove() error {
|
||||||
|
if t.file != nil {
|
||||||
|
return os.Remove(t.file.Name())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeContentToTempFileAndHash(ctx BuilderContext, content string, newline bool) (*tempFile, string) {
|
||||||
|
tempFile := newTempFile(ctx, "raw", ctx.Config().captureBuild)
|
||||||
|
defer tempFile.close()
|
||||||
|
|
||||||
|
hash := sha1.New()
|
||||||
|
w := io.MultiWriter(tempFile, hash)
|
||||||
|
|
||||||
|
_, err := io.WriteString(w, content)
|
||||||
|
if err == nil && newline {
|
||||||
|
_, err = io.WriteString(w, "\n")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("failed to write to temporary raw file %s: %w", tempFile.name(), err))
|
||||||
|
}
|
||||||
|
return tempFile, hex.EncodeToString(hash.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeFileRule(ctx BuilderContext, outputFile WritablePath, content string, newline bool, executable bool) {
|
||||||
|
// Write the contents to a temporary file while computing its hash.
|
||||||
|
tempFile, hash := writeContentToTempFileAndHash(ctx, content, newline)
|
||||||
|
|
||||||
|
// Shard the final location of the raw file into a subdirectory based on the first two characters of the
|
||||||
|
// hash to avoid making the raw directory too large and slowing down accesses.
|
||||||
|
relPath := filepath.Join(hash[0:2], hash)
|
||||||
|
|
||||||
|
// These files are written during soong_build. If something outside the build deleted them there would be no
|
||||||
|
// trigger to rerun soong_build, and the build would break with dependencies on missing files. Writing them
|
||||||
|
// to their final locations would risk having them deleted when cleaning a module, and would also pollute the
|
||||||
|
// output directory with files for modules that have never been built.
|
||||||
|
// Instead, the files are written to a separate "raw" directory next to the build.ninja file, and a ninja
|
||||||
|
// rule is created to copy the files into their final location as needed.
|
||||||
|
// Obsolete files written by previous runs of soong_build must be cleaned up to avoid continually growing
|
||||||
|
// disk usage as the hashes of the files change over time. The cleanup must not remove files that were
|
||||||
|
// created by previous runs of soong_build for other products, as the build.ninja files for those products
|
||||||
|
// may still exist and still reference those files. The raw files from different products are kept
|
||||||
|
// separate by appending the Make_suffix to the directory name.
|
||||||
|
rawPath := PathForOutput(ctx, "raw"+proptools.String(ctx.Config().productVariables.Make_suffix), relPath)
|
||||||
|
|
||||||
|
rawFileInfo := rawFileInfo{
|
||||||
|
relPath: relPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.Config().captureBuild {
|
||||||
|
// When running tests tempFile won't write to disk, instead store the contents for later retrieval by
|
||||||
|
// ContentFromFileRuleForTests.
|
||||||
|
rawFileInfo.contentForTests = tempFile.testBuf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
rawFileSet := getRawFileSet(ctx.Config())
|
||||||
|
if _, exists := rawFileSet.LoadOrStore(hash, rawFileInfo); exists {
|
||||||
|
// If a raw file with this hash has already been created delete the temporary file.
|
||||||
|
tempFile.remove()
|
||||||
|
} else {
|
||||||
|
// If this is the first time this hash has been seen then move it from the temporary directory
|
||||||
|
// to the raw directory. If the file already exists in the raw directory assume it has the correct
|
||||||
|
// contents.
|
||||||
|
absRawPath := absolutePath(rawPath.String())
|
||||||
|
_, err := os.Stat(absRawPath)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
tempFile.rename(absRawPath)
|
||||||
|
} else if err != nil {
|
||||||
|
panic(fmt.Errorf("failed to stat %q: %w", absRawPath, err))
|
||||||
|
} else {
|
||||||
|
tempFile.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit a rule to copy the file from raw directory to the final requested location in the output tree.
|
||||||
|
// Restat is used to ensure that two different products that produce identical files copied from their
|
||||||
|
// own raw directories they don't cause everything downstream to rebuild.
|
||||||
|
rule := rawFileCopy
|
||||||
|
if executable {
|
||||||
|
rule = rawFileCopyExecutable
|
||||||
|
}
|
||||||
|
ctx.Build(pctx, BuildParams{
|
||||||
|
Rule: rule,
|
||||||
|
Input: rawPath,
|
||||||
|
Output: outputFile,
|
||||||
|
Description: "raw " + outputFile.Base(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
rawFileCopy = pctx.AndroidStaticRule("rawFileCopy",
|
||||||
|
blueprint.RuleParams{
|
||||||
|
Command: "if ! cmp -s $in $out; then cp $in $out; fi",
|
||||||
|
Description: "copy raw file $out",
|
||||||
|
Restat: true,
|
||||||
|
})
|
||||||
|
rawFileCopyExecutable = pctx.AndroidStaticRule("rawFileCopyExecutable",
|
||||||
|
blueprint.RuleParams{
|
||||||
|
Command: "if ! cmp -s $in $out; then cp $in $out; fi && chmod +x $out",
|
||||||
|
Description: "copy raw exectuable file $out",
|
||||||
|
Restat: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
type rawFileInfo struct {
|
||||||
|
relPath string
|
||||||
|
contentForTests string
|
||||||
|
}
|
||||||
|
|
||||||
|
var rawFileSetKey OnceKey = NewOnceKey("raw file set")
|
||||||
|
|
||||||
|
func getRawFileSet(config Config) *SyncMap[string, rawFileInfo] {
|
||||||
|
return config.Once(rawFileSetKey, func() any {
|
||||||
|
return &SyncMap[string, rawFileInfo]{}
|
||||||
|
}).(*SyncMap[string, rawFileInfo])
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 params.Rule != rawFileCopy && params.Rule != rawFileCopyExecutable {
|
||||||
|
t.Errorf("expected params.Rule to be rawFileCopy or rawFileCopyExecutable, was %q", params.Rule)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
key := filepath.Base(params.Input.String())
|
||||||
|
rawFileSet := getRawFileSet(ctx.Config())
|
||||||
|
rawFileInfo, _ := rawFileSet.Load(key)
|
||||||
|
|
||||||
|
return rawFileInfo.contentForTests
|
||||||
|
}
|
||||||
|
|
||||||
|
func rawFilesSingletonFactory() Singleton {
|
||||||
|
return &rawFilesSingleton{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type rawFilesSingleton struct{}
|
||||||
|
|
||||||
|
func (rawFilesSingleton) GenerateBuildActions(ctx SingletonContext) {
|
||||||
|
if ctx.Config().captureBuild {
|
||||||
|
// Nothing to do when running in tests, no temporary files were created.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rawFileSet := getRawFileSet(ctx.Config())
|
||||||
|
rawFilesDir := PathForOutput(ctx, "raw"+proptools.String(ctx.Config().productVariables.Make_suffix)).String()
|
||||||
|
absRawFilesDir := absolutePath(rawFilesDir)
|
||||||
|
err := filepath.WalkDir(absRawFilesDir, func(path string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if d.IsDir() {
|
||||||
|
// Ignore obsolete directories for now.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume the basename of the file is a hash
|
||||||
|
key := filepath.Base(path)
|
||||||
|
relPath, err := filepath.Rel(absRawFilesDir, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a file with the same hash was written by this run of soong_build. If the file was not written,
|
||||||
|
// or if a file with the same hash was written but to a different path in the raw directory, then delete it.
|
||||||
|
// Checking that the path matches allows changing the structure of the raw directory, for example to increase
|
||||||
|
// the sharding.
|
||||||
|
rawFileInfo, written := rawFileSet.Load(key)
|
||||||
|
if !written || rawFileInfo.relPath != relPath {
|
||||||
|
os.Remove(path)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("failed to clean %q: %w", rawFilesDir, err))
|
||||||
|
}
|
||||||
|
}
|
@@ -191,8 +191,9 @@ func collateGloballyRegisteredSingletons() sortableComponents {
|
|||||||
// Register makevars after other singletons so they can export values through makevars
|
// Register makevars after other singletons so they can export values through makevars
|
||||||
singleton{parallel: false, name: "makevars", factory: makeVarsSingletonFunc},
|
singleton{parallel: false, name: "makevars", factory: makeVarsSingletonFunc},
|
||||||
|
|
||||||
// Register env and ninjadeps last so that they can track all used environment variables and
|
// Register rawfiles and ninjadeps last so that they can track all used environment variables and
|
||||||
// Ninja file dependencies stored in the config.
|
// Ninja file dependencies stored in the config.
|
||||||
|
singleton{parallel: false, name: "rawfiles", factory: rawFilesSingletonFactory},
|
||||||
singleton{parallel: false, name: "ninjadeps", factory: ninjaDepsSingletonFactory},
|
singleton{parallel: false, name: "ninjadeps", factory: ninjaDepsSingletonFactory},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -816,13 +816,13 @@ func TestRuleBuilderHashInputs(t *testing.T) {
|
|||||||
func TestRuleBuilderWithNinjaVarEscaping(t *testing.T) {
|
func TestRuleBuilderWithNinjaVarEscaping(t *testing.T) {
|
||||||
bp := `
|
bp := `
|
||||||
rule_builder_test {
|
rule_builder_test {
|
||||||
name: "foo_sbox_escaped_ninja",
|
name: "foo_sbox_escaped",
|
||||||
flags: ["${cmdFlags}"],
|
flags: ["${cmdFlags}"],
|
||||||
sbox: true,
|
sbox: true,
|
||||||
sbox_inputs: true,
|
sbox_inputs: true,
|
||||||
}
|
}
|
||||||
rule_builder_test {
|
rule_builder_test {
|
||||||
name: "foo_sbox",
|
name: "foo_sbox_unescaped",
|
||||||
flags: ["${cmdFlags}"],
|
flags: ["${cmdFlags}"],
|
||||||
sbox: true,
|
sbox: true,
|
||||||
sbox_inputs: true,
|
sbox_inputs: true,
|
||||||
@@ -834,15 +834,16 @@ func TestRuleBuilderWithNinjaVarEscaping(t *testing.T) {
|
|||||||
FixtureWithRootAndroidBp(bp),
|
FixtureWithRootAndroidBp(bp),
|
||||||
).RunTest(t)
|
).RunTest(t)
|
||||||
|
|
||||||
escapedNinjaMod := result.ModuleForTests("foo_sbox_escaped_ninja", "").Rule("writeFile")
|
escapedNinjaMod := result.ModuleForTests("foo_sbox_escaped", "").Output("sbox.textproto")
|
||||||
|
AssertStringEquals(t, "expected rule", "android/soong/android.rawFileCopy", escapedNinjaMod.Rule.String())
|
||||||
AssertStringDoesContain(
|
AssertStringDoesContain(
|
||||||
t,
|
t,
|
||||||
"",
|
"",
|
||||||
escapedNinjaMod.BuildParams.Args["content"],
|
ContentFromFileRuleForTests(t, result.TestContext, escapedNinjaMod),
|
||||||
"$${cmdFlags}",
|
"${cmdFlags}",
|
||||||
)
|
)
|
||||||
|
|
||||||
unescapedNinjaMod := result.ModuleForTests("foo_sbox", "").Rule("unescapedWriteFile")
|
unescapedNinjaMod := result.ModuleForTests("foo_sbox_unescaped", "").Rule("unescapedWriteFile")
|
||||||
AssertStringDoesContain(
|
AssertStringDoesContain(
|
||||||
t,
|
t,
|
||||||
"",
|
"",
|
||||||
|
@@ -22,6 +22,7 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CopyOf returns a new slice that has the same contents as s.
|
// CopyOf returns a new slice that has the same contents as s.
|
||||||
@@ -597,3 +598,32 @@ func AddToStringSet(set map[string]bool, items []string) {
|
|||||||
set[item] = true
|
set[item] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SyncMap is a wrapper around sync.Map that provides type safety via generics.
|
||||||
|
type SyncMap[K comparable, V any] struct {
|
||||||
|
sync.Map
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load returns the value stored in the map for a key, or the zero value if no
|
||||||
|
// value is present.
|
||||||
|
// The ok result indicates whether value was found in the map.
|
||||||
|
func (m *SyncMap[K, V]) Load(key K) (value V, ok bool) {
|
||||||
|
v, ok := m.Map.Load(key)
|
||||||
|
if !ok {
|
||||||
|
return *new(V), false
|
||||||
|
}
|
||||||
|
return v.(V), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store sets the value for a key.
|
||||||
|
func (m *SyncMap[K, V]) Store(key K, value V) {
|
||||||
|
m.Map.Store(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadOrStore returns the existing value for the key if present.
|
||||||
|
// Otherwise, it stores and returns the given value.
|
||||||
|
// The loaded result is true if the value was loaded, false if stored.
|
||||||
|
func (m *SyncMap[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) {
|
||||||
|
v, loaded := m.Map.LoadOrStore(key, value)
|
||||||
|
return v.(V), loaded
|
||||||
|
}
|
||||||
|
@@ -25,12 +25,10 @@ func TestCodeMetadata(t *testing.T) {
|
|||||||
}`
|
}`
|
||||||
result := runCodeMetadataTest(t, android.FixtureExpectsNoErrors, bp)
|
result := runCodeMetadataTest(t, android.FixtureExpectsNoErrors, bp)
|
||||||
|
|
||||||
module := result.ModuleForTests(
|
module := result.ModuleForTests("module-name", "")
|
||||||
"module-name", "",
|
|
||||||
).Module().(*soongTesting.CodeMetadataModule)
|
|
||||||
|
|
||||||
// Check that the provider has the right contents
|
// Check that the provider has the right contents
|
||||||
data, _ := android.SingletonModuleProvider(result, module, soongTesting.CodeMetadataProviderKey)
|
data, _ := android.SingletonModuleProvider(result, module.Module(), soongTesting.CodeMetadataProviderKey)
|
||||||
if !strings.HasSuffix(
|
if !strings.HasSuffix(
|
||||||
data.IntermediatePath.String(), "/intermediateCodeMetadata.pb",
|
data.IntermediatePath.String(), "/intermediateCodeMetadata.pb",
|
||||||
) {
|
) {
|
||||||
@@ -40,13 +38,8 @@ func TestCodeMetadata(t *testing.T) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
buildParamsSlice := module.BuildParamsForTests()
|
metadata := android.ContentFromFileRuleForTests(t, result.TestContext,
|
||||||
var metadata = ""
|
module.Output(data.IntermediatePath.String()))
|
||||||
for _, params := range buildParamsSlice {
|
|
||||||
if params.Rule.String() == "android/soong/android.writeFile" {
|
|
||||||
metadata = params.Args["content"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
metadataList := make([]*code_metadata_internal_proto.CodeMetadataInternal_TargetOwnership, 0, 2)
|
metadataList := make([]*code_metadata_internal_proto.CodeMetadataInternal_TargetOwnership, 0, 2)
|
||||||
teamId := "12345"
|
teamId := "12345"
|
||||||
@@ -63,9 +56,7 @@ func TestCodeMetadata(t *testing.T) {
|
|||||||
|
|
||||||
CodeMetadataMetadata := code_metadata_internal_proto.CodeMetadataInternal{TargetOwnershipList: metadataList}
|
CodeMetadataMetadata := code_metadata_internal_proto.CodeMetadataInternal{TargetOwnershipList: metadataList}
|
||||||
protoData, _ := proto.Marshal(&CodeMetadataMetadata)
|
protoData, _ := proto.Marshal(&CodeMetadataMetadata)
|
||||||
rawData := string(protoData)
|
expectedMetadata := string(protoData)
|
||||||
formattedData := strings.ReplaceAll(rawData, "\n", "\\n")
|
|
||||||
expectedMetadata := "'" + formattedData + "\\n'"
|
|
||||||
|
|
||||||
if metadata != expectedMetadata {
|
if metadata != expectedMetadata {
|
||||||
t.Errorf(
|
t.Errorf(
|
||||||
|
@@ -29,12 +29,10 @@ func TestTestSpec(t *testing.T) {
|
|||||||
}`
|
}`
|
||||||
result := runTestSpecTest(t, android.FixtureExpectsNoErrors, bp)
|
result := runTestSpecTest(t, android.FixtureExpectsNoErrors, bp)
|
||||||
|
|
||||||
module := result.ModuleForTests(
|
module := result.ModuleForTests("module-name", "")
|
||||||
"module-name", "",
|
|
||||||
).Module().(*soongTesting.TestSpecModule)
|
|
||||||
|
|
||||||
// Check that the provider has the right contents
|
// Check that the provider has the right contents
|
||||||
data, _ := android.SingletonModuleProvider(result, module, soongTesting.TestSpecProviderKey)
|
data, _ := android.SingletonModuleProvider(result, module.Module(), soongTesting.TestSpecProviderKey)
|
||||||
if !strings.HasSuffix(
|
if !strings.HasSuffix(
|
||||||
data.IntermediatePath.String(), "/intermediateTestSpecMetadata.pb",
|
data.IntermediatePath.String(), "/intermediateTestSpecMetadata.pb",
|
||||||
) {
|
) {
|
||||||
@@ -44,13 +42,8 @@ func TestTestSpec(t *testing.T) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
buildParamsSlice := module.BuildParamsForTests()
|
metadata := android.ContentFromFileRuleForTests(t, result.TestContext,
|
||||||
var metadata = ""
|
module.Output(data.IntermediatePath.String()))
|
||||||
for _, params := range buildParamsSlice {
|
|
||||||
if params.Rule.String() == "android/soong/android.writeFile" {
|
|
||||||
metadata = params.Args["content"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
metadataList := make([]*test_spec_proto.TestSpec_OwnershipMetadata, 0, 2)
|
metadataList := make([]*test_spec_proto.TestSpec_OwnershipMetadata, 0, 2)
|
||||||
teamId := "12345"
|
teamId := "12345"
|
||||||
@@ -70,9 +63,7 @@ func TestTestSpec(t *testing.T) {
|
|||||||
}
|
}
|
||||||
testSpecMetadata := test_spec_proto.TestSpec{OwnershipMetadataList: metadataList}
|
testSpecMetadata := test_spec_proto.TestSpec{OwnershipMetadataList: metadataList}
|
||||||
protoData, _ := proto.Marshal(&testSpecMetadata)
|
protoData, _ := proto.Marshal(&testSpecMetadata)
|
||||||
rawData := string(protoData)
|
expectedMetadata := string(protoData)
|
||||||
formattedData := strings.ReplaceAll(rawData, "\n", "\\n")
|
|
||||||
expectedMetadata := "'" + formattedData + "\\n'"
|
|
||||||
|
|
||||||
if metadata != expectedMetadata {
|
if metadata != expectedMetadata {
|
||||||
t.Errorf(
|
t.Errorf(
|
||||||
|
@@ -128,7 +128,7 @@ func (module *CodeMetadataModule) GenerateAndroidBuildActions(ctx android.Module
|
|||||||
intermediatePath := android.PathForModuleOut(
|
intermediatePath := android.PathForModuleOut(
|
||||||
ctx, "intermediateCodeMetadata.pb",
|
ctx, "intermediateCodeMetadata.pb",
|
||||||
)
|
)
|
||||||
android.WriteFileRule(ctx, intermediatePath, string(protoData))
|
android.WriteFileRuleVerbatim(ctx, intermediatePath, string(protoData))
|
||||||
|
|
||||||
android.SetProvider(ctx,
|
android.SetProvider(ctx,
|
||||||
CodeMetadataProviderKey,
|
CodeMetadataProviderKey,
|
||||||
|
@@ -117,7 +117,7 @@ func (module *TestSpecModule) GenerateAndroidBuildActions(ctx android.ModuleCont
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ModuleErrorf("Error: %s", err.Error())
|
ctx.ModuleErrorf("Error: %s", err.Error())
|
||||||
}
|
}
|
||||||
android.WriteFileRule(ctx, intermediatePath, string(protoData))
|
android.WriteFileRuleVerbatim(ctx, intermediatePath, string(protoData))
|
||||||
|
|
||||||
android.SetProvider(ctx,
|
android.SetProvider(ctx,
|
||||||
TestSpecProviderKey, TestSpecProviderData{
|
TestSpecProviderKey, TestSpecProviderData{
|
||||||
|
@@ -63,6 +63,7 @@ func testForDanglingRules(ctx Context, config Config) {
|
|||||||
|
|
||||||
outDir := config.OutDir()
|
outDir := config.OutDir()
|
||||||
modulePathsDir := filepath.Join(outDir, ".module_paths")
|
modulePathsDir := filepath.Join(outDir, ".module_paths")
|
||||||
|
rawFilesDir := filepath.Join(outDir, "soong", "raw")
|
||||||
variablesFilePath := filepath.Join(outDir, "soong", "soong.variables")
|
variablesFilePath := filepath.Join(outDir, "soong", "soong.variables")
|
||||||
|
|
||||||
// dexpreopt.config is an input to the soong_docs action, which runs the
|
// dexpreopt.config is an input to the soong_docs action, which runs the
|
||||||
@@ -88,6 +89,7 @@ func testForDanglingRules(ctx Context, config Config) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(line, modulePathsDir) ||
|
if strings.HasPrefix(line, modulePathsDir) ||
|
||||||
|
strings.HasPrefix(line, rawFilesDir) ||
|
||||||
line == variablesFilePath ||
|
line == variablesFilePath ||
|
||||||
line == dexpreoptConfigFilePath ||
|
line == dexpreoptConfigFilePath ||
|
||||||
line == buildDatetimeFilePath ||
|
line == buildDatetimeFilePath ||
|
||||||
|
Reference in New Issue
Block a user