Propagate transitive SDK Java library dependencies to dexpreopt.

For some dependencies, like stubs, the SDK library may not be found at
build time (either because the implementation library is not among the
dependencies of the dexpreopted module, or because it's part of a
prebuilt, or because it's missing from the build altogether). In such
cases dexpreopt is useless, because dex2oat does not have access to the
full classpath (unless the &-classpath is used). Therefore do not
dexpreopt in such cases.

Test: lunch aosp_cf_x86_phone-userdebug && m
Bug: 132357300
Change-Id: If289088cfd103011ccb16165e95a97b30fd31b81
This commit is contained in:
Ulya Trafimovich
2020-08-19 16:32:54 +01:00
parent 03333d0e2f
commit fc24ad3d4e
6 changed files with 177 additions and 60 deletions

View File

@@ -111,12 +111,9 @@ type LibraryPath struct {
// LibraryPaths is a map from library name to on-host and on-device paths to its DEX jar.
type LibraryPaths map[string]*LibraryPath
// Add a new path to the map of library paths, unless a path for this library already exists.
func (libPaths LibraryPaths) AddLibraryPath(ctx android.PathContext, lib *string, hostPath, installPath android.Path) {
if lib == nil {
return
}
if _, present := libPaths[*lib]; !present {
// Add a new library path to the map, unless a path for this library already exists.
func (libPaths LibraryPaths) addLibraryPath(ctx android.PathContext, lib string, hostPath, installPath android.Path) {
if _, present := libPaths[lib]; !present {
var devicePath string
if installPath != nil {
devicePath = android.InstallPathToOnDevicePath(ctx, installPath.(android.InstallPath))
@@ -127,9 +124,30 @@ func (libPaths LibraryPaths) AddLibraryPath(ctx android.PathContext, lib *string
// but we cannot use if for dexpreopt.
devicePath = UnknownInstallLibraryPath
}
libPaths[*lib] = &LibraryPath{hostPath, devicePath}
libPaths[lib] = &LibraryPath{hostPath, devicePath}
}
}
// Add a new library path to the map. Ensure that the build path to the library exists.
func (libPaths LibraryPaths) AddLibraryPath(ctx android.PathContext, lib string, hostPath, installPath android.Path) {
if hostPath != nil {
// Add a library only if the build path to it is known.
libPaths.addLibraryPath(ctx, lib, hostPath, installPath)
} else if !ctx.Config().AllowMissingDependencies() {
// Error on libraries with unknown build paths, unless missing dependencies are allowed.
android.ReportPathErrorf(ctx, "unknown build path to <uses-library> '%s'", lib)
} else {
// Not adding a library to the map will likely result in disabling dexpreopt.
}
}
// Add a new library path to the map, if the library exists (name is not nil).
func (libPaths LibraryPaths) MaybeAddLibraryPath(ctx android.PathContext, lib *string, hostPath, installPath android.Path) {
if lib != nil {
// Don't check the build paths, add in any case. Some libraries may be missing from the
// build, but their names still need to be added to <uses-library> tags in the manifest.
libPaths.addLibraryPath(ctx, *lib, hostPath, installPath)
}
return
}
// Add library paths from the second map to the first map (do not override existing entries).

View File

@@ -81,15 +81,14 @@ func GenerateDexpreoptRule(ctx android.PathContext, globalSoong *GlobalSoongConf
}
if !dexpreoptDisabled(ctx, global, module) {
// Don't preopt individual boot jars, they will be preopted together.
if !global.BootJars.ContainsJar(module.Name) {
if clc := genClassLoaderContext(ctx, global, module); clc != nil {
appImage := (generateProfile || module.ForceCreateAppImage || global.DefaultAppImages) &&
!module.NoCreateAppImage
generateDM := shouldGenerateDM(module, global)
for archIdx, _ := range module.Archs {
dexpreoptCommand(ctx, globalSoong, global, module, rule, archIdx, profile, appImage, generateDM)
dexpreoptCommand(ctx, globalSoong, global, module, rule, archIdx, *clc, profile, appImage, generateDM)
}
}
}
@@ -102,6 +101,11 @@ func dexpreoptDisabled(ctx android.PathContext, global *GlobalConfig, module *Mo
return true
}
// Don't preopt individual boot jars, they will be preopted together.
if global.BootJars.ContainsJar(module.Name) {
return true
}
// Don't preopt system server jars that are updatable.
if global.UpdatableSystemServerJars.ContainsJar(module.Name) {
return true
@@ -214,13 +218,17 @@ func (m classLoaderContextMap) getValue(sdkVer int) *classLoaderContext {
return m[sdkVer]
}
func (m classLoaderContextMap) addLibs(sdkVer int, module *ModuleConfig, libs ...string) {
func (m classLoaderContextMap) addLibs(sdkVer int, module *ModuleConfig, libs ...string) bool {
clc := m.getValue(sdkVer)
for _, lib := range libs {
p := pathForLibrary(module, lib)
clc.Host = append(clc.Host, p.Host)
clc.Target = append(clc.Target, p.Device)
if p := pathForLibrary(module, lib); p != nil {
clc.Host = append(clc.Host, p.Host)
clc.Target = append(clc.Target, p.Device)
} else {
return false
}
}
return true
}
func (m classLoaderContextMap) addSystemServerLibs(sdkVer int, ctx android.PathContext, module *ModuleConfig, libs ...string) {
@@ -231,9 +239,79 @@ func (m classLoaderContextMap) addSystemServerLibs(sdkVer int, ctx android.PathC
}
}
// genClassLoaderContext generates host and target class loader context to be passed to the dex2oat
// command for the dexpreopted module. There are three possible cases:
//
// 1. System server jars. They have a special class loader context that includes other system
// server jars.
//
// 2. Library jars or APKs which have precise list of their <uses-library> libs. Their class loader
// context includes build and on-device paths to these libs. In some cases it may happen that
// the path to a <uses-library> is unknown (e.g. the dexpreopted module may depend on stubs
// library, whose implementation library is missing from the build altogether). In such case
// dexpreopting with the <uses-library> is impossible, and dexpreopting without it is pointless,
// 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 <uses-library> 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.
//
func genClassLoaderContext(ctx android.PathContext, global *GlobalConfig, module *ModuleConfig) *classLoaderContextMap {
classLoaderContexts := make(classLoaderContextMap)
systemServerJars := NonUpdatableSystemServerJars(ctx, global)
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]...)
} else if module.EnforceUsesLibraries {
// Unconditional class loader context.
usesLibs := append(copyOf(module.UsesLibraries), module.OptionalUsesLibraries...)
if !classLoaderContexts.addLibs(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(28, module, httpLegacy) {
return nil
}
}
// Conditional class loader context for API version < 29.
usesLibs29 := []string{
"android.hidl.base-V1.0-java",
"android.hidl.manager-V1.0-java",
}
if !classLoaderContexts.addLibs(29, module, usesLibs29...) {
return nil
}
// Conditional class loader context for API version < 30.
const testBase = "android.test.base"
if !contains(usesLibs, testBase) {
if !classLoaderContexts.addLibs(30, module, testBase) {
return nil
}
}
} else {
// Pass special class loader context to skip the classpath and collision check.
// This will get removed once LOCAL_USES_LIBRARIES is enforced.
// Right now LOCAL_USES_LIBRARIES is opt in, for the case where it's not specified we still default
// to the &.
}
return &classLoaderContexts
}
func dexpreoptCommand(ctx android.PathContext, globalSoong *GlobalSoongConfig, global *GlobalConfig,
module *ModuleConfig, rule *android.RuleBuilder, archIdx int, profile android.WritablePath,
appImage bool, generateDM bool) {
module *ModuleConfig, rule *android.RuleBuilder, archIdx int, classLoaderContexts classLoaderContextMap,
profile android.WritablePath, appImage bool, generateDM bool) {
arch := module.Archs[archIdx]
@@ -264,17 +342,12 @@ func dexpreoptCommand(ctx android.PathContext, globalSoong *GlobalSoongConfig, g
invocationPath := odexPath.ReplaceExtension(ctx, "invocation")
classLoaderContexts := make(classLoaderContextMap)
systemServerJars := NonUpdatableSystemServerJars(ctx, global)
rule.Command().FlagWithArg("mkdir -p ", filepath.Dir(odexPath.String()))
rule.Command().FlagWithOutput("rm -f ", odexPath)
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]...)
// Copy the system server jar to a predefined location where dex2oat will find it.
dexPathHost := SystemServerDexJarHostPath(ctx, module.Name)
rule.Command().Text("mkdir -p").Flag(filepath.Dir(dexPathHost.String()))
@@ -288,29 +361,6 @@ func dexpreoptCommand(ctx android.PathContext, globalSoong *GlobalSoongConfig, g
Implicits(clc.Host).
Text("stored_class_loader_context_arg=--stored-class-loader-context=PCL[" + strings.Join(clc.Target, ":") + "]")
} else if module.EnforceUsesLibraries {
// Unconditional class loader context.
usesLibs := append(copyOf(module.UsesLibraries), module.OptionalUsesLibraries...)
classLoaderContexts.addLibs(anySdkVersion, module, usesLibs...)
// Conditional class loader context for API version < 28.
const httpLegacy = "org.apache.http.legacy"
if !contains(usesLibs, httpLegacy) {
classLoaderContexts.addLibs(28, module, httpLegacy)
}
// Conditional class loader context for API version < 29.
usesLibs29 := []string{
"android.hidl.base-V1.0-java",
"android.hidl.manager-V1.0-java",
}
classLoaderContexts.addLibs(29, module, usesLibs29...)
// Conditional class loader context for API version < 30.
const testBase = "android.test.base"
if !contains(usesLibs, testBase) {
classLoaderContexts.addLibs(30, module, testBase)
}
// Generate command that saves target SDK version in a shell variable.
if module.ManifestPath != nil {
rule.Command().Text(`target_sdk_version="$(`).
@@ -540,11 +590,11 @@ func PathToLocation(path android.Path, arch android.ArchType) string {
}
func pathForLibrary(module *ModuleConfig, lib string) *LibraryPath {
path, ok := module.LibraryPaths[lib]
if !ok {
panic(fmt.Errorf("unknown library path for %q", lib))
if path, ok := module.LibraryPaths[lib]; ok && path.Host != nil && path.Device != "error" {
return path
} else {
return nil
}
return path
}
func makefileMatch(pattern, s string) bool {

View File

@@ -390,7 +390,7 @@ func aaptLibs(ctx android.ModuleContext, sdkContext sdkContext) (transitiveStati
// (including the java_sdk_library) itself then append any implicit sdk library
// names to the list of sdk libraries to be added to the manifest.
if component, ok := module.(SdkLibraryComponentDependency); ok {
sdkLibraries.AddLibraryPath(ctx, component.OptionalImplicitSdkLibrary(),
sdkLibraries.MaybeAddLibraryPath(ctx, component.OptionalImplicitSdkLibrary(),
component.DexJarBuildPath(), component.DexJarInstallPath())
}

View File

@@ -586,7 +586,7 @@ func (a *AndroidApp) installPath(ctx android.ModuleContext) android.InstallPath
return android.PathForModuleInstall(ctx, installDir, a.installApkName+".apk")
}
func (a *AndroidApp) dexBuildActions(ctx android.ModuleContext) android.Path {
func (a *AndroidApp) dexBuildActions(ctx android.ModuleContext, sdkLibs dexpreopt.LibraryPaths) android.Path {
a.dexpreopter.installPath = a.installPath(ctx)
if a.dexProperties.Uncompress_dex == nil {
// If the value was not force-set by the user, use reasonable default based on the module.
@@ -597,6 +597,7 @@ func (a *AndroidApp) dexBuildActions(ctx android.ModuleContext) android.Path {
a.dexpreopter.usesLibs = a.usesLibrary.usesLibraryProperties.Uses_libs
a.dexpreopter.optionalUsesLibs = a.usesLibrary.presentOptionalUsesLibs(ctx)
a.dexpreopter.libraryPaths = a.usesLibrary.usesLibraryPaths(ctx)
a.dexpreopter.libraryPaths.AddLibraryPaths(sdkLibs)
a.dexpreopter.manifestFile = a.mergedManifestFile
a.exportedSdkLibs = make(dexpreopt.LibraryPaths)
@@ -767,6 +768,15 @@ func (a *AndroidApp) generateAndroidBuildActions(ctx android.ModuleContext) {
// Process all building blocks, from AAPT to certificates.
a.aaptBuildActions(ctx)
// The decision to enforce <uses-library> checks is made before adding implicit SDK libraries.
a.usesLibrary.freezeEnforceUsesLibraries()
// Add implicit SDK libraries to <uses-library> list.
for _, usesLib := range android.SortedStringKeys(a.aapt.sdkLibraries) {
a.usesLibrary.addLib(usesLib, inList(usesLib, optionalUsesLibs))
}
// Check that the <uses-library> list is coherent with the manifest.
if a.usesLibrary.enforceUsesLibraries() {
manifestCheckFile := a.usesLibrary.verifyUsesLibrariesManifest(ctx, a.mergedManifestFile)
apkDeps = append(apkDeps, manifestCheckFile)
@@ -779,7 +789,7 @@ func (a *AndroidApp) generateAndroidBuildActions(ctx android.ModuleContext) {
a.linter.resources = a.aapt.resourceFiles
a.linter.buildModuleReportZip = ctx.Config().UnbundledBuildApps()
dexJarFile := a.dexBuildActions(ctx)
dexJarFile := a.dexBuildActions(ctx, a.aapt.sdkLibraries)
jniLibs, certificateDeps := collectAppDeps(ctx, a, a.shouldEmbedJnis(ctx), !Bool(a.appProperties.Jni_uses_platform_apis))
jniJarFile := a.jniBuildActions(jniLibs, ctx)
@@ -1888,6 +1898,16 @@ type usesLibrary struct {
usesLibraryProperties UsesLibraryProperties
}
func (u *usesLibrary) addLib(lib string, optional bool) {
if !android.InList(lib, u.usesLibraryProperties.Uses_libs) && !android.InList(lib, u.usesLibraryProperties.Optional_uses_libs) {
if optional {
u.usesLibraryProperties.Optional_uses_libs = append(u.usesLibraryProperties.Optional_uses_libs, lib)
} else {
u.usesLibraryProperties.Uses_libs = append(u.usesLibraryProperties.Uses_libs, lib)
}
}
}
func (u *usesLibrary) deps(ctx android.BottomUpMutatorContext, hasFrameworkLibs bool) {
if !ctx.Config().UnbundledBuild() {
ctx.AddVariationDependencies(nil, usesLibTag, u.usesLibraryProperties.Uses_libs...)
@@ -1960,6 +1980,12 @@ func (u *usesLibrary) enforceUsesLibraries() bool {
return BoolDefault(u.usesLibraryProperties.Enforce_uses_libs, defaultEnforceUsesLibs)
}
// Freeze the value of `enforce_uses_libs` based on the current values of `uses_libs` and `optional_uses_libs`.
func (u *usesLibrary) freezeEnforceUsesLibraries() {
enforce := u.enforceUsesLibraries()
u.usesLibraryProperties.Enforce_uses_libs = &enforce
}
// verifyUsesLibrariesManifest checks the <uses-library> tags in an AndroidManifest.xml against the ones specified
// in the uses_libs and optional_uses_libs properties. It returns the path to a copy of the manifest.
func (u *usesLibrary) verifyUsesLibrariesManifest(ctx android.ModuleContext, manifest android.Path) android.Path {

View File

@@ -2542,6 +2542,19 @@ func TestUsesLibraries(t *testing.T) {
android_app {
name: "app",
srcs: ["a.java"],
libs: ["qux", "quuz"],
static_libs: ["static-runtime-helper"],
uses_libs: ["foo"],
sdk_version: "current",
optional_uses_libs: [
"bar",
"baz",
],
}
android_app {
name: "app_with_stub_deps",
srcs: ["a.java"],
libs: ["qux", "quuz.stubs"],
static_libs: ["static-runtime-helper"],
uses_libs: ["foo"],
@@ -2572,6 +2585,7 @@ func TestUsesLibraries(t *testing.T) {
run(t, ctx, config)
app := ctx.ModuleForTests("app", "android_common")
appWithStubDeps := ctx.ModuleForTests("app_with_stub_deps", "android_common")
prebuilt := ctx.ModuleForTests("prebuilt", "android_common")
// Test that implicit dependencies on java_sdk_library instances are passed to the manifest.
@@ -2602,15 +2616,24 @@ func TestUsesLibraries(t *testing.T) {
t.Errorf("wanted %q in %q", w, cmd)
}
// Test that only present libraries are preopted
// Test that all present libraries are preopted, including implicit SDK dependencies
cmd = app.Rule("dexpreopt").RuleParams.Command
if w := `--target-classpath-for-sdk any /system/framework/foo.jar:/system/framework/bar.jar`; !strings.Contains(cmd, w) {
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`
if !strings.Contains(cmd, w) {
t.Errorf("wanted %q in %q", w, cmd)
}
cmd = prebuilt.Rule("dexpreopt").RuleParams.Command
// TODO(skvadrik) fix dexpreopt for stub libraries for which the implementation is present
if appWithStubDeps.MaybeRule("dexpreopt").RuleParams.Command != "" {
t.Errorf("dexpreopt should be disabled for apps with dependencies on stub libraries")
}
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) {
t.Errorf("wanted %q in %q", w, cmd)
}

View File

@@ -991,7 +991,7 @@ func (j *Module) collectDeps(ctx android.ModuleContext) deps {
case libTag:
deps.classpath = append(deps.classpath, dep.SdkHeaderJars(ctx, j.sdkVersion())...)
// names of sdk libs that are directly depended are exported
j.exportedSdkLibs.AddLibraryPath(ctx, dep.OptionalImplicitSdkLibrary(), dep.DexJarBuildPath(), dep.DexJarInstallPath())
j.exportedSdkLibs.MaybeAddLibraryPath(ctx, dep.OptionalImplicitSdkLibrary(), dep.DexJarBuildPath(), dep.DexJarInstallPath())
case staticLibTag:
ctx.ModuleErrorf("dependency on java_sdk_library %q can only be in libs", otherName)
}
@@ -1970,7 +1970,7 @@ func (j *Library) GenerateAndroidBuildActions(ctx android.ModuleContext) {
// add the name of that java_sdk_library to the exported sdk libs to make sure
// that, if necessary, a <uses-library> element for that java_sdk_library is
// added to the Android manifest.
j.exportedSdkLibs.AddLibraryPath(ctx, j.OptionalImplicitSdkLibrary(), j.DexJarBuildPath(), j.DexJarInstallPath())
j.exportedSdkLibs.MaybeAddLibraryPath(ctx, j.OptionalImplicitSdkLibrary(), j.DexJarBuildPath(), j.DexJarInstallPath())
j.distFiles = j.GenerateTaggedDistFiles(ctx)
}
@@ -2608,7 +2608,7 @@ func (j *Import) GenerateAndroidBuildActions(ctx android.ModuleContext) {
switch tag {
case libTag:
// names of sdk libs that are directly depended are exported
j.exportedSdkLibs.AddLibraryPath(ctx, &otherName, dep.DexJarBuildPath(), dep.DexJarInstallPath())
j.exportedSdkLibs.AddLibraryPath(ctx, otherName, dep.DexJarBuildPath(), dep.DexJarInstallPath())
}
}
})
@@ -2623,7 +2623,7 @@ func (j *Import) GenerateAndroidBuildActions(ctx android.ModuleContext) {
// add the name of that java_sdk_library to the exported sdk libs to make sure
// that, if necessary, a <uses-library> element for that java_sdk_library is
// added to the Android manifest.
j.exportedSdkLibs.AddLibraryPath(ctx, j.OptionalImplicitSdkLibrary(), outputFile, installFile)
j.exportedSdkLibs.MaybeAddLibraryPath(ctx, j.OptionalImplicitSdkLibrary(), outputFile, installFile)
j.exportAidlIncludeDirs = android.PathsForModuleSrc(ctx, j.properties.Aidl.Export_include_dirs)
}