diff options
Diffstat (limited to 'java')
37 files changed, 2795 insertions, 358 deletions
diff --git a/java/Android.bp b/java/Android.bp index df0d1eb3d..e25accf2a 100644 --- a/java/Android.bp +++ b/java/Android.bp @@ -87,6 +87,7 @@ bootstrap_go_package { "dexpreopt_bootjars_test.go", "droiddoc_test.go", "droidstubs_test.go", + "genrule_test.go", "hiddenapi_singleton_test.go", "jacoco_test.go", "java_test.go", @@ -97,6 +98,7 @@ bootstrap_go_package { "platform_compat_config_test.go", "plugin_test.go", "prebuilt_apis_test.go", + "proto_test.go", "rro_test.go", "sdk_test.go", "sdk_library_test.go", diff --git a/java/android_manifest.go b/java/android_manifest.go index 3fa3520ad..a297b2c10 100644 --- a/java/android_manifest.go +++ b/java/android_manifest.go @@ -96,9 +96,9 @@ func ManifestFixer(ctx android.ModuleContext, manifest android.Path, } if params.ClassLoaderContexts != nil { - // manifest_fixer should add only the implicit SDK libraries inferred by Soong, not those added - // explicitly via `uses_libs`/`optional_uses_libs`. - requiredUsesLibs, optionalUsesLibs := params.ClassLoaderContexts.ImplicitUsesLibs() + // Libraries propagated via `uses_libs`/`optional_uses_libs` are also added (they may be + // propagated from dependencies). + requiredUsesLibs, optionalUsesLibs := params.ClassLoaderContexts.UsesLibs() for _, usesLib := range requiredUsesLibs { args = append(args, "--uses-library", usesLib) diff --git a/java/androidmk.go b/java/androidmk.go index f6ea6a930..82ef4137e 100644 --- a/java/androidmk.go +++ b/java/androidmk.go @@ -324,7 +324,7 @@ func (binary *Binary) AndroidMkEntries() []android.AndroidMkEntries { } func (app *AndroidApp) AndroidMkEntries() []android.AndroidMkEntries { - if app.hideApexVariantFromMake || app.appProperties.HideFromMake { + if app.hideApexVariantFromMake || app.IsHideFromMake() { return []android.AndroidMkEntries{android.AndroidMkEntries{ Disabled: true, }} @@ -424,8 +424,8 @@ func (app *AndroidApp) AndroidMkEntries() []android.AndroidMkEntries { func (a *AndroidApp) getOverriddenPackages() []string { var overridden []string - if len(a.appProperties.Overrides) > 0 { - overridden = append(overridden, a.appProperties.Overrides...) + if len(a.overridableAppProperties.Overrides) > 0 { + overridden = append(overridden, a.overridableAppProperties.Overrides...) } // When APK name is overridden via PRODUCT_PACKAGE_NAME_OVERRIDES // ensure that the original name is overridden. @@ -542,6 +542,9 @@ func (dstubs *Droidstubs) AndroidMkEntries() []android.AndroidMkEntries { if !outputFile.Valid() { outputFile = android.OptionalPathForPath(dstubs.apiFile) } + if !outputFile.Valid() { + outputFile = android.OptionalPathForPath(dstubs.apiVersionsXml) + } return []android.AndroidMkEntries{android.AndroidMkEntries{ Class: "JAVA_LIBRARIES", OutputFile: outputFile, @@ -620,6 +623,7 @@ func (dstubs *Droidstubs) AndroidMkEntries() []android.AndroidMkEntries { 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") + fmt.Fprintf(w, "$(call declare-0p-target,%s)\n", dstubs.apiLintReport.String()) } } if dstubs.checkNullabilityWarningsTimestamp != nil { diff --git a/java/androidmk_test.go b/java/androidmk_test.go index 246c0eb07..197da4f38 100644 --- a/java/androidmk_test.go +++ b/java/androidmk_test.go @@ -206,3 +206,49 @@ func TestAndroidTestHelperApp_LocalDisableTestConfig(t *testing.T) { t.Errorf("Unexpected flag value - expected: %q, actual: %q", expected, actual) } } + +func TestGetOverriddenPackages(t *testing.T) { + ctx, _ := testJava( + t, ` + android_app { + name: "foo", + srcs: ["a.java"], + sdk_version: "current", + overrides: ["qux"] + } + + override_android_app { + name: "foo_override", + base: "foo", + overrides: ["bar"] + } + `) + + expectedVariants := []struct { + name string + moduleName string + variantName string + overrides []string + }{ + { + name: "foo", + moduleName: "foo", + variantName: "android_common", + overrides: []string{"qux"}, + }, + { + name: "foo", + moduleName: "foo_override", + variantName: "android_common_foo_override", + overrides: []string{"bar", "foo"}, + }, + } + + for _, expected := range expectedVariants { + mod := ctx.ModuleForTests(expected.name, expected.variantName).Module() + entries := android.AndroidMkEntriesForTest(t, ctx, mod)[0] + actual := entries.EntryMap["LOCAL_OVERRIDES_PACKAGES"] + + android.AssertDeepEquals(t, "overrides property", expected.overrides, actual) + } +} diff --git a/java/app.go b/java/app.go index 2b52eab15..23a9816b4 100755 --- a/java/app.go +++ b/java/app.go @@ -63,13 +63,6 @@ type appProperties struct { // list of resource labels to generate individual resource packages Package_splits []string - // 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 - // list of native libraries that will be provided in or alongside the resulting jar Jni_libs []string `android:"arch_variant"` @@ -106,7 +99,6 @@ type appProperties struct { // 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 @@ -133,6 +125,13 @@ type overridableAppProperties struct { // Whether to rename the package in resources to the override name rather than the base name. Defaults to true. Rename_resources_package *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 } type AndroidApp struct { @@ -299,10 +298,6 @@ func (a *AndroidApp) checkAppSdkVersions(ctx android.ModuleContext) { // 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 sdkLinkType 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 android.ApiLevel) { // 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) { @@ -313,10 +308,10 @@ func (a *AndroidApp) checkJniLibsSdkVersion(ctx android.ModuleContext, minSdkVer // The domain of cc.sdk_version is "current" and <number> // We can rely on android.SdkSpec to convert it to <number> so that "current" is // handled properly regardless of sdk finalization. - jniSdkVersion, err := android.SdkSpecFrom(ctx, dep.SdkVersion()).EffectiveVersion(ctx) + jniSdkVersion, err := android.SdkSpecFrom(ctx, dep.MinSdkVersion()).EffectiveVersion(ctx) if err != nil || minSdkVersion.LessThan(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()) + ctx.OtherModuleErrorf(dep, "min_sdk_version(%v) is higher than min_sdk_version(%v) of the containing android_app(%v)", + dep.MinSdkVersion(), minSdkVersion, ctx.ModuleName()) return } @@ -586,20 +581,18 @@ func (a *AndroidApp) generateAndroidBuildActions(ctx android.ModuleContext) { } a.onDeviceDir = android.InstallPathToOnDevicePath(ctx, a.installDir) + a.classLoaderContexts = a.usesLibrary.classLoaderContextForUsesLibDeps(ctx) + + var noticeAssetPath android.WritablePath if Bool(a.appProperties.Embed_notices) || ctx.Config().IsEnvTrue("ALWAYS_EMBED_NOTICES") { - noticeFile := android.PathForModuleOut(ctx, "NOTICE.html.gz") - android.BuildNoticeHtmlOutputFromLicenseMetadata(ctx, noticeFile) - noticeAssetPath := android.PathForModuleOut(ctx, "NOTICE", "NOTICE.html.gz") - builder := android.NewRuleBuilder(pctx, ctx) - builder.Command().Text("cp"). - Input(noticeFile). - Output(noticeAssetPath) - builder.Build("notice_dir", "Building notice dir") + // The rule to create the notice file can't be generated yet, as the final output path + // for the apk isn't known yet. Add the path where the notice file will be generated to the + // aapt rules now before calling aaptBuildActions, the rule to create the notice file will + // be generated later. + noticeAssetPath = android.PathForModuleOut(ctx, "NOTICE", "NOTICE.html.gz") a.aapt.noticeFile = android.OptionalPathForPath(noticeAssetPath) } - a.classLoaderContexts = a.usesLibrary.classLoaderContextForUsesLibDeps(ctx) - // Process all building blocks, from AAPT to certificates. a.aaptBuildActions(ctx) @@ -671,6 +664,23 @@ func (a *AndroidApp) generateAndroidBuildActions(ctx android.ModuleContext) { a.extraOutputFiles = append(a.extraOutputFiles, v4SignatureFile) } + if a.aapt.noticeFile.Valid() { + // Generating the notice file rule has to be here after a.outputFile is known. + noticeFile := android.PathForModuleOut(ctx, "NOTICE.html.gz") + android.BuildNoticeHtmlOutputFromLicenseMetadata( + ctx, noticeFile, "", "", + []string{ + a.installDir.String() + "/", + android.PathForModuleInstall(ctx).String() + "/", + a.outputFile.String(), + }) + builder := android.NewRuleBuilder(pctx, ctx) + builder.Command().Text("cp"). + Input(noticeFile). + Output(noticeAssetPath) + builder.Build("notice_dir", "Building notice dir") + } + for _, split := range a.aapt.splits { // Sign the split APKs packageFile := android.PathForModuleOut(ctx, a.installApkName+"_"+split.suffix+".apk") @@ -730,7 +740,7 @@ func collectAppDeps(ctx android.ModuleContext, app appDepsInterface, tag := ctx.OtherModuleDependencyTag(module) if IsJniDepTag(tag) || cc.IsSharedDepTag(tag) { - if dep, ok := module.(*cc.Module); ok { + if dep, ok := module.(cc.LinkableInterface); ok { if dep.IsNdk(ctx.Config()) || dep.IsStubs() { return false } @@ -842,6 +852,10 @@ func (a *AndroidApp) Updatable() bool { return Bool(a.appProperties.Updatable) } +func (a *AndroidApp) SetUpdatable(val bool) { + a.appProperties.Updatable = &val +} + func (a *AndroidApp) getCertString(ctx android.BaseModuleContext) string { certificate, overridden := ctx.DeviceConfig().OverrideCertificateFor(ctx.ModuleName()) if overridden { @@ -880,10 +894,6 @@ func (a *AndroidApp) SetPreventInstall() { a.appProperties.PreventInstall = true } -func (a *AndroidApp) HideFromMake() { - a.appProperties.HideFromMake = true -} - func (a *AndroidApp) MarkAsCoverageVariant(coverage bool) { a.appProperties.IsCoverageVariant = coverage } @@ -900,6 +910,7 @@ func AndroidAppFactory() android.Module { module.Module.dexProperties.Optimize.Shrink = proptools.BoolPtr(true) module.Module.properties.Instrument = true + module.Module.properties.Supports_static_instrumentation = true module.Module.properties.Installable = proptools.BoolPtr(true) module.addHostAndDeviceProperties() @@ -912,7 +923,7 @@ func AndroidAppFactory() android.Module { android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon) android.InitDefaultableModule(module) - android.InitOverridableModule(module, &module.appProperties.Overrides) + android.InitOverridableModule(module, &module.overridableAppProperties.Overrides) android.InitApexModule(module) android.InitBazelModule(module) @@ -1016,9 +1027,10 @@ func (a *AndroidTest) OverridablePropertiesDepsMutator(ctx android.BottomUpMutat func AndroidTestFactory() android.Module { module := &AndroidTest{} - module.Module.dexProperties.Optimize.EnabledByDefault = true + module.Module.dexProperties.Optimize.EnabledByDefault = false module.Module.properties.Instrument = true + module.Module.properties.Supports_static_instrumentation = true module.Module.properties.Installable = proptools.BoolPtr(true) module.appProperties.Use_embedded_native_libs = proptools.BoolPtr(true) module.appProperties.AlwaysPackageNativeLibs = true @@ -1035,7 +1047,7 @@ func AndroidTestFactory() android.Module { android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon) android.InitDefaultableModule(module) - android.InitOverridableModule(module, &module.appProperties.Overrides) + android.InitOverridableModule(module, &module.overridableAppProperties.Overrides) return module } @@ -1069,6 +1081,7 @@ func (a *AndroidTestHelperApp) InstallInTestcases() bool { func AndroidTestHelperAppFactory() android.Module { module := &AndroidTestHelperApp{} + // TODO(b/192032291): Disable by default after auditing downstream usage. module.Module.dexProperties.Optimize.EnabledByDefault = true module.Module.properties.Installable = proptools.BoolPtr(true) @@ -1225,28 +1238,17 @@ func (u *usesLibrary) addLib(lib string, optional bool) { func (u *usesLibrary) deps(ctx android.BottomUpMutatorContext, hasFrameworkLibs bool) { if !ctx.Config().UnbundledBuild() || ctx.Config().UnbundledBuildImage() { - reqTag := makeUsesLibraryDependencyTag(dexpreopt.AnySdkVersion, false, false) - ctx.AddVariationDependencies(nil, reqTag, u.usesLibraryProperties.Uses_libs...) - - optTag := makeUsesLibraryDependencyTag(dexpreopt.AnySdkVersion, true, false) - ctx.AddVariationDependencies(nil, optTag, u.presentOptionalUsesLibs(ctx)...) - + ctx.AddVariationDependencies(nil, usesLibReqTag, u.usesLibraryProperties.Uses_libs...) + ctx.AddVariationDependencies(nil, usesLibOptTag, 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 { - // Add implicit <uses-library> dependencies on compatibility libraries. Some of them are - // optional, and some required --- this depends on the most common usage of the library - // and may be wrong for some apps (they need explicit `uses_libs`/`optional_uses_libs`). - - compat28OptTag := makeUsesLibraryDependencyTag(28, true, true) - ctx.AddVariationDependencies(nil, compat28OptTag, dexpreopt.OptionalCompatUsesLibs28...) - - compat29ReqTag := makeUsesLibraryDependencyTag(29, false, true) - ctx.AddVariationDependencies(nil, compat29ReqTag, dexpreopt.CompatUsesLibs29...) - - compat30OptTag := makeUsesLibraryDependencyTag(30, true, true) - ctx.AddVariationDependencies(nil, compat30OptTag, dexpreopt.OptionalCompatUsesLibs30...) + // Dexpreopt needs paths to the dex jars of these libraries in order to construct + // class loader context for dex2oat. Add them as a dependency with a special tag. + ctx.AddVariationDependencies(nil, usesLibCompat29ReqTag, dexpreopt.CompatUsesLibs29...) + ctx.AddVariationDependencies(nil, usesLibCompat28OptTag, dexpreopt.OptionalCompatUsesLibs28...) + ctx.AddVariationDependencies(nil, usesLibCompat30OptTag, dexpreopt.OptionalCompatUsesLibs30...) } } } @@ -1305,7 +1307,7 @@ func (u *usesLibrary) classLoaderContextForUsesLibDeps(ctx android.ModuleContext replaceInList(u.usesLibraryProperties.Uses_libs, dep, libName) replaceInList(u.usesLibraryProperties.Optional_uses_libs, dep, libName) } - clcMap.AddContext(ctx, tag.sdkVersion, libName, tag.optional, tag.implicit, + clcMap.AddContext(ctx, tag.sdkVersion, libName, tag.optional, lib.DexJarBuildPath().PathOrNil(), lib.DexJarInstallPath(), lib.ClassLoaderContexts()) } else if ctx.Config().AllowMissingDependencies() { diff --git a/java/app_test.go b/java/app_test.go index 6a4508cd6..c4ac4dfdb 100644 --- a/java/app_test.go +++ b/java/app_test.go @@ -427,7 +427,8 @@ func TestUpdatableApps_JniLibShouldBeBuiltAgainstMinSdkVersion(t *testing.T) { name: "libjni", stl: "none", system_shared_libs: [], - sdk_version: "29", + sdk_version: "current", + min_sdk_version: "29", } ` fs := map[string][]byte{ @@ -481,12 +482,13 @@ func TestUpdatableApps_ErrorIfJniLibDoesntSupportMinSdkVersion(t *testing.T) { name: "libjni", stl: "none", sdk_version: "current", + min_sdk_version: "current", } ` - testJavaError(t, `"libjni" .*: sdk_version\(current\) is higher than min_sdk_version\(29\)`, bp) + testJavaError(t, `"libjni" .*: min_sdk_version\(current\) is higher than min_sdk_version\(29\)`, bp) } -func TestUpdatableApps_ErrorIfDepSdkVersionIsHigher(t *testing.T) { +func TestUpdatableApps_ErrorIfDepMinSdkVersionIsHigher(t *testing.T) { bp := cc.GatherRequiredDepsForTest(android.Android) + ` android_app { name: "foo", @@ -503,6 +505,7 @@ func TestUpdatableApps_ErrorIfDepSdkVersionIsHigher(t *testing.T) { shared_libs: ["libbar"], system_shared_libs: [], sdk_version: "27", + min_sdk_version: "27", } cc_library { @@ -510,6 +513,7 @@ func TestUpdatableApps_ErrorIfDepSdkVersionIsHigher(t *testing.T) { stl: "none", system_shared_libs: [], sdk_version: "current", + min_sdk_version: "current", } ` testJavaError(t, `"libjni" .*: links "libbar" built against newer API version "current"`, bp) @@ -1962,7 +1966,7 @@ func TestOverrideAndroidApp(t *testing.T) { // Check if the overrides field values are correctly aggregated. mod := variant.Module().(*AndroidApp) - android.AssertDeepEquals(t, "overrides property", expected.overrides, mod.appProperties.Overrides) + android.AssertDeepEquals(t, "overrides property", expected.overrides, mod.overridableAppProperties.Overrides) // Test Overridable property: Logging_parent logging_parent := mod.aapt.LoggingParent @@ -1980,6 +1984,99 @@ func TestOverrideAndroidApp(t *testing.T) { } } +func TestOverrideAndroidAppOverrides(t *testing.T) { + ctx, _ := testJava( + t, ` + android_app { + name: "foo", + srcs: ["a.java"], + sdk_version: "current", + overrides: ["qux"] + } + + android_app { + name: "bar", + srcs: ["b.java"], + sdk_version: "current", + overrides: ["foo"] + } + + override_android_app { + name: "foo_override", + base: "foo", + overrides: ["bar"] + } + `) + + expectedVariants := []struct { + name string + moduleName string + variantName string + overrides []string + }{ + { + name: "foo", + moduleName: "foo", + variantName: "android_common", + overrides: []string{"qux"}, + }, + { + name: "bar", + moduleName: "bar", + variantName: "android_common", + overrides: []string{"foo"}, + }, + { + name: "foo", + moduleName: "foo_override", + variantName: "android_common_foo_override", + overrides: []string{"bar", "foo"}, + }, + } + for _, expected := range expectedVariants { + variant := ctx.ModuleForTests(expected.name, expected.variantName) + + // Check if the overrides field values are correctly aggregated. + mod := variant.Module().(*AndroidApp) + android.AssertDeepEquals(t, "overrides property", expected.overrides, mod.overridableAppProperties.Overrides) + } +} + +func TestOverrideAndroidAppWithPrebuilt(t *testing.T) { + result := PrepareForTestWithJavaDefaultModules.RunTestWithBp( + t, ` + android_app { + name: "foo", + srcs: ["a.java"], + sdk_version: "current", + } + + override_android_app { + name: "bar", + base: "foo", + } + + android_app_import { + name: "bar", + prefer: true, + apk: "bar.apk", + presigned: true, + } + `) + + // An app that has an override that also has a prebuilt should not be hidden. + foo := result.ModuleForTests("foo", "android_common") + if foo.Module().IsHideFromMake() { + t.Errorf("expected foo to have HideFromMake false") + } + + // An override that also has a prebuilt should be hidden. + barOverride := result.ModuleForTests("foo", "android_common_bar") + if !barOverride.Module().IsHideFromMake() { + t.Errorf("expected bar override variant of foo to have HideFromMake true") + } +} + func TestOverrideAndroidAppStem(t *testing.T) { ctx, _ := testJava(t, ` android_app { @@ -2160,9 +2257,9 @@ func TestOverrideAndroidTest(t *testing.T) { // Check if the overrides field values are correctly aggregated. mod := variant.Module().(*AndroidTest) - if !reflect.DeepEqual(expected.overrides, mod.appProperties.Overrides) { + if !reflect.DeepEqual(expected.overrides, mod.overridableAppProperties.Overrides) { t.Errorf("Incorrect overrides property value, expected: %q, got: %q", - expected.overrides, mod.appProperties.Overrides) + expected.overrides, mod.overridableAppProperties.Overrides) } // Check if javac classpath has the correct jar file path. This checks instrumentation_for overrides. @@ -2505,12 +2602,20 @@ func TestUsesLibraries(t *testing.T) { prebuilt := result.ModuleForTests("prebuilt", "android_common") // Test that implicit dependencies on java_sdk_library instances are passed to the manifest. - // This should not include explicit `uses_libs`/`optional_uses_libs` entries. + // These also include explicit `uses_libs`/`optional_uses_libs` entries, as they may be + // propagated from dependencies. actualManifestFixerArgs := app.Output("manifest_fixer/AndroidManifest.xml").Args["args"] expectManifestFixerArgs := `--extract-native-libs=true ` + `--uses-library qux ` + `--uses-library quuz ` + - `--uses-library runtime-library` + `--uses-library foo ` + + `--uses-library com.non.sdk.lib ` + + `--uses-library runtime-library ` + + `--uses-library runtime-required-x ` + + `--uses-library runtime-required-y ` + + `--optional-uses-library bar ` + + `--optional-uses-library runtime-optional-x ` + + `--optional-uses-library runtime-optional-y` android.AssertStringDoesContain(t, "manifest_fixer args", actualManifestFixerArgs, expectManifestFixerArgs) // Test that all libraries are verified (library order matters). diff --git a/java/base.go b/java/base.go index 4932c4831..c399c4063 100644 --- a/java/base.go +++ b/java/base.go @@ -170,6 +170,9 @@ type CommonProperties struct { } Instrument bool `blueprint:"mutated"` + // If true, then the module supports statically including the jacocoagent + // into the library. + Supports_static_instrumentation bool `blueprint:"mutated"` // List of files to include in the META-INF/services folder of the resulting jar. Services []string `android:"path,arch_variant"` @@ -602,7 +605,8 @@ func (j *Module) shouldInstrument(ctx android.BaseModuleContext) bool { } func (j *Module) shouldInstrumentStatic(ctx android.BaseModuleContext) bool { - return j.shouldInstrument(ctx) && + return j.properties.Supports_static_instrumentation && + j.shouldInstrument(ctx) && (ctx.Config().IsEnvTrue("EMMA_INSTRUMENT_STATIC") || ctx.Config().UnbundledBuild()) } @@ -720,8 +724,10 @@ func (j *Module) deps(ctx android.BottomUpMutatorContext) { if component, ok := dep.(SdkLibraryComponentDependency); ok { if lib := component.OptionalSdkLibraryImplementation(); lib != nil { // Add library as optional if it's one of the optional compatibility libs. - optional := android.InList(*lib, dexpreopt.OptionalCompatUsesLibs) - tag := makeUsesLibraryDependencyTag(dexpreopt.AnySdkVersion, optional, true) + tag := usesLibReqTag + if android.InList(*lib, dexpreopt.OptionalCompatUsesLibs) { + tag = usesLibOptTag + } ctx.AddVariationDependencies(nil, tag, *lib) } } @@ -742,9 +748,7 @@ func (j *Module) deps(ctx android.BottomUpMutatorContext) { // Kotlin files ctx.AddVariationDependencies(nil, kotlinStdlibTag, "kotlin-stdlib", "kotlin-stdlib-jdk7", "kotlin-stdlib-jdk8") - if len(j.properties.Plugins) > 0 { - ctx.AddVariationDependencies(nil, kotlinAnnotationsTag, "kotlin-annotations") - } + ctx.AddVariationDependencies(nil, kotlinAnnotationsTag, "kotlin-annotations") } // Framework libraries need special handling in static coverage builds: they should not have @@ -1016,6 +1020,7 @@ func (j *Module) compile(ctx android.ModuleContext, aaptSrcJar android.Path) { ctx.PropertyErrorf("common_srcs", "common_srcs must be .kt files") } + nonGeneratedSrcJars := srcFiles.FilterByExt(".srcjar") srcFiles = j.genSources(ctx, srcFiles, flags) // Collect javac flags only after computing the full set of srcFiles to @@ -1100,8 +1105,6 @@ func (j *Module) compile(ctx android.ModuleContext, aaptSrcJar android.Path) { flags.classpath = append(flags.classpath, deps.kotlinStdlib...) flags.classpath = append(flags.classpath, deps.kotlinAnnotations...) - flags.dexClasspath = append(flags.dexClasspath, deps.kotlinAnnotations...) - flags.kotlincClasspath = append(flags.kotlincClasspath, flags.bootClasspath...) flags.kotlincClasspath = append(flags.kotlincClasspath, flags.classpath...) @@ -1133,9 +1136,12 @@ func (j *Module) compile(ctx android.ModuleContext, aaptSrcJar android.Path) { // Jar kotlin classes into the final jar after javac if BoolDefault(j.properties.Static_kotlin_stdlib, true) { kotlinJars = append(kotlinJars, deps.kotlinStdlib...) + kotlinJars = append(kotlinJars, deps.kotlinAnnotations...) kotlinHeaderJars = append(kotlinHeaderJars, deps.kotlinStdlib...) + kotlinHeaderJars = append(kotlinHeaderJars, deps.kotlinAnnotations...) } else { flags.dexClasspath = append(flags.dexClasspath, deps.kotlinStdlib...) + flags.dexClasspath = append(flags.dexClasspath, deps.kotlinAnnotations...) } } @@ -1412,17 +1418,18 @@ func (j *Module) compile(ctx android.ModuleContext, aaptSrcJar android.Path) { j.implementationAndResourcesJar = implementationAndResourcesJar // Enable dex compilation for the APEX variants, unless it is disabled explicitly + compileDex := j.dexProperties.Compile_dex apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo) if j.DirectlyInAnyApex() && !apexInfo.IsForPlatform() { - if j.dexProperties.Compile_dex == nil { - j.dexProperties.Compile_dex = proptools.BoolPtr(true) + if compileDex == nil { + compileDex = proptools.BoolPtr(true) } if j.deviceProperties.Hostdex == nil { j.deviceProperties.Hostdex = proptools.BoolPtr(true) } } - if ctx.Device() && (Bool(j.properties.Installable) || Bool(j.dexProperties.Compile_dex)) { + if ctx.Device() && (Bool(j.properties.Installable) || Bool(compileDex)) { if j.hasCode(ctx) { if j.shouldInstrumentStatic(ctx) { j.dexer.extraProguardFlagFiles = append(j.dexer.extraProguardFlagFiles, @@ -1506,8 +1513,8 @@ func (j *Module) compile(ctx android.ModuleContext, aaptSrcJar android.Path) { } j.linter.name = ctx.ModuleName() - j.linter.srcs = srcFiles - j.linter.srcJars = srcJars + j.linter.srcs = append(srcFiles, nonGeneratedSrcJars...) + j.linter.srcJars, _ = android.FilterPathList(srcJars, nonGeneratedSrcJars) j.linter.classpath = append(append(android.Paths(nil), flags.bootClasspath...), flags.classpath...) j.linter.classes = j.implementationJarFile j.linter.minSdkVersion = lintSDKVersion(j.MinSdkVersion(ctx)) @@ -1926,6 +1933,9 @@ func (j *Module) collectDeps(ctx android.ModuleContext) deps { case bootClasspathTag: deps.bootClasspath = append(deps.bootClasspath, dep.HeaderJars...) case libTag, instrumentationForTag: + if _, ok := module.(*Plugin); ok { + ctx.ModuleErrorf("a java_plugin (%s) cannot be used as a libs dependency", otherName) + } deps.classpath = append(deps.classpath, dep.HeaderJars...) deps.dexClasspath = append(deps.dexClasspath, dep.HeaderJars...) deps.aidlIncludeDirs = append(deps.aidlIncludeDirs, dep.AidlIncludeDirs...) @@ -1934,6 +1944,9 @@ func (j *Module) collectDeps(ctx android.ModuleContext) deps { case java9LibTag: deps.java9Classpath = append(deps.java9Classpath, dep.HeaderJars...) case staticLibTag: + if _, ok := module.(*Plugin); ok { + ctx.ModuleErrorf("a java_plugin (%s) cannot be used as a static_libs dependency", otherName) + } deps.classpath = append(deps.classpath, dep.HeaderJars...) deps.staticJars = append(deps.staticJars, dep.ImplementationJars...) deps.staticHeaderJars = append(deps.staticHeaderJars, dep.HeaderJars...) diff --git a/java/bootclasspath.go b/java/bootclasspath.go index 52ce77d46..f4cef7fa6 100644 --- a/java/bootclasspath.go +++ b/java/bootclasspath.go @@ -84,6 +84,9 @@ func addDependencyOntoApexModulePair(ctx android.BottomUpMutatorContext, apex st } } + target := ctx.Module().Target() + variations = append(variations, target.Variations()...) + addedDep := false if ctx.OtherModuleDependencyVariantExists(variations, name) { ctx.AddFarVariationDependencies(variations, tag, name) diff --git a/java/bootclasspath_fragment.go b/java/bootclasspath_fragment.go index c3a5d5f70..0591012fd 100644 --- a/java/bootclasspath_fragment.go +++ b/java/bootclasspath_fragment.go @@ -42,6 +42,7 @@ func init() { func registerBootclasspathFragmentBuildComponents(ctx android.RegistrationContext) { ctx.RegisterModuleType("bootclasspath_fragment", bootclasspathFragmentFactory) + ctx.RegisterModuleType("bootclasspath_fragment_test", testBootclasspathFragmentFactory) ctx.RegisterModuleType("prebuilt_bootclasspath_fragment", prebuiltBootclasspathFragmentFactory) } @@ -227,6 +228,9 @@ type BootclasspathFragmentModule struct { android.SdkBase ClasspathFragmentBase + // True if this fragment is for testing purposes. + testFragment bool + properties bootclasspathFragmentProperties sourceOnlyProperties SourceOnlyBootclasspathProperties @@ -273,7 +277,7 @@ func bootclasspathFragmentFactory() android.Module { android.InitApexModule(m) android.InitSdkAwareModule(m) initClasspathFragment(m, BOOTCLASSPATH) - android.InitAndroidArchModule(m, android.HostAndDeviceSupported, android.MultilibCommon) + android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon) android.AddLoadHook(m, func(ctx android.LoadHookContext) { // If code coverage has been enabled for the framework then append the properties with @@ -298,6 +302,12 @@ func bootclasspathFragmentFactory() android.Module { return m } +func testBootclasspathFragmentFactory() android.Module { + m := bootclasspathFragmentFactory().(*BootclasspathFragmentModule) + m.testFragment = true + return m +} + // bootclasspathFragmentInitContentsFromImage will initialize the contents property from the image_name if // necessary. func bootclasspathFragmentInitContentsFromImage(ctx android.EarlyModuleContext, m *BootclasspathFragmentModule) { @@ -815,6 +825,26 @@ func (b *BootclasspathFragmentModule) createHiddenAPIFlagInput(ctx android.Modul return input } +// isTestFragment returns true if the current module is a test bootclasspath_fragment. +func (b *BootclasspathFragmentModule) isTestFragment() bool { + if b.testFragment { + return true + } + + // TODO(b/194063708): Once test fragments all use bootclasspath_fragment_test + // Some temporary exceptions until all test fragments use the + // bootclasspath_fragment_test module type. + name := b.BaseModuleName() + if strings.HasPrefix(name, "test_") { + return true + } + if name == "apex.apexd_test_bootclasspath-fragment" { + return true + } + + return false +} + // produceHiddenAPIOutput produces the hidden API all-flags.csv file (and supporting files) // for the fragment as well as encoding the flags in the boot dex jars. func (b *BootclasspathFragmentModule) produceHiddenAPIOutput(ctx android.ModuleContext, contents []android.Module, input HiddenAPIFlagInput) *HiddenAPIOutput { @@ -828,11 +858,18 @@ func (b *BootclasspathFragmentModule) produceHiddenAPIOutput(ctx android.ModuleC packagePrefixes := b.sourceOnlyProperties.Hidden_api.Package_prefixes singlePackages := b.sourceOnlyProperties.Hidden_api.Single_packages if splitPackages != nil || packagePrefixes != nil || singlePackages != nil { - if splitPackages == nil { - splitPackages = []string{"*"} - } output.SignaturePatternsPath = buildRuleSignaturePatternsFile( ctx, output.AllFlagsPath, splitPackages, packagePrefixes, singlePackages) + } else if !b.isTestFragment() { + ctx.ModuleErrorf(`Must specify at least one of the split_packages, package_prefixes and single_packages properties + If this is a new bootclasspath_fragment or you are unsure what to do add the + the following to the bootclasspath_fragment: + hidden_api: {split_packages: ["*"]}, + and then run the following: + m analyze_bcpf && analyze_bcpf --bcpf %q + it will analyze the bootclasspath_fragment and provide hints as to what you + should specify here. If you are happy with its suggestions then you can add + the --fix option and it will fix them for you.`, b.BaseModuleName()) } return output diff --git a/java/bootclasspath_fragment_test.go b/java/bootclasspath_fragment_test.go index d3de675d8..83beb6d23 100644 --- a/java/bootclasspath_fragment_test.go +++ b/java/bootclasspath_fragment_test.go @@ -121,6 +121,9 @@ func TestBootclasspathFragment_Coverage(t *testing.T) { ], }, }, + hidden_api: { + split_packages: ["*"], + }, } java_library { @@ -201,6 +204,9 @@ func TestBootclasspathFragment_StubLibs(t *testing.T) { core_platform_api: { stub_libs: ["mycoreplatform.stubs"], }, + hidden_api: { + split_packages: ["*"], + }, } java_library { @@ -278,3 +284,64 @@ func TestBootclasspathFragment_StubLibs(t *testing.T) { android.AssertPathsRelativeToTopEquals(t, "widest dex stubs jar", expectedWidestPaths, info.TransitiveStubDexJarsByScope.StubDexJarsForWidestAPIScope()) } + +func TestBootclasspathFragment_Test(t *testing.T) { + result := android.GroupFixturePreparers( + prepareForTestWithBootclasspathFragment, + PrepareForTestWithJavaSdkLibraryFiles, + FixtureWithLastReleaseApis("mysdklibrary"), + ).RunTestWithBp(t, ` + bootclasspath_fragment { + name: "myfragment", + contents: ["mysdklibrary"], + hidden_api: { + split_packages: [], + }, + } + + bootclasspath_fragment { + name: "test_fragment", + contents: ["mysdklibrary"], + hidden_api: { + split_packages: [], + }, + } + + bootclasspath_fragment { + name: "apex.apexd_test_bootclasspath-fragment", + contents: ["mysdklibrary"], + hidden_api: { + split_packages: [], + }, + } + + bootclasspath_fragment_test { + name: "a_test_fragment", + contents: ["mysdklibrary"], + hidden_api: { + split_packages: [], + }, + } + + + java_sdk_library { + name: "mysdklibrary", + srcs: ["a.java"], + shared_library: false, + public: {enabled: true}, + system: {enabled: true}, + } + `) + + fragment := result.Module("myfragment", "android_common").(*BootclasspathFragmentModule) + android.AssertBoolEquals(t, "not a test fragment", false, fragment.isTestFragment()) + + fragment = result.Module("test_fragment", "android_common").(*BootclasspathFragmentModule) + android.AssertBoolEquals(t, "is a test fragment by prefix", true, fragment.isTestFragment()) + + fragment = result.Module("a_test_fragment", "android_common").(*BootclasspathFragmentModule) + android.AssertBoolEquals(t, "is a test fragment by type", true, fragment.isTestFragment()) + + fragment = result.Module("apex.apexd_test_bootclasspath-fragment", "android_common").(*BootclasspathFragmentModule) + android.AssertBoolEquals(t, "is a test fragment by name", true, fragment.isTestFragment()) +} diff --git a/java/config/config.go b/java/config/config.go index 46c91a2f6..1d4b242f9 100644 --- a/java/config/config.go +++ b/java/config/config.go @@ -68,6 +68,11 @@ var ( "-J-XX:+TieredCompilation", "-J-XX:TieredStopAtLevel=1", } + dexerJavaVmFlagsList = []string{ + `-JXX:OnError="cat hs_err_pid%p.log"`, + "-JXX:CICompilerCount=6", + "-JXX:+UseDynamicNumberOfGCThreads", + } ) func init() { @@ -83,19 +88,16 @@ func init() { // D8 invocations are shorter lived, so we restrict their JIT tiering relative to R8. // Note that the `-JXX` prefix syntax is specific to the R8/D8 invocation wrappers. - exportedVars.ExportStringListStaticVariable("D8Flags", []string{ - `-JXX:OnError="cat hs_err_pid%p.log"`, - "-JXX:CICompilerCount=6", - "-JXX:+UseDynamicNumberOfGCThreads", + exportedVars.ExportStringListStaticVariable("D8Flags", append([]string{ + "-JXmx2048M", "-JXX:+TieredCompilation", "-JXX:TieredStopAtLevel=1", - }) - - exportedVars.ExportStringListStaticVariable("R8Flags", []string{ - `-JXX:OnError="cat hs_err_pid%p.log"`, - "-JXX:CICompilerCount=6", - "-JXX:+UseDynamicNumberOfGCThreads", - }) + }, dexerJavaVmFlagsList...)) + exportedVars.ExportStringListStaticVariable("R8Flags", append([]string{ + "-JXmx2048M", + // Disable this optimization as it can impact weak reference semantics. See b/233432839. + "-JDcom.android.tools.r8.disableEnqueuerDeferredTracing=true", + }, dexerJavaVmFlagsList...)) exportedVars.ExportStringListStaticVariable("CommonJdkFlags", []string{ `-Xmaxerrs 9999999`, @@ -157,7 +159,7 @@ func init() { pctx.HostBinToolVariable("ZipSyncCmd", "zipsync") pctx.HostBinToolVariable("ApiCheckCmd", "apicheck") pctx.HostBinToolVariable("D8Cmd", "d8") - pctx.HostBinToolVariable("R8Cmd", "r8-compat-proguard") + pctx.HostBinToolVariable("R8Cmd", "r8") pctx.HostBinToolVariable("HiddenAPICmd", "hiddenapi") pctx.HostBinToolVariable("ExtractApksCmd", "extract_apks") pctx.VariableFunc("TurbineJar", func(ctx android.PackageVarContext) string { @@ -175,7 +177,7 @@ 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("R8Jar", "r8.jar") pctx.HostJavaToolVariable("D8Jar", "d8.jar") pctx.HostBinToolVariable("SoongJavacWrapper", "soong_javac_wrapper") diff --git a/java/config/makevars.go b/java/config/makevars.go index bc6848fc3..273aca0b4 100644 --- a/java/config/makevars.go +++ b/java/config/makevars.go @@ -43,9 +43,10 @@ func makeVarsProvider(ctx android.MakeVarsContext) { ctx.Strict("JAVADOC", "${JavadocCmd}") ctx.Strict("COMMON_JDK_FLAGS", "${CommonJdkFlags}") - ctx.Strict("DX", "${D8Cmd}") - ctx.Strict("DX_COMMAND", "${D8Cmd} -JXms16M -JXmx2048M") - ctx.Strict("R8_COMPAT_PROGUARD", "${R8Cmd}") + ctx.Strict("D8", "${D8Cmd}") + ctx.Strict("R8", "${R8Cmd}") + ctx.Strict("D8_COMMAND", "${D8Cmd} ${D8Flags}") + ctx.Strict("R8_COMMAND", "${R8Cmd} ${R8Flags}") ctx.Strict("TURBINE", "${TurbineJar}") @@ -78,9 +79,6 @@ func makeVarsProvider(ctx android.MakeVarsContext) { ctx.Strict("CLASS2NONSDKLIST", "${Class2NonSdkList}") ctx.Strict("HIDDENAPI", "${HiddenAPI}") - ctx.Strict("D8_FLAGS", "${D8Flags}") - ctx.Strict("R8_FLAGS", "${R8Flags}") - ctx.Strict("AIDL", "${AidlCmd}") ctx.Strict("AAPT2", "${Aapt2Cmd}") ctx.Strict("ZIPALIGN", "${ZipAlign}") diff --git a/java/core-libraries/Android.bp b/java/core-libraries/Android.bp index cf3974601..513c6061b 100644 --- a/java/core-libraries/Android.bp +++ b/java/core-libraries/Android.bp @@ -138,11 +138,29 @@ java_library { }, } +// Same as core-module-lib-stubs-for-system-modules, but android annotations are +// stripped. This is used by the Java toolchain, while the annotated stub is to +// be used by Kotlin one. +java_library { + name: "core-module-lib-stubs-for-system-modules-no-annotations", + visibility: ["//visibility:private"], + static_libs: [ + "core-module-lib-stubs-for-system-modules", + ], + sdk_version: "none", + system_modules: "none", + dist: { + dest: "system-modules/module-lib/core-for-system-modules-no-annotations.jar", + targets: dist_targets, + }, + jarjar_rules: "jarjar-strip-annotations-rules.txt", +} + // Used when compiling higher-level code with sdk_version "module_current" java_system_modules { name: "core-module-lib-stubs-system-modules", libs: [ - "core-module-lib-stubs-for-system-modules", + "core-module-lib-stubs-for-system-modules-no-annotations", ], visibility: ["//visibility:public"], } @@ -174,6 +192,24 @@ java_library { patch_module: "java.base", } +// Same as legacy.core.platform.api.stubs, but android annotations are +// stripped. This is used by the Java toolchain, while the annotated stub is to +// be used by Kotlin one. +java_library { + name: "legacy.core.platform.api.no.annotations.stubs", + visibility: core_platform_visibility, + hostdex: true, + compile_dex: true, + + sdk_version: "none", + system_modules: "none", + static_libs: [ + "legacy.core.platform.api.stubs", + ], + patch_module: "java.base", + jarjar_rules: "jarjar-strip-annotations-rules.txt", +} + java_library { name: "stable.core.platform.api.stubs", visibility: core_platform_visibility, @@ -191,12 +227,30 @@ java_library { patch_module: "java.base", } +// Same as stable.core.platform.api.stubs, but android annotations are +// stripped. This is used by the Java toolchain, while the annotated stub is to +// be used by Kotlin one. +java_library { + name: "stable.core.platform.api.no.annotations.stubs", + visibility: core_platform_visibility, + hostdex: true, + compile_dex: true, + + sdk_version: "none", + system_modules: "none", + static_libs: [ + "stable.core.platform.api.stubs", + ], + patch_module: "java.base", + jarjar_rules: "jarjar-strip-annotations-rules.txt", +} + // Used when compiling higher-level code against *.core.platform.api.stubs. java_system_modules { name: "legacy-core-platform-api-stubs-system-modules", visibility: core_platform_visibility, libs: [ - "legacy.core.platform.api.stubs", + "legacy.core.platform.api.no.annotations.stubs", // This one is not on device but it's needed when javac compiles code // containing lambdas. "core-lambda-stubs-for-system-modules", @@ -212,7 +266,7 @@ java_system_modules { name: "stable-core-platform-api-stubs-system-modules", visibility: core_platform_visibility, libs: [ - "stable.core.platform.api.stubs", + "stable.core.platform.api.no.annotations.stubs", // This one is not on device but it's needed when javac compiles code // containing lambdas. "core-lambda-stubs-for-system-modules", diff --git a/java/core-libraries/jarjar-strip-annotations-rules.txt b/java/core-libraries/jarjar-strip-annotations-rules.txt new file mode 100644 index 000000000..a1c261b9a --- /dev/null +++ b/java/core-libraries/jarjar-strip-annotations-rules.txt @@ -0,0 +1,4 @@ +strip-annotation android.annotation.NotNull +strip-annotation android.annotation.Nullable +strip-annotation androidx.annotation.RecentlyNonNull +strip-annotation androidx.annotation.RecentlyNullable diff --git a/java/dex.go b/java/dex.go index 13d6e4a01..c943938e2 100644 --- a/java/dex.go +++ b/java/dex.go @@ -36,8 +36,8 @@ type DexProperties struct { Main_dex_rules []string `android:"path"` Optimize struct { - // If false, disable all optimization. Defaults to true for android_app and android_test - // modules, false for java_library and java_test modules. + // If false, disable all optimization. Defaults to true for android_app and + // android_test_helper_app modules, false for android_test, java_library, and java_test modules. Enabled *bool // True if the module containing this has it set by default. EnabledByDefault bool `blueprint:"mutated"` diff --git a/java/dexpreopt.go b/java/dexpreopt.go index 7c5f055e6..0adaf9917 100644 --- a/java/dexpreopt.go +++ b/java/dexpreopt.go @@ -259,10 +259,6 @@ func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, dexJarFile android.Wr isSystemServerJar := global.AllSystemServerJars(ctx).ContainsJar(moduleName(ctx)) bootImage := defaultBootImageConfig(ctx) - if global.UseArtImage { - bootImage = artBootImageConfig(ctx) - } - dexFiles, dexLocations := bcpForDexpreopt(ctx, global.PreoptWithUpdatableBcp) targets := ctx.MultiTargets() diff --git a/java/dexpreopt.go_v1 b/java/dexpreopt.go_v1 new file mode 100644 index 000000000..0adaf9917 --- /dev/null +++ b/java/dexpreopt.go_v1 @@ -0,0 +1,404 @@ +// Copyright 2018 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 ( + "path/filepath" + "strings" + + "android/soong/android" + "android/soong/dexpreopt" +) + +type DexpreopterInterface interface { + IsInstallable() bool // Structs that embed dexpreopter must implement this. + dexpreoptDisabled(ctx android.BaseModuleContext) bool + DexpreoptBuiltInstalledForApex() []dexpreopterInstall + AndroidMkEntriesForApex() []android.AndroidMkEntries +} + +type dexpreopterInstall struct { + // A unique name to distinguish an output from others for the same java library module. Usually in + // the form of `<arch>-<encoded-path>.odex/vdex/art`. + name string + + // The name of the input java module. + moduleName string + + // The path to the dexpreopt output on host. + outputPathOnHost android.Path + + // The directory on the device for the output to install to. + installDirOnDevice android.InstallPath + + // The basename (the last segment of the path) for the output to install as. + installFileOnDevice string +} + +// The full module name of the output in the makefile. +func (install *dexpreopterInstall) FullModuleName() string { + return install.moduleName + install.SubModuleName() +} + +// The sub-module name of the output in the makefile (the name excluding the java module name). +func (install *dexpreopterInstall) SubModuleName() string { + return "-dexpreopt-" + install.name +} + +// Returns Make entries for installing the file. +// +// This function uses a value receiver rather than a pointer receiver to ensure that the object is +// safe to use in `android.AndroidMkExtraEntriesFunc`. +func (install dexpreopterInstall) ToMakeEntries() android.AndroidMkEntries { + return android.AndroidMkEntries{ + Class: "ETC", + SubName: install.SubModuleName(), + OutputFile: android.OptionalPathForPath(install.outputPathOnHost), + ExtraEntries: []android.AndroidMkExtraEntriesFunc{ + func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) { + entries.SetString("LOCAL_MODULE_PATH", install.installDirOnDevice.String()) + entries.SetString("LOCAL_INSTALLED_MODULE_STEM", install.installFileOnDevice) + entries.SetString("LOCAL_NOT_AVAILABLE_FOR_PLATFORM", "false") + }, + }, + } +} + +type dexpreopter struct { + dexpreoptProperties DexpreoptProperties + + installPath android.InstallPath + uncompressedDex bool + isSDKLibrary bool + isApp bool + isTest bool + isPresignedPrebuilt bool + preventInstall bool + + manifestFile android.Path + statusFile android.WritablePath + enforceUsesLibs bool + classLoaderContexts dexpreopt.ClassLoaderContextMap + + // See the `dexpreopt` function for details. + builtInstalled string + builtInstalledForApex []dexpreopterInstall + + // The config is used for two purposes: + // - Passing dexpreopt information about libraries from Soong to Make. This is needed when + // a <uses-library> is defined in Android.bp, but used in Android.mk (see dex_preopt_config_merger.py). + // Note that dexpreopt.config might be needed even if dexpreopt is disabled for the library itself. + // - Dexpreopt post-processing (using dexpreopt artifacts from a prebuilt system image to incrementally + // dexpreopt another partition). + configPath android.WritablePath +} + +type DexpreoptProperties struct { + Dex_preopt struct { + // If false, prevent dexpreopting. Defaults to true. + Enabled *bool + + // If true, generate an app image (.art file) for this module. + App_image *bool + + // If true, use a checked-in profile to guide optimization. Defaults to false unless + // a matching profile is set or a profile is found in PRODUCT_DEX_PREOPT_PROFILE_DIR + // that matches the name of this module, in which case it is defaulted to true. + Profile_guided *bool + + // 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 `android:"path"` + } +} + +func init() { + dexpreopt.DexpreoptRunningInSoong = true +} + +func isApexVariant(ctx android.BaseModuleContext) bool { + apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo) + return !apexInfo.IsForPlatform() +} + +func forPrebuiltApex(ctx android.BaseModuleContext) bool { + apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo) + return apexInfo.ForPrebuiltApex +} + +func moduleName(ctx android.BaseModuleContext) string { + // Remove the "prebuilt_" prefix if the module is from a prebuilt because the prefix is not + // expected by dexpreopter. + return android.RemoveOptionalPrebuiltPrefix(ctx.ModuleName()) +} + +func (d *dexpreopter) dexpreoptDisabled(ctx android.BaseModuleContext) bool { + if !ctx.Device() { + return true + } + + if d.isTest { + return true + } + + if !BoolDefault(d.dexpreoptProperties.Dex_preopt.Enabled, true) { + return true + } + + // If the module is from a prebuilt APEX, it shouldn't be installable, but it can still be + // dexpreopted. + if !ctx.Module().(DexpreopterInterface).IsInstallable() && !forPrebuiltApex(ctx) { + return true + } + + if !android.IsModulePreferred(ctx.Module()) { + return true + } + + global := dexpreopt.GetGlobalConfig(ctx) + + if global.DisablePreopt { + return true + } + + if inList(moduleName(ctx), global.DisablePreoptModules) { + return true + } + + isApexSystemServerJar := global.AllApexSystemServerJars(ctx).ContainsJar(moduleName(ctx)) + if isApexVariant(ctx) { + // Don't preopt APEX variant module unless the module is an APEX system server jar and we are + // building the entire system image. + if !isApexSystemServerJar || ctx.Config().UnbundledBuild() { + return true + } + } else { + // Don't preopt the platform variant of an APEX system server jar to avoid conflicts. + if isApexSystemServerJar { + return true + } + } + + // TODO: contains no java code + + return false +} + +func dexpreoptToolDepsMutator(ctx android.BottomUpMutatorContext) { + if d, ok := ctx.Module().(DexpreopterInterface); !ok || d.dexpreoptDisabled(ctx) { + return + } + dexpreopt.RegisterToolDeps(ctx) +} + +func (d *dexpreopter) odexOnSystemOther(ctx android.ModuleContext, installPath android.InstallPath) bool { + return dexpreopt.OdexOnSystemOtherByName(moduleName(ctx), android.InstallPathToOnDevicePath(ctx, installPath), dexpreopt.GetGlobalConfig(ctx)) +} + +// Returns the install path of the dex jar of a module. +// +// Do not rely on `ApexInfo.ApexVariationName` because it can be something like "apex1000", rather +// than the `name` in the path `/apex/<name>` as suggested in its comment. +// +// This function is on a best-effort basis. It cannot handle the case where an APEX jar is not a +// system server jar, which is fine because we currently only preopt system server jars for APEXes. +func (d *dexpreopter) getInstallPath( + ctx android.ModuleContext, defaultInstallPath android.InstallPath) android.InstallPath { + global := dexpreopt.GetGlobalConfig(ctx) + if global.AllApexSystemServerJars(ctx).ContainsJar(moduleName(ctx)) { + dexLocation := dexpreopt.GetSystemServerDexLocation(ctx, global, moduleName(ctx)) + return android.PathForModuleInPartitionInstall(ctx, "", strings.TrimPrefix(dexLocation, "/")) + } + if !d.dexpreoptDisabled(ctx) && isApexVariant(ctx) && + filepath.Base(defaultInstallPath.PartitionDir()) != "apex" { + ctx.ModuleErrorf("unable to get the install path of the dex jar for dexpreopt") + } + return defaultInstallPath +} + +func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, dexJarFile android.WritablePath) { + global := dexpreopt.GetGlobalConfig(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.installPath.Base() == "." { + return + } + + dexLocation := android.InstallPathToOnDevicePath(ctx, d.installPath) + + providesUsesLib := moduleName(ctx) + if ulib, ok := ctx.Module().(ProvidesUsesLib); ok { + name := ulib.ProvidesUsesLib() + if name != nil { + providesUsesLib = *name + } + } + + // If it is test, make config files regardless of its dexpreopt setting. + // The config files are required for apps defined in make which depend on the lib. + if d.isTest && d.dexpreoptDisabled(ctx) { + return + } + + isSystemServerJar := global.AllSystemServerJars(ctx).ContainsJar(moduleName(ctx)) + + bootImage := defaultBootImageConfig(ctx) + dexFiles, dexLocations := bcpForDexpreopt(ctx, global.PreoptWithUpdatableBcp) + + 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] { + if target.NativeBridge == android.NativeBridgeDisabled { + targets = append(targets, target) + } + } + if isSystemServerJar && !d.isSDKLibrary { + // If the module is not an SDK library and it's a system server jar, only preopt the primary arch. + targets = targets[:1] + } + } + + var archs []android.ArchType + var images android.Paths + var imagesDeps []android.OutputPaths + for _, target := range targets { + archs = append(archs, target.Arch.ArchType) + variant := bootImage.getVariant(target) + images = append(images, variant.imagePathOnHost) + imagesDeps = append(imagesDeps, variant.imagesDeps) + } + // The image locations for all Android variants are identical. + hostImageLocations, deviceImageLocations := bootImage.getAnyAndroidVariant().imageLocations() + + 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 + // dexprepot.profile option or the profile class listing. + 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 if global.ProfileDir != "" { + profileClassListing = android.ExistentPathForSource(ctx, + global.ProfileDir, moduleName(ctx)+".prof") + } + } + + // Full dexpreopt config, used to create dexpreopt build rules. + dexpreoptConfig := &dexpreopt.ModuleConfig{ + Name: moduleName(ctx), + DexLocation: dexLocation, + BuildPath: android.PathForModuleOut(ctx, "dexpreopt", moduleName(ctx)+".jar").OutputPath, + DexPath: dexJarFile, + ManifestPath: android.OptionalPathForPath(d.manifestFile), + UncompressedDex: d.uncompressedDex, + HasApkLibraries: false, + PreoptFlags: nil, + + ProfileClassListing: profileClassListing, + ProfileIsTextListing: profileIsTextListing, + ProfileBootListing: profileBootListing, + + EnforceUsesLibrariesStatusFile: dexpreopt.UsesLibrariesStatusFile(ctx), + EnforceUsesLibraries: d.enforceUsesLibs, + ProvidesUsesLibrary: providesUsesLib, + ClassLoaderContexts: d.classLoaderContexts, + + Archs: archs, + DexPreoptImagesDeps: imagesDeps, + DexPreoptImageLocationsOnHost: hostImageLocations, + DexPreoptImageLocationsOnDevice: deviceImageLocations, + + PreoptBootClassPathDexFiles: dexFiles.Paths(), + PreoptBootClassPathDexLocations: dexLocations, + + PreoptExtractedApk: false, + + NoCreateAppImage: !BoolDefault(d.dexpreoptProperties.Dex_preopt.App_image, true), + ForceCreateAppImage: BoolDefault(d.dexpreoptProperties.Dex_preopt.App_image, false), + + PresignedPrebuilt: d.isPresignedPrebuilt, + } + + d.configPath = android.PathForModuleOut(ctx, "dexpreopt", "dexpreopt.config") + dexpreopt.WriteModuleConfig(ctx, dexpreoptConfig, d.configPath) + + if d.dexpreoptDisabled(ctx) { + return + } + + globalSoong := dexpreopt.GetGlobalSoongConfig(ctx) + + dexpreoptRule, err := dexpreopt.GenerateDexpreoptRule(ctx, globalSoong, global, dexpreoptConfig) + if err != nil { + ctx.ModuleErrorf("error generating dexpreopt rule: %s", err.Error()) + return + } + + dexpreoptRule.Build("dexpreopt", "dexpreopt") + + isApexSystemServerJar := global.AllApexSystemServerJars(ctx).ContainsJar(moduleName(ctx)) + + for _, install := range dexpreoptRule.Installs() { + // Remove the "/" prefix because the path should be relative to $ANDROID_PRODUCT_OUT. + installDir := strings.TrimPrefix(filepath.Dir(install.To), "/") + installBase := filepath.Base(install.To) + arch := filepath.Base(installDir) + installPath := android.PathForModuleInPartitionInstall(ctx, "", installDir) + + if isApexSystemServerJar { + // APEX variants of java libraries are hidden from Make, so their dexpreopt + // outputs need special handling. Currently, for APEX variants of java + // libraries, only those in the system server classpath are handled here. + // Preopting of boot classpath jars in the ART APEX are handled in + // java/dexpreopt_bootjars.go, and other APEX jars are not preopted. + // The installs will be handled by Make as sub-modules of the java library. + d.builtInstalledForApex = append(d.builtInstalledForApex, dexpreopterInstall{ + name: arch + "-" + installBase, + moduleName: moduleName(ctx), + outputPathOnHost: install.From, + installDirOnDevice: installPath, + installFileOnDevice: installBase, + }) + } else if !d.preventInstall { + ctx.InstallFile(installPath, installBase, install.From) + } + } + + if !isApexSystemServerJar { + d.builtInstalled = dexpreoptRule.Installs().String() + } +} + +func (d *dexpreopter) DexpreoptBuiltInstalledForApex() []dexpreopterInstall { + return d.builtInstalledForApex +} + +func (d *dexpreopter) AndroidMkEntriesForApex() []android.AndroidMkEntries { + var entries []android.AndroidMkEntries + for _, install := range d.builtInstalledForApex { + entries = append(entries, install.ToMakeEntries()) + } + return entries +} diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go index 3d91aec91..7c4da3ef6 100644 --- a/java/dexpreopt_bootjars.go +++ b/java/dexpreopt_bootjars.go @@ -213,12 +213,6 @@ import ( // writes out a few DEXPREOPT_IMAGE_* variables for Make; these variables contain boot image names, // paths and so on. // -// 2.5. JIT-Zygote configuration -// ----------------------------- -// -// One special configuration is JIT-Zygote build, when the primary ART image is used for compiling -// apps instead of the Framework boot image extension (see DEXPREOPT_USE_ART_IMAGE and UseArtImage). -// var artApexNames = []string{ "com.android.art", @@ -938,11 +932,8 @@ func (d *dexpreoptBootJars) MakeVars(ctx android.MakeVarsContext) { ctx.Strict("DEXPREOPT_BOOTCLASSPATH_DEX_LOCATIONS", strings.Join(dexLocations, " ")) var imageNames []string - // TODO: the primary ART boot image should not be exposed to Make, as it is installed in a - // different way as a part of the ART APEX. However, there is a special JIT-Zygote build - // configuration which uses the primary ART image instead of the Framework boot image - // extension, and it relies on the ART image being exposed to Make. To fix this, it is - // necessary to rework the logic in makefiles. + // The primary ART boot image is exposed to Make for testing (gtests) and benchmarking + // (golem) purposes. for _, current := range append(d.otherImages, image) { imageNames = append(imageNames, current.name) for _, variant := range current.variants { diff --git a/java/dexpreopt_bootjars.go_v1 b/java/dexpreopt_bootjars.go_v1 new file mode 100644 index 000000000..07a357bb5 --- /dev/null +++ b/java/dexpreopt_bootjars.go_v1 @@ -0,0 +1,952 @@ +// 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 ( + "path/filepath" + "strings" + + "android/soong/android" + "android/soong/dexpreopt" + + "github.com/google/blueprint/proptools" +) + +// ================================================================================================= +// WIP - see http://b/177892522 for details +// +// The build support for boot images is currently being migrated away from singleton to modules so +// the documentation may not be strictly accurate. Rather than update the documentation at every +// step which will create a lot of churn the changes that have been made will be listed here and the +// documentation will be updated once it is closer to the final result. +// +// Changes: +// 1) dex_bootjars is now a singleton module and not a plain singleton. +// 2) Boot images are now represented by the boot_image module type. +// 3) The art boot image is called "art-boot-image", the framework boot image is called +// "framework-boot-image". +// 4) They are defined in art/build/boot/Android.bp and frameworks/base/boot/Android.bp +// respectively. +// 5) Each boot_image retrieves the appropriate boot image configuration from the map returned by +// genBootImageConfigs() using the image_name specified in the boot_image module. +// ================================================================================================= + +// This comment describes: +// 1. ART boot images in general (their types, structure, file layout, etc.) +// 2. build system support for boot images +// +// 1. ART boot images +// ------------------ +// +// A boot image in ART is a set of files that contain AOT-compiled native code and a heap snapshot +// of AOT-initialized classes for the bootclasspath Java libraries. A boot image is compiled from a +// set of DEX jars by the dex2oat compiler. A boot image is used for two purposes: 1) it is +// installed on device and loaded at runtime, and 2) other Java libraries and apps are compiled +// against it (compilation may take place either on host, known as "dexpreopt", or on device, known +// as "dexopt"). +// +// A boot image is not a single file, but a collection of interrelated files. Each boot image has a +// number of components that correspond to the Java libraries that constitute it. For each component +// there are multiple files: +// - *.oat or *.odex file with native code (architecture-specific, one per instruction set) +// - *.art file with pre-initialized Java classes (architecture-specific, one per instruction set) +// - *.vdex file with verification metadata for the DEX bytecode (architecture independent) +// +// *.vdex files for the boot images do not contain the DEX bytecode itself, because the +// bootclasspath DEX files are stored on disk in uncompressed and aligned form. Consequently a boot +// image is not self-contained and cannot be used without its DEX files. To simplify the management +// of boot image files, ART uses a certain naming scheme and associates the following metadata with +// each boot image: +// - A stem, which is a symbolic name that is prepended to boot image file names. +// - A location (on-device path to the boot image files). +// - A list of boot image locations (on-device paths to dependency boot images). +// - A set of DEX locations (on-device paths to the DEX files, one location for one DEX file used +// to compile the boot image). +// +// There are two kinds of boot images: +// - primary boot images +// - boot image extensions +// +// 1.1. Primary boot images +// ------------------------ +// +// A primary boot image is compiled for a core subset of bootclasspath Java libraries. It does not +// depend on any other images, and other boot images may depend on it. +// +// For example, assuming that the stem is "boot", the location is /apex/com.android.art/javalib/, +// the set of core bootclasspath libraries is A B C, and the boot image is compiled for ARM targets +// (32 and 64 bits), it will have three components with the following files: +// - /apex/com.android.art/javalib/{arm,arm64}/boot.{art,oat,vdex} +// - /apex/com.android.art/javalib/{arm,arm64}/boot-B.{art,oat,vdex} +// - /apex/com.android.art/javalib/{arm,arm64}/boot-C.{art,oat,vdex} +// +// The files of the first component are special: they do not have the component name appended after +// the stem. This naming convention dates back to the times when the boot image was not split into +// components, and there were just boot.oat and boot.art. The decision to split was motivated by +// licensing reasons for one of the bootclasspath libraries. +// +// As of November 2020 the only primary boot image in Android is the image in the ART APEX +// com.android.art. The primary ART boot image contains the Core libraries that are part of the ART +// module. When the ART module gets updated, the primary boot image will be updated with it, and all +// dependent images will get invalidated (the checksum of the primary image stored in dependent +// images will not match), unless they are updated in sync with the ART module. +// +// 1.2. Boot image extensions +// -------------------------- +// +// A boot image extension is compiled for a subset of bootclasspath Java libraries (in particular, +// this subset does not include the Core bootclasspath libraries that go into the primary boot +// image). A boot image extension depends on the primary boot image and optionally some other boot +// image extensions. Other images may depend on it. In other words, boot image extensions can form +// acyclic dependency graphs. +// +// The motivation for boot image extensions comes from the Mainline project. Consider a situation +// when the list of bootclasspath libraries is A B C, and both A and B are parts of the Android +// platform, but C is part of an updatable APEX com.android.C. When the APEX is updated, the Java +// code for C might have changed compared to the code that was used to compile the boot image. +// Consequently, the whole boot image is obsolete and invalidated (even though the code for A and B +// that does not depend on C is up to date). To avoid this, the original monolithic boot image is +// split in two parts: the primary boot image that contains A B, and the boot image extension that +// contains C and depends on the primary boot image (extends it). +// +// For example, assuming that the stem is "boot", the location is /system/framework, the set of +// bootclasspath libraries is D E (where D is part of the platform and is located in +// /system/framework, and E is part of a non-updatable APEX com.android.E and is located in +// /apex/com.android.E/javalib), and the boot image is compiled for ARM targets (32 and 64 bits), +// it will have two components with the following files: +// - /system/framework/{arm,arm64}/boot-D.{art,oat,vdex} +// - /system/framework/{arm,arm64}/boot-E.{art,oat,vdex} +// +// As of November 2020 the only boot image extension in Android is the Framework boot image +// extension. It extends the primary ART boot image and contains Framework libraries and other +// bootclasspath libraries from the platform and non-updatable APEXes that are not included in the +// ART image. The Framework boot image extension is updated together with the platform. In the +// future other boot image extensions may be added for some updatable modules. +// +// +// 2. Build system support for boot images +// --------------------------------------- +// +// The primary ART boot image needs to be compiled with one dex2oat invocation that depends on DEX +// jars for the core libraries. Framework boot image extension needs to be compiled with one dex2oat +// invocation that depends on the primary ART boot image and all bootclasspath DEX jars except the +// core libraries as they are already part of the primary ART boot image. +// +// 2.1. Libraries that go in the boot images +// ----------------------------------------- +// +// The contents of each boot image are determined by the PRODUCT variables. The primary ART APEX +// boot image contains libraries listed in the ART_APEX_JARS variable in the AOSP makefiles. The +// Framework boot image extension contains libraries specified in the PRODUCT_BOOT_JARS and +// PRODUCT_BOOT_JARS_EXTRA variables. The AOSP makefiles specify some common Framework libraries, +// but more product-specific libraries can be added in the product makefiles. +// +// Each component of the PRODUCT_BOOT_JARS and PRODUCT_BOOT_JARS_EXTRA variables is a +// colon-separated pair <apex>:<library>, where <apex> is the variant name of a non-updatable APEX, +// "platform" if the library is a part of the platform in the system partition, or "system_ext" if +// it's in the system_ext partition. +// +// In these variables APEXes are identified by their "variant names", i.e. the names they get +// mounted as in /apex on device. In Soong modules that is the name set in the "apex_name" +// properties, which default to the "name" values. For example, many APEXes have both +// com.android.xxx and com.google.android.xxx modules in Soong, but take the same place +// /apex/com.android.xxx at runtime. In these cases the variant name is always com.android.xxx, +// regardless which APEX goes into the product. See also android.ApexInfo.ApexVariationName and +// apex.apexBundleProperties.Apex_name. +// +// A related variable PRODUCT_APEX_BOOT_JARS contains bootclasspath libraries that are in APEXes. +// They are not included in the boot image. The only exception here are ART jars and core-icu4j.jar +// that have been historically part of the boot image and are now in apexes; they are in boot images +// and core-icu4j.jar is generally treated as being part of PRODUCT_BOOT_JARS. +// +// One exception to the above rules are "coverage" builds (a special build flavor which requires +// setting environment variable EMMA_INSTRUMENT_FRAMEWORK=true). In coverage builds the Java code in +// boot image libraries is instrumented, which means that the instrumentation library (jacocoagent) +// needs to be added to the list of bootclasspath DEX jars. +// +// In general, there is a requirement that the source code for a boot image library must be +// available at build time (e.g. it cannot be a stub that has a separate implementation library). +// +// 2.2. Static configs +// ------------------- +// +// Because boot images are used to dexpreopt other Java modules, the paths to boot image files must +// be known by the time dexpreopt build rules for the dependent modules are generated. Boot image +// configs are constructed very early during the build, before build rule generation. The configs +// provide predefined paths to boot image files (these paths depend only on static build +// configuration, such as PRODUCT variables, and use hard-coded directory names). +// +// 2.3. Singleton +// -------------- +// +// Build rules for the boot images are generated with a Soong singleton. Because a singleton has no +// dependencies on other modules, it has to find the modules for the DEX jars using VisitAllModules. +// Soong loops through all modules and compares each module against a list of bootclasspath library +// names. Then it generates build rules that copy DEX jars from their intermediate module-specific +// locations to the hard-coded locations predefined in the boot image configs. +// +// It would be possible to use a module with proper dependencies instead, but that would require +// changes in the way Soong generates variables for Make: a singleton can use one MakeVars() method +// that writes variables to out/soong/make_vars-*.mk, which is included early by the main makefile, +// but module(s) would have to use out/soong/Android-*.mk which has a group of LOCAL_* variables +// for each module, and is included later. +// +// 2.4. Install rules +// ------------------ +// +// The primary boot image and the Framework extension are installed in different ways. The primary +// boot image is part of the ART APEX: it is copied into the APEX intermediate files, packaged +// together with other APEX contents, extracted and mounted on device. The Framework boot image +// extension is installed by the rules defined in makefiles (make/core/dex_preopt_libart.mk). Soong +// writes out a few DEXPREOPT_IMAGE_* variables for Make; these variables contain boot image names, +// paths and so on. +// + +var artApexNames = []string{ + "com.android.art", + "com.android.art.debug", + "com.android.art.testing", + "com.google.android.art", + "com.google.android.art.debug", + "com.google.android.art.testing", +} + +func init() { + RegisterDexpreoptBootJarsComponents(android.InitRegistrationContext) +} + +// Target-independent description of a boot image. +type bootImageConfig struct { + // If this image is an extension, the image that it extends. + extends *bootImageConfig + + // 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. + installDirOnHost string + + // Subdirectory where the image files on device are installed. + installDirOnDevice string + + // Install path of the boot image profile if it needs to be installed in the APEX, or empty if not + // needed. + profileInstallPathInApex string + + // A list of (location, jar) pairs for the Java modules in this image. + modules android.ConfiguredJarList + + // File paths to jars. + dexPaths android.WritablePaths // for this image + dexPathsDeps android.WritablePaths // for the dependency images and in this image + + // Map from module name (without prebuilt_ prefix) to the predefined build path. + dexPathsByModule map[string]android.WritablePath + + // 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 + + // Path to the license metadata file for the module that built the profile. + profileLicenseMetadataFile android.OptionalPath + + // Path to the image profile file on host (or empty, if profile is not generated). + profilePathOnHost android.Path + + // Target-dependent fields. + variants []*bootImageVariant + + // Path of the preloaded classes file. + preloadedClassesFile string +} + +// Target-dependent description of a boot image. +type bootImageVariant struct { + *bootImageConfig + + // Target for which the image is generated. + target android.Target + + // The "locations" of jars. + dexLocations []string // for this image + dexLocationsDeps []string // for the dependency images and in this image + + // Paths to image files. + imagePathOnHost android.OutputPath // first image file path on host + imagePathOnDevice string // first image file path on device + + // All the files that constitute this image variant, i.e. .art, .oat and .vdex files. + imagesDeps android.OutputPaths + + // The path to the primary image variant's imagePathOnHost field, where primary image variant + // means the image variant that this extends. + // + // This is only set for a variant of an image that extends another image. + primaryImages android.OutputPath + + // The paths to the primary image variant's imagesDeps field, where primary image variant + // means the image variant that this extends. + // + // This is only set for a variant of an image that extends another image. + primaryImagesDeps android.Paths + + // Rules which should be used in make to install the outputs on host. + installs android.RuleBuilderInstalls + vdexInstalls android.RuleBuilderInstalls + unstrippedInstalls android.RuleBuilderInstalls + + // Rules which should be used in make to install the outputs on device. + deviceInstalls android.RuleBuilderInstalls + + // Path to the license metadata file for the module that built the image. + licenseMetadataFile android.OptionalPath +} + +// Get target-specific boot image variant for the given boot image config and target. +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 +} + +// Return any (the first) variant which is for the device (as opposed to for the host). +func (image bootImageConfig) getAnyAndroidVariant() *bootImageVariant { + for _, variant := range image.variants { + if variant.target.Os == android.Android { + return variant + } + } + return nil +} + +// Return the name of a boot image module given a boot image config and a component (module) index. +// A module name is a combination of the Java library name, and the boot image stem (that is stored +// in the config). +func (image bootImageConfig) moduleName(ctx android.PathContext, idx int) string { + // The first module of the primary boot image is special: its module name has only the stem, but + // not the library name. All other module names are of the form <stem>-<library name> + m := image.modules.Jar(idx) + name := image.stem + if idx != 0 || image.extends != nil { + name += "-" + android.ModuleStem(m) + } + return name +} + +// Return the name of the first boot image module, or stem if the list of modules is empty. +func (image bootImageConfig) firstModuleNameOrStem(ctx android.PathContext) string { + if image.modules.Len() > 0 { + return image.moduleName(ctx, 0) + } else { + return image.stem + } +} + +// Return filenames for the given boot image component, given the output directory and a list of +// extensions. +func (image bootImageConfig) moduleFiles(ctx android.PathContext, dir android.OutputPath, exts ...string) android.OutputPaths { + ret := make(android.OutputPaths, 0, image.modules.Len()*len(exts)) + for i := 0; i < image.modules.Len(); i++ { + name := image.moduleName(ctx, i) + for _, ext := range exts { + ret = append(ret, dir.Join(ctx, name+ext)) + } + } + return ret +} + +// apexVariants returns a list of all *bootImageVariant that could be included in an apex. +func (image *bootImageConfig) apexVariants() []*bootImageVariant { + variants := []*bootImageVariant{} + for _, variant := range image.variants { + // We also generate boot images for host (for testing), but we don't need those in the apex. + // TODO(b/177892522) - consider changing this to check Os.OsClass = android.Device + if variant.target.Os == android.Android { + variants = append(variants, variant) + } + } + return variants +} + +// Returns true if the boot image should be installed in the APEX. +func (image *bootImageConfig) shouldInstallInApex() bool { + return strings.HasPrefix(image.installDirOnDevice, "apex/") +} + +// Return boot image locations (as a list of symbolic paths). +// +// The image "location" is a symbolic path that, with multiarchitecture support, doesn't really +// exist on the device. Typically it is /apex/com.android.art/javalib/boot.art and should be the +// same for all supported architectures on the device. The concrete architecture specific files +// actually end up in architecture-specific sub-directory such as arm, arm64, x86, or x86_64. +// +// For example a physical file /apex/com.android.art/javalib/x86/boot.art has "image location" +// /apex/com.android.art/javalib/boot.art (which is not an actual file). +// +// For a primary boot image the list of locations has a single element. +// +// For a boot image extension the list of locations contains a location for all dependency images +// (including the primary image) and the location of the extension itself. For example, for the +// Framework boot image extension that depends on the primary ART boot image the list contains two +// elements. +// +// The location is passed as an argument to the ART tools like dex2oat instead of the real path. +// ART tools will then reconstruct the architecture-specific real path. +// +func (image *bootImageVariant) imageLocations() (imageLocationsOnHost []string, imageLocationsOnDevice []string) { + if image.extends != nil { + imageLocationsOnHost, imageLocationsOnDevice = image.extends.getVariant(image.target).imageLocations() + } + return append(imageLocationsOnHost, dexpreopt.PathToLocation(image.imagePathOnHost, image.target.Arch.ArchType)), + append(imageLocationsOnDevice, dexpreopt.PathStringToLocation(image.imagePathOnDevice, image.target.Arch.ArchType)) +} + +func dexpreoptBootJarsFactory() android.SingletonModule { + m := &dexpreoptBootJars{} + android.InitAndroidModule(m) + return m +} + +func RegisterDexpreoptBootJarsComponents(ctx android.RegistrationContext) { + ctx.RegisterSingletonModuleType("dex_bootjars", dexpreoptBootJarsFactory) +} + +func SkipDexpreoptBootJars(ctx android.PathContext) bool { + return dexpreopt.GetGlobalConfig(ctx).DisablePreoptBootImages +} + +// Singleton module for generating boot image build rules. +type dexpreoptBootJars struct { + android.SingletonModuleBase + + // Default boot image config (currently always the Framework boot image extension). It should be + // noted that JIT-Zygote builds use ART APEX image instead of the Framework boot image extension, + // but the switch is handled not here, but in the makefiles (triggered with + // DEXPREOPT_USE_ART_IMAGE=true). + defaultBootImage *bootImageConfig + + // Build path to a config file that Soong writes for Make (to be used in makefiles that install + // the default boot image). + dexpreoptConfigForMake android.WritablePath +} + +// Provide paths to boot images for use by modules that depend upon them. +// +// The build rules are created in GenerateSingletonBuildActions(). +func (d *dexpreoptBootJars) GenerateAndroidBuildActions(ctx android.ModuleContext) { + // Placeholder for now. +} + +// Generate build rules for boot images. +func (d *dexpreoptBootJars) GenerateSingletonBuildActions(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 := dexpreopt.GetGlobalConfig(ctx) + if !shouldBuildBootImages(ctx.Config(), global) { + return + } + + defaultImageConfig := defaultBootImageConfig(ctx) + d.defaultBootImage = defaultImageConfig +} + +// shouldBuildBootImages determines whether boot images should be built. +func shouldBuildBootImages(config android.Config, global *dexpreopt.GlobalConfig) bool { + // 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. + // Note: this is technically incorrect. Compiled code contains stack checks which may depend + // on ASAN settings. + if len(config.SanitizeDevice()) == 1 && config.SanitizeDevice()[0] == "address" && global.SanitizeLite { + return false + } + return true +} + +// copyBootJarsToPredefinedLocations generates commands that will copy boot jars to predefined +// paths in the global config. +func copyBootJarsToPredefinedLocations(ctx android.ModuleContext, srcBootDexJarsByModule bootDexJarByModule, dstBootJarsByModule map[string]android.WritablePath) { + // Create the super set of module names. + names := []string{} + names = append(names, android.SortedStringKeys(srcBootDexJarsByModule)...) + names = append(names, android.SortedStringKeys(dstBootJarsByModule)...) + names = android.SortedUniqueStrings(names) + for _, name := range names { + src := srcBootDexJarsByModule[name] + dst := dstBootJarsByModule[name] + + if src == nil { + // A dex boot jar should be provided by the source java module. It needs to be installable or + // have compile_dex=true - cf. assignments to java.Module.dexJarFile. + // + // However, the source java module may be either replaced or overridden (using prefer:true) by + // a prebuilt java module with the same name. In that case the dex boot jar needs to be + // provided by the corresponding prebuilt APEX module. That APEX is the one that refers + // through a exported_(boot|systemserver)classpath_fragments property to a + // prebuilt_(boot|systemserver)classpath_fragment module, which in turn lists the prebuilt + // java module in the contents property. If that chain is broken then this dependency will + // fail. + if !ctx.Config().AllowMissingDependencies() { + ctx.ModuleErrorf("module %s does not provide a dex boot jar (see comment next to this message in Soong for details)", name) + } else { + ctx.AddMissingDependencies([]string{name}) + } + } else if dst == nil { + ctx.ModuleErrorf("module %s is not part of the boot configuration", name) + } else { + ctx.Build(pctx, android.BuildParams{ + Rule: android.Cp, + Input: src, + Output: dst, + }) + } + } +} + +// buildBootImageVariantsForAndroidOs generates rules to build the boot image variants for the +// android.Android OsType and returns a map from the architectures to the paths of the generated +// boot image files. +// +// The paths are returned because they are needed elsewhere in Soong, e.g. for populating an APEX. +func buildBootImageVariantsForAndroidOs(ctx android.ModuleContext, image *bootImageConfig, profile android.WritablePath) bootImageFilesByArch { + return buildBootImageForOsType(ctx, image, profile, android.Android) +} + +// buildBootImageVariantsForBuildOs generates rules to build the boot image variants for the +// config.BuildOS OsType, i.e. the type of OS on which the build is being running. +// +// The files need to be generated into their predefined location because they are used from there +// both within Soong and outside, e.g. for ART based host side testing and also for use by some +// cloud based tools. However, they are not needed by callers of this function and so the paths do +// not need to be returned from this func, unlike the buildBootImageVariantsForAndroidOs func. +func buildBootImageVariantsForBuildOs(ctx android.ModuleContext, image *bootImageConfig, profile android.WritablePath) { + buildBootImageForOsType(ctx, image, profile, ctx.Config().BuildOS) +} + +// buildBootImageForOsType takes a bootImageConfig, a profile file and an android.OsType +// boot image files are required for and it creates rules to build the boot image +// files for all the required architectures for them. +// +// It returns a map from android.ArchType to the predefined paths of the boot image files. +func buildBootImageForOsType(ctx android.ModuleContext, image *bootImageConfig, profile android.WritablePath, requiredOsType android.OsType) bootImageFilesByArch { + filesByArch := bootImageFilesByArch{} + for _, variant := range image.variants { + if variant.target.Os == requiredOsType { + buildBootImageVariant(ctx, variant, profile) + filesByArch[variant.target.Arch.ArchType] = variant.imagesDeps.Paths() + } + } + + return filesByArch +} + +// buildBootImageZipInPredefinedLocation generates a zip file containing all the boot image files. +// +// The supplied filesByArch is nil when the boot image files have not been generated. Otherwise, it +// is a map from android.ArchType to the predefined locations. +func buildBootImageZipInPredefinedLocation(ctx android.ModuleContext, image *bootImageConfig, filesByArch bootImageFilesByArch) { + if filesByArch == nil { + return + } + + // Compute the list of files from all the architectures. + zipFiles := android.Paths{} + for _, archType := range android.ArchTypeList() { + zipFiles = append(zipFiles, filesByArch[archType]...) + } + + rule := android.NewRuleBuilder(pctx, ctx) + rule.Command(). + BuiltTool("soong_zip"). + FlagWithOutput("-o ", image.zip). + FlagWithArg("-C ", image.dir.Join(ctx, android.Android.String()).String()). + FlagWithInputList("-f ", zipFiles, " -f ") + + rule.Build("zip_"+image.name, "zip "+image.name+" image") +} + +// Generate boot image build rules for a specific target. +func buildBootImageVariant(ctx android.ModuleContext, image *bootImageVariant, profile android.Path) { + + globalSoong := dexpreopt.GetGlobalSoongConfig(ctx) + global := dexpreopt.GetGlobalConfig(ctx) + + arch := image.target.Arch.ArchType + os := image.target.Os.String() // We need to distinguish host-x86 and device-x86. + symbolsDir := image.symbolsDir.Join(ctx, os, image.installDirOnHost, arch.String()) + symbolsFile := symbolsDir.Join(ctx, image.stem+".oat") + outputDir := image.dir.Join(ctx, os, image.installDirOnHost, arch.String()) + outputPath := outputDir.Join(ctx, image.stem+".oat") + oatLocation := dexpreopt.PathToLocation(outputPath, arch) + imagePath := outputPath.ReplaceExtension(ctx, "art") + + rule := android.NewRuleBuilder(pctx, ctx) + + rule.Command().Text("mkdir").Flag("-p").Flag(symbolsDir.String()) + rule.Command().Text("rm").Flag("-f"). + Flag(symbolsDir.Join(ctx, "*.art").String()). + Flag(symbolsDir.Join(ctx, "*.oat").String()). + Flag(symbolsDir.Join(ctx, "*.invocation").String()) + rule.Command().Text("rm").Flag("-f"). + Flag(outputDir.Join(ctx, "*.art").String()). + Flag(outputDir.Join(ctx, "*.oat").String()). + Flag(outputDir.Join(ctx, "*.invocation").String()) + + cmd := rule.Command() + + extraFlags := ctx.Config().Getenv("ART_BOOT_IMAGE_EXTRA_ARGS") + if extraFlags == "" { + // Use ANDROID_LOG_TAGS to suppress most logging by default... + cmd.Text(`ANDROID_LOG_TAGS="*:e"`) + } else { + // ...unless the boot image is generated specifically for testing, then allow all logging. + cmd.Text(`ANDROID_LOG_TAGS="*:v"`) + } + + invocationPath := outputPath.ReplaceExtension(ctx, "invocation") + + cmd.Tool(globalSoong.Dex2oat). + Flag("--avoid-storing-invocation"). + FlagWithOutput("--write-invocation-to=", invocationPath).ImplicitOutput(invocationPath). + Flag("--runtime-arg").FlagWithArg("-Xms", global.Dex2oatImageXms). + Flag("--runtime-arg").FlagWithArg("-Xmx", global.Dex2oatImageXmx) + + if profile != nil { + cmd.FlagWithInput("--profile-file=", profile) + } + + dirtyImageFile := "frameworks/base/config/dirty-image-objects" + dirtyImagePath := android.ExistentPathForSource(ctx, dirtyImageFile) + if dirtyImagePath.Valid() { + cmd.FlagWithInput("--dirty-image-objects=", dirtyImagePath.Path()) + } + + if image.extends != nil { + // It is a boot image extension, so it needs the boot image it depends on (in this case the + // primary ART APEX image). + artImage := image.primaryImages + cmd. + Flag("--runtime-arg").FlagWithInputList("-Xbootclasspath:", image.dexPathsDeps.Paths(), ":"). + Flag("--runtime-arg").FlagWithList("-Xbootclasspath-locations:", image.dexLocationsDeps, ":"). + // Add the path to the first file in the boot image with the arch specific directory removed, + // dex2oat will reconstruct the path to the actual file when it needs it. As the actual path + // to the file cannot be passed to the command make sure to add the actual path as an Implicit + // dependency to ensure that it is built before the command runs. + FlagWithArg("--boot-image=", dexpreopt.PathToLocation(artImage, arch)).Implicit(artImage). + // Similarly, the dex2oat tool will automatically find the paths to other files in the base + // boot image so make sure to add them as implicit dependencies to ensure that they are built + // before this command is run. + Implicits(image.primaryImagesDeps) + } else { + // It is a primary image, so it needs a base address. + cmd.FlagWithArg("--base=", ctx.Config().LibartImgDeviceBaseAddress()) + } + + // We always expect a preloaded classes file to be available. However, if we cannot find it, it's + // OK to not pass the flag to dex2oat. + preloadedClassesPath := android.ExistentPathForSource(ctx, image.preloadedClassesFile) + if preloadedClassesPath.Valid() { + cmd.FlagWithInput("--preloaded-classes=", preloadedClassesPath.Path()) + } + + cmd. + FlagForEachInput("--dex-file=", image.dexPaths.Paths()). + FlagForEachArg("--dex-location=", image.dexLocations). + Flag("--generate-debug-info"). + Flag("--generate-build-id"). + Flag("--image-format=lz4hc"). + FlagWithArg("--oat-symbols=", symbolsFile.String()). + Flag("--strip"). + FlagWithArg("--oat-file=", outputPath.String()). + FlagWithArg("--oat-location=", oatLocation). + FlagWithArg("--image=", imagePath.String()). + FlagWithArg("--instruction-set=", arch.String()). + FlagWithArg("--android-root=", global.EmptyDirectory). + FlagWithArg("--no-inline-from=", "core-oj.jar"). + Flag("--force-determinism"). + Flag("--abort-on-hard-verifier-error") + + // Use the default variant/features for host builds. + // The map below contains only device CPU info (which might be x86 on some devices). + if image.target.Os == android.Android { + cmd.FlagWithArg("--instruction-set-variant=", global.CpuVariant[arch]) + cmd.FlagWithArg("--instruction-set-features=", global.InstructionSetFeatures[arch]) + } + + if global.BootFlags != "" { + cmd.Flag(global.BootFlags) + } + + if extraFlags != "" { + cmd.Flag(extraFlags) + } + + cmd.Textf(`|| ( echo %s ; false )`, proptools.ShellEscape(failureMessage)) + + installDir := filepath.Join("/", image.installDirOnHost, arch.String()) + + var vdexInstalls android.RuleBuilderInstalls + var unstrippedInstalls android.RuleBuilderInstalls + var deviceInstalls android.RuleBuilderInstalls + + for _, artOrOat := range image.moduleFiles(ctx, outputDir, ".art", ".oat") { + cmd.ImplicitOutput(artOrOat) + + // Install the .oat and .art files + rule.Install(artOrOat, filepath.Join(installDir, artOrOat.Base())) + } + + for _, vdex := range image.moduleFiles(ctx, outputDir, ".vdex") { + cmd.ImplicitOutput(vdex) + + // Note that the vdex files are identical between architectures. + // Make rules will create symlinks to share them between architectures. + vdexInstalls = append(vdexInstalls, + android.RuleBuilderInstall{vdex, filepath.Join(installDir, 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())}) + } + + if image.installDirOnHost != image.installDirOnDevice && !image.shouldInstallInApex() && !ctx.Config().UnbundledBuild() { + installDirOnDevice := filepath.Join("/", image.installDirOnDevice, arch.String()) + for _, file := range image.moduleFiles(ctx, outputDir, ".art", ".oat", ".vdex") { + deviceInstalls = append(deviceInstalls, + android.RuleBuilderInstall{file, filepath.Join(installDirOnDevice, file.Base())}) + } + } + + rule.Build(image.name+"JarsDexpreopt_"+image.target.String(), "dexpreopt "+image.name+" jars "+arch.String()) + + // save output and installed files for makevars + image.installs = rule.Installs() + image.vdexInstalls = vdexInstalls + image.unstrippedInstalls = unstrippedInstalls + image.deviceInstalls = deviceInstalls + image.licenseMetadataFile = android.OptionalPathForPath(ctx.LicenseMetadataFile()) +} + +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.ModuleContext, image *bootImageConfig) android.WritablePath { + globalSoong := dexpreopt.GetGlobalSoongConfig(ctx) + global := dexpreopt.GetGlobalConfig(ctx) + + if global.DisableGenerateProfile { + return nil + } + + defaultProfile := "frameworks/base/config/boot-image-profile.txt" + + rule := android.NewRuleBuilder(pctx, ctx) + + var bootImageProfile android.Path + if len(global.BootImageProfiles) > 1 { + combinedBootImageProfile := image.dir.Join(ctx, "boot-image-profile.txt") + rule.Command().Text("cat").Inputs(global.BootImageProfiles).Text(">").Output(combinedBootImageProfile) + 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 { + // 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(globalSoong.Profman). + Flag("--output-profile-type=boot"). + FlagWithInput("--create-profile-from=", bootImageProfile). + FlagForEachInput("--apk=", image.dexPathsDeps.Paths()). + FlagForEachArg("--dex-location=", image.getAnyAndroidVariant().dexLocationsDeps). + FlagWithOutput("--reference-profile-file=", profile) + + if image == defaultBootImageConfig(ctx) { + rule.Install(profile, "/system/etc/boot-image.prof") + image.profileInstalls = append(image.profileInstalls, rule.Installs()...) + image.profileLicenseMetadataFile = android.OptionalPathForPath(ctx.LicenseMetadataFile()) + } + + rule.Build("bootJarsProfile", "profile boot jars") + + image.profilePathOnHost = profile + + return profile +} + +// bootFrameworkProfileRule generates the rule to create the boot framework profile and +// returns a path to the generated file. +func bootFrameworkProfileRule(ctx android.ModuleContext, image *bootImageConfig) android.WritablePath { + globalSoong := dexpreopt.GetGlobalSoongConfig(ctx) + global := dexpreopt.GetGlobalConfig(ctx) + + if global.DisableGenerateProfile || ctx.Config().UnbundledBuild() { + return nil + } + + defaultProfile := "frameworks/base/config/boot-profile.txt" + bootFrameworkProfile := android.PathForSource(ctx, defaultProfile) + + profile := image.dir.Join(ctx, "boot.bprof") + + rule := android.NewRuleBuilder(pctx, ctx) + rule.Command(). + Text(`ANDROID_LOG_TAGS="*:e"`). + Tool(globalSoong.Profman). + Flag("--output-profile-type=bprof"). + FlagWithInput("--create-profile-from=", bootFrameworkProfile). + FlagForEachInput("--apk=", image.dexPathsDeps.Paths()). + FlagForEachArg("--dex-location=", image.getAnyAndroidVariant().dexLocationsDeps). + FlagWithOutput("--reference-profile-file=", profile) + + rule.Install(profile, "/system/etc/boot-image.bprof") + rule.Build("bootFrameworkProfile", "profile boot framework jars") + image.profileInstalls = append(image.profileInstalls, rule.Installs()...) + image.profileLicenseMetadataFile = android.OptionalPathForPath(ctx.LicenseMetadataFile()) + + return profile +} + +func dumpOatRules(ctx android.ModuleContext, image *bootImageConfig) { + var allPhonies android.Paths + for _, image := range image.variants { + arch := image.target.Arch.ArchType + suffix := arch.String() + // Host and target might both use x86 arch. We need to ensure the names are unique. + if image.target.Os.Class == android.Host { + suffix = "host-" + suffix + } + // Create a rule to call oatdump. + output := android.PathForOutput(ctx, "boot."+suffix+".oatdump.txt") + rule := android.NewRuleBuilder(pctx, ctx) + imageLocationsOnHost, _ := image.imageLocations() + rule.Command(). + BuiltTool("oatdump"). + FlagWithInputList("--runtime-arg -Xbootclasspath:", image.dexPathsDeps.Paths(), ":"). + FlagWithList("--runtime-arg -Xbootclasspath-locations:", image.dexLocationsDeps, ":"). + FlagWithArg("--image=", strings.Join(imageLocationsOnHost, ":")).Implicits(image.imagesDeps.Paths()). + FlagWithOutput("--output=", output). + FlagWithArg("--instruction-set=", arch.String()) + rule.Build("dump-oat-boot-"+suffix, "dump oat boot "+arch.String()) + + // Create a phony rule that depends on the output file and prints the path. + phony := android.PathForPhony(ctx, "dump-oat-boot-"+suffix) + rule = android.NewRuleBuilder(pctx, ctx) + rule.Command(). + Implicit(output). + ImplicitOutput(phony). + Text("echo").FlagWithArg("Output in ", output.String()) + rule.Build("phony-dump-oat-boot-"+suffix, "dump oat boot "+arch.String()) + + allPhonies = append(allPhonies, phony) + } + + phony := android.PathForPhony(ctx, "dump-oat-boot") + ctx.Build(pctx, android.BuildParams{ + Rule: android.Phony, + Output: phony, + Inputs: allPhonies, + Description: "dump-oat-boot", + }) +} + +func writeGlobalConfigForMake(ctx android.SingletonContext, path android.WritablePath) { + data := dexpreopt.GetGlobalConfigRawData(ctx) + + android.WriteFileRule(ctx, path, string(data)) +} + +// Define Make variables for boot image names, paths, etc. These variables are used in makefiles +// (make/core/dex_preopt_libart.mk) to generate install rules that copy boot image files to the +// correct output directories. +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 { + return + } + + ctx.Strict("DEXPREOPT_IMAGE_PROFILE_BUILT_INSTALLED", image.profileInstalls.String()) + if image.profileLicenseMetadataFile.Valid() { + ctx.Strict("DEXPREOPT_IMAGE_PROFILE_LICENSE_METADATA", image.profileLicenseMetadataFile.String()) + } + + global := dexpreopt.GetGlobalConfig(ctx) + dexPaths, dexLocations := bcpForDexpreopt(ctx, global.PreoptWithUpdatableBcp) + ctx.Strict("DEXPREOPT_BOOTCLASSPATH_DEX_FILES", strings.Join(dexPaths.Strings(), " ")) + ctx.Strict("DEXPREOPT_BOOTCLASSPATH_DEX_LOCATIONS", strings.Join(dexLocations, " ")) + + for _, variant := range image.variants { + suffix := "" + if variant.target.Os.Class == android.Host { + suffix = "_host" + } + sfx := suffix + "_" + variant.target.Arch.ArchType.String() + ctx.Strict("DEXPREOPT_IMAGE_VDEX_BUILT_INSTALLED_"+sfx, variant.vdexInstalls.String()) + ctx.Strict("DEXPREOPT_IMAGE_"+sfx, variant.imagePathOnHost.String()) + ctx.Strict("DEXPREOPT_IMAGE_DEPS_"+sfx, strings.Join(variant.imagesDeps.Strings(), " ")) + ctx.Strict("DEXPREOPT_IMAGE_BUILT_INSTALLED_"+sfx, variant.installs.String()) + ctx.Strict("DEXPREOPT_IMAGE_UNSTRIPPED_BUILT_INSTALLED_"+sfx, variant.unstrippedInstalls.String()) + if variant.licenseMetadataFile.Valid() { + ctx.Strict("DEXPREOPT_IMAGE_LICENSE_METADATA_"+sfx, variant.licenseMetadataFile.String()) + } + } + imageLocationsOnHost, imageLocationsOnDevice := image.getAnyAndroidVariant().imageLocations() + ctx.Strict("DEXPREOPT_IMAGE_LOCATIONS_ON_HOST", strings.Join(imageLocationsOnHost, ":")) + ctx.Strict("DEXPREOPT_IMAGE_LOCATIONS_ON_DEVICE", strings.Join(imageLocationsOnDevice, ":")) + ctx.Strict("DEXPREOPT_IMAGE_ZIP", image.zip.String()) + + // There used to be multiple images for JIT-Zygote mode, not there's only one. + ctx.Strict("DEXPREOPT_IMAGE_NAMES", image.name) +} diff --git a/java/dexpreopt_config.go_v1 b/java/dexpreopt_config.go_v1 new file mode 100644 index 000000000..d71e2bbfd --- /dev/null +++ b/java/dexpreopt_config.go_v1 @@ -0,0 +1,215 @@ +// 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 ( + "path/filepath" + "strings" + + "android/soong/android" + "android/soong/dexpreopt" +) + +// 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) + } + } + // We may also need the images on host in order to run host-based tests. + for _, target := range ctx.Config().Targets[ctx.Config().BuildOS] { + targets = append(targets, target) + } + + return targets +} + +var ( + bootImageConfigKey = android.NewOnceKey("bootImageConfig") + bootImageConfigRawKey = android.NewOnceKey("bootImageConfigRaw") + artBootImageName = "art" + frameworkBootImageName = "boot" +) + +func genBootImageConfigRaw(ctx android.PathContext) map[string]*bootImageConfig { + return ctx.Config().Once(bootImageConfigRawKey, func() interface{} { + global := dexpreopt.GetGlobalConfig(ctx) + + artModules := global.ArtApexJars + frameworkModules := global.BootJars.RemoveList(artModules) + + // ART config for the primary boot image in the ART apex. + // It includes the Core Libraries. + artCfg := bootImageConfig{ + name: artBootImageName, + stem: "boot", + installDirOnHost: "apex/art_boot_images/javalib", + installDirOnDevice: "system/framework", + profileInstallPathInApex: "etc/boot-image.prof", + modules: artModules, + preloadedClassesFile: "art/build/boot/preloaded-classes", + } + + // Framework config for the boot image extension. + // It includes framework libraries and depends on the ART config. + frameworkSubdir := "system/framework" + frameworkCfg := bootImageConfig{ + extends: &artCfg, + name: frameworkBootImageName, + stem: "boot", + installDirOnHost: frameworkSubdir, + installDirOnDevice: frameworkSubdir, + modules: frameworkModules, + preloadedClassesFile: "frameworks/base/config/preloaded-classes", + } + + return map[string]*bootImageConfig{ + artBootImageName: &artCfg, + frameworkBootImageName: &frameworkCfg, + } + }).(map[string]*bootImageConfig) +} + +// Construct the global boot image configs. +func genBootImageConfigs(ctx android.PathContext) map[string]*bootImageConfig { + return ctx.Config().Once(bootImageConfigKey, func() interface{} { + targets := dexpreoptTargets(ctx) + deviceDir := android.PathForOutput(ctx, ctx.Config().DeviceName()) + + configs := genBootImageConfigRaw(ctx) + artCfg := configs[artBootImageName] + frameworkCfg := configs[frameworkBootImageName] + + // 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") + + // expands to <stem>.art for primary image and <stem>-<1st module>.art for extension + imageName := c.firstModuleNameOrStem(ctx) + ".art" + + // 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") + c.dexPaths = c.modules.BuildPaths(ctx, inputDir) + c.dexPathsByModule = c.modules.BuildPathsByModule(ctx, inputDir) + c.dexPathsDeps = c.dexPaths + + // Create target-specific variants. + for _, target := range targets { + arch := target.Arch.ArchType + imageDir := c.dir.Join(ctx, target.Os.String(), c.installDirOnHost, arch.String()) + variant := &bootImageVariant{ + bootImageConfig: c, + target: target, + imagePathOnHost: imageDir.Join(ctx, imageName), + imagePathOnDevice: filepath.Join("/", c.installDirOnDevice, arch.String(), imageName), + imagesDeps: c.moduleFiles(ctx, imageDir, ".art", ".oat", ".vdex"), + dexLocations: c.modules.DevicePaths(ctx.Config(), target.Os), + } + variant.dexLocationsDeps = variant.dexLocations + c.variants = append(c.variants, variant) + } + + c.zip = c.dir.Join(ctx, c.name+".zip") + } + + // specific to the framework config + frameworkCfg.dexPathsDeps = append(artCfg.dexPathsDeps, frameworkCfg.dexPathsDeps...) + for i := range targets { + frameworkCfg.variants[i].primaryImages = artCfg.variants[i].imagePathOnHost + frameworkCfg.variants[i].primaryImagesDeps = artCfg.variants[i].imagesDeps.Paths() + frameworkCfg.variants[i].dexLocationsDeps = append(artCfg.variants[i].dexLocations, frameworkCfg.variants[i].dexLocationsDeps...) + } + + return configs + }).(map[string]*bootImageConfig) +} + +func defaultBootImageConfig(ctx android.PathContext) *bootImageConfig { + return genBootImageConfigs(ctx)[frameworkBootImageName] +} + +// Apex boot config allows to access build/install paths of apex boot jars without going +// through the usual trouble of registering dependencies on those modules and extracting build paths +// from those dependencies. +type apexBootConfig struct { + // A list of apex boot jars. + modules android.ConfiguredJarList + + // A list of predefined build paths to apex boot jars. They are configured very early, + // before the modules for these jars are processed and the actual paths are generated, and + // later on a singleton adds commands to copy actual jars to the predefined paths. + dexPaths android.WritablePaths + + // Map from module name (without prebuilt_ prefix) to the predefined build path. + dexPathsByModule map[string]android.WritablePath + + // A list of dex locations (a.k.a. on-device paths) to the boot jars. + dexLocations []string +} + +var updatableBootConfigKey = android.NewOnceKey("apexBootConfig") + +// Returns apex boot config. +func GetApexBootConfig(ctx android.PathContext) apexBootConfig { + return ctx.Config().Once(updatableBootConfigKey, func() interface{} { + apexBootJars := dexpreopt.GetGlobalConfig(ctx).ApexBootJars + + dir := android.PathForOutput(ctx, ctx.Config().DeviceName(), "apex_bootjars") + dexPaths := apexBootJars.BuildPaths(ctx, dir) + dexPathsByModuleName := apexBootJars.BuildPathsByModule(ctx, dir) + + dexLocations := apexBootJars.DevicePaths(ctx.Config(), android.Android) + + return apexBootConfig{apexBootJars, dexPaths, dexPathsByModuleName, dexLocations} + }).(apexBootConfig) +} + +// Returns a list of paths and a list of locations for the boot jars used in dexpreopt (to be +// passed in -Xbootclasspath and -Xbootclasspath-locations arguments for dex2oat). +func bcpForDexpreopt(ctx android.PathContext, withUpdatable bool) (android.WritablePaths, []string) { + // Non-updatable boot jars (they are used both in the boot image and in dexpreopt). + bootImage := defaultBootImageConfig(ctx) + dexPaths := bootImage.dexPathsDeps + // The dex locations for all Android variants are identical. + dexLocations := bootImage.getAnyAndroidVariant().dexLocationsDeps + + if withUpdatable { + // Apex boot jars (they are used only in dexpreopt, but not in the boot image). + apexBootConfig := GetApexBootConfig(ctx) + dexPaths = append(dexPaths, apexBootConfig.dexPaths...) + dexLocations = append(dexLocations, apexBootConfig.dexLocations...) + } + + return dexPaths, dexLocations +} + +var defaultBootclasspathKey = android.NewOnceKey("defaultBootclasspath") + +var copyOf = android.CopyOf + +func init() { + android.RegisterMakeVarsProvider(pctx, dexpreoptConfigMakevars) +} + +func dexpreoptConfigMakevars(ctx android.MakeVarsContext) { + ctx.Strict("DEXPREOPT_BOOT_JARS_MODULES", strings.Join(defaultBootImageConfig(ctx).modules.CopyOfApexJarPairs(), ":")) +} diff --git a/java/droidstubs.go b/java/droidstubs.go index 3b1f7c041..932fb19ce 100644 --- a/java/droidstubs.go +++ b/java/droidstubs.go @@ -135,6 +135,9 @@ type DroidstubsProperties struct { // if set to true, Metalava will allow framework SDK to contain API levels annotations. Api_levels_annotations_enabled *bool + // Apply the api levels database created by this module rather than generating one in this droidstubs. + Api_levels_module *string + // the dirs which Metalava extracts API levels annotations from. Api_levels_annotations_dirs []string @@ -234,6 +237,7 @@ func (d *Droidstubs) StubsSrcJar() android.Path { var metalavaMergeAnnotationsDirTag = dependencyTag{name: "metalava-merge-annotations-dir"} var metalavaMergeInclusionAnnotationsDirTag = dependencyTag{name: "metalava-merge-inclusion-annotations-dir"} var metalavaAPILevelsAnnotationsDirTag = dependencyTag{name: "metalava-api-levels-annotations-dir"} +var metalavaAPILevelsModuleTag = dependencyTag{name: "metalava-api-levels-module-tag"} func (d *Droidstubs) DepsMutator(ctx android.BottomUpMutatorContext) { d.Javadoc.addDeps(ctx) @@ -255,6 +259,10 @@ func (d *Droidstubs) DepsMutator(ctx android.BottomUpMutatorContext) { ctx.AddDependency(ctx.Module(), metalavaAPILevelsAnnotationsDirTag, apiLevelsAnnotationsDir) } } + + if d.properties.Api_levels_module != nil { + ctx.AddDependency(ctx.Module(), metalavaAPILevelsModuleTag, proptools.String(d.properties.Api_levels_module)) + } } func (d *Droidstubs) stubsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand, stubsDir android.OptionalPath) { @@ -365,21 +373,35 @@ func (d *Droidstubs) inclusionAnnotationsFlags(ctx android.ModuleContext, cmd *a } func (d *Droidstubs) apiLevelsAnnotationsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) { - if !Bool(d.properties.Api_levels_annotations_enabled) { - return + var apiVersions android.Path + if proptools.Bool(d.properties.Api_levels_annotations_enabled) { + d.apiLevelsGenerationFlags(ctx, cmd) + apiVersions = d.apiVersionsXml + } else { + ctx.VisitDirectDepsWithTag(metalavaAPILevelsModuleTag, func(m android.Module) { + if s, ok := m.(*Droidstubs); ok { + apiVersions = s.apiVersionsXml + } else { + ctx.PropertyErrorf("api_levels_module", + "module %q is not a droidstubs module", ctx.OtherModuleName(m)) + } + }) } + if apiVersions != nil { + cmd.FlagWithArg("--current-version ", ctx.Config().PlatformSdkVersion().String()) + cmd.FlagWithArg("--current-codename ", ctx.Config().PlatformSdkCodename()) + cmd.FlagWithInput("--apply-api-levels ", apiVersions) + } +} - d.apiVersionsXml = android.PathForModuleOut(ctx, "metalava", "api-versions.xml") - +func (d *Droidstubs) apiLevelsGenerationFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) { 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!") } + d.apiVersionsXml = android.PathForModuleOut(ctx, "metalava", "api-versions.xml") cmd.FlagWithOutput("--generate-api-levels ", d.apiVersionsXml) - cmd.FlagWithInput("--apply-api-levels ", d.apiVersionsXml) - cmd.FlagWithArg("--current-version ", ctx.Config().PlatformSdkVersion().String()) - cmd.FlagWithArg("--current-codename ", ctx.Config().PlatformSdkCodename()) filename := proptools.StringDefault(d.properties.Api_levels_jar_filename, "android.jar") @@ -675,87 +697,16 @@ func (d *Droidstubs) GenerateAndroidBuildActions(ctx android.ModuleContext) { zipSyncCleanupCmd(rule, srcJarDir) - rule.Build("metalava", "metalava merged") - if apiCheckEnabled(ctx, d.properties.Check_api.Current, "current") { + d.generateCheckCurrentCheckedInApiIsUpToDateBuildRules(ctx) - 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() { - ctx.PropertyErrorf("baseline_file", "current API check can't have a baseline file. (module %s)", ctx.ModuleName()) - } - - d.checkCurrentApiTimestamp = android.PathForModuleOut(ctx, "metalava", "check_current_api.timestamp") - - rule := android.NewRuleBuilder(pctx, ctx) - - // 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`+ - ` m %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("metalavaCurrentApiCheck", "check current API") - - d.updateCurrentApiTimestamp = android.PathForModuleOut(ctx, "metalava", "update_current_api.timestamp") - - // update API rule - rule = android.NewRuleBuilder(pctx, ctx) - - 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("metalavaCurrentApiUpdate", "update current API") + // Make sure that whenever the API stubs are generated that the current checked in API files are + // checked to make sure that they are up-to-date. + cmd.Validation(d.checkCurrentApiTimestamp) } + rule.Build("metalava", "metalava merged") + if String(d.properties.Check_nullability_warnings) != "" { if d.nullabilityWarningsFile == nil { ctx.PropertyErrorf("check_nullability_warnings", @@ -792,6 +743,84 @@ func (d *Droidstubs) GenerateAndroidBuildActions(ctx android.ModuleContext) { } } +func (d *Droidstubs) generateCheckCurrentCheckedInApiIsUpToDateBuildRules(ctx android.ModuleContext) { + 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() { + ctx.PropertyErrorf("baseline_file", "current API check can't have a baseline file. (module %s)", ctx.ModuleName()) + } + + d.checkCurrentApiTimestamp = android.PathForModuleOut(ctx, "metalava", "check_current_api.timestamp") + + rule := android.NewRuleBuilder(pctx, ctx) + + // 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`+ + ` m %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("metalavaCurrentApiCheck", "check current API") + + d.updateCurrentApiTimestamp = android.PathForModuleOut(ctx, "metalava", "update_current_api.timestamp") + + // update API rule + rule = android.NewRuleBuilder(pctx, ctx) + + 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("metalavaCurrentApiUpdate", "update current API") +} + func StubsDefaultsFactory() android.Module { module := &DocDefaults{} diff --git a/java/droidstubs_test.go b/java/droidstubs_test.go index 10d99f3a5..9fdfddeb1 100644 --- a/java/droidstubs_test.go +++ b/java/droidstubs_test.go @@ -46,6 +46,12 @@ func TestDroidstubs(t *testing.T) { api_levels_annotations_enabled: true, api_levels_jar_filename: "android.other.jar", } + + droidstubs { + name: "stubs-applying-api-versions", + srcs: ["bar-doc/a.java"], + api_levels_module: "bar-stubs-other", + } `, map[string][]byte{ "bar-doc/a.java": nil, @@ -53,26 +59,37 @@ func TestDroidstubs(t *testing.T) { testcases := []struct { moduleName string expectedJarFilename string + generate_xml bool high_mem bool }{ { moduleName: "bar-stubs", + generate_xml: true, expectedJarFilename: "android.jar", high_mem: false, }, { moduleName: "bar-stubs-other", + generate_xml: true, expectedJarFilename: "android.other.jar", high_mem: true, }, + { + moduleName: "stubs-applying-api-versions", + generate_xml: false, + }, } for _, c := range testcases { m := ctx.ModuleForTests(c.moduleName, "android_common") manifest := m.Output("metalava.sbox.textproto") sboxProto := android.RuleBuilderSboxProtoForTests(t, manifest) - expected := "--android-jar-pattern ./%/public/" + c.expectedJarFilename - if actual := String(sboxProto.Commands[0].Command); !strings.Contains(actual, expected) { - t.Errorf("For %q, expected metalava argument %q, but was not found %q", c.moduleName, expected, actual) + cmdline := String(sboxProto.Commands[0].Command) + android.AssertStringContainsEquals(t, "api-versions generation flag", cmdline, "--generate-api-levels", c.generate_xml) + if c.expectedJarFilename != "" { + expected := "--android-jar-pattern ./%/public/" + c.expectedJarFilename + if !strings.Contains(cmdline, expected) { + t.Errorf("For %q, expected metalava argument %q, but was not found %q", c.moduleName, expected, cmdline) + } } metalava := m.Rule("metalava") diff --git a/java/fuzz.go b/java/fuzz.go index 584c80b0c..d0f369f2f 100644 --- a/java/fuzz.go +++ b/java/fuzz.go @@ -50,37 +50,19 @@ type JavaFuzzLibrary struct { jniFilePaths android.Paths } -// IsSanitizerEnabled implemented to make JavaFuzzLibrary implement -// cc.Sanitizeable -func (j *JavaFuzzLibrary) IsSanitizerEnabled(ctx android.BaseModuleContext, sanitizerName string) bool { - for _, s := range j.jniProperties.Sanitizers { - if sanitizerName == s { - return true - } - } - return false -} - // IsSanitizerEnabledForJni implemented to make JavaFuzzLibrary implement // cc.JniSanitizeable. It returns a bool for whether a cc dependency should be // sanitized for the given sanitizer or not. func (j *JavaFuzzLibrary) IsSanitizerEnabledForJni(ctx android.BaseModuleContext, sanitizerName string) bool { - return j.IsSanitizerEnabled(ctx, sanitizerName) -} - -// EnableSanitizer implemented to make JavaFuzzLibrary implement -// cc.Sanitizeable -func (j *JavaFuzzLibrary) EnableSanitizer(sanitizerName string) { -} - -// AddSanitizerDependencies implemented to make JavaFuzzLibrary implement -// cc.Sanitizeable -func (j *JavaFuzzLibrary) AddSanitizerDependencies(mctx android.BottomUpMutatorContext, sanitizerName string) { + // TODO: once b/231370928 is resolved, please uncomment the loop + // for _, s := range j.jniProperties.Sanitizers { + // if sanitizerName == s { + // return true + // } + // } + return false } -// To verify that JavaFuzzLibrary implements cc.Sanitizeable -var _ cc.Sanitizeable = (*JavaFuzzLibrary)(nil) - func (j *JavaFuzzLibrary) DepsMutator(mctx android.BottomUpMutatorContext) { if len(j.jniProperties.Jni_libs) > 0 { if j.fuzzPackagedModule.FuzzProperties.Fuzz_config == nil { @@ -189,6 +171,10 @@ func (s *javaFuzzPackager) GenerateBuildActions(ctx android.SingletonContext) { return } + if javaFuzzModule.Target().HostCross { + return + } + fuzzModuleValidator := fuzz.FuzzModule{ javaFuzzModule.ModuleBase, javaFuzzModule.DefaultableModuleBase, diff --git a/java/hiddenapi.go b/java/hiddenapi.go index 3af5f1c7b..cf9c7ad7a 100644 --- a/java/hiddenapi.go +++ b/java/hiddenapi.go @@ -65,6 +65,8 @@ func (h *hiddenAPI) uncompressDex() *bool { type hiddenAPIModule interface { android.Module hiddenAPIIntf + + MinSdkVersion(ctx android.EarlyModuleContext) android.SdkSpec } type hiddenAPIIntf interface { @@ -148,7 +150,7 @@ func (h *hiddenAPI) hiddenAPIEncodeDex(ctx android.ModuleContext, dexJar android // Create a copy of the dex jar which has been encoded with hiddenapi flags. flagsCSV := hiddenAPISingletonPaths(ctx).flags outputDir := android.PathForModuleOut(ctx, "hiddenapi").OutputPath - encodedDex := hiddenAPIEncodeDex(ctx, dexJar, flagsCSV, uncompressDex, outputDir) + encodedDex := hiddenAPIEncodeDex(ctx, dexJar, flagsCSV, uncompressDex, android.SdkSpecNone, outputDir) // Use the encoded dex jar from here onwards. return encodedDex @@ -246,7 +248,7 @@ var hiddenAPIEncodeDexRule = pctx.AndroidStaticRule("hiddenAPIEncodeDex", bluepr // The encode dex rule requires unzipping, encoding and rezipping the classes.dex files along with // all the resources from the input jar. It also ensures that if it was uncompressed in the input // it stays uncompressed in the output. -func hiddenAPIEncodeDex(ctx android.ModuleContext, dexInput, flagsCSV android.Path, uncompressDex bool, outputDir android.OutputPath) android.OutputPath { +func hiddenAPIEncodeDex(ctx android.ModuleContext, dexInput, flagsCSV android.Path, uncompressDex bool, minSdkVersion android.SdkSpec, outputDir android.OutputPath) android.OutputPath { // The output file has the same name as the input file and is in the output directory. output := outputDir.Join(ctx, dexInput.Base()) @@ -274,6 +276,15 @@ func hiddenAPIEncodeDex(ctx android.ModuleContext, dexInput, flagsCSV android.Pa hiddenapiFlags = "--no-force-assign-all" } + // If the library is targeted for Q and/or R then make sure that they do not + // have any S+ flags encoded as that will break the runtime. + minApiLevel := minSdkVersion.ApiLevel + if !minApiLevel.IsNone() { + if minApiLevel.LessThanOrEqualTo(android.ApiLevelOrPanic(ctx, "R")) { + hiddenapiFlags = hiddenapiFlags + " --max-hiddenapi-level=max-target-r" + } + } + ctx.Build(pctx, android.BuildParams{ Rule: hiddenAPIEncodeDexRule, Description: "hiddenapi encode dex", diff --git a/java/hiddenapi_modular.go b/java/hiddenapi_modular.go index 534a8145f..c90b2ff97 100644 --- a/java/hiddenapi_modular.go +++ b/java/hiddenapi_modular.go @@ -1104,7 +1104,7 @@ func hiddenAPIRulesForBootclasspathFragment(ctx android.ModuleContext, contents for _, name := range android.SortedStringKeys(bootDexInfoByModule) { bootDexInfo := bootDexInfoByModule[name] unencodedDex := bootDexInfo.path - encodedDex := hiddenAPIEncodeDex(ctx, unencodedDex, allFlagsCSV, bootDexInfo.uncompressDex, outputDir) + encodedDex := hiddenAPIEncodeDex(ctx, unencodedDex, allFlagsCSV, bootDexInfo.uncompressDex, bootDexInfo.minSdkVersion, outputDir) encodedBootDexJarsByModule[name] = encodedDex } @@ -1188,6 +1188,9 @@ type bootDexInfo struct { // Indicates whether the dex jar needs uncompressing before encoding. uncompressDex bool + + // The minimum sdk version that the dex jar will be used on. + minSdkVersion android.SdkSpec } // bootDexInfoByModule is a map from module name (as returned by module.Name()) to the boot dex @@ -1213,6 +1216,7 @@ func extractBootDexInfoFromModules(ctx android.ModuleContext, contents []android bootDexJarsByModule[module.Name()] = bootDexInfo{ path: bootDexJar, uncompressDex: *hiddenAPIModule.uncompressDex(), + minSdkVersion: hiddenAPIModule.MinSdkVersion(ctx), } } diff --git a/java/java.go b/java/java.go index b34d6de8a..2897fd7f5 100644 --- a/java/java.go +++ b/java/java.go @@ -300,19 +300,11 @@ var _ android.LicenseAnnotationsDependencyTag = dependencyTag{} type usesLibraryDependencyTag struct { dependencyTag - - // SDK version in which the library appared as a standalone library. - sdkVersion int - - // If the dependency is optional or required. - optional bool - - // Whether this is an implicit dependency inferred by Soong, or an explicit one added via - // `uses_libs`/`optional_uses_libs` properties. - implicit bool + sdkVersion int // SDK version in which the library appared as a standalone library. + optional bool // If the dependency is optional or required. } -func makeUsesLibraryDependencyTag(sdkVersion int, optional bool, implicit bool) usesLibraryDependencyTag { +func makeUsesLibraryDependencyTag(sdkVersion int, optional bool) usesLibraryDependencyTag { return usesLibraryDependencyTag{ dependencyTag: dependencyTag{ name: fmt.Sprintf("uses-library-%d", sdkVersion), @@ -320,7 +312,6 @@ func makeUsesLibraryDependencyTag(sdkVersion int, optional bool, implicit bool) }, sdkVersion: sdkVersion, optional: optional, - implicit: implicit, } } @@ -351,6 +342,11 @@ var ( syspropPublicStubDepTag = dependencyTag{name: "sysprop public stub"} jniInstallTag = installDependencyTag{name: "jni install"} binaryInstallTag = installDependencyTag{name: "binary install"} + usesLibReqTag = makeUsesLibraryDependencyTag(dexpreopt.AnySdkVersion, false) + usesLibOptTag = makeUsesLibraryDependencyTag(dexpreopt.AnySdkVersion, true) + usesLibCompat28OptTag = makeUsesLibraryDependencyTag(28, true) + usesLibCompat29ReqTag = makeUsesLibraryDependencyTag(29, false) + usesLibCompat30OptTag = makeUsesLibraryDependencyTag(30, true) ) func IsLibDepTag(depTag blueprint.DependencyTag) bool { @@ -472,6 +468,12 @@ func getJavaVersion(ctx android.ModuleContext, javaVersion string, sdkContext an return normalizeJavaVersion(ctx, javaVersion) } else if ctx.Device() { return defaultJavaLanguageVersion(ctx, sdkContext.SdkVersion(ctx)) + } else if ctx.Config().TargetsJava17() { + // Temporary experimental flag to be able to try and build with + // java version 17 options. The flag, if used, just sets Java + // 17 as the default version, leaving any components that + // target an older version intact. + return JAVA_VERSION_17 } else { return JAVA_VERSION_11 } @@ -486,6 +488,7 @@ const ( JAVA_VERSION_8 = 8 JAVA_VERSION_9 = 9 JAVA_VERSION_11 = 11 + JAVA_VERSION_17 = 17 ) func (v javaVersion) String() string { @@ -500,6 +503,8 @@ func (v javaVersion) String() string { return "1.9" case JAVA_VERSION_11: return "11" + case JAVA_VERSION_17: + return "17" default: return "unsupported" } @@ -522,8 +527,10 @@ func normalizeJavaVersion(ctx android.BaseModuleContext, javaVersion string) jav return JAVA_VERSION_9 case "11": return JAVA_VERSION_11 - case "10": - ctx.PropertyErrorf("java_version", "Java language levels 10 is not supported") + case "17": + return JAVA_VERSION_17 + case "10", "12", "13", "14", "15", "16": + ctx.PropertyErrorf("java_version", "Java language level %s is not supported", javaVersion) return JAVA_VERSION_UNSUPPORTED default: ctx.PropertyErrorf("java_version", "Unrecognized Java language level") @@ -596,12 +603,14 @@ func (j *Library) GenerateAndroidBuildActions(ctx android.ModuleContext) { } j.checkSdkVersions(ctx) - j.dexpreopter.installPath = j.dexpreopter.getInstallPath( - ctx, android.PathForModuleInstall(ctx, "framework", j.Stem()+".jar")) - j.dexpreopter.isSDKLibrary = j.deviceProperties.IsSDKLibrary - setUncompressDex(ctx, &j.dexpreopter, &j.dexer) - j.dexpreopter.uncompressedDex = *j.dexProperties.Uncompress_dex - j.classLoaderContexts = j.usesLibrary.classLoaderContextForUsesLibDeps(ctx) + if ctx.Device() { + j.dexpreopter.installPath = j.dexpreopter.getInstallPath( + ctx, android.PathForModuleInstall(ctx, "framework", j.Stem()+".jar")) + j.dexpreopter.isSDKLibrary = j.deviceProperties.IsSDKLibrary + setUncompressDex(ctx, &j.dexpreopter, &j.dexer) + j.dexpreopter.uncompressedDex = *j.dexProperties.Uncompress_dex + j.classLoaderContexts = j.usesLibrary.classLoaderContextForUsesLibDeps(ctx) + } j.compile(ctx, nil) // Collect the module directory for IDE info in java/jdeps.go. @@ -855,7 +864,25 @@ type hostTestProperties struct { Data_native_bins []string `android:"arch_variant"` // list of device binary modules that should be installed alongside the test - Data_device_bins []string `android:"arch_variant"` + // This property only adds the first variant of the dependency + Data_device_bins_first []string `android:"arch_variant"` + + // list of device binary modules that should be installed alongside the test + // This property adds 64bit AND 32bit variants of the dependency + Data_device_bins_both []string `android:"arch_variant"` + + // list of device binary modules that should be installed alongside the test + // This property only adds 64bit variants of the dependency + Data_device_bins_64 []string `android:"arch_variant"` + + // list of device binary modules that should be installed alongside the test + // This property adds 32bit variants of the dependency if available, or else + // defaults to the 64bit variant + Data_device_bins_prefer32 []string `android:"arch_variant"` + + // list of device binary modules that should be installed alongside the test + // This property only adds 32bit variants of the dependency + Data_device_bins_32 []string `android:"arch_variant"` } type testHelperLibraryProperties struct { @@ -922,6 +949,83 @@ func (j *JavaTestImport) InstallInTestcases() bool { return true } +func (j *TestHost) addDataDeviceBinsDeps(ctx android.BottomUpMutatorContext) { + if len(j.testHostProperties.Data_device_bins_first) > 0 { + deviceVariations := ctx.Config().AndroidFirstDeviceTarget.Variations() + ctx.AddFarVariationDependencies(deviceVariations, dataDeviceBinsTag, j.testHostProperties.Data_device_bins_first...) + } + + var maybeAndroid32Target *android.Target + var maybeAndroid64Target *android.Target + android32TargetList := android.FirstTarget(ctx.Config().Targets[android.Android], "lib32") + android64TargetList := android.FirstTarget(ctx.Config().Targets[android.Android], "lib64") + if len(android32TargetList) > 0 { + maybeAndroid32Target = &android32TargetList[0] + } + if len(android64TargetList) > 0 { + maybeAndroid64Target = &android64TargetList[0] + } + + if len(j.testHostProperties.Data_device_bins_both) > 0 { + if maybeAndroid32Target == nil && maybeAndroid64Target == nil { + ctx.PropertyErrorf("data_device_bins_both", "no device targets available. Targets: %q", ctx.Config().Targets) + return + } + if maybeAndroid32Target != nil { + ctx.AddFarVariationDependencies( + maybeAndroid32Target.Variations(), + dataDeviceBinsTag, + j.testHostProperties.Data_device_bins_both..., + ) + } + if maybeAndroid64Target != nil { + ctx.AddFarVariationDependencies( + maybeAndroid64Target.Variations(), + dataDeviceBinsTag, + j.testHostProperties.Data_device_bins_both..., + ) + } + } + + if len(j.testHostProperties.Data_device_bins_prefer32) > 0 { + if maybeAndroid32Target != nil { + ctx.AddFarVariationDependencies( + maybeAndroid32Target.Variations(), + dataDeviceBinsTag, + j.testHostProperties.Data_device_bins_prefer32..., + ) + } else { + if maybeAndroid64Target == nil { + ctx.PropertyErrorf("data_device_bins_prefer32", "no device targets available. Targets: %q", ctx.Config().Targets) + return + } + ctx.AddFarVariationDependencies( + maybeAndroid64Target.Variations(), + dataDeviceBinsTag, + j.testHostProperties.Data_device_bins_prefer32..., + ) + } + } + + if len(j.testHostProperties.Data_device_bins_32) > 0 { + if maybeAndroid32Target == nil { + ctx.PropertyErrorf("data_device_bins_32", "cannot find 32bit device target. Targets: %q", ctx.Config().Targets) + return + } + deviceVariations := maybeAndroid32Target.Variations() + ctx.AddFarVariationDependencies(deviceVariations, dataDeviceBinsTag, j.testHostProperties.Data_device_bins_32...) + } + + if len(j.testHostProperties.Data_device_bins_64) > 0 { + if maybeAndroid64Target == nil { + ctx.PropertyErrorf("data_device_bins_64", "cannot find 64bit device target. Targets: %q", ctx.Config().Targets) + return + } + deviceVariations := maybeAndroid64Target.Variations() + ctx.AddFarVariationDependencies(deviceVariations, dataDeviceBinsTag, j.testHostProperties.Data_device_bins_64...) + } +} + func (j *TestHost) DepsMutator(ctx android.BottomUpMutatorContext) { if len(j.testHostProperties.Data_native_bins) > 0 { for _, target := range ctx.MultiTargets() { @@ -929,11 +1033,6 @@ func (j *TestHost) DepsMutator(ctx android.BottomUpMutatorContext) { } } - if len(j.testHostProperties.Data_device_bins) > 0 { - deviceVariations := ctx.Config().AndroidFirstDeviceTarget.Variations() - ctx.AddFarVariationDependencies(deviceVariations, dataDeviceBinsTag, j.testHostProperties.Data_device_bins...) - } - if len(j.testProperties.Jni_libs) > 0 { for _, target := range ctx.MultiTargets() { sharedLibVariations := append(target.Variations(), blueprint.Variation{Mutator: "link", Variation: "shared"}) @@ -941,6 +1040,8 @@ func (j *TestHost) DepsMutator(ctx android.BottomUpMutatorContext) { } } + j.addDataDeviceBinsDeps(ctx) + j.deps(ctx) } @@ -948,17 +1049,40 @@ func (j *TestHost) AddExtraResource(p android.Path) { j.extraResources = append(j.extraResources, p) } +func (j *TestHost) dataDeviceBins() []string { + ret := make([]string, 0, + len(j.testHostProperties.Data_device_bins_first)+ + len(j.testHostProperties.Data_device_bins_both)+ + len(j.testHostProperties.Data_device_bins_prefer32)+ + len(j.testHostProperties.Data_device_bins_32)+ + len(j.testHostProperties.Data_device_bins_64), + ) + + ret = append(ret, j.testHostProperties.Data_device_bins_first...) + ret = append(ret, j.testHostProperties.Data_device_bins_both...) + ret = append(ret, j.testHostProperties.Data_device_bins_prefer32...) + ret = append(ret, j.testHostProperties.Data_device_bins_32...) + ret = append(ret, j.testHostProperties.Data_device_bins_64...) + + return ret +} + func (j *TestHost) GenerateAndroidBuildActions(ctx android.ModuleContext) { var configs []tradefed.Config - if len(j.testHostProperties.Data_device_bins) > 0 { + dataDeviceBins := j.dataDeviceBins() + if len(dataDeviceBins) > 0 { // add Tradefed configuration to push device bins to device for testing remoteDir := filepath.Join("/data/local/tests/unrestricted/", j.Name()) options := []tradefed.Option{{Name: "cleanup", Value: "true"}} - for _, bin := range j.testHostProperties.Data_device_bins { + for _, bin := range dataDeviceBins { fullPath := filepath.Join(remoteDir, bin) options = append(options, tradefed.Option{Name: "push-file", Key: bin, Value: fullPath}) } - configs = append(configs, tradefed.Object{"target_preparer", "com.android.tradefed.targetprep.PushFilePreparer", options}) + configs = append(configs, tradefed.Object{ + Type: "target_preparer", + Class: "com.android.tradefed.targetprep.PushFilePreparer", + Options: options, + }) } j.Test.generateAndroidBuildActionsWithConfig(ctx, configs) @@ -1249,10 +1373,10 @@ func (j *Binary) GenerateAndroidBuildActions(ctx android.ModuleContext) { } func (j *Binary) DepsMutator(ctx android.BottomUpMutatorContext) { - if ctx.Arch().ArchType == android.Common || ctx.BazelConversionMode() { + if ctx.Arch().ArchType == android.Common { j.deps(ctx) } - if ctx.Arch().ArchType != android.Common || ctx.BazelConversionMode() { + if ctx.Arch().ArchType != android.Common { // These dependencies ensure the host installation rules will install the jar file and // the jni libraries when the wrapper is installed. ctx.AddVariationDependencies(nil, jniInstallTag, j.binaryProperties.Jni_libs...) @@ -1996,10 +2120,8 @@ func addCLCFromDep(ctx android.ModuleContext, depModule android.Module, depTag := ctx.OtherModuleDependencyTag(depModule) if depTag == libTag { // Ok, propagate <uses-library> through non-static library dependencies. - } else if tag, ok := depTag.(usesLibraryDependencyTag); ok && - tag.sdkVersion == dexpreopt.AnySdkVersion && tag.implicit { - // Ok, propagate <uses-library> through non-compatibility implicit <uses-library> - // dependencies. + } else if tag, ok := depTag.(usesLibraryDependencyTag); ok && tag.sdkVersion == dexpreopt.AnySdkVersion { + // Ok, propagate <uses-library> through non-compatibility <uses-library> dependencies. } else if depTag == staticLibTag { // Propagate <uses-library> through static library dependencies, unless it is a component // library (such as stubs). Component libraries have a dependency on their SDK library, @@ -2017,14 +2139,56 @@ func addCLCFromDep(ctx android.ModuleContext, depModule android.Module, // <uses_library> and should not be added to CLC, but the transitive <uses-library> dependencies // from its CLC should be added to the current CLC. if sdkLib != nil { - clcMap.AddContext(ctx, dexpreopt.AnySdkVersion, *sdkLib, false, true, + clcMap.AddContext(ctx, dexpreopt.AnySdkVersion, *sdkLib, false, dep.DexJarBuildPath().PathOrNil(), dep.DexJarInstallPath(), dep.ClassLoaderContexts()) } else { clcMap.AddContextMap(dep.ClassLoaderContexts(), depName) } } +type javaResourcesAttributes struct { + Resources bazel.LabelListAttribute + Resource_strip_prefix *string +} + +func (m *Library) convertJavaResourcesAttributes(ctx android.TopDownMutatorContext) *javaResourcesAttributes { + var resources bazel.LabelList + var resourceStripPrefix *string + + if m.properties.Java_resources != nil { + resources.Append(android.BazelLabelForModuleSrc(ctx, m.properties.Java_resources)) + } + + //TODO(b/179889880) handle case where glob includes files outside package + resDeps := ResourceDirsToFiles( + ctx, + m.properties.Java_resource_dirs, + m.properties.Exclude_java_resource_dirs, + m.properties.Exclude_java_resources, + ) + + for i, resDep := range resDeps { + dir, files := resDep.dir, resDep.files + + resources.Append(bazel.MakeLabelList(android.RootToModuleRelativePaths(ctx, files))) + + // Bazel includes the relative path from the WORKSPACE root when placing the resource + // inside the JAR file, so we need to remove that prefix + resourceStripPrefix = proptools.StringPtr(dir.String()) + if i > 0 { + // TODO(b/226423379) allow multiple resource prefixes + ctx.ModuleErrorf("bp2build does not support more than one directory in java_resource_dirs (b/226423379)") + } + } + + return &javaResourcesAttributes{ + Resources: bazel.MakeLabelListAttribute(resources), + Resource_strip_prefix: resourceStripPrefix, + } +} + type javaCommonAttributes struct { + *javaResourcesAttributes Srcs bazel.LabelListAttribute Plugins bazel.LabelListAttribute Javacopts bazel.StringListAttribute @@ -2089,6 +2253,11 @@ func (m *Library) convertLibraryAttrsBp2Build(ctx android.TopDownMutatorContext) if m.properties.Javacflags != nil { javacopts = append(javacopts, m.properties.Javacflags...) } + if m.properties.Java_version != nil { + javaVersion := normalizeJavaVersion(ctx, *m.properties.Java_version).String() + javacopts = append(javacopts, fmt.Sprintf("-source %s -target %s", javaVersion, javaVersion)) + } + epEnabled := m.properties.Errorprone.Enabled //TODO(b/227504307) add configuration that depends on RUN_ERROR_PRONE environment variable if Bool(epEnabled) { @@ -2096,7 +2265,8 @@ func (m *Library) convertLibraryAttrsBp2Build(ctx android.TopDownMutatorContext) } commonAttrs := &javaCommonAttributes{ - Srcs: javaSrcs, + Srcs: javaSrcs, + javaResourcesAttributes: m.convertJavaResourcesAttributes(ctx), Plugins: bazel.MakeLabelListAttribute( android.BazelLabelForModuleDeps(ctx, m.properties.Plugins), ), diff --git a/java/java_resources.go b/java/java_resources.go index 787d74a0d..b0dc5a1cf 100644 --- a/java/java_resources.go +++ b/java/java_resources.go @@ -33,8 +33,13 @@ var resourceExcludes = []string{ "**/*~", } -func ResourceDirsToJarArgs(ctx android.ModuleContext, - resourceDirs, excludeResourceDirs, excludeResourceFiles []string) (args []string, deps android.Paths) { +type resourceDeps struct { + dir android.Path + files android.Paths +} + +func ResourceDirsToFiles(ctx android.BaseModuleContext, + resourceDirs, excludeResourceDirs, excludeResourceFiles []string) (deps []resourceDeps) { var excludeDirs []string var excludeFiles []string @@ -55,21 +60,36 @@ func ResourceDirsToJarArgs(ctx android.ModuleContext, dirs := ctx.Glob(android.PathForSource(ctx, ctx.ModuleDir()).Join(ctx, resourceDir).String(), excludeDirs) for _, dir := range dirs { files := ctx.GlobFiles(filepath.Join(dir.String(), "**/*"), excludeFiles) + deps = append(deps, resourceDeps{ + dir: dir, + files: files, + }) + } + } - deps = append(deps, files...) + return deps +} + +func ResourceDirsToJarArgs(ctx android.ModuleContext, + resourceDirs, excludeResourceDirs, excludeResourceFiles []string) (args []string, deps android.Paths) { + resDeps := ResourceDirsToFiles(ctx, resourceDirs, excludeResourceDirs, excludeResourceFiles) - if len(files) > 0 { - args = append(args, "-C", dir.String()) + for _, resDep := range resDeps { + dir, files := resDep.dir, resDep.files - for _, f := range files { - path := f.String() - if !strings.HasPrefix(path, dir.String()) { - panic(fmt.Errorf("path %q does not start with %q", path, dir)) - } - args = append(args, "-f", pathtools.MatchEscape(path)) + if len(files) > 0 { + args = append(args, "-C", dir.String()) + deps = append(deps, files...) + + for _, f := range files { + path := f.String() + if !strings.HasPrefix(path, dir.String()) { + panic(fmt.Errorf("path %q does not start with %q", path, dir)) } + args = append(args, "-f", pathtools.MatchEscape(path)) } } + } return args, deps diff --git a/java/java_test.go b/java/java_test.go index 4c9382413..32b0b0f68 100644 --- a/java/java_test.go +++ b/java/java_test.go @@ -723,9 +723,9 @@ func TestDefaults(t *testing.T) { t.Errorf("atestNoOptimize should not optimize APK") } - atestDefault := ctx.ModuleForTests("atestDefault", "android_common").MaybeRule("r8") + atestDefault := ctx.ModuleForTests("atestDefault", "android_common").MaybeRule("d8") if atestDefault.Output == nil { - t.Errorf("atestDefault should optimize APK") + t.Errorf("atestDefault should not optimize APK") } } @@ -1498,62 +1498,172 @@ func TestErrorproneEnabledOnlyByEnvironmentVariable(t *testing.T) { } func TestDataDeviceBinsBuildsDeviceBinary(t *testing.T) { - bp := ` + testCases := []struct { + dataDeviceBinType string + depCompileMultilib string + variants []string + expectedError string + }{ + { + dataDeviceBinType: "first", + depCompileMultilib: "first", + variants: []string{"android_arm64_armv8-a"}, + }, + { + dataDeviceBinType: "first", + depCompileMultilib: "both", + variants: []string{"android_arm64_armv8-a"}, + }, + { + // this is true because our testing framework is set up with + // Targets ~ [<64bit target>, <32bit target>], where 64bit is "first" + dataDeviceBinType: "first", + depCompileMultilib: "32", + expectedError: `Android.bp:2:3: dependency "bar" of "foo" missing variant`, + }, + { + dataDeviceBinType: "first", + depCompileMultilib: "64", + variants: []string{"android_arm64_armv8-a"}, + }, + { + dataDeviceBinType: "both", + depCompileMultilib: "both", + variants: []string{ + "android_arm_armv7-a-neon", + "android_arm64_armv8-a", + }, + }, + { + dataDeviceBinType: "both", + depCompileMultilib: "32", + expectedError: `Android.bp:2:3: dependency "bar" of "foo" missing variant`, + }, + { + dataDeviceBinType: "both", + depCompileMultilib: "64", + expectedError: `Android.bp:2:3: dependency "bar" of "foo" missing variant`, + }, + { + dataDeviceBinType: "both", + depCompileMultilib: "first", + expectedError: `Android.bp:2:3: dependency "bar" of "foo" missing variant`, + }, + { + dataDeviceBinType: "32", + depCompileMultilib: "32", + variants: []string{"android_arm_armv7-a-neon"}, + }, + { + dataDeviceBinType: "32", + depCompileMultilib: "first", + expectedError: `Android.bp:2:3: dependency "bar" of "foo" missing variant`, + }, + { + dataDeviceBinType: "32", + depCompileMultilib: "both", + variants: []string{"android_arm_armv7-a-neon"}, + }, + { + dataDeviceBinType: "32", + depCompileMultilib: "64", + expectedError: `Android.bp:2:3: dependency "bar" of "foo" missing variant`, + }, + { + dataDeviceBinType: "64", + depCompileMultilib: "64", + variants: []string{"android_arm64_armv8-a"}, + }, + { + dataDeviceBinType: "64", + depCompileMultilib: "both", + variants: []string{"android_arm64_armv8-a"}, + }, + { + dataDeviceBinType: "64", + depCompileMultilib: "first", + variants: []string{"android_arm64_armv8-a"}, + }, + { + dataDeviceBinType: "64", + depCompileMultilib: "32", + expectedError: `Android.bp:2:3: dependency "bar" of "foo" missing variant`, + }, + { + dataDeviceBinType: "prefer32", + depCompileMultilib: "32", + variants: []string{"android_arm_armv7-a-neon"}, + }, + { + dataDeviceBinType: "prefer32", + depCompileMultilib: "both", + variants: []string{"android_arm_armv7-a-neon"}, + }, + { + dataDeviceBinType: "prefer32", + depCompileMultilib: "first", + expectedError: `Android.bp:2:3: dependency "bar" of "foo" missing variant`, + }, + { + dataDeviceBinType: "prefer32", + depCompileMultilib: "64", + expectedError: `Android.bp:2:3: dependency "bar" of "foo" missing variant`, + }, + } + + bpTemplate := ` java_test_host { name: "foo", srcs: ["test.java"], - data_device_bins: ["bar"], + data_device_bins_%s: ["bar"], } cc_binary { name: "bar", + compile_multilib: "%s", } ` - ctx := android.GroupFixturePreparers( - PrepareForIntegrationTestWithJava, - ).RunTestWithBp(t, bp) - - buildOS := ctx.Config.BuildOS.String() - fooVariant := ctx.ModuleForTests("foo", buildOS+"_common") - barVariant := ctx.ModuleForTests("bar", "android_arm64_armv8-a") - fooMod := fooVariant.Module().(*TestHost) + for _, tc := range testCases { + bp := fmt.Sprintf(bpTemplate, tc.dataDeviceBinType, tc.depCompileMultilib) - relocated := barVariant.Output("bar") - expectedInput := "out/soong/.intermediates/bar/android_arm64_armv8-a/unstripped/bar" - android.AssertPathRelativeToTopEquals(t, "relocation input", expectedInput, relocated.Input) + errorHandler := android.FixtureExpectsNoErrors + if tc.expectedError != "" { + errorHandler = android.FixtureExpectsAtLeastOneErrorMatchingPattern(tc.expectedError) + } - entries := android.AndroidMkEntriesForTest(t, ctx.TestContext, fooMod)[0] - expectedData := []string{ - "out/soong/.intermediates/bar/android_arm64_armv8-a/bar:bar", - } - actualData := entries.EntryMap["LOCAL_COMPATIBILITY_SUPPORT_FILES"] - android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_TEST_DATA", ctx.Config, expectedData, actualData) -} + testName := fmt.Sprintf(`data_device_bins_%s with compile_multilib:"%s"`, tc.dataDeviceBinType, tc.depCompileMultilib) + t.Run(testName, func(t *testing.T) { + ctx := android.GroupFixturePreparers(PrepareForIntegrationTestWithJava). + ExtendWithErrorHandler(errorHandler). + RunTestWithBp(t, bp) + if tc.expectedError != "" { + return + } -func TestDataDeviceBinsAutogenTradefedConfig(t *testing.T) { - bp := ` - java_test_host { - name: "foo", - srcs: ["test.java"], - data_device_bins: ["bar"], - } + buildOS := ctx.Config.BuildOS.String() + fooVariant := ctx.ModuleForTests("foo", buildOS+"_common") + fooMod := fooVariant.Module().(*TestHost) + entries := android.AndroidMkEntriesForTest(t, ctx.TestContext, fooMod)[0] - cc_binary { - name: "bar", - } - ` + expectedAutogenConfig := `<option name="push-file" key="bar" value="/data/local/tests/unrestricted/foo/bar" />` + autogen := fooVariant.Rule("autogen") + if !strings.Contains(autogen.Args["extraConfigs"], expectedAutogenConfig) { + t.Errorf("foo extraConfigs %v does not contain %q", autogen.Args["extraConfigs"], expectedAutogenConfig) + } - ctx := android.GroupFixturePreparers( - PrepareForIntegrationTestWithJava, - ).RunTestWithBp(t, bp) + expectedData := []string{} + for _, variant := range tc.variants { + barVariant := ctx.ModuleForTests("bar", variant) + relocated := barVariant.Output("bar") + expectedInput := fmt.Sprintf("out/soong/.intermediates/bar/%s/unstripped/bar", variant) + android.AssertPathRelativeToTopEquals(t, "relocation input", expectedInput, relocated.Input) - buildOS := ctx.Config.BuildOS.String() - fooModule := ctx.ModuleForTests("foo", buildOS+"_common") - expectedAutogenConfig := `<option name="push-file" key="bar" value="/data/local/tests/unrestricted/foo/bar" />` + expectedData = append(expectedData, fmt.Sprintf("out/soong/.intermediates/bar/%s/bar:bar", variant)) + } - autogen := fooModule.Rule("autogen") - if !strings.Contains(autogen.Args["extraConfigs"], expectedAutogenConfig) { - t.Errorf("foo extraConfigs %v does not contain %q", autogen.Args["extraConfigs"], expectedAutogenConfig) + actualData := entries.EntryMap["LOCAL_COMPATIBILITY_SUPPORT_FILES"] + android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_TEST_DATA", ctx.Config, expectedData, actualData) + }) } } diff --git a/java/kotlin.go b/java/kotlin.go index eff5bb53f..903c6249b 100644 --- a/java/kotlin.go +++ b/java/kotlin.go @@ -175,6 +175,7 @@ func kotlinKapt(ctx android.ModuleContext, srcJarOutputFile, resJarOutputFile an var deps android.Paths deps = append(deps, flags.kotlincClasspath...) + deps = append(deps, flags.kotlincDeps...) deps = append(deps, srcJars...) deps = append(deps, flags.processorPath...) deps = append(deps, commonSrcFiles...) diff --git a/java/kotlin_test.go b/java/kotlin_test.go index f9ff98229..491ce2939 100644 --- a/java/kotlin_test.go +++ b/java/kotlin_test.go @@ -42,6 +42,11 @@ func TestKotlin(t *testing.T) { } `) + kotlinStdlib := ctx.ModuleForTests("kotlin-stdlib", "android_common"). + Output("turbine-combined/kotlin-stdlib.jar").Output + kotlinAnnotations := ctx.ModuleForTests("kotlin-annotations", "android_common"). + Output("turbine-combined/kotlin-annotations.jar").Output + fooKotlinc := ctx.ModuleForTests("foo", "android_common").Rule("kotlinc") fooJavac := ctx.ModuleForTests("foo", "android_common").Rule("javac") fooJar := ctx.ModuleForTests("foo", "android_common").Output("combined/foo.jar") @@ -69,6 +74,16 @@ func TestKotlin(t *testing.T) { fooJar.Inputs.Strings(), fooKotlincClasses.String()) } + if !inList(kotlinStdlib.String(), fooJar.Inputs.Strings()) { + t.Errorf("foo jar inputs %v does not contain %v", + fooJar.Inputs.Strings(), kotlinStdlib.String()) + } + + if !inList(kotlinAnnotations.String(), fooJar.Inputs.Strings()) { + t.Errorf("foo jar inputs %v does not contain %v", + fooJar.Inputs.Strings(), kotlinAnnotations.String()) + } + if !inList(fooKotlincHeaderClasses.String(), fooHeaderJar.Inputs.Strings()) { t.Errorf("foo header jar inputs %v does not contain %q", fooHeaderJar.Inputs.Strings(), fooKotlincHeaderClasses.String()) @@ -325,6 +340,7 @@ func TestKotlinCompose(t *testing.T) { java_library { name: "withcompose", srcs: ["a.kt"], + plugins: ["plugin"], static_libs: ["androidx.compose.runtime_runtime"], } @@ -332,6 +348,10 @@ func TestKotlinCompose(t *testing.T) { name: "nocompose", srcs: ["a.kt"], } + + java_plugin { + name: "plugin", + } `) buildOS := result.Config.BuildOS.String() @@ -346,6 +366,9 @@ func TestKotlinCompose(t *testing.T) { android.AssertStringDoesContain(t, "missing compose compiler plugin", withCompose.VariablesForTestsRelativeToTop()["kotlincFlags"], "-Xplugin="+composeCompiler.String()) + android.AssertStringListContains(t, "missing kapt compose compiler dependency", + withCompose.Rule("kapt").Implicits.Strings(), composeCompiler.String()) + android.AssertStringListDoesNotContain(t, "unexpected compose compiler dependency", noCompose.Rule("kotlinc").Implicits.Strings(), composeCompiler.String()) diff --git a/java/lint.go b/java/lint.go index f09db955d..e276345eb 100644 --- a/java/lint.go +++ b/java/lint.go @@ -525,10 +525,18 @@ func (l *lintSingleton) copyLintDependencies(ctx android.SingletonContext) { return } - frameworkDocStubs := findModuleOrErr(ctx, "framework-doc-stubs") - if frameworkDocStubs == nil { + apiVersionsDb := findModuleOrErr(ctx, "api_versions_public") + if apiVersionsDb == nil { if !ctx.Config().AllowMissingDependencies() { - ctx.Errorf("lint: missing framework-doc-stubs") + ctx.Errorf("lint: missing module api_versions_public") + } + return + } + + sdkAnnotations := findModuleOrErr(ctx, "sdk-annotations.zip") + if sdkAnnotations == nil { + if !ctx.Config().AllowMissingDependencies() { + ctx.Errorf("lint: missing module sdk-annotations.zip") } return } @@ -543,13 +551,13 @@ func (l *lintSingleton) copyLintDependencies(ctx android.SingletonContext) { ctx.Build(pctx, android.BuildParams{ Rule: android.CpIfChanged, - Input: android.OutputFileForModule(ctx, frameworkDocStubs, ".annotations.zip"), + Input: android.OutputFileForModule(ctx, sdkAnnotations, ""), Output: copiedAnnotationsZipPath(ctx), }) ctx.Build(pctx, android.BuildParams{ Rule: android.CpIfChanged, - Input: android.OutputFileForModule(ctx, frameworkDocStubs, ".api_versions.xml"), + Input: android.OutputFileForModule(ctx, apiVersionsDb, ".api_versions.xml"), Output: copiedAPIVersionsXmlPath(ctx, "api_versions.xml"), }) diff --git a/java/platform_bootclasspath_test.go b/java/platform_bootclasspath_test.go index 1c2a3aee5..10c918715 100644 --- a/java/platform_bootclasspath_test.go +++ b/java/platform_bootclasspath_test.go @@ -51,6 +51,7 @@ func TestPlatformBootclasspath(t *testing.T) { var addSourceBootclassPathModule = android.FixtureAddTextFile("source/Android.bp", ` java_library { name: "foo", + host_supported: true, // verify that b/232106778 is fixed srcs: ["a.java"], system_modules: "none", sdk_version: "none", @@ -271,7 +272,9 @@ func TestPlatformBootclasspath_Dist(t *testing.T) { entries := android.AndroidMkEntriesForTest(t, result.TestContext, platformBootclasspath) goals := entries[0].GetDistForGoals(platformBootclasspath) android.AssertStringEquals(t, "platform dist goals phony", ".PHONY: droidcore\n", goals[0]) - android.AssertStringEquals(t, "platform dist goals call", "$(call dist-for-goals,droidcore,out/soong/hiddenapi/hiddenapi-flags.csv:hiddenapi-flags.csv)\n", android.StringRelativeToTop(result.Config, goals[1])) + android.AssertStringDoesContain(t, "platform dist goals meta check", goals[1], "$(if $(strip $(ALL_TARGETS.") + android.AssertStringDoesContain(t, "platform dist goals meta assign", goals[1], "),,$(eval ALL_TARGETS.") + android.AssertStringEquals(t, "platform dist goals call", "$(call dist-for-goals,droidcore,out/soong/hiddenapi/hiddenapi-flags.csv:hiddenapi-flags.csv)\n", android.StringRelativeToTop(result.Config, goals[2])) } func TestPlatformBootclasspath_HiddenAPIMonolithicFiles(t *testing.T) { diff --git a/java/prebuilt_apis.go b/java/prebuilt_apis.go index 44650a6d0..944970783 100644 --- a/java/prebuilt_apis.go +++ b/java/prebuilt_apis.go @@ -212,6 +212,10 @@ func createSystemModules(mctx android.LoadHookContext, version, scope string) { mctx.CreateModule(systemModulesImportFactory, &props) } +func PrebuiltApiModuleName(module, scope, version string) string { + return module + ".api." + scope + "." + version +} + func prebuiltApiFiles(mctx android.LoadHookContext, p *prebuiltApis) { // <apiver>/<scope>/api/<module>.txt apiLevelFiles := globApiDirs(mctx, p, "api/*.txt") @@ -220,12 +224,9 @@ func prebuiltApiFiles(mctx android.LoadHookContext, p *prebuiltApis) { } // Create modules for all (<module>, <scope, <version>) triplets, - apiModuleName := func(module, scope, version string) string { - return module + ".api." + scope + "." + version - } for _, f := range apiLevelFiles { module, version, scope := parseFinalizedPrebuiltPath(mctx, f) - createApiModule(mctx, apiModuleName(module, scope, strconv.Itoa(version)), f) + createApiModule(mctx, PrebuiltApiModuleName(module, scope, strconv.Itoa(version)), f) } // Figure out the latest version of each module/scope @@ -266,7 +267,7 @@ func prebuiltApiFiles(mctx android.LoadHookContext, p *prebuiltApis) { // Sort the keys in order to make build.ninja stable for _, k := range android.SortedStringKeys(latest) { info := latest[k] - name := apiModuleName(info.module, info.scope, "latest") + name := PrebuiltApiModuleName(info.module, info.scope, "latest") createApiModule(mctx, name, info.path) } @@ -278,7 +279,7 @@ func prebuiltApiFiles(mctx android.LoadHookContext, p *prebuiltApis) { filename, _, scope := parsePrebuiltPath(mctx, f) referencedModule := strings.TrimSuffix(filename, "-incompatibilities") - createApiModule(mctx, apiModuleName(referencedModule+"-incompatibilities", scope, "latest"), f) + createApiModule(mctx, PrebuiltApiModuleName(referencedModule+"-incompatibilities", scope, "latest"), f) incompatibilities[referencedModule+"."+scope] = true } @@ -286,7 +287,7 @@ func prebuiltApiFiles(mctx android.LoadHookContext, p *prebuiltApis) { // Create empty incompatibilities files for remaining modules for _, k := range android.SortedStringKeys(latest) { if _, ok := incompatibilities[k]; !ok { - createEmptyFile(mctx, apiModuleName(latest[k].module+"-incompatibilities", latest[k].scope, "latest")) + createEmptyFile(mctx, PrebuiltApiModuleName(latest[k].module+"-incompatibilities", latest[k].scope, "latest")) } } } diff --git a/java/proto.go b/java/proto.go index 5ba486fd6..5280077f1 100644 --- a/java/proto.go +++ b/java/proto.go @@ -91,7 +91,7 @@ func protoDeps(ctx android.BottomUpMutatorContext, p *android.ProtoProperties) { case "lite", unspecifiedProtobufPluginType: ctx.AddVariationDependencies(nil, staticLibTag, "libprotobuf-java-lite") case "full": - if ctx.Host() || ctx.BazelConversionMode() { + if ctx.Host() { ctx.AddVariationDependencies(nil, staticLibTag, "libprotobuf-java-full") } else { ctx.PropertyErrorf("proto.type", "full java protos only supported on the host") diff --git a/java/sdk.go b/java/sdk.go index 0dddd40aa..b0da5afba 100644 --- a/java/sdk.go +++ b/java/sdk.go @@ -57,6 +57,12 @@ func defaultJavaLanguageVersion(ctx android.EarlyModuleContext, s android.SdkSpe return JAVA_VERSION_8 } else if sdk.FinalOrFutureInt() <= 31 { return JAVA_VERSION_9 + } else if ctx.Config().TargetsJava17() { + // Temporary experimental flag to be able to try and build with + // java version 17 options. The flag, if used, just sets Java + // 17 as the default version, leaving any components that + // target an older version intact. + return JAVA_VERSION_17 } else { return JAVA_VERSION_11 } diff --git a/java/sdk_library.go b/java/sdk_library.go index c37ed1a27..f7e5d9d40 100644 --- a/java/sdk_library.go +++ b/java/sdk_library.go @@ -97,6 +97,13 @@ type apiScope struct { // The tag to use to depend on the stubs source and API module. stubsSourceAndApiTag scopeDependencyTag + // The tag to use to depend on the module that provides the latest version of the API .txt file. + latestApiModuleTag scopeDependencyTag + + // The tag to use to depend on the module that provides the latest version of the API removed.txt + // file. + latestRemovedApiModuleTag scopeDependencyTag + // The scope specific prefix to add to the api file base of "current.txt" or "removed.txt". apiFilePrefix string @@ -158,6 +165,16 @@ func initApiScope(scope *apiScope) *apiScope { apiScope: scope, depInfoExtractor: (*scopePaths).extractStubsSourceAndApiInfoFromApiStubsProvider, } + scope.latestApiModuleTag = scopeDependencyTag{ + name: name + "-latest-api", + apiScope: scope, + depInfoExtractor: (*scopePaths).extractLatestApiPath, + } + scope.latestRemovedApiModuleTag = scopeDependencyTag{ + name: name + "-latest-removed-api", + apiScope: scope, + depInfoExtractor: (*scopePaths).extractLatestRemovedApiPath, + } // 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 @@ -203,6 +220,24 @@ func (scope *apiScope) String() string { return scope.name } +// snapshotRelativeDir returns the snapshot directory into which the files related to scopes will +// be stored. +func (scope *apiScope) snapshotRelativeDir() string { + return filepath.Join("sdk_library", scope.name) +} + +// snapshotRelativeCurrentApiTxtPath returns the snapshot path to the API .txt file for the named +// library. +func (scope *apiScope) snapshotRelativeCurrentApiTxtPath(name string) string { + return filepath.Join(scope.snapshotRelativeDir(), name+".txt") +} + +// snapshotRelativeRemovedApiTxtPath returns the snapshot path to the removed API .txt file for the +// named library. +func (scope *apiScope) snapshotRelativeRemovedApiTxtPath(name string) string { + return filepath.Join(scope.snapshotRelativeDir(), name+"-removed.txt") +} + type apiScopes []*apiScope func (scopes apiScopes) Strings(accessor func(*apiScope) string) []string { @@ -377,6 +412,9 @@ type sdkLibraryProperties struct { // List of Java libraries that will be in the classpath when building the implementation lib Impl_only_libs []string `android:"arch_variant"` + // List of Java libraries that will included in the implementation lib. + Impl_only_static_libs []string `android:"arch_variant"` + // List of Java libraries that will be in the classpath when building stubs Stub_only_libs []string `android:"arch_variant"` @@ -399,7 +437,7 @@ type sdkLibraryProperties struct { // 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. + // it is as if shared_library: false, was set. Api_only *bool // local files that are used within user customized droiddoc options. @@ -536,6 +574,12 @@ type scopePaths struct { // Extracted annotations. annotationsZip android.OptionalPath + + // The path to the latest API file. + latestApiPath android.OptionalPath + + // The path to the latest removed API file. + latestRemovedApiPath android.OptionalPath } func (paths *scopePaths) extractStubsLibraryInfoFromDependency(ctx android.ModuleContext, dep android.Module) error { @@ -599,6 +643,31 @@ func (paths *scopePaths) extractStubsSourceAndApiInfoFromApiStubsProvider(ctx an }) } +func extractSingleOptionalOutputPath(dep android.Module) (android.OptionalPath, error) { + var paths android.Paths + if sourceFileProducer, ok := dep.(android.SourceFileProducer); ok { + paths = sourceFileProducer.Srcs() + } else { + return android.OptionalPath{}, fmt.Errorf("module %q does not produce source files", dep) + } + if len(paths) != 1 { + return android.OptionalPath{}, fmt.Errorf("expected one path from %q, got %q", dep, paths) + } + return android.OptionalPathForPath(paths[0]), nil +} + +func (paths *scopePaths) extractLatestApiPath(ctx android.ModuleContext, dep android.Module) error { + outputPath, err := extractSingleOptionalOutputPath(dep) + paths.latestApiPath = outputPath + return err +} + +func (paths *scopePaths) extractLatestRemovedApiPath(ctx android.ModuleContext, dep android.Module) error { + outputPath, err := extractSingleOptionalOutputPath(dep) + paths.latestRemovedApiPath = outputPath + return err +} + type commonToSdkLibraryAndImportProperties struct { // The naming scheme to use for the components that this module creates. // @@ -1171,6 +1240,16 @@ func (module *SdkLibrary) ComponentDepsMutator(ctx android.BottomUpMutatorContex // 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)) + + if module.compareAgainstLatestApi(apiScope) { + // Add dependencies on the latest finalized version of the API .txt file. + latestApiModuleName := module.latestApiModuleName(apiScope) + ctx.AddDependency(module, apiScope.latestApiModuleTag, latestApiModuleName) + + // Add dependencies on the latest finalized version of the remove API .txt file. + latestRemovedApiModuleName := module.latestRemovedApiModuleName(apiScope) + ctx.AddDependency(module, apiScope.latestRemovedApiModuleTag, latestRemovedApiModuleName) + } } if module.requiresRuntimeImplementationLibrary() { @@ -1191,13 +1270,13 @@ func (module *SdkLibrary) DepsMutator(ctx android.BottomUpMutatorContext) { if apiScope.unstable { continue } - if m := android.SrcIsModule(module.latestApiFilegroupName(apiScope)); !ctx.OtherModuleExists(m) { + if m := module.latestApiModuleName(apiScope); !ctx.OtherModuleExists(m) { missingApiModules = append(missingApiModules, m) } - if m := android.SrcIsModule(module.latestRemovedApiFilegroupName(apiScope)); !ctx.OtherModuleExists(m) { + if m := module.latestRemovedApiModuleName(apiScope); !ctx.OtherModuleExists(m) { missingApiModules = append(missingApiModules, m) } - if m := android.SrcIsModule(module.latestIncompatibilitiesFilegroupName(apiScope)); !ctx.OtherModuleExists(m) { + if m := module.latestIncompatibilitiesModuleName(apiScope); !ctx.OtherModuleExists(m) { missingApiModules = append(missingApiModules, m) } } @@ -1271,6 +1350,26 @@ func (module *SdkLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) // Make the set of components exported by this module available for use elsewhere. exportedComponentInfo := android.ExportedComponentsInfo{Components: android.SortedStringKeys(exportedComponents)} ctx.SetProvider(android.ExportedComponentsInfoProvider, exportedComponentInfo) + + // Provide additional information for inclusion in an sdk's generated .info file. + additionalSdkInfo := map[string]interface{}{} + additionalSdkInfo["dist_stem"] = module.distStem() + baseModuleName := module.BaseModuleName() + scopes := map[string]interface{}{} + additionalSdkInfo["scopes"] = scopes + for scope, scopePaths := range module.scopePaths { + scopeInfo := map[string]interface{}{} + scopes[scope.name] = scopeInfo + scopeInfo["current_api"] = scope.snapshotRelativeCurrentApiTxtPath(baseModuleName) + scopeInfo["removed_api"] = scope.snapshotRelativeRemovedApiTxtPath(baseModuleName) + if p := scopePaths.latestApiPath; p.Valid() { + scopeInfo["latest_api"] = p.Path().String() + } + if p := scopePaths.latestRemovedApiPath; p.Valid() { + scopeInfo["latest_removed_api"] = p.Path().String() + } + } + ctx.SetProvider(android.AdditionalSdkInfoProvider, android.AdditionalSdkInfo{additionalSdkInfo}) } func (module *SdkLibrary) AndroidMkEntries() []android.AndroidMkEntries { @@ -1316,16 +1415,32 @@ func (module *SdkLibrary) distGroup() string { return proptools.StringDefault(module.sdkLibraryProperties.Dist_group, "unknown") } +func latestPrebuiltApiModuleName(name string, apiScope *apiScope) string { + return PrebuiltApiModuleName(name, apiScope.name, "latest") +} + func (module *SdkLibrary) latestApiFilegroupName(apiScope *apiScope) string { - return ":" + module.distStem() + ".api." + apiScope.name + ".latest" + return ":" + module.latestApiModuleName(apiScope) +} + +func (module *SdkLibrary) latestApiModuleName(apiScope *apiScope) string { + return latestPrebuiltApiModuleName(module.distStem(), apiScope) } func (module *SdkLibrary) latestRemovedApiFilegroupName(apiScope *apiScope) string { - return ":" + module.distStem() + "-removed.api." + apiScope.name + ".latest" + return ":" + module.latestRemovedApiModuleName(apiScope) +} + +func (module *SdkLibrary) latestRemovedApiModuleName(apiScope *apiScope) string { + return latestPrebuiltApiModuleName(module.distStem()+"-removed", apiScope) } func (module *SdkLibrary) latestIncompatibilitiesFilegroupName(apiScope *apiScope) string { - return ":" + module.distStem() + "-incompatibilities.api." + apiScope.name + ".latest" + return ":" + module.latestIncompatibilitiesModuleName(apiScope) +} + +func (module *SdkLibrary) latestIncompatibilitiesModuleName(apiScope *apiScope) string { + return latestPrebuiltApiModuleName(module.distStem()+"-incompatibilities", apiScope) } func childModuleVisibility(childVisibility []string) []string { @@ -1346,10 +1461,12 @@ func (module *SdkLibrary) createImplLibrary(mctx android.DefaultableHookContext) visibility := childModuleVisibility(module.sdkLibraryProperties.Impl_library_visibility) props := struct { - Name *string - Visibility []string - Instrument bool - Libs []string + Name *string + Visibility []string + Instrument bool + Libs []string + Static_libs []string + Apex_available []string }{ Name: proptools.StringPtr(module.implLibraryModuleName()), Visibility: visibility, @@ -1358,6 +1475,12 @@ func (module *SdkLibrary) createImplLibrary(mctx android.DefaultableHookContext) // Set the impl_only libs. Note that the module's "Libs" get appended as well, via the // addition of &module.properties below. Libs: module.sdkLibraryProperties.Impl_only_libs, + // Set the impl_only static libs. Note that the module's "static_libs" get appended as well, via the + // addition of &module.properties below. + Static_libs: module.sdkLibraryProperties.Impl_only_static_libs, + // Pass the apex_available settings down so that the impl library can be statically + // embedded within a library that is added to an APEX. Needed for updatable-media. + Apex_available: module.ApexAvailable(), } properties := []interface{}{ @@ -1546,7 +1669,7 @@ func (module *SdkLibrary) createStubsSourcesAndApi(mctx android.DefaultableHookC props.Check_api.Current.Api_file = proptools.StringPtr(currentApiFileName) props.Check_api.Current.Removed_api_file = proptools.StringPtr(removedApiFileName) - if !(apiScope.unstable || module.sdkLibraryProperties.Unsafe_ignore_missing_latest_api) { + if module.compareAgainstLatestApi(apiScope) { // check against the latest released API latestApiFilegroupName := proptools.StringPtr(module.latestApiFilegroupName(apiScope)) props.Previous_api = latestApiFilegroupName @@ -1598,6 +1721,10 @@ func (module *SdkLibrary) createStubsSourcesAndApi(mctx android.DefaultableHookC mctx.CreateModule(DroidstubsFactory, &props) } +func (module *SdkLibrary) compareAgainstLatestApi(apiScope *apiScope) bool { + return !(apiScope.unstable || module.sdkLibraryProperties.Unsafe_ignore_missing_latest_api) +} + // Implements android.ApexModule func (module *SdkLibrary) DepIsInSameApex(mctx android.BaseModuleContext, dep android.Module) bool { depTag := mctx.OtherModuleDependencyTag(dep) @@ -1814,8 +1941,9 @@ func (module *SdkLibrary) CreateInternalModules(mctx android.DefaultableHookCont *javaSdkLibraries = append(*javaSdkLibraries, module.BaseModuleName()) } - // Add the impl_only_libs *after* we're done using the Libs prop in submodules. + // Add the impl_only_libs and impl_only_static_libs *after* we're done using them in submodules. module.properties.Libs = append(module.properties.Libs, module.sdkLibraryProperties.Impl_only_libs...) + module.properties.Static_libs = append(module.properties.Static_libs, module.sdkLibraryProperties.Impl_only_static_libs...) } func (module *SdkLibrary) InitSdkLibraryProperties() { @@ -2211,8 +2339,23 @@ func (module *SdkLibraryImport) UniqueApexVariations() bool { return module.uniqueApexVariations() } +// MinSdkVersion - Implements hiddenAPIModule +func (module *SdkLibraryImport) MinSdkVersion(ctx android.EarlyModuleContext) android.SdkSpec { + return android.SdkSpecNone +} + +var _ hiddenAPIModule = (*SdkLibraryImport)(nil) + func (module *SdkLibraryImport) OutputFiles(tag string) (android.Paths, error) { - return module.commonOutputFiles(tag) + paths, err := module.commonOutputFiles(tag) + if paths != nil || err != nil { + return paths, err + } + if module.implLibraryModule != nil { + return module.implLibraryModule.OutputFiles(tag) + } else { + return nil, nil + } } func (module *SdkLibraryImport) GenerateAndroidBuildActions(ctx android.ModuleContext) { @@ -2876,7 +3019,7 @@ func (s *sdkLibrarySdkMemberProperties) AddToPropertySet(ctx android.SdkMemberCo if properties, ok := s.Scopes[apiScope]; ok { scopeSet := propertySet.AddPropertySet(apiScope.propertyName) - scopeDir := filepath.Join("sdk_library", s.OsPrefix(), apiScope.name) + scopeDir := apiScope.snapshotRelativeDir() var jars []string for _, p := range properties.Jars { @@ -2900,13 +3043,13 @@ func (s *sdkLibrarySdkMemberProperties) AddToPropertySet(ctx android.SdkMemberCo } if properties.CurrentApiFile != nil { - currentApiSnapshotPath := filepath.Join(scopeDir, ctx.Name()+".txt") + currentApiSnapshotPath := apiScope.snapshotRelativeCurrentApiTxtPath(ctx.Name()) ctx.SnapshotBuilder().CopyToSnapshot(properties.CurrentApiFile, currentApiSnapshotPath) scopeSet.AddProperty("current_api", currentApiSnapshotPath) } if properties.RemovedApiFile != nil { - removedApiSnapshotPath := filepath.Join(scopeDir, ctx.Name()+"-removed.txt") + removedApiSnapshotPath := apiScope.snapshotRelativeRemovedApiTxtPath(ctx.Name()) ctx.SnapshotBuilder().CopyToSnapshot(properties.RemovedApiFile, removedApiSnapshotPath) scopeSet.AddProperty("removed_api", removedApiSnapshotPath) } diff --git a/java/sdk_library_test.go b/java/sdk_library_test.go index 3500c84d2..805bc226f 100644 --- a/java/sdk_library_test.go +++ b/java/sdk_library_test.go @@ -126,6 +126,10 @@ func TestJavaSdkLibrary(t *testing.T) { exportedComponentsInfo := result.ModuleProvider(foo.Module(), android.ExportedComponentsInfoProvider).(android.ExportedComponentsInfo) expectedFooExportedComponents := []string{ + "foo-removed.api.public.latest", + "foo-removed.api.system.latest", + "foo.api.public.latest", + "foo.api.system.latest", "foo.stubs", "foo.stubs.source", "foo.stubs.source.system", @@ -529,6 +533,8 @@ func TestJavaSdkLibrary_Deps(t *testing.T) { CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{ `dex2oatd`, + `sdklib-removed.api.public.latest`, + `sdklib.api.public.latest`, `sdklib.impl`, `sdklib.stubs`, `sdklib.stubs.source`, @@ -851,6 +857,8 @@ func TestJavaSdkLibraryImport_WithSource(t *testing.T) { CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{ `dex2oatd`, `prebuilt_sdklib`, + `sdklib-removed.api.public.latest`, + `sdklib.api.public.latest`, `sdklib.impl`, `sdklib.stubs`, `sdklib.stubs.source`, @@ -894,6 +902,8 @@ func TestJavaSdkLibraryImport_Preferred(t *testing.T) { CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{ `prebuilt_sdklib`, + `sdklib-removed.api.public.latest`, + `sdklib.api.public.latest`, `sdklib.impl`, `sdklib.stubs`, `sdklib.stubs.source`, |