Merge "Migrate build.prop for (system|vendor|odm)_dlkm to soong" into main
diff --git a/android/android_info.go b/android/android_info.go
index dd78ee4..a8d3d4e 100644
--- a/android/android_info.go
+++ b/android/android_info.go
@@ -15,9 +15,23 @@
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
@@ -41,28 +55,28 @@
ctx.ModuleErrorf("Either Board_info_files or Bootloader_board_name should be set. Please remove one of them\n")
return
}
- outName := proptools.StringDefault(p.properties.Stem, ctx.ModuleName()+".txt")
- androidInfoTxt := PathForModuleOut(ctx, outName).OutputPath
+ androidInfoTxtName := proptools.StringDefault(p.properties.Stem, ctx.ModuleName()+".txt")
+ androidInfoTxt := PathForModuleOut(ctx, androidInfoTxtName)
androidInfoProp := androidInfoTxt.ReplaceExtension(ctx, "prop")
- rule := NewRuleBuilder(pctx, ctx)
-
if boardInfoFiles := PathsForModuleSrc(ctx, p.properties.Board_info_files); len(boardInfoFiles) > 0 {
- rule.Command().Text("cat").Inputs(boardInfoFiles).
- Text(" | grep").FlagWithArg("-v ", "'#'").FlagWithOutput("> ", androidInfoTxt)
+ ctx.Build(pctx, BuildParams{
+ Rule: mergeAndRemoveComments,
+ Inputs: boardInfoFiles,
+ Output: androidInfoTxt,
+ })
} else if bootloaderBoardName := proptools.String(p.properties.Bootloader_board_name); bootloaderBoardName != "" {
- rule.Command().Text("echo").Text("'board="+bootloaderBoardName+"'").FlagWithOutput("> ", androidInfoTxt)
+ WriteFileRule(ctx, androidInfoTxt, "board="+bootloaderBoardName)
} else {
- rule.Command().Text("echo").Text("''").FlagWithOutput("> ", androidInfoTxt)
+ WriteFileRule(ctx, androidInfoTxt, "")
}
- rule.Build(ctx.ModuleName(), "generating android-info.prop")
-
// Create android_info.prop
- rule = NewRuleBuilder(pctx, ctx)
- rule.Command().Text("cat").Input(androidInfoTxt).
- Text(" | grep 'require version-' | sed -e 's/require version-/ro.build.expect./g' >").Output(androidInfoProp)
- rule.Build(ctx.ModuleName()+"prop", "generating android-info.prop")
+ ctx.Build(pctx, BuildParams{
+ Rule: androidInfoTxtToProp,
+ Input: androidInfoTxt,
+ Output: androidInfoProp,
+ })
ctx.SetOutputFiles(Paths{androidInfoProp}, "")
}
diff --git a/android/mutator.go b/android/mutator.go
index 4ddc606..fdd16a8 100644
--- a/android/mutator.go
+++ b/android/mutator.go
@@ -70,7 +70,7 @@
TopDown(name string, m TopDownMutator) MutatorHandle
BottomUp(name string, m BottomUpMutator) MutatorHandle
BottomUpBlueprint(name string, m blueprint.BottomUpMutator) MutatorHandle
- Transition(name string, m TransitionMutator)
+ Transition(name string, m TransitionMutator) TransitionMutatorHandle
}
type RegisterMutatorFunc func(RegisterMutatorsContext)
@@ -579,7 +579,7 @@
}
}
-func (x *registerMutatorsContext) Transition(name string, m TransitionMutator) {
+func (x *registerMutatorsContext) Transition(name string, m TransitionMutator) TransitionMutatorHandle {
atm := &androidTransitionMutator{
finalPhase: x.finalPhase,
mutator: m,
@@ -587,8 +587,10 @@
}
mutator := &mutator{
name: name,
- transitionMutator: atm}
+ transitionMutator: atm,
+ }
x.mutators = append(x.mutators, mutator)
+ return mutator
}
func (x *registerMutatorsContext) mutatorName(name string) string {
@@ -625,7 +627,10 @@
} else if mutator.topDownMutator != nil {
handle = blueprintCtx.RegisterTopDownMutator(mutator.name, mutator.topDownMutator)
} else if mutator.transitionMutator != nil {
- blueprintCtx.RegisterTransitionMutator(mutator.name, mutator.transitionMutator)
+ handle := blueprintCtx.RegisterTransitionMutator(mutator.name, mutator.transitionMutator)
+ if mutator.neverFar {
+ handle.NeverFar()
+ }
}
// Forward booleans set on the MutatorHandle to the blueprint.MutatorHandle.
@@ -681,6 +686,14 @@
MutatesGlobalState() MutatorHandle
}
+type TransitionMutatorHandle interface {
+ // NeverFar causes the variations created by this mutator to never be ignored when adding
+ // far variation dependencies. Normally, far variation dependencies ignore all the variants
+ // of the source module, and only use the variants explicitly requested by the
+ // AddFarVariationDependencies call.
+ NeverFar() MutatorHandle
+}
+
func (mutator *mutator) Parallel() MutatorHandle {
return mutator
}
@@ -715,6 +728,11 @@
return mutator
}
+func (mutator *mutator) NeverFar() MutatorHandle {
+ mutator.neverFar = true
+ return mutator
+}
+
func RegisterComponentsMutator(ctx RegisterMutatorsContext) {
ctx.BottomUp("component-deps", componentDepsMutator)
}
diff --git a/android/neverallow.go b/android/neverallow.go
index 6251e2b..6176a99 100644
--- a/android/neverallow.go
+++ b/android/neverallow.go
@@ -336,6 +336,8 @@
"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 55e16b3..9cb872d 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -2092,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
diff --git a/android/register.go b/android/register.go
index bb1ead7..8d2f19e 100644
--- a/android/register.go
+++ b/android/register.go
@@ -98,6 +98,7 @@
usesCreateModule bool
mutatesDependencies bool
mutatesGlobalState bool
+ neverFar bool
}
var _ sortableComponent = &mutator{}
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/compiler.go b/cc/compiler.go
index 0fa058a..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)
@@ -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/etc/prebuilt_etc.go b/etc/prebuilt_etc.go
index 6bb7ee3..be943a3 100644
--- a/etc/prebuilt_etc.go
+++ b/etc/prebuilt_etc.go
@@ -78,6 +78,8 @@
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)
@@ -939,3 +941,23 @@
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/fsgen/filesystem_creator.go b/fsgen/filesystem_creator.go
index 80f0955..fe590a9 100644
--- a/fsgen/filesystem_creator.go
+++ b/fsgen/filesystem_creator.go
@@ -136,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/*",
diff --git a/fsgen/fsgen_mutators.go b/fsgen/fsgen_mutators.go
index bf07ab3..f3aec00 100644
--- a/fsgen/fsgen_mutators.go
+++ b/fsgen/fsgen_mutators.go
@@ -15,11 +15,12 @@
package fsgen
import (
- "android/soong/android"
"fmt"
"slices"
"sync"
+ "android/soong/android"
+
"github.com/google/blueprint/proptools"
)
@@ -113,7 +114,6 @@
"dex_bootjars": defaultDepCandidateProps(ctx.Config()),
"framework_compatibility_matrix.device.xml": defaultDepCandidateProps(ctx.Config()),
"init.environ.rc-soong": defaultDepCandidateProps(ctx.Config()),
- "libclang_rt.asan": defaultDepCandidateProps(ctx.Config()),
"libcompiler_rt": defaultDepCandidateProps(ctx.Config()),
"libdmabufheap": defaultDepCandidateProps(ctx.Config()),
"libgsi": defaultDepCandidateProps(ctx.Config()),
diff --git a/fsgen/prebuilt_etc_modules_gen.go b/fsgen/prebuilt_etc_modules_gen.go
index 1df7ec8..362ac31 100644
--- a/fsgen/prebuilt_etc_modules_gen.go
+++ b/fsgen/prebuilt_etc_modules_gen.go
@@ -176,11 +176,13 @@
"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,
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 6572dbd..94c9e5b 100644
--- a/java/app.go
+++ b/java/app.go
@@ -1361,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/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/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_module_config.go b/tradefed_modules/test_module_config.go
index 7a04c19..5c13d64 100644
--- a/tradefed_modules/test_module_config.go
+++ b/tradefed_modules/test_module_config.go
@@ -383,6 +383,19 @@
// 4) Module.config / AndroidTest.xml
m.testConfig = m.fixTestConfig(ctx, m.provider.TestConfig)
+
+ // 5) We provide so we can be listed in test_suites.
+ android.SetProvider(ctx, tradefed.BaseTestProviderKey, tradefed.BaseTestProviderData{
+ InstalledFiles: m.supportFiles.Paths(),
+ OutputFile: baseApk,
+ TestConfig: m.testConfig,
+ HostRequiredModuleNames: m.provider.HostRequiredModuleNames,
+ RequiredModuleNames: m.provider.RequiredModuleNames,
+ TestSuites: m.tradefedProperties.Test_suites,
+ IsHost: m.provider.IsHost,
+ LocalCertificate: m.provider.LocalCertificate,
+ IsUnitTest: m.provider.IsUnitTest,
+ })
}
var _ android.AndroidMkEntriesProvider = (*testModuleConfigHostModule)(nil)
diff --git a/tradefed_modules/test_suite.go b/tradefed_modules/test_suite.go
new file mode 100644
index 0000000..00585f5
--- /dev/null
+++ b/tradefed_modules/test_suite.go
@@ -0,0 +1,173 @@
+// 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 (
+ "encoding/json"
+ "path"
+ "path/filepath"
+
+ "android/soong/android"
+ "android/soong/tradefed"
+ "github.com/google/blueprint"
+)
+
+const testSuiteModuleType = "test_suite"
+
+type testSuiteTag struct{
+ blueprint.BaseDependencyTag
+}
+
+type testSuiteManifest struct {
+ Name string `json:"name"`
+ Files []string `json:"files"`
+}
+
+func init() {
+ RegisterTestSuiteBuildComponents(android.InitRegistrationContext)
+}
+
+func RegisterTestSuiteBuildComponents(ctx android.RegistrationContext) {
+ ctx.RegisterModuleType(testSuiteModuleType, 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) DepsMutator(ctx android.BottomUpMutatorContext) {
+ for _, test := range t.Tests {
+ if ctx.OtherModuleDependencyVariantExists(ctx.Config().BuildOSCommonTarget.Variations(), test) {
+ // Host tests.
+ ctx.AddVariationDependencies(ctx.Config().BuildOSCommonTarget.Variations(), testSuiteTag{}, test)
+ } else {
+ // Target tests.
+ ctx.AddDependency(ctx.Module(), testSuiteTag{}, test)
+ }
+ }
+}
+
+func (t *testSuiteModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+ suiteName := ctx.ModuleName()
+ modulesByName := make(map[string]android.Module)
+ ctx.WalkDeps(func(child, parent android.Module) bool {
+ // Recurse into test_suite dependencies.
+ if ctx.OtherModuleType(child) == testSuiteModuleType {
+ ctx.Phony(suiteName, android.PathForPhony(ctx, child.Name()))
+ return true
+ }
+
+ // Only write out top level test suite dependencies here.
+ if _, ok := ctx.OtherModuleDependencyTag(child).(testSuiteTag); !ok {
+ return false
+ }
+
+ if !child.InstallInTestcases() {
+ ctx.ModuleErrorf("test_suite only supports modules installed in testcases. %q is not installed in testcases.", child.Name())
+ return false
+ }
+
+ modulesByName[child.Name()] = child
+ return false
+ })
+
+ var files []string
+ for name, module := range modulesByName {
+ // Get the test provider data from the child.
+ tp, ok := android.OtherModuleProvider(ctx, module, tradefed.BaseTestProviderKey)
+ if !ok {
+ // TODO: Consider printing out a list of all module types.
+ ctx.ModuleErrorf("%q is not a test module.", name)
+ continue
+ }
+
+ files = append(files, packageModuleFiles(ctx, suiteName, module, tp)...)
+ ctx.Phony(suiteName, android.PathForPhony(ctx, name))
+ }
+
+ manifestPath := android.PathForSuiteInstall(ctx, suiteName, suiteName+".json")
+ b, err := json.Marshal(testSuiteManifest{Name: suiteName, Files: files})
+ if err != nil {
+ ctx.ModuleErrorf("Failed to marshal manifest: %v", err)
+ return
+ }
+ android.WriteFileRule(ctx, manifestPath, string(b))
+
+ ctx.Phony(suiteName, manifestPath)
+}
+
+func TestSuiteFactory() android.Module {
+ module := &testSuiteModule{}
+ module.AddProperties(&module.testSuiteProperties)
+
+ android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibCommon)
+ android.InitDefaultableModule(module)
+
+ return module
+}
+
+func packageModuleFiles(ctx android.ModuleContext, suiteName string, module android.Module, tp tradefed.BaseTestProviderData) []string {
+
+ hostOrTarget := "target"
+ if tp.IsHost {
+ hostOrTarget = "host"
+ }
+
+ // suiteRoot at out/soong/packaging/<suiteName>.
+ suiteRoot := android.PathForSuiteInstall(ctx, suiteName)
+
+ var installed android.InstallPaths
+ // Install links to installed files from the module.
+ if installFilesInfo, ok := android.OtherModuleProvider(ctx, module, android.InstallFilesProvider); ok {
+ for _, f := range installFilesInfo.InstallFiles {
+ // rel is anything under .../<partition>, normally under .../testcases.
+ rel := android.Rel(ctx, f.PartitionDir(), f.String())
+
+ // Install the file under <suiteRoot>/<host|target>/<partition>.
+ installDir := suiteRoot.Join(ctx, hostOrTarget, f.Partition(), path.Dir(rel))
+ linkTo, err := filepath.Rel(installDir.String(), f.String())
+ if err != nil {
+ ctx.ModuleErrorf("Failed to get relative path from %s to %s: %v", installDir.String(), f.String(), err)
+ continue
+ }
+ installed = append(installed, ctx.InstallAbsoluteSymlink(installDir, path.Base(rel), linkTo))
+ }
+ }
+
+ // Install config file.
+ if tp.TestConfig != nil {
+ moduleRoot := suiteRoot.Join(ctx, hostOrTarget, "testcases", module.Name())
+ installed = append(installed, ctx.InstallFile(moduleRoot, module.Name() + ".config", tp.TestConfig))
+ }
+
+ // Add to phony and manifest, manifestpaths are relative to suiteRoot.
+ var manifestEntries []string
+ for _, f := range installed {
+ manifestEntries = append(manifestEntries, android.Rel(ctx, suiteRoot.String(), f.String()))
+ ctx.Phony(suiteName, f)
+ }
+ return manifestEntries
+}
diff --git a/tradefed_modules/test_suite_test.go b/tradefed_modules/test_suite_test.go
new file mode 100644
index 0000000..3c0a9eb
--- /dev/null
+++ b/tradefed_modules/test_suite_test.go
@@ -0,0 +1,151 @@
+// 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"
+ "encoding/json"
+ "slices"
+ "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", "android_common").Output("out/soong/test_suites/my-suite/my-suite.json")
+ var actual testSuiteManifest
+ if err := json.Unmarshal([]byte(android.ContentFromFileRuleForTests(t, ctx.TestContext, manifestPath)), &actual); err != nil {
+ t.Errorf("failed to unmarshal manifest: %v", err)
+ }
+ slices.Sort(actual.Files)
+
+ expected := testSuiteManifest{
+ Name: "my-suite",
+ Files: []string{
+ "target/testcases/TestModule1/TestModule1.config",
+ "target/testcases/TestModule1/arm64/TestModule1.apk",
+ "target/testcases/TestModule2/TestModule2.config",
+ "target/testcases/TestModule2/arm64/TestModule2.apk",
+ },
+ }
+
+ android.AssertDeepEquals(t, "manifests differ", expected, actual)
+}
+
+func TestTestSuitesWithNested(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",
+ }
+
+ android_test {
+ name: "TestModule3",
+ sdk_version: "current",
+ }
+
+ test_suite {
+ name: "my-child-suite",
+ description: "a child test suite",
+ tests: [
+ "TestModule1",
+ "TestModule2",
+ ]
+ }
+
+ test_suite {
+ name: "my-all-tests-suite",
+ description: "a parent test suite",
+ tests: [
+ "TestModule1",
+ "TestModule3",
+ "my-child-suite",
+ ]
+ }
+ `)
+ manifestPath := ctx.ModuleForTests("my-all-tests-suite", "android_common").Output("out/soong/test_suites/my-all-tests-suite/my-all-tests-suite.json")
+ var actual testSuiteManifest
+ if err := json.Unmarshal([]byte(android.ContentFromFileRuleForTests(t, ctx.TestContext, manifestPath)), &actual); err != nil {
+ t.Errorf("failed to unmarshal manifest: %v", err)
+ }
+ slices.Sort(actual.Files)
+
+ expected := testSuiteManifest{
+ Name: "my-all-tests-suite",
+ Files: []string{
+ "target/testcases/TestModule1/TestModule1.config",
+ "target/testcases/TestModule1/arm64/TestModule1.apk",
+ "target/testcases/TestModule2/TestModule2.config",
+ "target/testcases/TestModule2/arm64/TestModule2.apk",
+ "target/testcases/TestModule3/TestModule3.config",
+ "target/testcases/TestModule3/arm64/TestModule3.apk",
+ },
+ }
+
+ android.AssertDeepEquals(t, "manifests differ", expected, actual)
+}
+
+func TestTestSuitesNotInstalledInTestcases(t *testing.T) {
+ t.Parallel()
+ android.GroupFixturePreparers(
+ java.PrepareForTestWithJavaDefaultModules,
+ android.FixtureRegisterWithContext(RegisterTestSuiteBuildComponents),
+ ).ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern([]string{
+ `"SomeHostTest" is not installed in testcases`,
+ })).RunTestWithBp(t, `
+ java_test_host {
+ name: "SomeHostTest",
+ srcs: ["a.java"],
+ }
+ test_suite {
+ name: "my-suite",
+ description: "a test suite",
+ tests: [
+ "SomeHostTest",
+ ]
+ }
+ `)
+}