blob: fee3e4080b20c9695c48c7c07484f2cafd1f2f89 [file] [log] [blame]
// Copyright 2023 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 java
import (
"strconv"
"android/soong/aconfig"
"android/soong/android"
"android/soong/tradefed"
"github.com/google/blueprint"
"github.com/google/blueprint/proptools"
)
func init() {
RegisterRavenwoodBuildComponents(android.InitRegistrationContext)
}
func RegisterRavenwoodBuildComponents(ctx android.RegistrationContext) {
ctx.RegisterModuleType("android_ravenwood_test", ravenwoodTestFactory)
ctx.RegisterModuleType("android_ravenwood_libgroup", ravenwoodLibgroupFactory)
}
var ravenwoodLibContentTag = dependencyTag{name: "ravenwoodlibcontent"}
var ravenwoodUtilsTag = dependencyTag{name: "ravenwoodutils"}
var ravenwoodRuntimeTag = dependencyTag{name: "ravenwoodruntime"}
var ravenwoodTargetResourceApkTag = dependencyTag{name: "ravenwood-target-res-apk"}
var allAconfigModuleTag = dependencyTag{name: "all_aconfig"}
var genManifestProperties = pctx.AndroidStaticRule("genManifestProperties",
blueprint.RuleParams{
Command: "echo targetSdkVersionInt=$targetSdkVersionInt > $out && " +
"echo targetSdkVersionRaw=$targetSdkVersionRaw >> $out && " +
"echo packageName=$packageName >> $out && " +
"echo targetPackageName=$targetPackageName >> $out && " +
"echo instrumentationClass=$instrumentationClass >> $out && " +
"echo moduleName=$moduleName >> $out && " +
"echo resourceApk=$resourceApk >> $out && " +
"echo targetResourceApk=$targetResourceApk >> $out",
},
"targetSdkVersionInt", "targetSdkVersionRaw", "packageName", "targetPackageName",
"instrumentationClass", "moduleName", "resourceApk", "targetResourceApk",
)
const ravenwoodUtilsName = "ravenwood-utils"
const ravenwoodRuntimeName = "ravenwood-runtime"
type ravenwoodLibgroupJniDepProviderInfo struct {
// All the jni_libs module names with transient dependencies.
names map[string]bool
}
var ravenwoodLibgroupJniDepProvider = blueprint.NewProvider[ravenwoodLibgroupJniDepProviderInfo]()
func getLibPath(archType android.ArchType) string {
if archType.Multilib == "lib64" {
return "lib64"
}
return "lib"
}
type ravenwoodTestProperties struct {
// Specify the name of the Instrumentation subclass to use.
// (e.g. "androidx.test.runner.AndroidJUnitRunner")
Instrumentation_class *string
// Specify the package name of the test target apk.
// This will be set to the target Context's package name.
// (i.e. Instrumentation.getTargetContext().getPackageName())
// If this is omitted, Package_name will be used.
Target_package_name *string
// Specify another android_app module here to copy it to the test directory, so that
// the ravenwood test can access it. This APK will be loaded as resources of the test
// target app.
Target_resource_apk *string
// Specify whether to build resources.
Build_resources *bool
}
type ravenwoodTest struct {
Library
aapt
ravenwoodTestProperties ravenwoodTestProperties
testProperties testProperties
testConfig android.Path
data android.Paths
forceOSType android.OsType
forceArchType android.ArchType
}
func ravenwoodTestFactory() android.Module {
module := &ravenwoodTest{}
module.addHostAndDeviceProperties()
module.AddProperties(&module.aaptProperties, &module.testProperties, &module.ravenwoodTestProperties)
module.Module.dexpreopter.isTest = true
module.Module.linter.properties.Lint.Test_module_type = proptools.BoolPtr(true)
module.testProperties.Test_suites = []string{
"general-tests",
"ravenwood-tests",
}
module.testProperties.Test_options.Unit_test = proptools.BoolPtr(false)
module.Module.sourceProperties.Test_only = proptools.BoolPtr(true)
module.Module.sourceProperties.Top_level_test_target = true
InitJavaModule(module, android.DeviceSupported)
android.InitDefaultableModule(module)
return module
}
func (r *ravenwoodTest) InstallInTestcases() bool { return true }
func (r *ravenwoodTest) InstallForceOS() (*android.OsType, *android.ArchType) {
return &r.forceOSType, &r.forceArchType
}
func (r *ravenwoodTest) TestSuites() []string {
return r.testProperties.Test_suites
}
func (r *ravenwoodTest) DepsMutator(ctx android.BottomUpMutatorContext) {
r.Library.DepsMutator(ctx)
// Generically depend on the runtime so that it's installed together with us
ctx.AddVariationDependencies(nil, ravenwoodRuntimeTag, ravenwoodRuntimeName)
// Directly depend on any utils so that we link against them
utils := ctx.AddVariationDependencies(nil, ravenwoodUtilsTag, ravenwoodUtilsName)[0]
if utils != nil {
for _, lib := range utils.(*ravenwoodLibgroup).ravenwoodLibgroupProperties.Libs {
ctx.AddVariationDependencies(nil, libTag, lib)
}
}
// Add jni libs
for _, lib := range r.testProperties.Jni_libs.GetOrDefault(ctx, nil) {
ctx.AddVariationDependencies(ctx.Config().BuildOSTarget.Variations(), jniLibTag, lib)
}
// Resources APK
if resourceApk := proptools.String(r.ravenwoodTestProperties.Target_resource_apk); resourceApk != "" {
ctx.AddVariationDependencies(nil, ravenwoodTargetResourceApkTag, resourceApk)
}
sdkDep := decodeSdkDep(ctx, android.SdkContext(r))
if sdkDep.hasFrameworkLibs() {
r.aapt.deps(ctx, sdkDep)
}
for _, aconfig_declaration := range r.aaptProperties.Flags_packages {
ctx.AddDependency(ctx.Module(), aconfigDeclarationTag, aconfig_declaration)
}
}
func (r *ravenwoodTest) GenerateAndroidBuildActions(ctx android.ModuleContext) {
r.forceOSType = ctx.Config().BuildOS
r.forceArchType = ctx.Config().BuildArch
r.testConfig = tradefed.AutoGenTestConfig(ctx, tradefed.AutoGenTestConfigOptions{
TestConfigProp: r.testProperties.Test_config,
TestConfigTemplateProp: r.testProperties.Test_config_template,
OptionsForAutogenerated: r.testProperties.Test_options.Tradefed_options,
TestRunnerOptions: r.testProperties.Test_options.Test_runner_options,
TestSuites: r.testProperties.Test_suites,
AutoGenConfig: r.testProperties.Auto_gen_config,
DeviceTemplate: "${RavenwoodTestConfigTemplate}",
HostTemplate: "${RavenwoodTestConfigTemplate}",
})
r.data = android.PathsForModuleSrc(ctx, r.testProperties.Data)
r.data = append(r.data, android.PathsForModuleSrc(ctx, r.testProperties.Device_common_data)...)
r.data = append(r.data, android.PathsForModuleSrc(ctx, r.testProperties.Device_first_data)...)
r.data = append(r.data, android.PathsForModuleSrc(ctx, r.testProperties.Device_first_prefer32_data)...)
r.data = append(r.data, android.PathsForModuleSrc(ctx, r.testProperties.Host_common_data)...)
r.data = append(r.data, android.PathsForModuleSrc(ctx, r.testProperties.Host_first_data)...)
r.data = android.SortedUniquePaths(r.data)
var testData []android.DataPath
for _, data := range r.data {
dataPath := android.DataPath{SrcPath: data}
testData = append(testData, dataPath)
}
for _, d := range r.extraOutputFiles {
testData = append(testData, android.DataPath{SrcPath: d})
}
// When setting the manifest property, we only want to set it for aaptProperties.
// Explicitly remove it from the Module properties to prevent it from using
// AndroidManifest.xml as JAR manifest, creating a malformed JAR file.
r.Module.properties.Manifest = nil
// Build resources before Java sources.
var resourceApk android.Path
if proptools.Bool(r.ravenwoodTestProperties.Build_resources) {
r.aaptBuildActions(ctx)
resourceApk = r.aapt.exportPackage
}
// Always enable Ravenizer for ravenwood tests.
r.Library.ravenizer.enabled = true
r.Library.GenerateAndroidBuildActions(ctx)
// Start by depending on all files installed by dependencies
var installDeps android.InstallPaths
// All JNI libraries included in the runtime
var runtimeJniModuleNames map[string]bool
utils := ctx.GetDirectDepsProxyWithTag(ravenwoodUtilsTag)[0]
for _, installFile := range android.OtherModuleProviderOrDefault(
ctx, utils, android.InstallFilesProvider).InstallFiles {
installDeps = append(installDeps, installFile)
}
jniDeps, ok := android.OtherModuleProvider(ctx, utils, ravenwoodLibgroupJniDepProvider)
if ok {
runtimeJniModuleNames = jniDeps.names
}
runtime := ctx.GetDirectDepsProxyWithTag(ravenwoodRuntimeTag)[0]
for _, installFile := range android.OtherModuleProviderOrDefault(
ctx, runtime, android.InstallFilesProvider).InstallFiles {
installDeps = append(installDeps, installFile)
}
jniDeps, ok = android.OtherModuleProvider(ctx, runtime, ravenwoodLibgroupJniDepProvider)
if ok {
runtimeJniModuleNames = jniDeps.names
}
// Also remember what JNI libs are in the runtime.
// Also depend on our config
installPath := android.PathForModuleInstall(ctx, r.BaseModuleName())
installConfig := ctx.InstallFile(installPath, ctx.ModuleName()+".config", r.testConfig)
installDeps = append(installDeps, installConfig)
// Depend on the JNI libraries, but don't install the ones that the runtime already
// contains.
soInstallPath := installPath.Join(ctx, getLibPath(r.forceArchType))
for _, jniLib := range collectTransitiveJniDeps(ctx) {
if _, ok := runtimeJniModuleNames[jniLib.name]; ok {
continue // Runtime already includes it.
}
installJni := ctx.InstallFile(soInstallPath, jniLib.path.Base(), jniLib.path)
installDeps = append(installDeps, installJni)
}
resApkInstallPath := installPath.Join(ctx, "ravenwood-res-apks")
var resApkName string
var targetResApkName string
if resourceApk != nil {
installResApk := ctx.InstallFile(resApkInstallPath, "ravenwood-res.apk", resourceApk)
installDeps = append(installDeps, installResApk)
resApkName = "ravenwood-res.apk"
}
if resApk := ctx.GetDirectDepsProxyWithTag(ravenwoodTargetResourceApkTag); len(resApk) > 0 {
installFile := android.OutputFileForModule(ctx, resApk[0], "")
installResApk := ctx.InstallFile(resApkInstallPath, "ravenwood-target-res.apk", installFile)
installDeps = append(installDeps, installResApk)
targetResApkName = "ravenwood-target-res.apk"
}
// Generate manifest properties
propertiesOutputPath := android.PathForModuleGen(ctx, "ravenwood.properties")
targetSdkVersion := proptools.StringDefault(r.deviceProperties.Target_sdk_version, "")
targetSdkVersionInt := r.TargetSdkVersion(ctx).FinalOrFutureInt() // FinalOrFutureInt may be 10000.
packageName := r.aapt.aaptProperties.Package_name.GetOrDefault(ctx, "")
targetPackageName := proptools.StringDefault(r.ravenwoodTestProperties.Target_package_name, "")
instClassName := proptools.StringDefault(r.ravenwoodTestProperties.Instrumentation_class, "")
ctx.Build(pctx, android.BuildParams{
Rule: genManifestProperties,
Description: "genManifestProperties",
Output: propertiesOutputPath,
Args: map[string]string{
"targetSdkVersionInt": strconv.Itoa(targetSdkVersionInt),
"targetSdkVersionRaw": targetSdkVersion,
"packageName": packageName,
"targetPackageName": targetPackageName,
"instrumentationClass": instClassName,
"moduleName": ctx.ModuleName(),
"resourceApk": resApkName,
"targetResourceApk": targetResApkName,
},
})
installProps := ctx.InstallFile(installPath, "ravenwood.properties", propertiesOutputPath)
installDeps = append(installDeps, installProps)
// Copy data files
for _, data := range r.data {
installedData := ctx.InstallFile(installPath, data.Rel(), data)
installDeps = append(installDeps, installedData)
}
// Install our JAR with all dependencies
ctx.InstallFile(installPath, ctx.ModuleName()+".jar", r.outputFile, installDeps...)
moduleInfoJSON := ctx.ModuleInfoJSON()
if _, ok := r.testConfig.(android.WritablePath); ok {
moduleInfoJSON.AutoTestConfig = []string{"true"}
}
if r.testConfig != nil {
moduleInfoJSON.TestConfig = append(moduleInfoJSON.TestConfig, r.testConfig.String())
}
moduleInfoJSON.CompatibilitySuites = []string{"general-tests", "ravenwood-tests"}
android.SetProvider(ctx, android.TestSuiteInfoProvider, android.TestSuiteInfo{
TestSuites: r.TestSuites(),
})
}
// This method is adapted from AndroidApp.aaptBuildActions() in app.go, with changes
// irrelevant to RavenwoodTest removed.
func (r *ravenwoodTest) aaptBuildActions(ctx android.ModuleContext) {
usePlatformAPI := proptools.Bool(r.Module.deviceProperties.Platform_apis)
if ctx.Module().(android.SdkContext).SdkVersion(ctx).Kind == android.SdkModule {
usePlatformAPI = true
}
r.aapt.usesNonSdkApis = usePlatformAPI
aconfigTextFilePaths := getAconfigFilePaths(ctx)
r.aapt.buildActions(ctx,
aaptBuildActionOptions{
sdkContext: android.SdkContext(r),
enforceDefaultTargetSdkVersion: true,
forceNonFinalResourceIDs: true,
aconfigTextFiles: aconfigTextFilePaths,
usesLibrary: &r.usesLibrary,
},
)
android.SetProvider(ctx, FlagsPackagesProvider, FlagsPackages{
AconfigTextFiles: aconfigTextFilePaths,
})
// Add R classes into classpath
if r.useResourceProcessorBusyBox(ctx) {
// When building an app with ResourceProcessorBusyBox enabled ResourceProcessorBusyBox has already
// created R.class files that provide IDs for resources in busybox/R.jar. Pass that file in the
// classpath when compiling everything else, and add it to the final classes jar.
r.extraClasspathJars = append(r.extraClasspathJars, r.aapt.rJar)
r.extraCombinedJars = append(r.extraCombinedJars, r.aapt.rJar)
} else {
// When building an app without ResourceProcessorBusyBox the aapt2 rule creates R.srcjar containing
// R.java files for the app's package and the packages from all transitive static android_library
// dependencies. Compile the srcjar alongside the rest of the sources.
r.extraSrcJars = append(r.extraSrcJars, r.aapt.aaptSrcJar)
}
}
func (r *ravenwoodTest) AndroidMkEntries() []android.AndroidMkEntries {
entriesList := r.Library.AndroidMkEntries()
entries := &entriesList[0]
entries.ExtraEntries = append(entries.ExtraEntries,
func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", true)
entries.AddStrings("LOCAL_COMPATIBILITY_SUITE",
"general-tests", "ravenwood-tests")
if r.testConfig != nil {
entries.SetPath("LOCAL_FULL_TEST_CONFIG", r.testConfig)
}
})
return entriesList
}
type ravenwoodLibgroupProperties struct {
Libs []string
Jni_libs proptools.Configurable[[]string]
// We use this to copy framework-res.apk to the ravenwood runtime directory.
Data []string `android:"path,arch_variant"`
// We use this to copy font files to the ravenwood runtime directory.
Fonts []string `android:"path,arch_variant"`
}
type ravenwoodLibgroup struct {
android.ModuleBase
ravenwoodLibgroupProperties ravenwoodLibgroupProperties
forceOSType android.OsType
forceArchType android.ArchType
}
func ravenwoodLibgroupFactory() android.Module {
module := &ravenwoodLibgroup{}
module.AddProperties(&module.ravenwoodLibgroupProperties)
android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
return module
}
func (r *ravenwoodLibgroup) InstallInTestcases() bool { return true }
func (r *ravenwoodLibgroup) InstallForceOS() (*android.OsType, *android.ArchType) {
return &r.forceOSType, &r.forceArchType
}
func (r *ravenwoodLibgroup) TestSuites() []string {
return []string{
"general-tests",
"ravenwood-tests",
}
}
func (r *ravenwoodLibgroup) DepsMutator(ctx android.BottomUpMutatorContext) {
// Always depends on our underlying libs
for _, lib := range r.ravenwoodLibgroupProperties.Libs {
ctx.AddVariationDependencies(nil, ravenwoodLibContentTag, lib)
}
for _, lib := range r.ravenwoodLibgroupProperties.Jni_libs.GetOrDefault(ctx, nil) {
ctx.AddVariationDependencies(ctx.Config().BuildOSTarget.Variations(), jniLibTag, lib)
}
if r.Name() == ravenwoodRuntimeName {
ctx.AddVariationDependencies(nil, allAconfigModuleTag, aconfig.AllAconfigModule)
}
}
func (r *ravenwoodLibgroup) GenerateAndroidBuildActions(ctx android.ModuleContext) {
r.forceOSType = ctx.Config().BuildOS
r.forceArchType = ctx.Config().BuildArch
// Collect the JNI dependencies, including the transitive deps.
jniDepNames := make(map[string]bool)
jniLibs := collectTransitiveJniDeps(ctx)
for _, jni := range jniLibs {
jniDepNames[jni.name] = true
}
android.SetProvider(ctx, ravenwoodLibgroupJniDepProvider, ravenwoodLibgroupJniDepProviderInfo{
names: jniDepNames,
})
install := func(to android.InstallPath, srcs ...android.Path) {
for _, s := range srcs {
ctx.InstallFile(to, s.Base(), s)
}
}
// Install our runtime into expected location for packaging
installPath := android.PathForModuleInstall(ctx, r.BaseModuleName())
for _, lib := range r.ravenwoodLibgroupProperties.Libs {
libModule := ctx.GetDirectDepProxyWithTag(lib, ravenwoodLibContentTag)
if libModule.IsNil() {
if ctx.Config().AllowMissingDependencies() {
ctx.AddMissingDependencies([]string{lib})
} else {
ctx.PropertyErrorf("lib", "missing dependency %q", lib)
}
continue
}
libJar := android.OutputFileForModule(ctx, libModule, "")
ctx.InstallFile(installPath, libJar.Base(), libJar)
}
soInstallPath := android.PathForModuleInstall(ctx, r.BaseModuleName()).Join(ctx, getLibPath(r.forceArchType))
for _, jniLib := range jniLibs {
install(soInstallPath, jniLib.path)
}
dataInstallPath := installPath.Join(ctx, "ravenwood-data")
data := android.PathsForModuleSrc(ctx, r.ravenwoodLibgroupProperties.Data)
for _, file := range data {
install(dataInstallPath, file)
}
fontsInstallPath := installPath.Join(ctx, "fonts")
fonts := android.PathsForModuleSrc(ctx, r.ravenwoodLibgroupProperties.Fonts)
for _, file := range fonts {
install(fontsInstallPath, file)
}
// Copy aconfig flag storage files.
if r.Name() == ravenwoodRuntimeName {
allAconfigFound := false
if allAconfig := ctx.GetDirectDepProxyWithTag(aconfig.AllAconfigModule, allAconfigModuleTag); !allAconfig.IsNil() {
aadi, ok := android.OtherModuleProvider(ctx, allAconfig, aconfig.AllAconfigDeclarationsInfoProvider)
if ok {
// Binary proto file and the text proto.
// We don't really use the text proto file, but having this would make debugging easier.
install(installPath.Join(ctx, "aconfig/metadata/aconfig/etc"), aadi.ParsedFlagsFile, aadi.TextProtoFlagsFile)
// The "new" storage files.
install(installPath.Join(ctx, "aconfig/metadata/aconfig/maps"), aadi.StoragePackageMap, aadi.StorageFlagMap)
install(installPath.Join(ctx, "aconfig/metadata/aconfig/boot"), aadi.StorageFlagVal, aadi.StorageFlagInfo)
allAconfigFound = true
}
}
if !allAconfigFound {
if ctx.Config().AllowMissingDependencies() {
ctx.AddMissingDependencies([]string{aconfig.AllAconfigModule})
} else {
ctx.ModuleErrorf("missing dependency %q", aconfig.AllAconfigModule)
}
}
}
// Normal build should perform install steps
ctx.Phony(r.BaseModuleName(), android.PathForPhony(ctx, r.BaseModuleName()+"-install"))
android.SetProvider(ctx, android.TestSuiteInfoProvider, android.TestSuiteInfo{
TestSuites: r.TestSuites(),
})
}
// collectTransitiveJniDeps returns all JNI dependencies, including transitive
// ones, including NDK / stub libs. (Because Ravenwood has no "preinstalled" libraries)
func collectTransitiveJniDeps(ctx android.ModuleContext) []jniLib {
libs, _ := collectJniDeps(ctx, true, false, nil)
return libs
}