diff --git a/android/config.go b/android/config.go index da78c7a12..ed90c3181 100644 --- a/android/config.go +++ b/android/config.go @@ -1690,6 +1690,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 { diff --git a/apex/apex_test.go b/apex/apex_test.go index 6a7c35c88..1bfe7e9ed 100644 --- a/apex/apex_test.go +++ b/apex/apex_test.go @@ -4769,7 +4769,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) { diff --git a/java/bootclasspath_fragment.go b/java/bootclasspath_fragment.go index 4aaf3d43f..10d2fe21e 100644 --- a/java/bootclasspath_fragment.go +++ b/java/bootclasspath_fragment.go @@ -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) diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go index c52995e15..dc8df5ec1 100644 --- a/java/dexpreopt_bootjars.go +++ b/java/dexpreopt_bootjars.go @@ -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, }) } } diff --git a/java/dexpreopt_config.go b/java/dexpreopt_config.go index 542881d3e..b13955fba 100644 --- a/java/dexpreopt_config.go +++ b/java/dexpreopt_config.go @@ -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) } diff --git a/java/hiddenapi_modular.go b/java/hiddenapi_modular.go index 54effa967..5600645e3 100644 --- a/java/hiddenapi_modular.go +++ b/java/hiddenapi_modular.go @@ -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 // .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 +} diff --git a/java/platform_bootclasspath.go b/java/platform_bootclasspath.go index 7d1e53f8c..a4beb8970 100644 --- a/java/platform_bootclasspath.go +++ b/java/platform_bootclasspath.go @@ -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)