Merge "Add NeverFar() option for transition mutators" into main
diff --git a/android/Android.bp b/android/Android.bp
index cf707bd..a9a3564 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -31,6 +31,7 @@
srcs: [
"aconfig_providers.go",
"all_teams.go",
+ "android_info.go",
"androidmk.go",
"apex.go",
"apex_contributions.go",
@@ -121,6 +122,7 @@
"apex_test.go",
"arch_test.go",
"blueprint_e2e_test.go",
+ "build_prop_test.go",
"config_test.go",
"configured_jars_test.go",
"csuite_config_test.go",
diff --git a/android/android_info.go b/android/android_info.go
new file mode 100644
index 0000000..a8d3d4e
--- /dev/null
+++ b/android/android_info.go
@@ -0,0 +1,91 @@
+// Copyright 2024 The Android Open Source Project
+//
+// 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 (
+ "github.com/google/blueprint"
+ "github.com/google/blueprint/proptools"
+)
+
+var (
+ mergeAndRemoveComments = pctx.AndroidStaticRule("merge_and_remove_comments",
+ blueprint.RuleParams{
+ Command: "cat $in | grep -v '#' > $out",
+ },
+ )
+ androidInfoTxtToProp = pctx.AndroidStaticRule("android_info_txt_to_prop",
+ blueprint.RuleParams{
+ Command: "grep 'require version-' $in | sed -e 's/require version-/ro.build.expect./g' > $out",
+ },
+ )
+)
+
+type androidInfoProperties struct {
+ // Name of output file. Defaults to module name
+ Stem *string
+
+ // Paths of board-info.txt files.
+ Board_info_files []string `android:"path"`
+
+ // Name of bootloader board. If board_info_files is empty, `board={bootloader_board_name}` will
+ // be printed to output. Ignored if board_info_files is not empty.
+ Bootloader_board_name *string
+}
+
+type androidInfoModule struct {
+ ModuleBase
+
+ properties androidInfoProperties
+}
+
+func (p *androidInfoModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+ if len(p.properties.Board_info_files) > 0 && p.properties.Bootloader_board_name != nil {
+ ctx.ModuleErrorf("Either Board_info_files or Bootloader_board_name should be set. Please remove one of them\n")
+ return
+ }
+ androidInfoTxtName := proptools.StringDefault(p.properties.Stem, ctx.ModuleName()+".txt")
+ androidInfoTxt := PathForModuleOut(ctx, androidInfoTxtName)
+ androidInfoProp := androidInfoTxt.ReplaceExtension(ctx, "prop")
+
+ if boardInfoFiles := PathsForModuleSrc(ctx, p.properties.Board_info_files); len(boardInfoFiles) > 0 {
+ ctx.Build(pctx, BuildParams{
+ Rule: mergeAndRemoveComments,
+ Inputs: boardInfoFiles,
+ Output: androidInfoTxt,
+ })
+ } else if bootloaderBoardName := proptools.String(p.properties.Bootloader_board_name); bootloaderBoardName != "" {
+ WriteFileRule(ctx, androidInfoTxt, "board="+bootloaderBoardName)
+ } else {
+ WriteFileRule(ctx, androidInfoTxt, "")
+ }
+
+ // Create android_info.prop
+ ctx.Build(pctx, BuildParams{
+ Rule: androidInfoTxtToProp,
+ Input: androidInfoTxt,
+ Output: androidInfoProp,
+ })
+
+ ctx.SetOutputFiles(Paths{androidInfoProp}, "")
+}
+
+// android_info module generate a file named android-info.txt that contains various information
+// about the device we're building for. This file is typically packaged up with everything else.
+func AndroidInfoFactory() Module {
+ module := &androidInfoModule{}
+ module.AddProperties(&module.properties)
+ InitAndroidModule(module)
+ return module
+}
diff --git a/android/base_module_context.go b/android/base_module_context.go
index 223b534..02c0158 100644
--- a/android/base_module_context.go
+++ b/android/base_module_context.go
@@ -17,6 +17,7 @@
import (
"fmt"
"regexp"
+ "slices"
"strings"
"github.com/google/blueprint"
@@ -570,7 +571,7 @@
}
func (b *baseModuleContext) GetWalkPath() []Module {
- return b.walkPath
+ return slices.Clone(b.walkPath)
}
func (b *baseModuleContext) GetTagPath() []blueprint.DependencyTag {
diff --git a/android/build_prop.go b/android/build_prop.go
index 7c3c506..5547680 100644
--- a/android/build_prop.go
+++ b/android/build_prop.go
@@ -19,8 +19,12 @@
)
func init() {
- ctx := InitRegistrationContext
+ registerBuildPropComponents(InitRegistrationContext)
+}
+
+func registerBuildPropComponents(ctx RegistrationContext) {
ctx.RegisterModuleType("build_prop", BuildPropFactory)
+ ctx.RegisterModuleType("android_info", AndroidInfoFactory)
}
type buildPropProperties struct {
@@ -38,6 +42,10 @@
// Path to a JSON file containing product configs.
Product_config *string `android:"path"`
+ // Path to android-info.txt file containing board specific info.
+ // This is empty for build.prop of all partitions except vendor.
+ Android_info *string `android:"path"`
+
// Optional subdirectory under which this file is installed into
Relative_install_path *string
}
@@ -66,7 +74,10 @@
} else if partition == "odm" {
return ctx.Config().OdmPropFiles(ctx)
} else if partition == "vendor" {
- // TODO (b/375500423): Add android-info.txt to prop files
+ if p.properties.Android_info != nil {
+ androidInfo := PathForModuleSrc(ctx, proptools.String(p.properties.Android_info))
+ return append(ctx.Config().VendorPropFiles(ctx), androidInfo)
+ }
return ctx.Config().VendorPropFiles(ctx)
}
return nil
@@ -111,13 +122,12 @@
}
func (p *buildPropModule) GenerateAndroidBuildActions(ctx ModuleContext) {
- p.outputFilePath = PathForModuleOut(ctx, "build.prop").OutputPath
- if !ctx.Config().KatiEnabled() {
- WriteFileRule(ctx, p.outputFilePath, "# no build.prop if kati is disabled")
- ctx.SetOutputFiles(Paths{p.outputFilePath}, "")
- return
+ if !p.SocSpecific() && p.properties.Android_info != nil {
+ ctx.ModuleErrorf("Android_info cannot be set if build.prop is not installed in vendor partition")
}
+ p.outputFilePath = PathForModuleOut(ctx, "build.prop").OutputPath
+
partition := p.partition(ctx.DeviceConfig())
if !InList(partition, validPartitions) {
ctx.PropertyErrorf("partition", "unsupported partition %q: only %q are supported", partition, validPartitions)
diff --git a/android/build_prop_test.go b/android/build_prop_test.go
new file mode 100644
index 0000000..e75975a
--- /dev/null
+++ b/android/build_prop_test.go
@@ -0,0 +1,41 @@
+// 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 android
+
+import (
+ "testing"
+)
+
+func TestPropFileInputs(t *testing.T) {
+ bp := `
+build_prop {
+ name: "vendor-build.prop",
+ stem: "build.prop",
+ vendor: true,
+ android_info: ":board-info",
+ //product_config: ":product_config",
+}
+android_info {
+ name: "board-info",
+ stem: "android-info.txt",
+}
+`
+
+ res := GroupFixturePreparers(
+ FixtureRegisterWithContext(registerBuildPropComponents),
+ ).RunTestWithBp(t, bp)
+ buildPropCmd := res.ModuleForTests("vendor-build.prop", "").Rule("vendor-build.prop_.vendor-build.prop").RuleParams.Command
+ AssertStringDoesContain(t, "Could not find android-info in prop files of vendor build.prop", buildPropCmd, "--prop-files=out/soong/.intermediates/board-info/android-info.prop")
+}
diff --git a/android/config.go b/android/config.go
index 16d77db..d9db64e 100644
--- a/android/config.go
+++ b/android/config.go
@@ -867,7 +867,7 @@
}
func (c *config) TargetsJava21() bool {
- return c.IsEnvTrue("EXPERIMENTAL_TARGET_JAVA_VERSION_21")
+ return c.productVariables.GetBuildFlagBool("RELEASE_TARGET_JAVA_21")
}
// EnvDeps returns the environment variables this build depends on. The first
@@ -1522,6 +1522,13 @@
return "vendor"
}
+func (c *deviceConfig) VendorDlkmPath() string {
+ if c.config.productVariables.VendorDlkmPath != nil {
+ return *c.config.productVariables.VendorDlkmPath
+ }
+ return "vendor_dlkm"
+}
+
func (c *deviceConfig) BuildingVendorImage() bool {
return proptools.Bool(c.config.productVariables.BuildingVendorImage)
}
@@ -1553,6 +1560,13 @@
return proptools.Bool(c.config.productVariables.BuildingOdmImage)
}
+func (c *deviceConfig) OdmDlkmPath() string {
+ if c.config.productVariables.OdmDlkmPath != nil {
+ return *c.config.productVariables.OdmDlkmPath
+ }
+ return "odm_dlkm"
+}
+
func (c *deviceConfig) ProductPath() string {
if c.config.productVariables.ProductPath != nil {
return *c.config.productVariables.ProductPath
@@ -1571,6 +1585,20 @@
return "system_ext"
}
+func (c *deviceConfig) SystemDlkmPath() string {
+ if c.config.productVariables.SystemDlkmPath != nil {
+ return *c.config.productVariables.SystemDlkmPath
+ }
+ return "system_dlkm"
+}
+
+func (c *deviceConfig) OemPath() string {
+ if c.config.productVariables.OemPath != nil {
+ return *c.config.productVariables.OemPath
+ }
+ return "oem"
+}
+
func (c *deviceConfig) BtConfigIncludeDir() string {
return String(c.config.productVariables.BtConfigIncludeDir)
}
diff --git a/android/dirgroup.go b/android/dirgroup.go
index 20c4d13..62fbaa5 100644
--- a/android/dirgroup.go
+++ b/android/dirgroup.go
@@ -39,8 +39,7 @@
}
type DirInfo struct {
- // TODO(b/358302178): Use DirectoryPaths instead of Paths
- Dirs Paths
+ Dirs DirectoryPaths
}
var DirProvider = blueprint.NewProvider[DirInfo]()
diff --git a/android/filegroup_test.go b/android/filegroup_test.go
index 14e9368..670037d 100644
--- a/android/filegroup_test.go
+++ b/android/filegroup_test.go
@@ -1,55 +1,9 @@
package android
import (
- "path/filepath"
"testing"
)
-func TestFileGroupWithPathProp(t *testing.T) {
- // TODO(b/247782695), TODO(b/242847534) Fix mixed builds for filegroups
- t.Skip("Re-enable once filegroups are corrected for mixed builds")
- outBaseDir := "outputbase"
- pathPrefix := outBaseDir + "/execroot/__main__"
- expectedOutputfile := filepath.Join(pathPrefix, "a/b/c/d/test.aidl")
-
- testCases := []struct {
- bp string
- rel string
- }{
- {
- bp: `
- filegroup {
- name: "baz",
- srcs: ["a/b/c/d/test.aidl"],
- path: "a/b",
- bazel_module: { label: "//:baz" },
- }
-`,
- rel: "c/d/test.aidl",
- },
- {
- bp: `
- filegroup {
- name: "baz",
- srcs: ["a/b/c/d/test.aidl"],
- bazel_module: { label: "//:baz" },
- }
-`,
- rel: "a/b/c/d/test.aidl",
- },
- }
-
- for _, testCase := range testCases {
- result := GroupFixturePreparers(
- PrepareForTestWithFilegroup,
- ).RunTestWithBp(t, testCase.bp)
-
- fg := result.Module("baz", "").(*fileGroup)
- AssertStringEquals(t, "src relativeRoot", testCase.rel, fg.srcs[0].Rel())
- AssertStringEquals(t, "src full path", expectedOutputfile, fg.srcs[0].String())
- }
-}
-
func TestFilegroupDefaults(t *testing.T) {
bp := FixtureAddTextFile("p/Android.bp", `
filegroup_defaults {
diff --git a/android/init.go b/android/init.go
index 1ace344..d3a13d0 100644
--- a/android/init.go
+++ b/android/init.go
@@ -20,6 +20,7 @@
gob.Register(extraFilesZip{})
gob.Register(InstallPath{})
gob.Register(ModuleGenPath{})
+ gob.Register(ModuleObjPath{})
gob.Register(ModuleOutPath{})
gob.Register(OutputPath{})
gob.Register(PhonyPath{})
diff --git a/android/module.go b/android/module.go
index a918d6e..6217833 100644
--- a/android/module.go
+++ b/android/module.go
@@ -81,6 +81,9 @@
InstallInProduct() bool
InstallInVendor() bool
InstallInSystemExt() bool
+ InstallInSystemDlkm() bool
+ InstallInVendorDlkm() bool
+ InstallInOdmDlkm() bool
InstallForceOS() (*OsType, *ArchType)
PartitionTag(DeviceConfig) string
HideFromMake()
@@ -386,6 +389,15 @@
// Whether this module is installed to debug ramdisk
Debug_ramdisk *bool
+ // Install to partition system_dlkm when set to true.
+ System_dlkm_specific *bool
+
+ // Install to partition vendor_dlkm when set to true.
+ Vendor_dlkm_specific *bool
+
+ // Install to partition odm_dlkm when set to true.
+ Odm_dlkm_specific *bool
+
// Whether this module is built for non-native architectures (also known as native bridge binary)
Native_bridge_supported *bool `android:"arch_variant"`
@@ -1535,6 +1547,18 @@
return false
}
+func (m *ModuleBase) InstallInSystemDlkm() bool {
+ return Bool(m.commonProperties.System_dlkm_specific)
+}
+
+func (m *ModuleBase) InstallInVendorDlkm() bool {
+ return Bool(m.commonProperties.Vendor_dlkm_specific)
+}
+
+func (m *ModuleBase) InstallInOdmDlkm() bool {
+ return Bool(m.commonProperties.Odm_dlkm_specific)
+}
+
func (m *ModuleBase) InstallForceOS() (*OsType, *ArchType) {
return nil, nil
}
@@ -1822,6 +1846,8 @@
Enabled bool
// Whether the module has been replaced by a prebuilt
ReplacedByPrebuilt bool
+ // The Target of artifacts that this module variant is responsible for creating.
+ CompileTarget Target
}
var CommonPropertiesProviderKey = blueprint.NewProvider[CommonPropertiesProviderData]()
@@ -2086,6 +2112,7 @@
commonData := CommonPropertiesProviderData{
ReplacedByPrebuilt: m.commonProperties.ReplacedByPrebuilt,
+ CompileTarget: m.commonProperties.CompileTarget,
}
if m.commonProperties.ForcedDisabled {
commonData.Enabled = false
diff --git a/android/module_context.go b/android/module_context.go
index 9fa3fa3..41cb0cc 100644
--- a/android/module_context.go
+++ b/android/module_context.go
@@ -19,6 +19,7 @@
"github.com/google/blueprint/depset"
"path"
"path/filepath"
+ "slices"
"strings"
"github.com/google/blueprint"
@@ -195,6 +196,9 @@
InstallInOdm() bool
InstallInProduct() bool
InstallInVendor() bool
+ InstallInSystemDlkm() bool
+ InstallInVendorDlkm() bool
+ InstallInOdmDlkm() bool
InstallForceOS() (*OsType, *ArchType)
RequiredModuleNames(ctx ConfigurableEvaluatorContext) []string
@@ -492,6 +496,18 @@
return m.module.InstallInVendor()
}
+func (m *moduleContext) InstallInSystemDlkm() bool {
+ return m.module.InstallInSystemDlkm()
+}
+
+func (m *moduleContext) InstallInVendorDlkm() bool {
+ return m.module.InstallInVendorDlkm()
+}
+
+func (m *moduleContext) InstallInOdmDlkm() bool {
+ return m.module.InstallInOdmDlkm()
+}
+
func (m *moduleContext) skipInstall() bool {
if m.module.base().commonProperties.SkipInstall {
return true
@@ -562,9 +578,22 @@
m.aconfigFilePaths = paths
}
+func (m *moduleContext) getOwnerAndOverrides() (string, []string) {
+ owner := m.ModuleName()
+ overrides := slices.Clone(m.Module().base().commonProperties.Overrides)
+ if b, ok := m.Module().(OverridableModule); ok {
+ if b.GetOverriddenBy() != "" {
+ // overriding variant of base module
+ overrides = append(overrides, m.ModuleName()) // com.android.foo
+ owner = m.Module().Name() // com.company.android.foo
+ }
+ }
+ return owner, overrides
+}
+
func (m *moduleContext) packageFile(fullInstallPath InstallPath, srcPath Path, executable bool) PackagingSpec {
licenseFiles := m.Module().EffectiveLicenseFiles()
- overrides := CopyOf(m.Module().base().commonProperties.Overrides)
+ owner, overrides := m.getOwnerAndOverrides()
spec := PackagingSpec{
relPathInPackage: Rel(m, fullInstallPath.PartitionDir(), fullInstallPath.String()),
srcPath: srcPath,
@@ -576,7 +605,7 @@
aconfigPaths: m.getAconfigPaths(),
archType: m.target.Arch.ArchType,
overrides: &overrides,
- owner: m.ModuleName(),
+ owner: owner,
}
m.packagingSpecs = append(m.packagingSpecs, spec)
return spec
@@ -695,7 +724,7 @@
m.installFiles = append(m.installFiles, fullInstallPath)
}
- overrides := CopyOf(m.Module().base().commonProperties.Overrides)
+ owner, overrides := m.getOwnerAndOverrides()
m.packagingSpecs = append(m.packagingSpecs, PackagingSpec{
relPathInPackage: Rel(m, fullInstallPath.PartitionDir(), fullInstallPath.String()),
srcPath: nil,
@@ -706,7 +735,7 @@
aconfigPaths: m.getAconfigPaths(),
archType: m.target.Arch.ArchType,
overrides: &overrides,
- owner: m.ModuleName(),
+ owner: owner,
})
return fullInstallPath
@@ -742,7 +771,7 @@
m.installFiles = append(m.installFiles, fullInstallPath)
}
- overrides := CopyOf(m.Module().base().commonProperties.Overrides)
+ owner, overrides := m.getOwnerAndOverrides()
m.packagingSpecs = append(m.packagingSpecs, PackagingSpec{
relPathInPackage: Rel(m, fullInstallPath.PartitionDir(), fullInstallPath.String()),
srcPath: nil,
@@ -753,7 +782,7 @@
aconfigPaths: m.getAconfigPaths(),
archType: m.target.Arch.ArchType,
overrides: &overrides,
- owner: m.ModuleName(),
+ owner: owner,
})
return fullInstallPath
diff --git a/android/module_proxy.go b/android/module_proxy.go
index a60a5a8..1f96799 100644
--- a/android/module_proxy.go
+++ b/android/module_proxy.go
@@ -106,6 +106,18 @@
panic("method is not implemented on ModuleProxy")
}
+func (m ModuleProxy) InstallInSystemDlkm() bool {
+ panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) InstallInVendorDlkm() bool {
+ panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) InstallInOdmDlkm() bool {
+ panic("method is not implemented on ModuleProxy")
+}
+
func (m ModuleProxy) InstallForceOS() (*OsType, *ArchType) {
panic("method is not implemented on ModuleProxy")
}
diff --git a/android/neverallow.go b/android/neverallow.go
index 7fb22bf..6176a99 100644
--- a/android/neverallow.go
+++ b/android/neverallow.go
@@ -334,6 +334,10 @@
"prebuilt_res",
"prebuilt_wlc_upt",
"prebuilt_odm",
+ "prebuilt_vendor_dlkm",
+ "prebuilt_bt_firmware",
+ "prebuilt_tvservice",
+ "prebuilt_optee",
).
DefinedInBpFile().
Because("module type not allowed to be defined in bp file")
diff --git a/android/paths.go b/android/paths.go
index bf2c3a0..9cb872d 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -117,6 +117,9 @@
InstallInOdm() bool
InstallInProduct() bool
InstallInVendor() bool
+ InstallInSystemDlkm() bool
+ InstallInVendorDlkm() bool
+ InstallInOdmDlkm() bool
InstallForceOS() (*OsType, *ArchType)
}
@@ -170,6 +173,18 @@
return ctx.Module().InstallInVendor()
}
+func (ctx *baseModuleContextToModuleInstallPathContext) InstallInSystemDlkm() bool {
+ return ctx.Module().InstallInSystemDlkm()
+}
+
+func (ctx *baseModuleContextToModuleInstallPathContext) InstallInVendorDlkm() bool {
+ return ctx.Module().InstallInVendorDlkm()
+}
+
+func (ctx *baseModuleContextToModuleInstallPathContext) InstallInOdmDlkm() bool {
+ return ctx.Module().InstallInOdmDlkm()
+}
+
func (ctx *baseModuleContextToModuleInstallPathContext) InstallForceOS() (*OsType, *ArchType) {
return ctx.Module().InstallForceOS()
}
@@ -551,15 +566,35 @@
return ret
}
+type directoryPath struct {
+ basePath
+}
+
+func (d *directoryPath) String() string {
+ return d.basePath.String()
+}
+
+func (d *directoryPath) base() basePath {
+ return d.basePath
+}
+
+// DirectoryPath represents a source path for directories. Incompatible with Path by design.
+type DirectoryPath interface {
+ String() string
+ base() basePath
+}
+
+var _ DirectoryPath = (*directoryPath)(nil)
+
+type DirectoryPaths []DirectoryPath
+
// DirectoryPathsForModuleSrcExcludes returns a Paths{} containing the resolved references in
// directory paths. Elements of paths are resolved as:
// - filepath, relative to local module directory, resolves as a filepath relative to the local
// source directory
// - other modules using the ":name" syntax. These modules must implement DirProvider.
-//
-// TODO(b/358302178): Implement DirectoryPath and change the return type.
-func DirectoryPathsForModuleSrc(ctx ModuleMissingDepsPathContext, paths []string) Paths {
- var ret Paths
+func DirectoryPathsForModuleSrc(ctx ModuleMissingDepsPathContext, paths []string) DirectoryPaths {
+ var ret DirectoryPaths
for _, path := range paths {
if m, t := SrcIsModuleWithTag(path); m != "" {
@@ -588,12 +623,12 @@
} else if !isDir {
ReportPathErrorf(ctx, "module directory path %q is not a directory", p)
} else {
- ret = append(ret, p)
+ ret = append(ret, &directoryPath{basePath{path: p.path, rel: p.rel}})
}
}
}
- seen := make(map[Path]bool, len(ret))
+ seen := make(map[DirectoryPath]bool, len(ret))
for _, path := range ret {
if seen[path] {
ReportPathErrorf(ctx, "duplicated path %q", path)
@@ -2057,6 +2092,10 @@
return base.Join(ctx, paths...)
}
+func PathForSuiteInstall(ctx PathContext, suite string, pathComponents ...string) InstallPath {
+ return pathForPartitionInstallDir(ctx, "test_suites", "test_suites", false).Join(ctx, suite).Join(ctx, pathComponents...)
+}
+
func InstallPathToOnDevicePath(ctx PathContext, path InstallPath) string {
rel := Rel(ctx, strings.TrimSuffix(path.PartitionDir(), path.partition), path.String())
return "/" + rel
@@ -2111,6 +2150,12 @@
partition = ctx.DeviceConfig().SystemExtPath()
} else if ctx.InstallInRoot() {
partition = "root"
+ } else if ctx.InstallInSystemDlkm() {
+ partition = ctx.DeviceConfig().SystemDlkmPath()
+ } else if ctx.InstallInVendorDlkm() {
+ partition = ctx.DeviceConfig().VendorDlkmPath()
+ } else if ctx.InstallInOdmDlkm() {
+ partition = ctx.DeviceConfig().OdmDlkmPath()
} else {
partition = "system"
}
@@ -2314,6 +2359,9 @@
inOdm bool
inProduct bool
inVendor bool
+ inSystemDlkm bool
+ inVendorDlkm bool
+ inOdmDlkm bool
forceOS *OsType
forceArch *ArchType
}
@@ -2368,6 +2416,18 @@
return m.inVendor
}
+func (m testModuleInstallPathContext) InstallInSystemDlkm() bool {
+ return m.inSystemDlkm
+}
+
+func (m testModuleInstallPathContext) InstallInVendorDlkm() bool {
+ return m.inVendorDlkm
+}
+
+func (m testModuleInstallPathContext) InstallInOdmDlkm() bool {
+ return m.inOdmDlkm
+}
+
func (m testModuleInstallPathContext) InstallForceOS() (*OsType, *ArchType) {
return m.forceOS, m.forceArch
}
diff --git a/android/paths_test.go b/android/paths_test.go
index 941f0ca..5e618f9 100644
--- a/android/paths_test.go
+++ b/android/paths_test.go
@@ -1592,6 +1592,12 @@
})
}
+func TestDirectoryPathIsIncompatibleWithPath(t *testing.T) {
+ d := (DirectoryPath)(&directoryPath{})
+ _, ok := d.(Path)
+ AssertBoolEquals(t, "directoryPath shouldn't implement Path", ok, false)
+}
+
func ExampleOutputPath_ReplaceExtension() {
ctx := &configErrorWrapper{
config: TestConfig("out", nil, "", nil),
diff --git a/android/rule_builder.go b/android/rule_builder.go
index 56de9cd..403c184 100644
--- a/android/rule_builder.go
+++ b/android/rule_builder.go
@@ -575,25 +575,28 @@
nsjailCmd.WriteString(r.outDir.String())
nsjailCmd.WriteString(":nsjail_build_sandbox/out")
- for _, input := range inputs {
+ addBindMount := func(src, dst string) {
nsjailCmd.WriteString(" -R $PWD/")
- nsjailCmd.WriteString(input.String())
+ nsjailCmd.WriteString(src)
nsjailCmd.WriteString(":nsjail_build_sandbox/")
- nsjailCmd.WriteString(r.nsjailPathForInputRel(input))
+ nsjailCmd.WriteString(dst)
+ }
+
+ for _, input := range inputs {
+ addBindMount(input.String(), r.nsjailPathForInputRel(input))
}
for _, tool := range tools {
- nsjailCmd.WriteString(" -R $PWD/")
- nsjailCmd.WriteString(tool.String())
- nsjailCmd.WriteString(":nsjail_build_sandbox/")
- nsjailCmd.WriteString(nsjailPathForToolRel(r.ctx, tool))
+ addBindMount(tool.String(), nsjailPathForToolRel(r.ctx, tool))
}
inputs = append(inputs, tools...)
for _, c := range r.commands {
+ for _, directory := range c.implicitDirectories {
+ addBindMount(directory.String(), directory.String())
+ // TODO(b/375551969): Add implicitDirectories to BuildParams, rather than relying on implicits
+ inputs = append(inputs, SourcePath{basePath: directory.base()})
+ }
for _, tool := range c.packagedTools {
- nsjailCmd.WriteString(" -R $PWD/")
- nsjailCmd.WriteString(tool.srcPath.String())
- nsjailCmd.WriteString(":nsjail_build_sandbox/")
- nsjailCmd.WriteString(nsjailPathForPackagedToolRel(tool))
+ addBindMount(tool.srcPath.String(), nsjailPathForPackagedToolRel(tool))
inputs = append(inputs, tool.srcPath)
}
}
@@ -917,16 +920,17 @@
type RuleBuilderCommand struct {
rule *RuleBuilder
- buf strings.Builder
- inputs Paths
- implicits Paths
- orderOnlys Paths
- validations Paths
- outputs WritablePaths
- depFiles WritablePaths
- tools Paths
- packagedTools []PackagingSpec
- rspFiles []rspFileAndPaths
+ buf strings.Builder
+ inputs Paths
+ implicits Paths
+ orderOnlys Paths
+ validations Paths
+ outputs WritablePaths
+ depFiles WritablePaths
+ tools Paths
+ packagedTools []PackagingSpec
+ rspFiles []rspFileAndPaths
+ implicitDirectories DirectoryPaths
}
type rspFileAndPaths struct {
@@ -951,6 +955,10 @@
c.implicits = append(c.implicits, path)
}
+func (c *RuleBuilderCommand) addImplicitDirectory(path DirectoryPath) {
+ c.implicitDirectories = append(c.implicitDirectories, path)
+}
+
func (c *RuleBuilderCommand) addOrderOnly(path Path) {
checkPathNotNil(path)
c.orderOnlys = append(c.orderOnlys, path)
@@ -1313,6 +1321,16 @@
return c
}
+// ImplicitDirectory adds the specified input directory to the dependencies without modifying the
+// command line. Added directories will be bind-mounted for the nsjail.
+func (c *RuleBuilderCommand) ImplicitDirectory(path DirectoryPath) *RuleBuilderCommand {
+ if !c.rule.nsjail {
+ panic("ImplicitDirectory() must be called after Nsjail()")
+ }
+ c.addImplicitDirectory(path)
+ return c
+}
+
// GetImplicits returns the command's implicit inputs.
func (c *RuleBuilderCommand) GetImplicits() Paths {
return c.implicits
diff --git a/android/singleton.go b/android/singleton.go
index 913bf6a..6438182 100644
--- a/android/singleton.go
+++ b/android/singleton.go
@@ -64,6 +64,7 @@
VisitAllModulesBlueprint(visit func(blueprint.Module))
VisitAllModules(visit func(Module))
+ VisitAllModuleProxies(visit func(proxy ModuleProxy))
VisitAllModulesIf(pred func(Module) bool, visit func(Module))
VisitDirectDeps(module Module, visit func(Module))
@@ -77,6 +78,8 @@
VisitAllModuleVariants(module Module, visit func(Module))
+ VisitAllModuleVariantProxies(module Module, visit func(proxy ModuleProxy))
+
PrimaryModule(module Module) Module
FinalModule(module Module) Module
@@ -193,7 +196,7 @@
}
// visitAdaptor wraps a visit function that takes an android.Module parameter into
-// a function that takes an blueprint.Module parameter and only calls the visit function if the
+// a function that takes a blueprint.Module parameter and only calls the visit function if the
// blueprint.Module is an android.Module.
func visitAdaptor(visit func(Module)) func(blueprint.Module) {
return func(module blueprint.Module) {
@@ -203,6 +206,16 @@
}
}
+// visitProxyAdaptor wraps a visit function that takes an android.ModuleProxy parameter into
+// a function that takes a blueprint.ModuleProxy parameter.
+func visitProxyAdaptor(visit func(proxy ModuleProxy)) func(proxy blueprint.ModuleProxy) {
+ return func(module blueprint.ModuleProxy) {
+ visit(ModuleProxy{
+ module: module,
+ })
+ }
+}
+
// predAdaptor wraps a pred function that takes an android.Module parameter
// into a function that takes an blueprint.Module parameter and only calls the visit function if the
// blueprint.Module is an android.Module, otherwise returns false.
@@ -224,6 +237,10 @@
s.SingletonContext.VisitAllModules(visitAdaptor(visit))
}
+func (s *singletonContextAdaptor) VisitAllModuleProxies(visit func(proxy ModuleProxy)) {
+ s.SingletonContext.VisitAllModuleProxies(visitProxyAdaptor(visit))
+}
+
func (s *singletonContextAdaptor) VisitAllModulesIf(pred func(Module) bool, visit func(Module)) {
s.SingletonContext.VisitAllModulesIf(predAdaptor(pred), visitAdaptor(visit))
}
@@ -248,6 +265,10 @@
s.SingletonContext.VisitAllModuleVariants(module, visitAdaptor(visit))
}
+func (s *singletonContextAdaptor) VisitAllModuleVariantProxies(module Module, visit func(proxy ModuleProxy)) {
+ s.SingletonContext.VisitAllModuleVariantProxies(module, visitProxyAdaptor(visit))
+}
+
func (s *singletonContextAdaptor) PrimaryModule(module Module) Module {
return s.SingletonContext.PrimaryModule(module).(Module)
}
diff --git a/android/util.go b/android/util.go
index 2d269b7..3fc4608 100644
--- a/android/util.go
+++ b/android/util.go
@@ -660,3 +660,13 @@
v, loaded := m.Map.LoadOrStore(key, value)
return v.(V), loaded
}
+
+// AppendIfNotZero append the given value to the slice if it is not the zero value
+// for its type.
+func AppendIfNotZero[T comparable](slice []T, value T) []T {
+ var zeroValue T // Get the zero value of the type T
+ if value != zeroValue {
+ return append(slice, value)
+ }
+ return slice
+}
diff --git a/android/variable.go b/android/variable.go
index a4ee886..142fab9 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -339,12 +339,16 @@
HWASanExcludePaths []string `json:",omitempty"`
VendorPath *string `json:",omitempty"`
+ VendorDlkmPath *string `json:",omitempty"`
BuildingVendorImage *bool `json:",omitempty"`
OdmPath *string `json:",omitempty"`
BuildingOdmImage *bool `json:",omitempty"`
+ OdmDlkmPath *string `json:",omitempty"`
ProductPath *string `json:",omitempty"`
BuildingProductImage *bool `json:",omitempty"`
SystemExtPath *string `json:",omitempty"`
+ SystemDlkmPath *string `json:",omitempty"`
+ OemPath *string `json:",omitempty"`
ClangTidy *bool `json:",omitempty"`
TidyChecks *string `json:",omitempty"`
@@ -604,7 +608,13 @@
VendorLinkerConfigSrcs []string `json:",omitempty"`
ProductLinkerConfigSrcs []string `json:",omitempty"`
+ BoardInfoFiles []string `json:",omitempty"`
+ BootLoaderBoardName string `json:",omitempty"`
+
ProductCopyFiles map[string]string `json:",omitempty"`
+
+ BuildingSystemDlkmImage bool `json:",omitempty"`
+ SystemKernelModules []string `json:",omitempty"`
}
func boolPtr(v bool) *bool {
diff --git a/apex/Android.bp b/apex/Android.bp
index 0e2f564..870ca7e 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -7,6 +7,7 @@
pkgPath: "android/soong/apex",
deps: [
"blueprint",
+ "blueprint-bpmodify",
"soong",
"soong-aconfig",
"soong-aconfig-codegen",
diff --git a/apex/apex.go b/apex/apex.go
index 80af9c5..587f63f 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -20,10 +20,12 @@
"fmt"
"path/filepath"
"regexp"
+ "slices"
"sort"
"strings"
"github.com/google/blueprint"
+ "github.com/google/blueprint/depset"
"github.com/google/blueprint/proptools"
"android/soong/android"
@@ -464,6 +466,12 @@
// GenerateAndroidBuildActions.
filesInfo []apexFile
+ // List of files that were excluded by the unwanted_transitive_deps property.
+ unwantedTransitiveFilesInfo []apexFile
+
+ // List of files that were excluded due to conflicts with other variants of the same module.
+ duplicateTransitiveFilesInfo []apexFile
+
// List of other module names that should be installed when this APEX gets installed (LOCAL_REQUIRED_MODULES).
makeModulesToInstall []string
@@ -1877,6 +1885,14 @@
// visitor skips these from this list of module names
unwantedTransitiveDeps []string
+
+ // unwantedTransitiveFilesInfo contains files that would have been in the apex
+ // except that they were listed in unwantedTransitiveDeps.
+ unwantedTransitiveFilesInfo []apexFile
+
+ // duplicateTransitiveFilesInfo contains files that would ahve been in the apex
+ // except that another variant of the same module was already in the apex.
+ duplicateTransitiveFilesInfo []apexFile
}
func (vctx *visitorContext) normalizeFileInfo(mctx android.ModuleContext) {
@@ -1887,6 +1903,7 @@
// Needs additional verification for the resulting APEX to ensure that skipped artifacts don't make problems.
// For example, DT_NEEDED modules should be found within the APEX unless they are marked in `requiredNativeLibs`.
if f.transitiveDep && f.module != nil && android.InList(mctx.OtherModuleName(f.module), vctx.unwantedTransitiveDeps) {
+ vctx.unwantedTransitiveFilesInfo = append(vctx.unwantedTransitiveFilesInfo, f)
continue
}
dest := filepath.Join(f.installDir, f.builtFile.Base())
@@ -1897,6 +1914,8 @@
mctx.ModuleErrorf("apex file %v is provided by two different files %v and %v",
dest, e.builtFile, f.builtFile)
return
+ } else {
+ vctx.duplicateTransitiveFilesInfo = append(vctx.duplicateTransitiveFilesInfo, f)
}
// If a module is directly included and also transitively depended on
// consider it as directly included.
@@ -1911,6 +1930,7 @@
for _, v := range encountered {
vctx.filesInfo = append(vctx.filesInfo, v)
}
+
sort.Slice(vctx.filesInfo, func(i, j int) bool {
// Sort by destination path so as to ensure consistent ordering even if the source of the files
// changes.
@@ -2341,6 +2361,8 @@
// 3) some fields in apexBundle struct are configured
a.installDir = android.PathForModuleInstall(ctx, "apex")
a.filesInfo = vctx.filesInfo
+ a.unwantedTransitiveFilesInfo = vctx.unwantedTransitiveFilesInfo
+ a.duplicateTransitiveFilesInfo = vctx.duplicateTransitiveFilesInfo
a.setPayloadFsType(ctx)
a.setSystemLibLink(ctx)
@@ -2367,6 +2389,8 @@
a.setOutputFiles(ctx)
a.enforcePartitionTagOnApexSystemServerJar(ctx)
+
+ a.verifyNativeImplementationLibs(ctx)
}
// Set prebuiltInfoProvider. This will be used by `apex_prebuiltinfo_singleton` to print out a metadata file
@@ -2920,3 +2944,105 @@
func (a *apexBundle) IsTestApex() bool {
return a.testApex
}
+
+// verifyNativeImplementationLibs compares the list of transitive implementation libraries used to link native
+// libraries in the apex against the list of implementation libraries in the apex, ensuring that none of the
+// libraries in the apex have references to private APIs from outside the apex.
+func (a *apexBundle) verifyNativeImplementationLibs(ctx android.ModuleContext) {
+ var directImplementationLibs android.Paths
+ var transitiveImplementationLibs []depset.DepSet[android.Path]
+
+ if a.properties.IsCoverageVariant {
+ return
+ }
+
+ if a.testApex {
+ return
+ }
+
+ if a.UsePlatformApis() {
+ return
+ }
+
+ checkApexTag := func(tag blueprint.DependencyTag) bool {
+ switch tag {
+ case sharedLibTag, jniLibTag, executableTag, androidAppTag:
+ return true
+ default:
+ return false
+ }
+ }
+
+ checkTransitiveTag := func(tag blueprint.DependencyTag) bool {
+ switch {
+ case cc.IsSharedDepTag(tag), java.IsJniDepTag(tag), rust.IsRlibDepTag(tag), rust.IsDylibDepTag(tag), checkApexTag(tag):
+ return true
+ default:
+ return false
+ }
+ }
+
+ var appEmbeddedJNILibs android.Paths
+ ctx.VisitDirectDeps(func(dep android.Module) {
+ tag := ctx.OtherModuleDependencyTag(dep)
+ if !checkApexTag(tag) {
+ return
+ }
+ if tag == sharedLibTag || tag == jniLibTag {
+ outputFile := android.OutputFileForModule(ctx, dep, "")
+ directImplementationLibs = append(directImplementationLibs, outputFile)
+ }
+ if info, ok := android.OtherModuleProvider(ctx, dep, cc.ImplementationDepInfoProvider); ok {
+ transitiveImplementationLibs = append(transitiveImplementationLibs, info.ImplementationDeps)
+ }
+ if info, ok := android.OtherModuleProvider(ctx, dep, java.AppInfoProvider); ok {
+ appEmbeddedJNILibs = append(appEmbeddedJNILibs, info.EmbeddedJNILibs...)
+ }
+ })
+
+ depSet := depset.New(depset.PREORDER, directImplementationLibs, transitiveImplementationLibs)
+ allImplementationLibs := depSet.ToList()
+
+ allFileInfos := slices.Concat(a.filesInfo, a.unwantedTransitiveFilesInfo, a.duplicateTransitiveFilesInfo)
+
+ for _, lib := range allImplementationLibs {
+ inApex := slices.ContainsFunc(allFileInfos, func(fi apexFile) bool {
+ return fi.builtFile == lib
+ })
+ inApkInApex := slices.Contains(appEmbeddedJNILibs, lib)
+
+ if !inApex && !inApkInApex {
+ ctx.ModuleErrorf("library in apex transitively linked against implementation library %q not in apex", lib)
+ var depPath []android.Module
+ ctx.WalkDeps(func(child, parent android.Module) bool {
+ if depPath != nil {
+ return false
+ }
+
+ tag := ctx.OtherModuleDependencyTag(child)
+
+ if parent == ctx.Module() {
+ if !checkApexTag(tag) {
+ return false
+ }
+ }
+
+ if checkTransitiveTag(tag) {
+ if android.OutputFileForModule(ctx, child, "") == lib {
+ depPath = ctx.GetWalkPath()
+ }
+ return true
+ }
+
+ return false
+ })
+ if depPath != nil {
+ ctx.ModuleErrorf("dependency path:")
+ for _, m := range depPath {
+ ctx.ModuleErrorf(" %s", ctx.OtherModuleName(m))
+ }
+ return
+ }
+ }
+ }
+}
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 988c1ce..54c1fac 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -20,6 +20,7 @@
"path/filepath"
"reflect"
"regexp"
+ "slices"
"sort"
"strconv"
"strings"
@@ -28,6 +29,7 @@
"android/soong/aconfig/codegen"
"github.com/google/blueprint"
+ "github.com/google/blueprint/bpmodify"
"github.com/google/blueprint/proptools"
"android/soong/android"
@@ -225,6 +227,10 @@
"system/sepolicy/apex/myapex-file_contexts": nil,
})
+var prepareForTestWithOtherapex = android.FixtureMergeMockFs(android.MockFS{
+ "system/sepolicy/apex/otherapex-file_contexts": nil,
+})
+
// ensure that 'result' equals 'expected'
func ensureEquals(t *testing.T, result string, expected string) {
t.Helper()
@@ -12114,3 +12120,398 @@
fileList := android.ContentFromFileRuleForTests(t, result, partition.Output("fileList"))
android.AssertDeepEquals(t, "filesystem with apex", "apex/myapex.apex\n", fileList)
}
+
+func TestApexVerifyNativeImplementationLibs(t *testing.T) {
+ t.Parallel()
+
+ extractDepenencyPathFromErrors := func(errs []error) []string {
+ i := slices.IndexFunc(errs, func(err error) bool {
+ return strings.Contains(err.Error(), "dependency path:")
+ })
+ if i < 0 {
+ return nil
+ }
+ var dependencyPath []string
+ for _, err := range errs[i+1:] {
+ s := err.Error()
+ lastSpace := strings.LastIndexByte(s, ' ')
+ if lastSpace >= 0 {
+ dependencyPath = append(dependencyPath, s[lastSpace+1:])
+ }
+ }
+ return dependencyPath
+ }
+
+ checkErrors := func(wantDependencyPath []string) func(t *testing.T, result *android.TestResult) {
+ return func(t *testing.T, result *android.TestResult) {
+ t.Helper()
+ if len(result.Errs) == 0 {
+ t.Fatalf("expected errors")
+ }
+ t.Log("found errors:")
+ for _, err := range result.Errs {
+ t.Log(err)
+ }
+ if g, w := result.Errs[0].Error(), "library in apex transitively linked against implementation library"; !strings.Contains(g, w) {
+ t.Fatalf("expected error %q, got %q", w, g)
+ }
+ dependencyPath := extractDepenencyPathFromErrors(result.Errs)
+ if g, w := dependencyPath, wantDependencyPath; !slices.Equal(g, w) {
+ t.Errorf("expected dependency path %q, got %q", w, g)
+ }
+ }
+ }
+
+ addToSharedLibs := func(module, lib string) func(bp *bpmodify.Blueprint) {
+ return func(bp *bpmodify.Blueprint) {
+ m := bp.ModulesByName(module)
+ props, err := m.GetOrCreateProperty(bpmodify.List, "shared_libs")
+ if err != nil {
+ panic(err)
+ }
+ props.AddStringToList(lib)
+ }
+ }
+
+ bpTemplate := `
+ apex {
+ name: "myapex",
+ key: "myapex.key",
+ native_shared_libs: ["mylib"],
+ rust_dyn_libs: ["libmyrust"],
+ binaries: ["mybin", "myrustbin"],
+ jni_libs: ["libjni"],
+ apps: ["myapp"],
+ updatable: false,
+ }
+
+ apex {
+ name: "otherapex",
+ key: "myapex.key",
+ native_shared_libs: ["libotherapex"],
+ updatable: false,
+ }
+
+ apex_key {
+ name: "myapex.key",
+ public_key: "testkey.avbpubkey",
+ private_key: "testkey.pem",
+ }
+
+ cc_library {
+ name: "mylib",
+ srcs: ["foo.cpp"],
+ apex_available: ["myapex"],
+ }
+
+ cc_binary {
+ name: "mybin",
+ srcs: ["foo.cpp"],
+ apex_available: ["myapex"],
+ }
+
+ rust_library {
+ name: "libmyrust",
+ crate_name: "myrust",
+ srcs: ["src/lib.rs"],
+ rustlibs: ["libmyrust_transitive_dylib"],
+ rlibs: ["libmyrust_transitive_rlib"],
+ apex_available: ["myapex"],
+ }
+
+ rust_library{
+ name: "libmyrust_transitive_dylib",
+ crate_name: "myrust_transitive_dylib",
+ srcs: ["src/lib.rs"],
+ apex_available: ["myapex"],
+ }
+
+ rust_library {
+ name: "libmyrust_transitive_rlib",
+ crate_name: "myrust_transitive_rlib",
+ srcs: ["src/lib.rs"],
+ apex_available: ["myapex"],
+ }
+
+ rust_binary {
+ name: "myrustbin",
+ srcs: ["src/main.rs"],
+ apex_available: ["myapex"],
+ }
+
+ cc_library {
+ name: "libbar",
+ sdk_version: "current",
+ srcs: ["bar.cpp"],
+ apex_available: ["myapex"],
+ stl: "none",
+ }
+
+ android_app {
+ name: "myapp",
+ jni_libs: ["libembeddedjni"],
+ use_embedded_native_libs: true,
+ sdk_version: "current",
+ apex_available: ["myapex"],
+ }
+
+ cc_library {
+ name: "libembeddedjni",
+ sdk_version: "current",
+ srcs: ["bar.cpp"],
+ apex_available: ["myapex"],
+ stl: "none",
+ }
+
+ cc_library {
+ name: "libjni",
+ sdk_version: "current",
+ srcs: ["bar.cpp"],
+ apex_available: ["myapex"],
+ stl: "none",
+ }
+
+ cc_library {
+ name: "libotherapex",
+ sdk_version: "current",
+ srcs: ["otherapex.cpp"],
+ apex_available: ["otherapex"],
+ stubs: {
+ symbol_file: "libotherapex.map.txt",
+ versions: ["1", "2", "3"],
+ },
+ stl: "none",
+ }
+
+ cc_library {
+ name: "libplatform",
+ sdk_version: "current",
+ srcs: ["libplatform.cpp"],
+ stubs: {
+ symbol_file: "libplatform.map.txt",
+ versions: ["1", "2", "3"],
+ },
+ stl: "none",
+ system_shared_libs: [],
+ }
+ `
+
+ testCases := []struct {
+ name string
+ bpModifier func(bp *bpmodify.Blueprint)
+ dependencyPath []string
+ }{
+ {
+ name: "library dependency in other apex",
+ bpModifier: addToSharedLibs("mylib", "libotherapex#impl"),
+ dependencyPath: []string{"myapex", "mylib", "libotherapex"},
+ },
+ {
+ name: "transitive library dependency in other apex",
+ bpModifier: func(bp *bpmodify.Blueprint) {
+ addToSharedLibs("mylib", "libbar")(bp)
+ addToSharedLibs("libbar", "libotherapex#impl")(bp)
+ },
+ dependencyPath: []string{"myapex", "mylib", "libbar", "libotherapex"},
+ },
+ {
+ name: "library dependency in platform",
+ bpModifier: addToSharedLibs("mylib", "libplatform#impl"),
+ dependencyPath: []string{"myapex", "mylib", "libplatform"},
+ },
+ {
+ name: "jni library dependency in other apex",
+ bpModifier: addToSharedLibs("libjni", "libotherapex#impl"),
+ dependencyPath: []string{"myapex", "libjni", "libotherapex"},
+ },
+ {
+ name: "transitive jni library dependency in other apex",
+ bpModifier: func(bp *bpmodify.Blueprint) {
+ addToSharedLibs("libjni", "libbar")(bp)
+ addToSharedLibs("libbar", "libotherapex#impl")(bp)
+ },
+ dependencyPath: []string{"myapex", "libjni", "libbar", "libotherapex"},
+ },
+ {
+ name: "jni library dependency in platform",
+ bpModifier: addToSharedLibs("libjni", "libplatform#impl"),
+ dependencyPath: []string{"myapex", "libjni", "libplatform"},
+ },
+ {
+ name: "transitive jni library dependency in platform",
+ bpModifier: func(bp *bpmodify.Blueprint) {
+ addToSharedLibs("libjni", "libbar")(bp)
+ addToSharedLibs("libbar", "libplatform#impl")(bp)
+ },
+ dependencyPath: []string{"myapex", "libjni", "libbar", "libplatform"},
+ },
+ // TODO: embedded JNI in apps should be checked too, but Soong currently just packages the transitive
+ // JNI libraries even if they came from another apex.
+ //{
+ // name: "app jni library dependency in other apex",
+ // bpModifier: addToSharedLibs("libembeddedjni", "libotherapex#impl"),
+ // dependencyPath: []string{"myapex", "myapp", "libembeddedjni", "libotherapex"},
+ //},
+ //{
+ // name: "transitive app jni library dependency in other apex",
+ // bpModifier: func(bp *bpmodify.Blueprint) {
+ // addToSharedLibs("libembeddedjni", "libbar")(bp)
+ // addToSharedLibs("libbar", "libotherapex#impl")(bp)
+ // },
+ // dependencyPath: []string{"myapex", "myapp", "libembeddedjni", "libbar", "libotherapex"},
+ //},
+ //{
+ // name: "app jni library dependency in platform",
+ // bpModifier: addToSharedLibs("libembeddedjni", "libplatform#impl"),
+ // dependencyPath: []string{"myapex", "myapp", "libembeddedjni", "libplatform"},
+ //},
+ //{
+ // name: "transitive app jni library dependency in platform",
+ // bpModifier: func(bp *bpmodify.Blueprint) {
+ // addToSharedLibs("libembeddedjni", "libbar")(bp)
+ // addToSharedLibs("libbar", "libplatform#impl")(bp)
+ // },
+ // dependencyPath: []string{"myapex", "myapp", "libembeddedjni", "libbar", "libplatform"},
+ //},
+ {
+ name: "binary dependency in other apex",
+ bpModifier: addToSharedLibs("mybin", "libotherapex#impl"),
+ dependencyPath: []string{"myapex", "mybin", "libotherapex"},
+ },
+ {
+ name: "transitive binary dependency in other apex",
+ bpModifier: func(bp *bpmodify.Blueprint) {
+ addToSharedLibs("mybin", "libbar")(bp)
+ addToSharedLibs("libbar", "libotherapex#impl")(bp)
+ },
+ dependencyPath: []string{"myapex", "mybin", "libbar", "libotherapex"},
+ },
+ {
+ name: "binary dependency in platform",
+ bpModifier: addToSharedLibs("mybin", "libplatform#impl"),
+ dependencyPath: []string{"myapex", "mybin", "libplatform"},
+ },
+ {
+ name: "transitive binary dependency in platform",
+ bpModifier: func(bp *bpmodify.Blueprint) {
+ addToSharedLibs("mybin", "libbar")(bp)
+ addToSharedLibs("libbar", "libplatform#impl")(bp)
+ },
+ dependencyPath: []string{"myapex", "mybin", "libbar", "libplatform"},
+ },
+
+ {
+ name: "rust library dependency in other apex",
+ bpModifier: addToSharedLibs("libmyrust", "libotherapex#impl"),
+ dependencyPath: []string{"myapex", "libmyrust", "libotherapex"},
+ },
+ {
+ name: "transitive rust library dependency in other apex",
+ bpModifier: func(bp *bpmodify.Blueprint) {
+ addToSharedLibs("libmyrust", "libbar")(bp)
+ addToSharedLibs("libbar", "libotherapex#impl")(bp)
+ },
+ dependencyPath: []string{"myapex", "libmyrust", "libbar", "libotherapex"},
+ },
+ {
+ name: "rust library dependency in platform",
+ bpModifier: addToSharedLibs("libmyrust", "libplatform#impl"),
+ dependencyPath: []string{"myapex", "libmyrust", "libplatform"},
+ },
+ {
+ name: "transitive rust library dependency in platform",
+ bpModifier: func(bp *bpmodify.Blueprint) {
+ addToSharedLibs("libmyrust", "libbar")(bp)
+ addToSharedLibs("libbar", "libplatform#impl")(bp)
+ },
+ dependencyPath: []string{"myapex", "libmyrust", "libbar", "libplatform"},
+ },
+ {
+ name: "transitive rust library dylib dependency in other apex",
+ bpModifier: func(bp *bpmodify.Blueprint) {
+ addToSharedLibs("libmyrust_transitive_dylib", "libotherapex#impl")(bp)
+ },
+ dependencyPath: []string{"myapex", "libmyrust", "libmyrust_transitive_dylib", "libotherapex"},
+ },
+ {
+ name: "transitive rust library dylib dependency in platform",
+ bpModifier: func(bp *bpmodify.Blueprint) {
+ addToSharedLibs("libmyrust_transitive_dylib", "libplatform#impl")(bp)
+ },
+ dependencyPath: []string{"myapex", "libmyrust", "libmyrust_transitive_dylib", "libplatform"},
+ },
+ {
+ name: "transitive rust library rlib dependency in other apex",
+ bpModifier: func(bp *bpmodify.Blueprint) {
+ addToSharedLibs("libmyrust_transitive_rlib", "libotherapex#impl")(bp)
+ },
+ dependencyPath: []string{"myapex", "libmyrust", "libmyrust_transitive_rlib", "libotherapex"},
+ },
+ {
+ name: "transitive rust library rlib dependency in platform",
+ bpModifier: func(bp *bpmodify.Blueprint) {
+ addToSharedLibs("libmyrust_transitive_rlib", "libplatform#impl")(bp)
+ },
+ dependencyPath: []string{"myapex", "libmyrust", "libmyrust_transitive_rlib", "libplatform"},
+ },
+ {
+ name: "rust binary dependency in other apex",
+ bpModifier: addToSharedLibs("myrustbin", "libotherapex#impl"),
+ dependencyPath: []string{"myapex", "myrustbin", "libotherapex"},
+ },
+ {
+ name: "transitive rust binary dependency in other apex",
+ bpModifier: func(bp *bpmodify.Blueprint) {
+ addToSharedLibs("myrustbin", "libbar")(bp)
+ addToSharedLibs("libbar", "libotherapex#impl")(bp)
+ },
+ dependencyPath: []string{"myapex", "myrustbin", "libbar", "libotherapex"},
+ },
+ {
+ name: "rust binary dependency in platform",
+ bpModifier: addToSharedLibs("myrustbin", "libplatform#impl"),
+ dependencyPath: []string{"myapex", "myrustbin", "libplatform"},
+ },
+ {
+ name: "transitive rust binary dependency in platform",
+ bpModifier: func(bp *bpmodify.Blueprint) {
+ addToSharedLibs("myrustbin", "libbar")(bp)
+ addToSharedLibs("libbar", "libplatform#impl")(bp)
+ },
+ dependencyPath: []string{"myapex", "myrustbin", "libbar", "libplatform"},
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ t.Parallel()
+ bp, err := bpmodify.NewBlueprint("", []byte(bpTemplate))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if testCase.bpModifier != nil {
+ func() {
+ defer func() {
+ if r := recover(); r != nil {
+ t.Fatalf("panic in bpModifier: %v", r)
+ }
+ }()
+ testCase.bpModifier(bp)
+ }()
+ }
+ android.GroupFixturePreparers(
+ android.PrepareForTestWithAndroidBuildComponents,
+ cc.PrepareForTestWithCcBuildComponents,
+ java.PrepareForTestWithDexpreopt,
+ rust.PrepareForTestWithRustDefaultModules,
+ PrepareForTestWithApexBuildComponents,
+ prepareForTestWithMyapex,
+ prepareForTestWithOtherapex,
+ android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+ variables.BuildId = proptools.StringPtr("TEST.BUILD_ID")
+ }),
+ ).ExtendWithErrorHandler(android.FixtureCustomErrorHandler(checkErrors(testCase.dependencyPath))).
+ RunTestWithBp(t, bp.String())
+ })
+ }
+}
diff --git a/apex/classpath_element_test.go b/apex/classpath_element_test.go
index f8e8899..f367174 100644
--- a/apex/classpath_element_test.go
+++ b/apex/classpath_element_test.go
@@ -45,10 +45,7 @@
prepareForTestWithPlatformBootclasspath,
prepareForTestWithArtApex,
prepareForTestWithMyapex,
- // For otherapex.
- android.FixtureMergeMockFs(android.MockFS{
- "system/sepolicy/apex/otherapex-file_contexts": nil,
- }),
+ prepareForTestWithOtherapex,
java.PrepareForTestWithJavaSdkLibraryFiles,
java.FixtureWithLastReleaseApis("foo", "othersdklibrary"),
java.FixtureConfigureApexBootJars("myapex:bar"),
diff --git a/cc/Android.bp b/cc/Android.bp
index a7b6d81..3b29ae8 100644
--- a/cc/Android.bp
+++ b/cc/Android.bp
@@ -122,3 +122,31 @@
// Used by plugins
visibility: ["//visibility:public"],
}
+
+phony {
+ name: "llndk_libs",
+ required: [
+ "libEGL",
+ "libGLESv1_CM",
+ "libGLESv2",
+ "libGLESv3",
+ "libRS",
+ "libandroid_net",
+ "libapexsupport",
+ "libbinder_ndk",
+ "libc",
+ "libcgrouprc",
+ "libclang_rt.asan",
+ "libdl",
+ "libft2",
+ "liblog",
+ "libm",
+ "libmediandk",
+ "libnativewindow",
+ "libselinux",
+ "libsync",
+ "libvendorsupport",
+ "libvndksupport",
+ "libvulkan",
+ ],
+}
diff --git a/cc/cc.go b/cc/cc.go
index 551f2cd..c6bc30e 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -19,8 +19,10 @@
// is handled in builder.go
import (
+ "errors"
"fmt"
"io"
+ "slices"
"strconv"
"strings"
@@ -35,6 +37,22 @@
"android/soong/genrule"
)
+type CcMakeVarsInfo struct {
+ WarningsAllowed string
+ UsingWnoError string
+ MissingProfile string
+}
+
+var CcMakeVarsInfoProvider = blueprint.NewProvider[*CcMakeVarsInfo]()
+
+type CcObjectInfo struct {
+ objFiles android.Paths
+ tidyFiles android.Paths
+ kytheFiles android.Paths
+}
+
+var CcObjectInfoProvider = blueprint.NewProvider[CcObjectInfo]()
+
func init() {
RegisterCCBuildComponents(android.InitRegistrationContext)
@@ -213,6 +231,9 @@
// LLNDK headers for the ABI checker to check LLNDK implementation library.
LlndkIncludeDirs android.Paths
LlndkSystemIncludeDirs android.Paths
+
+ directImplementationDeps android.Paths
+ transitiveImplementationDeps []depset.DepSet[android.Path]
}
// LocalOrGlobalFlags contains flags that need to have values set globally by the build system or locally by the module
@@ -531,6 +552,7 @@
getSharedFlags() *SharedFlags
notInPlatform() bool
optimizeForSize() bool
+ getOrCreateMakeVarsInfo() *CcMakeVarsInfo
}
type SharedFlags struct {
@@ -639,10 +661,6 @@
installInRoot() bool
}
-type xref interface {
- XrefCcFiles() android.Paths
-}
-
type overridable interface {
overriddenModules() []string
}
@@ -845,9 +863,10 @@
sourceProperties android.SourceProperties
// initialize before calling Init
- hod android.HostOrDeviceSupported
- multilib android.Multilib
- testModule bool
+ hod android.HostOrDeviceSupported
+ multilib android.Multilib
+ testModule bool
+ incremental bool
// Allowable SdkMemberTypes of this module type.
sdkMemberTypes []android.SdkMemberType
@@ -890,12 +909,6 @@
staticAnalogue *StaticLibraryInfo
makeLinkType string
- // Kythe (source file indexer) paths for this compilation module
- kytheFiles android.Paths
- // Object .o file output paths for this compilation module
- objFiles android.Paths
- // Tidy .tidy file output paths for this compilation module
- tidyFiles android.Paths
// For apex variants, this is set as apex.min_sdk_version
apexSdkVersion android.ApiLevel
@@ -913,8 +926,16 @@
hasSysprop bool
hasWinMsg bool
hasYacc bool
+
+ makeVarsInfo *CcMakeVarsInfo
}
+func (c *Module) IncrementalSupported() bool {
+ return c.incremental
+}
+
+var _ blueprint.Incremental = (*Module)(nil)
+
func (c *Module) AddJSONData(d *map[string]interface{}) {
c.AndroidModuleBase().AddJSONData(d)
(*d)["Cc"] = map[string]interface{}{
@@ -1455,10 +1476,6 @@
return isBionic(name)
}
-func (c *Module) XrefCcFiles() android.Paths {
- return c.kytheFiles
-}
-
func (c *Module) isCfiAssemblySupportEnabled() bool {
return c.sanitize != nil &&
Bool(c.sanitize.Properties.Sanitize.Config.Cfi_assembly_support)
@@ -1700,6 +1717,13 @@
return ctx.mod.NotInPlatform()
}
+func (ctx *moduleContextImpl) getOrCreateMakeVarsInfo() *CcMakeVarsInfo {
+ if ctx.mod.makeVarsInfo == nil {
+ ctx.mod.makeVarsInfo = &CcMakeVarsInfo{}
+ }
+ return ctx.mod.makeVarsInfo
+}
+
func newBaseModule(hod android.HostOrDeviceSupported, multilib android.Multilib) *Module {
return &Module{
hod: hod,
@@ -2023,9 +2047,6 @@
if ctx.Failed() {
return
}
- c.kytheFiles = objs.kytheFiles
- c.objFiles = objs.objFiles
- c.tidyFiles = objs.tidyFiles
}
if c.linker != nil {
@@ -2036,6 +2057,10 @@
c.outputFile = android.OptionalPathForPath(outputFile)
c.maybeUnhideFromMake()
+
+ android.SetProvider(ctx, ImplementationDepInfoProvider, &ImplementationDepInfo{
+ ImplementationDeps: depset.New(depset.PREORDER, deps.directImplementationDeps, deps.transitiveImplementationDeps),
+ })
}
android.SetProvider(ctx, blueprint.SrcsFileProviderKey, blueprint.SrcsFileProviderData{SrcPaths: deps.GeneratedSources.Strings()})
@@ -2090,7 +2115,28 @@
c.hasYacc = b.hasSrcExt(ctx, ".y") || b.hasSrcExt(ctx, ".yy")
}
+ ccObjectInfo := CcObjectInfo{
+ kytheFiles: objs.kytheFiles,
+ }
+ if !ctx.Config().KatiEnabled() || !android.ShouldSkipAndroidMkProcessing(ctx, c) {
+ ccObjectInfo.objFiles = objs.objFiles
+ ccObjectInfo.tidyFiles = objs.tidyFiles
+ }
+ if len(ccObjectInfo.kytheFiles)+len(ccObjectInfo.objFiles)+len(ccObjectInfo.tidyFiles) > 0 {
+ android.SetProvider(ctx, CcObjectInfoProvider, ccObjectInfo)
+ }
+
c.setOutputFiles(ctx)
+
+ if c.makeVarsInfo != nil {
+ android.SetProvider(ctx, CcMakeVarsInfoProvider, c.makeVarsInfo)
+ }
+}
+
+func setOutputFilesIfNotEmpty(ctx ModuleContext, files android.Paths, tag string) {
+ if len(files) > 0 {
+ ctx.SetOutputFiles(files, tag)
+ }
}
func (c *Module) setOutputFiles(ctx ModuleContext) {
@@ -2255,6 +2301,10 @@
deps.RuntimeLibs = android.LastUniqueStrings(deps.RuntimeLibs)
deps.LlndkHeaderLibs = android.LastUniqueStrings(deps.LlndkHeaderLibs)
+ if err := checkConflictingExplicitVersions(deps.SharedLibs); err != nil {
+ ctx.PropertyErrorf("shared_libs", "%s", err.Error())
+ }
+
for _, lib := range deps.ReexportSharedLibHeaders {
if !inList(lib, deps.SharedLibs) {
ctx.PropertyErrorf("export_shared_lib_headers", "Shared library not in shared_libs: '%s'", lib)
@@ -2282,6 +2332,26 @@
return deps
}
+func checkConflictingExplicitVersions(libs []string) error {
+ withoutVersion := func(s string) string {
+ name, _ := StubsLibNameAndVersion(s)
+ return name
+ }
+ var errs []error
+ for i, lib := range libs {
+ libName := withoutVersion(lib)
+ libsToCompare := libs[i+1:]
+ j := slices.IndexFunc(libsToCompare, func(s string) bool {
+ return withoutVersion(s) == libName
+ })
+ if j >= 0 {
+ errs = append(errs, fmt.Errorf("duplicate shared libraries with different explicit versions: %q and %q",
+ lib, libsToCompare[j]))
+ }
+ }
+ return errors.Join(errs...)
+}
+
func (c *Module) beginMutator(actx android.BottomUpMutatorContext) {
ctx := &baseModuleContext{
BaseModuleContext: actx,
@@ -2343,6 +2413,9 @@
if version != "" && canBeOrLinkAgainstVersionVariants(mod) {
// Version is explicitly specified. i.e. libFoo#30
+ if version == "impl" {
+ version = ""
+ }
variations = append(variations, blueprint.Variation{Mutator: "version", Variation: version})
if tag, ok := depTag.(libraryDependencyTag); ok {
tag.explicitlyVersioned = true
@@ -3041,6 +3114,13 @@
linkFile = android.OptionalPathForPath(sharedLibraryInfo.SharedLibrary)
depFile = sharedLibraryInfo.TableOfContents
+ if !sharedLibraryInfo.IsStubs {
+ depPaths.directImplementationDeps = append(depPaths.directImplementationDeps, android.OutputFileForModule(ctx, dep, ""))
+ if info, ok := android.OtherModuleProvider(ctx, dep, ImplementationDepInfoProvider); ok {
+ depPaths.transitiveImplementationDeps = append(depPaths.transitiveImplementationDeps, info.ImplementationDeps)
+ }
+ }
+
ptr = &depPaths.SharedLibs
switch libDepTag.Order {
case earlyLibraryDependency:
@@ -3929,9 +4009,10 @@
func (ks *kytheExtractAllSingleton) GenerateBuildActions(ctx android.SingletonContext) {
var xrefTargets android.Paths
- ctx.VisitAllModules(func(module android.Module) {
- if ccModule, ok := module.(xref); ok {
- xrefTargets = append(xrefTargets, ccModule.XrefCcFiles()...)
+ ctx.VisitAllModuleProxies(func(module android.ModuleProxy) {
+ files := android.OtherModuleProviderOrDefault(ctx, module, CcObjectInfoProvider).kytheFiles
+ if len(files) > 0 {
+ xrefTargets = append(xrefTargets, files...)
}
})
// TODO(asmundak): Perhaps emit a rule to output a warning if there were no xrefTargets
diff --git a/cc/cc_test.go b/cc/cc_test.go
index e906706..22f7c9f 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -3321,3 +3321,20 @@
t.Errorf("expected %q in cflags, got %q", "-Xclang -verify", cFlags_cv)
}
}
+
+func TestCheckConflictingExplicitVersions(t *testing.T) {
+ PrepareForIntegrationTestWithCc.
+ ExtendWithErrorHandler(android.FixtureExpectsOneErrorPattern(
+ `shared_libs: duplicate shared libraries with different explicit versions: "libbar" and "libbar#impl"`,
+ )).
+ RunTestWithBp(t, `
+ cc_library {
+ name: "libfoo",
+ shared_libs: ["libbar", "libbar#impl"],
+ }
+
+ cc_library {
+ name: "libbar",
+ }
+ `)
+}
diff --git a/cc/compiler.go b/cc/compiler.go
index f06287c..91f107c 100644
--- a/cc/compiler.go
+++ b/cc/compiler.go
@@ -78,11 +78,11 @@
// If possible, don't use this. If adding paths from the current directory use
// local_include_dirs, if adding paths from other modules use export_include_dirs in
// that module.
- Include_dirs []string `android:"arch_variant,variant_prepend"`
+ Include_dirs proptools.Configurable[[]string] `android:"arch_variant,variant_prepend"`
// list of directories relative to the Blueprints file that will
// be added to the include path using -I
- Local_include_dirs []string `android:"arch_variant,variant_prepend"`
+ Local_include_dirs proptools.Configurable[[]string] `android:"arch_variant,variant_prepend"`
// Add the directory containing the Android.bp file to the list of include
// directories. Defaults to true.
@@ -411,13 +411,13 @@
}
// Include dir cflags
- localIncludeDirs := android.PathsForModuleSrc(ctx, compiler.Properties.Local_include_dirs)
+ localIncludeDirs := android.PathsForModuleSrc(ctx, compiler.Properties.Local_include_dirs.GetOrDefault(ctx, nil))
if len(localIncludeDirs) > 0 {
f := includeDirsToFlags(localIncludeDirs)
flags.Local.CommonFlags = append(flags.Local.CommonFlags, f)
flags.Local.YasmFlags = append(flags.Local.YasmFlags, f)
}
- rootIncludeDirs := android.PathsForSource(ctx, compiler.Properties.Include_dirs)
+ rootIncludeDirs := android.PathsForSource(ctx, compiler.Properties.Include_dirs.GetOrDefault(ctx, nil))
if len(rootIncludeDirs) > 0 {
f := includeDirsToFlags(rootIncludeDirs)
flags.Local.CommonFlags = append(flags.Local.CommonFlags, f)
@@ -685,10 +685,10 @@
if len(srcs) > 0 {
module := ctx.ModuleDir() + "/Android.bp:" + ctx.ModuleName()
if inList("-Wno-error", flags.Local.CFlags) || inList("-Wno-error", flags.Local.CppFlags) {
- addToModuleList(ctx, modulesUsingWnoErrorKey, module)
+ ctx.getOrCreateMakeVarsInfo().UsingWnoError = module
} else if !inList("-Werror", flags.Local.CFlags) && !inList("-Werror", flags.Local.CppFlags) {
if warningsAreAllowed(ctx.ModuleDir()) {
- addToModuleList(ctx, modulesWarningsAllowedKey, module)
+ ctx.getOrCreateMakeVarsInfo().WarningsAllowed = module
} else {
flags.Local.CFlags = append([]string{"-Werror"}, flags.Local.CFlags...)
}
@@ -807,7 +807,7 @@
type RustBindgenClangProperties struct {
// list of directories relative to the Blueprints file that will
// be added to the include path using -I
- Local_include_dirs []string `android:"arch_variant,variant_prepend"`
+ Local_include_dirs proptools.Configurable[[]string] `android:"arch_variant,variant_prepend"`
// list of static libraries that provide headers for this binding.
Static_libs proptools.Configurable[[]string] `android:"arch_variant,variant_prepend"`
diff --git a/cc/library.go b/cc/library.go
index 1f21614..7dffa72 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -1194,6 +1194,7 @@
SharedLibrary: unstrippedOutputFile,
TransitiveStaticLibrariesForOrdering: transitiveStaticLibrariesForOrdering,
Target: ctx.Target(),
+ IsStubs: library.buildStubs(),
})
addStubDependencyProviders(ctx)
diff --git a/cc/linkable.go b/cc/linkable.go
index cd33e28..ef204eb 100644
--- a/cc/linkable.go
+++ b/cc/linkable.go
@@ -317,7 +317,9 @@
SharedLibrary android.Path
Target android.Target
- TableOfContents android.OptionalPath
+ TableOfContents android.OptionalPath
+ IsStubs bool
+ ImplementationDeps depset.DepSet[string]
// should be obtained from static analogue
TransitiveStaticLibrariesForOrdering depset.DepSet[android.Path]
@@ -386,3 +388,9 @@
}
var FlagExporterInfoProvider = blueprint.NewProvider[FlagExporterInfo]()
+
+var ImplementationDepInfoProvider = blueprint.NewProvider[*ImplementationDepInfo]()
+
+type ImplementationDepInfo struct {
+ ImplementationDeps depset.DepSet[android.Path]
+}
diff --git a/cc/makevars.go b/cc/makevars.go
index f82e0e9..4cb98e7 100644
--- a/cc/makevars.go
+++ b/cc/makevars.go
@@ -25,10 +25,7 @@
)
var (
- modulesWarningsAllowedKey = android.NewOnceKey("ModulesWarningsAllowed")
- modulesUsingWnoErrorKey = android.NewOnceKey("ModulesUsingWnoError")
- modulesMissingProfileFileKey = android.NewOnceKey("ModulesMissingProfileFile")
- sanitizerVariables = map[string]string{
+ sanitizerVariables = map[string]string{
"ADDRESS_SANITIZER_RUNTIME_LIBRARY": config.AddressSanitizerRuntimeLibrary(),
"HWADDRESS_SANITIZER_RUNTIME_LIBRARY": config.HWAddressSanitizerRuntimeLibrary(),
"HWADDRESS_SANITIZER_STATIC_LIBRARY": config.HWAddressSanitizerStaticLibrary(),
@@ -50,15 +47,9 @@
}).(*sync.Map)
}
-func makeStringOfKeys(ctx android.MakeVarsContext, key android.OnceKey) string {
- set := getNamedMapForConfig(ctx.Config(), key)
- keys := []string{}
- set.Range(func(key interface{}, value interface{}) bool {
- keys = append(keys, key.(string))
- return true
- })
- sort.Strings(keys)
- return strings.Join(keys, " ")
+func makeVarsString(items []string) string {
+ items = android.SortedUniqueStrings(items)
+ return strings.Join(items, " ")
}
func makeStringOfWarningAllowedProjects() string {
@@ -108,28 +99,33 @@
ctx.Strict("GLOBAL_CLANG_EXTERNAL_CFLAGS_NO_OVERRIDE", "${config.NoOverrideExternalGlobalCflags}")
// Filter vendor_public_library that are exported to make
- exportedVendorPublicLibraries := []string{}
+ var exportedVendorPublicLibraries []string
+ var warningsAllowed []string
+ var usingWnoErrors []string
+ var missingProfiles []string
ctx.VisitAllModules(func(module android.Module) {
+ if v, ok := android.OtherModuleProvider(ctx, module, CcMakeVarsInfoProvider); ok {
+ warningsAllowed = android.AppendIfNotZero(warningsAllowed, v.WarningsAllowed)
+ usingWnoErrors = android.AppendIfNotZero(usingWnoErrors, v.UsingWnoError)
+ missingProfiles = android.AppendIfNotZero(missingProfiles, v.MissingProfile)
+ }
if ccModule, ok := module.(*Module); ok {
baseName := ccModule.BaseModuleName()
if ccModule.IsVendorPublicLibrary() && module.ExportedToMake() {
- if !inList(baseName, exportedVendorPublicLibraries) {
- exportedVendorPublicLibraries = append(exportedVendorPublicLibraries, baseName)
- }
+ exportedVendorPublicLibraries = append(exportedVendorPublicLibraries, baseName)
}
}
})
- sort.Strings(exportedVendorPublicLibraries)
- ctx.Strict("VENDOR_PUBLIC_LIBRARIES", strings.Join(exportedVendorPublicLibraries, " "))
+ ctx.Strict("VENDOR_PUBLIC_LIBRARIES", makeVarsString(exportedVendorPublicLibraries))
lsdumpPaths := *lsdumpPaths(ctx.Config())
sort.Strings(lsdumpPaths)
ctx.Strict("LSDUMP_PATHS", strings.Join(lsdumpPaths, " "))
ctx.Strict("ANDROID_WARNING_ALLOWED_PROJECTS", makeStringOfWarningAllowedProjects())
- ctx.Strict("SOONG_MODULES_WARNINGS_ALLOWED", makeStringOfKeys(ctx, modulesWarningsAllowedKey))
- ctx.Strict("SOONG_MODULES_USING_WNO_ERROR", makeStringOfKeys(ctx, modulesUsingWnoErrorKey))
- ctx.Strict("SOONG_MODULES_MISSING_PGO_PROFILE_FILE", makeStringOfKeys(ctx, modulesMissingProfileFileKey))
+ ctx.Strict("SOONG_MODULES_WARNINGS_ALLOWED", makeVarsString(warningsAllowed))
+ ctx.Strict("SOONG_MODULES_USING_WNO_ERROR", makeVarsString(usingWnoErrors))
+ ctx.Strict("SOONG_MODULES_MISSING_PGO_PROFILE_FILE", makeVarsString(missingProfiles))
ctx.Strict("CLANG_COVERAGE_CONFIG_CFLAGS", strings.Join(clangCoverageCFlags, " "))
ctx.Strict("CLANG_COVERAGE_CONFIG_COMMFLAGS", strings.Join(clangCoverageCommonFlags, " "))
diff --git a/cc/orderfile.go b/cc/orderfile.go
index 38b8905..6e08da7 100644
--- a/cc/orderfile.go
+++ b/cc/orderfile.go
@@ -54,10 +54,6 @@
})
}
-func recordMissingOrderfile(ctx BaseModuleContext, missing string) {
- getNamedMapForConfig(ctx.Config(), modulesMissingProfileFileKey).Store(missing, true)
-}
-
type OrderfileProperties struct {
Orderfile struct {
Instrumentation *bool
@@ -117,7 +113,7 @@
// Record that this module's order file is absent
missing := *props.Orderfile.Order_file_path + ":" + ctx.ModuleDir() + "/Android.bp:" + ctx.ModuleName()
- recordMissingOrderfile(ctx, missing)
+ ctx.getOrCreateMakeVarsInfo().MissingProfile = missing
return android.OptionalPath{}
}
diff --git a/cc/prebuilt.go b/cc/prebuilt.go
index 7c87297..ba4c662 100644
--- a/cc/prebuilt.go
+++ b/cc/prebuilt.go
@@ -221,6 +221,7 @@
Target: ctx.Target(),
TableOfContents: p.tocFile,
+ IsStubs: p.buildStubs(),
})
return outputFile
@@ -232,6 +233,7 @@
android.SetProvider(ctx, SharedLibraryInfoProvider, SharedLibraryInfo{
SharedLibrary: latestStub,
Target: ctx.Target(),
+ IsStubs: true,
})
return latestStub
diff --git a/cc/tidy.go b/cc/tidy.go
index ec1e8a2..89bae17 100644
--- a/cc/tidy.go
+++ b/cc/tidy.go
@@ -219,15 +219,11 @@
subsetTidyFileGroups := make(map[string]android.Paths) // subset group name => tidy file Paths
// (1) Collect all obj/tidy files into OS-specific groups.
- ctx.VisitAllModuleVariants(module, func(variant android.Module) {
- if ctx.Config().KatiEnabled() && android.ShouldSkipAndroidMkProcessing(ctx, variant) {
- return
- }
- if m, ok := variant.(*Module); ok {
- osName := variant.Target().Os.Name
- addToOSGroup(osName, m.objFiles, allObjFileGroups, subsetObjFileGroups)
- addToOSGroup(osName, m.tidyFiles, allTidyFileGroups, subsetTidyFileGroups)
- }
+ ctx.VisitAllModuleVariantProxies(module, func(variant android.ModuleProxy) {
+ osName := android.OtherModuleProviderOrDefault(ctx, variant, android.CommonPropertiesProviderKey).CompileTarget.Os.Name
+ info := android.OtherModuleProviderOrDefault(ctx, variant, CcObjectInfoProvider)
+ addToOSGroup(osName, info.objFiles, allObjFileGroups, subsetObjFileGroups)
+ addToOSGroup(osName, info.tidyFiles, allTidyFileGroups, subsetTidyFileGroups)
})
// (2) Add an all-OS group, with "" or "subset" name, to include all os-specific phony targets.
diff --git a/cc/vndk_prebuilt.go b/cc/vndk_prebuilt.go
index e7dff40..4a2adf0 100644
--- a/cc/vndk_prebuilt.go
+++ b/cc/vndk_prebuilt.go
@@ -166,6 +166,7 @@
Target: ctx.Target(),
TableOfContents: p.tocFile,
+ IsStubs: false,
})
p.libraryDecorator.flagExporter.setProvider(ctx)
diff --git a/docs/selects.md b/docs/selects.md
new file mode 100644
index 0000000..4f436ac
--- /dev/null
+++ b/docs/selects.md
@@ -0,0 +1,170 @@
+# Select statements
+
+## Introduction
+
+Soong currently has the arch, target, product_variables, and soong config variable properties that all support changing the values of soong properties based on some condition. These are confusing for users, and particularly the soong config variable properties require a lot of boilerplate that is annoying to write.
+
+In addition, these properties are all implemented using reflection on property structs, and can combine in ways that the original authors did not expect. For example, soong config variables can be combined with arch/target by saying that they operate on "arch.arm.enabled" instead of just "enabled". But product variables do not have the same abilities.
+
+The goal here is to combine all these different configuration mechanisms into one, to reduce complexity and boilerplate both in Android.bp files and in soong code.
+
+## Usage
+
+The soong select statements take their name and inspiration from [bazel select statements](https://bazel.build/docs/configurable-attributes).
+
+### Syntax
+
+#### Basic
+
+The basic syntax for select statements looks like:
+
+```
+my_module_type {
+ name: "my_module",
+ some_string_property: select(arch(), {
+ "arm": "foo",
+ "x86": "bar",
+ default: "baz",
+ }),
+}
+```
+
+That is, `select(` followed by a variable function, then a map of values of the variable to values to set the property to.
+
+Arguments can be passed to the "functions" that look up axes:
+
+```
+select(soong_config_variable("my_namespace", "my_variable"), {
+ "value1": "foo",
+ default: "bar",
+})
+```
+
+
+The list of functions that can currently be selected on:
+ - `arch()`
+ - `os()`
+ - `soong_config_variable(namespace, variable)`
+ - `release_flag(flag)`
+
+The functions are [defined here](https://cs.android.com/android/platform/superproject/main/+/main:build/soong/android/module.go;l=2144;drc=3f01580c04bfe37c920e247015cce93cff2451c0), and it should be easy to add more.
+
+#### Multivariable
+
+To handle multivariable selects, multiple axes can be specified within parenthesis, to look like tuple destructuring. All of the variables being selected must match the corresponding value from the branch in order for the branch to be chosen.
+
+```
+select((arch(), os()), {
+ ("arm", "linux"): "foo",
+ (default, "windows"): "bar",
+ (default, default): "baz",
+})
+```
+
+#### Unset
+
+You can have unset branches of selects using the "unset" keyword, which will act as if the property was not assigned to. This is only really useful if you’re using defaults modules.
+
+```
+cc_binary {
+ name: "my_binary",
+ enabled: select(os(), {
+ "darwin": false,
+ default: unset,
+ }),
+}
+```
+
+#### Appending
+
+You can append select statements to both scalar values and other select statements:
+
+```
+my_module_type {
+ name: "my_module",
+ // string_property will be set to something like penguin-four, apple-two, etc.
+ string_property: select(os(), {
+ "linux_glibc": "penguin",
+ "darwin": "apple",
+ default: "unknown",
+ }) + "-" + select(soong_config_variable("ANDROID", "favorite_vehicle"), {
+ "car": "four",
+ "tricycle": "three",
+ "bike": "two",
+ default: "unknown",
+ })
+}
+```
+
+
+You can also append a select with a value with another select that may not have a value, because some of its branches are "unset". If an unset branch was selected it will not append anything to the other select.
+
+#### Binding the selected value to a Blueprint variable and the "any" keyword
+
+In case you want to allow a selected value to have an unbounded number of possible values, you can bind its value to a blueprint variable and use it within the expression for that select branch.
+
+```
+my_module_type {
+ name: "my_module",
+ my_string_property: select(soong_config_variable("my_namespace", "my_variable"), {
+ "some_value": "baz",
+ any @ my_var: "foo" + my_var,
+ default: "bar",
+ }),
+}
+```
+
+The syntax is `any @ my_variable_name: <expression using my_variable_name>`. `any` is currently the only pattern that can be bound to a variable, but we may add more in the future. `any` is equivalent to `default` except it will not match undefined select conditions.
+
+#### Errors
+
+If a select statement does not have a "default" branch, and none of the other branches match the variable being selected on, it’s a compile-time error. This may be useful for enforcing a variable is 1 of only a few values.
+
+```
+# in product config:
+$(call soong_config_set,ANDROID,my_variable,foo)
+
+// in an Android.bp:
+my_module_type {
+ name: "my_module",
+ // Will error out with: soong_config_variable("ANDROID", "my_variable") had value "foo", which was not handled by the select
+ enabled: select(soong_config_variable("ANDROID", "my_variable"), {
+ "bar": true,
+ "baz": false,
+ }),
+}
+```
+
+### Changes to property structs to support selects
+
+Currently, the way configurable properties work is that there is secretly another property struct that has the `target`, `arch`, etc. properties, and then when the arch mutator (or other relevant mutator) runs, it copies the values of these properties onto the regular property structs. There’s nothing stopping you from accessing your properties from a mutator that runs before the one that updates the properties based on configurable values. This is a potential source of bugs, and we want to make sure that select statements don’t have the same pitfall. For that reason, you have to read property’s values through a getter which can do this check. This requires changing the code on a property-by-property basis to support selects.
+
+To make a property support selects, it must be of type [proptools.Configurable[T]](https://cs.android.com/android/platform/superproject/main/+/main:build/blueprint/proptools/configurable.go;l=341;drc=a52b058cccd2caa778d0f97077adcd4ef7ffb68a). T is the old type of the property. Currently we support bool, string, and []string. Configurable has a `Get(evaluator)` method to get the value of the property. The evaluator can be a ModuleContext, or if you’re in a situation where you only have a very limited context and a module, (such as in a singleton) you can use [ModuleBase.ConfigurableEvaluator](https://cs.android.com/android/platform/superproject/main/+/main:build/soong/android/module.go;l=2133;drc=e19f741052cce097da940d9083d3f29e668de5cb).
+
+`proptools.Configurable[T]` will handle unset properties for you, so you don’t need to make it a pointer type. However, there is a not-widely-known feature of property structs, where normally, properties are appended when squashing defaults. But if the property was a pointer property, later defaults replace earlier values instead of appending. With selects, to maintain this behavior, add the `android:"replace_instead_of_append"` struct tag. The "append" behavior for boolean values is to boolean OR them together, which is rarely what you want, so most boolean properties are pointers today.
+
+Old:
+```
+type commonProperties struct {
+ Enabled *bool `android:"arch_variant"`
+}
+
+func (m *ModuleBase) Enabled() *bool {
+ return m.commonProperties.Enabled
+}
+```
+
+New:
+```
+type commonProperties struct {
+ Enabled proptools.Configurable[bool] `android:"arch_variant,replace_instead_of_append"`
+}
+
+func (m *ModuleBase) Enabled(ctx ConfigAndErrorContext) *bool {
+ return m.commonProperties.Enabled.Get(m.ConfigurableEvaluator(ctx))
+}
+```
+
+The `android:"arch_variant"` tag is kept to support the old `target:` and `arch:` properties with this property, but if all their usages in bp files were replaced by selects, then that tag could be removed.
+
+The enabled property underwent this migration in https://r.android.com/3066188
diff --git a/etc/adb_keys.go b/etc/adb_keys.go
index a2df41c..73bc347 100644
--- a/etc/adb_keys.go
+++ b/etc/adb_keys.go
@@ -36,6 +36,11 @@
func (m *AdbKeysModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
productVariables := ctx.Config().ProductVariables()
+
+ if !m.ProductSpecific() {
+ ctx.ModuleErrorf("adb_keys module type must set product_specific to true")
+ }
+
if !(android.Bool(productVariables.Debuggable) && len(android.String(productVariables.AdbKeys)) > 0) {
m.SkipInstall()
return
@@ -48,7 +53,7 @@
Output: m.outputPath,
Input: input.Path(),
})
- m.installPath = android.PathForModuleInPartitionInstall(ctx, ctx.DeviceConfig().ProductPath(), "etc/security")
+ m.installPath = android.PathForModuleInstall(ctx, "etc/security")
ctx.InstallFile(m.installPath, "adb_keys", m.outputPath)
}
diff --git a/etc/prebuilt_etc.go b/etc/prebuilt_etc.go
index a46da77..be943a3 100644
--- a/etc/prebuilt_etc.go
+++ b/etc/prebuilt_etc.go
@@ -76,6 +76,10 @@
ctx.RegisterModuleType("prebuilt_res", PrebuiltResFactory)
ctx.RegisterModuleType("prebuilt_wlc_upt", PrebuiltWlcUptFactory)
ctx.RegisterModuleType("prebuilt_odm", PrebuiltOdmFactory)
+ ctx.RegisterModuleType("prebuilt_vendor_dlkm", PrebuiltVendorDlkmFactory)
+ ctx.RegisterModuleType("prebuilt_bt_firmware", PrebuiltBtFirmwareFactory)
+ ctx.RegisterModuleType("prebuilt_tvservice", PrebuiltTvServiceFactory)
+ ctx.RegisterModuleType("prebuilt_optee", PrebuiltOpteeFactory)
ctx.RegisterModuleType("prebuilt_defaults", defaultsFactory)
@@ -132,6 +136,9 @@
// Install symlinks to the installed file.
Symlinks []string `android:"arch_variant"`
+
+ // Install to partition oem when set to true.
+ Oem_specific *bool `android:"arch_variant"`
}
type prebuiltSubdirProperties struct {
@@ -369,6 +376,10 @@
ctx.PropertyErrorf("sub_dir", "relative_install_path is set. Cannot set sub_dir")
}
baseInstallDirPath := android.PathForModuleInstall(ctx, p.installBaseDir(ctx), p.SubDir())
+ // TODO(b/377304441)
+ if android.Bool(p.properties.Oem_specific) {
+ baseInstallDirPath = android.PathForModuleInPartitionInstall(ctx, ctx.DeviceConfig().OemPath(), p.installBaseDir(ctx), p.SubDir())
+ }
filename := proptools.String(p.properties.Filename)
filenameFromSrc := proptools.Bool(p.properties.Filename_from_src)
@@ -910,3 +921,43 @@
android.InitDefaultableModule(module)
return module
}
+
+// prebuilt_vendor_dlkm installs files in <partition>/vendor_dlkm directory.
+func PrebuiltVendorDlkmFactory() android.Module {
+ module := &PrebuiltEtc{}
+ InitPrebuiltEtcModule(module, "vendor_dlkm")
+ // This module is device-only
+ android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+ android.InitDefaultableModule(module)
+ return module
+}
+
+// prebuilt_bt_firmware installs files in <partition>/bt_firmware directory.
+func PrebuiltBtFirmwareFactory() android.Module {
+ module := &PrebuiltEtc{}
+ InitPrebuiltEtcModule(module, "bt_firmware")
+ // This module is device-only
+ android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+ android.InitDefaultableModule(module)
+ return module
+}
+
+// prebuilt_tvservice installs files in <partition>/tvservice directory.
+func PrebuiltTvServiceFactory() android.Module {
+ module := &PrebuiltEtc{}
+ InitPrebuiltEtcModule(module, "tvservice")
+ // This module is device-only
+ android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+ android.InitDefaultableModule(module)
+ return module
+}
+
+// prebuilt_optee installs files in <partition>/optee directory.
+func PrebuiltOpteeFactory() android.Module {
+ module := &PrebuiltEtc{}
+ InitPrebuiltEtcModule(module, "optee")
+ // This module is device-only
+ android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+ android.InitDefaultableModule(module)
+ return module
+}
diff --git a/filesystem/filesystem_test.go b/filesystem/filesystem_test.go
index f767eae..29f9373 100644
--- a/filesystem/filesystem_test.go
+++ b/filesystem/filesystem_test.go
@@ -691,3 +691,27 @@
android.AssertStringDoesContain(t, "Could not find linker.config.json file in cmd", linkerConfigCmd, "conv_linker_config proto --force -s linker.config.json")
android.AssertStringDoesContain(t, "Could not find stub in `provideLibs`", linkerConfigCmd, "--key provideLibs --value libfoo_has_stubs.so")
}
+
+// override_android_* modules implicitly override their base module.
+// If both of these are listed in `deps`, the base module should not be installed.
+func TestOverrideModulesInDeps(t *testing.T) {
+ result := fixture.RunTestWithBp(t, `
+ android_filesystem {
+ name: "myfilesystem",
+ deps: ["myapp", "myoverrideapp"],
+ }
+
+ android_app {
+ name: "myapp",
+ platform_apis: true,
+ }
+ override_android_app {
+ name: "myoverrideapp",
+ base: "myapp",
+ }
+ `)
+
+ partition := result.ModuleForTests("myfilesystem", "android_common")
+ fileList := android.ContentFromFileRuleForTests(t, result.TestContext, partition.Output("fileList"))
+ android.AssertStringEquals(t, "filesystem with override app", "app/myoverrideapp/myoverrideapp.apk\n", fileList)
+}
diff --git a/fsgen/Android.bp b/fsgen/Android.bp
index baf9291..690ad28 100644
--- a/fsgen/Android.bp
+++ b/fsgen/Android.bp
@@ -10,9 +10,12 @@
"soong",
"soong-android",
"soong-filesystem",
+ "soong-kernel",
],
srcs: [
"filesystem_creator.go",
+ "fsgen_mutators.go",
+ "prebuilt_etc_modules_gen.go",
],
testSrcs: [
"filesystem_creator_test.go",
diff --git a/fsgen/filesystem_creator.go b/fsgen/filesystem_creator.go
index 001cac8..fe590a9 100644
--- a/fsgen/filesystem_creator.go
+++ b/fsgen/filesystem_creator.go
@@ -18,13 +18,12 @@
"crypto/sha256"
"fmt"
"path/filepath"
- "slices"
"strconv"
"strings"
- "sync"
"android/soong/android"
"android/soong/filesystem"
+ "android/soong/kernel"
"github.com/google/blueprint"
"github.com/google/blueprint/parser"
@@ -42,325 +41,6 @@
ctx.PreDepsMutators(RegisterCollectFileSystemDepsMutators)
}
-func RegisterCollectFileSystemDepsMutators(ctx android.RegisterMutatorsContext) {
- ctx.BottomUp("fs_collect_deps", collectDepsMutator).MutatesGlobalState()
- ctx.BottomUp("fs_set_deps", setDepsMutator)
-}
-
-var fsGenStateOnceKey = android.NewOnceKey("FsGenState")
-var fsGenRemoveOverridesOnceKey = android.NewOnceKey("FsGenRemoveOverrides")
-
-// Map of partition module name to its partition that may be generated by Soong.
-// Note that it is not guaranteed that all modules returned by this function are successfully
-// created.
-func getAllSoongGeneratedPartitionNames(config android.Config, partitions []string) map[string]string {
- ret := map[string]string{}
- for _, partition := range partitions {
- ret[generatedModuleNameForPartition(config, partition)] = partition
- }
- return ret
-}
-
-type depCandidateProps struct {
- Namespace string
- Multilib string
- Arch []android.ArchType
-}
-
-// Map of module name to depCandidateProps
-type multilibDeps *map[string]*depCandidateProps
-
-// Information necessary to generate the filesystem modules, including details about their
-// dependencies
-type FsGenState struct {
- // List of modules in `PRODUCT_PACKAGES` and `PRODUCT_PACKAGES_DEBUG`
- depCandidates []string
- // Map of names of partition to the information of modules to be added as deps
- fsDeps map[string]multilibDeps
- // List of name of partitions to be generated by the filesystem_creator module
- soongGeneratedPartitions []string
- // Mutex to protect the fsDeps
- fsDepsMutex sync.Mutex
- // Map of _all_ soong module names to their corresponding installation properties
- moduleToInstallationProps map[string]installationProperties
-}
-
-type installationProperties struct {
- Required []string
- Overrides []string
-}
-
-func newMultilibDeps() multilibDeps {
- return &map[string]*depCandidateProps{}
-}
-
-func defaultDepCandidateProps(config android.Config) *depCandidateProps {
- return &depCandidateProps{
- Namespace: ".",
- Arch: []android.ArchType{config.BuildArch},
- }
-}
-
-func createFsGenState(ctx android.LoadHookContext) *FsGenState {
- return ctx.Config().Once(fsGenStateOnceKey, func() interface{} {
- partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
- candidates := android.FirstUniqueStrings(android.Concat(partitionVars.ProductPackages, partitionVars.ProductPackagesDebug))
-
- generatedPartitions := []string{"system"}
- if ctx.DeviceConfig().SystemExtPath() == "system_ext" {
- generatedPartitions = append(generatedPartitions, "system_ext")
- }
- if ctx.DeviceConfig().BuildingVendorImage() && ctx.DeviceConfig().VendorPath() == "vendor" {
- generatedPartitions = append(generatedPartitions, "vendor")
- }
- if ctx.DeviceConfig().BuildingProductImage() && ctx.DeviceConfig().ProductPath() == "product" {
- generatedPartitions = append(generatedPartitions, "product")
- }
- if ctx.DeviceConfig().BuildingOdmImage() && ctx.DeviceConfig().OdmPath() == "odm" {
- generatedPartitions = append(generatedPartitions, "odm")
- }
-
- return &FsGenState{
- depCandidates: candidates,
- fsDeps: map[string]multilibDeps{
- // These additional deps are added according to the cuttlefish system image bp.
- "system": &map[string]*depCandidateProps{
- "com.android.apex.cts.shim.v1_prebuilt": defaultDepCandidateProps(ctx.Config()),
- "dex_bootjars": defaultDepCandidateProps(ctx.Config()),
- "framework_compatibility_matrix.device.xml": defaultDepCandidateProps(ctx.Config()),
- "idc_data": defaultDepCandidateProps(ctx.Config()),
- "init.environ.rc-soong": defaultDepCandidateProps(ctx.Config()),
- "keychars_data": defaultDepCandidateProps(ctx.Config()),
- "keylayout_data": defaultDepCandidateProps(ctx.Config()),
- "libclang_rt.asan": defaultDepCandidateProps(ctx.Config()),
- "libcompiler_rt": defaultDepCandidateProps(ctx.Config()),
- "libdmabufheap": defaultDepCandidateProps(ctx.Config()),
- "libgsi": defaultDepCandidateProps(ctx.Config()),
- "llndk.libraries.txt": defaultDepCandidateProps(ctx.Config()),
- "logpersist.start": defaultDepCandidateProps(ctx.Config()),
- "preloaded-classes": defaultDepCandidateProps(ctx.Config()),
- "public.libraries.android.txt": defaultDepCandidateProps(ctx.Config()),
- "update_engine_sideload": defaultDepCandidateProps(ctx.Config()),
- },
- "vendor": &map[string]*depCandidateProps{
- "fs_config_files_vendor": defaultDepCandidateProps(ctx.Config()),
- "fs_config_dirs_vendor": defaultDepCandidateProps(ctx.Config()),
- generatedModuleName(ctx.Config(), "vendor-build.prop"): defaultDepCandidateProps(ctx.Config()),
- },
- "odm": &map[string]*depCandidateProps{
- // fs_config_* files are automatically installed for all products with odm partitions.
- // https://cs.android.com/android/_/android/platform/build/+/e4849e87ab660b59a6501b3928693db065ee873b:tools/fs_config/Android.mk;l=34;drc=8d6481b92c4b4e9b9f31a61545b6862090fcc14b;bpv=1;bpt=0
- "fs_config_files_odm": defaultDepCandidateProps(ctx.Config()),
- "fs_config_dirs_odm": defaultDepCandidateProps(ctx.Config()),
- },
- "product": newMultilibDeps(),
- "system_ext": &map[string]*depCandidateProps{
- // VNDK apexes are automatically included.
- // This hardcoded list will need to be updated if `PRODUCT_EXTRA_VNDK_VERSIONS` is updated.
- // https://cs.android.com/android/_/android/platform/build/+/adba533072b00c53ac0f198c550a3cbd7a00e4cd:core/main.mk;l=984;bpv=1;bpt=0;drc=174db7b179592cf07cbfd2adb0119486fda911e7
- "com.android.vndk.v30": defaultDepCandidateProps(ctx.Config()),
- "com.android.vndk.v31": defaultDepCandidateProps(ctx.Config()),
- "com.android.vndk.v32": defaultDepCandidateProps(ctx.Config()),
- "com.android.vndk.v33": defaultDepCandidateProps(ctx.Config()),
- "com.android.vndk.v34": defaultDepCandidateProps(ctx.Config()),
- },
- },
- soongGeneratedPartitions: generatedPartitions,
- fsDepsMutex: sync.Mutex{},
- moduleToInstallationProps: map[string]installationProperties{},
- }
- }).(*FsGenState)
-}
-
-func checkDepModuleInMultipleNamespaces(mctx android.BottomUpMutatorContext, foundDeps map[string]*depCandidateProps, module string, partitionName string) {
- otherNamespace := mctx.Namespace().Path
- if val, found := foundDeps[module]; found && otherNamespace != "." && !android.InList(val.Namespace, []string{".", otherNamespace}) {
- mctx.ModuleErrorf("found in multiple namespaces(%s and %s) when including in %s partition", val.Namespace, otherNamespace, partitionName)
- }
-}
-
-func appendDepIfAppropriate(mctx android.BottomUpMutatorContext, deps *map[string]*depCandidateProps, installPartition string) {
- checkDepModuleInMultipleNamespaces(mctx, *deps, mctx.Module().Name(), installPartition)
- if _, ok := (*deps)[mctx.Module().Name()]; ok {
- // Prefer the namespace-specific module over the platform module
- if mctx.Namespace().Path != "." {
- (*deps)[mctx.Module().Name()].Namespace = mctx.Namespace().Path
- }
- (*deps)[mctx.Module().Name()].Arch = append((*deps)[mctx.Module().Name()].Arch, mctx.Module().Target().Arch.ArchType)
- } else {
- multilib, _ := mctx.Module().DecodeMultilib(mctx)
- (*deps)[mctx.Module().Name()] = &depCandidateProps{
- Namespace: mctx.Namespace().Path,
- Multilib: multilib,
- Arch: []android.ArchType{mctx.Module().Target().Arch.ArchType},
- }
- }
-}
-
-func collectDepsMutator(mctx android.BottomUpMutatorContext) {
- fsGenState := mctx.Config().Get(fsGenStateOnceKey).(*FsGenState)
-
- m := mctx.Module()
- if m.Target().Os.Class == android.Device && slices.Contains(fsGenState.depCandidates, m.Name()) {
- installPartition := m.PartitionTag(mctx.DeviceConfig())
- fsGenState.fsDepsMutex.Lock()
- // Only add the module as dependency when:
- // - its enabled
- // - its namespace is included in PRODUCT_SOONG_NAMESPACES
- if m.Enabled(mctx) && m.ExportedToMake() {
- appendDepIfAppropriate(mctx, fsGenState.fsDeps[installPartition], installPartition)
- }
- fsGenState.fsDepsMutex.Unlock()
- }
- // store the map of module to (required,overrides) even if the module is not in PRODUCT_PACKAGES.
- // the module might be installed transitively.
- if m.Target().Os.Class == android.Device && m.Enabled(mctx) && m.ExportedToMake() {
- fsGenState.fsDepsMutex.Lock()
- fsGenState.moduleToInstallationProps[m.Name()] = installationProperties{
- Required: m.RequiredModuleNames(mctx),
- Overrides: m.Overrides(),
- }
- fsGenState.fsDepsMutex.Unlock()
- }
-}
-
-type depsStruct struct {
- Deps []string
-}
-
-type multilibDepsStruct struct {
- Common depsStruct
- Lib32 depsStruct
- Lib64 depsStruct
- Both depsStruct
- Prefer32 depsStruct
-}
-
-type packagingPropsStruct struct {
- High_priority_deps []string
- Deps []string
- Multilib multilibDepsStruct
-}
-
-func fullyQualifiedModuleName(moduleName, namespace string) string {
- if namespace == "." {
- return moduleName
- }
- return fmt.Sprintf("//%s:%s", namespace, moduleName)
-}
-
-func getBitness(archTypes []android.ArchType) (ret []string) {
- for _, archType := range archTypes {
- if archType.Multilib == "" {
- ret = append(ret, android.COMMON_VARIANT)
- } else {
- ret = append(ret, archType.Bitness())
- }
- }
- return ret
-}
-
-func setDepsMutator(mctx android.BottomUpMutatorContext) {
- removeOverriddenDeps(mctx)
- fsGenState := mctx.Config().Get(fsGenStateOnceKey).(*FsGenState)
- fsDeps := fsGenState.fsDeps
- soongGeneratedPartitionMap := getAllSoongGeneratedPartitionNames(mctx.Config(), fsGenState.soongGeneratedPartitions)
- m := mctx.Module()
- if partition, ok := soongGeneratedPartitionMap[m.Name()]; ok {
- depsStruct := generateDepStruct(*fsDeps[partition])
- if err := proptools.AppendMatchingProperties(m.GetProperties(), depsStruct, nil); err != nil {
- mctx.ModuleErrorf(err.Error())
- }
- }
-}
-
-// removeOverriddenDeps collects PRODUCT_PACKAGES and (transitive) required deps.
-// it then removes any modules which appear in `overrides` of the above list.
-func removeOverriddenDeps(mctx android.BottomUpMutatorContext) {
- mctx.Config().Once(fsGenRemoveOverridesOnceKey, func() interface{} {
- fsGenState := mctx.Config().Get(fsGenStateOnceKey).(*FsGenState)
- fsDeps := fsGenState.fsDeps
- overridden := map[string]bool{}
- allDeps := []string{}
-
- // Step 1: Initialization: Append PRODUCT_PACKAGES to the queue
- for _, fsDep := range fsDeps {
- for depName, _ := range *fsDep {
- allDeps = append(allDeps, depName)
- }
- }
-
- // Step 2: Process the queue, and add required modules to the queue.
- i := 0
- for {
- if i == len(allDeps) {
- break
- }
- depName := allDeps[i]
- for _, overrides := range fsGenState.moduleToInstallationProps[depName].Overrides {
- overridden[overrides] = true
- }
- // add required dep to the queue.
- allDeps = append(allDeps, fsGenState.moduleToInstallationProps[depName].Required...)
- i += 1
- }
-
- // Step 3: Delete all the overridden modules.
- for overridden, _ := range overridden {
- for partition, _ := range fsDeps {
- delete(*fsDeps[partition], overridden)
- }
- }
- return nil
- })
-}
-
-var HighPriorityDeps = []string{}
-
-func generateDepStruct(deps map[string]*depCandidateProps) *packagingPropsStruct {
- depsStruct := packagingPropsStruct{}
- for depName, depProps := range deps {
- bitness := getBitness(depProps.Arch)
- fullyQualifiedDepName := fullyQualifiedModuleName(depName, depProps.Namespace)
- if android.InList(depName, HighPriorityDeps) {
- depsStruct.High_priority_deps = append(depsStruct.High_priority_deps, fullyQualifiedDepName)
- } else if android.InList("32", bitness) && android.InList("64", bitness) {
- // If both 32 and 64 bit variants are enabled for this module
- switch depProps.Multilib {
- case string(android.MultilibBoth):
- depsStruct.Multilib.Both.Deps = append(depsStruct.Multilib.Both.Deps, fullyQualifiedDepName)
- case string(android.MultilibCommon), string(android.MultilibFirst):
- depsStruct.Deps = append(depsStruct.Deps, fullyQualifiedDepName)
- case "32":
- depsStruct.Multilib.Lib32.Deps = append(depsStruct.Multilib.Lib32.Deps, fullyQualifiedDepName)
- case "64", "darwin_universal":
- depsStruct.Multilib.Lib64.Deps = append(depsStruct.Multilib.Lib64.Deps, fullyQualifiedDepName)
- case "prefer32", "first_prefer32":
- depsStruct.Multilib.Prefer32.Deps = append(depsStruct.Multilib.Prefer32.Deps, fullyQualifiedDepName)
- default:
- depsStruct.Multilib.Both.Deps = append(depsStruct.Multilib.Both.Deps, fullyQualifiedDepName)
- }
- } else if android.InList("64", bitness) {
- // If only 64 bit variant is enabled
- depsStruct.Multilib.Lib64.Deps = append(depsStruct.Multilib.Lib64.Deps, fullyQualifiedDepName)
- } else if android.InList("32", bitness) {
- // If only 32 bit variant is enabled
- depsStruct.Multilib.Lib32.Deps = append(depsStruct.Multilib.Lib32.Deps, fullyQualifiedDepName)
- } else {
- // If only common variant is enabled
- depsStruct.Multilib.Common.Deps = append(depsStruct.Multilib.Common.Deps, fullyQualifiedDepName)
- }
- }
- depsStruct.Deps = android.SortedUniqueStrings(depsStruct.Deps)
- depsStruct.Multilib.Lib32.Deps = android.SortedUniqueStrings(depsStruct.Multilib.Lib32.Deps)
- depsStruct.Multilib.Lib64.Deps = android.SortedUniqueStrings(depsStruct.Multilib.Lib64.Deps)
- depsStruct.Multilib.Prefer32.Deps = android.SortedUniqueStrings(depsStruct.Multilib.Prefer32.Deps)
- depsStruct.Multilib.Both.Deps = android.SortedUniqueStrings(depsStruct.Multilib.Both.Deps)
- depsStruct.Multilib.Common.Deps = android.SortedUniqueStrings(depsStruct.Multilib.Common.Deps)
-
- return &depsStruct
-}
-
type filesystemCreatorProps struct {
Generated_partition_types []string `blueprint:"mutated"`
Unsupported_partition_types []string `blueprint:"mutated"`
@@ -378,7 +58,8 @@
android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
module.AddProperties(&module.properties)
android.AddLoadHook(module, func(ctx android.LoadHookContext) {
- createFsGenState(ctx)
+ generatedPrebuiltEtcModuleNames := createPrebuiltEtcModules(ctx)
+ createFsGenState(ctx, generatedPrebuiltEtcModuleNames)
module.createInternalModules(ctx)
})
@@ -455,6 +136,26 @@
"framework/oat/*/*", // framework/oat/{arch}
}
fsProps.Fsverity.Libs = []string{":framework-res{.export-package.apk}"}
+ // TODO(b/377734331): only generate the symlinks if the relevant partitions exist
+ fsProps.Symlinks = []filesystem.SymlinkDefinition{
+ filesystem.SymlinkDefinition{
+ Target: proptools.StringPtr("/product"),
+ Name: proptools.StringPtr("system/product"),
+ },
+ filesystem.SymlinkDefinition{
+ Target: proptools.StringPtr("/system_ext"),
+ Name: proptools.StringPtr("system/system_ext"),
+ },
+ filesystem.SymlinkDefinition{
+ Target: proptools.StringPtr("/vendor"),
+ Name: proptools.StringPtr("system/vendor"),
+ },
+ filesystem.SymlinkDefinition{
+ Target: proptools.StringPtr("/system_dlkm/lib/modules"),
+ Name: proptools.StringPtr("system/lib/modules"),
+ },
+ }
+ fsProps.Base_dir = proptools.StringPtr("system")
case "system_ext":
fsProps.Fsverity.Inputs = []string{
"framework/*",
@@ -504,9 +205,19 @@
fsProps.Linkerconfig.Linker_config_srcs = f.createLinkerConfigSourceFilegroups(ctx, partitionType)
}
+ if partitionType == "system_dlkm" {
+ kernelModules := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.SystemKernelModules
+ f.createPrebuiltKernelModules(ctx, partitionType, kernelModules)
+ }
+
var module android.Module
if partitionType == "system" {
module = ctx.CreateModule(filesystem.SystemImageFactory, baseProps, fsProps)
+ } else if partitionType == "system_dlkm" {
+ // Do not set partition_type. build/soong/android/paths#modulePartition currently does not support dlkm
+ // partitions. Since `android_filesystem` uses a partition based filter, setting the partition here
+ // would result in missing in entries.
+ module = ctx.CreateModule(filesystem.FilesystemFactory, baseProps, fsProps)
} else {
// Explicitly set the partition.
fsProps.Partition_type = proptools.StringPtr(partitionType)
@@ -514,27 +225,80 @@
}
module.HideFromMake()
if partitionType == "vendor" {
- // Create a build prop for vendor
- vendorBuildProps := &struct {
- Name *string
- Vendor *bool
- Stem *string
- Product_config *string
- }{
- Name: proptools.StringPtr(generatedModuleName(ctx.Config(), "vendor-build.prop")),
- Vendor: proptools.BoolPtr(true),
- Stem: proptools.StringPtr("build.prop"),
- Product_config: proptools.StringPtr(":product_config"),
- }
- vendorBuildProp := ctx.CreateModule(
- android.BuildPropFactory,
- vendorBuildProps,
- )
- vendorBuildProp.HideFromMake()
+ f.createVendorBuildProp(ctx)
}
return true
}
+// createPrebuiltKernelModules creates `prebuilt_kernel_modules`. These modules will be added to deps of the
+// autogenerated *_dlkm filsystem modules. Each _dlkm partition should have a single prebuilt_kernel_modules dependency.
+// This ensures that the depmod artifacts (modules.* installed in /lib/modules/) are generated with a complete view.
+
+// The input `kernelModules` is a space separated list of .ko files in the workspace.
+func (f *filesystemCreator) createPrebuiltKernelModules(ctx android.LoadHookContext, partitionType string, kernelModules []string) {
+ fsGenState := ctx.Config().Get(fsGenStateOnceKey).(*FsGenState)
+ name := generatedModuleName(ctx.Config(), fmt.Sprintf("%s-kernel-modules", partitionType))
+ props := &struct {
+ Name *string
+ Srcs []string
+ }{
+ Name: proptools.StringPtr(name),
+ Srcs: kernelModules,
+ }
+ kernelModule := ctx.CreateModuleInDirectory(
+ kernel.PrebuiltKernelModulesFactory,
+ ".", // create in root directory for now
+ props,
+ )
+ kernelModule.HideFromMake()
+ // Add to deps
+ (*fsGenState.fsDeps[partitionType])[name] = defaultDepCandidateProps(ctx.Config())
+}
+
+// Create a build_prop and android_info module. This will be used to create /vendor/build.prop
+func (f *filesystemCreator) createVendorBuildProp(ctx android.LoadHookContext) {
+ // Create a android_info for vendor
+ // The board info files might be in a directory outside the root soong namespace, so create
+ // the module in "."
+ partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
+ androidInfoProps := &struct {
+ Name *string
+ Board_info_files []string
+ Bootloader_board_name *string
+ }{
+ Name: proptools.StringPtr(generatedModuleName(ctx.Config(), "android-info.prop")),
+ Board_info_files: partitionVars.BoardInfoFiles,
+ }
+ if len(androidInfoProps.Board_info_files) == 0 {
+ androidInfoProps.Bootloader_board_name = proptools.StringPtr(partitionVars.BootLoaderBoardName)
+ }
+ androidInfoProp := ctx.CreateModuleInDirectory(
+ android.AndroidInfoFactory,
+ ".",
+ androidInfoProps,
+ )
+ androidInfoProp.HideFromMake()
+ // Create a build prop for vendor
+ vendorBuildProps := &struct {
+ Name *string
+ Vendor *bool
+ Stem *string
+ Product_config *string
+ Android_info *string
+ }{
+ Name: proptools.StringPtr(generatedModuleName(ctx.Config(), "vendor-build.prop")),
+ Vendor: proptools.BoolPtr(true),
+ Stem: proptools.StringPtr("build.prop"),
+ Product_config: proptools.StringPtr(":product_config"),
+ Android_info: proptools.StringPtr(":" + androidInfoProp.Name()),
+ }
+ vendorBuildProp := ctx.CreateModule(
+ android.BuildPropFactory,
+ vendorBuildProps,
+ )
+ vendorBuildProp.HideFromMake()
+}
+
// createLinkerConfigSourceFilegroups creates filegroup modules to generate linker.config.pb for the following partitions
// 1. vendor: Using PRODUCT_VENDOR_LINKER_CONFIG_FRAGMENTS (space separated file list)
// 1. product: Using PRODUCT_PRODUCT_LINKER_CONFIG_FRAGMENTS (space separated file list)
@@ -728,9 +492,6 @@
if !fsTypeSupported {
return ""
}
- if partitionType == "vendor" || partitionType == "odm" {
- return "" // TODO: Handle struct props
- }
baseProps := generateBaseProps(proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), partitionType)))
deps := ctx.Config().Get(fsGenStateOnceKey).(*FsGenState).fsDeps[partitionType]
@@ -738,7 +499,8 @@
result, err := proptools.RepackProperties([]interface{}{baseProps, fsProps, depProps})
if err != nil {
- ctx.ModuleErrorf(err.Error())
+ ctx.ModuleErrorf("%s", err.Error())
+ return ""
}
moduleType := "android_filesystem"
diff --git a/fsgen/fsgen_mutators.go b/fsgen/fsgen_mutators.go
new file mode 100644
index 0000000..f3aec00
--- /dev/null
+++ b/fsgen/fsgen_mutators.go
@@ -0,0 +1,343 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// 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 fsgen
+
+import (
+ "fmt"
+ "slices"
+ "sync"
+
+ "android/soong/android"
+
+ "github.com/google/blueprint/proptools"
+)
+
+func RegisterCollectFileSystemDepsMutators(ctx android.RegisterMutatorsContext) {
+ ctx.BottomUp("fs_collect_deps", collectDepsMutator).MutatesGlobalState()
+ ctx.BottomUp("fs_set_deps", setDepsMutator)
+}
+
+var fsGenStateOnceKey = android.NewOnceKey("FsGenState")
+var fsGenRemoveOverridesOnceKey = android.NewOnceKey("FsGenRemoveOverrides")
+
+// Map of partition module name to its partition that may be generated by Soong.
+// Note that it is not guaranteed that all modules returned by this function are successfully
+// created.
+func getAllSoongGeneratedPartitionNames(config android.Config, partitions []string) map[string]string {
+ ret := map[string]string{}
+ for _, partition := range partitions {
+ ret[generatedModuleNameForPartition(config, partition)] = partition
+ }
+ return ret
+}
+
+type depCandidateProps struct {
+ Namespace string
+ Multilib string
+ Arch []android.ArchType
+}
+
+// Map of module name to depCandidateProps
+type multilibDeps map[string]*depCandidateProps
+
+// Information necessary to generate the filesystem modules, including details about their
+// dependencies
+type FsGenState struct {
+ // List of modules in `PRODUCT_PACKAGES` and `PRODUCT_PACKAGES_DEBUG`
+ depCandidates []string
+ // Map of names of partition to the information of modules to be added as deps
+ fsDeps map[string]*multilibDeps
+ // List of name of partitions to be generated by the filesystem_creator module
+ soongGeneratedPartitions []string
+ // Mutex to protect the fsDeps
+ fsDepsMutex sync.Mutex
+ // Map of _all_ soong module names to their corresponding installation properties
+ moduleToInstallationProps map[string]installationProperties
+}
+
+type installationProperties struct {
+ Required []string
+ Overrides []string
+}
+
+func defaultDepCandidateProps(config android.Config) *depCandidateProps {
+ return &depCandidateProps{
+ Namespace: ".",
+ Arch: []android.ArchType{config.BuildArch},
+ }
+}
+
+func generatedPartitions(ctx android.LoadHookContext) []string {
+ generatedPartitions := []string{"system"}
+ if ctx.DeviceConfig().SystemExtPath() == "system_ext" {
+ generatedPartitions = append(generatedPartitions, "system_ext")
+ }
+ if ctx.DeviceConfig().BuildingVendorImage() && ctx.DeviceConfig().VendorPath() == "vendor" {
+ generatedPartitions = append(generatedPartitions, "vendor")
+ }
+ if ctx.DeviceConfig().BuildingProductImage() && ctx.DeviceConfig().ProductPath() == "product" {
+ generatedPartitions = append(generatedPartitions, "product")
+ }
+ if ctx.DeviceConfig().BuildingOdmImage() && ctx.DeviceConfig().OdmPath() == "odm" {
+ generatedPartitions = append(generatedPartitions, "odm")
+ }
+ if ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.BuildingSystemDlkmImage {
+ generatedPartitions = append(generatedPartitions, "system_dlkm")
+ }
+ return generatedPartitions
+}
+
+func createFsGenState(ctx android.LoadHookContext, generatedPrebuiltEtcModuleNames []string) *FsGenState {
+ return ctx.Config().Once(fsGenStateOnceKey, func() interface{} {
+ partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
+ candidates := android.FirstUniqueStrings(android.Concat(partitionVars.ProductPackages, partitionVars.ProductPackagesDebug))
+ candidates = android.Concat(candidates, generatedPrebuiltEtcModuleNames)
+
+ return &FsGenState{
+ depCandidates: candidates,
+ fsDeps: map[string]*multilibDeps{
+ // These additional deps are added according to the cuttlefish system image bp.
+ "system": {
+ "com.android.apex.cts.shim.v1_prebuilt": defaultDepCandidateProps(ctx.Config()),
+ "dex_bootjars": defaultDepCandidateProps(ctx.Config()),
+ "framework_compatibility_matrix.device.xml": defaultDepCandidateProps(ctx.Config()),
+ "init.environ.rc-soong": defaultDepCandidateProps(ctx.Config()),
+ "libcompiler_rt": defaultDepCandidateProps(ctx.Config()),
+ "libdmabufheap": defaultDepCandidateProps(ctx.Config()),
+ "libgsi": defaultDepCandidateProps(ctx.Config()),
+ "llndk.libraries.txt": defaultDepCandidateProps(ctx.Config()),
+ "logpersist.start": defaultDepCandidateProps(ctx.Config()),
+ "update_engine_sideload": defaultDepCandidateProps(ctx.Config()),
+ },
+ "vendor": {
+ "fs_config_files_vendor": defaultDepCandidateProps(ctx.Config()),
+ "fs_config_dirs_vendor": defaultDepCandidateProps(ctx.Config()),
+ generatedModuleName(ctx.Config(), "vendor-build.prop"): defaultDepCandidateProps(ctx.Config()),
+ },
+ "odm": {
+ // fs_config_* files are automatically installed for all products with odm partitions.
+ // https://cs.android.com/android/_/android/platform/build/+/e4849e87ab660b59a6501b3928693db065ee873b:tools/fs_config/Android.mk;l=34;drc=8d6481b92c4b4e9b9f31a61545b6862090fcc14b;bpv=1;bpt=0
+ "fs_config_files_odm": defaultDepCandidateProps(ctx.Config()),
+ "fs_config_dirs_odm": defaultDepCandidateProps(ctx.Config()),
+ },
+ "product": {},
+ "system_ext": {
+ // VNDK apexes are automatically included.
+ // This hardcoded list will need to be updated if `PRODUCT_EXTRA_VNDK_VERSIONS` is updated.
+ // https://cs.android.com/android/_/android/platform/build/+/adba533072b00c53ac0f198c550a3cbd7a00e4cd:core/main.mk;l=984;bpv=1;bpt=0;drc=174db7b179592cf07cbfd2adb0119486fda911e7
+ "com.android.vndk.v30": defaultDepCandidateProps(ctx.Config()),
+ "com.android.vndk.v31": defaultDepCandidateProps(ctx.Config()),
+ "com.android.vndk.v32": defaultDepCandidateProps(ctx.Config()),
+ "com.android.vndk.v33": defaultDepCandidateProps(ctx.Config()),
+ "com.android.vndk.v34": defaultDepCandidateProps(ctx.Config()),
+ },
+ "system_dlkm": {},
+ },
+ soongGeneratedPartitions: generatedPartitions(ctx),
+ fsDepsMutex: sync.Mutex{},
+ moduleToInstallationProps: map[string]installationProperties{},
+ }
+ }).(*FsGenState)
+}
+
+func checkDepModuleInMultipleNamespaces(mctx android.BottomUpMutatorContext, foundDeps multilibDeps, module string, partitionName string) {
+ otherNamespace := mctx.Namespace().Path
+ if val, found := foundDeps[module]; found && otherNamespace != "." && !android.InList(val.Namespace, []string{".", otherNamespace}) {
+ mctx.ModuleErrorf("found in multiple namespaces(%s and %s) when including in %s partition", val.Namespace, otherNamespace, partitionName)
+ }
+}
+
+func appendDepIfAppropriate(mctx android.BottomUpMutatorContext, deps *multilibDeps, installPartition string) {
+ moduleName := mctx.ModuleName()
+ checkDepModuleInMultipleNamespaces(mctx, *deps, moduleName, installPartition)
+ if _, ok := (*deps)[moduleName]; ok {
+ // Prefer the namespace-specific module over the platform module
+ if mctx.Namespace().Path != "." {
+ (*deps)[moduleName].Namespace = mctx.Namespace().Path
+ }
+ (*deps)[moduleName].Arch = append((*deps)[moduleName].Arch, mctx.Module().Target().Arch.ArchType)
+ } else {
+ multilib, _ := mctx.Module().DecodeMultilib(mctx)
+ (*deps)[moduleName] = &depCandidateProps{
+ Namespace: mctx.Namespace().Path,
+ Multilib: multilib,
+ Arch: []android.ArchType{mctx.Module().Target().Arch.ArchType},
+ }
+ }
+}
+
+func collectDepsMutator(mctx android.BottomUpMutatorContext) {
+ fsGenState := mctx.Config().Get(fsGenStateOnceKey).(*FsGenState)
+
+ m := mctx.Module()
+ if m.Target().Os.Class == android.Device && slices.Contains(fsGenState.depCandidates, mctx.ModuleName()) {
+ installPartition := m.PartitionTag(mctx.DeviceConfig())
+ fsGenState.fsDepsMutex.Lock()
+ // Only add the module as dependency when:
+ // - its enabled
+ // - its namespace is included in PRODUCT_SOONG_NAMESPACES
+ if m.Enabled(mctx) && m.ExportedToMake() {
+ appendDepIfAppropriate(mctx, fsGenState.fsDeps[installPartition], installPartition)
+ }
+ fsGenState.fsDepsMutex.Unlock()
+ }
+ // store the map of module to (required,overrides) even if the module is not in PRODUCT_PACKAGES.
+ // the module might be installed transitively.
+ if m.Target().Os.Class == android.Device && m.Enabled(mctx) && m.ExportedToMake() {
+ fsGenState.fsDepsMutex.Lock()
+ fsGenState.moduleToInstallationProps[m.Name()] = installationProperties{
+ Required: m.RequiredModuleNames(mctx),
+ Overrides: m.Overrides(),
+ }
+ fsGenState.fsDepsMutex.Unlock()
+ }
+}
+
+type depsStruct struct {
+ Deps []string
+}
+
+type multilibDepsStruct struct {
+ Common depsStruct
+ Lib32 depsStruct
+ Lib64 depsStruct
+ Both depsStruct
+ Prefer32 depsStruct
+}
+
+type packagingPropsStruct struct {
+ High_priority_deps []string
+ Deps []string
+ Multilib multilibDepsStruct
+}
+
+func fullyQualifiedModuleName(moduleName, namespace string) string {
+ if namespace == "." {
+ return moduleName
+ }
+ return fmt.Sprintf("//%s:%s", namespace, moduleName)
+}
+
+func getBitness(archTypes []android.ArchType) (ret []string) {
+ for _, archType := range archTypes {
+ if archType.Multilib == "" {
+ ret = append(ret, android.COMMON_VARIANT)
+ } else {
+ ret = append(ret, archType.Bitness())
+ }
+ }
+ return ret
+}
+
+func setDepsMutator(mctx android.BottomUpMutatorContext) {
+ removeOverriddenDeps(mctx)
+ fsGenState := mctx.Config().Get(fsGenStateOnceKey).(*FsGenState)
+ fsDeps := fsGenState.fsDeps
+ soongGeneratedPartitionMap := getAllSoongGeneratedPartitionNames(mctx.Config(), fsGenState.soongGeneratedPartitions)
+ m := mctx.Module()
+ if partition, ok := soongGeneratedPartitionMap[m.Name()]; ok {
+ depsStruct := generateDepStruct(*fsDeps[partition])
+ if err := proptools.AppendMatchingProperties(m.GetProperties(), depsStruct, nil); err != nil {
+ mctx.ModuleErrorf(err.Error())
+ }
+ }
+}
+
+// removeOverriddenDeps collects PRODUCT_PACKAGES and (transitive) required deps.
+// it then removes any modules which appear in `overrides` of the above list.
+func removeOverriddenDeps(mctx android.BottomUpMutatorContext) {
+ mctx.Config().Once(fsGenRemoveOverridesOnceKey, func() interface{} {
+ fsGenState := mctx.Config().Get(fsGenStateOnceKey).(*FsGenState)
+ fsDeps := fsGenState.fsDeps
+ overridden := map[string]bool{}
+ allDeps := []string{}
+
+ // Step 1: Initialization: Append PRODUCT_PACKAGES to the queue
+ for _, fsDep := range fsDeps {
+ for depName, _ := range *fsDep {
+ allDeps = append(allDeps, depName)
+ }
+ }
+
+ // Step 2: Process the queue, and add required modules to the queue.
+ i := 0
+ for {
+ if i == len(allDeps) {
+ break
+ }
+ depName := allDeps[i]
+ for _, overrides := range fsGenState.moduleToInstallationProps[depName].Overrides {
+ overridden[overrides] = true
+ }
+ // add required dep to the queue.
+ allDeps = append(allDeps, fsGenState.moduleToInstallationProps[depName].Required...)
+ i += 1
+ }
+
+ // Step 3: Delete all the overridden modules.
+ for overridden, _ := range overridden {
+ for partition, _ := range fsDeps {
+ delete(*fsDeps[partition], overridden)
+ }
+ }
+ return nil
+ })
+}
+
+var HighPriorityDeps = []string{}
+
+func generateDepStruct(deps map[string]*depCandidateProps) *packagingPropsStruct {
+ depsStruct := packagingPropsStruct{}
+ for depName, depProps := range deps {
+ bitness := getBitness(depProps.Arch)
+ fullyQualifiedDepName := fullyQualifiedModuleName(depName, depProps.Namespace)
+ if android.InList(depName, HighPriorityDeps) {
+ depsStruct.High_priority_deps = append(depsStruct.High_priority_deps, fullyQualifiedDepName)
+ } else if android.InList("32", bitness) && android.InList("64", bitness) {
+ // If both 32 and 64 bit variants are enabled for this module
+ switch depProps.Multilib {
+ case string(android.MultilibBoth):
+ depsStruct.Multilib.Both.Deps = append(depsStruct.Multilib.Both.Deps, fullyQualifiedDepName)
+ case string(android.MultilibCommon), string(android.MultilibFirst):
+ depsStruct.Deps = append(depsStruct.Deps, fullyQualifiedDepName)
+ case "32":
+ depsStruct.Multilib.Lib32.Deps = append(depsStruct.Multilib.Lib32.Deps, fullyQualifiedDepName)
+ case "64", "darwin_universal":
+ depsStruct.Multilib.Lib64.Deps = append(depsStruct.Multilib.Lib64.Deps, fullyQualifiedDepName)
+ case "prefer32", "first_prefer32":
+ depsStruct.Multilib.Prefer32.Deps = append(depsStruct.Multilib.Prefer32.Deps, fullyQualifiedDepName)
+ default:
+ depsStruct.Multilib.Both.Deps = append(depsStruct.Multilib.Both.Deps, fullyQualifiedDepName)
+ }
+ } else if android.InList("64", bitness) {
+ // If only 64 bit variant is enabled
+ depsStruct.Multilib.Lib64.Deps = append(depsStruct.Multilib.Lib64.Deps, fullyQualifiedDepName)
+ } else if android.InList("32", bitness) {
+ // If only 32 bit variant is enabled
+ depsStruct.Multilib.Lib32.Deps = append(depsStruct.Multilib.Lib32.Deps, fullyQualifiedDepName)
+ } else {
+ // If only common variant is enabled
+ depsStruct.Multilib.Common.Deps = append(depsStruct.Multilib.Common.Deps, fullyQualifiedDepName)
+ }
+ }
+ depsStruct.Deps = android.SortedUniqueStrings(depsStruct.Deps)
+ depsStruct.Multilib.Lib32.Deps = android.SortedUniqueStrings(depsStruct.Multilib.Lib32.Deps)
+ depsStruct.Multilib.Lib64.Deps = android.SortedUniqueStrings(depsStruct.Multilib.Lib64.Deps)
+ depsStruct.Multilib.Prefer32.Deps = android.SortedUniqueStrings(depsStruct.Multilib.Prefer32.Deps)
+ depsStruct.Multilib.Both.Deps = android.SortedUniqueStrings(depsStruct.Multilib.Both.Deps)
+ depsStruct.Multilib.Common.Deps = android.SortedUniqueStrings(depsStruct.Multilib.Common.Deps)
+
+ return &depsStruct
+}
diff --git a/fsgen/prebuilt_etc_modules_gen.go b/fsgen/prebuilt_etc_modules_gen.go
new file mode 100644
index 0000000..362ac31
--- /dev/null
+++ b/fsgen/prebuilt_etc_modules_gen.go
@@ -0,0 +1,300 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// 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 fsgen
+
+import (
+ "android/soong/android"
+ "android/soong/etc"
+ "fmt"
+ "path/filepath"
+ "strings"
+
+ "github.com/google/blueprint/proptools"
+)
+
+type srcBaseFileInstallBaseFileTuple struct {
+ srcBaseFile string
+ installBaseFile string
+}
+
+// prebuilt src files grouped by the install partitions.
+// Each groups are a mapping of the relative install path to the name of the files
+type prebuiltSrcGroupByInstallPartition struct {
+ system map[string][]srcBaseFileInstallBaseFileTuple
+ system_ext map[string][]srcBaseFileInstallBaseFileTuple
+ product map[string][]srcBaseFileInstallBaseFileTuple
+ vendor map[string][]srcBaseFileInstallBaseFileTuple
+}
+
+func newPrebuiltSrcGroupByInstallPartition() *prebuiltSrcGroupByInstallPartition {
+ return &prebuiltSrcGroupByInstallPartition{
+ system: map[string][]srcBaseFileInstallBaseFileTuple{},
+ system_ext: map[string][]srcBaseFileInstallBaseFileTuple{},
+ product: map[string][]srcBaseFileInstallBaseFileTuple{},
+ vendor: map[string][]srcBaseFileInstallBaseFileTuple{},
+ }
+}
+
+func isSubdirectory(parent, child string) bool {
+ rel, err := filepath.Rel(parent, child)
+ if err != nil {
+ return false
+ }
+ return !strings.HasPrefix(rel, "..")
+}
+
+func appendIfCorrectInstallPartition(partitionToInstallPathList []partitionToInstallPath, destPath, srcPath string, srcGroup *prebuiltSrcGroupByInstallPartition) {
+ for _, part := range partitionToInstallPathList {
+ partition := part.name
+ installPath := part.installPath
+
+ if isSubdirectory(installPath, destPath) {
+ relativeInstallPath, _ := filepath.Rel(installPath, destPath)
+ relativeInstallDir := filepath.Dir(relativeInstallPath)
+ var srcMap map[string][]srcBaseFileInstallBaseFileTuple
+ switch partition {
+ case "system":
+ srcMap = srcGroup.system
+ case "system_ext":
+ srcMap = srcGroup.system_ext
+ case "product":
+ srcMap = srcGroup.product
+ case "vendor":
+ srcMap = srcGroup.vendor
+ }
+ if srcMap != nil {
+ srcMap[relativeInstallDir] = append(srcMap[relativeInstallDir], srcBaseFileInstallBaseFileTuple{
+ srcBaseFile: filepath.Base(srcPath),
+ installBaseFile: filepath.Base(destPath),
+ })
+ }
+ return
+ }
+ }
+}
+
+func uniqueExistingProductCopyFileMap(ctx android.LoadHookContext) map[string]string {
+ seen := make(map[string]bool)
+ filtered := make(map[string]string)
+
+ for src, dest := range ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.ProductCopyFiles {
+ if _, ok := seen[dest]; !ok {
+ if optionalPath := android.ExistentPathForSource(ctx, src); optionalPath.Valid() {
+ seen[dest] = true
+ filtered[src] = dest
+ }
+ }
+ }
+
+ return filtered
+}
+
+type partitionToInstallPath struct {
+ name string
+ installPath string
+}
+
+func processProductCopyFiles(ctx android.LoadHookContext) map[string]*prebuiltSrcGroupByInstallPartition {
+ // Filter out duplicate dest entries and non existing src entries
+ productCopyFileMap := uniqueExistingProductCopyFileMap(ctx)
+
+ // System is intentionally added at the last to consider the scenarios where
+ // non-system partitions are installed as part of the system partition
+ partitionToInstallPathList := []partitionToInstallPath{
+ {name: "vendor", installPath: ctx.DeviceConfig().VendorPath()},
+ {name: "product", installPath: ctx.DeviceConfig().ProductPath()},
+ {name: "system_ext", installPath: ctx.DeviceConfig().SystemExtPath()},
+ {name: "system", installPath: "system"},
+ }
+
+ groupedSources := map[string]*prebuiltSrcGroupByInstallPartition{}
+ for _, src := range android.SortedKeys(productCopyFileMap) {
+ dest := productCopyFileMap[src]
+ srcFileDir := filepath.Dir(src)
+ if _, ok := groupedSources[srcFileDir]; !ok {
+ groupedSources[srcFileDir] = newPrebuiltSrcGroupByInstallPartition()
+ }
+ appendIfCorrectInstallPartition(partitionToInstallPathList, dest, filepath.Base(src), groupedSources[srcFileDir])
+ }
+
+ return groupedSources
+}
+
+type prebuiltModuleProperties struct {
+ Name *string
+
+ Soc_specific *bool
+ Product_specific *bool
+ System_ext_specific *bool
+
+ Srcs []string
+ Dsts []string
+
+ No_full_install *bool
+
+ NamespaceExportedToMake bool
+
+ Visibility []string
+}
+
+// Split relative_install_path to a separate struct, because it is not supported for every
+// modules listed in [etcInstallPathToFactoryMap]
+type prebuiltSubdirProperties struct {
+ // If the base file name of the src and dst all match, dsts property does not need to be
+ // set, and only relative_install_path can be set.
+ Relative_install_path *string
+}
+
+var (
+ etcInstallPathToFactoryList = map[string]android.ModuleFactory{
+ "": etc.PrebuiltRootFactory,
+ "avb": etc.PrebuiltAvbFactory,
+ "bin": etc.PrebuiltBinaryFactory,
+ "bt_firmware": etc.PrebuiltBtFirmwareFactory,
+ "cacerts": etc.PrebuiltEtcCaCertsFactory,
+ "dsp": etc.PrebuiltDSPFactory,
+ "etc": etc.PrebuiltEtcFactory,
+ "etc/dsp": etc.PrebuiltDSPFactory,
+ "etc/firmware": etc.PrebuiltFirmwareFactory,
+ "firmware": etc.PrebuiltFirmwareFactory,
+ "fonts": etc.PrebuiltFontFactory,
+ "framework": etc.PrebuiltFrameworkFactory,
+ "lib": etc.PrebuiltRenderScriptBitcodeFactory,
+ "lib64": etc.PrebuiltRenderScriptBitcodeFactory,
+ "lib/rfsa": etc.PrebuiltRFSAFactory,
+ "media": etc.PrebuiltMediaFactory,
+ "odm": etc.PrebuiltOdmFactory,
+ "optee": etc.PrebuiltOpteeFactory,
+ "overlay": etc.PrebuiltOverlayFactory,
+ "priv-app": etc.PrebuiltPrivAppFactory,
+ "res": etc.PrebuiltResFactory,
+ "rfs": etc.PrebuiltRfsFactory,
+ "tts": etc.PrebuiltVoicepackFactory,
+ "tvservice": etc.PrebuiltTvServiceFactory,
+ "usr/share": etc.PrebuiltUserShareFactory,
+ "usr/hyphen-data": etc.PrebuiltUserHyphenDataFactory,
+ "usr/keylayout": etc.PrebuiltUserKeyLayoutFactory,
+ "usr/keychars": etc.PrebuiltUserKeyCharsFactory,
+ "usr/srec": etc.PrebuiltUserSrecFactory,
+ "usr/idc": etc.PrebuiltUserIdcFactory,
+ "vendor_dlkm": etc.PrebuiltVendorDlkmFactory,
+ "wallpaper": etc.PrebuiltWallpaperFactory,
+ "wlc_upt": etc.PrebuiltWlcUptFactory,
+ }
+)
+
+func createPrebuiltEtcModule(ctx android.LoadHookContext, partition, srcDir, destDir string, destFiles []srcBaseFileInstallBaseFileTuple) string {
+ moduleProps := &prebuiltModuleProperties{}
+ propsList := []interface{}{moduleProps}
+
+ // generated module name follows the pattern:
+ // <install partition>-<src file path>-<relative install path from partition root>-<install file extension>
+ // Note that all path separators are replaced with "_" in the name
+ moduleName := partition
+ if !android.InList(srcDir, []string{"", "."}) {
+ moduleName += fmt.Sprintf("-%s", strings.ReplaceAll(srcDir, string(filepath.Separator), "_"))
+ }
+ if !android.InList(destDir, []string{"", "."}) {
+ moduleName += fmt.Sprintf("-%s", strings.ReplaceAll(destDir, string(filepath.Separator), "_"))
+ }
+ if len(destFiles) > 0 {
+ if ext := filepath.Ext(destFiles[0].srcBaseFile); ext != "" {
+ moduleName += fmt.Sprintf("-%s", strings.TrimPrefix(ext, "."))
+ }
+ }
+ moduleProps.Name = proptools.StringPtr(moduleName)
+
+ allCopyFileNamesUnchanged := true
+ var srcBaseFiles, installBaseFiles []string
+ for _, tuple := range destFiles {
+ if tuple.srcBaseFile != tuple.installBaseFile {
+ allCopyFileNamesUnchanged = false
+ }
+ srcBaseFiles = append(srcBaseFiles, tuple.srcBaseFile)
+ installBaseFiles = append(installBaseFiles, tuple.installBaseFile)
+ }
+
+ // Find out the most appropriate module type to generate
+ var etcInstallPathKey string
+ for _, etcInstallPath := range android.SortedKeys(etcInstallPathToFactoryList) {
+ // Do not break when found but iterate until the end to find a module with more
+ // specific install path
+ if strings.HasPrefix(destDir, etcInstallPath) {
+ etcInstallPathKey = etcInstallPath
+ }
+ }
+ destDir, _ = filepath.Rel(etcInstallPathKey, destDir)
+
+ // Set partition specific properties
+ switch partition {
+ case "system_ext":
+ moduleProps.System_ext_specific = proptools.BoolPtr(true)
+ case "product":
+ moduleProps.Product_specific = proptools.BoolPtr(true)
+ case "vendor":
+ moduleProps.Soc_specific = proptools.BoolPtr(true)
+ }
+
+ // Set appropriate srcs, dsts, and releative_install_path based on
+ // the source and install file names
+ if allCopyFileNamesUnchanged {
+ moduleProps.Srcs = srcBaseFiles
+
+ // Specify relative_install_path if it is not installed in the root directory of the
+ // partition
+ if !android.InList(destDir, []string{"", "."}) {
+ propsList = append(propsList, &prebuiltSubdirProperties{
+ Relative_install_path: proptools.StringPtr(destDir),
+ })
+ }
+ } else {
+ moduleProps.Srcs = srcBaseFiles
+ dsts := []string{}
+ for _, installBaseFile := range installBaseFiles {
+ dsts = append(dsts, filepath.Join(destDir, installBaseFile))
+ }
+ moduleProps.Dsts = dsts
+ }
+
+ moduleProps.No_full_install = proptools.BoolPtr(true)
+ moduleProps.NamespaceExportedToMake = true
+ moduleProps.Visibility = []string{"//visibility:public"}
+
+ ctx.CreateModuleInDirectory(etcInstallPathToFactoryList[etcInstallPathKey], srcDir, propsList...)
+
+ return moduleName
+}
+
+func createPrebuiltEtcModulesForPartition(ctx android.LoadHookContext, partition, srcDir string, destDirFilesMap map[string][]srcBaseFileInstallBaseFileTuple) (ret []string) {
+ for _, destDir := range android.SortedKeys(destDirFilesMap) {
+ ret = append(ret, createPrebuiltEtcModule(ctx, partition, srcDir, destDir, destDirFilesMap[destDir]))
+ }
+ return ret
+}
+
+// Creates prebuilt_* modules based on the install paths and returns the list of generated
+// module names
+func createPrebuiltEtcModules(ctx android.LoadHookContext) (ret []string) {
+ groupedSources := processProductCopyFiles(ctx)
+ for _, srcDir := range android.SortedKeys(groupedSources) {
+ groupedSource := groupedSources[srcDir]
+ ret = append(ret, createPrebuiltEtcModulesForPartition(ctx, "system", srcDir, groupedSource.system)...)
+ ret = append(ret, createPrebuiltEtcModulesForPartition(ctx, "system_ext", srcDir, groupedSource.system_ext)...)
+ ret = append(ret, createPrebuiltEtcModulesForPartition(ctx, "product", srcDir, groupedSource.product)...)
+ ret = append(ret, createPrebuiltEtcModulesForPartition(ctx, "vendor", srcDir, groupedSource.vendor)...)
+ }
+
+ return ret
+}
diff --git a/genrule/genrule.go b/genrule/genrule.go
index 1ab1378..1282bfb 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -231,7 +231,7 @@
// For nsjail tasks
useNsjail bool
- dirSrcs android.Paths
+ dirSrcs android.DirectoryPaths
}
func (g *Module) GeneratedSourceFiles() android.Paths {
@@ -604,7 +604,8 @@
if task.useNsjail {
for _, input := range task.dirSrcs {
- cmd.Implicit(input)
+ cmd.ImplicitDirectory(input)
+ // TODO(b/375551969): remove glob
if paths, err := ctx.GlobWithDeps(filepath.Join(input.String(), "**/*"), nil); err == nil {
rule.NsjailImplicits(android.PathsForSource(ctx, paths))
} else {
diff --git a/java/aar.go b/java/aar.go
index 66ca00a..b5cdde3 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -77,7 +77,7 @@
// list of directories relative to the Blueprints file containing
// Android resources. Defaults to ["res"] if a directory called res exists.
// Set to [] to disable the default.
- Resource_dirs []string `android:"path"`
+ Resource_dirs proptools.Configurable[[]string] `android:"path"`
// list of zip files containing Android resources.
Resource_zips []string `android:"path"`
@@ -275,7 +275,7 @@
IncludeDirs: false,
})
assetDirs := android.PathsWithOptionalDefaultForModuleSrc(ctx, a.aaptProperties.Asset_dirs, "assets")
- resourceDirs := android.PathsWithOptionalDefaultForModuleSrc(ctx, a.aaptProperties.Resource_dirs, "res")
+ resourceDirs := android.PathsWithOptionalDefaultForModuleSrc(ctx, a.aaptProperties.Resource_dirs.GetOrDefault(ctx, nil), "res")
resourceZips := android.PathsForModuleSrc(ctx, a.aaptProperties.Resource_zips)
// Glob directories into lists of paths
diff --git a/java/app.go b/java/app.go
index e01a2ba..94c9e5b 100644
--- a/java/app.go
+++ b/java/app.go
@@ -67,6 +67,9 @@
// TestHelperApp is true if the module is a android_test_helper_app
TestHelperApp bool
+
+ // EmbeddedJNILibs is the list of paths to JNI libraries that were embedded in the APK.
+ EmbeddedJNILibs android.Paths
}
var AppInfoProvider = blueprint.NewProvider[*AppInfo]()
@@ -405,9 +408,18 @@
a.checkEmbedJnis(ctx)
a.generateAndroidBuildActions(ctx)
a.generateJavaUsedByApex(ctx)
+
+ var embeddedJniLibs []android.Path
+
+ if a.embeddedJniLibs {
+ for _, jni := range a.jniLibs {
+ embeddedJniLibs = append(embeddedJniLibs, jni.path)
+ }
+ }
android.SetProvider(ctx, AppInfoProvider, &AppInfo{
- Updatable: Bool(a.appProperties.Updatable),
- TestHelperApp: false,
+ Updatable: Bool(a.appProperties.Updatable),
+ TestHelperApp: false,
+ EmbeddedJNILibs: embeddedJniLibs,
})
}
@@ -1070,12 +1082,12 @@
app.SdkVersion(ctx).Kind != android.SdkCorePlatform && !app.RequiresStableAPIs(ctx)
}
jniLib, prebuiltJniPackages := collectJniDeps(ctx, shouldCollectRecursiveNativeDeps,
- checkNativeSdkVersion, func(dep cc.LinkableInterface) bool {
- return !dep.IsNdk(ctx.Config()) && !dep.IsStubs()
- })
+ checkNativeSdkVersion, func(dep cc.LinkableInterface) bool { return !dep.IsNdk(ctx.Config()) && !dep.IsStubs() })
var certificates []Certificate
+ var directImplementationDeps android.Paths
+ var transitiveImplementationDeps []depset.DepSet[android.Path]
ctx.VisitDirectDeps(func(module android.Module) {
otherName := ctx.OtherModuleName(module)
tag := ctx.OtherModuleDependencyTag(module)
@@ -1087,7 +1099,18 @@
ctx.ModuleErrorf("certificate dependency %q must be an android_app_certificate module", otherName)
}
}
+
+ if IsJniDepTag(tag) {
+ directImplementationDeps = append(directImplementationDeps, android.OutputFileForModule(ctx, module, ""))
+ if info, ok := android.OtherModuleProvider(ctx, module, cc.ImplementationDepInfoProvider); ok {
+ transitiveImplementationDeps = append(transitiveImplementationDeps, info.ImplementationDeps)
+ }
+ }
})
+ android.SetProvider(ctx, cc.ImplementationDepInfoProvider, &cc.ImplementationDepInfo{
+ ImplementationDeps: depset.New(depset.PREORDER, directImplementationDeps, transitiveImplementationDeps),
+ })
+
return jniLib, prebuiltJniPackages, certificates
}
@@ -1338,7 +1361,7 @@
Filter_product *string
Aaptflags []string
Manifest *string
- Resource_dirs []string
+ Resource_dirs proptools.Configurable[[]string]
Flags_packages []string
}{
Name: proptools.StringPtr(rroPackageName),
diff --git a/java/java.go b/java/java.go
index 53b3481..1d572fa 100644
--- a/java/java.go
+++ b/java/java.go
@@ -606,10 +606,8 @@
} else if ctx.Device() {
return defaultJavaLanguageVersion(ctx, sdkContext.SdkVersion(ctx))
} else if ctx.Config().TargetsJava21() {
- // Temporary experimental flag to be able to try and build with
- // java version 21 options. The flag, if used, just sets Java
- // 21 as the default version, leaving any components that
- // target an older version intact.
+ // Build flag that controls whether Java 21 is used as the default
+ // target version, or Java 17.
return JAVA_VERSION_21
} else {
return JAVA_VERSION_17
@@ -1610,6 +1608,8 @@
j.data = append(j.data, android.OutputFileForModule(ctx, dep, ""))
})
+ var directImplementationDeps android.Paths
+ var transitiveImplementationDeps []depset.DepSet[android.Path]
ctx.VisitDirectDepsWithTag(jniLibTag, func(dep android.Module) {
sharedLibInfo, _ := android.OtherModuleProvider(ctx, dep, cc.SharedLibraryInfoProvider)
if sharedLibInfo.SharedLibrary != nil {
@@ -1628,11 +1628,20 @@
Output: relocatedLib,
})
j.data = append(j.data, relocatedLib)
+
+ directImplementationDeps = append(directImplementationDeps, android.OutputFileForModule(ctx, dep, ""))
+ if info, ok := android.OtherModuleProvider(ctx, dep, cc.ImplementationDepInfoProvider); ok {
+ transitiveImplementationDeps = append(transitiveImplementationDeps, info.ImplementationDeps)
+ }
} else {
ctx.PropertyErrorf("jni_libs", "%q of type %q is not supported", dep.Name(), ctx.OtherModuleType(dep))
}
})
+ android.SetProvider(ctx, cc.ImplementationDepInfoProvider, &cc.ImplementationDepInfo{
+ ImplementationDeps: depset.New(depset.PREORDER, directImplementationDeps, transitiveImplementationDeps),
+ })
+
j.Library.GenerateAndroidBuildActions(ctx)
}
diff --git a/java/platform_bootclasspath.go b/java/platform_bootclasspath.go
index acfc774..8b0ca97 100644
--- a/java/platform_bootclasspath.go
+++ b/java/platform_bootclasspath.go
@@ -24,7 +24,7 @@
}
func registerPlatformBootclasspathBuildComponents(ctx android.RegistrationContext) {
- ctx.RegisterParallelSingletonModuleType("platform_bootclasspath", platformBootclasspathFactory)
+ ctx.RegisterModuleType("platform_bootclasspath", platformBootclasspathFactory)
}
// The tags used for the dependencies between the platform bootclasspath and any configured boot
@@ -37,7 +37,7 @@
)
type platformBootclasspathModule struct {
- android.SingletonModuleBase
+ android.ModuleBase
ClasspathFragmentBase
properties platformBootclasspathProperties
@@ -64,7 +64,7 @@
HiddenAPIFlagFileProperties
}
-func platformBootclasspathFactory() android.SingletonModule {
+func platformBootclasspathFactory() android.Module {
m := &platformBootclasspathModule{}
m.AddProperties(&m.properties)
initClasspathFragment(m, BOOTCLASSPATH)
@@ -156,18 +156,6 @@
}
}
-// GenerateSingletonBuildActions does nothing and must never do anything.
-//
-// This module only implements android.SingletonModule so that it can implement
-// android.SingletonMakeVarsProvider.
-func (b *platformBootclasspathModule) GenerateSingletonBuildActions(android.SingletonContext) {
- // Keep empty
-}
-
-func (d *platformBootclasspathModule) MakeVars(ctx android.MakeVarsContext) {
- d.generateHiddenApiMakeVars(ctx)
-}
-
func (b *platformBootclasspathModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
// Gather all the dependencies from the art, platform, and apex boot jars.
artModules := gatherApexModulePairDepsWithTag(ctx, platformBootclasspathArtBootJarDepTag)
@@ -428,13 +416,3 @@
rule.Build(desc, desc)
}
-
-// generateHiddenApiMakeVars generates make variables needed by hidden API related make rules, e.g.
-// veridex and run-appcompat.
-func (b *platformBootclasspathModule) generateHiddenApiMakeVars(ctx android.MakeVarsContext) {
- if ctx.Config().IsEnvTrue("UNSAFE_DISABLE_HIDDENAPI_FLAGS") {
- return
- }
- // INTERNAL_PLATFORM_HIDDENAPI_FLAGS is used by Make rules in art/ and cts/.
- ctx.Strict("INTERNAL_PLATFORM_HIDDENAPI_FLAGS", b.hiddenAPIFlagsCSV.String())
-}
diff --git a/java/sdk.go b/java/sdk.go
index 4537f19..036521c 100644
--- a/java/sdk.go
+++ b/java/sdk.go
@@ -65,11 +65,11 @@
return JAVA_VERSION_9
} else if sdk.FinalOrFutureInt() <= 33 {
return JAVA_VERSION_11
+ } else if sdk.FinalOrFutureInt() <= 35 {
+ return JAVA_VERSION_17
} else if ctx.Config().TargetsJava21() {
- // Temporary experimental flag to be able to try and build with
- // java version 21 options. The flag, if used, just sets Java
- // 21 as the default version, leaving any components that
- // target an older version intact.
+ // Build flag that controls whether Java 21 is used as the
+ // default target version, or Java 17.
return JAVA_VERSION_21
} else {
return JAVA_VERSION_17
diff --git a/kernel/prebuilt_kernel_modules.go b/kernel/prebuilt_kernel_modules.go
index e200ee2..4c0a911 100644
--- a/kernel/prebuilt_kernel_modules.go
+++ b/kernel/prebuilt_kernel_modules.go
@@ -32,7 +32,7 @@
}
func registerKernelBuildComponents(ctx android.RegistrationContext) {
- ctx.RegisterModuleType("prebuilt_kernel_modules", prebuiltKernelModulesFactory)
+ ctx.RegisterModuleType("prebuilt_kernel_modules", PrebuiltKernelModulesFactory)
}
type prebuiltKernelModules struct {
@@ -58,7 +58,7 @@
// prebuilt_kernel_modules installs a set of prebuilt kernel module files to the correct directory.
// In addition, this module builds modules.load, modules.dep, modules.softdep and modules.alias
// using depmod and installs them as well.
-func prebuiltKernelModulesFactory() android.Module {
+func PrebuiltKernelModulesFactory() android.Module {
module := &prebuiltKernelModules{}
module.AddProperties(&module.properties)
android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
diff --git a/rust/bindgen.go b/rust/bindgen.go
index 3944495..d590579 100644
--- a/rust/bindgen.go
+++ b/rust/bindgen.go
@@ -252,7 +252,7 @@
// Module defined clang flags and include paths
cflags = append(cflags, esc(cflagsProp)...)
- for _, include := range b.ClangProperties.Local_include_dirs {
+ for _, include := range b.ClangProperties.Local_include_dirs.GetOrDefault(ctx, nil) {
cflags = append(cflags, "-I"+android.PathForModuleSrc(ctx, include).String())
implicits = append(implicits, android.PathForModuleSrc(ctx, include))
}
diff --git a/rust/config/global.go b/rust/config/global.go
index 68a74c2..7b79fca 100644
--- a/rust/config/global.go
+++ b/rust/config/global.go
@@ -15,6 +15,7 @@
package config
import (
+ "fmt"
"strings"
"android/soong/android"
@@ -93,6 +94,16 @@
}
)
+func RustPath(ctx android.PathContext) string {
+ // I can't see any way to flatten the static variable inside Soong, so this
+ // reproduces the init logic.
+ var RustBase string = RustDefaultBase
+ if override := ctx.Config().Getenv("RUST_PREBUILTS_BASE"); override != "" {
+ RustBase = override
+ }
+ return fmt.Sprintf("%s/%s/%s", RustBase, HostPrebuiltTag(ctx.Config()), GetRustVersion(ctx))
+}
+
func init() {
pctx.SourcePathVariable("RustDefaultBase", RustDefaultBase)
pctx.VariableConfigMethod("HostPrebuiltTag", HostPrebuiltTag)
diff --git a/rust/library.go b/rust/library.go
index 20cd2af..bd3359b 100644
--- a/rust/library.go
+++ b/rust/library.go
@@ -617,6 +617,9 @@
TableOfContents: android.OptionalPathForPath(tocFile),
SharedLibrary: outputFile,
Target: ctx.Target(),
+ // TODO: when rust supports stubs uses the stubs state rather than inferring it from
+ // apex_exclude.
+ IsStubs: Bool(library.Properties.Apex_exclude),
})
}
diff --git a/rust/project_json.go b/rust/project_json.go
index 6c1e320..6e8cebe 100644
--- a/rust/project_json.go
+++ b/rust/project_json.go
@@ -19,6 +19,7 @@
"fmt"
"android/soong/android"
+ "android/soong/rust/config"
)
// This singleton collects Rust crate definitions and generates a JSON file
@@ -44,17 +45,19 @@
}
type rustProjectCrate struct {
- DisplayName string `json:"display_name"`
- RootModule string `json:"root_module"`
- Edition string `json:"edition,omitempty"`
- Deps []rustProjectDep `json:"deps"`
- Cfg []string `json:"cfg"`
- Env map[string]string `json:"env"`
- ProcMacro bool `json:"is_proc_macro"`
+ DisplayName string `json:"display_name"`
+ RootModule string `json:"root_module"`
+ Edition string `json:"edition,omitempty"`
+ Deps []rustProjectDep `json:"deps"`
+ Cfg []string `json:"cfg"`
+ Env map[string]string `json:"env"`
+ ProcMacro bool `json:"is_proc_macro"`
+ ProcMacroDylib *string `json:"proc_macro_dylib_path"`
}
type rustProjectJson struct {
- Crates []rustProjectCrate `json:"crates"`
+ Sysroot string `json:"sysroot"`
+ Crates []rustProjectCrate `json:"crates"`
}
// crateInfo is used during the processing to keep track of the known crates.
@@ -135,16 +138,21 @@
return 0, false
}
- _, procMacro := rModule.compiler.(*procMacroDecorator)
+ var procMacroDylib *string = nil
+ if procDec, procMacro := rModule.compiler.(*procMacroDecorator); procMacro {
+ procMacroDylib = new(string)
+ *procMacroDylib = procDec.baseCompiler.unstrippedOutputFilePath().String()
+ }
crate := rustProjectCrate{
- DisplayName: rModule.Name(),
- RootModule: rootModule.String(),
- Edition: rModule.compiler.edition(),
- Deps: make([]rustProjectDep, 0),
- Cfg: make([]string, 0),
- Env: make(map[string]string),
- ProcMacro: procMacro,
+ DisplayName: rModule.Name(),
+ RootModule: rootModule.String(),
+ Edition: rModule.compiler.edition(),
+ Deps: make([]rustProjectDep, 0),
+ Cfg: make([]string, 0),
+ Env: make(map[string]string),
+ ProcMacro: procMacroDylib != nil,
+ ProcMacroDylib: procMacroDylib,
}
if rModule.compiler.cargoOutDir().Valid() {
@@ -197,6 +205,8 @@
return
}
+ singleton.project.Sysroot = config.RustPath(ctx)
+
singleton.knownCrates = make(map[string]crateInfo)
ctx.VisitAllModules(func(module android.Module) {
singleton.appendCrateAndDependencies(ctx, module)
diff --git a/rust/rust.go b/rust/rust.go
index 9e06cd4..ed38ad7 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -454,6 +454,9 @@
// Paths to generated source files
SrcDeps android.Paths
srcProviderFiles android.Paths
+
+ directImplementationDeps android.Paths
+ transitiveImplementationDeps []depset.DepSet[android.Path]
}
type RustLibraries []RustLibrary
@@ -991,6 +994,10 @@
}
+ android.SetProvider(ctx, cc.ImplementationDepInfoProvider, &cc.ImplementationDepInfo{
+ ImplementationDeps: depset.New(depset.PREORDER, deps.directImplementationDeps, deps.transitiveImplementationDeps),
+ })
+
ctx.Phony("rust", ctx.RustModule().OutputFile().Path())
}
@@ -1244,6 +1251,11 @@
mod.Properties.AndroidMkDylibs = append(mod.Properties.AndroidMkDylibs, makeLibName)
mod.Properties.SnapshotDylibs = append(mod.Properties.SnapshotDylibs, cc.BaseLibName(depName))
+ depPaths.directImplementationDeps = append(depPaths.directImplementationDeps, android.OutputFileForModule(ctx, dep, ""))
+ if info, ok := android.OtherModuleProvider(ctx, dep, cc.ImplementationDepInfoProvider); ok {
+ depPaths.transitiveImplementationDeps = append(depPaths.transitiveImplementationDeps, info.ImplementationDeps)
+ }
+
case depTag == rlibDepTag:
rlib, ok := rustDep.compiler.(libraryInterface)
if !ok || !rlib.rlib() {
@@ -1259,6 +1271,11 @@
depPaths.depIncludePaths = append(depPaths.depIncludePaths, exportedInfo.IncludeDirs...)
depPaths.exportedLinkDirs = append(depPaths.exportedLinkDirs, linkPathFromFilePath(rustDep.OutputFile().Path()))
+ // rlibs are not installed, so don't add the output file to directImplementationDeps
+ if info, ok := android.OtherModuleProvider(ctx, dep, cc.ImplementationDepInfoProvider); ok {
+ depPaths.transitiveImplementationDeps = append(depPaths.transitiveImplementationDeps, info.ImplementationDeps)
+ }
+
case depTag == procMacroDepTag:
directProcMacroDeps = append(directProcMacroDeps, rustDep)
mod.Properties.AndroidMkProcMacroLibs = append(mod.Properties.AndroidMkProcMacroLibs, makeLibName)
@@ -1391,6 +1408,13 @@
// dependency crosses the APEX boundaries).
sharedLibraryInfo, exportedInfo := cc.ChooseStubOrImpl(ctx, dep)
+ if !sharedLibraryInfo.IsStubs {
+ depPaths.directImplementationDeps = append(depPaths.directImplementationDeps, android.OutputFileForModule(ctx, dep, ""))
+ if info, ok := android.OtherModuleProvider(ctx, dep, cc.ImplementationDepInfoProvider); ok {
+ depPaths.transitiveImplementationDeps = append(depPaths.transitiveImplementationDeps, info.ImplementationDeps)
+ }
+ }
+
// Re-get linkObject as ChooseStubOrImpl actually tells us which
// object (either from stub or non-stub) to use.
linkObject = android.OptionalPathForPath(sharedLibraryInfo.SharedLibrary)
diff --git a/tests/lib.sh b/tests/lib.sh
index 4c320d0..0e26de5 100644
--- a/tests/lib.sh
+++ b/tests/lib.sh
@@ -91,7 +91,6 @@
}
function create_mock_soong {
- create_mock_bazel
copy_directory build/blueprint
copy_directory build/soong
copy_directory build/make
@@ -143,41 +142,6 @@
USE_RBE=false TARGET_PRODUCT=aosp_arm TARGET_RELEASE=trunk_staging TARGET_BUILD_VARIANT=userdebug build/soong/soong_ui.bash --make-mode --skip-ninja --skip-config --soong-only --skip-soong-tests "$@"
}
-function create_mock_bazel {
- copy_directory build/bazel
- copy_directory build/bazel_common_rules
-
- # This requires pulling more tools into the mock top to build partitions
- delete_directory build/bazel/examples/partitions
-
- symlink_directory packages/modules/common/build
- symlink_directory prebuilts/bazel
- symlink_directory prebuilts/clang
- symlink_directory prebuilts/jdk
- symlink_directory external/bazel-skylib
- symlink_directory external/bazelbuild-rules_android
- symlink_directory external/bazelbuild-rules_go
- symlink_directory external/bazelbuild-rules_license
- symlink_directory external/bazelbuild-kotlin-rules
- symlink_directory external/bazelbuild-rules_cc
- symlink_directory external/bazelbuild-rules_python
- symlink_directory external/bazelbuild-rules_java
- symlink_directory external/bazelbuild-rules_rust
- symlink_directory external/bazelbuild-rules_testing
- symlink_directory external/rust/crates/tinyjson
-
- symlink_file WORKSPACE
- symlink_file BUILD
-}
-
-function run_bazel {
- # Remove the ninja_build output marker file to communicate to buildbot that this is not a regular Ninja build, and its
- # output should not be parsed as such.
- rm -rf out/ninja_build
-
- build/bazel/bin/bazel "$@"
-}
-
function run_ninja {
build/soong/soong_ui.bash --make-mode --skip-config --soong-only --skip-soong-tests "$@"
}
diff --git a/tradefed_modules/Android.bp b/tradefed_modules/Android.bp
index 9969ae2..67d91b2 100644
--- a/tradefed_modules/Android.bp
+++ b/tradefed_modules/Android.bp
@@ -13,9 +13,11 @@
],
srcs: [
"test_module_config.go",
+ "test_suite.go",
],
testSrcs: [
"test_module_config_test.go",
+ "test_suite_test.go",
],
pluginFor: ["soong_build"],
}
diff --git a/tradefed_modules/test_suite.go b/tradefed_modules/test_suite.go
new file mode 100644
index 0000000..a6c2727
--- /dev/null
+++ b/tradefed_modules/test_suite.go
@@ -0,0 +1,61 @@
+// 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 tradefed_modules
+
+import (
+ "fmt"
+
+ "android/soong/android"
+)
+
+func init() {
+ RegisterTestSuiteBuildComponents(android.InitRegistrationContext)
+}
+
+func RegisterTestSuiteBuildComponents(ctx android.RegistrationContext) {
+ ctx.RegisterModuleType("test_suite", TestSuiteFactory)
+}
+
+var PrepareForTestWithTestSuiteBuildComponents = android.GroupFixturePreparers(
+ android.FixtureRegisterWithContext(RegisterTestSuiteBuildComponents),
+)
+
+type testSuiteProperties struct {
+ Description string
+ Tests []string `android:"path,arch_variant"`
+}
+
+type testSuiteModule struct {
+ android.ModuleBase
+ android.DefaultableModuleBase
+ testSuiteProperties
+}
+
+func (t *testSuiteModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+ suiteName := ctx.ModuleName()
+ manifestPath := android.PathForSuiteInstall(ctx, suiteName, suiteName+".json")
+ android.WriteFileRule(ctx, manifestPath, fmt.Sprintf(`{"name": %q}`, suiteName))
+ ctx.Phony(suiteName, manifestPath)
+}
+
+func TestSuiteFactory() android.Module {
+ module := &testSuiteModule{}
+ module.AddProperties(&module.testSuiteProperties)
+
+ android.InitAndroidModule(module)
+ android.InitDefaultableModule(module)
+
+ return module
+}
diff --git a/tradefed_modules/test_suite_test.go b/tradefed_modules/test_suite_test.go
new file mode 100644
index 0000000..647ba4d
--- /dev/null
+++ b/tradefed_modules/test_suite_test.go
@@ -0,0 +1,53 @@
+// 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 tradefed_modules
+
+import (
+ "android/soong/android"
+ "android/soong/java"
+ "testing"
+)
+
+func TestTestSuites(t *testing.T) {
+ t.Parallel()
+ ctx := android.GroupFixturePreparers(
+ java.PrepareForTestWithJavaDefaultModules,
+ android.FixtureRegisterWithContext(RegisterTestSuiteBuildComponents),
+ ).RunTestWithBp(t, `
+ android_test {
+ name: "TestModule1",
+ sdk_version: "current",
+ }
+
+ android_test {
+ name: "TestModule2",
+ sdk_version: "current",
+ }
+
+ test_suite {
+ name: "my-suite",
+ description: "a test suite",
+ tests: [
+ "TestModule1",
+ "TestModule2",
+ ]
+ }
+ `)
+ manifestPath := ctx.ModuleForTests("my-suite", "").Output("out/soong/test_suites/my-suite/my-suite.json")
+ got := android.ContentFromFileRuleForTests(t, ctx.TestContext, manifestPath)
+ want := `{"name": "my-suite"}` + "\n"
+ if got != want {
+ t.Errorf("my-suite.json content was %q, want %q", got, want)
+ }
+}