diff --git a/dexpreopt/config.go b/dexpreopt/config.go index 7d8fbbc90..205284730 100644 --- a/dexpreopt/config.go +++ b/dexpreopt/config.go @@ -100,20 +100,30 @@ type GlobalSoongConfig struct { ConstructContext android.Path } -// These libs are added as optional dependencies ( with android:required set to false). -// This is because they haven't existed prior to certain SDK version, but classes in them were in -// bootclasspath jars, etc. So making them hard dependencies (android:required=true) would prevent -// apps from being installed to such legacy devices. -var OptionalCompatUsesLibs = []string{ - "org.apache.http.legacy", - "android.test.base", - "android.test.mock", -} +// These libs are added as dependencies for apps if the targetSdkVersion in the +// app manifest is less than the specified version. This is needed because these libraries haven't +// existed prior to certain SDK version, but classes in them were in bootclasspath jars, etc. +// Some of the compatibility libraries are optional (their tag has "required=false"), +// so that if this library is missing this in not a build or run-time error. +var OrgApacheHttpLegacy = "org.apache.http.legacy" +var AndroidTestBase = "android.test.base" +var AndroidTestMock = "android.test.mock" +var AndroidHidlBase = "android.hidl.base-V1.0-java" +var AndroidHidlManager = "android.hidl.manager-V1.0-java" -var CompatUsesLibs = []string{ - "android.hidl.base-V1.0-java", - "android.hidl.manager-V1.0-java", +var OptionalCompatUsesLibs28 = []string{ + OrgApacheHttpLegacy, } +var OptionalCompatUsesLibs30 = []string{ + AndroidTestBase, + AndroidTestMock, +} +var CompatUsesLibs29 = []string{ + AndroidHidlBase, + AndroidHidlManager, +} +var OptionalCompatUsesLibs = append(android.CopyOf(OptionalCompatUsesLibs28), OptionalCompatUsesLibs30...) +var CompatUsesLibs = android.CopyOf(CompatUsesLibs29) const UnknownInstallLibraryPath = "error" diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go index 814b75dc7..903677f06 100644 --- a/dexpreopt/dexpreopt.go +++ b/dexpreopt/dexpreopt.go @@ -195,6 +195,9 @@ func bootProfileCommand(ctx android.PathContext, globalSoong *GlobalSoongConfig, } type classLoaderContext struct { + // Library names + Names []string + // The class loader context using paths in the build. Host android.Paths @@ -209,7 +212,7 @@ type classLoaderContext struct { // targetSdkVersion in the manifest or APK is less than that API version. type classLoaderContextMap map[int]*classLoaderContext -const anySdkVersion int = 9999 // should go last in class loader context +const AnySdkVersion int = 9999 // should go last in class loader context func (m classLoaderContextMap) getValue(sdkVer int) *classLoaderContext { if _, ok := m[sdkVer]; !ok { @@ -218,14 +221,19 @@ func (m classLoaderContextMap) getValue(sdkVer int) *classLoaderContext { return m[sdkVer] } +func (clc *classLoaderContext) addLib(lib string, hostPath android.Path, targetPath string) { + clc.Names = append(clc.Names, lib) + clc.Host = append(clc.Host, hostPath) + clc.Target = append(clc.Target, targetPath) +} + func (m classLoaderContextMap) addLibs(ctx android.PathContext, sdkVer int, module *ModuleConfig, libs ...string) bool { clc := m.getValue(sdkVer) for _, lib := range libs { if p, ok := module.LibraryPaths[lib]; ok && p.Host != nil && p.Device != UnknownInstallLibraryPath { - clc.Host = append(clc.Host, p.Host) - clc.Target = append(clc.Target, p.Device) + clc.addLib(lib, p.Host, p.Device) } else { - if sdkVer == anySdkVersion { + if sdkVer == AnySdkVersion { // Fail the build if dexpreopt doesn't know paths to one of the // dependencies. In the future we may need to relax this and just disable dexpreopt. android.ReportPathErrorf(ctx, "dexpreopt cannot find path for '%s'", lib) @@ -239,11 +247,17 @@ func (m classLoaderContextMap) addLibs(ctx android.PathContext, sdkVer int, modu return true } +func (m classLoaderContextMap) usesLibs() []string { + if clc, ok := m[AnySdkVersion]; ok { + return clc.Names + } + return nil +} + func (m classLoaderContextMap) addSystemServerLibs(sdkVer int, ctx android.PathContext, module *ModuleConfig, libs ...string) { clc := m.getValue(sdkVer) for _, lib := range libs { - clc.Host = append(clc.Host, SystemServerDexJarHostPath(ctx, lib)) - clc.Target = append(clc.Target, filepath.Join("/system/framework", lib+".jar")) + clc.addLib(lib, SystemServerDexJarHostPath(ctx, lib), filepath.Join("/system/framework", lib+".jar")) } } @@ -261,7 +275,7 @@ func (m classLoaderContextMap) addSystemServerLibs(sdkVer int, ctx android.PathC // as the runtime classpath won't match and the dexpreopted code will be discarded. Therefore in // such cases the function returns nil, which disables dexpreopt. // -// 2. All other library jars or APKs for which the exact list is unknown. They use +// 3. All other library jars or APKs for which the exact list is unknown. They use // the unsafe &-classpath workaround that means empty class loader context and absence of runtime // check that the class loader context provided by the PackageManager agrees with the stored // class loader context recorded in the .odex file. @@ -273,21 +287,19 @@ func genClassLoaderContext(ctx android.PathContext, global *GlobalConfig, module if jarIndex := android.IndexList(module.Name, systemServerJars); jarIndex >= 0 { // System server jars should be dexpreopted together: class loader context of each jar // should include all preceding jars on the system server classpath. - classLoaderContexts.addSystemServerLibs(anySdkVersion, ctx, module, systemServerJars[:jarIndex]...) + classLoaderContexts.addSystemServerLibs(AnySdkVersion, ctx, module, systemServerJars[:jarIndex]...) } else if module.EnforceUsesLibraries { // Unconditional class loader context. usesLibs := append(copyOf(module.UsesLibraries), module.OptionalUsesLibraries...) - if !classLoaderContexts.addLibs(ctx, anySdkVersion, module, usesLibs...) { + if !classLoaderContexts.addLibs(ctx, AnySdkVersion, module, usesLibs...) { return nil } // Conditional class loader context for API version < 28. const httpLegacy = "org.apache.http.legacy" - if !contains(usesLibs, httpLegacy) { - if !classLoaderContexts.addLibs(ctx, 28, module, httpLegacy) { - return nil - } + if !classLoaderContexts.addLibs(ctx, 28, module, httpLegacy) { + return nil } // Conditional class loader context for API version < 29. @@ -301,10 +313,8 @@ func genClassLoaderContext(ctx android.PathContext, global *GlobalConfig, module // Conditional class loader context for API version < 30. const testBase = "android.test.base" - if !contains(usesLibs, testBase) { - if !classLoaderContexts.addLibs(ctx, 30, module, testBase) { - return nil - } + if !classLoaderContexts.addLibs(ctx, 30, module, testBase) { + return nil } } else { @@ -314,9 +324,32 @@ func genClassLoaderContext(ctx android.PathContext, global *GlobalConfig, module // to the &. } + fixConditionalClassLoaderContext(classLoaderContexts) + return &classLoaderContexts } +// Now that the full unconditional context is known, reconstruct conditional context. +// Apply filters for individual libraries, mirroring what the PackageManager does when it +// constructs class loader context on device. +func fixConditionalClassLoaderContext(clcMap classLoaderContextMap) { + usesLibs := clcMap.usesLibs() + + for sdkVer, clc := range clcMap { + if sdkVer == AnySdkVersion { + continue + } + clcMap[sdkVer] = &classLoaderContext{} + for i, lib := range clc.Names { + if android.InList(lib, usesLibs) { + // skip compatibility libraries that are already included in unconditional context + } else { + clcMap[sdkVer].addLib(lib, clc.Host[i], clc.Target[i]) + } + } + } +} + func dexpreoptCommand(ctx android.PathContext, globalSoong *GlobalSoongConfig, global *GlobalConfig, module *ModuleConfig, rule *android.RuleBuilder, archIdx int, classLoaderContexts classLoaderContextMap, profile android.WritablePath, appImage bool, generateDM bool) { @@ -363,7 +396,7 @@ func dexpreoptCommand(ctx android.PathContext, globalSoong *GlobalSoongConfig, g checkSystemServerOrder(ctx, jarIndex) - clc := classLoaderContexts[anySdkVersion] + clc := classLoaderContexts[AnySdkVersion] rule.Command(). Text("class_loader_context_arg=--class-loader-context=PCL[" + strings.Join(clc.Host.Strings(), ":") + "]"). Implicits(clc.Host). @@ -391,14 +424,15 @@ func dexpreoptCommand(ctx android.PathContext, globalSoong *GlobalSoongConfig, g Text(`eval "$(`).Tool(globalSoong.ConstructContext). Text(` --target-sdk-version ${target_sdk_version}`) for _, ver := range android.SortedIntKeys(classLoaderContexts) { - clc := classLoaderContexts.getValue(ver) - verString := fmt.Sprintf("%d", ver) - if ver == anySdkVersion { - verString = "any" // a special keyword that means any SDK version + if clc := classLoaderContexts.getValue(ver); len(clc.Host) > 0 { + verString := fmt.Sprintf("%d", ver) + if ver == AnySdkVersion { + verString = "any" // a special keyword that means any SDK version + } + cmd.Textf(`--host-classpath-for-sdk %s %s`, verString, strings.Join(clc.Host.Strings(), ":")). + Implicits(clc.Host). + Textf(`--target-classpath-for-sdk %s %s`, verString, strings.Join(clc.Target, ":")) } - cmd.Textf(`--host-classpath-for-sdk %s %s`, verString, strings.Join(clc.Host.Strings(), ":")). - Implicits(clc.Host). - Textf(`--target-classpath-for-sdk %s %s`, verString, strings.Join(clc.Target, ":")) } cmd.Text(`)"`) } else { diff --git a/java/app_test.go b/java/app_test.go index cec8a62f9..98945da0c 100644 --- a/java/app_test.go +++ b/java/app_test.go @@ -2766,7 +2766,7 @@ func TestUsesLibraries(t *testing.T) { name: "prebuilt", apk: "prebuilts/apk/app.apk", certificate: "platform", - uses_libs: ["foo"], + uses_libs: ["foo", "android.test.runner"], optional_uses_libs: [ "bar", "baz", @@ -2804,7 +2804,7 @@ func TestUsesLibraries(t *testing.T) { cmd = prebuilt.Rule("verify_uses_libraries").RuleParams.Command - if w := `uses_library_names="foo"`; !strings.Contains(cmd, w) { + if w := `uses_library_names="foo android.test.runner"`; !strings.Contains(cmd, w) { t.Errorf("wanted %q in %q", w, cmd) } @@ -2812,20 +2812,48 @@ func TestUsesLibraries(t *testing.T) { t.Errorf("wanted %q in %q", w, cmd) } - // Test that all present libraries are preopted, including implicit SDK dependencies, possibly stubs + // Test that all present libraries are preopted, including implicit SDK dependencies, possibly stubs. cmd = app.Rule("dexpreopt").RuleParams.Command w := `--target-classpath-for-sdk any` + ` /system/framework/foo.jar` + `:/system/framework/quuz.jar` + `:/system/framework/qux.jar` + `:/system/framework/runtime-library.jar` + - `:/system/framework/bar.jar` + `:/system/framework/bar.jar ` if !strings.Contains(cmd, w) { t.Errorf("wanted %q in %q", w, cmd) } + // Test conditional context for target SDK version 28. + if w := `--target-classpath-for-sdk 28` + + ` /system/framework/org.apache.http.legacy.jar `; !strings.Contains(cmd, w) { + t.Errorf("wanted %q in %q", w, cmd) + } + + // Test conditional context for target SDK version 29. + if w := `--target-classpath-for-sdk 29` + + ` /system/framework/android.hidl.base-V1.0-java.jar` + + `:/system/framework/android.hidl.manager-V1.0-java.jar `; !strings.Contains(cmd, w) { + t.Errorf("wanted %q in %q", w, cmd) + } + + // Test conditional context for target SDK version 30. + if w := `--target-classpath-for-sdk 30` + + ` /system/framework/android.test.base.jar `; !strings.Contains(cmd, w) { + t.Errorf("wanted %q in %q", w, cmd) + } + cmd = prebuilt.Rule("dexpreopt").RuleParams.Command - if w := `--target-classpath-for-sdk any /system/framework/foo.jar:/system/framework/bar.jar`; !strings.Contains(cmd, w) { + if w := `--target-classpath-for-sdk any` + + ` /system/framework/foo.jar` + + `:/system/framework/android.test.runner.jar` + + `:/system/framework/bar.jar `; !strings.Contains(cmd, w) { + t.Errorf("wanted %q in %q", w, cmd) + } + + // Test conditional context for target SDK version 30. + if w := `--target-classpath-for-sdk 30` + + ` /system/framework/android.test.base.jar `; !strings.Contains(cmd, w) { t.Errorf("wanted %q in %q", w, cmd) } } diff --git a/java/testing.go b/java/testing.go index 461fd3f9f..ab1312178 100644 --- a/java/testing.go +++ b/java/testing.go @@ -22,6 +22,7 @@ import ( "android/soong/android" "android/soong/cc" + "android/soong/dexpreopt" "android/soong/python" "github.com/google/blueprint" @@ -152,6 +153,24 @@ func GatherRequiredDepsForTest() string { `, extra) } + // For class loader context and tests. + dexpreoptModules := []string{"android.test.runner"} + dexpreoptModules = append(dexpreoptModules, dexpreopt.CompatUsesLibs...) + dexpreoptModules = append(dexpreoptModules, dexpreopt.OptionalCompatUsesLibs...) + + for _, extra := range dexpreoptModules { + bp += fmt.Sprintf(` + java_library { + name: "%s", + srcs: ["a.java"], + sdk_version: "none", + system_modules: "stable-core-platform-api-stubs-system-modules", + compile_dex: true, + installable: true, + } + `, extra) + } + bp += ` java_library { name: "framework", @@ -166,48 +185,7 @@ func GatherRequiredDepsForTest() string { android_app { name: "framework-res", sdk_version: "core_platform", - } - - java_library { - name: "android.hidl.base-V1.0-java", - srcs: ["a.java"], - sdk_version: "none", - system_modules: "stable-core-platform-api-stubs-system-modules", - installable: true, - } - - java_library { - name: "android.hidl.manager-V1.0-java", - srcs: ["a.java"], - sdk_version: "none", - system_modules: "stable-core-platform-api-stubs-system-modules", - installable: true, - } - - java_library { - name: "org.apache.http.legacy", - srcs: ["a.java"], - sdk_version: "none", - system_modules: "stable-core-platform-api-stubs-system-modules", - installable: true, - } - - java_library { - name: "android.test.base", - srcs: ["a.java"], - sdk_version: "none", - system_modules: "stable-core-platform-api-stubs-system-modules", - installable: true, - } - - java_library { - name: "android.test.mock", - srcs: ["a.java"], - sdk_version: "none", - system_modules: "stable-core-platform-api-stubs-system-modules", - installable: true, - } - ` + }` systemModules := []string{ "core-current-stubs-system-modules",