Merge "Add "force-read-only" mode to xx_aconfig_library build rules" into main
diff --git a/OWNERS b/OWNERS
index 58edc89..01025fb 100644
--- a/OWNERS
+++ b/OWNERS
@@ -12,4 +12,6 @@
mrziwang@google.com
spandandas@google.com
weiwli@google.com
-yudiliu@google.com
\ No newline at end of file
+yudiliu@google.com
+
+per-file build/soong/ui/build/androidmk_denylist.go = joeo@google.com, weiwli@google.com
\ No newline at end of file
diff --git a/aconfig/init.go b/aconfig/init.go
index 04176ec..3e9d297 100644
--- a/aconfig/init.go
+++ b/aconfig/init.go
@@ -43,7 +43,7 @@
// 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 @@
// 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 @@
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 b359df9..e40e462 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -63,6 +63,7 @@
"metrics.go",
"module.go",
"module_context.go",
+ "module_info_json.go",
"mutator.go",
"namespace.go",
"neverallow.go",
@@ -81,6 +82,7 @@
"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 a0ed1e4..f65e084 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -30,6 +30,7 @@
"reflect"
"runtime"
"sort"
+ "strconv"
"strings"
"github.com/google/blueprint"
@@ -626,6 +627,10 @@
a.SetPath("LOCAL_SOONG_LICENSE_METADATA", licenseMetadata.LicenseMetadataPath)
}
+ if _, ok := SingletonModuleProvider(ctx, mod, ModuleInfoJSONProvider); ok {
+ a.SetBool("LOCAL_SOONG_MODULE_INFO_JSON", true)
+ }
+
extraCtx := &androidMkExtraEntriesContext{
ctx: ctx,
mod: mod,
@@ -643,14 +648,14 @@
}
}
+func (a *AndroidMkEntries) disabled() bool {
+ return a.Disabled || !a.OutputFile.Valid()
+}
+
// write flushes the AndroidMkEntries's in-struct data populated by AndroidMkEntries into the
// given Writer object.
func (a *AndroidMkEntries) write(w io.Writer) {
- if a.Disabled {
- return
- }
-
- if !a.OutputFile.Valid() {
+ if a.disabled() {
return
}
@@ -696,7 +701,9 @@
return
}
- err := translateAndroidMk(ctx, absolutePath(transMk.String()), androidMkModulesList)
+ moduleInfoJSON := PathForOutput(ctx, "module-info"+String(ctx.Config().productVariables.Make_suffix)+".json")
+
+ err := translateAndroidMk(ctx, absolutePath(transMk.String()), moduleInfoJSON, androidMkModulesList)
if err != nil {
ctx.Errorf(err.Error())
}
@@ -707,14 +714,16 @@
})
}
-func translateAndroidMk(ctx SingletonContext, absMkFile string, mods []blueprint.Module) error {
+func translateAndroidMk(ctx SingletonContext, absMkFile string, moduleInfoJSONPath WritablePath, mods []blueprint.Module) error {
buf := &bytes.Buffer{}
+ var moduleInfoJSONs []*ModuleInfoJSON
+
fmt.Fprintln(buf, "LOCAL_MODULE_MAKEFILE := $(lastword $(MAKEFILE_LIST))")
typeStats := make(map[string]int)
for _, mod := range mods {
- err := translateAndroidMkModule(ctx, buf, mod)
+ err := translateAndroidMkModule(ctx, buf, &moduleInfoJSONs, mod)
if err != nil {
os.Remove(absMkFile)
return err
@@ -736,10 +745,36 @@
fmt.Fprintf(buf, "STATS.SOONG_MODULE_TYPE.%s := %d\n", mod_type, typeStats[mod_type])
}
- return pathtools.WriteFileIfChanged(absMkFile, buf.Bytes(), 0666)
+ err := pathtools.WriteFileIfChanged(absMkFile, buf.Bytes(), 0666)
+ if err != nil {
+ return err
+ }
+
+ return writeModuleInfoJSON(ctx, moduleInfoJSONs, moduleInfoJSONPath)
}
-func translateAndroidMkModule(ctx SingletonContext, w io.Writer, mod blueprint.Module) error {
+func writeModuleInfoJSON(ctx SingletonContext, moduleInfoJSONs []*ModuleInfoJSON, moduleInfoJSONPath WritablePath) error {
+ moduleInfoJSONBuf := &strings.Builder{}
+ moduleInfoJSONBuf.WriteString("[")
+ for i, moduleInfoJSON := range moduleInfoJSONs {
+ if i != 0 {
+ moduleInfoJSONBuf.WriteString(",\n")
+ }
+ moduleInfoJSONBuf.WriteString("{")
+ moduleInfoJSONBuf.WriteString(strconv.Quote(moduleInfoJSON.core.RegisterName))
+ moduleInfoJSONBuf.WriteString(":")
+ err := encodeModuleInfoJSON(moduleInfoJSONBuf, moduleInfoJSON)
+ moduleInfoJSONBuf.WriteString("}")
+ if err != nil {
+ return err
+ }
+ }
+ moduleInfoJSONBuf.WriteString("]")
+ WriteFileRule(ctx, moduleInfoJSONPath, moduleInfoJSONBuf.String())
+ return nil
+}
+
+func translateAndroidMkModule(ctx SingletonContext, w io.Writer, moduleInfoJSONs *[]*ModuleInfoJSON, mod blueprint.Module) error {
defer func() {
if r := recover(); r != nil {
panic(fmt.Errorf("%s in translateAndroidMkModule for module %s variant %s",
@@ -748,17 +783,23 @@
}()
// Additional cases here require review for correct license propagation to make.
+ var err error
switch x := mod.(type) {
case AndroidMkDataProvider:
- return translateAndroidModule(ctx, w, mod, x)
+ err = translateAndroidModule(ctx, w, moduleInfoJSONs, mod, x)
case bootstrap.GoBinaryTool:
- return translateGoBinaryModule(ctx, w, mod, x)
+ err = translateGoBinaryModule(ctx, w, mod, x)
case AndroidMkEntriesProvider:
- return translateAndroidMkEntriesModule(ctx, w, mod, x)
+ err = translateAndroidMkEntriesModule(ctx, w, moduleInfoJSONs, mod, x)
default:
// Not exported to make so no make variables to set.
- return nil
}
+
+ if err != nil {
+ return err
+ }
+
+ return err
}
// A simple, special Android.mk entry output func to make it possible to build blueprint tools using
@@ -801,8 +842,8 @@
// A support func for the deprecated AndroidMkDataProvider interface. Use AndroidMkEntryProvider
// instead.
-func translateAndroidModule(ctx SingletonContext, w io.Writer, mod blueprint.Module,
- provider AndroidMkDataProvider) error {
+func translateAndroidModule(ctx SingletonContext, w io.Writer, moduleInfoJSONs *[]*ModuleInfoJSON,
+ mod blueprint.Module, provider AndroidMkDataProvider) error {
amod := mod.(Module).base()
if shouldSkipAndroidMkProcessing(amod) {
@@ -852,6 +893,7 @@
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:
@@ -864,17 +906,19 @@
WriteAndroidMkData(w, data)
}
+ if !data.Entries.disabled() {
+ if moduleInfoJSON, ok := SingletonModuleProvider(ctx, mod, ModuleInfoJSONProvider); ok {
+ *moduleInfoJSONs = append(*moduleInfoJSONs, moduleInfoJSON)
+ }
+ }
+
return nil
}
// A support func for the deprecated AndroidMkDataProvider interface. Use AndroidMkEntryProvider
// instead.
func WriteAndroidMkData(w io.Writer, data AndroidMkData) {
- if data.Disabled {
- return
- }
-
- if !data.OutputFile.Valid() {
+ if data.Entries.disabled() {
return
}
@@ -889,18 +933,26 @@
fmt.Fprintln(w, "include "+data.Include)
}
-func translateAndroidMkEntriesModule(ctx SingletonContext, w io.Writer, mod blueprint.Module,
- provider AndroidMkEntriesProvider) error {
+func translateAndroidMkEntriesModule(ctx SingletonContext, w io.Writer, moduleInfoJSONs *[]*ModuleInfoJSON,
+ mod blueprint.Module, provider AndroidMkEntriesProvider) error {
if shouldSkipAndroidMkProcessing(mod.(Module).base()) {
return nil
}
+ entriesList := provider.AndroidMkEntries()
+
// Any new or special cases here need review to verify correct propagation of license information.
- for _, entries := range provider.AndroidMkEntries() {
+ for _, entries := range entriesList {
entries.fillInEntries(ctx, mod)
entries.write(w)
}
+ if len(entriesList) > 0 && !entriesList[0].disabled() {
+ if moduleInfoJSON, ok := SingletonModuleProvider(ctx, mod, ModuleInfoJSONProvider); ok {
+ *moduleInfoJSONs = append(*moduleInfoJSONs, moduleInfoJSON)
+ }
+ }
+
return nil
}
diff --git a/android/base_module_context.go b/android/base_module_context.go
index 2a4b12e..3dfe123 100644
--- a/android/base_module_context.go
+++ b/android/base_module_context.go
@@ -75,34 +75,28 @@
// 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) 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 312a5da..24b9b8a 100644
--- a/android/config.go
+++ b/android/config.go
@@ -18,6 +18,7 @@
// product variables necessary for soong_build's operation.
import (
+ "android/soong/shared"
"encoding/json"
"fmt"
"os"
@@ -118,6 +119,11 @@
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 03968c1..a988964 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 @@
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 @@
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/makevars.go b/android/makevars.go
index d4cfd29..4039e7e 100644
--- a/android/makevars.go
+++ b/android/makevars.go
@@ -16,9 +16,11 @@
import (
"bytes"
+ "cmp"
"fmt"
"path/filepath"
"runtime"
+ "slices"
"sort"
"strings"
@@ -242,6 +244,8 @@
var dists []dist
var phonies []phony
var katiInstalls []katiInstall
+ var katiInitRcInstalls []katiInstall
+ var katiVintfManifestInstalls []katiInstall
var katiSymlinks []katiInstall
providers := append([]makeVarsProvider(nil), makeVarsInitProviders...)
@@ -275,10 +279,33 @@
if m.ExportedToMake() {
katiInstalls = append(katiInstalls, m.base().katiInstalls...)
+ katiInitRcInstalls = append(katiInitRcInstalls, m.base().katiInitRcInstalls...)
+ katiVintfManifestInstalls = append(katiVintfManifestInstalls, m.base().katiVintfInstalls...)
katiSymlinks = append(katiSymlinks, m.base().katiSymlinks...)
}
})
+ compareKatiInstalls := func(a, b katiInstall) int {
+ aTo, bTo := a.to.String(), b.to.String()
+ if cmpTo := cmp.Compare(aTo, bTo); cmpTo != 0 {
+ return cmpTo
+ }
+
+ aFrom, bFrom := a.from.String(), b.from.String()
+ return cmp.Compare(aFrom, bFrom)
+ }
+
+ slices.SortFunc(katiInitRcInstalls, compareKatiInstalls)
+ katiInitRcInstalls = slices.CompactFunc(katiInitRcInstalls, func(a, b katiInstall) bool {
+ return compareKatiInstalls(a, b) == 0
+ })
+ katiInstalls = append(katiInstalls, katiInitRcInstalls...)
+
+ slices.SortFunc(katiVintfManifestInstalls, compareKatiInstalls)
+ katiVintfManifestInstalls = slices.CompactFunc(katiVintfManifestInstalls, func(a, b katiInstall) bool {
+ return compareKatiInstalls(a, b) == 0
+ })
+
if ctx.Failed() {
return
}
@@ -316,7 +343,7 @@
ctx.Errorf(err.Error())
}
- installsBytes := s.writeInstalls(katiInstalls, katiSymlinks)
+ installsBytes := s.writeInstalls(katiInstalls, katiSymlinks, katiVintfManifestInstalls)
if err := pathtools.WriteFileIfChanged(installsFile, installsBytes, 0666); err != nil {
ctx.Errorf(err.Error())
}
@@ -438,7 +465,7 @@
// writeInstalls writes the list of install rules generated by Soong to a makefile. The rules
// are exported to Make instead of written directly to the ninja file so that main.mk can add
// the dependencies from the `required` property that are hard to resolve in Soong.
-func (s *makeVarsSingleton) writeInstalls(installs, symlinks []katiInstall) []byte {
+func (s *makeVarsSingleton) writeInstalls(installs, symlinks, katiVintfManifestInstalls []katiInstall) []byte {
buf := &bytes.Buffer{}
fmt.Fprint(buf, `# Autogenerated file
@@ -486,9 +513,9 @@
for _, symlink := range symlinks {
fmt.Fprintf(buf, "%s:", symlink.to.String())
if symlink.from != nil {
- // The symlink doesn't need updating when the target is modified, but we sometimes
- // have a dependency on a symlink to a binary instead of to the binary directly, and
- // the mtime of the symlink must be updated when the binary is modified, so use a
+ // The katiVintfManifestInstall doesn't need updating when the target is modified, but we sometimes
+ // have a dependency on a katiVintfManifestInstall to a binary instead of to the binary directly, and
+ // the mtime of the katiVintfManifestInstall must be updated when the binary is modified, so use a
// normal dependency here instead of an order-only dependency.
fmt.Fprintf(buf, " %s", symlink.from.String())
}
@@ -507,7 +534,7 @@
if symlink.from != nil {
rel, err := filepath.Rel(filepath.Dir(symlink.to.String()), symlink.from.String())
if err != nil {
- panic(fmt.Errorf("failed to find relative path for symlink from %q to %q: %w",
+ panic(fmt.Errorf("failed to find relative path for katiVintfManifestInstall from %q to %q: %w",
symlink.from.String(), symlink.to.String(), err))
}
fromStr = rel
@@ -521,6 +548,19 @@
fmt.Fprintln(buf)
}
+ for _, install := range katiVintfManifestInstalls {
+ // Write a rule for each vintf install request that calls the copy-vintf-manifest-chedk make function.
+ fmt.Fprintf(buf, "$(eval $(call copy-vintf-manifest-checked, %s, %s))\n", install.from.String(), install.to.String())
+
+ if len(install.implicitDeps) > 0 {
+ panic(fmt.Errorf("unsupported implicitDeps %q in vintf install rule %q", install.implicitDeps, install.to))
+ }
+ if len(install.orderOnlyDeps) > 0 {
+ panic(fmt.Errorf("unsupported orderOnlyDeps %q in vintf install rule %q", install.orderOnlyDeps, install.to))
+ }
+
+ fmt.Fprintln(buf)
+ }
return buf.Bytes()
}
diff --git a/android/module.go b/android/module.go
index 1100fee..1a428e5 100644
--- a/android/module.go
+++ b/android/module.go
@@ -23,6 +23,7 @@
"net/url"
"path/filepath"
"reflect"
+ "slices"
"sort"
"strings"
@@ -839,8 +840,12 @@
// katiInstalls tracks the install rules that were created by Soong but are being exported
// to Make to convert to ninja rules so that Make can add additional dependencies.
katiInstalls katiInstalls
- katiSymlinks katiInstalls
- testData []DataPath
+ // katiInitRcInstalls and katiVintfInstalls track the install rules created by Soong that are
+ // allowed to have duplicates across modules and variants.
+ katiInitRcInstalls katiInstalls
+ katiVintfInstalls katiInstalls
+ katiSymlinks katiInstalls
+ testData []DataPath
// The files to copy to the dist as explicitly specified in the .bp file.
distFiles TaggedDistFiles
@@ -863,12 +868,19 @@
initRcPaths Paths
vintfFragmentsPaths Paths
+ installedInitRcPaths InstallPaths
+ installedVintfFragmentsPaths InstallPaths
+
// set of dependency module:location mappings used to populate the license metadata for
// apex containers.
licenseInstallMap []string
// The path to the generated license metadata file for the module.
licenseMetadataFile WritablePath
+
+ // moduleInfoJSON can be filled out by GenerateAndroidBuildActions to write a JSON file that will
+ // be included in the final module-info.json produced by Make.
+ moduleInfoJSON *ModuleInfoJSON
}
func (m *ModuleBase) AddJSONData(d *map[string]interface{}) {
@@ -1684,6 +1696,41 @@
}
})
+ if m.Device() {
+ // Handle any init.rc and vintf fragment files requested by the module. All files installed by this
+ // module will automatically have a dependency on the installed init.rc or vintf fragment file.
+ // The same init.rc or vintf fragment file may be requested by multiple modules or variants,
+ // so instead of installing them now just compute the install path and store it for later.
+ // The full list of all init.rc and vintf fragment install rules will be deduplicated later
+ // so only a single rule is created for each init.rc or vintf fragment file.
+
+ if !m.InVendorRamdisk() {
+ m.initRcPaths = PathsForModuleSrc(ctx, m.commonProperties.Init_rc)
+ rcDir := PathForModuleInstall(ctx, "etc", "init")
+ for _, src := range m.initRcPaths {
+ installedInitRc := rcDir.Join(ctx, src.Base())
+ m.katiInitRcInstalls = append(m.katiInitRcInstalls, katiInstall{
+ from: src,
+ to: installedInitRc,
+ })
+ ctx.PackageFile(rcDir, src.Base(), src)
+ m.installedInitRcPaths = append(m.installedInitRcPaths, installedInitRc)
+ }
+ }
+
+ m.vintfFragmentsPaths = PathsForModuleSrc(ctx, m.commonProperties.Vintf_fragments)
+ vintfDir := PathForModuleInstall(ctx, "etc", "vintf", "manifest")
+ for _, src := range m.vintfFragmentsPaths {
+ installedVintfFragment := vintfDir.Join(ctx, src.Base())
+ m.katiVintfInstalls = append(m.katiVintfInstalls, katiInstall{
+ from: src,
+ to: installedVintfFragment,
+ })
+ ctx.PackageFile(vintfDir, src.Base(), src)
+ m.installedVintfFragmentsPaths = append(m.installedVintfFragmentsPaths, installedVintfFragment)
+ }
+ }
+
licensesPropertyFlattener(ctx)
if ctx.Failed() {
return
@@ -1694,18 +1741,6 @@
return
}
- m.initRcPaths = PathsForModuleSrc(ctx, m.commonProperties.Init_rc)
- rcDir := PathForModuleInstall(ctx, "etc", "init")
- for _, src := range m.initRcPaths {
- ctx.PackageFile(rcDir, filepath.Base(src.String()), src)
- }
-
- m.vintfFragmentsPaths = PathsForModuleSrc(ctx, m.commonProperties.Vintf_fragments)
- vintfDir := PathForModuleInstall(ctx, "etc", "vintf", "manifest")
- for _, src := range m.vintfFragmentsPaths {
- ctx.PackageFile(vintfDir, filepath.Base(src.String()), src)
- }
-
// Create the set of tagged dist files after calling GenerateAndroidBuildActions
// as GenerateTaggedDistFiles() calls OutputFiles(tag) and so relies on the
// output paths being set which must be done before or during
@@ -1741,11 +1776,90 @@
buildLicenseMetadata(ctx, m.licenseMetadataFile)
+ if m.moduleInfoJSON != nil {
+ var installed InstallPaths
+ installed = append(installed, m.katiInstalls.InstallPaths()...)
+ installed = append(installed, m.katiSymlinks.InstallPaths()...)
+ installed = append(installed, m.katiInitRcInstalls.InstallPaths()...)
+ installed = append(installed, m.katiVintfInstalls.InstallPaths()...)
+ installedStrings := installed.Strings()
+
+ var targetRequired, hostRequired []string
+ if ctx.Host() {
+ targetRequired = m.commonProperties.Target_required
+ } else {
+ hostRequired = m.commonProperties.Host_required
+ }
+
+ var data []string
+ for _, d := range m.testData {
+ data = append(data, d.ToRelativeInstallPath())
+ }
+
+ if m.moduleInfoJSON.Uninstallable {
+ installedStrings = nil
+ if len(m.moduleInfoJSON.CompatibilitySuites) == 1 && m.moduleInfoJSON.CompatibilitySuites[0] == "null-suite" {
+ m.moduleInfoJSON.CompatibilitySuites = nil
+ m.moduleInfoJSON.TestConfig = nil
+ m.moduleInfoJSON.AutoTestConfig = nil
+ data = nil
+ }
+ }
+
+ m.moduleInfoJSON.core = CoreModuleInfoJSON{
+ RegisterName: m.moduleInfoRegisterName(ctx, m.moduleInfoJSON.SubName),
+ Path: []string{ctx.ModuleDir()},
+ Installed: installedStrings,
+ ModuleName: m.BaseModuleName() + m.moduleInfoJSON.SubName,
+ SupportedVariants: []string{m.moduleInfoVariant(ctx)},
+ TargetDependencies: targetRequired,
+ HostDependencies: hostRequired,
+ Data: data,
+ }
+ SetProvider(ctx, ModuleInfoJSONProvider, m.moduleInfoJSON)
+ }
+
m.buildParams = ctx.buildParams
m.ruleParams = ctx.ruleParams
m.variables = ctx.variables
}
+func (m *ModuleBase) moduleInfoRegisterName(ctx ModuleContext, subName string) string {
+ name := m.BaseModuleName()
+
+ prefix := ""
+ if ctx.Host() {
+ if ctx.Os() != ctx.Config().BuildOS {
+ prefix = "host_cross_"
+ }
+ }
+ suffix := ""
+ arches := slices.Clone(ctx.Config().Targets[ctx.Os()])
+ arches = slices.DeleteFunc(arches, func(target Target) bool {
+ return target.NativeBridge != ctx.Target().NativeBridge
+ })
+ if len(arches) > 0 && ctx.Arch().ArchType != arches[0].Arch.ArchType {
+ if ctx.Arch().ArchType.Multilib == "lib32" {
+ suffix = "_32"
+ } else {
+ suffix = "_64"
+ }
+ }
+ return prefix + name + subName + suffix
+}
+
+func (m *ModuleBase) moduleInfoVariant(ctx ModuleContext) string {
+ variant := "DEVICE"
+ if ctx.Host() {
+ if ctx.Os() != ctx.Config().BuildOS {
+ variant = "HOST_CROSS"
+ } else {
+ variant = "HOST"
+ }
+ }
+ return variant
+}
+
// Check the supplied dist structure to make sure that it is valid.
//
// property - the base property, e.g. dist or dists[1], which is combined with the
diff --git a/android/module_context.go b/android/module_context.go
index 81692d5..e772f8b 100644
--- a/android/module_context.go
+++ b/android/module_context.go
@@ -210,6 +210,11 @@
// LicenseMetadataFile returns the path where the license metadata for this module will be
// generated.
LicenseMetadataFile() Path
+
+ // ModuleInfoJSON returns a pointer to the ModuleInfoJSON struct that can be filled out by
+ // GenerateAndroidBuildActions. If it is called then the struct will be written out and included in
+ // the module-info.json generated by Make, and Make will not generate its own data for this module.
+ ModuleInfoJSON() *ModuleInfoJSON
}
type moduleContext struct {
@@ -518,6 +523,8 @@
if !m.skipInstall() {
deps = append(deps, InstallPaths(m.module.base().installFilesDepSet.ToList())...)
+ deps = append(deps, m.module.base().installedInitRcPaths...)
+ deps = append(deps, m.module.base().installedVintfFragmentsPaths...)
var implicitDeps, orderOnlyDeps Paths
@@ -695,6 +702,15 @@
return m.module.base().licenseMetadataFile
}
+func (m *moduleContext) ModuleInfoJSON() *ModuleInfoJSON {
+ if moduleInfoJSON := m.module.base().moduleInfoJSON; moduleInfoJSON != nil {
+ return moduleInfoJSON
+ }
+ moduleInfoJSON := &ModuleInfoJSON{}
+ m.module.base().moduleInfoJSON = moduleInfoJSON
+ return moduleInfoJSON
+}
+
// Returns a list of paths expanded from globs and modules referenced using ":module" syntax. The property must
// be tagged with `android:"path" to support automatic source module dependency resolution.
//
diff --git a/android/module_info_json.go b/android/module_info_json.go
new file mode 100644
index 0000000..1c0a38e
--- /dev/null
+++ b/android/module_info_json.go
@@ -0,0 +1,103 @@
+package android
+
+import (
+ "encoding/json"
+ "io"
+ "slices"
+
+ "github.com/google/blueprint"
+)
+
+type CoreModuleInfoJSON struct {
+ RegisterName string `json:"-"`
+ Path []string `json:"path,omitempty"` // $(sort $(ALL_MODULES.$(m).PATH))
+ Installed []string `json:"installed,omitempty"` // $(sort $(ALL_MODULES.$(m).INSTALLED))
+ ModuleName string `json:"module_name,omitempty"` // $(ALL_MODULES.$(m).MODULE_NAME)
+ SupportedVariants []string `json:"supported_variants,omitempty"` // $(sort $(ALL_MODULES.$(m).SUPPORTED_VARIANTS))
+ HostDependencies []string `json:"host_dependencies,omitempty"` // $(sort $(ALL_MODULES.$(m).HOST_REQUIRED_FROM_TARGET))
+ TargetDependencies []string `json:"target_dependencies,omitempty"` // $(sort $(ALL_MODULES.$(m).TARGET_REQUIRED_FROM_HOST))
+ Data []string `json:"data,omitempty"` // $(sort $(ALL_MODULES.$(m).TEST_DATA))
+}
+
+type ModuleInfoJSON struct {
+ core CoreModuleInfoJSON
+ SubName string `json:"-"`
+ Uninstallable bool `json:"-"`
+ Class []string `json:"class,omitempty"` // $(sort $(ALL_MODULES.$(m).CLASS))
+ Tags []string `json:"tags,omitempty"` // $(sort $(ALL_MODULES.$(m).TAGS))
+ Dependencies []string `json:"dependencies,omitempty"` // $(sort $(ALL_DEPS.$(m).ALL_DEPS))
+ SharedLibs []string `json:"shared_libs,omitempty"` // $(sort $(ALL_MODULES.$(m).SHARED_LIBS))
+ StaticLibs []string `json:"static_libs,omitempty"` // $(sort $(ALL_MODULES.$(m).STATIC_LIBS))
+ SystemSharedLibs []string `json:"system_shared_libs,omitempty"` // $(sort $(ALL_MODULES.$(m).SYSTEM_SHARED_LIBS))
+ Srcs []string `json:"srcs,omitempty"` // $(sort $(ALL_MODULES.$(m).SRCS))
+ SrcJars []string `json:"srcjars,omitempty"` // $(sort $(ALL_MODULES.$(m).SRCJARS))
+ ClassesJar []string `json:"classes_jar,omitempty"` // $(sort $(ALL_MODULES.$(m).CLASSES_JAR))
+ TestMainlineModules []string `json:"test_mainline_modules,omitempty"` // $(sort $(ALL_MODULES.$(m).TEST_MAINLINE_MODULES))
+ IsUnitTest bool `json:"is_unit_test,omitempty"` // $(ALL_MODULES.$(m).IS_UNIT_TEST)
+ TestOptionsTags []string `json:"test_options_tags,omitempty"` // $(sort $(ALL_MODULES.$(m).TEST_OPTIONS_TAGS))
+ RuntimeDependencies []string `json:"runtime_dependencies,omitempty"` // $(sort $(ALL_MODULES.$(m).LOCAL_RUNTIME_LIBRARIES))
+ StaticDependencies []string `json:"static_dependencies,omitempty"` // $(sort $(ALL_MODULES.$(m).LOCAL_STATIC_LIBRARIES))
+ DataDependencies []string `json:"data_dependencies,omitempty"` // $(sort $(ALL_MODULES.$(m).TEST_DATA_BINS))
+
+ CompatibilitySuites []string `json:"compatibility_suites,omitempty"` // $(sort $(ALL_MODULES.$(m).COMPATIBILITY_SUITES))
+ AutoTestConfig []string `json:"auto_test_config,omitempty"` // $(ALL_MODULES.$(m).auto_test_config)
+ TestConfig []string `json:"test_config,omitempty"` // $(strip $(ALL_MODULES.$(m).TEST_CONFIG) $(ALL_MODULES.$(m).EXTRA_TEST_CONFIGS)
+}
+
+//ALL_DEPS.$(LOCAL_MODULE).ALL_DEPS := $(sort \
+//$(ALL_DEPS.$(LOCAL_MODULE).ALL_DEPS) \
+//$(LOCAL_STATIC_LIBRARIES) \
+//$(LOCAL_WHOLE_STATIC_LIBRARIES) \
+//$(LOCAL_SHARED_LIBRARIES) \
+//$(LOCAL_DYLIB_LIBRARIES) \
+//$(LOCAL_RLIB_LIBRARIES) \
+//$(LOCAL_PROC_MACRO_LIBRARIES) \
+//$(LOCAL_HEADER_LIBRARIES) \
+//$(LOCAL_STATIC_JAVA_LIBRARIES) \
+//$(LOCAL_JAVA_LIBRARIES) \
+//$(LOCAL_JNI_SHARED_LIBRARIES))
+
+type combinedModuleInfoJSON struct {
+ *CoreModuleInfoJSON
+ *ModuleInfoJSON
+}
+
+func encodeModuleInfoJSON(w io.Writer, moduleInfoJSON *ModuleInfoJSON) error {
+ moduleInfoJSONCopy := *moduleInfoJSON
+
+ sortAndUnique := func(s *[]string) {
+ *s = slices.Clone(*s)
+ slices.Sort(*s)
+ *s = slices.Compact(*s)
+ }
+
+ sortAndUnique(&moduleInfoJSONCopy.core.Path)
+ sortAndUnique(&moduleInfoJSONCopy.core.Installed)
+ sortAndUnique(&moduleInfoJSONCopy.core.SupportedVariants)
+ sortAndUnique(&moduleInfoJSONCopy.core.HostDependencies)
+ sortAndUnique(&moduleInfoJSONCopy.core.TargetDependencies)
+ sortAndUnique(&moduleInfoJSONCopy.core.Data)
+
+ sortAndUnique(&moduleInfoJSONCopy.Class)
+ sortAndUnique(&moduleInfoJSONCopy.Tags)
+ sortAndUnique(&moduleInfoJSONCopy.Dependencies)
+ sortAndUnique(&moduleInfoJSONCopy.SharedLibs)
+ sortAndUnique(&moduleInfoJSONCopy.StaticLibs)
+ sortAndUnique(&moduleInfoJSONCopy.SystemSharedLibs)
+ sortAndUnique(&moduleInfoJSONCopy.Srcs)
+ sortAndUnique(&moduleInfoJSONCopy.SrcJars)
+ sortAndUnique(&moduleInfoJSONCopy.ClassesJar)
+ sortAndUnique(&moduleInfoJSONCopy.TestMainlineModules)
+ sortAndUnique(&moduleInfoJSONCopy.TestOptionsTags)
+ sortAndUnique(&moduleInfoJSONCopy.RuntimeDependencies)
+ sortAndUnique(&moduleInfoJSONCopy.StaticDependencies)
+ sortAndUnique(&moduleInfoJSONCopy.DataDependencies)
+ sortAndUnique(&moduleInfoJSONCopy.CompatibilitySuites)
+ sortAndUnique(&moduleInfoJSONCopy.AutoTestConfig)
+ sortAndUnique(&moduleInfoJSONCopy.TestConfig)
+
+ encoder := json.NewEncoder(w)
+ return encoder.Encode(combinedModuleInfoJSON{&moduleInfoJSONCopy.core, &moduleInfoJSONCopy})
+}
+
+var ModuleInfoJSONProvider = blueprint.NewProvider[*ModuleInfoJSON]()
diff --git a/android/packaging.go b/android/packaging.go
index 503bb97..2506378 100644
--- a/android/packaging.go
+++ b/android/packaging.go
@@ -17,6 +17,7 @@
import (
"fmt"
"path/filepath"
+ "strings"
"github.com/google/blueprint"
)
@@ -240,6 +241,9 @@
// entries into the specified directory.
func (p *PackagingBase) CopySpecsToDir(ctx ModuleContext, builder *RuleBuilder, specs map[string]PackagingSpec, dir WritablePath) (entries []string) {
seenDir := make(map[string]bool)
+ preparerPath := PathForModuleOut(ctx, "preparer.sh")
+ cmd := builder.Command().Tool(preparerPath)
+ var sb strings.Builder
for _, k := range SortedKeys(specs) {
ps := specs[k]
destPath := filepath.Join(dir.String(), ps.relPathInPackage)
@@ -247,18 +251,21 @@
entries = append(entries, ps.relPathInPackage)
if _, ok := seenDir[destDir]; !ok {
seenDir[destDir] = true
- builder.Command().Text("mkdir").Flag("-p").Text(destDir)
+ sb.WriteString(fmt.Sprintf("mkdir -p %s\n", destDir))
}
if ps.symlinkTarget == "" {
- builder.Command().Text("cp").Input(ps.srcPath).Text(destPath)
+ cmd.Implicit(ps.srcPath)
+ sb.WriteString(fmt.Sprintf("cp %s %s\n", ps.srcPath, destPath))
} else {
- builder.Command().Text("ln").Flag("-sf").Text(ps.symlinkTarget).Text(destPath)
+ sb.WriteString(fmt.Sprintf("ln -sf %s %s\n", ps.symlinkTarget, destPath))
}
if ps.executable {
- builder.Command().Text("chmod").Flag("a+x").Text(destPath)
+ sb.WriteString(fmt.Sprintf("chmod a+x %s\n", destPath))
}
}
+ WriteExecutableFileRuleVerbatim(ctx, preparerPath, sb.String())
+
return entries
}
diff --git a/android/provider.go b/android/provider.go
index b2cc7c0..3b9c5d2 100644
--- a/android/provider.go
+++ b/android/provider.go
@@ -79,7 +79,7 @@
// 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 @@
// 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 0000000..9d7f5e8
--- /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 cd968cd..d00c15f 100644
--- a/android/register.go
+++ b/android/register.go
@@ -191,8 +191,9 @@
// 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 d659dcc..9e5f12d 100644
--- a/android/rule_builder_test.go
+++ b/android/rule_builder_test.go
@@ -816,13 +816,13 @@
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 @@
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 39a268b..3d0300a 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -203,16 +203,6 @@
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 ae1c657..51313ce 100644
--- a/android/util.go
+++ b/android/util.go
@@ -22,6 +22,7 @@
"runtime"
"sort"
"strings"
+ "sync"
)
// CopyOf returns a new slice that has the same contents as s.
@@ -597,3 +598,32 @@
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 29d59e5..c4545d2 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -2386,7 +2386,7 @@
ProfilePathOnHost: info.ProfilePathOnHost(),
LibraryNameToDexJarPathOnHost: info.DexBootJarPathMap(),
}
- ctx.SetProvider(android.ApexExportsInfoProvider, exports)
+ android.SetProvider(ctx, android.ApexExportsInfoProvider, exports)
}
})
}
@@ -2885,14 +2885,6 @@
//
// Module separator
//
- m["com.android.tethering"] = []string{
- "android.hardware.tetheroffload.config-V1.0-java",
- "android.hardware.tetheroffload.control-V1.0-java",
- "net-utils-framework-common",
- }
- //
- // Module separator
- //
m["com.android.wifi"] = []string{
"PlatformProperties",
"android.hardware.wifi-V1.0-java",
@@ -2916,15 +2908,6 @@
"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 616421a..1b9fa19 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -11436,6 +11436,20 @@
}
}
+ // 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 @@
)
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 b741963..01b616b 100644
--- a/apex/platform_bootclasspath_test.go
+++ b/apex/platform_bootclasspath_test.go
@@ -382,6 +382,9 @@
// 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 @@
// 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 1f57b63..188875a 100644
--- a/apex/prebuilt.go
+++ b/apex/prebuilt.go
@@ -790,7 +790,7 @@
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/cc/androidmk.go b/cc/androidmk.go
index 54c7f97..bde096b 100644
--- a/cc/androidmk.go
+++ b/cc/androidmk.go
@@ -101,15 +101,6 @@
if len(c.Properties.AndroidMkSharedLibs) > 0 {
entries.AddStrings("LOCAL_SHARED_LIBRARIES", c.Properties.AndroidMkSharedLibs...)
}
- if len(c.Properties.AndroidMkStaticLibs) > 0 {
- entries.AddStrings("LOCAL_STATIC_LIBRARIES", c.Properties.AndroidMkStaticLibs...)
- }
- if len(c.Properties.AndroidMkWholeStaticLibs) > 0 {
- entries.AddStrings("LOCAL_WHOLE_STATIC_LIBRARIES", c.Properties.AndroidMkWholeStaticLibs...)
- }
- if len(c.Properties.AndroidMkHeaderLibs) > 0 {
- entries.AddStrings("LOCAL_HEADER_LIBRARIES", c.Properties.AndroidMkHeaderLibs...)
- }
if len(c.Properties.AndroidMkRuntimeLibs) > 0 {
entries.AddStrings("LOCAL_RUNTIME_LIBRARIES", c.Properties.AndroidMkRuntimeLibs...)
}
diff --git a/cc/binary.go b/cc/binary.go
index 61541ad..7aa8e20 100644
--- a/cc/binary.go
+++ b/cc/binary.go
@@ -541,6 +541,11 @@
return binary.Properties.Overrides
}
+func (binary *binaryDecorator) moduleInfoJSON(ctx ModuleContext, moduleInfoJSON *android.ModuleInfoJSON) {
+ moduleInfoJSON.Class = []string{"EXECUTABLES"}
+ binary.baseLinker.moduleInfoJSON(ctx, moduleInfoJSON)
+}
+
var _ overridable = (*binaryDecorator)(nil)
func init() {
diff --git a/cc/cc.go b/cc/cc.go
index 9f32c44..6e920f0 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -543,6 +543,7 @@
isPreventInstall() bool
isCfiAssemblySupportEnabled() bool
getSharedFlags() *SharedFlags
+ notInPlatform() bool
}
type SharedFlags struct {
@@ -623,6 +624,8 @@
// Get the deps that have been explicitly specified in the properties.
linkerSpecifiedDeps(specifiedDeps specifiedDeps) specifiedDeps
+
+ moduleInfoJSON(ctx ModuleContext, moduleInfoJSON *android.ModuleInfoJSON)
}
// specifiedDeps is a tuple struct representing dependencies of a linked binary owned by the linker.
@@ -1785,6 +1788,10 @@
return ctx.mod.isCfiAssemblySupportEnabled()
}
+func (ctx *moduleContextImpl) notInPlatform() bool {
+ return ctx.mod.NotInPlatform()
+}
+
func newBaseModule(hod android.HostOrDeviceSupported, multilib android.Multilib) *Module {
return &Module{
hod: hod,
@@ -2139,6 +2146,39 @@
android.CollectDependencyAconfigFiles(ctx, &c.mergedAconfigFiles)
c.maybeInstall(ctx, apexInfo)
+
+ if c.linker != nil {
+ moduleInfoJSON := ctx.ModuleInfoJSON()
+ c.linker.moduleInfoJSON(ctx, moduleInfoJSON)
+ moduleInfoJSON.SharedLibs = c.Properties.AndroidMkSharedLibs
+ moduleInfoJSON.StaticLibs = c.Properties.AndroidMkStaticLibs
+ moduleInfoJSON.SystemSharedLibs = c.Properties.AndroidMkSystemSharedLibs
+ moduleInfoJSON.RuntimeDependencies = c.Properties.AndroidMkRuntimeLibs
+
+ moduleInfoJSON.Dependencies = append(moduleInfoJSON.Dependencies, c.Properties.AndroidMkSharedLibs...)
+ moduleInfoJSON.Dependencies = append(moduleInfoJSON.Dependencies, c.Properties.AndroidMkStaticLibs...)
+ moduleInfoJSON.Dependencies = append(moduleInfoJSON.Dependencies, c.Properties.AndroidMkHeaderLibs...)
+ moduleInfoJSON.Dependencies = append(moduleInfoJSON.Dependencies, c.Properties.AndroidMkWholeStaticLibs...)
+
+ if c.sanitize != nil && len(moduleInfoJSON.Class) > 0 &&
+ (moduleInfoJSON.Class[0] == "STATIC_LIBRARIES" || moduleInfoJSON.Class[0] == "HEADER_LIBRARIES") {
+ if Bool(c.sanitize.Properties.SanitizeMutated.Cfi) {
+ moduleInfoJSON.SubName += ".cfi"
+ }
+ if Bool(c.sanitize.Properties.SanitizeMutated.Hwaddress) {
+ moduleInfoJSON.SubName += ".hwasan"
+ }
+ if Bool(c.sanitize.Properties.SanitizeMutated.Scs) {
+ moduleInfoJSON.SubName += ".scs"
+ }
+ }
+ moduleInfoJSON.SubName += c.Properties.SubName
+
+ if c.Properties.IsSdkVariant && c.Properties.SdkAndPlatformVariantVisibleToMake {
+ moduleInfoJSON.Uninstallable = true
+ }
+
+ }
}
func (c *Module) maybeUnhideFromMake() {
diff --git a/cc/fuzz.go b/cc/fuzz.go
index f5642d6..2436f33 100644
--- a/cc/fuzz.go
+++ b/cc/fuzz.go
@@ -144,25 +144,35 @@
}
func (fuzz *fuzzBinary) linkerFlags(ctx ModuleContext, flags Flags) Flags {
+ subdir := "lib"
+ if ctx.inVendor() {
+ subdir = "lib/vendor"
+ }
+
flags = fuzz.binaryDecorator.linkerFlags(ctx, flags)
// RunPaths on devices isn't instantiated by the base linker. `../lib` for
// installed fuzz targets (both host and device), and `./lib` for fuzz
// target packages.
- flags.Local.LdFlags = append(flags.Local.LdFlags, `-Wl,-rpath,\$$ORIGIN/lib`)
+ flags.Local.LdFlags = append(flags.Local.LdFlags, `-Wl,-rpath,\$$ORIGIN/`+subdir)
// When running on device, fuzz targets with vendor: true set will be in
// fuzzer_name/vendor/fuzzer_name (note the extra 'vendor' and thus need to
// link with libraries in ../../lib/. Non-vendor binaries only need to look
// one level up, in ../lib/.
if ctx.inVendor() {
- flags.Local.LdFlags = append(flags.Local.LdFlags, `-Wl,-rpath,\$$ORIGIN/../../lib`)
+ flags.Local.LdFlags = append(flags.Local.LdFlags, `-Wl,-rpath,\$$ORIGIN/../../`+subdir)
} else {
- flags.Local.LdFlags = append(flags.Local.LdFlags, `-Wl,-rpath,\$$ORIGIN/../lib`)
+ flags.Local.LdFlags = append(flags.Local.LdFlags, `-Wl,-rpath,\$$ORIGIN/../`+subdir)
}
return flags
}
+func (fuzz *fuzzBinary) moduleInfoJSON(ctx ModuleContext, moduleInfoJSON *android.ModuleInfoJSON) {
+ fuzz.binaryDecorator.moduleInfoJSON(ctx, moduleInfoJSON)
+ moduleInfoJSON.Class = []string{"EXECUTABLES"}
+}
+
// IsValidSharedDependency takes a module and determines if it is a unique shared library
// that should be installed in the fuzz target output directories. This function
// returns true, unless:
@@ -216,19 +226,27 @@
}
func SharedLibraryInstallLocation(
- libraryBase string, isHost bool, fuzzDir string, archString string) string {
+ libraryBase string, isHost bool, isVendor bool, fuzzDir string, archString string) string {
installLocation := "$(PRODUCT_OUT)/data"
if isHost {
installLocation = "$(HOST_OUT)"
}
+ subdir := "lib"
+ if isVendor {
+ subdir = "lib/vendor"
+ }
installLocation = filepath.Join(
- installLocation, fuzzDir, archString, "lib", libraryBase)
+ installLocation, fuzzDir, archString, subdir, libraryBase)
return installLocation
}
// Get the device-only shared library symbols install directory.
-func SharedLibrarySymbolsInstallLocation(libraryBase string, fuzzDir string, archString string) string {
- return filepath.Join("$(PRODUCT_OUT)/symbols/data/", fuzzDir, archString, "/lib/", libraryBase)
+func SharedLibrarySymbolsInstallLocation(libraryBase string, isVendor bool, fuzzDir string, archString string) string {
+ subdir := "lib"
+ if isVendor {
+ subdir = "lib/vendor"
+ }
+ return filepath.Join("$(PRODUCT_OUT)/symbols/data/", fuzzDir, archString, subdir, libraryBase)
}
func (fuzzBin *fuzzBinary) install(ctx ModuleContext, file android.Path) {
@@ -239,16 +257,29 @@
// Grab the list of required shared libraries.
fuzzBin.sharedLibraries, _ = CollectAllSharedDependencies(ctx)
+ // TODO: does not mirror Android linkernamespaces
+ // the logic here has special cases for vendor, but it would need more work to
+ // work in arbitrary partitions, so just surface errors early for a few cases
+ //
+ // Even without these, there are certain situations across linkernamespaces
+ // that this won't support. For instance, you might have:
+ //
+ // my_fuzzer (vendor) -> libbinder_ndk (core) -> libbinder (vendor)
+ //
+ // This dependency chain wouldn't be possible to express in the current
+ // logic because all the deps currently match the variant of the source
+ // module.
+
for _, ruleBuilderInstall := range fuzzBin.sharedLibraries {
install := ruleBuilderInstall.To
fuzzBin.installedSharedDeps = append(fuzzBin.installedSharedDeps,
SharedLibraryInstallLocation(
- install, ctx.Host(), installBase, ctx.Arch().ArchType.String()))
+ install, ctx.Host(), ctx.inVendor(), installBase, ctx.Arch().ArchType.String()))
// Also add the dependency on the shared library symbols dir.
if !ctx.Host() {
fuzzBin.installedSharedDeps = append(fuzzBin.installedSharedDeps,
- SharedLibrarySymbolsInstallLocation(install, installBase, ctx.Arch().ArchType.String()))
+ SharedLibrarySymbolsInstallLocation(install, ctx.inVendor(), installBase, ctx.Arch().ArchType.String()))
}
}
@@ -407,6 +438,10 @@
}
sharedLibsInstallDirPrefix := "lib"
+ if ccModule.InVendor() {
+ sharedLibsInstallDirPrefix = "lib/vendor"
+ }
+
if !ccModule.IsFuzzModule() {
return
}
@@ -499,7 +534,7 @@
// install it to the output directory. Setup the install destination here,
// which will be used by $(copy-many-files) in the Make backend.
installDestination := SharedLibraryInstallLocation(
- install, module.Host(), fuzzDir, archString)
+ install, module.Host(), module.InVendor(), fuzzDir, archString)
if (*sharedLibraryInstalled)[installDestination] {
continue
}
@@ -517,7 +552,7 @@
// we want symbolization tools (like `stack`) to be able to find the symbols
// in $ANDROID_PRODUCT_OUT/symbols automagically.
if !module.Host() {
- symbolsInstallDestination := SharedLibrarySymbolsInstallLocation(install, fuzzDir, archString)
+ symbolsInstallDestination := SharedLibrarySymbolsInstallLocation(install, module.InVendor(), fuzzDir, archString)
symbolsInstallDestination = strings.ReplaceAll(symbolsInstallDestination, "$", "$$")
s.SharedLibInstallStrings = append(s.SharedLibInstallStrings,
library.String()+":"+symbolsInstallDestination)
diff --git a/cc/library.go b/cc/library.go
index 4c8deef..592f70f 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -1041,6 +1041,40 @@
return specifiedDeps
}
+func (library *libraryDecorator) moduleInfoJSON(ctx ModuleContext, moduleInfoJSON *android.ModuleInfoJSON) {
+ if library.static() {
+ moduleInfoJSON.Class = []string{"STATIC_LIBRARIES"}
+ moduleInfoJSON.Uninstallable = true
+ } else if library.shared() {
+ moduleInfoJSON.Class = []string{"SHARED_LIBRARIES"}
+ } else if library.header() {
+ moduleInfoJSON.Class = []string{"HEADER_LIBRARIES"}
+ moduleInfoJSON.Uninstallable = true
+ }
+
+ if library.buildStubs() && library.stubsVersion() != "" {
+ moduleInfoJSON.SubName += "." + library.stubsVersion()
+ }
+
+ // If a library providing a stub is included in an APEX, the private APIs of the library
+ // is accessible only inside the APEX. From outside of the APEX, clients can only use the
+ // public APIs via the stub. To enforce this, the (latest version of the) stub gets the
+ // name of the library. The impl library instead gets the `.bootstrap` suffix to so that
+ // they can be exceptionally used directly when APEXes are not available (e.g. during the
+ // very early stage in the boot process).
+ if len(library.Properties.Stubs.Versions) > 0 && !ctx.Host() && ctx.notInPlatform() &&
+ !ctx.inRamdisk() && !ctx.inVendorRamdisk() && !ctx.inRecovery() && !ctx.useVndk() && !ctx.static() {
+ if library.buildStubs() && library.isLatestStubVersion() {
+ moduleInfoJSON.SubName = ""
+ }
+ if !library.buildStubs() {
+ moduleInfoJSON.SubName = ".bootstrap"
+ }
+ }
+
+ library.baseLinker.moduleInfoJSON(ctx, moduleInfoJSON)
+}
+
func (library *libraryDecorator) linkStatic(ctx ModuleContext,
flags Flags, deps PathDeps, objs Objects) android.Path {
diff --git a/cc/linker.go b/cc/linker.go
index 357d1ce..85c128e 100644
--- a/cc/linker.go
+++ b/cc/linker.go
@@ -648,6 +648,9 @@
return specifiedDeps
}
+func (linker *baseLinker) moduleInfoJSON(ctx ModuleContext, moduleInfoJSON *android.ModuleInfoJSON) {
+}
+
// Injecting version symbols
// Some host modules want a version number, but we don't want to rebuild it every time. Optionally add a step
// after linking that injects a constant placeholder with the current version number.
diff --git a/cc/ndk_library.go b/cc/ndk_library.go
index 98fb334..183e818 100644
--- a/cc/ndk_library.go
+++ b/cc/ndk_library.go
@@ -486,6 +486,12 @@
}
}
+func (linker *stubDecorator) moduleInfoJSON(ctx ModuleContext, moduleInfoJSON *android.ModuleInfoJSON) {
+ linker.libraryDecorator.moduleInfoJSON(ctx, moduleInfoJSON)
+ // Overwrites the SubName computed by libraryDecorator
+ moduleInfoJSON.SubName = ndkLibrarySuffix + "." + linker.apiLevel.String()
+}
+
func (linker *stubDecorator) Name(name string) string {
return name + ndkLibrarySuffix
}
diff --git a/cc/object.go b/cc/object.go
index 0dba99f..6c0391f 100644
--- a/cc/object.go
+++ b/cc/object.go
@@ -238,3 +238,8 @@
func (object *objectLinker) isCrt() bool {
return Bool(object.Properties.Crt)
}
+
+func (object *objectLinker) moduleInfoJSON(ctx ModuleContext, moduleInfoJSON *android.ModuleInfoJSON) {
+ object.baseLinker.moduleInfoJSON(ctx, moduleInfoJSON)
+ moduleInfoJSON.Class = []string{"STATIC_LIBRARIES"}
+}
diff --git a/cc/test.go b/cc/test.go
index d02145b..347d7c9 100644
--- a/cc/test.go
+++ b/cc/test.go
@@ -321,6 +321,13 @@
return []interface{}{&test.InstallerProperties}
}
+func (test *testDecorator) moduleInfoJSON(ctx android.ModuleContext, moduleInfoJSON *android.ModuleInfoJSON) {
+ if android.PrefixInList(moduleInfoJSON.CompatibilitySuites, "mts-") &&
+ !android.InList("mts", moduleInfoJSON.CompatibilitySuites) {
+ moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, "mts")
+ }
+}
+
func NewTestInstaller() *baseInstaller {
return NewBaseInstaller("nativetest", "nativetest64", InstallInData)
}
@@ -355,6 +362,38 @@
return flags
}
+func (test *testBinary) moduleInfoJSON(ctx ModuleContext, moduleInfoJSON *android.ModuleInfoJSON) {
+ if ctx.Host() && Bool(test.Properties.Test_options.Unit_test) {
+ moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, "host-unit-tests")
+ }
+ moduleInfoJSON.TestOptionsTags = append(moduleInfoJSON.TestOptionsTags, test.Properties.Test_options.Tags...)
+ moduleInfoJSON.TestMainlineModules = append(moduleInfoJSON.TestMainlineModules, test.Properties.Test_mainline_modules...)
+ if test.testConfig != nil {
+ if _, ok := test.testConfig.(android.WritablePath); ok {
+ moduleInfoJSON.AutoTestConfig = []string{"true"}
+ }
+ moduleInfoJSON.TestConfig = append(moduleInfoJSON.TestConfig, test.testConfig.String())
+ }
+ moduleInfoJSON.TestConfig = append(moduleInfoJSON.TestConfig, test.extraTestConfigs.Strings()...)
+
+ if Bool(test.Properties.Test_per_src) {
+ moduleInfoJSON.SubName = "_" + String(test.binaryDecorator.Properties.Stem)
+ }
+
+ moduleInfoJSON.DataDependencies = append(moduleInfoJSON.DataDependencies, test.Properties.Data_bins...)
+
+ if len(test.InstallerProperties.Test_suites) > 0 {
+ moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, test.InstallerProperties.Test_suites...)
+ } else {
+ moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, "null-suite")
+ }
+
+ test.binaryDecorator.moduleInfoJSON(ctx, moduleInfoJSON)
+ test.testDecorator.moduleInfoJSON(ctx, moduleInfoJSON)
+ moduleInfoJSON.Class = []string{"NATIVE_TESTS"}
+
+}
+
func (test *testBinary) installerProps() []interface{} {
return append(test.baseInstaller.installerProps(), test.testDecorator.installerProps()...)
}
@@ -532,6 +571,15 @@
return flags
}
+func (test *testLibrary) moduleInfoJSON(ctx ModuleContext, moduleInfoJSON *android.ModuleInfoJSON) {
+ if len(test.InstallerProperties.Test_suites) > 0 {
+ moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, test.InstallerProperties.Test_suites...)
+ }
+
+ test.libraryDecorator.moduleInfoJSON(ctx, moduleInfoJSON)
+ test.testDecorator.moduleInfoJSON(ctx, moduleInfoJSON)
+}
+
func (test *testLibrary) installerProps() []interface{} {
return append(test.baseInstaller.installerProps(), test.testDecorator.installerProps()...)
}
@@ -626,6 +674,29 @@
benchmark.binaryDecorator.baseInstaller.install(ctx, file)
}
+func (benchmark *benchmarkDecorator) moduleInfoJSON(ctx ModuleContext, moduleInfoJSON *android.ModuleInfoJSON) {
+ benchmark.binaryDecorator.moduleInfoJSON(ctx, moduleInfoJSON)
+
+ moduleInfoJSON.Class = []string{"NATIVE_TESTS"}
+ if len(benchmark.Properties.Test_suites) > 0 {
+ moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, benchmark.Properties.Test_suites...)
+ } else {
+ moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, "null-suite")
+ }
+
+ if android.PrefixInList(moduleInfoJSON.CompatibilitySuites, "mts-") &&
+ !android.InList("mts", moduleInfoJSON.CompatibilitySuites) {
+ moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, "mts")
+ }
+
+ if benchmark.testConfig != nil {
+ if _, ok := benchmark.testConfig.(android.WritablePath); ok {
+ moduleInfoJSON.AutoTestConfig = []string{"true"}
+ }
+ moduleInfoJSON.TestConfig = []string{benchmark.testConfig.String()}
+ }
+}
+
func NewBenchmark(hod android.HostOrDeviceSupported) *Module {
module, binary := newBinary(hod)
module.multilib = android.MultilibBoth
diff --git a/cc/vndk_prebuilt.go b/cc/vndk_prebuilt.go
index 3f3a025..eb1790f 100644
--- a/cc/vndk_prebuilt.go
+++ b/cc/vndk_prebuilt.go
@@ -187,6 +187,11 @@
return nil
}
+func (p *vndkPrebuiltLibraryDecorator) moduleInfoJSON(ctx ModuleContext, moduleInfoJSON *android.ModuleInfoJSON) {
+ p.libraryDecorator.moduleInfoJSON(ctx, moduleInfoJSON)
+ moduleInfoJSON.SubName += p.androidMkSuffix
+}
+
func (p *vndkPrebuiltLibraryDecorator) MatchesWithDevice(config android.DeviceConfig) bool {
arches := config.Arches()
if len(arches) == 0 || arches[0].ArchType.String() != p.arch() {
diff --git a/cmd/merge_module_info_json/Android.bp b/cmd/merge_module_info_json/Android.bp
new file mode 100644
index 0000000..1ae6a47
--- /dev/null
+++ b/cmd/merge_module_info_json/Android.bp
@@ -0,0 +1,30 @@
+// Copyright 2021 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+blueprint_go_binary {
+ name: "merge_module_info_json",
+ srcs: [
+ "merge_module_info_json.go",
+ ],
+ deps: [
+ "soong-response",
+ ],
+ testSrcs: [
+ "merge_module_info_json_test.go",
+ ],
+}
diff --git a/cmd/merge_module_info_json/merge_module_info_json.go b/cmd/merge_module_info_json/merge_module_info_json.go
new file mode 100644
index 0000000..0143984
--- /dev/null
+++ b/cmd/merge_module_info_json/merge_module_info_json.go
@@ -0,0 +1,223 @@
+// Copyright 2021 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.
+
+// merge_module_info_json is a utility that merges module_info.json files generated by
+// Soong and Make.
+
+package main
+
+import (
+ "android/soong/response"
+ "cmp"
+ "encoding/json"
+ "flag"
+ "fmt"
+ "os"
+ "slices"
+)
+
+var (
+ out = flag.String("o", "", "output file")
+ listFile = flag.String("l", "", "input file list file")
+)
+
+func usage() {
+ fmt.Fprintf(os.Stderr, "usage: %s -o <output file> <input files>\n", os.Args[0])
+ flag.PrintDefaults()
+ fmt.Fprintln(os.Stderr, "merge_module_info_json reads input files that each contain an array of json objects")
+ fmt.Fprintln(os.Stderr, "and writes them out as a single json array to the output file.")
+
+ os.Exit(2)
+}
+
+func main() {
+ flag.Usage = usage
+ flag.Parse()
+
+ if *out == "" {
+ fmt.Fprintf(os.Stderr, "%s: error: -o is required\n", os.Args[0])
+ usage()
+ }
+
+ inputs := flag.Args()
+ if *listFile != "" {
+ listFileInputs, err := readListFile(*listFile)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "failed to read list file %s: %s", *listFile, err)
+ os.Exit(1)
+ }
+ inputs = append(inputs, listFileInputs...)
+ }
+
+ err := mergeJsonObjects(*out, inputs)
+
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "%s: error: %s\n", os.Args[0], err.Error())
+ os.Exit(1)
+ }
+}
+
+func readListFile(file string) ([]string, error) {
+ f, err := os.Open(*listFile)
+ if err != nil {
+ return nil, err
+ }
+ return response.ReadRspFile(f)
+}
+
+func mergeJsonObjects(output string, inputs []string) error {
+ combined := make(map[string]any)
+ for _, input := range inputs {
+ objects, err := decodeObjectFromJson(input)
+ if err != nil {
+ return err
+ }
+
+ for _, object := range objects {
+ for k, v := range object {
+ if old, exists := combined[k]; exists {
+ v = combine(old, v)
+ }
+ combined[k] = v
+ }
+ }
+ }
+
+ f, err := os.Create(output)
+ if err != nil {
+ return fmt.Errorf("failed to open output file: %w", err)
+ }
+ encoder := json.NewEncoder(f)
+ encoder.SetIndent("", " ")
+ err = encoder.Encode(combined)
+ if err != nil {
+ return fmt.Errorf("failed to encode to output file: %w", err)
+ }
+
+ return nil
+}
+
+func decodeObjectFromJson(input string) ([]map[string]any, error) {
+ f, err := os.Open(input)
+ if err != nil {
+ return nil, fmt.Errorf("failed to open input file: %w", err)
+ }
+
+ decoder := json.NewDecoder(f)
+ var object any
+ err = decoder.Decode(&object)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse input file %q: %w", input, err)
+ }
+
+ switch o := object.(type) {
+ case []any:
+ var ret []map[string]any
+ for _, arrayElement := range o {
+ if m, ok := arrayElement.(map[string]any); ok {
+ ret = append(ret, m)
+ } else {
+ return nil, fmt.Errorf("unknown JSON type in array %T", arrayElement)
+ }
+ }
+ return ret, nil
+
+ case map[string]any:
+ return []map[string]any{o}, nil
+ }
+
+ return nil, fmt.Errorf("unknown JSON type %T", object)
+}
+
+func combine(old, new any) any {
+ // fmt.Printf("%#v %#v\n", old, new)
+ switch oldTyped := old.(type) {
+ case map[string]any:
+ if newObject, ok := new.(map[string]any); ok {
+ return combineObjects(oldTyped, newObject)
+ } else {
+ panic(fmt.Errorf("expected map[string]any, got %#v", new))
+ }
+ case []any:
+ if newArray, ok := new.([]any); ok {
+ return combineArrays(oldTyped, newArray)
+ } else {
+ panic(fmt.Errorf("expected []any, got %#v", new))
+ }
+ case string:
+ if newString, ok := new.(string); ok {
+ if oldTyped != newString {
+ panic(fmt.Errorf("strings %q and %q don't match", oldTyped, newString))
+ }
+ return oldTyped
+ } else {
+ panic(fmt.Errorf("expected []any, got %#v", new))
+ }
+ default:
+ panic(fmt.Errorf("can't combine type %T", old))
+ }
+}
+
+func combineObjects(old, new map[string]any) map[string]any {
+ for k, newField := range new {
+ // HACK: Don't merge "test_config" field. This matches the behavior in base_rules.mk that overwrites
+ // instead of appending ALL_MODULES.$(my_register_name).TEST_CONFIG, keeping the
+ if k == "test_config" {
+ old[k] = newField
+ continue
+ }
+ if oldField, exists := old[k]; exists {
+ oldField = combine(oldField, newField)
+ old[k] = oldField
+ } else {
+ old[k] = newField
+ }
+ }
+
+ return old
+}
+
+func combineArrays(old, new []any) []any {
+ containsNonStrings := false
+ for _, oldElement := range old {
+ switch oldElement.(type) {
+ case string:
+ default:
+ containsNonStrings = true
+ }
+ }
+ for _, newElement := range new {
+ found := false
+ for _, oldElement := range old {
+ if oldElement == newElement {
+ found = true
+ break
+ }
+ }
+ if !found {
+ switch newElement.(type) {
+ case string:
+ default:
+ containsNonStrings = true
+ }
+ old = append(old, newElement)
+ }
+ }
+ if !containsNonStrings {
+ slices.SortFunc(old, func(a, b any) int {
+ return cmp.Compare(a.(string), b.(string))
+ })
+ }
+ return old
+}
diff --git a/cmd/merge_module_info_json/merge_module_info_json_test.go b/cmd/merge_module_info_json/merge_module_info_json_test.go
new file mode 100644
index 0000000..dbf1aa1
--- /dev/null
+++ b/cmd/merge_module_info_json/merge_module_info_json_test.go
@@ -0,0 +1,58 @@
+// Copyright 2021 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 main
+
+import (
+ "reflect"
+ "testing"
+)
+
+func Test_combine(t *testing.T) {
+ tests := []struct {
+ name string
+ old any
+ new any
+ want any
+ }{
+ {
+ name: "objects",
+ old: map[string]any{
+ "foo": "bar",
+ "baz": []any{"a"},
+ },
+ new: map[string]any{
+ "foo": "bar",
+ "baz": []any{"b"},
+ },
+ want: map[string]any{
+ "foo": "bar",
+ "baz": []any{"a", "b"},
+ },
+ },
+ {
+ name: "arrays",
+ old: []any{"foo", "bar"},
+ new: []any{"foo", "baz"},
+ want: []any{"bar", "baz", "foo"},
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := combine(tt.old, tt.new); !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("combine() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index 7742492..4727566 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -28,6 +28,7 @@
"android/soong/android/allowlists"
"android/soong/bp2build"
"android/soong/shared"
+
"github.com/google/blueprint"
"github.com/google/blueprint/bootstrap"
"github.com/google/blueprint/deptools"
@@ -196,7 +197,7 @@
ctx.Context.PrintJSONGraphAndActions(graphFile, actionsFile)
}
-func writeBuildGlobsNinjaFile(ctx *android.Context) []string {
+func writeBuildGlobsNinjaFile(ctx *android.Context) {
ctx.EventHandler.Begin("globs_ninja_file")
defer ctx.EventHandler.End("globs_ninja_file")
@@ -208,7 +209,6 @@
SrcDir: ctx.SrcDir(),
}, ctx.Config())
maybeQuit(err, "")
- return bootstrap.GlobFileListFiles(globDir)
}
func writeDepFile(outputFile string, eventHandler *metrics.EventHandler, ninjaDeps []string) {
@@ -238,8 +238,7 @@
maybeQuit(err, "")
ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
- globListFiles := writeBuildGlobsNinjaFile(ctx)
- ninjaDeps = append(ninjaDeps, globListFiles...)
+ writeBuildGlobsNinjaFile(ctx)
// Convert the Soong module graph into Bazel BUILD files.
switch ctx.Config().BuildMode {
diff --git a/docs/map_files.md b/docs/map_files.md
index 35e8cbb..37f91ec 100644
--- a/docs/map_files.md
+++ b/docs/map_files.md
@@ -134,6 +134,9 @@
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 509e701..0ef348a 100644
--- a/java/code_metadata_test.go
+++ b/java/code_metadata_test.go
@@ -25,12 +25,10 @@
}`
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 @@
)
}
- 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 @@
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 d80ed41..6a945ac 100644
--- a/java/config/config.go
+++ b/java/config/config.go
@@ -95,13 +95,11 @@
"-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 205d3b9..82cece3 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -552,8 +552,8 @@
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 @@
// 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 @@
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 @@
invocationPath := outputPath.ReplaceExtension(ctx, "invocation")
- apexNameToBcpInfoMap := getApexNameToBcpInfoMap(ctx)
+ apexNameToApexExportsInfoMap := getApexNameToApexExportsInfoMap(ctx)
cmd.Tool(globalSoong.Dex2oat).
Flag("--avoid-storing-invocation").
@@ -966,7 +978,7 @@
}
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 bf99757..c3caa08 100644
--- a/java/hiddenapi_modular.go
+++ b/java/hiddenapi_modular.go
@@ -19,6 +19,7 @@
"strings"
"android/soong/android"
+ "android/soong/dexpreopt"
"github.com/google/blueprint"
)
@@ -1250,9 +1251,27 @@
}
// 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 88d1ae8..4db426e 100644
--- a/java/platform_bootclasspath.go
+++ b/java/platform_bootclasspath.go
@@ -106,6 +106,9 @@
}
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) 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 @@
// 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 1c79171..f344648 100644
--- a/java/system_modules.go
+++ b/java/system_modules.go
@@ -55,7 +55,8 @@
`${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 f628b4b..4144dad 100644
--- a/java/test_spec_test.go
+++ b/java/test_spec_test.go
@@ -29,12 +29,10 @@
}`
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 @@
)
}
- 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 @@
}
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 a8b651a..bb48788 100644
--- a/phony/phony.go
+++ b/phony/phony.go
@@ -24,6 +24,7 @@
func init() {
android.RegisterModuleType("phony", PhonyFactory)
+ android.RegisterModuleType("phony_rule", PhonyRuleFactory)
}
type phony struct {
@@ -71,3 +72,40 @@
},
}
}
+
+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/fuzz.go b/rust/fuzz.go
index bacfa2d..1770d2e 100644
--- a/rust/fuzz.go
+++ b/rust/fuzz.go
@@ -142,12 +142,12 @@
fuzz.installedSharedDeps = append(fuzz.installedSharedDeps,
cc.SharedLibraryInstallLocation(
- install, ctx.Host(), installBase, ctx.Arch().ArchType.String()))
+ install, ctx.Host(), ctx.InstallInVendor(), installBase, ctx.Arch().ArchType.String()))
// Also add the dependency on the shared library symbols dir.
if !ctx.Host() {
fuzz.installedSharedDeps = append(fuzz.installedSharedDeps,
- cc.SharedLibrarySymbolsInstallLocation(install, installBase, ctx.Arch().ArchType.String()))
+ cc.SharedLibrarySymbolsInstallLocation(install, ctx.InstallInVendor(), installBase, ctx.Arch().ArchType.String()))
}
}
diff --git a/rust/protobuf.go b/rust/protobuf.go
index d021076..0b26b80 100644
--- a/rust/protobuf.go
+++ b/rust/protobuf.go
@@ -54,11 +54,6 @@
// 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 b375a64..cae071b 100644
--- a/rust/protobuf_test.go
+++ b/rust/protobuf_test.go
@@ -28,7 +28,6 @@
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 @@
protos: ["proto.proto"],
crate_name: "rust_proto",
source_stem: "proto",
- use_protobuf3: true,
rustlibs: ["librust_exported_proto", "libfoo"],
}
rust_protobuf {
@@ -85,7 +83,6 @@
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 97f6ab4..7baaadb 100644
--- a/scripts/Android.bp
+++ b/scripts/Android.bp
@@ -254,3 +254,8 @@
"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 0000000..9c48fdb
--- /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 3cf7c59..11ba430 100644
--- a/testing/code_metadata.go
+++ b/testing/code_metadata.go
@@ -128,7 +128,7 @@
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 d259612..4d885c6 100644
--- a/testing/test_spec.go
+++ b/testing/test_spec.go
@@ -117,7 +117,7 @@
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/Android.bp b/ui/build/Android.bp
index 21453ba..ee286f6 100644
--- a/ui/build/Android.bp
+++ b/ui/build/Android.bp
@@ -35,6 +35,7 @@
"blueprint",
"blueprint-bootstrap",
"blueprint-microfactory",
+ "soong-android",
"soong-finder",
"soong-remoteexec",
"soong-shared",
@@ -46,6 +47,7 @@
"soong-ui-tracer",
],
srcs: [
+ "androidmk_denylist.go",
"build.go",
"cleanbuild.go",
"config.go",
diff --git a/ui/build/androidmk_denylist.go b/ui/build/androidmk_denylist.go
new file mode 100644
index 0000000..b2266b2
--- /dev/null
+++ b/ui/build/androidmk_denylist.go
@@ -0,0 +1,44 @@
+// Copyright 2024 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 build
+
+import (
+ "strings"
+
+ "android/soong/android"
+)
+
+var androidmk_denylist []string = []string{
+ "chained_build_config/",
+ "cts/",
+ "dalvik/",
+ "developers/",
+ "kernel/",
+ "libcore/",
+ "libnativehelper/",
+ "pdk/",
+ "toolchain/",
+}
+
+func blockAndroidMks(androidMks []string) []string {
+ return android.FilterListPred(androidMks, func(s string) bool {
+ for _, d := range androidmk_denylist {
+ if strings.HasPrefix(s, d) {
+ return false
+ }
+ }
+ return true
+ })
+}
diff --git a/ui/build/finder.go b/ui/build/finder.go
index d0bcf40..a114079 100644
--- a/ui/build/finder.go
+++ b/ui/build/finder.go
@@ -128,6 +128,7 @@
// Stop searching a subdirectory recursively after finding an Android.mk.
androidMks := f.FindFirstNamedAt(".", "Android.mk")
+ androidMks = blockAndroidMks(androidMks)
err := dumpListToFile(ctx, config, androidMks, filepath.Join(dumpDir, "Android.mk.list"))
if err != nil {
ctx.Fatalf("Could not export module list: %v", err)
diff --git a/ui/build/soong.go b/ui/build/soong.go
index 0bf8862..90c3bfc 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -36,6 +36,7 @@
"github.com/google/blueprint"
"github.com/google/blueprint/bootstrap"
"github.com/google/blueprint/microfactory"
+ "github.com/google/blueprint/pathtools"
"google.golang.org/protobuf/proto"
)
@@ -181,7 +182,15 @@
return globPathName
}
-func (pb PrimaryBuilderFactory) primaryBuilderInvocation() bootstrap.PrimaryBuilderInvocation {
+func getGlobPathNameFromPrimaryBuilderFactory(config Config, pb PrimaryBuilderFactory) string {
+ if pb.name == soongBuildTag {
+ // Glob path for soong build would be separated per product target
+ return getGlobPathName(config)
+ }
+ return pb.name
+}
+
+func (pb PrimaryBuilderFactory) primaryBuilderInvocation(config Config) bootstrap.PrimaryBuilderInvocation {
commonArgs := make([]string, 0, 0)
if !pb.config.skipSoongTests {
@@ -215,11 +224,7 @@
var allArgs []string
allArgs = append(allArgs, pb.specificArgs...)
- globPathName := pb.name
- // Glob path for soong build would be separated per product target
- if pb.name == soongBuildTag {
- globPathName = getGlobPathName(pb.config)
- }
+ globPathName := getGlobPathNameFromPrimaryBuilderFactory(config, pb)
allArgs = append(allArgs,
"--globListDir", globPathName,
"--globFile", pb.config.NamedGlobFile(globPathName))
@@ -234,8 +239,11 @@
}
allArgs = append(allArgs, "Android.bp")
+ globfiles := bootstrap.GlobFileListFiles(bootstrap.GlobDirectory(config.SoongOutDir(), globPathName))
+
return bootstrap.PrimaryBuilderInvocation{
Inputs: []string{"Android.bp"},
+ Implicits: globfiles,
Outputs: []string{pb.output},
Args: allArgs,
Description: pb.description,
@@ -376,17 +384,10 @@
if debuggedInvocations[pbf.name] {
pbf.debugPort = delvePort
}
- pbi := pbf.primaryBuilderInvocation()
+ pbi := pbf.primaryBuilderInvocation(config)
invocations = append(invocations, pbi)
}
- // The glob .ninja files are subninja'd. However, they are generated during
- // the build itself so we write an empty file if the file does not exist yet
- // so that the subninja doesn't fail on clean builds
- for _, globFile := range bootstrapGlobFileList(config) {
- writeEmptyFile(ctx, globFile)
- }
-
blueprintArgs := bootstrap.Args{
ModuleListFile: filepath.Join(config.FileListDir(), "Android.bp.list"),
OutFile: shared.JoinPath(config.SoongOutDir(), "bootstrap.ninja"),
@@ -408,6 +409,28 @@
primaryBuilderInvocations: invocations,
}
+ // The glob ninja files are generated during the main build phase. However, the
+ // primary buildifer invocation depends on all of its glob files, even before
+ // it's been run. Generate a "empty" glob ninja file on the first run,
+ // so that the files can be there to satisfy the dependency.
+ for _, pb := range pbfs {
+ globPathName := getGlobPathNameFromPrimaryBuilderFactory(config, pb)
+ globNinjaFile := config.NamedGlobFile(globPathName)
+ if _, err := os.Stat(globNinjaFile); os.IsNotExist(err) {
+ err := bootstrap.WriteBuildGlobsNinjaFile(&bootstrap.GlobSingleton{
+ GlobLister: func() pathtools.MultipleGlobResults { return nil },
+ GlobFile: globNinjaFile,
+ GlobDir: bootstrap.GlobDirectory(config.SoongOutDir(), globPathName),
+ SrcDir: ".",
+ }, blueprintConfig)
+ if err != nil {
+ ctx.Fatal(err)
+ }
+ } else if err != nil {
+ ctx.Fatal(err)
+ }
+ }
+
// since `bootstrap.ninja` is regenerated unconditionally, we ignore the deps, i.e. little
// reason to write a `bootstrap.ninja.d` file
_, err := bootstrap.RunBlueprint(blueprintArgs, bootstrap.DoEverything, blueprintCtx, blueprintConfig)
diff --git a/ui/build/test_build.go b/ui/build/test_build.go
index c5dc4c5..3095139 100644
--- a/ui/build/test_build.go
+++ b/ui/build/test_build.go
@@ -63,6 +63,7 @@
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 @@
continue
}
if strings.HasPrefix(line, modulePathsDir) ||
+ strings.HasPrefix(line, rawFilesDir) ||
line == variablesFilePath ||
line == dexpreoptConfigFilePath ||
line == buildDatetimeFilePath ||