diff options
| -rw-r--r-- | ci_tests/Android.bp | 21 | ||||
| -rw-r--r-- | ci_tests/ci_test_package_zip.go | 237 |
2 files changed, 258 insertions, 0 deletions
diff --git a/ci_tests/Android.bp b/ci_tests/Android.bp new file mode 100644 index 000000000..181ded46d --- /dev/null +++ b/ci_tests/Android.bp @@ -0,0 +1,21 @@ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +bootstrap_go_package { + name: "soong-ci-tests", + pkgPath: "android/soong/ci_tests", + deps: [ + "blueprint", + "blueprint-proptools", + "soong", + "soong-android", + ], + srcs: [ + "ci_test_package_zip.go", + ], + testSrcs: [ + ], + pluginFor: ["soong_build"], + visibility: ["//visibility:public"], +} diff --git a/ci_tests/ci_test_package_zip.go b/ci_tests/ci_test_package_zip.go new file mode 100644 index 000000000..d9573a3e3 --- /dev/null +++ b/ci_tests/ci_test_package_zip.go @@ -0,0 +1,237 @@ +// Copyright (C) 2025 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 ci_tests + +import ( + "fmt" + "path/filepath" + "strings" + + "android/soong/android" + "github.com/google/blueprint" + "github.com/google/blueprint/proptools" +) + +func init() { + pctx.Import("android/soong/android") + registerTestPackageZipBuildComponents(android.InitRegistrationContext) +} + +func registerTestPackageZipBuildComponents(ctx android.RegistrationContext) { + ctx.RegisterModuleType("test_package", TestPackageZipFactory) +} + +type testPackageZip struct { + android.ModuleBase + android.DefaultableModuleBase + + properties CITestPackageProperties + + output android.Path +} + +type CITestPackageProperties struct { + // test modules will be added as dependencies using the device os and the common architecture's variant. + Tests proptools.Configurable[[]string] `android:"arch_variant"` + // test modules that will be added as dependencies based on the first supported arch variant and the device os variant + Device_first_tests proptools.Configurable[[]string] `android:"arch_variant"` + // test modules that will be added as dependencies based on both 32bit and 64bit arch variant and the device os variant + Device_both_tests proptools.Configurable[[]string] `android:"arch_variant"` + // test modules that will be added as dependencies based on host + Host_tests proptools.Configurable[[]string] `android:"arch_variant"` + // git-main only test modules. Will only be added as dependencies using the device os and the common architecture's variant if exists. + Tests_if_exist_common proptools.Configurable[[]string] `android:"arch_variant"` + // git-main only test modules. Will only be added as dependencies based on both 32bit and 64bit arch variant and the device os variant if exists. + Tests_if_exist_device_both proptools.Configurable[[]string] `android:"arch_variant"` +} + +type testPackageZipDepTagType struct { + blueprint.BaseDependencyTag +} + +var testPackageZipDepTag testPackageZipDepTagType + +var ( + pctx = android.NewPackageContext("android/soong/ci_tests") + // test_package module type should only be used for the following modules. + // TODO: remove "_soong" from the module names inside when eliminating the corresponding make modules + moduleNamesAllowed = []string{"continuous_native_tests_soong", "continuous_instrumentation_tests_soong", "platform_tests"} +) + +func (p *testPackageZip) DepsMutator(ctx android.BottomUpMutatorContext) { + // adding tests property deps + for _, t := range p.properties.Tests.GetOrDefault(ctx, nil) { + ctx.AddVariationDependencies(ctx.Config().AndroidCommonTarget.Variations(), testPackageZipDepTag, t) + } + + // adding device_first_tests property deps + for _, t := range p.properties.Device_first_tests.GetOrDefault(ctx, nil) { + ctx.AddVariationDependencies(ctx.Config().AndroidFirstDeviceTarget.Variations(), testPackageZipDepTag, t) + } + + // adding device_both_tests property deps + p.addDeviceBothDeps(ctx, false) + + // adding host_tests property deps + for _, t := range p.properties.Host_tests.GetOrDefault(ctx, nil) { + ctx.AddVariationDependencies(ctx.Config().BuildOSTarget.Variations(), testPackageZipDepTag, t) + } + + // adding Tests_if_exist_* property deps + for _, t := range p.properties.Tests_if_exist_common.GetOrDefault(ctx, nil) { + if ctx.OtherModuleExists(t) { + ctx.AddVariationDependencies(ctx.Config().AndroidCommonTarget.Variations(), testPackageZipDepTag, t) + } + } + p.addDeviceBothDeps(ctx, true) +} + +func (p *testPackageZip) addDeviceBothDeps(ctx android.BottomUpMutatorContext, checkIfExist bool) { + android32TargetList := android.FirstTarget(ctx.Config().Targets[android.Android], "lib32") + android64TargetList := android.FirstTarget(ctx.Config().Targets[android.Android], "lib64") + if len(android32TargetList) > 0 { + maybeAndroid32Target := &android32TargetList[0] + if checkIfExist { + for _, t := range p.properties.Tests_if_exist_device_both.GetOrDefault(ctx, nil) { + if ctx.OtherModuleExists(t) { + ctx.AddFarVariationDependencies(maybeAndroid32Target.Variations(), testPackageZipDepTag, t) + } + } + } else { + ctx.AddFarVariationDependencies(maybeAndroid32Target.Variations(), testPackageZipDepTag, p.properties.Device_both_tests.GetOrDefault(ctx, nil)...) + } + } + if len(android64TargetList) > 0 { + maybeAndroid64Target := &android64TargetList[0] + if checkIfExist { + for _, t := range p.properties.Tests_if_exist_device_both.GetOrDefault(ctx, nil) { + if ctx.OtherModuleExists(t) { + ctx.AddFarVariationDependencies(maybeAndroid64Target.Variations(), testPackageZipDepTag, t) + } + } + } else { + ctx.AddFarVariationDependencies(maybeAndroid64Target.Variations(), testPackageZipDepTag, p.properties.Device_both_tests.GetOrDefault(ctx, nil)...) + } + } +} + +func TestPackageZipFactory() android.Module { + module := &testPackageZip{} + + module.AddProperties(&module.properties) + + android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon) + android.InitDefaultableModule(module) + + return module +} + +func (p *testPackageZip) GenerateAndroidBuildActions(ctx android.ModuleContext) { + if !android.InList(ctx.ModuleName(), moduleNamesAllowed) { + ctx.ModuleErrorf("%s is not allowed to use module type test_package") + } + + p.output = createOutput(ctx, pctx) + + ctx.SetOutputFiles(android.Paths{p.output}, "") + + // dist the test output + if ctx.ModuleName() == "platform_tests_soong" { + distedName := ctx.Config().Getenv("TARGET_PRODUCT") + "-tests-" + ctx.Config().BuildId() + ".zip" + ctx.DistForGoalsWithFilename([]string{"droid", "platform_tests"}, p.output, distedName) + } +} + +func createOutput(ctx android.ModuleContext, pctx android.PackageContext) android.ModuleOutPath { + productOut := filepath.Join(ctx.Config().OutDir(), "target", "product", ctx.Config().DeviceName()) + stagingDir := android.PathForModuleOut(ctx, "STAGING") + productVariables := ctx.Config().ProductVariables() + arch := proptools.String(productVariables.DeviceArch) + secondArch := proptools.String(productVariables.DeviceSecondaryArch) + + builder := android.NewRuleBuilder(pctx, ctx) + builder.Command().Text("rm").Flag("-rf").Text(stagingDir.String()) + builder.Command().Text("mkdir").Flag("-p").Output(stagingDir) + builder.Temporary(stagingDir) + ctx.VisitDirectDepsWithTag(testPackageZipDepTag, func(m android.Module) { + info, ok := android.OtherModuleProvider(ctx, m, android.ModuleInfoJSONProvider) + if !ok { + ctx.OtherModuleErrorf(m, "doesn't set ModuleInfoJSON provider") + } else if len(info) != 1 { + ctx.OtherModuleErrorf(m, "doesn't provide exactly one ModuleInfoJSON") + } + + classes := info[0].GetClass() + if len(info[0].Class) != 1 { + ctx.OtherModuleErrorf(m, "doesn't have exactly one class in its ModuleInfoJSON") + } + class := strings.ToLower(classes[0]) + if class == "apps" { + class = "app" + } else if class == "java_libraries" { + class = "framework" + } + + installedFilesInfo, ok := android.OtherModuleProvider(ctx, m, android.InstallFilesProvider) + if !ok { + ctx.ModuleErrorf("Module %s doesn't set InstallFilesProvider", m.Name()) + } + + for _, installedFile := range installedFilesInfo.InstallFiles { + name := removeFileExtension(installedFile.Base()) + f := strings.TrimPrefix(installedFile.String(), productOut+"/") + if strings.HasPrefix(f, "out") { + continue + } + f = strings.ReplaceAll(f, "system/", "DATA/") + f = strings.ReplaceAll(f, filepath.Join("testcases", name, arch), filepath.Join("DATA", class, name)) + f = strings.ReplaceAll(f, filepath.Join("testcases", name, secondArch), filepath.Join("DATA", class, name)) + f = strings.ReplaceAll(f, "testcases", filepath.Join("DATA", class)) + f = strings.ReplaceAll(f, "data/", "DATA/") + f = strings.ReplaceAll(f, "DATA_other", "system_other") + f = strings.ReplaceAll(f, "system_other/DATA", "system_other/system") + dir := filepath.Dir(f) + tempOut := android.PathForModuleOut(ctx, "STAGING", f) + builder.Command().Text("mkdir").Flag("-p").Text(filepath.Join(stagingDir.String(), dir)) + builder.Command().Text("cp").Flag("-Rf").Input(installedFile).Output(tempOut) + builder.Temporary(tempOut) + } + }) + + output := android.PathForModuleOut(ctx, ctx.ModuleName()+".zip") + builder.Command(). + BuiltTool("soong_zip"). + Flag("-o").Output(output). + Flag("-C").Text(stagingDir.String()). + Flag("-D").Text(stagingDir.String()) + builder.Command().Text("rm").Flag("-rf").Text(stagingDir.String()) + builder.Build("test_package", fmt.Sprintf("build test_package for %s", ctx.ModuleName())) + return output +} + +func removeFileExtension(filename string) string { + return strings.TrimSuffix(filename, filepath.Ext(filename)) +} + +// The only purpose of this method is to make sure we can build the module directly +// without adding suffix "-soong" +func (p *testPackageZip) AndroidMkEntries() []android.AndroidMkEntries { + return []android.AndroidMkEntries{ + android.AndroidMkEntries{ + Class: "ETC", + OutputFile: android.OptionalPathForPath(p.output), + }, + } +} |