blob: 1bfb94ff71115de738bdc5b63a97184805ed339d [file]
// Copyright 2020 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 (
"fmt"
"maps"
"path/filepath"
"regexp"
"slices"
"sort"
"strings"
"github.com/google/blueprint"
"github.com/google/blueprint/proptools"
)
//go:generate go run ../../blueprint/gobtools/codegen/gob_gen.go
func init() {
RegisterParallelSingletonType("testsuites", testSuiteFilesFactory)
RegisterModuleType("compatibility_test_suite_package", compatibilityTestSuitePackageFactory)
}
func testSuiteFilesFactory() Singleton {
return &testSuiteFiles{}
}
type testSuiteFiles struct{}
type TestSuiteModule interface {
Module
TestSuites() []string
}
// @auto-generate: gob
type TestSuiteInfo struct {
// A suffix to append to the name of the test.
// Useful because historically different variants of soong modules became differently-named
// make modules, like "my_test.vendor" for the vendor variant.
NameSuffix string
TestSuites []string
NeedsArchFolder bool
MainFile Path
MainFileStem string
MainFileExt string
ConfigFile Path
ConfigFileSuffix string
ExtraConfigs Paths
PerTestcaseDirectory bool
Data []DataPath
NonArchData []DataPath
CompatibilitySupportFiles []Path
// Eqivalent of LOCAL_DISABLE_TEST_CONFIG in make
DisableTestConfig bool
}
var TestSuiteInfoProvider = blueprint.NewProvider[TestSuiteInfo]()
// TestSuiteSharedLibsInfo is a provider of AndroidMk names of shared lib modules, for packaging
// shared libs into test suites. It's not intended as a general-purpose shared lib tracking
// mechanism. It's added to both test modules (to track their shared libs) and also shared lib
// modules (to track their transitive shared libs).
// @auto-generate: gob
type TestSuiteSharedLibsInfo struct {
MakeNames []string
}
var TestSuiteSharedLibsInfoProvider = blueprint.NewProvider[TestSuiteSharedLibsInfo]()
// MakeNameInfoProvider records the AndroidMk name for the module. This will match the names
// referenced in TestSuiteSharedLibsInfo
// @auto-generate: gob
type MakeNameInfo struct {
Name string
}
var MakeNameInfoProvider = blueprint.NewProvider[MakeNameInfo]()
// @auto-generate: gob
type filePair struct {
src Path
dst WritablePath
}
// @auto-generate: gob
type testSuiteInstallsInfo struct {
Files []filePair
OneVariantInstalls []filePair
}
var testSuiteInstallsInfoProvider = blueprint.NewProvider[testSuiteInstallsInfo]()
type testModulesInstallsMap map[ModuleProxy]InstallPaths
func (t testModulesInstallsMap) testModules() []ModuleProxy {
return slices.Collect(maps.Keys(t))
}
func (t *testSuiteFiles) GenerateBuildActions(ctx SingletonContext) {
hostOutTestCases := pathForInstall(ctx, ctx.Config().BuildOSTarget.Os, ctx.Config().BuildOSTarget.Arch.ArchType, "testcases")
files := make(map[string]testModulesInstallsMap)
sharedLibRoots := make(map[string][]string)
sharedLibGraph := make(map[string][]string)
allTestSuiteInstalls := make(map[string]Paths)
var toInstall []filePair
var oneVariantInstalls []filePair
var allCompatibilitySuitePackages []compatibilitySuitePackageInfo
ctx.VisitAllModuleProxies(func(m ModuleProxy) {
commonInfo := OtherModuleProviderOrDefault(ctx, m, CommonModuleInfoProvider)
testSuiteSharedLibsInfo := OtherModuleProviderOrDefault(ctx, m, TestSuiteSharedLibsInfoProvider)
makeName := OtherModuleProviderOrDefault(ctx, m, MakeNameInfoProvider).Name
if makeName != "" && commonInfo.Target.Os.Class == Host {
sharedLibGraph[makeName] = append(sharedLibGraph[makeName], testSuiteSharedLibsInfo.MakeNames...)
}
if tsm, ok := OtherModuleProvider(ctx, m, TestSuiteInfoProvider); ok {
installFilesProvider := OtherModuleProviderOrDefault(ctx, m, InstallFilesProvider)
for _, testSuite := range tsm.TestSuites {
if files[testSuite] == nil {
files[testSuite] = make(testModulesInstallsMap)
}
files[testSuite][m] = append(files[testSuite][m],
installFilesProvider.InstallFiles...)
if makeName != "" {
sharedLibRoots[testSuite] = append(sharedLibRoots[testSuite], makeName)
}
}
if testSuiteInstalls, ok := OtherModuleProvider(ctx, m, testSuiteInstallsInfoProvider); ok {
for _, testSuite := range tsm.TestSuites {
for _, f := range testSuiteInstalls.Files {
allTestSuiteInstalls[testSuite] = append(allTestSuiteInstalls[testSuite], f.dst)
}
for _, f := range testSuiteInstalls.OneVariantInstalls {
allTestSuiteInstalls[testSuite] = append(allTestSuiteInstalls[testSuite], f.dst)
}
}
installs := OtherModuleProviderOrDefault(ctx, m, InstallFilesProvider).InstallFiles
oneVariantInstalls = append(oneVariantInstalls, testSuiteInstalls.OneVariantInstalls...)
for _, f := range testSuiteInstalls.Files {
alreadyInstalled := false
for _, install := range installs {
if install.String() == f.dst.String() {
alreadyInstalled = true
break
}
}
if !alreadyInstalled {
toInstall = append(toInstall, f)
}
}
}
}
if info, ok := OtherModuleProvider(ctx, m, compatibilitySuitePackageProvider); ok {
allCompatibilitySuitePackages = append(allCompatibilitySuitePackages, info)
}
})
for suite, suiteInstalls := range allTestSuiteInstalls {
allTestSuiteInstalls[suite] = SortedUniquePaths(suiteInstalls)
}
hostSharedLibs := gatherHostSharedLibs(ctx, sharedLibRoots, sharedLibGraph)
if !ctx.Config().KatiEnabled() {
for _, testSuite := range SortedKeys(files) {
testSuiteSymbolsZipFile := pathForTestSymbols(ctx, fmt.Sprintf("%s-symbols.zip", testSuite))
testSuiteMergedMappingProtoFile := pathForTestSymbols(ctx, fmt.Sprintf("%s-symbols-mapping.textproto", testSuite))
allTestModules := files[testSuite].testModules()
allTestModulesOrProxy := make([]ModuleOrProxy, 0, len(allTestModules))
for _, m := range allTestModules {
allTestModulesOrProxy = append(allTestModulesOrProxy, m)
}
BuildSymbolsZip(ctx, allTestModulesOrProxy, testSuiteSymbolsZipFile, testSuiteMergedMappingProtoFile)
ctx.DistForGoalWithFilenameTag(testSuite, testSuiteSymbolsZipFile, testSuiteSymbolsZipFile.Base())
ctx.DistForGoalWithFilenameTag(testSuite, testSuiteMergedMappingProtoFile, testSuiteMergedMappingProtoFile.Base())
}
}
// https://source.corp.google.com/h/googleplex-android/platform/superproject/main/+/main:build/make/core/main.mk;l=674;drc=46bd04e115d34fd62b3167128854dfed95290eb0
testInstalledSharedLibs := make(map[string]Paths)
testInstalledSharedLibsDeduper := make(map[string]bool)
for _, install := range toInstall {
testInstalledSharedLibsDeduper[install.dst.String()] = true
}
for _, suite := range []string{
"general-tests",
"device-tests",
"vts",
"tvts",
"art-host-tests",
"host-unit-tests",
"camera-hal-tests",
"automotive-tests",
"automotive-general-tests",
"automotive-sdv-tests",
} {
var myTestCases WritablePath = hostOutTestCases
switch suite {
case "vts", "tvts":
suiteInfo := ctx.Config().productVariables.CompatibilityTestcases[suite]
outDir := suiteInfo.OutDir
if outDir == "" {
continue
}
rel, err := filepath.Rel(ctx.Config().OutDir(), outDir)
if err != nil || strings.HasPrefix(rel, "..") {
panic(fmt.Sprintf("Could not make COMPATIBILITY_TESTCASES_OUT_%s (%s) relative to the out dir (%s)", suite, suiteInfo.OutDir, ctx.Config().OutDir()))
}
myTestCases = PathForArbitraryOutput(ctx, rel)
}
for _, f := range hostSharedLibs[suite] {
dir := filepath.Base(filepath.Dir(f.String()))
out := joinWriteablePath(ctx, myTestCases, dir, filepath.Base(f.String()))
if _, ok := testInstalledSharedLibsDeduper[out.String()]; !ok {
ctx.Build(pctx, BuildParams{
Rule: Cp,
Input: f,
Output: out,
})
}
testInstalledSharedLibsDeduper[out.String()] = true
testInstalledSharedLibs[suite] = append(testInstalledSharedLibs[suite], out)
}
}
filePairSorter := func(arr []filePair) func(i, j int) bool {
return func(i, j int) bool {
c := strings.Compare(arr[i].dst.String(), arr[j].dst.String())
if c < 0 {
return true
} else if c > 0 {
return false
}
return arr[i].src.String() < arr[j].src.String()
}
}
sort.Slice(toInstall, filePairSorter(toInstall))
// Dedup, as multiple tests may install the same test data to the same folder
toInstall = slices.Compact(toInstall)
// Dedup the oneVariant files by only the dst locations, and ignore installs from other variants
sort.Slice(oneVariantInstalls, filePairSorter(oneVariantInstalls))
oneVariantInstalls = slices.CompactFunc(oneVariantInstalls, func(a, b filePair) bool {
return a.dst.String() == b.dst.String()
})
for _, install := range toInstall {
ctx.Build(pctx, BuildParams{
Rule: Cp,
Input: install.src,
Output: install.dst,
})
}
for _, install := range oneVariantInstalls {
ctx.Build(pctx, BuildParams{
Rule: Cp,
Input: install.src,
Output: install.dst,
})
}
robolectricZip, robolectrictListZip := buildTestSuite(ctx, "robolectric-tests", files["robolectric-tests"])
ctx.Phony("robolectric-tests", robolectricZip, robolectrictListZip)
ctx.DistForGoal("robolectric-tests", robolectricZip, robolectrictListZip)
ravenwoodZip, ravenwoodListZip := buildTestSuite(ctx, "ravenwood-tests", files["ravenwood-tests"])
ctx.Phony("ravenwood-tests", ravenwoodZip, ravenwoodListZip)
ctx.DistForGoal("ravenwood-tests", ravenwoodZip, ravenwoodListZip)
moblyTests := make(testModulesInstallsMap)
pathForTestCasesString := pathForTestCases(ctx).String()
for module, installedPaths := range files["mobly-tests"] {
for _, installedPath := range installedPaths {
if strings.HasPrefix(installedPath.String(), pathForTestCasesString) {
moblyTests[module] = append(moblyTests[module], installedPath)
}
}
}
moblyZip, moblyListZip := buildTestSuite(ctx, "mobly-tests", moblyTests)
ctx.Phony("mobly-tests", moblyZip, moblyListZip)
ctx.DistForGoal("mobly-tests", moblyZip, moblyListZip)
for _, testSuiteConfig := range testSuiteConfigs {
files := allTestSuiteInstalls[testSuiteConfig.name]
sharedLibs := testInstalledSharedLibs[testSuiteConfig.name]
packageTestSuite(ctx, files, sharedLibs, testSuiteConfig)
}
for _, suite := range allCompatibilitySuitePackages {
modules := slices.Collect(maps.Keys(files[suite.Name]))
sort.Slice(modules, func(i, j int) bool {
return modules[i].String() < modules[j].String()
})
buildCompatibilitySuitePackage(ctx, suite, slices.Clone(allTestSuiteInstalls[suite.Name]), modules)
}
}
// Get a mapping from testSuite -> list of host shared libraries, given:
// - sharedLibRoots: Mapping from testSuite -> androidMk name of all test modules in the suite
// - sharedLibGraph: Mapping from androidMk name of module -> androidMk names of its shared libs
//
// This mimics how make did it historically, which is filled with inaccuracies. Make didn't
// track variants and treated all variants as if they were merged into one big module. This means
// you can have a test that's only included in the "vts" test suite on the device variant, and
// only has a shared library on the host variant, and that shared library will still be included
// into the vts test suite.
func gatherHostSharedLibs(ctx SingletonContext, sharedLibRoots, sharedLibGraph map[string][]string) map[string]Paths {
hostOutTestCases := pathForInstall(ctx, ctx.Config().BuildOSTarget.Os, ctx.Config().BuildOSTarget.Arch.ArchType, "testcases")
hostOut := filepath.Dir(hostOutTestCases.String())
for k, v := range sharedLibGraph {
sharedLibGraph[k] = SortedUniqueStrings(v)
}
suiteToSharedLibModules := make(map[string]map[string]bool)
for suite, modules := range sharedLibRoots {
suiteToSharedLibModules[suite] = make(map[string]bool)
var queue []string
for _, root := range SortedUniqueStrings(modules) {
queue = append(queue, sharedLibGraph[root]...)
}
for len(queue) > 0 {
mod := queue[len(queue)-1]
queue = queue[:len(queue)-1]
if suiteToSharedLibModules[suite][mod] {
continue
}
suiteToSharedLibModules[suite][mod] = true
queue = append(queue, sharedLibGraph[mod]...)
}
}
hostSharedLibs := make(map[string]Paths)
ctx.VisitAllModuleProxies(func(m ModuleProxy) {
if makeName, ok := OtherModuleProvider(ctx, m, MakeNameInfoProvider); ok {
commonInfo := OtherModuleProviderOrDefault(ctx, m, CommonModuleInfoProvider)
if commonInfo.SkipInstall {
return
}
installFilesProvider := OtherModuleProviderOrDefault(ctx, m, InstallFilesProvider)
for suite, sharedLibModulesInSuite := range suiteToSharedLibModules {
if sharedLibModulesInSuite[makeName.Name] {
for _, f := range installFilesProvider.InstallFiles {
if strings.HasSuffix(f.String(), ".so") && strings.HasPrefix(f.String(), hostOut) {
hostSharedLibs[suite] = append(hostSharedLibs[suite], f)
}
}
}
}
}
})
for suite, files := range hostSharedLibs {
hostSharedLibs[suite] = SortedUniquePaths(files)
}
return hostSharedLibs
}
type testSuiteConfig struct {
name string
buildHostSharedLibsZip bool
includeHostSharedLibsInMainZip bool
hostJavaTools []string
}
var testSuiteConfigs = []testSuiteConfig{
{
name: "performance-tests",
},
{
name: "device-platinum-tests",
buildHostSharedLibsZip: true,
},
{
name: "device-tests",
includeHostSharedLibsInMainZip: true,
},
{
name: "automotive-tests",
},
{
name: "automotive-general-tests",
hostJavaTools: []string{"compatibility-host-util", "cts-tradefed", "vts-tradefed"},
buildHostSharedLibsZip: true,
},
{
name: "automotive-sdv-tests",
},
}
func buildTestSuite(ctx SingletonContext, suiteName string, files testModulesInstallsMap) (Path, Path) {
var installedPaths Paths
for _, module := range files.testModules() {
installedPaths = append(installedPaths, files[module].Paths()...)
}
installedPaths = SortedUniquePaths(installedPaths)
outputFile := pathForPackaging(ctx, suiteName+".zip")
rule := NewRuleBuilder(pctx, ctx)
rule.Command().BuiltTool("soong_zip").
FlagWithOutput("-o ", outputFile).
FlagWithArg("-P ", "host/testcases").
FlagWithArg("-C ", pathForTestCases(ctx).String()).
FlagWithRspFileInputList("-r ", outputFile.ReplaceExtension(ctx, "rsp"), installedPaths).
Flag("-sha256") // necessary to save cas_uploader's time
testList := buildTestList(ctx, suiteName+"_list", installedPaths)
testListZipOutputFile := pathForPackaging(ctx, suiteName+"_list.zip")
rule.Command().BuiltTool("soong_zip").
FlagWithOutput("-o ", testListZipOutputFile).
FlagWithArg("-C ", pathForPackaging(ctx).String()).
FlagWithInput("-f ", testList).
Flag("-sha256")
rule.Build(strings.ReplaceAll(suiteName, "-", "_")+"_zip", suiteName+".zip")
return outputFile, testListZipOutputFile
}
func buildTestList(ctx SingletonContext, listFile string, installedPaths Paths) Path {
buf := &strings.Builder{}
for _, p := range installedPaths {
if p.Ext() != ".config" {
continue
}
pc, err := toTestListPath(p.String(), pathForTestCases(ctx).String(), "host/testcases")
if err != nil {
ctx.Errorf("Failed to convert path: %s, %v", p.String(), err)
continue
}
buf.WriteString(pc)
buf.WriteString("\n")
}
outputFile := pathForPackaging(ctx, listFile)
WriteFileRuleVerbatim(ctx, outputFile, buf.String())
return outputFile
}
func toTestListPath(path, relativeRoot, prefix string) (string, error) {
dest, err := filepath.Rel(relativeRoot, path)
if err != nil {
return "", err
}
return filepath.Join(prefix, dest), nil
}
func pathForPackaging(ctx PathContext, pathComponents ...string) OutputPath {
pathComponents = append([]string{"packaging"}, pathComponents...)
return PathForOutput(ctx, pathComponents...)
}
func pathForTestCases(ctx PathContext) InstallPath {
return pathForInstall(ctx, ctx.Config().BuildOS, X86, "testcases")
}
func pathForTestSymbols(ctx PathContext, pathComponents ...string) InstallPath {
return pathForInstall(ctx, ctx.Config().BuildOS, ctx.Config().BuildArch, "", pathComponents...)
}
func packageTestSuite(ctx SingletonContext, files, sharedLibs Paths, suiteConfig testSuiteConfig) {
hostOutTestCases := pathForInstall(ctx, ctx.Config().BuildOSTarget.Os, ctx.Config().BuildOSTarget.Arch.ArchType, "testcases")
targetOutTestCases := pathForInstall(ctx, ctx.Config().AndroidFirstDeviceTarget.Os, ctx.Config().AndroidFirstDeviceTarget.Arch.ArchType, "testcases")
hostOut := filepath.Dir(hostOutTestCases.String())
targetOut := filepath.Dir(targetOutTestCases.String())
testsZip := pathForPackaging(ctx, suiteConfig.name+".zip")
testsListTxt := pathForPackaging(ctx, suiteConfig.name+"_list.txt")
testsListZip := pathForPackaging(ctx, suiteConfig.name+"_list.zip")
testsConfigsZip := pathForPackaging(ctx, suiteConfig.name+"_configs.zip")
testsHostSharedLibsZip := pathForPackaging(ctx, suiteConfig.name+"_host-shared-libs.zip")
var listLines []string
// use intermediate files to hold the file inputs, to prevent argument list from being too long
testsZipCmdHostFileInput := PathForIntermediates(ctx, suiteConfig.name+"_host_list.txt")
testsZipCmdTargetFileInput := PathForIntermediates(ctx, suiteConfig.name+"_target_list.txt")
var testsZipCmdHostFileInputContent, testsZipCmdTargetFileInputContent []string
testsZipBuilder := NewRuleBuilder(pctx, ctx)
testsZipCmd := testsZipBuilder.Command().
BuiltTool("soong_zip").
Flag("-sha256").
Flag("-d").
FlagWithOutput("-o ", testsZip).
FlagWithArg("-P ", "host").
FlagWithArg("-C ", hostOut)
testsConfigsZipBuilder := NewRuleBuilder(pctx, ctx)
testsConfigsZipCmd := testsConfigsZipBuilder.Command().
BuiltTool("soong_zip").
Flag("-d").
FlagWithOutput("-o ", testsConfigsZip).
FlagWithArg("-P ", "host").
FlagWithArg("-C ", hostOut)
for _, f := range files {
if strings.HasPrefix(f.String(), hostOutTestCases.String()) {
testsZipCmdHostFileInputContent = append(testsZipCmdHostFileInputContent, f.String())
testsZipCmd.Implicit(f)
if strings.HasSuffix(f.String(), ".config") {
testsConfigsZipCmd.FlagWithInput("-f ", f)
listLines = append(listLines, strings.Replace(f.String(), hostOut, "host", 1))
}
}
}
if suiteConfig.includeHostSharedLibsInMainZip {
for _, f := range sharedLibs {
if strings.HasPrefix(f.String(), hostOutTestCases.String()) {
testsZipCmdHostFileInputContent = append(testsZipCmdHostFileInputContent, f.String())
testsZipCmd.Implicit(f)
}
}
}
WriteFileRule(ctx, testsZipCmdHostFileInput, strings.Join(testsZipCmdHostFileInputContent, " "))
testsZipCmd.
FlagWithArg("-P ", "host").
FlagWithInput("-l ", testsZipCmdHostFileInput).
FlagWithArg("-P ", "target").
FlagWithArg("-C ", targetOut)
testsConfigsZipCmd.
FlagWithArg("-P ", "target").
FlagWithArg("-C ", targetOut)
for _, f := range files {
if strings.HasPrefix(f.String(), targetOutTestCases.String()) {
testsZipCmdTargetFileInputContent = append(testsZipCmdTargetFileInputContent, f.String())
testsZipCmd.Implicit(f)
if strings.HasSuffix(f.String(), ".config") {
testsConfigsZipCmd.FlagWithInput("-f ", f)
listLines = append(listLines, strings.Replace(f.String(), targetOut, "target", 1))
}
}
}
WriteFileRule(ctx, testsZipCmdTargetFileInput, strings.Join(testsZipCmdTargetFileInputContent, " "))
testsZipCmd.FlagWithInput("-l ", testsZipCmdTargetFileInput)
if len(suiteConfig.hostJavaTools) > 0 {
testsZipCmd.FlagWithArg("-P ", "host/tools")
testsZipCmd.Flag("-j")
for _, hostJavaTool := range suiteConfig.hostJavaTools {
testsZipCmd.FlagWithInput("-f ", ctx.Config().HostJavaToolPath(ctx, hostJavaTool+".jar"))
}
}
testsZipBuilder.Build(suiteConfig.name, "building "+suiteConfig.name+" zip")
testsConfigsZipBuilder.Build(suiteConfig.name+"-configs", "building "+suiteConfig.name+" configs zip")
if suiteConfig.buildHostSharedLibsZip {
testsHostSharedLibsZipBuilder := NewRuleBuilder(pctx, ctx)
testsHostSharedLibsZipCmd := testsHostSharedLibsZipBuilder.Command().
BuiltTool("soong_zip").
Flag("-d").
FlagWithOutput("-o ", testsHostSharedLibsZip).
FlagWithArg("-P ", "host").
FlagWithArg("-C ", hostOut)
for _, f := range sharedLibs {
if strings.HasPrefix(f.String(), hostOutTestCases.String()) && strings.HasSuffix(f.String(), ".so") {
testsHostSharedLibsZipCmd.FlagWithInput("-f ", f)
}
}
testsHostSharedLibsZipBuilder.Build(suiteConfig.name+"-host-shared-libs", "building "+suiteConfig.name+"host shared libs")
}
WriteFileRule(ctx, testsListTxt, strings.Join(listLines, "\n"))
testsListZipBuilder := NewRuleBuilder(pctx, ctx)
testsListZipBuilder.Command().
BuiltTool("soong_zip").
Flag("-d").
FlagWithOutput("-o ", testsListZip).
FlagWithArg("-e ", suiteConfig.name+"_list").
FlagWithInput("-f ", testsListTxt)
testsListZipBuilder.Build(suiteConfig.name+"_list_zip", "building "+suiteConfig.name+" list zip")
ctx.Phony(suiteConfig.name, testsZip)
ctx.DistForGoal(suiteConfig.name, testsZip, testsListZip, testsConfigsZip)
if suiteConfig.buildHostSharedLibsZip {
ctx.DistForGoal(suiteConfig.name, testsHostSharedLibsZip)
}
ctx.Phony("tests", PathForPhony(ctx, suiteConfig.name))
}
func buildCompatibilitySuitePackage(
ctx SingletonContext,
suite compatibilitySuitePackageInfo,
testSuiteFiles Paths,
testSuiteModules []ModuleProxy,
) {
testSuiteName := suite.Name
testSuiteTradefed := suite.Tradefed
subdir := fmt.Sprintf("android-%s", testSuiteName)
hostOutSuite := pathForInstall(ctx, ctx.Config().BuildOSTarget.Os, ctx.Config().BuildOSTarget.Arch.ArchType, testSuiteName)
hostOutTestCases := hostOutSuite.Join(ctx, subdir, "testcases")
hostOutTools := hostOutSuite.Join(ctx, subdir, "tools")
testSuiteFiles = slices.DeleteFunc(testSuiteFiles, func(f Path) bool {
return !strings.HasPrefix(f.String(), hostOutTestCases.String()+"/")
})
hostTools := Paths{
ctx.Config().HostJavaToolPath(ctx, "tradefed.jar"),
ctx.Config().HostJavaToolPath(ctx, "loganalysis.jar"),
ctx.Config().HostJavaToolPath(ctx, "compatibility-host-util.jar"),
ctx.Config().HostJavaToolPath(ctx, "compatibility-tradefed.jar"),
ctx.Config().HostJavaToolPath(ctx, testSuiteTradefed+".jar"),
ctx.Config().HostJavaToolPath(ctx, testSuiteTradefed+"-tests.jar"),
ctx.Config().HostToolPath(ctx, testSuiteTradefed),
ctx.Config().HostToolPath(ctx, "test-utils-script"),
}
// Some make rules still rely on the zip being at this location
out := hostOutSuite.Join(ctx, fmt.Sprintf("android-%s.zip", testSuiteName))
builder := NewRuleBuilder(pctx, ctx)
cmd := builder.Command().BuiltTool("soong_zip").
FlagWithOutput("-o ", out).
FlagWithArg("-e ", subdir+"/tools/version.txt").
FlagWithInput("-f ", ctx.Config().BuildNumberFile(ctx))
// Tools need to be copied to the test suite folder for other tools to use, like
// <suite>-tradefed run commandAndExit
copyTool := func(tool Path) {
builder.Command().Text("cp").Input(tool).Output(hostOutTools.Join(ctx, tool.Base()))
}
for _, hostTool := range hostTools {
cmd.
FlagWithArg("-e ", subdir+"/tools/"+hostTool.Base()).
FlagWithInput("-f ", hostTool)
copyTool(hostTool)
}
for _, tool := range suite.Tools {
if matched, err := regexp.MatchString("[a-zA-Z0-9_-]+", tool); err != nil || !matched {
ctx.Errorf("Invalid test suite tool, must match [a-zA-Z0-9_-]+, got %q", tool)
continue
}
toolPath := ctx.Config().HostJavaToolPath(ctx, tool)
cmd.
FlagWithArg("-e ", subdir+"/tools/"+tool).
FlagWithInput("-f ", toolPath)
copyTool(toolPath)
}
if suite.Readme != nil {
cmd.
FlagWithArg("-e ", subdir+"/tools/"+suite.Readme.Base()).
FlagWithInput("-f ", suite.Readme)
copyTool(suite.Readme)
}
if suite.DynamicConfig != nil {
cmd.
FlagWithArg("-e ", subdir+"/testcases/"+testSuiteName+".dynamic").
FlagWithInput("-f ", suite.DynamicConfig)
builder.Command().Text("cp").Input(suite.DynamicConfig).Output(hostOutTestCases.Join(ctx, testSuiteName+".dynamic"))
}
hostToolModules, err := getModulesForHostTools(ctx, hostTools)
// Defer error to execution time, as make historically did.
if err != nil {
builder.Command().Textf("echo %s && exit 1", proptools.ShellEscapeIncludingSpaces(err.Error()))
}
modulesForLicense := slices.Concat(testSuiteModules, hostToolModules)
if len(modulesForLicense) > 0 {
notice := PathForOutput(ctx, "compatibility_test_suites", testSuiteName, "NOTICE.txt")
BuildNoticeTextOutputFromLicenseMetadata(ctx, notice, "compatibility_"+testSuiteName, "Test suites",
BuildNoticeFromLicenseDataArgs{
Title: "Notices for files contained in the test suites filesystem image:",
StripPrefix: []string{hostOutSuite.String()},
Filter: slices.Concat(testSuiteFiles.Strings(), hostTools.Strings()),
Replace: []NoticeReplace{
{
From: ctx.Config().HostJavaToolPath(ctx, "").String(),
To: "/" + subdir + "/tools",
},
{
From: ctx.Config().HostToolPath(ctx, "").String(),
To: "/" + subdir + "/tools",
},
},
},
modulesForLicense...)
cmd.
FlagWithArg("-e ", subdir+"/NOTICE.txt").
FlagWithInput("-f ", notice)
}
cmd.FlagWithArg("-C ", hostOutSuite.String())
for _, f := range testSuiteFiles {
cmd.FlagWithInput("-f ", f)
}
addJdkToZip(ctx, cmd, subdir)
builder.Build("compatibility_zip_"+testSuiteName, fmt.Sprintf("Compatibility test suite zip %q", testSuiteName))
ctx.Phony(testSuiteName, out)
ctx.DistForGoal(testSuiteName, out)
}
func addJdkToZip(ctx SingletonContext, command *RuleBuilderCommand, subdir string) {
jdkHome := filepath.Dir(ctx.Config().Getenv("ANDROID_JAVA_HOME")) + "/linux-x86"
glob := jdkHome + "/**/*"
files, err := ctx.GlobWithDeps(glob, nil)
if err != nil {
ctx.Errorf("Could not glob %s: %s", glob, err)
return
}
paths := PathsForSource(ctx, files)
command.
FlagWithArg("-P ", subdir+"/jdk").
FlagWithArg("-C ", jdkHome).
FlagWithArg("-D ", jdkHome).
Flag("-sha256").Implicits(paths)
}
func getModulesForHostTools(ctx SingletonContext, paths Paths) ([]ModuleProxy, error) {
if len(paths) == 0 {
return nil, nil
}
foundInstalledFiles := make(map[string]struct{})
var modules []ModuleProxy
pathStrings := paths.Strings()
ctx.VisitAllModuleProxies(func(m ModuleProxy) {
installFilesProvider := OtherModuleProviderOrDefault(ctx, m, InstallFilesProvider)
found := false
for _, installed := range installFilesProvider.InstallFiles {
if slices.Contains(pathStrings, installed.String()) {
if !found {
modules = append(modules, m)
}
if _, ok := foundInstalledFiles[installed.String()]; ok {
ctx.Errorf(fmt.Sprintf("File %q found by two different modules, one of them is %s(%s)", installed.String(), ctx.ModuleName(m), ctx.ModuleSubDir(m)))
continue
}
foundInstalledFiles[installed.String()] = struct{}{}
found = true
}
}
})
if len(foundInstalledFiles) != len(paths) {
return nil, fmt.Errorf("Could not find modules for all compatibility files")
}
return modules, nil
}
// compatibility_test_suite_package builds a zip file of all the tests tagged with this suite's
// name. It's the equivalent of compatibility.mk from the make build system.
//
// In soong, the module does nothing. But a singleton finds all the compatibility_test_suite_package
// modules in the tree and emits build rules for them. This is because they need to search
// all the modules in the tree for ones tagged with the appropriate test suite, but this could
// maybe be replaced with reverse dependencies.
func compatibilityTestSuitePackageFactory() Module {
m := &compatibilityTestSuitePackage{}
InitAndroidModule(m)
m.AddProperties(&m.properties)
return m
}
type compatibilityTestSuitePackageProperties struct {
Tradefed *string
Readme *string `android:"path"`
Tools []string
Dynamic_config *string `android:"path"`
}
type compatibilityTestSuitePackage struct {
ModuleBase
properties compatibilityTestSuitePackageProperties
}
type compatibilitySuitePackageInfo struct {
Name string
Tradefed string
Readme Path
Tools []string
DynamicConfig Path
}
var compatibilitySuitePackageProvider = blueprint.NewProvider[compatibilitySuitePackageInfo]()
func (m *compatibilityTestSuitePackage) GenerateAndroidBuildActions(ctx ModuleContext) {
if matched, err := regexp.MatchString("[a-zA-Z0-9_-]+", m.Name()); err != nil || !matched {
ctx.ModuleErrorf("Invalid test suite name, must match [a-zA-Z0-9_-]+, got %q", m.Name())
return
}
if matched, err := regexp.MatchString("[a-zA-Z0-9_-]+", proptools.String(m.properties.Tradefed)); err != nil || !matched {
ctx.PropertyErrorf("tradefed", "Invalid test suite tradefed, must match [a-zA-Z0-9_-]+, got %q", proptools.String(m.properties.Tradefed))
return
}
var readme Path
if m.properties.Readme != nil {
readme = PathForModuleSrc(ctx, *m.properties.Readme)
}
var dynamicConfig Path
if m.properties.Dynamic_config != nil {
dynamicConfig = PathForModuleSrc(ctx, *m.properties.Dynamic_config)
}
SetProvider(ctx, compatibilitySuitePackageProvider, compatibilitySuitePackageInfo{
Name: m.Name(),
Tradefed: proptools.String(m.properties.Tradefed),
Readme: readme,
Tools: m.properties.Tools,
DynamicConfig: dynamicConfig,
})
}