diff options
32 files changed, 537 insertions, 237 deletions
diff --git a/aconfig/init.go b/aconfig/init.go index 04176ec86..3e9d29760 100644 --- a/aconfig/init.go +++ b/aconfig/init.go @@ -43,7 +43,7 @@ var ( // For create-device-config-sysprops: Generate aconfig flag value map text file aconfigTextRule = pctx.AndroidStaticRule("aconfig_text", blueprint.RuleParams{ - Command: `${aconfig} dump --format bool` + + Command: `${aconfig} dump-cache --format='{fully_qualified_name}={state:bool}'` + ` --cache ${in}` + ` --out ${out}.tmp` + ` && ( if cmp -s ${out}.tmp ${out} ; then rm ${out}.tmp ; else mv ${out}.tmp ${out} ; fi )`, @@ -56,7 +56,7 @@ var ( // For all_aconfig_declarations: Combine all parsed_flags proto files AllDeclarationsRule = pctx.AndroidStaticRule("All_aconfig_declarations_dump", blueprint.RuleParams{ - Command: `${aconfig} dump --format protobuf --out ${out} ${cache_files}`, + Command: `${aconfig} dump-cache --format protobuf --out ${out} ${cache_files}`, CommandDeps: []string{ "${aconfig}", }, @@ -73,7 +73,7 @@ var ( blueprint.RuleParams{ Command: `rm -rf ${out}.tmp` + `&& for cache in ${cache_files}; do ` + - ` if [ -n "$$(${aconfig} dump --cache $$cache --filter=is_exported:true --format='{fully_qualified_name}')" ]; then ` + + ` if [ -n "$$(${aconfig} dump-cache --cache $$cache --filter=is_exported:true --format='{fully_qualified_name}')" ]; then ` + ` ${aconfig} create-java-lib --cache $$cache --mode=exported --out ${out}.tmp; ` + ` fi ` + `done` + diff --git a/android/Android.bp b/android/Android.bp index b359df9dd..f71f34f18 100644 --- a/android/Android.bp +++ b/android/Android.bp @@ -81,6 +81,7 @@ bootstrap_go_package { "prebuilt_build_tool.go", "proto.go", "provider.go", + "raw_files.go", "register.go", "rule_builder.go", "sandbox.go", diff --git a/android/androidmk.go b/android/androidmk.go index a0ed1e449..004ae9edf 100644 --- a/android/androidmk.go +++ b/android/androidmk.go @@ -852,6 +852,7 @@ func translateAndroidModule(ctx SingletonContext, w io.Writer, mod blueprint.Mod case "*java.SystemModules": // doesn't go through base_rules case "*java.systemModulesImport": // doesn't go through base_rules case "*phony.phony": // license properties written + case "*phony.PhonyRule": // writes phony deps and acts like `.PHONY` case "*selinux.selinuxContextsModule": // license properties written case "*sysprop.syspropLibrary": // license properties written default: diff --git a/android/base_module_context.go b/android/base_module_context.go index 2a4b12ee5..3dfe1234b 100644 --- a/android/base_module_context.go +++ b/android/base_module_context.go @@ -75,34 +75,28 @@ type BaseModuleContext interface { // It is intended for use inside the visit functions of Visit* and WalkDeps. OtherModuleType(m blueprint.Module) string - // OtherModuleProvider returns the value for a provider for the given module. If the value is - // not set it returns the zero value of the type of the provider, so the return value can always - // be type asserted to the type of the provider. The value returned may be a deep copy of the - // value originally passed to SetProvider. - OtherModuleProvider(m blueprint.Module, provider blueprint.AnyProviderKey) any - - // OtherModuleHasProvider returns true if the provider for the given module has been set. - OtherModuleHasProvider(m blueprint.Module, provider blueprint.AnyProviderKey) bool - + // otherModuleProvider returns the value for a provider for the given module. If the value is + // not set it returns nil and false. The value returned may be a deep copy of the value originally + // passed to SetProvider. + // + // This method shouldn't be used directly, prefer the type-safe android.OtherModuleProvider instead. otherModuleProvider(m blueprint.Module, provider blueprint.AnyProviderKey) (any, bool) // Provider returns the value for a provider for the current module. If the value is - // not set it returns the zero value of the type of the provider, so the return value can always - // be type asserted to the type of the provider. It panics if called before the appropriate + // not set it returns nil and false. It panics if called before the appropriate // mutator or GenerateBuildActions pass for the provider. The value returned may be a deep // copy of the value originally passed to SetProvider. - Provider(provider blueprint.AnyProviderKey) any - - // HasProvider returns true if the provider for the current module has been set. - HasProvider(provider blueprint.AnyProviderKey) bool - + // + // This method shouldn't be used directly, prefer the type-safe android.ModuleProvider instead. provider(provider blueprint.AnyProviderKey) (any, bool) - // SetProvider sets the value for a provider for the current module. It panics if not called + // setProvider sets the value for a provider for the current module. It panics if not called // during the appropriate mutator or GenerateBuildActions pass for the provider, if the value // is not of the appropriate type, or if the value has already been set. The value should not // be modified after being passed to SetProvider. - SetProvider(provider blueprint.AnyProviderKey, value interface{}) + // + // This method shouldn't be used directly, prefer the type-safe android.SetProvider instead. + setProvider(provider blueprint.AnyProviderKey, value any) GetDirectDepsWithTag(tag blueprint.DependencyTag) []Module @@ -264,35 +258,16 @@ func (b *baseModuleContext) OtherModuleReverseDependencyVariantExists(name strin func (b *baseModuleContext) OtherModuleType(m blueprint.Module) string { return b.bp.OtherModuleType(m) } -func (b *baseModuleContext) OtherModuleProvider(m blueprint.Module, provider blueprint.AnyProviderKey) any { - value, _ := b.bp.OtherModuleProvider(m, provider) - return value -} - -func (b *baseModuleContext) OtherModuleHasProvider(m blueprint.Module, provider blueprint.AnyProviderKey) bool { - _, ok := b.bp.OtherModuleProvider(m, provider) - return ok -} func (b *baseModuleContext) otherModuleProvider(m blueprint.Module, provider blueprint.AnyProviderKey) (any, bool) { return b.bp.OtherModuleProvider(m, provider) } -func (b *baseModuleContext) Provider(provider blueprint.AnyProviderKey) any { - value, _ := b.bp.Provider(provider) - return value -} - -func (b *baseModuleContext) HasProvider(provider blueprint.AnyProviderKey) bool { - _, ok := b.bp.Provider(provider) - return ok -} - func (b *baseModuleContext) provider(provider blueprint.AnyProviderKey) (any, bool) { return b.bp.Provider(provider) } -func (b *baseModuleContext) SetProvider(provider blueprint.AnyProviderKey, value any) { +func (b *baseModuleContext) setProvider(provider blueprint.AnyProviderKey, value any) { b.bp.SetProvider(provider, value) } diff --git a/android/config.go b/android/config.go index 312a5da49..24b9b8a62 100644 --- a/android/config.go +++ b/android/config.go @@ -18,6 +18,7 @@ package android // product variables necessary for soong_build's operation. import ( + "android/soong/shared" "encoding/json" "fmt" "os" @@ -118,6 +119,11 @@ func (c Config) SoongOutDir() string { 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 { return c.outDir } diff --git a/android/defs.go b/android/defs.go index 03968c10d..a9889648b 100644 --- a/android/defs.go +++ b/android/defs.go @@ -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()) diff --git a/android/provider.go b/android/provider.go index b2cc7c06d..3b9c5d2ba 100644 --- a/android/provider.go +++ b/android/provider.go @@ -79,7 +79,7 @@ func SingletonModuleProvider[K any](ctx SingletonModuleProviderContext, module b // SetProviderContext is a helper interface that is a subset of ModuleContext, BottomUpMutatorContext, or // TopDownMutatorContext for use in SetProvider. type SetProviderContext interface { - SetProvider(provider blueprint.AnyProviderKey, value any) + setProvider(provider blueprint.AnyProviderKey, value any) } var _ SetProviderContext = BaseModuleContext(nil) @@ -95,7 +95,7 @@ var _ SetProviderContext = TopDownMutatorContext(nil) // SetProviderContext is a helper interface that accepts ModuleContext, BottomUpMutatorContext, or // TopDownMutatorContext. func SetProvider[K any](ctx SetProviderContext, provider blueprint.ProviderKey[K], value K) { - ctx.SetProvider(provider, value) + ctx.setProvider(provider, value) } var _ OtherModuleProviderContext = (*otherModuleProviderAdaptor)(nil) diff --git a/android/raw_files.go b/android/raw_files.go new file mode 100644 index 000000000..9d7f5e82d --- /dev/null +++ b/android/raw_files.go @@ -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)) + } +} diff --git a/android/register.go b/android/register.go index cd968cd02..d00c15fd0 100644 --- a/android/register.go +++ b/android/register.go @@ -191,8 +191,9 @@ func collateGloballyRegisteredSingletons() sortableComponents { // Register makevars after other singletons so they can export values through makevars 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. + singleton{parallel: false, name: "rawfiles", factory: rawFilesSingletonFactory}, singleton{parallel: false, name: "ninjadeps", factory: ninjaDepsSingletonFactory}, ) diff --git a/android/rule_builder_test.go b/android/rule_builder_test.go index d659dcca7..9e5f12dff 100644 --- a/android/rule_builder_test.go +++ b/android/rule_builder_test.go @@ -816,13 +816,13 @@ func TestRuleBuilderHashInputs(t *testing.T) { func TestRuleBuilderWithNinjaVarEscaping(t *testing.T) { bp := ` rule_builder_test { - name: "foo_sbox_escaped_ninja", + name: "foo_sbox_escaped", flags: ["${cmdFlags}"], sbox: true, sbox_inputs: true, } rule_builder_test { - name: "foo_sbox", + name: "foo_sbox_unescaped", flags: ["${cmdFlags}"], sbox: true, sbox_inputs: true, @@ -834,15 +834,16 @@ func TestRuleBuilderWithNinjaVarEscaping(t *testing.T) { FixtureWithRootAndroidBp(bp), ).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( t, "", - escapedNinjaMod.BuildParams.Args["content"], - "$${cmdFlags}", + ContentFromFileRuleForTests(t, result.TestContext, escapedNinjaMod), + "${cmdFlags}", ) - unescapedNinjaMod := result.ModuleForTests("foo_sbox", "").Rule("unescapedWriteFile") + unescapedNinjaMod := result.ModuleForTests("foo_sbox_unescaped", "").Rule("unescapedWriteFile") AssertStringDoesContain( t, "", diff --git a/android/testing.go b/android/testing.go index 39a268b23..3d0300a01 100644 --- a/android/testing.go +++ b/android/testing.go @@ -203,16 +203,6 @@ func (ctx *TestContext) HardCodedPreArchMutators(f RegisterMutatorFunc) { ctx.PreArchMutators(f) } -func (ctx *TestContext) ModuleProvider(m blueprint.Module, p blueprint.AnyProviderKey) any { - value, _ := ctx.Context.ModuleProvider(m, p) - return value -} - -func (ctx *TestContext) ModuleHasProvider(m blueprint.Module, p blueprint.AnyProviderKey) bool { - _, ok := ctx.Context.ModuleProvider(m, p) - return ok -} - func (ctx *TestContext) moduleProvider(m blueprint.Module, p blueprint.AnyProviderKey) (any, bool) { return ctx.Context.ModuleProvider(m, p) } diff --git a/android/util.go b/android/util.go index ae1c65756..51313ceec 100644 --- a/android/util.go +++ b/android/util.go @@ -22,6 +22,7 @@ import ( "runtime" "sort" "strings" + "sync" ) // 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 } } + +// 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 +} diff --git a/apex/apex.go b/apex/apex.go index 29d59e589..45abbbacc 100644 --- a/apex/apex.go +++ b/apex/apex.go @@ -2386,7 +2386,7 @@ func (a *apexBundle) provideApexExportsInfo(ctx android.ModuleContext) { ProfilePathOnHost: info.ProfilePathOnHost(), LibraryNameToDexJarPathOnHost: info.DexBootJarPathMap(), } - ctx.SetProvider(android.ApexExportsInfoProvider, exports) + android.SetProvider(ctx, android.ApexExportsInfoProvider, exports) } }) } @@ -2916,15 +2916,6 @@ func makeApexAvailableBaseline() map[string][]string { "wifi-nano-protos", "wifi-service-pre-jarjar", } - // - // Module separator - // - m[android.AvailableToAnyApex] = []string{ - "libprofile-clang-extras", - "libprofile-clang-extras_ndk", - "libprofile-extras", - "libprofile-extras_ndk", - } return m } diff --git a/apex/apex_test.go b/apex/apex_test.go index 616421af5..1b9fa19f0 100644 --- a/apex/apex_test.go +++ b/apex/apex_test.go @@ -11436,6 +11436,20 @@ func TestBootDexJarsMultipleApexPrebuilts(t *testing.T) { } } + // Check that the boot jars of the selected apex are run through boot_jars_package_check + // This validates that the jars on the bootclasspath do not contain packages outside an allowlist + checkBootJarsPackageCheck := func(t *testing.T, ctx *android.TestContext, expectedBootJar string) { + platformBcp := ctx.ModuleForTests("platform-bootclasspath", "android_common") + bootJarsCheckRule := platformBcp.Rule("boot_jars_package_check") + android.AssertStringMatches(t, "Could not find the correct boot dex jar in package check rule", bootJarsCheckRule.RuleParams.Command, "build/soong/scripts/check_boot_jars/package_allowed_list.txt.*"+expectedBootJar) + } + + // Check that the boot jars used to generate the monolithic hiddenapi flags come from the selected apex + checkBootJarsForMonolithicHiddenapi := func(t *testing.T, ctx *android.TestContext, expectedBootJar string) { + monolithicHiddenapiFlagsCmd := ctx.ModuleForTests("platform-bootclasspath", "android_common").Output("out/soong/hiddenapi/hiddenapi-stub-flags.txt").RuleParams.Command + android.AssertStringMatches(t, "Could not find the correct boot dex jar in monolithic hiddenapi flags generation command", monolithicHiddenapiFlagsCmd, "--boot-dex="+expectedBootJar) + } + bp := ` // Source APEX. @@ -11575,5 +11589,7 @@ func TestBootDexJarsMultipleApexPrebuilts(t *testing.T) { ) ctx := testDexpreoptWithApexes(t, bp, "", preparer, fragment) checkBootDexJarPath(t, ctx, "framework-foo", tc.expectedBootJar) + checkBootJarsPackageCheck(t, ctx, tc.expectedBootJar) + checkBootJarsForMonolithicHiddenapi(t, ctx, tc.expectedBootJar) } } diff --git a/apex/platform_bootclasspath_test.go b/apex/platform_bootclasspath_test.go index b741963c8..01b616bb6 100644 --- a/apex/platform_bootclasspath_test.go +++ b/apex/platform_bootclasspath_test.go @@ -382,6 +382,9 @@ func TestPlatformBootclasspathDependencies(t *testing.T) { // Make sure that the myplatform-bootclasspath has the correct dependencies. CheckModuleDependencies(t, result.TestContext, "myplatform-bootclasspath", "android_common", []string{ + // source vs prebuilt selection metadata module + `platform:all_apex_contributions`, + // The following are stubs. `platform:android_stubs_current`, `platform:android_system_stubs_current`, @@ -534,6 +537,9 @@ func TestPlatformBootclasspath_AlwaysUsePrebuiltSdks(t *testing.T) { // Make sure that the myplatform-bootclasspath has the correct dependencies. CheckModuleDependencies(t, result.TestContext, "myplatform-bootclasspath", "android_common", []string{ + // source vs prebuilt selection metadata module + `platform:all_apex_contributions`, + // The following are stubs. "platform:prebuilt_sdk_public_current_android", "platform:prebuilt_sdk_system_current_android", diff --git a/apex/prebuilt.go b/apex/prebuilt.go index 1f57b63e8..188875ac9 100644 --- a/apex/prebuilt.go +++ b/apex/prebuilt.go @@ -790,7 +790,7 @@ func (p *prebuiltCommon) provideApexExportsInfo(ctx android.ModuleContext) { ProfilePathOnHost: di.PrebuiltExportPath(java.ProfileInstallPathInApex), LibraryNameToDexJarPathOnHost: javaModuleToDexPath, } - ctx.SetProvider(android.ApexExportsInfoProvider, exports) + android.SetProvider(ctx, android.ApexExportsInfoProvider, exports) } else { ctx.ModuleErrorf(err.Error()) } diff --git a/docs/map_files.md b/docs/map_files.md index 35e8cbbfc..37f91ecb5 100644 --- a/docs/map_files.md +++ b/docs/map_files.md @@ -134,6 +134,9 @@ both APEX and the LL-NDK. Historically this annotation was spelled `vndk`, but it has always meant LL-NDK. +When an llndk API is deprecated, the `llndk` tag is dropped and +`llndk-deprecate=<V>` is added. + ### platform-only Indicates that the version or symbol is public in the implementation library but diff --git a/java/code_metadata_test.go b/java/code_metadata_test.go index 509e70112..0ef348afe 100644 --- a/java/code_metadata_test.go +++ b/java/code_metadata_test.go @@ -25,12 +25,10 @@ func TestCodeMetadata(t *testing.T) { }` result := runCodeMetadataTest(t, android.FixtureExpectsNoErrors, bp) - module := result.ModuleForTests( - "module-name", "", - ).Module().(*soongTesting.CodeMetadataModule) + module := result.ModuleForTests("module-name", "") // 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( data.IntermediatePath.String(), "/intermediateCodeMetadata.pb", ) { @@ -40,13 +38,8 @@ func TestCodeMetadata(t *testing.T) { ) } - buildParamsSlice := module.BuildParamsForTests() - var metadata = "" - for _, params := range buildParamsSlice { - if params.Rule.String() == "android/soong/android.writeFile" { - metadata = params.Args["content"] - } - } + metadata := android.ContentFromFileRuleForTests(t, result.TestContext, + module.Output(data.IntermediatePath.String())) metadataList := make([]*code_metadata_internal_proto.CodeMetadataInternal_TargetOwnership, 0, 2) teamId := "12345" @@ -63,9 +56,7 @@ func TestCodeMetadata(t *testing.T) { CodeMetadataMetadata := code_metadata_internal_proto.CodeMetadataInternal{TargetOwnershipList: metadataList} protoData, _ := proto.Marshal(&CodeMetadataMetadata) - rawData := string(protoData) - formattedData := strings.ReplaceAll(rawData, "\n", "\\n") - expectedMetadata := "'" + formattedData + "\\n'" + expectedMetadata := string(protoData) if metadata != expectedMetadata { t.Errorf( diff --git a/java/config/config.go b/java/config/config.go index d80ed4142..6a945ac9c 100644 --- a/java/config/config.go +++ b/java/config/config.go @@ -95,13 +95,11 @@ func init() { "-JXX:TieredStopAtLevel=1", "-JDcom.android.tools.r8.emitRecordAnnotationsInDex", "-JDcom.android.tools.r8.emitPermittedSubclassesAnnotationsInDex", - "-JDcom.android.tools.r8.emitRecordAnnotationsExInDex", }, dexerJavaVmFlagsList...)) exportedVars.ExportStringListStaticVariable("R8Flags", append([]string{ "-JXmx4096M", "-JDcom.android.tools.r8.emitRecordAnnotationsInDex", "-JDcom.android.tools.r8.emitPermittedSubclassesAnnotationsInDex", - "-JDcom.android.tools.r8.emitRecordAnnotationsExInDex", }, dexerJavaVmFlagsList...)) exportedVars.ExportStringListStaticVariable("CommonJdkFlags", []string{ diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go index 205d3b926..82cece346 100644 --- a/java/dexpreopt_bootjars.go +++ b/java/dexpreopt_bootjars.go @@ -552,8 +552,8 @@ func DexpreoptBootJarsMutator(ctx android.BottomUpMutatorContext) { func addDependenciesOntoSelectedBootImageApexes(ctx android.BottomUpMutatorContext, apexes ...string) { psi := android.PrebuiltSelectionInfoMap{} ctx.VisitDirectDepsWithTag(apexContributionsMetadataDepTag, func(am android.Module) { - if ctx.OtherModuleHasProvider(am, android.PrebuiltSelectionInfoProvider) { - psi = ctx.OtherModuleProvider(am, android.PrebuiltSelectionInfoProvider).(android.PrebuiltSelectionInfoMap) + if info, exists := android.OtherModuleProvider(ctx, am, android.PrebuiltSelectionInfoProvider); exists { + psi = info } }) for _, apex := range apexes { @@ -699,28 +699,40 @@ func getModulesForImage(ctx android.ModuleContext, imageConfig *bootImageConfig) // extractEncodedDexJarsFromModulesOrBootclasspathFragments gets the hidden API encoded dex jars for // the given modules. func extractEncodedDexJarsFromModulesOrBootclasspathFragments(ctx android.ModuleContext, apexJarModulePairs []apexJarModulePair) bootDexJarByModule { - apexNameToBcpInfoMap := getApexNameToBcpInfoMap(ctx) + apexNameToApexExportInfoMap := getApexNameToApexExportsInfoMap(ctx) encodedDexJarsByModuleName := bootDexJarByModule{} for _, pair := range apexJarModulePairs { - dexJarPath := getDexJarForApex(ctx, pair, apexNameToBcpInfoMap) + dexJarPath := getDexJarForApex(ctx, pair, apexNameToApexExportInfoMap) encodedDexJarsByModuleName.addPath(pair.jarModule, dexJarPath) } return encodedDexJarsByModuleName } +type apexNameToApexExportsInfoMap map[string]android.ApexExportsInfo + +// javaLibraryPathOnHost returns the path to the java library which is exported by the apex for hiddenapi and dexpreopt and a boolean indicating whether the java library exists +// For prebuilt apexes, this is created by deapexing the prebuilt apex +func (m *apexNameToApexExportsInfoMap) javaLibraryDexPathOnHost(ctx android.ModuleContext, apex string, javalib string) (android.Path, bool) { + if info, exists := (*m)[apex]; exists { + if dex, exists := info.LibraryNameToDexJarPathOnHost[javalib]; exists { + return dex, true + } else { + ctx.ModuleErrorf("Apex %s does not provide a dex boot jar for library %s\n", apex, javalib) + } + } + // An apex entry could not be found. Return false. + // TODO: b/308174306 - When all the mainline modules have been flagged, make this a hard error + return nil, false +} + // Returns the java libraries exported by the apex for hiddenapi and dexpreopt // This information can come from two mechanisms // 1. New: Direct deps to _selected_ apexes. The apexes return a ApexExportsInfo // 2. Legacy: An edge to java_library or java_import (java_sdk_library) module. For prebuilt apexes, this serves as a hook and is populated by deapexers of prebuilt apxes // TODO: b/308174306 - Once all mainline modules have been flagged, drop (2) -func getDexJarForApex(ctx android.ModuleContext, pair apexJarModulePair, apexNameToBcpInfoMap map[string]android.ApexExportsInfo) android.Path { - if info, exists := apexNameToBcpInfoMap[pair.apex]; exists { - libraryName := android.RemoveOptionalPrebuiltPrefix(pair.jarModule.Name()) - if dex, exists := info.LibraryNameToDexJarPathOnHost[libraryName]; exists { - return dex - } else { - ctx.ModuleErrorf("Apex %s does not provide a dex boot jar for library %s\n", pair.apex, libraryName) - } +func getDexJarForApex(ctx android.ModuleContext, pair apexJarModulePair, apexNameToApexExportsInfoMap apexNameToApexExportsInfoMap) android.Path { + if dex, found := apexNameToApexExportsInfoMap.javaLibraryDexPathOnHost(ctx, pair.apex, android.RemoveOptionalPrebuiltPrefix(pair.jarModule.Name())); found { + return dex } // TODO: b/308174306 - Remove the legacy mechanism if android.IsConfiguredJarForPlatform(pair.apex) || android.IsModulePrebuilt(pair.jarModule) { @@ -900,14 +912,14 @@ func getProfilePathForApex(ctx android.ModuleContext, apexName string, apexNameT return fragment.(commonBootclasspathFragment).getProfilePath() } -func getApexNameToBcpInfoMap(ctx android.ModuleContext) map[string]android.ApexExportsInfo { - apexNameToBcpInfoMap := map[string]android.ApexExportsInfo{} +func getApexNameToApexExportsInfoMap(ctx android.ModuleContext) apexNameToApexExportsInfoMap { + apexNameToApexExportsInfoMap := apexNameToApexExportsInfoMap{} ctx.VisitDirectDepsWithTag(dexpreoptBootJarDepTag, func(am android.Module) { if info, exists := android.OtherModuleProvider(ctx, am, android.ApexExportsInfoProvider); exists { - apexNameToBcpInfoMap[info.ApexName] = info + apexNameToApexExportsInfoMap[info.ApexName] = info } }) - return apexNameToBcpInfoMap + return apexNameToApexExportsInfoMap } // Generate boot image build rules for a specific target. @@ -952,7 +964,7 @@ func buildBootImageVariant(ctx android.ModuleContext, image *bootImageVariant, p invocationPath := outputPath.ReplaceExtension(ctx, "invocation") - apexNameToBcpInfoMap := getApexNameToBcpInfoMap(ctx) + apexNameToApexExportsInfoMap := getApexNameToApexExportsInfoMap(ctx) cmd.Tool(globalSoong.Dex2oat). Flag("--avoid-storing-invocation"). @@ -966,7 +978,7 @@ func buildBootImageVariant(ctx android.ModuleContext, image *bootImageVariant, p } for _, apex := range image.profileImports { - importedProfile := getProfilePathForApex(ctx, apex, apexNameToBcpInfoMap) + importedProfile := getProfilePathForApex(ctx, apex, apexNameToApexExportsInfoMap) if importedProfile == nil { ctx.ModuleErrorf("Boot image config '%[1]s' imports profile from '%[2]s', but '%[2]s' "+ "doesn't provide a profile", diff --git a/java/hiddenapi_modular.go b/java/hiddenapi_modular.go index bf9975784..c3caa084f 100644 --- a/java/hiddenapi_modular.go +++ b/java/hiddenapi_modular.go @@ -19,6 +19,7 @@ import ( "strings" "android/soong/android" + "android/soong/dexpreopt" "github.com/google/blueprint" ) @@ -1250,9 +1251,27 @@ func buildRuleToGenerateRemovedDexSignatures(ctx android.ModuleContext, suffix s } // extractBootDexJarsFromModules extracts the boot dex jars from the supplied modules. +// This information can come from two mechanisms +// 1. New: Direct deps to _selected_ apexes. The apexes contain a ApexExportsInfo +// 2. Legacy: An edge to java_sdk_library(_import) module. For prebuilt apexes, this serves as a hook and is populated by deapexers of prebuilt apxes +// TODO: b/308174306 - Once all mainline modules have been flagged, drop (2) func extractBootDexJarsFromModules(ctx android.ModuleContext, contents []android.Module) bootDexJarByModule { bootDexJars := bootDexJarByModule{} + + apexNameToApexExportsInfoMap := getApexNameToApexExportsInfoMap(ctx) + // For ART and mainline module jars, query apexNameToApexExportsInfoMap to get the dex file + apexJars := dexpreopt.GetGlobalConfig(ctx).ArtApexJars.AppendList(&dexpreopt.GetGlobalConfig(ctx).ApexBootJars) + for i := 0; i < apexJars.Len(); i++ { + if dex, found := apexNameToApexExportsInfoMap.javaLibraryDexPathOnHost(ctx, apexJars.Apex(i), apexJars.Jar(i)); found { + bootDexJars[apexJars.Jar(i)] = dex + } + } + + // TODO - b/308174306: Drop the legacy mechanism for _, module := range contents { + if _, exists := bootDexJars[android.RemoveOptionalPrebuiltPrefix(module.Name())]; exists { + continue + } hiddenAPIModule := hiddenAPIModuleFromModule(ctx, module) if hiddenAPIModule == nil { continue diff --git a/java/platform_bootclasspath.go b/java/platform_bootclasspath.go index 88d1ae8c0..4db426e0d 100644 --- a/java/platform_bootclasspath.go +++ b/java/platform_bootclasspath.go @@ -106,6 +106,9 @@ func (b *platformBootclasspathModule) OutputFiles(tag string) (android.Paths, er } func (b *platformBootclasspathModule) DepsMutator(ctx android.BottomUpMutatorContext) { + // Create a dependency on all_apex_contributions to determine the selected mainline module + ctx.AddDependency(ctx.Module(), apexContributionsMetadataDepTag, "all_apex_contributions") + b.hiddenAPIDepsMutator(ctx) if !dexpreopt.IsDex2oatNeeded(ctx) { @@ -130,6 +133,8 @@ func (b *platformBootclasspathModule) hiddenAPIDepsMutator(ctx android.BottomUpM func (b *platformBootclasspathModule) BootclasspathDepsMutator(ctx android.BottomUpMutatorContext) { // Add dependencies on all the ART jars. global := dexpreopt.GetGlobalConfig(ctx) + addDependenciesOntoSelectedBootImageApexes(ctx, "com.android.art") + // TODO: b/308174306 - Remove the mechanism of depending on the java_sdk_library(_import) directly addDependenciesOntoBootImageModules(ctx, global.ArtApexJars, platformBootclasspathArtBootJarDepTag) // Add dependencies on all the non-updatable jars, which are on the platform or in non-updatable @@ -138,6 +143,12 @@ func (b *platformBootclasspathModule) BootclasspathDepsMutator(ctx android.Botto // Add dependencies on all the updatable jars, except the ART jars. apexJars := dexpreopt.GetGlobalConfig(ctx).ApexBootJars + apexes := []string{} + for i := 0; i < apexJars.Len(); i++ { + apexes = append(apexes, apexJars.Apex(i)) + } + addDependenciesOntoSelectedBootImageApexes(ctx, android.FirstUniqueStrings(apexes)...) + // TODO: b/308174306 - Remove the mechanism of depending on the java_sdk_library(_import) directly addDependenciesOntoBootImageModules(ctx, apexJars, platformBootclasspathApexBootJarDepTag) // Add dependencies on all the fragments. diff --git a/java/system_modules.go b/java/system_modules.go index 1c7917109..f3446483b 100644 --- a/java/system_modules.go +++ b/java/system_modules.go @@ -55,7 +55,8 @@ var ( `${config.MergeZipsCmd} -j ${workDir}/module.jar ${workDir}/classes.jar $in && ` + // Note: The version of the java.base module created must match the version // of the jlink tool which consumes it. - `${config.JmodCmd} create --module-version ${config.JlinkVersion} --target-platform android ` + + // Use LINUX-OTHER to be compatible with JDK 21+ (b/294137077) + `${config.JmodCmd} create --module-version ${config.JlinkVersion} --target-platform LINUX-OTHER ` + ` --class-path ${workDir}/module.jar ${workDir}/jmod/java.base.jmod && ` + `${config.JlinkCmd} --module-path ${workDir}/jmod --add-modules java.base --output ${outDir} ` + // Note: The system-modules jlink plugin is disabled because (a) it is not diff --git a/java/test_spec_test.go b/java/test_spec_test.go index f628b4b74..4144dad69 100644 --- a/java/test_spec_test.go +++ b/java/test_spec_test.go @@ -29,12 +29,10 @@ func TestTestSpec(t *testing.T) { }` result := runTestSpecTest(t, android.FixtureExpectsNoErrors, bp) - module := result.ModuleForTests( - "module-name", "", - ).Module().(*soongTesting.TestSpecModule) + module := result.ModuleForTests("module-name", "") // 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( data.IntermediatePath.String(), "/intermediateTestSpecMetadata.pb", ) { @@ -44,13 +42,8 @@ func TestTestSpec(t *testing.T) { ) } - buildParamsSlice := module.BuildParamsForTests() - var metadata = "" - for _, params := range buildParamsSlice { - if params.Rule.String() == "android/soong/android.writeFile" { - metadata = params.Args["content"] - } - } + metadata := android.ContentFromFileRuleForTests(t, result.TestContext, + module.Output(data.IntermediatePath.String())) metadataList := make([]*test_spec_proto.TestSpec_OwnershipMetadata, 0, 2) teamId := "12345" @@ -70,9 +63,7 @@ func TestTestSpec(t *testing.T) { } testSpecMetadata := test_spec_proto.TestSpec{OwnershipMetadataList: metadataList} protoData, _ := proto.Marshal(&testSpecMetadata) - rawData := string(protoData) - formattedData := strings.ReplaceAll(rawData, "\n", "\\n") - expectedMetadata := "'" + formattedData + "\\n'" + expectedMetadata := string(protoData) if metadata != expectedMetadata { t.Errorf( diff --git a/phony/phony.go b/phony/phony.go index a8b651aa8..bb4878899 100644 --- a/phony/phony.go +++ b/phony/phony.go @@ -24,6 +24,7 @@ import ( func init() { android.RegisterModuleType("phony", PhonyFactory) + android.RegisterModuleType("phony_rule", PhonyRuleFactory) } type phony struct { @@ -71,3 +72,40 @@ func (p *phony) AndroidMk() android.AndroidMkData { }, } } + +type PhonyRule struct { + android.ModuleBase + + properties PhonyProperties +} + +type PhonyProperties struct { + // The Phony_deps is the set of all dependencies for this target, + // and it can function similarly to .PHONY in a makefile. + // Additionally, dependencies within it can even include genrule. + Phony_deps []string +} + +// The phony_rule provides functionality similar to the .PHONY in a makefile. +// It can create a phony target and include relevant dependencies associated with it. +func PhonyRuleFactory() android.Module { + module := &PhonyRule{} + android.InitAndroidModule(module) + module.AddProperties(&module.properties) + return module +} + +func (p *PhonyRule) GenerateAndroidBuildActions(ctx android.ModuleContext) { +} + +func (p *PhonyRule) AndroidMk() android.AndroidMkData { + return android.AndroidMkData{ + Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) { + if len(p.properties.Phony_deps) > 0 { + depModulesStr := strings.Join(p.properties.Phony_deps, " ") + fmt.Fprintln(w, ".PHONY:", name) + fmt.Fprintln(w, name, ":", depModulesStr) + } + }, + } +} diff --git a/rust/protobuf.go b/rust/protobuf.go index d021076eb..0b26b80fa 100644 --- a/rust/protobuf.go +++ b/rust/protobuf.go @@ -54,11 +54,6 @@ type ProtobufProperties struct { // List of libraries which export include paths required for this module Header_libs []string `android:"arch_variant,variant_prepend"` - // Use protobuf version 3.x. This will be deleted once we migrate all current users - // of protobuf off of 2.x. - // ludovicb@: DEPRECATED, to be removed - Use_protobuf3 *bool - // List of exported include paths containing proto files for dependent rust_protobuf modules. Exported_include_dirs []string } diff --git a/rust/protobuf_test.go b/rust/protobuf_test.go index b375a6463..cae071b8f 100644 --- a/rust/protobuf_test.go +++ b/rust/protobuf_test.go @@ -28,7 +28,6 @@ func TestRustProtobuf3(t *testing.T) { protos: ["buf.proto", "proto.proto"], crate_name: "rust_proto", source_stem: "buf", - use_protobuf3: true, shared_libs: ["libfoo_shared"], static_libs: ["libfoo_static"], } @@ -77,7 +76,6 @@ func TestRustProtobufInclude(t *testing.T) { protos: ["proto.proto"], crate_name: "rust_proto", source_stem: "proto", - use_protobuf3: true, rustlibs: ["librust_exported_proto", "libfoo"], } rust_protobuf { @@ -85,7 +83,6 @@ func TestRustProtobufInclude(t *testing.T) { protos: ["proto.proto"], crate_name: "rust_exported_proto", source_stem: "exported_proto", - use_protobuf3: true, exported_include_dirs: ["proto"] } rust_library { diff --git a/scripts/Android.bp b/scripts/Android.bp index 97f6ab424..7baaadb77 100644 --- a/scripts/Android.bp +++ b/scripts/Android.bp @@ -254,3 +254,8 @@ python_test_host { "modify_permissions_allowlist.py", ], } + +sh_binary_host { + name: "keep-flagged-apis", + src: "keep-flagged-apis.sh", +} diff --git a/scripts/keep-flagged-apis.sh b/scripts/keep-flagged-apis.sh new file mode 100755 index 000000000..9c48fdbad --- /dev/null +++ b/scripts/keep-flagged-apis.sh @@ -0,0 +1,46 @@ +#!/bin/bash -e +# +# 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. + +# Convert a list of flags in the input file to a list of metalava options +# that will keep the APIs for those flags will hiding all other flagged +# APIs. + +FLAGS="$1" + +FLAGGED="android.annotation.FlaggedApi" + +# Convert the list of feature flags in the input file to Metalava options +# of the form `--revert-annotation !android.annotation.FlaggedApi("<flag>")` +# to prevent the annotated APIs from being hidden, i.e. include the annotated +# APIs in the SDK snapshots. This also preserves the line comments, they will +# be ignored by Metalava but might be useful when debugging. +while read -r line; do + key=$(echo "$line" | cut -d= -f1) + value=$(echo "$line" | cut -d= -f2) + + # Skip if value is not true and line does not start with '#' + if [[ ( $value != "true" ) && ( $line =~ ^[^#] )]]; then + continue + fi + + # Escape and quote the key for sed + escaped_key=$(echo "$key" | sed "s/'/\\\'/g; s/ /\\ /g") + + echo $line | sed "s|^[^#].*$|--revert-annotation '!$FLAGGED(\"$escaped_key\")'|" +done < "$FLAGS" + +# Revert all flagged APIs, unless listed above. +echo "--revert-annotation $FLAGGED" diff --git a/testing/code_metadata.go b/testing/code_metadata.go index 3cf7c5965..11ba43037 100644 --- a/testing/code_metadata.go +++ b/testing/code_metadata.go @@ -128,7 +128,7 @@ func (module *CodeMetadataModule) GenerateAndroidBuildActions(ctx android.Module intermediatePath := android.PathForModuleOut( ctx, "intermediateCodeMetadata.pb", ) - android.WriteFileRule(ctx, intermediatePath, string(protoData)) + android.WriteFileRuleVerbatim(ctx, intermediatePath, string(protoData)) android.SetProvider(ctx, CodeMetadataProviderKey, diff --git a/testing/test_spec.go b/testing/test_spec.go index d25961229..4d885c6de 100644 --- a/testing/test_spec.go +++ b/testing/test_spec.go @@ -117,7 +117,7 @@ func (module *TestSpecModule) GenerateAndroidBuildActions(ctx android.ModuleCont if err != nil { ctx.ModuleErrorf("Error: %s", err.Error()) } - android.WriteFileRule(ctx, intermediatePath, string(protoData)) + android.WriteFileRuleVerbatim(ctx, intermediatePath, string(protoData)) android.SetProvider(ctx, TestSpecProviderKey, TestSpecProviderData{ diff --git a/ui/build/test_build.go b/ui/build/test_build.go index c5dc4c53f..309513919 100644 --- a/ui/build/test_build.go +++ b/ui/build/test_build.go @@ -63,6 +63,7 @@ func testForDanglingRules(ctx Context, config Config) { outDir := config.OutDir() modulePathsDir := filepath.Join(outDir, ".module_paths") + rawFilesDir := filepath.Join(outDir, "soong", "raw") variablesFilePath := filepath.Join(outDir, "soong", "soong.variables") // dexpreopt.config is an input to the soong_docs action, which runs the @@ -88,6 +89,7 @@ func testForDanglingRules(ctx Context, config Config) { continue } if strings.HasPrefix(line, modulePathsDir) || + strings.HasPrefix(line, rawFilesDir) || line == variablesFilePath || line == dexpreoptConfigFilePath || line == buildDatetimeFilePath || |