1// Copyright (C) 2025 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package ci_tests 16 17import ( 18 "fmt" 19 "path/filepath" 20 "strings" 21 22 "android/soong/android" 23 24 "github.com/google/blueprint" 25 "github.com/google/blueprint/proptools" 26) 27 28func init() { 29 pctx.Import("android/soong/android") 30 registerTestPackageZipBuildComponents(android.InitRegistrationContext) 31} 32 33func registerTestPackageZipBuildComponents(ctx android.RegistrationContext) { 34 ctx.RegisterModuleType("test_package", TestPackageZipFactory) 35} 36 37type testPackageZip struct { 38 android.ModuleBase 39 android.DefaultableModuleBase 40 41 properties CITestPackageProperties 42 43 output android.Path 44} 45 46type CITestPackageProperties struct { 47 // test modules will be added as dependencies using the device os and the common architecture's variant. 48 Tests proptools.Configurable[[]string] `android:"arch_variant"` 49 // test modules that will be added as dependencies based on the first supported arch variant and the device os variant 50 Device_first_tests proptools.Configurable[[]string] `android:"arch_variant"` 51 // test modules that will be added as dependencies based on both 32bit and 64bit arch variant and the device os variant 52 Device_both_tests proptools.Configurable[[]string] `android:"arch_variant"` 53 // test modules that will be added as dependencies based on host 54 Host_tests proptools.Configurable[[]string] `android:"arch_variant"` 55 // git-main only test modules. Will only be added as dependencies using the device os and the common architecture's variant if exists. 56 Tests_if_exist_common proptools.Configurable[[]string] `android:"arch_variant"` 57 // 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. 58 Tests_if_exist_device_both proptools.Configurable[[]string] `android:"arch_variant"` 59} 60 61type testPackageZipDepTagType struct { 62 blueprint.BaseDependencyTag 63} 64 65var testPackageZipDepTag testPackageZipDepTagType 66 67var ( 68 pctx = android.NewPackageContext("android/soong/ci_tests") 69 // test_package module type should only be used for the following modules. 70 // TODO: remove "_soong" from the module names inside when eliminating the corresponding make modules 71 moduleNamesAllowed = []string{"continuous_instrumentation_tests_soong", "continuous_instrumentation_metric_tests_soong", "continuous_native_tests_soong", "continuous_native_metric_tests_soong", "platform_tests"} 72) 73 74func (p *testPackageZip) DepsMutator(ctx android.BottomUpMutatorContext) { 75 // adding tests property deps 76 for _, t := range p.properties.Tests.GetOrDefault(ctx, nil) { 77 ctx.AddVariationDependencies(ctx.Config().AndroidCommonTarget.Variations(), testPackageZipDepTag, t) 78 } 79 80 // adding device_first_tests property deps 81 for _, t := range p.properties.Device_first_tests.GetOrDefault(ctx, nil) { 82 ctx.AddVariationDependencies(ctx.Config().AndroidFirstDeviceTarget.Variations(), testPackageZipDepTag, t) 83 } 84 85 // adding device_both_tests property deps 86 p.addDeviceBothDeps(ctx, false) 87 88 // adding host_tests property deps 89 for _, t := range p.properties.Host_tests.GetOrDefault(ctx, nil) { 90 ctx.AddVariationDependencies(ctx.Config().BuildOSTarget.Variations(), testPackageZipDepTag, t) 91 } 92 93 // adding Tests_if_exist_* property deps 94 for _, t := range p.properties.Tests_if_exist_common.GetOrDefault(ctx, nil) { 95 if ctx.OtherModuleExists(t) { 96 ctx.AddVariationDependencies(ctx.Config().AndroidCommonTarget.Variations(), testPackageZipDepTag, t) 97 } 98 } 99 p.addDeviceBothDeps(ctx, true) 100} 101 102func (p *testPackageZip) addDeviceBothDeps(ctx android.BottomUpMutatorContext, checkIfExist bool) { 103 android32TargetList := android.FirstTarget(ctx.Config().Targets[android.Android], "lib32") 104 android64TargetList := android.FirstTarget(ctx.Config().Targets[android.Android], "lib64") 105 if len(android32TargetList) > 0 { 106 maybeAndroid32Target := &android32TargetList[0] 107 if checkIfExist { 108 for _, t := range p.properties.Tests_if_exist_device_both.GetOrDefault(ctx, nil) { 109 if ctx.OtherModuleExists(t) { 110 ctx.AddFarVariationDependencies(maybeAndroid32Target.Variations(), testPackageZipDepTag, t) 111 } 112 } 113 } else { 114 ctx.AddFarVariationDependencies(maybeAndroid32Target.Variations(), testPackageZipDepTag, p.properties.Device_both_tests.GetOrDefault(ctx, nil)...) 115 } 116 } 117 if len(android64TargetList) > 0 { 118 maybeAndroid64Target := &android64TargetList[0] 119 if checkIfExist { 120 for _, t := range p.properties.Tests_if_exist_device_both.GetOrDefault(ctx, nil) { 121 if ctx.OtherModuleExists(t) { 122 ctx.AddFarVariationDependencies(maybeAndroid64Target.Variations(), testPackageZipDepTag, t) 123 } 124 } 125 } else { 126 ctx.AddFarVariationDependencies(maybeAndroid64Target.Variations(), testPackageZipDepTag, p.properties.Device_both_tests.GetOrDefault(ctx, nil)...) 127 } 128 } 129} 130 131func TestPackageZipFactory() android.Module { 132 module := &testPackageZip{} 133 134 module.AddProperties(&module.properties) 135 136 android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon) 137 android.InitDefaultableModule(module) 138 139 return module 140} 141 142func (p *testPackageZip) GenerateAndroidBuildActions(ctx android.ModuleContext) { 143 // Never install this test package, it's for disting only 144 p.SkipInstall() 145 146 if !android.InList(ctx.ModuleName(), moduleNamesAllowed) { 147 ctx.ModuleErrorf("%s is not allowed to use module type test_package") 148 } 149 150 p.output = createOutput(ctx, pctx) 151 152 ctx.SetOutputFiles(android.Paths{p.output}, "") 153} 154 155func createOutput(ctx android.ModuleContext, pctx android.PackageContext) android.ModuleOutPath { 156 productOut := filepath.Join(ctx.Config().OutDir(), "target", "product", ctx.Config().DeviceName()) 157 stagingDir := android.PathForModuleOut(ctx, "STAGING") 158 productVariables := ctx.Config().ProductVariables() 159 arch := proptools.String(productVariables.DeviceArch) 160 secondArch := proptools.String(productVariables.DeviceSecondaryArch) 161 162 builder := android.NewRuleBuilder(pctx, ctx) 163 builder.Command().Text("rm").Flag("-rf").Text(stagingDir.String()) 164 builder.Command().Text("mkdir").Flag("-p").Output(stagingDir) 165 builder.Temporary(stagingDir) 166 ctx.WalkDeps(func(child, parent android.Module) bool { 167 if !child.Enabled(ctx) { 168 return false 169 } 170 if android.EqualModules(parent, ctx.Module()) && ctx.OtherModuleDependencyTag(child) == testPackageZipDepTag { 171 // handle direct deps 172 extendBuilderCommand(ctx, child, builder, stagingDir, productOut, arch, secondArch) 173 return true 174 } else if !android.EqualModules(parent, ctx.Module()) && ctx.OtherModuleDependencyTag(child) == android.RequiredDepTag { 175 // handle the "required" from deps 176 extendBuilderCommand(ctx, child, builder, stagingDir, productOut, arch, secondArch) 177 return true 178 } else { 179 return false 180 } 181 }) 182 183 output := android.PathForModuleOut(ctx, ctx.ModuleName()+".zip") 184 builder.Command(). 185 BuiltTool("soong_zip"). 186 Flag("-o").Output(output). 187 Flag("-C").Text(stagingDir.String()). 188 Flag("-D").Text(stagingDir.String()) 189 builder.Command().Text("rm").Flag("-rf").Text(stagingDir.String()) 190 builder.Build("test_package", fmt.Sprintf("build test_package for %s", ctx.ModuleName())) 191 return output 192} 193 194func extendBuilderCommand(ctx android.ModuleContext, m android.Module, builder *android.RuleBuilder, stagingDir android.ModuleOutPath, productOut, arch, secondArch string) { 195 info, ok := android.OtherModuleProvider(ctx, m, android.ModuleInfoJSONProvider) 196 if !ok { 197 ctx.OtherModuleErrorf(m, "doesn't set ModuleInfoJSON provider") 198 } else if len(info) != 1 { 199 ctx.OtherModuleErrorf(m, "doesn't provide exactly one ModuleInfoJSON") 200 } 201 202 classes := info[0].GetClass() 203 if len(info[0].Class) != 1 { 204 ctx.OtherModuleErrorf(m, "doesn't have exactly one class in its ModuleInfoJSON") 205 } 206 class := strings.ToLower(classes[0]) 207 if class == "apps" { 208 class = "app" 209 } else if class == "java_libraries" { 210 class = "framework" 211 } 212 213 installedFilesInfo, ok := android.OtherModuleProvider(ctx, m, android.InstallFilesProvider) 214 if !ok { 215 ctx.ModuleErrorf("Module %s doesn't set InstallFilesProvider", m.Name()) 216 } 217 218 for _, spec := range installedFilesInfo.PackagingSpecs { 219 if spec.SrcPath() == nil { 220 // Probably a symlink 221 continue 222 } 223 installedFile := spec.FullInstallPath() 224 225 ext := installedFile.Ext() 226 // there are additional installed files for some app-class modules, we only need the .apk, .odex and .vdex files in the test package 227 excludeInstalledFile := ext != ".apk" && ext != ".odex" && ext != ".vdex" 228 if class == "app" && excludeInstalledFile { 229 continue 230 } 231 // only .jar files should be included for a framework dep 232 if class == "framework" && ext != ".jar" { 233 continue 234 } 235 name := removeFileExtension(installedFile.Base()) 236 // some apks have other apk as installed files, these additional files shouldn't be included 237 isAppOrFramework := class == "app" || class == "framework" 238 if isAppOrFramework && name != ctx.OtherModuleName(m) { 239 continue 240 } 241 242 f := strings.TrimPrefix(installedFile.String(), productOut+"/") 243 if strings.HasPrefix(f, "out") { 244 continue 245 } 246 if strings.HasPrefix(f, "system/") { 247 f = strings.Replace(f, "system/", "DATA/", 1) 248 } 249 f = strings.ReplaceAll(f, filepath.Join("testcases", name, arch), filepath.Join("DATA", class, name)) 250 f = strings.ReplaceAll(f, filepath.Join("testcases", name, secondArch), filepath.Join("DATA", class, name)) 251 if strings.HasPrefix(f, "testcases") { 252 f = strings.Replace(f, "testcases", filepath.Join("DATA", class), 1) 253 } 254 if strings.HasPrefix(f, "data/") { 255 f = strings.Replace(f, "data/", "DATA/", 1) 256 } 257 f = strings.ReplaceAll(f, "DATA_other", "system_other") 258 f = strings.ReplaceAll(f, "system_other/DATA", "system_other/system") 259 dir := filepath.Dir(f) 260 261 tempOut := android.PathForModuleOut(ctx, "STAGING", f) 262 builder.Command().Text("mkdir").Flag("-p").Text(filepath.Join(stagingDir.String(), dir)) 263 // Copy srcPath instead of installedFile because some rules like target-files.zip 264 // are non-hermetic and would be affected if we built the installed files. 265 builder.Command().Text("cp").Flag("-Rf").Input(spec.SrcPath()).Output(tempOut) 266 builder.Temporary(tempOut) 267 } 268} 269 270func removeFileExtension(filename string) string { 271 return strings.TrimSuffix(filename, filepath.Ext(filename)) 272} 273 274// The only purpose of this method is to make sure we can build the module directly 275// without adding suffix "-soong" 276func (p *testPackageZip) AndroidMkEntries() []android.AndroidMkEntries { 277 return []android.AndroidMkEntries{ 278 android.AndroidMkEntries{ 279 Class: "ETC", 280 OutputFile: android.OptionalPathForPath(p.output), 281 ExtraEntries: []android.AndroidMkExtraEntriesFunc{ 282 func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) { 283 entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", true) 284 }}, 285 }, 286 } 287} 288