diff options
Diffstat (limited to 'java')
51 files changed, 13561 insertions, 3692 deletions
diff --git a/java/Android.bp b/java/Android.bp new file mode 100644 index 000000000..1fda7f71d --- /dev/null +++ b/java/Android.bp @@ -0,0 +1,67 @@ +bootstrap_go_package { + name: "soong-java", + pkgPath: "android/soong/java", + deps: [ + "blueprint", + "blueprint-pathtools", + "soong", + "soong-android", + "soong-cc", + "soong-dexpreopt", + "soong-genrule", + "soong-java-config", + "soong-remoteexec", + "soong-tradefed", + ], + srcs: [ + "aapt2.go", + "aar.go", + "android_manifest.go", + "android_resources.go", + "androidmk.go", + "app_builder.go", + "app.go", + "builder.go", + "device_host_converter.go", + "dex.go", + "dexpreopt.go", + "dexpreopt_bootjars.go", + "dexpreopt_config.go", + "droiddoc.go", + "gen.go", + "genrule.go", + "hiddenapi.go", + "hiddenapi_singleton.go", + "jacoco.go", + "java.go", + "jdeps.go", + "java_resources.go", + "kotlin.go", + "lint.go", + "platform_compat_config.go", + "plugin.go", + "prebuilt_apis.go", + "proto.go", + "robolectric.go", + "sdk.go", + "sdk_library.go", + "support_libraries.go", + "sysprop.go", + "system_modules.go", + "testing.go", + "tradefed.go", + ], + testSrcs: [ + "androidmk_test.go", + "app_test.go", + "device_host_converter_test.go", + "dexpreopt_test.go", + "dexpreopt_bootjars_test.go", + "java_test.go", + "jdeps_test.go", + "kotlin_test.go", + "plugin_test.go", + "sdk_test.go", + ], + pluginFor: ["soong_build"], +} diff --git a/java/OWNERS b/java/OWNERS index d68a5b0f3..16ef4d812 100644 --- a/java/OWNERS +++ b/java/OWNERS @@ -1 +1 @@ -per-file dexpreopt.go = ngeoffray@google.com,calin@google.com,mathieuc@google.com +per-file dexpreopt*.go = ngeoffray@google.com,calin@google.com,mathieuc@google.com diff --git a/java/aapt2.go b/java/aapt2.go index f21408f97..04e4de52c 100644 --- a/java/aapt2.go +++ b/java/aapt2.go @@ -55,12 +55,14 @@ func pathsToAapt2Paths(ctx android.ModuleContext, resPaths android.Paths) androi var aapt2CompileRule = pctx.AndroidStaticRule("aapt2Compile", blueprint.RuleParams{ - Command: `${config.Aapt2Cmd} compile -o $outDir $cFlags --legacy $in`, + Command: `${config.Aapt2Cmd} compile -o $outDir $cFlags $in`, CommandDeps: []string{"${config.Aapt2Cmd}"}, }, "outDir", "cFlags") -func aapt2Compile(ctx android.ModuleContext, dir android.Path, paths android.Paths) android.WritablePaths { +func aapt2Compile(ctx android.ModuleContext, dir android.Path, paths android.Paths, + flags []string) android.WritablePaths { + shards := android.ShardPaths(paths, AAPT2_SHARD_SIZE) ret := make(android.WritablePaths, 0, len(paths)) @@ -81,9 +83,7 @@ func aapt2Compile(ctx android.ModuleContext, dir android.Path, paths android.Pat Outputs: outPaths, Args: map[string]string{ "outDir": android.PathForModuleOut(ctx, "aapt2", dir.String()).String(), - // Always set --pseudo-localize, it will be stripped out later for release - // builds that don't want it. - "cFlags": "--pseudo-localize", + "cFlags": strings.Join(flags, " "), }, }) } @@ -94,42 +94,31 @@ func aapt2Compile(ctx android.ModuleContext, dir android.Path, paths android.Pat return ret } -func aapt2CompileDirs(ctx android.ModuleContext, flata android.WritablePath, dirs android.Paths, deps android.Paths) { - ctx.Build(pctx, android.BuildParams{ - Rule: aapt2CompileRule, - Description: "aapt2 compile dirs", - Implicits: deps, - Output: flata, - Args: map[string]string{ - "outDir": flata.String(), - // Always set --pseudo-localize, it will be stripped out later for release - // builds that don't want it. - "cFlags": "--pseudo-localize " + android.JoinWithPrefix(dirs.Strings(), "--dir "), - }, - }) -} - var aapt2CompileZipRule = pctx.AndroidStaticRule("aapt2CompileZip", blueprint.RuleParams{ - Command: `${config.ZipSyncCmd} -d $resZipDir $in && ` + - `${config.Aapt2Cmd} compile -o $out $cFlags --legacy --dir $resZipDir`, + Command: `${config.ZipSyncCmd} -d $resZipDir $zipSyncFlags $in && ` + + `${config.Aapt2Cmd} compile -o $out $cFlags --dir $resZipDir`, CommandDeps: []string{ "${config.Aapt2Cmd}", "${config.ZipSyncCmd}", }, - }, "cFlags", "resZipDir") + }, "cFlags", "resZipDir", "zipSyncFlags") + +func aapt2CompileZip(ctx android.ModuleContext, flata android.WritablePath, zip android.Path, zipPrefix string, + flags []string) { -func aapt2CompileZip(ctx android.ModuleContext, flata android.WritablePath, zip android.Path) { + if zipPrefix != "" { + zipPrefix = "--zip-prefix " + zipPrefix + } ctx.Build(pctx, android.BuildParams{ Rule: aapt2CompileZipRule, Description: "aapt2 compile zip", Input: zip, Output: flata, Args: map[string]string{ - // Always set --pseudo-localize, it will be stripped out later for release - // builds that don't want it. - "cFlags": "--pseudo-localize", - "resZipDir": android.PathForModuleOut(ctx, "aapt2", "reszip", flata.Base()).String(), + "cFlags": strings.Join(flags, " "), + "resZipDir": android.PathForModuleOut(ctx, "aapt2", "reszip", flata.Base()).String(), + "zipSyncFlags": zipPrefix, }, }) } @@ -158,10 +147,16 @@ var fileListToFileRule = pctx.AndroidStaticRule("fileListToFile", RspfileContent: "$in", }) +var mergeAssetsRule = pctx.AndroidStaticRule("mergeAssets", + blueprint.RuleParams{ + Command: `${config.MergeZipsCmd} ${out} ${in}`, + CommandDeps: []string{"${config.MergeZipsCmd}"}, + }) + func aapt2Link(ctx android.ModuleContext, packageRes, genJar, proguardOptions, rTxt, extraPackages android.WritablePath, flags []string, deps android.Paths, - compiledRes, compiledOverlay android.Paths, splitPackages android.WritablePaths) { + compiledRes, compiledOverlay, assetPackages android.Paths, splitPackages android.WritablePaths) { genDir := android.PathForModuleGen(ctx, "aapt2", "R") @@ -197,12 +192,25 @@ func aapt2Link(ctx android.ModuleContext, } implicitOutputs := append(splitPackages, proguardOptions, genJar, rTxt, extraPackages) + linkOutput := packageRes + + // AAPT2 ignores assets in overlays. Merge them after linking. + if len(assetPackages) > 0 { + linkOutput = android.PathForModuleOut(ctx, "aapt2", "package-res.apk") + inputZips := append(android.Paths{linkOutput}, assetPackages...) + ctx.Build(pctx, android.BuildParams{ + Rule: mergeAssetsRule, + Inputs: inputZips, + Output: packageRes, + Description: "merge assets from dependencies", + }) + } ctx.Build(pctx, android.BuildParams{ Rule: aapt2LinkRule, Description: "aapt2 link", Implicits: deps, - Output: packageRes, + Output: linkOutput, ImplicitOutputs: implicitOutputs, Args: map[string]string{ "flags": strings.Join(flags, " "), diff --git a/java/aar.go b/java/aar.go index 6273a9b50..8dd752f12 100644 --- a/java/aar.go +++ b/java/aar.go @@ -15,11 +15,12 @@ package java import ( - "android/soong/android" "fmt" "path/filepath" "strings" + "android/soong/android" + "github.com/google/blueprint" "github.com/google/blueprint/proptools" ) @@ -30,12 +31,17 @@ type AndroidLibraryDependency interface { ExportedProguardFlagFiles() android.Paths ExportedRRODirs() []rroDir ExportedStaticPackages() android.Paths - ExportedManifest() android.Path + ExportedManifests() android.Paths + ExportedAssets() android.OptionalPath } func init() { - android.RegisterModuleType("android_library_import", AARImportFactory) - android.RegisterModuleType("android_library", AndroidLibraryFactory) + RegisterAARBuildComponents(android.InitRegistrationContext) +} + +func RegisterAARBuildComponents(ctx android.RegistrationContext) { + ctx.RegisterModuleType("android_library_import", AARImportFactory) + ctx.RegisterModuleType("android_library", AndroidLibraryFactory) } // @@ -69,21 +75,34 @@ type aaptProperties struct { // path to AndroidManifest.xml. If unset, defaults to "AndroidManifest.xml". Manifest *string `android:"path"` + + // paths to additional manifest files to merge with main manifest. + Additional_manifests []string `android:"path"` + + // do not include AndroidManifest from dependent libraries + Dont_merge_manifests *bool } type aapt struct { - aaptSrcJar android.Path - exportPackage android.Path - manifestPath android.Path - proguardOptionsFile android.Path - rroDirs []rroDir - rTxt android.Path - extraAaptPackagesFile android.Path - noticeFile android.OptionalPath - isLibrary bool - uncompressedJNI bool - useEmbeddedDex bool - usesNonSdkApis bool + aaptSrcJar android.Path + exportPackage android.Path + manifestPath android.Path + transitiveManifestPaths android.Paths + proguardOptionsFile android.Path + rroDirs []rroDir + rTxt android.Path + extraAaptPackagesFile android.Path + mergedManifestFile android.Path + noticeFile android.OptionalPath + assetPackage android.OptionalPath + isLibrary bool + useEmbeddedNativeLibs bool + useEmbeddedDex bool + usesNonSdkApis bool + sdkLibraries []string + hasNoCode bool + LoggingParent string + resourceFiles android.Paths splitNames []string splits []split @@ -105,24 +124,20 @@ func (a *aapt) ExportedRRODirs() []rroDir { return a.rroDirs } -func (a *aapt) ExportedManifest() android.Path { - return a.manifestPath +func (a *aapt) ExportedManifests() android.Paths { + return a.transitiveManifestPaths } -func (a *aapt) aapt2Flags(ctx android.ModuleContext, sdkContext sdkContext, manifestPath android.Path) (flags []string, - deps android.Paths, resDirs, overlayDirs []globbedResourceDir, rroDirs []rroDir, resZips android.Paths) { +func (a *aapt) ExportedAssets() android.OptionalPath { + return a.assetPackage +} - hasVersionCode := false - hasVersionName := false - for _, f := range a.aaptProperties.Aaptflags { - if strings.HasPrefix(f, "--version-code") { - hasVersionCode = true - } else if strings.HasPrefix(f, "--version-name") { - hasVersionName = true - } - } +func (a *aapt) aapt2Flags(ctx android.ModuleContext, sdkContext sdkContext, + manifestPath android.Path) (compileFlags, linkFlags []string, linkDeps android.Paths, + resDirs, overlayDirs []globbedResourceDir, rroDirs []rroDir, resZips android.Paths) { - var linkFlags []string + hasVersionCode := android.PrefixInList(a.aaptProperties.Aaptflags, "--version-code") + hasVersionName := android.PrefixInList(a.aaptProperties.Aaptflags, "--version-name") // Flags specified in Android.bp linkFlags = append(linkFlags, a.aaptProperties.Aaptflags...) @@ -134,8 +149,6 @@ func (a *aapt) aapt2Flags(ctx android.ModuleContext, sdkContext sdkContext, mani resourceDirs := android.PathsWithOptionalDefaultForModuleSrc(ctx, a.aaptProperties.Resource_dirs, "res") resourceZips := android.PathsForModuleSrc(ctx, a.aaptProperties.Resource_zips) - var linkDeps android.Paths - // Glob directories into lists of paths for _, dir := range resourceDirs { resDirs = append(resDirs, globbedResourceDir{ @@ -165,7 +178,10 @@ func (a *aapt) aapt2Flags(ctx android.ModuleContext, sdkContext sdkContext, mani linkDeps = append(linkDeps, assetFiles...) // SDK version flags - minSdkVersion := sdkVersionOrDefault(ctx, sdkContext.minSdkVersion()) + minSdkVersion, err := sdkContext.minSdkVersion().effectiveVersionString(ctx) + if err != nil { + ctx.ModuleErrorf("invalid minSdkVersion: %s", err) + } linkFlags = append(linkFlags, "--min-sdk-version "+minSdkVersion) linkFlags = append(linkFlags, "--target-sdk-version "+minSdkVersion) @@ -189,27 +205,58 @@ func (a *aapt) aapt2Flags(ctx android.ModuleContext, sdkContext sdkContext, mani linkFlags = append(linkFlags, "--version-name ", versionName) } - return linkFlags, linkDeps, resDirs, overlayDirs, rroDirs, resourceZips + linkFlags, compileFlags = android.FilterList(linkFlags, []string{"--legacy"}) + + // Always set --pseudo-localize, it will be stripped out later for release + // builds that don't want it. + compileFlags = append(compileFlags, "--pseudo-localize") + + return compileFlags, linkFlags, linkDeps, resDirs, overlayDirs, rroDirs, resourceZips } -func (a *aapt) deps(ctx android.BottomUpMutatorContext, sdkContext sdkContext) { - sdkDep := decodeSdkDep(ctx, sdkContext) +func (a *aapt) deps(ctx android.BottomUpMutatorContext, sdkDep sdkDep) { if sdkDep.frameworkResModule != "" { ctx.AddVariationDependencies(nil, frameworkResTag, sdkDep.frameworkResModule) } } +var extractAssetsRule = pctx.AndroidStaticRule("extractAssets", + blueprint.RuleParams{ + Command: `${config.Zip2ZipCmd} -i ${in} -o ${out} "assets/**/*"`, + CommandDeps: []string{"${config.Zip2ZipCmd}"}, + }) + func (a *aapt) buildActions(ctx android.ModuleContext, sdkContext sdkContext, extraLinkFlags ...string) { - transitiveStaticLibs, staticLibManifests, staticRRODirs, libDeps, libFlags := aaptLibs(ctx, sdkContext) + + transitiveStaticLibs, transitiveStaticLibManifests, staticRRODirs, assetPackages, libDeps, libFlags, sdkLibraries := + aaptLibs(ctx, sdkContext) // App manifest file manifestFile := proptools.StringDefault(a.aaptProperties.Manifest, "AndroidManifest.xml") manifestSrcPath := android.PathForModuleSrc(ctx, manifestFile) - manifestPath := manifestMerger(ctx, manifestSrcPath, sdkContext, staticLibManifests, a.isLibrary, - a.uncompressedJNI, a.useEmbeddedDex, a.usesNonSdkApis) + manifestPath := manifestFixer(ctx, manifestSrcPath, sdkContext, sdkLibraries, + a.isLibrary, a.useEmbeddedNativeLibs, a.usesNonSdkApis, a.useEmbeddedDex, a.hasNoCode, + a.LoggingParent) + + // Add additional manifest files to transitive manifests. + additionalManifests := android.PathsForModuleSrc(ctx, a.aaptProperties.Additional_manifests) + a.transitiveManifestPaths = append(android.Paths{manifestPath}, additionalManifests...) + a.transitiveManifestPaths = append(a.transitiveManifestPaths, transitiveStaticLibManifests...) + + if len(a.transitiveManifestPaths) > 1 && !Bool(a.aaptProperties.Dont_merge_manifests) { + a.mergedManifestFile = manifestMerger(ctx, a.transitiveManifestPaths[0], a.transitiveManifestPaths[1:], a.isLibrary) + if !a.isLibrary { + // Only use the merged manifest for applications. For libraries, the transitive closure of manifests + // will be propagated to the final application and merged there. The merged manifest for libraries is + // only passed to Make, which can't handle transitive dependencies. + manifestPath = a.mergedManifestFile + } + } else { + a.mergedManifestFile = manifestPath + } - linkFlags, linkDeps, resDirs, overlayDirs, rroDirs, resZips := a.aapt2Flags(ctx, sdkContext, manifestPath) + compileFlags, linkFlags, linkDeps, resDirs, overlayDirs, rroDirs, resZips := a.aapt2Flags(ctx, sdkContext, manifestPath) rroDirs = append(rroDirs, staticRRODirs...) linkFlags = append(linkFlags, libFlags...) @@ -220,7 +267,8 @@ func (a *aapt) buildActions(ctx android.ModuleContext, sdkContext sdkContext, ex } packageRes := android.PathForModuleOut(ctx, "package-res.apk") - srcJar := android.PathForModuleGen(ctx, "R.jar") + // the subdir "android" is required to be filtered by package names + srcJar := android.PathForModuleGen(ctx, "android", "R.srcjar") proguardOptionsFile := android.PathForModuleGen(ctx, "proguard.options") rTxt := android.PathForModuleOut(ctx, "R.txt") // This file isn't used by Soong, but is generated for exporting @@ -228,12 +276,13 @@ func (a *aapt) buildActions(ctx android.ModuleContext, sdkContext sdkContext, ex var compiledResDirs []android.Paths for _, dir := range resDirs { - compiledResDirs = append(compiledResDirs, aapt2Compile(ctx, dir.dir, dir.files).Paths()) + a.resourceFiles = append(a.resourceFiles, dir.files...) + compiledResDirs = append(compiledResDirs, aapt2Compile(ctx, dir.dir, dir.files, compileFlags).Paths()) } for i, zip := range resZips { flata := android.PathForModuleOut(ctx, fmt.Sprintf("reszip.%d.flata", i)) - aapt2CompileZip(ctx, flata, zip) + aapt2CompileZip(ctx, flata, zip, "", compileFlags) compiledResDirs = append(compiledResDirs, android.Paths{flata}) } @@ -262,7 +311,7 @@ func (a *aapt) buildActions(ctx android.ModuleContext, sdkContext sdkContext, ex } for _, dir := range overlayDirs { - compiledOverlay = append(compiledOverlay, aapt2Compile(ctx, dir.dir, dir.files).Paths()...) + compiledOverlay = append(compiledOverlay, aapt2Compile(ctx, dir.dir, dir.files, compileFlags).Paths()...) } var splitPackages android.WritablePaths @@ -281,7 +330,20 @@ func (a *aapt) buildActions(ctx android.ModuleContext, sdkContext sdkContext, ex } aapt2Link(ctx, packageRes, srcJar, proguardOptionsFile, rTxt, extraPackages, - linkFlags, linkDeps, compiledRes, compiledOverlay, splitPackages) + linkFlags, linkDeps, compiledRes, compiledOverlay, assetPackages, splitPackages) + + // Extract assets from the resource package output so that they can be used later in aapt2link + // for modules that depend on this one. + if android.PrefixInList(linkFlags, "-A ") || len(assetPackages) > 0 { + assets := android.PathForModuleOut(ctx, "assets.zip") + ctx.Build(pctx, android.BuildParams{ + Rule: extractAssetsRule, + Input: packageRes, + Output: assets, + Description: "extract assets from built resource file", + }) + a.assetPackage = android.OptionalPathForPath(assets) + } a.aaptSrcJar = srcJar a.exportPackage = packageRes @@ -294,8 +356,8 @@ func (a *aapt) buildActions(ctx android.ModuleContext, sdkContext sdkContext, ex } // aaptLibs collects libraries from dependencies and sdk_version and converts them into paths -func aaptLibs(ctx android.ModuleContext, sdkContext sdkContext) (transitiveStaticLibs, staticLibManifests android.Paths, - staticRRODirs []rroDir, deps android.Paths, flags []string) { +func aaptLibs(ctx android.ModuleContext, sdkContext sdkContext) (transitiveStaticLibs, transitiveStaticLibManifests android.Paths, + staticRRODirs []rroDir, assets, deps android.Paths, flags []string, sdkLibraries []string) { var sharedLibs android.Paths @@ -314,7 +376,19 @@ func aaptLibs(ctx android.ModuleContext, sdkContext sdkContext) (transitiveStati switch ctx.OtherModuleDependencyTag(module) { case instrumentationForTag: // Nothing, instrumentationForTag is treated as libTag for javac but not for aapt2. - case libTag, frameworkResTag: + case libTag: + if exportPackage != nil { + sharedLibs = append(sharedLibs, exportPackage) + } + + // If the module is (or possibly could be) a component of a java_sdk_library + // (including the java_sdk_library) itself then append any implicit sdk library + // names to the list of sdk libraries to be added to the manifest. + if component, ok := module.(SdkLibraryComponentDependency); ok { + sdkLibraries = append(sdkLibraries, component.OptionalImplicitSdkLibrary()...) + } + + case frameworkResTag: if exportPackage != nil { sharedLibs = append(sharedLibs, exportPackage) } @@ -322,7 +396,11 @@ func aaptLibs(ctx android.ModuleContext, sdkContext sdkContext) (transitiveStati if exportPackage != nil { transitiveStaticLibs = append(transitiveStaticLibs, aarDep.ExportedStaticPackages()...) transitiveStaticLibs = append(transitiveStaticLibs, exportPackage) - staticLibManifests = append(staticLibManifests, aarDep.ExportedManifest()) + transitiveStaticLibManifests = append(transitiveStaticLibManifests, aarDep.ExportedManifests()...) + sdkLibraries = append(sdkLibraries, aarDep.ExportedSdkLibs()...) + if aarDep.ExportedAssets().Valid() { + assets = append(assets, aarDep.ExportedAssets().Path()) + } outer: for _, d := range aarDep.ExportedRRODirs() { @@ -349,8 +427,10 @@ func aaptLibs(ctx android.ModuleContext, sdkContext sdkContext) (transitiveStati } transitiveStaticLibs = android.FirstUniquePaths(transitiveStaticLibs) + transitiveStaticLibManifests = android.FirstUniquePaths(transitiveStaticLibManifests) + sdkLibraries = android.FirstUniqueStrings(sdkLibraries) - return transitiveStaticLibs, staticLibManifests, staticRRODirs, deps, flags + return transitiveStaticLibs, transitiveStaticLibManifests, staticRRODirs, assets, deps, flags, sdkLibraries } type AndroidLibrary struct { @@ -377,13 +457,15 @@ var _ AndroidLibraryDependency = (*AndroidLibrary)(nil) func (a *AndroidLibrary) DepsMutator(ctx android.BottomUpMutatorContext) { a.Module.deps(ctx) - if !Bool(a.properties.No_framework_libs) && !Bool(a.properties.No_standard_libs) { - a.aapt.deps(ctx, sdkContext(a)) + sdkDep := decodeSdkDep(ctx, sdkContext(a)) + if sdkDep.hasFrameworkLibs() { + a.aapt.deps(ctx, sdkDep) } } func (a *AndroidLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) { a.aapt.isLibrary = true + a.aapt.sdkLibraries = a.exportedSdkLibs a.aapt.buildActions(ctx, sdkContext(a)) ctx.CheckbuildFile(a.proguardOptionsFile) @@ -393,6 +475,10 @@ func (a *AndroidLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) // apps manifests are handled by aapt, don't let Module see them a.properties.Manifest = nil + a.linter.mergedManifest = a.aapt.mergedManifestFile + a.linter.manifest = a.aapt.manifestPath + a.linter.resources = a.aapt.resourceFiles + a.Module.extraProguardFlagFiles = append(a.Module.extraProguardFlagFiles, a.proguardOptionsFile) @@ -426,16 +512,15 @@ func (a *AndroidLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) func AndroidLibraryFactory() android.Module { module := &AndroidLibrary{} + module.Module.addHostAndDeviceProperties() module.AddProperties( - &module.Module.properties, - &module.Module.deviceProperties, - &module.Module.dexpreoptProperties, - &module.Module.protoProperties, &module.aaptProperties, &module.androidLibraryProperties) module.androidLibraryProperties.BuildAAR = true + module.Module.linter.library = true + android.InitApexModule(module) InitJavaModule(module, android.DeviceSupported) return module } @@ -460,8 +545,12 @@ type AARImportProperties struct { type AARImport struct { android.ModuleBase android.DefaultableModuleBase + android.ApexModuleBase prebuilt android.Prebuilt + // Functionality common to Module and Import. + embeddableInModuleAndImport + properties AARImportProperties classpathFile android.WritablePath @@ -473,21 +562,29 @@ type AARImport struct { exportedStaticPackages android.Paths } -func (a *AARImport) sdkVersion() string { - return String(a.properties.Sdk_version) +func (a *AARImport) sdkVersion() sdkSpec { + return sdkSpecFrom(String(a.properties.Sdk_version)) } -func (a *AARImport) minSdkVersion() string { +func (a *AARImport) systemModules() string { + return "" +} + +func (a *AARImport) minSdkVersion() sdkSpec { if a.properties.Min_sdk_version != nil { - return *a.properties.Min_sdk_version + return sdkSpecFrom(*a.properties.Min_sdk_version) } return a.sdkVersion() } -func (a *AARImport) targetSdkVersion() string { +func (a *AARImport) targetSdkVersion() sdkSpec { return a.sdkVersion() } +func (a *AARImport) javaVersion() string { + return "" +} + var _ AndroidLibraryDependency = (*AARImport)(nil) func (a *AARImport) ExportPackage() android.Path { @@ -506,8 +603,13 @@ func (a *AARImport) ExportedStaticPackages() android.Paths { return a.exportedStaticPackages } -func (a *AARImport) ExportedManifest() android.Path { - return a.manifest +func (a *AARImport) ExportedManifests() android.Paths { + return android.Paths{a.manifest} +} + +// TODO(jungjw): Decide whether we want to implement this. +func (a *AARImport) ExportedAssets() android.OptionalPath { + return android.OptionalPath{} } func (a *AARImport) Prebuilt() *android.Prebuilt { @@ -518,6 +620,10 @@ func (a *AARImport) Name() string { return a.prebuilt.Name(a.ModuleBase.Name()) } +func (a *AARImport) JacocoReportClassesFile() android.Path { + return nil +} + func (a *AARImport) DepsMutator(ctx android.BottomUpMutatorContext) { if !ctx.Config().UnbundledBuildUsePrebuiltSdks() { sdkDep := decodeSdkDep(ctx, sdkContext(a)) @@ -531,13 +637,13 @@ func (a *AARImport) DepsMutator(ctx android.BottomUpMutatorContext) { } // Unzip an AAR into its constituent files and directories. Any files in Outputs that don't exist in the AAR will be -// touched to create an empty file, and any directories in $expectedDirs will be created. +// touched to create an empty file. The res directory is not extracted, as it will be extracted in its own rule. var unzipAAR = pctx.AndroidStaticRule("unzipAAR", blueprint.RuleParams{ - Command: `rm -rf $outDir && mkdir -p $outDir $expectedDirs && ` + - `unzip -qo -d $outDir $in && touch $out`, + Command: `rm -rf $outDir && mkdir -p $outDir && ` + + `unzip -qoDD -d $outDir $in && rm -rf $outDir/res && touch $out`, }, - "expectedDirs", "outDir") + "outDir") func (a *AARImport) GenerateAndroidBuildActions(ctx android.ModuleContext) { if len(a.properties.Aars) != 1 { @@ -555,7 +661,6 @@ func (a *AARImport) GenerateAndroidBuildActions(ctx android.ModuleContext) { } extractedAARDir := android.PathForModuleOut(ctx, "aar") - extractedResDir := extractedAARDir.Join(ctx, "res") a.classpathFile = extractedAARDir.Join(ctx, "classes.jar") a.proguardFlags = extractedAARDir.Join(ctx, "proguard.txt") a.manifest = extractedAARDir.Join(ctx, "AndroidManifest.xml") @@ -566,19 +671,20 @@ func (a *AARImport) GenerateAndroidBuildActions(ctx android.ModuleContext) { Outputs: android.WritablePaths{a.classpathFile, a.proguardFlags, a.manifest}, Description: "unzip AAR", Args: map[string]string{ - "expectedDirs": extractedResDir.String(), - "outDir": extractedAARDir.String(), + "outDir": extractedAARDir.String(), }, }) + // Always set --pseudo-localize, it will be stripped out later for release + // builds that don't want it. + compileFlags := []string{"--pseudo-localize"} compiledResDir := android.PathForModuleOut(ctx, "flat-res") - aaptCompileDeps := android.Paths{a.classpathFile} - aaptCompileDirs := android.Paths{extractedResDir} flata := compiledResDir.Join(ctx, "gen_res.flata") - aapt2CompileDirs(ctx, flata, aaptCompileDirs, aaptCompileDeps) + aapt2CompileZip(ctx, flata, aar, "res", compileFlags) a.exportPackage = android.PathForModuleOut(ctx, "package-res.apk") - srcJar := android.PathForModuleGen(ctx, "R.jar") + // the subdir "android" is required to be filtered by package names + srcJar := android.PathForModuleGen(ctx, "android", "R.srcjar") proguardOptionsFile := android.PathForModuleGen(ctx, "proguard.options") rTxt := android.PathForModuleOut(ctx, "R.txt") a.extraAaptPackagesFile = android.PathForModuleOut(ctx, "extra_packages") @@ -594,10 +700,12 @@ func (a *AARImport) GenerateAndroidBuildActions(ctx android.ModuleContext) { linkFlags = append(linkFlags, "--manifest "+a.manifest.String()) linkDeps = append(linkDeps, a.manifest) - transitiveStaticLibs, staticLibManifests, staticRRODirs, libDeps, libFlags := aaptLibs(ctx, sdkContext(a)) + transitiveStaticLibs, staticLibManifests, staticRRODirs, transitiveAssets, libDeps, libFlags, sdkLibraries := + aaptLibs(ctx, sdkContext(a)) _ = staticLibManifests _ = staticRRODirs + _ = sdkLibraries linkDeps = append(linkDeps, libDeps...) linkFlags = append(linkFlags, libFlags...) @@ -605,7 +713,7 @@ func (a *AARImport) GenerateAndroidBuildActions(ctx android.ModuleContext) { overlayRes := append(android.Paths{flata}, transitiveStaticLibs...) aapt2Link(ctx, a.exportPackage, srcJar, proguardOptionsFile, rTxt, a.extraAaptPackagesFile, - linkFlags, linkDeps, nil, overlayRes, nil) + linkFlags, linkDeps, nil, overlayRes, transitiveAssets, nil) } var _ Dependency = (*AARImport)(nil) @@ -638,6 +746,18 @@ func (a *AARImport) ExportedSdkLibs() []string { return nil } +func (d *AARImport) ExportedPlugins() (android.Paths, []string) { + return nil, nil +} + +func (a *AARImport) SrcJarArgs() ([]string, android.Paths) { + return nil, nil +} + +func (a *AARImport) DepIsInSameApex(ctx android.BaseModuleContext, dep android.Module) bool { + return a.depIsInSameApex(ctx, dep) +} + var _ android.PrebuiltInterface = (*Import)(nil) // android_library_import imports an `.aar` file into the build graph as if it was built with android_library. @@ -650,6 +770,7 @@ func AARImportFactory() android.Module { module.AddProperties(&module.properties) android.InitPrebuiltModule(module, &module.properties.Aars) + android.InitApexModule(module) InitJavaModule(module, android.DeviceSupported) return module } diff --git a/java/android_manifest.go b/java/android_manifest.go index 8dc3b4752..8280cb1b1 100644 --- a/java/android_manifest.go +++ b/java/android_manifest.go @@ -36,25 +36,36 @@ var manifestFixerRule = pctx.AndroidStaticRule("manifestFixer", var manifestMergerRule = pctx.AndroidStaticRule("manifestMerger", blueprint.RuleParams{ - Command: `${config.ManifestMergerCmd} --main $in $libs --out $out`, + Command: `${config.ManifestMergerCmd} $args --main $in $libs --out $out`, CommandDeps: []string{"${config.ManifestMergerCmd}"}, }, - "libs") + "args", "libs") -func manifestMerger(ctx android.ModuleContext, manifest android.Path, sdkContext sdkContext, - staticLibManifests android.Paths, isLibrary, uncompressedJNI, useEmbeddedDex, usesNonSdkApis bool) android.Path { +// These two libs are added as optional dependencies (<uses-library> with +// android:required set to false). This is because they haven't existed in pre-P +// devices, but classes in them were in bootclasspath jars, etc. So making them +// hard dependencies (android:required=true) would prevent apps from being +// installed to such legacy devices. +var optionalUsesLibs = []string{ + "android.test.base", + "android.test.mock", +} + +// Uses manifest_fixer.py to inject minSdkVersion, etc. into an AndroidManifest.xml +func manifestFixer(ctx android.ModuleContext, manifest android.Path, sdkContext sdkContext, sdkLibraries []string, + isLibrary, useEmbeddedNativeLibs, usesNonSdkApis, useEmbeddedDex, hasNoCode bool, loggingParent string) android.Path { var args []string if isLibrary { args = append(args, "--library") } else { - minSdkVersion, err := sdkVersionToNumber(ctx, sdkContext.minSdkVersion()) + minSdkVersion, err := sdkContext.minSdkVersion().effectiveVersion(ctx) if err != nil { ctx.ModuleErrorf("invalid minSdkVersion: %s", err) } if minSdkVersion >= 23 { - args = append(args, fmt.Sprintf("--extract-native-libs=%v", !uncompressedJNI)) - } else if uncompressedJNI { + args = append(args, fmt.Sprintf("--extract-native-libs=%v", !useEmbeddedNativeLibs)) + } else if useEmbeddedNativeLibs { ctx.ModuleErrorf("module attempted to store uncompressed native libraries, but minSdkVersion=%d doesn't support it", minSdkVersion) } @@ -65,49 +76,84 @@ func manifestMerger(ctx android.ModuleContext, manifest android.Path, sdkContext } if useEmbeddedDex { - args = append(args, "--use-embedded-dex=true") + args = append(args, "--use-embedded-dex") + } + + for _, usesLib := range sdkLibraries { + if inList(usesLib, optionalUsesLibs) { + args = append(args, "--optional-uses-library", usesLib) + } else { + args = append(args, "--uses-library", usesLib) + } } + if hasNoCode { + args = append(args, "--has-no-code") + } + + if loggingParent != "" { + args = append(args, "--logging-parent", loggingParent) + } var deps android.Paths - targetSdkVersion := sdkVersionOrDefault(ctx, sdkContext.targetSdkVersion()) - if targetSdkVersion == ctx.Config().PlatformSdkCodename() && - ctx.Config().UnbundledBuild() && - !ctx.Config().UnbundledBuildUsePrebuiltSdks() && - ctx.Config().IsEnvTrue("UNBUNDLED_BUILD_TARGET_SDK_WITH_API_FINGERPRINT") { - apiFingerprint := ApiFingerprintPath(ctx) - targetSdkVersion += fmt.Sprintf(".$$(cat %s)", apiFingerprint.String()) - deps = append(deps, apiFingerprint) + targetSdkVersion, err := sdkContext.targetSdkVersion().effectiveVersionString(ctx) + if err != nil { + ctx.ModuleErrorf("invalid targetSdkVersion: %s", err) + } + if UseApiFingerprint(ctx) { + targetSdkVersion = ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", ApiFingerprintPath(ctx).String()) + deps = append(deps, ApiFingerprintPath(ctx)) + } + + minSdkVersion, err := sdkContext.minSdkVersion().effectiveVersionString(ctx) + if err != nil { + ctx.ModuleErrorf("invalid minSdkVersion: %s", err) + } + if UseApiFingerprint(ctx) { + minSdkVersion = ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", ApiFingerprintPath(ctx).String()) + deps = append(deps, ApiFingerprintPath(ctx)) } - // Inject minSdkVersion into the manifest fixedManifest := android.PathForModuleOut(ctx, "manifest_fixer", "AndroidManifest.xml") + if err != nil { + ctx.ModuleErrorf("invalid minSdkVersion: %s", err) + } ctx.Build(pctx, android.BuildParams{ - Rule: manifestFixerRule, - Input: manifest, - Implicits: deps, - Output: fixedManifest, + Rule: manifestFixerRule, + Description: "fix manifest", + Input: manifest, + Implicits: deps, + Output: fixedManifest, Args: map[string]string{ - "minSdkVersion": sdkVersionOrDefault(ctx, sdkContext.minSdkVersion()), + "minSdkVersion": minSdkVersion, "targetSdkVersion": targetSdkVersion, "args": strings.Join(args, " "), }, }) - manifest = fixedManifest - - // Merge static aar dependency manifests if necessary - if len(staticLibManifests) > 0 { - mergedManifest := android.PathForModuleOut(ctx, "manifest_merger", "AndroidManifest.xml") - ctx.Build(pctx, android.BuildParams{ - Rule: manifestMergerRule, - Input: manifest, - Implicits: staticLibManifests, - Output: mergedManifest, - Args: map[string]string{ - "libs": android.JoinWithPrefix(staticLibManifests.Strings(), "--libs "), - }, - }) - manifest = mergedManifest + + return fixedManifest +} + +func manifestMerger(ctx android.ModuleContext, manifest android.Path, staticLibManifests android.Paths, + isLibrary bool) android.Path { + + var args string + if !isLibrary { + // Follow Gradle's behavior, only pass --remove-tools-declarations when merging app manifests. + args = "--remove-tools-declarations" } - return manifest + mergedManifest := android.PathForModuleOut(ctx, "manifest_merger", "AndroidManifest.xml") + ctx.Build(pctx, android.BuildParams{ + Rule: manifestMergerRule, + Description: "merge manifest", + Input: manifest, + Implicits: staticLibManifests, + Output: mergedManifest, + Args: map[string]string{ + "libs": android.JoinWithPrefix(staticLibManifests.Strings(), "--libs "), + "args": args, + }, + }) + + return mergedManifest } diff --git a/java/androidmk.go b/java/androidmk.go index 45fd1c1c0..ae257d7ad 100644 --- a/java/androidmk.go +++ b/java/androidmk.go @@ -17,265 +17,330 @@ package java import ( "fmt" "io" - "strings" "android/soong/android" ) -func (library *Library) AndroidMkHostDex(w io.Writer, name string, data android.AndroidMkData) { - if Bool(library.deviceProperties.Hostdex) && !library.Host() { - fmt.Fprintln(w, "include $(CLEAR_VARS)") - fmt.Fprintln(w, "LOCAL_MODULE := "+name+"-hostdex") - fmt.Fprintln(w, "LOCAL_IS_HOST_MODULE := true") - fmt.Fprintln(w, "LOCAL_MODULE_CLASS := JAVA_LIBRARIES") +func (library *Library) AndroidMkEntriesHostDex() android.AndroidMkEntries { + hostDexNeeded := Bool(library.deviceProperties.Hostdex) && !library.Host() + if !library.IsForPlatform() { + // Don't emit hostdex modules from the APEX variants + hostDexNeeded = false + } + + if hostDexNeeded { + var output android.Path if library.dexJarFile != nil { - fmt.Fprintln(w, "LOCAL_PREBUILT_MODULE_FILE :=", library.dexJarFile.String()) + output = library.dexJarFile } else { - fmt.Fprintln(w, "LOCAL_PREBUILT_MODULE_FILE :=", library.implementationAndResourcesJar.String()) + output = library.implementationAndResourcesJar } - if library.dexJarFile != nil { - fmt.Fprintln(w, "LOCAL_SOONG_DEX_JAR :=", library.dexJarFile.String()) - } - fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", library.headerJarFile.String()) - fmt.Fprintln(w, "LOCAL_SOONG_CLASSES_JAR :=", library.implementationAndResourcesJar.String()) - fmt.Fprintln(w, "LOCAL_REQUIRED_MODULES := "+strings.Join(data.Required, " ")) - if r := library.deviceProperties.Target.Hostdex.Required; len(r) > 0 { - fmt.Fprintln(w, "LOCAL_REQUIRED_MODULES +=", strings.Join(r, " ")) + return android.AndroidMkEntries{ + Class: "JAVA_LIBRARIES", + SubName: "-hostdex", + OutputFile: android.OptionalPathForPath(output), + Required: library.deviceProperties.Target.Hostdex.Required, + Include: "$(BUILD_SYSTEM)/soong_java_prebuilt.mk", + ExtraEntries: []android.AndroidMkExtraEntriesFunc{ + func(entries *android.AndroidMkEntries) { + entries.SetBool("LOCAL_IS_HOST_MODULE", true) + entries.SetPath("LOCAL_PREBUILT_MODULE_FILE", output) + if library.dexJarFile != nil { + entries.SetPath("LOCAL_SOONG_DEX_JAR", library.dexJarFile) + } + entries.SetPath("LOCAL_SOONG_HEADER_JAR", library.headerJarFile) + entries.SetPath("LOCAL_SOONG_CLASSES_JAR", library.implementationAndResourcesJar) + entries.SetString("LOCAL_MODULE_STEM", library.Stem()+"-hostdex") + }, + }, } - fmt.Fprintln(w, "include $(BUILD_SYSTEM)/soong_java_prebuilt.mk") } + return android.AndroidMkEntries{Disabled: true} } -func (library *Library) AndroidMk() android.AndroidMkData { - return android.AndroidMkData{ - Class: "JAVA_LIBRARIES", - OutputFile: android.OptionalPathForPath(library.outputFile), - Include: "$(BUILD_SYSTEM)/soong_java_prebuilt.mk", - Extra: []android.AndroidMkExtraFunc{ - func(w io.Writer, outputFile android.Path) { - if len(library.logtagsSrcs) > 0 { - var logtags []string - for _, l := range library.logtagsSrcs { - logtags = append(logtags, l.Rel()) +func (library *Library) AndroidMkEntries() []android.AndroidMkEntries { + var entriesList []android.AndroidMkEntries + + mainEntries := android.AndroidMkEntries{Disabled: true} + + // For a java library built for an APEX, we don't need Make module + hideFromMake := !library.IsForPlatform() + // If not available for platform, don't emit to make. + if !library.ApexModuleBase.AvailableFor(android.AvailableToPlatform) { + hideFromMake = true + } + if hideFromMake { + // May still need to add some additional dependencies. This will be called + // once for the platform variant (even if it is not being used) and once each + // for the APEX specific variants. In order to avoid adding the dependency + // multiple times only add it for the platform variant. + checkedModulePaths := library.additionalCheckedModules + if library.IsForPlatform() && len(checkedModulePaths) != 0 { + mainEntries = android.AndroidMkEntries{ + Class: "FAKE", + // Need at least one output file in order for this to take effect. + OutputFile: android.OptionalPathForPath(checkedModulePaths[0]), + Include: "$(BUILD_PHONY_PACKAGE)", + ExtraEntries: []android.AndroidMkExtraEntriesFunc{ + func(entries *android.AndroidMkEntries) { + entries.AddStrings("LOCAL_ADDITIONAL_CHECKED_MODULE", checkedModulePaths.Strings()...) + }, + }, + } + } + } else { + mainEntries = android.AndroidMkEntries{ + Class: "JAVA_LIBRARIES", + DistFile: android.OptionalPathForPath(library.distFile), + OutputFile: android.OptionalPathForPath(library.outputFile), + Include: "$(BUILD_SYSTEM)/soong_java_prebuilt.mk", + ExtraEntries: []android.AndroidMkExtraEntriesFunc{ + func(entries *android.AndroidMkEntries) { + if len(library.logtagsSrcs) > 0 { + var logtags []string + for _, l := range library.logtagsSrcs { + logtags = append(logtags, l.Rel()) + } + entries.AddStrings("LOCAL_LOGTAGS_FILES", logtags...) } - fmt.Fprintln(w, "LOCAL_LOGTAGS_FILES :=", strings.Join(logtags, " ")) - } - if library.installFile == nil { - fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := true") - } - if library.dexJarFile != nil { - fmt.Fprintln(w, "LOCAL_SOONG_DEX_JAR :=", library.dexJarFile.String()) - } - if len(library.dexpreopter.builtInstalled) > 0 { - fmt.Fprintln(w, "LOCAL_SOONG_BUILT_INSTALLED :=", library.dexpreopter.builtInstalled) - } - fmt.Fprintln(w, "LOCAL_SDK_VERSION :=", library.sdkVersion()) - fmt.Fprintln(w, "LOCAL_SOONG_CLASSES_JAR :=", library.implementationAndResourcesJar.String()) - fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", library.headerJarFile.String()) + if library.installFile == nil { + entries.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", true) + } + if library.dexJarFile != nil { + entries.SetPath("LOCAL_SOONG_DEX_JAR", library.dexJarFile) + } + if len(library.dexpreopter.builtInstalled) > 0 { + entries.SetString("LOCAL_SOONG_BUILT_INSTALLED", library.dexpreopter.builtInstalled) + } + entries.SetString("LOCAL_SDK_VERSION", library.sdkVersion().raw) + entries.SetPath("LOCAL_SOONG_CLASSES_JAR", library.implementationAndResourcesJar) + entries.SetPath("LOCAL_SOONG_HEADER_JAR", library.headerJarFile) - if library.jacocoReportClassesFile != nil { - fmt.Fprintln(w, "LOCAL_SOONG_JACOCO_REPORT_CLASSES_JAR :=", library.jacocoReportClassesFile.String()) - } + if library.jacocoReportClassesFile != nil { + entries.SetPath("LOCAL_SOONG_JACOCO_REPORT_CLASSES_JAR", library.jacocoReportClassesFile) + } - if len(library.exportedSdkLibs) != 0 { - fmt.Fprintln(w, "LOCAL_EXPORT_SDK_LIBRARIES :=", strings.Join(library.exportedSdkLibs, " ")) - } + entries.AddStrings("LOCAL_EXPORT_SDK_LIBRARIES", library.exportedSdkLibs...) - if len(library.additionalCheckedModules) != 0 { - fmt.Fprintln(w, "LOCAL_ADDITIONAL_CHECKED_MODULE +=", strings.Join(library.additionalCheckedModules.Strings(), " ")) - } + if len(library.additionalCheckedModules) != 0 { + entries.AddStrings("LOCAL_ADDITIONAL_CHECKED_MODULE", library.additionalCheckedModules.Strings()...) + } - // Temporary hack: export sources used to compile framework.jar to Make - // to be used for droiddoc - // TODO(ccross): remove this once droiddoc is in soong - if (library.Name() == "framework") || (library.Name() == "framework-annotation-proc") { - fmt.Fprintln(w, "SOONG_FRAMEWORK_SRCS :=", strings.Join(library.compiledJavaSrcs.Strings(), " ")) - fmt.Fprintln(w, "SOONG_FRAMEWORK_SRCJARS :=", strings.Join(library.compiledSrcJars.Strings(), " ")) - } + if library.proguardDictionary != nil { + entries.SetPath("LOCAL_SOONG_PROGUARD_DICT", library.proguardDictionary) + } + entries.SetString("LOCAL_MODULE_STEM", library.Stem()) + + entries.SetOptionalPaths("LOCAL_SOONG_LINT_REPORTS", library.linter.reports) + }, }, - }, - Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) { - android.WriteAndroidMkData(w, data) - library.AndroidMkHostDex(w, name, data) - }, + } } + + hostDexEntries := library.AndroidMkEntriesHostDex() + + entriesList = append(entriesList, mainEntries, hostDexEntries) + return entriesList } // Called for modules that are a component of a test suite. -func testSuiteComponent(w io.Writer, test_suites []string) { - fmt.Fprintln(w, "LOCAL_MODULE_TAGS := tests") +func testSuiteComponent(entries *android.AndroidMkEntries, test_suites []string) { + entries.SetString("LOCAL_MODULE_TAGS", "tests") if len(test_suites) > 0 { - fmt.Fprintln(w, "LOCAL_COMPATIBILITY_SUITE :=", - strings.Join(test_suites, " ")) + entries.AddStrings("LOCAL_COMPATIBILITY_SUITE", test_suites...) } else { - fmt.Fprintln(w, "LOCAL_COMPATIBILITY_SUITE := null-suite") + entries.SetString("LOCAL_COMPATIBILITY_SUITE", "null-suite") } } -func (j *Test) AndroidMk() android.AndroidMkData { - data := j.Library.AndroidMk() - data.Extra = append(data.Extra, func(w io.Writer, outputFile android.Path) { - testSuiteComponent(w, j.testProperties.Test_suites) +func (j *Test) AndroidMkEntries() []android.AndroidMkEntries { + entriesList := j.Library.AndroidMkEntries() + entries := &entriesList[0] + entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) { + testSuiteComponent(entries, j.testProperties.Test_suites) if j.testConfig != nil { - fmt.Fprintln(w, "LOCAL_FULL_TEST_CONFIG :=", j.testConfig.String()) + entries.SetPath("LOCAL_FULL_TEST_CONFIG", j.testConfig) + } + androidMkWriteTestData(j.data, entries) + if !BoolDefault(j.testProperties.Auto_gen_config, true) { + entries.SetString("LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG", "true") } }) - androidMkWriteTestData(j.data, &data) - - return data + return entriesList } -func (j *TestHelperLibrary) AndroidMk() android.AndroidMkData { - data := j.Library.AndroidMk() - data.Extra = append(data.Extra, func(w io.Writer, outputFile android.Path) { - testSuiteComponent(w, j.testHelperLibraryProperties.Test_suites) +func (j *TestHelperLibrary) AndroidMkEntries() []android.AndroidMkEntries { + entriesList := j.Library.AndroidMkEntries() + entries := &entriesList[0] + entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) { + testSuiteComponent(entries, j.testHelperLibraryProperties.Test_suites) }) - return data + return entriesList } -func (prebuilt *Import) AndroidMk() android.AndroidMkData { - return android.AndroidMkData{ +func (prebuilt *Import) AndroidMkEntries() []android.AndroidMkEntries { + if !prebuilt.IsForPlatform() || !prebuilt.ContainingSdk().Unversioned() { + return []android.AndroidMkEntries{android.AndroidMkEntries{ + Disabled: true, + }} + } + return []android.AndroidMkEntries{android.AndroidMkEntries{ Class: "JAVA_LIBRARIES", OutputFile: android.OptionalPathForPath(prebuilt.combinedClasspathFile), Include: "$(BUILD_SYSTEM)/soong_java_prebuilt.mk", - Extra: []android.AndroidMkExtraFunc{ - func(w io.Writer, outputFile android.Path) { - fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := ", !Bool(prebuilt.properties.Installable)) - fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", prebuilt.combinedClasspathFile.String()) - fmt.Fprintln(w, "LOCAL_SOONG_CLASSES_JAR :=", prebuilt.combinedClasspathFile.String()) - fmt.Fprintln(w, "LOCAL_SDK_VERSION :=", prebuilt.sdkVersion()) + ExtraEntries: []android.AndroidMkExtraEntriesFunc{ + func(entries *android.AndroidMkEntries) { + entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", !Bool(prebuilt.properties.Installable)) + entries.SetPath("LOCAL_SOONG_HEADER_JAR", prebuilt.combinedClasspathFile) + entries.SetPath("LOCAL_SOONG_CLASSES_JAR", prebuilt.combinedClasspathFile) + entries.SetString("LOCAL_SDK_VERSION", prebuilt.sdkVersion().raw) + entries.SetString("LOCAL_MODULE_STEM", prebuilt.Stem()) }, }, - } + }} } -func (prebuilt *DexImport) AndroidMk() android.AndroidMkData { - return android.AndroidMkData{ +func (prebuilt *DexImport) AndroidMkEntries() []android.AndroidMkEntries { + if !prebuilt.IsForPlatform() { + return []android.AndroidMkEntries{android.AndroidMkEntries{ + Disabled: true, + }} + } + return []android.AndroidMkEntries{android.AndroidMkEntries{ Class: "JAVA_LIBRARIES", OutputFile: android.OptionalPathForPath(prebuilt.maybeStrippedDexJarFile), Include: "$(BUILD_SYSTEM)/soong_java_prebuilt.mk", - Extra: []android.AndroidMkExtraFunc{ - func(w io.Writer, outputFile android.Path) { + ExtraEntries: []android.AndroidMkExtraEntriesFunc{ + func(entries *android.AndroidMkEntries) { if prebuilt.dexJarFile != nil { - fmt.Fprintln(w, "LOCAL_SOONG_DEX_JAR :=", prebuilt.dexJarFile.String()) + entries.SetPath("LOCAL_SOONG_DEX_JAR", prebuilt.dexJarFile) // TODO(b/125517186): export the dex jar as a classes jar to match some mis-uses in Make until // boot_jars_package_check.mk can check dex jars. - fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", prebuilt.dexJarFile.String()) - fmt.Fprintln(w, "LOCAL_SOONG_CLASSES_JAR :=", prebuilt.dexJarFile.String()) + entries.SetPath("LOCAL_SOONG_HEADER_JAR", prebuilt.dexJarFile) + entries.SetPath("LOCAL_SOONG_CLASSES_JAR", prebuilt.dexJarFile) } if len(prebuilt.dexpreopter.builtInstalled) > 0 { - fmt.Fprintln(w, "LOCAL_SOONG_BUILT_INSTALLED :=", prebuilt.dexpreopter.builtInstalled) + entries.SetString("LOCAL_SOONG_BUILT_INSTALLED", prebuilt.dexpreopter.builtInstalled) } + entries.SetString("LOCAL_MODULE_STEM", prebuilt.Stem()) }, }, - } + }} } -func (prebuilt *AARImport) AndroidMk() android.AndroidMkData { - return android.AndroidMkData{ +func (prebuilt *AARImport) AndroidMkEntries() []android.AndroidMkEntries { + if !prebuilt.IsForPlatform() { + return []android.AndroidMkEntries{{ + Disabled: true, + }} + } + return []android.AndroidMkEntries{android.AndroidMkEntries{ Class: "JAVA_LIBRARIES", OutputFile: android.OptionalPathForPath(prebuilt.classpathFile), Include: "$(BUILD_SYSTEM)/soong_java_prebuilt.mk", - Extra: []android.AndroidMkExtraFunc{ - func(w io.Writer, outputFile android.Path) { - fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := true") - fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", prebuilt.classpathFile.String()) - fmt.Fprintln(w, "LOCAL_SOONG_CLASSES_JAR :=", prebuilt.classpathFile.String()) - fmt.Fprintln(w, "LOCAL_SOONG_RESOURCE_EXPORT_PACKAGE :=", prebuilt.exportPackage.String()) - fmt.Fprintln(w, "LOCAL_SOONG_EXPORT_PROGUARD_FLAGS :=", prebuilt.proguardFlags.String()) - fmt.Fprintln(w, "LOCAL_SOONG_STATIC_LIBRARY_EXTRA_PACKAGES :=", prebuilt.extraAaptPackagesFile.String()) - fmt.Fprintln(w, "LOCAL_FULL_MANIFEST_FILE :=", prebuilt.manifest.String()) - fmt.Fprintln(w, "LOCAL_SDK_VERSION :=", prebuilt.sdkVersion()) + ExtraEntries: []android.AndroidMkExtraEntriesFunc{ + func(entries *android.AndroidMkEntries) { + entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", true) + entries.SetPath("LOCAL_SOONG_HEADER_JAR", prebuilt.classpathFile) + entries.SetPath("LOCAL_SOONG_CLASSES_JAR", prebuilt.classpathFile) + entries.SetPath("LOCAL_SOONG_RESOURCE_EXPORT_PACKAGE", prebuilt.exportPackage) + entries.SetPath("LOCAL_SOONG_EXPORT_PROGUARD_FLAGS", prebuilt.proguardFlags) + entries.SetPath("LOCAL_SOONG_STATIC_LIBRARY_EXTRA_PACKAGES", prebuilt.extraAaptPackagesFile) + entries.SetPath("LOCAL_FULL_MANIFEST_FILE", prebuilt.manifest) + entries.SetString("LOCAL_SDK_VERSION", prebuilt.sdkVersion().raw) }, }, - } + }} } -func (binary *Binary) AndroidMk() android.AndroidMkData { +func (binary *Binary) AndroidMkEntries() []android.AndroidMkEntries { if !binary.isWrapperVariant { - return android.AndroidMkData{ + return []android.AndroidMkEntries{android.AndroidMkEntries{ Class: "JAVA_LIBRARIES", OutputFile: android.OptionalPathForPath(binary.outputFile), Include: "$(BUILD_SYSTEM)/soong_java_prebuilt.mk", - Extra: []android.AndroidMkExtraFunc{ - func(w io.Writer, outputFile android.Path) { - fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", binary.headerJarFile.String()) - fmt.Fprintln(w, "LOCAL_SOONG_CLASSES_JAR :=", binary.implementationAndResourcesJar.String()) + ExtraEntries: []android.AndroidMkExtraEntriesFunc{ + func(entries *android.AndroidMkEntries) { + entries.SetPath("LOCAL_SOONG_HEADER_JAR", binary.headerJarFile) + entries.SetPath("LOCAL_SOONG_CLASSES_JAR", binary.implementationAndResourcesJar) if binary.dexJarFile != nil { - fmt.Fprintln(w, "LOCAL_SOONG_DEX_JAR :=", binary.dexJarFile.String()) + entries.SetPath("LOCAL_SOONG_DEX_JAR", binary.dexJarFile) } if len(binary.dexpreopter.builtInstalled) > 0 { - fmt.Fprintln(w, "LOCAL_SOONG_BUILT_INSTALLED :=", binary.dexpreopter.builtInstalled) + entries.SetString("LOCAL_SOONG_BUILT_INSTALLED", binary.dexpreopter.builtInstalled) } }, }, - Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) { - android.WriteAndroidMkData(w, data) - - fmt.Fprintln(w, "jar_installed_module := $(LOCAL_INSTALLED_MODULE)") + ExtraFooters: []android.AndroidMkExtraFootersFunc{ + func(w io.Writer, name, prefix, moduleDir string, entries *android.AndroidMkEntries) { + fmt.Fprintln(w, "jar_installed_module := $(LOCAL_INSTALLED_MODULE)") + }, }, - } + }} } else { - return android.AndroidMkData{ + return []android.AndroidMkEntries{android.AndroidMkEntries{ Class: "EXECUTABLES", OutputFile: android.OptionalPathForPath(binary.wrapperFile), - Extra: []android.AndroidMkExtraFunc{ - func(w io.Writer, outputFile android.Path) { - fmt.Fprintln(w, "LOCAL_STRIP_MODULE := false") + ExtraEntries: []android.AndroidMkExtraEntriesFunc{ + func(entries *android.AndroidMkEntries) { + entries.SetBool("LOCAL_STRIP_MODULE", false) }, }, - Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) { - android.WriteAndroidMkData(w, data) - - // Ensure that the wrapper script timestamp is always updated when the jar is updated - fmt.Fprintln(w, "$(LOCAL_INSTALLED_MODULE): $(jar_installed_module)") - fmt.Fprintln(w, "jar_installed_module :=") + ExtraFooters: []android.AndroidMkExtraFootersFunc{ + func(w io.Writer, name, prefix, moduleDir string, entries *android.AndroidMkEntries) { + // Ensure that the wrapper script timestamp is always updated when the jar is updated + fmt.Fprintln(w, "$(LOCAL_INSTALLED_MODULE): $(jar_installed_module)") + fmt.Fprintln(w, "jar_installed_module :=") + }, }, - } + }} } } -func (app *AndroidApp) AndroidMk() android.AndroidMkData { - return android.AndroidMkData{ +func (app *AndroidApp) AndroidMkEntries() []android.AndroidMkEntries { + if !app.IsForPlatform() || app.appProperties.HideFromMake { + return []android.AndroidMkEntries{android.AndroidMkEntries{ + Disabled: true, + }} + } + return []android.AndroidMkEntries{android.AndroidMkEntries{ Class: "APPS", OutputFile: android.OptionalPathForPath(app.outputFile), Include: "$(BUILD_SYSTEM)/soong_app_prebuilt.mk", - Extra: []android.AndroidMkExtraFunc{ - func(w io.Writer, outputFile android.Path) { - // TODO(jungjw): This, outputting two LOCAL_MODULE lines, works, but is not ideal. Find a better solution. - if app.Name() != app.installApkName { - fmt.Fprintln(w, "# Overridden by PRODUCT_PACKAGE_NAME_OVERRIDES") - fmt.Fprintln(w, "LOCAL_MODULE :=", app.installApkName) - } - fmt.Fprintln(w, "LOCAL_SOONG_RESOURCE_EXPORT_PACKAGE :=", app.exportPackage.String()) + ExtraEntries: []android.AndroidMkExtraEntriesFunc{ + func(entries *android.AndroidMkEntries) { + // App module names can be overridden. + entries.SetString("LOCAL_MODULE", app.installApkName) + entries.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", app.appProperties.PreventInstall) + entries.SetPath("LOCAL_SOONG_RESOURCE_EXPORT_PACKAGE", app.exportPackage) if app.dexJarFile != nil { - fmt.Fprintln(w, "LOCAL_SOONG_DEX_JAR :=", app.dexJarFile.String()) + entries.SetPath("LOCAL_SOONG_DEX_JAR", app.dexJarFile) } if app.implementationAndResourcesJar != nil { - fmt.Fprintln(w, "LOCAL_SOONG_CLASSES_JAR :=", app.implementationAndResourcesJar.String()) + entries.SetPath("LOCAL_SOONG_CLASSES_JAR", app.implementationAndResourcesJar) } if app.headerJarFile != nil { - fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", app.headerJarFile.String()) + entries.SetPath("LOCAL_SOONG_HEADER_JAR", app.headerJarFile) } if app.bundleFile != nil { - fmt.Fprintln(w, "LOCAL_SOONG_BUNDLE :=", app.bundleFile.String()) + entries.SetPath("LOCAL_SOONG_BUNDLE", app.bundleFile) } if app.jacocoReportClassesFile != nil { - fmt.Fprintln(w, "LOCAL_SOONG_JACOCO_REPORT_CLASSES_JAR :=", app.jacocoReportClassesFile.String()) + entries.SetPath("LOCAL_SOONG_JACOCO_REPORT_CLASSES_JAR", app.jacocoReportClassesFile) } if app.proguardDictionary != nil { - fmt.Fprintln(w, "LOCAL_SOONG_PROGUARD_DICT :=", app.proguardDictionary.String()) + entries.SetPath("LOCAL_SOONG_PROGUARD_DICT", app.proguardDictionary) } if app.Name() == "framework-res" { - fmt.Fprintln(w, "LOCAL_MODULE_PATH := $(TARGET_OUT_JAVA_LIBRARIES)") + entries.SetString("LOCAL_MODULE_PATH", "$(TARGET_OUT_JAVA_LIBRARIES)") // Make base_rules.mk not put framework-res in a subdirectory called // framework_res. - fmt.Fprintln(w, "LOCAL_NO_STANDARD_LIBRARIES := true") + entries.SetBoolIfTrue("LOCAL_NO_STANDARD_LIBRARIES", true) } filterRRO := func(filter overlayType) android.Paths { @@ -291,41 +356,62 @@ func (app *AndroidApp) AndroidMk() android.AndroidMkData { } deviceRRODirs := filterRRO(device) if len(deviceRRODirs) > 0 { - fmt.Fprintln(w, "LOCAL_SOONG_DEVICE_RRO_DIRS :=", strings.Join(deviceRRODirs.Strings(), " ")) + entries.AddStrings("LOCAL_SOONG_DEVICE_RRO_DIRS", deviceRRODirs.Strings()...) } productRRODirs := filterRRO(product) if len(productRRODirs) > 0 { - fmt.Fprintln(w, "LOCAL_SOONG_PRODUCT_RRO_DIRS :=", strings.Join(productRRODirs.Strings(), " ")) + entries.AddStrings("LOCAL_SOONG_PRODUCT_RRO_DIRS", productRRODirs.Strings()...) } - if Bool(app.appProperties.Export_package_resources) { - fmt.Fprintln(w, "LOCAL_EXPORT_PACKAGE_RESOURCES := true") - } + entries.SetBoolIfTrue("LOCAL_EXPORT_PACKAGE_RESOURCES", Bool(app.appProperties.Export_package_resources)) - fmt.Fprintln(w, "LOCAL_FULL_MANIFEST_FILE :=", app.manifestPath.String()) + entries.SetPath("LOCAL_FULL_MANIFEST_FILE", app.manifestPath) - if Bool(app.appProperties.Privileged) { - fmt.Fprintln(w, "LOCAL_PRIVILEGED_MODULE := true") - } + entries.SetBoolIfTrue("LOCAL_PRIVILEGED_MODULE", app.Privileged()) + + entries.SetString("LOCAL_CERTIFICATE", app.certificate.AndroidMkString()) + entries.AddStrings("LOCAL_OVERRIDES_PACKAGES", app.getOverriddenPackages()...) - fmt.Fprintln(w, "LOCAL_CERTIFICATE :=", app.certificate.Pem.String()) - if overriddenPkgs := app.getOverriddenPackages(); len(overriddenPkgs) > 0 { - fmt.Fprintln(w, "LOCAL_OVERRIDES_PACKAGES :=", strings.Join(overriddenPkgs, " ")) + if app.embeddedJniLibs { + jniSymbols := app.JNISymbolsInstalls(app.installPathForJNISymbols.String()) + entries.SetString("LOCAL_SOONG_JNI_LIBS_SYMBOLS", jniSymbols.String()) + } else { + for _, jniLib := range app.jniLibs { + entries.AddStrings("LOCAL_SOONG_JNI_LIBS_"+jniLib.target.Arch.ArchType.String(), jniLib.name) + } } - for _, jniLib := range app.installJniLibs { - fmt.Fprintln(w, "LOCAL_SOONG_JNI_LIBS_"+jniLib.target.Arch.ArchType.String(), "+=", jniLib.name) + if len(app.jniCoverageOutputs) > 0 { + entries.AddStrings("LOCAL_PREBUILT_COVERAGE_ARCHIVE", app.jniCoverageOutputs.Strings()...) } if len(app.dexpreopter.builtInstalled) > 0 { - fmt.Fprintln(w, "LOCAL_SOONG_BUILT_INSTALLED :=", app.dexpreopter.builtInstalled) + entries.SetString("LOCAL_SOONG_BUILT_INSTALLED", app.dexpreopter.builtInstalled) } - for _, split := range app.aapt.splits { - install := "$(LOCAL_MODULE_PATH)/" + strings.TrimSuffix(app.installApkName, ".apk") + split.suffix + ".apk" - fmt.Fprintln(w, "LOCAL_SOONG_BUILT_INSTALLED +=", split.path.String()+":"+install) + for _, extra := range app.extraOutputFiles { + install := app.onDeviceDir + "/" + extra.Base() + entries.AddStrings("LOCAL_SOONG_BUILT_INSTALLED", extra.String()+":"+install) } + + entries.SetOptionalPaths("LOCAL_SOONG_LINT_REPORTS", app.linter.reports) }, }, - } + ExtraFooters: []android.AndroidMkExtraFootersFunc{ + func(w io.Writer, name, prefix, moduleDir string, entries *android.AndroidMkEntries) { + if app.noticeOutputs.Merged.Valid() { + fmt.Fprintf(w, "$(call dist-for-goals,%s,%s:%s)\n", + app.installApkName, app.noticeOutputs.Merged.String(), app.installApkName+"_NOTICE") + } + if app.noticeOutputs.TxtOutput.Valid() { + fmt.Fprintf(w, "$(call dist-for-goals,%s,%s:%s)\n", + app.installApkName, app.noticeOutputs.TxtOutput.String(), app.installApkName+"_NOTICE.txt") + } + if app.noticeOutputs.HtmlOutput.Valid() { + fmt.Fprintf(w, "$(call dist-for-goals,%s,%s:%s)\n", + app.installApkName, app.noticeOutputs.HtmlOutput.String(), app.installApkName+"_NOTICE.html") + } + }, + }, + }} } func (a *AndroidApp) getOverriddenPackages() []string { @@ -339,88 +425,96 @@ func (a *AndroidApp) getOverriddenPackages() []string { return overridden } -func (a *AndroidTest) AndroidMk() android.AndroidMkData { - data := a.AndroidApp.AndroidMk() - data.Extra = append(data.Extra, func(w io.Writer, outputFile android.Path) { - testSuiteComponent(w, a.testProperties.Test_suites) +func (a *AndroidTest) AndroidMkEntries() []android.AndroidMkEntries { + entriesList := a.AndroidApp.AndroidMkEntries() + entries := &entriesList[0] + entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) { + testSuiteComponent(entries, a.testProperties.Test_suites) if a.testConfig != nil { - fmt.Fprintln(w, "LOCAL_FULL_TEST_CONFIG :=", a.testConfig.String()) + entries.SetPath("LOCAL_FULL_TEST_CONFIG", a.testConfig) } + androidMkWriteTestData(a.data, entries) }) - androidMkWriteTestData(a.data, &data) - return data + return entriesList } -func (a *AndroidTestHelperApp) AndroidMk() android.AndroidMkData { - data := a.AndroidApp.AndroidMk() - data.Extra = append(data.Extra, func(w io.Writer, outputFile android.Path) { - testSuiteComponent(w, a.appTestHelperAppProperties.Test_suites) +func (a *AndroidTestHelperApp) AndroidMkEntries() []android.AndroidMkEntries { + entriesList := a.AndroidApp.AndroidMkEntries() + entries := &entriesList[0] + entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) { + testSuiteComponent(entries, a.appTestHelperAppProperties.Test_suites) }) - return data + return entriesList } -func (a *AndroidLibrary) AndroidMk() android.AndroidMkData { - data := a.Library.AndroidMk() +func (a *AndroidLibrary) AndroidMkEntries() []android.AndroidMkEntries { + if !a.IsForPlatform() { + return []android.AndroidMkEntries{{ + Disabled: true, + }} + } + entriesList := a.Library.AndroidMkEntries() + entries := &entriesList[0] - data.Extra = append(data.Extra, func(w io.Writer, outputFile android.Path) { + entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) { if a.aarFile != nil { - fmt.Fprintln(w, "LOCAL_SOONG_AAR :=", a.aarFile.String()) - } - if a.proguardDictionary != nil { - fmt.Fprintln(w, "LOCAL_SOONG_PROGUARD_DICT :=", a.proguardDictionary.String()) + entries.SetPath("LOCAL_SOONG_AAR", a.aarFile) } if a.Name() == "framework-res" { - fmt.Fprintln(w, "LOCAL_MODULE_PATH := $(TARGET_OUT_JAVA_LIBRARIES)") + entries.SetString("LOCAL_MODULE_PATH", "$(TARGET_OUT_JAVA_LIBRARIES)") // Make base_rules.mk not put framework-res in a subdirectory called // framework_res. - fmt.Fprintln(w, "LOCAL_NO_STANDARD_LIBRARIES := true") + entries.SetBoolIfTrue("LOCAL_NO_STANDARD_LIBRARIES", true) } - fmt.Fprintln(w, "LOCAL_SOONG_RESOURCE_EXPORT_PACKAGE :=", a.exportPackage.String()) - fmt.Fprintln(w, "LOCAL_SOONG_STATIC_LIBRARY_EXTRA_PACKAGES :=", a.extraAaptPackagesFile.String()) - fmt.Fprintln(w, "LOCAL_FULL_MANIFEST_FILE :=", a.manifestPath.String()) - fmt.Fprintln(w, "LOCAL_SOONG_EXPORT_PROGUARD_FLAGS :=", - strings.Join(a.exportedProguardFlagFiles.Strings(), " ")) - fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := true") + entries.SetPath("LOCAL_SOONG_RESOURCE_EXPORT_PACKAGE", a.exportPackage) + entries.SetPath("LOCAL_SOONG_STATIC_LIBRARY_EXTRA_PACKAGES", a.extraAaptPackagesFile) + entries.SetPath("LOCAL_FULL_MANIFEST_FILE", a.mergedManifestFile) + entries.AddStrings("LOCAL_SOONG_EXPORT_PROGUARD_FLAGS", a.exportedProguardFlagFiles.Strings()...) + entries.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", true) }) - return data + return entriesList } -func (jd *Javadoc) AndroidMk() android.AndroidMkData { - return android.AndroidMkData{ +func (jd *Javadoc) AndroidMkEntries() []android.AndroidMkEntries { + return []android.AndroidMkEntries{android.AndroidMkEntries{ Class: "JAVA_LIBRARIES", OutputFile: android.OptionalPathForPath(jd.stubsSrcJar), Include: "$(BUILD_SYSTEM)/soong_droiddoc_prebuilt.mk", - Extra: []android.AndroidMkExtraFunc{ - func(w io.Writer, outputFile android.Path) { + ExtraEntries: []android.AndroidMkExtraEntriesFunc{ + func(entries *android.AndroidMkEntries) { if BoolDefault(jd.properties.Installable, true) { - fmt.Fprintln(w, "LOCAL_DROIDDOC_DOC_ZIP := ", jd.docZip.String()) + entries.SetPath("LOCAL_DROIDDOC_DOC_ZIP", jd.docZip) } if jd.stubsSrcJar != nil { - fmt.Fprintln(w, "LOCAL_DROIDDOC_STUBS_SRCJAR := ", jd.stubsSrcJar.String()) + entries.SetPath("LOCAL_DROIDDOC_STUBS_SRCJAR", jd.stubsSrcJar) } }, }, - } + }} } -func (ddoc *Droiddoc) AndroidMk() android.AndroidMkData { - return android.AndroidMkData{ +func (ddoc *Droiddoc) AndroidMkEntries() []android.AndroidMkEntries { + return []android.AndroidMkEntries{android.AndroidMkEntries{ Class: "JAVA_LIBRARIES", OutputFile: android.OptionalPathForPath(ddoc.stubsSrcJar), Include: "$(BUILD_SYSTEM)/soong_droiddoc_prebuilt.mk", - Extra: []android.AndroidMkExtraFunc{ - func(w io.Writer, outputFile android.Path) { + ExtraEntries: []android.AndroidMkExtraEntriesFunc{ + func(entries *android.AndroidMkEntries) { if BoolDefault(ddoc.Javadoc.properties.Installable, true) && ddoc.Javadoc.docZip != nil { - fmt.Fprintln(w, "LOCAL_DROIDDOC_DOC_ZIP := ", ddoc.Javadoc.docZip.String()) + entries.SetPath("LOCAL_DROIDDOC_DOC_ZIP", ddoc.Javadoc.docZip) } if ddoc.Javadoc.stubsSrcJar != nil { - fmt.Fprintln(w, "LOCAL_DROIDDOC_STUBS_SRCJAR := ", ddoc.Javadoc.stubsSrcJar.String()) + entries.SetPath("LOCAL_DROIDDOC_STUBS_SRCJAR", ddoc.Javadoc.stubsSrcJar) } + }, + }, + ExtraFooters: []android.AndroidMkExtraFootersFunc{ + func(w io.Writer, name, prefix, moduleDir string, entries *android.AndroidMkEntries) { if ddoc.checkCurrentApiTimestamp != nil { fmt.Fprintln(w, ".PHONY:", ddoc.Name()+"-check-current-api") fmt.Fprintln(w, ddoc.Name()+"-check-current-api:", @@ -456,60 +550,56 @@ func (ddoc *Droiddoc) AndroidMk() android.AndroidMkData { fmt.Fprintln(w, "droidcore: checkapi") } } - apiFilePrefix := "INTERNAL_PLATFORM_" - if String(ddoc.properties.Api_tag_name) != "" { - apiFilePrefix += String(ddoc.properties.Api_tag_name) + "_" - } - if ddoc.apiFile != nil { - fmt.Fprintln(w, apiFilePrefix+"API_FILE := ", ddoc.apiFile.String()) - } - if ddoc.dexApiFile != nil { - fmt.Fprintln(w, apiFilePrefix+"DEX_API_FILE := ", ddoc.dexApiFile.String()) - } - if ddoc.privateApiFile != nil { - fmt.Fprintln(w, apiFilePrefix+"PRIVATE_API_FILE := ", ddoc.privateApiFile.String()) - } - if ddoc.privateDexApiFile != nil { - fmt.Fprintln(w, apiFilePrefix+"PRIVATE_DEX_API_FILE := ", ddoc.privateDexApiFile.String()) - } - if ddoc.removedApiFile != nil { - fmt.Fprintln(w, apiFilePrefix+"REMOVED_API_FILE := ", ddoc.removedApiFile.String()) - } - if ddoc.removedDexApiFile != nil { - fmt.Fprintln(w, apiFilePrefix+"REMOVED_DEX_API_FILE := ", ddoc.removedDexApiFile.String()) - } - if ddoc.exactApiFile != nil { - fmt.Fprintln(w, apiFilePrefix+"EXACT_API_FILE := ", ddoc.exactApiFile.String()) - } - if ddoc.proguardFile != nil { - fmt.Fprintln(w, apiFilePrefix+"PROGUARD_FILE := ", ddoc.proguardFile.String()) - } }, }, - } + }} } -func (dstubs *Droidstubs) AndroidMk() android.AndroidMkData { - return android.AndroidMkData{ +func (dstubs *Droidstubs) AndroidMkEntries() []android.AndroidMkEntries { + // If the stubsSrcJar is not generated (because generate_stubs is false) then + // use the api file as the output file to ensure the relevant phony targets + // are created in make if only the api txt file is being generated. This is + // needed because an invalid output file would prevent the make entries from + // being written. + // TODO(b/146727827): Revert when we do not need to generate stubs and API separately. + distFile := android.OptionalPathForPath(dstubs.apiFile) + outputFile := android.OptionalPathForPath(dstubs.stubsSrcJar) + if !outputFile.Valid() { + outputFile = distFile + } + return []android.AndroidMkEntries{android.AndroidMkEntries{ Class: "JAVA_LIBRARIES", - OutputFile: android.OptionalPathForPath(dstubs.stubsSrcJar), + DistFile: distFile, + OutputFile: outputFile, Include: "$(BUILD_SYSTEM)/soong_droiddoc_prebuilt.mk", - Extra: []android.AndroidMkExtraFunc{ - func(w io.Writer, outputFile android.Path) { + ExtraEntries: []android.AndroidMkExtraEntriesFunc{ + func(entries *android.AndroidMkEntries) { if dstubs.Javadoc.stubsSrcJar != nil { - fmt.Fprintln(w, "LOCAL_DROIDDOC_STUBS_SRCJAR := ", dstubs.Javadoc.stubsSrcJar.String()) + entries.SetPath("LOCAL_DROIDDOC_STUBS_SRCJAR", dstubs.Javadoc.stubsSrcJar) } if dstubs.apiVersionsXml != nil { - fmt.Fprintln(w, "LOCAL_DROIDDOC_API_VERSIONS_XML := ", dstubs.apiVersionsXml.String()) + entries.SetPath("LOCAL_DROIDDOC_API_VERSIONS_XML", dstubs.apiVersionsXml) } if dstubs.annotationsZip != nil { - fmt.Fprintln(w, "LOCAL_DROIDDOC_ANNOTATIONS_ZIP := ", dstubs.annotationsZip.String()) + entries.SetPath("LOCAL_DROIDDOC_ANNOTATIONS_ZIP", dstubs.annotationsZip) } if dstubs.jdiffDocZip != nil { - fmt.Fprintln(w, "LOCAL_DROIDDOC_JDIFF_DOC_ZIP := ", dstubs.jdiffDocZip.String()) + entries.SetPath("LOCAL_DROIDDOC_JDIFF_DOC_ZIP", dstubs.jdiffDocZip) } if dstubs.metadataZip != nil { - fmt.Fprintln(w, "LOCAL_DROIDDOC_METADATA_ZIP := ", dstubs.metadataZip.String()) + entries.SetPath("LOCAL_DROIDDOC_METADATA_ZIP", dstubs.metadataZip) + } + }, + }, + ExtraFooters: []android.AndroidMkExtraFootersFunc{ + func(w io.Writer, name, prefix, moduleDir string, entries *android.AndroidMkEntries) { + if dstubs.apiFile != nil { + fmt.Fprintf(w, ".PHONY: %s %s.txt\n", dstubs.Name(), dstubs.Name()) + fmt.Fprintf(w, "%s %s.txt: %s\n", dstubs.Name(), dstubs.Name(), dstubs.apiFile) + } + if dstubs.removedApiFile != nil { + fmt.Fprintf(w, ".PHONY: %s %s.txt\n", dstubs.Name(), dstubs.Name()) + fmt.Fprintf(w, "%s %s.txt: %s\n", dstubs.Name(), dstubs.Name(), dstubs.removedApiFile) } if dstubs.checkCurrentApiTimestamp != nil { fmt.Fprintln(w, ".PHONY:", dstubs.Name()+"-check-current-api") @@ -537,13 +627,28 @@ func (dstubs *Droidstubs) AndroidMk() android.AndroidMkData { fmt.Fprintln(w, dstubs.Name()+"-check-last-released-api:", dstubs.checkLastReleasedApiTimestamp.String()) - if dstubs.Name() == "api-stubs-docs" || dstubs.Name() == "system-api-stubs-docs" { - fmt.Fprintln(w, ".PHONY: checkapi") - fmt.Fprintln(w, "checkapi:", - dstubs.checkLastReleasedApiTimestamp.String()) + fmt.Fprintln(w, ".PHONY: checkapi") + fmt.Fprintln(w, "checkapi:", + dstubs.checkLastReleasedApiTimestamp.String()) - fmt.Fprintln(w, ".PHONY: droidcore") - fmt.Fprintln(w, "droidcore: checkapi") + fmt.Fprintln(w, ".PHONY: droidcore") + fmt.Fprintln(w, "droidcore: checkapi") + } + if dstubs.apiLintTimestamp != nil { + fmt.Fprintln(w, ".PHONY:", dstubs.Name()+"-api-lint") + fmt.Fprintln(w, dstubs.Name()+"-api-lint:", + dstubs.apiLintTimestamp.String()) + + fmt.Fprintln(w, ".PHONY: checkapi") + fmt.Fprintln(w, "checkapi:", + dstubs.Name()+"-api-lint") + + fmt.Fprintln(w, ".PHONY: droidcore") + fmt.Fprintln(w, "droidcore: checkapi") + + if dstubs.apiLintReport != nil { + fmt.Fprintf(w, "$(call dist-for-goals,%s,%s:%s)\n", dstubs.Name()+"-api-lint", + dstubs.apiLintReport.String(), "apilint/"+dstubs.Name()+"-lint-report.txt") } } if dstubs.checkNullabilityWarningsTimestamp != nil { @@ -554,61 +659,76 @@ func (dstubs *Droidstubs) AndroidMk() android.AndroidMkData { fmt.Fprintln(w, ".PHONY:", "droidcore") fmt.Fprintln(w, "droidcore: ", dstubs.Name()+"-check-nullability-warnings") } - apiFilePrefix := "INTERNAL_PLATFORM_" - if String(dstubs.properties.Api_tag_name) != "" { - apiFilePrefix += String(dstubs.properties.Api_tag_name) + "_" - } - if dstubs.apiFile != nil { - fmt.Fprintln(w, apiFilePrefix+"API_FILE := ", dstubs.apiFile.String()) - } - if dstubs.dexApiFile != nil { - fmt.Fprintln(w, apiFilePrefix+"DEX_API_FILE := ", dstubs.dexApiFile.String()) - } - if dstubs.privateApiFile != nil { - fmt.Fprintln(w, apiFilePrefix+"PRIVATE_API_FILE := ", dstubs.privateApiFile.String()) - } - if dstubs.privateDexApiFile != nil { - fmt.Fprintln(w, apiFilePrefix+"PRIVATE_DEX_API_FILE := ", dstubs.privateDexApiFile.String()) - } - if dstubs.removedApiFile != nil { - fmt.Fprintln(w, apiFilePrefix+"REMOVED_API_FILE := ", dstubs.removedApiFile.String()) - } - if dstubs.removedDexApiFile != nil { - fmt.Fprintln(w, apiFilePrefix+"REMOVED_DEX_API_FILE := ", dstubs.removedDexApiFile.String()) - } - if dstubs.exactApiFile != nil { - fmt.Fprintln(w, apiFilePrefix+"EXACT_API_FILE := ", dstubs.exactApiFile.String()) - } }, }, - } + }} } -func androidMkWriteTestData(data android.Paths, ret *android.AndroidMkData) { +func (a *AndroidAppImport) AndroidMkEntries() []android.AndroidMkEntries { + return []android.AndroidMkEntries{android.AndroidMkEntries{ + Class: "APPS", + OutputFile: android.OptionalPathForPath(a.outputFile), + Include: "$(BUILD_SYSTEM)/soong_app_prebuilt.mk", + ExtraEntries: []android.AndroidMkExtraEntriesFunc{ + func(entries *android.AndroidMkEntries) { + entries.SetBoolIfTrue("LOCAL_PRIVILEGED_MODULE", a.Privileged()) + entries.SetString("LOCAL_CERTIFICATE", a.certificate.AndroidMkString()) + entries.AddStrings("LOCAL_OVERRIDES_PACKAGES", a.properties.Overrides...) + if len(a.dexpreopter.builtInstalled) > 0 { + entries.SetString("LOCAL_SOONG_BUILT_INSTALLED", a.dexpreopter.builtInstalled) + } + entries.AddStrings("LOCAL_INSTALLED_MODULE_STEM", a.installPath.Rel()) + }, + }, + }} +} + +func (a *AndroidTestImport) AndroidMkEntries() []android.AndroidMkEntries { + entriesList := a.AndroidAppImport.AndroidMkEntries() + entries := &entriesList[0] + entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) { + testSuiteComponent(entries, a.testProperties.Test_suites) + androidMkWriteTestData(a.data, entries) + }) + return entriesList +} + +func androidMkWriteTestData(data android.Paths, entries *android.AndroidMkEntries) { var testFiles []string for _, d := range data { testFiles = append(testFiles, d.String()+":"+d.Rel()) } - if len(testFiles) > 0 { - ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) { - fmt.Fprintln(w, "LOCAL_COMPATIBILITY_SUPPORT_FILES := "+strings.Join(testFiles, " ")) - }) - } + entries.AddStrings("LOCAL_COMPATIBILITY_SUPPORT_FILES", testFiles...) } -func (apkSet *AndroidAppSet) AndroidMk() android.AndroidMkData { - return android.AndroidMkData{ - Class: "APPS", - OutputFile: android.OptionalPathForPath(apkSet.packedOutput), - Include: "$(BUILD_SYSTEM)/soong_android_app_set.mk", - Extra: []android.AndroidMkExtraFunc{ - func(w io.Writer, outputFile android.Path) { - if apkSet.Privileged() { - fmt.Fprintln(w, "LOCAL_PRIVILEGED_MODULE := true") - } - fmt.Fprintln(w, "LOCAL_APK_SET_MASTER_FILE := ", apkSet.masterFile) - fmt.Fprintln(w, "LOCAL_APKCERTS_FILE := ", apkSet.apkcertsFile) - fmt.Fprintln(w, "LOCAL_OVERRIDES_PACKAGES :=", strings.Join(apkSet.properties.Overrides, " ")) +func (r *RuntimeResourceOverlay) AndroidMkEntries() []android.AndroidMkEntries { + return []android.AndroidMkEntries{android.AndroidMkEntries{ + Class: "ETC", + OutputFile: android.OptionalPathForPath(r.outputFile), + Include: "$(BUILD_SYSTEM)/soong_app_prebuilt.mk", + ExtraEntries: []android.AndroidMkExtraEntriesFunc{ + func(entries *android.AndroidMkEntries) { + entries.SetString("LOCAL_CERTIFICATE", r.certificate.AndroidMkString()) + entries.SetPath("LOCAL_MODULE_PATH", r.installDir.ToMakePath()) + entries.AddStrings("LOCAL_OVERRIDES_PACKAGES", r.properties.Overrides...) + }, + }, + }} +} + +func (apkSet *AndroidAppSet) AndroidMkEntries() []android.AndroidMkEntries { + return []android.AndroidMkEntries{ + android.AndroidMkEntries{ + Class: "APPS", + OutputFile: android.OptionalPathForPath(apkSet.packedOutput), + Include: "$(BUILD_SYSTEM)/soong_android_app_set.mk", + ExtraEntries: []android.AndroidMkExtraEntriesFunc{ + func(entries *android.AndroidMkEntries) { + entries.SetBoolIfTrue("LOCAL_PRIVILEGED_MODULE", apkSet.Privileged()) + entries.SetString("LOCAL_APK_SET_MASTER_FILE", apkSet.masterFile) + entries.SetPath("LOCAL_APKCERTS_FILE", apkSet.apkcertsFile) + entries.AddStrings("LOCAL_OVERRIDES_PACKAGES", apkSet.properties.Overrides...) + }, }, }, } diff --git a/java/androidmk_test.go b/java/androidmk_test.go new file mode 100644 index 000000000..7daa6244f --- /dev/null +++ b/java/androidmk_test.go @@ -0,0 +1,171 @@ +// Copyright 2019 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 ( + "reflect" + "strings" + "testing" + + "android/soong/android" +) + +func TestRequired(t *testing.T) { + ctx, config := testJava(t, ` + java_library { + name: "foo", + srcs: ["a.java"], + required: ["libfoo"], + } + `) + + mod := ctx.ModuleForTests("foo", "android_common").Module() + entries := android.AndroidMkEntriesForTest(t, config, "", mod)[0] + + expected := []string{"libfoo"} + actual := entries.EntryMap["LOCAL_REQUIRED_MODULES"] + if !reflect.DeepEqual(expected, actual) { + t.Errorf("Unexpected required modules - expected: %q, actual: %q", expected, actual) + } +} + +func TestHostdex(t *testing.T) { + ctx, config := testJava(t, ` + java_library { + name: "foo", + srcs: ["a.java"], + hostdex: true, + } + `) + + mod := ctx.ModuleForTests("foo", "android_common").Module() + entriesList := android.AndroidMkEntriesForTest(t, config, "", mod) + if len(entriesList) != 2 { + t.Errorf("two entries are expected, but got %d", len(entriesList)) + } + + mainEntries := &entriesList[0] + expected := []string{"foo"} + actual := mainEntries.EntryMap["LOCAL_MODULE"] + if !reflect.DeepEqual(expected, actual) { + t.Errorf("Unexpected module name - expected: %q, actual: %q", expected, actual) + } + + subEntries := &entriesList[1] + expected = []string{"foo-hostdex"} + actual = subEntries.EntryMap["LOCAL_MODULE"] + if !reflect.DeepEqual(expected, actual) { + t.Errorf("Unexpected module name - expected: %q, actual: %q", expected, actual) + } +} + +func TestHostdexRequired(t *testing.T) { + ctx, config := testJava(t, ` + java_library { + name: "foo", + srcs: ["a.java"], + hostdex: true, + required: ["libfoo"], + } + `) + + mod := ctx.ModuleForTests("foo", "android_common").Module() + entriesList := android.AndroidMkEntriesForTest(t, config, "", mod) + if len(entriesList) != 2 { + t.Errorf("two entries are expected, but got %d", len(entriesList)) + } + + mainEntries := &entriesList[0] + expected := []string{"libfoo"} + actual := mainEntries.EntryMap["LOCAL_REQUIRED_MODULES"] + if !reflect.DeepEqual(expected, actual) { + t.Errorf("Unexpected required modules - expected: %q, actual: %q", expected, actual) + } + + subEntries := &entriesList[1] + expected = []string{"libfoo"} + actual = subEntries.EntryMap["LOCAL_REQUIRED_MODULES"] + if !reflect.DeepEqual(expected, actual) { + t.Errorf("Unexpected required modules - expected: %q, actual: %q", expected, actual) + } +} + +func TestHostdexSpecificRequired(t *testing.T) { + ctx, config := testJava(t, ` + java_library { + name: "foo", + srcs: ["a.java"], + hostdex: true, + target: { + hostdex: { + required: ["libfoo"], + }, + }, + } + `) + + mod := ctx.ModuleForTests("foo", "android_common").Module() + entriesList := android.AndroidMkEntriesForTest(t, config, "", mod) + if len(entriesList) != 2 { + t.Errorf("two entries are expected, but got %d", len(entriesList)) + } + + mainEntries := &entriesList[0] + if r, ok := mainEntries.EntryMap["LOCAL_REQUIRED_MODULES"]; ok { + t.Errorf("Unexpected required modules: %q", r) + } + + subEntries := &entriesList[1] + expected := []string{"libfoo"} + actual := subEntries.EntryMap["LOCAL_REQUIRED_MODULES"] + if !reflect.DeepEqual(expected, actual) { + t.Errorf("Unexpected required modules - expected: %q, actual: %q", expected, actual) + } +} + +func TestDistWithTag(t *testing.T) { + ctx, config := testJava(t, ` + java_library { + name: "foo_without_tag", + srcs: ["a.java"], + compile_dex: true, + dist: { + targets: ["hi"], + }, + } + java_library { + name: "foo_with_tag", + srcs: ["a.java"], + compile_dex: true, + dist: { + targets: ["hi"], + tag: ".jar", + }, + } + `) + + without_tag_entries := android.AndroidMkEntriesForTest(t, config, "", ctx.ModuleForTests("foo_without_tag", "android_common").Module()) + with_tag_entries := android.AndroidMkEntriesForTest(t, config, "", ctx.ModuleForTests("foo_with_tag", "android_common").Module()) + + if len(without_tag_entries) != 2 || len(with_tag_entries) != 2 { + t.Errorf("two mk entries per module expected, got %d and %d", len(without_tag_entries), len(with_tag_entries)) + } + if !with_tag_entries[0].DistFile.Valid() || !strings.Contains(with_tag_entries[0].DistFile.String(), "/javac/foo_with_tag.jar") { + t.Errorf("expected classes.jar DistFile, got %v", with_tag_entries[0].DistFile) + } + if without_tag_entries[0].DistFile.Valid() { + t.Errorf("did not expect explicit DistFile, got %v", without_tag_entries[0].DistFile) + } +} diff --git a/java/app.go b/java/app.go index 586b66d85..e75d8749f 100644..100755 --- a/java/app.go +++ b/java/app.go @@ -18,6 +18,7 @@ package java import ( "path/filepath" + "reflect" "sort" "strconv" "strings" @@ -30,18 +31,31 @@ import ( "android/soong/tradefed" ) +var supportedDpis = []string{"ldpi", "mdpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"} + func init() { - android.RegisterModuleType("android_app", AndroidAppFactory) - android.RegisterModuleType("android_test", AndroidTestFactory) - android.RegisterModuleType("android_test_helper_app", AndroidTestHelperAppFactory) - android.RegisterModuleType("android_app_certificate", AndroidAppCertificateFactory) - android.RegisterModuleType("override_android_app", OverrideAndroidAppModuleFactory) - android.RegisterModuleType("android_app_set", AndroidApkSetFactory) + RegisterAppBuildComponents(android.InitRegistrationContext) + + initAndroidAppImportVariantGroupTypes() +} + +func RegisterAppBuildComponents(ctx android.RegistrationContext) { + ctx.RegisterModuleType("android_app", AndroidAppFactory) + ctx.RegisterModuleType("android_test", AndroidTestFactory) + ctx.RegisterModuleType("android_test_helper_app", AndroidTestHelperAppFactory) + ctx.RegisterModuleType("android_app_certificate", AndroidAppCertificateFactory) + ctx.RegisterModuleType("override_android_app", OverrideAndroidAppModuleFactory) + ctx.RegisterModuleType("override_android_test", OverrideAndroidTestModuleFactory) + ctx.RegisterModuleType("override_runtime_resource_overlay", OverrideRuntimeResourceOverlayModuleFactory) + ctx.RegisterModuleType("android_app_import", AndroidAppImportFactory) + ctx.RegisterModuleType("android_test_import", AndroidTestImportFactory) + ctx.RegisterModuleType("runtime_resource_overlay", RuntimeResourceOverlayFactory) + ctx.RegisterModuleType("android_app_set", AndroidApkSetFactory) } type AndroidAppSetProperties struct { // APK Set path - Set string + Set *string // Specifies that this app should be installed to the priv-app directory, // where the system will grant it additional privileges not available to @@ -83,6 +97,18 @@ func (as *AndroidAppSet) Privileged() bool { return Bool(as.properties.Privileged) } +func (as *AndroidAppSet) OutputFile() android.Path { + return as.packedOutput +} + +func (as *AndroidAppSet) MasterFile() string { + return as.masterFile +} + +func (as *AndroidAppSet) APKCertsFile() android.Path { + return as.apkcertsFile +} + var TargetCpuAbi = map[string]string{ "arm": "ARMEABI_V7A", "arm64": "ARM64_V8A", @@ -91,28 +117,28 @@ var TargetCpuAbi = map[string]string{ } func SupportedAbis(ctx android.ModuleContext) []string { - abiName := func(archVar string, deviceArch string) string { + abiName := func(targetIdx int, deviceArch string) string { if abi, found := TargetCpuAbi[deviceArch]; found { return abi } - ctx.ModuleErrorf("Invalid %s: %s", archVar, deviceArch) + ctx.ModuleErrorf("Target %d has invalid Arch: %s", targetIdx, deviceArch) return "BAD_ABI" } - result := []string{abiName("TARGET_ARCH", ctx.DeviceConfig().DeviceArch())} - if s := ctx.DeviceConfig().DeviceSecondaryArch(); s != "" { - result = append(result, abiName("TARGET_2ND_ARCH", s)) + var result []string + for i, target := range ctx.Config().Targets[android.Android] { + result = append(result, abiName(i, target.Arch.ArchType.String())) } return result } func (as *AndroidAppSet) GenerateAndroidBuildActions(ctx android.ModuleContext) { - as.packedOutput = android.PathForModuleOut(ctx, "extracted.zip") + as.packedOutput = android.PathForModuleOut(ctx, ctx.ModuleName()+".zip") as.apkcertsFile = android.PathForModuleOut(ctx, "apkcerts.txt") // We are assuming here that the master file in the APK // set has `.apk` suffix. If it doesn't the build will fail. // APK sets containing APEX files are handled elsewhere. - as.masterFile = ctx.ModuleName() + ".apk" + as.masterFile = as.BaseModuleName() + ".apk" screenDensities := "all" if dpis := ctx.Config().ProductAAPTPrebuiltDPI(); len(dpis) > 0 { screenDensities = strings.ToUpper(strings.Join(dpis, ",")) @@ -131,36 +157,27 @@ func (as *AndroidAppSet) GenerateAndroidBuildActions(ctx android.ModuleContext) "allow-prereleased": strconv.FormatBool(proptools.Bool(as.properties.Prerelease)), "screen-densities": screenDensities, "sdk-version": ctx.Config().PlatformSdkVersion(), - "stem": ctx.ModuleName(), + "stem": as.BaseModuleName(), "apkcerts": as.apkcertsFile.String(), "partition": as.PartitionTag(ctx.DeviceConfig()), }, }) - // TODO(asmundak): add this (it's wrong now, will cause copying extracted.zip) - /* - var installDir android.InstallPath - if Bool(as.properties.Privileged) { - installDir = android.PathForModuleInstall(ctx, "priv-app", as.BaseModuleName()) - } else if ctx.InstallInTestcases() { - installDir = android.PathForModuleInstall(ctx, as.BaseModuleName(), ctx.DeviceConfig().DeviceArch()) - } else { - installDir = android.PathForModuleInstall(ctx, "app", as.BaseModuleName()) - } - ctx.InstallFile(installDir, as.masterFile", as.packedOutput) - */ } // android_app_set extracts a set of APKs based on the target device // configuration and installs this set as "split APKs". -// The set will always contain `base-master.apk` and every APK built -// to the target device. All density-specific APK will be included, too, -// unless PRODUCT_APPT_PREBUILT_DPI is defined (should contain comma-sepearated -// list of density names (LDPI, MDPI, HDPI, etc.) +// The extracted set always contains 'master' APK whose name is +// _module_name_.apk and every split APK matching target device. +// The extraction of the density-specific splits depends on +// PRODUCT_AAPT_PREBUILT_DPI variable. If present (its value should +// be a list density names: LDPI, MDPI, HDPI, etc.), only listed +// splits will be extracted. Otherwise all density-specific splits +// will be extracted. func AndroidApkSetFactory() android.Module { module := &AndroidAppSet{} module.AddProperties(&module.properties) InitJavaModule(module, android.DeviceSupported) - android.InitSingleSourcePrebuiltModule(module, &module.properties.Set) + android.InitSingleSourcePrebuiltModule(module, &module.properties, "Set") return module } @@ -193,10 +210,22 @@ type appProperties struct { // list of native libraries that will be provided in or alongside the resulting jar Jni_libs []string `android:"arch_variant"` + // if true, use JNI libraries that link against platform APIs even if this module sets + // sdk_version. + Jni_uses_platform_apis *bool + + // if true, use JNI libraries that link against SDK APIs even if this module does not set + // sdk_version. + Jni_uses_sdk_apis *bool + + // STL library to use for JNI libraries. + Stl *string `android:"arch_variant"` + // Store native libraries uncompressed in the APK and set the android:extractNativeLibs="false" manifest // flag so that they are used from inside the APK at runtime. Defaults to true for android_test modules unless - // sdk_version or min_sdk_version is set to a version that doesn't support it (<23), defaults to false for other - // module types where the native libraries are generally preinstalled outside the APK. + // sdk_version or min_sdk_version is set to a version that doesn't support it (<23), defaults to true for + // android_app modules that are embedded to APEXes, defaults to false for other module types where the native + // libraries are generally preinstalled outside the APK. Use_embedded_native_libs *bool // Store dex files uncompressed in the APK and set the android:useEmbeddedDex="true" manifest attribute so that @@ -211,6 +240,17 @@ type appProperties struct { // If set, find and merge all NOTICE files that this module and its dependencies have and store // it in the APK as an asset. Embed_notices *bool + + // cc.Coverage related properties + PreventInstall bool `blueprint:"mutated"` + HideFromMake bool `blueprint:"mutated"` + IsCoverageVariant bool `blueprint:"mutated"` + + // Whether this app is considered mainline updatable or not. When set to true, this will enforce + // additional rules to make sure an app can safely be updated. Default is false. + // Prefer using other specific properties if build behaviour must be changed; avoid using this + // flag for anything but neverallow rules (unless the behaviour change is invisible to owners). + Updatable *bool } // android_app properties that can be overridden by override_android_app @@ -219,8 +259,23 @@ type overridableAppProperties struct { // or an android_app_certificate module name in the form ":module". Certificate *string + // Name of the signing certificate lineage file. + Lineage *string + + // the package name of this app. The package name in the manifest file is used if one was not given. + Package_name *string + + // the logging parent of this app. + Logging_parent *string +} + +// runtime_resource_overlay properties that can be overridden by override_runtime_resource_overlay +type OverridableRuntimeResourceOverlayProperties struct { // the package name of this app. The package name in the manifest file is used if one was not given. Package_name *string + + // the target package name of this overlay app. The target package name in the manifest file is used if one was not given. + Target_package_name *string } type AndroidApp struct { @@ -228,20 +283,39 @@ type AndroidApp struct { aapt android.OverridableModuleBase + usesLibrary usesLibrary + certificate Certificate appProperties appProperties overridableAppProperties overridableAppProperties - installJniLibs []jniLib + jniLibs []jniLib + installPathForJNISymbols android.Path + embeddedJniLibs bool + jniCoverageOutputs android.Paths bundleFile android.Path // the install APK name is normally the same as the module name, but can be overridden with PRODUCT_PACKAGE_NAME_OVERRIDES. installApkName string + installDir android.InstallPath + + onDeviceDir string + additionalAaptFlags []string + + noticeOutputs android.NoticeOutputs + + overriddenManifestPackageName string + + android.ApexBundleDepsInfo +} + +func (a *AndroidApp) IsInstallable() bool { + return Bool(a.properties.Installable) } func (a *AndroidApp) ExportedProguardFlagFiles() android.Paths { @@ -252,30 +326,78 @@ func (a *AndroidApp) ExportedStaticPackages() android.Paths { return nil } +func (a *AndroidApp) OutputFile() android.Path { + return a.outputFile +} + +func (a *AndroidApp) Certificate() Certificate { + return a.certificate +} + +func (a *AndroidApp) JniCoverageOutputs() android.Paths { + return a.jniCoverageOutputs +} + var _ AndroidLibraryDependency = (*AndroidApp)(nil) type Certificate struct { - Pem, Key android.Path + Pem, Key android.Path + presigned bool +} + +var PresignedCertificate = Certificate{presigned: true} + +func (c Certificate) AndroidMkString() string { + if c.presigned { + return "PRESIGNED" + } else { + return c.Pem.String() + } } func (a *AndroidApp) DepsMutator(ctx android.BottomUpMutatorContext) { a.Module.deps(ctx) - if !Bool(a.properties.No_framework_libs) && !Bool(a.properties.No_standard_libs) { - a.aapt.deps(ctx, sdkContext(a)) + if String(a.appProperties.Stl) == "c++_shared" && !a.sdkVersion().specified() { + ctx.PropertyErrorf("stl", "sdk_version must be set in order to use c++_shared") + } + + sdkDep := decodeSdkDep(ctx, sdkContext(a)) + if sdkDep.hasFrameworkLibs() { + a.aapt.deps(ctx, sdkDep) + } + + usesSDK := a.sdkVersion().specified() && a.sdkVersion().kind != sdkCorePlatform + + if usesSDK && Bool(a.appProperties.Jni_uses_sdk_apis) { + ctx.PropertyErrorf("jni_uses_sdk_apis", + "can only be set for modules that do not set sdk_version") + } else if !usesSDK && Bool(a.appProperties.Jni_uses_platform_apis) { + ctx.PropertyErrorf("jni_uses_platform_apis", + "can only be set for modules that set sdk_version") } + tag := &jniDependencyTag{} for _, jniTarget := range ctx.MultiTargets() { - variation := []blueprint.Variation{ - {Mutator: "arch", Variation: jniTarget.String()}, - {Mutator: "link", Variation: "shared"}, - } - tag := &jniDependencyTag{ - target: jniTarget, + variation := append(jniTarget.Variations(), + blueprint.Variation{Mutator: "link", Variation: "shared"}) + + // If the app builds against an Android SDK use the SDK variant of JNI dependencies + // unless jni_uses_platform_apis is set. + // Don't require the SDK variant for apps that are shipped on vendor, etc., as they already + // have stable APIs through the VNDK. + if (usesSDK && !a.RequiresStableAPIs(ctx) && + !Bool(a.appProperties.Jni_uses_platform_apis)) || + Bool(a.appProperties.Jni_uses_sdk_apis) { + variation = append(variation, blueprint.Variation{Mutator: "sdk", Variation: "sdk"}) } ctx.AddFarVariationDependencies(variation, tag, a.appProperties.Jni_libs...) } + a.usesLibrary.deps(ctx, sdkDep.hasFrameworkLibs()) +} + +func (a *AndroidApp) OverridablePropertiesDepsMutator(ctx android.BottomUpMutatorContext) { cert := android.SrcIsModule(a.getCertString(ctx)) if cert != "" { ctx.AddDependency(ctx.Module(), certificateTag, cert) @@ -292,21 +414,70 @@ func (a *AndroidApp) DepsMutator(ctx android.BottomUpMutatorContext) { } } +func (a *AndroidTestHelperApp) GenerateAndroidBuildActions(ctx android.ModuleContext) { + a.generateAndroidBuildActions(ctx) +} + func (a *AndroidApp) GenerateAndroidBuildActions(ctx android.ModuleContext) { - a.aapt.uncompressedJNI = a.shouldUncompressJNI(ctx) - a.aapt.useEmbeddedDex = Bool(a.appProperties.Use_embedded_dex) + a.checkAppSdkVersions(ctx) a.generateAndroidBuildActions(ctx) } -// shouldUncompressJNI returns true if the native libraries should be stored in the APK uncompressed and the +func (a *AndroidApp) checkAppSdkVersions(ctx android.ModuleContext) { + if a.Updatable() { + if !a.sdkVersion().stable() { + ctx.PropertyErrorf("sdk_version", "Updatable apps must use stable SDKs, found %v", a.sdkVersion()) + } + if String(a.deviceProperties.Min_sdk_version) == "" { + ctx.PropertyErrorf("updatable", "updatable apps must set min_sdk_version.") + } + if minSdkVersion, err := a.minSdkVersion().effectiveVersion(ctx); err == nil { + a.checkJniLibsSdkVersion(ctx, minSdkVersion) + } else { + ctx.PropertyErrorf("min_sdk_version", "%s", err.Error()) + } + } + + a.checkPlatformAPI(ctx) + a.checkSdkVersions(ctx) +} + +// If an updatable APK sets min_sdk_version, min_sdk_vesion of JNI libs should match with it. +// This check is enforced for "updatable" APKs (including APK-in-APEX). +// b/155209650: until min_sdk_version is properly supported, use sdk_version instead. +// because, sdk_version is overridden by min_sdk_version (if set as smaller) +// and linkType is checked with dependencies so we can be sure that the whole dependency tree +// will meet the requirements. +func (a *AndroidApp) checkJniLibsSdkVersion(ctx android.ModuleContext, minSdkVersion sdkVersion) { + // It's enough to check direct JNI deps' sdk_version because all transitive deps from JNI deps are checked in cc.checkLinkType() + ctx.VisitDirectDeps(func(m android.Module) { + if !IsJniDepTag(ctx.OtherModuleDependencyTag(m)) { + return + } + dep, _ := m.(*cc.Module) + // The domain of cc.sdk_version is "current" and <number> + // We can rely on sdkSpec to convert it to <number> so that "current" is handled + // properly regardless of sdk finalization. + jniSdkVersion, err := sdkSpecFrom(dep.SdkVersion()).effectiveVersion(ctx) + if err != nil || minSdkVersion < jniSdkVersion { + ctx.OtherModuleErrorf(dep, "sdk_version(%v) is higher than min_sdk_version(%v) of the containing android_app(%v)", + dep.SdkVersion(), minSdkVersion, ctx.ModuleName()) + return + } + + }) +} + +// Returns true if the native libraries should be stored in the APK uncompressed and the // extractNativeLibs application flag should be set to false in the manifest. -func (a *AndroidApp) shouldUncompressJNI(ctx android.ModuleContext) bool { - minSdkVersion, err := sdkVersionToNumber(ctx, a.minSdkVersion()) +func (a *AndroidApp) useEmbeddedNativeLibs(ctx android.ModuleContext) bool { + minSdkVersion, err := a.minSdkVersion().effectiveVersion(ctx) if err != nil { ctx.PropertyErrorf("min_sdk_version", "invalid value %q: %s", a.minSdkVersion(), err) } - return minSdkVersion >= 23 && Bool(a.appProperties.Use_embedded_native_libs) + return (minSdkVersion >= 23 && Bool(a.appProperties.Use_embedded_native_libs)) || + !a.IsForPlatform() } // Returns whether this module should have the dex file stored uncompressed in the APK. @@ -315,11 +486,9 @@ func (a *AndroidApp) shouldUncompressDex(ctx android.ModuleContext) bool { return true } - // Uncompress dex in APKs of privileged apps, and modules used by privileged apps - // (even for unbundled builds, they may be preinstalled as prebuilts). - if ctx.Config().UncompressPrivAppDex() && - (Bool(a.appProperties.Privileged) || - inList(ctx.ModuleName(), ctx.Config().ModulesLoadedByPrivilegedModules())) { + // Uncompress dex in APKs of privileged apps (even for unbundled builds, they may + // be preinstalled as prebuilts). + if ctx.Config().UncompressPrivAppDex() && a.Privileged() { return true } @@ -327,27 +496,28 @@ func (a *AndroidApp) shouldUncompressDex(ctx android.ModuleContext) bool { return false } - // Uncompress if the dex files is preopted on /system. - if !a.dexpreopter.dexpreoptDisabled(ctx) && (ctx.Host() || !odexOnSystemOther(ctx, a.dexpreopter.installPath)) { - return true - } + return shouldUncompressDex(ctx, &a.dexpreopter) +} + +func (a *AndroidApp) shouldEmbedJnis(ctx android.BaseModuleContext) bool { + return ctx.Config().UnbundledBuild() || Bool(a.appProperties.Use_embedded_native_libs) || + !a.IsForPlatform() || a.appProperties.AlwaysPackageNativeLibs +} - return false +func (a *AndroidApp) OverriddenManifestPackageName() string { + return a.overriddenManifestPackageName } func (a *AndroidApp) aaptBuildActions(ctx android.ModuleContext) { a.aapt.usesNonSdkApis = Bool(a.Module.deviceProperties.Platform_apis) + // Ask manifest_fixer to add or update the application element indicating this app has no code. + a.aapt.hasNoCode = !a.hasCode(ctx) + aaptLinkFlags := []string{} // Add TARGET_AAPT_CHARACTERISTICS values to AAPT link flags if they exist and --product flags were not provided. - hasProduct := false - for _, f := range a.aaptProperties.Aaptflags { - if strings.HasPrefix(f, "--product") { - hasProduct = true - break - } - } + hasProduct := android.PrefixInList(a.aaptProperties.Aaptflags, "--product") if !hasProduct && len(ctx.Config().ProductAAPTCharacteristics()) > 0 { aaptLinkFlags = append(aaptLinkFlags, "--product", ctx.Config().ProductAAPTCharacteristics()) } @@ -371,12 +541,14 @@ func (a *AndroidApp) aaptBuildActions(ctx android.ModuleContext) { manifestPackageName = *a.overridableAppProperties.Package_name } aaptLinkFlags = append(aaptLinkFlags, "--rename-manifest-package "+manifestPackageName) + a.overriddenManifestPackageName = manifestPackageName } aaptLinkFlags = append(aaptLinkFlags, a.additionalAaptFlags...) a.aapt.splitNames = a.appProperties.Package_splits - + a.aapt.sdkLibraries = a.exportedSdkLibs + a.aapt.LoggingParent = String(a.overridableAppProperties.Logging_parent) a.aapt.buildActions(ctx, sdkContext(a), aaptLinkFlags...) // apps manifests are handled by aapt, don't let Module see them @@ -397,21 +569,32 @@ func (a *AndroidApp) proguardBuildActions(ctx android.ModuleContext) { a.Module.extraProguardFlagFiles = append(a.Module.extraProguardFlagFiles, a.proguardOptionsFile) } -func (a *AndroidApp) dexBuildActions(ctx android.ModuleContext) android.Path { - +func (a *AndroidApp) installPath(ctx android.ModuleContext) android.InstallPath { var installDir string if ctx.ModuleName() == "framework-res" { // framework-res.apk is installed as system/framework/framework-res.apk installDir = "framework" - } else if Bool(a.appProperties.Privileged) { + } else if a.Privileged() { installDir = filepath.Join("priv-app", a.installApkName) } else { installDir = filepath.Join("app", a.installApkName) } - a.dexpreopter.installPath = android.PathForModuleInstall(ctx, installDir, a.installApkName+".apk") - a.dexpreopter.isInstallable = Bool(a.properties.Installable) - a.dexpreopter.uncompressedDex = a.shouldUncompressDex(ctx) - a.deviceProperties.UncompressDex = a.dexpreopter.uncompressedDex + + return android.PathForModuleInstall(ctx, installDir, a.installApkName+".apk") +} + +func (a *AndroidApp) dexBuildActions(ctx android.ModuleContext) android.Path { + a.dexpreopter.installPath = a.installPath(ctx) + if a.deviceProperties.Uncompress_dex == nil { + // If the value was not force-set by the user, use reasonable default based on the module. + a.deviceProperties.Uncompress_dex = proptools.BoolPtr(a.shouldUncompressDex(ctx)) + } + a.dexpreopter.uncompressedDex = *a.deviceProperties.Uncompress_dex + a.dexpreopter.enforceUsesLibs = a.usesLibrary.enforceUsesLibraries() + a.dexpreopter.usesLibs = a.usesLibrary.usesLibraryProperties.Uses_libs + a.dexpreopter.optionalUsesLibs = a.usesLibrary.presentOptionalUsesLibs(ctx) + a.dexpreopter.libraryPaths = a.usesLibrary.usesLibraryPaths(ctx) + a.dexpreopter.manifestFile = a.mergedManifestFile if ctx.ModuleName() != "framework-res" { a.Module.compile(ctx, a.aaptSrcJar) @@ -423,77 +606,64 @@ func (a *AndroidApp) dexBuildActions(ctx android.ModuleContext) android.Path { func (a *AndroidApp) jniBuildActions(jniLibs []jniLib, ctx android.ModuleContext) android.WritablePath { var jniJarFile android.WritablePath if len(jniLibs) > 0 { - embedJni := ctx.Config().UnbundledBuild() || Bool(a.appProperties.Use_embedded_native_libs) || - a.appProperties.AlwaysPackageNativeLibs - if embedJni { + a.jniLibs = jniLibs + if a.shouldEmbedJnis(ctx) { jniJarFile = android.PathForModuleOut(ctx, "jnilibs.zip") - TransformJniLibsToJar(ctx, jniJarFile, jniLibs, a.shouldUncompressJNI(ctx)) - } else { - a.installJniLibs = jniLibs + a.installPathForJNISymbols = a.installPath(ctx).ToMakePath() + TransformJniLibsToJar(ctx, jniJarFile, jniLibs, a.useEmbeddedNativeLibs(ctx)) + for _, jni := range jniLibs { + if jni.coverageFile.Valid() { + // Only collect coverage for the first target arch if this is a multilib target. + // TODO(jungjw): Ideally, we want to collect both reports, but that would cause coverage + // data file path collisions since the current coverage file path format doesn't contain + // arch-related strings. This is fine for now though; the code coverage team doesn't use + // multi-arch targets such as test_suite_* for coverage collections yet. + // + // Work with the team to come up with a new format that handles multilib modules properly + // and change this. + if len(ctx.Config().Targets[android.Android]) == 1 || + ctx.Config().Targets[android.Android][0].Arch.ArchType == jni.target.Arch.ArchType { + a.jniCoverageOutputs = append(a.jniCoverageOutputs, jni.coverageFile.Path()) + } + } + } + a.embeddedJniLibs = true } } return jniJarFile } -func (a *AndroidApp) certificateBuildActions(certificateDeps []Certificate, ctx android.ModuleContext) []Certificate { - cert := a.getCertString(ctx) - certModule := android.SrcIsModule(cert) - if certModule != "" { - a.certificate = certificateDeps[0] - certificateDeps = certificateDeps[1:] - } else if cert != "" { - defaultDir := ctx.Config().DefaultAppCertificateDir(ctx) - a.certificate = Certificate{ - defaultDir.Join(ctx, cert+".x509.pem"), - defaultDir.Join(ctx, cert+".pk8"), +func (a *AndroidApp) JNISymbolsInstalls(installPath string) android.RuleBuilderInstalls { + var jniSymbols android.RuleBuilderInstalls + for _, jniLib := range a.jniLibs { + if jniLib.unstrippedFile != nil { + jniSymbols = append(jniSymbols, android.RuleBuilderInstall{ + From: jniLib.unstrippedFile, + To: filepath.Join(installPath, targetToJniDir(jniLib.target), jniLib.unstrippedFile.Base()), + }) } - } else { - pem, key := ctx.Config().DefaultAppCertificate(ctx) - a.certificate = Certificate{pem, key} } - - if !a.Module.Platform() { - certPath := a.certificate.Pem.String() - systemCertPath := ctx.Config().DefaultAppCertificateDir(ctx).String() - if strings.HasPrefix(certPath, systemCertPath) { - enforceSystemCert := ctx.Config().EnforceSystemCertificate() - whitelist := ctx.Config().EnforceSystemCertificateWhitelist() - - if enforceSystemCert && !inList(a.Module.Name(), whitelist) { - ctx.PropertyErrorf("certificate", "The module in product partition cannot be signed with certificate in system.") - } - } - } - - return append([]Certificate{a.certificate}, certificateDeps...) + return jniSymbols } -func (a *AndroidApp) noticeBuildActions(ctx android.ModuleContext, installDir android.OutputPath) android.OptionalPath { - if !Bool(a.appProperties.Embed_notices) && !ctx.Config().IsEnvTrue("ALWAYS_EMBED_NOTICES") { - return android.OptionalPath{} - } - +func (a *AndroidApp) noticeBuildActions(ctx android.ModuleContext) { // Collect NOTICE files from all dependencies. seenModules := make(map[android.Module]bool) noticePathSet := make(map[android.Path]bool) - ctx.WalkDepsBlueprint(func(child blueprint.Module, parent blueprint.Module) bool { - if _, ok := child.(android.Module); !ok { - return false - } - module := child.(android.Module) + ctx.WalkDeps(func(child android.Module, parent android.Module) bool { // Have we already seen this? - if _, ok := seenModules[module]; ok { + if _, ok := seenModules[child]; ok { return false } - seenModules[module] = true + seenModules[child] = true // Skip host modules. - if module.Target().Os.Class == android.Host || module.Target().Os.Class == android.HostCross { + if child.Target().Os.Class == android.Host || child.Target().Os.Class == android.HostCross { return false } - path := module.NoticeFile() + path := child.(android.Module).NoticeFile() if path.Valid() { noticePathSet[path.Path()] = true } @@ -506,7 +676,7 @@ func (a *AndroidApp) noticeBuildActions(ctx android.ModuleContext, installDir an } if len(noticePathSet) == 0 { - return android.OptionalPath{} + return } var noticePaths []android.Path for path := range noticePathSet { @@ -515,54 +685,132 @@ func (a *AndroidApp) noticeBuildActions(ctx android.ModuleContext, installDir an sort.Slice(noticePaths, func(i, j int) bool { return noticePaths[i].String() < noticePaths[j].String() }) - noticeFile := android.BuildNoticeOutput(ctx, installDir, a.installApkName+".apk", noticePaths) - return android.OptionalPathForPath(noticeFile) + a.noticeOutputs = android.BuildNoticeOutput(ctx, a.installDir, a.installApkName+".apk", noticePaths) +} + +// Reads and prepends a main cert from the default cert dir if it hasn't been set already, i.e. it +// isn't a cert module reference. Also checks and enforces system cert restriction if applicable. +func processMainCert(m android.ModuleBase, certPropValue string, certificates []Certificate, ctx android.ModuleContext) []Certificate { + if android.SrcIsModule(certPropValue) == "" { + var mainCert Certificate + if certPropValue != "" { + defaultDir := ctx.Config().DefaultAppCertificateDir(ctx) + mainCert = Certificate{ + Pem: defaultDir.Join(ctx, certPropValue+".x509.pem"), + Key: defaultDir.Join(ctx, certPropValue+".pk8"), + } + } else { + pem, key := ctx.Config().DefaultAppCertificate(ctx) + mainCert = Certificate{ + Pem: pem, + Key: key, + } + } + certificates = append([]Certificate{mainCert}, certificates...) + } + + if !m.Platform() { + certPath := certificates[0].Pem.String() + systemCertPath := ctx.Config().DefaultAppCertificateDir(ctx).String() + if strings.HasPrefix(certPath, systemCertPath) { + enforceSystemCert := ctx.Config().EnforceSystemCertificate() + allowed := ctx.Config().EnforceSystemCertificateAllowList() + + if enforceSystemCert && !inList(m.Name(), allowed) { + ctx.PropertyErrorf("certificate", "The module in product partition cannot be signed with certificate in system.") + } + } + } + + return certificates +} + +func (a *AndroidApp) InstallApkName() string { + return a.installApkName } func (a *AndroidApp) generateAndroidBuildActions(ctx android.ModuleContext) { + var apkDeps android.Paths + + a.aapt.useEmbeddedNativeLibs = a.useEmbeddedNativeLibs(ctx) + a.aapt.useEmbeddedDex = Bool(a.appProperties.Use_embedded_dex) + // Check if the install APK name needs to be overridden. a.installApkName = ctx.DeviceConfig().OverridePackageNameFor(a.Name()) - var installDir android.OutputPath if ctx.ModuleName() == "framework-res" { // framework-res.apk is installed as system/framework/framework-res.apk - installDir = android.PathForModuleInstall(ctx, "framework") - } else if Bool(a.appProperties.Privileged) { - installDir = android.PathForModuleInstall(ctx, "priv-app", a.installApkName) + a.installDir = android.PathForModuleInstall(ctx, "framework") + } else if a.Privileged() { + a.installDir = android.PathForModuleInstall(ctx, "priv-app", a.installApkName) + } else if ctx.InstallInTestcases() { + a.installDir = android.PathForModuleInstall(ctx, a.installApkName, ctx.DeviceConfig().DeviceArch()) } else { - installDir = android.PathForModuleInstall(ctx, "app", a.installApkName) + a.installDir = android.PathForModuleInstall(ctx, "app", a.installApkName) } + a.onDeviceDir = android.InstallPathToOnDevicePath(ctx, a.installDir) - a.aapt.noticeFile = a.noticeBuildActions(ctx, installDir) + a.noticeBuildActions(ctx) + if Bool(a.appProperties.Embed_notices) || ctx.Config().IsEnvTrue("ALWAYS_EMBED_NOTICES") { + a.aapt.noticeFile = a.noticeOutputs.HtmlGzOutput + } // Process all building blocks, from AAPT to certificates. a.aaptBuildActions(ctx) + if a.usesLibrary.enforceUsesLibraries() { + manifestCheckFile := a.usesLibrary.verifyUsesLibrariesManifest(ctx, a.mergedManifestFile) + apkDeps = append(apkDeps, manifestCheckFile) + } + a.proguardBuildActions(ctx) + a.linter.mergedManifest = a.aapt.mergedManifestFile + a.linter.manifest = a.aapt.manifestPath + a.linter.resources = a.aapt.resourceFiles + a.linter.buildModuleReportZip = ctx.Config().UnbundledBuild() + dexJarFile := a.dexBuildActions(ctx) - jniLibs, certificateDeps := a.collectAppDeps(ctx) + jniLibs, certificateDeps := collectAppDeps(ctx, a, a.shouldEmbedJnis(ctx), !Bool(a.appProperties.Jni_uses_platform_apis)) jniJarFile := a.jniBuildActions(jniLibs, ctx) if ctx.Failed() { return } - certificates := a.certificateBuildActions(certificateDeps, ctx) + certificates := processMainCert(a.ModuleBase, a.getCertString(ctx), certificateDeps, ctx) + a.certificate = certificates[0] // Build a final signed app package. - // TODO(jungjw): Consider changing this to installApkName. - packageFile := android.PathForModuleOut(ctx, ctx.ModuleName()+".apk") - CreateAppPackage(ctx, packageFile, a.exportPackage, jniJarFile, dexJarFile, certificates) + packageFile := android.PathForModuleOut(ctx, a.installApkName+".apk") + v4SigningRequested := Bool(a.Module.deviceProperties.V4_signature) + var v4SignatureFile android.WritablePath = nil + if v4SigningRequested { + v4SignatureFile = android.PathForModuleOut(ctx, a.installApkName+".apk.idsig") + } + var lineageFile android.Path + if lineage := String(a.overridableAppProperties.Lineage); lineage != "" { + lineageFile = android.PathForModuleSrc(ctx, lineage) + } + CreateAndSignAppPackage(ctx, packageFile, a.exportPackage, jniJarFile, dexJarFile, certificates, apkDeps, v4SignatureFile, lineageFile) a.outputFile = packageFile + if v4SigningRequested { + a.extraOutputFiles = append(a.extraOutputFiles, v4SignatureFile) + } for _, split := range a.aapt.splits { // Sign the split APKs - packageFile := android.PathForModuleOut(ctx, ctx.ModuleName()+"_"+split.suffix+".apk") - CreateAppPackage(ctx, packageFile, split.path, nil, nil, certificates) + packageFile := android.PathForModuleOut(ctx, a.installApkName+"_"+split.suffix+".apk") + if v4SigningRequested { + v4SignatureFile = android.PathForModuleOut(ctx, a.installApkName+"_"+split.suffix+".apk.idsig") + } + CreateAndSignAppPackage(ctx, packageFile, split.path, nil, nil, certificates, apkDeps, v4SignatureFile, lineageFile) a.extraOutputFiles = append(a.extraOutputFiles, packageFile) + if v4SigningRequested { + a.extraOutputFiles = append(a.extraOutputFiles, v4SignatureFile) + } } // Build an app bundle. @@ -571,49 +819,137 @@ func (a *AndroidApp) generateAndroidBuildActions(ctx android.ModuleContext) { a.bundleFile = bundleFile // Install the app package. - ctx.InstallFile(installDir, a.installApkName+".apk", a.outputFile) - for _, split := range a.aapt.splits { - ctx.InstallFile(installDir, a.installApkName+"_"+split.suffix+".apk", split.path) + if (Bool(a.Module.properties.Installable) || ctx.Host()) && a.IsForPlatform() { + ctx.InstallFile(a.installDir, a.outputFile.Base(), a.outputFile) + for _, extra := range a.extraOutputFiles { + ctx.InstallFile(a.installDir, extra.Base(), extra) + } } + + a.buildAppDependencyInfo(ctx) } -func (a *AndroidApp) collectAppDeps(ctx android.ModuleContext) ([]jniLib, []Certificate) { +type appDepsInterface interface { + sdkVersion() sdkSpec + minSdkVersion() sdkSpec + RequiresStableAPIs(ctx android.BaseModuleContext) bool +} + +func collectAppDeps(ctx android.ModuleContext, app appDepsInterface, + shouldCollectRecursiveNativeDeps bool, + checkNativeSdkVersion bool) ([]jniLib, []Certificate) { + var jniLibs []jniLib var certificates []Certificate + seenModulePaths := make(map[string]bool) + + if checkNativeSdkVersion { + checkNativeSdkVersion = app.sdkVersion().specified() && + app.sdkVersion().kind != sdkCorePlatform && !app.RequiresStableAPIs(ctx) + } - ctx.VisitDirectDeps(func(module android.Module) { + ctx.WalkDeps(func(module android.Module, parent android.Module) bool { otherName := ctx.OtherModuleName(module) tag := ctx.OtherModuleDependencyTag(module) - if jniTag, ok := tag.(*jniDependencyTag); ok { + if IsJniDepTag(tag) || tag == cc.SharedDepTag { if dep, ok := module.(*cc.Module); ok { + if dep.IsNdk() || dep.IsStubs() { + return false + } + lib := dep.OutputFile() + path := lib.Path() + if seenModulePaths[path.String()] { + return false + } + seenModulePaths[path.String()] = true + + if checkNativeSdkVersion && dep.SdkVersion() == "" { + ctx.PropertyErrorf("jni_libs", "JNI dependency %q uses platform APIs, but this module does not", + otherName) + } + if lib.Valid() { jniLibs = append(jniLibs, jniLib{ - name: ctx.OtherModuleName(module), - path: lib.Path(), - target: jniTag.target, + name: ctx.OtherModuleName(module), + path: path, + target: module.Target(), + coverageFile: dep.CoverageOutputFile(), + unstrippedFile: dep.UnstrippedOutputFile(), }) } else { ctx.ModuleErrorf("dependency %q missing output file", otherName) } } else { ctx.ModuleErrorf("jni_libs dependency %q must be a cc library", otherName) - } - } else if tag == certificateTag { + + return shouldCollectRecursiveNativeDeps + } + + if tag == certificateTag { if dep, ok := module.(*AndroidAppCertificate); ok { certificates = append(certificates, dep.Certificate) } else { ctx.ModuleErrorf("certificate dependency %q must be an android_app_certificate module", otherName) } } + + return false }) return jniLibs, certificates } -func (a *AndroidApp) getCertString(ctx android.BaseContext) string { +func (a *AndroidApp) walkPayloadDeps(ctx android.ModuleContext, + do func(ctx android.ModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool)) { + + ctx.WalkDeps(func(child, parent android.Module) bool { + isExternal := !a.DepIsInSameApex(ctx, child) + if am, ok := child.(android.ApexModule); ok { + do(ctx, parent, am, isExternal) + } + return !isExternal + }) +} + +func (a *AndroidApp) buildAppDependencyInfo(ctx android.ModuleContext) { + if ctx.Host() { + return + } + + depsInfo := android.DepNameToDepInfoMap{} + a.walkPayloadDeps(ctx, func(ctx android.ModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) { + depName := to.Name() + if info, exist := depsInfo[depName]; exist { + info.From = append(info.From, from.Name()) + info.IsExternal = info.IsExternal && externalDep + depsInfo[depName] = info + } else { + toMinSdkVersion := "(no version)" + if m, ok := to.(interface{ MinSdkVersion() string }); ok { + if v := m.MinSdkVersion(); v != "" { + toMinSdkVersion = v + } + } + depsInfo[depName] = android.ApexModuleDepInfo{ + To: depName, + From: []string{from.Name()}, + IsExternal: externalDep, + MinSdkVersion: toMinSdkVersion, + } + } + }) + + a.ApexBundleDepsInfo.BuildDepsInfoLists(ctx, a.MinSdkVersion(), depsInfo) +} + +func (a *AndroidApp) Updatable() bool { + return Bool(a.appProperties.Updatable) || a.ApexModuleBase.Updatable() +} + +func (a *AndroidApp) getCertString(ctx android.BaseModuleContext) string { certificate, overridden := ctx.DeviceConfig().OverrideCertificateFor(ctx.ModuleName()) if overridden { return ":" + certificate @@ -621,6 +957,44 @@ func (a *AndroidApp) getCertString(ctx android.BaseContext) string { return String(a.overridableAppProperties.Certificate) } +func (a *AndroidApp) DepIsInSameApex(ctx android.BaseModuleContext, dep android.Module) bool { + if IsJniDepTag(ctx.OtherModuleDependencyTag(dep)) { + return true + } + return a.Library.DepIsInSameApex(ctx, dep) +} + +// For OutputFileProducer interface +func (a *AndroidApp) OutputFiles(tag string) (android.Paths, error) { + switch tag { + case ".aapt.srcjar": + return []android.Path{a.aaptSrcJar}, nil + } + return a.Library.OutputFiles(tag) +} + +func (a *AndroidApp) Privileged() bool { + return Bool(a.appProperties.Privileged) +} + +func (a *AndroidApp) IsNativeCoverageNeeded(ctx android.BaseModuleContext) bool { + return ctx.Device() && ctx.DeviceConfig().NativeCoverageEnabled() +} + +func (a *AndroidApp) PreventInstall() { + a.appProperties.PreventInstall = true +} + +func (a *AndroidApp) HideFromMake() { + a.appProperties.HideFromMake = true +} + +func (a *AndroidApp) MarkAsCoverageVariant(coverage bool) { + a.appProperties.IsCoverageVariant = coverage +} + +var _ cc.Coverage = (*AndroidApp)(nil) + // android_app compiles sources and Android resources into an Android application package `.apk` file. func AndroidAppFactory() android.Module { module := &AndroidApp{} @@ -631,14 +1005,12 @@ func AndroidAppFactory() android.Module { module.Module.properties.Instrument = true module.Module.properties.Installable = proptools.BoolPtr(true) + module.addHostAndDeviceProperties() module.AddProperties( - &module.Module.properties, - &module.Module.deviceProperties, - &module.Module.dexpreoptProperties, - &module.Module.protoProperties, &module.aaptProperties, &module.appProperties, - &module.overridableAppProperties) + &module.overridableAppProperties, + &module.usesLibrary.usesLibraryProperties) module.Prefer32(func(ctx android.BaseModuleContext, base *android.ModuleBase, class android.OsClass) bool { return class == android.Device && ctx.Config().DevicePrefer32BitApps() @@ -647,12 +1019,16 @@ func AndroidAppFactory() android.Module { android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon) android.InitDefaultableModule(module) android.InitOverridableModule(module, &module.appProperties.Overrides) + android.InitApexModule(module) return module } type appTestProperties struct { Instrumentation_for *string + + // if specified, the instrumentation target package name in the manifest is overwritten by it. + Instrumentation_target_package *string } type AndroidTest struct { @@ -666,9 +1042,17 @@ type AndroidTest struct { data android.Paths } +func (a *AndroidTest) InstallInTestcases() bool { + return true +} + func (a *AndroidTest) GenerateAndroidBuildActions(ctx android.ModuleContext) { - // Check if the instrumentation target package is overridden before generating build actions. - if a.appTestProperties.Instrumentation_for != nil { + var configs []tradefed.Config + if a.appTestProperties.Instrumentation_target_package != nil { + a.additionalAaptFlags = append(a.additionalAaptFlags, + "--rename-instrumentation-target-package "+*a.appTestProperties.Instrumentation_target_package) + } else if a.appTestProperties.Instrumentation_for != nil { + // Check if the instrumentation target package is overridden. manifestPackageName, overridden := ctx.DeviceConfig().OverrideManifestPackageNameFor(*a.appTestProperties.Instrumentation_for) if overridden { a.additionalAaptFlags = append(a.additionalAaptFlags, "--rename-instrumentation-target-package "+manifestPackageName) @@ -676,12 +1060,50 @@ func (a *AndroidTest) GenerateAndroidBuildActions(ctx android.ModuleContext) { } a.generateAndroidBuildActions(ctx) - a.testConfig = tradefed.AutoGenInstrumentationTestConfig(ctx, a.testProperties.Test_config, a.testProperties.Test_config_template, a.manifestPath, a.testProperties.Test_suites) + for _, module := range a.testProperties.Test_mainline_modules { + configs = append(configs, tradefed.Option{Name: "config-descriptor:metadata", Key: "mainline-param", Value: module}) + } + + testConfig := tradefed.AutoGenInstrumentationTestConfig(ctx, a.testProperties.Test_config, + a.testProperties.Test_config_template, a.manifestPath, a.testProperties.Test_suites, a.testProperties.Auto_gen_config, configs) + a.testConfig = a.FixTestConfig(ctx, testConfig) a.data = android.PathsForModuleSrc(ctx, a.testProperties.Data) } +func (a *AndroidTest) FixTestConfig(ctx android.ModuleContext, testConfig android.Path) android.Path { + if testConfig == nil { + return nil + } + + fixedConfig := android.PathForModuleOut(ctx, "test_config_fixer", "AndroidTest.xml") + rule := android.NewRuleBuilder() + command := rule.Command().BuiltTool(ctx, "test_config_fixer").Input(testConfig).Output(fixedConfig) + fixNeeded := false + + if ctx.ModuleName() != a.installApkName { + fixNeeded = true + command.FlagWithArg("--test-file-name ", a.installApkName+".apk") + } + + if a.overridableAppProperties.Package_name != nil { + fixNeeded = true + command.FlagWithInput("--manifest ", a.manifestPath). + FlagWithArg("--package-name ", *a.overridableAppProperties.Package_name) + } + + if fixNeeded { + rule.Build(pctx, ctx, "fix_test_config", "fix test config") + return fixedConfig + } + return testConfig +} + func (a *AndroidTest) DepsMutator(ctx android.BottomUpMutatorContext) { a.AndroidApp.DepsMutator(ctx) +} + +func (a *AndroidTest) OverridablePropertiesDepsMutator(ctx android.BottomUpMutatorContext) { + a.AndroidApp.OverridablePropertiesDepsMutator(ctx) if a.appTestProperties.Instrumentation_for != nil { // The android_app dependency listed in instrumentation_for needs to be added to the classpath for javac, // but not added to the aapt2 link includes like a normal android_app or android_library dependency, so @@ -702,20 +1124,20 @@ func AndroidTestFactory() android.Module { module.appProperties.Use_embedded_native_libs = proptools.BoolPtr(true) module.appProperties.AlwaysPackageNativeLibs = true module.Module.dexpreopter.isTest = true + module.Module.linter.test = true + module.addHostAndDeviceProperties() module.AddProperties( - &module.Module.properties, - &module.Module.deviceProperties, - &module.Module.dexpreoptProperties, - &module.Module.protoProperties, &module.aaptProperties, &module.appProperties, &module.appTestProperties, &module.overridableAppProperties, + &module.usesLibrary.usesLibraryProperties, &module.testProperties) android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon) android.InitDefaultableModule(module) + android.InitOverridableModule(module, &module.appProperties.Overrides) return module } @@ -723,6 +1145,11 @@ type appTestHelperAppProperties struct { // list of compatibility suites (for example "cts", "vts") that the module should be // installed into. Test_suites []string `android:"arch_variant"` + + // Flag to indicate whether or not to create test config automatically. If AndroidTest.xml + // doesn't exist next to the Android.bp, this attribute doesn't need to be set to true + // explicitly. + Auto_gen_config *bool } type AndroidTestHelperApp struct { @@ -731,6 +1158,10 @@ type AndroidTestHelperApp struct { appTestHelperAppProperties appTestHelperAppProperties } +func (a *AndroidTestHelperApp) InstallInTestcases() bool { + return true +} + // android_test_helper_app compiles sources and Android resources into an Android application package `.apk` file that // will be used by tests, but does not produce an `AndroidTest.xml` file so the module will not be run directly as a // test. @@ -743,19 +1174,19 @@ func AndroidTestHelperAppFactory() android.Module { module.appProperties.Use_embedded_native_libs = proptools.BoolPtr(true) module.appProperties.AlwaysPackageNativeLibs = true module.Module.dexpreopter.isTest = true + module.Module.linter.test = true + module.addHostAndDeviceProperties() module.AddProperties( - &module.Module.properties, - &module.Module.deviceProperties, - &module.Module.dexpreoptProperties, - &module.Module.protoProperties, &module.aaptProperties, &module.appProperties, &module.appTestHelperAppProperties, - &module.overridableAppProperties) + &module.overridableAppProperties, + &module.usesLibrary.usesLibraryProperties) android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon) android.InitDefaultableModule(module) + android.InitApexModule(module) return module } @@ -782,8 +1213,8 @@ func AndroidAppCertificateFactory() android.Module { func (c *AndroidAppCertificate) GenerateAndroidBuildActions(ctx android.ModuleContext) { cert := String(c.properties.Certificate) c.Certificate = Certificate{ - android.PathForModuleSrc(ctx, cert+".x509.pem"), - android.PathForModuleSrc(ctx, cert+".pk8"), + Pem: android.PathForModuleSrc(ctx, cert+".x509.pem"), + Key: android.PathForModuleSrc(ctx, cert+".pk8"), } } @@ -792,7 +1223,7 @@ type OverrideAndroidApp struct { android.OverrideModuleBase } -func (i *OverrideAndroidApp) GenerateAndroidBuildActions(ctx android.ModuleContext) { +func (i *OverrideAndroidApp) GenerateAndroidBuildActions(_ android.ModuleContext) { // All the overrides happen in the base module. // TODO(jungjw): Check the base module type. } @@ -803,7 +1234,723 @@ func OverrideAndroidAppModuleFactory() android.Module { m := &OverrideAndroidApp{} m.AddProperties(&overridableAppProperties{}) - android.InitAndroidModule(m) + android.InitAndroidMultiTargetsArchModule(m, android.DeviceSupported, android.MultilibCommon) android.InitOverrideModule(m) return m } + +type OverrideAndroidTest struct { + android.ModuleBase + android.OverrideModuleBase +} + +func (i *OverrideAndroidTest) GenerateAndroidBuildActions(_ android.ModuleContext) { + // All the overrides happen in the base module. + // TODO(jungjw): Check the base module type. +} + +// override_android_test is used to create an android_app module based on another android_test by overriding +// some of its properties. +func OverrideAndroidTestModuleFactory() android.Module { + m := &OverrideAndroidTest{} + m.AddProperties(&overridableAppProperties{}) + m.AddProperties(&appTestProperties{}) + + android.InitAndroidMultiTargetsArchModule(m, android.DeviceSupported, android.MultilibCommon) + android.InitOverrideModule(m) + return m +} + +type OverrideRuntimeResourceOverlay struct { + android.ModuleBase + android.OverrideModuleBase +} + +func (i *OverrideRuntimeResourceOverlay) GenerateAndroidBuildActions(_ android.ModuleContext) { + // All the overrides happen in the base module. + // TODO(jungjw): Check the base module type. +} + +// override_runtime_resource_overlay is used to create a module based on another +// runtime_resource_overlay module by overriding some of its properties. +func OverrideRuntimeResourceOverlayModuleFactory() android.Module { + m := &OverrideRuntimeResourceOverlay{} + m.AddProperties(&OverridableRuntimeResourceOverlayProperties{}) + + android.InitAndroidMultiTargetsArchModule(m, android.DeviceSupported, android.MultilibCommon) + android.InitOverrideModule(m) + return m +} + +type AndroidAppImport struct { + android.ModuleBase + android.DefaultableModuleBase + prebuilt android.Prebuilt + + properties AndroidAppImportProperties + dpiVariants interface{} + archVariants interface{} + + outputFile android.Path + certificate Certificate + + dexpreopter + + usesLibrary usesLibrary + + preprocessed bool + + installPath android.InstallPath +} + +type AndroidAppImportProperties struct { + // A prebuilt apk to import + Apk *string + + // The name of a certificate in the default certificate directory or an android_app_certificate + // module name in the form ":module". Should be empty if presigned or default_dev_cert is set. + Certificate *string + + // Set this flag to true if the prebuilt apk is already signed. The certificate property must not + // be set for presigned modules. + Presigned *bool + + // Name of the signing certificate lineage file. + Lineage *string + + // Sign with the default system dev certificate. Must be used judiciously. Most imported apps + // need to either specify a specific certificate or be presigned. + Default_dev_cert *bool + + // Specifies that this app should be installed to the priv-app directory, + // where the system will grant it additional privileges not available to + // normal apps. + Privileged *bool + + // Names of modules to be overridden. Listed modules can only be other binaries + // (in Make or Soong). + // This does not completely prevent installation of the overridden binaries, but if both + // binaries would be installed by default (in PRODUCT_PACKAGES) the other binary will be removed + // from PRODUCT_PACKAGES. + Overrides []string + + // Optional name for the installed app. If unspecified, it is derived from the module name. + Filename *string +} + +func (a *AndroidAppImport) IsInstallable() bool { + return true +} + +// Updates properties with variant-specific values. +func (a *AndroidAppImport) processVariants(ctx android.LoadHookContext) { + config := ctx.Config() + + dpiProps := reflect.ValueOf(a.dpiVariants).Elem().FieldByName("Dpi_variants") + // Try DPI variant matches in the reverse-priority order so that the highest priority match + // overwrites everything else. + // TODO(jungjw): Can we optimize this by making it priority order? + for i := len(config.ProductAAPTPrebuiltDPI()) - 1; i >= 0; i-- { + MergePropertiesFromVariant(ctx, &a.properties, dpiProps, config.ProductAAPTPrebuiltDPI()[i]) + } + if config.ProductAAPTPreferredConfig() != "" { + MergePropertiesFromVariant(ctx, &a.properties, dpiProps, config.ProductAAPTPreferredConfig()) + } + + archProps := reflect.ValueOf(a.archVariants).Elem().FieldByName("Arch") + archType := ctx.Config().Targets[android.Android][0].Arch.ArchType + MergePropertiesFromVariant(ctx, &a.properties, archProps, archType.Name) +} + +func MergePropertiesFromVariant(ctx android.EarlyModuleContext, + dst interface{}, variantGroup reflect.Value, variant string) { + src := variantGroup.FieldByName(proptools.FieldNameForProperty(variant)) + if !src.IsValid() { + return + } + + err := proptools.ExtendMatchingProperties([]interface{}{dst}, src.Interface(), nil, proptools.OrderAppend) + if err != nil { + if propertyErr, ok := err.(*proptools.ExtendPropertyError); ok { + ctx.PropertyErrorf(propertyErr.Property, "%s", propertyErr.Err.Error()) + } else { + panic(err) + } + } +} + +func (a *AndroidAppImport) DepsMutator(ctx android.BottomUpMutatorContext) { + cert := android.SrcIsModule(String(a.properties.Certificate)) + if cert != "" { + ctx.AddDependency(ctx.Module(), certificateTag, cert) + } + + a.usesLibrary.deps(ctx, true) +} + +func (a *AndroidAppImport) uncompressEmbeddedJniLibs( + ctx android.ModuleContext, inputPath android.Path, outputPath android.OutputPath) { + // Test apps don't need their JNI libraries stored uncompressed. As a matter of fact, messing + // with them may invalidate pre-existing signature data. + if ctx.InstallInTestcases() && (Bool(a.properties.Presigned) || a.preprocessed) { + ctx.Build(pctx, android.BuildParams{ + Rule: android.Cp, + Output: outputPath, + Input: inputPath, + }) + return + } + rule := android.NewRuleBuilder() + rule.Command(). + Textf(`if (zipinfo %s 'lib/*.so' 2>/dev/null | grep -v ' stor ' >/dev/null) ; then`, inputPath). + BuiltTool(ctx, "zip2zip"). + FlagWithInput("-i ", inputPath). + FlagWithOutput("-o ", outputPath). + FlagWithArg("-0 ", "'lib/**/*.so'"). + Textf(`; else cp -f %s %s; fi`, inputPath, outputPath) + rule.Build(pctx, ctx, "uncompress-embedded-jni-libs", "Uncompress embedded JIN libs") +} + +// Returns whether this module should have the dex file stored uncompressed in the APK. +func (a *AndroidAppImport) shouldUncompressDex(ctx android.ModuleContext) bool { + if ctx.Config().UnbundledBuild() || a.preprocessed { + return false + } + + // Uncompress dex in APKs of privileged apps + if ctx.Config().UncompressPrivAppDex() && a.Privileged() { + return true + } + + return shouldUncompressDex(ctx, &a.dexpreopter) +} + +func (a *AndroidAppImport) uncompressDex( + ctx android.ModuleContext, inputPath android.Path, outputPath android.OutputPath) { + rule := android.NewRuleBuilder() + rule.Command(). + Textf(`if (zipinfo %s '*.dex' 2>/dev/null | grep -v ' stor ' >/dev/null) ; then`, inputPath). + BuiltTool(ctx, "zip2zip"). + FlagWithInput("-i ", inputPath). + FlagWithOutput("-o ", outputPath). + FlagWithArg("-0 ", "'classes*.dex'"). + Textf(`; else cp -f %s %s; fi`, inputPath, outputPath) + rule.Build(pctx, ctx, "uncompress-dex", "Uncompress dex files") +} + +func (a *AndroidAppImport) GenerateAndroidBuildActions(ctx android.ModuleContext) { + a.generateAndroidBuildActions(ctx) +} + +func (a *AndroidAppImport) InstallApkName() string { + return a.BaseModuleName() +} + +func (a *AndroidAppImport) generateAndroidBuildActions(ctx android.ModuleContext) { + numCertPropsSet := 0 + if String(a.properties.Certificate) != "" { + numCertPropsSet++ + } + if Bool(a.properties.Presigned) { + numCertPropsSet++ + } + if Bool(a.properties.Default_dev_cert) { + numCertPropsSet++ + } + if numCertPropsSet != 1 { + ctx.ModuleErrorf("One and only one of certficate, presigned, and default_dev_cert properties must be set") + } + + _, certificates := collectAppDeps(ctx, a, false, false) + + // TODO: LOCAL_EXTRACT_APK/LOCAL_EXTRACT_DPI_APK + // TODO: LOCAL_PACKAGE_SPLITS + + srcApk := a.prebuilt.SingleSourcePath(ctx) + + if a.usesLibrary.enforceUsesLibraries() { + srcApk = a.usesLibrary.verifyUsesLibrariesAPK(ctx, srcApk) + } + + // TODO: Install or embed JNI libraries + + // Uncompress JNI libraries in the apk + jnisUncompressed := android.PathForModuleOut(ctx, "jnis-uncompressed", ctx.ModuleName()+".apk") + a.uncompressEmbeddedJniLibs(ctx, srcApk, jnisUncompressed.OutputPath) + + var installDir android.InstallPath + if Bool(a.properties.Privileged) { + installDir = android.PathForModuleInstall(ctx, "priv-app", a.BaseModuleName()) + } else if ctx.InstallInTestcases() { + installDir = android.PathForModuleInstall(ctx, a.BaseModuleName(), ctx.DeviceConfig().DeviceArch()) + } else { + installDir = android.PathForModuleInstall(ctx, "app", a.BaseModuleName()) + } + + a.dexpreopter.installPath = installDir.Join(ctx, a.BaseModuleName()+".apk") + a.dexpreopter.isPresignedPrebuilt = Bool(a.properties.Presigned) + a.dexpreopter.uncompressedDex = a.shouldUncompressDex(ctx) + + a.dexpreopter.enforceUsesLibs = a.usesLibrary.enforceUsesLibraries() + a.dexpreopter.usesLibs = a.usesLibrary.usesLibraryProperties.Uses_libs + a.dexpreopter.optionalUsesLibs = a.usesLibrary.presentOptionalUsesLibs(ctx) + a.dexpreopter.libraryPaths = a.usesLibrary.usesLibraryPaths(ctx) + + dexOutput := a.dexpreopter.dexpreopt(ctx, jnisUncompressed) + if a.dexpreopter.uncompressedDex { + dexUncompressed := android.PathForModuleOut(ctx, "dex-uncompressed", ctx.ModuleName()+".apk") + a.uncompressDex(ctx, dexOutput, dexUncompressed.OutputPath) + dexOutput = dexUncompressed + } + + apkFilename := proptools.StringDefault(a.properties.Filename, a.BaseModuleName()+".apk") + + // TODO: Handle EXTERNAL + + // Sign or align the package if package has not been preprocessed + if a.preprocessed { + a.outputFile = srcApk + a.certificate = PresignedCertificate + } else if !Bool(a.properties.Presigned) { + // If the certificate property is empty at this point, default_dev_cert must be set to true. + // Which makes processMainCert's behavior for the empty cert string WAI. + certificates = processMainCert(a.ModuleBase, String(a.properties.Certificate), certificates, ctx) + if len(certificates) != 1 { + ctx.ModuleErrorf("Unexpected number of certificates were extracted: %q", certificates) + } + a.certificate = certificates[0] + signed := android.PathForModuleOut(ctx, "signed", apkFilename) + var lineageFile android.Path + if lineage := String(a.properties.Lineage); lineage != "" { + lineageFile = android.PathForModuleSrc(ctx, lineage) + } + SignAppPackage(ctx, signed, dexOutput, certificates, nil, lineageFile) + a.outputFile = signed + } else { + alignedApk := android.PathForModuleOut(ctx, "zip-aligned", apkFilename) + TransformZipAlign(ctx, alignedApk, dexOutput) + a.outputFile = alignedApk + a.certificate = PresignedCertificate + } + + // TODO: Optionally compress the output apk. + + a.installPath = ctx.InstallFile(installDir, apkFilename, a.outputFile) + + // TODO: androidmk converter jni libs +} + +func (a *AndroidAppImport) Prebuilt() *android.Prebuilt { + return &a.prebuilt +} + +func (a *AndroidAppImport) Name() string { + return a.prebuilt.Name(a.ModuleBase.Name()) +} + +func (a *AndroidAppImport) OutputFile() android.Path { + return a.outputFile +} + +func (a *AndroidAppImport) JacocoReportClassesFile() android.Path { + return nil +} + +func (a *AndroidAppImport) Certificate() Certificate { + return a.certificate +} + +var dpiVariantGroupType reflect.Type +var archVariantGroupType reflect.Type + +func initAndroidAppImportVariantGroupTypes() { + dpiVariantGroupType = createVariantGroupType(supportedDpis, "Dpi_variants") + + archNames := make([]string, len(android.ArchTypeList())) + for i, archType := range android.ArchTypeList() { + archNames[i] = archType.Name + } + archVariantGroupType = createVariantGroupType(archNames, "Arch") +} + +// Populates all variant struct properties at creation time. +func (a *AndroidAppImport) populateAllVariantStructs() { + a.dpiVariants = reflect.New(dpiVariantGroupType).Interface() + a.AddProperties(a.dpiVariants) + + a.archVariants = reflect.New(archVariantGroupType).Interface() + a.AddProperties(a.archVariants) +} + +func (a *AndroidAppImport) Privileged() bool { + return Bool(a.properties.Privileged) +} + +func (a *AndroidAppImport) sdkVersion() sdkSpec { + return sdkSpecFrom("") +} + +func (a *AndroidAppImport) minSdkVersion() sdkSpec { + return sdkSpecFrom("") +} + +func createVariantGroupType(variants []string, variantGroupName string) reflect.Type { + props := reflect.TypeOf((*AndroidAppImportProperties)(nil)) + + variantFields := make([]reflect.StructField, len(variants)) + for i, variant := range variants { + variantFields[i] = reflect.StructField{ + Name: proptools.FieldNameForProperty(variant), + Type: props, + } + } + + variantGroupStruct := reflect.StructOf(variantFields) + return reflect.StructOf([]reflect.StructField{ + { + Name: variantGroupName, + Type: variantGroupStruct, + }, + }) +} + +// android_app_import imports a prebuilt apk with additional processing specified in the module. +// DPI-specific apk source files can be specified using dpi_variants. Example: +// +// android_app_import { +// name: "example_import", +// apk: "prebuilts/example.apk", +// dpi_variants: { +// mdpi: { +// apk: "prebuilts/example_mdpi.apk", +// }, +// xhdpi: { +// apk: "prebuilts/example_xhdpi.apk", +// }, +// }, +// certificate: "PRESIGNED", +// } +func AndroidAppImportFactory() android.Module { + module := &AndroidAppImport{} + module.AddProperties(&module.properties) + module.AddProperties(&module.dexpreoptProperties) + module.AddProperties(&module.usesLibrary.usesLibraryProperties) + module.populateAllVariantStructs() + android.AddLoadHook(module, func(ctx android.LoadHookContext) { + module.processVariants(ctx) + }) + + android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon) + android.InitDefaultableModule(module) + android.InitSingleSourcePrebuiltModule(module, &module.properties, "Apk") + + return module +} + +type androidTestImportProperties struct { + // Whether the prebuilt apk can be installed without additional processing. Default is false. + Preprocessed *bool +} + +type AndroidTestImport struct { + AndroidAppImport + + testProperties testProperties + + testImportProperties androidTestImportProperties + + data android.Paths +} + +func (a *AndroidTestImport) GenerateAndroidBuildActions(ctx android.ModuleContext) { + a.preprocessed = Bool(a.testImportProperties.Preprocessed) + + a.generateAndroidBuildActions(ctx) + + a.data = android.PathsForModuleSrc(ctx, a.testProperties.Data) +} + +func (a *AndroidTestImport) InstallInTestcases() bool { + return true +} + +// android_test_import imports a prebuilt test apk with additional processing specified in the +// module. DPI or arch variant configurations can be made as with android_app_import. +func AndroidTestImportFactory() android.Module { + module := &AndroidTestImport{} + module.AddProperties(&module.properties) + module.AddProperties(&module.dexpreoptProperties) + module.AddProperties(&module.usesLibrary.usesLibraryProperties) + module.AddProperties(&module.testProperties) + module.AddProperties(&module.testImportProperties) + module.populateAllVariantStructs() + android.AddLoadHook(module, func(ctx android.LoadHookContext) { + module.processVariants(ctx) + }) + + module.dexpreopter.isTest = true + + android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon) + android.InitDefaultableModule(module) + android.InitSingleSourcePrebuiltModule(module, &module.properties, "Apk") + + return module +} + +type RuntimeResourceOverlay struct { + android.ModuleBase + android.DefaultableModuleBase + android.OverridableModuleBase + aapt + + properties RuntimeResourceOverlayProperties + overridableProperties OverridableRuntimeResourceOverlayProperties + + certificate Certificate + + outputFile android.Path + installDir android.InstallPath +} + +type RuntimeResourceOverlayProperties struct { + // the name of a certificate in the default certificate directory or an android_app_certificate + // module name in the form ":module". + Certificate *string + + // Name of the signing certificate lineage file. + Lineage *string + + // optional theme name. If specified, the overlay package will be applied + // only when the ro.boot.vendor.overlay.theme system property is set to the same value. + Theme *string + + // if not blank, set to the version of the sdk to compile against. + // Defaults to compiling against the current platform. + Sdk_version *string + + // if not blank, set the minimum version of the sdk that the compiled artifacts will run against. + // Defaults to sdk_version if not set. + Min_sdk_version *string + + // list of android_library modules whose resources are extracted and linked against statically + Static_libs []string + + // list of android_app modules whose resources are extracted and linked against + Resource_libs []string + + // Names of modules to be overridden. Listed modules can only be other overlays + // (in Make or Soong). + // This does not completely prevent installation of the overridden overlays, but if both + // overlays would be installed by default (in PRODUCT_PACKAGES) the other overlay will be removed + // from PRODUCT_PACKAGES. + Overrides []string +} + +func (r *RuntimeResourceOverlay) DepsMutator(ctx android.BottomUpMutatorContext) { + sdkDep := decodeSdkDep(ctx, sdkContext(r)) + if sdkDep.hasFrameworkLibs() { + r.aapt.deps(ctx, sdkDep) + } + + cert := android.SrcIsModule(String(r.properties.Certificate)) + if cert != "" { + ctx.AddDependency(ctx.Module(), certificateTag, cert) + } + + ctx.AddVariationDependencies(nil, staticLibTag, r.properties.Static_libs...) + ctx.AddVariationDependencies(nil, libTag, r.properties.Resource_libs...) +} + +func (r *RuntimeResourceOverlay) GenerateAndroidBuildActions(ctx android.ModuleContext) { + // Compile and link resources + r.aapt.hasNoCode = true + // Do not remove resources without default values nor dedupe resource configurations with the same value + aaptLinkFlags := []string{"--no-resource-deduping", "--no-resource-removal"} + // Allow the override of "package name" and "overlay target package name" + manifestPackageName, overridden := ctx.DeviceConfig().OverrideManifestPackageNameFor(ctx.ModuleName()) + if overridden || r.overridableProperties.Package_name != nil { + // The product override variable has a priority over the package_name property. + if !overridden { + manifestPackageName = *r.overridableProperties.Package_name + } + aaptLinkFlags = append(aaptLinkFlags, "--rename-manifest-package "+manifestPackageName) + } + if r.overridableProperties.Target_package_name != nil { + aaptLinkFlags = append(aaptLinkFlags, + "--rename-overlay-target-package "+*r.overridableProperties.Target_package_name) + } + r.aapt.buildActions(ctx, r, aaptLinkFlags...) + + // Sign the built package + _, certificates := collectAppDeps(ctx, r, false, false) + certificates = processMainCert(r.ModuleBase, String(r.properties.Certificate), certificates, ctx) + signed := android.PathForModuleOut(ctx, "signed", r.Name()+".apk") + var lineageFile android.Path + if lineage := String(r.properties.Lineage); lineage != "" { + lineageFile = android.PathForModuleSrc(ctx, lineage) + } + SignAppPackage(ctx, signed, r.aapt.exportPackage, certificates, nil, lineageFile) + r.certificate = certificates[0] + + r.outputFile = signed + r.installDir = android.PathForModuleInstall(ctx, "overlay", String(r.properties.Theme)) + ctx.InstallFile(r.installDir, r.outputFile.Base(), r.outputFile) +} + +func (r *RuntimeResourceOverlay) sdkVersion() sdkSpec { + return sdkSpecFrom(String(r.properties.Sdk_version)) +} + +func (r *RuntimeResourceOverlay) systemModules() string { + return "" +} + +func (r *RuntimeResourceOverlay) minSdkVersion() sdkSpec { + if r.properties.Min_sdk_version != nil { + return sdkSpecFrom(*r.properties.Min_sdk_version) + } + return r.sdkVersion() +} + +func (r *RuntimeResourceOverlay) targetSdkVersion() sdkSpec { + return r.sdkVersion() +} + +// runtime_resource_overlay generates a resource-only apk file that can overlay application and +// system resources at run time. +func RuntimeResourceOverlayFactory() android.Module { + module := &RuntimeResourceOverlay{} + module.AddProperties( + &module.properties, + &module.aaptProperties, + &module.overridableProperties) + + android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon) + android.InitDefaultableModule(module) + android.InitOverridableModule(module, &module.properties.Overrides) + return module +} + +type UsesLibraryProperties struct { + // A list of shared library modules that will be listed in uses-library tags in the AndroidManifest.xml file. + Uses_libs []string + + // A list of shared library modules that will be listed in uses-library tags in the AndroidManifest.xml file with + // required=false. + Optional_uses_libs []string + + // If true, the list of uses_libs and optional_uses_libs modules must match the AndroidManifest.xml file. Defaults + // to true if either uses_libs or optional_uses_libs is set. Will unconditionally default to true in the future. + Enforce_uses_libs *bool +} + +// usesLibrary provides properties and helper functions for AndroidApp and AndroidAppImport to verify that the +// <uses-library> tags that end up in the manifest of an APK match the ones known to the build system through the +// uses_libs and optional_uses_libs properties. The build system's values are used by dexpreopt to preopt apps +// with knowledge of their shared libraries. +type usesLibrary struct { + usesLibraryProperties UsesLibraryProperties +} + +func (u *usesLibrary) deps(ctx android.BottomUpMutatorContext, hasFrameworkLibs bool) { + if !ctx.Config().UnbundledBuild() { + ctx.AddVariationDependencies(nil, usesLibTag, u.usesLibraryProperties.Uses_libs...) + ctx.AddVariationDependencies(nil, usesLibTag, u.presentOptionalUsesLibs(ctx)...) + // Only add these extra dependencies if the module depends on framework libs. This avoids + // creating a cyclic dependency: + // e.g. framework-res -> org.apache.http.legacy -> ... -> framework-res. + if hasFrameworkLibs { + // dexpreopt/dexpreopt.go needs the paths to the dex jars of these libraries in case construct_context.sh needs + // to pass them to dex2oat. Add them as a dependency so we can determine the path to the dex jar of each + // library to dexpreopt. + ctx.AddVariationDependencies(nil, usesLibTag, + "org.apache.http.legacy", + "android.hidl.base-V1.0-java", + "android.hidl.manager-V1.0-java") + } + } +} + +// presentOptionalUsesLibs returns optional_uses_libs after filtering out MissingUsesLibraries, which don't exist in the +// build. +func (u *usesLibrary) presentOptionalUsesLibs(ctx android.BaseModuleContext) []string { + optionalUsesLibs, _ := android.FilterList(u.usesLibraryProperties.Optional_uses_libs, ctx.Config().MissingUsesLibraries()) + return optionalUsesLibs +} + +// usesLibraryPaths returns a map of module names of shared library dependencies to the paths to their dex jars. +func (u *usesLibrary) usesLibraryPaths(ctx android.ModuleContext) map[string]android.Path { + usesLibPaths := make(map[string]android.Path) + + if !ctx.Config().UnbundledBuild() { + ctx.VisitDirectDepsWithTag(usesLibTag, func(m android.Module) { + if lib, ok := m.(Dependency); ok { + if dexJar := lib.DexJar(); dexJar != nil { + usesLibPaths[ctx.OtherModuleName(m)] = dexJar + } else { + ctx.ModuleErrorf("module %q in uses_libs or optional_uses_libs must produce a dex jar, does it have installable: true?", + ctx.OtherModuleName(m)) + } + } else if ctx.Config().AllowMissingDependencies() { + ctx.AddMissingDependencies([]string{ctx.OtherModuleName(m)}) + } else { + ctx.ModuleErrorf("module %q in uses_libs or optional_uses_libs must be a java library", + ctx.OtherModuleName(m)) + } + }) + } + + return usesLibPaths +} + +// enforceUsesLibraries returns true of <uses-library> tags should be checked against uses_libs and optional_uses_libs +// properties. Defaults to true if either of uses_libs or optional_uses_libs is specified. Will default to true +// unconditionally in the future. +func (u *usesLibrary) enforceUsesLibraries() bool { + defaultEnforceUsesLibs := len(u.usesLibraryProperties.Uses_libs) > 0 || + len(u.usesLibraryProperties.Optional_uses_libs) > 0 + return BoolDefault(u.usesLibraryProperties.Enforce_uses_libs, defaultEnforceUsesLibs) +} + +// verifyUsesLibrariesManifest checks the <uses-library> tags in an AndroidManifest.xml against the ones specified +// in the uses_libs and optional_uses_libs properties. It returns the path to a copy of the manifest. +func (u *usesLibrary) verifyUsesLibrariesManifest(ctx android.ModuleContext, manifest android.Path) android.Path { + outputFile := android.PathForModuleOut(ctx, "manifest_check", "AndroidManifest.xml") + + rule := android.NewRuleBuilder() + cmd := rule.Command().BuiltTool(ctx, "manifest_check"). + Flag("--enforce-uses-libraries"). + Input(manifest). + FlagWithOutput("-o ", outputFile) + + for _, lib := range u.usesLibraryProperties.Uses_libs { + cmd.FlagWithArg("--uses-library ", lib) + } + + for _, lib := range u.usesLibraryProperties.Optional_uses_libs { + cmd.FlagWithArg("--optional-uses-library ", lib) + } + + rule.Build(pctx, ctx, "verify_uses_libraries", "verify <uses-library>") + + return outputFile +} + +// verifyUsesLibrariesAPK checks the <uses-library> tags in the manifest of an APK against the ones specified +// in the uses_libs and optional_uses_libs properties. It returns the path to a copy of the APK. +func (u *usesLibrary) verifyUsesLibrariesAPK(ctx android.ModuleContext, apk android.Path) android.Path { + outputFile := android.PathForModuleOut(ctx, "verify_uses_libraries", apk.Base()) + + rule := android.NewRuleBuilder() + aapt := ctx.Config().HostToolPath(ctx, "aapt") + rule.Command(). + Textf("aapt_binary=%s", aapt.String()).Implicit(aapt). + Textf(`uses_library_names="%s"`, strings.Join(u.usesLibraryProperties.Uses_libs, " ")). + Textf(`optional_uses_library_names="%s"`, strings.Join(u.usesLibraryProperties.Optional_uses_libs, " ")). + Tool(android.PathForSource(ctx, "build/make/core/verify_uses_libraries.sh")).Input(apk) + rule.Command().Text("cp -f").Input(apk).Output(outputFile) + + rule.Build(pctx, ctx, "verify_uses_libraries", "verify <uses-library>") + + return outputFile +} diff --git a/java/app_builder.go b/java/app_builder.go index 5bacb6776..97ec269ee 100644 --- a/java/app_builder.go +++ b/java/app_builder.go @@ -26,44 +26,33 @@ import ( "github.com/google/blueprint/proptools" "android/soong/android" + "android/soong/remoteexec" ) var ( - Signapk = pctx.AndroidStaticRule("signapk", + Signapk, SignapkRE = remoteexec.StaticRules(pctx, "signapk", blueprint.RuleParams{ - Command: `${config.JavaCmd} -Djava.library.path=$$(dirname $signapkJniLibrary) ` + - `-jar $signapkCmd $flags $certificates $in $out`, - CommandDeps: []string{"$signapkCmd", "$signapkJniLibrary"}, + Command: `$reTemplate${config.JavaCmd} ${config.JavaVmFlags} -Djava.library.path=$$(dirname ${config.SignapkJniLibrary}) ` + + `-jar ${config.SignapkCmd} $flags $certificates $in $out`, + CommandDeps: []string{"${config.SignapkCmd}", "${config.SignapkJniLibrary}"}, }, - "flags", "certificates") - - androidManifestMerger = pctx.AndroidStaticRule("androidManifestMerger", - blueprint.RuleParams{ - Command: "java -classpath $androidManifestMergerCmd com.android.manifmerger.Main merge " + - "--main $in --libs $libsManifests --out $out", - CommandDeps: []string{"$androidManifestMergerCmd"}, - Description: "merge manifest files", - }, - "libsManifests") + &remoteexec.REParams{Labels: map[string]string{"type": "tool", "name": "signapk"}, + ExecStrategy: "${config.RESignApkExecStrategy}", + Inputs: []string{"${config.SignapkCmd}", "$in", "$$(dirname ${config.SignapkJniLibrary})", "$implicits"}, + OutputFiles: []string{"$outCommaList"}, + ToolchainInputs: []string{"${config.JavaCmd}"}, + Platform: map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"}, + }, []string{"flags", "certificates"}, []string{"implicits", "outCommaList"}) ) -func init() { - pctx.SourcePathVariable("androidManifestMergerCmd", "prebuilts/devtools/tools/lib/manifest-merger.jar") - pctx.HostBinToolVariable("aaptCmd", "aapt") - pctx.HostJavaToolVariable("signapkCmd", "signapk.jar") - // TODO(ccross): this should come from the signapk dependencies, but we don't have any way - // to express host JNI dependencies yet. - pctx.HostJNIToolVariable("signapkJniLibrary", "libconscrypt_openjdk_jni") -} - var combineApk = pctx.AndroidStaticRule("combineApk", blueprint.RuleParams{ Command: `${config.MergeZipsCmd} $out $in`, CommandDeps: []string{"${config.MergeZipsCmd}"}, }) -func CreateAppPackage(ctx android.ModuleContext, outputFile android.WritablePath, - packageFile, jniJarFile, dexJarFile android.Path, certificates []Certificate) { +func CreateAndSignAppPackage(ctx android.ModuleContext, outputFile android.WritablePath, + packageFile, jniJarFile, dexJarFile android.Path, certificates []Certificate, deps android.Paths, v4SignatureFile android.WritablePath, lineageFile android.Path) { unsignedApkName := strings.TrimSuffix(outputFile.Base(), ".apk") + "-unsigned.apk" unsignedApk := android.PathForModuleOut(ctx, unsignedApkName) @@ -78,11 +67,17 @@ func CreateAppPackage(ctx android.ModuleContext, outputFile android.WritablePath } ctx.Build(pctx, android.BuildParams{ - Rule: combineApk, - Inputs: inputs, - Output: unsignedApk, + Rule: combineApk, + Inputs: inputs, + Output: unsignedApk, + Implicits: deps, }) + SignAppPackage(ctx, outputFile, unsignedApk, certificates, v4SignatureFile, lineageFile) +} + +func SignAppPackage(ctx android.ModuleContext, signedApk android.WritablePath, unsignedApk android.Path, certificates []Certificate, v4SignatureFile android.WritablePath, lineageFile android.Path) { + var certificateArgs []string var deps android.Paths for _, c := range certificates { @@ -90,15 +85,35 @@ func CreateAppPackage(ctx android.ModuleContext, outputFile android.WritablePath deps = append(deps, c.Pem, c.Key) } + outputFiles := android.WritablePaths{signedApk} + var flags []string + if v4SignatureFile != nil { + outputFiles = append(outputFiles, v4SignatureFile) + flags = append(flags, "--enable-v4") + } + + if lineageFile != nil { + flags = append(flags, "--lineage", lineageFile.String()) + deps = append(deps, lineageFile) + } + + rule := Signapk + args := map[string]string{ + "certificates": strings.Join(certificateArgs, " "), + "flags": strings.Join(flags, " "), + } + if ctx.Config().IsEnvTrue("RBE_SIGNAPK") { + rule = SignapkRE + args["implicits"] = strings.Join(deps.Strings(), ",") + args["outCommaList"] = strings.Join(outputFiles.Strings(), ",") + } ctx.Build(pctx, android.BuildParams{ - Rule: Signapk, + Rule: rule, Description: "signapk", - Output: outputFile, + Outputs: outputFiles, Input: unsignedApk, Implicits: deps, - Args: map[string]string{ - "certificates": strings.Join(certificateArgs, " "), - }, + Args: args, }) } @@ -212,24 +227,30 @@ func TransformJniLibsToJar(ctx android.ModuleContext, outputFile android.Writabl } if uncompressJNI { - jarArgs = append(jarArgs, "-L 0") + jarArgs = append(jarArgs, "-L", "0") } for _, j := range jniLibs { deps = append(deps, j.path) jarArgs = append(jarArgs, - "-P "+targetToJniDir(j.target), - "-f "+j.path.String()) + "-P", targetToJniDir(j.target), + "-f", j.path.String()) } + rule := zip + args := map[string]string{ + "jarArgs": strings.Join(proptools.NinjaAndShellEscapeList(jarArgs), " "), + } + if ctx.Config().IsEnvTrue("RBE_ZIP") { + rule = zipRE + args["implicits"] = strings.Join(deps.Strings(), ",") + } ctx.Build(pctx, android.BuildParams{ - Rule: zip, + Rule: rule, Description: "zip jni libs", Output: outputFile, Implicits: deps, - Args: map[string]string{ - "jarArgs": strings.Join(proptools.NinjaAndShellEscapeList(jarArgs), " "), - }, + Args: args, }) } diff --git a/java/app_test.go b/java/app_test.go index 1d497703a..8ef315206 100644 --- a/java/app_test.go +++ b/java/app_test.go @@ -15,17 +15,18 @@ package java import ( - "android/soong/android" - "android/soong/cc" - "fmt" "path/filepath" "reflect" + "regexp" "sort" "strings" "testing" "github.com/google/blueprint/proptools" + + "android/soong/android" + "android/soong/cc" ) var ( @@ -42,7 +43,7 @@ var ( } ) -func testAppContext(config android.Config, bp string, fs map[string][]byte) *android.TestContext { +func testAppConfig(env map[string]string, bp string, fs map[string][]byte) android.Config { appFS := map[string][]byte{} for k, v := range fs { appFS[k] = v @@ -52,13 +53,13 @@ func testAppContext(config android.Config, bp string, fs map[string][]byte) *and appFS[file] = nil } - return testContext(config, bp, appFS) + return testConfig(env, bp, appFS) } func testApp(t *testing.T, bp string) *android.TestContext { - config := testConfig(nil) + config := testAppConfig(nil, bp, nil) - ctx := testAppContext(config, bp, nil) + ctx := testContext() run(t, ctx, config) @@ -71,6 +72,7 @@ func TestApp(t *testing.T) { ctx := testApp(t, moduleType+` { name: "foo", srcs: ["a.java"], + sdk_version: "current" } `) @@ -116,6 +118,7 @@ func TestAppSplits(t *testing.T) { name: "foo", srcs: ["a.java"], package_splits: ["v4", "v7,hdpi"], + sdk_version: "current" }`) foo := ctx.ModuleForTests("foo", "android_common") @@ -129,23 +132,24 @@ func TestAppSplits(t *testing.T) { foo.Output(expectedOutput) } - if g, w := foo.Module().(*AndroidApp).Srcs().Strings(), expectedOutputs; !reflect.DeepEqual(g, w) { - t.Errorf("want Srcs() = %q, got %q", w, g) + outputFiles, err := foo.Module().(*AndroidApp).OutputFiles("") + if err != nil { + t.Fatal(err) + } + if g, w := outputFiles.Strings(), expectedOutputs; !reflect.DeepEqual(g, w) { + t.Errorf(`want OutputFiles("") = %q, got %q`, w, g) } } func TestAndroidAppSet(t *testing.T) { - config := testConfig(nil) - config.TestProductVariables.DeviceArch = proptools.StringPtr("arm64") - ctx := testAppContext(config, ` - android_app_set { - name: "foo", - set: "prebuilts/apks/app.apks", - prerelease: true, - }`, nil) - run(t, ctx, config) + ctx, config := testJava(t, ` + android_app_set { + name: "foo", + set: "prebuilts/apks/app.apks", + prerelease: true, + }`) module := ctx.ModuleForTests("foo", "android_common") - const packedSplitApks = "extracted.zip" + const packedSplitApks = "foo.zip" params := module.Output(packedSplitApks) if params.Rule == nil { t.Errorf("expected output %s is missing", packedSplitApks) @@ -156,6 +160,13 @@ func TestAndroidAppSet(t *testing.T) { if s := params.Args["partition"]; s != "system" { t.Errorf("wrong partition value: '%s', expected 'system'", s) } + mkEntries := android.AndroidMkEntriesForTest(t, config, "", module.Module())[0] + actualMaster := mkEntries.EntryMap["LOCAL_APK_SET_MASTER_FILE"] + expectedMaster := []string{"foo.apk"} + if !reflect.DeepEqual(actualMaster, expectedMaster) { + t.Errorf("Unexpected LOCAL_APK_SET_MASTER_FILE value: '%s', expected: '%s',", + actualMaster, expectedMaster) + } } func TestAndroidAppSet_Variants(t *testing.T) { @@ -165,16 +176,17 @@ func TestAndroidAppSet_Variants(t *testing.T) { set: "prebuilts/apks/app.apks", }` testCases := []struct { - name string - deviceArch *string - deviceSecondaryArch *string - aaptPrebuiltDPI []string - sdkVersion int - expected map[string]string + name string + targets []android.Target + aaptPrebuiltDPI []string + sdkVersion int + expected map[string]string }{ { - name: "One", - deviceArch: proptools.StringPtr("x86"), + name: "One", + targets: []android.Target{ + {Os: android.Android, Arch: android.Arch{ArchType: android.X86}}, + }, aaptPrebuiltDPI: []string{"ldpi", "xxhdpi"}, sdkVersion: 29, expected: map[string]string{ @@ -186,11 +198,13 @@ func TestAndroidAppSet_Variants(t *testing.T) { }, }, { - name: "Two", - deviceArch: proptools.StringPtr("x86_64"), - deviceSecondaryArch: proptools.StringPtr("x86"), - aaptPrebuiltDPI: nil, - sdkVersion: 30, + name: "Two", + targets: []android.Target{ + {Os: android.Android, Arch: android.Arch{ArchType: android.X86_64}}, + {Os: android.Android, Arch: android.Arch{ArchType: android.X86}}, + }, + aaptPrebuiltDPI: nil, + sdkVersion: 30, expected: map[string]string{ "abis": "X86_64,X86", "allow-prereleased": "false", @@ -202,15 +216,14 @@ func TestAndroidAppSet_Variants(t *testing.T) { } for _, test := range testCases { - config := testConfig(nil) + config := testAppConfig(nil, bp, nil) config.TestProductVariables.AAPTPrebuiltDPI = test.aaptPrebuiltDPI config.TestProductVariables.Platform_sdk_version = &test.sdkVersion - config.TestProductVariables.DeviceArch = test.deviceArch - config.TestProductVariables.DeviceSecondaryArch = test.deviceSecondaryArch - ctx := testAppContext(config, bp, nil) + config.Targets[android.Android] = test.targets + ctx := testContext() run(t, ctx, config) module := ctx.ModuleForTests("foo", "android_common") - const packedSplitApks = "extracted.zip" + const packedSplitApks = "foo.zip" params := module.Output(packedSplitApks) for k, v := range test.expected { if actual := params.Args[k]; actual != v { @@ -221,6 +234,371 @@ func TestAndroidAppSet_Variants(t *testing.T) { } } +func TestPlatformAPIs(t *testing.T) { + testJava(t, ` + android_app { + name: "foo", + srcs: ["a.java"], + platform_apis: true, + } + `) + + testJava(t, ` + android_app { + name: "foo", + srcs: ["a.java"], + sdk_version: "current", + } + `) + + testJavaError(t, "platform_apis must be true when sdk_version is empty.", ` + android_app { + name: "bar", + srcs: ["b.java"], + } + `) + + testJavaError(t, "platform_apis must be false when sdk_version is not empty.", ` + android_app { + name: "bar", + srcs: ["b.java"], + sdk_version: "system_current", + platform_apis: true, + } + `) +} + +func TestAndroidAppLinkType(t *testing.T) { + testJava(t, ` + android_app { + name: "foo", + srcs: ["a.java"], + libs: ["bar"], + static_libs: ["baz"], + platform_apis: true, + } + + java_library { + name: "bar", + sdk_version: "current", + srcs: ["b.java"], + } + + android_library { + name: "baz", + sdk_version: "system_current", + srcs: ["c.java"], + } + `) + + testJavaError(t, "Adjust sdk_version: property of the source or target module so that target module is built with the same or smaller API set than the source.", ` + android_app { + name: "foo", + srcs: ["a.java"], + libs: ["bar"], + sdk_version: "current", + static_libs: ["baz"], + } + + java_library { + name: "bar", + sdk_version: "current", + srcs: ["b.java"], + } + + android_library { + name: "baz", + sdk_version: "system_current", + srcs: ["c.java"], + } + `) + + testJava(t, ` + android_app { + name: "foo", + srcs: ["a.java"], + libs: ["bar"], + sdk_version: "system_current", + static_libs: ["baz"], + } + + java_library { + name: "bar", + sdk_version: "current", + srcs: ["b.java"], + } + + android_library { + name: "baz", + sdk_version: "system_current", + srcs: ["c.java"], + } + `) + + testJavaError(t, "Adjust sdk_version: property of the source or target module so that target module is built with the same or smaller API set than the source.", ` + android_app { + name: "foo", + srcs: ["a.java"], + libs: ["bar"], + sdk_version: "system_current", + static_libs: ["baz"], + } + + java_library { + name: "bar", + sdk_version: "current", + srcs: ["b.java"], + } + + android_library { + name: "baz", + srcs: ["c.java"], + } + `) +} + +func TestUpdatableApps(t *testing.T) { + testCases := []struct { + name string + bp string + expectedError string + }{ + { + name: "Stable public SDK", + bp: `android_app { + name: "foo", + srcs: ["a.java"], + sdk_version: "29", + min_sdk_version: "29", + updatable: true, + }`, + }, + { + name: "Stable system SDK", + bp: `android_app { + name: "foo", + srcs: ["a.java"], + sdk_version: "system_29", + min_sdk_version: "29", + updatable: true, + }`, + }, + { + name: "Current public SDK", + bp: `android_app { + name: "foo", + srcs: ["a.java"], + sdk_version: "current", + min_sdk_version: "29", + updatable: true, + }`, + }, + { + name: "Current system SDK", + bp: `android_app { + name: "foo", + srcs: ["a.java"], + sdk_version: "system_current", + min_sdk_version: "29", + updatable: true, + }`, + }, + { + name: "Current module SDK", + bp: `android_app { + name: "foo", + srcs: ["a.java"], + sdk_version: "module_current", + min_sdk_version: "29", + updatable: true, + }`, + }, + { + name: "Current core SDK", + bp: `android_app { + name: "foo", + srcs: ["a.java"], + sdk_version: "core_current", + min_sdk_version: "29", + updatable: true, + }`, + }, + { + name: "No Platform APIs", + bp: `android_app { + name: "foo", + srcs: ["a.java"], + platform_apis: true, + min_sdk_version: "29", + updatable: true, + }`, + expectedError: "Updatable apps must use stable SDKs", + }, + { + name: "No Core Platform APIs", + bp: `android_app { + name: "foo", + srcs: ["a.java"], + sdk_version: "core_platform", + min_sdk_version: "29", + updatable: true, + }`, + expectedError: "Updatable apps must use stable SDKs", + }, + { + name: "No unspecified APIs", + bp: `android_app { + name: "foo", + srcs: ["a.java"], + updatable: true, + min_sdk_version: "29", + }`, + expectedError: "Updatable apps must use stable SDK", + }, + { + name: "Must specify min_sdk_version", + bp: `android_app { + name: "app_without_min_sdk_version", + srcs: ["a.java"], + sdk_version: "29", + updatable: true, + }`, + expectedError: "updatable apps must set min_sdk_version.", + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + if test.expectedError == "" { + testJava(t, test.bp) + } else { + testJavaError(t, test.expectedError, test.bp) + } + }) + } +} + +func TestUpdatableApps_JniLibsShouldShouldSupportMinSdkVersion(t *testing.T) { + testJava(t, cc.GatherRequiredDepsForTest(android.Android)+` + android_app { + name: "foo", + srcs: ["a.java"], + updatable: true, + sdk_version: "current", + min_sdk_version: "current", + jni_libs: ["libjni"], + } + + cc_library { + name: "libjni", + stl: "none", + system_shared_libs: [], + sdk_version: "current", + } + `) +} + +func TestUpdatableApps_JniLibShouldBeBuiltAgainstMinSdkVersion(t *testing.T) { + bp := cc.GatherRequiredDepsForTest(android.Android) + ` + android_app { + name: "foo", + srcs: ["a.java"], + updatable: true, + sdk_version: "current", + min_sdk_version: "29", + jni_libs: ["libjni"], + } + + cc_library { + name: "libjni", + stl: "none", + system_shared_libs: [], + sdk_version: "29", + } + + ndk_prebuilt_object { + name: "ndk_crtbegin_so.29", + sdk_version: "29", + } + + ndk_prebuilt_object { + name: "ndk_crtend_so.29", + sdk_version: "29", + } + ` + fs := map[string][]byte{ + "prebuilts/ndk/current/platforms/android-29/arch-arm64/usr/lib/crtbegin_so.o": nil, + "prebuilts/ndk/current/platforms/android-29/arch-arm64/usr/lib/crtend_so.o": nil, + "prebuilts/ndk/current/platforms/android-29/arch-arm/usr/lib/crtbegin_so.o": nil, + "prebuilts/ndk/current/platforms/android-29/arch-arm/usr/lib/crtend_so.o": nil, + } + + ctx, _ := testJavaWithConfig(t, testConfig(nil, bp, fs)) + + inputs := ctx.ModuleForTests("libjni", "android_arm64_armv8-a_sdk_shared").Description("link").Implicits + var crtbeginFound, crtendFound bool + for _, input := range inputs { + switch input.String() { + case "prebuilts/ndk/current/platforms/android-29/arch-arm64/usr/lib/crtbegin_so.o": + crtbeginFound = true + case "prebuilts/ndk/current/platforms/android-29/arch-arm64/usr/lib/crtend_so.o": + crtendFound = true + } + } + if !crtbeginFound || !crtendFound { + t.Error("should link with ndk_crtbegin_so.29 and ndk_crtend_so.29") + } +} + +func TestUpdatableApps_ErrorIfJniLibDoesntSupportMinSdkVersion(t *testing.T) { + bp := cc.GatherRequiredDepsForTest(android.Android) + ` + android_app { + name: "foo", + srcs: ["a.java"], + updatable: true, + sdk_version: "current", + min_sdk_version: "29", // this APK should support 29 + jni_libs: ["libjni"], + } + + cc_library { + name: "libjni", + stl: "none", + sdk_version: "current", + } + ` + testJavaError(t, `"libjni" .*: sdk_version\(current\) is higher than min_sdk_version\(29\)`, bp) +} + +func TestUpdatableApps_ErrorIfDepSdkVersionIsHigher(t *testing.T) { + bp := cc.GatherRequiredDepsForTest(android.Android) + ` + android_app { + name: "foo", + srcs: ["a.java"], + updatable: true, + sdk_version: "current", + min_sdk_version: "29", // this APK should support 29 + jni_libs: ["libjni"], + } + + cc_library { + name: "libjni", + stl: "none", + shared_libs: ["libbar"], + system_shared_libs: [], + sdk_version: "27", + } + + cc_library { + name: "libbar", + stl: "none", + system_shared_libs: [], + sdk_version: "current", + } + ` + testJavaError(t, `"libjni" .*: links "libbar" built against newer API version "current"`, bp) +} + func TestResourceDirs(t *testing.T) { testCases := []struct { name string @@ -251,14 +629,15 @@ func TestResourceDirs(t *testing.T) { bp := ` android_app { name: "foo", + sdk_version: "current", %s } ` for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - config := testConfig(nil) - ctx := testContext(config, fmt.Sprintf(bp, testCase.prop), fs) + config := testConfig(nil, fmt.Sprintf(bp, testCase.prop), fs) + ctx := testContext() run(t, ctx, config) module := ctx.ModuleForTests("foo", "android_common") @@ -279,6 +658,107 @@ func TestResourceDirs(t *testing.T) { } } +func TestLibraryAssets(t *testing.T) { + bp := ` + android_app { + name: "foo", + sdk_version: "current", + static_libs: ["lib1", "lib2", "lib3"], + } + + android_library { + name: "lib1", + sdk_version: "current", + asset_dirs: ["assets_a"], + } + + android_library { + name: "lib2", + sdk_version: "current", + } + + android_library { + name: "lib3", + sdk_version: "current", + static_libs: ["lib4"], + } + + android_library { + name: "lib4", + sdk_version: "current", + asset_dirs: ["assets_b"], + } + ` + + testCases := []struct { + name string + assetFlag string + assetPackages []string + }{ + { + name: "foo", + // lib1 has its own asset. lib3 doesn't have any, but provides lib4's transitively. + assetPackages: []string{ + buildDir + "/.intermediates/foo/android_common/aapt2/package-res.apk", + buildDir + "/.intermediates/lib1/android_common/assets.zip", + buildDir + "/.intermediates/lib3/android_common/assets.zip", + }, + }, + { + name: "lib1", + assetFlag: "-A assets_a", + }, + { + name: "lib2", + }, + { + name: "lib3", + assetPackages: []string{ + buildDir + "/.intermediates/lib3/android_common/aapt2/package-res.apk", + buildDir + "/.intermediates/lib4/android_common/assets.zip", + }, + }, + { + name: "lib4", + assetFlag: "-A assets_b", + }, + } + ctx := testApp(t, bp) + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + m := ctx.ModuleForTests(test.name, "android_common") + + // Check asset flag in aapt2 link flags + var aapt2link android.TestingBuildParams + if len(test.assetPackages) > 0 { + aapt2link = m.Output("aapt2/package-res.apk") + } else { + aapt2link = m.Output("package-res.apk") + } + aapt2Flags := aapt2link.Args["flags"] + if test.assetFlag != "" { + if !strings.Contains(aapt2Flags, test.assetFlag) { + t.Errorf("Can't find asset flag %q in aapt2 link flags %q", test.assetFlag, aapt2Flags) + } + } else { + if strings.Contains(aapt2Flags, " -A ") { + t.Errorf("aapt2 link flags %q contain unexpected asset flag", aapt2Flags) + } + } + + // Check asset merge rule. + if len(test.assetPackages) > 0 { + mergeAssets := m.Output("package-res.apk") + if !reflect.DeepEqual(test.assetPackages, mergeAssets.Inputs.Strings()) { + t.Errorf("Unexpected mergeAssets inputs: %v, expected: %v", + mergeAssets.Inputs.Strings(), test.assetPackages) + } + } + }) + } +} + func TestAndroidResources(t *testing.T) { testCases := []struct { name string @@ -431,36 +911,41 @@ func TestAndroidResources(t *testing.T) { bp := ` android_app { name: "foo", + sdk_version: "current", resource_dirs: ["foo/res"], static_libs: ["lib", "lib3"], } android_app { name: "bar", + sdk_version: "current", resource_dirs: ["bar/res"], } android_library { name: "lib", + sdk_version: "current", resource_dirs: ["lib/res"], static_libs: ["lib2"], } android_library { name: "lib2", + sdk_version: "current", resource_dirs: ["lib2/res"], } // This library has the same resources as lib (should not lead to dupe RROs) android_library { name: "lib3", + sdk_version: "current", resource_dirs: ["lib/res"] } ` for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - config := testConfig(nil) + config := testAppConfig(nil, bp, fs) config.TestProductVariables.DeviceResourceOverlays = deviceResourceOverlays config.TestProductVariables.ProductResourceOverlays = productResourceOverlays if testCase.enforceRROTargets != nil { @@ -470,7 +955,7 @@ func TestAndroidResources(t *testing.T) { config.TestProductVariables.EnforceRROExcludedOverlays = testCase.enforceRROExcludedOverlays } - ctx := testAppContext(config, bp, fs) + ctx := testContext() run(t, ctx, config) resourceListToFiles := func(module android.TestingModule, list []string) (files []string) { @@ -543,6 +1028,7 @@ func TestAppSdkVersion(t *testing.T) { platformSdkCodename string platformSdkFinal bool expectedMinSdkVersion string + platformApis bool }{ { name: "current final SDK", @@ -563,6 +1049,7 @@ func TestAppSdkVersion(t *testing.T) { { name: "default final SDK", sdkVersion: "", + platformApis: true, platformSdkInt: 27, platformSdkCodename: "REL", platformSdkFinal: true, @@ -571,6 +1058,7 @@ func TestAppSdkVersion(t *testing.T) { { name: "default non-final SDK", sdkVersion: "", + platformApis: true, platformSdkInt: 27, platformSdkCodename: "OMR1", platformSdkFinal: false, @@ -586,18 +1074,23 @@ func TestAppSdkVersion(t *testing.T) { for _, moduleType := range []string{"android_app", "android_library"} { for _, test := range testCases { t.Run(moduleType+" "+test.name, func(t *testing.T) { + platformApiProp := "" + if test.platformApis { + platformApiProp = "platform_apis: true," + } bp := fmt.Sprintf(`%s { name: "foo", srcs: ["a.java"], sdk_version: "%s", - }`, moduleType, test.sdkVersion) + %s + }`, moduleType, test.sdkVersion, platformApiProp) - config := testConfig(nil) + config := testAppConfig(nil, bp, nil) config.TestProductVariables.Platform_sdk_version = &test.platformSdkInt config.TestProductVariables.Platform_sdk_codename = &test.platformSdkCodename config.TestProductVariables.Platform_sdk_final = &test.platformSdkFinal - ctx := testAppContext(config, bp, nil) + ctx := testContext() run(t, ctx, config) @@ -629,43 +1122,44 @@ func TestAppSdkVersion(t *testing.T) { } func TestJNIABI(t *testing.T) { - ctx := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+` + ctx, _ := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+` cc_library { name: "libjni", system_shared_libs: [], + sdk_version: "current", stl: "none", } android_test { name: "test", - no_framework_libs: true, + sdk_version: "core_platform", jni_libs: ["libjni"], } android_test { name: "test_first", - no_framework_libs: true, + sdk_version: "core_platform", compile_multilib: "first", jni_libs: ["libjni"], } android_test { name: "test_both", - no_framework_libs: true, + sdk_version: "core_platform", compile_multilib: "both", jni_libs: ["libjni"], } android_test { name: "test_32", - no_framework_libs: true, + sdk_version: "core_platform", compile_multilib: "32", jni_libs: ["libjni"], } android_test { name: "test_64", - no_framework_libs: true, + sdk_version: "core_platform", compile_multilib: "64", jni_libs: ["libjni"], } @@ -701,53 +1195,95 @@ func TestJNIABI(t *testing.T) { } } +func TestAppSdkVersionByPartition(t *testing.T) { + testJavaError(t, "sdk_version must have a value when the module is located at vendor or product", ` + android_app { + name: "foo", + srcs: ["a.java"], + vendor: true, + platform_apis: true, + } + `) + + testJava(t, ` + android_app { + name: "bar", + srcs: ["b.java"], + platform_apis: true, + } + `) + + for _, enforce := range []bool{true, false} { + bp := ` + android_app { + name: "foo", + srcs: ["a.java"], + product_specific: true, + platform_apis: true, + } + ` + + config := testAppConfig(nil, bp, nil) + config.TestProductVariables.EnforceProductPartitionInterface = proptools.BoolPtr(enforce) + if enforce { + testJavaErrorWithConfig(t, "sdk_version must have a value when the module is located at vendor or product", config) + } else { + testJavaWithConfig(t, config) + } + } +} + func TestJNIPackaging(t *testing.T) { - ctx := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+` + ctx, _ := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+` cc_library { name: "libjni", system_shared_libs: [], stl: "none", + sdk_version: "current", } android_app { name: "app", jni_libs: ["libjni"], + sdk_version: "current", } android_app { name: "app_noembed", jni_libs: ["libjni"], use_embedded_native_libs: false, + sdk_version: "current", } android_app { name: "app_embed", jni_libs: ["libjni"], use_embedded_native_libs: true, + sdk_version: "current", } android_test { name: "test", - no_framework_libs: true, + sdk_version: "current", jni_libs: ["libjni"], } android_test { name: "test_noembed", - no_framework_libs: true, + sdk_version: "current", jni_libs: ["libjni"], use_embedded_native_libs: false, } android_test_helper_app { name: "test_helper", - no_framework_libs: true, + sdk_version: "current", jni_libs: ["libjni"], } android_test_helper_app { name: "test_helper_noembed", - no_framework_libs: true, + sdk_version: "current", jni_libs: ["libjni"], use_embedded_native_libs: false, } @@ -779,9 +1315,129 @@ func TestJNIPackaging(t *testing.T) { if g, w := !strings.Contains(jniLibZip.Args["jarArgs"], "-L 0"), test.compressed; g != w { t.Errorf("expected jni compressed %v, got %v", w, g) } + + if !strings.Contains(jniLibZip.Implicits[0].String(), "_sdk_") { + t.Errorf("expected input %q to use sdk variant", jniLibZip.Implicits[0].String()) + } } }) } +} + +func TestJNISDK(t *testing.T) { + ctx, _ := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+` + cc_library { + name: "libjni", + system_shared_libs: [], + stl: "none", + sdk_version: "current", + } + + android_test { + name: "app_platform", + jni_libs: ["libjni"], + platform_apis: true, + } + + android_test { + name: "app_sdk", + jni_libs: ["libjni"], + sdk_version: "current", + } + + android_test { + name: "app_force_platform", + jni_libs: ["libjni"], + sdk_version: "current", + jni_uses_platform_apis: true, + } + + android_test { + name: "app_force_sdk", + jni_libs: ["libjni"], + platform_apis: true, + jni_uses_sdk_apis: true, + } + + cc_library { + name: "libvendorjni", + system_shared_libs: [], + stl: "none", + vendor: true, + } + + android_test { + name: "app_vendor", + jni_libs: ["libvendorjni"], + sdk_version: "current", + vendor: true, + } + `) + + testCases := []struct { + name string + sdkJNI bool + vendorJNI bool + }{ + {name: "app_platform"}, + {name: "app_sdk", sdkJNI: true}, + {name: "app_force_platform"}, + {name: "app_force_sdk", sdkJNI: true}, + {name: "app_vendor", vendorJNI: true}, + } + + platformJNI := ctx.ModuleForTests("libjni", "android_arm64_armv8-a_shared"). + Output("libjni.so").Output.String() + sdkJNI := ctx.ModuleForTests("libjni", "android_arm64_armv8-a_sdk_shared"). + Output("libjni.so").Output.String() + vendorJNI := ctx.ModuleForTests("libvendorjni", "android_arm64_armv8-a_shared"). + Output("libvendorjni.so").Output.String() + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + app := ctx.ModuleForTests(test.name, "android_common") + + jniLibZip := app.MaybeOutput("jnilibs.zip") + if len(jniLibZip.Implicits) != 1 { + t.Fatalf("expected exactly one jni library, got %q", jniLibZip.Implicits.Strings()) + } + gotJNI := jniLibZip.Implicits[0].String() + + if test.sdkJNI { + if gotJNI != sdkJNI { + t.Errorf("expected SDK JNI library %q, got %q", sdkJNI, gotJNI) + } + } else if test.vendorJNI { + if gotJNI != vendorJNI { + t.Errorf("expected platform JNI library %q, got %q", vendorJNI, gotJNI) + } + } else { + if gotJNI != platformJNI { + t.Errorf("expected platform JNI library %q, got %q", platformJNI, gotJNI) + } + } + }) + } + + t.Run("jni_uses_platform_apis_error", func(t *testing.T) { + testJavaError(t, `jni_uses_platform_apis: can only be set for modules that set sdk_version`, ` + android_test { + name: "app_platform", + platform_apis: true, + jni_uses_platform_apis: true, + } + `) + }) + + t.Run("jni_uses_sdk_apis_error", func(t *testing.T) { + testJavaError(t, `jni_uses_sdk_apis: can only be set for modules that do not set sdk_version`, ` + android_test { + name: "app_sdk", + sdk_version: "current", + jni_uses_sdk_apis: true, + } + `) + }) } @@ -790,7 +1446,8 @@ func TestCertificates(t *testing.T) { name string bp string certificateOverride string - expected string + expectedLineage string + expectedCertificate string }{ { name: "default", @@ -798,10 +1455,12 @@ func TestCertificates(t *testing.T) { android_app { name: "foo", srcs: ["a.java"], + sdk_version: "current", } `, certificateOverride: "", - expected: "build/target/product/security/testkey.x509.pem build/target/product/security/testkey.pk8", + expectedLineage: "", + expectedCertificate: "build/make/target/product/security/testkey.x509.pem build/make/target/product/security/testkey.pk8", }, { name: "module certificate property", @@ -809,16 +1468,18 @@ func TestCertificates(t *testing.T) { android_app { name: "foo", srcs: ["a.java"], - certificate: ":new_certificate" + certificate: ":new_certificate", + sdk_version: "current", } android_app_certificate { name: "new_certificate", - certificate: "cert/new_cert", + certificate: "cert/new_cert", } `, certificateOverride: "", - expected: "cert/new_cert.x509.pem cert/new_cert.pk8", + expectedLineage: "", + expectedCertificate: "cert/new_cert.x509.pem cert/new_cert.pk8", }, { name: "path certificate property", @@ -826,11 +1487,13 @@ func TestCertificates(t *testing.T) { android_app { name: "foo", srcs: ["a.java"], - certificate: "expiredkey" + certificate: "expiredkey", + sdk_version: "current", } `, certificateOverride: "", - expected: "build/target/product/security/expiredkey.x509.pem build/target/product/security/expiredkey.pk8", + expectedLineage: "", + expectedCertificate: "build/make/target/product/security/expiredkey.x509.pem build/make/target/product/security/expiredkey.pk8", }, { name: "certificate overrides", @@ -838,32 +1501,119 @@ func TestCertificates(t *testing.T) { android_app { name: "foo", srcs: ["a.java"], - certificate: "expiredkey" + certificate: "expiredkey", + sdk_version: "current", } android_app_certificate { name: "new_certificate", - certificate: "cert/new_cert", + certificate: "cert/new_cert", } `, certificateOverride: "foo:new_certificate", - expected: "cert/new_cert.x509.pem cert/new_cert.pk8", + expectedLineage: "", + expectedCertificate: "cert/new_cert.x509.pem cert/new_cert.pk8", + }, + { + name: "certificate lineage", + bp: ` + android_app { + name: "foo", + srcs: ["a.java"], + certificate: ":new_certificate", + lineage: "lineage.bin", + sdk_version: "current", + } + + android_app_certificate { + name: "new_certificate", + certificate: "cert/new_cert", + } + `, + certificateOverride: "", + expectedLineage: "--lineage lineage.bin", + expectedCertificate: "cert/new_cert.x509.pem cert/new_cert.pk8", }, } for _, test := range testCases { t.Run(test.name, func(t *testing.T) { - config := testConfig(nil) + config := testAppConfig(nil, test.bp, nil) if test.certificateOverride != "" { config.TestProductVariables.CertificateOverrides = []string{test.certificateOverride} } - ctx := testAppContext(config, test.bp, nil) + ctx := testContext() + + run(t, ctx, config) + foo := ctx.ModuleForTests("foo", "android_common") + + signapk := foo.Output("foo.apk") + signCertificateFlags := signapk.Args["certificates"] + if test.expectedCertificate != signCertificateFlags { + t.Errorf("Incorrect signing flags, expected: %q, got: %q", test.expectedCertificate, signCertificateFlags) + } + + signFlags := signapk.Args["flags"] + if test.expectedLineage != signFlags { + t.Errorf("Incorrect signing flags, expected: %q, got: %q", test.expectedLineage, signFlags) + } + }) + } +} + +func TestRequestV4SigningFlag(t *testing.T) { + testCases := []struct { + name string + bp string + expected string + }{ + { + name: "default", + bp: ` + android_app { + name: "foo", + srcs: ["a.java"], + sdk_version: "current", + } + `, + expected: "", + }, + { + name: "default", + bp: ` + android_app { + name: "foo", + srcs: ["a.java"], + sdk_version: "current", + v4_signature: false, + } + `, + expected: "", + }, + { + name: "module certificate property", + bp: ` + android_app { + name: "foo", + srcs: ["a.java"], + sdk_version: "current", + v4_signature: true, + } + `, + expected: "--enable-v4", + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + config := testAppConfig(nil, test.bp, nil) + ctx := testContext() run(t, ctx, config) foo := ctx.ModuleForTests("foo", "android_common") signapk := foo.Output("foo.apk") - signFlags := signapk.Args["certificates"] + signFlags := signapk.Args["flags"] if test.expected != signFlags { t.Errorf("Incorrect signing flags, expected: %q, got: %q", test.expected, signFlags) } @@ -884,6 +1634,7 @@ func TestPackageNameOverride(t *testing.T) { android_app { name: "foo", srcs: ["a.java"], + sdk_version: "current", } `, packageNameOverride: "", @@ -898,12 +1649,13 @@ func TestPackageNameOverride(t *testing.T) { android_app { name: "foo", srcs: ["a.java"], + sdk_version: "current", } `, packageNameOverride: "foo:bar", expected: []string{ // The package apk should be still be the original name for test dependencies. - buildDir + "/.intermediates/foo/android_common/foo.apk", + buildDir + "/.intermediates/foo/android_common/bar.apk", buildDir + "/target/product/test_device/system/app/bar/bar.apk", }, }, @@ -911,11 +1663,11 @@ func TestPackageNameOverride(t *testing.T) { for _, test := range testCases { t.Run(test.name, func(t *testing.T) { - config := testConfig(nil) + config := testAppConfig(nil, test.bp, nil) if test.packageNameOverride != "" { config.TestProductVariables.PackageNameOverrides = []string{test.packageNameOverride} } - ctx := testAppContext(config, test.bp, nil) + ctx := testContext() run(t, ctx, config) foo := ctx.ModuleForTests("foo", "android_common") @@ -939,16 +1691,18 @@ func TestInstrumentationTargetOverridden(t *testing.T) { android_app { name: "foo", srcs: ["a.java"], + sdk_version: "current", } android_test { name: "bar", instrumentation_for: "foo", + sdk_version: "current", } ` - config := testConfig(nil) + config := testAppConfig(nil, bp, nil) config.TestProductVariables.ManifestPackageNameOverrides = []string{"foo:org.dandroid.bp"} - ctx := testAppContext(config, bp, nil) + ctx := testContext() run(t, ctx, config) @@ -962,18 +1716,21 @@ func TestInstrumentationTargetOverridden(t *testing.T) { } func TestOverrideAndroidApp(t *testing.T) { - ctx := testJava(t, ` + ctx, _ := testJava(t, ` android_app { name: "foo", srcs: ["a.java"], certificate: "expiredkey", - overrides: ["baz"], + overrides: ["qux"], + sdk_version: "current", } override_android_app { name: "bar", base: "foo", certificate: ":new_certificate", + lineage: "lineage.bin", + logging_parent: "bah", } android_app_certificate { @@ -989,33 +1746,45 @@ func TestOverrideAndroidApp(t *testing.T) { `) expectedVariants := []struct { - variantName string - apkName string - apkPath string - signFlag string - overrides []string - aaptFlag string + moduleName string + variantName string + apkName string + apkPath string + certFlag string + lineageFlag string + overrides []string + aaptFlag string + logging_parent string }{ { - variantName: "android_common", - apkPath: "/target/product/test_device/system/app/foo/foo.apk", - signFlag: "build/target/product/security/expiredkey.x509.pem build/target/product/security/expiredkey.pk8", - overrides: []string{"baz"}, - aaptFlag: "", + moduleName: "foo", + variantName: "android_common", + apkPath: "/target/product/test_device/system/app/foo/foo.apk", + certFlag: "build/make/target/product/security/expiredkey.x509.pem build/make/target/product/security/expiredkey.pk8", + lineageFlag: "", + overrides: []string{"qux"}, + aaptFlag: "", + logging_parent: "", }, { - variantName: "bar_android_common", - apkPath: "/target/product/test_device/system/app/bar/bar.apk", - signFlag: "cert/new_cert.x509.pem cert/new_cert.pk8", - overrides: []string{"baz", "foo"}, - aaptFlag: "", + moduleName: "bar", + variantName: "android_common_bar", + apkPath: "/target/product/test_device/system/app/bar/bar.apk", + certFlag: "cert/new_cert.x509.pem cert/new_cert.pk8", + lineageFlag: "--lineage lineage.bin", + overrides: []string{"qux", "foo"}, + aaptFlag: "", + logging_parent: "bah", }, { - variantName: "baz_android_common", - apkPath: "/target/product/test_device/system/app/baz/baz.apk", - signFlag: "build/target/product/security/expiredkey.x509.pem build/target/product/security/expiredkey.pk8", - overrides: []string{"baz", "foo"}, - aaptFlag: "--rename-manifest-package org.dandroid.bp", + moduleName: "baz", + variantName: "android_common_baz", + apkPath: "/target/product/test_device/system/app/baz/baz.apk", + certFlag: "build/make/target/product/security/expiredkey.x509.pem build/make/target/product/security/expiredkey.pk8", + lineageFlag: "", + overrides: []string{"qux", "foo"}, + aaptFlag: "--rename-manifest-package org.dandroid.bp", + logging_parent: "", }, } for _, expected := range expectedVariants { @@ -1036,10 +1805,16 @@ func TestOverrideAndroidApp(t *testing.T) { } // Check the certificate paths - signapk := variant.Output("foo.apk") - signFlag := signapk.Args["certificates"] - if expected.signFlag != signFlag { - t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected.signFlag, signFlag) + signapk := variant.Output(expected.moduleName + ".apk") + certFlag := signapk.Args["certificates"] + if expected.certFlag != certFlag { + t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected.certFlag, certFlag) + } + + // Check the lineage flags + lineageFlag := signapk.Args["flags"] + if expected.lineageFlag != lineageFlag { + t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected.lineageFlag, lineageFlag) } // Check if the overrides field values are correctly aggregated. @@ -1049,6 +1824,13 @@ func TestOverrideAndroidApp(t *testing.T) { expected.overrides, mod.appProperties.Overrides) } + // Test Overridable property: Logging_parent + logging_parent := mod.aapt.LoggingParent + if expected.logging_parent != logging_parent { + t.Errorf("Incorrect overrides property value for logging parent, expected: %q, got: %q", + expected.logging_parent, logging_parent) + } + // Check the package renaming flag, if exists. res := variant.Output("package-res.apk") aapt2Flags := res.Args["flags"] @@ -1058,8 +1840,889 @@ func TestOverrideAndroidApp(t *testing.T) { } } +func TestOverrideAndroidAppDependency(t *testing.T) { + ctx, _ := testJava(t, ` + android_app { + name: "foo", + srcs: ["a.java"], + sdk_version: "current", + } + + override_android_app { + name: "bar", + base: "foo", + package_name: "org.dandroid.bp", + } + + android_test { + name: "baz", + srcs: ["b.java"], + instrumentation_for: "foo", + } + + android_test { + name: "qux", + srcs: ["b.java"], + instrumentation_for: "bar", + } + `) + + // Verify baz, which depends on the overridden module foo, has the correct classpath javac arg. + javac := ctx.ModuleForTests("baz", "android_common").Rule("javac") + fooTurbine := filepath.Join(buildDir, ".intermediates", "foo", "android_common", "turbine-combined", "foo.jar") + if !strings.Contains(javac.Args["classpath"], fooTurbine) { + t.Errorf("baz classpath %v does not contain %q", javac.Args["classpath"], fooTurbine) + } + + // Verify qux, which depends on the overriding module bar, has the correct classpath javac arg. + javac = ctx.ModuleForTests("qux", "android_common").Rule("javac") + barTurbine := filepath.Join(buildDir, ".intermediates", "foo", "android_common_bar", "turbine-combined", "foo.jar") + if !strings.Contains(javac.Args["classpath"], barTurbine) { + t.Errorf("qux classpath %v does not contain %q", javac.Args["classpath"], barTurbine) + } +} + +func TestOverrideAndroidTest(t *testing.T) { + ctx, _ := testJava(t, ` + android_app { + name: "foo", + srcs: ["a.java"], + package_name: "com.android.foo", + sdk_version: "current", + } + + override_android_app { + name: "bar", + base: "foo", + package_name: "com.android.bar", + } + + android_test { + name: "foo_test", + srcs: ["b.java"], + instrumentation_for: "foo", + } + + override_android_test { + name: "bar_test", + base: "foo_test", + package_name: "com.android.bar.test", + instrumentation_for: "bar", + instrumentation_target_package: "com.android.bar", + } + `) + + expectedVariants := []struct { + moduleName string + variantName string + apkPath string + overrides []string + targetVariant string + packageFlag string + targetPackageFlag string + }{ + { + variantName: "android_common", + apkPath: "/target/product/test_device/testcases/foo_test/arm64/foo_test.apk", + overrides: nil, + targetVariant: "android_common", + packageFlag: "", + targetPackageFlag: "", + }, + { + variantName: "android_common_bar_test", + apkPath: "/target/product/test_device/testcases/bar_test/arm64/bar_test.apk", + overrides: []string{"foo_test"}, + targetVariant: "android_common_bar", + packageFlag: "com.android.bar.test", + targetPackageFlag: "com.android.bar", + }, + } + for _, expected := range expectedVariants { + variant := ctx.ModuleForTests("foo_test", expected.variantName) + + // Check the final apk name + outputs := variant.AllOutputs() + expectedApkPath := buildDir + expected.apkPath + found := false + for _, o := range outputs { + if o == expectedApkPath { + found = true + break + } + } + if !found { + t.Errorf("Can't find %q in output files.\nAll outputs:%v", expectedApkPath, outputs) + } + + // Check if the overrides field values are correctly aggregated. + mod := variant.Module().(*AndroidTest) + if !reflect.DeepEqual(expected.overrides, mod.appProperties.Overrides) { + t.Errorf("Incorrect overrides property value, expected: %q, got: %q", + expected.overrides, mod.appProperties.Overrides) + } + + // Check if javac classpath has the correct jar file path. This checks instrumentation_for overrides. + javac := variant.Rule("javac") + turbine := filepath.Join(buildDir, ".intermediates", "foo", expected.targetVariant, "turbine-combined", "foo.jar") + if !strings.Contains(javac.Args["classpath"], turbine) { + t.Errorf("classpath %q does not contain %q", javac.Args["classpath"], turbine) + } + + // Check aapt2 flags. + res := variant.Output("package-res.apk") + aapt2Flags := res.Args["flags"] + checkAapt2LinkFlag(t, aapt2Flags, "rename-manifest-package", expected.packageFlag) + checkAapt2LinkFlag(t, aapt2Flags, "rename-instrumentation-target-package", expected.targetPackageFlag) + } +} + +func TestAndroidTest_FixTestConfig(t *testing.T) { + ctx, _ := testJava(t, ` + android_app { + name: "foo", + srcs: ["a.java"], + package_name: "com.android.foo", + sdk_version: "current", + } + + android_test { + name: "foo_test", + srcs: ["b.java"], + instrumentation_for: "foo", + } + + android_test { + name: "bar_test", + srcs: ["b.java"], + package_name: "com.android.bar.test", + instrumentation_for: "foo", + } + + override_android_test { + name: "baz_test", + base: "foo_test", + package_name: "com.android.baz.test", + } + `) + + testCases := []struct { + moduleName string + variantName string + expectedFlags []string + }{ + { + moduleName: "foo_test", + variantName: "android_common", + }, + { + moduleName: "bar_test", + variantName: "android_common", + expectedFlags: []string{ + "--manifest " + buildDir + "/.intermediates/bar_test/android_common/manifest_fixer/AndroidManifest.xml", + "--package-name com.android.bar.test", + }, + }, + { + moduleName: "foo_test", + variantName: "android_common_baz_test", + expectedFlags: []string{ + "--manifest " + buildDir + + "/.intermediates/foo_test/android_common_baz_test/manifest_fixer/AndroidManifest.xml", + "--package-name com.android.baz.test", + "--test-file-name baz_test.apk", + }, + }, + } + + for _, test := range testCases { + variant := ctx.ModuleForTests(test.moduleName, test.variantName) + params := variant.MaybeOutput("test_config_fixer/AndroidTest.xml") + + if len(test.expectedFlags) > 0 { + if params.Rule == nil { + t.Errorf("test_config_fixer was expected to run, but didn't") + } else { + for _, flag := range test.expectedFlags { + if !strings.Contains(params.RuleParams.Command, flag) { + t.Errorf("Flag %q was not found in command: %q", flag, params.RuleParams.Command) + } + } + } + } else { + if params.Rule != nil { + t.Errorf("test_config_fixer was not expected to run, but did: %q", params.RuleParams.Command) + } + } + + } +} + +func TestAndroidAppImport(t *testing.T) { + ctx, _ := testJava(t, ` + android_app_import { + name: "foo", + apk: "prebuilts/apk/app.apk", + certificate: "platform", + dex_preopt: { + enabled: true, + }, + } + `) + + variant := ctx.ModuleForTests("foo", "android_common") + + // Check dexpreopt outputs. + if variant.MaybeOutput("dexpreopt/oat/arm64/package.vdex").Rule == nil || + variant.MaybeOutput("dexpreopt/oat/arm64/package.odex").Rule == nil { + t.Errorf("can't find dexpreopt outputs") + } + + // Check cert signing flag. + signedApk := variant.Output("signed/foo.apk") + signingFlag := signedApk.Args["certificates"] + expected := "build/make/target/product/security/platform.x509.pem build/make/target/product/security/platform.pk8" + if expected != signingFlag { + t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected, signingFlag) + } +} + +func TestAndroidAppImport_NoDexPreopt(t *testing.T) { + ctx, _ := testJava(t, ` + android_app_import { + name: "foo", + apk: "prebuilts/apk/app.apk", + certificate: "platform", + dex_preopt: { + enabled: false, + }, + } + `) + + variant := ctx.ModuleForTests("foo", "android_common") + + // Check dexpreopt outputs. They shouldn't exist. + if variant.MaybeOutput("dexpreopt/oat/arm64/package.vdex").Rule != nil || + variant.MaybeOutput("dexpreopt/oat/arm64/package.odex").Rule != nil { + t.Errorf("dexpreopt shouldn't have run.") + } +} + +func TestAndroidAppImport_Presigned(t *testing.T) { + ctx, _ := testJava(t, ` + android_app_import { + name: "foo", + apk: "prebuilts/apk/app.apk", + presigned: true, + dex_preopt: { + enabled: true, + }, + } + `) + + variant := ctx.ModuleForTests("foo", "android_common") + + // Check dexpreopt outputs. + if variant.MaybeOutput("dexpreopt/oat/arm64/package.vdex").Rule == nil || + variant.MaybeOutput("dexpreopt/oat/arm64/package.odex").Rule == nil { + t.Errorf("can't find dexpreopt outputs") + } + // Make sure signing was skipped and aligning was done. + if variant.MaybeOutput("signed/foo.apk").Rule != nil { + t.Errorf("signing rule shouldn't be included.") + } + if variant.MaybeOutput("zip-aligned/foo.apk").Rule == nil { + t.Errorf("can't find aligning rule") + } +} + +func TestAndroidAppImport_SigningLineage(t *testing.T) { + ctx, _ := testJava(t, ` + android_app_import { + name: "foo", + apk: "prebuilts/apk/app.apk", + certificate: "platform", + lineage: "lineage.bin", + } + `) + + variant := ctx.ModuleForTests("foo", "android_common") + + // Check cert signing lineage flag. + signedApk := variant.Output("signed/foo.apk") + signingFlag := signedApk.Args["flags"] + expected := "--lineage lineage.bin" + if expected != signingFlag { + t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected, signingFlag) + } +} + +func TestAndroidAppImport_DefaultDevCert(t *testing.T) { + ctx, _ := testJava(t, ` + android_app_import { + name: "foo", + apk: "prebuilts/apk/app.apk", + default_dev_cert: true, + dex_preopt: { + enabled: true, + }, + } + `) + + variant := ctx.ModuleForTests("foo", "android_common") + + // Check dexpreopt outputs. + if variant.MaybeOutput("dexpreopt/oat/arm64/package.vdex").Rule == nil || + variant.MaybeOutput("dexpreopt/oat/arm64/package.odex").Rule == nil { + t.Errorf("can't find dexpreopt outputs") + } + + // Check cert signing flag. + signedApk := variant.Output("signed/foo.apk") + signingFlag := signedApk.Args["certificates"] + expected := "build/make/target/product/security/testkey.x509.pem build/make/target/product/security/testkey.pk8" + if expected != signingFlag { + t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected, signingFlag) + } +} + +func TestAndroidAppImport_DpiVariants(t *testing.T) { + bp := ` + android_app_import { + name: "foo", + apk: "prebuilts/apk/app.apk", + dpi_variants: { + xhdpi: { + apk: "prebuilts/apk/app_xhdpi.apk", + }, + xxhdpi: { + apk: "prebuilts/apk/app_xxhdpi.apk", + }, + }, + presigned: true, + dex_preopt: { + enabled: true, + }, + } + ` + testCases := []struct { + name string + aaptPreferredConfig *string + aaptPrebuiltDPI []string + expected string + }{ + { + name: "no preferred", + aaptPreferredConfig: nil, + aaptPrebuiltDPI: []string{}, + expected: "prebuilts/apk/app.apk", + }, + { + name: "AAPTPreferredConfig matches", + aaptPreferredConfig: proptools.StringPtr("xhdpi"), + aaptPrebuiltDPI: []string{"xxhdpi", "ldpi"}, + expected: "prebuilts/apk/app_xhdpi.apk", + }, + { + name: "AAPTPrebuiltDPI matches", + aaptPreferredConfig: proptools.StringPtr("mdpi"), + aaptPrebuiltDPI: []string{"xxhdpi", "xhdpi"}, + expected: "prebuilts/apk/app_xxhdpi.apk", + }, + { + name: "non-first AAPTPrebuiltDPI matches", + aaptPreferredConfig: proptools.StringPtr("mdpi"), + aaptPrebuiltDPI: []string{"ldpi", "xhdpi"}, + expected: "prebuilts/apk/app_xhdpi.apk", + }, + { + name: "no matches", + aaptPreferredConfig: proptools.StringPtr("mdpi"), + aaptPrebuiltDPI: []string{"ldpi", "xxxhdpi"}, + expected: "prebuilts/apk/app.apk", + }, + } + + jniRuleRe := regexp.MustCompile("^if \\(zipinfo (\\S+)") + for _, test := range testCases { + config := testAppConfig(nil, bp, nil) + config.TestProductVariables.AAPTPreferredConfig = test.aaptPreferredConfig + config.TestProductVariables.AAPTPrebuiltDPI = test.aaptPrebuiltDPI + ctx := testContext() + + run(t, ctx, config) + + variant := ctx.ModuleForTests("foo", "android_common") + jniRuleCommand := variant.Output("jnis-uncompressed/foo.apk").RuleParams.Command + matches := jniRuleRe.FindStringSubmatch(jniRuleCommand) + if len(matches) != 2 { + t.Errorf("failed to extract the src apk path from %q", jniRuleCommand) + } + if test.expected != matches[1] { + t.Errorf("wrong src apk, expected: %q got: %q", test.expected, matches[1]) + } + } +} + +func TestAndroidAppImport_Filename(t *testing.T) { + ctx, config := testJava(t, ` + android_app_import { + name: "foo", + apk: "prebuilts/apk/app.apk", + presigned: true, + } + + android_app_import { + name: "bar", + apk: "prebuilts/apk/app.apk", + presigned: true, + filename: "bar_sample.apk" + } + `) + + testCases := []struct { + name string + expected string + }{ + { + name: "foo", + expected: "foo.apk", + }, + { + name: "bar", + expected: "bar_sample.apk", + }, + } + + for _, test := range testCases { + variant := ctx.ModuleForTests(test.name, "android_common") + if variant.MaybeOutput(test.expected).Rule == nil { + t.Errorf("can't find output named %q - all outputs: %v", test.expected, variant.AllOutputs()) + } + + a := variant.Module().(*AndroidAppImport) + expectedValues := []string{test.expected} + actualValues := android.AndroidMkEntriesForTest( + t, config, "", a)[0].EntryMap["LOCAL_INSTALLED_MODULE_STEM"] + if !reflect.DeepEqual(actualValues, expectedValues) { + t.Errorf("Incorrect LOCAL_INSTALLED_MODULE_STEM value '%s', expected '%s'", + actualValues, expectedValues) + } + } +} + +func TestAndroidAppImport_ArchVariants(t *testing.T) { + // The test config's target arch is ARM64. + testCases := []struct { + name string + bp string + expected string + }{ + { + name: "matching arch", + bp: ` + android_app_import { + name: "foo", + apk: "prebuilts/apk/app.apk", + arch: { + arm64: { + apk: "prebuilts/apk/app_arm64.apk", + }, + }, + presigned: true, + dex_preopt: { + enabled: true, + }, + } + `, + expected: "prebuilts/apk/app_arm64.apk", + }, + { + name: "no matching arch", + bp: ` + android_app_import { + name: "foo", + apk: "prebuilts/apk/app.apk", + arch: { + arm: { + apk: "prebuilts/apk/app_arm.apk", + }, + }, + presigned: true, + dex_preopt: { + enabled: true, + }, + } + `, + expected: "prebuilts/apk/app.apk", + }, + } + + jniRuleRe := regexp.MustCompile("^if \\(zipinfo (\\S+)") + for _, test := range testCases { + ctx, _ := testJava(t, test.bp) + + variant := ctx.ModuleForTests("foo", "android_common") + jniRuleCommand := variant.Output("jnis-uncompressed/foo.apk").RuleParams.Command + matches := jniRuleRe.FindStringSubmatch(jniRuleCommand) + if len(matches) != 2 { + t.Errorf("failed to extract the src apk path from %q", jniRuleCommand) + } + if test.expected != matches[1] { + t.Errorf("wrong src apk, expected: %q got: %q", test.expected, matches[1]) + } + } +} + +func TestAndroidTestImport(t *testing.T) { + ctx, config := testJava(t, ` + android_test_import { + name: "foo", + apk: "prebuilts/apk/app.apk", + presigned: true, + data: [ + "testdata/data", + ], + } + `) + + test := ctx.ModuleForTests("foo", "android_common").Module().(*AndroidTestImport) + + // Check android mks. + entries := android.AndroidMkEntriesForTest(t, config, "", test)[0] + expected := []string{"tests"} + actual := entries.EntryMap["LOCAL_MODULE_TAGS"] + if !reflect.DeepEqual(expected, actual) { + t.Errorf("Unexpected module tags - expected: %q, actual: %q", expected, actual) + } + expected = []string{"testdata/data:testdata/data"} + actual = entries.EntryMap["LOCAL_COMPATIBILITY_SUPPORT_FILES"] + if !reflect.DeepEqual(expected, actual) { + t.Errorf("Unexpected test data - expected: %q, actual: %q", expected, actual) + } +} + +func TestAndroidTestImport_NoJinUncompressForPresigned(t *testing.T) { + ctx, _ := testJava(t, ` + android_test_import { + name: "foo", + apk: "prebuilts/apk/app.apk", + certificate: "cert/new_cert", + data: [ + "testdata/data", + ], + } + + android_test_import { + name: "foo_presigned", + apk: "prebuilts/apk/app.apk", + presigned: true, + data: [ + "testdata/data", + ], + } + `) + + variant := ctx.ModuleForTests("foo", "android_common") + jniRule := variant.Output("jnis-uncompressed/foo.apk").RuleParams.Command + if !strings.HasPrefix(jniRule, "if (zipinfo") { + t.Errorf("Unexpected JNI uncompress rule command: " + jniRule) + } + + variant = ctx.ModuleForTests("foo_presigned", "android_common") + jniRule = variant.Output("jnis-uncompressed/foo_presigned.apk").BuildParams.Rule.String() + if jniRule != android.Cp.String() { + t.Errorf("Unexpected JNI uncompress rule: " + jniRule) + } + if variant.MaybeOutput("zip-aligned/foo_presigned.apk").Rule == nil { + t.Errorf("Presigned test apk should be aligned") + } +} + +func TestAndroidTestImport_Preprocessed(t *testing.T) { + ctx, _ := testJava(t, ` + android_test_import { + name: "foo", + apk: "prebuilts/apk/app.apk", + presigned: true, + preprocessed: true, + } + + android_test_import { + name: "foo_cert", + apk: "prebuilts/apk/app.apk", + certificate: "cert/new_cert", + preprocessed: true, + } + `) + + testModules := []string{"foo", "foo_cert"} + for _, m := range testModules { + apkName := m + ".apk" + variant := ctx.ModuleForTests(m, "android_common") + jniRule := variant.Output("jnis-uncompressed/" + apkName).BuildParams.Rule.String() + if jniRule != android.Cp.String() { + t.Errorf("Unexpected JNI uncompress rule: " + jniRule) + } + + // Make sure signing and aligning were skipped. + if variant.MaybeOutput("signed/"+apkName).Rule != nil { + t.Errorf("signing rule shouldn't be included for preprocessed.") + } + if variant.MaybeOutput("zip-aligned/"+apkName).Rule != nil { + t.Errorf("aligning rule shouldn't be for preprocessed") + } + } +} + +func TestStl(t *testing.T) { + ctx, _ := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+` + cc_library { + name: "libjni", + sdk_version: "current", + stl: "c++_shared", + } + + android_test { + name: "stl", + jni_libs: ["libjni"], + compile_multilib: "both", + sdk_version: "current", + stl: "c++_shared", + } + + android_test { + name: "system", + jni_libs: ["libjni"], + compile_multilib: "both", + sdk_version: "current", + } + `) + + testCases := []struct { + name string + jnis []string + }{ + {"stl", + []string{ + "libjni.so", + "libc++_shared.so", + }, + }, + {"system", + []string{ + "libjni.so", + }, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + app := ctx.ModuleForTests(test.name, "android_common") + jniLibZip := app.Output("jnilibs.zip") + var jnis []string + args := strings.Fields(jniLibZip.Args["jarArgs"]) + for i := 0; i < len(args); i++ { + if args[i] == "-f" { + jnis = append(jnis, args[i+1]) + i += 1 + } + } + jnisJoined := strings.Join(jnis, " ") + for _, jni := range test.jnis { + if !strings.Contains(jnisJoined, jni) { + t.Errorf("missing jni %q in %q", jni, jnis) + } + } + }) + } +} + +func TestUsesLibraries(t *testing.T) { + bp := ` + java_sdk_library { + name: "foo", + srcs: ["a.java"], + api_packages: ["foo"], + sdk_version: "current", + } + + java_sdk_library { + name: "qux", + srcs: ["a.java"], + api_packages: ["qux"], + sdk_version: "current", + } + + java_sdk_library { + name: "quuz", + srcs: ["a.java"], + api_packages: ["quuz"], + sdk_version: "current", + } + + java_sdk_library { + name: "bar", + srcs: ["a.java"], + api_packages: ["bar"], + sdk_version: "current", + } + + android_app { + name: "app", + srcs: ["a.java"], + libs: ["qux", "quuz.stubs"], + uses_libs: ["foo"], + sdk_version: "current", + optional_uses_libs: [ + "bar", + "baz", + ], + } + + android_app_import { + name: "prebuilt", + apk: "prebuilts/apk/app.apk", + certificate: "platform", + uses_libs: ["foo"], + optional_uses_libs: [ + "bar", + "baz", + ], + } + ` + + config := testAppConfig(nil, bp, nil) + config.TestProductVariables.MissingUsesLibraries = []string{"baz"} + + ctx := testContext() + + run(t, ctx, config) + + app := ctx.ModuleForTests("app", "android_common") + prebuilt := ctx.ModuleForTests("prebuilt", "android_common") + + // Test that implicit dependencies on java_sdk_library instances are passed to the manifest. + manifestFixerArgs := app.Output("manifest_fixer/AndroidManifest.xml").Args["args"] + if w := "--uses-library qux"; !strings.Contains(manifestFixerArgs, w) { + t.Errorf("unexpected manifest_fixer args: wanted %q in %q", w, manifestFixerArgs) + } + if w := "--uses-library quuz"; !strings.Contains(manifestFixerArgs, w) { + t.Errorf("unexpected manifest_fixer args: wanted %q in %q", w, manifestFixerArgs) + } + + // Test that all libraries are verified + cmd := app.Rule("verify_uses_libraries").RuleParams.Command + if w := "--uses-library foo"; !strings.Contains(cmd, w) { + t.Errorf("wanted %q in %q", w, cmd) + } + + if w := "--optional-uses-library bar --optional-uses-library baz"; !strings.Contains(cmd, w) { + t.Errorf("wanted %q in %q", w, cmd) + } + + cmd = prebuilt.Rule("verify_uses_libraries").RuleParams.Command + + if w := `uses_library_names="foo"`; !strings.Contains(cmd, w) { + t.Errorf("wanted %q in %q", w, cmd) + } + + if w := `optional_uses_library_names="bar baz"`; !strings.Contains(cmd, w) { + t.Errorf("wanted %q in %q", w, cmd) + } + + // Test that only present libraries are preopted + cmd = app.Rule("dexpreopt").RuleParams.Command + + if w := `dex_preopt_target_libraries="/system/framework/foo.jar /system/framework/bar.jar"`; !strings.Contains(cmd, w) { + t.Errorf("wanted %q in %q", w, cmd) + } + + cmd = prebuilt.Rule("dexpreopt").RuleParams.Command + + if w := `dex_preopt_target_libraries="/system/framework/foo.jar /system/framework/bar.jar"`; !strings.Contains(cmd, w) { + t.Errorf("wanted %q in %q", w, cmd) + } +} + +func TestCodelessApp(t *testing.T) { + testCases := []struct { + name string + bp string + noCode bool + }{ + { + name: "normal", + bp: ` + android_app { + name: "foo", + srcs: ["a.java"], + sdk_version: "current", + } + `, + noCode: false, + }, + { + name: "app without sources", + bp: ` + android_app { + name: "foo", + sdk_version: "current", + } + `, + noCode: true, + }, + { + name: "app with libraries", + bp: ` + android_app { + name: "foo", + static_libs: ["lib"], + sdk_version: "current", + } + + java_library { + name: "lib", + srcs: ["a.java"], + sdk_version: "current", + } + `, + noCode: false, + }, + { + name: "app with sourceless libraries", + bp: ` + android_app { + name: "foo", + static_libs: ["lib"], + sdk_version: "current", + } + + java_library { + name: "lib", + sdk_version: "current", + } + `, + // TODO(jungjw): this should probably be true + noCode: false, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + ctx := testApp(t, test.bp) + + foo := ctx.ModuleForTests("foo", "android_common") + manifestFixerArgs := foo.Output("manifest_fixer/AndroidManifest.xml").Args["args"] + if strings.Contains(manifestFixerArgs, "--has-no-code") != test.noCode { + t.Errorf("unexpected manifest_fixer args: %q", manifestFixerArgs) + } + }) + } +} + func TestEmbedNotice(t *testing.T) { - ctx := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+` + ctx, _ := testJavaWithFS(t, cc.GatherRequiredDepsForTest(android.Android)+` android_app { name: "foo", srcs: ["a.java"], @@ -1067,6 +2730,7 @@ func TestEmbedNotice(t *testing.T) { jni_libs: ["libjni"], notice: "APP_NOTICE", embed_notices: true, + sdk_version: "current", } // No embed_notice flag @@ -1075,6 +2739,7 @@ func TestEmbedNotice(t *testing.T) { srcs: ["a.java"], jni_libs: ["libjni"], notice: "APP_NOTICE", + sdk_version: "current", } // No NOTICE files @@ -1082,6 +2747,7 @@ func TestEmbedNotice(t *testing.T) { name: "baz", srcs: ["a.java"], embed_notices: true, + sdk_version: "current", } cc_library { @@ -1089,6 +2755,7 @@ func TestEmbedNotice(t *testing.T) { system_shared_libs: [], stl: "none", notice: "LIB_NOTICE", + sdk_version: "current", } java_library { @@ -1096,6 +2763,7 @@ func TestEmbedNotice(t *testing.T) { srcs: [ ":gen", ], + sdk_version: "current", } genrule { @@ -1110,7 +2778,12 @@ func TestEmbedNotice(t *testing.T) { srcs: ["b.java"], notice: "TOOL_NOTICE", } - `) + `, map[string][]byte{ + "APP_NOTICE": nil, + "GENRULE_NOTICE": nil, + "LIB_NOTICE": nil, + "TOOL_NOTICE": nil, + }) // foo has NOTICE files to process, and embed_notices is true. foo := ctx.ModuleForTests("foo", "android_common") @@ -1140,16 +2813,20 @@ func TestEmbedNotice(t *testing.T) { // bar has NOTICE files to process, but embed_notices is not set. bar := ctx.ModuleForTests("bar", "android_common") - mergeNotices = bar.MaybeRule("mergeNoticesRule") - if mergeNotices.Rule != nil { - t.Errorf("mergeNotices shouldn't have run for bar") + res = bar.Output("package-res.apk") + aapt2Flags = res.Args["flags"] + e = "-A " + buildDir + "/.intermediates/bar/android_common/NOTICE" + if strings.Contains(aapt2Flags, e) { + t.Errorf("bar shouldn't have the asset dir flag for NOTICE: %q", e) } // baz's embed_notice is true, but it doesn't have any NOTICE files. baz := ctx.ModuleForTests("baz", "android_common") - mergeNotices = baz.MaybeRule("mergeNoticesRule") - if mergeNotices.Rule != nil { - t.Errorf("mergeNotices shouldn't have run for baz") + res = baz.Output("package-res.apk") + aapt2Flags = res.Args["flags"] + e = "-A " + buildDir + "/.intermediates/baz/android_common/NOTICE" + if strings.Contains(aapt2Flags, e) { + t.Errorf("baz shouldn't have the asset dir flag for NOTICE: %q", e) } } @@ -1167,6 +2844,7 @@ func TestUncompressDex(t *testing.T) { android_app { name: "foo", srcs: ["a.java"], + sdk_version: "current", } `, uncompressedPlatform: true, @@ -1179,6 +2857,7 @@ func TestUncompressDex(t *testing.T) { name: "foo", use_embedded_dex: true, srcs: ["a.java"], + sdk_version: "current", } `, uncompressedPlatform: true, @@ -1191,22 +2870,49 @@ func TestUncompressDex(t *testing.T) { name: "foo", privileged: true, srcs: ["a.java"], + sdk_version: "current", + } + `, + uncompressedPlatform: true, + uncompressedUnbundled: true, + }, + { + name: "normal_uncompress_dex_true", + bp: ` + android_app { + name: "foo", + srcs: ["a.java"], + sdk_version: "current", + uncompress_dex: true, } `, uncompressedPlatform: true, uncompressedUnbundled: true, }, + { + name: "normal_uncompress_dex_false", + bp: ` + android_app { + name: "foo", + srcs: ["a.java"], + sdk_version: "current", + uncompress_dex: false, + } + `, + uncompressedPlatform: false, + uncompressedUnbundled: false, + }, } test := func(t *testing.T, bp string, want bool, unbundled bool) { t.Helper() - config := testConfig(nil) + config := testAppConfig(nil, bp, nil) if unbundled { config.TestProductVariables.Unbundled_build = proptools.BoolPtr(true) } - ctx := testAppContext(config, bp, nil) + ctx := testContext() run(t, ctx, config) @@ -1235,3 +2941,255 @@ func TestUncompressDex(t *testing.T) { }) } } + +func checkAapt2LinkFlag(t *testing.T, aapt2Flags, flagName, expectedValue string) { + if expectedValue != "" { + expectedFlag := "--" + flagName + " " + expectedValue + if !strings.Contains(aapt2Flags, expectedFlag) { + t.Errorf("%q is missing in aapt2 link flags, %q", expectedFlag, aapt2Flags) + } + } else { + unexpectedFlag := "--" + flagName + if strings.Contains(aapt2Flags, unexpectedFlag) { + t.Errorf("unexpected flag, %q is found in aapt2 link flags, %q", unexpectedFlag, aapt2Flags) + } + } +} + +func TestRuntimeResourceOverlay(t *testing.T) { + fs := map[string][]byte{ + "baz/res/res/values/strings.xml": nil, + "bar/res/res/values/strings.xml": nil, + } + bp := ` + runtime_resource_overlay { + name: "foo", + certificate: "platform", + lineage: "lineage.bin", + product_specific: true, + static_libs: ["bar"], + resource_libs: ["baz"], + aaptflags: ["--keep-raw-values"], + } + + runtime_resource_overlay { + name: "foo_themed", + certificate: "platform", + product_specific: true, + theme: "faza", + overrides: ["foo"], + } + + android_library { + name: "bar", + resource_dirs: ["bar/res"], + } + + android_app { + name: "baz", + sdk_version: "current", + resource_dirs: ["baz/res"], + } + ` + config := testAppConfig(nil, bp, fs) + ctx := testContext() + run(t, ctx, config) + + m := ctx.ModuleForTests("foo", "android_common") + + // Check AAPT2 link flags. + aapt2Flags := m.Output("package-res.apk").Args["flags"] + expectedFlags := []string{"--keep-raw-values", "--no-resource-deduping", "--no-resource-removal"} + absentFlags := android.RemoveListFromList(expectedFlags, strings.Split(aapt2Flags, " ")) + if len(absentFlags) > 0 { + t.Errorf("expected values, %q are missing in aapt2 link flags, %q", absentFlags, aapt2Flags) + } + + // Check overlay.list output for static_libs dependency. + overlayList := m.Output("aapt2/overlay.list").Inputs.Strings() + staticLibPackage := buildDir + "/.intermediates/bar/android_common/package-res.apk" + if !inList(staticLibPackage, overlayList) { + t.Errorf("Stactic lib res package %q missing in overlay list: %q", staticLibPackage, overlayList) + } + + // Check AAPT2 link flags for resource_libs dependency. + resourceLibFlag := "-I " + buildDir + "/.intermediates/baz/android_common/package-res.apk" + if !strings.Contains(aapt2Flags, resourceLibFlag) { + t.Errorf("Resource lib flag %q missing in aapt2 link flags: %q", resourceLibFlag, aapt2Flags) + } + + // Check cert signing flag. + signedApk := m.Output("signed/foo.apk") + lineageFlag := signedApk.Args["flags"] + expectedLineageFlag := "--lineage lineage.bin" + if expectedLineageFlag != lineageFlag { + t.Errorf("Incorrect signing lineage flags, expected: %q, got: %q", expectedLineageFlag, lineageFlag) + } + signingFlag := signedApk.Args["certificates"] + expected := "build/make/target/product/security/platform.x509.pem build/make/target/product/security/platform.pk8" + if expected != signingFlag { + t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected, signingFlag) + } + androidMkEntries := android.AndroidMkEntriesForTest(t, config, "", m.Module())[0] + path := androidMkEntries.EntryMap["LOCAL_CERTIFICATE"] + expectedPath := []string{"build/make/target/product/security/platform.x509.pem"} + if !reflect.DeepEqual(path, expectedPath) { + t.Errorf("Unexpected LOCAL_CERTIFICATE value: %v, expected: %v", path, expectedPath) + } + + // Check device location. + path = androidMkEntries.EntryMap["LOCAL_MODULE_PATH"] + expectedPath = []string{"/tmp/target/product/test_device/product/overlay"} + if !reflect.DeepEqual(path, expectedPath) { + t.Errorf("Unexpected LOCAL_MODULE_PATH value: %v, expected: %v", path, expectedPath) + } + + // A themed module has a different device location + m = ctx.ModuleForTests("foo_themed", "android_common") + androidMkEntries = android.AndroidMkEntriesForTest(t, config, "", m.Module())[0] + path = androidMkEntries.EntryMap["LOCAL_MODULE_PATH"] + expectedPath = []string{"/tmp/target/product/test_device/product/overlay/faza"} + if !reflect.DeepEqual(path, expectedPath) { + t.Errorf("Unexpected LOCAL_MODULE_PATH value: %v, expected: %v", path, expectedPath) + } + + overrides := androidMkEntries.EntryMap["LOCAL_OVERRIDES_PACKAGES"] + expectedOverrides := []string{"foo"} + if !reflect.DeepEqual(overrides, expectedOverrides) { + t.Errorf("Unexpected LOCAL_OVERRIDES_PACKAGES value: %v, expected: %v", overrides, expectedOverrides) + } +} + +func TestRuntimeResourceOverlay_JavaDefaults(t *testing.T) { + ctx, config := testJava(t, ` + java_defaults { + name: "rro_defaults", + theme: "default_theme", + product_specific: true, + aaptflags: ["--keep-raw-values"], + } + + runtime_resource_overlay { + name: "foo_with_defaults", + defaults: ["rro_defaults"], + } + + runtime_resource_overlay { + name: "foo_barebones", + } + `) + + // + // RRO module with defaults + // + m := ctx.ModuleForTests("foo_with_defaults", "android_common") + + // Check AAPT2 link flags. + aapt2Flags := strings.Split(m.Output("package-res.apk").Args["flags"], " ") + expectedFlags := []string{"--keep-raw-values", "--no-resource-deduping", "--no-resource-removal"} + absentFlags := android.RemoveListFromList(expectedFlags, aapt2Flags) + if len(absentFlags) > 0 { + t.Errorf("expected values, %q are missing in aapt2 link flags, %q", absentFlags, aapt2Flags) + } + + // Check device location. + path := android.AndroidMkEntriesForTest(t, config, "", m.Module())[0].EntryMap["LOCAL_MODULE_PATH"] + expectedPath := []string{"/tmp/target/product/test_device/product/overlay/default_theme"} + if !reflect.DeepEqual(path, expectedPath) { + t.Errorf("Unexpected LOCAL_MODULE_PATH value: %q, expected: %q", path, expectedPath) + } + + // + // RRO module without defaults + // + m = ctx.ModuleForTests("foo_barebones", "android_common") + + // Check AAPT2 link flags. + aapt2Flags = strings.Split(m.Output("package-res.apk").Args["flags"], " ") + unexpectedFlags := "--keep-raw-values" + if inList(unexpectedFlags, aapt2Flags) { + t.Errorf("unexpected value, %q is present in aapt2 link flags, %q", unexpectedFlags, aapt2Flags) + } + + // Check device location. + path = android.AndroidMkEntriesForTest(t, config, "", m.Module())[0].EntryMap["LOCAL_MODULE_PATH"] + expectedPath = []string{"/tmp/target/product/test_device/system/overlay"} + if !reflect.DeepEqual(path, expectedPath) { + t.Errorf("Unexpected LOCAL_MODULE_PATH value: %v, expected: %v", path, expectedPath) + } +} + +func TestOverrideRuntimeResourceOverlay(t *testing.T) { + ctx, _ := testJava(t, ` + runtime_resource_overlay { + name: "foo_overlay", + certificate: "platform", + product_specific: true, + sdk_version: "current", + } + + override_runtime_resource_overlay { + name: "bar_overlay", + base: "foo_overlay", + package_name: "com.android.bar.overlay", + target_package_name: "com.android.bar", + } + `) + + expectedVariants := []struct { + moduleName string + variantName string + apkPath string + overrides []string + targetVariant string + packageFlag string + targetPackageFlag string + }{ + { + variantName: "android_common", + apkPath: "/target/product/test_device/product/overlay/foo_overlay.apk", + overrides: nil, + targetVariant: "android_common", + packageFlag: "", + targetPackageFlag: "", + }, + { + variantName: "android_common_bar_overlay", + apkPath: "/target/product/test_device/product/overlay/bar_overlay.apk", + overrides: []string{"foo_overlay"}, + targetVariant: "android_common_bar", + packageFlag: "com.android.bar.overlay", + targetPackageFlag: "com.android.bar", + }, + } + for _, expected := range expectedVariants { + variant := ctx.ModuleForTests("foo_overlay", expected.variantName) + + // Check the final apk name + outputs := variant.AllOutputs() + expectedApkPath := buildDir + expected.apkPath + found := false + for _, o := range outputs { + if o == expectedApkPath { + found = true + break + } + } + if !found { + t.Errorf("Can't find %q in output files.\nAll outputs:%v", expectedApkPath, outputs) + } + + // Check if the overrides field values are correctly aggregated. + mod := variant.Module().(*RuntimeResourceOverlay) + if !reflect.DeepEqual(expected.overrides, mod.properties.Overrides) { + t.Errorf("Incorrect overrides property value, expected: %q, got: %q", + expected.overrides, mod.properties.Overrides) + } + + // Check aapt2 flags. + res := variant.Output("package-res.apk") + aapt2Flags := res.Args["flags"] + checkAapt2LinkFlag(t, aapt2Flags, "rename-manifest-package", expected.packageFlag) + checkAapt2LinkFlag(t, aapt2Flags, "rename-overlay-target-package", expected.targetPackageFlag) + } +} diff --git a/java/builder.go b/java/builder.go index e5ac7b00a..7318fcbad 100644 --- a/java/builder.go +++ b/java/builder.go @@ -27,6 +27,7 @@ import ( "github.com/google/blueprint/proptools" "android/soong/android" + "android/soong/remoteexec" ) var ( @@ -38,16 +39,18 @@ var ( // this, all java rules write into separate directories and then are combined into a .jar file // (if the rule produces .class files) or a .srcjar file (if the rule produces .java files). // .srcjar files are unzipped into a temporary directory when compiled with javac. - javac = pctx.AndroidRemoteStaticRule("javac", android.RemoteRuleSupports{Goma: true, RBE: true, RBEFlag: android.RBE_JAVAC}, + // TODO(b/143658984): goma can't handle the --system argument to javac. + javac, javacRE = remoteexec.MultiCommandStaticRules(pctx, "javac", blueprint.RuleParams{ Command: `rm -rf "$outDir" "$annoDir" "$srcJarDir" && mkdir -p "$outDir" "$annoDir" "$srcJarDir" && ` + `${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` + `(if [ -s $srcJarDir/list ] || [ -s $out.rsp ] ; then ` + - `${config.SoongJavacWrapper} ${config.JavacWrapper}${config.JavacCmd} ${config.JavacHeapFlags} ${config.CommonJdkFlags} ` + + `${config.SoongJavacWrapper} $javaTemplate${config.JavacCmd} ` + + `${config.JavacHeapFlags} ${config.JavacVmFlags} ${config.CommonJdkFlags} ` + `$processorpath $processor $javacFlags $bootClasspath $classpath ` + `-source $javaVersion -target $javaVersion ` + `-d $outDir -s $annoDir @$out.rsp @$srcJarDir/list ; fi ) && ` + - `${config.SoongZipCmd} -jar -o $out -C $outDir -D $outDir && ` + + `$zipTemplate${config.SoongZipCmd} -jar -o $out -C $outDir -D $outDir && ` + `rm -rf "$srcJarDir"`, CommandDeps: []string{ "${config.JavacCmd}", @@ -57,6 +60,55 @@ var ( CommandOrderOnly: []string{"${config.SoongJavacWrapper}"}, Rspfile: "$out.rsp", RspfileContent: "$in", + }, map[string]*remoteexec.REParams{ + "$javaTemplate": &remoteexec.REParams{ + Labels: map[string]string{"type": "compile", "lang": "java", "compiler": "javac"}, + ExecStrategy: "${config.REJavacExecStrategy}", + Platform: map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"}, + }, + "$zipTemplate": &remoteexec.REParams{ + Labels: map[string]string{"type": "tool", "name": "soong_zip"}, + Inputs: []string{"${config.SoongZipCmd}", "$outDir"}, + OutputFiles: []string{"$out"}, + ExecStrategy: "${config.REJavacExecStrategy}", + Platform: map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"}, + }, + }, []string{"javacFlags", "bootClasspath", "classpath", "processorpath", "processor", "srcJars", "srcJarDir", + "outDir", "annoDir", "javaVersion"}, nil) + + _ = pctx.VariableFunc("kytheCorpus", + func(ctx android.PackageVarContext) string { return ctx.Config().XrefCorpusName() }) + _ = pctx.VariableFunc("kytheCuEncoding", + func(ctx android.PackageVarContext) string { return ctx.Config().XrefCuEncoding() }) + _ = pctx.SourcePathVariable("kytheVnames", "build/soong/vnames.json") + // Run it with -add-opens=java.base/java.nio=ALL-UNNAMED to avoid JDK9's warning about + // "Illegal reflective access by com.google.protobuf.Utf8$UnsafeProcessor ... + // to field java.nio.Buffer.address" + kytheExtract = pctx.AndroidStaticRule("kythe", + blueprint.RuleParams{ + Command: `${config.ZipSyncCmd} -d $srcJarDir ` + + `-l $srcJarDir/list -f "*.java" $srcJars && ` + + `( [ ! -s $srcJarDir/list -a ! -s $out.rsp ] || ` + + `KYTHE_ROOT_DIRECTORY=. KYTHE_OUTPUT_FILE=$out ` + + `KYTHE_CORPUS=${kytheCorpus} ` + + `KYTHE_VNAMES=${kytheVnames} ` + + `KYTHE_KZIP_ENCODING=${kytheCuEncoding} ` + + `${config.SoongJavacWrapper} ${config.JavaCmd} ` + + `--add-opens=java.base/java.nio=ALL-UNNAMED ` + + `-jar ${config.JavaKytheExtractorJar} ` + + `${config.JavacHeapFlags} ${config.CommonJdkFlags} ` + + `$processorpath $processor $javacFlags $bootClasspath $classpath ` + + `-source $javaVersion -target $javaVersion ` + + `-d $outDir -s $annoDir @$out.rsp @$srcJarDir/list)`, + CommandDeps: []string{ + "${config.JavaCmd}", + "${config.JavaKytheExtractorJar}", + "${kytheVnames}", + "${config.ZipSyncCmd}", + }, + CommandOrderOnly: []string{"${config.SoongJavacWrapper}"}, + Rspfile: "$out.rsp", + RspfileContent: "$in", }, "javacFlags", "bootClasspath", "classpath", "processorpath", "processor", "srcJars", "srcJarDir", "outDir", "annoDir", "javaVersion") @@ -74,10 +126,10 @@ var ( }, "abis", "allow-prereleased", "screen-densities", "sdk-version", "stem", "apkcerts", "partition") - turbine = pctx.AndroidStaticRule("turbine", + turbine, turbineRE = remoteexec.StaticRules(pctx, "turbine", blueprint.RuleParams{ Command: `rm -rf "$outDir" && mkdir -p "$outDir" && ` + - `${config.JavaCmd} -jar ${config.TurbineJar} --output $out.tmp ` + + `$reTemplate${config.JavaCmd} ${config.JavaVmFlags} -jar ${config.TurbineJar} --output $out.tmp ` + `--temp_dir "$outDir" --sources @$out.rsp --source_jars $srcJars ` + `--javacopts ${config.CommonJdkFlags} ` + `$javacFlags -source $javaVersion -target $javaVersion -- $bootClasspath $classpath && ` + @@ -92,25 +144,45 @@ var ( RspfileContent: "$in", Restat: true, }, - "javacFlags", "bootClasspath", "classpath", "srcJars", "outDir", "javaVersion") - - jar = pctx.AndroidStaticRule("jar", + &remoteexec.REParams{Labels: map[string]string{"type": "tool", "name": "turbine"}, + ExecStrategy: "${config.RETurbineExecStrategy}", + Inputs: []string{"${config.TurbineJar}", "${out}.rsp", "$implicits"}, + RSPFile: "${out}.rsp", + OutputFiles: []string{"$out.tmp"}, + OutputDirectories: []string{"$outDir"}, + ToolchainInputs: []string{"${config.JavaCmd}"}, + Platform: map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"}, + }, []string{"javacFlags", "bootClasspath", "classpath", "srcJars", "outDir", "javaVersion"}, []string{"implicits"}) + + jar, jarRE = remoteexec.StaticRules(pctx, "jar", blueprint.RuleParams{ - Command: `${config.SoongZipCmd} -jar -o $out @$out.rsp`, + Command: `$reTemplate${config.SoongZipCmd} -jar -o $out @$out.rsp`, CommandDeps: []string{"${config.SoongZipCmd}"}, Rspfile: "$out.rsp", RspfileContent: "$jarArgs", }, - "jarArgs") - - zip = pctx.AndroidStaticRule("zip", + &remoteexec.REParams{ + ExecStrategy: "${config.REJarExecStrategy}", + Inputs: []string{"${config.SoongZipCmd}", "${out}.rsp"}, + RSPFile: "${out}.rsp", + OutputFiles: []string{"$out"}, + Platform: map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"}, + }, []string{"jarArgs"}, nil) + + zip, zipRE = remoteexec.StaticRules(pctx, "zip", blueprint.RuleParams{ Command: `${config.SoongZipCmd} -o $out @$out.rsp`, CommandDeps: []string{"${config.SoongZipCmd}"}, Rspfile: "$out.rsp", RspfileContent: "$jarArgs", }, - "jarArgs") + &remoteexec.REParams{ + ExecStrategy: "${config.REZipExecStrategy}", + Inputs: []string{"${config.SoongZipCmd}", "${out}.rsp", "$implicits"}, + RSPFile: "${out}.rsp", + OutputFiles: []string{"$out"}, + Platform: map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"}, + }, []string{"jarArgs"}, []string{"implicits"}) combineJar = pctx.AndroidStaticRule("combineJar", blueprint.RuleParams{ @@ -121,7 +193,12 @@ var ( jarjar = pctx.AndroidStaticRule("jarjar", blueprint.RuleParams{ - Command: "${config.JavaCmd} -jar ${config.JarjarCmd} process $rulesFile $in $out", + Command: "${config.JavaCmd} ${config.JavaVmFlags}" + + // b/146418363 Enable Android specific jarjar transformer to drop compat annotations + // for newly repackaged classes. Dropping @UnsupportedAppUsage on repackaged classes + // avoids adding new hiddenapis after jarjar'ing. + " -DremoveAndroidCompatAnnotations=true" + + " -jar ${config.JarjarCmd} process $rulesFile $in $out", CommandDeps: []string{"${config.JavaCmd}", "${config.JarjarCmd}", "$rulesFile"}, }, "rulesFile") @@ -137,7 +214,7 @@ var ( jetifier = pctx.AndroidStaticRule("jetifier", blueprint.RuleParams{ - Command: "${config.JavaCmd} -jar ${config.JetifierJar} -l error -o $out -i $in", + Command: "${config.JavaCmd} ${config.JavaVmFlags} -jar ${config.JetifierJar} -l error -o $out -i $in", CommandDeps: []string{"${config.JavaCmd}", "${config.JetifierJar}"}, }, ) @@ -157,18 +234,20 @@ var ( func init() { pctx.Import("android/soong/android") pctx.Import("android/soong/java/config") + pctx.Import("android/soong/remoteexec") } type javaBuilderFlags struct { - javacFlags string - bootClasspath classpath - classpath classpath - processorPath classpath - processor string - systemModules classpath - aidlFlags string - aidlDeps android.Paths - javaVersion string + javacFlags string + bootClasspath classpath + classpath classpath + java9Classpath classpath + processorPath classpath + processors []string + systemModules *systemModules + aidlFlags string + aidlDeps android.Paths + javaVersion javaVersion errorProneExtraJavacFlags string errorProneProcessorPath classpath @@ -208,37 +287,115 @@ func RunErrorProne(ctx android.ModuleContext, outputFile android.WritablePath, "errorprone", "errorprone") } +// Emits the rule to generate Xref input file (.kzip file) for the given set of source files and source jars +// to compile with given set of builder flags, etc. +func emitXrefRule(ctx android.ModuleContext, xrefFile android.WritablePath, idx int, + srcFiles, srcJars android.Paths, + flags javaBuilderFlags, deps android.Paths) { + + deps = append(deps, srcJars...) + classpath := flags.classpath + + var bootClasspath string + if flags.javaVersion.usesJavaModules() { + var systemModuleDeps android.Paths + bootClasspath, systemModuleDeps = flags.systemModules.FormJavaSystemModulesPath(ctx.Device()) + deps = append(deps, systemModuleDeps...) + classpath = append(flags.java9Classpath, classpath...) + } else { + deps = append(deps, flags.bootClasspath...) + if len(flags.bootClasspath) == 0 && ctx.Device() { + // explicitly specify -bootclasspath "" if the bootclasspath is empty to + // ensure java does not fall back to the default bootclasspath. + bootClasspath = `-bootclasspath ""` + } else { + bootClasspath = flags.bootClasspath.FormJavaClassPath("-bootclasspath") + } + } + + deps = append(deps, classpath...) + deps = append(deps, flags.processorPath...) + + processor := "-proc:none" + if len(flags.processors) > 0 { + processor = "-processor " + strings.Join(flags.processors, ",") + } + + intermediatesDir := "xref" + if idx >= 0 { + intermediatesDir += strconv.Itoa(idx) + } + + ctx.Build(pctx, + android.BuildParams{ + Rule: kytheExtract, + Description: "Xref Java extractor", + Output: xrefFile, + Inputs: srcFiles, + Implicits: deps, + Args: map[string]string{ + "annoDir": android.PathForModuleOut(ctx, intermediatesDir, "anno").String(), + "bootClasspath": bootClasspath, + "classpath": classpath.FormJavaClassPath("-classpath"), + "javacFlags": flags.javacFlags, + "javaVersion": flags.javaVersion.String(), + "outDir": android.PathForModuleOut(ctx, "javac", "classes.xref").String(), + "processorpath": flags.processorPath.FormJavaClassPath("-processorpath"), + "processor": processor, + "srcJarDir": android.PathForModuleOut(ctx, intermediatesDir, "srcjars.xref").String(), + "srcJars": strings.Join(srcJars.Strings(), " "), + }, + }) +} + func TransformJavaToHeaderClasses(ctx android.ModuleContext, outputFile android.WritablePath, srcFiles, srcJars android.Paths, flags javaBuilderFlags) { var deps android.Paths deps = append(deps, srcJars...) - deps = append(deps, flags.bootClasspath...) - deps = append(deps, flags.classpath...) + + classpath := flags.classpath var bootClasspath string - if len(flags.bootClasspath) == 0 && ctx.Device() { - // explicitly specify -bootclasspath "" if the bootclasspath is empty to - // ensure java does not fall back to the default bootclasspath. - bootClasspath = `--bootclasspath ""` + if flags.javaVersion.usesJavaModules() { + var systemModuleDeps android.Paths + bootClasspath, systemModuleDeps = flags.systemModules.FormTurbineSystemModulesPath(ctx.Device()) + deps = append(deps, systemModuleDeps...) + classpath = append(flags.java9Classpath, classpath...) } else { - bootClasspath = strings.Join(flags.bootClasspath.FormTurbineClasspath("--bootclasspath "), " ") + deps = append(deps, flags.bootClasspath...) + if len(flags.bootClasspath) == 0 && ctx.Device() { + // explicitly specify -bootclasspath "" if the bootclasspath is empty to + // ensure turbine does not fall back to the default bootclasspath. + bootClasspath = `--bootclasspath ""` + } else { + bootClasspath = flags.bootClasspath.FormTurbineClassPath("--bootclasspath ") + } } + deps = append(deps, classpath...) + deps = append(deps, flags.processorPath...) + + rule := turbine + args := map[string]string{ + "javacFlags": flags.javacFlags, + "bootClasspath": bootClasspath, + "srcJars": strings.Join(srcJars.Strings(), " "), + "classpath": classpath.FormTurbineClassPath("--classpath "), + "outDir": android.PathForModuleOut(ctx, "turbine", "classes").String(), + "javaVersion": flags.javaVersion.String(), + } + if ctx.Config().IsEnvTrue("RBE_TURBINE") { + rule = turbineRE + args["implicits"] = strings.Join(deps.Strings(), ",") + } ctx.Build(pctx, android.BuildParams{ - Rule: turbine, + Rule: rule, Description: "turbine", Output: outputFile, Inputs: srcFiles, Implicits: deps, - Args: map[string]string{ - "javacFlags": flags.javacFlags, - "bootClasspath": bootClasspath, - "srcJars": strings.Join(srcJars.Strings(), " "), - "classpath": strings.Join(flags.classpath.FormTurbineClasspath("--classpath "), " "), - "outDir": android.PathForModuleOut(ctx, "turbine", "classes").String(), - "javaVersion": flags.javaVersion, - }, + Args: args, }) } @@ -258,10 +415,14 @@ func transformJavaToClasses(ctx android.ModuleContext, outputFile android.Writab deps = append(deps, srcJars...) + classpath := flags.classpath + var bootClasspath string - if flags.javaVersion == "1.9" { - deps = append(deps, flags.systemModules...) - bootClasspath = flags.systemModules.FormJavaSystemModulesPath("--system=", ctx.Device()) + if flags.javaVersion.usesJavaModules() { + var systemModuleDeps android.Paths + bootClasspath, systemModuleDeps = flags.systemModules.FormJavaSystemModulesPath(ctx.Device()) + deps = append(deps, systemModuleDeps...) + classpath = append(flags.java9Classpath, classpath...) } else { deps = append(deps, flags.bootClasspath...) if len(flags.bootClasspath) == 0 && ctx.Device() { @@ -273,12 +434,12 @@ func transformJavaToClasses(ctx android.ModuleContext, outputFile android.Writab } } - deps = append(deps, flags.classpath...) + deps = append(deps, classpath...) deps = append(deps, flags.processorPath...) processor := "-proc:none" - if flags.processor != "" { - processor = "-processor " + flags.processor + if len(flags.processors) > 0 { + processor = "-processor " + strings.Join(flags.processors, ",") } srcJarDir := "srcjars" @@ -290,8 +451,12 @@ func transformJavaToClasses(ctx android.ModuleContext, outputFile android.Writab outDir = filepath.Join(shardDir, outDir) annoDir = filepath.Join(shardDir, annoDir) } + rule := javac + if ctx.Config().IsEnvTrue("RBE_JAVAC") { + rule = javacRE + } ctx.Build(pctx, android.BuildParams{ - Rule: javac, + Rule: rule, Description: desc, Output: outputFile, Inputs: srcFiles, @@ -299,14 +464,14 @@ func transformJavaToClasses(ctx android.ModuleContext, outputFile android.Writab Args: map[string]string{ "javacFlags": flags.javacFlags, "bootClasspath": bootClasspath, - "classpath": flags.classpath.FormJavaClassPath("-classpath"), + "classpath": classpath.FormJavaClassPath("-classpath"), "processorpath": flags.processorPath.FormJavaClassPath("-processorpath"), "processor": processor, "srcJars": strings.Join(srcJars.Strings(), " "), "srcJarDir": android.PathForModuleOut(ctx, intermediatesDir, srcJarDir).String(), "outDir": android.PathForModuleOut(ctx, intermediatesDir, outDir).String(), "annoDir": android.PathForModuleOut(ctx, intermediatesDir, annoDir).String(), - "javaVersion": flags.javaVersion, + "javaVersion": flags.javaVersion.String(), }, }) } @@ -314,8 +479,12 @@ func transformJavaToClasses(ctx android.ModuleContext, outputFile android.Writab func TransformResourcesToJar(ctx android.ModuleContext, outputFile android.WritablePath, jarArgs []string, deps android.Paths) { + rule := jar + if ctx.Config().IsEnvTrue("RBE_JAR") { + rule = jarRE + } ctx.Build(pctx, android.BuildParams{ - Rule: jar, + Rule: rule, Description: "jar", Output: outputFile, Implicits: deps, @@ -422,35 +591,28 @@ func TransformZipAlign(ctx android.ModuleContext, outputFile android.WritablePat }) } -type classpath []android.Path +type classpath android.Paths -func (x *classpath) FormJavaClassPath(optName string) string { +func (x *classpath) formJoinedClassPath(optName string, sep string) string { if optName != "" && !strings.HasSuffix(optName, "=") && !strings.HasSuffix(optName, " ") { optName += " " } if len(*x) > 0 { - return optName + strings.Join(x.Strings(), ":") + return optName + strings.Join(x.Strings(), sep) } else { return "" } } +func (x *classpath) FormJavaClassPath(optName string) string { + return x.formJoinedClassPath(optName, ":") +} -// Returns a --system argument in the form javac expects with -source 1.9. If forceEmpty is true, -// returns --system=none if the list is empty to ensure javac does not fall back to the default -// system modules. -func (x *classpath) FormJavaSystemModulesPath(optName string, forceEmpty bool) string { - if len(*x) > 1 { - panic("more than one system module") - } else if len(*x) == 1 { - return optName + strings.TrimSuffix((*x)[0].String(), "lib/modules") - } else if forceEmpty { - return optName + "none" - } else { - return "" - } +func (x *classpath) FormTurbineClassPath(optName string) string { + return x.formJoinedClassPath(optName, " ") } -func (x *classpath) FormTurbineClasspath(optName string) []string { +// FormRepeatedClassPath returns a list of arguments with the given optName prefixed to each element of the classpath. +func (x *classpath) FormRepeatedClassPath(optName string) []string { if x == nil || *x == nil { return nil } @@ -477,3 +639,34 @@ func (x *classpath) Strings() []string { } return ret } + +type systemModules struct { + dir android.Path + deps android.Paths +} + +// Returns a --system argument in the form javac expects with -source 1.9 and the list of files to +// depend on. If forceEmpty is true, returns --system=none if the list is empty to ensure javac +// does not fall back to the default system modules. +func (x *systemModules) FormJavaSystemModulesPath(forceEmpty bool) (string, android.Paths) { + if x != nil { + return "--system=" + x.dir.String(), x.deps + } else if forceEmpty { + return "--system=none", nil + } else { + return "", nil + } +} + +// Returns a --system argument in the form turbine expects with -source 1.9 and the list of files to +// depend on. If forceEmpty is true, returns --bootclasspath "" if the list is empty to ensure turbine +// does not fall back to the default bootclasspath. +func (x *systemModules) FormTurbineSystemModulesPath(forceEmpty bool) (string, android.Paths) { + if x != nil { + return "--system " + x.dir.String(), x.deps + } else if forceEmpty { + return `--bootclasspath ""`, nil + } else { + return "", nil + } +} diff --git a/java/config/Android.bp b/java/config/Android.bp new file mode 100644 index 000000000..198352187 --- /dev/null +++ b/java/config/Android.bp @@ -0,0 +1,15 @@ +bootstrap_go_package { + name: "soong-java-config", + pkgPath: "android/soong/java/config", + deps: [ + "blueprint-proptools", + "soong-android", + "soong-remoteexec", + ], + srcs: [ + "config.go", + "error_prone.go", + "kotlin.go", + "makevars.go", + ], +} diff --git a/java/config/config.go b/java/config/config.go index f76a39315..1d0dd617e 100644 --- a/java/config/config.go +++ b/java/config/config.go @@ -22,6 +22,7 @@ import ( _ "github.com/google/blueprint/bootstrap" "android/soong/android" + "android/soong/remoteexec" ) var ( @@ -29,31 +30,43 @@ var ( DefaultBootclasspathLibraries = []string{"core.platform.api.stubs", "core-lambda-stubs"} DefaultSystemModules = "core-platform-api-stubs-system-modules" - DefaultLibraries = []string{"ext", "framework", "updatable_media_stubs"} + DefaultLibraries = []string{"ext", "framework"} DefaultLambdaStubsLibrary = "core-lambda-stubs" SdkLambdaStubsPath = "prebuilts/sdk/tools/core-lambda-stubs.jar" - DefaultJacocoExcludeFilter = []string{"org.junit.*", "org.jacoco.*", "org.mockito.*"} + DefaultMakeJacocoExcludeFilter = []string{"org.junit.*", "org.jacoco.*", "org.mockito.*"} + DefaultJacocoExcludeFilter = []string{"org.junit.**", "org.jacoco.**", "org.mockito.**"} InstrumentFrameworkModules = []string{ "framework", + "framework-minus-apex", "telephony-common", "services", "android.car", "android.car7", "conscrypt", + "core-icu4j", "core-oj", "core-libart", + // TODO: Could this be all updatable bootclasspath jars? "updatable-media", + "framework-mediaprovider", + "framework-sdkextensions", + "android.net.ipsec.ike", } ) +const ( + JavaVmFlags = `-XX:OnError="cat hs_err_pid%p.log" -XX:CICompilerCount=6 -XX:+UseDynamicNumberOfGCThreads` + JavacVmFlags = `-J-XX:OnError="cat hs_err_pid%p.log" -J-XX:CICompilerCount=6 -J-XX:+UseDynamicNumberOfGCThreads` +) + func init() { pctx.Import("github.com/google/blueprint/bootstrap") pctx.StaticVariable("JavacHeapSize", "2048M") pctx.StaticVariable("JavacHeapFlags", "-J-Xmx${JavacHeapSize}") - pctx.StaticVariable("DexFlags", "-JXX:+TieredCompilation -JXX:TieredStopAtLevel=1") + pctx.StaticVariable("DexFlags", "-JXX:OnError='cat hs_err_pid%p.log' -JXX:CICompilerCount=6 -JXX:+UseDynamicNumberOfGCThreads") pctx.StaticVariable("CommonJdkFlags", strings.Join([]string{ `-Xmaxerrs 9999999`, @@ -71,12 +84,21 @@ func init() { `-XDstringConcat=inline`, }, " ")) + pctx.StaticVariable("JavaVmFlags", JavaVmFlags) + pctx.StaticVariable("JavacVmFlags", JavacVmFlags) + pctx.VariableConfigMethod("hostPrebuiltTag", android.Config.PrebuiltOS) pctx.VariableFunc("JavaHome", func(ctx android.PackageVarContext) string { // This is set up and guaranteed by soong_ui return ctx.Config().Getenv("ANDROID_JAVA_HOME") }) + pctx.VariableFunc("JlinkVersion", func(ctx android.PackageVarContext) string { + if override := ctx.Config().Getenv("OVERRIDE_JLINK_VERSION_NUMBER"); override != "" { + return override + } + return "11" + }) pctx.SourcePathVariable("JavaToolchain", "${JavaHome}/bin") pctx.SourcePathVariableWithEnvOverride("JavacCmd", @@ -87,6 +109,7 @@ func init() { pctx.SourcePathVariable("JlinkCmd", "${JavaToolchain}/jlink") pctx.SourcePathVariable("JmodCmd", "${JavaToolchain}/jmod") pctx.SourcePathVariable("JrtFsJar", "${JavaHome}/lib/jrt-fs.jar") + pctx.SourcePathVariable("JavaKytheExtractorJar", "prebuilts/build-tools/common/framework/javac_extractor.jar") pctx.SourcePathVariable("Ziptime", "prebuilts/build-tools/${hostPrebuiltTag}/bin/ziptime") pctx.SourcePathVariable("GenKotlinBuildFileCmd", "build/soong/scripts/gen-kotlin-build-file.sh") @@ -108,7 +131,7 @@ func init() { if ctx.Config().UnbundledBuild() { return "prebuilts/build-tools/common/framework/" + turbine } else { - return pctx.HostJavaToolPath(ctx, turbine).String() + return ctx.Config().HostJavaToolPath(ctx, turbine).String() } }) @@ -118,51 +141,119 @@ func init() { pctx.HostJavaToolVariable("MetalavaJar", "metalava.jar") pctx.HostJavaToolVariable("DokkaJar", "dokka.jar") pctx.HostJavaToolVariable("JetifierJar", "jetifier.jar") + pctx.HostJavaToolVariable("R8Jar", "r8-compat-proguard.jar") + pctx.HostJavaToolVariable("D8Jar", "d8.jar") pctx.HostBinToolVariable("SoongJavacWrapper", "soong_javac_wrapper") pctx.HostBinToolVariable("DexpreoptGen", "dexpreopt_gen") - pctx.VariableFunc("JavacWrapper", func(ctx android.PackageVarContext) string { - if override := ctx.Config().Getenv("JAVAC_WRAPPER"); override != "" { - return override + " " + pctx.VariableFunc("REJavaPool", remoteexec.EnvOverrideFunc("RBE_JAVA_POOL", "java16")) + pctx.VariableFunc("REJavacExecStrategy", remoteexec.EnvOverrideFunc("RBE_JAVAC_EXEC_STRATEGY", remoteexec.RemoteLocalFallbackExecStrategy)) + pctx.VariableFunc("RED8ExecStrategy", remoteexec.EnvOverrideFunc("RBE_D8_EXEC_STRATEGY", remoteexec.RemoteLocalFallbackExecStrategy)) + pctx.VariableFunc("RER8ExecStrategy", remoteexec.EnvOverrideFunc("RBE_R8_EXEC_STRATEGY", remoteexec.RemoteLocalFallbackExecStrategy)) + pctx.VariableFunc("RETurbineExecStrategy", remoteexec.EnvOverrideFunc("RBE_TURBINE_EXEC_STRATEGY", remoteexec.LocalExecStrategy)) + pctx.VariableFunc("RESignApkExecStrategy", remoteexec.EnvOverrideFunc("RBE_SIGNAPK_EXEC_STRATEGY", remoteexec.LocalExecStrategy)) + pctx.VariableFunc("REJarExecStrategy", remoteexec.EnvOverrideFunc("RBE_JAR_EXEC_STRATEGY", remoteexec.LocalExecStrategy)) + pctx.VariableFunc("REZipExecStrategy", remoteexec.EnvOverrideFunc("RBE_ZIP_EXEC_STRATEGY", remoteexec.LocalExecStrategy)) + + pctx.HostJavaToolVariable("JacocoCLIJar", "jacoco-cli.jar") + + pctx.HostBinToolVariable("ManifestCheckCmd", "manifest_check") + pctx.HostBinToolVariable("ManifestFixerCmd", "manifest_fixer") + + pctx.HostBinToolVariable("ManifestMergerCmd", "manifest-merger") + + pctx.HostBinToolVariable("Class2Greylist", "class2greylist") + pctx.HostBinToolVariable("HiddenAPI", "hiddenapi") + + hostBinToolVariableWithSdkToolsPrebuilt("Aapt2Cmd", "aapt2") + hostBinToolVariableWithBuildToolsPrebuilt("AidlCmd", "aidl") + hostBinToolVariableWithBuildToolsPrebuilt("ZipAlign", "zipalign") + + hostJavaToolVariableWithSdkToolsPrebuilt("SignapkCmd", "signapk") + // TODO(ccross): this should come from the signapk dependencies, but we don't have any way + // to express host JNI dependencies yet. + hostJNIToolVariableWithSdkToolsPrebuilt("SignapkJniLibrary", "libconscrypt_openjdk_jni") +} + +func hostBinToolVariableWithSdkToolsPrebuilt(name, tool string) { + pctx.VariableFunc(name, func(ctx android.PackageVarContext) string { + if ctx.Config().UnbundledBuild() || ctx.Config().IsPdkBuild() { + return filepath.Join("prebuilts/sdk/tools", runtime.GOOS, "bin", tool) + } else { + return ctx.Config().HostToolPath(ctx, tool).String() + } + }) +} + +func hostJavaToolVariableWithSdkToolsPrebuilt(name, tool string) { + pctx.VariableFunc(name, func(ctx android.PackageVarContext) string { + if ctx.Config().UnbundledBuild() || ctx.Config().IsPdkBuild() { + return filepath.Join("prebuilts/sdk/tools/lib", tool+".jar") + } else { + return ctx.Config().HostJavaToolPath(ctx, tool+".jar").String() } - return "" }) +} - pctx.VariableFunc("R8Wrapper", func(ctx android.PackageVarContext) string { - if override := ctx.Config().Getenv("R8_WRAPPER"); override != "" { - return override + " " +func hostJNIToolVariableWithSdkToolsPrebuilt(name, tool string) { + pctx.VariableFunc(name, func(ctx android.PackageVarContext) string { + if ctx.Config().UnbundledBuild() || ctx.Config().IsPdkBuild() { + ext := ".so" + if runtime.GOOS == "darwin" { + ext = ".dylib" + } + return filepath.Join("prebuilts/sdk/tools", runtime.GOOS, "lib64", tool+ext) + } else { + return ctx.Config().HostJNIToolPath(ctx, tool).String() } - return "" }) +} - pctx.VariableFunc("D8Wrapper", func(ctx android.PackageVarContext) string { - if override := ctx.Config().Getenv("D8_WRAPPER"); override != "" { - return override + " " +func hostBinToolVariableWithBuildToolsPrebuilt(name, tool string) { + pctx.VariableFunc(name, func(ctx android.PackageVarContext) string { + if ctx.Config().UnbundledBuild() || ctx.Config().IsPdkBuild() { + return filepath.Join("prebuilts/build-tools", ctx.Config().PrebuiltOS(), "bin", tool) + } else { + return ctx.Config().HostToolPath(ctx, tool).String() } - return "" }) +} - pctx.HostJavaToolVariable("JacocoCLIJar", "jacoco-cli.jar") +// JavaCmd returns a SourcePath object with the path to the java command. +func JavaCmd(ctx android.PathContext) android.SourcePath { + return javaTool(ctx, "java") +} - hostBinToolVariableWithPrebuilt := func(name, prebuiltDir, tool string) { - pctx.VariableFunc(name, func(ctx android.PackageVarContext) string { - if ctx.Config().UnbundledBuild() || ctx.Config().IsPdkBuild() { - return filepath.Join(prebuiltDir, runtime.GOOS, "bin", tool) - } else { - return pctx.HostBinToolPath(ctx, tool).String() - } - }) - } +// JavadocCmd returns a SourcePath object with the path to the java command. +func JavadocCmd(ctx android.PathContext) android.SourcePath { + return javaTool(ctx, "javadoc") +} - hostBinToolVariableWithPrebuilt("Aapt2Cmd", "prebuilts/sdk/tools", "aapt2") +func javaTool(ctx android.PathContext, tool string) android.SourcePath { + type javaToolKey string - pctx.SourcePathVariable("ManifestFixerCmd", "build/soong/scripts/manifest_fixer.py") + key := android.NewCustomOnceKey(javaToolKey(tool)) - pctx.HostBinToolVariable("ManifestMergerCmd", "manifest-merger") + return ctx.Config().OnceSourcePath(key, func() android.SourcePath { + return javaToolchain(ctx).Join(ctx, tool) + }) - pctx.HostBinToolVariable("ZipAlign", "zipalign") +} - pctx.HostBinToolVariable("Class2Greylist", "class2greylist") - pctx.HostBinToolVariable("HiddenAPI", "hiddenapi") +var javaToolchainKey = android.NewOnceKey("javaToolchain") + +func javaToolchain(ctx android.PathContext) android.SourcePath { + return ctx.Config().OnceSourcePath(javaToolchainKey, func() android.SourcePath { + return javaHome(ctx).Join(ctx, "bin") + }) +} + +var javaHomeKey = android.NewOnceKey("javaHome") + +func javaHome(ctx android.PathContext) android.SourcePath { + return ctx.Config().OnceSourcePath(javaHomeKey, func() android.SourcePath { + // This is set up and guaranteed by soong_ui + return android.PathForSource(ctx, ctx.Config().Getenv("ANDROID_JAVA_HOME")) + }) } diff --git a/java/config/kotlin.go b/java/config/kotlin.go index 2af7b3c3a..fd8e3dbe9 100644 --- a/java/config/kotlin.go +++ b/java/config/kotlin.go @@ -27,7 +27,12 @@ var ( func init() { pctx.SourcePathVariable("KotlincCmd", "external/kotlinc/bin/kotlinc") pctx.SourcePathVariable("KotlinCompilerJar", "external/kotlinc/lib/kotlin-compiler.jar") + pctx.SourcePathVariable("KotlinPreloaderJar", "external/kotlinc/lib/kotlin-preloader.jar") + pctx.SourcePathVariable("KotlinReflectJar", "external/kotlinc/lib/kotlin-reflect.jar") + pctx.SourcePathVariable("KotlinScriptRuntimeJar", "external/kotlinc/lib/kotlin-script-runtime.jar") + pctx.SourcePathVariable("KotlinTrove4jJar", "external/kotlinc/lib/trove4j.jar") pctx.SourcePathVariable("KotlinKaptJar", "external/kotlinc/lib/kotlin-annotation-processing.jar") + pctx.SourcePathVariable("KotlinAnnotationJar", "external/kotlinc/lib/annotations-13.0.jar") pctx.SourcePathVariable("KotlinStdlibJar", KotlinStdlibJar) // These flags silence "Illegal reflective access" warnings when running kotlinc in OpenJDK9 diff --git a/java/config/makevars.go b/java/config/makevars.go index 6881caff6..b355fad87 100644 --- a/java/config/makevars.go +++ b/java/config/makevars.go @@ -29,18 +29,13 @@ func makeVarsProvider(ctx android.MakeVarsContext) { ctx.Strict("TARGET_DEFAULT_BOOTCLASSPATH_LIBRARIES", strings.Join(DefaultBootclasspathLibraries, " ")) ctx.Strict("DEFAULT_SYSTEM_MODULES", DefaultSystemModules) - if ctx.Config().TargetOpenJDK9() { - ctx.Strict("DEFAULT_JAVA_LANGUAGE_VERSION", "1.9") - } else { - ctx.Strict("DEFAULT_JAVA_LANGUAGE_VERSION", "1.8") - } - ctx.Strict("ANDROID_JAVA_HOME", "${JavaHome}") ctx.Strict("ANDROID_JAVA8_HOME", "prebuilts/jdk/jdk8/${hostPrebuiltTag}") ctx.Strict("ANDROID_JAVA9_HOME", "prebuilts/jdk/jdk9/${hostPrebuiltTag}") + ctx.Strict("ANDROID_JAVA11_HOME", "prebuilts/jdk/jdk11/${hostPrebuiltTag}") ctx.Strict("ANDROID_JAVA_TOOLCHAIN", "${JavaToolchain}") - ctx.Strict("JAVA", "${JavaCmd}") - ctx.Strict("JAVAC", "${JavacCmd}") + ctx.Strict("JAVA", "${JavaCmd} ${JavaVmFlags}") + ctx.Strict("JAVAC", "${JavacCmd} ${JavacVmFlags}") ctx.Strict("JAR", "${JarCmd}") ctx.Strict("JAR_ARGS", "${JarArgsCmd}") ctx.Strict("JAVADOC", "${JavadocCmd}") @@ -58,8 +53,8 @@ func makeVarsProvider(ctx android.MakeVarsContext) { ctx.Strict("ERROR_PRONE_CHECKS", "${ErrorProneChecks}") } - ctx.Strict("TARGET_JAVAC", "${JavacCmd} ${CommonJdkFlags}") - ctx.Strict("HOST_JAVAC", "${JavacCmd} ${CommonJdkFlags}") + ctx.Strict("TARGET_JAVAC", "${JavacCmd} ${JavacVmFlags} ${CommonJdkFlags}") + ctx.Strict("HOST_JAVAC", "${JavacCmd} ${JavacVmFlags} ${CommonJdkFlags}") ctx.Strict("JLINK", "${JlinkCmd}") ctx.Strict("JMOD", "${JmodCmd}") @@ -69,10 +64,11 @@ func makeVarsProvider(ctx android.MakeVarsContext) { ctx.Strict("ZIPSYNC", "${ZipSyncCmd}") ctx.Strict("JACOCO_CLI_JAR", "${JacocoCLIJar}") - ctx.Strict("DEFAULT_JACOCO_EXCLUDE_FILTER", strings.Join(DefaultJacocoExcludeFilter, ",")) + ctx.Strict("DEFAULT_JACOCO_EXCLUDE_FILTER", strings.Join(DefaultMakeJacocoExcludeFilter, ",")) ctx.Strict("EXTRACT_JAR_PACKAGES", "${ExtractJarPackagesCmd}") + ctx.Strict("MANIFEST_CHECK", "${ManifestCheckCmd}") ctx.Strict("MANIFEST_FIXER", "${ManifestFixerCmd}") ctx.Strict("ANDROID_MANIFEST_MERGER", "${ManifestMergerCmd}") @@ -81,4 +77,17 @@ func makeVarsProvider(ctx android.MakeVarsContext) { ctx.Strict("HIDDENAPI", "${HiddenAPI}") ctx.Strict("DEX_FLAGS", "${DexFlags}") + + ctx.Strict("AIDL", "${AidlCmd}") + ctx.Strict("AAPT2", "${Aapt2Cmd}") + ctx.Strict("ZIPALIGN", "${ZipAlign}") + ctx.Strict("SIGNAPK_JAR", "${SignapkCmd}") + ctx.Strict("SIGNAPK_JNI_LIBRARY_PATH", "${SignapkJniLibrary}") + + ctx.Strict("SOONG_ZIP", "${SoongZipCmd}") + ctx.Strict("MERGE_ZIPS", "${MergeZipsCmd}") + ctx.Strict("ZIP2ZIP", "${Zip2ZipCmd}") + + ctx.Strict("ZIPTIME", "${Ziptime}") + } diff --git a/java/device_host_converter.go b/java/device_host_converter.go index 9f40a6c0d..11e68eb6c 100644 --- a/java/device_host_converter.go +++ b/java/device_host_converter.go @@ -15,9 +15,10 @@ package java import ( - "android/soong/android" + "fmt" + "io" - "github.com/google/blueprint" + "android/soong/android" ) type DeviceHostConverter struct { @@ -30,6 +31,12 @@ type DeviceHostConverter struct { implementationJars android.Paths implementationAndResourceJars android.Paths resourceJars android.Paths + + srcJarArgs []string + srcJarDeps android.Paths + + combinedHeaderJar android.Path + combinedImplementationJar android.Path } type DeviceHostConverterProperties struct { @@ -44,7 +51,7 @@ type DeviceForHost struct { // java_device_for_host makes the classes.jar output of a device java_library module available to host // java_library modules. // -// It is rarely necessary, and its used is restricted to a few whitelisted projects. +// It is rarely necessary, and its usage is restricted to a few allowed projects. func DeviceForHostFactory() android.Module { module := &DeviceForHost{} @@ -61,7 +68,7 @@ type HostForDevice struct { // java_host_for_device makes the classes.jar output of a host java_library module available to device // java_library modules. // -// It is rarely necessary, and its used is restricted to a few whitelisted projects. +// It is rarely necessary, and its usage is restricted to a few allowed projects. func HostForDeviceFactory() android.Module { module := &HostForDevice{} @@ -74,13 +81,13 @@ func HostForDeviceFactory() android.Module { var deviceHostConverterDepTag = dependencyTag{name: "device_host_converter"} func (d *DeviceForHost) DepsMutator(ctx android.BottomUpMutatorContext) { - variation := []blueprint.Variation{{Mutator: "arch", Variation: "android_common"}} - ctx.AddFarVariationDependencies(variation, deviceHostConverterDepTag, d.properties.Libs...) + ctx.AddFarVariationDependencies(ctx.Config().AndroidCommonTarget.Variations(), + deviceHostConverterDepTag, d.properties.Libs...) } func (d *HostForDevice) DepsMutator(ctx android.BottomUpMutatorContext) { - variation := []blueprint.Variation{{Mutator: "arch", Variation: ctx.Config().BuildOsCommonVariant}} - ctx.AddFarVariationDependencies(variation, deviceHostConverterDepTag, d.properties.Libs...) + ctx.AddFarVariationDependencies(ctx.Config().BuildOSCommonTarget.Variations(), + deviceHostConverterDepTag, d.properties.Libs...) } func (d *DeviceHostConverter) GenerateAndroidBuildActions(ctx android.ModuleContext) { @@ -94,10 +101,35 @@ func (d *DeviceHostConverter) GenerateAndroidBuildActions(ctx android.ModuleCont d.implementationJars = append(d.implementationJars, dep.ImplementationJars()...) d.implementationAndResourceJars = append(d.implementationAndResourceJars, dep.ImplementationAndResourcesJars()...) d.resourceJars = append(d.resourceJars, dep.ResourceJars()...) + + srcJarArgs, srcJarDeps := dep.SrcJarArgs() + d.srcJarArgs = append(d.srcJarArgs, srcJarArgs...) + d.srcJarDeps = append(d.srcJarDeps, srcJarDeps...) } else { ctx.PropertyErrorf("libs", "module %q cannot be used as a dependency", ctx.OtherModuleName(m)) } }) + + jarName := ctx.ModuleName() + ".jar" + + if len(d.implementationAndResourceJars) > 1 { + outputFile := android.PathForModuleOut(ctx, "combined", jarName) + TransformJarsToJar(ctx, outputFile, "combine", d.implementationAndResourceJars, + android.OptionalPath{}, false, nil, nil) + d.combinedImplementationJar = outputFile + } else { + d.combinedImplementationJar = d.implementationAndResourceJars[0] + } + + if len(d.headerJars) > 1 { + outputFile := android.PathForModuleOut(ctx, "turbine-combined", jarName) + TransformJarsToJar(ctx, outputFile, "turbine combine", d.headerJars, + android.OptionalPath{}, false, nil, []string{"META-INF/TRANSITIVE"}) + d.combinedHeaderJar = outputFile + } else { + d.combinedHeaderJar = d.headerJars[0] + } + } var _ Dependency = (*DeviceHostConverter)(nil) @@ -129,3 +161,30 @@ func (d *DeviceHostConverter) AidlIncludeDirs() android.Paths { func (d *DeviceHostConverter) ExportedSdkLibs() []string { return nil } + +func (d *DeviceHostConverter) ExportedPlugins() (android.Paths, []string) { + return nil, nil +} + +func (d *DeviceHostConverter) SrcJarArgs() ([]string, android.Paths) { + return d.srcJarArgs, d.srcJarDeps +} + +func (d *DeviceHostConverter) JacocoReportClassesFile() android.Path { + return nil +} + +func (d *DeviceHostConverter) AndroidMk() android.AndroidMkData { + return android.AndroidMkData{ + Class: "JAVA_LIBRARIES", + OutputFile: android.OptionalPathForPath(d.combinedImplementationJar), + Include: "$(BUILD_SYSTEM)/soong_java_prebuilt.mk", + Extra: []android.AndroidMkExtraFunc{ + func(w io.Writer, outputFile android.Path) { + fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := true") + fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", d.combinedHeaderJar.String()) + fmt.Fprintln(w, "LOCAL_SOONG_CLASSES_JAR :=", d.combinedImplementationJar.String()) + }, + }, + } +} diff --git a/java/device_host_converter_test.go b/java/device_host_converter_test.go index 146bf6f40..3c9a0f3f1 100644 --- a/java/device_host_converter_test.go +++ b/java/device_host_converter_test.go @@ -50,9 +50,7 @@ func TestDeviceForHost(t *testing.T) { } ` - config := testConfig(nil) - ctx := testContext(config, bp, nil) - run(t, ctx, config) + ctx, config := testJava(t, bp) deviceModule := ctx.ModuleForTests("device_module", "android_common") deviceTurbineCombined := deviceModule.Output("turbine-combined/device_module.jar") @@ -62,7 +60,7 @@ func TestDeviceForHost(t *testing.T) { deviceImportModule := ctx.ModuleForTests("device_import_module", "android_common") deviceImportCombined := deviceImportModule.Output("combined/device_import_module.jar") - hostModule := ctx.ModuleForTests("host_module", config.BuildOsCommonVariant) + hostModule := ctx.ModuleForTests("host_module", config.BuildOSCommonTarget.String()) hostJavac := hostModule.Output("javac/host_module.jar") hostRes := hostModule.Output("res/host_module.jar") combined := hostModule.Output("combined/host_module.jar") @@ -126,22 +124,20 @@ func TestHostForDevice(t *testing.T) { java_library { name: "device_module", - no_framework_libs: true, + sdk_version: "core_platform", srcs: ["b.java"], java_resources: ["java-res/b/b"], static_libs: ["host_for_device_module"], } ` - config := testConfig(nil) - ctx := testContext(config, bp, nil) - run(t, ctx, config) + ctx, config := testJava(t, bp) - hostModule := ctx.ModuleForTests("host_module", config.BuildOsCommonVariant) + hostModule := ctx.ModuleForTests("host_module", config.BuildOSCommonTarget.String()) hostJavac := hostModule.Output("javac/host_module.jar") hostRes := hostModule.Output("res/host_module.jar") - hostImportModule := ctx.ModuleForTests("host_import_module", config.BuildOsCommonVariant) + hostImportModule := ctx.ModuleForTests("host_import_module", config.BuildOSCommonTarget.String()) hostImportCombined := hostImportModule.Output("combined/host_import_module.jar") deviceModule := ctx.ModuleForTests("device_module", "android_common") diff --git a/java/dex.go b/java/dex.go index 86a28fc18..9e61e95ad 100644 --- a/java/dex.go +++ b/java/dex.go @@ -18,43 +18,73 @@ import ( "strings" "github.com/google/blueprint" + "github.com/google/blueprint/proptools" "android/soong/android" + "android/soong/remoteexec" ) -var d8 = pctx.AndroidRemoteStaticRule("d8", android.RemoteRuleSupports{RBE: true, RBEFlag: android.RBE_D8}, +var d8, d8RE = remoteexec.MultiCommandStaticRules(pctx, "d8", blueprint.RuleParams{ Command: `rm -rf "$outDir" && mkdir -p "$outDir" && ` + - `${config.D8Wrapper}${config.D8Cmd} ${config.DexFlags} --output $outDir $d8Flags $in && ` + - `${config.SoongZipCmd} $zipFlags -o $outDir/classes.dex.jar -C $outDir -f "$outDir/classes*.dex" && ` + + `$d8Template${config.D8Cmd} ${config.DexFlags} --output $outDir $d8Flags $in && ` + + `$zipTemplate${config.SoongZipCmd} $zipFlags -o $outDir/classes.dex.jar -C $outDir -f "$outDir/classes*.dex" && ` + `${config.MergeZipsCmd} -D -stripFile "**/*.class" $out $outDir/classes.dex.jar $in`, CommandDeps: []string{ "${config.D8Cmd}", "${config.SoongZipCmd}", "${config.MergeZipsCmd}", }, - }, - "outDir", "d8Flags", "zipFlags") + }, map[string]*remoteexec.REParams{ + "$d8Template": &remoteexec.REParams{ + Labels: map[string]string{"type": "compile", "compiler": "d8"}, + Inputs: []string{"${config.D8Jar}"}, + ExecStrategy: "${config.RED8ExecStrategy}", + ToolchainInputs: []string{"${config.JavaCmd}"}, + Platform: map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"}, + }, + "$zipTemplate": &remoteexec.REParams{ + Labels: map[string]string{"type": "tool", "name": "soong_zip"}, + Inputs: []string{"${config.SoongZipCmd}", "$outDir"}, + OutputFiles: []string{"$outDir/classes.dex.jar"}, + ExecStrategy: "${config.RED8ExecStrategy}", + Platform: map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"}, + }, + }, []string{"outDir", "d8Flags", "zipFlags"}, nil) -var r8 = pctx.AndroidRemoteStaticRule("r8", android.RemoteRuleSupports{RBE: true, RBEFlag: android.RBE_R8}, +var r8, r8RE = remoteexec.MultiCommandStaticRules(pctx, "r8", blueprint.RuleParams{ Command: `rm -rf "$outDir" && mkdir -p "$outDir" && ` + `rm -f "$outDict" && ` + - `${config.R8Wrapper}${config.R8Cmd} ${config.DexFlags} -injars $in --output $outDir ` + + `$r8Template${config.R8Cmd} ${config.DexFlags} -injars $in --output $outDir ` + `--force-proguard-compatibility ` + `--no-data-resources ` + `-printmapping $outDict ` + `$r8Flags && ` + `touch "$outDict" && ` + - `${config.SoongZipCmd} $zipFlags -o $outDir/classes.dex.jar -C $outDir -f "$outDir/classes*.dex" && ` + + `$zipTemplate${config.SoongZipCmd} $zipFlags -o $outDir/classes.dex.jar -C $outDir -f "$outDir/classes*.dex" && ` + `${config.MergeZipsCmd} -D -stripFile "**/*.class" $out $outDir/classes.dex.jar $in`, CommandDeps: []string{ "${config.R8Cmd}", "${config.SoongZipCmd}", "${config.MergeZipsCmd}", }, - }, - "outDir", "outDict", "r8Flags", "zipFlags") + }, map[string]*remoteexec.REParams{ + "$r8Template": &remoteexec.REParams{ + Labels: map[string]string{"type": "compile", "compiler": "r8"}, + Inputs: []string{"$implicits", "${config.R8Jar}"}, + ExecStrategy: "${config.RER8ExecStrategy}", + ToolchainInputs: []string{"${config.JavaCmd}"}, + Platform: map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"}, + }, + "$zipTemplate": &remoteexec.REParams{ + Labels: map[string]string{"type": "tool", "name": "soong_zip"}, + Inputs: []string{"${config.SoongZipCmd}", "$outDir"}, + OutputFiles: []string{"$outDir/classes.dex.jar"}, + ExecStrategy: "${config.RER8ExecStrategy}", + Platform: map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"}, + }, + }, []string{"outDir", "outDict", "r8Flags", "zipFlags"}, []string{"implicits"}) func (j *Module) dexCommonFlags(ctx android.ModuleContext) []string { flags := j.deviceProperties.Dxflags @@ -73,20 +103,20 @@ func (j *Module) dexCommonFlags(ctx android.ModuleContext) []string { "--verbose") } - minSdkVersion, err := sdkVersionToNumberAsString(ctx, j.minSdkVersion()) + minSdkVersion, err := j.minSdkVersion().effectiveVersion(ctx) if err != nil { ctx.PropertyErrorf("min_sdk_version", "%s", err) } - flags = append(flags, "--min-api "+minSdkVersion) + flags = append(flags, "--min-api "+minSdkVersion.asNumberString()) return flags } func (j *Module) d8Flags(ctx android.ModuleContext, flags javaBuilderFlags) ([]string, android.Paths) { d8Flags := j.dexCommonFlags(ctx) - d8Flags = append(d8Flags, flags.bootClasspath.FormTurbineClasspath("--lib ")...) - d8Flags = append(d8Flags, flags.classpath.FormTurbineClasspath("--lib ")...) + d8Flags = append(d8Flags, flags.bootClasspath.FormRepeatedClassPath("--lib ")...) + d8Flags = append(d8Flags, flags.classpath.FormRepeatedClassPath("--lib ")...) var d8Deps android.Paths d8Deps = append(d8Deps, flags.bootClasspath...) @@ -115,7 +145,6 @@ func (j *Module) r8Flags(ctx android.ModuleContext, flags javaBuilderFlags) (r8F r8Flags = append(r8Flags, proguardRaiseDeps.FormJavaClassPath("-libraryjars")) r8Flags = append(r8Flags, flags.bootClasspath.FormJavaClassPath("-libraryjars")) r8Flags = append(r8Flags, flags.classpath.FormJavaClassPath("-libraryjars")) - r8Flags = append(r8Flags, "-forceprocessing") r8Deps = append(r8Deps, proguardRaiseDeps...) r8Deps = append(r8Deps, flags.bootClasspath...) @@ -178,7 +207,7 @@ func (j *Module) compileDex(ctx android.ModuleContext, flags javaBuilderFlags, outDir := android.PathForModuleOut(ctx, "dex") zipFlags := "--ignore_missing_files" - if j.deviceProperties.UncompressDex { + if proptools.Bool(j.deviceProperties.Uncompress_dex) { zipFlags += " -L 0" } @@ -186,24 +215,34 @@ func (j *Module) compileDex(ctx android.ModuleContext, flags javaBuilderFlags, proguardDictionary := android.PathForModuleOut(ctx, "proguard_dictionary") j.proguardDictionary = proguardDictionary r8Flags, r8Deps := j.r8Flags(ctx, flags) + rule := r8 + args := map[string]string{ + "r8Flags": strings.Join(r8Flags, " "), + "zipFlags": zipFlags, + "outDict": j.proguardDictionary.String(), + "outDir": outDir.String(), + } + if ctx.Config().IsEnvTrue("RBE_R8") { + rule = r8RE + args["implicits"] = strings.Join(r8Deps.Strings(), ",") + } ctx.Build(pctx, android.BuildParams{ - Rule: r8, + Rule: rule, Description: "r8", Output: javalibJar, ImplicitOutput: proguardDictionary, Input: classesJar, Implicits: r8Deps, - Args: map[string]string{ - "r8Flags": strings.Join(r8Flags, " "), - "zipFlags": zipFlags, - "outDict": j.proguardDictionary.String(), - "outDir": outDir.String(), - }, + Args: args, }) } else { d8Flags, d8Deps := j.d8Flags(ctx, flags) + rule := d8 + if ctx.Config().IsEnvTrue("RBE_D8") { + rule = d8RE + } ctx.Build(pctx, android.BuildParams{ - Rule: d8, + Rule: rule, Description: "d8", Output: javalibJar, Input: classesJar, @@ -215,7 +254,7 @@ func (j *Module) compileDex(ctx android.ModuleContext, flags javaBuilderFlags, }, }) } - if j.deviceProperties.UncompressDex { + if proptools.Bool(j.deviceProperties.Uncompress_dex) { alignedJavalibJar := android.PathForModuleOut(ctx, "aligned", jarName) TransformZipAlign(ctx, alignedJavalibJar, javalibJar) javalibJar = alignedJavalibJar diff --git a/java/dexpreopt.go b/java/dexpreopt.go index 9141f9ef9..28a2c8ae6 100644 --- a/java/dexpreopt.go +++ b/java/dexpreopt.go @@ -19,27 +19,34 @@ import ( "android/soong/dexpreopt" ) +type dexpreopterInterface interface { + IsInstallable() bool // Structs that embed dexpreopter must implement this. + dexpreoptDisabled(ctx android.BaseModuleContext) bool +} + type dexpreopter struct { dexpreoptProperties DexpreoptProperties - installPath android.OutputPath - uncompressedDex bool - isSDKLibrary bool - isTest bool - isInstallable bool + installPath android.InstallPath + uncompressedDex bool + isSDKLibrary bool + isTest bool + isPresignedPrebuilt bool + + manifestFile android.Path + usesLibs []string + optionalUsesLibs []string + enforceUsesLibs bool + libraryPaths map[string]android.Path builtInstalled string } type DexpreoptProperties struct { Dex_preopt struct { - // If false, prevent dexpreopting and stripping the dex file from the final jar. Defaults to - // true. + // If false, prevent dexpreopting. Defaults to true. Enabled *bool - // If true, never strip the dex files from the final jar when dexpreopting. Defaults to false. - No_stripping *bool - // If true, generate an app image (.art file) for this module. App_image *bool @@ -51,12 +58,16 @@ type DexpreoptProperties struct { // If set, provides the path to profile relative to the Android.bp file. If not set, // defaults to searching for a file that matches the name of this module in the default // profile location set by PRODUCT_DEX_PREOPT_PROFILE_DIR, or empty if not found. - Profile *string + Profile *string `android:"path"` } } -func (d *dexpreopter) dexpreoptDisabled(ctx android.ModuleContext) bool { - global := dexpreoptGlobalConfig(ctx) +func init() { + dexpreopt.DexpreoptRunningInSoong = true +} + +func (d *dexpreopter) dexpreoptDisabled(ctx android.BaseModuleContext) bool { + global := dexpreopt.GetGlobalConfig(ctx) if global.DisablePreopt { return true @@ -78,7 +89,16 @@ func (d *dexpreopter) dexpreoptDisabled(ctx android.ModuleContext) bool { return true } - if !d.isInstallable { + if !ctx.Module().(dexpreopterInterface).IsInstallable() { + return true + } + + if ctx.Host() { + return true + } + + // Don't preopt APEX variant module + if am, ok := ctx.Module().(android.ApexModule); ok && !am.IsForPlatform() { return true } @@ -87,51 +107,63 @@ func (d *dexpreopter) dexpreoptDisabled(ctx android.ModuleContext) bool { return false } -func odexOnSystemOther(ctx android.ModuleContext, installPath android.OutputPath) bool { - return dexpreopt.OdexOnSystemOtherByName(ctx.ModuleName(), android.InstallPathToOnDevicePath(ctx, installPath), dexpreoptGlobalConfig(ctx)) +func dexpreoptToolDepsMutator(ctx android.BottomUpMutatorContext) { + if d, ok := ctx.Module().(dexpreopterInterface); !ok || d.dexpreoptDisabled(ctx) { + return + } + dexpreopt.RegisterToolDeps(ctx) +} + +func odexOnSystemOther(ctx android.ModuleContext, installPath android.InstallPath) bool { + return dexpreopt.OdexOnSystemOtherByName(ctx.ModuleName(), android.InstallPathToOnDevicePath(ctx, installPath), dexpreopt.GetGlobalConfig(ctx)) } func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, dexJarFile android.ModuleOutPath) android.ModuleOutPath { - if d.dexpreoptDisabled(ctx) { + // TODO(b/148690468): The check on d.installPath is to bail out in cases where + // the dexpreopter struct hasn't been fully initialized before we're called, + // e.g. in aar.go. This keeps the behaviour that dexpreopting is effectively + // disabled, even if installable is true. + if d.dexpreoptDisabled(ctx) || d.installPath.Base() == "." { return dexJarFile } - global := dexpreoptGlobalConfig(ctx) + globalSoong := dexpreopt.GetGlobalSoongConfig(ctx) + global := dexpreopt.GetGlobalConfig(ctx) bootImage := defaultBootImageConfig(ctx) - defaultBootImage := bootImage - if global.UseApexImage { - bootImage = apexBootImageConfig(ctx) + dexFiles := bootImage.dexPathsDeps.Paths() + dexLocations := bootImage.dexLocationsDeps + if global.UseArtImage { + bootImage = artBootImageConfig(ctx) } - var archs []android.ArchType - for _, a := range ctx.MultiTargets() { - archs = append(archs, a.Arch.ArchType) - } - if len(archs) == 0 { + targets := ctx.MultiTargets() + if len(targets) == 0 { // assume this is a java library, dexpreopt for all arches for now for _, target := range ctx.Config().Targets[android.Android] { - archs = append(archs, target.Arch.ArchType) + if target.NativeBridge == android.NativeBridgeDisabled { + targets = append(targets, target) + } } if inList(ctx.ModuleName(), global.SystemServerJars) && !d.isSDKLibrary { // If the module is not an SDK library and it's a system server jar, only preopt the primary arch. - archs = archs[:1] + targets = targets[:1] } } - if ctx.Config().SecondArchIsTranslated() { - // Only preopt primary arch for translated arch since there is only an image there. - archs = archs[:1] - } + var archs []android.ArchType var images android.Paths - for _, arch := range archs { - images = append(images, bootImage.images[arch]) + var imagesDeps []android.OutputPaths + for _, target := range targets { + archs = append(archs, target.Arch.ArchType) + variant := bootImage.getVariant(target) + images = append(images, variant.images) + imagesDeps = append(imagesDeps, variant.imagesDeps) } dexLocation := android.InstallPathToOnDevicePath(ctx, d.installPath) - strippedDexJarFile := android.PathForModuleOut(ctx, "dexpreopt", dexJarFile.Base()) - var profileClassListing android.OptionalPath + var profileBootListing android.OptionalPath profileIsTextListing := false if BoolDefault(d.dexpreoptProperties.Dex_preopt.Profile_guided, true) { // If dex_preopt.profile_guided is not set, default it based on the existence of the @@ -139,6 +171,8 @@ func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, dexJarFile android.Mo if String(d.dexpreoptProperties.Dex_preopt.Profile) != "" { profileClassListing = android.OptionalPathForPath( android.PathForModuleSrc(ctx, String(d.dexpreoptProperties.Dex_preopt.Profile))) + profileBootListing = android.ExistentPathForSource(ctx, + ctx.ModuleDir(), String(d.dexpreoptProperties.Dex_preopt.Profile)+"-boot") profileIsTextListing = true } else { profileClassListing = android.ExistentPathForSource(ctx, @@ -146,43 +180,42 @@ func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, dexJarFile android.Mo } } - dexpreoptConfig := dexpreopt.ModuleConfig{ + dexpreoptConfig := &dexpreopt.ModuleConfig{ Name: ctx.ModuleName(), DexLocation: dexLocation, BuildPath: android.PathForModuleOut(ctx, "dexpreopt", ctx.ModuleName()+".jar").OutputPath, DexPath: dexJarFile, + ManifestPath: d.manifestFile, UncompressedDex: d.uncompressedDex, HasApkLibraries: false, PreoptFlags: nil, ProfileClassListing: profileClassListing, ProfileIsTextListing: profileIsTextListing, + ProfileBootListing: profileBootListing, - EnforceUsesLibraries: false, - OptionalUsesLibraries: nil, - UsesLibraries: nil, - LibraryPaths: nil, + EnforceUsesLibraries: d.enforceUsesLibs, + PresentOptionalUsesLibraries: d.optionalUsesLibs, + UsesLibraries: d.usesLibs, + LibraryPaths: d.libraryPaths, - Archs: archs, - DexPreoptImages: images, + Archs: archs, + DexPreoptImages: images, + DexPreoptImagesDeps: imagesDeps, + DexPreoptImageLocations: bootImage.imageLocations, - // We use the dex paths and dex locations of the default boot image, as it - // contains the full dexpreopt boot classpath. Other images may just contain a subset of - // the dexpreopt boot classpath. - PreoptBootClassPathDexFiles: defaultBootImage.dexPaths.Paths(), - PreoptBootClassPathDexLocations: defaultBootImage.dexLocations, + PreoptBootClassPathDexFiles: dexFiles, + PreoptBootClassPathDexLocations: dexLocations, PreoptExtractedApk: false, NoCreateAppImage: !BoolDefault(d.dexpreoptProperties.Dex_preopt.App_image, true), ForceCreateAppImage: BoolDefault(d.dexpreoptProperties.Dex_preopt.App_image, false), - NoStripping: Bool(d.dexpreoptProperties.Dex_preopt.No_stripping), - StripInputPath: dexJarFile, - StripOutputPath: strippedDexJarFile.OutputPath, + PresignedPrebuilt: d.isPresignedPrebuilt, } - dexpreoptRule, err := dexpreopt.GenerateDexpreoptRule(ctx, global, dexpreoptConfig) + dexpreoptRule, err := dexpreopt.GenerateDexpreoptRule(ctx, globalSoong, global, dexpreoptConfig) if err != nil { ctx.ModuleErrorf("error generating dexpreopt rule: %s", err.Error()) return dexJarFile @@ -192,13 +225,5 @@ func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, dexJarFile android.Mo d.builtInstalled = dexpreoptRule.Installs().String() - stripRule, err := dexpreopt.GenerateStripRule(global, dexpreoptConfig) - if err != nil { - ctx.ModuleErrorf("error generating dexpreopt strip rule: %s", err.Error()) - return dexJarFile - } - - stripRule.Build(pctx, ctx, "dexpreopt_strip", "dexpreopt strip") - - return strippedDexJarFile + return dexJarFile } diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go index 4d87b2f77..2f0cbdb8c 100644 --- a/java/dexpreopt_bootjars.go +++ b/java/dexpreopt_bootjars.go @@ -22,12 +22,11 @@ import ( "android/soong/android" "android/soong/dexpreopt" - "github.com/google/blueprint/pathtools" "github.com/google/blueprint/proptools" ) func init() { - android.RegisterSingletonType("dex_bootjars", dexpreoptBootJarsFactory) + RegisterDexpreoptBootJarsComponents(android.InitRegistrationContext) } // The image "location" is a symbolic path that with multiarchitecture @@ -49,36 +48,108 @@ func init() { // The location is passed as an argument to the ART tools like dex2oat instead of the real path. The ART tools // will then reconstruct the real path, so the rules must have a dependency on the real path. +// Target-independent description of pre-compiled boot image. type bootImageConfig struct { - name string - modules []string - dexLocations []string - dexPaths android.WritablePaths - dir android.OutputPath - symbolsDir android.OutputPath - images map[android.ArchType]android.OutputPath -} + // Whether this image is an extension. + extension bool + + // Image name (used in directory names and ninja rule names). + name string + + // Basename of the image: the resulting filenames are <stem>[-<jar>].{art,oat,vdex}. + stem string + + // Output directory for the image files. + dir android.OutputPath + + // Output directory for the image files with debug symbols. + symbolsDir android.OutputPath + + // Subdirectory where the image files are installed. + installSubdir string + + // The names of jars that constitute this image. + modules []string + + // The "locations" of jars. + dexLocations []string // for this image + dexLocationsDeps []string // for the dependency images and in this image -type bootImage struct { - bootImageConfig + // File paths to jars. + dexPaths android.WritablePaths // for this image + dexPathsDeps android.WritablePaths // for the dependency images and in this image - installs map[android.ArchType]android.RuleBuilderInstalls - vdexInstalls map[android.ArchType]android.RuleBuilderInstalls - unstrippedInstalls map[android.ArchType]android.RuleBuilderInstalls + // The "locations" of the dependency images and in this image. + imageLocations []string + // File path to a zip archive with all image files (or nil, if not needed). + zip android.WritablePath + + // Rules which should be used in make to install the outputs. profileInstalls android.RuleBuilderInstalls + + // Target-dependent fields. + variants []*bootImageVariant +} + +// Target-dependent description of pre-compiled boot image. +type bootImageVariant struct { + *bootImageConfig + + // Target for which the image is generated. + target android.Target + + // Paths to image files. + images android.OutputPath // first image file + imagesDeps android.OutputPaths // all files + + // Only for extensions, paths to the primary boot images. + primaryImages android.OutputPath + + // Rules which should be used in make to install the outputs. + installs android.RuleBuilderInstalls + vdexInstalls android.RuleBuilderInstalls + unstrippedInstalls android.RuleBuilderInstalls } -func newBootImage(ctx android.PathContext, config bootImageConfig) *bootImage { - image := &bootImage{ - bootImageConfig: config, +func (image bootImageConfig) getVariant(target android.Target) *bootImageVariant { + for _, variant := range image.variants { + if variant.target.Os == target.Os && variant.target.Arch.ArchType == target.Arch.ArchType { + return variant + } + } + return nil +} - installs: make(map[android.ArchType]android.RuleBuilderInstalls), - vdexInstalls: make(map[android.ArchType]android.RuleBuilderInstalls), - unstrippedInstalls: make(map[android.ArchType]android.RuleBuilderInstalls), +func (image bootImageConfig) moduleName(idx int) string { + // Dexpreopt on the boot class path produces multiple files. The first dex file + // is converted into 'name'.art (to match the legacy assumption that 'name'.art + // exists), and the rest are converted to 'name'-<jar>.art. + m := image.modules[idx] + name := image.stem + if idx != 0 || image.extension { + name += "-" + stemOf(m) } + return name +} - return image +func (image bootImageConfig) firstModuleNameOrStem() string { + if len(image.modules) > 0 { + return image.moduleName(0) + } else { + return image.stem + } +} + +func (image bootImageConfig) moduleFiles(ctx android.PathContext, dir android.OutputPath, exts ...string) android.OutputPaths { + ret := make(android.OutputPaths, 0, len(image.modules)*len(exts)) + for i := range image.modules { + name := image.moduleName(i) + for _, ext := range exts { + ret = append(ret, dir.Join(ctx, name+ext)) + } + } + return ret } func concat(lists ...[]string) []string { @@ -97,7 +168,15 @@ func dexpreoptBootJarsFactory() android.Singleton { return &dexpreoptBootJars{} } +func RegisterDexpreoptBootJarsComponents(ctx android.RegistrationContext) { + ctx.RegisterSingletonType("dex_bootjars", dexpreoptBootJarsFactory) +} + func skipDexpreoptBootJars(ctx android.PathContext) bool { + if dexpreopt.GetGlobalConfig(ctx).DisablePreopt { + return true + } + if ctx.Config().UnbundledBuild() { return true } @@ -111,8 +190,23 @@ func skipDexpreoptBootJars(ctx android.PathContext) bool { } type dexpreoptBootJars struct { - defaultBootImage *bootImage - otherImages []*bootImage + defaultBootImage *bootImageConfig + otherImages []*bootImageConfig + + dexpreoptConfigForMake android.WritablePath +} + +// Accessor function for the apex package. Returns nil if dexpreopt is disabled. +func DexpreoptedArtApexJars(ctx android.BuilderContext) map[android.ArchType]android.OutputPaths { + if skipDexpreoptBootJars(ctx) { + return nil + } + // Include dexpreopt files for the primary boot image. + files := map[android.ArchType]android.OutputPaths{} + for _, variant := range artBootImageConfig(ctx).variants { + files[variant.target.Arch.ArchType] = variant.imagesDeps + } + return files } // dexpreoptBoot singleton rules @@ -120,8 +214,15 @@ func (d *dexpreoptBootJars) GenerateBuildActions(ctx android.SingletonContext) { if skipDexpreoptBootJars(ctx) { return } + if dexpreopt.GetCachedGlobalSoongConfig(ctx) == nil { + // No module has enabled dexpreopting, so we assume there will be no boot image to make. + return + } + + d.dexpreoptConfigForMake = android.PathForOutput(ctx, ctx.Config().DeviceName(), "dexpreopt.config") + writeGlobalConfigForMake(ctx, d.dexpreoptConfigForMake) - global := dexpreoptGlobalConfig(ctx) + global := dexpreopt.GetGlobalConfig(ctx) // Skip recompiling the boot image for the second sanitization phase. We'll get separate paths // and invalidate first-stage artifacts which are crucial to SANITIZE_LITE builds. @@ -135,28 +236,73 @@ func (d *dexpreoptBootJars) GenerateBuildActions(ctx android.SingletonContext) { // Always create the default boot image first, to get a unique profile rule for all images. d.defaultBootImage = buildBootImage(ctx, defaultBootImageConfig(ctx)) - if global.GenerateApexImage { - d.otherImages = append(d.otherImages, buildBootImage(ctx, apexBootImageConfig(ctx))) - } + // Create boot image for the ART apex (build artifacts are accessed via the global boot image config). + d.otherImages = append(d.otherImages, buildBootImage(ctx, artBootImageConfig(ctx))) dumpOatRules(ctx, d.defaultBootImage) } -// buildBootImage takes a bootImageConfig, creates rules to build it, and returns a *bootImage. -func buildBootImage(ctx android.SingletonContext, config bootImageConfig) *bootImage { - global := dexpreoptGlobalConfig(ctx) +// Inspect this module to see if it contains a bootclasspath dex jar. +// Note that the same jar may occur in multiple modules. +// This logic is tested in the apex package to avoid import cycle apex <-> java. +func getBootImageJar(ctx android.SingletonContext, image *bootImageConfig, module android.Module) (int, android.Path) { + // All apex Java libraries have non-installable platform variants, skip them. + if module.IsSkipInstall() { + return -1, nil + } - image := newBootImage(ctx, config) + jar, hasJar := module.(interface{ DexJar() android.Path }) + if !hasJar { + return -1, nil + } - bootDexJars := make(android.Paths, len(image.modules)) + name := ctx.ModuleName(module) + index := android.IndexList(name, image.modules) + if index == -1 { + return -1, nil + } + + // Check that this module satisfies constraints for a particular boot image. + apex, isApexModule := module.(android.ApexModule) + fromUpdatableApex := isApexModule && apex.Updatable() + if image.name == artBootImageName { + if isApexModule && strings.HasPrefix(apex.ApexName(), "com.android.art.") { + // ok: found the jar in the ART apex + } else if isApexModule && apex.IsForPlatform() && Bool(module.(*Library).deviceProperties.Hostdex) { + // exception (skip and continue): special "hostdex" platform variant + return -1, nil + } else if name == "jacocoagent" && ctx.Config().IsEnvTrue("EMMA_INSTRUMENT_FRAMEWORK") { + // exception (skip and continue): Jacoco platform variant for a coverage build + return -1, nil + } else if fromUpdatableApex { + // error: this jar is part of an updatable apex other than ART + ctx.Errorf("module '%s' from updatable apex '%s' is not allowed in the ART boot image", name, apex.ApexName()) + } else { + // error: this jar is part of the platform or a non-updatable apex + ctx.Errorf("module '%s' is not allowed in the ART boot image", name) + } + } else if image.name == frameworkBootImageName { + if !fromUpdatableApex { + // ok: this jar is part of the platform or a non-updatable apex + } else { + // error: this jar is part of an updatable apex + ctx.Errorf("module '%s' from updatable apex '%s' is not allowed in the framework boot image", name, apex.ApexName()) + } + } else { + panic("unknown boot image: " + image.name) + } + return index, jar.DexJar() +} + +// buildBootImage takes a bootImageConfig, creates rules to build it, and returns the image. +func buildBootImage(ctx android.SingletonContext, image *bootImageConfig) *bootImageConfig { + // Collect dex jar paths for the boot image modules. + // This logic is tested in the apex package to avoid import cycle apex <-> java. + bootDexJars := make(android.Paths, len(image.modules)) ctx.VisitAllModules(func(module android.Module) { - // Collect dex jar paths for the modules listed above. - if j, ok := module.(interface{ DexJar() android.Path }); ok { - name := ctx.ModuleName(module) - if i := android.IndexList(name, image.modules); i != -1 { - bootDexJars[i] = j.DexJar() - } + if i, j := getBootImageJar(ctx, image, module); i != -1 { + bootDexJars[i] = j } }) @@ -168,7 +314,8 @@ func buildBootImage(ctx android.SingletonContext, config bootImageConfig) *bootI missingDeps = append(missingDeps, image.modules[i]) bootDexJars[i] = android.PathForOutput(ctx, "missing") } else { - ctx.Errorf("failed to find dex jar path for module %q", + ctx.Errorf("failed to find a dex jar path for module '%s'"+ + ", note that some jars may be filtered out by module constraints", image.modules[i]) } } @@ -186,31 +333,42 @@ func buildBootImage(ctx android.SingletonContext, config bootImageConfig) *bootI } profile := bootImageProfileRule(ctx, image, missingDeps) + bootFrameworkProfileRule(ctx, image, missingDeps) + updatableBcpPackagesRule(ctx, image, missingDeps) - if !global.DisablePreopt { - targets := ctx.Config().Targets[android.Android] - if ctx.Config().SecondArchIsTranslated() { - targets = targets[:1] - } + var allFiles android.Paths + for _, variant := range image.variants { + files := buildBootImageVariant(ctx, variant, profile, missingDeps) + allFiles = append(allFiles, files.Paths()...) + } - for _, target := range targets { - buildBootImageRuleForArch(ctx, image, target.Arch.ArchType, profile, missingDeps) - } + if image.zip != nil { + rule := android.NewRuleBuilder() + rule.Command(). + BuiltTool(ctx, "soong_zip"). + FlagWithOutput("-o ", image.zip). + FlagWithArg("-C ", image.dir.String()). + FlagWithInputList("-f ", allFiles, " -f ") + + rule.Build(pctx, ctx, "zip_"+image.name, "zip "+image.name+" image") } return image } -func buildBootImageRuleForArch(ctx android.SingletonContext, image *bootImage, - arch android.ArchType, profile android.Path, missingDeps []string) { +func buildBootImageVariant(ctx android.SingletonContext, image *bootImageVariant, + profile android.Path, missingDeps []string) android.WritablePaths { - global := dexpreoptGlobalConfig(ctx) + globalSoong := dexpreopt.GetCachedGlobalSoongConfig(ctx) + global := dexpreopt.GetGlobalConfig(ctx) - symbolsDir := image.symbolsDir.Join(ctx, "system/framework", arch.String()) - symbolsFile := symbolsDir.Join(ctx, image.name+".oat") - outputDir := image.dir.Join(ctx, "system/framework", arch.String()) - outputPath := image.images[arch] - oatLocation := pathtools.ReplaceExtension(dexpreopt.PathToLocation(outputPath, arch), "oat") + arch := image.target.Arch.ArchType + symbolsDir := image.symbolsDir.Join(ctx, image.installSubdir, arch.String()) + symbolsFile := symbolsDir.Join(ctx, image.stem+".oat") + outputDir := image.dir.Join(ctx, image.installSubdir, arch.String()) + outputPath := outputDir.Join(ctx, image.stem+".oat") + oatLocation := dexpreopt.PathToLocation(outputPath, arch) + imagePath := outputPath.ReplaceExtension(ctx, "art") rule := android.NewRuleBuilder() rule.MissingDeps(missingDeps) @@ -238,7 +396,7 @@ func buildBootImageRuleForArch(ctx android.SingletonContext, image *bootImage, invocationPath := outputPath.ReplaceExtension(ctx, "invocation") - cmd.Tool(global.Tools.Dex2oat). + cmd.Tool(globalSoong.Dex2oat). Flag("--avoid-storing-invocation"). FlagWithOutput("--write-invocation-to=", invocationPath).ImplicitOutput(invocationPath). Flag("--runtime-arg").FlagWithArg("-Xms", global.Dex2oatImageXms). @@ -247,30 +405,39 @@ func buildBootImageRuleForArch(ctx android.SingletonContext, image *bootImage, if profile != nil { cmd.FlagWithArg("--compiler-filter=", "speed-profile") cmd.FlagWithInput("--profile-file=", profile) - } else if global.PreloadedClasses.Valid() { - cmd.FlagWithInput("--image-classes=", global.PreloadedClasses.Path()) } if global.DirtyImageObjects.Valid() { cmd.FlagWithInput("--dirty-image-objects=", global.DirtyImageObjects.Path()) } + if image.extension { + artImage := image.primaryImages + cmd. + Flag("--runtime-arg").FlagWithInputList("-Xbootclasspath:", image.dexPathsDeps.Paths(), ":"). + Flag("--runtime-arg").FlagWithList("-Xbootclasspath-locations:", image.dexLocationsDeps, ":"). + FlagWithArg("--boot-image=", dexpreopt.PathToLocation(artImage, arch)).Implicit(artImage) + } else { + cmd.FlagWithArg("--base=", ctx.Config().LibartImgDeviceBaseAddress()) + } + cmd. FlagForEachInput("--dex-file=", image.dexPaths.Paths()). FlagForEachArg("--dex-location=", image.dexLocations). Flag("--generate-debug-info"). Flag("--generate-build-id"). - FlagWithOutput("--oat-symbols=", symbolsFile). + Flag("--image-format=lz4hc"). + FlagWithArg("--oat-symbols=", symbolsFile.String()). Flag("--strip"). - FlagWithOutput("--oat-file=", outputPath.ReplaceExtension(ctx, "oat")). + FlagWithArg("--oat-file=", outputPath.String()). FlagWithArg("--oat-location=", oatLocation). - FlagWithOutput("--image=", outputPath). - FlagWithArg("--base=", ctx.Config().LibartImgDeviceBaseAddress()). + FlagWithArg("--image=", imagePath.String()). FlagWithArg("--instruction-set=", arch.String()). FlagWithArg("--instruction-set-variant=", global.CpuVariant[arch]). FlagWithArg("--instruction-set-features=", global.InstructionSetFeatures[arch]). FlagWithArg("--android-root=", global.EmptyDirectory). FlagWithArg("--no-inline-from=", "core-oj.jar"). + Flag("--force-determinism"). Flag("--abort-on-hard-verifier-error") if global.BootFlags != "" { @@ -283,68 +450,64 @@ func buildBootImageRuleForArch(ctx android.SingletonContext, image *bootImage, cmd.Textf(`|| ( echo %s ; false )`, proptools.ShellEscape(failureMessage)) - installDir := filepath.Join("/system/framework", arch.String()) - vdexInstallDir := filepath.Join("/system/framework") + installDir := filepath.Join("/", image.installSubdir, arch.String()) + vdexInstallDir := filepath.Join("/", image.installSubdir) - var extraFiles android.WritablePaths var vdexInstalls android.RuleBuilderInstalls var unstrippedInstalls android.RuleBuilderInstalls - // dex preopt on the bootclasspath produces multiple files. The first dex file - // is converted into to 'name'.art (to match the legacy assumption that 'name'.art - // exists), and the rest are converted to 'name'-<jar>.art. - // In addition, each .art file has an associated .oat and .vdex file, and an - // unstripped .oat file - for i, m := range image.modules { - name := image.name - if i != 0 { - name += "-" + m - } + var zipFiles android.WritablePaths - art := outputDir.Join(ctx, name+".art") - oat := outputDir.Join(ctx, name+".oat") - vdex := outputDir.Join(ctx, name+".vdex") - unstrippedOat := symbolsDir.Join(ctx, name+".oat") + for _, artOrOat := range image.moduleFiles(ctx, outputDir, ".art", ".oat") { + cmd.ImplicitOutput(artOrOat) + zipFiles = append(zipFiles, artOrOat) - extraFiles = append(extraFiles, art, oat, vdex, unstrippedOat) + // Install the .oat and .art files + rule.Install(artOrOat, filepath.Join(installDir, artOrOat.Base())) + } - // Install the .oat and .art files. - rule.Install(art, filepath.Join(installDir, art.Base())) - rule.Install(oat, filepath.Join(installDir, oat.Base())) + for _, vdex := range image.moduleFiles(ctx, outputDir, ".vdex") { + cmd.ImplicitOutput(vdex) + zipFiles = append(zipFiles, vdex) // The vdex files are identical between architectures, install them to a shared location. The Make rules will // only use the install rules for one architecture, and will create symlinks into the architecture-specific // directories. vdexInstalls = append(vdexInstalls, android.RuleBuilderInstall{vdex, filepath.Join(vdexInstallDir, vdex.Base())}) + } + + for _, unstrippedOat := range image.moduleFiles(ctx, symbolsDir, ".oat") { + cmd.ImplicitOutput(unstrippedOat) // Install the unstripped oat files. The Make rules will put these in $(TARGET_OUT_UNSTRIPPED) unstrippedInstalls = append(unstrippedInstalls, android.RuleBuilderInstall{unstrippedOat, filepath.Join(installDir, unstrippedOat.Base())}) } - cmd.ImplicitOutputs(extraFiles) - rule.Build(pctx, ctx, image.name+"JarsDexpreopt_"+arch.String(), "dexpreopt "+image.name+" jars "+arch.String()) // save output and installed files for makevars - image.installs[arch] = rule.Installs() - image.vdexInstalls[arch] = vdexInstalls - image.unstrippedInstalls[arch] = unstrippedInstalls + image.installs = rule.Installs() + image.vdexInstalls = vdexInstalls + image.unstrippedInstalls = unstrippedInstalls + + return zipFiles } const failureMessage = `ERROR: Dex2oat failed to compile a boot image. It is likely that the boot classpath is inconsistent. Rebuild with ART_BOOT_IMAGE_EXTRA_ARGS="--runtime-arg -verbose:verifier" to see verification errors.` -func bootImageProfileRule(ctx android.SingletonContext, image *bootImage, missingDeps []string) android.WritablePath { - global := dexpreoptGlobalConfig(ctx) +func bootImageProfileRule(ctx android.SingletonContext, image *bootImageConfig, missingDeps []string) android.WritablePath { + globalSoong := dexpreopt.GetCachedGlobalSoongConfig(ctx) + global := dexpreopt.GetGlobalConfig(ctx) - if !global.UseProfileForBootImage || ctx.Config().IsPdkBuild() || ctx.Config().UnbundledBuild() { + if global.DisableGenerateProfile || ctx.Config().IsPdkBuild() || ctx.Config().UnbundledBuild() { return nil } - return ctx.Config().Once(bootImageProfileRuleKey, func() interface{} { - tools := global.Tools + profile := ctx.Config().Once(bootImageProfileRuleKey, func() interface{} { + defaultProfile := "frameworks/base/config/boot-image-profile.txt" rule := android.NewRuleBuilder() rule.MissingDeps(missingDeps) @@ -356,28 +519,23 @@ func bootImageProfileRule(ctx android.SingletonContext, image *bootImage, missin bootImageProfile = combinedBootImageProfile } else if len(global.BootImageProfiles) == 1 { bootImageProfile = global.BootImageProfiles[0] + } else if path := android.ExistentPathForSource(ctx, defaultProfile); path.Valid() { + bootImageProfile = path.Path() } else { - // If not set, use the default. Some branches like master-art-host don't have frameworks/base, so manually - // handle the case that the default is missing. Those branches won't attempt to build the profile rule, - // and if they do they'll get a missing deps error. - defaultProfile := "frameworks/base/config/boot-image-profile.txt" - path := android.ExistentPathForSource(ctx, defaultProfile) - if path.Valid() { - bootImageProfile = path.Path() - } else { - missingDeps = append(missingDeps, defaultProfile) - bootImageProfile = android.PathForOutput(ctx, "missing") - } + // No profile (not even a default one, which is the case on some branches + // like master-art-host that don't have frameworks/base). + // Return nil and continue without profile. + return nil } profile := image.dir.Join(ctx, "boot.prof") rule.Command(). Text(`ANDROID_LOG_TAGS="*:e"`). - Tool(tools.Profman). + Tool(globalSoong.Profman). FlagWithInput("--create-profile-from=", bootImageProfile). - FlagForEachInput("--apk=", image.dexPaths.Paths()). - FlagForEachArg("--dex-location=", image.dexLocations). + FlagForEachInput("--apk=", image.dexPathsDeps.Paths()). + FlagForEachArg("--dex-location=", image.dexLocationsDeps). FlagWithOutput("--reference-profile-file=", profile) rule.Install(profile, "/system/etc/boot-image.prof") @@ -387,29 +545,128 @@ func bootImageProfileRule(ctx android.SingletonContext, image *bootImage, missin image.profileInstalls = rule.Installs() return profile - }).(android.WritablePath) + }) + if profile == nil { + return nil // wrap nil into a typed pointer with value nil + } + return profile.(android.WritablePath) } var bootImageProfileRuleKey = android.NewOnceKey("bootImageProfileRule") -func dumpOatRules(ctx android.SingletonContext, image *bootImage) { - var archs []android.ArchType - for arch := range image.images { - archs = append(archs, arch) +func bootFrameworkProfileRule(ctx android.SingletonContext, image *bootImageConfig, missingDeps []string) android.WritablePath { + globalSoong := dexpreopt.GetCachedGlobalSoongConfig(ctx) + global := dexpreopt.GetGlobalConfig(ctx) + + if global.DisableGenerateProfile || ctx.Config().IsPdkBuild() || ctx.Config().UnbundledBuild() { + return nil } - sort.Slice(archs, func(i, j int) bool { return archs[i].String() < archs[j].String() }) + return ctx.Config().Once(bootFrameworkProfileRuleKey, func() interface{} { + rule := android.NewRuleBuilder() + rule.MissingDeps(missingDeps) + + // Some branches like master-art-host don't have frameworks/base, so manually + // handle the case that the default is missing. Those branches won't attempt to build the profile rule, + // and if they do they'll get a missing deps error. + defaultProfile := "frameworks/base/config/boot-profile.txt" + path := android.ExistentPathForSource(ctx, defaultProfile) + var bootFrameworkProfile android.Path + if path.Valid() { + bootFrameworkProfile = path.Path() + } else { + missingDeps = append(missingDeps, defaultProfile) + bootFrameworkProfile = android.PathForOutput(ctx, "missing") + } + + profile := image.dir.Join(ctx, "boot.bprof") + + rule.Command(). + Text(`ANDROID_LOG_TAGS="*:e"`). + Tool(globalSoong.Profman). + Flag("--generate-boot-profile"). + FlagWithInput("--create-profile-from=", bootFrameworkProfile). + FlagForEachInput("--apk=", image.dexPathsDeps.Paths()). + FlagForEachArg("--dex-location=", image.dexLocationsDeps). + FlagWithOutput("--reference-profile-file=", profile) + rule.Install(profile, "/system/etc/boot-image.bprof") + rule.Build(pctx, ctx, "bootFrameworkProfile", "profile boot framework jars") + image.profileInstalls = append(image.profileInstalls, rule.Installs()...) + + return profile + }).(android.WritablePath) +} + +var bootFrameworkProfileRuleKey = android.NewOnceKey("bootFrameworkProfileRule") + +func updatableBcpPackagesRule(ctx android.SingletonContext, image *bootImageConfig, missingDeps []string) android.WritablePath { + if ctx.Config().IsPdkBuild() || ctx.Config().UnbundledBuild() { + return nil + } + + return ctx.Config().Once(updatableBcpPackagesRuleKey, func() interface{} { + global := dexpreopt.GetGlobalConfig(ctx) + updatableModules := dexpreopt.GetJarsFromApexJarPairs(global.UpdatableBootJars) + + // Collect `permitted_packages` for updatable boot jars. + var updatablePackages []string + ctx.VisitAllModules(func(module android.Module) { + if j, ok := module.(PermittedPackagesForUpdatableBootJars); ok { + name := ctx.ModuleName(module) + if i := android.IndexList(name, updatableModules); i != -1 { + pp := j.PermittedPackagesForUpdatableBootJars() + if len(pp) > 0 { + updatablePackages = append(updatablePackages, pp...) + } else { + ctx.Errorf("Missing permitted_packages for %s", name) + } + // Do not match the same library repeatedly. + updatableModules = append(updatableModules[:i], updatableModules[i+1:]...) + } + } + }) + + // Sort updatable packages to ensure deterministic ordering. + sort.Strings(updatablePackages) + + updatableBcpPackagesName := "updatable-bcp-packages.txt" + updatableBcpPackages := image.dir.Join(ctx, updatableBcpPackagesName) + + ctx.Build(pctx, android.BuildParams{ + Rule: android.WriteFile, + Output: updatableBcpPackages, + Args: map[string]string{ + // WriteFile automatically adds the last end-of-line. + "content": strings.Join(updatablePackages, "\\n"), + }, + }) + + rule := android.NewRuleBuilder() + rule.MissingDeps(missingDeps) + rule.Install(updatableBcpPackages, "/system/etc/"+updatableBcpPackagesName) + // TODO: Rename `profileInstalls` to `extraInstalls`? + // Maybe even move the field out of the bootImageConfig into some higher level type? + image.profileInstalls = append(image.profileInstalls, rule.Installs()...) + + return updatableBcpPackages + }).(android.WritablePath) +} + +var updatableBcpPackagesRuleKey = android.NewOnceKey("updatableBcpPackagesRule") + +func dumpOatRules(ctx android.SingletonContext, image *bootImageConfig) { var allPhonies android.Paths - for _, arch := range archs { + for _, image := range image.variants { + arch := image.target.Arch.ArchType // Create a rule to call oatdump. output := android.PathForOutput(ctx, "boot."+arch.String()+".oatdump.txt") rule := android.NewRuleBuilder() rule.Command(). // TODO: for now, use the debug version for better error reporting - Tool(ctx.Config().HostToolPath(ctx, "oatdumpd")). - FlagWithInputList("--runtime-arg -Xbootclasspath:", image.dexPaths.Paths(), ":"). - FlagWithList("--runtime-arg -Xbootclasspath-locations:", image.dexLocations, ":"). - FlagWithArg("--image=", dexpreopt.PathToLocation(image.images[arch], arch)).Implicit(image.images[arch]). + BuiltTool(ctx, "oatdumpd"). + FlagWithInputList("--runtime-arg -Xbootclasspath:", image.dexPathsDeps.Paths(), ":"). + FlagWithList("--runtime-arg -Xbootclasspath-locations:", image.dexLocationsDeps, ":"). + FlagWithArg("--image=", strings.Join(image.imageLocations, ":")).Implicits(image.imagesDeps.Paths()). FlagWithOutput("--output=", output). FlagWithArg("--instruction-set=", arch.String()) rule.Build(pctx, ctx, "dump-oat-boot-"+arch.String(), "dump oat boot "+arch.String()) @@ -436,30 +693,45 @@ func dumpOatRules(ctx android.SingletonContext, image *bootImage) { } +func writeGlobalConfigForMake(ctx android.SingletonContext, path android.WritablePath) { + data := dexpreopt.GetGlobalConfigRawData(ctx) + + ctx.Build(pctx, android.BuildParams{ + Rule: android.WriteFile, + Output: path, + Args: map[string]string{ + "content": string(data), + }, + }) +} + // Export paths for default boot image to Make func (d *dexpreoptBootJars) MakeVars(ctx android.MakeVarsContext) { + if d.dexpreoptConfigForMake != nil { + ctx.Strict("DEX_PREOPT_CONFIG_FOR_MAKE", d.dexpreoptConfigForMake.String()) + ctx.Strict("DEX_PREOPT_SOONG_CONFIG_FOR_MAKE", android.PathForOutput(ctx, "dexpreopt_soong.config").String()) + } + image := d.defaultBootImage if image != nil { ctx.Strict("DEXPREOPT_IMAGE_PROFILE_BUILT_INSTALLED", image.profileInstalls.String()) - ctx.Strict("DEXPREOPT_BOOTCLASSPATH_DEX_FILES", strings.Join(image.dexPaths.Strings(), " ")) - ctx.Strict("DEXPREOPT_BOOTCLASSPATH_DEX_LOCATIONS", strings.Join(image.dexLocations, " ")) + ctx.Strict("DEXPREOPT_BOOTCLASSPATH_DEX_FILES", strings.Join(image.dexPathsDeps.Strings(), " ")) + ctx.Strict("DEXPREOPT_BOOTCLASSPATH_DEX_LOCATIONS", strings.Join(image.dexLocationsDeps, " ")) var imageNames []string for _, current := range append(d.otherImages, image) { imageNames = append(imageNames, current.name) - var arches []android.ArchType - for arch, _ := range current.images { - arches = append(arches, arch) + for _, current := range current.variants { + sfx := current.name + "_" + current.target.Arch.ArchType.String() + ctx.Strict("DEXPREOPT_IMAGE_VDEX_BUILT_INSTALLED_"+sfx, current.vdexInstalls.String()) + ctx.Strict("DEXPREOPT_IMAGE_"+sfx, current.images.String()) + ctx.Strict("DEXPREOPT_IMAGE_DEPS_"+sfx, strings.Join(current.imagesDeps.Strings(), " ")) + ctx.Strict("DEXPREOPT_IMAGE_BUILT_INSTALLED_"+sfx, current.installs.String()) + ctx.Strict("DEXPREOPT_IMAGE_UNSTRIPPED_BUILT_INSTALLED_"+sfx, current.unstrippedInstalls.String()) } - sort.Slice(arches, func(i, j int) bool { return arches[i].String() < arches[j].String() }) - - for _, arch := range arches { - ctx.Strict("DEXPREOPT_IMAGE_VDEX_BUILT_INSTALLED_"+current.name+"_"+arch.String(), current.vdexInstalls[arch].String()) - ctx.Strict("DEXPREOPT_IMAGE_"+current.name+"_"+arch.String(), current.images[arch].String()) - ctx.Strict("DEXPREOPT_IMAGE_BUILT_INSTALLED_"+current.name+"_"+arch.String(), current.installs[arch].String()) - ctx.Strict("DEXPREOPT_IMAGE_UNSTRIPPED_BUILT_INSTALLED_"+current.name+"_"+arch.String(), current.unstrippedInstalls[arch].String()) - } + ctx.Strict("DEXPREOPT_IMAGE_LOCATIONS_"+current.name, strings.Join(current.imageLocations, ":")) + ctx.Strict("DEXPREOPT_IMAGE_ZIP_"+current.name, current.zip.String()) } ctx.Strict("DEXPREOPT_IMAGE_NAMES", strings.Join(imageNames, " ")) } diff --git a/java/dexpreopt_bootjars_test.go b/java/dexpreopt_bootjars_test.go index cbb52f15c..e7b3c3ba9 100644 --- a/java/dexpreopt_bootjars_test.go +++ b/java/dexpreopt_bootjars_test.go @@ -44,24 +44,25 @@ func TestDexpreoptBootJars(t *testing.T) { } ` - config := testConfig(nil) + config := testConfig(nil, bp, nil) - pathCtx := android.PathContextForTesting(config, nil) + pathCtx := android.PathContextForTesting(config) dexpreoptConfig := dexpreopt.GlobalConfigForTests(pathCtx) - dexpreoptConfig.RuntimeApexJars = []string{"foo", "bar", "baz"} - setDexpreoptTestGlobalConfig(config, dexpreoptConfig) + dexpreoptConfig.BootJars = []string{"foo", "bar", "baz"} + dexpreopt.SetTestGlobalConfig(config, dexpreoptConfig) - ctx := testContext(config, bp, nil) + ctx := testContext() - ctx.RegisterSingletonType("dex_bootjars", android.SingletonFactoryAdaptor(dexpreoptBootJarsFactory)) + RegisterDexpreoptBootJarsComponents(ctx) run(t, ctx, config) dexpreoptBootJars := ctx.SingletonForTests("dex_bootjars") - bootArt := dexpreoptBootJars.Output("boot.art") + bootArt := dexpreoptBootJars.Output("boot-foo.art") expectedInputs := []string{ + "dex_artjars/apex/com.android.art/javalib/arm64/boot.art", "dex_bootjars_input/foo.jar", "dex_bootjars_input/bar.jar", "dex_bootjars_input/baz.jar", @@ -82,19 +83,19 @@ func TestDexpreoptBootJars(t *testing.T) { expectedOutputs := []string{ "dex_bootjars/system/framework/arm64/boot.invocation", - "dex_bootjars/system/framework/arm64/boot.art", + "dex_bootjars/system/framework/arm64/boot-foo.art", "dex_bootjars/system/framework/arm64/boot-bar.art", "dex_bootjars/system/framework/arm64/boot-baz.art", - "dex_bootjars/system/framework/arm64/boot.oat", + "dex_bootjars/system/framework/arm64/boot-foo.oat", "dex_bootjars/system/framework/arm64/boot-bar.oat", "dex_bootjars/system/framework/arm64/boot-baz.oat", - "dex_bootjars/system/framework/arm64/boot.vdex", + "dex_bootjars/system/framework/arm64/boot-foo.vdex", "dex_bootjars/system/framework/arm64/boot-bar.vdex", "dex_bootjars/system/framework/arm64/boot-baz.vdex", - "dex_bootjars_unstripped/system/framework/arm64/boot.oat", + "dex_bootjars_unstripped/system/framework/arm64/boot-foo.oat", "dex_bootjars_unstripped/system/framework/arm64/boot-bar.oat", "dex_bootjars_unstripped/system/framework/arm64/boot-baz.oat", } diff --git a/java/dexpreopt_config.go b/java/dexpreopt_config.go index abc5fa18f..f8356d188 100644 --- a/java/dexpreopt_config.go +++ b/java/dexpreopt_config.go @@ -15,181 +15,194 @@ package java import ( - "android/soong/android" - "android/soong/dexpreopt" + "fmt" "path/filepath" "strings" -) - -// dexpreoptGlobalConfig returns the global dexpreopt.config. It is loaded once the first time it is called for any -// ctx.Config(), and returns the same data for all future calls with the same ctx.Config(). A value can be inserted -// for tests using setDexpreoptTestGlobalConfig. -func dexpreoptGlobalConfig(ctx android.PathContext) dexpreopt.GlobalConfig { - return ctx.Config().Once(dexpreoptGlobalConfigKey, func() interface{} { - if f := ctx.Config().DexpreoptGlobalConfig(); f != "" { - ctx.AddNinjaFileDeps(f) - globalConfig, err := dexpreopt.LoadGlobalConfig(ctx, f) - if err != nil { - panic(err) - } - return globalConfig - } - // No global config filename set, see if there is a test config set - return ctx.Config().Once(dexpreoptTestGlobalConfigKey, func() interface{} { - // Nope, return a config with preopting disabled - return dexpreopt.GlobalConfig{ - DisablePreopt: true, - } - }) - }).(dexpreopt.GlobalConfig) -} - -// setDexpreoptTestGlobalConfig sets a GlobalConfig that future calls to dexpreoptGlobalConfig will return. It must -// be called before the first call to dexpreoptGlobalConfig for the config. -func setDexpreoptTestGlobalConfig(config android.Config, globalConfig dexpreopt.GlobalConfig) { - config.Once(dexpreoptTestGlobalConfigKey, func() interface{} { return globalConfig }) -} - -var dexpreoptGlobalConfigKey = android.NewOnceKey("DexpreoptGlobalConfig") -var dexpreoptTestGlobalConfigKey = android.NewOnceKey("TestDexpreoptGlobalConfig") + "android/soong/android" + "android/soong/dexpreopt" +) // systemServerClasspath returns the on-device locations of the modules in the system server classpath. It is computed // once the first time it is called for any ctx.Config(), and returns the same slice for all future calls with the same // ctx.Config(). -func systemServerClasspath(ctx android.PathContext) []string { +func systemServerClasspath(ctx android.MakeVarsContext) []string { return ctx.Config().OnceStringSlice(systemServerClasspathKey, func() []string { - global := dexpreoptGlobalConfig(ctx) - + global := dexpreopt.GetGlobalConfig(ctx) var systemServerClasspathLocations []string - for _, m := range global.SystemServerJars { + nonUpdatable := dexpreopt.NonUpdatableSystemServerJars(ctx, global) + // 1) Non-updatable jars. + for _, m := range nonUpdatable { systemServerClasspathLocations = append(systemServerClasspathLocations, filepath.Join("/system/framework", m+".jar")) } + // 2) The jars that are from an updatable apex. + for _, m := range global.UpdatableSystemServerJars { + systemServerClasspathLocations = append(systemServerClasspathLocations, + dexpreopt.GetJarLocationFromApexJarPair(m)) + } + if len(systemServerClasspathLocations) != len(global.SystemServerJars)+len(global.UpdatableSystemServerJars) { + panic(fmt.Errorf("Wrong number of system server jars, got %d, expected %d", + len(systemServerClasspathLocations), + len(global.SystemServerJars)+len(global.UpdatableSystemServerJars))) + } return systemServerClasspathLocations }) } var systemServerClasspathKey = android.NewOnceKey("systemServerClasspath") -// defaultBootImageConfig returns the bootImageConfig that will be used to dexpreopt modules. It is computed once the -// first time it is called for any ctx.Config(), and returns the same slice for all future calls with the same -// ctx.Config(). -func defaultBootImageConfig(ctx android.PathContext) bootImageConfig { - return ctx.Config().Once(defaultBootImageConfigKey, func() interface{} { - global := dexpreoptGlobalConfig(ctx) +// dexpreoptTargets returns the list of targets that are relevant to dexpreopting, which excludes architectures +// supported through native bridge. +func dexpreoptTargets(ctx android.PathContext) []android.Target { + var targets []android.Target + for _, target := range ctx.Config().Targets[android.Android] { + if target.NativeBridge == android.NativeBridgeDisabled { + targets = append(targets, target) + } + } - runtimeModules := global.RuntimeApexJars - nonFrameworkModules := concat(runtimeModules, global.ProductUpdatableBootModules) - frameworkModules := android.RemoveListFromList(global.BootJars, nonFrameworkModules) + return targets +} - var nonUpdatableBootModules []string - var nonUpdatableBootLocations []string +func stemOf(moduleName string) string { + // b/139391334: the stem of framework-minus-apex is framework + // This is hard coded here until we find a good way to query the stem + // of a module before any other mutators are run + if moduleName == "framework-minus-apex" { + return "framework" + } + return moduleName +} - for _, m := range runtimeModules { - nonUpdatableBootModules = append(nonUpdatableBootModules, m) - nonUpdatableBootLocations = append(nonUpdatableBootLocations, - filepath.Join("/apex/com.android.runtime/javalib", m+".jar")) +var ( + bootImageConfigKey = android.NewOnceKey("bootImageConfig") + artBootImageName = "art" + frameworkBootImageName = "boot" +) + +// Construct the global boot image configs. +func genBootImageConfigs(ctx android.PathContext) map[string]*bootImageConfig { + return ctx.Config().Once(bootImageConfigKey, func() interface{} { + + global := dexpreopt.GetGlobalConfig(ctx) + targets := dexpreoptTargets(ctx) + deviceDir := android.PathForOutput(ctx, ctx.Config().DeviceName()) + + artModules := global.ArtApexJars + // With EMMA_INSTRUMENT_FRAMEWORK=true the Core libraries depend on jacoco. + if ctx.Config().IsEnvTrue("EMMA_INSTRUMENT_FRAMEWORK") { + artModules = append(artModules, "jacocoagent") } + frameworkModules := android.RemoveListFromList(global.BootJars, + concat(artModules, dexpreopt.GetJarsFromApexJarPairs(global.UpdatableBootJars))) + artSubdir := "apex/com.android.art/javalib" + frameworkSubdir := "system/framework" + + var artLocations, frameworkLocations []string + for _, m := range artModules { + artLocations = append(artLocations, filepath.Join("/"+artSubdir, stemOf(m)+".jar")) + } for _, m := range frameworkModules { - nonUpdatableBootModules = append(nonUpdatableBootModules, m) - nonUpdatableBootLocations = append(nonUpdatableBootLocations, - filepath.Join("/system/framework", m+".jar")) + frameworkLocations = append(frameworkLocations, filepath.Join("/"+frameworkSubdir, stemOf(m)+".jar")) } - // The path to bootclasspath dex files needs to be known at module GenerateAndroidBuildAction time, before - // the bootclasspath modules have been compiled. Set up known paths for them, the singleton rules will copy - // them there. - // TODO: use module dependencies instead - var nonUpdatableBootDexPaths android.WritablePaths - for _, m := range nonUpdatableBootModules { - nonUpdatableBootDexPaths = append(nonUpdatableBootDexPaths, - android.PathForOutput(ctx, ctx.Config().DeviceName(), "dex_bootjars_input", m+".jar")) + // ART config for the primary boot image in the ART apex. + // It includes the Core Libraries. + artCfg := bootImageConfig{ + extension: false, + name: artBootImageName, + stem: "boot", + installSubdir: artSubdir, + modules: artModules, + dexLocations: artLocations, + dexLocationsDeps: artLocations, } - dir := android.PathForOutput(ctx, ctx.Config().DeviceName(), "dex_bootjars") - symbolsDir := android.PathForOutput(ctx, ctx.Config().DeviceName(), "dex_bootjars_unstripped") - images := make(map[android.ArchType]android.OutputPath) - - for _, target := range ctx.Config().Targets[android.Android] { - images[target.Arch.ArchType] = dir.Join(ctx, - "system/framework", target.Arch.ArchType.String()).Join(ctx, "boot.art") + // Framework config for the boot image extension. + // It includes framework libraries and depends on the ART config. + frameworkCfg := bootImageConfig{ + extension: true, + name: frameworkBootImageName, + stem: "boot", + installSubdir: frameworkSubdir, + modules: frameworkModules, + dexLocations: frameworkLocations, + dexLocationsDeps: append(artLocations, frameworkLocations...), } - return bootImageConfig{ - name: "boot", - modules: nonUpdatableBootModules, - dexLocations: nonUpdatableBootLocations, - dexPaths: nonUpdatableBootDexPaths, - dir: dir, - symbolsDir: symbolsDir, - images: images, + configs := map[string]*bootImageConfig{ + artBootImageName: &artCfg, + frameworkBootImageName: &frameworkCfg, } - }).(bootImageConfig) -} - -var defaultBootImageConfigKey = android.NewOnceKey("defaultBootImageConfig") -func apexBootImageConfig(ctx android.PathContext) bootImageConfig { - return ctx.Config().Once(apexBootImageConfigKey, func() interface{} { - global := dexpreoptGlobalConfig(ctx) + // common to all configs + for _, c := range configs { + c.dir = deviceDir.Join(ctx, "dex_"+c.name+"jars") + c.symbolsDir = deviceDir.Join(ctx, "dex_"+c.name+"jars_unstripped") - runtimeModules := global.RuntimeApexJars - nonFrameworkModules := concat(runtimeModules, global.ProductUpdatableBootModules) - frameworkModules := android.RemoveListFromList(global.BootJars, nonFrameworkModules) - imageModules := concat(runtimeModules, frameworkModules) + // expands to <stem>.art for primary image and <stem>-<1st module>.art for extension + imageName := c.firstModuleNameOrStem() + ".art" - var bootLocations []string + c.imageLocations = []string{c.dir.Join(ctx, c.installSubdir, imageName).String()} - for _, m := range runtimeModules { - bootLocations = append(bootLocations, - filepath.Join("/apex/com.android.runtime/javalib", m+".jar")) - } + // The path to bootclasspath dex files needs to be known at module + // GenerateAndroidBuildAction time, before the bootclasspath modules have been compiled. + // Set up known paths for them, the singleton rules will copy them there. + // TODO(b/143682396): use module dependencies instead + inputDir := deviceDir.Join(ctx, "dex_"+c.name+"jars_input") + for _, m := range c.modules { + c.dexPaths = append(c.dexPaths, inputDir.Join(ctx, stemOf(m)+".jar")) + } + c.dexPathsDeps = c.dexPaths + + // Create target-specific variants. + for _, target := range targets { + arch := target.Arch.ArchType + imageDir := c.dir.Join(ctx, c.installSubdir, arch.String()) + variant := &bootImageVariant{ + bootImageConfig: c, + target: target, + images: imageDir.Join(ctx, imageName), + imagesDeps: c.moduleFiles(ctx, imageDir, ".art", ".oat", ".vdex"), + } + c.variants = append(c.variants, variant) + } - for _, m := range frameworkModules { - bootLocations = append(bootLocations, - filepath.Join("/system/framework", m+".jar")) + c.zip = c.dir.Join(ctx, c.name+".zip") } - // The path to bootclasspath dex files needs to be known at module GenerateAndroidBuildAction time, before - // the bootclasspath modules have been compiled. Set up known paths for them, the singleton rules will copy - // them there. - // TODO: use module dependencies instead - var bootDexPaths android.WritablePaths - for _, m := range imageModules { - bootDexPaths = append(bootDexPaths, - android.PathForOutput(ctx, ctx.Config().DeviceName(), "dex_apexjars_input", m+".jar")) + // specific to the framework config + frameworkCfg.dexPathsDeps = append(artCfg.dexPathsDeps, frameworkCfg.dexPathsDeps...) + for i := range targets { + frameworkCfg.variants[i].primaryImages = artCfg.variants[i].images } + frameworkCfg.imageLocations = append(artCfg.imageLocations, frameworkCfg.imageLocations...) - dir := android.PathForOutput(ctx, ctx.Config().DeviceName(), "dex_apexjars") - symbolsDir := android.PathForOutput(ctx, ctx.Config().DeviceName(), "dex_apexjars_unstripped") - images := make(map[android.ArchType]android.OutputPath) - - for _, target := range ctx.Config().Targets[android.Android] { - images[target.Arch.ArchType] = dir.Join(ctx, - "system/framework", target.Arch.ArchType.String(), "apex.art") - } + return configs + }).(map[string]*bootImageConfig) +} - return bootImageConfig{ - name: "apex", - modules: imageModules, - dexLocations: bootLocations, - dexPaths: bootDexPaths, - dir: dir, - symbolsDir: symbolsDir, - images: images, - } - }).(bootImageConfig) +func artBootImageConfig(ctx android.PathContext) *bootImageConfig { + return genBootImageConfigs(ctx)[artBootImageName] } -var apexBootImageConfigKey = android.NewOnceKey("apexBootImageConfig") +func defaultBootImageConfig(ctx android.PathContext) *bootImageConfig { + return genBootImageConfigs(ctx)[frameworkBootImageName] +} func defaultBootclasspath(ctx android.PathContext) []string { return ctx.Config().OnceStringSlice(defaultBootclasspathKey, func() []string { - global := dexpreoptGlobalConfig(ctx) + global := dexpreopt.GetGlobalConfig(ctx) image := defaultBootImageConfig(ctx) - bootclasspath := append(copyOf(image.dexLocations), global.ProductUpdatableBootLocations...) + + updatableBootclasspath := make([]string, len(global.UpdatableBootJars)) + for i, p := range global.UpdatableBootJars { + updatableBootclasspath[i] = dexpreopt.GetJarLocationFromApexJarPair(p) + } + + bootclasspath := append(copyOf(image.dexLocationsDeps), updatableBootclasspath...) return bootclasspath }) } @@ -204,7 +217,7 @@ func init() { func dexpreoptConfigMakevars(ctx android.MakeVarsContext) { ctx.Strict("PRODUCT_BOOTCLASSPATH", strings.Join(defaultBootclasspath(ctx), ":")) - ctx.Strict("PRODUCT_DEX2OAT_BOOTCLASSPATH", strings.Join(defaultBootImageConfig(ctx).dexLocations, ":")) + ctx.Strict("PRODUCT_DEX2OAT_BOOTCLASSPATH", strings.Join(defaultBootImageConfig(ctx).dexLocationsDeps, ":")) ctx.Strict("PRODUCT_SYSTEM_SERVER_CLASSPATH", strings.Join(systemServerClasspath(ctx), ":")) ctx.Strict("DEXPREOPT_BOOT_JARS_MODULES", strings.Join(defaultBootImageConfig(ctx).modules, ":")) diff --git a/java/dexpreopt_test.go b/java/dexpreopt_test.go index 4af2f5c38..5550a4c17 100644 --- a/java/dexpreopt_test.go +++ b/java/dexpreopt_test.go @@ -30,6 +30,7 @@ func TestDexpreoptEnabled(t *testing.T) { android_app { name: "foo", srcs: ["a.java"], + sdk_version: "current", }`, enabled: true, }, @@ -52,14 +53,29 @@ func TestDexpreoptEnabled(t *testing.T) { }`, enabled: true, }, - { name: "app without sources", bp: ` android_app { name: "foo", + sdk_version: "current", + }`, + enabled: false, + }, + { + name: "app with libraries", + bp: ` + android_app { + name: "foo", + static_libs: ["lib"], + sdk_version: "current", + } + + java_library { + name: "lib", + srcs: ["a.java"], + sdk_version: "current", }`, - // TODO(ccross): this should probably be false enabled: true, }, { @@ -69,10 +85,8 @@ func TestDexpreoptEnabled(t *testing.T) { name: "foo", installable: true, }`, - // TODO(ccross): this should probably be false - enabled: true, + enabled: false, }, - { name: "static java library", bp: ` @@ -132,7 +146,7 @@ func TestDexpreoptEnabled(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - ctx := testJava(t, test.bp) + ctx, _ := testJava(t, test.bp) dexpreopt := ctx.ModuleForTests("foo", "android_common").MaybeDescription("dexpreopt") enabled := dexpreopt.Rule != nil diff --git a/java/droiddoc.go b/java/droiddoc.go index f56cae826..b564fea01 100644 --- a/java/droiddoc.go +++ b/java/droiddoc.go @@ -15,141 +15,50 @@ package java import ( - "android/soong/android" - "android/soong/java/config" "fmt" "path/filepath" - "runtime" "strings" "github.com/google/blueprint" + "github.com/google/blueprint/proptools" + + "android/soong/android" + "android/soong/java/config" + "android/soong/remoteexec" ) -var ( - javadoc = pctx.AndroidStaticRule("javadoc", - blueprint.RuleParams{ - Command: `rm -rf "$outDir" "$srcJarDir" "$stubsDir" && mkdir -p "$outDir" "$srcJarDir" "$stubsDir" && ` + - `${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` + - `${config.SoongJavacWrapper} ${config.JavadocCmd} -encoding UTF-8 @$out.rsp @$srcJarDir/list ` + - `$opts $bootclasspathArgs $classpathArgs $sourcepathArgs ` + - `-d $outDir -quiet && ` + - `${config.SoongZipCmd} -write_if_changed -d -o $docZip -C $outDir -D $outDir && ` + - `${config.SoongZipCmd} -write_if_changed -jar -o $out -C $stubsDir -D $stubsDir $postDoclavaCmds && ` + - `rm -rf "$srcJarDir"`, - - CommandDeps: []string{ - "${config.ZipSyncCmd}", - "${config.JavadocCmd}", - "${config.SoongZipCmd}", - }, - CommandOrderOnly: []string{"${config.SoongJavacWrapper}"}, - Rspfile: "$out.rsp", - RspfileContent: "$in", - Restat: true, - }, - "outDir", "srcJarDir", "stubsDir", "srcJars", "opts", - "bootclasspathArgs", "classpathArgs", "sourcepathArgs", "docZip", "postDoclavaCmds") - - apiCheck = pctx.AndroidStaticRule("apiCheck", - blueprint.RuleParams{ - Command: `( ${config.ApiCheckCmd} -JXmx1024m -J"classpath $classpath" $opts ` + - `$apiFile $apiFileToCheck $removedApiFile $removedApiFileToCheck ` + - `&& touch $out ) || (echo -e "$msg" ; exit 38)`, - CommandDeps: []string{ - "${config.ApiCheckCmd}", - }, +func init() { + RegisterDocsBuildComponents(android.InitRegistrationContext) + RegisterStubsBuildComponents(android.InitRegistrationContext) + + // Register sdk member type. + android.RegisterSdkMemberType(&droidStubsSdkMemberType{ + SdkMemberTypeBase: android.SdkMemberTypeBase{ + PropertyName: "stubs_sources", + // stubs_sources can be used with sdk to provide the source stubs for APIs provided by + // the APEX. + SupportsSdk: true, }, - "classpath", "opts", "apiFile", "apiFileToCheck", "removedApiFile", "removedApiFileToCheck", "msg") + }) +} - updateApi = pctx.AndroidStaticRule("updateApi", - blueprint.RuleParams{ - Command: `( ( cp -f $srcApiFile $destApiFile && cp -f $srcRemovedApiFile $destRemovedApiFile ) ` + - `&& touch $out ) || (echo failed to update public API ; exit 38)`, - }, - "srcApiFile", "destApiFile", "srcRemovedApiFile", "destRemovedApiFile") - - metalava = pctx.AndroidStaticRule("metalava", - blueprint.RuleParams{ - Command: `rm -rf "$outDir" "$srcJarDir" "$stubsDir" && ` + - `mkdir -p "$outDir" "$srcJarDir" "$stubsDir" && ` + - `${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` + - `${config.JavaCmd} -jar ${config.MetalavaJar} -encoding UTF-8 -source $javaVersion @$out.rsp @$srcJarDir/list ` + - `$bootclasspathArgs $classpathArgs $sourcepathArgs --no-banner --color --quiet --format=v2 ` + - `$opts && ` + - `${config.SoongZipCmd} -write_if_changed -jar -o $out -C $stubsDir -D $stubsDir && ` + - `(if $writeSdkValues; then ${config.SoongZipCmd} -write_if_changed -d -o $metadataZip ` + - `-C $metadataDir -D $metadataDir; fi) && ` + - `rm -rf "$srcJarDir"`, - CommandDeps: []string{ - "${config.ZipSyncCmd}", - "${config.JavaCmd}", - "${config.MetalavaJar}", - "${config.SoongZipCmd}", - }, - Rspfile: "$out.rsp", - RspfileContent: "$in", - Restat: true, - }, - "outDir", "srcJarDir", "stubsDir", "srcJars", "javaVersion", "bootclasspathArgs", - "classpathArgs", "sourcepathArgs", "opts", "writeSdkValues", "metadataZip", "metadataDir") - - metalavaApiCheck = pctx.AndroidStaticRule("metalavaApiCheck", - blueprint.RuleParams{ - Command: `( rm -rf "$srcJarDir" && mkdir -p "$srcJarDir" && ` + - `${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` + - `${config.JavaCmd} -jar ${config.MetalavaJar} -encoding UTF-8 -source $javaVersion @$out.rsp @$srcJarDir/list ` + - `$bootclasspathArgs $classpathArgs $sourcepathArgs --no-banner --color --quiet --format=v2 ` + - `$opts && touch $out && rm -rf "$srcJarDir") || ` + - `( echo -e "$msg" ; exit 38 )`, - CommandDeps: []string{ - "${config.ZipSyncCmd}", - "${config.JavaCmd}", - "${config.MetalavaJar}", - }, - Rspfile: "$out.rsp", - RspfileContent: "$in", - }, - "srcJarDir", "srcJars", "javaVersion", "bootclasspathArgs", "classpathArgs", "sourcepathArgs", "opts", "msg") +func RegisterDocsBuildComponents(ctx android.RegistrationContext) { + ctx.RegisterModuleType("doc_defaults", DocDefaultsFactory) - nullabilityWarningsCheck = pctx.AndroidStaticRule("nullabilityWarningsCheck", - blueprint.RuleParams{ - Command: `( diff $expected $actual && touch $out ) || ( echo -e "$msg" ; exit 38 )`, - }, - "expected", "actual", "msg") - - dokka = pctx.AndroidStaticRule("dokka", - blueprint.RuleParams{ - Command: `rm -rf "$outDir" "$srcJarDir" "$stubsDir" && ` + - `mkdir -p "$outDir" "$srcJarDir" "$stubsDir" && ` + - `${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` + - `${config.JavaCmd} -jar ${config.DokkaJar} $srcJarDir ` + - `$classpathArgs -format dac -dacRoot /reference/kotlin -output $outDir $opts && ` + - `${config.SoongZipCmd} -write_if_changed -d -o $docZip -C $outDir -D $outDir && ` + - `${config.SoongZipCmd} -write_if_changed -jar -o $out -C $stubsDir -D $stubsDir && ` + - `rm -rf "$srcJarDir"`, - CommandDeps: []string{ - "${config.ZipSyncCmd}", - "${config.DokkaJar}", - "${config.MetalavaJar}", - "${config.SoongZipCmd}", - }, - Restat: true, - }, - "outDir", "srcJarDir", "stubsDir", "srcJars", "classpathArgs", "opts", "docZip") -) + ctx.RegisterModuleType("droiddoc", DroiddocFactory) + ctx.RegisterModuleType("droiddoc_host", DroiddocHostFactory) + ctx.RegisterModuleType("droiddoc_exported_dir", ExportedDroiddocDirFactory) + ctx.RegisterModuleType("javadoc", JavadocFactory) + ctx.RegisterModuleType("javadoc_host", JavadocHostFactory) +} -func init() { - android.RegisterModuleType("doc_defaults", DocDefaultsFactory) - android.RegisterModuleType("stubs_defaults", StubsDefaultsFactory) +func RegisterStubsBuildComponents(ctx android.RegistrationContext) { + ctx.RegisterModuleType("stubs_defaults", StubsDefaultsFactory) - android.RegisterModuleType("droiddoc", DroiddocFactory) - android.RegisterModuleType("droiddoc_host", DroiddocHostFactory) - android.RegisterModuleType("droiddoc_exported_dir", ExportedDroiddocDirFactory) - android.RegisterModuleType("javadoc", JavadocFactory) - android.RegisterModuleType("javadoc_host", JavadocHostFactory) + ctx.RegisterModuleType("droidstubs", DroidstubsFactory) + ctx.RegisterModuleType("droidstubs_host", DroidstubsHostFactory) - android.RegisterModuleType("droidstubs", DroidstubsFactory) - android.RegisterModuleType("droidstubs_host", DroidstubsHostFactory) + ctx.RegisterModuleType("prebuilt_stubs_sources", PrebuiltStubsSourcesFactory) } var ( @@ -170,31 +79,25 @@ type JavadocProperties struct { // filegroup or genrule can be included within this property. Exclude_srcs []string `android:"path,arch_variant"` + // list of package names that should actually be used. If this property is left unspecified, + // all the sources from the srcs property is used. + Filter_packages []string + // list of java libraries that will be in the classpath. Libs []string `android:"arch_variant"` - // don't build against the default libraries (bootclasspath, ext, and framework for device - // targets) - No_standard_libs *bool - - // don't build against the framework libraries (ext, and framework for device targets) - No_framework_libs *bool - - // the java library (in classpath) for documentation that provides java srcs and srcjars. - Srcs_lib *string - - // the base dirs under srcs_lib will be scanned for java srcs. - Srcs_lib_whitelist_dirs []string - - // the sub dirs under srcs_lib_whitelist_dirs will be scanned for java srcs. - Srcs_lib_whitelist_pkgs []string - // If set to false, don't allow this module(-docs.zip) to be exported. Defaults to true. Installable *bool - // if not blank, set to the version of the sdk to compile against + // if not blank, set to the version of the sdk to compile against. + // Defaults to compiling against the current platform. Sdk_version *string `android:"arch_variant"` + // When targeting 1.9 and above, override the modules to use with --system, + // otherwise provides defaults libraries to add to the bootclasspath. + // Defaults to "none" + System_modules *string + Aidl struct { // Top level directories to pass to aidl tool Include_dirs []string @@ -213,10 +116,15 @@ type JavadocProperties struct { // Available variables for substitution: // // $(location <label>): the path to the arg_files with name <label> + // $$: a literal $ Args *string // names of the output files used in args that will be generated Out []string + + // If set, metalava is sandboxed to only read files explicitly specified on the command + // line. Defaults to false. + Sandbox *bool } type ApiToCheck struct { @@ -248,7 +156,7 @@ type DroiddocProperties struct { // proofread file contains all of the text content of the javadocs concatenated into one file, // suitable for spell-checking and other goodness. - Proofread_file *string `android:"path"` + Proofread_file *string // a todo file lists the program elements that are missing documentation. // At some point, this might be improved to show more warnings. @@ -274,37 +182,15 @@ type DroiddocProperties struct { // filegroup or genrule can be included within this property. Knowntags []string `android:"path"` - // the tag name used to distinguish if the API files belong to public/system/test. - Api_tag_name *string - // the generated public API filename by Doclava. Api_filename *string - // the generated public Dex API filename by Doclava. - Dex_api_filename *string - - // the generated private API filename by Doclava. - Private_api_filename *string - - // the generated private Dex API filename by Doclava. - Private_dex_api_filename *string - // the generated removed API filename by Doclava. Removed_api_filename *string // the generated removed Dex API filename by Doclava. Removed_dex_api_filename *string - // mapping of dex signatures to source file and line number. This is a temporary property and - // will be deleted; you probably shouldn't be using it. - Dex_mapping_filename *string - - // the generated exact API filename by Doclava. - Exact_api_filename *string - - // the generated proguard filename by Doclava. - Proguard_filename *string - // if set to false, don't allow droiddoc to generate stubs source files. Defaults to true. Create_stubs *bool @@ -320,48 +206,53 @@ type DroiddocProperties struct { // if set to true, generate docs through Dokka instead of Doclava. Dokka_enabled *bool + + // Compat config XML. Generates compat change documentation if set. + Compat_config *string `android:"path"` } type DroidstubsProperties struct { - // the tag name used to distinguish if the API files belong to public/system/test. - Api_tag_name *string - // the generated public API filename by Metalava. Api_filename *string - // the generated public Dex API filename by Metalava. - Dex_api_filename *string - - // the generated private API filename by Metalava. - Private_api_filename *string - - // the generated private Dex API filename by Metalava. - Private_dex_api_filename *string - // the generated removed API filename by Metalava. Removed_api_filename *string // the generated removed Dex API filename by Metalava. Removed_dex_api_filename *string - // mapping of dex signatures to source file and line number. This is a temporary property and - // will be deleted; you probably shouldn't be using it. - Dex_mapping_filename *string - - // the generated exact API filename by Metalava. - Exact_api_filename *string - - // the generated proguard filename by Metalava. - Proguard_filename *string - Check_api struct { Last_released ApiToCheck Current ApiToCheck - // do not perform API check against Last_released, in the case that both two specified API - // files by Last_released are modules which don't exist. + // The java_sdk_library module generates references to modules (i.e. filegroups) + // from which information about the latest API version can be obtained. As those + // modules may not exist (e.g. because a previous version has not been released) it + // sets ignore_missing_latest_api=true on the droidstubs modules it creates so + // that droidstubs can ignore those references if the modules do not yet exist. + // + // If true then this will ignore module references for modules that do not exist + // in properties that supply the previous version of the API. + // + // There are two sets of those: + // * Api_file, Removed_api_file in check_api.last_released + // * New_since in check_api.api_lint.new_since + // + // The first two must be set as a pair, so either they should both exist or neither + // should exist - in which case when this property is true they are ignored. If one + // exists and the other does not then it is an error. Ignore_missing_latest_api *bool `blueprint:"mutated"` + + Api_lint struct { + Enabled *bool + + // If set, performs api_lint on any new APIs not found in the given signature file + New_since *string `android:"path"` + + // If not blank, path to the baseline txt file for approved API lint violations. + Baseline_file *string `android:"path"` + } } // user can specify the version of previous released API file in order to do compatibility check. @@ -385,12 +276,20 @@ type DroidstubsProperties struct { // if set to true, allow Metalava to generate doc_stubs source files. Defaults to false. Create_doc_stubs *bool + // if set to false then do not write out stubs. Defaults to true. + // + // TODO(b/146727827): Remove capability when we do not need to generate stubs and API separately. + Generate_stubs *bool + // is set to true, Metalava will allow framework SDK to contain API levels annotations. Api_levels_annotations_enabled *bool // the dirs which Metalava extracts API levels annotations from. Api_levels_annotations_dirs []string + // the filename which Metalava extracts API levels annotations from. Defaults to android.jar. + Api_levels_jar_filename *string + // if set to true, collect the values used by the Dev tools and // write them in files packaged with the SDK. Defaults to false. Write_sdk_values *bool @@ -414,14 +313,6 @@ type droiddocBuilderFlags struct { doclavaStubsFlags string doclavaDocsFlags string postDoclavaCmds string - - metalavaStubsFlags string - metalavaAnnotationsFlags string - metalavaMergeAnnoDirFlags string - metalavaInclusionAnnotationsFlags string - metalavaApiLevelsAnnotationsFlags string - - metalavaApiToXmlFlags string } func InitDroiddocModule(module android.DefaultableModule, hod android.HostOrDeviceSupported) { @@ -429,8 +320,10 @@ func InitDroiddocModule(module android.DefaultableModule, hod android.HostOrDevi android.InitDefaultableModule(module) } -func apiCheckEnabled(apiToCheck ApiToCheck, apiVersionTag string) bool { - if String(apiToCheck.Api_file) != "" && String(apiToCheck.Removed_api_file) != "" { +func apiCheckEnabled(ctx android.ModuleContext, apiToCheck ApiToCheck, apiVersionTag string) bool { + if ctx.Config().IsEnvTrue("WITHOUT_CHECK_API") { + return false + } else if String(apiToCheck.Api_file) != "" && String(apiToCheck.Removed_api_file) != "" { return true } else if String(apiToCheck.Api_file) != "" { panic("for " + apiVersionTag + " removed_api_file has to be non-empty!") @@ -460,25 +353,21 @@ func ignoreMissingModules(ctx android.BottomUpMutatorContext, apiToCheck *ApiToC apiToCheck.Removed_api_file = nil } +// Used by xsd_config type ApiFilePath interface { ApiFilePath() android.Path } -func transformUpdateApi(ctx android.ModuleContext, destApiFile, destRemovedApiFile, - srcApiFile, srcRemovedApiFile android.Path, output android.WritablePath) { - ctx.Build(pctx, android.BuildParams{ - Rule: updateApi, - Description: "Update API", - Output: output, - Implicits: append(android.Paths{}, srcApiFile, srcRemovedApiFile, - destApiFile, destRemovedApiFile), - Args: map[string]string{ - "destApiFile": destApiFile.String(), - "srcApiFile": srcApiFile.String(), - "destRemovedApiFile": destRemovedApiFile.String(), - "srcRemovedApiFile": srcRemovedApiFile.String(), - }, - }) +type ApiStubsSrcProvider interface { + StubsSrcJar() android.Path +} + +// Provider of information about API stubs, used by java_sdk_library. +type ApiStubsProvider interface { + ApiFilePath + RemovedApiFilePath() android.Path + + ApiStubsSrcProvider } // @@ -494,6 +383,7 @@ type Javadoc struct { srcFiles android.Paths sourcepaths android.Paths argFiles android.Paths + implicits android.Paths args string @@ -501,10 +391,18 @@ type Javadoc struct { stubsSrcJar android.WritablePath } -func (j *Javadoc) Srcs() android.Paths { - return android.Paths{j.stubsSrcJar} +func (j *Javadoc) OutputFiles(tag string) (android.Paths, error) { + switch tag { + case "": + return android.Paths{j.stubsSrcJar}, nil + case ".docs.zip": + return android.Paths{j.docZip}, nil + default: + return nil, fmt.Errorf("unsupported module reference tag %q", tag) + } } +// javadoc converts .java source files to documentation using javadoc. func JavadocFactory() android.Module { module := &Javadoc{} @@ -514,6 +412,7 @@ func JavadocFactory() android.Module { return module } +// javadoc_host converts .java source files to documentation using javadoc. func JavadocHostFactory() android.Module { module := &Javadoc{} @@ -523,58 +422,41 @@ func JavadocHostFactory() android.Module { return module } -var _ android.SourceFileProducer = (*Javadoc)(nil) +var _ android.OutputFileProducer = (*Javadoc)(nil) + +func (j *Javadoc) sdkVersion() sdkSpec { + return sdkSpecFrom(String(j.properties.Sdk_version)) +} -func (j *Javadoc) sdkVersion() string { - return String(j.properties.Sdk_version) +func (j *Javadoc) systemModules() string { + return proptools.String(j.properties.System_modules) } -func (j *Javadoc) minSdkVersion() string { +func (j *Javadoc) minSdkVersion() sdkSpec { return j.sdkVersion() } -func (j *Javadoc) targetSdkVersion() string { +func (j *Javadoc) targetSdkVersion() sdkSpec { return j.sdkVersion() } func (j *Javadoc) addDeps(ctx android.BottomUpMutatorContext) { if ctx.Device() { - if !Bool(j.properties.No_standard_libs) { - sdkDep := decodeSdkDep(ctx, sdkContext(j)) - if sdkDep.useDefaultLibs { - ctx.AddVariationDependencies(nil, bootClasspathTag, config.DefaultBootclasspathLibraries...) - if ctx.Config().TargetOpenJDK9() { - ctx.AddVariationDependencies(nil, systemModulesTag, config.DefaultSystemModules) - } - if !Bool(j.properties.No_framework_libs) { - ctx.AddVariationDependencies(nil, libTag, config.DefaultLibraries...) - } - } else if sdkDep.useModule { - if ctx.Config().TargetOpenJDK9() { - ctx.AddVariationDependencies(nil, systemModulesTag, sdkDep.systemModules) - } - ctx.AddVariationDependencies(nil, bootClasspathTag, sdkDep.modules...) + sdkDep := decodeSdkDep(ctx, sdkContext(j)) + if sdkDep.useDefaultLibs { + ctx.AddVariationDependencies(nil, bootClasspathTag, config.DefaultBootclasspathLibraries...) + ctx.AddVariationDependencies(nil, systemModulesTag, config.DefaultSystemModules) + if sdkDep.hasFrameworkLibs() { + ctx.AddVariationDependencies(nil, libTag, config.DefaultLibraries...) } + } else if sdkDep.useModule { + ctx.AddVariationDependencies(nil, bootClasspathTag, sdkDep.bootclasspath...) + ctx.AddVariationDependencies(nil, systemModulesTag, sdkDep.systemModules) + ctx.AddVariationDependencies(nil, java9LibTag, sdkDep.java9Classpath...) } } ctx.AddVariationDependencies(nil, libTag, j.properties.Libs...) - if j.properties.Srcs_lib != nil { - ctx.AddVariationDependencies(nil, srcsLibTag, *j.properties.Srcs_lib) - } -} - -func (j *Javadoc) genWhitelistPathPrefixes(whitelistPathPrefixes map[string]bool) { - for _, dir := range j.properties.Srcs_lib_whitelist_dirs { - for _, pkg := range j.properties.Srcs_lib_whitelist_pkgs { - // convert foo.bar.baz to foo/bar/baz - pkgAsPath := filepath.Join(strings.Split(pkg, ".")...) - prefix := filepath.Join(dir, pkgAsPath) - if _, found := whitelistPathPrefixes[prefix]; !found { - whitelistPathPrefixes[prefix] = true - } - } - } } func (j *Javadoc) collectAidlFlags(ctx android.ModuleContext, deps deps) droiddocBuilderFlags { @@ -610,24 +492,33 @@ func (j *Javadoc) aidlFlags(ctx android.ModuleContext, aidlPreprocess android.Op return strings.Join(flags, " "), deps } +// TODO: remove the duplication between this and the one in gen.go func (j *Javadoc) genSources(ctx android.ModuleContext, srcFiles android.Paths, flags droiddocBuilderFlags) android.Paths { outSrcFiles := make(android.Paths, 0, len(srcFiles)) + var aidlSrcs android.Paths + + aidlIncludeFlags := genAidlIncludeFlags(srcFiles) for _, srcFile := range srcFiles { switch srcFile.Ext() { case ".aidl": - javaFile := genAidl(ctx, srcFile, flags.aidlFlags, flags.aidlDeps) - outSrcFiles = append(outSrcFiles, javaFile) - case ".sysprop": - javaFile := genSysprop(ctx, srcFile) + aidlSrcs = append(aidlSrcs, srcFile) + case ".logtags": + javaFile := genLogtags(ctx, srcFile) outSrcFiles = append(outSrcFiles, javaFile) default: outSrcFiles = append(outSrcFiles, srcFile) } } + // Process all aidl files together to support sharding them into one or more rules that produce srcjars. + if len(aidlSrcs) > 0 { + srcJarFiles := genAidl(ctx, aidlSrcs, flags.aidlFlags+aidlIncludeFlags, flags.aidlDeps) + outSrcFiles = append(outSrcFiles, srcJarFiles...) + } + return outSrcFiles } @@ -636,9 +527,13 @@ func (j *Javadoc) collectDeps(ctx android.ModuleContext) deps { sdkDep := decodeSdkDep(ctx, sdkContext(j)) if sdkDep.invalidVersion { - ctx.AddMissingDependencies(sdkDep.modules) + ctx.AddMissingDependencies(sdkDep.bootclasspath) + ctx.AddMissingDependencies(sdkDep.java9Classpath) } else if sdkDep.useFiles { deps.bootClasspath = append(deps.bootClasspath, sdkDep.jars...) + deps.aidlPreprocess = sdkDep.aidl + } else { + deps.aidlPreprocess = sdkDep.aidl } ctx.VisitDirectDeps(func(module android.Module) { @@ -649,40 +544,30 @@ func (j *Javadoc) collectDeps(ctx android.ModuleContext) deps { case bootClasspathTag: if dep, ok := module.(Dependency); ok { deps.bootClasspath = append(deps.bootClasspath, dep.ImplementationJars()...) + } else if sm, ok := module.(SystemModulesProvider); ok { + // A system modules dependency has been added to the bootclasspath + // so add its libs to the bootclasspath. + deps.bootClasspath = append(deps.bootClasspath, sm.HeaderJars()...) } else { panic(fmt.Errorf("unknown dependency %q for %q", otherName, ctx.ModuleName())) } case libTag: switch dep := module.(type) { case SdkLibraryDependency: - deps.classpath = append(deps.classpath, dep.SdkImplementationJars(ctx, j.sdkVersion())...) + deps.classpath = append(deps.classpath, dep.SdkHeaderJars(ctx, j.sdkVersion())...) case Dependency: deps.classpath = append(deps.classpath, dep.HeaderJars()...) + deps.aidlIncludeDirs = append(deps.aidlIncludeDirs, dep.AidlIncludeDirs()...) case android.SourceFileProducer: checkProducesJars(ctx, dep) deps.classpath = append(deps.classpath, dep.Srcs()...) default: ctx.ModuleErrorf("depends on non-java module %q", otherName) } - case srcsLibTag: + case java9LibTag: switch dep := module.(type) { case Dependency: - srcs := dep.(SrcDependency).CompiledSrcs() - whitelistPathPrefixes := make(map[string]bool) - j.genWhitelistPathPrefixes(whitelistPathPrefixes) - for _, src := range srcs { - if _, ok := src.(android.WritablePath); ok { // generated sources - deps.srcs = append(deps.srcs, src) - } else { // select source path for documentation based on whitelist path prefixs. - for k := range whitelistPathPrefixes { - if strings.HasPrefix(src.Rel(), k) { - deps.srcs = append(deps.srcs, src) - break - } - } - } - } - deps.srcJars = append(deps.srcJars, dep.(SrcDependency).CompiledSrcJars()...) + deps.java9Classpath = append(deps.java9Classpath, dep.HeaderJars()...) default: ctx.ModuleErrorf("depends on non-java module %q", otherName) } @@ -690,16 +575,58 @@ func (j *Javadoc) collectDeps(ctx android.ModuleContext) deps { if deps.systemModules != nil { panic("Found two system module dependencies") } - sm := module.(*SystemModules) - if sm.outputFile == nil { - panic("Missing directory for system module dependency") - } - deps.systemModules = sm.outputFile + sm := module.(SystemModulesProvider) + outputDir, outputDeps := sm.OutputDirAndDeps() + deps.systemModules = &systemModules{outputDir, outputDeps} } }) // do not pass exclude_srcs directly when expanding srcFiles since exclude_srcs // may contain filegroup or genrule. srcFiles := android.PathsForModuleSrcExcludes(ctx, j.properties.Srcs, j.properties.Exclude_srcs) + j.implicits = append(j.implicits, srcFiles...) + + filterByPackage := func(srcs []android.Path, filterPackages []string) []android.Path { + if filterPackages == nil { + return srcs + } + filtered := []android.Path{} + for _, src := range srcs { + if src.Ext() != ".java" { + // Don't filter-out non-Java (=generated sources) by package names. This is not ideal, + // but otherwise metalava emits stub sources having references to the generated AIDL classes + // in filtered-out pacages (e.g. com.android.internal.*). + // TODO(b/141149570) We need to fix this by introducing default private constructors or + // fixing metalava to not emit constructors having references to unknown classes. + filtered = append(filtered, src) + continue + } + packageName := strings.ReplaceAll(filepath.Dir(src.Rel()), "/", ".") + if android.HasAnyPrefix(packageName, filterPackages) { + filtered = append(filtered, src) + } + } + return filtered + } + srcFiles = filterByPackage(srcFiles, j.properties.Filter_packages) + + // While metalava needs package html files, it does not need them to be explicit on the command + // line. More importantly, the metalava rsp file is also used by the subsequent jdiff action if + // jdiff_enabled=true. javadoc complains if it receives html files on the command line. The filter + // below excludes html files from the rsp file for both metalava and jdiff. Note that the html + // files are still included as implicit inputs for successful remote execution and correct + // incremental builds. + filterHtml := func(srcs []android.Path) []android.Path { + filtered := []android.Path{} + for _, src := range srcs { + if src.Ext() == ".html" { + continue + } + filtered = append(filtered, src) + } + return filtered + } + srcFiles = filterHtml(srcFiles) + flags := j.collectAidlFlags(ctx, deps) srcFiles = j.genSources(ctx, srcFiles, flags) @@ -710,9 +637,6 @@ func (j *Javadoc) collectDeps(ctx android.ModuleContext) deps { j.srcFiles = srcFiles.FilterOutByExt(".srcjar") j.srcFiles = append(j.srcFiles, deps.srcs...) - j.docZip = android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"docs.zip") - j.stubsSrcJar = android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"stubs.srcjar") - if j.properties.Local_sourcepaths == nil && len(j.srcFiles) > 0 { j.properties.Local_sourcepaths = append(j.properties.Local_sourcepaths, ".") } @@ -763,51 +687,43 @@ func (j *Javadoc) DepsMutator(ctx android.BottomUpMutatorContext) { func (j *Javadoc) GenerateAndroidBuildActions(ctx android.ModuleContext) { deps := j.collectDeps(ctx) - var implicits android.Paths - implicits = append(implicits, deps.bootClasspath...) - implicits = append(implicits, deps.classpath...) + j.docZip = android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"docs.zip") + + outDir := android.PathForModuleOut(ctx, "out") + srcJarDir := android.PathForModuleOut(ctx, "srcjars") + + j.stubsSrcJar = nil + + rule := android.NewRuleBuilder() + + rule.Command().Text("rm -rf").Text(outDir.String()) + rule.Command().Text("mkdir -p").Text(outDir.String()) - var bootClasspathArgs, classpathArgs, sourcepathArgs string + srcJarList := zipSyncCmd(ctx, rule, srcJarDir, j.srcJars) javaVersion := getJavaVersion(ctx, String(j.properties.Java_version), sdkContext(j)) - if len(deps.bootClasspath) > 0 { - var systemModules classpath - if deps.systemModules != nil { - systemModules = append(systemModules, deps.systemModules) - } - bootClasspathArgs = systemModules.FormJavaSystemModulesPath("--system ", ctx.Device()) - bootClasspathArgs = bootClasspathArgs + " --patch-module java.base=." - } - if len(deps.classpath.Strings()) > 0 { - classpathArgs = "-classpath " + strings.Join(deps.classpath.Strings(), ":") - } - - implicits = append(implicits, j.srcJars...) - implicits = append(implicits, j.argFiles...) - - opts := "-source " + javaVersion + " -J-Xmx1024m -XDignore.symbol.file -Xdoclint:none" - - sourcepathArgs = "-sourcepath " + strings.Join(j.sourcepaths.Strings(), ":") - - ctx.Build(pctx, android.BuildParams{ - Rule: javadoc, - Description: "Javadoc", - Output: j.stubsSrcJar, - ImplicitOutput: j.docZip, - Inputs: j.srcFiles, - Implicits: implicits, - Args: map[string]string{ - "outDir": android.PathForModuleOut(ctx, "out").String(), - "srcJarDir": android.PathForModuleOut(ctx, "srcjars").String(), - "stubsDir": android.PathForModuleOut(ctx, "stubsDir").String(), - "srcJars": strings.Join(j.srcJars.Strings(), " "), - "opts": opts, - "bootclasspathArgs": bootClasspathArgs, - "classpathArgs": classpathArgs, - "sourcepathArgs": sourcepathArgs, - "docZip": j.docZip.String(), - }, - }) + + cmd := javadocSystemModulesCmd(ctx, rule, j.srcFiles, outDir, srcJarDir, srcJarList, + deps.systemModules, deps.classpath, j.sourcepaths) + + cmd.FlagWithArg("-source ", javaVersion.String()). + Flag("-J-Xmx1024m"). + Flag("-XDignore.symbol.file"). + Flag("-Xdoclint:none") + + rule.Command(). + BuiltTool(ctx, "soong_zip"). + Flag("-write_if_changed"). + Flag("-d"). + FlagWithOutput("-o ", j.docZip). + FlagWithArg("-C ", outDir.String()). + FlagWithArg("-D ", outDir.String()) + + rule.Restat() + + zipSyncCleanupCmd(rule, srcJarDir) + + rule.Build(pctx, ctx, "javadoc", "javadoc") } // @@ -818,14 +734,9 @@ type Droiddoc struct { properties DroiddocProperties apiFile android.WritablePath - dexApiFile android.WritablePath privateApiFile android.WritablePath - privateDexApiFile android.WritablePath removedApiFile android.WritablePath removedDexApiFile android.WritablePath - exactApiFile android.WritablePath - apiMappingFile android.WritablePath - proguardFile android.WritablePath checkCurrentApiTimestamp android.WritablePath updateCurrentApiTimestamp android.WritablePath @@ -834,6 +745,7 @@ type Droiddoc struct { apiFilePath android.Path } +// droiddoc converts .java source files to documentation using doclava or dokka. func DroiddocFactory() android.Module { module := &Droiddoc{} @@ -844,6 +756,7 @@ func DroiddocFactory() android.Module { return module } +// droiddoc_host converts .java source files to documentation using doclava or dokka. func DroiddocHostFactory() android.Module { module := &Droiddoc{} @@ -870,58 +783,19 @@ func (d *Droiddoc) DepsMutator(ctx android.BottomUpMutatorContext) { } } -func (d *Droiddoc) initBuilderFlags(ctx android.ModuleContext, implicits *android.Paths, - deps deps) (droiddocBuilderFlags, error) { - var flags droiddocBuilderFlags - - *implicits = append(*implicits, deps.bootClasspath...) - *implicits = append(*implicits, deps.classpath...) - - if len(deps.bootClasspath.Strings()) > 0 { - // For OpenJDK 8 we can use -bootclasspath to define the core libraries code. - flags.bootClasspathArgs = deps.bootClasspath.FormJavaClassPath("-bootclasspath") - } - flags.classpathArgs = deps.classpath.FormJavaClassPath("-classpath") - // Dokka doesn't support bootClasspath, so combine these two classpath vars for Dokka. - dokkaClasspath := classpath{} - dokkaClasspath = append(dokkaClasspath, deps.bootClasspath...) - dokkaClasspath = append(dokkaClasspath, deps.classpath...) - flags.dokkaClasspathArgs = dokkaClasspath.FormJavaClassPath("-classpath") - - // TODO(nanzhang): Remove this if- statement once we finish migration for all Doclava - // based stubs generation. - // In the future, all the docs generation depends on Metalava stubs (droidstubs) srcjar - // dir. We need add the srcjar dir to -sourcepath arg, so that Javadoc can figure out - // the correct package name base path. - if len(d.Javadoc.properties.Local_sourcepaths) > 0 { - flags.sourcepathArgs = "-sourcepath " + strings.Join(d.Javadoc.sourcepaths.Strings(), ":") - } else { - flags.sourcepathArgs = "-sourcepath " + android.PathForModuleOut(ctx, "srcjars").String() - } - - return flags, nil -} - -func (d *Droiddoc) collectDoclavaDocsFlags(ctx android.ModuleContext, implicits *android.Paths, - jsilver, doclava android.Path) string { - - *implicits = append(*implicits, jsilver) - *implicits = append(*implicits, doclava) - - var date string - if runtime.GOOS == "darwin" { - date = `date -r` - } else { - date = `date -d` - } - +func (d *Droiddoc) doclavaDocsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand, docletPath classpath) { + buildNumberFile := ctx.Config().BuildNumberFile(ctx) // Droiddoc always gets "-source 1.8" because it doesn't support 1.9 sources. For modules with 1.9 // sources, droiddoc will get sources produced by metalava which will have already stripped out the // 1.9 language features. - args := " -source 1.8 -J-Xmx1600m -J-XX:-OmitStackTraceInFastThrow -XDignore.symbol.file " + - "-doclet com.google.doclava.Doclava -docletpath " + jsilver.String() + ":" + doclava.String() + " " + - "-hdf page.build " + ctx.Config().BuildId() + "-" + ctx.Config().BuildNumberFromFile() + " " + - `-hdf page.now "$$(` + date + ` @$$(cat ` + ctx.Config().Getenv("BUILD_DATETIME_FILE") + `) "+%d %b %Y %k:%M")" ` + cmd.FlagWithArg("-source ", "1.8"). + Flag("-J-Xmx1600m"). + Flag("-J-XX:-OmitStackTraceInFastThrow"). + Flag("-XDignore.symbol.file"). + FlagWithArg("-doclet ", "com.google.doclava.Doclava"). + FlagWithInputList("-docletpath ", docletPath.Paths(), ":"). + FlagWithArg("-hdf page.build ", ctx.Config().BuildId()+"-$(cat "+buildNumberFile.String()+")").OrderOnly(buildNumberFile). + FlagWithArg("-hdf page.now ", `"$(date -d @$(cat `+ctx.Config().Getenv("BUILD_DATETIME_FILE")+`) "+%d %b %Y %k:%M")" `) if String(d.properties.Custom_template) == "" { // TODO: This is almost always droiddoc-templates-sdk @@ -930,23 +804,22 @@ func (d *Droiddoc) collectDoclavaDocsFlags(ctx android.ModuleContext, implicits ctx.VisitDirectDepsWithTag(droiddocTemplateTag, func(m android.Module) { if t, ok := m.(*ExportedDroiddocDir); ok { - *implicits = append(*implicits, t.deps...) - args = args + " -templatedir " + t.dir.String() + cmd.FlagWithArg("-templatedir ", t.dir.String()).Implicits(t.deps) } else { - ctx.PropertyErrorf("custom_template", "module %q is not a droiddoc_template", ctx.OtherModuleName(m)) + ctx.PropertyErrorf("custom_template", "module %q is not a droiddoc_exported_dir", ctx.OtherModuleName(m)) } }) if len(d.properties.Html_dirs) > 0 { - htmlDir := d.properties.Html_dirs[0] - *implicits = append(*implicits, android.PathsForModuleSrc(ctx, []string{filepath.Join(d.properties.Html_dirs[0], "**/*")})...) - args = args + " -htmldir " + htmlDir + htmlDir := android.PathForModuleSrc(ctx, d.properties.Html_dirs[0]) + cmd.FlagWithArg("-htmldir ", htmlDir.String()). + Implicits(android.PathsForModuleSrc(ctx, []string{filepath.Join(d.properties.Html_dirs[0], "**/*")})) } if len(d.properties.Html_dirs) > 1 { - htmlDir2 := d.properties.Html_dirs[1] - *implicits = append(*implicits, android.PathsForModuleSrc(ctx, []string{filepath.Join(htmlDir2, "**/*")})...) - args = args + " -htmldir2 " + htmlDir2 + htmlDir2 := android.PathForModuleSrc(ctx, d.properties.Html_dirs[1]) + cmd.FlagWithArg("-htmldir2 ", htmlDir2.String()). + Implicits(android.PathsForModuleSrc(ctx, []string{filepath.Join(d.properties.Html_dirs[1], "**/*")})) } if len(d.properties.Html_dirs) > 2 { @@ -954,275 +827,343 @@ func (d *Droiddoc) collectDoclavaDocsFlags(ctx android.ModuleContext, implicits } knownTags := android.PathsForModuleSrc(ctx, d.properties.Knowntags) - *implicits = append(*implicits, knownTags...) + cmd.FlagForEachInput("-knowntags ", knownTags) - for _, kt := range knownTags { - args = args + " -knowntags " + kt.String() - } - - for _, hdf := range d.properties.Hdf { - args = args + " -hdf " + hdf - } + cmd.FlagForEachArg("-hdf ", d.properties.Hdf) if String(d.properties.Proofread_file) != "" { proofreadFile := android.PathForModuleOut(ctx, String(d.properties.Proofread_file)) - args = args + " -proofread " + proofreadFile.String() + cmd.FlagWithOutput("-proofread ", proofreadFile) } if String(d.properties.Todo_file) != "" { // tricky part: // we should not compute full path for todo_file through PathForModuleOut(). // the non-standard doclet will get the full path relative to "-o". - args = args + " -todo " + String(d.properties.Todo_file) + cmd.FlagWithArg("-todo ", String(d.properties.Todo_file)). + ImplicitOutput(android.PathForModuleOut(ctx, String(d.properties.Todo_file))) } if String(d.properties.Resourcesdir) != "" { // TODO: should we add files under resourcesDir to the implicits? It seems that // resourcesDir is one sub dir of htmlDir resourcesDir := android.PathForModuleSrc(ctx, String(d.properties.Resourcesdir)) - args = args + " -resourcesdir " + resourcesDir.String() + cmd.FlagWithArg("-resourcesdir ", resourcesDir.String()) } if String(d.properties.Resourcesoutdir) != "" { // TODO: it seems -resourceoutdir reference/android/images/ didn't get generated anywhere. - args = args + " -resourcesoutdir " + String(d.properties.Resourcesoutdir) + cmd.FlagWithArg("-resourcesoutdir ", String(d.properties.Resourcesoutdir)) } - return args } -func (d *Droiddoc) collectStubsFlags(ctx android.ModuleContext, - implicitOutputs *android.WritablePaths) string { - var doclavaFlags string - if apiCheckEnabled(d.properties.Check_api.Current, "current") || - apiCheckEnabled(d.properties.Check_api.Last_released, "last_released") || +func (d *Droiddoc) stubsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand, stubsDir android.WritablePath) { + if apiCheckEnabled(ctx, d.properties.Check_api.Current, "current") || + apiCheckEnabled(ctx, d.properties.Check_api.Last_released, "last_released") || String(d.properties.Api_filename) != "" { + d.apiFile = android.PathForModuleOut(ctx, ctx.ModuleName()+"_api.txt") - doclavaFlags += " -api " + d.apiFile.String() - *implicitOutputs = append(*implicitOutputs, d.apiFile) + cmd.FlagWithOutput("-api ", d.apiFile) d.apiFilePath = d.apiFile } - if apiCheckEnabled(d.properties.Check_api.Current, "current") || - apiCheckEnabled(d.properties.Check_api.Last_released, "last_released") || + if apiCheckEnabled(ctx, d.properties.Check_api.Current, "current") || + apiCheckEnabled(ctx, d.properties.Check_api.Last_released, "last_released") || String(d.properties.Removed_api_filename) != "" { d.removedApiFile = android.PathForModuleOut(ctx, ctx.ModuleName()+"_removed.txt") - doclavaFlags += " -removedApi " + d.removedApiFile.String() - *implicitOutputs = append(*implicitOutputs, d.removedApiFile) + cmd.FlagWithOutput("-removedApi ", d.removedApiFile) } - if String(d.properties.Private_api_filename) != "" { - d.privateApiFile = android.PathForModuleOut(ctx, String(d.properties.Private_api_filename)) - doclavaFlags += " -privateApi " + d.privateApiFile.String() - *implicitOutputs = append(*implicitOutputs, d.privateApiFile) + if String(d.properties.Removed_dex_api_filename) != "" { + d.removedDexApiFile = android.PathForModuleOut(ctx, String(d.properties.Removed_dex_api_filename)) + cmd.FlagWithOutput("-removedDexApi ", d.removedDexApiFile) } - if String(d.properties.Dex_api_filename) != "" { - d.dexApiFile = android.PathForModuleOut(ctx, String(d.properties.Dex_api_filename)) - doclavaFlags += " -dexApi " + d.dexApiFile.String() - *implicitOutputs = append(*implicitOutputs, d.dexApiFile) + if BoolDefault(d.properties.Create_stubs, true) { + cmd.FlagWithArg("-stubs ", stubsDir.String()) } - if String(d.properties.Private_dex_api_filename) != "" { - d.privateDexApiFile = android.PathForModuleOut(ctx, String(d.properties.Private_dex_api_filename)) - doclavaFlags += " -privateDexApi " + d.privateDexApiFile.String() - *implicitOutputs = append(*implicitOutputs, d.privateDexApiFile) + if Bool(d.properties.Write_sdk_values) { + cmd.FlagWithArg("-sdkvalues ", android.PathForModuleOut(ctx, "out").String()) } +} - if String(d.properties.Removed_dex_api_filename) != "" { - d.removedDexApiFile = android.PathForModuleOut(ctx, String(d.properties.Removed_dex_api_filename)) - doclavaFlags += " -removedDexApi " + d.removedDexApiFile.String() - *implicitOutputs = append(*implicitOutputs, d.removedDexApiFile) +func (d *Droiddoc) postDoclavaCmds(ctx android.ModuleContext, rule *android.RuleBuilder) { + if String(d.properties.Static_doc_index_redirect) != "" { + staticDocIndexRedirect := android.PathForModuleSrc(ctx, String(d.properties.Static_doc_index_redirect)) + rule.Command().Text("cp"). + Input(staticDocIndexRedirect). + Output(android.PathForModuleOut(ctx, "out", "index.html")) } - if String(d.properties.Exact_api_filename) != "" { - d.exactApiFile = android.PathForModuleOut(ctx, String(d.properties.Exact_api_filename)) - doclavaFlags += " -exactApi " + d.exactApiFile.String() - *implicitOutputs = append(*implicitOutputs, d.exactApiFile) + if String(d.properties.Static_doc_properties) != "" { + staticDocProperties := android.PathForModuleSrc(ctx, String(d.properties.Static_doc_properties)) + rule.Command().Text("cp"). + Input(staticDocProperties). + Output(android.PathForModuleOut(ctx, "out", "source.properties")) } +} - if String(d.properties.Dex_mapping_filename) != "" { - d.apiMappingFile = android.PathForModuleOut(ctx, String(d.properties.Dex_mapping_filename)) - doclavaFlags += " -apiMapping " + d.apiMappingFile.String() - *implicitOutputs = append(*implicitOutputs, d.apiMappingFile) - } +func javadocCmd(ctx android.ModuleContext, rule *android.RuleBuilder, srcs android.Paths, + outDir, srcJarDir, srcJarList android.Path, sourcepaths android.Paths) *android.RuleBuilderCommand { - if String(d.properties.Proguard_filename) != "" { - d.proguardFile = android.PathForModuleOut(ctx, String(d.properties.Proguard_filename)) - doclavaFlags += " -proguard " + d.proguardFile.String() - *implicitOutputs = append(*implicitOutputs, d.proguardFile) - } + cmd := rule.Command(). + BuiltTool(ctx, "soong_javac_wrapper").Tool(config.JavadocCmd(ctx)). + Flag(config.JavacVmFlags). + FlagWithArg("-encoding ", "UTF-8"). + FlagWithRspFileInputList("@", srcs). + FlagWithInput("@", srcJarList) - if BoolDefault(d.properties.Create_stubs, true) { - doclavaFlags += " -stubs " + android.PathForModuleOut(ctx, "stubsDir").String() + // TODO(ccross): Remove this if- statement once we finish migration for all Doclava + // based stubs generation. + // In the future, all the docs generation depends on Metalava stubs (droidstubs) srcjar + // dir. We need add the srcjar dir to -sourcepath arg, so that Javadoc can figure out + // the correct package name base path. + if len(sourcepaths) > 0 { + cmd.FlagWithList("-sourcepath ", sourcepaths.Strings(), ":") + } else { + cmd.FlagWithArg("-sourcepath ", srcJarDir.String()) } - if Bool(d.properties.Write_sdk_values) { - doclavaFlags += " -sdkvalues " + android.PathForModuleOut(ctx, "out").String() - } + cmd.FlagWithArg("-d ", outDir.String()). + Flag("-quiet") - return doclavaFlags + return cmd } -func (d *Droiddoc) getPostDoclavaCmds(ctx android.ModuleContext, implicits *android.Paths) string { - var cmds string - if String(d.properties.Static_doc_index_redirect) != "" { - static_doc_index_redirect := ctx.ExpandSource(String(d.properties.Static_doc_index_redirect), - "static_doc_index_redirect") - *implicits = append(*implicits, static_doc_index_redirect) - cmds = cmds + " && cp " + static_doc_index_redirect.String() + " " + - android.PathForModuleOut(ctx, "out", "index.html").String() +func javadocSystemModulesCmd(ctx android.ModuleContext, rule *android.RuleBuilder, srcs android.Paths, + outDir, srcJarDir, srcJarList android.Path, systemModules *systemModules, + classpath classpath, sourcepaths android.Paths) *android.RuleBuilderCommand { + + cmd := javadocCmd(ctx, rule, srcs, outDir, srcJarDir, srcJarList, sourcepaths) + + flag, deps := systemModules.FormJavaSystemModulesPath(ctx.Device()) + cmd.Flag(flag).Implicits(deps) + + cmd.FlagWithArg("--patch-module ", "java.base=.") + + if len(classpath) > 0 { + cmd.FlagWithInputList("-classpath ", classpath.Paths(), ":") } - if String(d.properties.Static_doc_properties) != "" { - static_doc_properties := ctx.ExpandSource(String(d.properties.Static_doc_properties), - "static_doc_properties") - *implicits = append(*implicits, static_doc_properties) - cmds = cmds + " && cp " + static_doc_properties.String() + " " + - android.PathForModuleOut(ctx, "out", "source.properties").String() - } - return cmds -} - -func (d *Droiddoc) transformDoclava(ctx android.ModuleContext, implicits android.Paths, - implicitOutputs android.WritablePaths, - bootclasspathArgs, classpathArgs, sourcepathArgs, opts, postDoclavaCmds string) { - ctx.Build(pctx, android.BuildParams{ - Rule: javadoc, - Description: "Doclava", - Output: d.Javadoc.stubsSrcJar, - Inputs: d.Javadoc.srcFiles, - Implicits: implicits, - ImplicitOutputs: implicitOutputs, - Args: map[string]string{ - "outDir": android.PathForModuleOut(ctx, "out").String(), - "srcJarDir": android.PathForModuleOut(ctx, "srcjars").String(), - "stubsDir": android.PathForModuleOut(ctx, "stubsDir").String(), - "srcJars": strings.Join(d.Javadoc.srcJars.Strings(), " "), - "opts": opts, - "bootclasspathArgs": bootclasspathArgs, - "classpathArgs": classpathArgs, - "sourcepathArgs": sourcepathArgs, - "docZip": d.Javadoc.docZip.String(), - "postDoclavaCmds": postDoclavaCmds, - }, - }) + return cmd } -func (d *Droiddoc) transformCheckApi(ctx android.ModuleContext, apiFile, removedApiFile android.Path, - checkApiClasspath classpath, msg, opts string, output android.WritablePath) { - ctx.Build(pctx, android.BuildParams{ - Rule: apiCheck, - Description: "Doclava Check API", - Output: output, - Inputs: nil, - Implicits: append(android.Paths{apiFile, removedApiFile, d.apiFile, d.removedApiFile}, - checkApiClasspath...), - Args: map[string]string{ - "msg": msg, - "classpath": checkApiClasspath.FormJavaClassPath(""), - "opts": opts, - "apiFile": apiFile.String(), - "apiFileToCheck": d.apiFile.String(), - "removedApiFile": removedApiFile.String(), - "removedApiFileToCheck": d.removedApiFile.String(), - }, - }) +func javadocBootclasspathCmd(ctx android.ModuleContext, rule *android.RuleBuilder, srcs android.Paths, + outDir, srcJarDir, srcJarList android.Path, bootclasspath, classpath classpath, + sourcepaths android.Paths) *android.RuleBuilderCommand { + + cmd := javadocCmd(ctx, rule, srcs, outDir, srcJarDir, srcJarList, sourcepaths) + + if len(bootclasspath) == 0 && ctx.Device() { + // explicitly specify -bootclasspath "" if the bootclasspath is empty to + // ensure java does not fall back to the default bootclasspath. + cmd.FlagWithArg("-bootclasspath ", `""`) + } else if len(bootclasspath) > 0 { + cmd.FlagWithInputList("-bootclasspath ", bootclasspath.Paths(), ":") + } + + if len(classpath) > 0 { + cmd.FlagWithInputList("-classpath ", classpath.Paths(), ":") + } + + return cmd } -func (d *Droiddoc) transformDokka(ctx android.ModuleContext, implicits android.Paths, - classpathArgs, opts string) { - ctx.Build(pctx, android.BuildParams{ - Rule: dokka, - Description: "Dokka", - Output: d.Javadoc.stubsSrcJar, - Inputs: d.Javadoc.srcFiles, - Implicits: implicits, - Args: map[string]string{ - "outDir": android.PathForModuleOut(ctx, "dokka-out").String(), - "srcJarDir": android.PathForModuleOut(ctx, "dokka-srcjars").String(), - "stubsDir": android.PathForModuleOut(ctx, "dokka-stubsDir").String(), - "srcJars": strings.Join(d.Javadoc.srcJars.Strings(), " "), - "classpathArgs": classpathArgs, - "opts": opts, - "docZip": d.Javadoc.docZip.String(), - }, - }) +func dokkaCmd(ctx android.ModuleContext, rule *android.RuleBuilder, + outDir, srcJarDir android.Path, bootclasspath, classpath classpath) *android.RuleBuilderCommand { + + // Dokka doesn't support bootClasspath, so combine these two classpath vars for Dokka. + dokkaClasspath := append(bootclasspath.Paths(), classpath.Paths()...) + + return rule.Command(). + BuiltTool(ctx, "dokka"). + Flag(config.JavacVmFlags). + Flag(srcJarDir.String()). + FlagWithInputList("-classpath ", dokkaClasspath, ":"). + FlagWithArg("-format ", "dac"). + FlagWithArg("-dacRoot ", "/reference/kotlin"). + FlagWithArg("-output ", outDir.String()) } func (d *Droiddoc) GenerateAndroidBuildActions(ctx android.ModuleContext) { deps := d.Javadoc.collectDeps(ctx) + d.Javadoc.docZip = android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"docs.zip") + d.Javadoc.stubsSrcJar = android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"stubs.srcjar") + jsilver := android.PathForOutput(ctx, "host", ctx.Config().PrebuiltOS(), "framework", "jsilver.jar") doclava := android.PathForOutput(ctx, "host", ctx.Config().PrebuiltOS(), "framework", "doclava.jar") java8Home := ctx.Config().Getenv("ANDROID_JAVA8_HOME") checkApiClasspath := classpath{jsilver, doclava, android.PathForSource(ctx, java8Home, "lib/tools.jar")} - var implicits android.Paths - implicits = append(implicits, d.Javadoc.srcJars...) - implicits = append(implicits, d.Javadoc.argFiles...) + outDir := android.PathForModuleOut(ctx, "out") + srcJarDir := android.PathForModuleOut(ctx, "srcjars") + stubsDir := android.PathForModuleOut(ctx, "stubsDir") - var implicitOutputs android.WritablePaths - implicitOutputs = append(implicitOutputs, d.Javadoc.docZip) - for _, o := range d.Javadoc.properties.Out { - implicitOutputs = append(implicitOutputs, android.PathForModuleGen(ctx, o)) + rule := android.NewRuleBuilder() + + rule.Command().Text("rm -rf").Text(outDir.String()).Text(stubsDir.String()) + rule.Command().Text("mkdir -p").Text(outDir.String()).Text(stubsDir.String()) + + srcJarList := zipSyncCmd(ctx, rule, srcJarDir, d.Javadoc.srcJars) + + var cmd *android.RuleBuilderCommand + if Bool(d.properties.Dokka_enabled) { + cmd = dokkaCmd(ctx, rule, outDir, srcJarDir, deps.bootClasspath, deps.classpath) + } else { + cmd = javadocBootclasspathCmd(ctx, rule, d.Javadoc.srcFiles, outDir, srcJarDir, srcJarList, + deps.bootClasspath, deps.classpath, d.Javadoc.sourcepaths) } - flags, err := d.initBuilderFlags(ctx, &implicits, deps) - if err != nil { - return + d.stubsFlags(ctx, cmd, stubsDir) + + cmd.Flag(d.Javadoc.args).Implicits(d.Javadoc.argFiles) + + if d.properties.Compat_config != nil { + compatConfig := android.PathForModuleSrc(ctx, String(d.properties.Compat_config)) + cmd.FlagWithInput("-compatconfig ", compatConfig) } - flags.doclavaStubsFlags = d.collectStubsFlags(ctx, &implicitOutputs) + var desc string if Bool(d.properties.Dokka_enabled) { - d.transformDokka(ctx, implicits, flags.classpathArgs, d.Javadoc.args) + desc = "dokka" } else { - flags.doclavaDocsFlags = d.collectDoclavaDocsFlags(ctx, &implicits, jsilver, doclava) - flags.postDoclavaCmds = d.getPostDoclavaCmds(ctx, &implicits) - d.transformDoclava(ctx, implicits, implicitOutputs, flags.bootClasspathArgs, flags.classpathArgs, - flags.sourcepathArgs, flags.doclavaDocsFlags+flags.doclavaStubsFlags+" "+d.Javadoc.args, - flags.postDoclavaCmds) + d.doclavaDocsFlags(ctx, cmd, classpath{jsilver, doclava}) + + for _, o := range d.Javadoc.properties.Out { + cmd.ImplicitOutput(android.PathForModuleGen(ctx, o)) + } + + d.postDoclavaCmds(ctx, rule) + desc = "doclava" } - if apiCheckEnabled(d.properties.Check_api.Current, "current") && + rule.Command(). + BuiltTool(ctx, "soong_zip"). + Flag("-write_if_changed"). + Flag("-d"). + FlagWithOutput("-o ", d.docZip). + FlagWithArg("-C ", outDir.String()). + FlagWithArg("-D ", outDir.String()) + + rule.Command(). + BuiltTool(ctx, "soong_zip"). + Flag("-write_if_changed"). + Flag("-jar"). + FlagWithOutput("-o ", d.stubsSrcJar). + FlagWithArg("-C ", stubsDir.String()). + FlagWithArg("-D ", stubsDir.String()) + + rule.Restat() + + zipSyncCleanupCmd(rule, srcJarDir) + + rule.Build(pctx, ctx, "javadoc", desc) + + if apiCheckEnabled(ctx, d.properties.Check_api.Current, "current") && !ctx.Config().IsPdkBuild() { - apiFile := ctx.ExpandSource(String(d.properties.Check_api.Current.Api_file), - "check_api.current.api_file") - removedApiFile := ctx.ExpandSource(String(d.properties.Check_api.Current.Removed_api_file), - "check_api.current_removed_api_file") + + apiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Current.Api_file)) + removedApiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Current.Removed_api_file)) d.checkCurrentApiTimestamp = android.PathForModuleOut(ctx, "check_current_api.timestamp") - d.transformCheckApi(ctx, apiFile, removedApiFile, checkApiClasspath, - fmt.Sprintf(`\n******************************\n`+ - `You have tried to change the API from what has been previously approved.\n\n`+ - `To make these errors go away, you have two choices:\n`+ - ` 1. You can add '@hide' javadoc comments to the methods, etc. listed in the\n`+ - ` errors above.\n\n`+ - ` 2. You can update current.txt by executing the following command:\n`+ - ` make %s-update-current-api\n\n`+ - ` To submit the revised current.txt to the main Android repository,\n`+ - ` you will need approval.\n`+ - `******************************\n`, ctx.ModuleName()), String(d.properties.Check_api.Current.Args), - d.checkCurrentApiTimestamp) + + rule := android.NewRuleBuilder() + + rule.Command().Text("( true") + + rule.Command(). + BuiltTool(ctx, "apicheck"). + Flag("-JXmx1024m"). + FlagWithInputList("-Jclasspath\\ ", checkApiClasspath.Paths(), ":"). + OptionalFlag(d.properties.Check_api.Current.Args). + Input(apiFile). + Input(d.apiFile). + Input(removedApiFile). + Input(d.removedApiFile) + + msg := fmt.Sprintf(`\n******************************\n`+ + `You have tried to change the API from what has been previously approved.\n\n`+ + `To make these errors go away, you have two choices:\n`+ + ` 1. You can add '@hide' javadoc comments to the methods, etc. listed in the\n`+ + ` errors above.\n\n`+ + ` 2. You can update current.txt by executing the following command:\n`+ + ` make %s-update-current-api\n\n`+ + ` To submit the revised current.txt to the main Android repository,\n`+ + ` you will need approval.\n`+ + `******************************\n`, ctx.ModuleName()) + + rule.Command(). + Text("touch").Output(d.checkCurrentApiTimestamp). + Text(") || ("). + Text("echo").Flag("-e").Flag(`"` + msg + `"`). + Text("; exit 38"). + Text(")") + + rule.Build(pctx, ctx, "doclavaCurrentApiCheck", "check current API") d.updateCurrentApiTimestamp = android.PathForModuleOut(ctx, "update_current_api.timestamp") - transformUpdateApi(ctx, apiFile, removedApiFile, d.apiFile, d.removedApiFile, - d.updateCurrentApiTimestamp) + + // update API rule + rule = android.NewRuleBuilder() + + rule.Command().Text("( true") + + rule.Command(). + Text("cp").Flag("-f"). + Input(d.apiFile).Flag(apiFile.String()) + + rule.Command(). + Text("cp").Flag("-f"). + Input(d.removedApiFile).Flag(removedApiFile.String()) + + msg = "failed to update public API" + + rule.Command(). + Text("touch").Output(d.updateCurrentApiTimestamp). + Text(") || ("). + Text("echo").Flag("-e").Flag(`"` + msg + `"`). + Text("; exit 38"). + Text(")") + + rule.Build(pctx, ctx, "doclavaCurrentApiUpdate", "update current API") } - if apiCheckEnabled(d.properties.Check_api.Last_released, "last_released") && + if apiCheckEnabled(ctx, d.properties.Check_api.Last_released, "last_released") && !ctx.Config().IsPdkBuild() { - apiFile := ctx.ExpandSource(String(d.properties.Check_api.Last_released.Api_file), - "check_api.last_released.api_file") - removedApiFile := ctx.ExpandSource(String(d.properties.Check_api.Last_released.Removed_api_file), - "check_api.last_released.removed_api_file") + + apiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Last_released.Api_file)) + removedApiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Last_released.Removed_api_file)) d.checkLastReleasedApiTimestamp = android.PathForModuleOut(ctx, "check_last_released_api.timestamp") - d.transformCheckApi(ctx, apiFile, removedApiFile, checkApiClasspath, - `\n******************************\n`+ - `You have tried to change the API from what has been previously released in\n`+ - `an SDK. Please fix the errors listed above.\n`+ - `******************************\n`, String(d.properties.Check_api.Last_released.Args), - d.checkLastReleasedApiTimestamp) + + rule := android.NewRuleBuilder() + + rule.Command(). + Text("("). + BuiltTool(ctx, "apicheck"). + Flag("-JXmx1024m"). + FlagWithInputList("-Jclasspath\\ ", checkApiClasspath.Paths(), ":"). + OptionalFlag(d.properties.Check_api.Last_released.Args). + Input(apiFile). + Input(d.apiFile). + Input(removedApiFile). + Input(d.removedApiFile) + + msg := `\n******************************\n` + + `You have tried to change the API from what has been previously released in\n` + + `an SDK. Please fix the errors listed above.\n` + + `******************************\n` + + rule.Command(). + Text("touch").Output(d.checkLastReleasedApiTimestamp). + Text(") || ("). + Text("echo").Flag("-e").Flag(`"` + msg + `"`). + Text("; exit 38"). + Text(")") + + rule.Build(pctx, ctx, "doclavaLastApiCheck", "check last API") } } @@ -1231,24 +1172,22 @@ func (d *Droiddoc) GenerateAndroidBuildActions(ctx android.ModuleContext) { // type Droidstubs struct { Javadoc + android.SdkBase properties DroidstubsProperties apiFile android.WritablePath apiXmlFile android.WritablePath lastReleasedApiXmlFile android.WritablePath - dexApiFile android.WritablePath privateApiFile android.WritablePath - privateDexApiFile android.WritablePath removedApiFile android.WritablePath removedDexApiFile android.WritablePath - apiMappingFile android.WritablePath - exactApiFile android.WritablePath - proguardFile android.WritablePath nullabilityWarningsFile android.WritablePath checkCurrentApiTimestamp android.WritablePath updateCurrentApiTimestamp android.WritablePath checkLastReleasedApiTimestamp android.WritablePath + apiLintTimestamp android.WritablePath + apiLintReport android.WritablePath checkNullabilityWarningsTimestamp android.WritablePath @@ -1264,6 +1203,9 @@ type Droidstubs struct { metadataDir android.WritablePath } +// droidstubs passes sources files through Metalava to generate stub .java files that only contain the API to be +// documented, filtering out hidden classes and methods. The resulting .java files are intended to be passed to +// a droiddoc module to generate documentation. func DroidstubsFactory() android.Module { module := &Droidstubs{} @@ -1271,9 +1213,14 @@ func DroidstubsFactory() android.Module { &module.Javadoc.properties) InitDroiddocModule(module, android.HostAndDeviceSupported) + android.InitSdkAwareModule(module) return module } +// droidstubs_host passes sources files through Metalava to generate stub .java files that only contain the API +// to be documented, filtering out hidden classes and methods. The resulting .java files are intended to be +// passed to a droiddoc_host module to generate documentation. Use a droidstubs_host instead of a droidstubs +// module when symbols needed by the source files are provided by java_library_host modules. func DroidstubsHostFactory() android.Module { module := &Droidstubs{} @@ -1284,15 +1231,48 @@ func DroidstubsHostFactory() android.Module { return module } +func (d *Droidstubs) OutputFiles(tag string) (android.Paths, error) { + switch tag { + case "": + return android.Paths{d.stubsSrcJar}, nil + case ".docs.zip": + return android.Paths{d.docZip}, nil + case ".annotations.zip": + return android.Paths{d.annotationsZip}, nil + case ".api_versions.xml": + return android.Paths{d.apiVersionsXml}, nil + default: + return nil, fmt.Errorf("unsupported module reference tag %q", tag) + } +} + func (d *Droidstubs) ApiFilePath() android.Path { return d.apiFilePath } +func (d *Droidstubs) RemovedApiFilePath() android.Path { + return d.removedApiFile +} + +func (d *Droidstubs) StubsSrcJar() android.Path { + return d.stubsSrcJar +} + func (d *Droidstubs) DepsMutator(ctx android.BottomUpMutatorContext) { d.Javadoc.addDeps(ctx) + // If requested clear any properties that provide information about the latest version + // of an API and which reference non-existent modules. if Bool(d.properties.Check_api.Ignore_missing_latest_api) { ignoreMissingModules(ctx, &d.properties.Check_api.Last_released) + + // If the new_since references a module, e.g. :module-latest-api and the module + // does not exist then clear it. + newSinceSrc := d.properties.Check_api.Api_lint.New_since + newSinceSrcModule := android.SrcIsModule(proptools.String(newSinceSrc)) + if newSinceSrcModule != "" && !ctx.OtherModuleExists(newSinceSrcModule) { + d.properties.Check_api.Api_lint.New_since = nil + } } if len(d.properties.Merge_annotations_dirs) != 0 { @@ -1314,333 +1294,225 @@ func (d *Droidstubs) DepsMutator(ctx android.BottomUpMutatorContext) { } } -func (d *Droidstubs) initBuilderFlags(ctx android.ModuleContext, implicits *android.Paths, - deps deps) (droiddocBuilderFlags, error) { - var flags droiddocBuilderFlags - - *implicits = append(*implicits, deps.bootClasspath...) - *implicits = append(*implicits, deps.classpath...) - - // continue to use -bootclasspath even if Metalava under -source 1.9 is enabled - // since it doesn't support system modules yet. - if len(deps.bootClasspath.Strings()) > 0 { - // For OpenJDK 8 we can use -bootclasspath to define the core libraries code. - flags.bootClasspathArgs = deps.bootClasspath.FormJavaClassPath("-bootclasspath") - } - flags.classpathArgs = deps.classpath.FormJavaClassPath("-classpath") - - flags.sourcepathArgs = "-sourcepath \"" + strings.Join(d.Javadoc.sourcepaths.Strings(), ":") + "\"" - return flags, nil -} - -func (d *Droidstubs) collectStubsFlags(ctx android.ModuleContext, - implicitOutputs *android.WritablePaths) string { - var metalavaFlags string - if apiCheckEnabled(d.properties.Check_api.Current, "current") || - apiCheckEnabled(d.properties.Check_api.Last_released, "last_released") || +func (d *Droidstubs) stubsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand, stubsDir android.OptionalPath) { + if apiCheckEnabled(ctx, d.properties.Check_api.Current, "current") || + apiCheckEnabled(ctx, d.properties.Check_api.Last_released, "last_released") || String(d.properties.Api_filename) != "" { d.apiFile = android.PathForModuleOut(ctx, ctx.ModuleName()+"_api.txt") - metalavaFlags = metalavaFlags + " --api " + d.apiFile.String() - *implicitOutputs = append(*implicitOutputs, d.apiFile) + cmd.FlagWithOutput("--api ", d.apiFile) d.apiFilePath = d.apiFile } - if apiCheckEnabled(d.properties.Check_api.Current, "current") || - apiCheckEnabled(d.properties.Check_api.Last_released, "last_released") || + if apiCheckEnabled(ctx, d.properties.Check_api.Current, "current") || + apiCheckEnabled(ctx, d.properties.Check_api.Last_released, "last_released") || String(d.properties.Removed_api_filename) != "" { d.removedApiFile = android.PathForModuleOut(ctx, ctx.ModuleName()+"_removed.txt") - metalavaFlags = metalavaFlags + " --removed-api " + d.removedApiFile.String() - *implicitOutputs = append(*implicitOutputs, d.removedApiFile) - } - - if String(d.properties.Private_api_filename) != "" { - d.privateApiFile = android.PathForModuleOut(ctx, String(d.properties.Private_api_filename)) - metalavaFlags = metalavaFlags + " --private-api " + d.privateApiFile.String() - *implicitOutputs = append(*implicitOutputs, d.privateApiFile) - } - - if String(d.properties.Dex_api_filename) != "" { - d.dexApiFile = android.PathForModuleOut(ctx, String(d.properties.Dex_api_filename)) - metalavaFlags += " --dex-api " + d.dexApiFile.String() - *implicitOutputs = append(*implicitOutputs, d.dexApiFile) - } - - if String(d.properties.Private_dex_api_filename) != "" { - d.privateDexApiFile = android.PathForModuleOut(ctx, String(d.properties.Private_dex_api_filename)) - metalavaFlags = metalavaFlags + " --private-dex-api " + d.privateDexApiFile.String() - *implicitOutputs = append(*implicitOutputs, d.privateDexApiFile) + cmd.FlagWithOutput("--removed-api ", d.removedApiFile) } if String(d.properties.Removed_dex_api_filename) != "" { d.removedDexApiFile = android.PathForModuleOut(ctx, String(d.properties.Removed_dex_api_filename)) - metalavaFlags = metalavaFlags + " --removed-dex-api " + d.removedDexApiFile.String() - *implicitOutputs = append(*implicitOutputs, d.removedDexApiFile) - } - - if String(d.properties.Exact_api_filename) != "" { - d.exactApiFile = android.PathForModuleOut(ctx, String(d.properties.Exact_api_filename)) - metalavaFlags = metalavaFlags + " --exact-api " + d.exactApiFile.String() - *implicitOutputs = append(*implicitOutputs, d.exactApiFile) - } - - if String(d.properties.Dex_mapping_filename) != "" { - d.apiMappingFile = android.PathForModuleOut(ctx, String(d.properties.Dex_mapping_filename)) - metalavaFlags = metalavaFlags + " --dex-api-mapping " + d.apiMappingFile.String() - *implicitOutputs = append(*implicitOutputs, d.apiMappingFile) - } - - if String(d.properties.Proguard_filename) != "" { - d.proguardFile = android.PathForModuleOut(ctx, String(d.properties.Proguard_filename)) - metalavaFlags += " --proguard " + d.proguardFile.String() - *implicitOutputs = append(*implicitOutputs, d.proguardFile) + cmd.FlagWithOutput("--removed-dex-api ", d.removedDexApiFile) } if Bool(d.properties.Write_sdk_values) { d.metadataDir = android.PathForModuleOut(ctx, "metadata") - metalavaFlags = metalavaFlags + " --sdk-values " + d.metadataDir.String() + cmd.FlagWithArg("--sdk-values ", d.metadataDir.String()) } - if Bool(d.properties.Create_doc_stubs) { - metalavaFlags += " --doc-stubs " + android.PathForModuleOut(ctx, "stubsDir").String() - } else { - metalavaFlags += " --stubs " + android.PathForModuleOut(ctx, "stubsDir").String() + if stubsDir.Valid() { + if Bool(d.properties.Create_doc_stubs) { + cmd.FlagWithArg("--doc-stubs ", stubsDir.String()) + } else { + cmd.FlagWithArg("--stubs ", stubsDir.String()) + cmd.Flag("--exclude-documentation-from-stubs") + } } - return metalavaFlags } -func (d *Droidstubs) collectAnnotationsFlags(ctx android.ModuleContext, - implicits *android.Paths, implicitOutputs *android.WritablePaths) (string, string) { - var flags, mergeAnnoDirFlags string +func (d *Droidstubs) annotationsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) { if Bool(d.properties.Annotations_enabled) { - flags += " --include-annotations" + cmd.Flag("--include-annotations") + validatingNullability := strings.Contains(d.Javadoc.args, "--validate-nullability-from-merged-stubs") || String(d.properties.Validate_nullability_from_list) != "" + migratingNullability := String(d.properties.Previous_api) != "" - if !(migratingNullability || validatingNullability) { - ctx.PropertyErrorf("previous_api", - "has to be non-empty if annotations was enabled (unless validating nullability)") - } if migratingNullability { previousApi := android.PathForModuleSrc(ctx, String(d.properties.Previous_api)) - *implicits = append(*implicits, previousApi) - flags += " --migrate-nullness " + previousApi.String() + cmd.FlagWithInput("--migrate-nullness ", previousApi) } + if s := String(d.properties.Validate_nullability_from_list); s != "" { - flags += " --validate-nullability-from-list " + android.PathForModuleSrc(ctx, s).String() + cmd.FlagWithInput("--validate-nullability-from-list ", android.PathForModuleSrc(ctx, s)) } + if validatingNullability { d.nullabilityWarningsFile = android.PathForModuleOut(ctx, ctx.ModuleName()+"_nullability_warnings.txt") - *implicitOutputs = append(*implicitOutputs, d.nullabilityWarningsFile) - flags += " --nullability-warnings-txt " + d.nullabilityWarningsFile.String() + cmd.FlagWithOutput("--nullability-warnings-txt ", d.nullabilityWarningsFile) } d.annotationsZip = android.PathForModuleOut(ctx, ctx.ModuleName()+"_annotations.zip") - *implicitOutputs = append(*implicitOutputs, d.annotationsZip) - - flags += " --extract-annotations " + d.annotationsZip.String() + cmd.FlagWithOutput("--extract-annotations ", d.annotationsZip) - if len(d.properties.Merge_annotations_dirs) == 0 { - ctx.PropertyErrorf("merge_annotations_dirs", - "has to be non-empty if annotations was enabled!") + if len(d.properties.Merge_annotations_dirs) != 0 { + d.mergeAnnoDirFlags(ctx, cmd) } - ctx.VisitDirectDepsWithTag(metalavaMergeAnnotationsDirTag, func(m android.Module) { - if t, ok := m.(*ExportedDroiddocDir); ok { - *implicits = append(*implicits, t.deps...) - mergeAnnoDirFlags += " --merge-qualifier-annotations " + t.dir.String() - } else { - ctx.PropertyErrorf("merge_annotations_dirs", - "module %q is not a metalava merge-annotations dir", ctx.OtherModuleName(m)) - } - }) - flags += mergeAnnoDirFlags + // TODO(tnorbye): find owners to fix these warnings when annotation was enabled. - flags += " --hide HiddenTypedefConstant --hide SuperfluousPrefix --hide AnnotationExtraction" + cmd.FlagWithArg("--hide ", "HiddenTypedefConstant"). + FlagWithArg("--hide ", "SuperfluousPrefix"). + FlagWithArg("--hide ", "AnnotationExtraction") } +} - return flags, mergeAnnoDirFlags +func (d *Droidstubs) mergeAnnoDirFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) { + ctx.VisitDirectDepsWithTag(metalavaMergeAnnotationsDirTag, func(m android.Module) { + if t, ok := m.(*ExportedDroiddocDir); ok { + cmd.FlagWithArg("--merge-qualifier-annotations ", t.dir.String()).Implicits(t.deps) + } else { + ctx.PropertyErrorf("merge_annotations_dirs", + "module %q is not a metalava merge-annotations dir", ctx.OtherModuleName(m)) + } + }) } -func (d *Droidstubs) collectInclusionAnnotationsFlags(ctx android.ModuleContext, - implicits *android.Paths, implicitOutputs *android.WritablePaths) string { - var flags string +func (d *Droidstubs) inclusionAnnotationsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) { ctx.VisitDirectDepsWithTag(metalavaMergeInclusionAnnotationsDirTag, func(m android.Module) { if t, ok := m.(*ExportedDroiddocDir); ok { - *implicits = append(*implicits, t.deps...) - flags += " --merge-inclusion-annotations " + t.dir.String() + cmd.FlagWithArg("--merge-inclusion-annotations ", t.dir.String()).Implicits(t.deps) } else { ctx.PropertyErrorf("merge_inclusion_annotations_dirs", "module %q is not a metalava merge-annotations dir", ctx.OtherModuleName(m)) } }) - - return flags } -func (d *Droidstubs) collectAPILevelsAnnotationsFlags(ctx android.ModuleContext, - implicits *android.Paths, implicitOutputs *android.WritablePaths) string { - var flags string - if Bool(d.properties.Api_levels_annotations_enabled) { - d.apiVersionsXml = android.PathForModuleOut(ctx, "api-versions.xml") - *implicitOutputs = append(*implicitOutputs, d.apiVersionsXml) +func (d *Droidstubs) apiLevelsAnnotationsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) { + if !Bool(d.properties.Api_levels_annotations_enabled) { + return + } - if len(d.properties.Api_levels_annotations_dirs) == 0 { - ctx.PropertyErrorf("api_levels_annotations_dirs", - "has to be non-empty if api levels annotations was enabled!") - } - - flags = " --generate-api-levels " + d.apiVersionsXml.String() + " --apply-api-levels " + - d.apiVersionsXml.String() + " --current-version " + ctx.Config().PlatformSdkVersion() + - " --current-codename " + ctx.Config().PlatformSdkCodename() + " " - - ctx.VisitDirectDepsWithTag(metalavaAPILevelsAnnotationsDirTag, func(m android.Module) { - if t, ok := m.(*ExportedDroiddocDir); ok { - var androidJars android.Paths - for _, dep := range t.deps { - if strings.HasSuffix(dep.String(), "android.jar") { - androidJars = append(androidJars, dep) - } - } - *implicits = append(*implicits, androidJars...) - flags += " --android-jar-pattern " + t.dir.String() + "/%/public/android.jar " - } else { - ctx.PropertyErrorf("api_levels_annotations_dirs", - "module %q is not a metalava api-levels-annotations dir", ctx.OtherModuleName(m)) - } - }) + d.apiVersionsXml = android.PathForModuleOut(ctx, "api-versions.xml") + if len(d.properties.Api_levels_annotations_dirs) == 0 { + ctx.PropertyErrorf("api_levels_annotations_dirs", + "has to be non-empty if api levels annotations was enabled!") } - return flags + cmd.FlagWithOutput("--generate-api-levels ", d.apiVersionsXml) + cmd.FlagWithInput("--apply-api-levels ", d.apiVersionsXml) + cmd.FlagWithArg("--current-version ", ctx.Config().PlatformSdkVersion()) + cmd.FlagWithArg("--current-codename ", ctx.Config().PlatformSdkCodename()) + + filename := proptools.StringDefault(d.properties.Api_levels_jar_filename, "android.jar") + + ctx.VisitDirectDepsWithTag(metalavaAPILevelsAnnotationsDirTag, func(m android.Module) { + if t, ok := m.(*ExportedDroiddocDir); ok { + for _, dep := range t.deps { + if strings.HasSuffix(dep.String(), filename) { + cmd.Implicit(dep) + } + } + cmd.FlagWithArg("--android-jar-pattern ", t.dir.String()+"/%/public/"+filename) + } else { + ctx.PropertyErrorf("api_levels_annotations_dirs", + "module %q is not a metalava api-levels-annotations dir", ctx.OtherModuleName(m)) + } + }) } -func (d *Droidstubs) collectApiToXmlFlags(ctx android.ModuleContext, implicits *android.Paths, - implicitOutputs *android.WritablePaths) string { - var flags string - if Bool(d.properties.Jdiff_enabled) && !ctx.Config().IsPdkBuild() { +func (d *Droidstubs) apiToXmlFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) { + if Bool(d.properties.Jdiff_enabled) && !ctx.Config().IsPdkBuild() && d.apiFile != nil { if d.apiFile.String() == "" { ctx.ModuleErrorf("API signature file has to be specified in Metalava when jdiff is enabled.") } d.apiXmlFile = android.PathForModuleOut(ctx, ctx.ModuleName()+"_api.xml") - *implicitOutputs = append(*implicitOutputs, d.apiXmlFile) - - flags = " --api-xml " + d.apiXmlFile.String() + cmd.FlagWithOutput("--api-xml ", d.apiXmlFile) if String(d.properties.Check_api.Last_released.Api_file) == "" { ctx.PropertyErrorf("check_api.last_released.api_file", "has to be non-empty if jdiff was enabled!") } - lastReleasedApi := ctx.ExpandSource(String(d.properties.Check_api.Last_released.Api_file), - "check_api.last_released.api_file") - *implicits = append(*implicits, lastReleasedApi) + lastReleasedApi := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Last_released.Api_file)) d.lastReleasedApiXmlFile = android.PathForModuleOut(ctx, ctx.ModuleName()+"_last_released_api.xml") - *implicitOutputs = append(*implicitOutputs, d.lastReleasedApiXmlFile) + cmd.FlagWithInput("--convert-to-jdiff ", lastReleasedApi).Output(d.lastReleasedApiXmlFile) + } +} - flags += " --convert-to-jdiff " + lastReleasedApi.String() + " " + - d.lastReleasedApiXmlFile.String() +func metalavaCmd(ctx android.ModuleContext, rule *android.RuleBuilder, javaVersion javaVersion, srcs android.Paths, + srcJarList android.Path, bootclasspath, classpath classpath, sourcepaths android.Paths, implicitsRsp android.WritablePath, sandbox bool) *android.RuleBuilderCommand { + // Metalava uses lots of memory, restrict the number of metalava jobs that can run in parallel. + rule.HighMem() + cmd := rule.Command() + if ctx.Config().IsEnvTrue("RBE_METALAVA") { + rule.Remoteable(android.RemoteRuleSupports{RBE: true}) + pool := ctx.Config().GetenvWithDefault("RBE_METALAVA_POOL", "metalava") + execStrategy := ctx.Config().GetenvWithDefault("RBE_METALAVA_EXEC_STRATEGY", remoteexec.LocalExecStrategy) + labels := map[string]string{"type": "compile", "lang": "java", "compiler": "metalava"} + if !sandbox { + execStrategy = remoteexec.LocalExecStrategy + labels["shallow"] = "true" + } + inputs := []string{android.PathForOutput(ctx, "host", ctx.Config().PrebuiltOS(), "framework", "metalava.jar").String()} + inputs = append(inputs, sourcepaths.Strings()...) + if v := ctx.Config().Getenv("RBE_METALAVA_INPUTS"); v != "" { + inputs = append(inputs, strings.Split(v, ",")...) + } + cmd.Text((&remoteexec.REParams{ + Labels: labels, + ExecStrategy: execStrategy, + Inputs: inputs, + RSPFile: implicitsRsp.String(), + ToolchainInputs: []string{config.JavaCmd(ctx).String()}, + Platform: map[string]string{remoteexec.PoolKey: pool}, + }).NoVarTemplate(ctx.Config())) } - return flags -} + cmd.BuiltTool(ctx, "metalava"). + Flag(config.JavacVmFlags). + FlagWithArg("-encoding ", "UTF-8"). + FlagWithArg("-source ", javaVersion.String()). + FlagWithRspFileInputList("@", srcs). + FlagWithInput("@", srcJarList) -func (d *Droidstubs) transformMetalava(ctx android.ModuleContext, implicits android.Paths, - implicitOutputs android.WritablePaths, javaVersion, - bootclasspathArgs, classpathArgs, sourcepathArgs, opts string) { + if javaHome := ctx.Config().Getenv("ANDROID_JAVA_HOME"); javaHome != "" { + cmd.Implicit(android.PathForSource(ctx, javaHome)) + } - var writeSdkValues, metadataZip, metadataDir string - if Bool(d.properties.Write_sdk_values) { - writeSdkValues = "true" - d.metadataZip = android.PathForModuleOut(ctx, ctx.ModuleName()+"-metadata.zip") - metadataZip = d.metadataZip.String() - metadataDir = d.metadataDir.String() - implicitOutputs = append(implicitOutputs, d.metadataZip) + if sandbox { + cmd.FlagWithOutput("--strict-input-files ", android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"violations.txt")) } else { - writeSdkValues = "false" - metadataZip = "" - metadataDir = "" - } - - ctx.Build(pctx, android.BuildParams{ - Rule: metalava, - Description: "Metalava", - Output: d.Javadoc.stubsSrcJar, - Inputs: d.Javadoc.srcFiles, - Implicits: implicits, - ImplicitOutputs: implicitOutputs, - Args: map[string]string{ - "outDir": android.PathForModuleOut(ctx, "out").String(), - "srcJarDir": android.PathForModuleOut(ctx, "srcjars").String(), - "stubsDir": android.PathForModuleOut(ctx, "stubsDir").String(), - "srcJars": strings.Join(d.Javadoc.srcJars.Strings(), " "), - "javaVersion": javaVersion, - "bootclasspathArgs": bootclasspathArgs, - "classpathArgs": classpathArgs, - "sourcepathArgs": sourcepathArgs, - "opts": opts, - "writeSdkValues": writeSdkValues, - "metadataZip": metadataZip, - "metadataDir": metadataDir, - }, - }) -} + cmd.FlagWithOutput("--strict-input-files:warn ", android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"violations.txt")) + } -func (d *Droidstubs) transformCheckApi(ctx android.ModuleContext, - apiFile, removedApiFile android.Path, baselineFile android.OptionalPath, updatedBaselineOut android.WritablePath, implicits android.Paths, - javaVersion, bootclasspathArgs, classpathArgs, sourcepathArgs, opts, subdir, msg string, - output android.WritablePath) { - - implicits = append(android.Paths{apiFile, removedApiFile, d.apiFile, d.removedApiFile}, implicits...) - var implicitOutputs android.WritablePaths - - if baselineFile.Valid() { - implicits = append(implicits, baselineFile.Path()) - implicitOutputs = append(implicitOutputs, updatedBaselineOut) - } - - ctx.Build(pctx, android.BuildParams{ - Rule: metalavaApiCheck, - Description: "Metalava Check API", - Output: output, - Inputs: d.Javadoc.srcFiles, - Implicits: implicits, - ImplicitOutputs: implicitOutputs, - Args: map[string]string{ - "srcJarDir": android.PathForModuleOut(ctx, subdir, "srcjars").String(), - "srcJars": strings.Join(d.Javadoc.srcJars.Strings(), " "), - "javaVersion": javaVersion, - "bootclasspathArgs": bootclasspathArgs, - "classpathArgs": classpathArgs, - "sourcepathArgs": sourcepathArgs, - "opts": opts, - "msg": msg, - }, - }) -} + if implicitsRsp != nil { + cmd.FlagWithArg("--strict-input-files-exempt ", "@"+implicitsRsp.String()) + } -func (d *Droidstubs) transformJdiff(ctx android.ModuleContext, implicits android.Paths, - implicitOutputs android.WritablePaths, - bootclasspathArgs, classpathArgs, sourcepathArgs, opts string) { - ctx.Build(pctx, android.BuildParams{ - Rule: javadoc, - Description: "Jdiff", - Output: d.jdiffStubsSrcJar, - Inputs: d.Javadoc.srcFiles, - Implicits: implicits, - ImplicitOutputs: implicitOutputs, - Args: map[string]string{ - "outDir": android.PathForModuleOut(ctx, "jdiff-out").String(), - "srcJarDir": android.PathForModuleOut(ctx, "jdiff-srcjars").String(), - "stubsDir": android.PathForModuleOut(ctx, "jdiff-stubsDir").String(), - "srcJars": strings.Join(d.Javadoc.srcJars.Strings(), " "), - "opts": opts, - "bootclasspathArgs": bootclasspathArgs, - "classpathArgs": classpathArgs, - "sourcepathArgs": sourcepathArgs, - "docZip": d.jdiffDocZip.String(), - }, - }) + if len(bootclasspath) > 0 { + cmd.FlagWithInputList("-bootclasspath ", bootclasspath.Paths(), ":") + } + + if len(classpath) > 0 { + cmd.FlagWithInputList("-classpath ", classpath.Paths(), ":") + } + + if len(sourcepaths) > 0 { + cmd.FlagWithList("-sourcepath ", sourcepaths.Strings(), ":") + } else { + cmd.FlagWithArg("-sourcepath ", `""`) + } + + cmd.Flag("--no-banner"). + Flag("--color"). + Flag("--quiet"). + Flag("--format=v2") + + return cmd } func (d *Droidstubs) GenerateAndroidBuildActions(ctx android.ModuleContext) { @@ -1648,29 +1520,36 @@ func (d *Droidstubs) GenerateAndroidBuildActions(ctx android.ModuleContext) { javaVersion := getJavaVersion(ctx, String(d.Javadoc.properties.Java_version), sdkContext(d)) - var implicits android.Paths - implicits = append(implicits, d.Javadoc.srcJars...) - implicits = append(implicits, d.Javadoc.argFiles...) + // Create rule for metalava - var implicitOutputs android.WritablePaths - for _, o := range d.Javadoc.properties.Out { - implicitOutputs = append(implicitOutputs, android.PathForModuleGen(ctx, o)) - } + srcJarDir := android.PathForModuleOut(ctx, "srcjars") - flags, err := d.initBuilderFlags(ctx, &implicits, deps) - metalavaCheckApiImplicits := implicits - jdiffImplicits := implicits + rule := android.NewRuleBuilder() - if err != nil { - return + generateStubs := BoolDefault(d.properties.Generate_stubs, true) + var stubsDir android.OptionalPath + if generateStubs { + d.Javadoc.stubsSrcJar = android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"stubs.srcjar") + stubsDir = android.OptionalPathForPath(android.PathForModuleOut(ctx, "stubsDir")) + rule.Command().Text("rm -rf").Text(stubsDir.String()) + rule.Command().Text("mkdir -p").Text(stubsDir.String()) } - flags.metalavaStubsFlags = d.collectStubsFlags(ctx, &implicitOutputs) - flags.metalavaAnnotationsFlags, flags.metalavaMergeAnnoDirFlags = - d.collectAnnotationsFlags(ctx, &implicits, &implicitOutputs) - flags.metalavaInclusionAnnotationsFlags = d.collectInclusionAnnotationsFlags(ctx, &implicits, &implicitOutputs) - flags.metalavaApiLevelsAnnotationsFlags = d.collectAPILevelsAnnotationsFlags(ctx, &implicits, &implicitOutputs) - flags.metalavaApiToXmlFlags = d.collectApiToXmlFlags(ctx, &implicits, &implicitOutputs) + srcJarList := zipSyncCmd(ctx, rule, srcJarDir, d.Javadoc.srcJars) + + implicitsRsp := android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"implicits.rsp") + + cmd := metalavaCmd(ctx, rule, javaVersion, d.Javadoc.srcFiles, srcJarList, + deps.bootClasspath, deps.classpath, d.Javadoc.sourcepaths, implicitsRsp, + Bool(d.Javadoc.properties.Sandbox)) + cmd.Implicits(d.Javadoc.implicits) + + d.stubsFlags(ctx, cmd, stubsDir) + + d.annotationsFlags(ctx, cmd) + d.inclusionAnnotationsFlags(ctx, cmd) + d.apiLevelsAnnotationsFlags(ctx, cmd) + d.apiToXmlFlags(ctx, cmd) if strings.Contains(d.Javadoc.args, "--generate-documentation") { // Currently Metalava have the ability to invoke Javadoc in a seperate process. @@ -1678,73 +1557,243 @@ func (d *Droidstubs) GenerateAndroidBuildActions(ctx android.ModuleContext) { // "--generate-documentation" arg. This is not needed when Metalava removes this feature. d.Javadoc.args = d.Javadoc.args + " -nodocs " } - d.transformMetalava(ctx, implicits, implicitOutputs, javaVersion, - flags.bootClasspathArgs, flags.classpathArgs, flags.sourcepathArgs, - flags.metalavaStubsFlags+flags.metalavaAnnotationsFlags+flags.metalavaInclusionAnnotationsFlags+ - flags.metalavaApiLevelsAnnotationsFlags+flags.metalavaApiToXmlFlags+" "+d.Javadoc.args) - if apiCheckEnabled(d.properties.Check_api.Current, "current") && + cmd.Flag(d.Javadoc.args).Implicits(d.Javadoc.argFiles) + for _, o := range d.Javadoc.properties.Out { + cmd.ImplicitOutput(android.PathForModuleGen(ctx, o)) + } + + // Add options for the other optional tasks: API-lint and check-released. + // We generate separate timestamp files for them. + + doApiLint := false + doCheckReleased := false + + // Add API lint options. + + if BoolDefault(d.properties.Check_api.Api_lint.Enabled, false) && !ctx.Config().IsPdkBuild() { + doApiLint = true + + newSince := android.OptionalPathForModuleSrc(ctx, d.properties.Check_api.Api_lint.New_since) + if newSince.Valid() { + cmd.FlagWithInput("--api-lint ", newSince.Path()) + } else { + cmd.Flag("--api-lint") + } + d.apiLintReport = android.PathForModuleOut(ctx, "api_lint_report.txt") + cmd.FlagWithOutput("--report-even-if-suppressed ", d.apiLintReport) // TODO: Change to ":api-lint" + + // TODO(b/154317059): Clean up this whitelist by baselining and/or checking in last-released. + if d.Name() != "android.car-system-stubs-docs" && + d.Name() != "android.car-stubs-docs" && + d.Name() != "system-api-stubs-docs" && + d.Name() != "test-api-stubs-docs" { + cmd.Flag("--lints-as-errors") + cmd.Flag("--warnings-as-errors") // Most lints are actually warnings. + } + + baselineFile := android.OptionalPathForModuleSrc(ctx, d.properties.Check_api.Api_lint.Baseline_file) + updatedBaselineOutput := android.PathForModuleOut(ctx, "api_lint_baseline.txt") + d.apiLintTimestamp = android.PathForModuleOut(ctx, "api_lint.timestamp") + + // Note this string includes a special shell quote $' ... ', which decodes the "\n"s. + // However, because $' ... ' doesn't expand environmental variables, we can't just embed + // $PWD, so we have to terminate $'...', use "$PWD", then start $' ... ' again, + // which is why we have '"$PWD"$' in it. + // + // TODO: metalava also has a slightly different message hardcoded. Should we unify this + // message and metalava's one? + msg := `$'` + // Enclose with $' ... ' + `************************************************************\n` + + `Your API changes are triggering API Lint warnings or errors.\n` + + `To make these errors go away, fix the code according to the\n` + + `error and/or warning messages above.\n` + + `\n` + + `If it is not possible to do so, there are workarounds:\n` + + `\n` + + `1. You can suppress the errors with @SuppressLint("<id>")\n` + + if baselineFile.Valid() { + cmd.FlagWithInput("--baseline:api-lint ", baselineFile.Path()) + cmd.FlagWithOutput("--update-baseline:api-lint ", updatedBaselineOutput) + + msg += fmt.Sprintf(``+ + `2. You can update the baseline by executing the following\n`+ + ` command:\n`+ + ` cp \\\n`+ + ` "'"$PWD"$'/%s" \\\n`+ + ` "'"$PWD"$'/%s"\n`+ + ` To submit the revised baseline.txt to the main Android\n`+ + ` repository, you will need approval.\n`, updatedBaselineOutput, baselineFile.Path()) + } else { + msg += fmt.Sprintf(``+ + `2. You can add a baseline file of existing lint failures\n`+ + ` to the build rule of %s.\n`, d.Name()) + } + // Note the message ends with a ' (single quote), to close the $' ... ' . + msg += `************************************************************\n'` + + cmd.FlagWithArg("--error-message:api-lint ", msg) + } + + // Add "check released" options. (Detect incompatible API changes from the last public release) + + if apiCheckEnabled(ctx, d.properties.Check_api.Last_released, "last_released") && !ctx.Config().IsPdkBuild() { - apiFile := ctx.ExpandSource(String(d.properties.Check_api.Current.Api_file), - "check_api.current.api_file") - removedApiFile := ctx.ExpandSource(String(d.properties.Check_api.Current.Removed_api_file), - "check_api.current_removed_api_file") - baselineFile := ctx.ExpandOptionalSource(d.properties.Check_api.Current.Baseline_file, - "check_api.current.baseline_file") + doCheckReleased = true + + if len(d.Javadoc.properties.Out) > 0 { + ctx.PropertyErrorf("out", "out property may not be combined with check_api") + } + + apiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Last_released.Api_file)) + removedApiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Last_released.Removed_api_file)) + baselineFile := android.OptionalPathForModuleSrc(ctx, d.properties.Check_api.Last_released.Baseline_file) + updatedBaselineOutput := android.PathForModuleOut(ctx, "last_released_baseline.txt") + + d.checkLastReleasedApiTimestamp = android.PathForModuleOut(ctx, "check_last_released_api.timestamp") + + cmd.FlagWithInput("--check-compatibility:api:released ", apiFile) + cmd.FlagWithInput("--check-compatibility:removed:released ", removedApiFile) - d.checkCurrentApiTimestamp = android.PathForModuleOut(ctx, "check_current_api.timestamp") - opts := " " + d.Javadoc.args + " --check-compatibility:api:current " + apiFile.String() + - " --check-compatibility:removed:current " + removedApiFile.String() + - flags.metalavaInclusionAnnotationsFlags + flags.metalavaMergeAnnoDirFlags + " " - baselineOut := android.PathForModuleOut(ctx, "current_baseline.txt") if baselineFile.Valid() { - opts = opts + "--baseline " + baselineFile.String() + " --update-baseline " + baselineOut.String() + " " + cmd.FlagWithInput("--baseline:compatibility:released ", baselineFile.Path()) + cmd.FlagWithOutput("--update-baseline:compatibility:released ", updatedBaselineOutput) } - d.transformCheckApi(ctx, apiFile, removedApiFile, baselineFile, baselineOut, metalavaCheckApiImplicits, - javaVersion, flags.bootClasspathArgs, flags.classpathArgs, flags.sourcepathArgs, opts, "current-apicheck", - fmt.Sprintf(`\n******************************\n`+ - `You have tried to change the API from what has been previously approved.\n\n`+ - `To make these errors go away, you have two choices:\n`+ - ` 1. You can add '@hide' javadoc comments to the methods, etc. listed in the\n`+ - ` errors above.\n\n`+ - ` 2. You can update current.txt by executing the following command:\n`+ - ` make %s-update-current-api\n\n`+ - ` To submit the revised current.txt to the main Android repository,\n`+ - ` you will need approval.\n`+ - `******************************\n`, ctx.ModuleName()), - d.checkCurrentApiTimestamp) + // Note this string includes quote ($' ... '), which decodes the "\n"s. + msg := `$'\n******************************\n` + + `You have tried to change the API from what has been previously released in\n` + + `an SDK. Please fix the errors listed above.\n` + + `******************************\n'` - d.updateCurrentApiTimestamp = android.PathForModuleOut(ctx, "update_current_api.timestamp") - transformUpdateApi(ctx, apiFile, removedApiFile, d.apiFile, d.removedApiFile, - d.updateCurrentApiTimestamp) + cmd.FlagWithArg("--error-message:compatibility:released ", msg) + } + + impRule := android.NewRuleBuilder() + impCmd := impRule.Command() + // A dummy action that copies the ninja generated rsp file to a new location. This allows us to + // add a large number of inputs to a file without exceeding bash command length limits (which + // would happen if we use the WriteFile rule). The cp is needed because RuleBuilder sets the + // rsp file to be ${output}.rsp. + impCmd.Text("cp").FlagWithRspFileInputList("", cmd.GetImplicits()).Output(implicitsRsp) + impRule.Build(pctx, ctx, "implicitsGen", "implicits generation") + cmd.Implicit(implicitsRsp) + + if generateStubs { + rule.Command(). + BuiltTool(ctx, "soong_zip"). + Flag("-write_if_changed"). + Flag("-jar"). + FlagWithOutput("-o ", d.Javadoc.stubsSrcJar). + FlagWithArg("-C ", stubsDir.String()). + FlagWithArg("-D ", stubsDir.String()) + } + + if Bool(d.properties.Write_sdk_values) { + d.metadataZip = android.PathForModuleOut(ctx, ctx.ModuleName()+"-metadata.zip") + rule.Command(). + BuiltTool(ctx, "soong_zip"). + Flag("-write_if_changed"). + Flag("-d"). + FlagWithOutput("-o ", d.metadataZip). + FlagWithArg("-C ", d.metadataDir.String()). + FlagWithArg("-D ", d.metadataDir.String()) + } + + // TODO: We don't really need two separate API files, but this is a reminiscence of how + // we used to run metalava separately for API lint and the "last_released" check. Unify them. + if doApiLint { + rule.Command().Text("touch").Output(d.apiLintTimestamp) + } + if doCheckReleased { + rule.Command().Text("touch").Output(d.checkLastReleasedApiTimestamp) } - if apiCheckEnabled(d.properties.Check_api.Last_released, "last_released") && + rule.Restat() + + zipSyncCleanupCmd(rule, srcJarDir) + + rule.Build(pctx, ctx, "metalava", "metalava merged") + + if apiCheckEnabled(ctx, d.properties.Check_api.Current, "current") && !ctx.Config().IsPdkBuild() { - apiFile := ctx.ExpandSource(String(d.properties.Check_api.Last_released.Api_file), - "check_api.last_released.api_file") - removedApiFile := ctx.ExpandSource(String(d.properties.Check_api.Last_released.Removed_api_file), - "check_api.last_released.removed_api_file") - baselineFile := ctx.ExpandOptionalSource(d.properties.Check_api.Last_released.Baseline_file, - "check_api.last_released.baseline_file") - d.checkLastReleasedApiTimestamp = android.PathForModuleOut(ctx, "check_last_released_api.timestamp") - opts := " " + d.Javadoc.args + " --check-compatibility:api:released " + apiFile.String() + - flags.metalavaInclusionAnnotationsFlags + " --check-compatibility:removed:released " + - removedApiFile.String() + flags.metalavaMergeAnnoDirFlags + " " - baselineOut := android.PathForModuleOut(ctx, "last_released_baseline.txt") + if len(d.Javadoc.properties.Out) > 0 { + ctx.PropertyErrorf("out", "out property may not be combined with check_api") + } + + apiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Current.Api_file)) + removedApiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Current.Removed_api_file)) + baselineFile := android.OptionalPathForModuleSrc(ctx, d.properties.Check_api.Current.Baseline_file) + if baselineFile.Valid() { - opts = opts + "--baseline " + baselineFile.String() + " --update-baseline " + baselineOut.String() + " " + ctx.PropertyErrorf("baseline_file", "current API check can't have a baseline file. (module %s)", ctx.ModuleName()) } - d.transformCheckApi(ctx, apiFile, removedApiFile, baselineFile, baselineOut, metalavaCheckApiImplicits, - javaVersion, flags.bootClasspathArgs, flags.classpathArgs, flags.sourcepathArgs, opts, "last-apicheck", - `\n******************************\n`+ - `You have tried to change the API from what has been previously released in\n`+ - `an SDK. Please fix the errors listed above.\n`+ - `******************************\n`, - d.checkLastReleasedApiTimestamp) + d.checkCurrentApiTimestamp = android.PathForModuleOut(ctx, "check_current_api.timestamp") + + rule := android.NewRuleBuilder() + + // Diff command line. + // -F matches the closest "opening" line, such as "package android {" + // and " public class Intent {". + diff := `diff -u -F '{ *$'` + + rule.Command().Text("( true") + rule.Command(). + Text(diff). + Input(apiFile).Input(d.apiFile) + + rule.Command(). + Text(diff). + Input(removedApiFile).Input(d.removedApiFile) + + msg := fmt.Sprintf(`\n******************************\n`+ + `You have tried to change the API from what has been previously approved.\n\n`+ + `To make these errors go away, you have two choices:\n`+ + ` 1. You can add '@hide' javadoc comments (and remove @SystemApi/@TestApi/etc)\n`+ + ` to the new methods, etc. shown in the above diff.\n\n`+ + ` 2. You can update current.txt and/or removed.txt by executing the following command:\n`+ + ` make %s-update-current-api\n\n`+ + ` To submit the revised current.txt to the main Android repository,\n`+ + ` you will need approval.\n`+ + `******************************\n`, ctx.ModuleName()) + + rule.Command(). + Text("touch").Output(d.checkCurrentApiTimestamp). + Text(") || ("). + Text("echo").Flag("-e").Flag(`"` + msg + `"`). + Text("; exit 38"). + Text(")") + + rule.Build(pctx, ctx, "metalavaCurrentApiCheck", "check current API") + + d.updateCurrentApiTimestamp = android.PathForModuleOut(ctx, "update_current_api.timestamp") + + // update API rule + rule = android.NewRuleBuilder() + + rule.Command().Text("( true") + + rule.Command(). + Text("cp").Flag("-f"). + Input(d.apiFile).Flag(apiFile.String()) + + rule.Command(). + Text("cp").Flag("-f"). + Input(d.removedApiFile).Flag(removedApiFile.String()) + + msg = "failed to update public API" + + rule.Command(). + Text("touch").Output(d.updateCurrentApiTimestamp). + Text(") || ("). + Text("echo").Flag("-e").Flag(`"` + msg + `"`). + Text("; exit 38"). + Text(")") + + rule.Build(pctx, ctx, "metalavaCurrentApiUpdate", "update current API") } if String(d.properties.Check_nullability_warnings) != "" { @@ -1752,9 +1801,11 @@ func (d *Droidstubs) GenerateAndroidBuildActions(ctx android.ModuleContext) { ctx.PropertyErrorf("check_nullability_warnings", "Cannot specify check_nullability_warnings unless validating nullability") } - checkNullabilityWarnings := ctx.ExpandSource(String(d.properties.Check_nullability_warnings), - "check_nullability_warnings") + + checkNullabilityWarnings := android.PathForModuleSrc(ctx, String(d.properties.Check_nullability_warnings)) + d.checkNullabilityWarningsTimestamp = android.PathForModuleOut(ctx, "check_nullability_warnings.timestamp") + msg := fmt.Sprintf(`\n******************************\n`+ `The warnings encountered during nullability annotation validation did\n`+ `not match the checked in file of expected warnings. The diffs are shown\n`+ @@ -1764,20 +1815,32 @@ func (d *Droidstubs) GenerateAndroidBuildActions(ctx android.ModuleContext) { ` cp %s %s\n`+ ` and submitting the updated file as part of your change.`, d.nullabilityWarningsFile, checkNullabilityWarnings) - ctx.Build(pctx, android.BuildParams{ - Rule: nullabilityWarningsCheck, - Description: "Nullability Warnings Check", - Output: d.checkNullabilityWarningsTimestamp, - Implicits: android.Paths{checkNullabilityWarnings, d.nullabilityWarningsFile}, - Args: map[string]string{ - "expected": checkNullabilityWarnings.String(), - "actual": d.nullabilityWarningsFile.String(), - "msg": msg, - }, - }) + + rule := android.NewRuleBuilder() + + rule.Command(). + Text("("). + Text("diff").Input(checkNullabilityWarnings).Input(d.nullabilityWarningsFile). + Text("&&"). + Text("touch").Output(d.checkNullabilityWarningsTimestamp). + Text(") || ("). + Text("echo").Flag("-e").Flag(`"` + msg + `"`). + Text("; exit 38"). + Text(")") + + rule.Build(pctx, ctx, "nullabilityWarningsCheck", "nullability warnings check") } if Bool(d.properties.Jdiff_enabled) && !ctx.Config().IsPdkBuild() { + if len(d.Javadoc.properties.Out) > 0 { + ctx.PropertyErrorf("out", "out property may not be combined with jdiff") + } + + outDir := android.PathForModuleOut(ctx, "jdiff-out") + srcJarDir := android.PathForModuleOut(ctx, "jdiff-srcjars") + stubsDir := android.PathForModuleOut(ctx, "jdiff-stubsDir") + + rule := android.NewRuleBuilder() // Please sync with android-api-council@ before making any changes for the name of jdiffDocZip below // since there's cron job downstream that fetch this .zip file periodically. @@ -1785,21 +1848,55 @@ func (d *Droidstubs) GenerateAndroidBuildActions(ctx android.ModuleContext) { d.jdiffDocZip = android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"jdiff-docs.zip") d.jdiffStubsSrcJar = android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"jdiff-stubs.srcjar") - var jdiffImplicitOutputs android.WritablePaths - jdiffImplicitOutputs = append(jdiffImplicitOutputs, d.jdiffDocZip) - jdiff := android.PathForOutput(ctx, "host", ctx.Config().PrebuiltOS(), "framework", "jdiff.jar") - jdiffImplicits = append(jdiffImplicits, android.Paths{jdiff, d.apiXmlFile, d.lastReleasedApiXmlFile}...) - opts := " -encoding UTF-8 -source 1.8 -J-Xmx1600m -XDignore.symbol.file " + - "-doclet jdiff.JDiff -docletpath " + jdiff.String() + " -quiet " + - "-newapi " + strings.TrimSuffix(d.apiXmlFile.Base(), d.apiXmlFile.Ext()) + - " -newapidir " + filepath.Dir(d.apiXmlFile.String()) + - " -oldapi " + strings.TrimSuffix(d.lastReleasedApiXmlFile.Base(), d.lastReleasedApiXmlFile.Ext()) + - " -oldapidir " + filepath.Dir(d.lastReleasedApiXmlFile.String()) + rule.Command().Text("rm -rf").Text(outDir.String()).Text(stubsDir.String()) + rule.Command().Text("mkdir -p").Text(outDir.String()).Text(stubsDir.String()) + + srcJarList := zipSyncCmd(ctx, rule, srcJarDir, d.Javadoc.srcJars) + + cmd := javadocBootclasspathCmd(ctx, rule, d.Javadoc.srcFiles, outDir, srcJarDir, srcJarList, + deps.bootClasspath, deps.classpath, d.sourcepaths) + + cmd.Flag("-J-Xmx1600m"). + Flag("-XDignore.symbol.file"). + FlagWithArg("-doclet ", "jdiff.JDiff"). + FlagWithInput("-docletpath ", jdiff). + Flag("-quiet") + + if d.apiXmlFile != nil { + cmd.FlagWithArg("-newapi ", strings.TrimSuffix(d.apiXmlFile.Base(), d.apiXmlFile.Ext())). + FlagWithArg("-newapidir ", filepath.Dir(d.apiXmlFile.String())). + Implicit(d.apiXmlFile) + } + + if d.lastReleasedApiXmlFile != nil { + cmd.FlagWithArg("-oldapi ", strings.TrimSuffix(d.lastReleasedApiXmlFile.Base(), d.lastReleasedApiXmlFile.Ext())). + FlagWithArg("-oldapidir ", filepath.Dir(d.lastReleasedApiXmlFile.String())). + Implicit(d.lastReleasedApiXmlFile) + } + + rule.Command(). + BuiltTool(ctx, "soong_zip"). + Flag("-write_if_changed"). + Flag("-d"). + FlagWithOutput("-o ", d.jdiffDocZip). + FlagWithArg("-C ", outDir.String()). + FlagWithArg("-D ", outDir.String()) - d.transformJdiff(ctx, jdiffImplicits, jdiffImplicitOutputs, flags.bootClasspathArgs, flags.classpathArgs, - flags.sourcepathArgs, opts) + rule.Command(). + BuiltTool(ctx, "soong_zip"). + Flag("-write_if_changed"). + Flag("-jar"). + FlagWithOutput("-o ", d.jdiffStubsSrcJar). + FlagWithArg("-C ", stubsDir.String()). + FlagWithArg("-D ", stubsDir.String()) + + rule.Restat() + + zipSyncCleanupCmd(rule, srcJarDir) + + rule.Build(pctx, ctx, "jdiff", "jdiff") } } @@ -1825,6 +1922,7 @@ type ExportedDroiddocDir struct { dir android.Path } +// droiddoc_exported_dir exports a directory of html templates or nullability annotations for use by doclava. func ExportedDroiddocDirFactory() android.Module { module := &ExportedDroiddocDir{} module.AddProperties(&module.properties) @@ -1848,9 +1946,6 @@ type DocDefaults struct { android.DefaultsModuleBase } -func (*DocDefaults) GenerateAndroidBuildActions(ctx android.ModuleContext) { -} - func DocDefaultsFactory() android.Module { module := &DocDefaults{} @@ -1876,3 +1971,154 @@ func StubsDefaultsFactory() android.Module { return module } + +func zipSyncCmd(ctx android.ModuleContext, rule *android.RuleBuilder, + srcJarDir android.ModuleOutPath, srcJars android.Paths) android.OutputPath { + + rule.Command().Text("rm -rf").Text(srcJarDir.String()) + rule.Command().Text("mkdir -p").Text(srcJarDir.String()) + srcJarList := srcJarDir.Join(ctx, "list") + + rule.Temporary(srcJarList) + + rule.Command().BuiltTool(ctx, "zipsync"). + FlagWithArg("-d ", srcJarDir.String()). + FlagWithOutput("-l ", srcJarList). + FlagWithArg("-f ", `"*.java"`). + Inputs(srcJars) + + return srcJarList +} + +func zipSyncCleanupCmd(rule *android.RuleBuilder, srcJarDir android.ModuleOutPath) { + rule.Command().Text("rm -rf").Text(srcJarDir.String()) +} + +var _ android.PrebuiltInterface = (*PrebuiltStubsSources)(nil) + +type PrebuiltStubsSourcesProperties struct { + Srcs []string `android:"path"` +} + +type PrebuiltStubsSources struct { + android.ModuleBase + android.DefaultableModuleBase + prebuilt android.Prebuilt + android.SdkBase + + properties PrebuiltStubsSourcesProperties + + // The source directories containing stubs source files. + srcDirs android.Paths + stubsSrcJar android.ModuleOutPath +} + +func (p *PrebuiltStubsSources) OutputFiles(tag string) (android.Paths, error) { + switch tag { + case "": + return android.Paths{p.stubsSrcJar}, nil + default: + return nil, fmt.Errorf("unsupported module reference tag %q", tag) + } +} + +func (d *PrebuiltStubsSources) StubsSrcJar() android.Path { + return d.stubsSrcJar +} + +func (p *PrebuiltStubsSources) GenerateAndroidBuildActions(ctx android.ModuleContext) { + p.stubsSrcJar = android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"stubs.srcjar") + + p.srcDirs = android.PathsForModuleSrc(ctx, p.properties.Srcs) + + rule := android.NewRuleBuilder() + command := rule.Command(). + BuiltTool(ctx, "soong_zip"). + Flag("-write_if_changed"). + Flag("-jar"). + FlagWithOutput("-o ", p.stubsSrcJar) + + for _, d := range p.srcDirs { + dir := d.String() + command. + FlagWithArg("-C ", dir). + FlagWithInput("-D ", d) + } + + rule.Restat() + + rule.Build(pctx, ctx, "zip src", "Create srcjar from prebuilt source") +} + +func (p *PrebuiltStubsSources) Prebuilt() *android.Prebuilt { + return &p.prebuilt +} + +func (p *PrebuiltStubsSources) Name() string { + return p.prebuilt.Name(p.ModuleBase.Name()) +} + +// prebuilt_stubs_sources imports a set of java source files as if they were +// generated by droidstubs. +// +// By default, a prebuilt_stubs_sources has a single variant that expects a +// set of `.java` files generated by droidstubs. +// +// Specifying `host_supported: true` will produce two variants, one for use as a dependency of device modules and one +// for host modules. +// +// Intended only for use by sdk snapshots. +func PrebuiltStubsSourcesFactory() android.Module { + module := &PrebuiltStubsSources{} + + module.AddProperties(&module.properties) + + android.InitPrebuiltModule(module, &module.properties.Srcs) + android.InitSdkAwareModule(module) + InitDroiddocModule(module, android.HostAndDeviceSupported) + return module +} + +type droidStubsSdkMemberType struct { + android.SdkMemberTypeBase +} + +func (mt *droidStubsSdkMemberType) AddDependencies(mctx android.BottomUpMutatorContext, dependencyTag blueprint.DependencyTag, names []string) { + mctx.AddVariationDependencies(nil, dependencyTag, names...) +} + +func (mt *droidStubsSdkMemberType) IsInstance(module android.Module) bool { + _, ok := module.(*Droidstubs) + return ok +} + +func (mt *droidStubsSdkMemberType) AddPrebuiltModule(ctx android.SdkMemberContext, member android.SdkMember) android.BpModule { + return ctx.SnapshotBuilder().AddPrebuiltModule(member, "prebuilt_stubs_sources") +} + +func (mt *droidStubsSdkMemberType) CreateVariantPropertiesStruct() android.SdkMemberProperties { + return &droidStubsInfoProperties{} +} + +type droidStubsInfoProperties struct { + android.SdkMemberPropertiesBase + + StubsSrcJar android.Path +} + +func (p *droidStubsInfoProperties) PopulateFromVariant(ctx android.SdkMemberContext, variant android.Module) { + droidstubs := variant.(*Droidstubs) + p.StubsSrcJar = droidstubs.stubsSrcJar +} + +func (p *droidStubsInfoProperties) AddToPropertySet(ctx android.SdkMemberContext, propertySet android.BpPropertySet) { + if p.StubsSrcJar != nil { + builder := ctx.SnapshotBuilder() + + snapshotRelativeDir := filepath.Join("java", ctx.Name()+"_stubs_sources") + + builder.UnzipToSnapshot(p.StubsSrcJar, snapshotRelativeDir) + + propertySet.AddProperty("srcs", []string{snapshotRelativeDir}) + } +} diff --git a/java/gen.go b/java/gen.go index 09776283f..d50a6653e 100644 --- a/java/gen.go +++ b/java/gen.go @@ -15,67 +15,93 @@ package java import ( + "strconv" + "strings" + "github.com/google/blueprint" + "github.com/google/blueprint/pathtools" "android/soong/android" ) func init() { - pctx.HostBinToolVariable("aidlCmd", "aidl") - pctx.HostBinToolVariable("syspropCmd", "sysprop_java") - pctx.SourcePathVariable("logtagsCmd", "build/tools/java-event-log-tags.py") - pctx.SourcePathVariable("mergeLogtagsCmd", "build/tools/merge-event-log-tags.py") + pctx.SourcePathVariable("logtagsCmd", "build/make/tools/java-event-log-tags.py") + pctx.SourcePathVariable("mergeLogtagsCmd", "build/make/tools/merge-event-log-tags.py") + pctx.SourcePathVariable("logtagsLib", "build/make/tools/event_log_tags.py") } var ( - aidl = pctx.AndroidStaticRule("aidl", - blueprint.RuleParams{ - Command: "$aidlCmd -d$depFile $aidlFlags $in $out", - CommandDeps: []string{"$aidlCmd"}, - }, - "depFile", "aidlFlags") - logtags = pctx.AndroidStaticRule("logtags", blueprint.RuleParams{ Command: "$logtagsCmd -o $out $in", - CommandDeps: []string{"$logtagsCmd"}, + CommandDeps: []string{"$logtagsCmd", "$logtagsLib"}, }) mergeLogtags = pctx.AndroidStaticRule("mergeLogtags", blueprint.RuleParams{ Command: "$mergeLogtagsCmd -o $out $in", - CommandDeps: []string{"$mergeLogtagsCmd"}, - }) - - sysprop = pctx.AndroidStaticRule("sysprop", - blueprint.RuleParams{ - Command: `rm -rf $out.tmp && mkdir -p $out.tmp && ` + - `$syspropCmd --java-output-dir $out.tmp $in && ` + - `${config.SoongZipCmd} -jar -o $out -C $out.tmp -D $out.tmp && rm -rf $out.tmp`, - CommandDeps: []string{ - "$syspropCmd", - "${config.SoongZipCmd}", - }, + CommandDeps: []string{"$mergeLogtagsCmd", "$logtagsLib"}, }) ) -func genAidl(ctx android.ModuleContext, aidlFile android.Path, aidlFlags string, deps android.Paths) android.Path { - javaFile := android.GenPathWithExt(ctx, "aidl", aidlFile, "java") - depFile := javaFile.String() + ".d" +func genAidl(ctx android.ModuleContext, aidlFiles android.Paths, aidlFlags string, deps android.Paths) android.Paths { + // Shard aidl files into groups of 50 to avoid having to recompile all of them if one changes and to avoid + // hitting command line length limits. + shards := android.ShardPaths(aidlFiles, 50) - ctx.Build(pctx, android.BuildParams{ - Rule: aidl, - Description: "aidl " + aidlFile.Rel(), - Output: javaFile, - Input: aidlFile, - Implicits: deps, - Args: map[string]string{ - "depFile": depFile, - "aidlFlags": aidlFlags, - }, - }) + srcJarFiles := make(android.Paths, 0, len(shards)) - return javaFile + for i, shard := range shards { + srcJarFile := android.PathForModuleGen(ctx, "aidl", "aidl"+strconv.Itoa(i)+".srcjar") + srcJarFiles = append(srcJarFiles, srcJarFile) + + outDir := srcJarFile.ReplaceExtension(ctx, "tmp") + + rule := android.NewRuleBuilder() + + rule.Command().Text("rm -rf").Flag(outDir.String()) + rule.Command().Text("mkdir -p").Flag(outDir.String()) + rule.Command().Text("FLAGS=' " + aidlFlags + "'") + + for _, aidlFile := range shard { + depFile := srcJarFile.InSameDir(ctx, aidlFile.String()+".d") + javaFile := outDir.Join(ctx, pathtools.ReplaceExtension(aidlFile.String(), "java")) + rule.Command(). + Tool(ctx.Config().HostToolPath(ctx, "aidl")). + FlagWithDepFile("-d", depFile). + Flag("$FLAGS"). + Input(aidlFile). + Output(javaFile). + Implicits(deps) + rule.Temporary(javaFile) + } + + rule.Command(). + Tool(ctx.Config().HostToolPath(ctx, "soong_zip")). + // TODO(b/124333557): this can't use -srcjar for now, aidl on parcelables generates java files + // without a package statement, which causes -srcjar to put them in the top level of the zip file. + // Once aidl skips parcelables we can use -srcjar. + //Flag("-srcjar"). + Flag("-write_if_changed"). + FlagWithOutput("-o ", srcJarFile). + FlagWithArg("-C ", outDir.String()). + FlagWithArg("-D ", outDir.String()) + + rule.Command().Text("rm -rf").Flag(outDir.String()) + + rule.Restat() + + ruleName := "aidl" + ruleDesc := "aidl" + if len(shards) > 1 { + ruleName += "_" + strconv.Itoa(i) + ruleDesc += " " + strconv.Itoa(i) + } + + rule.Build(pctx, ctx, ruleName, ruleDesc) + } + + return srcJarFiles } func genLogtags(ctx android.ModuleContext, logtagsFile android.Path) android.Path { @@ -91,44 +117,55 @@ func genLogtags(ctx android.ModuleContext, logtagsFile android.Path) android.Pat return javaFile } -func genSysprop(ctx android.ModuleContext, syspropFile android.Path) android.Path { - srcJarFile := android.GenPathWithExt(ctx, "sysprop", syspropFile, "srcjar") - - ctx.Build(pctx, android.BuildParams{ - Rule: sysprop, - Description: "sysprop_java " + syspropFile.Rel(), - Output: srcJarFile, - Input: syspropFile, - }) - - return srcJarFile +func genAidlIncludeFlags(srcFiles android.Paths) string { + var baseDirs []string + for _, srcFile := range srcFiles { + if srcFile.Ext() == ".aidl" { + baseDir := strings.TrimSuffix(srcFile.String(), srcFile.Rel()) + if baseDir != "" && !android.InList(baseDir, baseDirs) { + baseDirs = append(baseDirs, baseDir) + } + } + } + return android.JoinWithPrefix(baseDirs, " -I") } func (j *Module) genSources(ctx android.ModuleContext, srcFiles android.Paths, flags javaBuilderFlags) android.Paths { outSrcFiles := make(android.Paths, 0, len(srcFiles)) + var protoSrcs android.Paths + var aidlSrcs android.Paths + + aidlIncludeFlags := genAidlIncludeFlags(srcFiles) for _, srcFile := range srcFiles { switch srcFile.Ext() { case ".aidl": - javaFile := genAidl(ctx, srcFile, flags.aidlFlags, flags.aidlDeps) - outSrcFiles = append(outSrcFiles, javaFile) + aidlSrcs = append(aidlSrcs, srcFile) case ".logtags": j.logtagsSrcs = append(j.logtagsSrcs, srcFile) javaFile := genLogtags(ctx, srcFile) outSrcFiles = append(outSrcFiles, javaFile) case ".proto": - srcJarFile := genProto(ctx, srcFile, flags.proto) - outSrcFiles = append(outSrcFiles, srcJarFile) - case ".sysprop": - srcJarFile := genSysprop(ctx, srcFile) - outSrcFiles = append(outSrcFiles, srcJarFile) + protoSrcs = append(protoSrcs, srcFile) default: outSrcFiles = append(outSrcFiles, srcFile) } } + // Process all proto files together to support sharding them into one or more rules that produce srcjars. + if len(protoSrcs) > 0 { + srcJarFiles := genProto(ctx, protoSrcs, flags.proto) + outSrcFiles = append(outSrcFiles, srcJarFiles...) + } + + // Process all aidl files together to support sharding them into one or more rules that produce srcjars. + if len(aidlSrcs) > 0 { + srcJarFiles := genAidl(ctx, aidlSrcs, flags.aidlFlags+aidlIncludeFlags, flags.aidlDeps) + outSrcFiles = append(outSrcFiles, srcJarFiles...) + } + return outSrcFiles } diff --git a/java/genrule.go b/java/genrule.go index 25494ec8a..e0a9c8faf 100644 --- a/java/genrule.go +++ b/java/genrule.go @@ -20,8 +20,12 @@ import ( ) func init() { - android.RegisterModuleType("java_genrule", genRuleFactory) - android.RegisterModuleType("java_genrule_host", genRuleFactoryHost) + RegisterGenRuleBuildComponents(android.InitRegistrationContext) +} + +func RegisterGenRuleBuildComponents(ctx android.RegistrationContext) { + ctx.RegisterModuleType("java_genrule", genRuleFactory) + ctx.RegisterModuleType("java_genrule_host", genRuleFactoryHost) } // java_genrule is a genrule that can depend on other java_* objects. diff --git a/java/hiddenapi.go b/java/hiddenapi.go index 6020aba6e..b5a021785 100644 --- a/java/hiddenapi.go +++ b/java/hiddenapi.go @@ -28,9 +28,10 @@ var hiddenAPIGenerateCSVRule = pctx.AndroidStaticRule("hiddenAPIGenerateCSV", bl }, "outFlag", "stubAPIFlags") type hiddenAPI struct { + bootDexJarPath android.Path flagsCSVPath android.Path + indexCSVPath android.Path metadataCSVPath android.Path - bootDexJarPath android.Path } func (h *hiddenAPI) flagsCSV() android.Path { @@ -45,19 +46,22 @@ func (h *hiddenAPI) bootDexJar() android.Path { return h.bootDexJarPath } +func (h *hiddenAPI) indexCSV() android.Path { + return h.indexCSVPath +} + type hiddenAPIIntf interface { + bootDexJar() android.Path flagsCSV() android.Path + indexCSV() android.Path metadataCSV() android.Path - bootDexJar() android.Path } var _ hiddenAPIIntf = (*hiddenAPI)(nil) -func (h *hiddenAPI) hiddenAPI(ctx android.ModuleContext, dexJar android.ModuleOutPath, implementationJar android.Path, - uncompressDex bool) android.ModuleOutPath { - +func (h *hiddenAPI) hiddenAPI(ctx android.ModuleContext, name string, primary bool, dexJar android.ModuleOutPath, + implementationJar android.Path, uncompressDex bool) android.ModuleOutPath { if !ctx.Config().IsEnvTrue("UNSAFE_DISABLE_HIDDENAPI_FLAGS") { - name := ctx.ModuleName() // Modules whose names are of the format <x>-hiddenapi provide hiddenapi information // for the boot jar module <x>. Otherwise, the module provides information for itself. @@ -77,16 +81,22 @@ func (h *hiddenAPI) hiddenAPI(ctx android.ModuleContext, dexJar android.ModuleOu // Derive the greylist from classes jar. flagsCSV := android.PathForModuleOut(ctx, "hiddenapi", "flags.csv") metadataCSV := android.PathForModuleOut(ctx, "hiddenapi", "metadata.csv") - hiddenAPIGenerateCSV(ctx, flagsCSV, metadataCSV, implementationJar) - h.flagsCSVPath = flagsCSV - h.metadataCSVPath = metadataCSV + indexCSV := android.PathForModuleOut(ctx, "hiddenapi", "index.csv") + h.hiddenAPIGenerateCSV(ctx, flagsCSV, metadataCSV, indexCSV, implementationJar) // If this module is actually on the boot jars list and not providing // hiddenapi information for a module on the boot jars list then encode // the gathered information in the generated dex file. if name == bootJarName { hiddenAPIJar := android.PathForModuleOut(ctx, "hiddenapi", name+".jar") - h.bootDexJarPath = dexJar + + // More than one library with the same classes can be encoded but only one can + // be added to the global set of flags, otherwise it will result in duplicate + // classes which is an error. Therefore, only add the dex jar of one of them + // to the global set of flags. + if primary { + h.bootDexJarPath = dexJar + } hiddenAPIEncodeDex(ctx, hiddenAPIJar, dexJar, uncompressDex) dexJar = hiddenAPIJar } @@ -96,9 +106,7 @@ func (h *hiddenAPI) hiddenAPI(ctx android.ModuleContext, dexJar android.ModuleOu return dexJar } -func hiddenAPIGenerateCSV(ctx android.ModuleContext, flagsCSV, metadataCSV android.WritablePath, - classesJar android.Path) { - +func (h *hiddenAPI) hiddenAPIGenerateCSV(ctx android.ModuleContext, flagsCSV, metadataCSV, indexCSV android.WritablePath, classesJar android.Path) { stubFlagsCSV := hiddenAPISingletonPaths(ctx).stubFlags ctx.Build(pctx, android.BuildParams{ @@ -112,6 +120,7 @@ func hiddenAPIGenerateCSV(ctx android.ModuleContext, flagsCSV, metadataCSV andro "stubAPIFlags": stubFlagsCSV.String(), }, }) + h.flagsCSVPath = flagsCSV ctx.Build(pctx, android.BuildParams{ Rule: hiddenAPIGenerateCSVRule, @@ -124,18 +133,26 @@ func hiddenAPIGenerateCSV(ctx android.ModuleContext, flagsCSV, metadataCSV andro "stubAPIFlags": stubFlagsCSV.String(), }, }) - + h.metadataCSVPath = metadataCSV + + rule := android.NewRuleBuilder() + rule.Command(). + BuiltTool(ctx, "merge_csv"). + FlagWithInput("--zip_input=", classesJar). + FlagWithOutput("--output=", indexCSV) + rule.Build(pctx, ctx, "merged-hiddenapi-index", "Merged Hidden API index") + h.indexCSVPath = indexCSV } var hiddenAPIEncodeDexRule = pctx.AndroidStaticRule("hiddenAPIEncodeDex", blueprint.RuleParams{ - Command: `rm -rf $tmpDir && mkdir -p $tmpDir && mkdir $tmpDir/dex-input && mkdir $tmpDir/dex-output && ` + - `unzip -o -q $in 'classes*.dex' -d $tmpDir/dex-input && ` + - `for INPUT_DEX in $$(find $tmpDir/dex-input -maxdepth 1 -name 'classes*.dex' | sort); do ` + - ` echo "--input-dex=$${INPUT_DEX}"; ` + - ` echo "--output-dex=$tmpDir/dex-output/$$(basename $${INPUT_DEX})"; ` + - `done | xargs ${config.HiddenAPI} encode --api-flags=$flagsCsv $hiddenapiFlags && ` + - `${config.SoongZipCmd} $soongZipFlags -o $tmpDir/dex.jar -C $tmpDir/dex-output -f "$tmpDir/dex-output/classes*.dex" && ` + - `${config.MergeZipsCmd} -D -zipToNotStrip $tmpDir/dex.jar -stripFile "classes*.dex" $out $tmpDir/dex.jar $in`, + Command: `rm -rf $tmpDir && mkdir -p $tmpDir && mkdir $tmpDir/dex-input && mkdir $tmpDir/dex-output && + unzip -qoDD $in 'classes*.dex' -d $tmpDir/dex-input && + for INPUT_DEX in $$(find $tmpDir/dex-input -maxdepth 1 -name 'classes*.dex' | sort); do + echo "--input-dex=$${INPUT_DEX}"; + echo "--output-dex=$tmpDir/dex-output/$$(basename $${INPUT_DEX})"; + done | xargs ${config.HiddenAPI} encode --api-flags=$flagsCsv $hiddenapiFlags && + ${config.SoongZipCmd} $soongZipFlags -o $tmpDir/dex.jar -C $tmpDir/dex-output -f "$tmpDir/dex-output/classes*.dex" && + ${config.MergeZipsCmd} -D -zipToNotStrip $tmpDir/dex.jar -stripFile "classes*.dex" -stripFile "**/*.uau" $out $tmpDir/dex.jar $in`, CommandDeps: []string{ "${config.HiddenAPI}", "${config.SoongZipCmd}", @@ -159,9 +176,23 @@ func hiddenAPIEncodeDex(ctx android.ModuleContext, output android.WritablePath, tmpOutput = android.PathForModuleOut(ctx, "hiddenapi", "unaligned", "unaligned.jar") tmpDir = android.PathForModuleOut(ctx, "hiddenapi", "unaligned") } + + enforceHiddenApiFlagsToAllMembers := true // If frameworks/base doesn't exist we must be building with the 'master-art' manifest. // Disable assertion that all methods/fields have hidden API flags assigned. if !ctx.Config().FrameworksBaseDirExists(ctx) { + enforceHiddenApiFlagsToAllMembers = false + } + // b/149353192: when a module is instrumented, jacoco adds synthetic members + // $jacocoData and $jacocoInit. Since they don't exist when building the hidden API flags, + // don't complain when we don't find hidden API flags for the synthetic members. + if j, ok := ctx.Module().(interface { + shouldInstrument(android.BaseModuleContext) bool + }); ok && j.shouldInstrument(ctx) { + enforceHiddenApiFlagsToAllMembers = false + } + + if !enforceHiddenApiFlagsToAllMembers { hiddenapiFlags = "--no-force-assign-all" } diff --git a/java/hiddenapi_singleton.go b/java/hiddenapi_singleton.go index 09936ea78..95dd0bb09 100644 --- a/java/hiddenapi_singleton.go +++ b/java/hiddenapi_singleton.go @@ -15,17 +15,22 @@ package java import ( + "fmt" + "android/soong/android" ) func init() { android.RegisterSingletonType("hiddenapi", hiddenAPISingletonFactory) + android.RegisterSingletonType("hiddenapi_index", hiddenAPIIndexSingletonFactory) + android.RegisterModuleType("hiddenapi_flags", hiddenAPIFlagsFactory) } type hiddenAPISingletonPathsStruct struct { - stubFlags android.OutputPath flags android.OutputPath + index android.OutputPath metadata android.OutputPath + stubFlags android.OutputPath } var hiddenAPISingletonPathsKey = android.NewOnceKey("hiddenAPISingletonPathsKey") @@ -36,9 +41,10 @@ var hiddenAPISingletonPathsKey = android.NewOnceKey("hiddenAPISingletonPathsKey" func hiddenAPISingletonPaths(ctx android.PathContext) hiddenAPISingletonPathsStruct { return ctx.Config().Once(hiddenAPISingletonPathsKey, func() interface{} { return hiddenAPISingletonPathsStruct{ - stubFlags: android.PathForOutput(ctx, "hiddenapi", "hiddenapi-stub-flags.txt"), flags: android.PathForOutput(ctx, "hiddenapi", "hiddenapi-flags.csv"), + index: android.PathForOutput(ctx, "hiddenapi", "hiddenapi-index.csv"), metadata: android.PathForOutput(ctx, "hiddenapi", "hiddenapi-greylist.csv"), + stubFlags: android.PathForOutput(ctx, "hiddenapi", "hiddenapi-stub-flags.txt"), } }).(hiddenAPISingletonPathsStruct) } @@ -149,6 +155,14 @@ func stubFlagsRule(ctx android.SingletonContext) { // Collect dex jar paths for modules that had hiddenapi encode called on them. if h, ok := module.(hiddenAPIIntf); ok { if jar := h.bootDexJar(); jar != nil { + // For a java lib included in an APEX, only take the one built for + // the platform variant, and skip the variants for APEXes. + // Otherwise, the hiddenapi tool will complain about duplicated classes + if a, ok := module.(android.ApexModule); ok { + if android.InAnyApex(module.Name()) && !a.IsForPlatform() { + return + } + } bootDexJars = append(bootDexJars, jar) } } @@ -179,7 +193,7 @@ func stubFlagsRule(ctx android.SingletonContext) { rule.MissingDeps(missingDeps) rule.Command(). - Tool(pctx.HostBinToolPath(ctx, "hiddenapi")). + Tool(ctx.Config().HostToolPath(ctx, "hiddenapi")). Text("list"). FlagForEachInput("--boot-dex=", bootDexJars). FlagWithInputList("--public-stub-classpath=", publicStubPaths, ":"). @@ -193,27 +207,43 @@ func stubFlagsRule(ctx android.SingletonContext) { rule.Build(pctx, ctx, "hiddenAPIStubFlagsFile", "hiddenapi stub flags") } +func moduleForGreyListRemovedApis(ctx android.SingletonContext, module android.Module) bool { + switch ctx.ModuleName(module) { + case "api-stubs-docs", "system-api-stubs-docs", "android.car-stubs-docs", "android.car-system-stubs-docs": + return true + default: + return false + } +} + // flagsRule creates a rule to build hiddenapi-flags.csv out of flags.csv files generated for boot image modules and // the greylists. func flagsRule(ctx android.SingletonContext) android.Path { var flagsCSV android.Paths - - var greylistIgnoreConflicts android.Path + var greylistRemovedApis android.Paths ctx.VisitAllModules(func(module android.Module) { if h, ok := module.(hiddenAPIIntf); ok { if csv := h.flagsCSV(); csv != nil { flagsCSV = append(flagsCSV, csv) } - } else if ds, ok := module.(*Droidstubs); ok && ctx.ModuleName(module) == "hiddenapi-lists-docs" { - greylistIgnoreConflicts = ds.removedDexApiFile + } else if ds, ok := module.(*Droidstubs); ok { + // Track @removed public and system APIs via corresponding droidstubs targets. + // These APIs are not present in the stubs, however, we have to keep allowing access + // to them at runtime. + if moduleForGreyListRemovedApis(ctx, module) { + greylistRemovedApis = append(greylistRemovedApis, ds.removedDexApiFile) + } } }) - if greylistIgnoreConflicts == nil { - ctx.Errorf("failed to find removed_dex_api_filename from hiddenapi-lists-docs module") - return nil - } + combinedRemovedApis := android.PathForOutput(ctx, "hiddenapi", "combined-removed-dex.txt") + ctx.Build(pctx, android.BuildParams{ + Rule: android.Cat, + Inputs: greylistRemovedApis, + Output: combinedRemovedApis, + Description: "Combine removed apis for " + combinedRemovedApis.String(), + }) rule := android.NewRuleBuilder() @@ -228,8 +258,9 @@ func flagsRule(ctx android.SingletonContext) android.Path { Inputs(flagsCSV). FlagWithInput("--greylist ", android.PathForSource(ctx, "frameworks/base/config/hiddenapi-greylist.txt")). - FlagWithInput("--greylist-ignore-conflicts ", - greylistIgnoreConflicts). + FlagWithInput("--greylist-ignore-conflicts ", combinedRemovedApis). + FlagWithInput("--greylist-max-q ", + android.PathForSource(ctx, "frameworks/base/config/hiddenapi-greylist-max-q.txt")). FlagWithInput("--greylist-max-p ", android.PathForSource(ctx, "frameworks/base/config/hiddenapi-greylist-max-p.txt")). FlagWithInput("--greylist-max-o-ignore-conflicts ", @@ -280,10 +311,9 @@ func metadataRule(ctx android.SingletonContext) android.Path { outputPath := hiddenAPISingletonPaths(ctx).metadata rule.Command(). - Tool(android.PathForSource(ctx, "frameworks/base/tools/hiddenapi/merge_csv.py")). - Inputs(metadataCSV). - Text(">"). - Output(outputPath) + BuiltTool(ctx, "merge_csv"). + FlagWithOutput("--output=", outputPath). + Inputs(metadataCSV) rule.Build(pctx, ctx, "hiddenAPIGreylistMetadataFile", "hiddenapi greylist metadata") @@ -307,3 +337,90 @@ func commitChangeForRestat(rule *android.RuleBuilder, tempPath, outputPath andro Text("fi"). Text(")") } + +type hiddenAPIFlagsProperties struct { + // name of the file into which the flags will be copied. + Filename *string +} + +type hiddenAPIFlags struct { + android.ModuleBase + + properties hiddenAPIFlagsProperties + + outputFilePath android.OutputPath +} + +func (h *hiddenAPIFlags) GenerateAndroidBuildActions(ctx android.ModuleContext) { + filename := String(h.properties.Filename) + + inputPath := hiddenAPISingletonPaths(ctx).flags + h.outputFilePath = android.PathForModuleOut(ctx, filename).OutputPath + + // This ensures that outputFilePath has the correct name for others to + // use, as the source file may have a different name. + ctx.Build(pctx, android.BuildParams{ + Rule: android.Cp, + Output: h.outputFilePath, + Input: inputPath, + }) +} + +func (h *hiddenAPIFlags) OutputFiles(tag string) (android.Paths, error) { + switch tag { + case "": + return android.Paths{h.outputFilePath}, nil + default: + return nil, fmt.Errorf("unsupported module reference tag %q", tag) + } +} + +// hiddenapi-flags provides access to the hiddenapi-flags.csv file generated during the build. +func hiddenAPIFlagsFactory() android.Module { + module := &hiddenAPIFlags{} + module.AddProperties(&module.properties) + android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibCommon) + return module +} + +func hiddenAPIIndexSingletonFactory() android.Singleton { + return &hiddenAPIIndexSingleton{} +} + +type hiddenAPIIndexSingleton struct { + index android.Path +} + +func (h *hiddenAPIIndexSingleton) GenerateBuildActions(ctx android.SingletonContext) { + // Don't run any hiddenapi rules if UNSAFE_DISABLE_HIDDENAPI_FLAGS=true + if ctx.Config().IsEnvTrue("UNSAFE_DISABLE_HIDDENAPI_FLAGS") { + return + } + + indexes := android.Paths{} + ctx.VisitAllModules(func(module android.Module) { + if h, ok := module.(hiddenAPIIntf); ok { + if h.indexCSV() != nil { + indexes = append(indexes, h.indexCSV()) + } + } + }) + + rule := android.NewRuleBuilder() + rule.Command(). + BuiltTool(ctx, "merge_csv"). + FlagWithArg("--header=", "signature,file,startline,startcol,endline,endcol,properties"). + FlagWithOutput("--output=", hiddenAPISingletonPaths(ctx).index). + Inputs(indexes) + rule.Build(pctx, ctx, "singleton-merged-hiddenapi-index", "Singleton merged Hidden API index") + + h.index = hiddenAPISingletonPaths(ctx).index +} + +func (h *hiddenAPIIndexSingleton) MakeVars(ctx android.MakeVarsContext) { + if ctx.Config().IsEnvTrue("UNSAFE_DISABLE_HIDDENAPI_FLAGS") { + return + } + + ctx.Strict("INTERNAL_PLATFORM_HIDDENAPI_INDEX", h.index.String()) +} diff --git a/java/jacoco.go b/java/jacoco.go index 8b6d4ac87..9162161d3 100644 --- a/java/jacoco.go +++ b/java/jacoco.go @@ -25,13 +25,14 @@ import ( "github.com/google/blueprint/proptools" "android/soong/android" + "android/soong/java/config" ) var ( jacoco = pctx.AndroidStaticRule("jacoco", blueprint.RuleParams{ Command: `rm -rf $tmpDir && mkdir -p $tmpDir && ` + `${config.Zip2ZipCmd} -i $in -o $strippedJar $stripSpec && ` + - `${config.JavaCmd} -jar ${config.JacocoCLIJar} ` + + `${config.JavaCmd} ${config.JavaVmFlags} -jar ${config.JacocoCLIJar} ` + ` instrument --quiet --dest $tmpDir $strippedJar && ` + `${config.Ziptime} $tmpJar && ` + `${config.MergeZipsCmd} --ignore-duplicates -j $out $tmpJar $in`, @@ -76,7 +77,8 @@ func (j *Module) jacocoModuleToZipCommand(ctx android.ModuleContext) string { if err != nil { ctx.PropertyErrorf("jacoco.include_filter", "%s", err.Error()) } - excludes, err := jacocoFiltersToSpecs(j.properties.Jacoco.Exclude_filter) + // Also include the default list of classes to exclude from instrumentation. + excludes, err := jacocoFiltersToSpecs(append(j.properties.Jacoco.Exclude_filter, config.DefaultJacocoExcludeFilter...)) if err != nil { ctx.PropertyErrorf("jacoco.exclude_filter", "%s", err.Error()) } diff --git a/java/java.go b/java/java.go index 9ac38c92c..4612b76fa 100644 --- a/java/java.go +++ b/java/java.go @@ -25,6 +25,7 @@ import ( "strings" "github.com/google/blueprint" + "github.com/google/blueprint/pathtools" "github.com/google/blueprint/proptools" "android/soong/android" @@ -33,23 +34,100 @@ import ( ) func init() { - android.RegisterModuleType("java_defaults", defaultsFactory) + RegisterJavaBuildComponents(android.InitRegistrationContext) + + // Register sdk member types. + android.RegisterSdkMemberType(javaHeaderLibsSdkMemberType) + + android.RegisterSdkMemberType(&librarySdkMemberType{ + android.SdkMemberTypeBase{ + PropertyName: "java_libs", + }, + func(j *Library) android.Path { + implementationJars := j.ImplementationJars() + if len(implementationJars) != 1 { + panic(fmt.Errorf("there must be only one implementation jar from %q", j.Name())) + } + + return implementationJars[0] + }, + }) + + android.RegisterSdkMemberType(&testSdkMemberType{ + SdkMemberTypeBase: android.SdkMemberTypeBase{ + PropertyName: "java_tests", + }, + }) +} + +func RegisterJavaBuildComponents(ctx android.RegistrationContext) { + ctx.RegisterModuleType("java_defaults", DefaultsFactory) + + ctx.RegisterModuleType("java_library", LibraryFactory) + ctx.RegisterModuleType("java_library_static", LibraryStaticFactory) + ctx.RegisterModuleType("java_library_host", LibraryHostFactory) + ctx.RegisterModuleType("java_binary", BinaryFactory) + ctx.RegisterModuleType("java_binary_host", BinaryHostFactory) + ctx.RegisterModuleType("java_test", TestFactory) + ctx.RegisterModuleType("java_test_helper_library", TestHelperLibraryFactory) + ctx.RegisterModuleType("java_test_host", TestHostFactory) + ctx.RegisterModuleType("java_test_import", JavaTestImportFactory) + ctx.RegisterModuleType("java_import", ImportFactory) + ctx.RegisterModuleType("java_import_host", ImportFactoryHost) + ctx.RegisterModuleType("java_device_for_host", DeviceForHostFactory) + ctx.RegisterModuleType("java_host_for_device", HostForDeviceFactory) + ctx.RegisterModuleType("dex_import", DexImportFactory) + + ctx.FinalDepsMutators(func(ctx android.RegisterMutatorsContext) { + ctx.BottomUp("dexpreopt_tool_deps", dexpreoptToolDepsMutator).Parallel() + }) + + ctx.RegisterSingletonType("logtags", LogtagsSingleton) + ctx.RegisterSingletonType("kythe_java_extract", kytheExtractJavaFactory) +} + +func (j *Module) CheckStableSdkVersion() error { + sdkVersion := j.sdkVersion() + if sdkVersion.stable() { + return nil + } + return fmt.Errorf("non stable SDK %v", sdkVersion) +} - android.RegisterModuleType("java_library", LibraryFactory) - android.RegisterModuleType("java_library_static", LibraryStaticFactory) - android.RegisterModuleType("java_library_host", LibraryHostFactory) - android.RegisterModuleType("java_binary", BinaryFactory) - android.RegisterModuleType("java_binary_host", BinaryHostFactory) - android.RegisterModuleType("java_test", TestFactory) - android.RegisterModuleType("java_test_helper_library", TestHelperLibraryFactory) - android.RegisterModuleType("java_test_host", TestHostFactory) - android.RegisterModuleType("java_import", ImportFactory) - android.RegisterModuleType("java_import_host", ImportFactoryHost) - android.RegisterModuleType("java_device_for_host", DeviceForHostFactory) - android.RegisterModuleType("java_host_for_device", HostForDeviceFactory) - android.RegisterModuleType("dex_import", DexImportFactory) +func (j *Module) checkSdkVersions(ctx android.ModuleContext) { + if j.RequiresStableAPIs(ctx) { + if sc, ok := ctx.Module().(sdkContext); ok { + if !sc.sdkVersion().specified() { + ctx.PropertyErrorf("sdk_version", + "sdk_version must have a value when the module is located at vendor or product(only if PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE is set).") + } + } + } - android.RegisterSingletonType("logtags", LogtagsSingleton) + ctx.VisitDirectDeps(func(module android.Module) { + tag := ctx.OtherModuleDependencyTag(module) + switch module.(type) { + // TODO(satayev): cover other types as well, e.g. imports + case *Library, *AndroidLibrary: + switch tag { + case bootClasspathTag, libTag, staticLibTag, java9LibTag: + checkLinkType(ctx, j, module.(linkTypeContext), tag.(dependencyTag)) + } + } + }) +} + +func (j *Module) checkPlatformAPI(ctx android.ModuleContext) { + if sc, ok := ctx.Module().(sdkContext); ok { + usePlatformAPI := proptools.Bool(j.deviceProperties.Platform_apis) + sdkVersionSpecified := sc.sdkVersion().specified() + if usePlatformAPI && sdkVersionSpecified { + ctx.PropertyErrorf("platform_apis", "platform_apis must be false when sdk_version is not empty.") + } else if !usePlatformAPI && !sdkVersionSpecified { + ctx.PropertyErrorf("platform_apis", "platform_apis must be true when sdk_version is empty.") + } + + } } // TODO: @@ -82,13 +160,6 @@ type CompilerProperties struct { // list of files that should be excluded from java_resources and java_resource_dirs Exclude_java_resources []string `android:"path,arch_variant"` - // don't build against the default libraries (bootclasspath, ext, and framework for device - // targets) - No_standard_libs *bool - - // don't build against the framework libraries (ext, and framework for device targets) - No_framework_libs *bool - // list of module-specific flags that will be used for javac compiles Javacflags []string `android:"arch_variant"` @@ -124,6 +195,9 @@ type CompilerProperties struct { // List of modules to use as annotation processors Plugins []string + // List of modules to export to libraries that directly depend on this library as annotation processors + Exported_plugins []string + // The number of Java source entries each Javac instance can process Javac_shard_size *int64 @@ -131,10 +205,10 @@ type CompilerProperties struct { Use_tools_jar *bool Openjdk9 struct { - // List of source files that should only be used when passing -source 1.9 + // List of source files that should only be used when passing -source 1.9 or higher Srcs []string `android:"path"` - // List of javac flags that should only be used when passing -source 1.9 + // List of javac flags that should only be used when passing -source 1.9 or higher Javacflags []string } @@ -179,14 +253,17 @@ type CompilerProperties struct { // List of files to include in the META-INF/services folder of the resulting jar. Services []string `android:"path,arch_variant"` + + // If true, package the kotlin stdlib into the jar. Defaults to true. + Static_kotlin_stdlib *bool `android:"arch_variant"` } type CompilerDeviceProperties struct { // list of module-specific flags that will be used for dex compiles Dxflags []string `android:"arch_variant"` - // if not blank, set to the version of the sdk to compile against. Defaults to compiling against the current - // sdk if platform_apis is not set. + // if not blank, set to the version of the sdk to compile against. + // Defaults to compiling against the current platform. Sdk_version *string // if not blank, set the minimum version of the sdk that the compiled artifacts will run against. @@ -197,7 +274,9 @@ type CompilerDeviceProperties struct { // Defaults to sdk_version if not set. Target_sdk_version *string - // if true, compile against the platform APIs instead of an SDK. + // Whether to compile against the platform APIs instead of an SDK. + // If true, then sdk_version must be empty. The value of this field + // is ignored when module's type isn't android_app. Platform_apis *bool Aidl struct { @@ -259,21 +338,71 @@ type CompilerDeviceProperties struct { Proguard_flags_files []string `android:"path"` } - // When targeting 1.9, override the modules to use with --system + // When targeting 1.9 and above, override the modules to use with --system, + // otherwise provides defaults libraries to add to the bootclasspath. System_modules *string - UncompressDex bool `blueprint:"mutated"` - IsSDKLibrary bool `blueprint:"mutated"` + // The name of the module as used in build configuration. + // + // Allows a library to separate its actual name from the name used in + // build configuration, e.g.ctx.Config().BootJars(). + ConfigurationName *string `blueprint:"mutated"` + + // set the name of the output + Stem *string + + // Keep the data uncompressed. We always need uncompressed dex for execution, + // so this might actually save space by avoiding storing the same data twice. + // This defaults to reasonable value based on module and should not be set. + // It exists only to support ART tests. + Uncompress_dex *bool + + IsSDKLibrary bool `blueprint:"mutated"` + + // If true, generate the signature file of APK Signing Scheme V4, along side the signed APK file. + // Defaults to false. + V4_signature *bool } func (me *CompilerDeviceProperties) EffectiveOptimizeEnabled() bool { return BoolDefault(me.Optimize.Enabled, me.Optimize.EnabledByDefault) } +// Functionality common to Module and Import +// +// It is embedded in Module so its functionality can be used by methods in Module +// but it is currently only initialized by Import and Library. +type embeddableInModuleAndImport struct { + + // Functionality related to this being used as a component of a java_sdk_library. + EmbeddableSdkLibraryComponent +} + +func (e *embeddableInModuleAndImport) initModuleAndImport(moduleBase *android.ModuleBase) { + e.initSdkLibraryComponent(moduleBase) +} + +// Module/Import's DepIsInSameApex(...) delegates to this method. +// +// This cannot implement DepIsInSameApex(...) directly as that leads to ambiguity with +// the one provided by ApexModuleBase. +func (e *embeddableInModuleAndImport) depIsInSameApex(ctx android.BaseModuleContext, dep android.Module) bool { + // dependencies other than the static linkage are all considered crossing APEX boundary + if staticLibTag == ctx.OtherModuleDependencyTag(dep) { + return true + } + return false +} + // Module contains the properties and members used by all java module types type Module struct { android.ModuleBase android.DefaultableModuleBase + android.ApexModuleBase + android.SdkBase + + // Functionality common to Module and Import. + embeddableInModuleAndImport properties CompilerProperties protoProperties android.ProtoProperties @@ -290,6 +419,10 @@ type Module struct { // jar file containing only resources including from static library dependencies resourceJar android.Path + // args and dependencies to package source files into a srcjar + srcJarArgs []string + srcJarDeps android.Paths + // jar file containing implementation classes and resources including static library // dependencies implementationAndResourcesJar android.Path @@ -327,11 +460,17 @@ type Module struct { // manifest file to use instead of properties.Manifest overrideManifest android.OptionalPath - // list of SDK lib names that this java moudule is exporting + // list of SDK lib names that this java module is exporting exportedSdkLibs []string - // list of source files, collected from compiledJavaSrcs and compiledSrcJars - // filter out Exclude_srcs, will be used by android.IDEInfo struct + // list of plugins that this java module is exporting + exportedPluginJars android.Paths + + // list of plugins that this java module is exporting + exportedPluginClasses []string + + // list of source files, collected from srcFiles with unique java and all kt files, + // will be used by android.IDEInfo struct expandIDEInfoCompiledSrcs []string // expanded Jarjar_rules @@ -340,50 +479,77 @@ type Module struct { // list of additional targets for checkbuild additionalCheckedModules android.Paths + // Extra files generated by the module type to be added as java resources. + extraResources android.Paths + hiddenAPI dexpreopter + linter + + // list of the xref extraction files + kytheFiles android.Paths + + distFile android.Path } -func (j *Module) Srcs() android.Paths { - return append(android.Paths{j.outputFile}, j.extraOutputFiles...) +func (j *Module) addHostProperties() { + j.AddProperties( + &j.properties, + &j.protoProperties, + ) } -func (j *Module) DexJarFile() android.Path { - return j.dexJarFile +func (j *Module) addHostAndDeviceProperties() { + j.addHostProperties() + j.AddProperties( + &j.deviceProperties, + &j.dexpreoptProperties, + &j.linter.properties, + ) } -var _ android.SourceFileProducer = (*Module)(nil) +func (j *Module) OutputFiles(tag string) (android.Paths, error) { + switch tag { + case "": + return append(android.Paths{j.outputFile}, j.extraOutputFiles...), nil + case ".jar": + return android.Paths{j.implementationAndResourcesJar}, nil + case ".proguard_map": + return android.Paths{j.proguardDictionary}, nil + default: + return nil, fmt.Errorf("unsupported module reference tag %q", tag) + } +} -type Dependency interface { +var _ android.OutputFileProducer = (*Module)(nil) + +// Methods that need to be implemented for a module that is added to apex java_libs property. +type ApexDependency interface { HeaderJars() android.Paths + ImplementationAndResourcesJars() android.Paths +} + +type Dependency interface { + ApexDependency ImplementationJars() android.Paths ResourceJars() android.Paths - ImplementationAndResourcesJars() android.Paths DexJar() android.Path AidlIncludeDirs() android.Paths ExportedSdkLibs() []string + ExportedPlugins() (android.Paths, []string) + SrcJarArgs() ([]string, android.Paths) + BaseModuleName() string + JacocoReportClassesFile() android.Path } -type SdkLibraryDependency interface { - SdkHeaderJars(ctx android.BaseContext, sdkVersion string) android.Paths - SdkImplementationJars(ctx android.BaseContext, sdkVersion string) android.Paths -} - -type SrcDependency interface { - CompiledSrcs() android.Paths - CompiledSrcJars() android.Paths +type xref interface { + XrefJavaFiles() android.Paths } -func (j *Module) CompiledSrcs() android.Paths { - return j.compiledJavaSrcs +func (j *Module) XrefJavaFiles() android.Paths { + return j.kytheFiles } -func (j *Module) CompiledSrcJars() android.Paths { - return j.compiledSrcJars -} - -var _ SrcDependency = (*Module)(nil) - func InitJavaModule(module android.DefaultableModule, hod android.HostOrDeviceSupported) { android.InitAndroidArchModule(module, hod, android.MultilibCommon) android.InitDefaultableModule(module) @@ -396,13 +562,19 @@ type dependencyTag struct { type jniDependencyTag struct { blueprint.BaseDependencyTag - target android.Target +} + +func IsJniDepTag(depTag blueprint.DependencyTag) bool { + _, ok := depTag.(*jniDependencyTag) + return ok } var ( staticLibTag = dependencyTag{name: "staticlib"} libTag = dependencyTag{name: "javalib"} + java9LibTag = dependencyTag{name: "java9lib"} pluginTag = dependencyTag{name: "plugin"} + exportedPluginTag = dependencyTag{name: "exported-plugin"} bootClasspathTag = dependencyTag{name: "bootclasspath"} systemModulesTag = dependencyTag{name: "system modules"} frameworkResTag = dependencyTag{name: "framework-res"} @@ -412,81 +584,126 @@ var ( proguardRaiseTag = dependencyTag{name: "proguard-raise"} certificateTag = dependencyTag{name: "certificate"} instrumentationForTag = dependencyTag{name: "instrumentation_for"} + usesLibTag = dependencyTag{name: "uses-library"} + extraLintCheckTag = dependencyTag{name: "extra-lint-check"} ) +func IsLibDepTag(depTag blueprint.DependencyTag) bool { + return depTag == libTag +} + +func IsStaticLibDepTag(depTag blueprint.DependencyTag) bool { + return depTag == staticLibTag +} + type sdkDep struct { useModule, useFiles, useDefaultLibs, invalidVersion bool - modules []string + // The modules that will be added to the bootclasspath when targeting 1.8 or lower + bootclasspath []string + + // The default system modules to use. Will be an empty string if no system + // modules are to be used. systemModules string + // The modules that will be added ot the classpath when targeting 1.9 or higher + java9Classpath []string + frameworkResModule string jars android.Paths aidl android.OptionalPath + + noStandardLibs, noFrameworksLibs bool +} + +func (s sdkDep) hasStandardLibs() bool { + return !s.noStandardLibs +} + +func (s sdkDep) hasFrameworkLibs() bool { + return !s.noStandardLibs && !s.noFrameworksLibs } type jniLib struct { - name string - path android.Path - target android.Target + name string + path android.Path + target android.Target + coverageFile android.OptionalPath + unstrippedFile android.Path } -func (j *Module) shouldInstrument(ctx android.BaseContext) bool { - return j.properties.Instrument && ctx.Config().IsEnvTrue("EMMA_INSTRUMENT") +func (j *Module) shouldInstrument(ctx android.BaseModuleContext) bool { + return j.properties.Instrument && + ctx.Config().IsEnvTrue("EMMA_INSTRUMENT") && + ctx.DeviceConfig().JavaCoverageEnabledForPath(ctx.ModuleDir()) } -func (j *Module) shouldInstrumentStatic(ctx android.BaseContext) bool { +func (j *Module) shouldInstrumentStatic(ctx android.BaseModuleContext) bool { return j.shouldInstrument(ctx) && (ctx.Config().IsEnvTrue("EMMA_INSTRUMENT_STATIC") || ctx.Config().UnbundledBuild()) } -func (j *Module) sdkVersion() string { - return String(j.deviceProperties.Sdk_version) +func (j *Module) sdkVersion() sdkSpec { + return sdkSpecFrom(String(j.deviceProperties.Sdk_version)) } -func (j *Module) minSdkVersion() string { +func (j *Module) systemModules() string { + return proptools.String(j.deviceProperties.System_modules) +} + +func (j *Module) minSdkVersion() sdkSpec { if j.deviceProperties.Min_sdk_version != nil { - return *j.deviceProperties.Min_sdk_version + return sdkSpecFrom(*j.deviceProperties.Min_sdk_version) } return j.sdkVersion() } -func (j *Module) targetSdkVersion() string { +func (j *Module) targetSdkVersion() sdkSpec { if j.deviceProperties.Target_sdk_version != nil { - return *j.deviceProperties.Target_sdk_version + return sdkSpecFrom(*j.deviceProperties.Target_sdk_version) } return j.sdkVersion() } +func (j *Module) MinSdkVersion() string { + return j.minSdkVersion().version.String() +} + +func (j *Module) AvailableFor(what string) bool { + if what == android.AvailableToPlatform && Bool(j.deviceProperties.Hostdex) { + // Exception: for hostdex: true libraries, the platform variant is created + // even if it's not marked as available to platform. In that case, the platform + // variant is used only for the hostdex and not installed to the device. + return true + } + return j.ApexModuleBase.AvailableFor(what) +} + func (j *Module) deps(ctx android.BottomUpMutatorContext) { if ctx.Device() { - if !Bool(j.properties.No_standard_libs) { - sdkDep := decodeSdkDep(ctx, sdkContext(j)) - if sdkDep.useDefaultLibs { - ctx.AddVariationDependencies(nil, bootClasspathTag, config.DefaultBootclasspathLibraries...) - ctx.AddVariationDependencies(nil, systemModulesTag, config.DefaultSystemModules) - if !Bool(j.properties.No_framework_libs) { - ctx.AddVariationDependencies(nil, libTag, config.DefaultLibraries...) - } - } else if sdkDep.useModule { - ctx.AddVariationDependencies(nil, systemModulesTag, sdkDep.systemModules) - ctx.AddVariationDependencies(nil, bootClasspathTag, sdkDep.modules...) - if j.deviceProperties.EffectiveOptimizeEnabled() { - ctx.AddVariationDependencies(nil, proguardRaiseTag, config.DefaultBootclasspathLibraries...) - ctx.AddVariationDependencies(nil, proguardRaiseTag, config.DefaultLibraries...) - } + j.linter.deps(ctx) + + sdkDep := decodeSdkDep(ctx, sdkContext(j)) + if sdkDep.useDefaultLibs { + ctx.AddVariationDependencies(nil, bootClasspathTag, config.DefaultBootclasspathLibraries...) + ctx.AddVariationDependencies(nil, systemModulesTag, config.DefaultSystemModules) + if sdkDep.hasFrameworkLibs() { + ctx.AddVariationDependencies(nil, libTag, config.DefaultLibraries...) + } + } else if sdkDep.useModule { + ctx.AddVariationDependencies(nil, bootClasspathTag, sdkDep.bootclasspath...) + ctx.AddVariationDependencies(nil, java9LibTag, sdkDep.java9Classpath...) + if j.deviceProperties.EffectiveOptimizeEnabled() && sdkDep.hasStandardLibs() { + ctx.AddVariationDependencies(nil, proguardRaiseTag, config.DefaultBootclasspathLibraries...) + ctx.AddVariationDependencies(nil, proguardRaiseTag, config.DefaultLibraries...) } - } else if j.deviceProperties.System_modules == nil { - ctx.PropertyErrorf("no_standard_libs", - "system_modules is required to be set when no_standard_libs is true, did you mean no_framework_libs?") - } else if *j.deviceProperties.System_modules != "none" { - ctx.AddVariationDependencies(nil, systemModulesTag, *j.deviceProperties.System_modules) } - if (ctx.ModuleName() == "framework") || (ctx.ModuleName() == "framework-annotation-proc") { - ctx.AddVariationDependencies(nil, frameworkResTag, "framework-res") + if sdkDep.systemModules != "" { + ctx.AddVariationDependencies(nil, systemModulesTag, sdkDep.systemModules) } + if ctx.ModuleName() == "android_stubs_current" || ctx.ModuleName() == "android_system_stubs_current" || ctx.ModuleName() == "android_test_stubs_current" { @@ -494,12 +711,36 @@ func (j *Module) deps(ctx android.BottomUpMutatorContext) { } } - ctx.AddVariationDependencies(nil, libTag, j.properties.Libs...) - ctx.AddVariationDependencies(nil, staticLibTag, j.properties.Static_libs...) + syspropPublicStubs := syspropPublicStubs(ctx.Config()) - ctx.AddFarVariationDependencies([]blueprint.Variation{ - {Mutator: "arch", Variation: ctx.Config().BuildOsCommonVariant}, - }, pluginTag, j.properties.Plugins...) + // rewriteSyspropLibs validates if a java module can link against platform's sysprop_library, + // and redirects dependency to public stub depending on the link type. + rewriteSyspropLibs := func(libs []string, prop string) []string { + // make a copy + ret := android.CopyOf(libs) + + for idx, lib := range libs { + stub, ok := syspropPublicStubs[lib] + + if !ok { + continue + } + + linkType, _ := j.getLinkType(ctx.ModuleName()) + // only platform modules can use internal props + if linkType != javaPlatform { + ret[idx] = stub + } + } + + return ret + } + + ctx.AddVariationDependencies(nil, libTag, rewriteSyspropLibs(j.properties.Libs, "libs")...) + ctx.AddVariationDependencies(nil, staticLibTag, rewriteSyspropLibs(j.properties.Static_libs, "static_libs")...) + + ctx.AddFarVariationDependencies(ctx.Config().BuildOSCommonTarget.Variations(), pluginTag, j.properties.Plugins...) + ctx.AddFarVariationDependencies(ctx.Config().BuildOSCommonTarget.Variations(), exportedPluginTag, j.properties.Exported_plugins...) android.ProtoDeps(ctx, &j.protoProperties) if j.hasSrcExt(".proto") { @@ -509,13 +750,21 @@ func (j *Module) deps(ctx android.BottomUpMutatorContext) { if j.hasSrcExt(".kt") { // TODO(ccross): move this to a mutator pass that can tell if generated sources contain // Kotlin files - ctx.AddVariationDependencies(nil, kotlinStdlibTag, "kotlin-stdlib") + ctx.AddVariationDependencies(nil, kotlinStdlibTag, + "kotlin-stdlib", "kotlin-stdlib-jdk7", "kotlin-stdlib-jdk8") if len(j.properties.Plugins) > 0 { ctx.AddVariationDependencies(nil, kotlinAnnotationsTag, "kotlin-annotations") } } - if j.shouldInstrumentStatic(ctx) { + // Framework libraries need special handling in static coverage builds: they should not have + // static dependency on jacoco, otherwise there would be multiple conflicting definitions of + // the same jacoco classes coming from different bootclasspath jars. + if inList(ctx.ModuleName(), config.InstrumentFrameworkModules) { + if ctx.Config().IsEnvTrue("EMMA_INSTRUMENT_FRAMEWORK") { + j.properties.Instrument = true + } + } else if j.shouldInstrumentStatic(ctx) { ctx.AddVariationDependencies(nil, staticLibTag, "jacocoagent") } } @@ -579,6 +828,7 @@ func (j *Module) aidlFlags(ctx android.ModuleContext, aidlPreprocess android.Opt type deps struct { classpath classpath + java9Classpath classpath bootClasspath classpath processorPath classpath processorClasses []string @@ -588,7 +838,7 @@ type deps struct { aidlIncludeDirs android.Paths srcs android.Paths srcJars android.Paths - systemModules android.Path + systemModules *systemModules aidlPreprocess android.OptionalPath kotlinStdlib android.Paths kotlinAnnotations android.Paths @@ -608,53 +858,74 @@ func checkProducesJars(ctx android.ModuleContext, dep android.SourceFileProducer type linkType int const ( + // TODO(jiyong) rename these for better readability. Make the allowed + // and disallowed link types explicit javaCore linkType = iota javaSdk javaSystem + javaModule + javaSystemServer javaPlatform ) -func getLinkType(m *Module, name string) (ret linkType, stubs bool) { - ver := m.sdkVersion() - switch { - case name == "core.current.stubs" || name == "core.platform.api.stubs" || - name == "stub-annotations" || name == "private-stub-annotations-jar" || - name == "core-lambda-stubs": +type linkTypeContext interface { + android.Module + getLinkType(name string) (ret linkType, stubs bool) +} + +func (m *Module) getLinkType(name string) (ret linkType, stubs bool) { + switch name { + case "core.current.stubs", "core.platform.api.stubs", "stub-annotations", + "private-stub-annotations-jar", "core-lambda-stubs", "core-generated-annotation-stubs": return javaCore, true - case ver == "core_current": - return javaCore, false - case name == "android_system_stubs_current": + case "android_stubs_current": + return javaSdk, true + case "android_system_stubs_current": return javaSystem, true - case strings.HasPrefix(ver, "system_"): - return javaSystem, false - case name == "android_test_stubs_current": + case "android_module_lib_stubs_current": + return javaModule, true + case "android_system_server_stubs_current": + return javaSystemServer, true + case "android_test_stubs_current": return javaSystem, true - case strings.HasPrefix(ver, "test_"): - return javaPlatform, false - case name == "android_stubs_current": - return javaSdk, true - case ver == "current": + } + + if stub, linkType := moduleStubLinkType(name); stub { + return linkType, true + } + + ver := m.sdkVersion() + switch ver.kind { + case sdkCore: + return javaCore, false + case sdkSystem: + return javaSystem, false + case sdkPublic: return javaSdk, false - case ver == "": + case sdkModule: + return javaModule, false + case sdkSystemServer: + return javaSystemServer, false + case sdkPrivate, sdkNone, sdkCorePlatform, sdkTest: return javaPlatform, false - default: - if _, err := strconv.Atoi(ver); err != nil { - panic(fmt.Errorf("expected sdk_version to be a number, got %q", ver)) - } - return javaSdk, false } + + if !ver.valid() { + panic(fmt.Errorf("sdk_version is invalid. got %q", ver.raw)) + } + return javaSdk, false } -func checkLinkType(ctx android.ModuleContext, from *Module, to *Library, tag dependencyTag) { +func checkLinkType(ctx android.ModuleContext, from *Module, to linkTypeContext, tag dependencyTag) { if ctx.Host() { return } - myLinkType, stubs := getLinkType(from, ctx.ModuleName()) + myLinkType, stubs := from.getLinkType(ctx.ModuleName()) if stubs { return } - otherLinkType, _ := getLinkType(&to.Module, ctx.OtherModuleName(to)) + otherLinkType, _ := to.getLinkType(ctx.OtherModuleName(to)) commonMessage := "Adjust sdk_version: property of the source or target module so that target module is built with the same or smaller API set than the source." switch myLinkType { @@ -671,11 +942,23 @@ func checkLinkType(ctx android.ModuleContext, from *Module, to *Library, tag dep } break case javaSystem: - if otherLinkType == javaPlatform { + if otherLinkType == javaPlatform || otherLinkType == javaModule || otherLinkType == javaSystemServer { ctx.ModuleErrorf("compiles against system API, but dependency %q is compiling against private API."+commonMessage, ctx.OtherModuleName(to)) } break + case javaModule: + if otherLinkType == javaPlatform || otherLinkType == javaSystemServer { + ctx.ModuleErrorf("compiles against module API, but dependency %q is compiling against private API."+commonMessage, + ctx.OtherModuleName(to)) + } + break + case javaSystemServer: + if otherLinkType == javaPlatform { + ctx.ModuleErrorf("compiles against system server API, but dependency %q is compiling against private API."+commonMessage, + ctx.OtherModuleName(to)) + } + break case javaPlatform: // no restriction on link-type break @@ -688,7 +971,8 @@ func (j *Module) collectDeps(ctx android.ModuleContext) deps { if ctx.Device() { sdkDep := decodeSdkDep(ctx, sdkContext(j)) if sdkDep.invalidVersion { - ctx.AddMissingDependencies(sdkDep.modules) + ctx.AddMissingDependencies(sdkDep.bootclasspath) + ctx.AddMissingDependencies(sdkDep.java9Classpath) } else if sdkDep.useFiles { // sdkDep.jar is actually equivalent to turbine header.jar. deps.classpath = append(deps.classpath, sdkDep.jars...) @@ -698,6 +982,12 @@ func (j *Module) collectDeps(ctx android.ModuleContext) deps { } } + // If this is a component library (stubs, etc.) for a java_sdk_library then + // add the name of that java_sdk_library to the exported sdk libs to make sure + // that, if necessary, a <uses-library> element for that java_sdk_library is + // added to the Android manifest. + j.exportedSdkLibs = append(j.exportedSdkLibs, j.OptionalImplicitSdkLibrary()...) + ctx.VisitDirectDeps(func(module android.Module) { otherName := ctx.OtherModuleName(module) tag := ctx.OtherModuleDependencyTag(module) @@ -711,20 +1001,14 @@ func (j *Module) collectDeps(ctx android.ModuleContext) deps { return } - if to, ok := module.(*Library); ok { - switch tag { - case bootClasspathTag, libTag, staticLibTag: - checkLinkType(ctx, j, to, tag.(dependencyTag)) - } - } switch dep := module.(type) { case SdkLibraryDependency: switch tag { case libTag: deps.classpath = append(deps.classpath, dep.SdkHeaderJars(ctx, j.sdkVersion())...) // names of sdk libs that are directly depended are exported - j.exportedSdkLibs = append(j.exportedSdkLibs, otherName) - default: + j.exportedSdkLibs = append(j.exportedSdkLibs, dep.OptionalImplicitSdkLibrary()...) + case staticLibTag: ctx.ModuleErrorf("dependency on java_sdk_library %q can only be in libs", otherName) } case Dependency: @@ -736,6 +1020,10 @@ func (j *Module) collectDeps(ctx android.ModuleContext) deps { // sdk lib names from dependencies are re-exported j.exportedSdkLibs = append(j.exportedSdkLibs, dep.ExportedSdkLibs()...) deps.aidlIncludeDirs = append(deps.aidlIncludeDirs, dep.AidlIncludeDirs()...) + pluginJars, pluginClasses := dep.ExportedPlugins() + addPlugins(&deps, pluginJars, pluginClasses...) + case java9LibTag: + deps.java9Classpath = append(deps.java9Classpath, dep.HeaderJars()...) case staticLibTag: deps.classpath = append(deps.classpath, dep.HeaderJars()...) deps.staticJars = append(deps.staticJars, dep.ImplementationJars()...) @@ -744,21 +1032,30 @@ func (j *Module) collectDeps(ctx android.ModuleContext) deps { // sdk lib names from dependencies are re-exported j.exportedSdkLibs = append(j.exportedSdkLibs, dep.ExportedSdkLibs()...) deps.aidlIncludeDirs = append(deps.aidlIncludeDirs, dep.AidlIncludeDirs()...) + pluginJars, pluginClasses := dep.ExportedPlugins() + addPlugins(&deps, pluginJars, pluginClasses...) case pluginTag: if plugin, ok := dep.(*Plugin); ok { - deps.processorPath = append(deps.processorPath, dep.ImplementationAndResourcesJars()...) if plugin.pluginProperties.Processor_class != nil { - deps.processorClasses = append(deps.processorClasses, *plugin.pluginProperties.Processor_class) + addPlugins(&deps, plugin.ImplementationAndResourcesJars(), *plugin.pluginProperties.Processor_class) + } else { + addPlugins(&deps, plugin.ImplementationAndResourcesJars()) } deps.disableTurbine = deps.disableTurbine || Bool(plugin.pluginProperties.Generates_api) } else { ctx.PropertyErrorf("plugins", "%q is not a java_plugin module", otherName) } - case frameworkResTag: - if (ctx.ModuleName() == "framework") || (ctx.ModuleName() == "framework-annotation-proc") { - // framework.jar has a one-off dependency on the R.java and Manifest.java files - // generated by framework-res.apk - deps.srcJars = append(deps.srcJars, dep.(*AndroidApp).aaptSrcJar) + case exportedPluginTag: + if plugin, ok := dep.(*Plugin); ok { + if plugin.pluginProperties.Generates_api != nil && *plugin.pluginProperties.Generates_api { + ctx.PropertyErrorf("exported_plugins", "Cannot export plugins with generates_api = true, found %v", otherName) + } + j.exportedPluginJars = append(j.exportedPluginJars, plugin.ImplementationAndResourcesJars()...) + if plugin.pluginProperties.Processor_class != nil { + j.exportedPluginClasses = append(j.exportedPluginClasses, *plugin.pluginProperties.Processor_class) + } + } else { + ctx.PropertyErrorf("exported_plugins", "%q is not a java_plugin module", otherName) } case frameworkApkTag: if ctx.ModuleName() == "android_stubs_current" || @@ -773,7 +1070,7 @@ func (j *Module) collectDeps(ctx android.ModuleContext) deps { deps.staticResourceJars = append(deps.staticResourceJars, dep.(*AndroidApp).exportPackage) } case kotlinStdlibTag: - deps.kotlinStdlib = dep.HeaderJars() + deps.kotlinStdlib = append(deps.kotlinStdlib, dep.HeaderJars()...) case kotlinAnnotationsTag: deps.kotlinAnnotations = dep.HeaderJars() } @@ -791,19 +1088,19 @@ func (j *Module) collectDeps(ctx android.ModuleContext) deps { } default: switch tag { - case android.DefaultsDepTag, android.SourceDepTag: - // Nothing to do + case bootClasspathTag: + // If a system modules dependency has been added to the bootclasspath + // then add its libs to the bootclasspath. + sm := module.(SystemModulesProvider) + deps.bootClasspath = append(deps.bootClasspath, sm.HeaderJars()...) + case systemModulesTag: if deps.systemModules != nil { panic("Found two system module dependencies") } - sm := module.(*SystemModules) - if sm.outputFile == nil { - panic("Missing directory for system module dependency") - } - deps.systemModules = sm.outputFile - default: - ctx.ModuleErrorf("depends on non-java module %q", otherName) + sm := module.(SystemModulesProvider) + outputDir, outputDeps := sm.OutputDirAndDeps() + deps.systemModules = &systemModules{outputDir, outputDeps} } } }) @@ -813,37 +1110,68 @@ func (j *Module) collectDeps(ctx android.ModuleContext) deps { return deps } -func getJavaVersion(ctx android.ModuleContext, javaVersion string, sdkContext sdkContext) string { - var ret string - v := sdkContext.sdkVersion() - // For PDK builds, use the latest SDK version instead of "current" - if ctx.Config().IsPdkBuild() && (v == "" || v == "current") { - sdkVersions := ctx.Config().Get(sdkVersionsKey).([]int) - latestSdkVersion := 0 - if len(sdkVersions) > 0 { - latestSdkVersion = sdkVersions[len(sdkVersions)-1] - } - v = strconv.Itoa(latestSdkVersion) - } +func addPlugins(deps *deps, pluginJars android.Paths, pluginClasses ...string) { + deps.processorPath = append(deps.processorPath, pluginJars...) + deps.processorClasses = append(deps.processorClasses, pluginClasses...) +} - sdk, err := sdkVersionToNumber(ctx, v) - if err != nil { - ctx.PropertyErrorf("sdk_version", "%s", err) - } +func getJavaVersion(ctx android.ModuleContext, javaVersion string, sdkContext sdkContext) javaVersion { if javaVersion != "" { - ret = javaVersion - } else if ctx.Device() && sdk <= 23 { - ret = "1.7" - } else if ctx.Device() && sdk <= 28 || !ctx.Config().TargetOpenJDK9() { - ret = "1.8" - } else if ctx.Device() && sdkContext.sdkVersion() != "" && sdk == android.FutureApiLevel { - // TODO(ccross): once we generate stubs we should be able to use 1.9 for sdk_version: "current" - ret = "1.8" + return normalizeJavaVersion(ctx, javaVersion) + } else if ctx.Device() { + return sdkContext.sdkVersion().defaultJavaLanguageVersion(ctx) } else { - ret = "1.9" + return JAVA_VERSION_9 } +} + +type javaVersion int + +const ( + JAVA_VERSION_UNSUPPORTED = 0 + JAVA_VERSION_6 = 6 + JAVA_VERSION_7 = 7 + JAVA_VERSION_8 = 8 + JAVA_VERSION_9 = 9 +) - return ret +func (v javaVersion) String() string { + switch v { + case JAVA_VERSION_6: + return "1.6" + case JAVA_VERSION_7: + return "1.7" + case JAVA_VERSION_8: + return "1.8" + case JAVA_VERSION_9: + return "1.9" + default: + return "unsupported" + } +} + +// Returns true if javac targeting this version uses system modules instead of a bootclasspath. +func (v javaVersion) usesJavaModules() bool { + return v >= 9 +} + +func normalizeJavaVersion(ctx android.BaseModuleContext, javaVersion string) javaVersion { + switch javaVersion { + case "1.6", "6": + return JAVA_VERSION_6 + case "1.7", "7": + return JAVA_VERSION_7 + case "1.8", "8": + return JAVA_VERSION_8 + case "1.9", "9": + return JAVA_VERSION_9 + case "10", "11": + ctx.PropertyErrorf("java_version", "Java language levels above 9 are not supported") + return JAVA_VERSION_UNSUPPORTED + default: + ctx.PropertyErrorf("java_version", "Unrecognized Java language level") + return JAVA_VERSION_UNSUPPORTED + } } func (j *Module) collectBuilderFlags(ctx android.ModuleContext, deps deps) javaBuilderFlags { @@ -855,7 +1183,7 @@ func (j *Module) collectBuilderFlags(ctx android.ModuleContext, deps deps) javaB // javac flags. javacFlags := j.properties.Javacflags - if flags.javaVersion == "1.9" { + if flags.javaVersion.usesJavaModules() { javacFlags = append(javacFlags, j.properties.Openjdk9.Javacflags...) } if ctx.Config().MinimizeJavaDebugInfo() { @@ -863,6 +1191,7 @@ func (j *Module) collectBuilderFlags(ctx android.ModuleContext, deps deps) javaB // disk and memory usage. javacFlags = append(javacFlags, "-g:source,lines") } + javacFlags = append(javacFlags, "-Xlint:-dep-ann") if ctx.Config().RunErrorProne() { if config.ErrorProneClasspath == nil { @@ -883,13 +1212,14 @@ func (j *Module) collectBuilderFlags(ctx android.ModuleContext, deps deps) javaB // classpath flags.bootClasspath = append(flags.bootClasspath, deps.bootClasspath...) flags.classpath = append(flags.classpath, deps.classpath...) + flags.java9Classpath = append(flags.java9Classpath, deps.java9Classpath...) flags.processorPath = append(flags.processorPath, deps.processorPath...) - flags.processor = strings.Join(deps.processorClasses, ",") + flags.processors = append(flags.processors, deps.processorClasses...) + flags.processors = android.FirstUniqueStrings(flags.processors) - if len(flags.bootClasspath) == 0 && ctx.Host() && flags.javaVersion != "1.9" && - !Bool(j.properties.No_standard_libs) && - inList(flags.javaVersion, []string{"1.6", "1.7", "1.8"}) { + if len(flags.bootClasspath) == 0 && ctx.Host() && !flags.javaVersion.usesJavaModules() && + decodeSdkDep(ctx, sdkContext(j)).hasStandardLibs() { // Give host-side tools a version of OpenJDK's standard libraries // close to what they're targeting. As of Dec 2017, AOSP is only // bundling OpenJDK 8 and 9, so nothing < 8 is available. @@ -913,7 +1243,7 @@ func (j *Module) collectBuilderFlags(ctx android.ModuleContext, deps deps) javaB } } - if j.properties.Patch_module != nil && flags.javaVersion == "1.9" { + if j.properties.Patch_module != nil && flags.javaVersion.usesJavaModules() { // Manually specify build directory in case it is not under the repo root. // (javac doesn't seem to expand into symbolc links when searching for patch-module targets, so // just adding a symlink under the root doesn't help.) @@ -926,9 +1256,7 @@ func (j *Module) collectBuilderFlags(ctx android.ModuleContext, deps deps) javaB } // systemModules - if deps.systemModules != nil { - flags.systemModules = append(flags.systemModules, deps.systemModules) - } + flags.systemModules = deps.systemModules // aidl flags. flags.aidlFlags, flags.aidlDeps = j.aidlFlags(ctx, deps.aidlPreprocess, deps.aidlIncludeDirs) @@ -942,14 +1270,13 @@ func (j *Module) collectBuilderFlags(ctx android.ModuleContext, deps deps) javaB return flags } -func (j *Module) compile(ctx android.ModuleContext, extraSrcJars ...android.Path) { - +func (j *Module) compile(ctx android.ModuleContext, aaptSrcJar android.Path) { j.exportAidlIncludeDirs = android.PathsForModuleSrc(ctx, j.deviceProperties.Aidl.Export_include_dirs) deps := j.collectDeps(ctx) flags := j.collectBuilderFlags(ctx, deps) - if flags.javaVersion == "1.9" { + if flags.javaVersion.usesJavaModules() { j.properties.Srcs = append(j.properties.Srcs, j.properties.Openjdk9.Srcs...) } srcFiles := android.PathsForModuleSrcExcludes(ctx, j.properties.Srcs, j.properties.Exclude_srcs) @@ -961,11 +1288,9 @@ func (j *Module) compile(ctx android.ModuleContext, extraSrcJars ...android.Path srcJars := srcFiles.FilterByExt(".srcjar") srcJars = append(srcJars, deps.srcJars...) - srcJars = append(srcJars, extraSrcJars...) - - // Collect source files from compiledJavaSrcs, compiledSrcJars and filter out Exclude_srcs - // that IDEInfo struct will use - j.expandIDEInfoCompiledSrcs = append(j.expandIDEInfoCompiledSrcs, srcFiles.Strings()...) + if aaptSrcJar != nil { + srcJars = append(srcJars, aaptSrcJar) + } if j.properties.Jarjar_rules != nil { j.expandJarjarRules = android.PathForModuleSrc(ctx, *j.properties.Jarjar_rules) @@ -983,6 +1308,9 @@ func (j *Module) compile(ctx android.ModuleContext, extraSrcJars ...android.Path } } + // Collect .java files for AIDEGen + j.expandIDEInfoCompiledSrcs = append(j.expandIDEInfoCompiledSrcs, uniqueSrcFiles.Strings()...) + var kotlinJars android.Paths if srcFiles.HasExt(".kt") { @@ -1007,6 +1335,9 @@ func (j *Module) compile(ctx android.ModuleContext, extraSrcJars ...android.Path kotlinSrcFiles = append(kotlinSrcFiles, uniqueSrcFiles...) kotlinSrcFiles = append(kotlinSrcFiles, srcFiles.FilterByExt(".kt")...) + // Collect .kt files for AIDEGen + j.expandIDEInfoCompiledSrcs = append(j.expandIDEInfoCompiledSrcs, srcFiles.FilterByExt(".kt").Strings()...) + flags.classpath = append(flags.classpath, deps.kotlinStdlib...) flags.classpath = append(flags.classpath, deps.kotlinAnnotations...) @@ -1016,11 +1347,13 @@ func (j *Module) compile(ctx android.ModuleContext, extraSrcJars ...android.Path if len(flags.processorPath) > 0 { // Use kapt for annotation processing kaptSrcJar := android.PathForModuleOut(ctx, "kapt", "kapt-sources.jar") - kotlinKapt(ctx, kaptSrcJar, kotlinSrcFiles, srcJars, flags) + kaptResJar := android.PathForModuleOut(ctx, "kapt", "kapt-res.jar") + kotlinKapt(ctx, kaptSrcJar, kaptResJar, kotlinSrcFiles, srcJars, flags) srcJars = append(srcJars, kaptSrcJar) + kotlinJars = append(kotlinJars, kaptResJar) // Disable annotation processing in javac, it's already been handled by kapt flags.processorPath = nil - flags.processor = "" + flags.processors = nil } kotlinJar := android.PathForModuleOut(ctx, "kotlin", jarName) @@ -1032,9 +1365,11 @@ func (j *Module) compile(ctx android.ModuleContext, extraSrcJars ...android.Path // Make javac rule depend on the kotlinc rule flags.classpath = append(flags.classpath, kotlinJar) - // Jar kotlin classes into the final jar after javac kotlinJars = append(kotlinJars, kotlinJar) - kotlinJars = append(kotlinJars, deps.kotlinStdlib...) + // Jar kotlin classes into the final jar after javac + if BoolDefault(j.properties.Static_kotlin_stdlib, true) { + kotlinJars = append(kotlinJars, deps.kotlinStdlib...) + } } jars := append(android.Paths(nil), kotlinJars...) @@ -1044,6 +1379,7 @@ func (j *Module) compile(ctx android.ModuleContext, extraSrcJars ...android.Path j.compiledSrcJars = srcJars enable_sharding := false + var headerJarFileWithoutJarjar android.Path if ctx.Device() && !ctx.Config().IsEnvFalse("TURBINE_ENABLED") && !deps.disableTurbine { if j.properties.Javac_shard_size != nil && *(j.properties.Javac_shard_size) > 0 { enable_sharding = true @@ -1053,7 +1389,8 @@ func (j *Module) compile(ctx android.ModuleContext, extraSrcJars ...android.Path // allow for the use of annotation processors that do function correctly // with sharding enabled. See: b/77284273. } - j.headerJarFile = j.compileJavaHeader(ctx, uniqueSrcFiles, srcJars, deps, flags, jarName, kotlinJars) + headerJarFileWithoutJarjar, j.headerJarFile = + j.compileJavaHeader(ctx, uniqueSrcFiles, srcJars, deps, flags, jarName, kotlinJars) if ctx.Failed() { return } @@ -1072,25 +1409,24 @@ func (j *Module) compile(ctx android.ModuleContext, extraSrcJars ...android.Path } if enable_sharding { - flags.classpath = append(flags.classpath, j.headerJarFile) + flags.classpath = append(flags.classpath, headerJarFileWithoutJarjar) shardSize := int(*(j.properties.Javac_shard_size)) var shardSrcs []android.Paths if len(uniqueSrcFiles) > 0 { shardSrcs = android.ShardPaths(uniqueSrcFiles, shardSize) for idx, shardSrc := range shardSrcs { - classes := android.PathForModuleOut(ctx, "javac", jarName+strconv.Itoa(idx)) - TransformJavaToClasses(ctx, classes, idx, shardSrc, nil, flags, extraJarDeps) + classes := j.compileJavaClasses(ctx, jarName, idx, shardSrc, + nil, flags, extraJarDeps) jars = append(jars, classes) } } if len(srcJars) > 0 { - classes := android.PathForModuleOut(ctx, "javac", jarName+strconv.Itoa(len(shardSrcs))) - TransformJavaToClasses(ctx, classes, len(shardSrcs), nil, srcJars, flags, extraJarDeps) + classes := j.compileJavaClasses(ctx, jarName, len(shardSrcs), + nil, srcJars, flags, extraJarDeps) jars = append(jars, classes) } } else { - classes := android.PathForModuleOut(ctx, "javac", jarName) - TransformJavaToClasses(ctx, classes, -1, uniqueSrcFiles, srcJars, flags, extraJarDeps) + classes := j.compileJavaClasses(ctx, jarName, -1, uniqueSrcFiles, srcJars, flags, extraJarDeps) jars = append(jars, classes) } if ctx.Failed() { @@ -1098,9 +1434,18 @@ func (j *Module) compile(ctx android.ModuleContext, extraSrcJars ...android.Path } } + j.srcJarArgs, j.srcJarDeps = resourcePathsToJarArgs(srcFiles), srcFiles + + var includeSrcJar android.WritablePath + if Bool(j.properties.Include_srcs) { + includeSrcJar = android.PathForModuleOut(ctx, ctx.ModuleName()+".srcjar") + TransformResourcesToJar(ctx, includeSrcJar, j.srcJarArgs, j.srcJarDeps) + } + dirArgs, dirDeps := ResourceDirsToJarArgs(ctx, j.properties.Java_resource_dirs, j.properties.Exclude_java_resource_dirs, j.properties.Exclude_java_resources) fileArgs, fileDeps := ResourceFilesToJarArgs(ctx, j.properties.Java_resources, j.properties.Exclude_java_resources) + extraArgs, extraDeps := resourcePathsToJarArgs(j.extraResources), j.extraResources var resArgs []string var resDeps android.Paths @@ -1111,11 +1456,8 @@ func (j *Module) compile(ctx android.ModuleContext, extraSrcJars ...android.Path resArgs = append(resArgs, fileArgs...) resDeps = append(resDeps, fileDeps...) - if Bool(j.properties.Include_srcs) { - srcArgs, srcDeps := SourceFilesToJarArgs(ctx, j.properties.Srcs, j.properties.Exclude_srcs) - resArgs = append(resArgs, srcArgs...) - resDeps = append(resDeps, srcDeps...) - } + resArgs = append(resArgs, extraArgs...) + resDeps = append(resDeps, extraDeps...) if len(resArgs) > 0 { resourceJar := android.PathForModuleOut(ctx, "res", jarName) @@ -1126,20 +1468,27 @@ func (j *Module) compile(ctx android.ModuleContext, extraSrcJars ...android.Path } } - if len(deps.staticResourceJars) > 0 { - var jars android.Paths - if j.resourceJar != nil { - jars = append(jars, j.resourceJar) - } - jars = append(jars, deps.staticResourceJars...) + var resourceJars android.Paths + if j.resourceJar != nil { + resourceJars = append(resourceJars, j.resourceJar) + } + if Bool(j.properties.Include_srcs) { + resourceJars = append(resourceJars, includeSrcJar) + } + resourceJars = append(resourceJars, deps.staticResourceJars...) + if len(resourceJars) > 1 { combinedJar := android.PathForModuleOut(ctx, "res-combined", jarName) - TransformJarsToJar(ctx, combinedJar, "for resources", jars, android.OptionalPath{}, + TransformJarsToJar(ctx, combinedJar, "for resources", resourceJars, android.OptionalPath{}, false, nil, nil) j.resourceJar = combinedJar + } else if len(resourceJars) == 1 { + j.resourceJar = resourceJars[0] } - jars = append(jars, deps.staticJars...) + if len(deps.staticJars) > 0 { + jars = append(jars, deps.staticJars...) + } manifest := j.overrideManifest if !manifest.Valid() && j.properties.Manifest != nil { @@ -1154,13 +1503,19 @@ func (j *Module) compile(ctx android.ModuleContext, extraSrcJars ...android.Path serviceFile := file.String() zipargs = append(zipargs, "-C", filepath.Dir(serviceFile), "-f", serviceFile) } + rule := zip + args := map[string]string{ + "jarArgs": "-P META-INF/services/ " + strings.Join(proptools.NinjaAndShellEscapeList(zipargs), " "), + } + if ctx.Config().IsEnvTrue("RBE_ZIP") { + rule = zipRE + args["implicits"] = strings.Join(services.Strings(), ",") + } ctx.Build(pctx, android.BuildParams{ - Rule: zip, + Rule: rule, Output: servicesJar, Implicits: services, - Args: map[string]string{ - "jarArgs": "-P META-INF/services/ " + strings.Join(proptools.NinjaAndShellEscapeList(zipargs), " "), - }, + Args: args, }) jars = append(jars, servicesJar) } @@ -1228,10 +1583,12 @@ func (j *Module) compile(ctx android.ModuleContext, extraSrcJars ...android.Path j.headerJarFile = j.implementationJarFile } - if ctx.Config().IsEnvTrue("EMMA_INSTRUMENT_FRAMEWORK") { - if inList(ctx.ModuleName(), config.InstrumentFrameworkModules) { - j.properties.Instrument = true - } + // Force enable the instrumentation for java code that is built for APEXes ... + // except for the jacocoagent itself (because instrumenting jacocoagent using jacocoagent + // doesn't make sense) + isJacocoAgent := ctx.ModuleName() == "jacocoagent" + if android.DirectlyInAnyApex(ctx, ctx.ModuleName()) && !isJacocoAgent && !j.IsForPlatform() { + j.properties.Instrument = true } if j.shouldInstrument(ctx) { @@ -1241,16 +1598,27 @@ func (j *Module) compile(ctx android.ModuleContext, extraSrcJars ...android.Path // merge implementation jar with resources if necessary implementationAndResourcesJar := outputFile if j.resourceJar != nil { - jars := android.Paths{implementationAndResourcesJar, j.resourceJar} + jars := android.Paths{j.resourceJar, implementationAndResourcesJar} combinedJar := android.PathForModuleOut(ctx, "withres", jarName) - TransformJarsToJar(ctx, combinedJar, "for resources", jars, android.OptionalPath{}, + TransformJarsToJar(ctx, combinedJar, "for resources", jars, manifest, false, nil, nil) implementationAndResourcesJar = combinedJar } j.implementationAndResourcesJar = implementationAndResourcesJar - if ctx.Device() && (Bool(j.properties.Installable) || Bool(j.deviceProperties.Compile_dex)) { + // Enable dex compilation for the APEX variants, unless it is disabled explicitly + if android.DirectlyInAnyApex(ctx, ctx.ModuleName()) && !j.IsForPlatform() { + if j.deviceProperties.Compile_dex == nil { + j.deviceProperties.Compile_dex = proptools.BoolPtr(true) + } + if j.deviceProperties.Hostdex == nil { + j.deviceProperties.Hostdex = proptools.BoolPtr(true) + } + } + + if ctx.Device() && j.hasCode(ctx) && + (Bool(j.properties.Installable) || Bool(j.deviceProperties.Compile_dex)) { // Dex compilation var dexOutputFile android.ModuleOutPath dexOutputFile = j.compileDex(ctx, flags, outputFile, jarName) @@ -1258,9 +1626,12 @@ func (j *Module) compile(ctx android.ModuleContext, extraSrcJars ...android.Path return } + configurationName := j.ConfigurationName() + primary := configurationName == ctx.ModuleName() + // Hidden API CSV generation and dex encoding - dexOutputFile = j.hiddenAPI.hiddenAPI(ctx, dexOutputFile, j.implementationJarFile, - j.deviceProperties.UncompressDex) + dexOutputFile = j.hiddenAPI.hiddenAPI(ctx, configurationName, primary, dexOutputFile, j.implementationJarFile, + proptools.Bool(j.deviceProperties.Uncompress_dex)) // merge dex jar with resources if necessary if j.resourceJar != nil { @@ -1268,7 +1639,7 @@ func (j *Module) compile(ctx android.ModuleContext, extraSrcJars ...android.Path combinedJar := android.PathForModuleOut(ctx, "dex-withres", jarName) TransformJarsToJar(ctx, combinedJar, "for dex resources", jars, android.OptionalPath{}, false, nil, nil) - if j.deviceProperties.UncompressDex { + if *j.deviceProperties.Uncompress_dex { combinedAlignedJar := android.PathForModuleOut(ctx, "dex-withres-aligned", jarName) TransformZipAlign(ctx, combinedAlignedJar, combinedJar) dexOutputFile = combinedAlignedJar @@ -1293,12 +1664,58 @@ func (j *Module) compile(ctx android.ModuleContext, extraSrcJars ...android.Path outputFile = implementationAndResourcesJar } + if ctx.Device() { + lintSDKVersionString := func(sdkSpec sdkSpec) string { + if v := sdkSpec.version; v.isNumbered() { + return v.String() + } else { + return ctx.Config().DefaultAppTargetSdk() + } + } + + j.linter.name = ctx.ModuleName() + j.linter.srcs = srcFiles + j.linter.srcJars = srcJars + j.linter.classpath = append(append(android.Paths(nil), flags.bootClasspath...), flags.classpath...) + j.linter.classes = j.implementationJarFile + j.linter.minSdkVersion = lintSDKVersionString(j.minSdkVersion()) + j.linter.targetSdkVersion = lintSDKVersionString(j.targetSdkVersion()) + j.linter.compileSdkVersion = lintSDKVersionString(j.sdkVersion()) + j.linter.javaLanguageLevel = flags.javaVersion.String() + j.linter.kotlinLanguageLevel = "1.3" + if j.ApexName() != "" && ctx.Config().UnbundledBuild() { + j.linter.buildModuleReportZip = true + } + j.linter.lint(ctx) + } + ctx.CheckbuildFile(outputFile) // Save the output file with no relative path so that it doesn't end up in a subdirectory when used as a resource j.outputFile = outputFile.WithoutRel() } +func (j *Module) compileJavaClasses(ctx android.ModuleContext, jarName string, idx int, + srcFiles, srcJars android.Paths, flags javaBuilderFlags, extraJarDeps android.Paths) android.WritablePath { + + kzipName := pathtools.ReplaceExtension(jarName, "kzip") + if idx >= 0 { + kzipName = strings.TrimSuffix(jarName, filepath.Ext(jarName)) + strconv.Itoa(idx) + ".kzip" + jarName += strconv.Itoa(idx) + } + + classes := android.PathForModuleOut(ctx, "javac", jarName) + TransformJavaToClasses(ctx, classes, idx, srcFiles, srcJars, flags, extraJarDeps) + + if ctx.Config().EmitXrefRules() { + extractionFile := android.PathForModuleOut(ctx, kzipName) + emitXrefRule(ctx, extractionFile, idx, srcFiles, srcJars, flags, extraJarDeps) + j.kytheFiles = append(j.kytheFiles, extractionFile) + } + + return classes +} + // Check for invalid kotlinc flags. Only use this for flags explicitly passed by the user, // since some of these flags may be used internally. func CheckKotlincFlags(ctx android.ModuleContext, flags []string) { @@ -1325,7 +1742,8 @@ func CheckKotlincFlags(ctx android.ModuleContext, flags []string) { } func (j *Module) compileJavaHeader(ctx android.ModuleContext, srcFiles, srcJars android.Paths, - deps deps, flags javaBuilderFlags, jarName string, extraJars android.Paths) android.Path { + deps deps, flags javaBuilderFlags, jarName string, + extraJars android.Paths) (headerJar, jarjarHeaderJar android.Path) { var jars android.Paths if len(srcFiles) > 0 || len(srcJars) > 0 { @@ -1333,7 +1751,7 @@ func (j *Module) compileJavaHeader(ctx android.ModuleContext, srcFiles, srcJars turbineJar := android.PathForModuleOut(ctx, "turbine", jarName) TransformJavaToHeaderClasses(ctx, turbineJar, srcFiles, srcJars, flags) if ctx.Failed() { - return nil + return nil, nil } jars = append(jars, turbineJar) } @@ -1342,27 +1760,27 @@ func (j *Module) compileJavaHeader(ctx android.ModuleContext, srcFiles, srcJars // Combine any static header libraries into classes-header.jar. If there is only // one input jar this step will be skipped. - var headerJar android.Path jars = append(jars, deps.staticHeaderJars...) // we cannot skip the combine step for now if there is only one jar // since we have to strip META-INF/TRANSITIVE dir from turbine.jar combinedJar := android.PathForModuleOut(ctx, "turbine-combined", jarName) TransformJarsToJar(ctx, combinedJar, "for turbine", jars, android.OptionalPath{}, - false, nil, []string{"META-INF"}) + false, nil, []string{"META-INF/TRANSITIVE"}) headerJar = combinedJar + jarjarHeaderJar = combinedJar if j.expandJarjarRules != nil { // Transform classes.jar into classes-jarjar.jar jarjarFile := android.PathForModuleOut(ctx, "turbine-jarjar", jarName) TransformJarJar(ctx, jarjarFile, headerJar, j.expandJarjarRules) - headerJar = jarjarFile + jarjarHeaderJar = jarjarFile if ctx.Failed() { - return nil + return nil, nil } } - return headerJar + return headerJar, jarjarHeaderJar } func (j *Module) instrument(ctx android.ModuleContext, flags javaBuilderFlags, @@ -1424,6 +1842,14 @@ func (j *Module) ExportedSdkLibs() []string { return j.exportedSdkLibs } +func (j *Module) ExportedPlugins() (android.Paths, []string) { + return j.exportedPluginJars, j.exportedPluginClasses +} + +func (j *Module) SrcJarArgs() ([]string, android.Paths) { + return j.srcJarArgs, j.srcJarDeps +} + var _ logtagsProducer = (*Module)(nil) func (j *Module) logtags() android.Paths { @@ -1434,6 +1860,7 @@ func (j *Module) logtags() android.Paths { func (j *Module) IDEInfo(dpInfo *android.IdeInfo) { dpInfo.Deps = append(dpInfo.Deps, j.CompilerDeps()...) dpInfo.Srcs = append(dpInfo.Srcs, j.expandIDEInfoCompiledSrcs...) + dpInfo.SrcJars = append(dpInfo.SrcJars, j.compiledSrcJars.Strings()...) dpInfo.Aidl_include_dirs = append(dpInfo.Aidl_include_dirs, j.deviceProperties.Aidl.Include_dirs...) if j.expandJarjarRules != nil { dpInfo.Jarjar_rules = append(dpInfo.Jarjar_rules, j.expandJarjarRules.String()) @@ -1447,15 +1874,67 @@ func (j *Module) CompilerDeps() []string { return jdeps } +func (j *Module) hasCode(ctx android.ModuleContext) bool { + srcFiles := android.PathsForModuleSrcExcludes(ctx, j.properties.Srcs, j.properties.Exclude_srcs) + return len(srcFiles) > 0 || len(ctx.GetDirectDepsWithTag(staticLibTag)) > 0 +} + +func (j *Module) DepIsInSameApex(ctx android.BaseModuleContext, dep android.Module) bool { + return j.depIsInSameApex(ctx, dep) +} + +func (j *Module) Stem() string { + return proptools.StringDefault(j.deviceProperties.Stem, j.Name()) +} + +func (j *Module) ConfigurationName() string { + return proptools.StringDefault(j.deviceProperties.ConfigurationName, j.BaseModuleName()) +} + +func (j *Module) JacocoReportClassesFile() android.Path { + return j.jacocoReportClassesFile +} + +func (j *Module) IsInstallable() bool { + return Bool(j.properties.Installable) +} + // // Java libraries (.jar file) // +type LibraryProperties struct { + Dist struct { + // The tag of the output of this module that should be output. + Tag *string `android:"arch_variant"` + } `android:"arch_variant"` +} + type Library struct { Module + + libraryProperties LibraryProperties + + InstallMixin func(ctx android.ModuleContext, installPath android.Path) (extraInstallDeps android.Paths) +} + +// Provides access to the list of permitted packages from updatable boot jars. +type PermittedPackagesForUpdatableBootJars interface { + PermittedPackagesForUpdatableBootJars() []string +} + +var _ PermittedPackagesForUpdatableBootJars = (*Library)(nil) + +func (j *Library) PermittedPackagesForUpdatableBootJars() []string { + return j.properties.Permitted_packages } func shouldUncompressDex(ctx android.ModuleContext, dexpreopter *dexpreopter) bool { + // Store uncompressed (and aligned) any dex files from jars in APEXes. + if am, ok := ctx.Module().(android.ApexModule); ok && !am.IsForPlatform() { + return true + } + // Store uncompressed (and do not strip) dex files from boot class path jars. if inList(ctx.ModuleName(), ctx.Config().BootJars()) { return true @@ -1474,16 +1953,33 @@ func shouldUncompressDex(ctx android.ModuleContext, dexpreopter *dexpreopter) bo } func (j *Library) GenerateAndroidBuildActions(ctx android.ModuleContext) { - j.dexpreopter.installPath = android.PathForModuleInstall(ctx, "framework", ctx.ModuleName()+".jar") + j.checkSdkVersions(ctx) + j.dexpreopter.installPath = android.PathForModuleInstall(ctx, "framework", j.Stem()+".jar") j.dexpreopter.isSDKLibrary = j.deviceProperties.IsSDKLibrary - j.dexpreopter.isInstallable = Bool(j.properties.Installable) - j.dexpreopter.uncompressedDex = shouldUncompressDex(ctx, &j.dexpreopter) - j.deviceProperties.UncompressDex = j.dexpreopter.uncompressedDex - j.compile(ctx) - - if (Bool(j.properties.Installable) || ctx.Host()) && !android.DirectlyInAnyApex(ctx, ctx.ModuleName()) { + if j.deviceProperties.Uncompress_dex == nil { + // If the value was not force-set by the user, use reasonable default based on the module. + j.deviceProperties.Uncompress_dex = proptools.BoolPtr(shouldUncompressDex(ctx, &j.dexpreopter)) + } + j.dexpreopter.uncompressedDex = *j.deviceProperties.Uncompress_dex + j.compile(ctx, nil) + + exclusivelyForApex := android.InAnyApex(ctx.ModuleName()) && !j.IsForPlatform() + if (Bool(j.properties.Installable) || ctx.Host()) && !exclusivelyForApex { + var extraInstallDeps android.Paths + if j.InstallMixin != nil { + extraInstallDeps = j.InstallMixin(ctx, j.outputFile) + } j.installFile = ctx.InstallFile(android.PathForModuleInstall(ctx, "framework"), - ctx.ModuleName()+".jar", j.outputFile) + j.Stem()+".jar", j.outputFile, extraInstallDeps...) + } + + // Verify Dist.Tag is set to a supported output + if j.libraryProperties.Dist.Tag != nil { + distFiles, err := j.OutputFiles(*j.libraryProperties.Dist.Tag) + if err != nil { + ctx.PropertyErrorf("dist.tag", "%s", err.Error()) + } + j.distFile = distFiles[0] } } @@ -1491,6 +1987,102 @@ func (j *Library) DepsMutator(ctx android.BottomUpMutatorContext) { j.deps(ctx) } +const ( + aidlIncludeDir = "aidl" + javaDir = "java" + jarFileSuffix = ".jar" + testConfigSuffix = "-AndroidTest.xml" +) + +// path to the jar file of a java library. Relative to <sdk_root>/<api_dir> +func sdkSnapshotFilePathForJar(osPrefix, name string) string { + return sdkSnapshotFilePathForMember(osPrefix, name, jarFileSuffix) +} + +func sdkSnapshotFilePathForMember(osPrefix, name string, suffix string) string { + return filepath.Join(javaDir, osPrefix, name+suffix) +} + +type librarySdkMemberType struct { + android.SdkMemberTypeBase + + // Function to retrieve the appropriate output jar (implementation or header) from + // the library. + jarToExportGetter func(j *Library) android.Path +} + +func (mt *librarySdkMemberType) AddDependencies(mctx android.BottomUpMutatorContext, dependencyTag blueprint.DependencyTag, names []string) { + mctx.AddVariationDependencies(nil, dependencyTag, names...) +} + +func (mt *librarySdkMemberType) IsInstance(module android.Module) bool { + _, ok := module.(*Library) + return ok +} + +func (mt *librarySdkMemberType) AddPrebuiltModule(ctx android.SdkMemberContext, member android.SdkMember) android.BpModule { + return ctx.SnapshotBuilder().AddPrebuiltModule(member, "java_import") +} + +func (mt *librarySdkMemberType) CreateVariantPropertiesStruct() android.SdkMemberProperties { + return &librarySdkMemberProperties{} +} + +type librarySdkMemberProperties struct { + android.SdkMemberPropertiesBase + + JarToExport android.Path `android:"arch_variant"` + AidlIncludeDirs android.Paths +} + +func (p *librarySdkMemberProperties) PopulateFromVariant(ctx android.SdkMemberContext, variant android.Module) { + j := variant.(*Library) + + p.JarToExport = ctx.MemberType().(*librarySdkMemberType).jarToExportGetter(j) + p.AidlIncludeDirs = j.AidlIncludeDirs() +} + +func (p *librarySdkMemberProperties) AddToPropertySet(ctx android.SdkMemberContext, propertySet android.BpPropertySet) { + builder := ctx.SnapshotBuilder() + + exportedJar := p.JarToExport + if exportedJar != nil { + snapshotRelativeJavaLibPath := sdkSnapshotFilePathForJar(p.OsPrefix(), ctx.Name()) + builder.CopyToSnapshot(exportedJar, snapshotRelativeJavaLibPath) + + propertySet.AddProperty("jars", []string{snapshotRelativeJavaLibPath}) + } + + aidlIncludeDirs := p.AidlIncludeDirs + if len(aidlIncludeDirs) != 0 { + sdkModuleContext := ctx.SdkModuleContext() + for _, dir := range aidlIncludeDirs { + // TODO(jiyong): copy parcelable declarations only + aidlFiles, _ := sdkModuleContext.GlobWithDeps(dir.String()+"/**/*.aidl", nil) + for _, file := range aidlFiles { + builder.CopyToSnapshot(android.PathForSource(sdkModuleContext, file), filepath.Join(aidlIncludeDir, file)) + } + } + + // TODO(b/151933053) - add aidl include dirs property + } +} + +var javaHeaderLibsSdkMemberType android.SdkMemberType = &librarySdkMemberType{ + android.SdkMemberTypeBase{ + PropertyName: "java_header_libs", + SupportsSdk: true, + }, + func(j *Library) android.Path { + headerJars := j.HeaderJars() + if len(headerJars) != 1 { + panic(fmt.Errorf("there must be only one header jar from %q", j.Name())) + } + + return headerJars[0] + }, +} + // java_library builds and links sources into a `.jar` file for the device, and possibly for the host as well. // // By default, a java_library has a single variant that produces a `.jar` file containing `.class` files that were @@ -1505,12 +2097,13 @@ func (j *Library) DepsMutator(ctx android.BottomUpMutatorContext) { func LibraryFactory() android.Module { module := &Library{} - module.AddProperties( - &module.Module.properties, - &module.Module.deviceProperties, - &module.Module.dexpreoptProperties, - &module.Module.protoProperties) + module.addHostAndDeviceProperties() + module.AddProperties(&module.libraryProperties) + + module.initModuleAndImport(&module.ModuleBase) + android.InitApexModule(module) + android.InitSdkAwareModule(module) InitJavaModule(module, android.HostAndDeviceSupported) return module } @@ -1527,12 +2120,11 @@ func LibraryStaticFactory() android.Module { func LibraryHostFactory() android.Module { module := &Library{} - module.AddProperties( - &module.Module.properties, - &module.Module.protoProperties) + module.addHostProperties() module.Module.properties.Installable = proptools.BoolPtr(true) + android.InitApexModule(module) InitJavaModule(module, android.HostSupported) return module } @@ -1557,6 +2149,15 @@ type testProperties struct { // list of files or filegroup modules that provide data that should be installed alongside // the test Data []string `android:"path"` + + // Flag to indicate whether or not to create test config automatically. If AndroidTest.xml + // doesn't exist next to the Android.bp, this attribute doesn't need to be set to true + // explicitly. + Auto_gen_config *bool + + // Add parameterized mainline modules to auto generated test config. The options will be + // handled by TradeFed to do downloading and installing the specified modules on the device. + Test_mainline_modules []string } type testHelperLibraryProperties struct { @@ -1565,6 +2166,16 @@ type testHelperLibraryProperties struct { Test_suites []string `android:"arch_variant"` } +type prebuiltTestProperties struct { + // list of compatibility suites (for example "cts", "vts") that the module should be + // installed into. + Test_suites []string `android:"arch_variant"` + + // the name of the test configuration (for example "AndroidTest.xml") that should be + // installed with the module. + Test_config *string `android:"path,arch_variant"` +} + type Test struct { Library @@ -1580,8 +2191,17 @@ type TestHelperLibrary struct { testHelperLibraryProperties testHelperLibraryProperties } +type JavaTestImport struct { + Import + + prebuiltTestProperties prebuiltTestProperties + + testConfig android.Path +} + func (j *Test) GenerateAndroidBuildActions(ctx android.ModuleContext) { - j.testConfig = tradefed.AutoGenJavaTestConfig(ctx, j.testProperties.Test_config, j.testProperties.Test_config_template, j.testProperties.Test_suites) + j.testConfig = tradefed.AutoGenJavaTestConfig(ctx, j.testProperties.Test_config, j.testProperties.Test_config_template, + j.testProperties.Test_suites, j.testProperties.Auto_gen_config) j.data = android.PathsForModuleSrc(ctx, j.testProperties.Data) j.Library.GenerateAndroidBuildActions(ctx) @@ -1591,6 +2211,72 @@ func (j *TestHelperLibrary) GenerateAndroidBuildActions(ctx android.ModuleContex j.Library.GenerateAndroidBuildActions(ctx) } +func (j *JavaTestImport) GenerateAndroidBuildActions(ctx android.ModuleContext) { + j.testConfig = tradefed.AutoGenJavaTestConfig(ctx, j.prebuiltTestProperties.Test_config, nil, + j.prebuiltTestProperties.Test_suites, nil) + + j.Import.GenerateAndroidBuildActions(ctx) +} + +type testSdkMemberType struct { + android.SdkMemberTypeBase +} + +func (mt *testSdkMemberType) AddDependencies(mctx android.BottomUpMutatorContext, dependencyTag blueprint.DependencyTag, names []string) { + mctx.AddVariationDependencies(nil, dependencyTag, names...) +} + +func (mt *testSdkMemberType) IsInstance(module android.Module) bool { + _, ok := module.(*Test) + return ok +} + +func (mt *testSdkMemberType) AddPrebuiltModule(ctx android.SdkMemberContext, member android.SdkMember) android.BpModule { + return ctx.SnapshotBuilder().AddPrebuiltModule(member, "java_test_import") +} + +func (mt *testSdkMemberType) CreateVariantPropertiesStruct() android.SdkMemberProperties { + return &testSdkMemberProperties{} +} + +type testSdkMemberProperties struct { + android.SdkMemberPropertiesBase + + JarToExport android.Path + TestConfig android.Path +} + +func (p *testSdkMemberProperties) PopulateFromVariant(ctx android.SdkMemberContext, variant android.Module) { + test := variant.(*Test) + + implementationJars := test.ImplementationJars() + if len(implementationJars) != 1 { + panic(fmt.Errorf("there must be only one implementation jar from %q", test.Name())) + } + + p.JarToExport = implementationJars[0] + p.TestConfig = test.testConfig +} + +func (p *testSdkMemberProperties) AddToPropertySet(ctx android.SdkMemberContext, propertySet android.BpPropertySet) { + builder := ctx.SnapshotBuilder() + + exportedJar := p.JarToExport + if exportedJar != nil { + snapshotRelativeJavaLibPath := sdkSnapshotFilePathForJar(p.OsPrefix(), ctx.Name()) + builder.CopyToSnapshot(exportedJar, snapshotRelativeJavaLibPath) + + propertySet.AddProperty("jars", []string{snapshotRelativeJavaLibPath}) + } + + testConfig := p.TestConfig + if testConfig != nil { + snapshotRelativeTestConfigPath := sdkSnapshotFilePathForMember(p.OsPrefix(), ctx.Name(), testConfigSuffix) + builder.CopyToSnapshot(testConfig, snapshotRelativeTestConfigPath) + propertySet.AddProperty("test_config", snapshotRelativeTestConfigPath) + } +} + // java_test builds a and links sources into a `.jar` file for the device, and possibly for the host as well, and // creates an `AndroidTest.xml` file to allow running the test with `atest` or a `TEST_MAPPING` file. // @@ -1602,15 +2288,12 @@ func (j *TestHelperLibrary) GenerateAndroidBuildActions(ctx android.ModuleContex func TestFactory() android.Module { module := &Test{} - module.AddProperties( - &module.Module.properties, - &module.Module.deviceProperties, - &module.Module.dexpreoptProperties, - &module.Module.protoProperties, - &module.testProperties) + module.addHostAndDeviceProperties() + module.AddProperties(&module.testProperties) module.Module.properties.Installable = proptools.BoolPtr(true) module.Module.dexpreopter.isTest = true + module.Module.linter.test = true InitJavaModule(module, android.HostAndDeviceSupported) return module @@ -1620,13 +2303,37 @@ func TestFactory() android.Module { func TestHelperLibraryFactory() android.Module { module := &TestHelperLibrary{} + module.addHostAndDeviceProperties() + module.AddProperties(&module.testHelperLibraryProperties) + + module.Module.properties.Installable = proptools.BoolPtr(true) + module.Module.dexpreopter.isTest = true + module.Module.linter.test = true + + InitJavaModule(module, android.HostAndDeviceSupported) + return module +} + +// java_test_import imports one or more `.jar` files into the build graph as if they were built by a java_test module +// and makes sure that it is added to the appropriate test suite. +// +// By default, a java_test_import has a single variant that expects a `.jar` file containing `.class` files that were +// compiled against an Android classpath. +// +// Specifying `host_supported: true` will produce two variants, one for use as a dependency of device modules and one +// for host modules. +func JavaTestImportFactory() android.Module { + module := &JavaTestImport{} + module.AddProperties( - &module.Module.properties, - &module.Module.deviceProperties, - &module.Module.dexpreoptProperties, - &module.Module.protoProperties, - &module.testHelperLibraryProperties) + &module.Import.properties, + &module.prebuiltTestProperties) + + module.Import.properties.Installable = proptools.BoolPtr(true) + android.InitPrebuiltModule(module, &module.properties.Jars) + android.InitApexModule(module) + android.InitSdkAwareModule(module) InitJavaModule(module, android.HostAndDeviceSupported) return module } @@ -1639,10 +2346,8 @@ func TestHelperLibraryFactory() android.Module { func TestHostFactory() android.Module { module := &Test{} - module.AddProperties( - &module.Module.properties, - &module.Module.protoProperties, - &module.testProperties) + module.addHostProperties() + module.AddProperties(&module.testProperties) module.Module.properties.Installable = proptools.BoolPtr(true) @@ -1670,7 +2375,7 @@ type Binary struct { isWrapperVariant bool wrapperFile android.Path - binaryFile android.OutputPath + binaryFile android.InstallPath } func (j *Binary) HostToolPath() android.OptionalPath { @@ -1726,12 +2431,8 @@ func (j *Binary) DepsMutator(ctx android.BottomUpMutatorContext) { func BinaryFactory() android.Module { module := &Binary{} - module.AddProperties( - &module.Module.properties, - &module.Module.deviceProperties, - &module.Module.dexpreoptProperties, - &module.Module.protoProperties, - &module.binaryProperties) + module.addHostAndDeviceProperties() + module.AddProperties(&module.binaryProperties) module.Module.properties.Installable = proptools.BoolPtr(true) @@ -1747,10 +2448,8 @@ func BinaryFactory() android.Module { func BinaryHostFactory() android.Module { module := &Binary{} - module.AddProperties( - &module.Module.properties, - &module.Module.protoProperties, - &module.binaryProperties) + module.addHostProperties() + module.AddProperties(&module.binaryProperties) module.Module.properties.Installable = proptools.BoolPtr(true) @@ -1764,7 +2463,7 @@ func BinaryHostFactory() android.Module { // type ImportProperties struct { - Jars []string `android:"path"` + Jars []string `android:"path,arch_variant"` Sdk_version *string @@ -1781,12 +2480,20 @@ type ImportProperties struct { // if set to true, run Jetifier against .jar file. Defaults to false. Jetifier *bool + + // set the name of the output + Stem *string } type Import struct { android.ModuleBase android.DefaultableModuleBase + android.ApexModuleBase prebuilt android.Prebuilt + android.SdkBase + + // Functionality common to Module and Import. + embeddableInModuleAndImport properties ImportProperties @@ -1794,14 +2501,18 @@ type Import struct { exportedSdkLibs []string } -func (j *Import) sdkVersion() string { - return String(j.properties.Sdk_version) +func (j *Import) sdkVersion() sdkSpec { + return sdkSpecFrom(String(j.properties.Sdk_version)) } -func (j *Import) minSdkVersion() string { +func (j *Import) minSdkVersion() sdkSpec { return j.sdkVersion() } +func (j *Import) MinSdkVersion() string { + return j.minSdkVersion().version.String() +} + func (j *Import) Prebuilt() *android.Prebuilt { return &j.prebuilt } @@ -1814,6 +2525,14 @@ func (j *Import) Name() string { return j.prebuilt.Name(j.ModuleBase.Name()) } +func (j *Import) Stem() string { + return proptools.StringDefault(j.properties.Stem, j.ModuleBase.Name()) +} + +func (a *Import) JacocoReportClassesFile() android.Path { + return nil +} + func (j *Import) DepsMutator(ctx android.BottomUpMutatorContext) { ctx.AddVariationDependencies(nil, libTag, j.properties.Libs...) } @@ -1821,7 +2540,7 @@ func (j *Import) DepsMutator(ctx android.BottomUpMutatorContext) { func (j *Import) GenerateAndroidBuildActions(ctx android.ModuleContext) { jars := android.PathsForModuleSrc(ctx, j.properties.Jars) - jarName := ctx.ModuleName() + ".jar" + jarName := j.Stem() + ".jar" outputFile := android.PathForModuleOut(ctx, "combined", jarName) TransformJarsToJar(ctx, outputFile, "for prebuilts", jars, android.OptionalPath{}, false, j.properties.Exclude_files, j.properties.Exclude_dirs) @@ -1832,6 +2551,12 @@ func (j *Import) GenerateAndroidBuildActions(ctx android.ModuleContext) { } j.combinedClasspathFile = outputFile + // If this is a component library (impl, stubs, etc.) for a java_sdk_library then + // add the name of that java_sdk_library to the exported sdk libs to make sure + // that, if necessary, a <uses-library> element for that java_sdk_library is + // added to the Android manifest. + j.exportedSdkLibs = append(j.exportedSdkLibs, j.OptionalImplicitSdkLibrary()...) + ctx.VisitDirectDeps(func(module android.Module) { otherName := ctx.OtherModuleName(module) tag := ctx.OtherModuleDependencyTag(module) @@ -1855,7 +2580,7 @@ func (j *Import) GenerateAndroidBuildActions(ctx android.ModuleContext) { j.exportedSdkLibs = android.FirstUniqueStrings(j.exportedSdkLibs) if Bool(j.properties.Installable) { ctx.InstallFile(android.PathForModuleInstall(ctx, "framework"), - ctx.ModuleName()+".jar", outputFile) + jarName, outputFile) } } @@ -1898,6 +2623,18 @@ func (j *Import) ExportedSdkLibs() []string { return j.exportedSdkLibs } +func (j *Import) ExportedPlugins() (android.Paths, []string) { + return nil, nil +} + +func (j *Import) SrcJarArgs() ([]string, android.Paths) { + return nil, nil +} + +func (j *Import) DepIsInSameApex(ctx android.BaseModuleContext, dep android.Module) bool { + return j.depIsInSameApex(ctx, dep) +} + // Add compile time check for interface implementation var _ android.IDEInfo = (*Import)(nil) var _ android.IDECustomizedModuleName = (*Import)(nil) @@ -1936,7 +2673,11 @@ func ImportFactory() android.Module { module.AddProperties(&module.properties) + module.initModuleAndImport(&module.ModuleBase) + android.InitPrebuiltModule(module, &module.properties.Jars) + android.InitApexModule(module) + android.InitSdkAwareModule(module) InitJavaModule(module, android.HostAndDeviceSupported) return module } @@ -1952,6 +2693,7 @@ func ImportFactoryHost() android.Module { module.AddProperties(&module.properties) android.InitPrebuiltModule(module, &module.properties.Jars) + android.InitApexModule(module) InitJavaModule(module, android.HostSupported) return module } @@ -1959,12 +2701,16 @@ func ImportFactoryHost() android.Module { // dex_import module type DexImportProperties struct { - Jars []string + Jars []string `android:"path"` + + // set the name of the output + Stem *string } type DexImport struct { android.ModuleBase android.DefaultableModuleBase + android.ApexModuleBase prebuilt android.Prebuilt properties DexImportProperties @@ -1987,8 +2733,20 @@ func (j *DexImport) Name() string { return j.prebuilt.Name(j.ModuleBase.Name()) } -func (j *DexImport) DepsMutator(ctx android.BottomUpMutatorContext) { - android.ExtractSourcesDeps(ctx, j.properties.Jars) +func (j *DexImport) Stem() string { + return proptools.StringDefault(j.properties.Stem, j.ModuleBase.Name()) +} + +func (a *DexImport) JacocoReportClassesFile() android.Path { + return nil +} + +func (a *DexImport) LintDepSets() LintDepSets { + return LintDepSets{} +} + +func (j *DexImport) IsInstallable() bool { + return true } func (j *DexImport) GenerateAndroidBuildActions(ctx android.ModuleContext) { @@ -1996,8 +2754,7 @@ func (j *DexImport) GenerateAndroidBuildActions(ctx android.ModuleContext) { ctx.PropertyErrorf("jars", "exactly one jar must be provided") } - j.dexpreopter.installPath = android.PathForModuleInstall(ctx, "framework", ctx.ModuleName()+".jar") - j.dexpreopter.isInstallable = true + j.dexpreopter.installPath = android.PathForModuleInstall(ctx, "framework", j.Stem()+".jar") j.dexpreopter.uncompressedDex = shouldUncompressDex(ctx, &j.dexpreopter) inputJar := ctx.ExpandSource(j.properties.Jars[0], "jars") @@ -2011,14 +2768,14 @@ func (j *DexImport) GenerateAndroidBuildActions(ctx android.ModuleContext) { // use zip2zip to uncompress classes*.dex files rule.Command(). - Tool(ctx.Config().HostToolPath(ctx, "zip2zip")). + BuiltTool(ctx, "zip2zip"). FlagWithInput("-i ", inputJar). FlagWithOutput("-o ", temporary). FlagWithArg("-0 ", "'classes*.dex'") // use zipalign to align uncompressed classes*.dex files rule.Command(). - Tool(ctx.Config().HostToolPath(ctx, "zipalign")). + BuiltTool(ctx, "zipalign"). Flag("-f"). Text("4"). Input(temporary). @@ -2041,8 +2798,10 @@ func (j *DexImport) GenerateAndroidBuildActions(ctx android.ModuleContext) { j.maybeStrippedDexJarFile = dexOutputFile - ctx.InstallFile(android.PathForModuleInstall(ctx, "framework"), - ctx.ModuleName()+".jar", dexOutputFile) + if j.IsForPlatform() { + ctx.InstallFile(android.PathForModuleInstall(ctx, "framework"), + j.Stem()+".jar", dexOutputFile) + } } func (j *DexImport) DexJar() android.Path { @@ -2059,6 +2818,7 @@ func DexImportFactory() android.Module { module.AddProperties(&module.properties) android.InitPrebuiltModule(module, &module.properties.Jars) + android.InitApexModule(module) InitJavaModule(module, android.DeviceSupported) return module } @@ -2069,9 +2829,7 @@ func DexImportFactory() android.Module { type Defaults struct { android.ModuleBase android.DefaultsModuleBase -} - -func (*Defaults) GenerateAndroidBuildActions(ctx android.ModuleContext) { + android.ApexModuleBase } // java_defaults provides a set of properties that can be inherited by other java or android modules. @@ -2109,10 +2867,9 @@ func defaultsFactory() android.Module { return DefaultsFactory() } -func DefaultsFactory(props ...interface{}) android.Module { +func DefaultsFactory() android.Module { module := &Defaults{} - module.AddProperties(props...) module.AddProperties( &CompilerProperties{}, &CompilerDeviceProperties{}, @@ -2126,14 +2883,37 @@ func DefaultsFactory(props ...interface{}) android.Module { &ImportProperties{}, &AARImportProperties{}, &sdkLibraryProperties{}, + &commonToSdkLibraryAndImportProperties{}, &DexImportProperties{}, + &android.ApexProperties{}, + &RuntimeResourceOverlayProperties{}, + &LintProperties{}, ) android.InitDefaultsModule(module) - return module } +func kytheExtractJavaFactory() android.Singleton { + return &kytheExtractJavaSingleton{} +} + +type kytheExtractJavaSingleton struct { +} + +func (ks *kytheExtractJavaSingleton) GenerateBuildActions(ctx android.SingletonContext) { + var xrefTargets android.Paths + ctx.VisitAllModules(func(module android.Module) { + if javaModule, ok := module.(xref); ok { + xrefTargets = append(xrefTargets, javaModule.XrefJavaFiles()...) + } + }) + // TODO(asmundak): perhaps emit a rule to output a warning if there were no xrefTargets + if len(xrefTargets) > 0 { + ctx.Phony("xref_java", xrefTargets...) + } +} + var Bool = proptools.Bool var BoolDefault = proptools.BoolDefault var String = proptools.String diff --git a/java/java_resources.go b/java/java_resources.go index 71611689a..787d74a0d 100644 --- a/java/java_resources.go +++ b/java/java_resources.go @@ -85,19 +85,19 @@ func ResourceFilesToJarArgs(ctx android.ModuleContext, return resourceFilesToJarArgs(ctx, res, exclude) } -// Convert java_resources properties to arguments to soong_zip -jar, keeping files that should -// normally not used as resources like *.java -func SourceFilesToJarArgs(ctx android.ModuleContext, - res, exclude []string) (args []string, deps android.Paths) { - - return resourceFilesToJarArgs(ctx, res, exclude) -} - func resourceFilesToJarArgs(ctx android.ModuleContext, res, exclude []string) (args []string, deps android.Paths) { files := android.PathsForModuleSrcExcludes(ctx, res, exclude) + args = resourcePathsToJarArgs(files) + + return args, files +} + +func resourcePathsToJarArgs(files android.Paths) []string { + var args []string + lastDir := "" for i, f := range files { rel := f.Rel() @@ -113,5 +113,5 @@ func resourceFilesToJarArgs(ctx android.ModuleContext, lastDir = dir } - return args, files + return args } diff --git a/java/java_test.go b/java/java_test.go index 50b2f69d9..8797119b5 100644 --- a/java/java_test.go +++ b/java/java_test.go @@ -18,10 +18,15 @@ import ( "io/ioutil" "os" "path/filepath" + "reflect" + "regexp" + "sort" "strconv" "strings" "testing" + "github.com/google/blueprint/proptools" + "android/soong/android" "android/soong/cc" "android/soong/dexpreopt" @@ -53,151 +58,44 @@ func TestMain(m *testing.M) { os.Exit(run()) } -func testConfig(env map[string]string) android.Config { - return TestConfig(buildDir, env) +func testConfig(env map[string]string, bp string, fs map[string][]byte) android.Config { + bp += dexpreopt.BpToolModulesForTest() + + config := TestConfig(buildDir, env, bp, fs) + + // Set up the global Once cache used for dexpreopt.GlobalSoongConfig, so that + // it doesn't create a real one, which would fail. + _ = dexpreopt.GlobalSoongConfigForTests(config) + + return config } -func testContext(config android.Config, bp string, - fs map[string][]byte) *android.TestContext { +func testContext() *android.TestContext { ctx := android.NewTestArchContext() - ctx.RegisterModuleType("android_app", android.ModuleFactoryAdaptor(AndroidAppFactory)) - ctx.RegisterModuleType("android_app_certificate", android.ModuleFactoryAdaptor(AndroidAppCertificateFactory)) - ctx.RegisterModuleType("android_library", android.ModuleFactoryAdaptor(AndroidLibraryFactory)) - ctx.RegisterModuleType("android_test", android.ModuleFactoryAdaptor(AndroidTestFactory)) - ctx.RegisterModuleType("android_test_helper_app", android.ModuleFactoryAdaptor(AndroidTestHelperAppFactory)) - ctx.RegisterModuleType("java_binary", android.ModuleFactoryAdaptor(BinaryFactory)) - ctx.RegisterModuleType("java_binary_host", android.ModuleFactoryAdaptor(BinaryHostFactory)) - ctx.RegisterModuleType("java_device_for_host", android.ModuleFactoryAdaptor(DeviceForHostFactory)) - ctx.RegisterModuleType("java_host_for_device", android.ModuleFactoryAdaptor(HostForDeviceFactory)) - ctx.RegisterModuleType("java_library", android.ModuleFactoryAdaptor(LibraryFactory)) - ctx.RegisterModuleType("java_library_host", android.ModuleFactoryAdaptor(LibraryHostFactory)) - ctx.RegisterModuleType("java_test", android.ModuleFactoryAdaptor(TestFactory)) - ctx.RegisterModuleType("java_import", android.ModuleFactoryAdaptor(ImportFactory)) - ctx.RegisterModuleType("java_import_host", android.ModuleFactoryAdaptor(ImportFactoryHost)) - ctx.RegisterModuleType("java_defaults", android.ModuleFactoryAdaptor(defaultsFactory)) - ctx.RegisterModuleType("java_system_modules", android.ModuleFactoryAdaptor(SystemModulesFactory)) - ctx.RegisterModuleType("java_genrule", android.ModuleFactoryAdaptor(genRuleFactory)) - ctx.RegisterModuleType("java_plugin", android.ModuleFactoryAdaptor(PluginFactory)) - ctx.RegisterModuleType("dex_import", android.ModuleFactoryAdaptor(DexImportFactory)) - ctx.RegisterModuleType("filegroup", android.ModuleFactoryAdaptor(android.FileGroupFactory)) - ctx.RegisterModuleType("genrule", android.ModuleFactoryAdaptor(genrule.GenRuleFactory)) - ctx.RegisterModuleType("droiddoc", android.ModuleFactoryAdaptor(DroiddocFactory)) - ctx.RegisterModuleType("droiddoc_host", android.ModuleFactoryAdaptor(DroiddocHostFactory)) - ctx.RegisterModuleType("droiddoc_template", android.ModuleFactoryAdaptor(ExportedDroiddocDirFactory)) - ctx.RegisterModuleType("java_sdk_library", android.ModuleFactoryAdaptor(SdkLibraryFactory)) - ctx.RegisterModuleType("override_android_app", android.ModuleFactoryAdaptor(OverrideAndroidAppModuleFactory)) - ctx.RegisterModuleType("prebuilt_apis", android.ModuleFactoryAdaptor(PrebuiltApisFactory)) - ctx.RegisterModuleType("android_app_set", android.ModuleFactoryAdaptor(AndroidApkSetFactory)) - ctx.PreArchMutators(android.RegisterPrebuiltsPreArchMutators) - ctx.PreArchMutators(android.RegisterPrebuiltsPostDepsMutators) + RegisterJavaBuildComponents(ctx) + RegisterAppBuildComponents(ctx) + RegisterAARBuildComponents(ctx) + RegisterGenRuleBuildComponents(ctx) + RegisterSystemModulesBuildComponents(ctx) + ctx.RegisterModuleType("java_plugin", PluginFactory) + ctx.RegisterModuleType("filegroup", android.FileGroupFactory) + ctx.RegisterModuleType("genrule", genrule.GenRuleFactory) + RegisterDocsBuildComponents(ctx) + RegisterStubsBuildComponents(ctx) + RegisterSdkLibraryBuildComponents(ctx) ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators) - ctx.PreArchMutators(android.RegisterOverridePreArchMutators) - ctx.PreArchMutators(func(ctx android.RegisterMutatorsContext) { - ctx.TopDown("prebuilt_apis", PrebuiltApisMutator).Parallel() - ctx.TopDown("java_sdk_library", SdkLibraryMutator).Parallel() - }) + + RegisterPrebuiltApisBuildComponents(ctx) + + ctx.PostDepsMutators(android.RegisterOverridePostDepsMutators) ctx.RegisterPreSingletonType("overlay", android.SingletonFactoryAdaptor(OverlaySingletonFactory)) ctx.RegisterPreSingletonType("sdk_versions", android.SingletonFactoryAdaptor(sdkPreSingletonFactory)) // Register module types and mutators from cc needed for JNI testing - ctx.RegisterModuleType("cc_library", android.ModuleFactoryAdaptor(cc.LibraryFactory)) - ctx.RegisterModuleType("cc_object", android.ModuleFactoryAdaptor(cc.ObjectFactory)) - ctx.RegisterModuleType("toolchain_library", android.ModuleFactoryAdaptor(cc.ToolchainLibraryFactory)) - ctx.RegisterModuleType("llndk_library", android.ModuleFactoryAdaptor(cc.LlndkLibraryFactory)) - ctx.PreDepsMutators(func(ctx android.RegisterMutatorsContext) { - ctx.BottomUp("link", cc.LinkageMutator).Parallel() - ctx.BottomUp("begin", cc.BeginMutator).Parallel() - }) + cc.RegisterRequiredBuildComponentsForTest(ctx) - bp += GatherRequiredDepsForTest() - - mockFS := map[string][]byte{ - "Android.bp": []byte(bp), - "a.java": nil, - "b.java": nil, - "c.java": nil, - "b.kt": nil, - "a.jar": nil, - "b.jar": nil, - "APP_NOTICE": nil, - "GENRULE_NOTICE": nil, - "LIB_NOTICE": nil, - "TOOL_NOTICE": nil, - "java-res/a/a": nil, - "java-res/b/b": nil, - "java-res2/a": nil, - "java-fg/a.java": nil, - "java-fg/b.java": nil, - "java-fg/c.java": nil, - "api/current.txt": nil, - "api/removed.txt": nil, - "api/system-current.txt": nil, - "api/system-removed.txt": nil, - "api/test-current.txt": nil, - "api/test-removed.txt": nil, - "framework/aidl/a.aidl": nil, - - "prebuilts/sdk/14/public/android.jar": nil, - "prebuilts/sdk/14/public/framework.aidl": nil, - "prebuilts/sdk/14/system/android.jar": nil, - "prebuilts/sdk/17/public/android.jar": nil, - "prebuilts/sdk/17/public/framework.aidl": nil, - "prebuilts/sdk/17/system/android.jar": nil, - "prebuilts/sdk/25/public/android.jar": nil, - "prebuilts/sdk/25/public/framework.aidl": nil, - "prebuilts/sdk/25/system/android.jar": nil, - "prebuilts/sdk/current/core/android.jar": nil, - "prebuilts/sdk/current/public/android.jar": nil, - "prebuilts/sdk/current/public/framework.aidl": nil, - "prebuilts/sdk/current/public/core.jar": nil, - "prebuilts/sdk/current/system/android.jar": nil, - "prebuilts/sdk/current/test/android.jar": nil, - "prebuilts/sdk/28/public/api/foo.txt": nil, - "prebuilts/sdk/28/system/api/foo.txt": nil, - "prebuilts/sdk/28/test/api/foo.txt": nil, - "prebuilts/sdk/28/public/api/foo-removed.txt": nil, - "prebuilts/sdk/28/system/api/foo-removed.txt": nil, - "prebuilts/sdk/28/test/api/foo-removed.txt": nil, - "prebuilts/sdk/28/public/api/bar.txt": nil, - "prebuilts/sdk/28/system/api/bar.txt": nil, - "prebuilts/sdk/28/test/api/bar.txt": nil, - "prebuilts/sdk/28/public/api/bar-removed.txt": nil, - "prebuilts/sdk/28/system/api/bar-removed.txt": nil, - "prebuilts/sdk/28/test/api/bar-removed.txt": nil, - "prebuilts/sdk/tools/core-lambda-stubs.jar": nil, - "prebuilts/sdk/Android.bp": []byte(`prebuilt_apis { name: "sdk", api_dirs: ["14", "28", "current"],}`), - - "prebuilts/apks/app.apks": nil, - - // For framework-res, which is an implicit dependency for framework - "AndroidManifest.xml": nil, - "build/target/product/security/testkey": nil, - - "build/soong/scripts/jar-wrapper.sh": nil, - - "build/make/core/proguard.flags": nil, - "build/make/core/proguard_basic_keeps.flags": nil, - - "jdk8/jre/lib/jce.jar": nil, - "jdk8/jre/lib/rt.jar": nil, - "jdk8/lib/tools.jar": nil, - - "bar-doc/a.java": nil, - "bar-doc/b.java": nil, - "bar-doc/IFoo.aidl": nil, - "bar-doc/known_oj_tags.txt": nil, - "external/doclava/templates-sdk": nil, - - "cert/new_cert.x509.pem": nil, - "cert/new_cert.pk8": nil, - } - - for k, v := range fs { - mockFS[k] = v - } - - ctx.MockFileSystem(mockFS) + dexpreopt.RegisterToolModulesForTest(ctx) return ctx } @@ -205,11 +103,11 @@ func testContext(config android.Config, bp string, func run(t *testing.T, ctx *android.TestContext, config android.Config) { t.Helper() - pathCtx := android.PathContextForTesting(config, nil) - setDexpreoptTestGlobalConfig(config, dexpreopt.GlobalConfigForTests(pathCtx)) + pathCtx := android.PathContextForTesting(config) + dexpreopt.SetTestGlobalConfig(config, dexpreopt.GlobalConfigForTests(pathCtx)) - ctx.Register() - _, errs := ctx.ParseFileList(".", []string{"Android.bp", "prebuilts/sdk/Android.bp"}) + ctx.Register(config) + _, errs := ctx.ParseBlueprintsFiles("Android.bp") android.FailIfErrored(t, errs) _, errs = ctx.PrepareBuildActions(config) android.FailIfErrored(t, errs) @@ -217,9 +115,17 @@ func run(t *testing.T, ctx *android.TestContext, config android.Config) { func testJavaError(t *testing.T, pattern string, bp string) (*android.TestContext, android.Config) { t.Helper() - config := testConfig(nil) - ctx := testContext(config, bp, nil) + return testJavaErrorWithConfig(t, pattern, testConfig(nil, bp, nil)) +} + +func testJavaErrorWithConfig(t *testing.T, pattern string, config android.Config) (*android.TestContext, android.Config) { + t.Helper() + ctx := testContext() + pathCtx := android.PathContextForTesting(config) + dexpreopt.SetTestGlobalConfig(config, dexpreopt.GlobalConfigForTests(pathCtx)) + + ctx.Register(config) _, errs := ctx.ParseBlueprintsFiles("Android.bp") if len(errs) > 0 { android.FailIfNoMatchingErrors(t, pattern, errs) @@ -236,13 +142,22 @@ func testJavaError(t *testing.T, pattern string, bp string) (*android.TestContex return ctx, config } -func testJava(t *testing.T, bp string) *android.TestContext { +func testJavaWithFS(t *testing.T, bp string, fs map[string][]byte) (*android.TestContext, android.Config) { t.Helper() - config := testConfig(nil) - ctx := testContext(config, bp, nil) + return testJavaWithConfig(t, testConfig(nil, bp, fs)) +} + +func testJava(t *testing.T, bp string) (*android.TestContext, android.Config) { + t.Helper() + return testJavaWithFS(t, bp, nil) +} + +func testJavaWithConfig(t *testing.T, config android.Config) (*android.TestContext, android.Config) { + t.Helper() + ctx := testContext() run(t, ctx, config) - return ctx + return ctx, config } func moduleToPath(name string) string { @@ -256,8 +171,96 @@ func moduleToPath(name string) string { } } +func TestJavaLinkType(t *testing.T) { + testJava(t, ` + java_library { + name: "foo", + srcs: ["a.java"], + libs: ["bar"], + static_libs: ["baz"], + } + + java_library { + name: "bar", + sdk_version: "current", + srcs: ["b.java"], + } + + java_library { + name: "baz", + sdk_version: "system_current", + srcs: ["c.java"], + } + `) + + testJavaError(t, "Adjust sdk_version: property of the source or target module so that target module is built with the same or smaller API set than the source.", ` + java_library { + name: "foo", + srcs: ["a.java"], + libs: ["bar"], + sdk_version: "current", + static_libs: ["baz"], + } + + java_library { + name: "bar", + sdk_version: "current", + srcs: ["b.java"], + } + + java_library { + name: "baz", + sdk_version: "system_current", + srcs: ["c.java"], + } + `) + + testJava(t, ` + java_library { + name: "foo", + srcs: ["a.java"], + libs: ["bar"], + sdk_version: "system_current", + static_libs: ["baz"], + } + + java_library { + name: "bar", + sdk_version: "current", + srcs: ["b.java"], + } + + java_library { + name: "baz", + sdk_version: "system_current", + srcs: ["c.java"], + } + `) + + testJavaError(t, "Adjust sdk_version: property of the source or target module so that target module is built with the same or smaller API set than the source.", ` + java_library { + name: "foo", + srcs: ["a.java"], + libs: ["bar"], + sdk_version: "system_current", + static_libs: ["baz"], + } + + java_library { + name: "bar", + sdk_version: "current", + srcs: ["b.java"], + } + + java_library { + name: "baz", + srcs: ["c.java"], + } + `) +} + func TestSimple(t *testing.T) { - ctx := testJava(t, ` + ctx, _ := testJava(t, ` java_library { name: "foo", srcs: ["a.java"], @@ -300,8 +303,126 @@ func TestSimple(t *testing.T) { } } +func TestExportedPlugins(t *testing.T) { + type Result struct { + library string + processors string + } + var tests = []struct { + name string + extra string + results []Result + }{ + { + name: "Exported plugin is not a direct plugin", + extra: `java_library { name: "exports", srcs: ["a.java"], exported_plugins: ["plugin"] }`, + results: []Result{{library: "exports", processors: "-proc:none"}}, + }, + { + name: "Exports plugin to dependee", + extra: ` + java_library{name: "exports", exported_plugins: ["plugin"]} + java_library{name: "foo", srcs: ["a.java"], libs: ["exports"]} + java_library{name: "bar", srcs: ["a.java"], static_libs: ["exports"]} + `, + results: []Result{ + {library: "foo", processors: "-processor com.android.TestPlugin"}, + {library: "bar", processors: "-processor com.android.TestPlugin"}, + }, + }, + { + name: "Exports plugin to android_library", + extra: ` + java_library{name: "exports", exported_plugins: ["plugin"]} + android_library{name: "foo", srcs: ["a.java"], libs: ["exports"]} + android_library{name: "bar", srcs: ["a.java"], static_libs: ["exports"]} + `, + results: []Result{ + {library: "foo", processors: "-processor com.android.TestPlugin"}, + {library: "bar", processors: "-processor com.android.TestPlugin"}, + }, + }, + { + name: "Exports plugin is not propagated via transitive deps", + extra: ` + java_library{name: "exports", exported_plugins: ["plugin"]} + java_library{name: "foo", srcs: ["a.java"], libs: ["exports"]} + java_library{name: "bar", srcs: ["a.java"], static_libs: ["foo"]} + `, + results: []Result{ + {library: "foo", processors: "-processor com.android.TestPlugin"}, + {library: "bar", processors: "-proc:none"}, + }, + }, + { + name: "Exports plugin appends to plugins", + extra: ` + java_plugin{name: "plugin2", processor_class: "com.android.TestPlugin2"} + java_library{name: "exports", exported_plugins: ["plugin"]} + java_library{name: "foo", srcs: ["a.java"], libs: ["exports"], plugins: ["plugin2"]} + `, + results: []Result{ + {library: "foo", processors: "-processor com.android.TestPlugin,com.android.TestPlugin2"}, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctx, _ := testJava(t, ` + java_plugin { + name: "plugin", + processor_class: "com.android.TestPlugin", + } + `+test.extra) + + for _, want := range test.results { + javac := ctx.ModuleForTests(want.library, "android_common").Rule("javac") + if javac.Args["processor"] != want.processors { + t.Errorf("For library %v, expected %v, found %v", want.library, want.processors, javac.Args["processor"]) + } + } + }) + } +} + +func TestSdkVersionByPartition(t *testing.T) { + testJavaError(t, "sdk_version must have a value when the module is located at vendor or product", ` + java_library { + name: "foo", + srcs: ["a.java"], + vendor: true, + } + `) + + testJava(t, ` + java_library { + name: "bar", + srcs: ["b.java"], + } + `) + + for _, enforce := range []bool{true, false} { + bp := ` + java_library { + name: "foo", + srcs: ["a.java"], + product_specific: true, + } + ` + + config := testConfig(nil, bp, nil) + config.TestProductVariables.EnforceProductPartitionInterface = proptools.BoolPtr(enforce) + if enforce { + testJavaErrorWithConfig(t, "sdk_version must have a value when the module is located at vendor or product", config) + } else { + testJavaWithConfig(t, config) + } + } +} + func TestArchSpecific(t *testing.T) { - ctx := testJava(t, ` + ctx, _ := testJava(t, ` java_library { name: "foo", srcs: ["a.java"], @@ -320,7 +441,7 @@ func TestArchSpecific(t *testing.T) { } func TestBinary(t *testing.T) { - ctx := testJava(t, ` + ctx, _ := testJava(t, ` java_library_host { name: "foo", srcs: ["a.java"], @@ -349,11 +470,11 @@ func TestBinary(t *testing.T) { } func TestPrebuilts(t *testing.T) { - ctx := testJava(t, ` + ctx, _ := testJava(t, ` java_library { name: "foo", - srcs: ["a.java"], - libs: ["bar"], + srcs: ["a.java", ":stubs-source"], + libs: ["bar", "sdklib"], static_libs: ["baz"], } @@ -371,17 +492,50 @@ func TestPrebuilts(t *testing.T) { name: "qux", jars: ["b.jar"], } + + java_sdk_library_import { + name: "sdklib", + public: { + jars: ["c.jar"], + }, + } + + prebuilt_stubs_sources { + name: "stubs-source", + srcs: ["stubs/sources"], + } + + java_test_import { + name: "test", + jars: ["a.jar"], + test_suites: ["cts"], + test_config: "AndroidTest.xml", + } `) - javac := ctx.ModuleForTests("foo", "android_common").Rule("javac") + fooModule := ctx.ModuleForTests("foo", "android_common") + javac := fooModule.Rule("javac") combineJar := ctx.ModuleForTests("foo", "android_common").Description("for javac") barJar := ctx.ModuleForTests("bar", "android_common").Rule("combineJar").Output bazJar := ctx.ModuleForTests("baz", "android_common").Rule("combineJar").Output + sdklibStubsJar := ctx.ModuleForTests("sdklib.stubs", "android_common").Rule("combineJar").Output + + fooLibrary := fooModule.Module().(*Library) + assertDeepEquals(t, "foo java sources incorrect", + []string{"a.java"}, fooLibrary.compiledJavaSrcs.Strings()) + + assertDeepEquals(t, "foo java source jars incorrect", + []string{".intermediates/stubs-source/android_common/stubs-source-stubs.srcjar"}, + android.NormalizePathsForTesting(fooLibrary.compiledSrcJars)) if !strings.Contains(javac.Args["classpath"], barJar.String()) { t.Errorf("foo classpath %v does not contain %q", javac.Args["classpath"], barJar.String()) } + if !strings.Contains(javac.Args["classpath"], sdklibStubsJar.String()) { + t.Errorf("foo classpath %v does not contain %q", javac.Args["classpath"], sdklibStubsJar.String()) + } + if len(combineJar.Inputs) != 2 || combineJar.Inputs[1].String() != bazJar.String() { t.Errorf("foo combineJar inputs %v does not contain %q", combineJar.Inputs, bazJar.String()) } @@ -389,8 +543,63 @@ func TestPrebuilts(t *testing.T) { ctx.ModuleForTests("qux", "android_common").Rule("Cp") } +func assertDeepEquals(t *testing.T, message string, expected interface{}, actual interface{}) { + if !reflect.DeepEqual(expected, actual) { + t.Errorf("%s: expected %q, found %q", message, expected, actual) + } +} + +func TestJavaSdkLibraryImport(t *testing.T) { + ctx, _ := testJava(t, ` + java_library { + name: "foo", + srcs: ["a.java"], + libs: ["sdklib"], + sdk_version: "current", + } + + java_library { + name: "foo.system", + srcs: ["a.java"], + libs: ["sdklib"], + sdk_version: "system_current", + } + + java_library { + name: "foo.test", + srcs: ["a.java"], + libs: ["sdklib"], + sdk_version: "test_current", + } + + java_sdk_library_import { + name: "sdklib", + public: { + jars: ["a.jar"], + }, + system: { + jars: ["b.jar"], + }, + test: { + jars: ["c.jar"], + stub_srcs: ["c.java"], + }, + } + `) + + for _, scope := range []string{"", ".system", ".test"} { + fooModule := ctx.ModuleForTests("foo"+scope, "android_common") + javac := fooModule.Rule("javac") + + sdklibStubsJar := ctx.ModuleForTests("sdklib.stubs"+scope, "android_common").Rule("combineJar").Output + if !strings.Contains(javac.Args["classpath"], sdklibStubsJar.String()) { + t.Errorf("foo classpath %v does not contain %q", javac.Args["classpath"], sdklibStubsJar.String()) + } + } +} + func TestDefaults(t *testing.T) { - ctx := testJava(t, ` + ctx, _ := testJava(t, ` java_defaults { name: "defaults", srcs: ["a.java"], @@ -497,12 +706,6 @@ func TestResources(t *testing.T) { args: "-C java-res -f java-res/a/a -f java-res/b/b", }, { - // Test that a module with "include_srcs: true" includes its source files in the resources jar - name: "include sources", - prop: `include_srcs: true`, - args: "-C . -f a.java -f b.java -f c.java", - }, - { // Test that a module with wildcards in java_resource_dirs has the correct path prefixes name: "wildcard dirs", prop: `java_resource_dirs: ["java-res/*"]`, @@ -542,7 +745,7 @@ func TestResources(t *testing.T) { for _, test := range table { t.Run(test.name, func(t *testing.T) { - ctx := testJava(t, ` + ctx, _ := testJavaWithFS(t, ` java_library { name: "foo", srcs: [ @@ -552,7 +755,13 @@ func TestResources(t *testing.T) { ], `+test.prop+`, } - `+test.extra) + `+test.extra, + map[string][]byte{ + "java-res/a/a": nil, + "java-res/b/b": nil, + "java-res2/a": nil, + }, + ) foo := ctx.ModuleForTests("foo", "android_common").Output("withres/foo.jar") fooRes := ctx.ModuleForTests("foo", "android_common").Output("res/foo.jar") @@ -570,8 +779,75 @@ func TestResources(t *testing.T) { } } +func TestIncludeSrcs(t *testing.T) { + ctx, _ := testJavaWithFS(t, ` + java_library { + name: "foo", + srcs: [ + "a.java", + "b.java", + "c.java", + ], + include_srcs: true, + } + + java_library { + name: "bar", + srcs: [ + "a.java", + "b.java", + "c.java", + ], + java_resource_dirs: ["java-res"], + include_srcs: true, + } + `, map[string][]byte{ + "java-res/a/a": nil, + "java-res/b/b": nil, + "java-res2/a": nil, + }) + + // Test a library with include_srcs: true + foo := ctx.ModuleForTests("foo", "android_common").Output("withres/foo.jar") + fooSrcJar := ctx.ModuleForTests("foo", "android_common").Output("foo.srcjar") + + if g, w := fooSrcJar.Output.String(), foo.Inputs.Strings(); !inList(g, w) { + t.Errorf("foo combined jars %v does not contain %q", w, g) + } + + if g, w := fooSrcJar.Args["jarArgs"], "-C . -f a.java -f b.java -f c.java"; g != w { + t.Errorf("foo source jar args %q is not %q", w, g) + } + + // Test a library with include_srcs: true and resources + bar := ctx.ModuleForTests("bar", "android_common").Output("withres/bar.jar") + barResCombined := ctx.ModuleForTests("bar", "android_common").Output("res-combined/bar.jar") + barRes := ctx.ModuleForTests("bar", "android_common").Output("res/bar.jar") + barSrcJar := ctx.ModuleForTests("bar", "android_common").Output("bar.srcjar") + + if g, w := barSrcJar.Output.String(), barResCombined.Inputs.Strings(); !inList(g, w) { + t.Errorf("bar combined resource jars %v does not contain %q", w, g) + } + + if g, w := barRes.Output.String(), barResCombined.Inputs.Strings(); !inList(g, w) { + t.Errorf("bar combined resource jars %v does not contain %q", w, g) + } + + if g, w := barResCombined.Output.String(), bar.Inputs.Strings(); !inList(g, w) { + t.Errorf("bar combined jars %v does not contain %q", w, g) + } + + if g, w := barSrcJar.Args["jarArgs"], "-C . -f a.java -f b.java -f c.java"; g != w { + t.Errorf("bar source jar args %q is not %q", w, g) + } + + if g, w := barRes.Args["jarArgs"], "-C java-res -f java-res/a/a -f java-res/b/b"; g != w { + t.Errorf("bar resource jar args %q is not %q", w, g) + } +} + func TestGeneratedSources(t *testing.T) { - ctx := testJava(t, ` + ctx, _ := testJavaWithFS(t, ` java_library { name: "foo", srcs: [ @@ -586,7 +862,10 @@ func TestGeneratedSources(t *testing.T) { tool_files: ["java-res/a"], out: ["gen.java"], } - `) + `, map[string][]byte{ + "a.java": nil, + "b.java": nil, + }) javac := ctx.ModuleForTests("foo", "android_common").Rule("javac") genrule := ctx.ModuleForTests("gen", "").Rule("generator") @@ -604,7 +883,7 @@ func TestGeneratedSources(t *testing.T) { } func TestTurbine(t *testing.T) { - ctx := testJava(t, ` + ctx, _ := testJava(t, ` java_library { name: "foo", srcs: ["a.java"], @@ -653,7 +932,7 @@ func TestTurbine(t *testing.T) { } func TestSharding(t *testing.T) { - ctx := testJava(t, ` + ctx, _ := testJava(t, ` java_library { name: "bar", srcs: ["a.java","b.java","c.java"], @@ -671,16 +950,22 @@ func TestSharding(t *testing.T) { } func TestDroiddoc(t *testing.T) { - ctx := testJava(t, ` - droiddoc_template { + ctx, _ := testJavaWithFS(t, ` + droiddoc_exported_dir { name: "droiddoc-templates-sdk", path: ".", } + filegroup { + name: "bar-doc-aidl-srcs", + srcs: ["bar-doc/IBar.aidl"], + path: "bar-doc", + } droiddoc { name: "bar-doc", srcs: [ - "bar-doc/*.java", + "bar-doc/a.java", "bar-doc/IFoo.aidl", + ":bar-doc-aidl-srcs", ], exclude_srcs: [ "bar-doc/b.java" @@ -696,25 +981,148 @@ func TestDroiddoc(t *testing.T) { todo_file: "libcore-docs-todo.html", args: "-offlinemode -title \"libcore\"", } - `) + `, + map[string][]byte{ + "bar-doc/a.java": nil, + "bar-doc/b.java": nil, + }) - stubsJar := filepath.Join(buildDir, ".intermediates", "bar-doc", "android_common", "bar-doc-stubs.srcjar") - barDoc := ctx.ModuleForTests("bar-doc", "android_common").Output("bar-doc-stubs.srcjar") - if stubsJar != barDoc.Output.String() { - t.Errorf("expected stubs Jar [%q], got %q", stubsJar, barDoc.Output.String()) - } - inputs := ctx.ModuleForTests("bar-doc", "android_common").Rule("javadoc").Inputs + barDoc := ctx.ModuleForTests("bar-doc", "android_common").Rule("javadoc") var javaSrcs []string - for _, i := range inputs { + for _, i := range barDoc.Inputs { javaSrcs = append(javaSrcs, i.Base()) } - if len(javaSrcs) != 2 || javaSrcs[0] != "a.java" || javaSrcs[1] != "IFoo.java" { - t.Errorf("inputs of bar-doc must be []string{\"a.java\", \"IFoo.java\", but was %#v.", javaSrcs) + if len(javaSrcs) != 1 || javaSrcs[0] != "a.java" { + t.Errorf("inputs of bar-doc must be []string{\"a.java\"}, but was %#v.", javaSrcs) + } + + aidl := ctx.ModuleForTests("bar-doc", "android_common").Rule("aidl") + if g, w := barDoc.Implicits.Strings(), aidl.Output.String(); !inList(w, g) { + t.Errorf("implicits of bar-doc must contain %q, but was %q.", w, g) + } + + if g, w := aidl.Implicits.Strings(), []string{"bar-doc/IBar.aidl", "bar-doc/IFoo.aidl"}; !reflect.DeepEqual(w, g) { + t.Errorf("aidl inputs must be %q, but was %q", w, g) + } +} + +func TestDroidstubs(t *testing.T) { + ctx, _ := testJavaWithFS(t, ` + droiddoc_exported_dir { + name: "droiddoc-templates-sdk", + path: ".", + } + + droidstubs { + name: "bar-stubs", + srcs: [ + "bar-doc/a.java", + ], + api_levels_annotations_dirs: [ + "droiddoc-templates-sdk", + ], + api_levels_annotations_enabled: true, + } + + droidstubs { + name: "bar-stubs-other", + srcs: [ + "bar-doc/a.java", + ], + api_levels_annotations_dirs: [ + "droiddoc-templates-sdk", + ], + api_levels_annotations_enabled: true, + api_levels_jar_filename: "android.other.jar", + } + `, + map[string][]byte{ + "bar-doc/a.java": nil, + }) + testcases := []struct { + moduleName string + expectedJarFilename string + }{ + { + moduleName: "bar-stubs", + expectedJarFilename: "android.jar", + }, + { + moduleName: "bar-stubs-other", + expectedJarFilename: "android.other.jar", + }, + } + for _, c := range testcases { + m := ctx.ModuleForTests(c.moduleName, "android_common") + metalava := m.Rule("metalava") + expected := "--android-jar-pattern ./%/public/" + c.expectedJarFilename + if actual := metalava.RuleParams.Command; !strings.Contains(actual, expected) { + t.Errorf("For %q, expected metalava argument %q, but was not found %q", c.moduleName, expected, actual) + } + } +} + +func TestDroidstubsWithSystemModules(t *testing.T) { + ctx, _ := testJava(t, ` + droidstubs { + name: "stubs-source-system-modules", + srcs: [ + "bar-doc/a.java", + ], + sdk_version: "none", + system_modules: "source-system-modules", + } + + java_library { + name: "source-jar", + srcs: [ + "a.java", + ], + } + + java_system_modules { + name: "source-system-modules", + libs: ["source-jar"], + } + + droidstubs { + name: "stubs-prebuilt-system-modules", + srcs: [ + "bar-doc/a.java", + ], + sdk_version: "none", + system_modules: "prebuilt-system-modules", + } + + java_import { + name: "prebuilt-jar", + jars: ["a.jar"], + } + + java_system_modules_import { + name: "prebuilt-system-modules", + libs: ["prebuilt-jar"], + } + `) + + checkSystemModulesUseByDroidstubs(t, ctx, "stubs-source-system-modules", "source-jar.jar") + + checkSystemModulesUseByDroidstubs(t, ctx, "stubs-prebuilt-system-modules", "prebuilt-jar.jar") +} + +func checkSystemModulesUseByDroidstubs(t *testing.T, ctx *android.TestContext, moduleName string, systemJar string) { + metalavaRule := ctx.ModuleForTests(moduleName, "android_common").Rule("metalava") + var systemJars []string + for _, i := range metalavaRule.Implicits { + systemJars = append(systemJars, i.Base()) + } + if len(systemJars) < 1 || systemJars[0] != systemJar { + t.Errorf("inputs of %q must be []string{%q}, but was %#v.", moduleName, systemJar, systemJars) } } func TestJarGenrules(t *testing.T) { - ctx := testJava(t, ` + ctx, _ := testJava(t, ` java_library { name: "foo", srcs: ["a.java"], @@ -768,7 +1176,7 @@ func TestJarGenrules(t *testing.T) { } func TestExcludeFileGroupInSrcs(t *testing.T) { - ctx := testJava(t, ` + ctx, _ := testJava(t, ` java_library { name: "foo", srcs: ["a.java", ":foo-srcs"], @@ -793,9 +1201,22 @@ func TestExcludeFileGroupInSrcs(t *testing.T) { } } +func TestJavaLibrary(t *testing.T) { + config := testConfig(nil, "", map[string][]byte{ + "libcore/Android.bp": []byte(` + java_library { + name: "core", + sdk_version: "none", + system_modules: "none", + }`), + }) + ctx := testContext() + run(t, ctx, config) +} + func TestJavaSdkLibrary(t *testing.T) { - ctx := testJava(t, ` - droiddoc_template { + ctx, _ := testJava(t, ` + droiddoc_exported_dir { name: "droiddoc-templates-sdk", path: ".", } @@ -812,26 +1233,67 @@ func TestJavaSdkLibrary(t *testing.T) { java_library { name: "baz", srcs: ["c.java"], - libs: ["foo", "bar"], + libs: ["foo", "bar.stubs"], sdk_version: "system_current", } + java_sdk_library { + name: "barney", + srcs: ["c.java"], + api_only: true, + } + java_sdk_library { + name: "betty", + srcs: ["c.java"], + shared_library: false, + } + java_sdk_library_import { + name: "quuz", + public: { + jars: ["c.jar"], + }, + } + java_sdk_library_import { + name: "fred", + public: { + jars: ["b.jar"], + }, + } + java_sdk_library_import { + name: "wilma", + public: { + jars: ["b.jar"], + }, + shared_library: false, + } java_library { name: "qux", srcs: ["c.java"], - libs: ["baz"], + libs: ["baz", "fred", "quuz.stubs", "wilma", "barney", "betty"], sdk_version: "system_current", } + java_library { + name: "baz-test", + srcs: ["c.java"], + libs: ["foo"], + sdk_version: "test_current", + } + java_library { + name: "baz-29", + srcs: ["c.java"], + libs: ["foo"], + sdk_version: "system_29", + } `) // check the existence of the internal modules ctx.ModuleForTests("foo", "android_common") - ctx.ModuleForTests("foo"+sdkStubsLibrarySuffix, "android_common") - ctx.ModuleForTests("foo"+sdkStubsLibrarySuffix+sdkSystemApiSuffix, "android_common") - ctx.ModuleForTests("foo"+sdkStubsLibrarySuffix+sdkTestApiSuffix, "android_common") - ctx.ModuleForTests("foo"+sdkDocsSuffix, "android_common") - ctx.ModuleForTests("foo"+sdkDocsSuffix+sdkSystemApiSuffix, "android_common") - ctx.ModuleForTests("foo"+sdkDocsSuffix+sdkTestApiSuffix, "android_common") - ctx.ModuleForTests("foo"+sdkXmlFileSuffix, "android_arm64_armv8-a") + ctx.ModuleForTests(apiScopePublic.stubsLibraryModuleName("foo"), "android_common") + ctx.ModuleForTests(apiScopeSystem.stubsLibraryModuleName("foo"), "android_common") + ctx.ModuleForTests(apiScopeTest.stubsLibraryModuleName("foo"), "android_common") + ctx.ModuleForTests(apiScopePublic.stubsSourceModuleName("foo"), "android_common") + ctx.ModuleForTests(apiScopeSystem.stubsSourceModuleName("foo"), "android_common") + ctx.ModuleForTests(apiScopeTest.stubsSourceModuleName("foo"), "android_common") + ctx.ModuleForTests("foo"+sdkXmlFileSuffix, "android_common") ctx.ModuleForTests("foo.api.public.28", "") ctx.ModuleForTests("foo.api.system.28", "") ctx.ModuleForTests("foo.api.test.28", "") @@ -853,13 +1315,290 @@ func TestJavaSdkLibrary(t *testing.T) { "foo.stubs.jar") } + bazTestJavac := ctx.ModuleForTests("baz-test", "android_common").Rule("javac") + // tests if baz-test is actually linked to the test stubs lib + if !strings.Contains(bazTestJavac.Args["classpath"], "foo.stubs.test.jar") { + t.Errorf("baz-test javac classpath %v does not contain %q", bazTestJavac.Args["classpath"], + "foo.stubs.test.jar") + } + + baz29Javac := ctx.ModuleForTests("baz-29", "android_common").Rule("javac") + // tests if baz-29 is actually linked to the system 29 stubs lib + if !strings.Contains(baz29Javac.Args["classpath"], "prebuilts/sdk/29/system/foo.jar") { + t.Errorf("baz-29 javac classpath %v does not contain %q", baz29Javac.Args["classpath"], + "prebuilts/sdk/29/system/foo.jar") + } + // test if baz has exported SDK lib names foo and bar to qux qux := ctx.ModuleForTests("qux", "android_common") if quxLib, ok := qux.Module().(*Library); ok { sdkLibs := quxLib.ExportedSdkLibs() - if len(sdkLibs) != 2 || !android.InList("foo", sdkLibs) || !android.InList("bar", sdkLibs) { - t.Errorf("qux should export \"foo\" and \"bar\" but exports %v", sdkLibs) + sort.Strings(sdkLibs) + if w := []string{"bar", "foo", "fred", "quuz"}; !reflect.DeepEqual(w, sdkLibs) { + t.Errorf("qux should export %q but exports %q", w, sdkLibs) + } + } +} + +func TestJavaSdkLibrary_DoNotAccessImplWhenItIsNotBuilt(t *testing.T) { + ctx, _ := testJava(t, ` + java_sdk_library { + name: "foo", + srcs: ["a.java"], + api_only: true, + public: { + enabled: true, + }, + } + + java_library { + name: "bar", + srcs: ["b.java"], + libs: ["foo"], } + `) + + // The bar library should depend on the stubs jar. + barLibrary := ctx.ModuleForTests("bar", "android_common").Rule("javac") + if expected, actual := `^-classpath .*:/[^:]*/turbine-combined/foo\.stubs\.jar$`, barLibrary.Args["classpath"]; !regexp.MustCompile(expected).MatchString(actual) { + t.Errorf("expected %q, found %#q", expected, actual) + } +} + +func TestJavaSdkLibrary_UseSourcesFromAnotherSdkLibrary(t *testing.T) { + testJava(t, ` + java_sdk_library { + name: "foo", + srcs: ["a.java"], + api_packages: ["foo"], + public: { + enabled: true, + }, + } + + java_library { + name: "bar", + srcs: ["b.java", ":foo{.public.stubs.source}"], + } + `) +} + +func TestJavaSdkLibrary_AccessOutputFiles_MissingScope(t *testing.T) { + testJavaError(t, `"foo" does not provide api scope system`, ` + java_sdk_library { + name: "foo", + srcs: ["a.java"], + api_packages: ["foo"], + public: { + enabled: true, + }, + } + + java_library { + name: "bar", + srcs: ["b.java", ":foo{.system.stubs.source}"], + } + `) +} + +func TestJavaSdkLibraryImport_AccessOutputFiles(t *testing.T) { + testJava(t, ` + java_sdk_library_import { + name: "foo", + public: { + jars: ["a.jar"], + stub_srcs: ["a.java"], + current_api: "api/current.txt", + removed_api: "api/removed.txt", + }, + } + + java_library { + name: "bar", + srcs: [":foo{.public.stubs.source}"], + java_resources: [ + ":foo{.public.api.txt}", + ":foo{.public.removed-api.txt}", + ], + } + `) +} + +func TestJavaSdkLibraryImport_AccessOutputFiles_Invalid(t *testing.T) { + bp := ` + java_sdk_library_import { + name: "foo", + public: { + jars: ["a.jar"], + }, + } + ` + + t.Run("stubs.source", func(t *testing.T) { + testJavaError(t, `stubs.source not available for api scope public`, bp+` + java_library { + name: "bar", + srcs: [":foo{.public.stubs.source}"], + java_resources: [ + ":foo{.public.api.txt}", + ":foo{.public.removed-api.txt}", + ], + } + `) + }) + + t.Run("api.txt", func(t *testing.T) { + testJavaError(t, `api.txt not available for api scope public`, bp+` + java_library { + name: "bar", + srcs: ["a.java"], + java_resources: [ + ":foo{.public.api.txt}", + ], + } + `) + }) + + t.Run("removed-api.txt", func(t *testing.T) { + testJavaError(t, `removed-api.txt not available for api scope public`, bp+` + java_library { + name: "bar", + srcs: ["a.java"], + java_resources: [ + ":foo{.public.removed-api.txt}", + ], + } + `) + }) +} + +func TestJavaSdkLibrary_InvalidScopes(t *testing.T) { + testJavaError(t, `module "foo": enabled api scope "system" depends on disabled scope "public"`, ` + java_sdk_library { + name: "foo", + srcs: ["a.java", "b.java"], + api_packages: ["foo"], + // Explicitly disable public to test the check that ensures the set of enabled + // scopes is consistent. + public: { + enabled: false, + }, + system: { + enabled: true, + }, + } + `) +} + +func TestJavaSdkLibrary_SdkVersion_ForScope(t *testing.T) { + testJava(t, ` + java_sdk_library { + name: "foo", + srcs: ["a.java", "b.java"], + api_packages: ["foo"], + system: { + enabled: true, + sdk_version: "module_current", + }, + } + `) +} + +func TestJavaSdkLibrary_ModuleLib(t *testing.T) { + testJava(t, ` + java_sdk_library { + name: "foo", + srcs: ["a.java", "b.java"], + api_packages: ["foo"], + system: { + enabled: true, + }, + module_lib: { + enabled: true, + }, + } + `) +} + +func TestJavaSdkLibrary_SystemServer(t *testing.T) { + testJava(t, ` + java_sdk_library { + name: "foo", + srcs: ["a.java", "b.java"], + api_packages: ["foo"], + system: { + enabled: true, + }, + system_server: { + enabled: true, + }, + } + `) +} + +func TestJavaSdkLibrary_MissingScope(t *testing.T) { + testJavaError(t, `requires api scope module-lib from foo but it only has \[\] available`, ` + java_sdk_library { + name: "foo", + srcs: ["a.java"], + public: { + enabled: false, + }, + } + + java_library { + name: "baz", + srcs: ["a.java"], + libs: ["foo"], + sdk_version: "module_current", + } + `) +} + +func TestJavaSdkLibrary_FallbackScope(t *testing.T) { + testJava(t, ` + java_sdk_library { + name: "foo", + srcs: ["a.java"], + system: { + enabled: true, + }, + } + + java_library { + name: "baz", + srcs: ["a.java"], + libs: ["foo"], + // foo does not have module-lib scope so it should fallback to system + sdk_version: "module_current", + } + `) +} + +func TestJavaSdkLibrary_DefaultToStubs(t *testing.T) { + ctx, _ := testJava(t, ` + java_sdk_library { + name: "foo", + srcs: ["a.java"], + system: { + enabled: true, + }, + default_to_stubs: true, + } + + java_library { + name: "baz", + srcs: ["a.java"], + libs: ["foo"], + // does not have sdk_version set, should fallback to module, + // which will then fallback to system because the module scope + // is not enabled. + } + `) + // The baz library should depend on the system stubs jar. + bazLibrary := ctx.ModuleForTests("baz", "android_common").Rule("javac") + if expected, actual := `^-classpath .*:/[^:]*/turbine-combined/foo\.stubs.system\.jar$`, bazLibrary.Args["classpath"]; !regexp.MustCompile(expected).MatchString(actual) { + t.Errorf("expected %q, found %#q", expected, actual) } } @@ -942,46 +1681,186 @@ func checkPatchModuleFlag(t *testing.T, ctx *android.TestContext, moduleName str } func TestPatchModule(t *testing.T) { - bp := ` - java_library { - name: "foo", - srcs: ["a.java"], - } - - java_library { - name: "bar", - srcs: ["b.java"], - no_standard_libs: true, - system_modules: "none", - patch_module: "java.base", - } + t.Run("Java language level 8", func(t *testing.T) { + // Test with legacy javac -source 1.8 -target 1.8 + bp := ` + java_library { + name: "foo", + srcs: ["a.java"], + java_version: "1.8", + } - java_library { - name: "baz", - srcs: ["c.java"], - patch_module: "java.base", - } - ` + java_library { + name: "bar", + srcs: ["b.java"], + sdk_version: "none", + system_modules: "none", + patch_module: "java.base", + java_version: "1.8", + } - t.Run("1.8", func(t *testing.T) { - // Test default javac 1.8 - ctx := testJava(t, bp) + java_library { + name: "baz", + srcs: ["c.java"], + patch_module: "java.base", + java_version: "1.8", + } + ` + ctx, _ := testJava(t, bp) checkPatchModuleFlag(t, ctx, "foo", "") checkPatchModuleFlag(t, ctx, "bar", "") checkPatchModuleFlag(t, ctx, "baz", "") }) - t.Run("1.9", func(t *testing.T) { - // Test again with javac 1.9 - config := testConfig(map[string]string{"EXPERIMENTAL_USE_OPENJDK9": "true"}) - ctx := testContext(config, bp, nil) - run(t, ctx, config) + t.Run("Java language level 9", func(t *testing.T) { + // Test with default javac -source 9 -target 9 + bp := ` + java_library { + name: "foo", + srcs: ["a.java"], + } + + java_library { + name: "bar", + srcs: ["b.java"], + sdk_version: "none", + system_modules: "none", + patch_module: "java.base", + } + + java_library { + name: "baz", + srcs: ["c.java"], + patch_module: "java.base", + } + ` + ctx, _ := testJava(t, bp) checkPatchModuleFlag(t, ctx, "foo", "") expected := "java.base=.:" + buildDir checkPatchModuleFlag(t, ctx, "bar", expected) - expected = "java.base=" + strings.Join([]string{".", buildDir, moduleToPath("ext"), moduleToPath("framework"), moduleToPath("updatable_media_stubs")}, ":") + expected = "java.base=" + strings.Join([]string{".", buildDir, moduleToPath("ext"), moduleToPath("framework")}, ":") checkPatchModuleFlag(t, ctx, "baz", expected) }) } + +func TestJavaSystemModules(t *testing.T) { + ctx, _ := testJava(t, ` + java_system_modules { + name: "system-modules", + libs: ["system-module1", "system-module2"], + } + java_library { + name: "system-module1", + srcs: ["a.java"], + sdk_version: "none", + system_modules: "none", + } + java_library { + name: "system-module2", + srcs: ["b.java"], + sdk_version: "none", + system_modules: "none", + } + `) + + // check the existence of the module + systemModules := ctx.ModuleForTests("system-modules", "android_common") + + cmd := systemModules.Rule("jarsTosystemModules") + + // make sure the command compiles against the supplied modules. + for _, module := range []string{"system-module1.jar", "system-module2.jar"} { + if !strings.Contains(cmd.Args["classpath"], module) { + t.Errorf("system modules classpath %v does not contain %q", cmd.Args["classpath"], + module) + } + } +} + +func TestJavaSystemModulesImport(t *testing.T) { + ctx, _ := testJava(t, ` + java_system_modules_import { + name: "system-modules", + libs: ["system-module1", "system-module2"], + } + java_import { + name: "system-module1", + jars: ["a.jar"], + } + java_import { + name: "system-module2", + jars: ["b.jar"], + } + `) + + // check the existence of the module + systemModules := ctx.ModuleForTests("system-modules", "android_common") + + cmd := systemModules.Rule("jarsTosystemModules") + + // make sure the command compiles against the supplied modules. + for _, module := range []string{"system-module1.jar", "system-module2.jar"} { + if !strings.Contains(cmd.Args["classpath"], module) { + t.Errorf("system modules classpath %v does not contain %q", cmd.Args["classpath"], + module) + } + } +} + +func TestJavaLibraryWithSystemModules(t *testing.T) { + ctx, _ := testJava(t, ` + java_library { + name: "lib-with-source-system-modules", + srcs: [ + "a.java", + ], + sdk_version: "none", + system_modules: "source-system-modules", + } + + java_library { + name: "source-jar", + srcs: [ + "a.java", + ], + } + + java_system_modules { + name: "source-system-modules", + libs: ["source-jar"], + } + + java_library { + name: "lib-with-prebuilt-system-modules", + srcs: [ + "a.java", + ], + sdk_version: "none", + system_modules: "prebuilt-system-modules", + } + + java_import { + name: "prebuilt-jar", + jars: ["a.jar"], + } + + java_system_modules_import { + name: "prebuilt-system-modules", + libs: ["prebuilt-jar"], + } + `) + + checkBootClasspathForSystemModule(t, ctx, "lib-with-source-system-modules", "/source-jar.jar") + + checkBootClasspathForSystemModule(t, ctx, "lib-with-prebuilt-system-modules", "/prebuilt-jar.jar") +} + +func checkBootClasspathForSystemModule(t *testing.T, ctx *android.TestContext, moduleName string, expectedSuffix string) { + javacRule := ctx.ModuleForTests(moduleName, "android_common").Rule("javac") + bootClasspath := javacRule.Args["bootClasspath"] + if strings.HasPrefix(bootClasspath, "--system ") && strings.HasSuffix(bootClasspath, expectedSuffix) { + t.Errorf("bootclasspath of %q must start with --system and end with %q, but was %#v.", moduleName, expectedSuffix, bootClasspath) + } +} diff --git a/java/jdeps.go b/java/jdeps.go index 18498befc..4f636a59f 100644 --- a/java/jdeps.go +++ b/java/jdeps.go @@ -17,7 +17,6 @@ package java import ( "encoding/json" "fmt" - "os" "android/soong/android" ) @@ -35,8 +34,11 @@ func jDepsGeneratorSingleton() android.Singleton { } type jdepsGeneratorSingleton struct { + outputPath android.Path } +var _ android.SingletonMakeVarsProvider = (*jdepsGeneratorSingleton)(nil) + const ( // Environment variables used to modify behavior of this singleton. envVariableCollectJavaDeps = "SOONG_COLLECT_JAVA_DEPS" @@ -72,6 +74,7 @@ func (j *jdepsGeneratorSingleton) GenerateBuildActions(ctx android.SingletonCont dpInfo.Aidl_include_dirs = android.FirstUniqueStrings(dpInfo.Aidl_include_dirs) dpInfo.Jarjar_rules = android.FirstUniqueStrings(dpInfo.Jarjar_rules) dpInfo.Jars = android.FirstUniqueStrings(dpInfo.Jars) + dpInfo.SrcJars = android.FirstUniqueStrings(dpInfo.SrcJars) moduleInfos[name] = dpInfo mkProvider, ok := module.(android.AndroidMkDataProvider) @@ -91,23 +94,36 @@ func (j *jdepsGeneratorSingleton) GenerateBuildActions(ctx android.SingletonCont moduleInfos[name] = dpInfo }) - jfpath := android.PathForOutput(ctx, jdepsJsonFileName).String() + jfpath := android.PathForOutput(ctx, jdepsJsonFileName) err := createJsonFile(moduleInfos, jfpath) if err != nil { ctx.Errorf(err.Error()) } + j.outputPath = jfpath + + // This is necessary to satisfy the dangling rules check as this file is written by Soong rather than a rule. + ctx.Build(pctx, android.BuildParams{ + Rule: android.Touch, + Output: jfpath, + }) } -func createJsonFile(moduleInfos map[string]android.IdeInfo, jfpath string) error { - file, err := os.Create(jfpath) - if err != nil { - return fmt.Errorf("Failed to create file: %s, relative: %v", jdepsJsonFileName, err) +func (j *jdepsGeneratorSingleton) MakeVars(ctx android.MakeVarsContext) { + if j.outputPath == nil { + return } - defer file.Close() + + ctx.DistForGoal("general-tests", j.outputPath) +} + +func createJsonFile(moduleInfos map[string]android.IdeInfo, jfpath android.WritablePath) error { buf, err := json.MarshalIndent(moduleInfos, "", "\t") if err != nil { - return fmt.Errorf("Write file failed: %s, relative: %v", jdepsJsonFileName, err) + return fmt.Errorf("JSON marshal of java deps failed: %s", err) + } + err = android.WriteFileToOutputDir(jfpath, buf, 0666) + if err != nil { + return fmt.Errorf("Writing java deps to %s failed: %s", jfpath.String(), err) } - fmt.Fprintf(file, string(buf)) return nil } diff --git a/java/kotlin.go b/java/kotlin.go index 58dc64cd1..673970b9c 100644 --- a/java/kotlin.go +++ b/java/kotlin.go @@ -18,6 +18,7 @@ import ( "bytes" "encoding/base64" "encoding/binary" + "path/filepath" "strings" "android/soong/android" @@ -27,16 +28,23 @@ import ( var kotlinc = pctx.AndroidRemoteStaticRule("kotlinc", android.RemoteRuleSupports{Goma: true}, blueprint.RuleParams{ - Command: `rm -rf "$classesDir" "$srcJarDir" "$kotlinBuildFile" && mkdir -p "$classesDir" "$srcJarDir" && ` + + Command: `rm -rf "$classesDir" "$srcJarDir" "$kotlinBuildFile" "$emptyDir" && ` + + `mkdir -p "$classesDir" "$srcJarDir" "$emptyDir" && ` + `${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` + - `${config.GenKotlinBuildFileCmd} $classpath $classesDir $out.rsp $srcJarDir/list > $kotlinBuildFile &&` + + `${config.GenKotlinBuildFileCmd} $classpath "$name" $classesDir $out.rsp $srcJarDir/list > $kotlinBuildFile &&` + `${config.KotlincCmd} ${config.JavacHeapFlags} $kotlincFlags ` + - `-jvm-target $kotlinJvmTarget -Xbuild-file=$kotlinBuildFile && ` + + `-jvm-target $kotlinJvmTarget -Xbuild-file=$kotlinBuildFile -kotlin-home $emptyDir && ` + `${config.SoongZipCmd} -jar -o $out -C $classesDir -D $classesDir && ` + `rm -rf "$srcJarDir"`, CommandDeps: []string{ "${config.KotlincCmd}", "${config.KotlinCompilerJar}", + "${config.KotlinPreloaderJar}", + "${config.KotlinReflectJar}", + "${config.KotlinScriptRuntimeJar}", + "${config.KotlinStdlibJar}", + "${config.KotlinTrove4jJar}", + "${config.KotlinAnnotationJar}", "${config.GenKotlinBuildFileCmd}", "${config.SoongZipCmd}", "${config.ZipSyncCmd}", @@ -44,7 +52,8 @@ var kotlinc = pctx.AndroidRemoteStaticRule("kotlinc", android.RemoteRuleSupports Rspfile: "$out.rsp", RspfileContent: `$in`, }, - "kotlincFlags", "classpath", "srcJars", "srcJarDir", "classesDir", "kotlinJvmTarget", "kotlinBuildFile") + "kotlincFlags", "classpath", "srcJars", "srcJarDir", "classesDir", "kotlinJvmTarget", "kotlinBuildFile", + "emptyDir", "name") // kotlinCompile takes .java and .kt sources and srcJars, and compiles the .kt sources into a classes jar in outputFile. func kotlinCompile(ctx android.ModuleContext, outputFile android.WritablePath, @@ -55,6 +64,9 @@ func kotlinCompile(ctx android.ModuleContext, outputFile android.WritablePath, deps = append(deps, flags.kotlincClasspath...) deps = append(deps, srcJars...) + kotlinName := filepath.Join(ctx.ModuleDir(), ctx.ModuleSubDir(), ctx.ModuleName()) + kotlinName = strings.ReplaceAll(kotlinName, "/", "__") + ctx.Build(pctx, android.BuildParams{ Rule: kotlinc, Description: "kotlinc", @@ -68,17 +80,20 @@ func kotlinCompile(ctx android.ModuleContext, outputFile android.WritablePath, "classesDir": android.PathForModuleOut(ctx, "kotlinc", "classes").String(), "srcJarDir": android.PathForModuleOut(ctx, "kotlinc", "srcJars").String(), "kotlinBuildFile": android.PathForModuleOut(ctx, "kotlinc-build.xml").String(), + "emptyDir": android.PathForModuleOut(ctx, "kotlinc", "empty").String(), // http://b/69160377 kotlinc only supports -jvm-target 1.6 and 1.8 "kotlinJvmTarget": "1.8", + "name": kotlinName, }, }) } var kapt = pctx.AndroidRemoteStaticRule("kapt", android.RemoteRuleSupports{Goma: true}, blueprint.RuleParams{ - Command: `rm -rf "$srcJarDir" "$kotlinBuildFile" "$kaptDir" && mkdir -p "$srcJarDir" "$kaptDir" && ` + + Command: `rm -rf "$srcJarDir" "$kotlinBuildFile" "$kaptDir" && ` + + `mkdir -p "$srcJarDir" "$kaptDir/sources" "$kaptDir/classes" && ` + `${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` + - `${config.GenKotlinBuildFileCmd} $classpath "" $out.rsp $srcJarDir/list > $kotlinBuildFile &&` + + `${config.GenKotlinBuildFileCmd} $classpath "$name" "" $out.rsp $srcJarDir/list > $kotlinBuildFile &&` + `${config.KotlincCmd} ${config.KotlincSuppressJDK9Warnings} ${config.JavacHeapFlags} $kotlincFlags ` + `-Xplugin=${config.KotlinKaptJar} ` + `-P plugin:org.jetbrains.kotlin.kapt3:sources=$kaptDir/sources ` + @@ -91,6 +106,7 @@ var kapt = pctx.AndroidRemoteStaticRule("kapt", android.RemoteRuleSupports{Goma: `$kaptProcessor ` + `-Xbuild-file=$kotlinBuildFile && ` + `${config.SoongZipCmd} -jar -o $out -C $kaptDir/sources -D $kaptDir/sources && ` + + `${config.SoongZipCmd} -jar -o $classesJarOut -C $kaptDir/classes -D $kaptDir/classes && ` + `rm -rf "$srcJarDir"`, CommandDeps: []string{ "${config.KotlincCmd}", @@ -104,13 +120,14 @@ var kapt = pctx.AndroidRemoteStaticRule("kapt", android.RemoteRuleSupports{Goma: RspfileContent: `$in`, }, "kotlincFlags", "encodedJavacFlags", "kaptProcessorPath", "kaptProcessor", - "classpath", "srcJars", "srcJarDir", "kaptDir", "kotlinJvmTarget", "kotlinBuildFile") + "classpath", "srcJars", "srcJarDir", "kaptDir", "kotlinJvmTarget", "kotlinBuildFile", "name", + "classesJarOut") // kotlinKapt performs Kotlin-compatible annotation processing. It takes .kt and .java sources and srcjars, and runs // annotation processors over all of them, producing a srcjar of generated code in outputFile. The srcjar should be // added as an additional input to kotlinc and javac rules, and the javac rule should have annotation processing // disabled. -func kotlinKapt(ctx android.ModuleContext, outputFile android.WritablePath, +func kotlinKapt(ctx android.ModuleContext, srcJarOutputFile, resJarOutputFile android.WritablePath, srcFiles, srcJars android.Paths, flags javaBuilderFlags) { @@ -119,24 +136,31 @@ func kotlinKapt(ctx android.ModuleContext, outputFile android.WritablePath, deps = append(deps, srcJars...) deps = append(deps, flags.processorPath...) - kaptProcessorPath := flags.processorPath.FormTurbineClasspath("-P plugin:org.jetbrains.kotlin.kapt3:apclasspath=") + kaptProcessorPath := flags.processorPath.FormRepeatedClassPath("-P plugin:org.jetbrains.kotlin.kapt3:apclasspath=") kaptProcessor := "" - if flags.processor != "" { - kaptProcessor = "-P plugin:org.jetbrains.kotlin.kapt3:processors=" + flags.processor + for i, p := range flags.processors { + if i > 0 { + kaptProcessor += " " + } + kaptProcessor += "-P plugin:org.jetbrains.kotlin.kapt3:processors=" + p } encodedJavacFlags := kaptEncodeFlags([][2]string{ - {"-source", flags.javaVersion}, - {"-target", flags.javaVersion}, + {"-source", flags.javaVersion.String()}, + {"-target", flags.javaVersion.String()}, }) + kotlinName := filepath.Join(ctx.ModuleDir(), ctx.ModuleSubDir(), ctx.ModuleName()) + kotlinName = strings.ReplaceAll(kotlinName, "/", "__") + ctx.Build(pctx, android.BuildParams{ - Rule: kapt, - Description: "kapt", - Output: outputFile, - Inputs: srcFiles, - Implicits: deps, + Rule: kapt, + Description: "kapt", + Output: srcJarOutputFile, + ImplicitOutput: resJarOutputFile, + Inputs: srcFiles, + Implicits: deps, Args: map[string]string{ "classpath": flags.kotlincClasspath.FormJavaClassPath("-classpath"), "kotlincFlags": flags.kotlincFlags, @@ -147,6 +171,8 @@ func kotlinKapt(ctx android.ModuleContext, outputFile android.WritablePath, "kaptProcessor": kaptProcessor, "kaptDir": android.PathForModuleOut(ctx, "kapt/gen").String(), "encodedJavacFlags": encodedJavacFlags, + "name": kotlinName, + "classesJarOut": resJarOutputFile.String(), }, }) } diff --git a/java/kotlin_test.go b/java/kotlin_test.go index e0eb0c06d..60ca1c476 100644 --- a/java/kotlin_test.go +++ b/java/kotlin_test.go @@ -22,7 +22,7 @@ import ( ) func TestKotlin(t *testing.T) { - ctx := testJava(t, ` + ctx, _ := testJava(t, ` java_library { name: "foo", srcs: ["a.java", "b.kt"], @@ -84,11 +84,11 @@ func TestKotlin(t *testing.T) { } func TestKapt(t *testing.T) { - ctx := testJava(t, ` + ctx, _ := testJava(t, ` java_library { name: "foo", srcs: ["a.java", "b.kt"], - plugins: ["bar"], + plugins: ["bar", "baz"], } java_plugin { @@ -96,6 +96,12 @@ func TestKapt(t *testing.T) { processor_class: "com.bar", srcs: ["b.java"], } + + java_plugin { + name: "baz", + processor_class: "com.baz", + srcs: ["b.java"], + } `) buildOS := android.BuildOs.String() @@ -105,6 +111,7 @@ func TestKapt(t *testing.T) { javac := ctx.ModuleForTests("foo", "android_common").Rule("javac") bar := ctx.ModuleForTests("bar", buildOS+"_common").Rule("javac").Output.String() + baz := ctx.ModuleForTests("baz", buildOS+"_common").Rule("javac").Output.String() // Test that the kotlin and java sources are passed to kapt and kotlinc if len(kapt.Inputs) != 2 || kapt.Inputs[0].String() != "a.java" || kapt.Inputs[1].String() != "b.kt" { @@ -136,11 +143,12 @@ func TestKapt(t *testing.T) { } // Test that the processors are passed to kapt - expectedProcessorPath := "-P plugin:org.jetbrains.kotlin.kapt3:apclasspath=" + bar + expectedProcessorPath := "-P plugin:org.jetbrains.kotlin.kapt3:apclasspath=" + bar + + " -P plugin:org.jetbrains.kotlin.kapt3:apclasspath=" + baz if kapt.Args["kaptProcessorPath"] != expectedProcessorPath { t.Errorf("expected kaptProcessorPath %q, got %q", expectedProcessorPath, kapt.Args["kaptProcessorPath"]) } - expectedProcessor := "-P plugin:org.jetbrains.kotlin.kapt3:processors=com.bar" + expectedProcessor := "-P plugin:org.jetbrains.kotlin.kapt3:processors=com.bar -P plugin:org.jetbrains.kotlin.kapt3:processors=com.baz" if kapt.Args["kaptProcessor"] != expectedProcessor { t.Errorf("expected kaptProcessor %q, got %q", expectedProcessor, kapt.Args["kaptProcessor"]) } diff --git a/java/lint.go b/java/lint.go new file mode 100644 index 000000000..639106793 --- /dev/null +++ b/java/lint.go @@ -0,0 +1,519 @@ +// Copyright 2020 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package java + +import ( + "fmt" + "sort" + "strings" + + "android/soong/android" +) + +type LintProperties struct { + // Controls for running Android Lint on the module. + Lint struct { + + // If true, run Android Lint on the module. Defaults to true. + Enabled *bool + + // Flags to pass to the Android Lint tool. + Flags []string + + // Checks that should be treated as fatal. + Fatal_checks []string + + // Checks that should be treated as errors. + Error_checks []string + + // Checks that should be treated as warnings. + Warning_checks []string + + // Checks that should be skipped. + Disabled_checks []string + + // Modules that provide extra lint checks + Extra_check_modules []string + } +} + +type linter struct { + name string + manifest android.Path + mergedManifest android.Path + srcs android.Paths + srcJars android.Paths + resources android.Paths + classpath android.Paths + classes android.Path + extraLintCheckJars android.Paths + test bool + library bool + minSdkVersion string + targetSdkVersion string + compileSdkVersion string + javaLanguageLevel string + kotlinLanguageLevel string + outputs lintOutputs + properties LintProperties + + reports android.Paths + + buildModuleReportZip bool +} + +type lintOutputs struct { + html android.Path + text android.Path + xml android.Path + + depSets LintDepSets +} + +type lintOutputsIntf interface { + lintOutputs() *lintOutputs +} + +type lintDepSetsIntf interface { + LintDepSets() LintDepSets +} + +type LintDepSets struct { + HTML, Text, XML *android.DepSet +} + +type LintDepSetsBuilder struct { + HTML, Text, XML *android.DepSetBuilder +} + +func NewLintDepSetBuilder() LintDepSetsBuilder { + return LintDepSetsBuilder{ + HTML: android.NewDepSetBuilder(android.POSTORDER), + Text: android.NewDepSetBuilder(android.POSTORDER), + XML: android.NewDepSetBuilder(android.POSTORDER), + } +} + +func (l LintDepSetsBuilder) Direct(html, text, xml android.Path) LintDepSetsBuilder { + l.HTML.Direct(html) + l.Text.Direct(text) + l.XML.Direct(xml) + return l +} + +func (l LintDepSetsBuilder) Transitive(depSets LintDepSets) LintDepSetsBuilder { + if depSets.HTML != nil { + l.HTML.Transitive(depSets.HTML) + } + if depSets.Text != nil { + l.Text.Transitive(depSets.Text) + } + if depSets.XML != nil { + l.XML.Transitive(depSets.XML) + } + return l +} + +func (l LintDepSetsBuilder) Build() LintDepSets { + return LintDepSets{ + HTML: l.HTML.Build(), + Text: l.Text.Build(), + XML: l.XML.Build(), + } +} + +func (l *linter) LintDepSets() LintDepSets { + return l.outputs.depSets +} + +var _ lintDepSetsIntf = (*linter)(nil) + +var _ lintOutputsIntf = (*linter)(nil) + +func (l *linter) lintOutputs() *lintOutputs { + return &l.outputs +} + +func (l *linter) enabled() bool { + return BoolDefault(l.properties.Lint.Enabled, true) +} + +func (l *linter) deps(ctx android.BottomUpMutatorContext) { + if !l.enabled() { + return + } + + extraCheckModules := l.properties.Lint.Extra_check_modules + + if checkOnly := ctx.Config().Getenv("ANDROID_LINT_CHECK"); checkOnly != "" { + if checkOnlyModules := ctx.Config().Getenv("ANDROID_LINT_CHECK_EXTRA_MODULES"); checkOnlyModules != "" { + extraCheckModules = strings.Split(checkOnlyModules, ",") + } + } + + ctx.AddFarVariationDependencies(ctx.Config().BuildOSCommonTarget.Variations(), + extraLintCheckTag, extraCheckModules...) +} + +func (l *linter) writeLintProjectXML(ctx android.ModuleContext, + rule *android.RuleBuilder) (projectXMLPath, configXMLPath, cacheDir, homeDir android.WritablePath, deps android.Paths) { + + var resourcesList android.WritablePath + if len(l.resources) > 0 { + // The list of resources may be too long to put on the command line, but + // we can't use the rsp file because it is already being used for srcs. + // Insert a second rule to write out the list of resources to a file. + resourcesList = android.PathForModuleOut(ctx, "lint", "resources.list") + resListRule := android.NewRuleBuilder() + resListRule.Command().Text("cp").FlagWithRspFileInputList("", l.resources).Output(resourcesList) + resListRule.Build(pctx, ctx, "lint_resources_list", "lint resources list") + deps = append(deps, l.resources...) + } + + projectXMLPath = android.PathForModuleOut(ctx, "lint", "project.xml") + // Lint looks for a lint.xml file next to the project.xml file, give it one. + configXMLPath = android.PathForModuleOut(ctx, "lint", "lint.xml") + cacheDir = android.PathForModuleOut(ctx, "lint", "cache") + homeDir = android.PathForModuleOut(ctx, "lint", "home") + + srcJarDir := android.PathForModuleOut(ctx, "lint-srcjars") + srcJarList := zipSyncCmd(ctx, rule, srcJarDir, l.srcJars) + + cmd := rule.Command(). + BuiltTool(ctx, "lint-project-xml"). + FlagWithOutput("--project_out ", projectXMLPath). + FlagWithOutput("--config_out ", configXMLPath). + FlagWithArg("--name ", ctx.ModuleName()) + + if l.library { + cmd.Flag("--library") + } + if l.test { + cmd.Flag("--test") + } + if l.manifest != nil { + deps = append(deps, l.manifest) + cmd.FlagWithArg("--manifest ", l.manifest.String()) + } + if l.mergedManifest != nil { + deps = append(deps, l.mergedManifest) + cmd.FlagWithArg("--merged_manifest ", l.mergedManifest.String()) + } + + // TODO(ccross): some of the files in l.srcs are generated sources and should be passed to + // lint separately. + cmd.FlagWithRspFileInputList("--srcs ", l.srcs) + deps = append(deps, l.srcs...) + + cmd.FlagWithInput("--generated_srcs ", srcJarList) + deps = append(deps, l.srcJars...) + + if resourcesList != nil { + cmd.FlagWithInput("--resources ", resourcesList) + } + + if l.classes != nil { + deps = append(deps, l.classes) + cmd.FlagWithArg("--classes ", l.classes.String()) + } + + cmd.FlagForEachArg("--classpath ", l.classpath.Strings()) + deps = append(deps, l.classpath...) + + cmd.FlagForEachArg("--extra_checks_jar ", l.extraLintCheckJars.Strings()) + deps = append(deps, l.extraLintCheckJars...) + + cmd.FlagWithArg("--root_dir ", "$PWD") + + // The cache tag in project.xml is relative to the root dir, or the project.xml file if + // the root dir is not set. + cmd.FlagWithArg("--cache_dir ", cacheDir.String()) + + cmd.FlagWithInput("@", + android.PathForSource(ctx, "build/soong/java/lint_defaults.txt")) + + cmd.FlagForEachArg("--disable_check ", l.properties.Lint.Disabled_checks) + cmd.FlagForEachArg("--warning_check ", l.properties.Lint.Warning_checks) + cmd.FlagForEachArg("--error_check ", l.properties.Lint.Error_checks) + cmd.FlagForEachArg("--fatal_check ", l.properties.Lint.Fatal_checks) + + return projectXMLPath, configXMLPath, cacheDir, homeDir, deps +} + +// generateManifest adds a command to the rule to write a dummy manifest cat contains the +// minSdkVersion and targetSdkVersion for modules (like java_library) that don't have a manifest. +func (l *linter) generateManifest(ctx android.ModuleContext, rule *android.RuleBuilder) android.Path { + manifestPath := android.PathForModuleOut(ctx, "lint", "AndroidManifest.xml") + + rule.Command().Text("("). + Text(`echo "<?xml version='1.0' encoding='utf-8'?>" &&`). + Text(`echo "<manifest xmlns:android='http://schemas.android.com/apk/res/android'" &&`). + Text(`echo " android:versionCode='1' android:versionName='1' >" &&`). + Textf(`echo " <uses-sdk android:minSdkVersion='%s' android:targetSdkVersion='%s'/>" &&`, + l.minSdkVersion, l.targetSdkVersion). + Text(`echo "</manifest>"`). + Text(") >").Output(manifestPath) + + return manifestPath +} + +func (l *linter) lint(ctx android.ModuleContext) { + if !l.enabled() { + return + } + + extraLintCheckModules := ctx.GetDirectDepsWithTag(extraLintCheckTag) + for _, extraLintCheckModule := range extraLintCheckModules { + if dep, ok := extraLintCheckModule.(Dependency); ok { + l.extraLintCheckJars = append(l.extraLintCheckJars, dep.ImplementationAndResourcesJars()...) + } else { + ctx.PropertyErrorf("lint.extra_check_modules", + "%s is not a java module", ctx.OtherModuleName(extraLintCheckModule)) + } + } + + rule := android.NewRuleBuilder() + + if l.manifest == nil { + manifest := l.generateManifest(ctx, rule) + l.manifest = manifest + } + + projectXML, lintXML, cacheDir, homeDir, deps := l.writeLintProjectXML(ctx, rule) + + html := android.PathForModuleOut(ctx, "lint-report.html") + text := android.PathForModuleOut(ctx, "lint-report.txt") + xml := android.PathForModuleOut(ctx, "lint-report.xml") + + depSetsBuilder := NewLintDepSetBuilder().Direct(html, text, xml) + + ctx.VisitDirectDepsWithTag(staticLibTag, func(dep android.Module) { + if depLint, ok := dep.(lintDepSetsIntf); ok { + depSetsBuilder.Transitive(depLint.LintDepSets()) + } + }) + + rule.Command().Text("rm -rf").Flag(cacheDir.String()).Flag(homeDir.String()) + rule.Command().Text("mkdir -p").Flag(cacheDir.String()).Flag(homeDir.String()) + + var annotationsZipPath, apiVersionsXMLPath android.Path + if ctx.Config().UnbundledBuildUsePrebuiltSdks() { + annotationsZipPath = android.PathForSource(ctx, "prebuilts/sdk/current/public/data/annotations.zip") + apiVersionsXMLPath = android.PathForSource(ctx, "prebuilts/sdk/current/public/data/api-versions.xml") + } else { + annotationsZipPath = copiedAnnotationsZipPath(ctx) + apiVersionsXMLPath = copiedAPIVersionsXmlPath(ctx) + } + + cmd := rule.Command(). + Text("("). + Flag("JAVA_OPTS=-Xmx2048m"). + FlagWithArg("ANDROID_SDK_HOME=", homeDir.String()). + FlagWithInput("SDK_ANNOTATIONS=", annotationsZipPath). + FlagWithInput("LINT_OPTS=-DLINT_API_DATABASE=", apiVersionsXMLPath). + Tool(android.PathForSource(ctx, "prebuilts/cmdline-tools/tools/bin/lint")). + Implicit(android.PathForSource(ctx, "prebuilts/cmdline-tools/tools/lib/lint-classpath.jar")). + Flag("--quiet"). + FlagWithInput("--project ", projectXML). + FlagWithInput("--config ", lintXML). + FlagWithOutput("--html ", html). + FlagWithOutput("--text ", text). + FlagWithOutput("--xml ", xml). + FlagWithArg("--compile-sdk-version ", l.compileSdkVersion). + FlagWithArg("--java-language-level ", l.javaLanguageLevel). + FlagWithArg("--kotlin-language-level ", l.kotlinLanguageLevel). + FlagWithArg("--url ", fmt.Sprintf(".=.,%s=out", android.PathForOutput(ctx).String())). + Flag("--exitcode"). + Flags(l.properties.Lint.Flags). + Implicits(deps) + + if checkOnly := ctx.Config().Getenv("ANDROID_LINT_CHECK"); checkOnly != "" { + cmd.FlagWithArg("--check ", checkOnly) + } + + cmd.Text("|| (").Text("cat").Input(text).Text("; exit 7)").Text(")") + + rule.Command().Text("rm -rf").Flag(cacheDir.String()).Flag(homeDir.String()) + + rule.Build(pctx, ctx, "lint", "lint") + + l.outputs = lintOutputs{ + html: html, + text: text, + xml: xml, + + depSets: depSetsBuilder.Build(), + } + + if l.buildModuleReportZip { + l.reports = BuildModuleLintReportZips(ctx, l.LintDepSets()) + } +} + +func BuildModuleLintReportZips(ctx android.ModuleContext, depSets LintDepSets) android.Paths { + htmlList := depSets.HTML.ToSortedList() + textList := depSets.Text.ToSortedList() + xmlList := depSets.XML.ToSortedList() + + if len(htmlList) == 0 && len(textList) == 0 && len(xmlList) == 0 { + return nil + } + + htmlZip := android.PathForModuleOut(ctx, "lint-report-html.zip") + lintZip(ctx, htmlList, htmlZip) + + textZip := android.PathForModuleOut(ctx, "lint-report-text.zip") + lintZip(ctx, textList, textZip) + + xmlZip := android.PathForModuleOut(ctx, "lint-report-xml.zip") + lintZip(ctx, xmlList, xmlZip) + + return android.Paths{htmlZip, textZip, xmlZip} +} + +type lintSingleton struct { + htmlZip android.WritablePath + textZip android.WritablePath + xmlZip android.WritablePath +} + +func (l *lintSingleton) GenerateBuildActions(ctx android.SingletonContext) { + l.generateLintReportZips(ctx) + l.copyLintDependencies(ctx) +} + +func (l *lintSingleton) copyLintDependencies(ctx android.SingletonContext) { + if ctx.Config().UnbundledBuildUsePrebuiltSdks() { + return + } + + var frameworkDocStubs android.Module + ctx.VisitAllModules(func(m android.Module) { + if ctx.ModuleName(m) == "framework-doc-stubs" { + if frameworkDocStubs == nil { + frameworkDocStubs = m + } else { + ctx.Errorf("lint: multiple framework-doc-stubs modules found: %s and %s", + ctx.ModuleSubDir(m), ctx.ModuleSubDir(frameworkDocStubs)) + } + } + }) + + if frameworkDocStubs == nil { + if !ctx.Config().AllowMissingDependencies() { + ctx.Errorf("lint: missing framework-doc-stubs") + } + return + } + + ctx.Build(pctx, android.BuildParams{ + Rule: android.Cp, + Input: android.OutputFileForModule(ctx, frameworkDocStubs, ".annotations.zip"), + Output: copiedAnnotationsZipPath(ctx), + }) + + ctx.Build(pctx, android.BuildParams{ + Rule: android.Cp, + Input: android.OutputFileForModule(ctx, frameworkDocStubs, ".api_versions.xml"), + Output: copiedAPIVersionsXmlPath(ctx), + }) +} + +func copiedAnnotationsZipPath(ctx android.PathContext) android.WritablePath { + return android.PathForOutput(ctx, "lint", "annotations.zip") +} + +func copiedAPIVersionsXmlPath(ctx android.PathContext) android.WritablePath { + return android.PathForOutput(ctx, "lint", "api_versions.xml") +} + +func (l *lintSingleton) generateLintReportZips(ctx android.SingletonContext) { + if ctx.Config().UnbundledBuild() { + return + } + + var outputs []*lintOutputs + var dirs []string + ctx.VisitAllModules(func(m android.Module) { + if ctx.Config().EmbeddedInMake() && !m.ExportedToMake() { + return + } + + if apex, ok := m.(android.ApexModule); ok && apex.NotAvailableForPlatform() && apex.IsForPlatform() { + // There are stray platform variants of modules in apexes that are not available for + // the platform, and they sometimes can't be built. Don't depend on them. + return + } + + if l, ok := m.(lintOutputsIntf); ok { + outputs = append(outputs, l.lintOutputs()) + } + }) + + dirs = android.SortedUniqueStrings(dirs) + + zip := func(outputPath android.WritablePath, get func(*lintOutputs) android.Path) { + var paths android.Paths + + for _, output := range outputs { + if p := get(output); p != nil { + paths = append(paths, p) + } + } + + lintZip(ctx, paths, outputPath) + } + + l.htmlZip = android.PathForOutput(ctx, "lint-report-html.zip") + zip(l.htmlZip, func(l *lintOutputs) android.Path { return l.html }) + + l.textZip = android.PathForOutput(ctx, "lint-report-text.zip") + zip(l.textZip, func(l *lintOutputs) android.Path { return l.text }) + + l.xmlZip = android.PathForOutput(ctx, "lint-report-xml.zip") + zip(l.xmlZip, func(l *lintOutputs) android.Path { return l.xml }) + + ctx.Phony("lint-check", l.htmlZip, l.textZip, l.xmlZip) +} + +func (l *lintSingleton) MakeVars(ctx android.MakeVarsContext) { + if !ctx.Config().UnbundledBuild() { + ctx.DistForGoal("lint-check", l.htmlZip, l.textZip, l.xmlZip) + } +} + +var _ android.SingletonMakeVarsProvider = (*lintSingleton)(nil) + +func init() { + android.RegisterSingletonType("lint", + func() android.Singleton { return &lintSingleton{} }) +} + +func lintZip(ctx android.BuilderContext, paths android.Paths, outputPath android.WritablePath) { + paths = android.SortedUniquePaths(android.CopyOfPaths(paths)) + + sort.Slice(paths, func(i, j int) bool { + return paths[i].String() < paths[j].String() + }) + + rule := android.NewRuleBuilder() + + rule.Command().BuiltTool(ctx, "soong_zip"). + FlagWithOutput("-o ", outputPath). + FlagWithArg("-C ", android.PathForIntermediates(ctx).String()). + FlagWithRspFileInputList("-l ", paths) + + rule.Build(pctx, ctx, outputPath.Base(), outputPath.Base()) +} diff --git a/java/lint_defaults.txt b/java/lint_defaults.txt new file mode 100644 index 000000000..0786b7c00 --- /dev/null +++ b/java/lint_defaults.txt @@ -0,0 +1,78 @@ +# Treat LintError as fatal to catch invocation errors +--fatal_check LintError + +# Downgrade existing errors to warnings +--warning_check AppCompatResource # 55 occurences in 10 modules +--warning_check AppLinkUrlError # 111 occurences in 53 modules +--warning_check BlockedPrivateApi # 2 occurences in 2 modules +--warning_check ByteOrderMark # 2 occurences in 2 modules +--warning_check DuplicateActivity # 3 occurences in 3 modules +--warning_check DuplicateDefinition # 3623 occurences in 48 modules +--warning_check DuplicateIds # 207 occurences in 22 modules +--warning_check EllipsizeMaxLines # 12 occurences in 7 modules +--warning_check ExtraTranslation # 21276 occurences in 27 modules +--warning_check FontValidationError # 4 occurences in 1 modules +--warning_check FullBackupContent # 16 occurences in 1 modules +--warning_check GetContentDescriptionOverride # 3 occurences in 2 modules +--warning_check HalfFloat # 31 occurences in 1 modules +--warning_check HardcodedDebugMode # 99 occurences in 95 modules +--warning_check ImpliedQuantity # 703 occurences in 27 modules +--warning_check ImpliedTouchscreenHardware # 4 occurences in 4 modules +--warning_check IncludeLayoutParam # 11 occurences in 6 modules +--warning_check Instantiatable # 145 occurences in 19 modules +--warning_check InvalidPermission # 6 occurences in 4 modules +--warning_check InvalidUsesTagAttribute # 6 occurences in 2 modules +--warning_check InvalidWakeLockTag # 111 occurences in 37 modules +--warning_check JavascriptInterface # 3 occurences in 2 modules +--warning_check LibraryCustomView # 9 occurences in 4 modules +--warning_check LogTagMismatch # 81 occurences in 13 modules +--warning_check LongLogTag # 249 occurences in 12 modules +--warning_check MenuTitle # 5 occurences in 4 modules +--warning_check MissingClass # 537 occurences in 141 modules +--warning_check MissingConstraints # 39 occurences in 10 modules +--warning_check MissingDefaultResource # 1257 occurences in 40 modules +--warning_check MissingIntentFilterForMediaSearch # 1 occurences in 1 modules +--warning_check MissingLeanbackLauncher # 3 occurences in 3 modules +--warning_check MissingLeanbackSupport # 2 occurences in 2 modules +--warning_check MissingOnPlayFromSearch # 1 occurences in 1 modules +--warning_check MissingPermission # 2071 occurences in 150 modules +--warning_check MissingPrefix # 46 occurences in 41 modules +--warning_check MissingQuantity # 100 occurences in 1 modules +--warning_check MissingSuperCall # 121 occurences in 36 modules +--warning_check MissingTvBanner # 3 occurences in 3 modules +--warning_check NamespaceTypo # 3 occurences in 3 modules +--warning_check NetworkSecurityConfig # 46 occurences in 12 modules +--warning_check NewApi # 1996 occurences in 122 modules +--warning_check NotSibling # 15 occurences in 10 modules +--warning_check ObjectAnimatorBinding # 14 occurences in 5 modules +--warning_check OnClick # 49 occurences in 21 modules +--warning_check Orientation # 77 occurences in 19 modules +--warning_check Override # 385 occurences in 36 modules +--warning_check ParcelCreator # 23 occurences in 2 modules +--warning_check ProtectedPermissions # 2413 occurences in 381 modules +--warning_check Range # 80 occurences in 28 modules +--warning_check RecyclerView # 1 occurences in 1 modules +--warning_check ReferenceType # 4 occurences in 1 modules +--warning_check ResourceAsColor # 19 occurences in 14 modules +--warning_check RequiredSize # 52 occurences in 13 modules +--warning_check ResAuto # 3 occurences in 1 modules +--warning_check ResourceCycle # 37 occurences in 10 modules +--warning_check ResourceType # 137 occurences in 36 modules +--warning_check RestrictedApi # 28 occurences in 5 modules +--warning_check RtlCompat # 9 occurences in 6 modules +--warning_check ServiceCast # 3 occurences in 1 modules +--warning_check SoonBlockedPrivateApi # 5 occurences in 3 modules +--warning_check StringFormatInvalid # 148 occurences in 11 modules +--warning_check StringFormatMatches # 4800 occurences in 30 modules +--warning_check UnknownId # 8 occurences in 7 modules +--warning_check ValidFragment # 12 occurences in 5 modules +--warning_check ValidRestrictions # 5 occurences in 1 modules +--warning_check WebViewLayout # 3 occurences in 1 modules +--warning_check WrongCall # 21 occurences in 3 modules +--warning_check WrongConstant # 894 occurences in 126 modules +--warning_check WrongManifestParent # 10 occurences in 4 modules +--warning_check WrongThread # 14 occurences in 6 modules +--warning_check WrongViewCast # 1 occurences in 1 modules + +# TODO(b/158390965): remove this when lint doesn't crash +--disable_check HardcodedDebugMode diff --git a/java/platform_compat_config.go b/java/platform_compat_config.go new file mode 100644 index 000000000..cb8e6841a --- /dev/null +++ b/java/platform_compat_config.go @@ -0,0 +1,197 @@ +// Copyright 2019 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 ( + "android/soong/android" + "fmt" +) + +func init() { + android.RegisterSingletonType("platform_compat_config_singleton", platformCompatConfigSingletonFactory) + android.RegisterModuleType("platform_compat_config", PlatformCompatConfigFactory) + android.RegisterModuleType("global_compat_config", globalCompatConfigFactory) +} + +func platformCompatConfigPath(ctx android.PathContext) android.OutputPath { + return android.PathForOutput(ctx, "compat_config", "merged_compat_config.xml") +} + +type platformCompatConfigSingleton struct { + metadata android.Path +} + +type platformCompatConfigProperties struct { + Src *string `android:"path"` +} + +type platformCompatConfig struct { + android.ModuleBase + + properties platformCompatConfigProperties + installDirPath android.InstallPath + configFile android.OutputPath + metadataFile android.OutputPath +} + +func (p *platformCompatConfig) compatConfigMetadata() android.OutputPath { + return p.metadataFile +} + +func (p *platformCompatConfig) CompatConfig() android.OutputPath { + return p.configFile +} + +func (p *platformCompatConfig) SubDir() string { + return "compatconfig" +} + +type PlatformCompatConfigIntf interface { + android.Module + + compatConfigMetadata() android.OutputPath + CompatConfig() android.OutputPath + // Sub dir under etc dir. + SubDir() string +} + +var _ PlatformCompatConfigIntf = (*platformCompatConfig)(nil) + +// compat singleton rules +func (p *platformCompatConfigSingleton) GenerateBuildActions(ctx android.SingletonContext) { + + var compatConfigMetadata android.Paths + + ctx.VisitAllModules(func(module android.Module) { + if c, ok := module.(PlatformCompatConfigIntf); ok { + metadata := c.compatConfigMetadata() + compatConfigMetadata = append(compatConfigMetadata, metadata) + } + }) + + if compatConfigMetadata == nil { + // nothing to do. + return + } + + rule := android.NewRuleBuilder() + outputPath := platformCompatConfigPath(ctx) + + rule.Command(). + BuiltTool(ctx, "process-compat-config"). + FlagForEachInput("--xml ", compatConfigMetadata). + FlagWithOutput("--merged-config ", outputPath) + + rule.Build(pctx, ctx, "merged-compat-config", "Merge compat config") + + p.metadata = outputPath +} + +func (p *platformCompatConfigSingleton) MakeVars(ctx android.MakeVarsContext) { + if p.metadata != nil { + ctx.Strict("INTERNAL_PLATFORM_MERGED_COMPAT_CONFIG", p.metadata.String()) + } +} + +func (p *platformCompatConfig) GenerateAndroidBuildActions(ctx android.ModuleContext) { + rule := android.NewRuleBuilder() + + configFileName := p.Name() + ".xml" + metadataFileName := p.Name() + "_meta.xml" + p.configFile = android.PathForModuleOut(ctx, configFileName).OutputPath + p.metadataFile = android.PathForModuleOut(ctx, metadataFileName).OutputPath + path := android.PathForModuleSrc(ctx, String(p.properties.Src)) + + rule.Command(). + BuiltTool(ctx, "process-compat-config"). + FlagWithInput("--jar ", path). + FlagWithOutput("--device-config ", p.configFile). + FlagWithOutput("--merged-config ", p.metadataFile) + + p.installDirPath = android.PathForModuleInstall(ctx, "etc", "compatconfig") + rule.Build(pctx, ctx, configFileName, "Extract compat/compat_config.xml and install it") + +} + +func (p *platformCompatConfig) AndroidMkEntries() []android.AndroidMkEntries { + return []android.AndroidMkEntries{android.AndroidMkEntries{ + Class: "ETC", + OutputFile: android.OptionalPathForPath(p.configFile), + Include: "$(BUILD_PREBUILT)", + ExtraEntries: []android.AndroidMkExtraEntriesFunc{ + func(entries *android.AndroidMkEntries) { + entries.SetString("LOCAL_MODULE_PATH", p.installDirPath.ToMakePath().String()) + entries.SetString("LOCAL_INSTALLED_MODULE_STEM", p.configFile.Base()) + }, + }, + }} +} + +func platformCompatConfigSingletonFactory() android.Singleton { + return &platformCompatConfigSingleton{} +} + +func PlatformCompatConfigFactory() android.Module { + module := &platformCompatConfig{} + module.AddProperties(&module.properties) + android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst) + return module +} + +//============== merged_compat_config ================= +type globalCompatConfigProperties struct { + // name of the file into which the metadata will be copied. + Filename *string +} + +type globalCompatConfig struct { + android.ModuleBase + + properties globalCompatConfigProperties + + outputFilePath android.OutputPath +} + +func (c *globalCompatConfig) GenerateAndroidBuildActions(ctx android.ModuleContext) { + filename := String(c.properties.Filename) + + inputPath := platformCompatConfigPath(ctx) + c.outputFilePath = android.PathForModuleOut(ctx, filename).OutputPath + + // This ensures that outputFilePath has the correct name for others to + // use, as the source file may have a different name. + ctx.Build(pctx, android.BuildParams{ + Rule: android.Cp, + Output: c.outputFilePath, + Input: inputPath, + }) +} + +func (h *globalCompatConfig) OutputFiles(tag string) (android.Paths, error) { + switch tag { + case "": + return android.Paths{h.outputFilePath}, nil + default: + return nil, fmt.Errorf("unsupported module reference tag %q", tag) + } +} + +// global_compat_config provides access to the merged compat config xml file generated by the build. +func globalCompatConfigFactory() android.Module { + module := &globalCompatConfig{} + module.AddProperties(&module.properties) + android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibCommon) + return module +} diff --git a/java/plugin.go b/java/plugin.go index a5e8292e7..947c28626 100644 --- a/java/plugin.go +++ b/java/plugin.go @@ -24,10 +24,8 @@ func init() { func PluginFactory() android.Module { module := &Plugin{} - module.AddProperties( - &module.Module.properties, - &module.Module.protoProperties, - &module.pluginProperties) + module.addHostProperties() + module.AddProperties(&module.pluginProperties) InitJavaModule(module, android.HostSupported) return module diff --git a/java/plugin_test.go b/java/plugin_test.go index d1aef2c19..c7913d3db 100644 --- a/java/plugin_test.go +++ b/java/plugin_test.go @@ -20,7 +20,7 @@ import ( ) func TestNoPlugin(t *testing.T) { - ctx := testJava(t, ` + ctx, _ := testJava(t, ` java_library { name: "foo", srcs: ["a.java"], @@ -44,7 +44,7 @@ func TestNoPlugin(t *testing.T) { } func TestPlugin(t *testing.T) { - ctx := testJava(t, ` + ctx, _ := testJava(t, ` java_library { name: "foo", srcs: ["a.java"], @@ -83,7 +83,7 @@ func TestPlugin(t *testing.T) { } func TestPluginGeneratesApi(t *testing.T) { - ctx := testJava(t, ` + ctx, _ := testJava(t, ` java_library { name: "foo", srcs: ["a.java"], diff --git a/java/prebuilt_apis.go b/java/prebuilt_apis.go index c37081130..999c72f3c 100644 --- a/java/prebuilt_apis.go +++ b/java/prebuilt_apis.go @@ -15,19 +15,20 @@ package java import ( - "android/soong/android" "sort" "strings" "github.com/google/blueprint/proptools" + + "android/soong/android" ) func init() { - android.RegisterModuleType("prebuilt_apis", PrebuiltApisFactory) + RegisterPrebuiltApisBuildComponents(android.InitRegistrationContext) +} - android.PreArchMutators(func(ctx android.RegisterMutatorsContext) { - ctx.TopDown("prebuilt_apis", PrebuiltApisMutator).Parallel() - }) +func RegisterPrebuiltApisBuildComponents(ctx android.RegistrationContext) { + ctx.RegisterModuleType("prebuilt_apis", PrebuiltApisFactory) } type prebuiltApisProperties struct { @@ -44,7 +45,7 @@ func (module *prebuiltApis) GenerateAndroidBuildActions(ctx android.ModuleContex // no need to implement } -func parseJarPath(ctx android.BaseModuleContext, path string) (module string, apiver string, scope string) { +func parseJarPath(path string) (module string, apiver string, scope string) { elements := strings.Split(path, "/") apiver = elements[0] @@ -54,12 +55,12 @@ func parseJarPath(ctx android.BaseModuleContext, path string) (module string, ap return } -func parseApiFilePath(ctx android.BaseModuleContext, path string) (module string, apiver string, scope string) { +func parseApiFilePath(ctx android.LoadHookContext, path string) (module string, apiver string, scope string) { elements := strings.Split(path, "/") apiver = elements[0] scope = elements[1] - if scope != "public" && scope != "system" && scope != "test" { + if scope != "public" && scope != "system" && scope != "test" && scope != "module-lib" && scope != "system-server" { ctx.ModuleErrorf("invalid scope %q found in path: %q", scope, path) return } @@ -69,23 +70,27 @@ func parseApiFilePath(ctx android.BaseModuleContext, path string) (module string return } -func createImport(mctx android.TopDownMutatorContext, module string, scope string, apiver string, path string) { +func prebuiltApiModuleName(mctx android.LoadHookContext, module string, scope string, apiver string) string { + return mctx.ModuleName() + "_" + scope + "_" + apiver + "_" + module +} + +func createImport(mctx android.LoadHookContext, module string, scope string, apiver string, path string) { props := struct { Name *string Jars []string Sdk_version *string Installable *bool }{} - props.Name = proptools.StringPtr(mctx.ModuleName() + "_" + scope + "_" + apiver + "_" + module) + props.Name = proptools.StringPtr(prebuiltApiModuleName(mctx, module, scope, apiver)) props.Jars = append(props.Jars, path) // TODO(hansson): change to scope after migration is done. props.Sdk_version = proptools.StringPtr("current") props.Installable = proptools.BoolPtr(false) - mctx.CreateModule(android.ModuleFactoryAdaptor(ImportFactory), &props) + mctx.CreateModule(ImportFactory, &props) } -func createFilegroup(mctx android.TopDownMutatorContext, module string, scope string, apiver string, path string) { +func createFilegroup(mctx android.LoadHookContext, module string, scope string, apiver string, path string) { fgName := module + ".api." + scope + "." + apiver filegroupProps := struct { Name *string @@ -93,14 +98,14 @@ func createFilegroup(mctx android.TopDownMutatorContext, module string, scope st }{} filegroupProps.Name = proptools.StringPtr(fgName) filegroupProps.Srcs = []string{path} - mctx.CreateModule(android.ModuleFactoryAdaptor(android.FileGroupFactory), &filegroupProps) + mctx.CreateModule(android.FileGroupFactory, &filegroupProps) } -func getPrebuiltFiles(mctx android.TopDownMutatorContext, name string) []string { +func getPrebuiltFiles(mctx android.LoadHookContext, name string) []string { mydir := mctx.ModuleDir() + "/" var files []string for _, apiver := range mctx.Module().(*prebuiltApis).properties.Api_dirs { - for _, scope := range []string{"public", "system", "test", "core"} { + for _, scope := range []string{"public", "system", "test", "core", "module-lib", "system-server"} { vfiles, err := mctx.GlobWithDeps(mydir+apiver+"/"+scope+"/"+name, nil) if err != nil { mctx.ModuleErrorf("failed to glob %s files under %q: %s", name, mydir+apiver+"/"+scope, err) @@ -111,7 +116,7 @@ func getPrebuiltFiles(mctx android.TopDownMutatorContext, name string) []string return files } -func prebuiltSdkStubs(mctx android.TopDownMutatorContext) { +func prebuiltSdkStubs(mctx android.LoadHookContext) { mydir := mctx.ModuleDir() + "/" // <apiver>/<scope>/<module>.jar files := getPrebuiltFiles(mctx, "*.jar") @@ -119,12 +124,33 @@ func prebuiltSdkStubs(mctx android.TopDownMutatorContext) { for _, f := range files { // create a Import module for each jar file localPath := strings.TrimPrefix(f, mydir) - module, apiver, scope := parseJarPath(mctx, localPath) + module, apiver, scope := parseJarPath(localPath) createImport(mctx, module, scope, apiver, localPath) } } -func prebuiltApiFiles(mctx android.TopDownMutatorContext) { +func createSystemModules(mctx android.LoadHookContext, apiver string) { + props := struct { + Name *string + Libs []string + }{} + props.Name = proptools.StringPtr(prebuiltApiModuleName(mctx, "system_modules", "public", apiver)) + props.Libs = append(props.Libs, prebuiltApiModuleName(mctx, "core-for-system-modules", "public", apiver)) + + mctx.CreateModule(SystemModulesFactory, &props) +} + +func prebuiltSdkSystemModules(mctx android.LoadHookContext) { + for _, apiver := range mctx.Module().(*prebuiltApis).properties.Api_dirs { + jar := android.ExistentPathForSource(mctx, + mctx.ModuleDir(), apiver, "public", "core-for-system-modules.jar") + if jar.Valid() { + createSystemModules(mctx, apiver) + } + } +} + +func prebuiltApiFiles(mctx android.LoadHookContext) { mydir := mctx.ModuleDir() + "/" // <apiver>/<scope>/api/<module>.txt files := getPrebuiltFiles(mctx, "api/*.txt") @@ -174,10 +200,11 @@ func prebuiltApiFiles(mctx android.TopDownMutatorContext) { } } -func PrebuiltApisMutator(mctx android.TopDownMutatorContext) { +func createPrebuiltApiModules(mctx android.LoadHookContext) { if _, ok := mctx.Module().(*prebuiltApis); ok { prebuiltApiFiles(mctx) prebuiltSdkStubs(mctx) + prebuiltSdkSystemModules(mctx) } } @@ -187,9 +214,17 @@ func PrebuiltApisMutator(mctx android.TopDownMutatorContext) { // generates a filegroup module named <module>-api.<scope>.<ver>. // // It also creates <module>-api.<scope>.latest for the latest <ver>. +// +// Similarly, it generates a java_import for all API .jar files found under the +// directory where the Android.bp is located. Specifically, an API file located +// at ./<ver>/<scope>/api/<module>.jar generates a java_import module named +// <prebuilt-api-module>_<scope>_<ver>_<module>, and for SDK versions >= 30 +// a java_system_modules module named +// <prebuilt-api-module>_public_<ver>_system_modules func PrebuiltApisFactory() android.Module { module := &prebuiltApis{} module.AddProperties(&module.properties) android.InitAndroidModule(module) + android.AddLoadHook(module, createPrebuiltApiModules) return module } diff --git a/java/proto.go b/java/proto.go index 37de1d283..4d735ebbe 100644 --- a/java/proto.go +++ b/java/proto.go @@ -15,36 +15,61 @@ package java import ( + "path/filepath" + "strconv" + "android/soong/android" ) -func genProto(ctx android.ModuleContext, protoFile android.Path, flags android.ProtoFlags) android.Path { - srcJarFile := android.GenPathWithExt(ctx, "proto", protoFile, "srcjar") +func genProto(ctx android.ModuleContext, protoFiles android.Paths, flags android.ProtoFlags) android.Paths { + // Shard proto files into groups of 100 to avoid having to recompile all of them if one changes and to avoid + // hitting command line length limits. + shards := android.ShardPaths(protoFiles, 100) - outDir := srcJarFile.ReplaceExtension(ctx, "tmp") - depFile := srcJarFile.ReplaceExtension(ctx, "srcjar.d") + srcJarFiles := make(android.Paths, 0, len(shards)) - rule := android.NewRuleBuilder() + for i, shard := range shards { + srcJarFile := android.PathForModuleGen(ctx, "proto", "proto"+strconv.Itoa(i)+".srcjar") + srcJarFiles = append(srcJarFiles, srcJarFile) - rule.Command().Text("rm -rf").Flag(outDir.String()) - rule.Command().Text("mkdir -p").Flag(outDir.String()) + outDir := srcJarFile.ReplaceExtension(ctx, "tmp") - android.ProtoRule(ctx, rule, protoFile, flags, flags.Deps, outDir, depFile, nil) + rule := android.NewRuleBuilder() - // Proto generated java files have an unknown package name in the path, so package the entire output directory - // into a srcjar. - rule.Command(). - Tool(ctx.Config().HostToolPath(ctx, "soong_zip")). - Flag("-jar"). - FlagWithOutput("-o ", srcJarFile). - FlagWithArg("-C ", outDir.String()). - FlagWithArg("-D ", outDir.String()) + rule.Command().Text("rm -rf").Flag(outDir.String()) + rule.Command().Text("mkdir -p").Flag(outDir.String()) - rule.Command().Text("rm -rf").Flag(outDir.String()) + for _, protoFile := range shard { + depFile := srcJarFile.InSameDir(ctx, protoFile.String()+".d") + rule.Command().Text("mkdir -p").Flag(filepath.Dir(depFile.String())) + android.ProtoRule(ctx, rule, protoFile, flags, flags.Deps, outDir, depFile, nil) + } - rule.Build(pctx, ctx, "protoc_"+protoFile.Rel(), "protoc "+protoFile.Rel()) + // Proto generated java files have an unknown package name in the path, so package the entire output directory + // into a srcjar. + rule.Command(). + BuiltTool(ctx, "soong_zip"). + Flag("-jar"). + Flag("-write_if_changed"). + FlagWithOutput("-o ", srcJarFile). + FlagWithArg("-C ", outDir.String()). + FlagWithArg("-D ", outDir.String()) + + rule.Command().Text("rm -rf").Flag(outDir.String()) + + rule.Restat() + + ruleName := "protoc" + ruleDesc := "protoc" + if len(shards) > 1 { + ruleName += "_" + strconv.Itoa(i) + ruleDesc += " " + strconv.Itoa(i) + } + + rule.Build(pctx, ctx, ruleName, ruleDesc) + } - return srcJarFile + return srcJarFiles } func protoDeps(ctx android.BottomUpMutatorContext, p *android.ProtoProperties) { @@ -75,20 +100,29 @@ func protoFlags(ctx android.ModuleContext, j *CompilerProperties, p *android.Pro flags.proto = android.GetProtoFlags(ctx, p) if String(p.Proto.Plugin) == "" { + var typeToPlugin string switch String(p.Proto.Type) { case "micro": flags.proto.OutTypeFlag = "--javamicro_out" + typeToPlugin = "javamicro" case "nano": flags.proto.OutTypeFlag = "--javanano_out" - case "lite": + typeToPlugin = "javanano" + case "lite", "": flags.proto.OutTypeFlag = "--java_out" flags.proto.OutParams = append(flags.proto.OutParams, "lite") - case "full", "": + case "full": flags.proto.OutTypeFlag = "--java_out" default: ctx.PropertyErrorf("proto.type", "unknown proto type %q", String(p.Proto.Type)) } + + if typeToPlugin != "" { + hostTool := ctx.Config().HostToolPath(ctx, "protoc-gen-"+typeToPlugin) + flags.proto.Deps = append(flags.proto.Deps, hostTool) + flags.proto.Flags = append(flags.proto.Flags, "--plugin=protoc-gen-"+typeToPlugin+"="+hostTool.String()) + } } flags.proto.OutParams = append(flags.proto.OutParams, j.Proto.Output_params...) diff --git a/java/robolectric.go b/java/robolectric.go new file mode 100644 index 000000000..c6b07a17e --- /dev/null +++ b/java/robolectric.go @@ -0,0 +1,228 @@ +// Copyright 2019 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 ( + "fmt" + "io" + "strconv" + "strings" + + "android/soong/android" +) + +func init() { + android.RegisterModuleType("android_robolectric_test", RobolectricTestFactory) +} + +var robolectricDefaultLibs = []string{ + "robolectric_android-all-stub", + "Robolectric_all-target", + "mockito-robolectric-prebuilt", + "truth-prebuilt", +} + +var ( + roboCoverageLibsTag = dependencyTag{name: "roboSrcs"} +) + +type robolectricProperties struct { + // The name of the android_app module that the tests will run against. + Instrumentation_for *string + + // Additional libraries for which coverage data should be generated + Coverage_libs []string + + Test_options struct { + // Timeout in seconds when running the tests. + Timeout *int64 + + // Number of shards to use when running the tests. + Shards *int64 + } +} + +type robolectricTest struct { + Library + + robolectricProperties robolectricProperties + + libs []string + tests []string + + roboSrcJar android.Path +} + +func (r *robolectricTest) DepsMutator(ctx android.BottomUpMutatorContext) { + r.Library.DepsMutator(ctx) + + if r.robolectricProperties.Instrumentation_for != nil { + ctx.AddVariationDependencies(nil, instrumentationForTag, String(r.robolectricProperties.Instrumentation_for)) + } else { + ctx.PropertyErrorf("instrumentation_for", "missing required instrumented module") + } + + ctx.AddVariationDependencies(nil, libTag, robolectricDefaultLibs...) + + ctx.AddVariationDependencies(nil, roboCoverageLibsTag, r.robolectricProperties.Coverage_libs...) +} + +func (r *robolectricTest) GenerateAndroidBuildActions(ctx android.ModuleContext) { + roboTestConfig := android.PathForModuleGen(ctx, "robolectric"). + Join(ctx, "com/android/tools/test_config.properties") + + // TODO: this inserts paths to built files into the test, it should really be inserting the contents. + instrumented := ctx.GetDirectDepsWithTag(instrumentationForTag) + + if len(instrumented) != 1 { + panic(fmt.Errorf("expected exactly 1 instrumented dependency, got %d", len(instrumented))) + } + + instrumentedApp, ok := instrumented[0].(*AndroidApp) + if !ok { + ctx.PropertyErrorf("instrumentation_for", "dependency must be an android_app") + } + + generateRoboTestConfig(ctx, roboTestConfig, instrumentedApp) + r.extraResources = android.Paths{roboTestConfig} + + r.Library.GenerateAndroidBuildActions(ctx) + + roboSrcJar := android.PathForModuleGen(ctx, "robolectric", ctx.ModuleName()+".srcjar") + r.generateRoboSrcJar(ctx, roboSrcJar, instrumentedApp) + r.roboSrcJar = roboSrcJar + + for _, dep := range ctx.GetDirectDepsWithTag(libTag) { + r.libs = append(r.libs, dep.(Dependency).BaseModuleName()) + } + + // TODO: this could all be removed if tradefed was used as the test runner, it will find everything + // annotated as a test and run it. + for _, src := range r.compiledJavaSrcs { + s := src.Rel() + if !strings.HasSuffix(s, "Test.java") { + continue + } else if strings.HasSuffix(s, "/BaseRobolectricTest.java") { + continue + } else if strings.HasPrefix(s, "src/") { + s = strings.TrimPrefix(s, "src/") + } + r.tests = append(r.tests, s) + } +} + +func generateRoboTestConfig(ctx android.ModuleContext, outputFile android.WritablePath, instrumentedApp *AndroidApp) { + manifest := instrumentedApp.mergedManifestFile + resourceApk := instrumentedApp.outputFile + + rule := android.NewRuleBuilder() + + rule.Command().Text("rm -f").Output(outputFile) + rule.Command(). + Textf(`echo "android_merged_manifest=%s" >>`, manifest.String()).Output(outputFile).Text("&&"). + Textf(`echo "android_resource_apk=%s" >>`, resourceApk.String()).Output(outputFile). + // Make it depend on the files to which it points so the test file's timestamp is updated whenever the + // contents change + Implicit(manifest). + Implicit(resourceApk) + + rule.Build(pctx, ctx, "generate_test_config", "generate test_config.properties") +} + +func (r *robolectricTest) generateRoboSrcJar(ctx android.ModuleContext, outputFile android.WritablePath, + instrumentedApp *AndroidApp) { + + srcJarArgs := copyOf(instrumentedApp.srcJarArgs) + srcJarDeps := append(android.Paths(nil), instrumentedApp.srcJarDeps...) + + for _, m := range ctx.GetDirectDepsWithTag(roboCoverageLibsTag) { + if dep, ok := m.(Dependency); ok { + depSrcJarArgs, depSrcJarDeps := dep.SrcJarArgs() + srcJarArgs = append(srcJarArgs, depSrcJarArgs...) + srcJarDeps = append(srcJarDeps, depSrcJarDeps...) + } + } + + TransformResourcesToJar(ctx, outputFile, srcJarArgs, srcJarDeps) +} + +func (r *robolectricTest) AndroidMkEntries() []android.AndroidMkEntries { + entriesList := r.Library.AndroidMkEntries() + entries := &entriesList[0] + + entries.ExtraFooters = []android.AndroidMkExtraFootersFunc{ + func(w io.Writer, name, prefix, moduleDir string, entries *android.AndroidMkEntries) { + if s := r.robolectricProperties.Test_options.Shards; s != nil && *s > 1 { + numShards := int(*s) + shardSize := (len(r.tests) + numShards - 1) / numShards + shards := android.ShardStrings(r.tests, shardSize) + for i, shard := range shards { + r.writeTestRunner(w, name, "Run"+name+strconv.Itoa(i), shard) + } + + // TODO: add rules to dist the outputs of the individual tests, or combine them together? + fmt.Fprintln(w, "") + fmt.Fprintln(w, ".PHONY:", "Run"+name) + fmt.Fprintln(w, "Run"+name, ": \\") + for i := range shards { + fmt.Fprintln(w, " ", "Run"+name+strconv.Itoa(i), "\\") + } + fmt.Fprintln(w, "") + } else { + r.writeTestRunner(w, name, "Run"+name, r.tests) + } + }, + } + + return entriesList +} + +func (r *robolectricTest) writeTestRunner(w io.Writer, module, name string, tests []string) { + fmt.Fprintln(w, "") + fmt.Fprintln(w, "include $(CLEAR_VARS)") + fmt.Fprintln(w, "LOCAL_MODULE :=", name) + fmt.Fprintln(w, "LOCAL_JAVA_LIBRARIES :=", module) + fmt.Fprintln(w, "LOCAL_JAVA_LIBRARIES += ", strings.Join(r.libs, " ")) + fmt.Fprintln(w, "LOCAL_TEST_PACKAGE :=", String(r.robolectricProperties.Instrumentation_for)) + fmt.Fprintln(w, "LOCAL_INSTRUMENT_SRCJARS :=", r.roboSrcJar.String()) + fmt.Fprintln(w, "LOCAL_ROBOTEST_FILES :=", strings.Join(tests, " ")) + if t := r.robolectricProperties.Test_options.Timeout; t != nil { + fmt.Fprintln(w, "LOCAL_ROBOTEST_TIMEOUT :=", *t) + } + fmt.Fprintln(w, "-include external/robolectric-shadows/run_robotests.mk") + +} + +// An android_robolectric_test module compiles tests against the Robolectric framework that can run on the local host +// instead of on a device. It also generates a rule with the name of the module prefixed with "Run" that can be +// used to run the tests. Running the tests with build rule will eventually be deprecated and replaced with atest. +// +// The test runner considers any file listed in srcs whose name ends with Test.java to be a test class, unless +// it is named BaseRobolectricTest.java. The path to the each source file must exactly match the package +// name, or match the package name when the prefix "src/" is removed. +func RobolectricTestFactory() android.Module { + module := &robolectricTest{} + + module.addHostProperties() + module.AddProperties( + &module.Module.deviceProperties, + &module.robolectricProperties) + + module.Module.dexpreopter.isTest = true + module.Module.linter.test = true + + InitJavaModule(module, android.DeviceSupported) + return module +} diff --git a/java/sdk.go b/java/sdk.go index e93f8fb69..f96ecded4 100644 --- a/java/sdk.go +++ b/java/sdk.go @@ -15,15 +15,15 @@ package java import ( - "android/soong/android" - "android/soong/java/config" "fmt" "path/filepath" - "runtime" "sort" "strconv" "strings" + "android/soong/android" + "android/soong/java/config" + "github.com/google/blueprint/pathtools" ) @@ -35,83 +35,309 @@ func init() { var sdkVersionsKey = android.NewOnceKey("sdkVersionsKey") var sdkFrameworkAidlPathKey = android.NewOnceKey("sdkFrameworkAidlPathKey") +var nonUpdatableFrameworkAidlPathKey = android.NewOnceKey("nonUpdatableFrameworkAidlPathKey") var apiFingerprintPathKey = android.NewOnceKey("apiFingerprintPathKey") type sdkContext interface { - // sdkVersion eturns the sdk_version property of the current module, or an empty string if it is not set. - sdkVersion() string - // minSdkVersion returns the min_sdk_version property of the current module, or sdkVersion() if it is not set. - minSdkVersion() string - // targetSdkVersion returns the target_sdk_version property of the current module, or sdkVersion() if it is not set. - targetSdkVersion() string -} - -func sdkVersionOrDefault(ctx android.BaseContext, v string) string { - switch v { - case "", "current", "system_current", "test_current", "core_current": - return ctx.Config().DefaultAppTargetSdk() + // sdkVersion returns sdkSpec that corresponds to the sdk_version property of the current module + sdkVersion() sdkSpec + // systemModules returns the system_modules property of the current module, or an empty string if it is not set. + systemModules() string + // minSdkVersion returns sdkSpec that corresponds to the min_sdk_version property of the current module, + // or from sdk_version if it is not set. + minSdkVersion() sdkSpec + // targetSdkVersion returns the sdkSpec that corresponds to the target_sdk_version property of the current module, + // or from sdk_version if it is not set. + targetSdkVersion() sdkSpec +} + +func UseApiFingerprint(ctx android.BaseModuleContext) bool { + if ctx.Config().UnbundledBuild() && + !ctx.Config().UnbundledBuildUsePrebuiltSdks() && + ctx.Config().IsEnvTrue("UNBUNDLED_BUILD_TARGET_SDK_WITH_API_FINGERPRINT") { + return true + } + return false +} + +// sdkKind represents a particular category of an SDK spec like public, system, test, etc. +type sdkKind int + +const ( + sdkInvalid sdkKind = iota + sdkNone + sdkCore + sdkCorePlatform + sdkPublic + sdkSystem + sdkTest + sdkModule + sdkSystemServer + sdkPrivate +) + +// String returns the string representation of this sdkKind +func (k sdkKind) String() string { + switch k { + case sdkPrivate: + return "private" + case sdkNone: + return "none" + case sdkPublic: + return "public" + case sdkSystem: + return "system" + case sdkTest: + return "test" + case sdkCore: + return "core" + case sdkCorePlatform: + return "core_platform" + case sdkModule: + return "module" + case sdkSystemServer: + return "system_server" default: - return v + return "invalid" } } -// Returns a sdk version as a number. For modules targeting an unreleased SDK (meaning it does not yet have a number) -// it returns android.FutureApiLevel (10000). -func sdkVersionToNumber(ctx android.BaseContext, v string) (int, error) { - switch v { - case "", "current", "test_current", "system_current", "core_current": - return ctx.Config().DefaultAppTargetSdkInt(), nil +// sdkVersion represents a specific version number of an SDK spec of a particular kind +type sdkVersion int + +const ( + // special version number for a not-yet-frozen SDK + sdkVersionCurrent sdkVersion = sdkVersion(android.FutureApiLevel) + // special version number to be used for SDK specs where version number doesn't + // make sense, e.g. "none", "", etc. + sdkVersionNone sdkVersion = sdkVersion(0) +) + +// isCurrent checks if the sdkVersion refers to the not-yet-published version of an sdkKind +func (v sdkVersion) isCurrent() bool { + return v == sdkVersionCurrent +} + +// isNumbered checks if the sdkVersion refers to the published (a.k.a numbered) version of an sdkKind +func (v sdkVersion) isNumbered() bool { + return !v.isCurrent() && v != sdkVersionNone +} + +// String returns the string representation of this sdkVersion. +func (v sdkVersion) String() string { + if v.isCurrent() { + return "current" + } else if v.isNumbered() { + return strconv.Itoa(int(v)) + } + return "(no version)" +} + +// asNumberString directly converts the numeric value of this sdk version as a string. +// When isNumbered() is true, this method is the same as String(). However, for sdkVersionCurrent +// and sdkVersionNone, this returns 10000 and 0 while String() returns "current" and "(no version"), +// respectively. +func (v sdkVersion) asNumberString() string { + return strconv.Itoa(int(v)) +} + +// sdkSpec represents the kind and the version of an SDK for a module to build against +type sdkSpec struct { + kind sdkKind + version sdkVersion + raw string +} + +func (s sdkSpec) String() string { + return fmt.Sprintf("%s_%s", s.kind, s.version) +} + +// valid checks if this sdkSpec is well-formed. Note however that true doesn't mean that the +// specified SDK actually exists. +func (s sdkSpec) valid() bool { + return s.kind != sdkInvalid +} + +// specified checks if this sdkSpec is well-formed and is not "". +func (s sdkSpec) specified() bool { + return s.valid() && s.kind != sdkPrivate +} + +// whether the API surface is managed and versioned, i.e. has .txt file that +// get frozen on SDK freeze and changes get reviewed by API council. +func (s sdkSpec) stable() bool { + if !s.specified() { + return false + } + switch s.kind { + case sdkNone: + // there is nothing to manage and version in this case; de facto stable API. + return true + case sdkCore, sdkPublic, sdkSystem, sdkModule, sdkSystemServer: + return true + case sdkCorePlatform, sdkTest, sdkPrivate: + return false default: - n := android.GetNumericSdkVersion(v) - if i, err := strconv.Atoi(n); err != nil { - return -1, fmt.Errorf("invalid sdk version %q", n) - } else { - return i, nil - } + panic(fmt.Errorf("unknown sdkKind=%v", s.kind)) } + return false } -func sdkVersionToNumberAsString(ctx android.BaseContext, v string) (string, error) { - n, err := sdkVersionToNumber(ctx, v) - if err != nil { - return "", err +// prebuiltSdkAvailableForUnbundledBuilt tells whether this sdkSpec can have a prebuilt SDK +// that can be used for unbundled builds. +func (s sdkSpec) prebuiltSdkAvailableForUnbundledBuild() bool { + // "", "none", and "core_platform" are not available for unbundled build + // as we don't/can't have prebuilt stub for the versions + return s.kind != sdkPrivate && s.kind != sdkNone && s.kind != sdkCorePlatform +} + +// forPdkBuild converts this sdkSpec into another sdkSpec that is for the PDK builds. +func (s sdkSpec) forPdkBuild(ctx android.EarlyModuleContext) sdkSpec { + // For PDK builds, use the latest SDK version instead of "current" or "" + if s.kind == sdkPrivate || s.kind == sdkPublic { + kind := s.kind + if kind == sdkPrivate { + // We don't have prebuilt SDK for private APIs, so use the public SDK + // instead. This looks odd, but that's how it has been done. + // TODO(b/148271073): investigate the need for this. + kind = sdkPublic + } + version := sdkVersion(LatestSdkVersionInt(ctx)) + return sdkSpec{kind, version, s.raw} } - return strconv.Itoa(n), nil + return s } -func decodeSdkDep(ctx android.BaseContext, sdkContext sdkContext) sdkDep { - v := sdkContext.sdkVersion() - // For PDK builds, use the latest SDK version instead of "current" - if ctx.Config().IsPdkBuild() && (v == "" || v == "current") { - sdkVersions := ctx.Config().Get(sdkVersionsKey).([]int) - latestSdkVersion := 0 - if len(sdkVersions) > 0 { - latestSdkVersion = sdkVersions[len(sdkVersions)-1] +// usePrebuilt determines whether prebuilt SDK should be used for this sdkSpec with the given context. +func (s sdkSpec) usePrebuilt(ctx android.EarlyModuleContext) bool { + if s.version.isCurrent() { + // "current" can be built from source and be from prebuilt SDK + return ctx.Config().UnbundledBuildUsePrebuiltSdks() + } else if s.version.isNumbered() { + // sanity check + if s.kind != sdkPublic && s.kind != sdkSystem && s.kind != sdkTest { + panic(fmt.Errorf("prebuilt SDK is not not available for sdkKind=%q", s.kind)) + return false } - v = strconv.Itoa(latestSdkVersion) + // numbered SDKs are always from prebuilt + return true } + // "", "none", "core_platform" fall here + return false +} - numericSdkVersion, err := sdkVersionToNumber(ctx, v) +// effectiveVersion converts an sdkSpec into the concrete sdkVersion that the module +// should use. For modules targeting an unreleased SDK (meaning it does not yet have a number) +// it returns android.FutureApiLevel(10000). +func (s sdkSpec) effectiveVersion(ctx android.EarlyModuleContext) (sdkVersion, error) { + if !s.valid() { + return s.version, fmt.Errorf("invalid sdk version %q", s.raw) + } + if ctx.Config().IsPdkBuild() { + s = s.forPdkBuild(ctx) + } + if s.version.isNumbered() { + return s.version, nil + } + return sdkVersion(ctx.Config().DefaultAppTargetSdkInt()), nil +} + +// effectiveVersionString converts an sdkSpec into the concrete version string that the module +// should use. For modules targeting an unreleased SDK (meaning it does not yet have a number) +// it returns the codename (P, Q, R, etc.) +func (s sdkSpec) effectiveVersionString(ctx android.EarlyModuleContext) (string, error) { + ver, err := s.effectiveVersion(ctx) + if err == nil && int(ver) == ctx.Config().DefaultAppTargetSdkInt() { + return ctx.Config().DefaultAppTargetSdk(), nil + } + return ver.String(), err +} + +func (s sdkSpec) defaultJavaLanguageVersion(ctx android.EarlyModuleContext) javaVersion { + sdk, err := s.effectiveVersion(ctx) if err != nil { ctx.PropertyErrorf("sdk_version", "%s", err) - return sdkDep{} } + if sdk <= 23 { + return JAVA_VERSION_7 + } else if sdk <= 29 { + return JAVA_VERSION_8 + } else { + return JAVA_VERSION_9 + } +} + +func sdkSpecFrom(str string) sdkSpec { + switch str { + // special cases first + case "": + return sdkSpec{sdkPrivate, sdkVersionNone, str} + case "none": + return sdkSpec{sdkNone, sdkVersionNone, str} + case "core_platform": + return sdkSpec{sdkCorePlatform, sdkVersionNone, str} + default: + // the syntax is [kind_]version + sep := strings.LastIndex(str, "_") + + var kindString string + if sep == 0 { + return sdkSpec{sdkInvalid, sdkVersionNone, str} + } else if sep == -1 { + kindString = "" + } else { + kindString = str[0:sep] + } + versionString := str[sep+1 : len(str)] + + var kind sdkKind + switch kindString { + case "": + kind = sdkPublic + case "core": + kind = sdkCore + case "system": + kind = sdkSystem + case "test": + kind = sdkTest + case "module": + kind = sdkModule + case "system_server": + kind = sdkSystemServer + default: + return sdkSpec{sdkInvalid, sdkVersionNone, str} + } - toPrebuilt := func(sdk string) sdkDep { - var api, v string - if strings.Contains(sdk, "_") { - t := strings.Split(sdk, "_") - api = t[0] - v = t[1] + var version sdkVersion + if versionString == "current" { + version = sdkVersionCurrent + } else if i, err := strconv.Atoi(versionString); err == nil { + version = sdkVersion(i) } else { - api = "public" - v = sdk + return sdkSpec{sdkInvalid, sdkVersionNone, str} } - dir := filepath.Join("prebuilts", "sdk", v, api) + + return sdkSpec{kind, version, str} + } +} + +func decodeSdkDep(ctx android.EarlyModuleContext, sdkContext sdkContext) sdkDep { + sdkVersion := sdkContext.sdkVersion() + if !sdkVersion.valid() { + ctx.PropertyErrorf("sdk_version", "invalid version %q", sdkVersion.raw) + return sdkDep{} + } + + if ctx.Config().IsPdkBuild() { + sdkVersion = sdkVersion.forPdkBuild(ctx) + } + + if sdkVersion.usePrebuilt(ctx) { + dir := filepath.Join("prebuilts", "sdk", sdkVersion.version.String(), sdkVersion.kind.String()) jar := filepath.Join(dir, "android.jar") // There's no aidl for other SDKs yet. // TODO(77525052): Add aidl files for other SDKs too. - public_dir := filepath.Join("prebuilts", "sdk", v, "public") + public_dir := filepath.Join("prebuilts", "sdk", sdkVersion.version.String(), "public") aidl := filepath.Join(public_dir, "framework.aidl") jarPath := android.ExistentPathForSource(ctx, jar) aidlPath := android.ExistentPathForSource(ctx, aidl) @@ -120,79 +346,104 @@ func decodeSdkDep(ctx android.BaseContext, sdkContext sdkContext) sdkDep { if (!jarPath.Valid() || !aidlPath.Valid()) && ctx.Config().AllowMissingDependencies() { return sdkDep{ invalidVersion: true, - modules: []string{fmt.Sprintf("sdk_%s_%s_android", api, v)}, + bootclasspath: []string{fmt.Sprintf("sdk_%s_%s_android", sdkVersion.kind, sdkVersion.version.String())}, } } if !jarPath.Valid() { - ctx.PropertyErrorf("sdk_version", "invalid sdk version %q, %q does not exist", v, jar) + ctx.PropertyErrorf("sdk_version", "invalid sdk version %q, %q does not exist", sdkVersion.raw, jar) return sdkDep{} } if !aidlPath.Valid() { - ctx.PropertyErrorf("sdk_version", "invalid sdk version %q, %q does not exist", v, aidl) + ctx.PropertyErrorf("sdk_version", "invalid sdk version %q, %q does not exist", sdkVersion.raw, aidl) return sdkDep{} } + var systemModules string + if sdkVersion.defaultJavaLanguageVersion(ctx).usesJavaModules() { + systemModules = "sdk_public_" + sdkVersion.version.String() + "_system_modules" + } + return sdkDep{ - useFiles: true, - jars: android.Paths{jarPath.Path(), lambdaStubsPath}, - aidl: android.OptionalPathForPath(aidlPath.Path()), + useFiles: true, + jars: android.Paths{jarPath.Path(), lambdaStubsPath}, + aidl: android.OptionalPathForPath(aidlPath.Path()), + systemModules: systemModules, } } - toModule := func(m, r string, aidl android.Path) sdkDep { - ret := sdkDep{ + toModule := func(modules []string, res string, aidl android.Path) sdkDep { + return sdkDep{ useModule: true, - modules: []string{m, config.DefaultLambdaStubsLibrary}, - systemModules: m + "_system_modules", - frameworkResModule: r, + bootclasspath: append(modules, config.DefaultLambdaStubsLibrary), + systemModules: "core-current-stubs-system-modules", + java9Classpath: modules, + frameworkResModule: res, aidl: android.OptionalPathForPath(aidl), } - - if m == "core.current.stubs" { - ret.systemModules = "core-system-modules" - } else if m == "core.platform.api.stubs" { - ret.systemModules = "core-platform-api-stubs-system-modules" - } - return ret } // Ensures that the specificed system SDK version is one of BOARD_SYSTEMSDK_VERSIONS (for vendor apks) // or PRODUCT_SYSTEMSDK_VERSIONS (for other apks or when BOARD_SYSTEMSDK_VERSIONS is not set) - if strings.HasPrefix(v, "system_") && numericSdkVersion != android.FutureApiLevel { + if sdkVersion.kind == sdkSystem && sdkVersion.version.isNumbered() { allowed_versions := ctx.DeviceConfig().PlatformSystemSdkVersions() if ctx.DeviceSpecific() || ctx.SocSpecific() { if len(ctx.DeviceConfig().SystemSdkVersions()) > 0 { allowed_versions = ctx.DeviceConfig().SystemSdkVersions() } } - if len(allowed_versions) > 0 && !android.InList(strconv.Itoa(numericSdkVersion), allowed_versions) { + if len(allowed_versions) > 0 && !android.InList(sdkVersion.version.String(), allowed_versions) { ctx.PropertyErrorf("sdk_version", "incompatible sdk version %q. System SDK version should be one of %q", - v, allowed_versions) + sdkVersion.raw, allowed_versions) } } - if ctx.Config().UnbundledBuildUsePrebuiltSdks() && v != "" { - return toPrebuilt(v) - } + switch sdkVersion.kind { + case sdkPrivate: + return sdkDep{ + useDefaultLibs: true, + frameworkResModule: "framework-res", + } + case sdkNone: + systemModules := sdkContext.systemModules() + if systemModules == "" { + ctx.PropertyErrorf("sdk_version", + `system_modules is required to be set to a non-empty value when sdk_version is "none", did you mean sdk_version: "core_platform"?`) + } else if systemModules == "none" { + return sdkDep{ + noStandardLibs: true, + } + } - switch v { - case "": + return sdkDep{ + useModule: true, + noStandardLibs: true, + systemModules: systemModules, + bootclasspath: []string{systemModules}, + } + case sdkCorePlatform: return sdkDep{ useDefaultLibs: true, frameworkResModule: "framework-res", + noFrameworksLibs: true, } - case "current": - return toModule("android_stubs_current", "framework-res", sdkFrameworkAidlPath(ctx)) - case "system_current": - return toModule("android_system_stubs_current", "framework-res", sdkFrameworkAidlPath(ctx)) - case "test_current": - return toModule("android_test_stubs_current", "framework-res", sdkFrameworkAidlPath(ctx)) - case "core_current": - return toModule("core.current.stubs", "", nil) + case sdkPublic: + return toModule([]string{"android_stubs_current"}, "framework-res", sdkFrameworkAidlPath(ctx)) + case sdkSystem: + return toModule([]string{"android_system_stubs_current"}, "framework-res", sdkFrameworkAidlPath(ctx)) + case sdkTest: + return toModule([]string{"android_test_stubs_current"}, "framework-res", sdkFrameworkAidlPath(ctx)) + case sdkCore: + return toModule([]string{"core.current.stubs"}, "", nil) + case sdkModule: + // TODO(146757305): provide .apk and .aidl that have more APIs for modules + return toModule([]string{"android_module_lib_stubs_current"}, "framework-res", nonUpdatableFrameworkAidlPath(ctx)) + case sdkSystemServer: + // TODO(146757305): provide .apk and .aidl that have more APIs for modules + return toModule([]string{"android_system_server_stubs_current"}, "framework-res", sdkFrameworkAidlPath(ctx)) default: - return toPrebuilt(v) + panic(fmt.Errorf("invalid sdk %q", sdkVersion.raw)) } } @@ -225,6 +476,15 @@ func (sdkPreSingleton) GenerateBuildActions(ctx android.SingletonContext) { ctx.Config().Once(sdkVersionsKey, func() interface{} { return sdkVersions }) } +func LatestSdkVersionInt(ctx android.EarlyModuleContext) int { + sdkVersions := ctx.Config().Get(sdkVersionsKey).([]int) + latestSdkVersion := 0 + if len(sdkVersions) > 0 { + latestSdkVersion = sdkVersions[len(sdkVersions)-1] + } + return latestSdkVersion +} + func sdkSingletonFactory() android.Singleton { return sdkSingleton{} } @@ -237,6 +497,7 @@ func (sdkSingleton) GenerateBuildActions(ctx android.SingletonContext) { } createSdkFrameworkAidl(ctx) + createNonUpdatableFrameworkAidl(ctx) createAPIFingerprint(ctx) } @@ -248,6 +509,31 @@ func createSdkFrameworkAidl(ctx android.SingletonContext) { "android_system_stubs_current", } + combinedAidl := sdkFrameworkAidlPath(ctx) + tempPath := combinedAidl.ReplaceExtension(ctx, "aidl.tmp") + + rule := createFrameworkAidl(stubsModules, tempPath, ctx) + + commitChangeForRestat(rule, tempPath, combinedAidl) + + rule.Build(pctx, ctx, "framework_aidl", "generate framework.aidl") +} + +// Creates a version of framework.aidl for the non-updatable part of the platform. +func createNonUpdatableFrameworkAidl(ctx android.SingletonContext) { + stubsModules := []string{"android_module_lib_stubs_current"} + + combinedAidl := nonUpdatableFrameworkAidlPath(ctx) + tempPath := combinedAidl.ReplaceExtension(ctx, "aidl.tmp") + + rule := createFrameworkAidl(stubsModules, tempPath, ctx) + + commitChangeForRestat(rule, tempPath, combinedAidl) + + rule.Build(pctx, ctx, "framework_non_updatable_aidl", "generate framework_non_updatable.aidl") +} + +func createFrameworkAidl(stubsModules []string, path android.OutputPath, ctx android.SingletonContext) *android.RuleBuilder { stubsJars := make([]android.Paths, len(stubsModules)) ctx.VisitAllModules(func(module android.Module) { @@ -267,8 +553,7 @@ func createSdkFrameworkAidl(ctx android.SingletonContext) { if ctx.Config().AllowMissingDependencies() { missingDeps = append(missingDeps, stubsModules[i]) } else { - ctx.Errorf("failed to find dex jar path for module %q", - stubsModules[i]) + ctx.Errorf("failed to find dex jar path for module %q", stubsModules[i]) } } } @@ -284,7 +569,7 @@ func createSdkFrameworkAidl(ctx android.SingletonContext) { rule.Command(). Text("rm -f").Output(aidl) rule.Command(). - Tool(ctx.Config().HostToolPath(ctx, "sdkparcelables")). + BuiltTool(ctx, "sdkparcelables"). Input(jar). Output(aidl) @@ -292,20 +577,15 @@ func createSdkFrameworkAidl(ctx android.SingletonContext) { } } - combinedAidl := sdkFrameworkAidlPath(ctx) - tempPath := combinedAidl.ReplaceExtension(ctx, "aidl.tmp") - rule.Command(). - Text("rm -f").Output(tempPath) + Text("rm -f").Output(path) rule.Command(). Text("cat"). Inputs(aidls). Text("| sort -u >"). - Output(tempPath) + Output(path) - commitChangeForRestat(rule, tempPath, combinedAidl) - - rule.Build(pctx, ctx, "framework_aidl", "generate framework.aidl") + return rule } func sdkFrameworkAidlPath(ctx android.PathContext) android.OutputPath { @@ -314,6 +594,12 @@ func sdkFrameworkAidlPath(ctx android.PathContext) android.OutputPath { }).(android.OutputPath) } +func nonUpdatableFrameworkAidlPath(ctx android.PathContext) android.OutputPath { + return ctx.Config().Once(nonUpdatableFrameworkAidlPathKey, func() interface{} { + return android.PathForOutput(ctx, "framework_non_updatable.aidl") + }).(android.OutputPath) +} + // Create api_fingerprint.txt func createAPIFingerprint(ctx android.SingletonContext) { out := ApiFingerprintPath(ctx) @@ -337,15 +623,7 @@ func createAPIFingerprint(ctx android.SingletonContext) { cmd.Text("cat"). Inputs(android.PathsForSource(ctx, in)). - Text("|") - - if runtime.GOOS == "darwin" { - cmd.Text("md5") - } else { - cmd.Text("md5sum") - } - - cmd.Text("| cut -d' ' -f1 >"). + Text("| md5sum | cut -d' ' -f1 >"). Output(out) } else { // Unbundled build diff --git a/java/sdk_library.go b/java/sdk_library.go index 84be4dda9..f2a509ad2 100644 --- a/java/sdk_library.go +++ b/java/sdk_library.go @@ -15,95 +15,396 @@ package java import ( - "android/soong/android" - "android/soong/genrule" "fmt" - "io" "path" "path/filepath" + "reflect" + "regexp" "sort" "strings" "sync" "github.com/google/blueprint" "github.com/google/blueprint/proptools" + + "android/soong/android" ) -var ( - sdkStubsLibrarySuffix = ".stubs" - sdkSystemApiSuffix = ".system" - sdkTestApiSuffix = ".test" - sdkDocsSuffix = ".docs" - sdkXmlFileSuffix = ".xml" +const ( + sdkXmlFileSuffix = ".xml" + permissionsTemplate = `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n` + + `<!-- Copyright (C) 2018 The Android Open Source Project\n` + + `\n` + + ` Licensed under the Apache License, Version 2.0 (the \"License\");\n` + + ` you may not use this file except in compliance with the License.\n` + + ` You may obtain a copy of the License at\n` + + `\n` + + ` http://www.apache.org/licenses/LICENSE-2.0\n` + + `\n` + + ` Unless required by applicable law or agreed to in writing, software\n` + + ` distributed under the License is distributed on an \"AS IS\" BASIS,\n` + + ` WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n` + + ` See the License for the specific language governing permissions and\n` + + ` limitations under the License.\n` + + `-->\n` + + `<permissions>\n` + + ` <library name=\"%s\" file=\"%s\"/>\n` + + `</permissions>\n` ) -type stubsLibraryDependencyTag struct { +// A tag to associated a dependency with a specific api scope. +type scopeDependencyTag struct { blueprint.BaseDependencyTag + name string + apiScope *apiScope + + // Function for extracting appropriate path information from the dependency. + depInfoExtractor func(paths *scopePaths, dep android.Module) error +} + +// Extract tag specific information from the dependency. +func (tag scopeDependencyTag) extractDepInfo(ctx android.ModuleContext, dep android.Module, paths *scopePaths) { + err := tag.depInfoExtractor(paths, dep) + if err != nil { + ctx.ModuleErrorf("has an invalid {scopeDependencyTag: %s} dependency on module %s: %s", tag.name, ctx.OtherModuleName(dep), err.Error()) + } +} + +// Provides information about an api scope, e.g. public, system, test. +type apiScope struct { + // The name of the api scope, e.g. public, system, test name string + + // The api scope that this scope extends. + extends *apiScope + + // The legacy enabled status for a specific scope can be dependent on other + // properties that have been specified on the library so it is provided by + // a function that can determine the status by examining those properties. + legacyEnabledStatus func(module *SdkLibrary) bool + + // The default enabled status for non-legacy behavior, which is triggered by + // explicitly enabling at least one api scope. + defaultEnabledStatus bool + + // Gets a pointer to the scope specific properties. + scopeSpecificProperties func(module *SdkLibrary) *ApiScopeProperties + + // The name of the field in the dynamically created structure. + fieldName string + + // The name of the property in the java_sdk_library_import + propertyName string + + // The tag to use to depend on the stubs library module. + stubsTag scopeDependencyTag + + // The tag to use to depend on the stubs source module (if separate from the API module). + stubsSourceTag scopeDependencyTag + + // The tag to use to depend on the API file generating module (if separate from the stubs source module). + apiFileTag scopeDependencyTag + + // The tag to use to depend on the stubs source and API module. + stubsSourceAndApiTag scopeDependencyTag + + // The scope specific prefix to add to the api file base of "current.txt" or "removed.txt". + apiFilePrefix string + + // The scope specific prefix to add to the sdk library module name to construct a scope specific + // module name. + moduleSuffix string + + // SDK version that the stubs library is built against. Note that this is always + // *current. Older stubs library built with a numbered SDK version is created from + // the prebuilt jar. + sdkVersion string + + // Extra arguments to pass to droidstubs for this scope. + droidstubsArgs []string + + // The args that must be passed to droidstubs to generate the stubs source + // for this scope. + // + // The stubs source must include the definitions of everything that is in this + // api scope and all the scopes that this one extends. + droidstubsArgsForGeneratingStubsSource []string + + // The args that must be passed to droidstubs to generate the API for this scope. + // + // The API only includes the additional members that this scope adds over the scope + // that it extends. + droidstubsArgsForGeneratingApi []string + + // True if the stubs source and api can be created by the same metalava invocation. + createStubsSourceAndApiTogether bool + + // Whether the api scope can be treated as unstable, and should skip compat checks. + unstable bool } -type syspropLibraryInterface interface { - SyspropJavaModule() *SdkLibrary +// Initialize a scope, creating and adding appropriate dependency tags +func initApiScope(scope *apiScope) *apiScope { + name := scope.name + scopeByName[name] = scope + allScopeNames = append(allScopeNames, name) + scope.propertyName = strings.ReplaceAll(name, "-", "_") + scope.fieldName = proptools.FieldNameForProperty(scope.propertyName) + scope.stubsTag = scopeDependencyTag{ + name: name + "-stubs", + apiScope: scope, + depInfoExtractor: (*scopePaths).extractStubsLibraryInfoFromDependency, + } + scope.stubsSourceTag = scopeDependencyTag{ + name: name + "-stubs-source", + apiScope: scope, + depInfoExtractor: (*scopePaths).extractStubsSourceInfoFromDep, + } + scope.apiFileTag = scopeDependencyTag{ + name: name + "-api", + apiScope: scope, + depInfoExtractor: (*scopePaths).extractApiInfoFromDep, + } + scope.stubsSourceAndApiTag = scopeDependencyTag{ + name: name + "-stubs-source-and-api", + apiScope: scope, + depInfoExtractor: (*scopePaths).extractStubsSourceAndApiInfoFromApiStubsProvider, + } + + // To get the args needed to generate the stubs source append all the args from + // this scope and all the scopes it extends as each set of args adds additional + // members to the stubs. + var stubsSourceArgs []string + for s := scope; s != nil; s = s.extends { + stubsSourceArgs = append(stubsSourceArgs, s.droidstubsArgs...) + } + scope.droidstubsArgsForGeneratingStubsSource = stubsSourceArgs + + // Currently the args needed to generate the API are the same as the args + // needed to add additional members. + apiArgs := scope.droidstubsArgs + scope.droidstubsArgsForGeneratingApi = apiArgs + + // If the args needed to generate the stubs and API are the same then they + // can be generated in a single invocation of metalava, otherwise they will + // need separate invocations. + scope.createStubsSourceAndApiTogether = reflect.DeepEqual(stubsSourceArgs, apiArgs) + + return scope } -var ( - publicApiStubsTag = dependencyTag{name: "public"} - systemApiStubsTag = dependencyTag{name: "system"} - testApiStubsTag = dependencyTag{name: "test"} - publicApiFileTag = dependencyTag{name: "publicApi"} - systemApiFileTag = dependencyTag{name: "systemApi"} - testApiFileTag = dependencyTag{name: "testApi"} -) +func (scope *apiScope) stubsLibraryModuleName(baseName string) string { + return baseName + ".stubs" + scope.moduleSuffix +} -type apiScope int +func (scope *apiScope) stubsSourceModuleName(baseName string) string { + return baseName + ".stubs.source" + scope.moduleSuffix +} -const ( - apiScopePublic apiScope = iota - apiScopeSystem - apiScopeTest +func (scope *apiScope) apiModuleName(baseName string) string { + return baseName + ".api" + scope.moduleSuffix +} + +func (scope *apiScope) String() string { + return scope.name +} + +type apiScopes []*apiScope + +func (scopes apiScopes) Strings(accessor func(*apiScope) string) []string { + var list []string + for _, scope := range scopes { + list = append(list, accessor(scope)) + } + return list +} + +var ( + scopeByName = make(map[string]*apiScope) + allScopeNames []string + apiScopePublic = initApiScope(&apiScope{ + name: "public", + + // Public scope is enabled by default for both legacy and non-legacy modes. + legacyEnabledStatus: func(module *SdkLibrary) bool { + return true + }, + defaultEnabledStatus: true, + + scopeSpecificProperties: func(module *SdkLibrary) *ApiScopeProperties { + return &module.sdkLibraryProperties.Public + }, + sdkVersion: "current", + }) + apiScopeSystem = initApiScope(&apiScope{ + name: "system", + extends: apiScopePublic, + legacyEnabledStatus: (*SdkLibrary).generateTestAndSystemScopesByDefault, + scopeSpecificProperties: func(module *SdkLibrary) *ApiScopeProperties { + return &module.sdkLibraryProperties.System + }, + apiFilePrefix: "system-", + moduleSuffix: ".system", + sdkVersion: "system_current", + droidstubsArgs: []string{"-showAnnotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\)"}, + }) + apiScopeTest = initApiScope(&apiScope{ + name: "test", + extends: apiScopePublic, + legacyEnabledStatus: (*SdkLibrary).generateTestAndSystemScopesByDefault, + scopeSpecificProperties: func(module *SdkLibrary) *ApiScopeProperties { + return &module.sdkLibraryProperties.Test + }, + apiFilePrefix: "test-", + moduleSuffix: ".test", + sdkVersion: "test_current", + droidstubsArgs: []string{"-showAnnotation android.annotation.TestApi"}, + unstable: true, + }) + apiScopeModuleLib = initApiScope(&apiScope{ + name: "module-lib", + extends: apiScopeSystem, + // The module-lib scope is disabled by default in legacy mode. + // + // Enabling this would break existing usages. + legacyEnabledStatus: func(module *SdkLibrary) bool { + return false + }, + scopeSpecificProperties: func(module *SdkLibrary) *ApiScopeProperties { + return &module.sdkLibraryProperties.Module_lib + }, + apiFilePrefix: "module-lib-", + moduleSuffix: ".module_lib", + sdkVersion: "module_current", + droidstubsArgs: []string{ + "--show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES\\)", + }, + }) + apiScopeSystemServer = initApiScope(&apiScope{ + name: "system-server", + extends: apiScopePublic, + // The system-server scope is disabled by default in legacy mode. + // + // Enabling this would break existing usages. + legacyEnabledStatus: func(module *SdkLibrary) bool { + return false + }, + scopeSpecificProperties: func(module *SdkLibrary) *ApiScopeProperties { + return &module.sdkLibraryProperties.System_server + }, + apiFilePrefix: "system-server-", + moduleSuffix: ".system_server", + sdkVersion: "system_server_current", + droidstubsArgs: []string{ + "--show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.SYSTEM_SERVER\\) ", + "--hide-annotation android.annotation.Hide", + // com.android.* classes are okay in this interface" + "--hide InternalClasses", + }, + }) + allApiScopes = apiScopes{ + apiScopePublic, + apiScopeSystem, + apiScopeTest, + apiScopeModuleLib, + apiScopeSystemServer, + } ) var ( javaSdkLibrariesLock sync.Mutex ) -// java_sdk_library is to make a Java library that implements optional platform APIs to apps. -// It is actually a wrapper of several modules: 1) stubs library that clients are linked against -// to, 2) droiddoc module that internally generates API stubs source files, 3) the real runtime -// shared library that implements the APIs, and 4) XML file for adding the runtime lib to the -// classpath at runtime if requested via <uses-library>. -// // TODO: these are big features that are currently missing // 1) disallowing linking to the runtime shared lib // 2) HTML generation func init() { - android.RegisterModuleType("java_sdk_library", SdkLibraryFactory) - - android.PreArchMutators(func(ctx android.RegisterMutatorsContext) { - ctx.TopDown("java_sdk_library", SdkLibraryMutator).Parallel() - }) + RegisterSdkLibraryBuildComponents(android.InitRegistrationContext) android.RegisterMakeVarsProvider(pctx, func(ctx android.MakeVarsContext) { javaSdkLibraries := javaSdkLibraries(ctx.Config()) sort.Strings(*javaSdkLibraries) ctx.Strict("JAVA_SDK_LIBRARIES", strings.Join(*javaSdkLibraries, " ")) }) + + // Register sdk member types. + android.RegisterSdkMemberType(&sdkLibrarySdkMemberType{ + android.SdkMemberTypeBase{ + PropertyName: "java_sdk_libs", + SupportsSdk: true, + }, + }) +} + +func RegisterSdkLibraryBuildComponents(ctx android.RegistrationContext) { + ctx.RegisterModuleType("java_sdk_library", SdkLibraryFactory) + ctx.RegisterModuleType("java_sdk_library_import", sdkLibraryImportFactory) +} + +// Properties associated with each api scope. +type ApiScopeProperties struct { + // Indicates whether the api surface is generated. + // + // If this is set for any scope then all scopes must explicitly specify if they + // are enabled. This is to prevent new usages from depending on legacy behavior. + // + // Otherwise, if this is not set for any scope then the default behavior is + // scope specific so please refer to the scope specific property documentation. + Enabled *bool + + // The sdk_version to use for building the stubs. + // + // If not specified then it will use an sdk_version determined as follows: + // 1) If the sdk_version specified on the java_sdk_library is none then this + // will be none. This is used for java_sdk_library instances that are used + // to create stubs that contribute to the core_current sdk version. + // 2) Otherwise, it is assumed that this library extends but does not contribute + // directly to a specific sdk_version and so this uses the sdk_version appropriate + // for the api scope. e.g. public will use sdk_version: current, system will use + // sdk_version: system_current, etc. + // + // This does not affect the sdk_version used for either generating the stubs source + // or the API file. They both have to use the same sdk_version as is used for + // compiling the implementation library. + Sdk_version *string } type sdkLibraryProperties struct { - // list of optional source files that are part of API but not part of runtime library. - Api_srcs []string `android:"arch_variant"` + // Visibility for impl library module. If not specified then defaults to the + // visibility property. + Impl_library_visibility []string + + // Visibility for stubs library modules. If not specified then defaults to the + // visibility property. + Stubs_library_visibility []string + + // Visibility for stubs source modules. If not specified then defaults to the + // visibility property. + Stubs_source_visibility []string // List of Java libraries that will be in the classpath when building stubs Stub_only_libs []string `android:"arch_variant"` - // list of package names that will be documented and publicized as API + // list of package names that will be documented and publicized as API. + // This allows the API to be restricted to a subset of the source files provided. + // If this is unspecified then all the source files will be treated as being part + // of the API. Api_packages []string // list of package names that must be hidden from the API Hidden_api_packages []string + // the relative path to the directory containing the api specification files. + // Defaults to "api". + Api_dir *string + + // Determines whether a runtime implementation library is built; defaults to false. + // + // If true then it also prevents the module from being used as a shared module, i.e. + // it is as is shared_library: false, was set. + Api_only *bool + // local files that are used within user customized droiddoc options. Droiddoc_option_files []string @@ -113,15 +414,8 @@ type sdkLibraryProperties struct { // $(location <label>): the path to the droiddoc_option_files with name <label> Droiddoc_options []string - // the java library (in classpath) for documentation that provides java srcs and srcjars. - Srcs_lib *string - - // the base dirs under srcs_lib will be scanned for java srcs. - Srcs_lib_whitelist_dirs []string - - // the sub dirs under srcs_lib_whitelist_dirs will be scanned for java srcs. - // Defaults to "android.annotation". - Srcs_lib_whitelist_pkgs []string + // is set to true, Metalava will allow framework SDK to contain annotations. + Annotations_enabled *bool // a list of top-level directories containing files to merge qualifier annotations // (i.e. those intended to be included in the stubs written) from. @@ -136,273 +430,708 @@ type sdkLibraryProperties struct { // don't create dist rules. No_dist *bool `blueprint:"mutated"` + // indicates whether system and test apis should be generated. + Generate_system_and_test_apis bool `blueprint:"mutated"` + + // The properties specific to the public api scope + // + // Unless explicitly specified by using public.enabled the public api scope is + // enabled by default in both legacy and non-legacy mode. + Public ApiScopeProperties + + // The properties specific to the system api scope + // + // In legacy mode the system api scope is enabled by default when sdk_version + // is set to something other than "none". + // + // In non-legacy mode the system api scope is disabled by default. + System ApiScopeProperties + + // The properties specific to the test api scope + // + // In legacy mode the test api scope is enabled by default when sdk_version + // is set to something other than "none". + // + // In non-legacy mode the test api scope is disabled by default. + Test ApiScopeProperties + + // The properties specific to the module-lib api scope + // + // Unless explicitly specified by using test.enabled the module-lib api scope is + // disabled by default. + Module_lib ApiScopeProperties + + // The properties specific to the system-server api scope + // + // Unless explicitly specified by using test.enabled the module-lib api scope is + // disabled by default. + System_server ApiScopeProperties + + // Determines if the stubs are preferred over the implementation library + // for linking, even when the client doesn't specify sdk_version. When this + // is set to true, such clients are provided with the widest API surface that + // this lib provides. Note however that this option doesn't affect the clients + // that are in the same APEX as this library. In that case, the clients are + // always linked with the implementation library. Default is false. + Default_to_stubs *bool + + // Properties related to api linting. + Api_lint struct { + // Enable api linting. + Enabled *bool + } + // TODO: determines whether to create HTML doc or not //Html_doc *bool } -type SdkLibrary struct { - Library +// Paths to outputs from java_sdk_library and java_sdk_library_import. +// +// Fields that are android.Paths are always set (during GenerateAndroidBuildActions). +// OptionalPaths are always set by java_sdk_library but may not be set by +// java_sdk_library_import as not all instances provide that information. +type scopePaths struct { + // The path (represented as Paths for convenience when returning) to the stubs header jar. + // + // That is the jar that is created by turbine. + stubsHeaderPath android.Paths - sdkLibraryProperties sdkLibraryProperties + // The path (represented as Paths for convenience when returning) to the stubs implementation jar. + // + // This is not the implementation jar, it still only contains stubs. + stubsImplPath android.Paths - publicApiStubsPath android.Paths - systemApiStubsPath android.Paths - testApiStubsPath android.Paths + // The API specification file, e.g. system_current.txt. + currentApiFilePath android.OptionalPath - publicApiStubsImplPath android.Paths - systemApiStubsImplPath android.Paths - testApiStubsImplPath android.Paths + // The specification of API elements removed since the last release. + removedApiFilePath android.OptionalPath - publicApiFilePath android.Path - systemApiFilePath android.Path - testApiFilePath android.Path + // The stubs source jar. + stubsSrcJar android.OptionalPath } -var _ Dependency = (*SdkLibrary)(nil) -var _ SdkLibraryDependency = (*SdkLibrary)(nil) +func (paths *scopePaths) extractStubsLibraryInfoFromDependency(dep android.Module) error { + if lib, ok := dep.(Dependency); ok { + paths.stubsHeaderPath = lib.HeaderJars() + paths.stubsImplPath = lib.ImplementationJars() + return nil + } else { + return fmt.Errorf("expected module that implements Dependency, e.g. java_library") + } +} -func (module *SdkLibrary) DepsMutator(ctx android.BottomUpMutatorContext) { - useBuiltStubs := !ctx.Config().UnbundledBuildUsePrebuiltSdks() - // Add dependencies to the stubs library - if useBuiltStubs { - ctx.AddVariationDependencies(nil, publicApiStubsTag, module.stubsName(apiScopePublic)) +func (paths *scopePaths) treatDepAsApiStubsProvider(dep android.Module, action func(provider ApiStubsProvider)) error { + if apiStubsProvider, ok := dep.(ApiStubsProvider); ok { + action(apiStubsProvider) + return nil + } else { + return fmt.Errorf("expected module that implements ApiStubsProvider, e.g. droidstubs") } - ctx.AddVariationDependencies(nil, publicApiFileTag, module.docsName(apiScopePublic)) +} - if !Bool(module.properties.No_standard_libs) { - if useBuiltStubs { - ctx.AddVariationDependencies(nil, systemApiStubsTag, module.stubsName(apiScopeSystem)) - ctx.AddVariationDependencies(nil, testApiStubsTag, module.stubsName(apiScopeTest)) - } - ctx.AddVariationDependencies(nil, systemApiFileTag, module.docsName(apiScopeSystem)) - ctx.AddVariationDependencies(nil, testApiFileTag, module.docsName(apiScopeTest)) +func (paths *scopePaths) treatDepAsApiStubsSrcProvider(dep android.Module, action func(provider ApiStubsSrcProvider)) error { + if apiStubsProvider, ok := dep.(ApiStubsSrcProvider); ok { + action(apiStubsProvider) + return nil + } else { + return fmt.Errorf("expected module that implements ApiStubsSrcProvider, e.g. droidstubs") } +} - module.Library.deps(ctx) +func (paths *scopePaths) extractApiInfoFromApiStubsProvider(provider ApiStubsProvider) { + paths.currentApiFilePath = android.OptionalPathForPath(provider.ApiFilePath()) + paths.removedApiFilePath = android.OptionalPathForPath(provider.RemovedApiFilePath()) } -func (module *SdkLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) { - module.Library.GenerateAndroidBuildActions(ctx) +func (paths *scopePaths) extractApiInfoFromDep(dep android.Module) error { + return paths.treatDepAsApiStubsProvider(dep, func(provider ApiStubsProvider) { + paths.extractApiInfoFromApiStubsProvider(provider) + }) +} - // Record the paths to the header jars of the library (stubs and impl). - // When this java_sdk_library is dependened from others via "libs" property, - // the recorded paths will be returned depending on the link type of the caller. - ctx.VisitDirectDeps(func(to android.Module) { - otherName := ctx.OtherModuleName(to) - tag := ctx.OtherModuleDependencyTag(to) +func (paths *scopePaths) extractStubsSourceInfoFromApiStubsProviders(provider ApiStubsSrcProvider) { + paths.stubsSrcJar = android.OptionalPathForPath(provider.StubsSrcJar()) +} + +func (paths *scopePaths) extractStubsSourceInfoFromDep(dep android.Module) error { + return paths.treatDepAsApiStubsSrcProvider(dep, func(provider ApiStubsSrcProvider) { + paths.extractStubsSourceInfoFromApiStubsProviders(provider) + }) +} + +func (paths *scopePaths) extractStubsSourceAndApiInfoFromApiStubsProvider(dep android.Module) error { + return paths.treatDepAsApiStubsProvider(dep, func(provider ApiStubsProvider) { + paths.extractApiInfoFromApiStubsProvider(provider) + paths.extractStubsSourceInfoFromApiStubsProviders(provider) + }) +} - if lib, ok := to.(Dependency); ok { - switch tag { - case publicApiStubsTag: - module.publicApiStubsPath = lib.HeaderJars() - module.publicApiStubsImplPath = lib.ImplementationJars() - case systemApiStubsTag: - module.systemApiStubsPath = lib.HeaderJars() - module.systemApiStubsImplPath = lib.ImplementationJars() - case testApiStubsTag: - module.testApiStubsPath = lib.HeaderJars() - module.testApiStubsImplPath = lib.ImplementationJars() +type commonToSdkLibraryAndImportProperties struct { + // The naming scheme to use for the components that this module creates. + // + // If not specified then it defaults to "default". The other allowable value is + // "framework-modules" which matches the scheme currently used by framework modules + // for the equivalent components represented as separate Soong modules. + // + // This is a temporary mechanism to simplify conversion from separate modules for each + // component that follow a different naming pattern to the default one. + // + // TODO(b/155480189) - Remove once naming inconsistencies have been resolved. + Naming_scheme *string + + // Specifies whether this module can be used as an Android shared library; defaults + // to true. + // + // An Android shared library is one that can be referenced in a <uses-library> element + // in an AndroidManifest.xml. + Shared_library *bool +} + +// Common code between sdk library and sdk library import +type commonToSdkLibraryAndImport struct { + moduleBase *android.ModuleBase + + scopePaths map[*apiScope]*scopePaths + + namingScheme sdkLibraryComponentNamingScheme + + commonSdkLibraryProperties commonToSdkLibraryAndImportProperties + + // Functionality related to this being used as a component of a java_sdk_library. + EmbeddableSdkLibraryComponent +} + +func (c *commonToSdkLibraryAndImport) initCommon(moduleBase *android.ModuleBase) { + c.moduleBase = moduleBase + + moduleBase.AddProperties(&c.commonSdkLibraryProperties) + + // Initialize this as an sdk library component. + c.initSdkLibraryComponent(moduleBase) +} + +func (c *commonToSdkLibraryAndImport) initCommonAfterDefaultsApplied(ctx android.DefaultableHookContext) bool { + schemeProperty := proptools.StringDefault(c.commonSdkLibraryProperties.Naming_scheme, "default") + switch schemeProperty { + case "default": + c.namingScheme = &defaultNamingScheme{} + case "framework-modules": + c.namingScheme = &frameworkModulesNamingScheme{} + default: + ctx.PropertyErrorf("naming_scheme", "expected 'default' but was %q", schemeProperty) + return false + } + + // Only track this sdk library if this can be used as a shared library. + if c.sharedLibrary() { + // Use the name specified in the module definition as the owner. + c.sdkLibraryComponentProperties.SdkLibraryToImplicitlyTrack = proptools.StringPtr(c.moduleBase.BaseModuleName()) + } + + return true +} + +// Module name of the runtime implementation library +func (c *commonToSdkLibraryAndImport) implLibraryModuleName() string { + return c.moduleBase.BaseModuleName() + ".impl" +} + +// Module name of the XML file for the lib +func (c *commonToSdkLibraryAndImport) xmlPermissionsModuleName() string { + return c.moduleBase.BaseModuleName() + sdkXmlFileSuffix +} + +// Name of the java_library module that compiles the stubs source. +func (c *commonToSdkLibraryAndImport) stubsLibraryModuleName(apiScope *apiScope) string { + return c.namingScheme.stubsLibraryModuleName(apiScope, c.moduleBase.BaseModuleName()) +} + +// Name of the droidstubs module that generates the stubs source and may also +// generate/check the API. +func (c *commonToSdkLibraryAndImport) stubsSourceModuleName(apiScope *apiScope) string { + return c.namingScheme.stubsSourceModuleName(apiScope, c.moduleBase.BaseModuleName()) +} + +// Name of the droidstubs module that generates/checks the API. Only used if it +// requires different arts to the stubs source generating module. +func (c *commonToSdkLibraryAndImport) apiModuleName(apiScope *apiScope) string { + return c.namingScheme.apiModuleName(apiScope, c.moduleBase.BaseModuleName()) +} + +// The component names for different outputs of the java_sdk_library. +// +// They are similar to the names used for the child modules it creates +const ( + stubsSourceComponentName = "stubs.source" + + apiTxtComponentName = "api.txt" + + removedApiTxtComponentName = "removed-api.txt" +) + +// A regular expression to match tags that reference a specific stubs component. +// +// It will only match if given a valid scope and a valid component. It is verfy strict +// to ensure it does not accidentally match a similar looking tag that should be processed +// by the embedded Library. +var tagSplitter = func() *regexp.Regexp { + // Given a list of literal string items returns a regular expression that will + // match any one of the items. + choice := func(items ...string) string { + return `\Q` + strings.Join(items, `\E|\Q`) + `\E` + } + + // Regular expression to match one of the scopes. + scopesRegexp := choice(allScopeNames...) + + // Regular expression to match one of the components. + componentsRegexp := choice(stubsSourceComponentName, apiTxtComponentName, removedApiTxtComponentName) + + // Regular expression to match any combination of one scope and one component. + return regexp.MustCompile(fmt.Sprintf(`^\.(%s)\.(%s)$`, scopesRegexp, componentsRegexp)) +}() + +// For OutputFileProducer interface +// +// .<scope>.stubs.source +// .<scope>.api.txt +// .<scope>.removed-api.txt +func (c *commonToSdkLibraryAndImport) commonOutputFiles(tag string) (android.Paths, error) { + if groups := tagSplitter.FindStringSubmatch(tag); groups != nil { + scopeName := groups[1] + component := groups[2] + + if scope, ok := scopeByName[scopeName]; ok { + paths := c.findScopePaths(scope) + if paths == nil { + return nil, fmt.Errorf("%q does not provide api scope %s", c.moduleBase.BaseModuleName(), scopeName) + } + + switch component { + case stubsSourceComponentName: + if paths.stubsSrcJar.Valid() { + return android.Paths{paths.stubsSrcJar.Path()}, nil + } + + case apiTxtComponentName: + if paths.currentApiFilePath.Valid() { + return android.Paths{paths.currentApiFilePath.Path()}, nil + } + + case removedApiTxtComponentName: + if paths.removedApiFilePath.Valid() { + return android.Paths{paths.removedApiFilePath.Path()}, nil + } } + + return nil, fmt.Errorf("%s not available for api scope %s", component, scopeName) + } else { + return nil, fmt.Errorf("unknown scope %s in %s", scope, tag) } - if doc, ok := to.(ApiFilePath); ok { - switch tag { - case publicApiFileTag: - module.publicApiFilePath = doc.ApiFilePath() - case systemApiFileTag: - module.systemApiFilePath = doc.ApiFilePath() - case testApiFileTag: - module.testApiFilePath = doc.ApiFilePath() - default: - ctx.ModuleErrorf("depends on module %q of unknown tag %q", otherName, tag) + + } else { + return nil, nil + } +} + +func (c *commonToSdkLibraryAndImport) getScopePathsCreateIfNeeded(scope *apiScope) *scopePaths { + if c.scopePaths == nil { + c.scopePaths = make(map[*apiScope]*scopePaths) + } + paths := c.scopePaths[scope] + if paths == nil { + paths = &scopePaths{} + c.scopePaths[scope] = paths + } + + return paths +} + +func (c *commonToSdkLibraryAndImport) findScopePaths(scope *apiScope) *scopePaths { + if c.scopePaths == nil { + return nil + } + + return c.scopePaths[scope] +} + +// If this does not support the requested api scope then find the closest available +// scope it does support. Returns nil if no such scope is available. +func (c *commonToSdkLibraryAndImport) findClosestScopePath(scope *apiScope) *scopePaths { + for s := scope; s != nil; s = s.extends { + if paths := c.findScopePaths(s); paths != nil { + return paths + } + } + + // This should never happen outside tests as public should be the base scope for every + // scope and is enabled by default. + return nil +} + +func (c *commonToSdkLibraryAndImport) selectHeaderJarsForSdkVersion(ctx android.BaseModuleContext, sdkVersion sdkSpec) android.Paths { + + // If a specific numeric version has been requested then use prebuilt versions of the sdk. + if sdkVersion.version.isNumbered() { + return PrebuiltJars(ctx, c.moduleBase.BaseModuleName(), sdkVersion) + } + + var apiScope *apiScope + switch sdkVersion.kind { + case sdkSystem: + apiScope = apiScopeSystem + case sdkModule: + apiScope = apiScopeModuleLib + case sdkTest: + apiScope = apiScopeTest + case sdkSystemServer: + apiScope = apiScopeSystemServer + default: + apiScope = apiScopePublic + } + + paths := c.findClosestScopePath(apiScope) + if paths == nil { + var scopes []string + for _, s := range allApiScopes { + if c.findScopePaths(s) != nil { + scopes = append(scopes, s.name) } } - }) + ctx.ModuleErrorf("requires api scope %s from %s but it only has %q available", apiScope.name, c.moduleBase.BaseModuleName(), scopes) + return nil + } + + return paths.stubsHeaderPath } -func (module *SdkLibrary) AndroidMk() android.AndroidMkData { - data := module.Library.AndroidMk() - data.Required = append(data.Required, module.xmlFileName()) +func (c *commonToSdkLibraryAndImport) sdkComponentPropertiesForChildLibrary() interface{} { + componentProps := &struct { + SdkLibraryToImplicitlyTrack *string + }{} - data.Custom = func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) { - android.WriteAndroidMkData(w, data) + if c.sharedLibrary() { + // Mark the stubs library as being components of this java_sdk_library so that + // any app that includes code which depends (directly or indirectly) on the stubs + // library will have the appropriate <uses-library> invocation inserted into its + // manifest if necessary. + componentProps.SdkLibraryToImplicitlyTrack = proptools.StringPtr(c.moduleBase.BaseModuleName()) + } - module.Library.AndroidMkHostDex(w, name, data) - if !Bool(module.sdkLibraryProperties.No_dist) { - // Create a phony module that installs the impl library, for the case when this lib is - // in PRODUCT_PACKAGES. - owner := module.ModuleBase.Owner() - if owner == "" { - if Bool(module.sdkLibraryProperties.Core_lib) { - owner = "core" - } else { - owner = "android" + return componentProps +} + +// Check if this can be used as a shared library. +func (c *commonToSdkLibraryAndImport) sharedLibrary() bool { + return proptools.BoolDefault(c.commonSdkLibraryProperties.Shared_library, true) +} + +// Properties related to the use of a module as an component of a java_sdk_library. +type SdkLibraryComponentProperties struct { + + // The name of the java_sdk_library/_import to add to a <uses-library> entry + // in the AndroidManifest.xml of any Android app that includes code that references + // this module. If not set then no java_sdk_library/_import is tracked. + SdkLibraryToImplicitlyTrack *string `blueprint:"mutated"` +} + +// Structure to be embedded in a module struct that needs to support the +// SdkLibraryComponentDependency interface. +type EmbeddableSdkLibraryComponent struct { + sdkLibraryComponentProperties SdkLibraryComponentProperties +} + +func (e *EmbeddableSdkLibraryComponent) initSdkLibraryComponent(moduleBase *android.ModuleBase) { + moduleBase.AddProperties(&e.sdkLibraryComponentProperties) +} + +// to satisfy SdkLibraryComponentDependency +func (e *EmbeddableSdkLibraryComponent) OptionalImplicitSdkLibrary() []string { + if e.sdkLibraryComponentProperties.SdkLibraryToImplicitlyTrack != nil { + return []string{*e.sdkLibraryComponentProperties.SdkLibraryToImplicitlyTrack} + } + return nil +} + +// Implemented by modules that are (or possibly could be) a component of a java_sdk_library +// (including the java_sdk_library) itself. +type SdkLibraryComponentDependency interface { + // The optional name of the sdk library that should be implicitly added to the + // AndroidManifest of an app that contains code which references the sdk library. + // + // Returns an array containing 0 or 1 items rather than a *string to make it easier + // to append this to the list of exported sdk libraries. + OptionalImplicitSdkLibrary() []string +} + +// Make sure that all the module types that are components of java_sdk_library/_import +// and which can be referenced (directly or indirectly) from an android app implement +// the SdkLibraryComponentDependency interface. +var _ SdkLibraryComponentDependency = (*Library)(nil) +var _ SdkLibraryComponentDependency = (*Import)(nil) +var _ SdkLibraryComponentDependency = (*SdkLibrary)(nil) +var _ SdkLibraryComponentDependency = (*SdkLibraryImport)(nil) + +// Provides access to sdk_version related header and implentation jars. +type SdkLibraryDependency interface { + SdkLibraryComponentDependency + + // Get the header jars appropriate for the supplied sdk_version. + // + // These are turbine generated jars so they only change if the externals of the + // class changes but it does not contain and implementation or JavaDoc. + SdkHeaderJars(ctx android.BaseModuleContext, sdkVersion sdkSpec) android.Paths + + // Get the implementation jars appropriate for the supplied sdk version. + // + // These are either the implementation jar for the whole sdk library or the implementation + // jars for the stubs. The latter should only be needed when generating JavaDoc as otherwise + // they are identical to the corresponding header jars. + SdkImplementationJars(ctx android.BaseModuleContext, sdkVersion sdkSpec) android.Paths +} + +type SdkLibrary struct { + Library + + sdkLibraryProperties sdkLibraryProperties + + // Map from api scope to the scope specific property structure. + scopeToProperties map[*apiScope]*ApiScopeProperties + + commonToSdkLibraryAndImport +} + +var _ Dependency = (*SdkLibrary)(nil) +var _ SdkLibraryDependency = (*SdkLibrary)(nil) + +func (module *SdkLibrary) generateTestAndSystemScopesByDefault() bool { + return module.sdkLibraryProperties.Generate_system_and_test_apis +} + +func (module *SdkLibrary) getGeneratedApiScopes(ctx android.EarlyModuleContext) apiScopes { + // Check to see if any scopes have been explicitly enabled. If any have then all + // must be. + anyScopesExplicitlyEnabled := false + for _, scope := range allApiScopes { + scopeProperties := module.scopeToProperties[scope] + if scopeProperties.Enabled != nil { + anyScopesExplicitlyEnabled = true + break + } + } + + var generatedScopes apiScopes + enabledScopes := make(map[*apiScope]struct{}) + for _, scope := range allApiScopes { + scopeProperties := module.scopeToProperties[scope] + // If any scopes are explicitly enabled then ignore the legacy enabled status. + // This is to ensure that any new usages of this module type do not rely on legacy + // behaviour. + defaultEnabledStatus := false + if anyScopesExplicitlyEnabled { + defaultEnabledStatus = scope.defaultEnabledStatus + } else { + defaultEnabledStatus = scope.legacyEnabledStatus(module) + } + enabled := proptools.BoolDefault(scopeProperties.Enabled, defaultEnabledStatus) + if enabled { + enabledScopes[scope] = struct{}{} + generatedScopes = append(generatedScopes, scope) + } + } + + // Now check to make sure that any scope that is extended by an enabled scope is also + // enabled. + for _, scope := range allApiScopes { + if _, ok := enabledScopes[scope]; ok { + extends := scope.extends + if extends != nil { + if _, ok := enabledScopes[extends]; !ok { + ctx.ModuleErrorf("enabled api scope %q depends on disabled scope %q", scope, extends) } } - // Create dist rules to install the stubs libs to the dist dir - if len(module.publicApiStubsPath) == 1 { - fmt.Fprintln(w, "$(call dist-for-goals,sdk win_sdk,"+ - module.publicApiStubsImplPath.Strings()[0]+ - ":"+path.Join("apistubs", owner, "public", - module.BaseModuleName()+".jar")+")") - } - if len(module.systemApiStubsPath) == 1 { - fmt.Fprintln(w, "$(call dist-for-goals,sdk win_sdk,"+ - module.systemApiStubsImplPath.Strings()[0]+ - ":"+path.Join("apistubs", owner, "system", - module.BaseModuleName()+".jar")+")") - } - if len(module.testApiStubsPath) == 1 { - fmt.Fprintln(w, "$(call dist-for-goals,sdk win_sdk,"+ - module.testApiStubsImplPath.Strings()[0]+ - ":"+path.Join("apistubs", owner, "test", - module.BaseModuleName()+".jar")+")") - } - if module.publicApiFilePath != nil { - fmt.Fprintln(w, "$(call dist-for-goals,sdk win_sdk,"+ - module.publicApiFilePath.String()+ - ":"+path.Join("apistubs", owner, "public", "api", - module.BaseModuleName()+".txt")+")") - } - if module.systemApiFilePath != nil { - fmt.Fprintln(w, "$(call dist-for-goals,sdk win_sdk,"+ - module.systemApiFilePath.String()+ - ":"+path.Join("apistubs", owner, "system", "api", - module.BaseModuleName()+".txt")+")") - } - if module.testApiFilePath != nil { - fmt.Fprintln(w, "$(call dist-for-goals,sdk win_sdk,"+ - module.testApiFilePath.String()+ - ":"+path.Join("apistubs", owner, "test", "api", - module.BaseModuleName()+".txt")+")") - } } } - return data + + return generatedScopes } -// Module name of the stubs library -func (module *SdkLibrary) stubsName(apiScope apiScope) string { - stubsName := module.BaseModuleName() + sdkStubsLibrarySuffix - switch apiScope { - case apiScopeSystem: - stubsName = stubsName + sdkSystemApiSuffix - case apiScopeTest: - stubsName = stubsName + sdkTestApiSuffix +type sdkLibraryComponentTag struct { + blueprint.BaseDependencyTag + name string +} + +// Mark this tag so dependencies that use it are excluded from visibility enforcement. +func (t sdkLibraryComponentTag) ExcludeFromVisibilityEnforcement() {} + +var xmlPermissionsFileTag = sdkLibraryComponentTag{name: "xml-permissions-file"} + +func IsXmlPermissionsFileDepTag(depTag blueprint.DependencyTag) bool { + if dt, ok := depTag.(sdkLibraryComponentTag); ok { + return dt == xmlPermissionsFileTag } - return stubsName + return false } -// Module name of the docs -func (module *SdkLibrary) docsName(apiScope apiScope) string { - docsName := module.BaseModuleName() + sdkDocsSuffix - switch apiScope { - case apiScopeSystem: - docsName = docsName + sdkSystemApiSuffix - case apiScopeTest: - docsName = docsName + sdkTestApiSuffix +var implLibraryTag = sdkLibraryComponentTag{name: "impl-library"} + +func (module *SdkLibrary) DepsMutator(ctx android.BottomUpMutatorContext) { + for _, apiScope := range module.getGeneratedApiScopes(ctx) { + // Add dependencies to the stubs library + ctx.AddVariationDependencies(nil, apiScope.stubsTag, module.stubsLibraryModuleName(apiScope)) + + // If the stubs source and API cannot be generated together then add an additional dependency on + // the API module. + if apiScope.createStubsSourceAndApiTogether { + // Add a dependency on the stubs source in order to access both stubs source and api information. + ctx.AddVariationDependencies(nil, apiScope.stubsSourceAndApiTag, module.stubsSourceModuleName(apiScope)) + } else { + // Add separate dependencies on the creators of the stubs source files and the API. + ctx.AddVariationDependencies(nil, apiScope.stubsSourceTag, module.stubsSourceModuleName(apiScope)) + ctx.AddVariationDependencies(nil, apiScope.apiFileTag, module.apiModuleName(apiScope)) + } + } + + if module.requiresRuntimeImplementationLibrary() { + // Add dependency to the rule for generating the implementation library. + ctx.AddDependency(module, implLibraryTag, module.implLibraryModuleName()) + + if module.sharedLibrary() { + // Add dependency to the rule for generating the xml permissions file + ctx.AddDependency(module, xmlPermissionsFileTag, module.xmlPermissionsModuleName()) + } + + // Only add the deps for the library if it is actually going to be built. + module.Library.deps(ctx) } - return docsName } -// Module name of the runtime implementation library -func (module *SdkLibrary) implName() string { - return module.BaseModuleName() +func (module *SdkLibrary) OutputFiles(tag string) (android.Paths, error) { + paths, err := module.commonOutputFiles(tag) + if paths == nil && err == nil { + return module.Library.OutputFiles(tag) + } else { + return paths, err + } } -// File path to the runtime implementation library -func (module *SdkLibrary) implPath() string { - partition := "system" - if module.SocSpecific() { - partition = "vendor" - } else if module.DeviceSpecific() { - partition = "odm" - } else if module.ProductSpecific() { - partition = "product" +func (module *SdkLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) { + // Only build an implementation library if required. + if module.requiresRuntimeImplementationLibrary() { + module.Library.GenerateAndroidBuildActions(ctx) } - return "/" + partition + "/framework/" + module.implName() + ".jar" + + // Record the paths to the header jars of the library (stubs and impl). + // When this java_sdk_library is depended upon from others via "libs" property, + // the recorded paths will be returned depending on the link type of the caller. + ctx.VisitDirectDeps(func(to android.Module) { + tag := ctx.OtherModuleDependencyTag(to) + + // Extract information from any of the scope specific dependencies. + if scopeTag, ok := tag.(scopeDependencyTag); ok { + apiScope := scopeTag.apiScope + scopePaths := module.getScopePathsCreateIfNeeded(apiScope) + + // Extract information from the dependency. The exact information extracted + // is determined by the nature of the dependency which is determined by the tag. + scopeTag.extractDepInfo(ctx, to, scopePaths) + } + }) } -// Module name of the XML file for the lib -func (module *SdkLibrary) xmlFileName() string { - return module.BaseModuleName() + sdkXmlFileSuffix -} - -// SDK version that the stubs library is built against. Note that this is always -// *current. Older stubs library built with a numberd SDK version is created from -// the prebuilt jar. -func (module *SdkLibrary) sdkVersion(apiScope apiScope) string { - switch apiScope { - case apiScopePublic: - return "current" - case apiScopeSystem: - return "system_current" - case apiScopeTest: - return "test_current" - default: - return "current" +func (module *SdkLibrary) AndroidMkEntries() []android.AndroidMkEntries { + if !module.requiresRuntimeImplementationLibrary() { + return nil } + entriesList := module.Library.AndroidMkEntries() + entries := &entriesList[0] + entries.Required = append(entries.Required, module.xmlPermissionsModuleName()) + return entriesList } -// $(INTERNAL_PLATFORM_<apiTagName>_API_FILE) points to the generated -// api file for the current source -// TODO: remove this when apicheck is done in soong -func (module *SdkLibrary) apiTagName(apiScope apiScope) string { - apiTagName := strings.Replace(strings.ToUpper(module.BaseModuleName()), ".", "_", -1) - switch apiScope { - case apiScopeSystem: - apiTagName = apiTagName + "_SYSTEM" - case apiScopeTest: - apiTagName = apiTagName + "_TEST" +// The dist path of the stub artifacts +func (module *SdkLibrary) apiDistPath(apiScope *apiScope) string { + if module.ModuleBase.Owner() != "" { + return path.Join("apistubs", module.ModuleBase.Owner(), apiScope.name) + } else if Bool(module.sdkLibraryProperties.Core_lib) { + return path.Join("apistubs", "core", apiScope.name) + } else { + return path.Join("apistubs", "android", apiScope.name) } - return apiTagName } -func (module *SdkLibrary) latestApiFilegroupName(apiScope apiScope) string { - name := ":" + module.BaseModuleName() + ".api." - switch apiScope { - case apiScopePublic: - name = name + "public" - case apiScopeSystem: - name = name + "system" - case apiScopeTest: - name = name + "test" +// Get the sdk version for use when compiling the stubs library. +func (module *SdkLibrary) sdkVersionForStubsLibrary(mctx android.EarlyModuleContext, apiScope *apiScope) string { + scopeProperties := module.scopeToProperties[apiScope] + if scopeProperties.Sdk_version != nil { + return proptools.String(scopeProperties.Sdk_version) } - name = name + ".latest" - return name + + sdkDep := decodeSdkDep(mctx, sdkContext(&module.Library)) + if sdkDep.hasStandardLibs() { + // If building against a standard sdk then use the sdk version appropriate for the scope. + return apiScope.sdkVersion + } else { + // Otherwise, use no system module. + return "none" + } +} + +func (module *SdkLibrary) latestApiFilegroupName(apiScope *apiScope) string { + return ":" + module.BaseModuleName() + ".api." + apiScope.name + ".latest" +} + +func (module *SdkLibrary) latestRemovedApiFilegroupName(apiScope *apiScope) string { + return ":" + module.BaseModuleName() + "-removed.api." + apiScope.name + ".latest" } -func (module *SdkLibrary) latestRemovedApiFilegroupName(apiScope apiScope) string { - name := ":" + module.BaseModuleName() + "-removed.api." - switch apiScope { - case apiScopePublic: - name = name + "public" - case apiScopeSystem: - name = name + "system" - case apiScopeTest: - name = name + "test" +// Creates the implementation java library +func (module *SdkLibrary) createImplLibrary(mctx android.DefaultableHookContext) { + + moduleNamePtr := proptools.StringPtr(module.BaseModuleName()) + + props := struct { + Name *string + Visibility []string + Instrument bool + ConfigurationName *string + }{ + Name: proptools.StringPtr(module.implLibraryModuleName()), + Visibility: module.sdkLibraryProperties.Impl_library_visibility, + // Set the instrument property to ensure it is instrumented when instrumentation is required. + Instrument: true, + + // Make the created library behave as if it had the same name as this module. + ConfigurationName: moduleNamePtr, + } + + properties := []interface{}{ + &module.properties, + &module.protoProperties, + &module.deviceProperties, + &module.dexpreoptProperties, + &module.linter.properties, + &props, + module.sdkComponentPropertiesForChildLibrary(), } - name = name + ".latest" - return name + mctx.CreateModule(LibraryFactory, properties...) } // Creates a static java library that has API stubs -func (module *SdkLibrary) createStubsLibrary(mctx android.TopDownMutatorContext, apiScope apiScope) { +func (module *SdkLibrary) createStubsLibrary(mctx android.DefaultableHookContext, apiScope *apiScope) { props := struct { Name *string + Visibility []string Srcs []string + Installable *bool Sdk_version *string + System_modules *string + Patch_module *string Libs []string - Soc_specific *bool - Device_specific *bool - Product_specific *bool Compile_dex *bool - No_standard_libs *bool - System_modules *string Java_version *string Product_variables struct { - Unbundled_build struct { - Enabled *bool - } Pdk struct { Enabled *bool } @@ -411,251 +1140,313 @@ func (module *SdkLibrary) createStubsLibrary(mctx android.TopDownMutatorContext, Srcs []string Javacflags []string } + Dist struct { + Targets []string + Dest *string + Dir *string + Tag *string + } }{} - props.Name = proptools.StringPtr(module.stubsName(apiScope)) + props.Name = proptools.StringPtr(module.stubsLibraryModuleName(apiScope)) + + // If stubs_library_visibility is not set then the created module will use the + // visibility of this module. + visibility := module.sdkLibraryProperties.Stubs_library_visibility + props.Visibility = visibility + // sources are generated from the droiddoc - props.Srcs = []string{":" + module.docsName(apiScope)} - props.Sdk_version = proptools.StringPtr(module.sdkVersion(apiScope)) + props.Srcs = []string{":" + module.stubsSourceModuleName(apiScope)} + sdkVersion := module.sdkVersionForStubsLibrary(mctx, apiScope) + props.Sdk_version = proptools.StringPtr(sdkVersion) + props.System_modules = module.deviceProperties.System_modules + props.Patch_module = module.properties.Patch_module + props.Installable = proptools.BoolPtr(false) props.Libs = module.sdkLibraryProperties.Stub_only_libs - // Unbundled apps will use the prebult one from /prebuilts/sdk - if mctx.Config().UnbundledBuildUsePrebuiltSdks() { - props.Product_variables.Unbundled_build.Enabled = proptools.BoolPtr(false) + // The stub-annotations library contains special versions of the annotations + // with CLASS retention policy, so that they're kept. + if proptools.Bool(module.sdkLibraryProperties.Annotations_enabled) { + props.Libs = append(props.Libs, "stub-annotations") } props.Product_variables.Pdk.Enabled = proptools.BoolPtr(false) - props.No_standard_libs = module.Library.Module.properties.No_standard_libs - props.System_modules = module.Library.Module.deviceProperties.System_modules - props.Openjdk9.Srcs = module.Library.Module.properties.Openjdk9.Srcs - props.Openjdk9.Javacflags = module.Library.Module.properties.Openjdk9.Javacflags - props.Java_version = module.Library.Module.properties.Java_version - if module.Library.Module.deviceProperties.Compile_dex != nil { - props.Compile_dex = module.Library.Module.deviceProperties.Compile_dex + props.Openjdk9.Srcs = module.properties.Openjdk9.Srcs + props.Openjdk9.Javacflags = module.properties.Openjdk9.Javacflags + // We compile the stubs for 1.8 in line with the main android.jar stubs, and potential + // interop with older developer tools that don't support 1.9. + props.Java_version = proptools.StringPtr("1.8") + if module.deviceProperties.Compile_dex != nil { + props.Compile_dex = module.deviceProperties.Compile_dex } - if module.SocSpecific() { - props.Soc_specific = proptools.BoolPtr(true) - } else if module.DeviceSpecific() { - props.Device_specific = proptools.BoolPtr(true) - } else if module.ProductSpecific() { - props.Product_specific = proptools.BoolPtr(true) + // Dist the class jar artifact for sdk builds. + if !Bool(module.sdkLibraryProperties.No_dist) { + props.Dist.Targets = []string{"sdk", "win_sdk"} + props.Dist.Dest = proptools.StringPtr(fmt.Sprintf("%v.jar", module.BaseModuleName())) + props.Dist.Dir = proptools.StringPtr(module.apiDistPath(apiScope)) + props.Dist.Tag = proptools.StringPtr(".jar") } - mctx.CreateModule(android.ModuleFactoryAdaptor(LibraryFactory), &props) + mctx.CreateModule(LibraryFactory, &props, module.sdkComponentPropertiesForChildLibrary()) } -// Creates a droiddoc module that creates stubs source files from the given full source -// files -func (module *SdkLibrary) createDocs(mctx android.TopDownMutatorContext, apiScope apiScope) { +// Creates a droidstubs module that creates stubs source files from the given full source +// files and also updates and checks the API specification files. +func (module *SdkLibrary) createStubsSourcesAndApi(mctx android.DefaultableHookContext, apiScope *apiScope, name string, createStubSources, createApi bool, scopeSpecificDroidstubsArgs []string) { props := struct { Name *string + Visibility []string Srcs []string Installable *bool - Srcs_lib *string - Srcs_lib_whitelist_dirs []string - Srcs_lib_whitelist_pkgs []string + Sdk_version *string + System_modules *string Libs []string Arg_files []string Args *string - Api_tag_name *string - Api_filename *string - Removed_api_filename *string - No_standard_libs *bool Java_version *string + Annotations_enabled *bool Merge_annotations_dirs []string Merge_inclusion_annotations_dirs []string + Generate_stubs *bool Check_api struct { Current ApiToCheck Last_released ApiToCheck Ignore_missing_latest_api *bool + + Api_lint struct { + Enabled *bool + New_since *string + Baseline_file *string + } } Aidl struct { Include_dirs []string Local_include_dirs []string } + Dist struct { + Targets []string + Dest *string + Dir *string + } }{} - props.Name = proptools.StringPtr(module.docsName(apiScope)) - props.Srcs = append(props.Srcs, module.Library.Module.properties.Srcs...) - props.Srcs = append(props.Srcs, module.sdkLibraryProperties.Api_srcs...) + // The stubs source processing uses the same compile time classpath when extracting the + // API from the implementation library as it does when compiling it. i.e. the same + // * sdk version + // * system_modules + // * libs (static_libs/libs) + + props.Name = proptools.StringPtr(name) + + // If stubs_source_visibility is not set then the created module will use the + // visibility of this module. + visibility := module.sdkLibraryProperties.Stubs_source_visibility + props.Visibility = visibility + + props.Srcs = append(props.Srcs, module.properties.Srcs...) + props.Sdk_version = module.deviceProperties.Sdk_version + props.System_modules = module.deviceProperties.System_modules props.Installable = proptools.BoolPtr(false) // A droiddoc module has only one Libs property and doesn't distinguish between // shared libs and static libs. So we need to add both of these libs to Libs property. - props.Libs = module.Library.Module.properties.Libs - props.Libs = append(props.Libs, module.Library.Module.properties.Static_libs...) - props.Aidl.Include_dirs = module.Library.Module.deviceProperties.Aidl.Include_dirs - props.Aidl.Local_include_dirs = module.Library.Module.deviceProperties.Aidl.Local_include_dirs - props.No_standard_libs = module.Library.Module.properties.No_standard_libs - props.Java_version = module.Library.Module.properties.Java_version + props.Libs = module.properties.Libs + props.Libs = append(props.Libs, module.properties.Static_libs...) + props.Aidl.Include_dirs = module.deviceProperties.Aidl.Include_dirs + props.Aidl.Local_include_dirs = module.deviceProperties.Aidl.Local_include_dirs + props.Java_version = module.properties.Java_version + props.Annotations_enabled = module.sdkLibraryProperties.Annotations_enabled props.Merge_annotations_dirs = module.sdkLibraryProperties.Merge_annotations_dirs props.Merge_inclusion_annotations_dirs = module.sdkLibraryProperties.Merge_inclusion_annotations_dirs - droiddocArgs := " --stub-packages " + strings.Join(module.sdkLibraryProperties.Api_packages, ":") + - " " + android.JoinWithPrefix(module.sdkLibraryProperties.Hidden_api_packages, " --hide-package ") + - " " + android.JoinWithPrefix(module.sdkLibraryProperties.Droiddoc_options, " ") + - " --hide MissingPermission --hide BroadcastBehavior " + - "--hide HiddenSuperclass --hide DeprecationMismatch --hide UnavailableSymbol " + - "--hide SdkConstant --hide HiddenTypeParameter --hide Todo --hide Typo" + droidstubsArgs := []string{} + if len(module.sdkLibraryProperties.Api_packages) != 0 { + droidstubsArgs = append(droidstubsArgs, "--stub-packages "+strings.Join(module.sdkLibraryProperties.Api_packages, ":")) + } + if len(module.sdkLibraryProperties.Hidden_api_packages) != 0 { + droidstubsArgs = append(droidstubsArgs, + android.JoinWithPrefix(module.sdkLibraryProperties.Hidden_api_packages, " --hide-package ")) + } + droidstubsArgs = append(droidstubsArgs, module.sdkLibraryProperties.Droiddoc_options...) + disabledWarnings := []string{ + "MissingPermission", + "BroadcastBehavior", + "HiddenSuperclass", + "DeprecationMismatch", + "UnavailableSymbol", + "SdkConstant", + "HiddenTypeParameter", + "Todo", + "Typo", + } + droidstubsArgs = append(droidstubsArgs, android.JoinWithPrefix(disabledWarnings, "--hide ")) - switch apiScope { - case apiScopeSystem: - droiddocArgs = droiddocArgs + " -showAnnotation android.annotation.SystemApi" - case apiScopeTest: - droiddocArgs = droiddocArgs + " -showAnnotation android.annotation.TestApi" + if !createStubSources { + // Stubs are not required. + props.Generate_stubs = proptools.BoolPtr(false) } + + // Add in scope specific arguments. + droidstubsArgs = append(droidstubsArgs, scopeSpecificDroidstubsArgs...) props.Arg_files = module.sdkLibraryProperties.Droiddoc_option_files - props.Args = proptools.StringPtr(droiddocArgs) - - // List of APIs identified from the provided source files are created. They are later - // compared against to the not-yet-released (a.k.a current) list of APIs and to the - // last-released (a.k.a numbered) list of API. - currentApiFileName := "current.txt" - removedApiFileName := "removed.txt" - switch apiScope { - case apiScopeSystem: - currentApiFileName = "system-" + currentApiFileName - removedApiFileName = "system-" + removedApiFileName - case apiScopeTest: - currentApiFileName = "test-" + currentApiFileName - removedApiFileName = "test-" + removedApiFileName - } - currentApiFileName = path.Join("api", currentApiFileName) - removedApiFileName = path.Join("api", removedApiFileName) - // TODO(jiyong): remove these three props - props.Api_tag_name = proptools.StringPtr(module.apiTagName(apiScope)) - props.Api_filename = proptools.StringPtr(currentApiFileName) - props.Removed_api_filename = proptools.StringPtr(removedApiFileName) - - // check against the not-yet-release API - props.Check_api.Current.Api_file = proptools.StringPtr(currentApiFileName) - props.Check_api.Current.Removed_api_file = proptools.StringPtr(removedApiFileName) - - // check against the latest released API - props.Check_api.Last_released.Api_file = proptools.StringPtr( - module.latestApiFilegroupName(apiScope)) - props.Check_api.Last_released.Removed_api_file = proptools.StringPtr( - module.latestRemovedApiFilegroupName(apiScope)) - props.Check_api.Ignore_missing_latest_api = proptools.BoolPtr(true) - props.Srcs_lib = module.sdkLibraryProperties.Srcs_lib - props.Srcs_lib_whitelist_dirs = module.sdkLibraryProperties.Srcs_lib_whitelist_dirs - props.Srcs_lib_whitelist_pkgs = module.sdkLibraryProperties.Srcs_lib_whitelist_pkgs - - mctx.CreateModule(android.ModuleFactoryAdaptor(DroidstubsFactory), &props) + props.Args = proptools.StringPtr(strings.Join(droidstubsArgs, " ")) + + if createApi { + // List of APIs identified from the provided source files are created. They are later + // compared against to the not-yet-released (a.k.a current) list of APIs and to the + // last-released (a.k.a numbered) list of API. + currentApiFileName := apiScope.apiFilePrefix + "current.txt" + removedApiFileName := apiScope.apiFilePrefix + "removed.txt" + apiDir := module.getApiDir() + currentApiFileName = path.Join(apiDir, currentApiFileName) + removedApiFileName = path.Join(apiDir, removedApiFileName) + + // check against the not-yet-release API + props.Check_api.Current.Api_file = proptools.StringPtr(currentApiFileName) + props.Check_api.Current.Removed_api_file = proptools.StringPtr(removedApiFileName) + + if !apiScope.unstable { + // check against the latest released API + latestApiFilegroupName := proptools.StringPtr(module.latestApiFilegroupName(apiScope)) + props.Check_api.Last_released.Api_file = latestApiFilegroupName + props.Check_api.Last_released.Removed_api_file = proptools.StringPtr( + module.latestRemovedApiFilegroupName(apiScope)) + props.Check_api.Ignore_missing_latest_api = proptools.BoolPtr(true) + + if proptools.Bool(module.sdkLibraryProperties.Api_lint.Enabled) { + // Enable api lint. + props.Check_api.Api_lint.Enabled = proptools.BoolPtr(true) + props.Check_api.Api_lint.New_since = latestApiFilegroupName + + // If it exists then pass a lint-baseline.txt through to droidstubs. + baselinePath := path.Join(apiDir, apiScope.apiFilePrefix+"lint-baseline.txt") + baselinePathRelativeToRoot := path.Join(mctx.ModuleDir(), baselinePath) + paths, err := mctx.GlobWithDeps(baselinePathRelativeToRoot, nil) + if err != nil { + mctx.ModuleErrorf("error checking for presence of %s: %s", baselinePathRelativeToRoot, err) + } + if len(paths) == 1 { + props.Check_api.Api_lint.Baseline_file = proptools.StringPtr(baselinePath) + } else if len(paths) != 0 { + mctx.ModuleErrorf("error checking for presence of %s: expected one path, found: %v", baselinePathRelativeToRoot, paths) + } + } + } + + // Dist the api txt artifact for sdk builds. + if !Bool(module.sdkLibraryProperties.No_dist) { + props.Dist.Targets = []string{"sdk", "win_sdk"} + props.Dist.Dest = proptools.StringPtr(fmt.Sprintf("%v.txt", module.BaseModuleName())) + props.Dist.Dir = proptools.StringPtr(path.Join(module.apiDistPath(apiScope), "api")) + } + } + + mctx.CreateModule(DroidstubsFactory, &props) +} + +func (module *SdkLibrary) DepIsInSameApex(mctx android.BaseModuleContext, dep android.Module) bool { + depTag := mctx.OtherModuleDependencyTag(dep) + if depTag == xmlPermissionsFileTag { + return true + } + return module.Library.DepIsInSameApex(mctx, dep) } // Creates the xml file that publicizes the runtime library -func (module *SdkLibrary) createXmlFile(mctx android.TopDownMutatorContext) { - template := ` -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2018 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. ---> - -<permissions> - <library name="%s" file="%s"/> -</permissions> -` - // genrule to generate the xml file content from the template above - // TODO: preserve newlines in the generate xml file. Newlines are being squashed - // in the ninja file. Do we need to have an external tool for this? - xmlContent := fmt.Sprintf(template, module.BaseModuleName(), module.implPath()) - genruleProps := struct { - Name *string - Cmd *string - Out []string - }{} - genruleProps.Name = proptools.StringPtr(module.xmlFileName() + "-gen") - genruleProps.Cmd = proptools.StringPtr("echo '" + xmlContent + "' > $(out)") - genruleProps.Out = []string{module.xmlFileName()} - mctx.CreateModule(android.ModuleFactoryAdaptor(genrule.GenRuleFactory), &genruleProps) - - // creates a prebuilt_etc module to actually place the xml file under - // <partition>/etc/permissions - etcProps := struct { - Name *string - Src *string - Sub_dir *string - Soc_specific *bool - Device_specific *bool - Product_specific *bool - }{} - etcProps.Name = proptools.StringPtr(module.xmlFileName()) - etcProps.Src = proptools.StringPtr(":" + module.xmlFileName() + "-gen") - etcProps.Sub_dir = proptools.StringPtr("permissions") - if module.SocSpecific() { - etcProps.Soc_specific = proptools.BoolPtr(true) - } else if module.DeviceSpecific() { - etcProps.Device_specific = proptools.BoolPtr(true) - } else if module.ProductSpecific() { - etcProps.Product_specific = proptools.BoolPtr(true) +func (module *SdkLibrary) createXmlFile(mctx android.DefaultableHookContext) { + props := struct { + Name *string + Lib_name *string + Apex_available []string + }{ + Name: proptools.StringPtr(module.xmlPermissionsModuleName()), + Lib_name: proptools.StringPtr(module.BaseModuleName()), + Apex_available: module.ApexProperties.Apex_available, } - mctx.CreateModule(android.ModuleFactoryAdaptor(android.PrebuiltEtcFactory), &etcProps) + + mctx.CreateModule(sdkLibraryXmlFactory, &props) } -func (module *SdkLibrary) PrebuiltJars(ctx android.BaseContext, sdkVersion string) android.Paths { - var api, v string - if sdkVersion == "" { - api = "system" - v = "current" - } else if strings.Contains(sdkVersion, "_") { - t := strings.Split(sdkVersion, "_") - api = t[0] - v = t[1] +func PrebuiltJars(ctx android.BaseModuleContext, baseName string, s sdkSpec) android.Paths { + var ver sdkVersion + var kind sdkKind + if s.usePrebuilt(ctx) { + ver = s.version + kind = s.kind } else { - api = "public" - v = sdkVersion + // We don't have prebuilt SDK for the specific sdkVersion. + // Instead of breaking the build, fallback to use "system_current" + ver = sdkVersionCurrent + kind = sdkSystem } - dir := filepath.Join("prebuilts", "sdk", v, api) - jar := filepath.Join(dir, module.BaseModuleName()+".jar") + + dir := filepath.Join("prebuilts", "sdk", ver.String(), kind.String()) + jar := filepath.Join(dir, baseName+".jar") jarPath := android.ExistentPathForSource(ctx, jar) if !jarPath.Valid() { - ctx.PropertyErrorf("sdk_library", "invalid sdk version %q, %q does not exist", v, jar) + if ctx.Config().AllowMissingDependencies() { + return android.Paths{android.PathForSource(ctx, jar)} + } else { + ctx.PropertyErrorf("sdk_library", "invalid sdk version %q, %q does not exist", s.raw, jar) + } return nil } return android.Paths{jarPath.Path()} } -// to satisfy SdkLibraryDependency interface -func (module *SdkLibrary) SdkHeaderJars(ctx android.BaseContext, sdkVersion string) android.Paths { - // This module is just a wrapper for the stubs. - if ctx.Config().UnbundledBuildUsePrebuiltSdks() { - return module.PrebuiltJars(ctx, sdkVersion) - } else { - if strings.HasPrefix(sdkVersion, "system_") { - return module.systemApiStubsPath - } else if sdkVersion == "" { - return module.Library.HeaderJars() - } else { - return module.publicApiStubsPath - } +// Get the apex name for module, "" if it is for platform. +func getApexNameForModule(module android.Module) string { + if apex, ok := module.(android.ApexModule); ok { + return apex.ApexName() } + + return "" } -// to satisfy SdkLibraryDependency interface -func (module *SdkLibrary) SdkImplementationJars(ctx android.BaseContext, sdkVersion string) android.Paths { - // This module is just a wrapper for the stubs. - if ctx.Config().UnbundledBuildUsePrebuiltSdks() { - return module.PrebuiltJars(ctx, sdkVersion) - } else { - if strings.HasPrefix(sdkVersion, "system_") { - return module.systemApiStubsImplPath - } else if sdkVersion == "" { - return module.Library.ImplementationJars() - } else { - return module.publicApiStubsImplPath +// Check to see if the other module is within the same named APEX as this module. +// +// If either this or the other module are on the platform then this will return +// false. +func withinSameApexAs(module android.ApexModule, other android.Module) bool { + name := module.ApexName() + return name != "" && getApexNameForModule(other) == name +} + +func (module *SdkLibrary) sdkJars(ctx android.BaseModuleContext, sdkVersion sdkSpec, headerJars bool) android.Paths { + // If the client doesn't set sdk_version, but if this library prefers stubs over + // the impl library, let's provide the widest API surface possible. To do so, + // force override sdk_version to module_current so that the closest possible API + // surface could be found in selectHeaderJarsForSdkVersion + if module.defaultsToStubs() && !sdkVersion.specified() { + sdkVersion = sdkSpecFrom("module_current") + } + + // Only provide access to the implementation library if it is actually built. + if module.requiresRuntimeImplementationLibrary() { + // Check any special cases for java_sdk_library. + // + // Only allow access to the implementation library in the following condition: + // * No sdk_version specified on the referencing module. + // * The referencing module is in the same apex as this. + if sdkVersion.kind == sdkPrivate || withinSameApexAs(module, ctx.Module()) { + if headerJars { + return module.HeaderJars() + } else { + return module.ImplementationJars() + } } } + + return module.selectHeaderJarsForSdkVersion(ctx, sdkVersion) +} + +// to satisfy SdkLibraryDependency interface +func (module *SdkLibrary) SdkHeaderJars(ctx android.BaseModuleContext, sdkVersion sdkSpec) android.Paths { + return module.sdkJars(ctx, sdkVersion, true /*headerJars*/) +} + +// to satisfy SdkLibraryDependency interface +func (module *SdkLibrary) SdkImplementationJars(ctx android.BaseModuleContext, sdkVersion sdkSpec) android.Paths { + return module.sdkJars(ctx, sdkVersion, false /*headerJars*/) } func (module *SdkLibrary) SetNoDist() { @@ -670,31 +1461,40 @@ func javaSdkLibraries(config android.Config) *[]string { }).(*[]string) } +func (module *SdkLibrary) getApiDir() string { + return proptools.StringDefault(module.sdkLibraryProperties.Api_dir, "api") +} + // For a java_sdk_library module, create internal modules for stubs, docs, // runtime libs and xml file. If requested, the stubs and docs are created twice // once for public API level and once for system API level -func SdkLibraryMutator(mctx android.TopDownMutatorContext) { - if module, ok := mctx.Module().(*SdkLibrary); ok { - module.createInternalModules(mctx) - } else if module, ok := mctx.Module().(syspropLibraryInterface); ok { - module.SyspropJavaModule().createInternalModules(mctx) +func (module *SdkLibrary) CreateInternalModules(mctx android.DefaultableHookContext) { + // If the module has been disabled then don't create any child modules. + if !module.Enabled() { + return } -} -func (module *SdkLibrary) createInternalModules(mctx android.TopDownMutatorContext) { - if len(module.Library.Module.properties.Srcs) == 0 { + if len(module.properties.Srcs) == 0 { mctx.PropertyErrorf("srcs", "java_sdk_library must specify srcs") + return } - if len(module.sdkLibraryProperties.Api_packages) == 0 { - mctx.PropertyErrorf("api_packages", "java_sdk_library must specify api_packages") - } + // If this builds against standard libraries (i.e. is not part of the core libraries) + // then assume it provides both system and test apis. Otherwise, assume it does not and + // also assume it does not contribute to the dist build. + sdkDep := decodeSdkDep(mctx, sdkContext(&module.Library)) + hasSystemAndTestApis := sdkDep.hasStandardLibs() + module.sdkLibraryProperties.Generate_system_and_test_apis = hasSystemAndTestApis + module.sdkLibraryProperties.No_dist = proptools.BoolPtr(!hasSystemAndTestApis) missing_current_api := false - for _, scope := range []string{"", "system-", "test-"} { + generatedScopes := module.getGeneratedApiScopes(mctx) + + apiDir := module.getApiDir() + for _, scope := range generatedScopes { for _, api := range []string{"current.txt", "removed.txt"} { - path := path.Join(mctx.ModuleDir(), "api", scope+api) + path := path.Join(mctx.ModuleDir(), apiDir, scope.apiFilePrefix+api) p := android.ExistentPathForSource(mctx, path) if !p.Valid() { mctx.ModuleErrorf("Current api file %#v doesn't exist", path) @@ -713,50 +1513,772 @@ func (module *SdkLibrary) createInternalModules(mctx android.TopDownMutatorConte mctx.ModuleErrorf("One or more current api files are missing. "+ "You can update them by:\n"+ - "%s %q && m update-api", script, mctx.ModuleDir()) + "%s %q %s && m update-api", + script, filepath.Join(mctx.ModuleDir(), apiDir), + strings.Join(generatedScopes.Strings(func(s *apiScope) string { return s.apiFilePrefix }), " ")) return } - // for public API stubs - module.createStubsLibrary(mctx, apiScopePublic) - module.createDocs(mctx, apiScopePublic) + for _, scope := range generatedScopes { + stubsSourceArgs := scope.droidstubsArgsForGeneratingStubsSource + stubsSourceModuleName := module.stubsSourceModuleName(scope) - if !Bool(module.properties.No_standard_libs) { - // for system API stubs - module.createStubsLibrary(mctx, apiScopeSystem) - module.createDocs(mctx, apiScopeSystem) + // If the args needed to generate the stubs and API are the same then they + // can be generated in a single invocation of metalava, otherwise they will + // need separate invocations. + if scope.createStubsSourceAndApiTogether { + // Use the stubs source name for legacy reasons. + module.createStubsSourcesAndApi(mctx, scope, stubsSourceModuleName, true, true, stubsSourceArgs) + } else { + module.createStubsSourcesAndApi(mctx, scope, stubsSourceModuleName, true, false, stubsSourceArgs) - // for test API stubs - module.createStubsLibrary(mctx, apiScopeTest) - module.createDocs(mctx, apiScopeTest) + apiArgs := scope.droidstubsArgsForGeneratingApi + apiName := module.apiModuleName(scope) + module.createStubsSourcesAndApi(mctx, scope, apiName, false, true, apiArgs) + } - // for runtime - module.createXmlFile(mctx) + module.createStubsLibrary(mctx, scope) } - // record java_sdk_library modules so that they are exported to make - javaSdkLibraries := javaSdkLibraries(mctx.Config()) - javaSdkLibrariesLock.Lock() - defer javaSdkLibrariesLock.Unlock() - *javaSdkLibraries = append(*javaSdkLibraries, module.BaseModuleName()) + if module.requiresRuntimeImplementationLibrary() { + // Create child module to create an implementation library. + // + // This temporarily creates a second implementation library that can be explicitly + // referenced. + // + // TODO(b/156618935) - update comment once only one implementation library is created. + module.createImplLibrary(mctx) + + // Only create an XML permissions file that declares the library as being usable + // as a shared library if required. + if module.sharedLibrary() { + module.createXmlFile(mctx) + } + + // record java_sdk_library modules so that they are exported to make + javaSdkLibraries := javaSdkLibraries(mctx.Config()) + javaSdkLibrariesLock.Lock() + defer javaSdkLibrariesLock.Unlock() + *javaSdkLibraries = append(*javaSdkLibraries, module.BaseModuleName()) + } } func (module *SdkLibrary) InitSdkLibraryProperties() { - module.AddProperties( - &module.sdkLibraryProperties, - &module.Library.Module.properties, - &module.Library.Module.dexpreoptProperties, - &module.Library.Module.deviceProperties, - &module.Library.Module.protoProperties, - ) + module.addHostAndDeviceProperties() + module.AddProperties(&module.sdkLibraryProperties) + + module.initSdkLibraryComponent(&module.ModuleBase) + + module.properties.Installable = proptools.BoolPtr(true) + module.deviceProperties.IsSDKLibrary = true +} + +func (module *SdkLibrary) requiresRuntimeImplementationLibrary() bool { + return !proptools.Bool(module.sdkLibraryProperties.Api_only) +} + +func (module *SdkLibrary) defaultsToStubs() bool { + return proptools.Bool(module.sdkLibraryProperties.Default_to_stubs) +} + +// Defines how to name the individual component modules the sdk library creates. +type sdkLibraryComponentNamingScheme interface { + stubsLibraryModuleName(scope *apiScope, baseName string) string + + stubsSourceModuleName(scope *apiScope, baseName string) string + + apiModuleName(scope *apiScope, baseName string) string +} + +type defaultNamingScheme struct { +} + +func (s *defaultNamingScheme) stubsLibraryModuleName(scope *apiScope, baseName string) string { + return scope.stubsLibraryModuleName(baseName) +} + +func (s *defaultNamingScheme) stubsSourceModuleName(scope *apiScope, baseName string) string { + return scope.stubsSourceModuleName(baseName) +} + +func (s *defaultNamingScheme) apiModuleName(scope *apiScope, baseName string) string { + return scope.apiModuleName(baseName) +} + +var _ sdkLibraryComponentNamingScheme = (*defaultNamingScheme)(nil) - module.Library.Module.properties.Installable = proptools.BoolPtr(true) - module.Library.Module.deviceProperties.IsSDKLibrary = true +type frameworkModulesNamingScheme struct { } +func (s *frameworkModulesNamingScheme) moduleSuffix(scope *apiScope) string { + suffix := scope.name + if scope == apiScopeModuleLib { + suffix = "module_libs_" + } + return suffix +} + +func (s *frameworkModulesNamingScheme) stubsLibraryModuleName(scope *apiScope, baseName string) string { + return fmt.Sprintf("%s-stubs-%sapi", baseName, s.moduleSuffix(scope)) +} + +func (s *frameworkModulesNamingScheme) stubsSourceModuleName(scope *apiScope, baseName string) string { + return fmt.Sprintf("%s-stubs-srcs-%sapi", baseName, s.moduleSuffix(scope)) +} + +func (s *frameworkModulesNamingScheme) apiModuleName(scope *apiScope, baseName string) string { + return fmt.Sprintf("%s-api-%sapi", baseName, s.moduleSuffix(scope)) +} + +var _ sdkLibraryComponentNamingScheme = (*frameworkModulesNamingScheme)(nil) + +func moduleStubLinkType(name string) (stub bool, ret linkType) { + // This suffix-based approach is fragile and could potentially mis-trigger. + // TODO(b/155164730): Clean this up when modules no longer reference sdk_lib stubs directly. + if strings.HasSuffix(name, ".stubs.public") || strings.HasSuffix(name, "-stubs-publicapi") { + return true, javaSdk + } + if strings.HasSuffix(name, ".stubs.system") || strings.HasSuffix(name, "-stubs-systemapi") { + return true, javaSystem + } + if strings.HasSuffix(name, ".stubs.module_lib") || strings.HasSuffix(name, "-stubs-module_libs_api") { + return true, javaModule + } + if strings.HasSuffix(name, ".stubs.test") { + return true, javaSystem + } + return false, javaPlatform +} + +// java_sdk_library is a special Java library that provides optional platform APIs to apps. +// In practice, it can be viewed as a combination of several modules: 1) stubs library that clients +// are linked against to, 2) droiddoc module that internally generates API stubs source files, +// 3) the real runtime shared library that implements the APIs, and 4) XML file for adding +// the runtime lib to the classpath at runtime if requested via <uses-library>. func SdkLibraryFactory() android.Module { module := &SdkLibrary{} + + // Initialize information common between source and prebuilt. + module.initCommon(&module.ModuleBase) + module.InitSdkLibraryProperties() + android.InitApexModule(module) InitJavaModule(module, android.HostAndDeviceSupported) + + // Initialize the map from scope to scope specific properties. + scopeToProperties := make(map[*apiScope]*ApiScopeProperties) + for _, scope := range allApiScopes { + scopeToProperties[scope] = scope.scopeSpecificProperties(module) + } + module.scopeToProperties = scopeToProperties + + // Add the properties containing visibility rules so that they are checked. + android.AddVisibilityProperty(module, "impl_library_visibility", &module.sdkLibraryProperties.Impl_library_visibility) + android.AddVisibilityProperty(module, "stubs_library_visibility", &module.sdkLibraryProperties.Stubs_library_visibility) + android.AddVisibilityProperty(module, "stubs_source_visibility", &module.sdkLibraryProperties.Stubs_source_visibility) + + module.SetDefaultableHook(func(ctx android.DefaultableHookContext) { + // If no implementation is required then it cannot be used as a shared library + // either. + if !module.requiresRuntimeImplementationLibrary() { + // If shared_library has been explicitly set to true then it is incompatible + // with api_only: true. + if proptools.Bool(module.commonSdkLibraryProperties.Shared_library) { + ctx.PropertyErrorf("api_only/shared_library", "inconsistent settings, shared_library and api_only cannot both be true") + } + // Set shared_library: false. + module.commonSdkLibraryProperties.Shared_library = proptools.BoolPtr(false) + } + + if module.initCommonAfterDefaultsApplied(ctx) { + module.CreateInternalModules(ctx) + } + }) + return module +} + +// +// SDK library prebuilts +// + +// Properties associated with each api scope. +type sdkLibraryScopeProperties struct { + Jars []string `android:"path"` + + Sdk_version *string + + // List of shared java libs that this module has dependencies to + Libs []string + + // The stubs source. + Stub_srcs []string `android:"path"` + + // The current.txt + Current_api *string `android:"path"` + + // The removed.txt + Removed_api *string `android:"path"` +} + +type sdkLibraryImportProperties struct { + // List of shared java libs, common to all scopes, that this module has + // dependencies to + Libs []string +} + +type SdkLibraryImport struct { + android.ModuleBase + android.DefaultableModuleBase + prebuilt android.Prebuilt + android.ApexModuleBase + android.SdkBase + + properties sdkLibraryImportProperties + + // Map from api scope to the scope specific property structure. + scopeProperties map[*apiScope]*sdkLibraryScopeProperties + + commonToSdkLibraryAndImport + + // The reference to the implementation library created by the source module. + // Is nil if the source module does not exist. + implLibraryModule *Library + + // The reference to the xml permissions module created by the source module. + // Is nil if the source module does not exist. + xmlPermissionsFileModule *sdkLibraryXml +} + +var _ SdkLibraryDependency = (*SdkLibraryImport)(nil) + +// The type of a structure that contains a field of type sdkLibraryScopeProperties +// for each apiscope in allApiScopes, e.g. something like: +// struct { +// Public sdkLibraryScopeProperties +// System sdkLibraryScopeProperties +// ... +// } +var allScopeStructType = createAllScopePropertiesStructType() + +// Dynamically create a structure type for each apiscope in allApiScopes. +func createAllScopePropertiesStructType() reflect.Type { + var fields []reflect.StructField + for _, apiScope := range allApiScopes { + field := reflect.StructField{ + Name: apiScope.fieldName, + Type: reflect.TypeOf(sdkLibraryScopeProperties{}), + } + fields = append(fields, field) + } + + return reflect.StructOf(fields) +} + +// Create an instance of the scope specific structure type and return a map +// from apiscope to a pointer to each scope specific field. +func createPropertiesInstance() (interface{}, map[*apiScope]*sdkLibraryScopeProperties) { + allScopePropertiesPtr := reflect.New(allScopeStructType) + allScopePropertiesStruct := allScopePropertiesPtr.Elem() + scopeProperties := make(map[*apiScope]*sdkLibraryScopeProperties) + + for _, apiScope := range allApiScopes { + field := allScopePropertiesStruct.FieldByName(apiScope.fieldName) + scopeProperties[apiScope] = field.Addr().Interface().(*sdkLibraryScopeProperties) + } + + return allScopePropertiesPtr.Interface(), scopeProperties +} + +// java_sdk_library_import imports a prebuilt java_sdk_library. +func sdkLibraryImportFactory() android.Module { + module := &SdkLibraryImport{} + + allScopeProperties, scopeToProperties := createPropertiesInstance() + module.scopeProperties = scopeToProperties + module.AddProperties(&module.properties, allScopeProperties) + + // Initialize information common between source and prebuilt. + module.initCommon(&module.ModuleBase) + + android.InitPrebuiltModule(module, &[]string{""}) + android.InitApexModule(module) + android.InitSdkAwareModule(module) + InitJavaModule(module, android.HostAndDeviceSupported) + + module.SetDefaultableHook(func(mctx android.DefaultableHookContext) { + if module.initCommonAfterDefaultsApplied(mctx) { + module.createInternalModules(mctx) + } + }) + return module +} + +func (module *SdkLibraryImport) Prebuilt() *android.Prebuilt { + return &module.prebuilt +} + +func (module *SdkLibraryImport) Name() string { + return module.prebuilt.Name(module.ModuleBase.Name()) +} + +func (module *SdkLibraryImport) createInternalModules(mctx android.DefaultableHookContext) { + + // If the build is configured to use prebuilts then force this to be preferred. + if mctx.Config().UnbundledBuildUsePrebuiltSdks() { + module.prebuilt.ForcePrefer() + } + + for apiScope, scopeProperties := range module.scopeProperties { + if len(scopeProperties.Jars) == 0 { + continue + } + + module.createJavaImportForStubs(mctx, apiScope, scopeProperties) + + if len(scopeProperties.Stub_srcs) > 0 { + module.createPrebuiltStubsSources(mctx, apiScope, scopeProperties) + } + } + + javaSdkLibraries := javaSdkLibraries(mctx.Config()) + javaSdkLibrariesLock.Lock() + defer javaSdkLibrariesLock.Unlock() + *javaSdkLibraries = append(*javaSdkLibraries, module.BaseModuleName()) +} + +func (module *SdkLibraryImport) createJavaImportForStubs(mctx android.DefaultableHookContext, apiScope *apiScope, scopeProperties *sdkLibraryScopeProperties) { + // Creates a java import for the jar with ".stubs" suffix + props := struct { + Name *string + Sdk_version *string + Libs []string + Jars []string + Prefer *bool + }{} + props.Name = proptools.StringPtr(module.stubsLibraryModuleName(apiScope)) + props.Sdk_version = scopeProperties.Sdk_version + // Prepend any of the libs from the legacy public properties to the libs for each of the + // scopes to avoid having to duplicate them in each scope. + props.Libs = append(module.properties.Libs, scopeProperties.Libs...) + props.Jars = scopeProperties.Jars + + // The imports are preferred if the java_sdk_library_import is preferred. + props.Prefer = proptools.BoolPtr(module.prebuilt.Prefer()) + + mctx.CreateModule(ImportFactory, &props, module.sdkComponentPropertiesForChildLibrary()) +} + +func (module *SdkLibraryImport) createPrebuiltStubsSources(mctx android.DefaultableHookContext, apiScope *apiScope, scopeProperties *sdkLibraryScopeProperties) { + props := struct { + Name *string + Srcs []string + Prefer *bool + }{} + props.Name = proptools.StringPtr(module.stubsSourceModuleName(apiScope)) + props.Srcs = scopeProperties.Stub_srcs + mctx.CreateModule(PrebuiltStubsSourcesFactory, &props) + + // The stubs source is preferred if the java_sdk_library_import is preferred. + props.Prefer = proptools.BoolPtr(module.prebuilt.Prefer()) +} + +func (module *SdkLibraryImport) DepsMutator(ctx android.BottomUpMutatorContext) { + for apiScope, scopeProperties := range module.scopeProperties { + if len(scopeProperties.Jars) == 0 { + continue + } + + // Add dependencies to the prebuilt stubs library + ctx.AddVariationDependencies(nil, apiScope.stubsTag, module.stubsLibraryModuleName(apiScope)) + + if len(scopeProperties.Stub_srcs) > 0 { + // Add dependencies to the prebuilt stubs source library + ctx.AddVariationDependencies(nil, apiScope.stubsSourceTag, module.stubsSourceModuleName(apiScope)) + } + } + + implName := module.implLibraryModuleName() + if ctx.OtherModuleExists(implName) { + ctx.AddVariationDependencies(nil, implLibraryTag, implName) + + xmlPermissionsModuleName := module.xmlPermissionsModuleName() + if module.sharedLibrary() && ctx.OtherModuleExists(xmlPermissionsModuleName) { + // Add dependency to the rule for generating the xml permissions file + ctx.AddDependency(module, xmlPermissionsFileTag, xmlPermissionsModuleName) + } + } +} + +func (module *SdkLibraryImport) DepIsInSameApex(mctx android.BaseModuleContext, dep android.Module) bool { + depTag := mctx.OtherModuleDependencyTag(dep) + if depTag == xmlPermissionsFileTag { + return true + } + + // None of the other dependencies of the java_sdk_library_import are in the same apex + // as the one that references this module. + return false +} + +func (module *SdkLibraryImport) OutputFiles(tag string) (android.Paths, error) { + return module.commonOutputFiles(tag) +} + +func (module *SdkLibraryImport) GenerateAndroidBuildActions(ctx android.ModuleContext) { + // Record the paths to the prebuilt stubs library and stubs source. + ctx.VisitDirectDeps(func(to android.Module) { + tag := ctx.OtherModuleDependencyTag(to) + + // Extract information from any of the scope specific dependencies. + if scopeTag, ok := tag.(scopeDependencyTag); ok { + apiScope := scopeTag.apiScope + scopePaths := module.getScopePathsCreateIfNeeded(apiScope) + + // Extract information from the dependency. The exact information extracted + // is determined by the nature of the dependency which is determined by the tag. + scopeTag.extractDepInfo(ctx, to, scopePaths) + } else if tag == implLibraryTag { + if implLibrary, ok := to.(*Library); ok { + module.implLibraryModule = implLibrary + } else { + ctx.ModuleErrorf("implementation library must be of type *java.Library but was %T", to) + } + } else if tag == xmlPermissionsFileTag { + if xmlPermissionsFileModule, ok := to.(*sdkLibraryXml); ok { + module.xmlPermissionsFileModule = xmlPermissionsFileModule + } else { + ctx.ModuleErrorf("xml permissions file module must be of type *sdkLibraryXml but was %T", to) + } + } + }) + + // Populate the scope paths with information from the properties. + for apiScope, scopeProperties := range module.scopeProperties { + if len(scopeProperties.Jars) == 0 { + continue + } + + paths := module.getScopePathsCreateIfNeeded(apiScope) + paths.currentApiFilePath = android.OptionalPathForModuleSrc(ctx, scopeProperties.Current_api) + paths.removedApiFilePath = android.OptionalPathForModuleSrc(ctx, scopeProperties.Removed_api) + } +} + +func (module *SdkLibraryImport) sdkJars(ctx android.BaseModuleContext, sdkVersion sdkSpec, headerJars bool) android.Paths { + + // For consistency with SdkLibrary make the implementation jar available to libraries that + // are within the same APEX. + implLibraryModule := module.implLibraryModule + if implLibraryModule != nil && withinSameApexAs(module, ctx.Module()) { + if headerJars { + return implLibraryModule.HeaderJars() + } else { + return implLibraryModule.ImplementationJars() + } + } + + return module.selectHeaderJarsForSdkVersion(ctx, sdkVersion) +} + +// to satisfy SdkLibraryDependency interface +func (module *SdkLibraryImport) SdkHeaderJars(ctx android.BaseModuleContext, sdkVersion sdkSpec) android.Paths { + // This module is just a wrapper for the prebuilt stubs. + return module.sdkJars(ctx, sdkVersion, true) +} + +// to satisfy SdkLibraryDependency interface +func (module *SdkLibraryImport) SdkImplementationJars(ctx android.BaseModuleContext, sdkVersion sdkSpec) android.Paths { + // This module is just a wrapper for the stubs. + return module.sdkJars(ctx, sdkVersion, false) +} + +// to satisfy apex.javaDependency interface +func (module *SdkLibraryImport) DexJar() android.Path { + if module.implLibraryModule == nil { + return nil + } else { + return module.implLibraryModule.DexJar() + } +} + +// to satisfy apex.javaDependency interface +func (module *SdkLibraryImport) JacocoReportClassesFile() android.Path { + if module.implLibraryModule == nil { + return nil + } else { + return module.implLibraryModule.JacocoReportClassesFile() + } +} + +// to satisfy apex.javaDependency interface +func (module *SdkLibraryImport) LintDepSets() LintDepSets { + if module.implLibraryModule == nil { + return LintDepSets{} + } else { + return module.implLibraryModule.LintDepSets() + } +} + +// to satisfy apex.javaDependency interface +func (module *SdkLibraryImport) Stem() string { + return module.BaseModuleName() +} + +var _ ApexDependency = (*SdkLibraryImport)(nil) + +// to satisfy java.ApexDependency interface +func (module *SdkLibraryImport) HeaderJars() android.Paths { + if module.implLibraryModule == nil { + return nil + } else { + return module.implLibraryModule.HeaderJars() + } +} + +// to satisfy java.ApexDependency interface +func (module *SdkLibraryImport) ImplementationAndResourcesJars() android.Paths { + if module.implLibraryModule == nil { + return nil + } else { + return module.implLibraryModule.ImplementationAndResourcesJars() + } +} + +// +// java_sdk_library_xml +// +type sdkLibraryXml struct { + android.ModuleBase + android.DefaultableModuleBase + android.ApexModuleBase + + properties sdkLibraryXmlProperties + + outputFilePath android.OutputPath + installDirPath android.InstallPath +} + +type sdkLibraryXmlProperties struct { + // canonical name of the lib + Lib_name *string +} + +// java_sdk_library_xml builds the permission xml file for a java_sdk_library. +// Not to be used directly by users. java_sdk_library internally uses this. +func sdkLibraryXmlFactory() android.Module { + module := &sdkLibraryXml{} + + module.AddProperties(&module.properties) + + android.InitApexModule(module) + android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon) + return module } + +// from android.PrebuiltEtcModule +func (module *sdkLibraryXml) SubDir() string { + return "permissions" +} + +// from android.PrebuiltEtcModule +func (module *sdkLibraryXml) OutputFile() android.OutputPath { + return module.outputFilePath +} + +// from android.ApexModule +func (module *sdkLibraryXml) AvailableFor(what string) bool { + return true +} + +func (module *sdkLibraryXml) DepsMutator(ctx android.BottomUpMutatorContext) { + // do nothing +} + +// File path to the runtime implementation library +func (module *sdkLibraryXml) implPath() string { + implName := proptools.String(module.properties.Lib_name) + if apexName := module.ApexName(); apexName != "" { + // TODO(b/146468504): ApexName() is only a soong module name, not apex name. + // In most cases, this works fine. But when apex_name is set or override_apex is used + // this can be wrong. + return fmt.Sprintf("/apex/%s/javalib/%s.jar", apexName, implName) + } + partition := "system" + if module.SocSpecific() { + partition = "vendor" + } else if module.DeviceSpecific() { + partition = "odm" + } else if module.ProductSpecific() { + partition = "product" + } else if module.SystemExtSpecific() { + partition = "system_ext" + } + return "/" + partition + "/framework/" + implName + ".jar" +} + +func (module *sdkLibraryXml) GenerateAndroidBuildActions(ctx android.ModuleContext) { + libName := proptools.String(module.properties.Lib_name) + xmlContent := fmt.Sprintf(permissionsTemplate, libName, module.implPath()) + + module.outputFilePath = android.PathForModuleOut(ctx, libName+".xml").OutputPath + rule := android.NewRuleBuilder() + rule.Command(). + Text("/bin/bash -c \"echo -e '" + xmlContent + "'\" > "). + Output(module.outputFilePath) + + rule.Build(pctx, ctx, "java_sdk_xml", "Permission XML") + + module.installDirPath = android.PathForModuleInstall(ctx, "etc", module.SubDir()) +} + +func (module *sdkLibraryXml) AndroidMkEntries() []android.AndroidMkEntries { + if !module.IsForPlatform() { + return []android.AndroidMkEntries{android.AndroidMkEntries{ + Disabled: true, + }} + } + + return []android.AndroidMkEntries{android.AndroidMkEntries{ + Class: "ETC", + OutputFile: android.OptionalPathForPath(module.outputFilePath), + ExtraEntries: []android.AndroidMkExtraEntriesFunc{ + func(entries *android.AndroidMkEntries) { + entries.SetString("LOCAL_MODULE_TAGS", "optional") + entries.SetString("LOCAL_MODULE_PATH", module.installDirPath.ToMakePath().String()) + entries.SetString("LOCAL_INSTALLED_MODULE_STEM", module.outputFilePath.Base()) + }, + }, + }} +} + +type sdkLibrarySdkMemberType struct { + android.SdkMemberTypeBase +} + +func (s *sdkLibrarySdkMemberType) AddDependencies(mctx android.BottomUpMutatorContext, dependencyTag blueprint.DependencyTag, names []string) { + mctx.AddVariationDependencies(nil, dependencyTag, names...) +} + +func (s *sdkLibrarySdkMemberType) IsInstance(module android.Module) bool { + _, ok := module.(*SdkLibrary) + return ok +} + +func (s *sdkLibrarySdkMemberType) AddPrebuiltModule(ctx android.SdkMemberContext, member android.SdkMember) android.BpModule { + return ctx.SnapshotBuilder().AddPrebuiltModule(member, "java_sdk_library_import") +} + +func (s *sdkLibrarySdkMemberType) CreateVariantPropertiesStruct() android.SdkMemberProperties { + return &sdkLibrarySdkMemberProperties{} +} + +type sdkLibrarySdkMemberProperties struct { + android.SdkMemberPropertiesBase + + // Scope to per scope properties. + Scopes map[*apiScope]scopeProperties + + // Additional libraries that the exported stubs libraries depend upon. + Libs []string + + // The Java stubs source files. + Stub_srcs []string + + // The naming scheme. + Naming_scheme *string + + // True if the java_sdk_library_import is for a shared library, false + // otherwise. + Shared_library *bool +} + +type scopeProperties struct { + Jars android.Paths + StubsSrcJar android.Path + CurrentApiFile android.Path + RemovedApiFile android.Path + SdkVersion string +} + +func (s *sdkLibrarySdkMemberProperties) PopulateFromVariant(ctx android.SdkMemberContext, variant android.Module) { + sdk := variant.(*SdkLibrary) + + s.Scopes = make(map[*apiScope]scopeProperties) + for _, apiScope := range allApiScopes { + paths := sdk.findScopePaths(apiScope) + if paths == nil { + continue + } + + jars := paths.stubsImplPath + if len(jars) > 0 { + properties := scopeProperties{} + properties.Jars = jars + properties.SdkVersion = sdk.sdkVersionForStubsLibrary(ctx.SdkModuleContext(), apiScope) + properties.StubsSrcJar = paths.stubsSrcJar.Path() + if paths.currentApiFilePath.Valid() { + properties.CurrentApiFile = paths.currentApiFilePath.Path() + } + if paths.removedApiFilePath.Valid() { + properties.RemovedApiFile = paths.removedApiFilePath.Path() + } + s.Scopes[apiScope] = properties + } + } + + s.Libs = sdk.properties.Libs + s.Naming_scheme = sdk.commonSdkLibraryProperties.Naming_scheme + s.Shared_library = proptools.BoolPtr(sdk.sharedLibrary()) +} + +func (s *sdkLibrarySdkMemberProperties) AddToPropertySet(ctx android.SdkMemberContext, propertySet android.BpPropertySet) { + if s.Naming_scheme != nil { + propertySet.AddProperty("naming_scheme", proptools.String(s.Naming_scheme)) + } + if s.Shared_library != nil { + propertySet.AddProperty("shared_library", *s.Shared_library) + } + + for _, apiScope := range allApiScopes { + if properties, ok := s.Scopes[apiScope]; ok { + scopeSet := propertySet.AddPropertySet(apiScope.propertyName) + + scopeDir := filepath.Join("sdk_library", s.OsPrefix(), apiScope.name) + + var jars []string + for _, p := range properties.Jars { + dest := filepath.Join(scopeDir, ctx.Name()+"-stubs.jar") + ctx.SnapshotBuilder().CopyToSnapshot(p, dest) + jars = append(jars, dest) + } + scopeSet.AddProperty("jars", jars) + + // Merge the stubs source jar into the snapshot zip so that when it is unpacked + // the source files are also unpacked. + snapshotRelativeDir := filepath.Join(scopeDir, ctx.Name()+"_stub_sources") + ctx.SnapshotBuilder().UnzipToSnapshot(properties.StubsSrcJar, snapshotRelativeDir) + scopeSet.AddProperty("stub_srcs", []string{snapshotRelativeDir}) + + if properties.CurrentApiFile != nil { + currentApiSnapshotPath := filepath.Join(scopeDir, ctx.Name()+".txt") + ctx.SnapshotBuilder().CopyToSnapshot(properties.CurrentApiFile, currentApiSnapshotPath) + scopeSet.AddProperty("current_api", currentApiSnapshotPath) + } + + if properties.RemovedApiFile != nil { + removedApiSnapshotPath := filepath.Join(scopeDir, ctx.Name()+"-removed.txt") + ctx.SnapshotBuilder().CopyToSnapshot(properties.RemovedApiFile, removedApiSnapshotPath) + scopeSet.AddProperty("removed_api", removedApiSnapshotPath) + } + + if properties.SdkVersion != "" { + scopeSet.AddProperty("sdk_version", properties.SdkVersion) + } + } + } + + if len(s.Libs) > 0 { + propertySet.AddPropertyWithTag("libs", s.Libs, ctx.SnapshotBuilder().SdkMemberReferencePropertyTag(false)) + } +} diff --git a/java/sdk_test.go b/java/sdk_test.go index e446129a5..52d2df552 100644 --- a/java/sdk_test.go +++ b/java/sdk_test.go @@ -28,173 +28,239 @@ import ( func TestClasspath(t *testing.T) { var classpathTestcases = []struct { - name string - unbundled bool - pdk bool - moduleType string - host android.OsClass - properties string - bootclasspath []string - system string - classpath []string - aidl string + name string + unbundled bool + pdk bool + moduleType string + host android.OsClass + properties string + + // for java 8 + bootclasspath []string + java8classpath []string + + // for java 9 + system string + java9classpath []string + + forces8 bool // if set, javac will always be called with java 8 arguments + + aidl string }{ { - name: "default", - bootclasspath: config.DefaultBootclasspathLibraries, - system: config.DefaultSystemModules, - classpath: config.DefaultLibraries, - aidl: "-Iframework/aidl", + name: "default", + bootclasspath: config.DefaultBootclasspathLibraries, + system: config.DefaultSystemModules, + java8classpath: config.DefaultLibraries, + java9classpath: config.DefaultLibraries, + aidl: "-Iframework/aidl", }, { - name: "blank sdk version", - properties: `sdk_version: "",`, - bootclasspath: config.DefaultBootclasspathLibraries, - system: config.DefaultSystemModules, - classpath: config.DefaultLibraries, - aidl: "-Iframework/aidl", + name: `sdk_version:"core_platform"`, + properties: `sdk_version:"core_platform"`, + bootclasspath: config.DefaultBootclasspathLibraries, + system: config.DefaultSystemModules, + java8classpath: []string{}, + aidl: "", + }, + { + name: "blank sdk version", + properties: `sdk_version: "",`, + bootclasspath: config.DefaultBootclasspathLibraries, + system: config.DefaultSystemModules, + java8classpath: config.DefaultLibraries, + java9classpath: config.DefaultLibraries, + aidl: "-Iframework/aidl", }, { - name: "sdk v25", - properties: `sdk_version: "25",`, - bootclasspath: []string{`""`}, - system: "bootclasspath", // special value to tell 1.9 test to expect bootclasspath - classpath: []string{"prebuilts/sdk/25/public/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"}, - aidl: "-pprebuilts/sdk/25/public/framework.aidl", + name: "sdk v29", + properties: `sdk_version: "29",`, + bootclasspath: []string{`""`}, + forces8: true, + java8classpath: []string{"prebuilts/sdk/29/public/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"}, + aidl: "-pprebuilts/sdk/29/public/framework.aidl", }, { - name: "current", - properties: `sdk_version: "current",`, - bootclasspath: []string{"android_stubs_current", "core-lambda-stubs"}, - system: "bootclasspath", // special value to tell 1.9 test to expect bootclasspath - aidl: "-p" + buildDir + "/framework.aidl", + name: "sdk v30", + properties: `sdk_version: "30",`, + bootclasspath: []string{`""`}, + system: "sdk_public_30_system_modules", + java8classpath: []string{"prebuilts/sdk/30/public/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"}, + java9classpath: []string{"prebuilts/sdk/30/public/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"}, + aidl: "-pprebuilts/sdk/30/public/framework.aidl", }, { - name: "system_current", - properties: `sdk_version: "system_current",`, - bootclasspath: []string{"android_system_stubs_current", "core-lambda-stubs"}, - system: "bootclasspath", // special value to tell 1.9 test to expect bootclasspath - aidl: "-p" + buildDir + "/framework.aidl", + name: "current", + properties: `sdk_version: "current",`, + bootclasspath: []string{"android_stubs_current", "core-lambda-stubs"}, + system: "core-current-stubs-system-modules", + java9classpath: []string{"android_stubs_current"}, + aidl: "-p" + buildDir + "/framework.aidl", }, { - name: "system_25", - properties: `sdk_version: "system_25",`, - bootclasspath: []string{`""`}, - system: "bootclasspath", // special value to tell 1.9 test to expect bootclasspath - classpath: []string{"prebuilts/sdk/25/system/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"}, - aidl: "-pprebuilts/sdk/25/public/framework.aidl", + name: "system_current", + properties: `sdk_version: "system_current",`, + bootclasspath: []string{"android_system_stubs_current", "core-lambda-stubs"}, + system: "core-current-stubs-system-modules", + java9classpath: []string{"android_system_stubs_current"}, + aidl: "-p" + buildDir + "/framework.aidl", }, { - name: "test_current", - properties: `sdk_version: "test_current",`, - bootclasspath: []string{"android_test_stubs_current", "core-lambda-stubs"}, - system: "bootclasspath", // special value to tell 1.9 test to expect bootclasspath - aidl: "-p" + buildDir + "/framework.aidl", + name: "system_29", + properties: `sdk_version: "system_29",`, + bootclasspath: []string{`""`}, + forces8: true, + java8classpath: []string{"prebuilts/sdk/29/system/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"}, + aidl: "-pprebuilts/sdk/29/public/framework.aidl", }, { - name: "core_current", - properties: `sdk_version: "core_current",`, - bootclasspath: []string{"core.current.stubs", "core-lambda-stubs"}, - system: "bootclasspath", // special value to tell 1.9 test to expect bootclasspath + name: "system_30", + properties: `sdk_version: "system_30",`, + bootclasspath: []string{`""`}, + system: "sdk_public_30_system_modules", + java8classpath: []string{"prebuilts/sdk/30/system/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"}, + java9classpath: []string{"prebuilts/sdk/30/system/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"}, + aidl: "-pprebuilts/sdk/30/public/framework.aidl", }, { - name: "nostdlib", - properties: `no_standard_libs: true, system_modules: "none"`, - system: "none", - bootclasspath: []string{`""`}, - classpath: []string{}, + name: "test_current", + properties: `sdk_version: "test_current",`, + bootclasspath: []string{"android_test_stubs_current", "core-lambda-stubs"}, + system: "core-current-stubs-system-modules", + java9classpath: []string{"android_test_stubs_current"}, + aidl: "-p" + buildDir + "/framework.aidl", }, { - name: "nostdlib system_modules", - properties: `no_standard_libs: true, system_modules: "core-platform-api-stubs-system-modules"`, - system: "core-platform-api-stubs-system-modules", - bootclasspath: []string{`""`}, - classpath: []string{}, + name: "core_current", + properties: `sdk_version: "core_current",`, + bootclasspath: []string{"core.current.stubs", "core-lambda-stubs"}, + system: "core-current-stubs-system-modules", + java9classpath: []string{"core.current.stubs"}, }, { - name: "host default", - moduleType: "java_library_host", - properties: ``, - host: android.Host, - bootclasspath: []string{"jdk8/jre/lib/jce.jar", "jdk8/jre/lib/rt.jar"}, - classpath: []string{}, + name: "nostdlib", + properties: `sdk_version: "none", system_modules: "none"`, + system: "none", + bootclasspath: []string{`""`}, + java8classpath: []string{}, }, { - name: "host nostdlib", - moduleType: "java_library_host", - host: android.Host, - properties: `no_standard_libs: true`, - classpath: []string{}, + + name: "nostdlib system_modules", + properties: `sdk_version: "none", system_modules: "core-platform-api-stubs-system-modules"`, + system: "core-platform-api-stubs-system-modules", + bootclasspath: []string{"core-platform-api-stubs-system-modules-lib"}, + java8classpath: []string{}, }, { - name: "host supported default", - host: android.Host, - properties: `host_supported: true,`, - classpath: []string{}, - bootclasspath: []string{"jdk8/jre/lib/jce.jar", "jdk8/jre/lib/rt.jar"}, + name: "host default", + moduleType: "java_library_host", + properties: ``, + host: android.Host, + bootclasspath: []string{"jdk8/jre/lib/jce.jar", "jdk8/jre/lib/rt.jar"}, + java8classpath: []string{}, }, { - name: "host supported nostdlib", - host: android.Host, - properties: `host_supported: true, no_standard_libs: true, system_modules: "none"`, - classpath: []string{}, + + name: "host supported default", + host: android.Host, + properties: `host_supported: true,`, + java8classpath: []string{}, + bootclasspath: []string{"jdk8/jre/lib/jce.jar", "jdk8/jre/lib/rt.jar"}, + }, + { + name: "host supported nostdlib", + host: android.Host, + properties: `host_supported: true, sdk_version: "none", system_modules: "none"`, + java8classpath: []string{}, }, { - name: "unbundled sdk v25", - unbundled: true, - properties: `sdk_version: "25",`, - bootclasspath: []string{`""`}, - system: "bootclasspath", // special value to tell 1.9 test to expect bootclasspath - classpath: []string{"prebuilts/sdk/25/public/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"}, - aidl: "-pprebuilts/sdk/25/public/framework.aidl", + name: "unbundled sdk v29", + unbundled: true, + properties: `sdk_version: "29",`, + bootclasspath: []string{`""`}, + forces8: true, + java8classpath: []string{"prebuilts/sdk/29/public/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"}, + aidl: "-pprebuilts/sdk/29/public/framework.aidl", }, { - name: "unbundled current", - unbundled: true, - properties: `sdk_version: "current",`, - bootclasspath: []string{`""`}, - system: "bootclasspath", // special value to tell 1.9 test to expect bootclasspath - classpath: []string{"prebuilts/sdk/current/public/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"}, - aidl: "-pprebuilts/sdk/current/public/framework.aidl", + name: "unbundled sdk v30", + unbundled: true, + properties: `sdk_version: "30",`, + bootclasspath: []string{`""`}, + system: "sdk_public_30_system_modules", + java8classpath: []string{"prebuilts/sdk/30/public/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"}, + java9classpath: []string{"prebuilts/sdk/30/public/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"}, + aidl: "-pprebuilts/sdk/30/public/framework.aidl", }, + { + name: "unbundled current", + unbundled: true, + properties: `sdk_version: "current",`, + bootclasspath: []string{`""`}, + system: "sdk_public_current_system_modules", + java8classpath: []string{"prebuilts/sdk/current/public/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"}, + java9classpath: []string{"prebuilts/sdk/current/public/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"}, + aidl: "-pprebuilts/sdk/current/public/framework.aidl", + }, + + { + name: "pdk default", + pdk: true, + bootclasspath: []string{`""`}, + system: "sdk_public_30_system_modules", + java8classpath: []string{"prebuilts/sdk/30/public/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"}, + java9classpath: []string{"prebuilts/sdk/30/public/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"}, + aidl: "-pprebuilts/sdk/30/public/framework.aidl", + }, { - name: "pdk default", - pdk: true, - bootclasspath: []string{`""`}, - system: "bootclasspath", // special value to tell 1.9 test to expect bootclasspath - classpath: []string{"prebuilts/sdk/25/public/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"}, - aidl: "-pprebuilts/sdk/25/public/framework.aidl", + name: "pdk current", + pdk: true, + properties: `sdk_version: "current",`, + bootclasspath: []string{`""`}, + system: "sdk_public_30_system_modules", + java8classpath: []string{"prebuilts/sdk/30/public/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"}, + java9classpath: []string{"prebuilts/sdk/30/public/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"}, + aidl: "-pprebuilts/sdk/30/public/framework.aidl", }, { - name: "pdk current", - pdk: true, - properties: `sdk_version: "current",`, - bootclasspath: []string{`""`}, - system: "bootclasspath", // special value to tell 1.9 test to expect bootclasspath - classpath: []string{"prebuilts/sdk/25/public/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"}, - aidl: "-pprebuilts/sdk/25/public/framework.aidl", + name: "pdk 29", + pdk: true, + properties: `sdk_version: "29",`, + bootclasspath: []string{`""`}, + system: "sdk_public_30_system_modules", + java8classpath: []string{"prebuilts/sdk/30/public/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"}, + java9classpath: []string{"prebuilts/sdk/30/public/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"}, + aidl: "-pprebuilts/sdk/30/public/framework.aidl", }, { - name: "pdk 25", - pdk: true, - properties: `sdk_version: "25",`, - bootclasspath: []string{`""`}, - system: "bootclasspath", // special value to tell 1.9 test to expect bootclasspath - classpath: []string{"prebuilts/sdk/25/public/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"}, - aidl: "-pprebuilts/sdk/25/public/framework.aidl", + name: "module_current", + properties: `sdk_version: "module_current",`, + bootclasspath: []string{"android_module_lib_stubs_current", "core-lambda-stubs"}, + system: "core-current-stubs-system-modules", + java9classpath: []string{"android_module_lib_stubs_current"}, + aidl: "-p" + buildDir + "/framework_non_updatable.aidl", + }, + { + name: "system_server_current", + properties: `sdk_version: "system_server_current",`, + bootclasspath: []string{"android_system_server_stubs_current", "core-lambda-stubs"}, + system: "core-current-stubs-system-modules", + java9classpath: []string{"android_system_server_stubs_current"}, + aidl: "-p" + buildDir + "/framework.aidl", }, } @@ -205,7 +271,7 @@ func TestClasspath(t *testing.T) { moduleType = testcase.moduleType } - bp := moduleType + ` { + props := ` name: "foo", srcs: ["a.java"], target: { @@ -213,6 +279,10 @@ func TestClasspath(t *testing.T) { srcs: ["bar-doc/IFoo.aidl"], }, }, + ` + bp := moduleType + " {" + props + testcase.properties + ` + }` + bpJava8 := moduleType + " {" + props + `java_version: "1.8", ` + testcase.properties + ` }` @@ -230,101 +300,153 @@ func TestClasspath(t *testing.T) { } bootclasspath := convertModulesToPaths(testcase.bootclasspath) - classpath := convertModulesToPaths(testcase.classpath) + java8classpath := convertModulesToPaths(testcase.java8classpath) + java9classpath := convertModulesToPaths(testcase.java9classpath) + + bc := "" + var bcDeps []string + if len(bootclasspath) > 0 { + bc = "-bootclasspath " + strings.Join(bootclasspath, ":") + if bootclasspath[0] != `""` { + bcDeps = bootclasspath + } + } - bc := strings.Join(bootclasspath, ":") - if bc != "" { - bc = "-bootclasspath " + bc + j8c := "" + if len(java8classpath) > 0 { + j8c = "-classpath " + strings.Join(java8classpath, ":") } - c := strings.Join(classpath, ":") - if c != "" { - c = "-classpath " + c + j9c := "" + if len(java9classpath) > 0 { + j9c = "-classpath " + strings.Join(java9classpath, ":") } + system := "" + var systemDeps []string if testcase.system == "none" { system = "--system=none" } else if testcase.system != "" { - system = "--system=" + filepath.Join(buildDir, ".intermediates", testcase.system, "android_common", "system") + "/" + dir := "" + if strings.HasPrefix(testcase.system, "sdk_public_") { + dir = "prebuilts/sdk" + } + system = "--system=" + filepath.Join(buildDir, ".intermediates", dir, testcase.system, "android_common", "system") + // The module-relative parts of these paths are hardcoded in system_modules.go: + systemDeps = []string{ + filepath.Join(buildDir, ".intermediates", dir, testcase.system, "android_common", "system", "lib", "modules"), + filepath.Join(buildDir, ".intermediates", dir, testcase.system, "android_common", "system", "lib", "jrt-fs.jar"), + filepath.Join(buildDir, ".intermediates", dir, testcase.system, "android_common", "system", "release"), + } } - checkClasspath := func(t *testing.T, ctx *android.TestContext) { - javac := ctx.ModuleForTests("foo", variant).Rule("javac") + checkClasspath := func(t *testing.T, ctx *android.TestContext, isJava8 bool) { + foo := ctx.ModuleForTests("foo", variant) + javac := foo.Rule("javac") + var deps []string - got := javac.Args["bootClasspath"] - if got != bc { - t.Errorf("bootclasspath expected %q != got %q", bc, got) + aidl := foo.MaybeRule("aidl") + if aidl.Rule != nil { + deps = append(deps, aidl.Output.String()) } - got = javac.Args["classpath"] - if got != c { - t.Errorf("classpath expected %q != got %q", c, got) + got := javac.Args["bootClasspath"] + expected := "" + if isJava8 || testcase.forces8 { + expected = bc + deps = append(deps, bcDeps...) + } else { + expected = system + deps = append(deps, systemDeps...) + } + if got != expected { + t.Errorf("bootclasspath expected %q != got %q", expected, got) } - var deps []string - if len(bootclasspath) > 0 && bootclasspath[0] != `""` { - deps = append(deps, bootclasspath...) + if isJava8 || testcase.forces8 { + expected = j8c + deps = append(deps, java8classpath...) + } else { + expected = j9c + deps = append(deps, java9classpath...) + } + got = javac.Args["classpath"] + if got != expected { + t.Errorf("classpath expected %q != got %q", expected, got) } - deps = append(deps, classpath...) if !reflect.DeepEqual(javac.Implicits.Strings(), deps) { t.Errorf("implicits expected %q != got %q", deps, javac.Implicits.Strings()) } } - t.Run("1.8", func(t *testing.T) { - // Test default javac 1.8 - config := testConfig(nil) + // Test with legacy javac -source 1.8 -target 1.8 + t.Run("Java language level 8", func(t *testing.T) { + config := testConfig(nil, bpJava8, nil) if testcase.unbundled { config.TestProductVariables.Unbundled_build = proptools.BoolPtr(true) } if testcase.pdk { config.TestProductVariables.Pdk = proptools.BoolPtr(true) } - ctx := testContext(config, bp, nil) + ctx := testContext() run(t, ctx, config) - checkClasspath(t, ctx) + checkClasspath(t, ctx, true /* isJava8 */) if testcase.host != android.Host { aidl := ctx.ModuleForTests("foo", variant).Rule("aidl") - aidlFlags := aidl.Args["aidlFlags"] - // Trim trailing "-I." to avoid having to specify it in every test - aidlFlags = strings.TrimSpace(strings.TrimSuffix(aidlFlags, "-I.")) - - if g, w := aidlFlags, testcase.aidl; g != w { - t.Errorf("want aidl flags %q, got %q", w, g) + if g, w := aidl.RuleParams.Command, testcase.aidl+" -I."; !strings.Contains(g, w) { + t.Errorf("want aidl command to contain %q, got %q", w, g) } } }) - // Test again with javac 1.9 - t.Run("1.9", func(t *testing.T) { - config := testConfig(map[string]string{"EXPERIMENTAL_USE_OPENJDK9": "true"}) + // Test with default javac -source 9 -target 9 + t.Run("Java language level 9", func(t *testing.T) { + config := testConfig(nil, bp, nil) if testcase.unbundled { config.TestProductVariables.Unbundled_build = proptools.BoolPtr(true) } if testcase.pdk { config.TestProductVariables.Pdk = proptools.BoolPtr(true) } - ctx := testContext(config, bp, nil) + ctx := testContext() run(t, ctx, config) - javac := ctx.ModuleForTests("foo", variant).Rule("javac") - got := javac.Args["bootClasspath"] - expected := system - if testcase.system == "bootclasspath" { - expected = bc + checkClasspath(t, ctx, false /* isJava8 */) + + if testcase.host != android.Host { + aidl := ctx.ModuleForTests("foo", variant).Rule("aidl") + + if g, w := aidl.RuleParams.Command, testcase.aidl+" -I."; !strings.Contains(g, w) { + t.Errorf("want aidl command to contain %q, got %q", w, g) + } } - if got != expected { - t.Errorf("bootclasspath expected %q != got %q", expected, got) + }) + + // Test again with PLATFORM_VERSION_CODENAME=REL, javac -source 8 -target 8 + t.Run("REL + Java language level 8", func(t *testing.T) { + config := testConfig(nil, bpJava8, nil) + config.TestProductVariables.Platform_sdk_codename = proptools.StringPtr("REL") + config.TestProductVariables.Platform_sdk_final = proptools.BoolPtr(true) + + if testcase.unbundled { + config.TestProductVariables.Unbundled_build = proptools.BoolPtr(true) + } + if testcase.pdk { + config.TestProductVariables.Pdk = proptools.BoolPtr(true) } + ctx := testContext() + run(t, ctx, config) + + checkClasspath(t, ctx, true /* isJava8 */) }) - // Test again with PLATFORM_VERSION_CODENAME=REL - t.Run("REL", func(t *testing.T) { - config := testConfig(nil) + // Test again with PLATFORM_VERSION_CODENAME=REL, javac -source 9 -target 9 + t.Run("REL + Java language level 9", func(t *testing.T) { + config := testConfig(nil, bp, nil) config.TestProductVariables.Platform_sdk_codename = proptools.StringPtr("REL") config.TestProductVariables.Platform_sdk_final = proptools.BoolPtr(true) @@ -334,12 +456,11 @@ func TestClasspath(t *testing.T) { if testcase.pdk { config.TestProductVariables.Pdk = proptools.BoolPtr(true) } - ctx := testContext(config, bp, nil) + ctx := testContext() run(t, ctx, config) - checkClasspath(t, ctx) + checkClasspath(t, ctx, false /* isJava8 */) }) }) } - } diff --git a/java/support_libraries.go b/java/support_libraries.go index 5a72f41a9..af7c3c2a0 100644 --- a/java/support_libraries.go +++ b/java/support_libraries.go @@ -52,8 +52,6 @@ func supportLibrariesMakeVarsProvider(ctx android.MakeVarsContext) { supportAars = append(supportAars, name) case *Library, *Import: supportJars = append(supportJars, name) - default: - ctx.ModuleErrorf(module, "unknown module type %t", module) } }) diff --git a/java/sysprop.go b/java/sysprop.go new file mode 100644 index 000000000..1a70499b8 --- /dev/null +++ b/java/sysprop.go @@ -0,0 +1,60 @@ +// Copyright (C) 2019 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 java + +import ( + "sync" + + "android/soong/android" +) + +type syspropLibraryInterface interface { + BaseModuleName() string + Owner() string + HasPublicStub() bool + JavaPublicStubName() string +} + +var ( + syspropPublicStubsKey = android.NewOnceKey("syspropPublicStubsJava") + syspropPublicStubsLock sync.Mutex +) + +func init() { + android.PreDepsMutators(func(ctx android.RegisterMutatorsContext) { + ctx.BottomUp("sysprop_java", SyspropMutator).Parallel() + }) +} + +func syspropPublicStubs(config android.Config) map[string]string { + return config.Once(syspropPublicStubsKey, func() interface{} { + return make(map[string]string) + }).(map[string]string) +} + +// gather list of sysprop libraries owned by platform. +func SyspropMutator(mctx android.BottomUpMutatorContext) { + if m, ok := mctx.Module().(syspropLibraryInterface); ok { + if m.Owner() != "Platform" || !m.HasPublicStub() { + return + } + + syspropPublicStubs := syspropPublicStubs(mctx.Config()) + syspropPublicStubsLock.Lock() + defer syspropPublicStubsLock.Unlock() + + syspropPublicStubs[m.BaseModuleName()] = m.JavaPublicStubName() + } +} diff --git a/java/system_modules.go b/java/system_modules.go index 9ee0307ca..7394fd547 100644 --- a/java/system_modules.go +++ b/java/system_modules.go @@ -28,21 +28,41 @@ import ( // system modules in a runtime image using the jmod and jlink tools. func init() { - android.RegisterModuleType("java_system_modules", SystemModulesFactory) + RegisterSystemModulesBuildComponents(android.InitRegistrationContext) pctx.SourcePathVariable("moduleInfoJavaPath", "build/soong/scripts/jars-to-module-info-java.sh") + + // Register sdk member types. + android.RegisterSdkMemberType(&systemModulesSdkMemberType{ + android.SdkMemberTypeBase{ + PropertyName: "java_system_modules", + SupportsSdk: true, + TransitiveSdkMembers: true, + }, + }) +} + +func RegisterSystemModulesBuildComponents(ctx android.RegistrationContext) { + ctx.RegisterModuleType("java_system_modules", SystemModulesFactory) + ctx.RegisterModuleType("java_system_modules_import", systemModulesImportFactory) } var ( jarsTosystemModules = pctx.AndroidStaticRule("jarsTosystemModules", blueprint.RuleParams{ Command: `rm -rf ${outDir} ${workDir} && mkdir -p ${workDir}/jmod && ` + - `${moduleInfoJavaPath} ${moduleName} $in > ${workDir}/module-info.java && ` + + `${moduleInfoJavaPath} java.base $in > ${workDir}/module-info.java && ` + `${config.JavacCmd} --system=none --patch-module=java.base=${classpath} ${workDir}/module-info.java && ` + `${config.SoongZipCmd} -jar -o ${workDir}/classes.jar -C ${workDir} -f ${workDir}/module-info.class && ` + `${config.MergeZipsCmd} -j ${workDir}/module.jar ${workDir}/classes.jar $in && ` + - `${config.JmodCmd} create --module-version 9 --target-platform android ` + - ` --class-path ${workDir}/module.jar ${workDir}/jmod/${moduleName}.jmod && ` + - `${config.JlinkCmd} --module-path ${workDir}/jmod --add-modules ${moduleName} --output ${outDir} && ` + + // Note: The version of the java.base module created must match the version + // of the jlink tool which consumes it. + `${config.JmodCmd} create --module-version ${config.JlinkVersion} --target-platform android ` + + ` --class-path ${workDir}/module.jar ${workDir}/jmod/java.base.jmod && ` + + `${config.JlinkCmd} --module-path ${workDir}/jmod --add-modules java.base --output ${outDir} ` + + // Note: The system-modules jlink plugin is disabled because (a) it is not + // useful on Android, and (b) it causes errors with later versions of jlink + // when the jdk.internal.module is absent from java.base (as it is here). + ` --disable-plugin system-modules && ` + `cp ${config.JrtFsJar} ${outDir}/lib/`, CommandDeps: []string{ "${moduleInfoJavaPath}", @@ -54,10 +74,14 @@ var ( "${config.JrtFsJar}", }, }, - "moduleName", "classpath", "outDir", "workDir") + "classpath", "outDir", "workDir") + + // Dependency tag that causes the added dependencies to be added as java_header_libs + // to the sdk/module_exports/snapshot. + systemModulesLibsTag = android.DependencyTagForSdkMemberType(javaHeaderLibsSdkMemberType) ) -func TransformJarsToSystemModules(ctx android.ModuleContext, moduleName string, jars android.Paths) android.WritablePath { +func TransformJarsToSystemModules(ctx android.ModuleContext, jars android.Paths) (android.Path, android.Paths) { outDir := android.PathForModuleOut(ctx, "system") workDir := android.PathForModuleOut(ctx, "modules") outputFile := android.PathForModuleOut(ctx, "system/lib/modules") @@ -73,72 +97,173 @@ func TransformJarsToSystemModules(ctx android.ModuleContext, moduleName string, Outputs: outputs, Inputs: jars, Args: map[string]string{ - "moduleName": moduleName, - "classpath": strings.Join(jars.Strings(), ":"), - "workDir": workDir.String(), - "outDir": outDir.String(), + "classpath": strings.Join(jars.Strings(), ":"), + "workDir": workDir.String(), + "outDir": outDir.String(), }, }) - return outputFile + return outDir, outputs.Paths() } +// java_system_modules creates a system module from a set of java libraries that can +// be referenced from the system_modules property. It must contain at a minimum the +// java.base module which must include classes from java.lang amongst other java packages. func SystemModulesFactory() android.Module { module := &SystemModules{} module.AddProperties(&module.properties) android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibCommon) + android.InitDefaultableModule(module) return module } +type SystemModulesProvider interface { + HeaderJars() android.Paths + OutputDirAndDeps() (android.Path, android.Paths) +} + +var _ SystemModulesProvider = (*SystemModules)(nil) + +var _ SystemModulesProvider = (*systemModulesImport)(nil) + type SystemModules struct { android.ModuleBase + android.DefaultableModuleBase + android.SdkBase properties SystemModulesProperties - outputFile android.Path + // The aggregated header jars from all jars specified in the libs property. + // Used when system module is added as a dependency to bootclasspath. + headerJars android.Paths + outputDir android.Path + outputDeps android.Paths } type SystemModulesProperties struct { // List of java library modules that should be included in the system modules Libs []string +} - // List of prebuilt jars that should be included in the system modules - Jars []string +func (system *SystemModules) HeaderJars() android.Paths { + return system.headerJars +} - // Sdk version that should be included in the system modules - Sdk_version *string +func (system *SystemModules) OutputDirAndDeps() (android.Path, android.Paths) { + if system.outputDir == nil || len(system.outputDeps) == 0 { + panic("Missing directory for system module dependency") + } + return system.outputDir, system.outputDeps } func (system *SystemModules) GenerateAndroidBuildActions(ctx android.ModuleContext) { var jars android.Paths - ctx.VisitDirectDepsWithTag(libTag, func(module android.Module) { + ctx.VisitDirectDepsWithTag(systemModulesLibsTag, func(module android.Module) { dep, _ := module.(Dependency) jars = append(jars, dep.HeaderJars()...) }) - jars = append(jars, android.PathsForModuleSrc(ctx, system.properties.Jars)...) + system.headerJars = jars - system.outputFile = TransformJarsToSystemModules(ctx, "java.base", jars) + system.outputDir, system.outputDeps = TransformJarsToSystemModules(ctx, jars) } func (system *SystemModules) DepsMutator(ctx android.BottomUpMutatorContext) { - ctx.AddVariationDependencies(nil, libTag, system.properties.Libs...) + ctx.AddVariationDependencies(nil, systemModulesLibsTag, system.properties.Libs...) } func (system *SystemModules) AndroidMk() android.AndroidMkData { return android.AndroidMkData{ Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) { + fmt.Fprintln(w) + makevar := "SOONG_SYSTEM_MODULES_" + name + fmt.Fprintln(w, makevar, ":=$=", system.outputDir.String()) + fmt.Fprintln(w) + + makevar = "SOONG_SYSTEM_MODULES_LIBS_" + name + fmt.Fprintln(w, makevar, ":=$=", strings.Join(system.properties.Libs, " ")) fmt.Fprintln(w) - fmt.Fprintln(w, makevar, ":=", system.outputFile.String()) - fmt.Fprintln(w, ".KATI_READONLY", ":=", makevar) + + makevar = "SOONG_SYSTEM_MODULES_DEPS_" + name + fmt.Fprintln(w, makevar, ":=$=", strings.Join(system.outputDeps.Strings(), " ")) + fmt.Fprintln(w) + fmt.Fprintln(w, name+":", "$("+makevar+")") fmt.Fprintln(w, ".PHONY:", name) - fmt.Fprintln(w) - makevar = "SOONG_SYSTEM_MODULES_LIBS_" + name - fmt.Fprintln(w, makevar, ":=", strings.Join(system.properties.Libs, " ")) - fmt.Fprintln(w, ".KATI_READONLY :=", makevar) }, } } + +// A prebuilt version of java_system_modules. It does not import the +// generated system module, it generates the system module from imported +// java libraries in the same way that java_system_modules does. It just +// acts as a prebuilt, i.e. can have the same base name as another module +// type and the one to use is selected at runtime. +func systemModulesImportFactory() android.Module { + module := &systemModulesImport{} + module.AddProperties(&module.properties) + android.InitPrebuiltModule(module, &module.properties.Libs) + android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibCommon) + android.InitDefaultableModule(module) + android.InitSdkAwareModule(module) + return module +} + +type systemModulesImport struct { + SystemModules + prebuilt android.Prebuilt +} + +func (system *systemModulesImport) Name() string { + return system.prebuilt.Name(system.ModuleBase.Name()) +} + +func (system *systemModulesImport) Prebuilt() *android.Prebuilt { + return &system.prebuilt +} + +type systemModulesSdkMemberType struct { + android.SdkMemberTypeBase +} + +func (mt *systemModulesSdkMemberType) AddDependencies(mctx android.BottomUpMutatorContext, dependencyTag blueprint.DependencyTag, names []string) { + mctx.AddVariationDependencies(nil, dependencyTag, names...) +} + +func (mt *systemModulesSdkMemberType) IsInstance(module android.Module) bool { + if _, ok := module.(*SystemModules); ok { + // A prebuilt system module cannot be added as a member of an sdk because the source and + // snapshot instances would conflict. + _, ok := module.(*systemModulesImport) + return !ok + } + return false +} + +func (mt *systemModulesSdkMemberType) AddPrebuiltModule(ctx android.SdkMemberContext, member android.SdkMember) android.BpModule { + return ctx.SnapshotBuilder().AddPrebuiltModule(member, "java_system_modules_import") +} + +type systemModulesInfoProperties struct { + android.SdkMemberPropertiesBase + + Libs []string +} + +func (mt *systemModulesSdkMemberType) CreateVariantPropertiesStruct() android.SdkMemberProperties { + return &systemModulesInfoProperties{} +} + +func (p *systemModulesInfoProperties) PopulateFromVariant(ctx android.SdkMemberContext, variant android.Module) { + systemModule := variant.(*SystemModules) + p.Libs = systemModule.properties.Libs +} + +func (p *systemModulesInfoProperties) AddToPropertySet(ctx android.SdkMemberContext, propertySet android.BpPropertySet) { + if len(p.Libs) > 0 { + // Add the references to the libraries that form the system module. + propertySet.AddPropertyWithTag("libs", p.Libs, ctx.SnapshotBuilder().SdkMemberReferencePropertyTag(true)) + } +} diff --git a/java/testing.go b/java/testing.go index 6b35bd040..48e449f34 100644 --- a/java/testing.go +++ b/java/testing.go @@ -18,16 +18,90 @@ import ( "fmt" "android/soong/android" + "android/soong/cc" ) -func TestConfig(buildDir string, env map[string]string) android.Config { +func TestConfig(buildDir string, env map[string]string, bp string, fs map[string][]byte) android.Config { + bp += GatherRequiredDepsForTest() + + mockFS := map[string][]byte{ + "api/current.txt": nil, + "api/removed.txt": nil, + "api/system-current.txt": nil, + "api/system-removed.txt": nil, + "api/test-current.txt": nil, + "api/test-removed.txt": nil, + + "prebuilts/sdk/14/public/android.jar": nil, + "prebuilts/sdk/14/public/framework.aidl": nil, + "prebuilts/sdk/14/system/android.jar": nil, + "prebuilts/sdk/17/public/android.jar": nil, + "prebuilts/sdk/17/public/framework.aidl": nil, + "prebuilts/sdk/17/system/android.jar": nil, + "prebuilts/sdk/29/public/android.jar": nil, + "prebuilts/sdk/29/public/framework.aidl": nil, + "prebuilts/sdk/29/system/android.jar": nil, + "prebuilts/sdk/29/system/foo.jar": nil, + "prebuilts/sdk/30/public/android.jar": nil, + "prebuilts/sdk/30/public/framework.aidl": nil, + "prebuilts/sdk/30/system/android.jar": nil, + "prebuilts/sdk/30/system/foo.jar": nil, + "prebuilts/sdk/30/public/core-for-system-modules.jar": nil, + "prebuilts/sdk/current/core/android.jar": nil, + "prebuilts/sdk/current/public/android.jar": nil, + "prebuilts/sdk/current/public/framework.aidl": nil, + "prebuilts/sdk/current/public/core.jar": nil, + "prebuilts/sdk/current/public/core-for-system-modules.jar": nil, + "prebuilts/sdk/current/system/android.jar": nil, + "prebuilts/sdk/current/test/android.jar": nil, + "prebuilts/sdk/28/public/api/foo.txt": nil, + "prebuilts/sdk/28/system/api/foo.txt": nil, + "prebuilts/sdk/28/test/api/foo.txt": nil, + "prebuilts/sdk/28/public/api/foo-removed.txt": nil, + "prebuilts/sdk/28/system/api/foo-removed.txt": nil, + "prebuilts/sdk/28/test/api/foo-removed.txt": nil, + "prebuilts/sdk/28/public/api/bar.txt": nil, + "prebuilts/sdk/28/system/api/bar.txt": nil, + "prebuilts/sdk/28/test/api/bar.txt": nil, + "prebuilts/sdk/28/public/api/bar-removed.txt": nil, + "prebuilts/sdk/28/system/api/bar-removed.txt": nil, + "prebuilts/sdk/28/test/api/bar-removed.txt": nil, + "prebuilts/sdk/30/public/api/foo.txt": nil, + "prebuilts/sdk/30/system/api/foo.txt": nil, + "prebuilts/sdk/30/test/api/foo.txt": nil, + "prebuilts/sdk/30/public/api/foo-removed.txt": nil, + "prebuilts/sdk/30/system/api/foo-removed.txt": nil, + "prebuilts/sdk/30/test/api/foo-removed.txt": nil, + "prebuilts/sdk/30/public/api/bar.txt": nil, + "prebuilts/sdk/30/system/api/bar.txt": nil, + "prebuilts/sdk/30/test/api/bar.txt": nil, + "prebuilts/sdk/30/public/api/bar-removed.txt": nil, + "prebuilts/sdk/30/system/api/bar-removed.txt": nil, + "prebuilts/sdk/30/test/api/bar-removed.txt": nil, + "prebuilts/sdk/tools/core-lambda-stubs.jar": nil, + "prebuilts/sdk/Android.bp": []byte(`prebuilt_apis { name: "sdk", api_dirs: ["14", "28", "30", "current"],}`), + + // For java_sdk_library + "api/module-lib-current.txt": nil, + "api/module-lib-removed.txt": nil, + "api/system-server-current.txt": nil, + "api/system-server-removed.txt": nil, + "build/soong/scripts/gen-java-current-api-files.sh": nil, + } + + cc.GatherRequiredFilesForTest(mockFS) + + for k, v := range fs { + mockFS[k] = v + } + if env == nil { env = make(map[string]string) } if env["ANDROID_JAVA8_HOME"] == "" { env["ANDROID_JAVA8_HOME"] = "jdk8" } - config := android.TestArchConfig(buildDir, env) + config := android.TestArchConfig(buildDir, env, bp, mockFS) return config } @@ -38,13 +112,16 @@ func GatherRequiredDepsForTest() string { extraModules := []string{ "core-lambda-stubs", "ext", - "updatable_media_stubs", "android_stubs_current", "android_system_stubs_current", "android_test_stubs_current", + "android_module_lib_stubs_current", + "android_system_server_stubs_current", "core.current.stubs", "core.platform.api.stubs", "kotlin-stdlib", + "kotlin-stdlib-jdk7", + "kotlin-stdlib-jdk8", "kotlin-annotations", } @@ -53,8 +130,7 @@ func GatherRequiredDepsForTest() string { java_library { name: "%s", srcs: ["a.java"], - no_standard_libs: true, - sdk_version: "core_current", + sdk_version: "none", system_modules: "core-platform-api-stubs-system-modules", } `, extra) @@ -64,8 +140,7 @@ func GatherRequiredDepsForTest() string { java_library { name: "framework", srcs: ["a.java"], - no_standard_libs: true, - sdk_version: "core_current", + sdk_version: "none", system_modules: "core-platform-api-stubs-system-modules", aidl: { export_include_dirs: ["framework/aidl"], @@ -74,22 +149,49 @@ func GatherRequiredDepsForTest() string { android_app { name: "framework-res", - no_framework_libs: true, + sdk_version: "core_platform", + } + + java_library { + name: "android.hidl.base-V1.0-java", + srcs: ["a.java"], + sdk_version: "none", + system_modules: "core-platform-api-stubs-system-modules", + installable: true, + } + + java_library { + name: "android.hidl.manager-V1.0-java", + srcs: ["a.java"], + sdk_version: "none", + system_modules: "core-platform-api-stubs-system-modules", + installable: true, + } + + java_library { + name: "org.apache.http.legacy", + srcs: ["a.java"], + sdk_version: "none", + system_modules: "core-platform-api-stubs-system-modules", + installable: true, } ` systemModules := []string{ - "core-system-modules", + "core-current-stubs-system-modules", "core-platform-api-stubs-system-modules", - "android_stubs_current_system_modules", - "android_system_stubs_current_system_modules", - "android_test_stubs_current_system_modules", } for _, extra := range systemModules { bp += fmt.Sprintf(` java_system_modules { - name: "%s", + name: "%[1]s", + libs: ["%[1]s-lib"], + } + java_library { + name: "%[1]s-lib", + sdk_version: "none", + system_modules: "none", } `, extra) } diff --git a/java/tradefed.go b/java/tradefed.go new file mode 100644 index 000000000..ebbdec13d --- /dev/null +++ b/java/tradefed.go @@ -0,0 +1,37 @@ +// Copyright 2019 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 ( + "android/soong/android" +) + +func init() { + android.RegisterModuleType("tradefed_java_library_host", tradefedJavaLibraryFactory) +} + +// tradefed_java_library_factory wraps java_library and installs an additional +// copy of the output jar to $HOST_OUT/tradefed. +func tradefedJavaLibraryFactory() android.Module { + module := LibraryHostFactory().(*Library) + module.InstallMixin = tradefedJavaLibraryInstall + return module +} + +func tradefedJavaLibraryInstall(ctx android.ModuleContext, path android.Path) android.Paths { + installedPath := ctx.InstallFile(android.PathForModuleInstall(ctx, "tradefed"), + ctx.ModuleName()+".jar", path) + return android.Paths{installedPath} +} |