Merge changes from topic "soong-fixit-skavdrik"

* changes:
  Documenting java/dexpreopt_bootjars.go
  Documenting dexpreopt/class_loader_context.go.
This commit is contained in:
Ulyana Trafimovich 2020-12-01 10:01:43 +00:00 committed by Gerrit Code Review
commit 0d18ef42ec
2 changed files with 424 additions and 57 deletions

View file

@ -22,17 +22,214 @@ import (
"android/soong/android"
)
// These libs are added as <uses-library> 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 <uses-library> tag has "required=false"),
// so that if this library is missing this in not a build or run-time error.
// This comment describes the following:
// 1. the concept of class loader context (CLC) and its relation to classpath
// 2. how PackageManager constructs CLC from shared libraries and their dependencies
// 3. build-time vs. run-time CLC and why this matters for dexpreopt
// 4. manifest fixer: a tool that adds missing <uses-library> tags to the manifests
// 5. build system support for CLC
//
// 1. Class loader context
// -----------------------
//
// Java libraries and apps that have run-time dependency on other libraries should list the used
// libraries in their manifest (AndroidManifest.xml file). Each used library should be specified in
// a <uses-library> tag that has the library name and an optional attribute specifying if the
// library is optional or required. Required libraries are necessary for the library/app to run (it
// will fail at runtime if the library cannot be loaded), and optional libraries are used only if
// they are present (if not, the library/app can run without them).
//
// The libraries listed in <uses-library> tags are in the classpath of a library/app.
//
// Besides libraries, an app may also use another APK (for example in the case of split APKs), or
// anything that gets added by the app dynamically. In general, it is impossible to know at build
// time what the app may use at runtime. In the build system we focus on the known part: libraries.
//
// Class loader context (CLC) is a tree-like structure that describes class loader hierarchy. The
// build system uses CLC in a more narrow sense: it is a tree of libraries that represents
// transitive closure of all <uses-library> dependencies of a library/app. The top-level elements of
// a CLC are the direct <uses-library> dependencies specified in the manifest (aka. classpath). Each
// node of a CLC tree is a <uses-library> which may have its own <uses-library> sub-nodes.
//
// Because <uses-library> dependencies are, in general, a graph and not necessarily a tree, CLC may
// contain subtrees for the same library multiple times. In other words, CLC is the dependency graph
// "unfolded" to a tree. The duplication is only on a logical level, and the actual underlying class
// loaders are not duplicated (at runtime there is a single class loader instance for each library).
//
// Example: A has <uses-library> tags B, C and D; C has <uses-library tags> B and D;
// D has <uses-library> E; B and E have no <uses-library> dependencies. The CLC is:
// A
// ├── B
// ├── C
// │ ├── B
// │ └── D
// │ └── E
// └── D
// └── E
//
// CLC defines the lookup order of libraries when resolving Java classes used by the library/app.
// The lookup order is important because libraries may contain duplicate classes, and the class is
// resolved to the first match.
//
// 2. PackageManager and "shared" libraries
// ----------------------------------------
//
// In order to load an APK at runtime, PackageManager (in frameworks/base) creates a CLC. It adds
// the libraries listed in the <uses-library> tags in the app's manifest as top-level CLC elements.
// For each of the used libraries PackageManager gets all its <uses-library> dependencies (specified
// as tags in the manifest of that library) and adds a nested CLC for each dependency. This process
// continues recursively until all leaf nodes of the constructed CLC tree are libraries that have no
// <uses-library> dependencies.
//
// PackageManager is aware only of "shared" libraries. The definition of "shared" here differs from
// its usual meaning (as in shared vs. static). In Android, Java "shared" libraries are those listed
// in /system/etc/permissions/platform.xml file. This file is installed on device. Each entry in it
// contains the name of a "shared" library, a path to its DEX jar file and a list of dependencies
// (other "shared" libraries that this one uses at runtime and specifies them in <uses-library> tags
// in its manifest).
//
// In other words, there are two sources of information that allow PackageManager to construct CLC
// at runtime: <uses-library> tags in the manifests and "shared" library dependencies in
// /system/etc/permissions/platform.xml.
//
// 3. Build-time and run-time CLC and dexpreopt
// --------------------------------------------
//
// CLC is needed not only when loading a library/app, but also when compiling it. Compilation may
// happen either on device (known as "dexopt") or during the build (known as "dexpreopt"). Since
// dexopt takes place on device, it has the same information as PackageManager (manifests and
// shared library dependencies). Dexpreopt, on the other hand, takes place on host and in a totally
// different environment, and it has to get the same information from the build system (see the
// section about build system support below).
//
// Thus, the build-time CLC used by dexpreopt and the run-time CLC used by PackageManager are
// the same thing, but computed in two different ways.
//
// It is important that build-time and run-time CLCs coincide, otherwise the AOT-compiled code
// created by dexpreopt will be rejected. In order to check the equality of build-time and
// run-time CLCs, the dex2oat compiler records build-time CLC in the *.odex files (in the
// "classpath" field of the OAT file header). To find the stored CLC, use the following command:
// `oatdump --oat-file=<FILE> | grep '^classpath = '`.
//
// Mismatch between build-time and run-time CLC is reported in logcat during boot (search with
// `logcat | grep -E 'ClassLoaderContext [a-z ]+ mismatch'`. Mismatch is bad for performance, as it
// forces the library/app to either be dexopted, or to run without any optimizations (e.g. the app's
// code may need to be extracted in memory from the APK, a very expensive operation).
//
// A <uses-library> can be either optional or required. From dexpreopt standpoint, required library
// must be present at build time (its absence is a build error). An optional library may be either
// present or absent at build time: if present, it will be added to the CLC, passed to dex2oat and
// recorded in the *.odex file; otherwise, if the library is absent, it will be skipped and not
// added to CLC. If there is a mismatch between built-time and run-time status (optional library is
// present in one case, but not the other), then the build-time and run-time CLCs won't match and
// the compiled code will be rejected. It is unknown at build time if the library will be present at
// runtime, therefore either including or excluding it may cause CLC mismatch.
//
// 4. Manifest fixer
// -----------------
//
// Sometimes <uses-library> tags are missing from the source manifest of a library/app. This may
// happen for example if one of the transitive dependencies of the library/app starts using another
// <uses-library>, and the library/app's manifest isn't updated to include it.
//
// Soong can compute some of the missing <uses-library> tags for a given library/app automatically
// as SDK libraries in the transitive dependency closure of the library/app. The closure is needed
// because a library/app may depend on a static library that may in turn depend on an SDK library,
// (possibly transitively via another library).
//
// Not all <uses-library> tags can be computed in this way, because some of the <uses-library>
// dependencies are not SDK libraries, or they are not reachable via transitive dependency closure.
// But when possible, allowing Soong to calculate the manifest entries is less prone to errors and
// simplifies maintenance. For example, consider a situation when many apps use some static library
// that adds a new <uses-library> dependency -- all the apps will have to be updated. That is
// difficult to maintain.
//
// Soong computes the libraries that need to be in the manifest as the top-level libraries in CLC.
// These libraries are passed to the manifest_fixer.
//
// All libraries added to the manifest should be "shared" libraries, so that PackageManager can look
// up their dependencies and reconstruct the nested subcontexts at runtime. There is no build check
// to ensure this, it is an assumption.
//
// 5. Build system support
// -----------------------
//
// In order to construct CLC for dexpreopt and manifest_fixer, the build system needs to know all
// <uses-library> dependencies of the dexpreopted library/app (including transitive dependencies).
// For each <uses-librarry> dependency it needs to know the following information:
//
// - the real name of the <uses-library> (it may be different from the module name)
// - build-time (on host) and run-time (on device) paths to the DEX jar file of the library
// - whether this library is optional or required
// - all <uses-library> dependencies
//
// Since the build system doesn't have access to the manifest contents (it cannot read manifests at
// the time of build rule generation), it is necessary to copy this information to the Android.bp
// and Android.mk files. For blueprints, the relevant properties are `uses_libs` and
// `optional_uses_libs`. For makefiles, relevant variables are `LOCAL_USES_LIBRARIES` and
// `LOCAL_OPTIONAL_USES_LIBRARIES`. It is preferable to avoid specifying these properties explicilty
// when they can be computed automatically by Soong (as the transitive closure of SDK library
// dependencies).
//
// Some of the Java libraries that are used as <uses-library> are not SDK libraries (they are
// defined as `java_library` rather than `java_sdk_library` in the Android.bp files). In order for
// the build system to handle them automatically like SDK libraries, it is possible to set a
// property `provides_uses_lib` or variable `LOCAL_PROVIDES_USES_LIBRARY` on the blueprint/makefile
// module of such library. This property can also be used to specify real library name in cases
// when it differs from the module name.
//
// Because the information from the manifests has to be duplicated in the Android.bp/Android.mk
// files, there is a danger that it may get out of sync. To guard against that, the build system
// generates a rule that checks the metadata in the build files against the contents of a manifest
// (verify_uses_libraries). The manifest can be available as a source file, or as part of a prebuilt
// APK. Note that reading the manifests at the Ninja stage of the build is fine, unlike the build
// rule generation phase.
//
// ClassLoaderContext is a structure that represents CLC.
//
type ClassLoaderContext struct {
// The name of the library.
Name string
// On-host build path to the library dex file (used in dex2oat argument --class-loader-context).
Host android.Path
// On-device install path (used in dex2oat argument --stored-class-loader-context).
Device string
// Nested sub-CLC for dependencies.
Subcontexts []*ClassLoaderContext
}
// ClassLoaderContextMap is a map from SDK version to CLC. There is a special entry with key
// AnySdkVersion that stores unconditional CLC that is added regardless of the target SDK version.
//
// Conditional CLC is for compatibility libraries which didn't exist prior to a certain SDK version
// (say, N), but classes in them were in the bootclasspath jars, etc., and in version N they have
// been separated into a standalone <uses-library>. Compatibility libraries should only be in the
// CLC if the library/app that uses them has `targetSdkVersion` less than N in the manifest.
//
// Currently only apps (but not libraries) use conditional CLC.
//
// Target SDK version information is unavailable to the build system at rule generation time, so
// the build system doesn't know whether conditional CLC is needed for a given app or not. So it
// generates a build rule that includes conditional CLC for all versions, extracts the target SDK
// version from the manifest, and filters the CLCs based on that version. Exact final CLC that is
// passed to dex2oat is unknown to the build system, and gets known only at Ninja stage.
//
type ClassLoaderContextMap map[int][]*ClassLoaderContext
// Compatibility libraries. Some are optional, and some are required: this is the default that
// affects how they are handled by the Soong logic that automatically adds implicit SDK libraries
// to the manifest_fixer, but an explicit `uses_libs`/`optional_uses_libs` can override this.
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"
// Compatibility libraries grouped by version/optionality (for convenience, to avoid repeating the
// same lists in multiple places).
var OptionalCompatUsesLibs28 = []string{
OrgApacheHttpLegacy,
}
@ -55,30 +252,6 @@ const UnknownInstallLibraryPath = "error"
// last). We use the converntional "current" SDK level (10000), but any big number would do as well.
const AnySdkVersion int = android.FutureApiLevelInt
// ClassLoaderContext is a tree of libraries used by the dexpreopted module with their dependencies.
// The context is used by dex2oat to compile the module and recorded in the AOT-compiled files, so
// that it can be checked agains the run-time class loader context on device. If there is a mismatch
// at runtime, AOT-compiled code is rejected.
type ClassLoaderContext struct {
// The name of the library (same as the name of the module that contains it).
Name string
// On-host build path to the library dex file (used in dex2oat argument --class-loader-context).
Host android.Path
// On-device install path (used in dex2oat argument --stored-class-loader-context).
Device string
// Nested class loader subcontexts for dependencies.
Subcontexts []*ClassLoaderContext
}
// ClassLoaderContextMap is a map from SDK version to a class loader context.
// There is a special entry with key AnySdkVersion that stores unconditional class loader context.
// Other entries store conditional contexts that should be added for some apps that have
// targetSdkVersion in the manifest lower than the key SDK version.
type ClassLoaderContextMap map[int][]*ClassLoaderContext
// Add class loader context for the given library to the map entry for the given SDK version.
func (clcMap ClassLoaderContextMap) addContext(ctx android.ModuleInstallPathContext, sdkVer int, lib string,
hostPath, installPath android.Path, strict bool, nestedClcMap ClassLoaderContextMap) error {
@ -257,6 +430,7 @@ func validateClassLoaderContext(clcMap ClassLoaderContextMap) (bool, error) {
return true, nil
}
// Helper function for validateClassLoaderContext() that handles recursion.
func validateClassLoaderContextRec(sdkVer int, clcs []*ClassLoaderContext) (bool, error) {
for _, clc := range clcs {
if clc.Host == nil || clc.Device == UnknownInstallLibraryPath {
@ -296,6 +470,7 @@ func ComputeClassLoaderContext(clcMap ClassLoaderContextMap) (clcStr string, pat
return clcStr, android.FirstUniquePaths(paths)
}
// Helper function for ComputeClassLoaderContext() that handles recursion.
func computeClassLoaderContextRec(clcs []*ClassLoaderContext) (string, string, android.Paths) {
var paths android.Paths
var clcsHost, clcsTarget []string
@ -320,7 +495,7 @@ func computeClassLoaderContextRec(clcs []*ClassLoaderContext) (string, string, a
return clcHost, clcTarget, paths
}
// Paths to a <uses-library> on host and on device.
// JSON representation of <uses-library> paths on host and on device.
type jsonLibraryPath struct {
Host string
Device string

View file

@ -25,11 +25,177 @@ import (
"github.com/google/blueprint/proptools"
)
// 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.
//
// 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 either a simple
// name (if the library is a part of the Platform), or a colon-separated pair <apex, name> (if the
// library is a part of a non-updatable APEX).
//
// A related variable PRODUCT_UPDATABLE_BOOT_JARS contains bootclasspath libraries that are in
// updatable APEXes. They are not included in the boot image.
//
// 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.
//
// 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).
//
func init() {
RegisterDexpreoptBootJarsComponents(android.InitRegistrationContext)
}
// Target-independent description of pre-compiled boot image.
// Target-independent description of a boot image.
type bootImageConfig struct {
// If this image is an extension, the image that it extends.
extends *bootImageConfig
@ -66,7 +232,7 @@ type bootImageConfig struct {
variants []*bootImageVariant
}
// Target-dependent description of pre-compiled boot image.
// Target-dependent description of a boot image.
type bootImageVariant struct {
*bootImageConfig
@ -90,6 +256,7 @@ type bootImageVariant struct {
unstrippedInstalls android.RuleBuilderInstalls
}
// 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 {
@ -99,7 +266,7 @@ func (image bootImageConfig) getVariant(target android.Target) *bootImageVariant
return nil
}
// Return any (the first) variant which is for the device (as opposed to for the host)
// 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 {
@ -109,10 +276,12 @@ func (image bootImageConfig) getAnyAndroidVariant() *bootImageVariant {
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 {
// Dexpreopt on the boot class path produces multiple files. The first dex file
// is converted into 'name'.art (to match the legacy assumption that 'name'.art
// exists), and the rest are converted to 'name'-<jar>.art.
// 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 {
@ -121,6 +290,7 @@ func (image bootImageConfig) moduleName(ctx android.PathContext, idx int) string
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)
@ -129,6 +299,8 @@ func (image bootImageConfig) firstModuleNameOrStem(ctx android.PathContext) stri
}
}
// 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++ {
@ -140,17 +312,26 @@ func (image bootImageConfig) moduleFiles(ctx android.PathContext, dir android.Ou
return ret
}
// 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 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() (imageLocations []string) {
if image.extends != nil {
imageLocations = image.extends.getVariant(image.target).imageLocations()
@ -158,18 +339,6 @@ func (image *bootImageVariant) imageLocations() (imageLocations []string) {
return append(imageLocations, dexpreopt.PathToLocation(image.images, image.target.Arch.ArchType))
}
func concat(lists ...[]string) []string {
var size int
for _, l := range lists {
size += len(l)
}
ret := make([]string, 0, size)
for _, l := range lists {
ret = append(ret, l...)
}
return ret
}
func dexpreoptBootJarsFactory() android.Singleton {
return &dexpreoptBootJars{}
}
@ -182,10 +351,21 @@ func skipDexpreoptBootJars(ctx android.PathContext) bool {
return dexpreopt.GetGlobalConfig(ctx).DisablePreopt
}
// Singleton for generating boot image build rules.
type dexpreoptBootJars struct {
// 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
otherImages []*bootImageConfig
// Other boot image configs (currently the list contains only the primary ART APEX image. It
// used to contain an experimental JIT-Zygote image (now replaced with the ART APEX image). In
// the future other boot image extensions may be added.
otherImages []*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
}
@ -205,7 +385,7 @@ func DexpreoptedArtApexJars(ctx android.BuilderContext) map[android.ArchType]and
return files
}
// dexpreoptBoot singleton rules
// Generate build rules for boot images.
func (d *dexpreoptBootJars) GenerateBuildActions(ctx android.SingletonContext) {
if skipDexpreoptBootJars(ctx) {
return
@ -334,9 +514,10 @@ func buildBootImage(ctx android.SingletonContext, image *bootImageConfig) *bootI
}
}
// The path to bootclasspath dex files needs to be known at module GenerateAndroidBuildAction time, before
// the bootclasspath modules have been compiled. Copy the dex jars there so the module rules that have
// already been set up can find them.
// 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 bootDexJars {
ctx.Build(pctx, android.BuildParams{
Rule: android.Cp,
@ -371,6 +552,7 @@ func buildBootImage(ctx android.SingletonContext, image *bootImageConfig) *bootI
return image
}
// Generate boot image build rules for a specific target.
func buildBootImageVariant(ctx android.SingletonContext, image *bootImageVariant,
profile android.Path, missingDeps []string) android.WritablePaths {
@ -428,12 +610,15 @@ func buildBootImageVariant(ctx android.SingletonContext, image *bootImageVariant
}
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, ":").
FlagWithArg("--boot-image=", dexpreopt.PathToLocation(artImage, arch)).Implicit(artImage)
} else {
// It is a primary image, so it needs a base address.
cmd.FlagWithArg("--base=", ctx.Config().LibartImgDeviceBaseAddress())
}
@ -717,7 +902,9 @@ func writeGlobalConfigForMake(ctx android.SingletonContext, path android.Writabl
android.WriteFileRule(ctx, path, string(data))
}
// Export paths for default boot image to Make
// 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())
@ -731,6 +918,11 @@ func (d *dexpreoptBootJars) MakeVars(ctx android.MakeVarsContext) {
ctx.Strict("DEXPREOPT_BOOTCLASSPATH_DEX_LOCATIONS", strings.Join(image.getAnyAndroidVariant().dexLocationsDeps, " "))
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.
for _, current := range append(d.otherImages, image) {
imageNames = append(imageNames, current.name)
for _, variant := range current.variants {