Make copyBootJarsToPredefinedLocations simpler and less fragile

Previously, copyBootJarsToPredefinedLocations relied on all its
parameters having the same length and the same order. That made it
quite fragile as changes to one of the parameters without corresponding
changes to the other would cause failures. It also combined the
retrieval of the boot dex jars from the modules, handling of missing
boot dex jar files and the generation of the rules to copy the files.

This change separates the retrieval of boot dex jars and handling of
missing files from the copying of those files while at the same time
making the function less fragile by replacing the three ordered
parameters with two maps that shared common keys.

Bug: 179354495
Test: m nothing
Merged-In: Idbcd24a7e8af89f7895a20aeddc58502dcbaad03
Change-Id: Idbcd24a7e8af89f7895a20aeddc58502dcbaad03
(cherry picked from commit 5f148ca7cf2e9a9478922577b7ed70b1caacb55e)
This commit is contained in:
Paul Duffin 2021-06-02 17:24:22 +01:00
parent 8c666a3665
commit 3e2db5c10b
7 changed files with 163 additions and 50 deletions

View File

@ -1661,6 +1661,16 @@ func (l *ConfiguredJarList) BuildPaths(ctx PathContext, dir OutputPath) Writable
return paths
}
// BuildPathsByModule returns a map from module name to build paths based on the given directory
// prefix.
func (l *ConfiguredJarList) BuildPathsByModule(ctx PathContext, dir OutputPath) map[string]WritablePath {
paths := map[string]WritablePath{}
for _, jar := range l.jars {
paths[jar] = dir.Join(ctx, ModuleStem(jar)+".jar")
}
return paths
}
// UnmarshalJSON converts JSON configuration from raw bytes into a
// ConfiguredJarList structure.
func (l *ConfiguredJarList) UnmarshalJSON(b []byte) error {

View File

@ -4546,7 +4546,7 @@ func TestBootDexJarsFromSourcesAndPrebuilts(t *testing.T) {
// prebuilt_apex module always depends on the prebuilt, and so it doesn't
// find the dex boot jar in it. We either need to disable the source libfoo
// or make the prebuilt libfoo preferred.
testDexpreoptWithApexes(t, bp, "failed to find a dex jar path for module 'libfoo'", preparer)
testDexpreoptWithApexes(t, bp, "module libfoo does not provide a dex boot jar", preparer)
})
t.Run("prebuilt library preferred with source", func(t *testing.T) {

View File

@ -648,7 +648,8 @@ func (b *BootclasspathFragmentModule) generateBootImageBuildActions(ctx android.
}
// Copy the dex jars of this fragment's content modules to their predefined locations.
copyBootJarsToPredefinedLocations(ctx, contents, imageConfig.modules, imageConfig.dexPaths)
bootDexJarByModule := extractEncodedDexJarsFromModules(ctx, contents)
copyBootJarsToPredefinedLocations(ctx, bootDexJarByModule, imageConfig.dexPathsByModule)
// Build a profile for the image config and then use that to build the boot image.
profile := bootImageProfileRule(ctx, imageConfig)

View File

@ -15,7 +15,6 @@
package java
import (
"fmt"
"path/filepath"
"sort"
"strings"
@ -254,6 +253,9 @@ type bootImageConfig struct {
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
@ -461,53 +463,27 @@ func shouldBuildBootImages(config android.Config, global *dexpreopt.GlobalConfig
return true
}
// copyBootJarsToPredefinedLocations generates commands that will copy boot jars to
// predefined paths in the global config.
func copyBootJarsToPredefinedLocations(ctx android.ModuleContext, bootModules []android.Module, bootjars android.ConfiguredJarList, jarPathsPredefined android.WritablePaths) {
jarPaths := make(android.Paths, bootjars.Len())
for i, module := range bootModules {
if module != nil {
bootDexJar := module.(interface{ DexJarBuildPath() android.Path }).DexJarBuildPath()
jarPaths[i] = bootDexJar
name := android.RemoveOptionalPrebuiltPrefix(ctx.OtherModuleName(module))
if bootjars.Jar(i) != name {
ctx.ModuleErrorf("expected module %s at position %d but found %s", bootjars.Jar(i), i, name)
}
}
}
// The paths to bootclasspath DEX files need to be known at module GenerateAndroidBuildAction
// time, before the boot images are built (these paths are used in dexpreopt rule generation for
// Java libraries and apps). Generate rules that copy bootclasspath DEX jars to the predefined
// paths.
for i := range jarPaths {
input := jarPaths[i]
output := jarPathsPredefined[i]
module := bootjars.Jar(i)
if input == nil {
if ctx.Config().AllowMissingDependencies() {
apex := bootjars.Apex(i)
// Create an error rule that pretends to create the output file but will actually fail if it
// is run.
ctx.Build(pctx, android.BuildParams{
Rule: android.ErrorRule,
Output: output,
Args: map[string]string{
"error": fmt.Sprintf("missing dependencies: dex jar for %s:%s", module, apex),
},
})
} else {
ctx.ModuleErrorf("failed to find a dex jar path for module '%s'"+
", note that some jars may be filtered out by module constraints", module)
}
// 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 {
ctx.ModuleErrorf("module %s does not provide a dex boot jar", 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: input,
Output: output,
Input: src,
Output: dst,
})
}
}

View File

@ -100,6 +100,7 @@ func genBootImageConfigs(ctx android.PathContext) map[string]*bootImageConfig {
// 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.
@ -153,6 +154,9 @@ type updatableBootConfig struct {
// 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
}
@ -166,10 +170,11 @@ func GetUpdatableBootConfig(ctx android.PathContext) updatableBootConfig {
dir := android.PathForOutput(ctx, ctx.Config().DeviceName(), "updatable_bootjars")
dexPaths := updatableBootJars.BuildPaths(ctx, dir)
dexPathsByModuleName := updatableBootJars.BuildPathsByModule(ctx, dir)
dexLocations := updatableBootJars.DevicePaths(ctx.Config(), android.Android)
return updatableBootConfig{updatableBootJars, dexPaths, dexLocations}
return updatableBootConfig{updatableBootJars, dexPaths, dexPathsByModuleName, dexLocations}
}).(updatableBootConfig)
}

View File

@ -571,6 +571,15 @@ type HiddenAPIFlagOutput struct {
AllFlagsPath android.Path
}
// bootDexJarByModule is a map from base module name (without prebuilt_ prefix) to the boot dex
// path.
type bootDexJarByModule map[string]android.Path
// addPath adds the path for a module to the map.
func (b bootDexJarByModule) addPath(module android.Module, path android.Path) {
b[android.RemoveOptionalPrebuiltPrefix(module.Name())] = path
}
// pathForValidation creates a path of the same type as the supplied type but with a name of
// <path>.valid.
//
@ -759,7 +768,7 @@ func extractBootDexJarsFromHiddenAPIModules(ctx android.ModuleContext, contents
bootDexJar := module.bootDexJar()
if bootDexJar == nil {
if ctx.Config().AlwaysUsePrebuiltSdks() {
// TODO(b/179354495): Remove this work around when it is unnecessary.
// TODO(b/179354495): Remove this workaround when it is unnecessary.
// Prebuilt modules like framework-wifi do not yet provide dex implementation jars. So,
// create a fake one that will cause a build error only if it is used.
fake := android.PathForModuleOut(ctx, "fake/boot-dex/%s.jar", module.Name())
@ -792,3 +801,113 @@ func extractClassJarsFromHiddenAPIModules(ctx android.ModuleContext, contents []
}
return classesJars
}
// deferReportingMissingBootDexJar returns true if a missing boot dex jar should not be reported by
// Soong but should instead only be reported in ninja if the file is actually built.
func deferReportingMissingBootDexJar(ctx android.ModuleContext, module android.Module) bool {
// TODO(b/179354495): Remove this workaround when it is unnecessary.
// Prebuilt modules like framework-wifi do not yet provide dex implementation jars. So,
// create a fake one that will cause a build error only if it is used.
if ctx.Config().AlwaysUsePrebuiltSdks() {
return true
}
// This is called for both platform_bootclasspath and bootclasspath_fragment modules.
//
// A bootclasspath_fragment module should only use the APEX variant of source or prebuilt modules.
// Ideally, a bootclasspath_fragment module should never have a platform variant created for it
// but unfortunately, due to b/187910671 it does.
//
// That causes issues when obtaining a boot dex jar for a prebuilt module as a prebuilt module
// used by a bootclasspath_fragment can only provide a boot dex jar when it is part of APEX, i.e.
// has an APEX variant not a platform variant.
//
// There are some other situations when a prebuilt module used by a bootclasspath_fragment cannot
// provide a boot dex jar:
// 1. If the bootclasspath_fragment is not exported by the prebuilt_apex/apex_set module then it
// does not have an APEX variant and only has a platform variant and neither do its content
// modules.
// 2. Some build configurations, e.g. setting TARGET_BUILD_USE_PREBUILT_SDKS causes all
// java_sdk_library_import modules to be treated as preferred and as many of them are not part
// of an apex they cannot provide a boot dex jar.
//
// The first case causes problems when the affected prebuilt modules are preferred but that is an
// invalid configuration and it is ok for it to fail as the work to enable that is not yet
// complete. The second case is used for building targets that do not use boot dex jars and so
// deferring error reporting to ninja is fine as the affected ninja targets should never be built.
// That is handled above.
//
// A platform_bootclasspath module can use libraries from both platform and APEX variants. Unlike
// the bootclasspath_fragment it supports dex_import modules which provides the dex file. So, it
// can obtain a boot dex jar from a prebuilt that is not part of an APEX. However, it is assumed
// that if the library can be part of an APEX then it is the APEX variant that is used.
//
// This check handles the slightly different requirements of the bootclasspath_fragment and
// platform_bootclasspath modules by only deferring error reporting for the platform variant of
// a prebuilt modules that has other variants which are part of an APEX.
//
// TODO(b/187910671): Remove this once platform variants are no longer created unnecessarily.
if android.IsModulePrebuilt(module) {
if am, ok := module.(android.ApexModule); ok && am.InAnyApex() {
apexInfo := ctx.OtherModuleProvider(module, android.ApexInfoProvider).(android.ApexInfo)
if apexInfo.IsForPlatform() {
return true
}
}
}
// A bootclasspath module that is part of a versioned sdk never provides a boot dex jar as there
// is no equivalently versioned prebuilt APEX file from which it can be obtained. However,
// versioned bootclasspath modules are processed by Soong so in order to avoid them causing build
// failures missing boot dex jars need to be deferred.
if android.IsModuleInVersionedSdk(ctx.Module()) {
return true
}
return false
}
// handleMissingDexBootFile will either log a warning or create an error rule to create the fake
// file depending on the value returned from deferReportingMissingBootDexJar.
func handleMissingDexBootFile(ctx android.ModuleContext, module android.Module, fake android.WritablePath) {
if deferReportingMissingBootDexJar(ctx, module) {
// Create an error rule that pretends to create the output file but will actually fail if it
// is run.
ctx.Build(pctx, android.BuildParams{
Rule: android.ErrorRule,
Output: fake,
Args: map[string]string{
"error": fmt.Sprintf("missing dependencies: boot dex jar for %s", module),
},
})
} else {
ctx.ModuleErrorf("module %s does not provide a dex jar", module)
}
}
// retrieveEncodedBootDexJarFromModule returns a path to the boot dex jar from the supplied module's
// DexJarBuildPath() method.
//
// The returned path will usually be to a dex jar file that has been encoded with hidden API flags.
// However, under certain conditions, e.g. errors, or special build configurations it will return
// a path to a fake file.
func retrieveEncodedBootDexJarFromModule(ctx android.ModuleContext, module android.Module) android.Path {
bootDexJar := module.(interface{ DexJarBuildPath() android.Path }).DexJarBuildPath()
if bootDexJar == nil {
fake := android.PathForModuleOut(ctx, fmt.Sprintf("fake/encoded-dex/%s.jar", module.Name()))
bootDexJar = fake
handleMissingDexBootFile(ctx, module, fake)
}
return bootDexJar
}
// extractEncodedDexJarsFromModules extracts the encoded dex jars from the supplied modules.
func extractEncodedDexJarsFromModules(ctx android.ModuleContext, contents []android.Module) bootDexJarByModule {
encodedDexJarsByModuleName := bootDexJarByModule{}
for _, module := range contents {
path := retrieveEncodedBootDexJarFromModule(ctx, module)
encodedDexJarsByModuleName.addPath(module, path)
}
return encodedDexJarsByModuleName
}

View File

@ -389,11 +389,13 @@ func (b *platformBootclasspathModule) generateBootImageBuildActions(ctx android.
generateUpdatableBcpPackagesRule(ctx, imageConfig, updatableModules)
// Copy non-updatable module dex jars to their predefined locations.
copyBootJarsToPredefinedLocations(ctx, nonUpdatableModules, imageConfig.modules, imageConfig.dexPaths)
nonUpdatableBootDexJarsByModule := extractEncodedDexJarsFromModules(ctx, nonUpdatableModules)
copyBootJarsToPredefinedLocations(ctx, nonUpdatableBootDexJarsByModule, imageConfig.dexPathsByModule)
// Copy updatable module dex jars to their predefined locations.
config := GetUpdatableBootConfig(ctx)
copyBootJarsToPredefinedLocations(ctx, updatableModules, config.modules, config.dexPaths)
updatableBootDexJarsByModule := extractEncodedDexJarsFromModules(ctx, updatableModules)
copyBootJarsToPredefinedLocations(ctx, updatableBootDexJarsByModule, config.dexPathsByModule)
// Build a profile for the image config and then use that to build the boot image.
profile := bootImageProfileRule(ctx, imageConfig)