Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Impressum test_suites.go   Sprache: unbekannt

 
Spracherkennung für: .go vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

// Copyright 2025 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 testsuites

import (
 "fmt"
 "path/filepath"
 "regexp"
 "slices"
 "sort"
 "strings"

 "github.com/google/blueprint"
 "github.com/google/blueprint/proptools"

 "android/soong/android"
 "android/soong/java"
 "android/soong/phony"
)

//go:generate go run ../../blueprint/gobtools/codegen

var (
 pctx = android.NewPackageContext("android/soong/test_suites")

 _ = pctx.HostBinToolVariable("buildLicenseMetadataCmd", "build_license_metadata")

 licenseMetadataRule = pctx.AndroidStaticRule("licenseMetadataRule", blueprint.RuleParams{
  Command:         "${buildLicenseMetadataCmd} -o $out @${out}.rsp",
  CommandDeps:     []string{"${buildLicenseMetadataCmd}"},
  Rspfile:         "${out}.rsp",
  RspfileContent:  "${args}",
  SandboxDisabled: true,
 }, "args")
)

func init() {
 android.RegisterParallelSingletonType("testsuites", testSuiteFilesFactory)
 android.RegisterModuleType("compatibility_test_suite_package", compatibilityTestSuitePackageFactory)
 android.RegisterModuleType("test_suite_package", testSuitePackageFactory)
 pctx.Import("android/soong/android")
}

func testSuiteFilesFactory() android.Singleton {
 return &testSuiteFiles{}
}

type testSuiteFiles struct{}

func (t *testSuiteFiles) GenerateBuildActions(ctx android.SingletonContext) {
 hostOutTestCases := android.PathForHostInstall(ctx, "testcases")
 // regularInstalledFiles maps from test suite name -> all installed files from all modules
 // in the suite. These are the regular "installed" files used prevelantly in soong, not specific
 // to test suites, in contrast to allTestSuiteInstalls, which contains the testcases/ folder
 // installed files.
 regularInstalledFiles := make(map[string]android.InstallPaths)
 // A mapping from suite name to modules in the suite
 testSuiteModules := make(map[string][]android.ModuleProxy)
 sharedLibRoots := make(map[string][]string)
 sharedLibGraph := make(map[string][]string)
 allTestSuiteInstalls := make(map[string]android.Paths)
 allTestSuiteSrcs := make(map[string]android.Paths)
 seenSymlinks := make(map[string]bool)
 var toInstall []android.FilePair
 var oneVariantInstalls []android.FilePair
 var allCompatibilitySuitePackages []compatibilitySuitePackageInfo

 var allTestSuiteConfigs []testSuiteConfig
 allTestSuitesWithHostSharedLibs := []string{
  "general-tests",
  "device-tests",
  "vts",
  "tvts",
  "sdvts",
  "art-host-tests",
  "host-unit-tests",
  "sdv-host-unit-tests",
  "camera-hal-tests",
  "automotive-tests",
  "automotive-general-tests",
  "automotive-sdv-tests",
  "chdts",
 }

 ctx.VisitAllModuleProxies(func(m android.ModuleProxy) {
  commonInfo := android.OtherModuleProviderOrDefault(ctx, m, android.CommonModuleInfoProvider)
  var makeName string
  var sharedLibsMakeNames []string
  if makeNameInfo := commonInfo.MakeNames; makeNameInfo != nil {
   makeName = makeNameInfo.MakeName
   sharedLibsMakeNames = makeNameInfo.SharedLibsMakeNames
  }
  if makeName != "" && commonInfo.Target.Os == ctx.Config().BuildOS {
   sharedLibGraph[makeName] = append(sharedLibGraph[makeName], sharedLibsMakeNames...)
  }

  if tsm, ok := android.OtherModuleProvider(ctx, m, android.TestSuiteInfoProvider); ok {
   installFilesProvider := android.GetInstallFilesCommon(commonInfo)

   for _, testSuite := range tsm.TestSuites {
    regularInstalledFiles[testSuite] = append(regularInstalledFiles[testSuite], installFilesProvider.InstallFiles...)
    testSuiteModules[testSuite] = append(testSuiteModules[testSuite], m)

    if makeName != "" {
     sharedLibRoots[testSuite] = append(sharedLibRoots[testSuite], makeName)
    }
   }

   if tsm.TestSuiteInstalls != nil {
    testSuiteInstalls := tsm.TestSuiteInstalls
    for _, testSuite := range tsm.TestSuites {
     for _, f := range testSuiteInstalls.Files {
      allTestSuiteInstalls[testSuite] = append(allTestSuiteInstalls[testSuite], f.Dst)
      allTestSuiteSrcs[testSuite] = append(allTestSuiteSrcs[testSuite], f.Src)
     }
     for _, f := range testSuiteInstalls.OneVariantInstalls {
      allTestSuiteInstalls[testSuite] = append(allTestSuiteInstalls[testSuite], f.Dst)
      allTestSuiteSrcs[testSuite] = append(allTestSuiteSrcs[testSuite], f.Src)
     }
    }
    installs := 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 := android.OtherModuleProvider(ctx, m, compatibilitySuitePackageProvider); ok {
   allCompatibilitySuitePackages = append(allCompatibilitySuitePackages, info)
  }
  if info, ok := android.OtherModuleProvider(ctx, m, testSuitePackageProvider); ok {
   allTestSuiteConfigs = append(allTestSuiteConfigs, info)
   if info.buildHostSharedLibsZip || info.includeHostSharedLibsInMainZip {
    allTestSuitesWithHostSharedLibs = append(allTestSuitesWithHostSharedLibs, info.name)
   }
  }
 })

 sort.Strings(allTestSuitesWithHostSharedLibs)
 slices.SortFunc(allTestSuiteConfigs, func(a testSuiteConfig, b testSuiteConfig) int {
  return strings.Compare(a.name, b.name)
 })

 for suite, suiteInstalls := range allTestSuiteInstalls {
  allTestSuiteInstalls[suite] = android.SortedUniquePaths(suiteInstalls)
 }

 hostSharedLibs := gatherHostSharedLibs(ctx, sharedLibRoots, sharedLibGraph)

 for _, testSuite := range android.SortedKeys(testSuiteModules) {
  testSuiteSymbolsZipFile := android.PathForHostInstall(ctx, fmt.Sprintf("%s-symbols.zip", testSuite))
  testSuiteMergedMappingProtoFile := android.PathForHostInstall(ctx, fmt.Sprintf("%s-symbols-mapping.textproto", testSuite))
  android.BuildSymbolsZip(ctx, testSuiteModules[testSuite], nil, 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]android.Paths)
 testInstalledSharedLibsDeduper := make(map[string]bool)
 for _, install := range toInstall {
  testInstalledSharedLibsDeduper[install.Dst.String()] = true
 }
 for _, suite := range allTestSuitesWithHostSharedLibs {
  var myTestCases android.WritablePath = hostOutTestCases
  switch suite {
  case "vts", "tvts", "sdvts":
   suiteInfo := ctx.Config().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 = android.PathForArbitraryOutput(ctx, rel)
  }

  for _, f := range hostSharedLibs[suite] {
   dir := filepath.Base(filepath.Dir(f.String()))
   out := android.JoinWriteablePath(ctx, myTestCases, dir, filepath.Base(f.String()))
   if _, ok := testInstalledSharedLibsDeduper[out.String()]; !ok {
    ctx.Build(pctx, android.BuildParams{
     Rule:   android.CpRule,
     Input:  f,
     Output: out,
    })
   }
   testInstalledSharedLibsDeduper[out.String()] = true
   testInstalledSharedLibs[suite] = append(testInstalledSharedLibs[suite], out)
  }
 }

 filePairSorter := func(arr []android.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 android.FilePair) bool {
  return a.Dst.String() == b.Dst.String()
 })

 for _, install := range toInstall {
  ctx.Build(pctx, android.BuildParams{
   Rule:   android.CpRule,
   Input:  install.Src,
   Output: install.Dst,
  })
 }
 for _, install := range oneVariantInstalls {
  ctx.Build(pctx, android.BuildParams{
   Rule:   android.CpRule,
   Input:  install.Src,
   Output: install.Dst,
  })
 }

 robolectricZip, robolectrictListZip := buildTestSuite(ctx, "robolectric-tests", regularInstalledFiles["robolectric-tests"])
 ctx.Phony("robolectric-tests", robolectricZip, robolectrictListZip)
 ctx.DistForGoal("robolectric-tests", robolectricZip, robolectrictListZip)

 ravenwoodZip, ravenwoodListZip := buildTestSuite(ctx, "ravenwood-tests", regularInstalledFiles["ravenwood-tests"])
 ctx.Phony("ravenwood-tests", ravenwoodZip, ravenwoodListZip)
 ctx.DistForGoal("ravenwood-tests", ravenwoodZip, ravenwoodListZip)

 for _, testSuiteConfig := range allTestSuiteConfigs {
  files := allTestSuiteInstalls[testSuiteConfig.name]
  sharedLibs := testInstalledSharedLibs[testSuiteConfig.name]
  packageTestSuite(ctx, testSuiteModules[testSuiteConfig.name], files, sharedLibs, testSuiteConfig, hostSharedLibs[testSuiteConfig.name], seenSymlinks)
 }

 for _, suite := range allCompatibilitySuitePackages {
  buildCompatibilitySuitePackage(ctx, suite, slices.Clone(allTestSuiteInstalls[suite.Name]), testSuiteModules[suite.Name], slices.Clone(testInstalledSharedLibs[suite.Name]))
  if suite.Name == "cts" {
   generateCtsCoverageReports(ctx, allTestSuiteSrcs)
  }
 }
}

type apiReportType int

const (
 apiMapReportType apiReportType = iota
 apiInheritReportType
)

func (a apiReportType) String() string {
 switch a {
 case apiMapReportType:
  return "api-map"
 case apiInheritReportType:
  return "api-inherit"
 default:
  return "unknown"
 }
}

// The cts test suite will generate reports with "cts-api-map". It needs test suite dependencies
// and api.xml files to analyze jar files. Finally dist the report as build goal "cts-api-coverage".
func generateCtsCoverageReports(ctx android.SingletonContext, allTestSuiteSrcs map[string]android.Paths) {
 hostOutApiMap := android.PathForHostInstall(ctx, "cts-api-map")
 allApiMapFile := make(map[string]android.InstallPath)
 ctsReportModules := []string{"cts", "cts-v-host"}
 ctsVerifierAppListModule := "android-cts-verifier-app-list"
 apiXmlModules := []string{"android_stubs_current", "android_system_stubs_current", "android_module_lib_stubs_current", "android_system_server_stubs_current"}
 foundApiXmlFiles := make(map[string]android.Path)
 testSuiteOutput := make(map[string]android.Path)
 var apiXmlFiles android.Paths
 var ctsVerifierApiMapFile android.Path

 // Collect apk and jar paths in {suite}_api_map_files.txt as input for coverage report.
 for _, suite := range android.SortedKeys(allTestSuiteSrcs) {
  if slices.Contains(ctsReportModules, suite) {
   allTestSuiteSrcs[suite] = android.SortedUniquePaths(allTestSuiteSrcs[suite])
   var apkJarSrcs android.Paths
   for _, srcPath := range allTestSuiteSrcs[suite] {
    if srcPath.Ext() == ".apk" || srcPath.Ext() == ".jar" {
     apkJarSrcs = append(apkJarSrcs, srcPath)
    }
   }
   allApiMapFile[suite] = hostOutApiMap.Join(ctx, fmt.Sprintf("%s_api_map_files.txt", suite))
   android.WriteFileRule(ctx, allApiMapFile[suite], strings.Join(apkJarSrcs.Strings(), " "))
  }
 }

 ctx.VisitAllModuleProxies(func(m android.ModuleProxy) {
  if slices.Contains(apiXmlModules, m.Name()) {
   if _, exists := foundApiXmlFiles[m.Name()]; exists {
    ctx.Errorf("Found multiple variants for module %q while looking for .api.xml", m.Name())
    return
   }
   foundApiXmlFiles[m.Name()] = android.OutputFileForModule(ctx, m, ".api.xml")
  }
  if m.Name() == ctsVerifierAppListModule {
   if ctsVerifierApiMapFile != nil {
    ctx.Errorf("Found multiple variants for module %q", ctsVerifierAppListModule)
    return
   }
   ctsVerifierApiMapFile = android.OutputFileForModule(ctx, m, "")
  }
  if slices.Contains(ctsReportModules, m.Name()) {
   if _, exists := testSuiteOutput[m.Name()]; exists {
    ctx.Errorf("Found multiple variants for module %q while looking for suite zip", m.Name())
    return
   }
   testSuiteOutput[m.Name()] = android.OutputFileForModule(ctx, m, "")
  }
 })
 for _, moduleName := range apiXmlModules {
  apiXmlFiles = append(apiXmlFiles, foundApiXmlFiles[moduleName])
 }
 generateApiMapReport(ctx, pctx, "cts-v-host", apiMapReportType, apiXmlFiles, android.Paths{ctsVerifierApiMapFile, allApiMapFile["cts-v-host"]}, android.Paths{testSuiteOutput["cts-v-host"]})
 generateApiMapReport(ctx, pctx, "cts", apiMapReportType, apiXmlFiles, android.Paths{allApiMapFile["cts"]}, android.Paths{testSuiteOutput["cts"]})
 // Suite "cts-combined": "cts" and "cts-v-host"
 generateApiMapReport(ctx, pctx, "cts-combined", apiMapReportType, apiXmlFiles, android.Paths{ctsVerifierApiMapFile, allApiMapFile["cts"], allApiMapFile["cts-v-host"]}, android.Paths{testSuiteOutput["cts"], testSuiteOutput["cts-v-host"]})
 ctx.DistForGoalWithFilename("cts-api-coverage", hostOutApiMap.Join(ctx, "cts-combined-api-map.xml"), "cts-api-map-report.xml")
 generateApiMapReport(ctx, pctx, "cts-combined", apiInheritReportType, apiXmlFiles, android.Paths{ctsVerifierApiMapFile, allApiMapFile["cts"], allApiMapFile["cts-v-host"]}, android.Paths{testSuiteOutput["cts"], testSuiteOutput["cts-v-host"]})
 ctx.DistForGoalWithFilename("cts-api-coverage", hostOutApiMap.Join(ctx, "cts-combined-api-inherit.xml"), "cts-api-inherit-report.xml")
}

func generateApiMapReport(ctx android.SingletonContext, pctx android.PackageContext, suite string, reportType apiReportType, apiXmlFiles android.Paths, jarFileLists android.Paths, dependencies android.Paths) {
 hostOutApiMap := android.PathForHostInstall(ctx, "cts-api-map")
 moduleName := fmt.Sprintf("%s-%s-xml", suite, reportType.String())
 sboxOut := android.PathForOutput(ctx, "api_map_report_temps", moduleName, "gen")
 sboxManifest := android.PathForOutput(ctx, "api_map_report_temps", moduleName, fmt.Sprintf("%s_genrule.sbox.textproto", moduleName))
 outputFileName := fmt.Sprintf("%s-%s.xml", suite, reportType.String())
 jarFilesList := hostOutApiMap.Join(ctx, fmt.Sprintf("%s_jar_files_%s.txt", suite, reportType.String()))
 jarFiles_rule := android.NewRuleBuilder(pctx, ctx).SandboxDisabled()
 jarFiles_rule.Command().
  Text("cat").
  Inputs(jarFileLists).
  Text(">").
  Output(jarFilesList)
 jarFiles_rule.Build(jarFilesList.Base(), fmt.Sprintf("Jar files list for %s", suite))

 rule := android.NewRuleBuilder(pctx, ctx).
  SandboxDisabled().
  Sbox(sboxOut, sboxManifest)
 switch reportType {
 case apiMapReportType:
  rule.Command().BuiltTool("cts-api-map").
   Flag("-j 4").
   Flag("-m api_map").
   Flag("-m xts_annotation").
   FlagWithArg("-a ", strings.Join(apiXmlFiles.Strings(), ",")).
   FlagWithArg("-i ", jarFilesList.String()).
   Flag("-f xml").
   FlagWithOutput("-o ", sboxOut.Join(ctx, outputFileName)).
   Implicit(jarFilesList).
   Implicits(apiXmlFiles).
   Implicits(dependencies)
 case apiInheritReportType:
  rule.Command().BuiltTool("cts-api-map").
   Flag("-j 4").
   Flag("-m xts_api_inherit").
   FlagWithArg("-a ", strings.Join(apiXmlFiles.Strings(), ",")).
   FlagWithArg("-i ", jarFilesList.String()).
   Flag("-f xml").
   FlagWithOutput("-o ", sboxOut.Join(ctx, outputFileName)).
   Implicit(jarFilesList).
   Implicits(apiXmlFiles).
   Implicits(dependencies)
 }
 reportDesc := fmt.Sprintf("%s report for %s", reportType.String(), suite)
 rule.Command().Text("echo").Text(reportDesc + ":").Text(hostOutApiMap.Join(ctx, outputFileName).String())
 rule.Build(fmt.Sprintf("generate_%s_report_%s", reportType.String(), suite), reportDesc)
 ctx.Phony(moduleName, hostOutApiMap.Join(ctx, outputFileName))
 ctx.Build(pctx, android.BuildParams{
  Rule:   android.CpRule,
  Input:  sboxOut.Join(ctx, outputFileName),
  Output: hostOutApiMap.Join(ctx, outputFileName),
 })
}

// 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 android.SingletonContext, sharedLibRoots, sharedLibGraph map[string][]string) map[string]android.Paths {
 hostOutTestCases := android.PathForHostInstall(ctx, "testcases")
 hostOut := filepath.Dir(hostOutTestCases.String())

 for k, v := range sharedLibGraph {
  sharedLibGraph[k] = android.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 android.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]android.Paths)

 ctx.VisitAllModuleProxies(func(m android.ModuleProxy) {
  commonInfo := android.OtherModuleProviderOrDefault(ctx, m, android.CommonModuleInfoProvider)
  if commonInfo.SkipInstall {
   return
  }
  if makeName := commonInfo.MakeNames; makeName != nil {
   installFilesProvider := android.GetInstallFilesCommon(commonInfo)
   for suite, sharedLibModulesInSuite := range suiteToSharedLibModules {
    if sharedLibModulesInSuite[makeName.MakeName] {
     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] = android.SortedUniquePaths(files)
 }

 return hostSharedLibs
}

// Gather shared library dependencies of host tests that are not installed algonside the test.
// These common dependencies are installed in testcases/lib[64]/ (to reduce duplication).
func gatherCommonHostSharedLibsForSymlinks(ctx android.SingletonContext, suite string) map[string]android.Paths {
 hostOut32And64 := android.PathForHostInstall(ctx, "lib").String()
 moduleNameToCommonHostSharedLibs := make(map[string]android.Paths)
 ctx.VisitAllModuleProxies(func(m android.ModuleProxy) {
  commonInfo := android.OtherModuleProviderOrDefault(ctx, m, android.CommonModuleInfoProvider)
  testInfo := android.OtherModuleProviderOrDefault(ctx, m, android.TestSuiteInfoProvider)
  if commonInfo.SkipInstall || !commonInfo.Host || !android.InList(suite, testInfo.TestSuites) {
   return
  }
  installFilesProvider := android.GetInstallFilesCommon(commonInfo)
  for _, transitive := range installFilesProvider.TransitiveInstallFiles.ToList() {
   transitivePathString := transitive.String()
   if strings.HasPrefix(transitivePathString, hostOut32And64) &&
    strings.HasSuffix(transitivePathString, ".so") {
    moduleNameToCommonHostSharedLibs[m.Name()] = append(moduleNameToCommonHostSharedLibs[m.Name()], transitive)
   }
  }
 })
 return moduleNameToCommonHostSharedLibs
}

// @auto-generate: gob
type testSuiteConfig struct {
 name                                         string
 buildHostSharedLibsZip                       bool
 includeHostSharedLibsInMainZip               bool
 includeCommonHostSharedLibsSymlinksInMainZip bool
 hostJavaToolFiles                            android.Paths
 Art_data_zips                                android.Paths
}

func buildTestSuite(ctx android.SingletonContext, suiteName string, files android.InstallPaths) (android.Path, android.Path) {
 installedPaths := android.SortedUniquePaths(files.Paths())

 outputFile := pathForPackaging(ctx, suiteName+".zip")
 rule := android.NewRuleBuilder(pctx, ctx).SandboxDisabled()
 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 android.SingletonContext, listFile string, installedPaths android.Paths) android.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)
 android.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 android.PathContext, pathComponents ...string) android.OutputPath {
 pathComponents = append([]string{"packaging"}, pathComponents...)
 return android.PathForOutput(ctx, pathComponents...)
}

func pathForTestCases(ctx android.PathContext) android.InstallPath {
 return android.PathForHostInstall(ctx, "testcases")
}

func packageTestSuite(
 ctx android.SingletonContext,
 modules []android.ModuleProxy,
 files android.Paths,
 sharedLibs android.Paths,
 suiteConfig testSuiteConfig,
 hostSharedLibs android.Paths,
 seenSymlinks map[string]bool) {
 hostOutTestCases := android.PathForHostInstall(ctx, "testcases")
 targetOutTestCases := android.PathForDeviceFirstInstall(ctx, "testcases")
 hostOut := filepath.Dir(hostOutTestCases.String())
 targetOut := filepath.Dir(targetOutTestCases.String())

 testsZip := pathForPackaging(ctx, suiteConfig.name+".zip")
 generalTestsFilesListText := android.PathForDeviceFirstInstall(ctx, "general-tests_files")
 generalTestsHostFilesListText := android.PathForDeviceFirstInstall(ctx, "general-tests_host_files")
 generalTestsTargetFilesListText := android.PathForDeviceFirstInstall(ctx, "general-tests_target_files")
 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, filesListLines, hostFilesListLines, targetFilesListLines []string

 // use intermediate files to hold the file inputs, to prevent argument list from being too long
 testsZipCmdHostFileInput := android.PathForIntermediates(ctx, suiteConfig.name+"_host_list.txt")
 testsZipCmdTargetFileInput := android.PathForIntermediates(ctx, suiteConfig.name+"_target_list.txt")
 var testsZipCmdHostFileInputContent, testsZipCmdTargetFileInputContent []string

 testsZipBuilder := android.NewRuleBuilder(pctx, ctx).SandboxDisabled()
 testsZipCmd := testsZipBuilder.Command().
  BuiltTool("soong_zip").
  Flag("-sha256").
  Flag("-d").
  FlagWithArg("-P ", "host").
  FlagWithArg("-C ", hostOut)

 testsConfigsZipBuilder := android.NewRuleBuilder(pctx, ctx).SandboxDisabled()
 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))
   }
   // Adding files installed in out/host to general-tests-files-list, e.g.,
   // out/host/linux-x86/testcases/hello_world_test/hello_world_test.config
   filesListLines = append(filesListLines, f.String())
   hostFilesListLines = append(hostFilesListLines, f.String())
  }
 }

 if suiteConfig.includeHostSharedLibsInMainZip {
  for _, f := range sharedLibs {
   // Adding host shared libs to general-tests-files-list, e.g.,
   // out/host/linux-x86/testcases/lib64/libc++.so
   filesListLines = append(filesListLines, f.String())
   hostFilesListLines = append(hostFilesListLines, f.String())
   if strings.HasPrefix(f.String(), hostOutTestCases.String()) {
    testsZipCmdHostFileInputContent = append(testsZipCmdHostFileInputContent, f.String())
    testsZipCmd.Implicit(f)
   }
  }
 }

 if suiteConfig.includeCommonHostSharedLibsSymlinksInMainZip {
  commonHostSharedLibsForSymlinks := gatherCommonHostSharedLibsForSymlinks(ctx, suiteConfig.name)
  for _, moduleName := range android.SortedKeys(commonHostSharedLibsForSymlinks) {
   var symlinksForModuleTarget []android.Path
   for _, common := range commonHostSharedLibsForSymlinks[moduleName] {
    if !android.InList(common, hostSharedLibs) {
     continue
    }
    var symlink, libInTestCase android.WritablePath
    var symlinkTargetPrefix string
    if strings.Contains(common.String(), "/lib64/") {
     symlink = android.PathForHostInstall(ctx, "testcases", moduleName, "x86_64", "shared_libs", common.Base())
     symlinkTargetPrefix = "../../../lib64"
     libInTestCase = android.PathForHostInstall(ctx, "testcases", "lib64", common.Base())
    } else {
     symlink = android.PathForHostInstall(ctx, "testcases", moduleName, "x86", "shared_libs", common.Base())
     symlinkTargetPrefix = "../../../lib"
     libInTestCase = android.PathForHostInstall(ctx, "testcases", "lib", common.Base())
    }

    testsZipCmdHostFileInputContent = append(testsZipCmdHostFileInputContent, symlink.String())
    testsZipCmd.Implicit(symlink)

    symlinksForModuleTarget = append(symlinksForModuleTarget, symlink, libInTestCase)

    // Adding host shared libs symbolic links to general-tests-files-list, e.g.,
    // out/host/linux-x86/testcases/hello_world_test/x86_64/shared_libs/libc++.so
    filesListLines = append(filesListLines, symlink.String())
    hostFilesListLines = append(hostFilesListLines, symlink.String())

    if _, exists := seenSymlinks[symlink.String()]; exists {
     continue
    }
    seenSymlinks[symlink.String()] = true
    ctx.Build(pctx, android.BuildParams{
     Rule:   android.Symlink,
     Output: symlink,
     Args: map[string]string{
      "fromPath": fmt.Sprintf("%s/%s", symlinkTargetPrefix, common.Base()),
     },
    })
   }
   if len(symlinksForModuleTarget) != 0 {
    ctx.Phony(moduleName, android.SortedUniquePaths(symlinksForModuleTarget)...)
   }
  }
 }

 android.WriteFileRule(ctx, testsZipCmdHostFileInput, strings.Join(testsZipCmdHostFileInputContent, "\n"))

 testsZipCmd.
  FlagWithArg("-P ", "host").
  FlagWithArg("-C ", hostOut).
  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))
   }

   // Adding files installed in out/target to general-tests-files-list, e.g.,
   // out/target/product/vsoc_x86_64_only/testcases/hello_world_test/hello_world_test.config
   filesListLines = append(filesListLines, f.String())
   targetFilesListLines = append(targetFilesListLines, f.String())
  }
 }

 android.WriteFileRule(ctx, testsZipCmdTargetFileInput, strings.Join(testsZipCmdTargetFileInputContent, "\n"))
 testsZipCmd.FlagWithInput("-l ", testsZipCmdTargetFileInput)

 if len(suiteConfig.hostJavaToolFiles) > 0 {
  testsZipCmd.FlagWithArg("-P ", "host/tools")
  testsZipCmd.Flag("-j")

  for _, hostJavaTool := range suiteConfig.hostJavaToolFiles {
   testsZipCmd.FlagWithInput("-f ", hostJavaTool)
  }
 }

 // Special handling for art_data_zips. This is a custom property for ART's needs.
 // It points to a filegroup that generates the art_common directory, which
 // must be placed under host/testcases in the final zip.
 if len(suiteConfig.Art_data_zips) > 0 {
  unmergedTestsZip := pathForPackaging(ctx, suiteConfig.name+"-unmerged.zip")
  testsZipCmd.FlagWithOutput("-o ", unmergedTestsZip)
  testsZipBuilder.Build(suiteConfig.name+"-unmerged", "building "+suiteConfig.name+" unmerged zip")

  mergeBuilder := android.NewRuleBuilder(pctx, ctx).SandboxDisabled()
  mergeBuilder.Command().
   BuiltTool("merge_zips").
   Output(testsZip).
   Inputs(android.Paths{unmergedTestsZip}).
   Inputs(suiteConfig.Art_data_zips)
  mergeBuilder.Build(suiteConfig.name+"-merge", "merging "+suiteConfig.name+" zip")
 } else {
  testsZipCmd.FlagWithOutput("-o ", testsZip)
  testsZipBuilder.Build(suiteConfig.name, "building "+suiteConfig.name+" zip")
 }

 testsConfigsZipBuilder.Build(suiteConfig.name+"-configs", "building "+suiteConfig.name+" configs zip")

 if suiteConfig.buildHostSharedLibsZip {
  testsHostSharedLibsZipBuilder := android.NewRuleBuilder(pctx, ctx).SandboxDisabled()
  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")
 }

 android.WriteFileRule(ctx, testsListTxt, strings.Join(listLines, "\n"))
 // https://source.corp.google.com/h/googleplex-android/platform/build/+/c34c3738ba6be7ef1fc3acb7be5122bede415789
 if suiteConfig.name == "general-tests" {
  android.WriteFileRule(ctx, generalTestsFilesListText, strings.Join(filesListLines, "\n"))
  android.WriteFileRule(ctx, generalTestsHostFilesListText, strings.Join(hostFilesListLines, "\n"))
  android.WriteFileRule(ctx, generalTestsTargetFilesListText, strings.Join(targetFilesListLines, "\n"))
 }
 testsListZipBuilder := android.NewRuleBuilder(pctx, ctx).SandboxDisabled()
 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")

 if ctx.Config().JavaCoverageEnabled() {
  jacocoJar := pathForPackaging(ctx, suiteConfig.name+"_jacoco_report_classes.jar")
  java.BuildJacocoZip(ctx, modules, jacocoJar)
 }

 ctx.Phony(suiteConfig.name, testsZip, testsListZip, testsConfigsZip)
 ctx.Phony("general-tests-files-list", generalTestsFilesListText, generalTestsHostFilesListText, generalTestsTargetFilesListText)
 ctx.DistForGoal(suiteConfig.name, testsZip, testsListZip, testsConfigsZip)
 if suiteConfig.buildHostSharedLibsZip {
  ctx.DistForGoal(suiteConfig.name, testsHostSharedLibsZip)
 }
 ctx.Phony("tests", android.PathForPhony(ctx, suiteConfig.name))
}

func buildCompatibilitySuitePackage(
 ctx android.SingletonContext,
 suite compatibilitySuitePackageInfo,
 testSuiteFiles android.Paths,
 testSuiteModules []android.ModuleProxy,
 testSuiteLibs android.Paths,
) {
 testSuiteName := suite.Name
 subdir := fmt.Sprintf("android-%s", testSuiteName)
 if suite.TestSuiteSubdir != "" {
  subdir = suite.TestSuiteSubdir
 }

 hostOutSuite := android.PathForHostInstall(ctx, testSuiteName)
 hostOutTestCases := hostOutSuite.Join(ctx, subdir, "testcases")
 hostOutTools := hostOutSuite.Join(ctx, subdir, "tools")
 testSuiteFiles = slices.DeleteFunc(testSuiteFiles, func(f android.Path) bool {
  return !strings.HasPrefix(f.String(), hostOutTestCases.String()+"/")
 })

 // Some make rules still rely on the zip being at this location
 out := hostOutSuite.Join(ctx, fmt.Sprintf("android-%s.zip", testSuiteName))
 builder := android.NewRuleBuilder(pctx, ctx).SandboxDisabled()
 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 android.Path) {
  builder.Command().Text("cp").Input(tool).Output(hostOutTools.Join(ctx, tool.Base()))
 }

 hostTools := suite.ToolFiles
 for _, hostTool := range hostTools {
  cmd.
   FlagWithArg("-e ", subdir+"/tools/"+hostTool.Base()).
   FlagWithInput("-f ", hostTool)
  copyTool(hostTool)
 }

 if suite.Readme != nil {
  cmd.
   FlagWithArg("-e ", subdir+"/tools/"+suite.Readme.Base()).
   FlagWithInput("-f ", suite.Readme)
  copyTool(suite.Readme)
 }

 if suite.Aliases != nil {
  cmd.
   FlagWithArg("-e ", subdir+"/tools/aliases").
   FlagWithInput("-f ", suite.Aliases)
  builder.Command().Text("cp").Input(suite.Aliases).Output(hostOutTools.Join(ctx, "aliases"))
 }

 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"))
 }

 hostSharedLibs := suite.HostSharedLibs
 for _, hostSharedLib := range hostSharedLibs {
  cmd.
   FlagWithArg("-e ", subdir+"/lib64/"+hostSharedLib.Base()).
   FlagWithInput("-f ", hostSharedLib)
  builder.Command().Text("cp").Input(hostSharedLib).Output(hostOutSuite.Join(ctx, subdir, "lib64", hostSharedLib.Base()))
 }

 licenceInfos := slices.Concat(android.GetNoticeModuleInfos(ctx, testSuiteModules), suite.ToolNoticeInfo)
 if len(licenceInfos) > 0 {
  notice := android.PathForOutput(ctx, "compatibility_test_suites", testSuiteName, "NOTICE.txt")
  android.BuildNoticeTextOutputFromNoticeModuleInfos(ctx, notice, "compatibility_"+testSuiteName, "Test suites",
   android.BuildNoticeFromLicenseDataArgs{
    Title:       "Notices for files contained in the test suites filesystem image:",
    StripPrefix: []string{hostOutSuite.String()},
    Filter:      slices.Concat(testSuiteFiles.Strings(), hostTools.Strings()),
    Replace: []android.NoticeReplace{
     {
      From: ctx.Config().HostJavaToolPath(ctx, "").String(),
      To:   "/" + subdir + "/tools",
     },
     {
      From: ctx.Config().HostToolPath(ctx, "").String(),
      To:   "/" + subdir + "/tools",
     },
    },
   },
   licenceInfos)

  cmd.
   FlagWithArg("-e ", subdir+"/NOTICE.txt").
   FlagWithInput("-f ", notice)
 }

 cmd.FlagWithArg("-C ", hostOutSuite.String())
 for _, f := range testSuiteFiles {
  cmd.FlagWithInput("-f ", f)
 }

 cmd.FlagWithArg("-C ", hostOutSuite.String())
 for _, f := range testSuiteLibs {
  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)
 // TODO: Enable NoDist by default for all compatibility test suites.
 if !suite.NoDist {
  ctx.DistForGoal(testSuiteName, out)
 }

 if suite.BuildSharedReport {
  buildSharedReport(ctx, suite, testSuiteFiles, testSuiteModules, testSuiteLibs, subdir, out)
 }

 if suite.BuildTestList == true {
  // Original compatibility_tests_list_zip in build/make/core/tasks/tools/compatibility.mk
  // output is $(HOST_OUT)/$(test_suite_name)/android-$(test_suite_name)-tests_list.zip
  testsListTxt := hostOutSuite.Join(ctx, fmt.Sprintf("android-%s-tests_list", testSuiteName))
  testsListZip := hostOutSuite.Join(ctx, fmt.Sprintf("android-%s-tests_list.zip", testSuiteName))
  var listLines []string
  for _, f := range testSuiteFiles {
   if f.Ext() == ".config" {
    listLines = append(listLines, strings.TrimPrefix(f.String(), hostOutTestCases.String()+"/"))
   }
  }
  sort.Strings(listLines)
  android.WriteFileRule(ctx, testsListTxt, strings.Join(listLines, "\n"))

  testsListZipBuilder := android.NewRuleBuilder(pctx, ctx).SandboxDisabled()
  testsListZipBuilder.Command().
   BuiltTool("soong_zip").
   FlagWithOutput("-o ", testsListZip).
   FlagWithArg("-C ", hostOutSuite.String()).
   FlagWithInput("-f ", testsListTxt)
  testsListZipBuilder.Build("tests_list_"+testSuiteName, fmt.Sprintf("Compatibility test suite test list %q", testSuiteName))

  ctx.Phony(testSuiteName, testsListZip)
  ctx.DistForGoal(testSuiteName, testsListZip)
 }

 if suite.BuildMetadata {
  metadata_rule := android.NewRuleBuilder(pctx, ctx).SandboxDisabled()
  compatibility_files_metadata := hostOutSuite.Join(ctx, fmt.Sprintf("%s_files_metadata.textproto", testSuiteName))

  metadata_rule.Command().BuiltTool("file_metadata_generation").
   FlagWithArg("--testcases_dir ", hostOutTestCases.String()).
   FlagWithInput("--aapt2 ", ctx.Config().HostToolPath(ctx, "aapt2")).
   FlagWithArg("--sdk_version ", ctx.Config().PlatformSdkVersion().String()).
   FlagWithOutput("--output ", compatibility_files_metadata).
   Implicit(out)
  metadata_rule.Build("compatibility_metadata_"+testSuiteName, fmt.Sprintf("Compatibility test suite metadata file %q", testSuiteName))

  ctx.Phony(testSuiteName, compatibility_files_metadata)
  if !suite.NoDist {
   ctx.DistForGoal(testSuiteName, compatibility_files_metadata)
  }
 }
}

func buildSharedReport(
 ctx android.SingletonContext,
 suite compatibilitySuitePackageInfo,
 testSuiteFiles android.Paths,
 testSuiteModules []android.ModuleProxy,
 testSuiteLibs android.Paths,
 subdir string,
 out android.InstallPath,
) {
 testSuiteName := suite.Name
 hostOutSuite := android.PathForHostInstall(ctx, testSuiteName)
 hostOutSubDir := hostOutSuite.String() + "/" + subdir + "/"

 compatibilityOutput := android.PathForOutput(ctx, "compatibility_test_suites", testSuiteName)
 metaLicOutput := compatibilityOutput.Join(ctx, "meta_lic")
 // Aggregate license metadata from all component modules into a single file.
 var componentMetadataFiles android.Paths
 meta_builder := android.NewRuleBuilder(pctx, ctx).SandboxDisabled()
 for _, mod := range testSuiteModules {
  if provider := android.OtherModulePointerProviderOrDefault(ctx, mod, android.CommonModuleInfoProvider).LicenseMetadata; provider != nil && provider.LicenseMetadataPath != nil {
   if testSuite, ok := android.OtherModuleProvider(ctx, mod, android.TestSuiteInfoProvider); ok && testSuite.TestSuiteInstalls != nil {
    testSuiteInstalls := testSuite.TestSuiteInstalls
    for _, f := range testSuiteInstalls.Files {
     if strings.Contains(f.Dst.String(), mod.Name()) && strings.HasPrefix(f.Dst.String(), hostOutSubDir) {
      if f.Dst.Ext() == ".jar" || f.Dst.Ext() == ".apk" {
       subdir_meta_file := strings.TrimPrefix(f.Dst.String(), hostOutSubDir) + ".meta_lic"
       target_meta_file := metaLicOutput.Join(ctx, subdir_meta_file)
       meta_builder.Command().Text("cp").
        Input(provider.LicenseMetadataPath).
        Output(target_meta_file)
       componentMetadataFiles = append(componentMetadataFiles, target_meta_file)
       break
      }
     }
    }
   }
  }
 }

 var toolFilenames []string
 for _, f := range suite.ToolFiles {
  toolFilenames = append(toolFilenames, f.Base())
 }
 for _, info := range suite.ToolNoticeInfo {
  if info.LicenseMetadataFile != nil {
   var installed_files []string
   for _, filename := range toolFilenames {
    fname := strings.SplitN(filename, ".", 2)
    if fname[0] == info.Name {
     installed_files = append(installed_files, filename)
    }
   }
   for _, f := range installed_files {
    target_tool_file := metaLicOutput.Join(ctx, "tools", f+".meta_lic")
    meta_builder.Command().Text("cp").
     Input(info.LicenseMetadataFile).
     Output(target_tool_file)
    componentMetadataFiles = append(componentMetadataFiles, target_tool_file)
   }
  }
 }
 meta_builder.Build("cp_meta_lic_"+testSuiteName, fmt.Sprintf("cp meta lic %q", testSuiteName))
 componentMetadataFiles = android.SortedUniquePaths(componentMetadataFiles)

 // Also include all the files that are directly included in the zip as sources.
 allSources := slices.Concat(testSuiteFiles, testSuiteLibs)
 allSources = android.SortedUniquePaths(allSources)
 metaLic := android.PathForOutput(ctx, out.Base()+".meta_lic")
 var args []string
 args = append(args, "-mn ", out.Base())
 args = append(args, "-mt compatibility_test_suite_package")
 args = append(args, "--is_container")
 args = append(args, android.JoinWithPrefix(proptools.NinjaAndShellEscapeListIncludingSpaces(componentMetadataFiles.Strings()), "-d "))
 args = append(args, android.JoinWithPrefix(proptools.NinjaAndShellEscapeListIncludingSpaces(allSources.Strings()), "-s "))
 ctx.Build(pctx, android.BuildParams{
  Rule:        licenseMetadataRule,
  Output:      metaLic,
  Implicits:   append(componentMetadataFiles, allSources...),
  Description: fmt.Sprintf("aggregate %q license metadata", testSuiteName),
  Args: map[string]string{
   "args": strings.Join(args, " "),
  },
 })

 // Only gts build shared report. The output is gts-shared-report.txt.
 gtsReportBuilder := android.NewRuleBuilder(pctx, ctx).SandboxDisabled()
 reportCmd := gtsReportBuilder.Command().BuiltTool("generate_gts_shared_report")
 reportCmd.FlagWithInput("--checkshare ", ctx.Config().HostToolPath(ctx, "compliance_checkshare"))
 shared_report := hostOutSuite.Join(ctx, "gts-shared-report.txt")
 reportCmd.FlagWithOutput("-o ", shared_report)
 reportCmd.FlagWithInput("--gts-test-metalic ", metaLic)
 reportCmd.FlagWithArg("--gts-test-dir ", "compatibility_test_suites/"+testSuiteName+"/meta_lic")
 reportCmd.Implicit(out)
 gtsReportBuilder.Build("gen_"+testSuiteName+"_shared_report", "generate gts_shared_report.txt")

 ctx.Phony(testSuiteName, shared_report)
 ctx.DistForGoal(testSuiteName, shared_report)
}

func addJdkToZip(ctx android.SingletonContext, command *android.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 := android.PathsForSource(ctx, files)

 command.
  FlagWithArg("-P ", subdir+"/jdk").
  FlagWithArg("-C ", jdkHome).
  FlagWithArg("-D ", jdkHome).
  Flag("-sha256").Implicits(paths)
}

// 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() android.Module {
 m := &compatibilityTestSuitePackage{}
 android.InitAndroidModule(m)
 m.AddProperties(&m.properties)
 return m
}

type compatibilityTestSuitePackageProperties struct {
 Tradefed            *string
 Readme              *string `android:"path"`
 Tools               []string
 Dynamic_config      *string `android:"path"`
 Host_shared_libs    []string
 Build_test_list     *bool
 Build_metadata      *bool
 Build_shared_report *bool
 // If true, the test suite will not be included in the dist-for-goal method.
 No_dist *bool
 // If set, this will override the name property used in the zip file. This is useful when the test suite
 // requires post-processing, so the module name does not conflict with the original test suite name.
 Test_suite_name   *string `json:"test_suite_name"`
 Test_suite_subdir *string
 // Path to the config defining command aliases for the test suite console.
 Aliases *string `android:"path"`
 phony.PhonyProperties
}

type compatibilityTestSuitePackage struct {
 android.ModuleBase
 properties compatibilityTestSuitePackageProperties
}

// @auto-generate: gob
type compatibilitySuitePackageInfo struct {
 Name              string
 Readme            android.Path
 DynamicConfig     android.Path
 ToolFiles         android.Paths
 ToolNoticeInfo    android.NoticeModuleInfos
 HostSharedLibs    android.Paths
 BuildTestList     bool
 BuildMetadata     bool
 BuildSharedReport bool
 NoDist            bool
 TestSuiteSubdir   string
 Aliases           android.Path
}

var compatibilitySuitePackageProvider = blueprint.NewProvider[compatibilitySuitePackageInfo]()

type ctspTradefedDeptagType struct {
 blueprint.BaseDependencyTag
}

var ctspTradefedDeptag = ctspTradefedDeptagType{}

type ctspHostJavaToolDeptagType struct {
 blueprint.BaseDependencyTag
}

var ctspHostJavaToolDeptag = ctspHostJavaToolDeptagType{}

type ctspHostToolDeptagType struct {
 blueprint.BaseDependencyTag
}

var ctspHostToolDeptag = ctspHostToolDeptagType{}

type ctspHostSharedLibDeptagType struct {
 blueprint.BaseDependencyTag
}

var ctspHostSharedLibDeptag = ctspHostSharedLibDeptagType{}

func (m *compatibilityTestSuitePackage) DepsMutator(ctx android.BottomUpMutatorContext) {
 variations := ctx.Config().BuildOSTarget.Variations()
 ctx.AddVariationDependencies(variations, ctspTradefedDeptag, proptools.String(m.properties.Tradefed))
 ctx.AddVariationDependencies(variations, ctspHostJavaToolDeptag, proptools.String(m.properties.Tradefed)+"-tests")
 ctx.AddVariationDependencies(variations, ctspHostJavaToolDeptag, "tradefed")
 ctx.AddVariationDependencies(variations, ctspHostJavaToolDeptag, "loganalysis")
 ctx.AddVariationDependencies(variations, ctspHostJavaToolDeptag, "compatibility-host-util")
 ctx.AddVariationDependencies(variations, ctspHostJavaToolDeptag, "compatibility-tradefed")
 ctx.AddVariationDependencies(variations, ctspHostToolDeptag, "test-utils-script")
 for _, tool := range m.properties.Tools {
  ctx.AddVariationDependencies(variations, ctspHostJavaToolDeptag, tool)
 }
 for _, host_shared_lib := range m.properties.Host_shared_libs {
  ctx.AddVariationDependencies(variations, ctspHostSharedLibDeptag, host_shared_lib)
 }
}

func (m *compatibilityTestSuitePackage) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 suiteName := m.Name()
 if m.properties.Test_suite_name != nil && *m.properties.Test_suite_name != "" {
  suiteName = *m.properties.Test_suite_name
 }

 if matched, err := regexp.MatchString("[a-zA-Z0-9_-]+", suiteName); err != nil || !matched {
  ctx.ModuleErrorf("Invalid test suite name, must match [a-zA-Z0-9_-]+, got %q", suiteName)
  return
 }

 tradefedName := proptools.String(m.properties.Tradefed)
 tradefed := ctx.GetDirectDepProxyWithTag(tradefedName, ctspTradefedDeptag)

 tradefedFiles := android.GetInstallFiles(ctx, tradefed).InstallFiles

 if len(tradefedFiles) != 2 || tradefedFiles[0].Base() != tradefedName || tradefedFiles[1].Base() != tradefedName+".jar" {
  ctx.PropertyErrorf("tradefed", "Dependency %q did not provide expected files, produced: %s", tradefedName, tradefedFiles.Strings())
 }

 var toolFiles android.Paths
 for _, f := range tradefedFiles {
  toolFiles = append(toolFiles, f)
 }
 toolNoticeinfo := android.NoticeModuleInfos{android.GetNoticeModuleInfo(ctx, tradefed)}

 ctx.VisitDirectDepsProxyWithTag(ctspHostJavaToolDeptag, func(dep android.ModuleProxy) {
  file := android.OtherModuleProviderOrDefault(ctx, dep, java.JavaInfoProvider).InstallFile
  if file == nil {
   ctx.PropertyErrorf("tools", "Dependency %q did not provide java installfile, is it a java module?", ctx.OtherModuleName(dep))
  }
  toolFiles = append(toolFiles, file)
  toolNoticeinfo = append(toolNoticeinfo, android.GetNoticeModuleInfo(ctx, dep))
 })

 ctx.VisitDirectDepsProxyWithTag(ctspHostToolDeptag, func(dep android.ModuleProxy) {
  files := android.GetInstallFiles(ctx, dep).InstallFiles
  if len(files) != 1 {
   ctx.ModuleErrorf("Dependency %q did not provide expected single file", ctx.OtherModuleName(dep))
  }
  for _, f := range files {
   toolFiles = append(toolFiles, f)
  }
  toolNoticeinfo = append(toolNoticeinfo, android.GetNoticeModuleInfo(ctx, dep))
 })

 var hostSharedLibs android.Paths
 ctx.VisitDirectDepsProxyWithTag(ctspHostSharedLibDeptag, func(dep android.ModuleProxy) {
  libs := android.GetInstallFiles(ctx, dep).InstallFiles
  for _, lib := range libs {
   hostSharedLibs = append(hostSharedLibs, lib)
  }
  toolNoticeinfo = append(toolNoticeinfo, android.GetNoticeModuleInfo(ctx, dep))
 })

 var readme android.Path
 if m.properties.Readme != nil {
  readme = android.PathForModuleSrc(ctx, *m.properties.Readme)
 }

 var dynamicConfig android.Path
 if m.properties.Dynamic_config != nil {
  dynamicConfig = android.PathForModuleSrc(ctx, *m.properties.Dynamic_config)
 }

 var aliases android.Path
 if m.properties.Aliases != nil {
  aliases = android.PathForModuleSrc(ctx, *m.properties.Aliases)
 }

 android.SetProvider(ctx, compatibilitySuitePackageProvider, compatibilitySuitePackageInfo{
  Name:              suiteName,
  Readme:            readme,
  DynamicConfig:     dynamicConfig,
  ToolFiles:         toolFiles,
  ToolNoticeInfo:    toolNoticeinfo,
  HostSharedLibs:    hostSharedLibs,
  BuildTestList:     proptools.BoolDefault(m.properties.Build_test_list, true),
  BuildMetadata:     proptools.Bool(m.properties.Build_metadata),
  BuildSharedReport: proptools.Bool(m.properties.Build_shared_report),
  NoDist:            proptools.Bool(m.properties.No_dist),
  TestSuiteSubdir:   proptools.String(m.properties.Test_suite_subdir),
  Aliases:           aliases,
 })

 // Make compatibility_test_suite_package a SourceFileProducer so that it can be used by other modules.
 ctx.SetOutputFiles(android.Paths{android.PathForHostInstall(ctx, suiteName, fmt.Sprintf("android-%s.zip", suiteName))}, "")
 for _, dep := range m.properties.Phony_deps.GetOrDefault(ctx, nil) {
  ctx.Phony(m.Name(), android.PathForPhony(ctx, dep))
 }
}

func testSuitePackageFactory() android.Module {
 m := &testSuitePackage{}
 android.InitAndroidModule(m)
 m.AddProperties(&m.properties)
 return m
}

type testSuitePackageProperties struct {
 Build_host_shared_libs_zip           *bool
 Include_host_shared_libs_in_main_zip *bool
 // When true, a symlink will be created per test for any
 // shared library dependencies that are not installed alongside the test.
 //
 // e.g. host/testcases/$test/x86_64/shared_libs/libfoo.so --> ../../../lib64/libfoo.so
 // The target of the symlink will be the host/testcases/lib64/libfoo.so
 Include_common_host_shared_libs_symlinks_in_main_zip *bool
 Host_java_tools                                      []string
 // Custom property for ART's needs. This points to a zip file (or a list of zip files)
 // that provides extra data files for ART host tests. The content of the zip(s) will be
 // merged into the final test suite zip. For example, it is used to package the
 // art_common_host_data_files.zip file.
 Art_data_zips []string `android:"path"`
}

type testSuitePackage struct {
 android.ModuleBase
 properties testSuitePackageProperties
 outputFile android.Path
}

var testSuitePackageProvider = blueprint.NewProvider[testSuiteConfig]()

type tspHostJavaToolDeptagType struct {
 blueprint.BaseDependencyTag
}

var tspHostJavaToolDeptag = tspHostJavaToolDeptagType{}

func (t *testSuitePackage) DepsMutator(ctx android.BottomUpMutatorContext) {
 variations := ctx.Config().BuildOSTarget.Variations()
 ctx.AddVariationDependencies(variations, tspHostJavaToolDeptag, t.properties.Host_java_tools...)
}

// testSuitePackage does not have build actions of its own.
// It sets a provider with info about its packaging config.
// A singleton will create the build rules to create the .zip files.
func (t *testSuitePackage) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 var toolFiles android.Paths
 ctx.VisitDirectDepsProxyWithTag(tspHostJavaToolDeptag, func(dep android.ModuleProxy) {
  file := android.OtherModuleProviderOrDefault(ctx, dep, java.JavaInfoProvider).InstallFile
  if file == nil {
   ctx.PropertyErrorf("host_java_tools", "Dependency %q did not provide java installfile, is it a java module?", ctx.OtherModuleName(dep))
  }
  toolFiles = append(toolFiles, file)
 })

 android.SetProvider(ctx, testSuitePackageProvider, testSuiteConfig{
  name:                           t.Name(),
  buildHostSharedLibsZip:         proptools.Bool(t.properties.Build_host_shared_libs_zip),
  includeHostSharedLibsInMainZip: proptools.Bool(t.properties.Include_host_shared_libs_in_main_zip),
  includeCommonHostSharedLibsSymlinksInMainZip: proptools.Bool(t.properties.Include_common_host_shared_libs_symlinks_in_main_zip),
  hostJavaToolFiles: toolFiles,
  Art_data_zips:     android.PathsForModuleSrc(ctx, t.properties.Art_data_zips),
 })

 t.outputFile = pathForPackaging(ctx, t.Name()+".zip")

 if ctx.Config().JavaCoverageEnabled() {
  jacocoJar := pathForPackaging(ctx, t.Name()+"_jacoco_report_classes.jar")
  ctx.SetOutputFiles(android.Paths{jacocoJar}, ".jacoco")

  // This phony is for BWYN, as it will try to "optimize" the test zip file but doesn't
  // have logic to optimize the jacoco zip. So it just builds the jacoco zip separately.
  ctx.Phony(ctx.ModuleName()+"-jacoco", jacocoJar)
 }
}

// Some things (like the module phony and disting) still need an AndroidMkEntries() function
// despite this module not being used from make.
func (p *testSuitePackage) AndroidMkEntries() []android.AndroidMkEntries {
 return []android.AndroidMkEntries{
  android.AndroidMkEntries{
   Class:      "ETC",
   OutputFile: android.OptionalPathForPath(p.outputFile),
   ExtraEntries: []android.AndroidMkExtraEntriesFunc{
    func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
     entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", true)
    }},
  },
 }
}

[Seitenstruktur0.36Druckenetwas mehr zur Ethik2026-06-28]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik