1## Dexpreopt implementation 2 3### Introduction 4 5All dexpreopted Java code falls into three categories: 6 7- bootclasspath 8- system server 9- apps and libraries 10 11Dexpreopt implementation for bootclasspath libraries (boot images) is located in 12[soong/java] (see e.g. [soong/java/dexpreopt_bootjars.go]), and install rules 13are in [make/core/dex_preopt.mk]. 14 15Dexpreopt implementation for system server, libraries and apps is located in 16[soong/dexpreopt]. For the rest of this section we focus primarily on it (and 17not boot images). 18 19Dexpeopt implementation is split across the Soong part and the Make part. The 20core logic is in Soong, and Make only generates configs and scripts to pass 21information to Soong. 22 23### Global and module dexpreopt.config 24 25The build system generates a global JSON dexpreopt config that is populated from 26product variables. This is static configuration that is passed to both Soong and 27Make. The `$OUT/soong/dexpreopt.config` file is generated in 28[make/core/dex_preopt_config.mk]. Soong reads it in [soong/dexpreopt/config.go] 29and makes a device-specific copy (this is needed to ensure incremental build 30correctness). The global config contains lists of bootclasspath jars, system 31server jars, dex2oat options, global switches that enable and disable parts of 32dexpreopt and so on. 33 34The build system also generates a module config for each dexpreopted package. It 35contains package-specific configuration that is derived from the global 36configuration and Android.bp or Android.mk module for the package. 37 38Module configs for Make packages are generated in 39[make/core/dex_preopt_odex_install.mk]; they are materialized as per-package 40JSON dexpreopt.config files. 41 42Module configs in Soong are not materialized as dexpreopt.config files and exist 43as Go structures in memory, unless it is necessary to materialize them as a file 44for dependent Make packages or for post-dexpreopting. Module configs are defined 45in [soong/dexpreopt/config.go]. 46 47### Dexpreopt in Soong 48 49The Soong implementation of dexpreopt consists roughly of the following steps: 50 51- Read global dexpreopt config passed from Make ([soong/dexpreopt/config.go]). 52 53- Construct a static boot image config ([soong/java/dexpreopt_config.go]). 54 55- During dependency mutator pass, for each suitable module: 56 - add uses-library dependencies (e.g. for apps: [soong/java/app.go:deps]) 57 58- During rule generation pass, for each suitable module: 59 - compute transitive uses-library dependency closure 60 ([soong/java/java.go:addCLCFromDep]) 61 62 - construct CLC from the dependency closure 63 ([soong/dexpreopt/class_loader_context.go]) 64 65 - construct module config with CLC, boot image locations, etc. 66 ([soong/java/dexpreopt.go]) 67 68 - generate build rules to verify build-time CLC against the manifest (e.g. 69 for apps: [soong/java/app.go:verifyUsesLibraries]) 70 71 - generate dexpreopt build rule ([soong/dexpreopt/dexpreopt.go]) 72 73- At the end of rule generation pass: 74 - generate build rules for boot images ([soong/java/dexpreopt_bootjars.go], 75 [soong/java/bootclasspath_fragment.go] and 76 [soong/java/platform_bootclasspath.go]) 77 78### Dexpreopt in Make - dexpreopt_gen 79 80In order to reuse the same dexpreopt implementation for both Soong and Make 81packages, part of Soong is compiled into a standalone binary dexpreopt_gen. It 82runs during the Ninja stage of the build and generates shell scripts with 83dexpreopt build rules for Make packages, and then executes them. 84 85This setup causes many inconveniences. To name a few: 86 87- Errors in the build rules are only revealed at the late stage of the build. 88 89- These rules are not tested by the presubmit builds that run `m nothing` on 90 many build targets/products. 91 92- It is impossible to find dexpreopt build rules in the generated Ninja files. 93 94However all these issues are a lesser evil compared to having a duplicate 95dexpreopt implementation in Make. Also note that it would be problematic to 96reimplement the logic in Make anyway, because Android.mk modules are not 97processed in the order of uses-library dependencies and propagating dependency 98information from one module to another would require a similar workaround with 99a script. 100 101Dexpreopt for Make packages involves a few steps: 102 103- At Soong phase (during `m nothing`), see dexpreopt_gen: 104 - generate build rules for dexpreopt_gen binary 105 106- At Make/Kati phase (during `m nothing`), see 107 [make/core/dex_preopt_odex_install.mk]: 108 - generate build rules for module dexpreopt.config 109 110 - generate build rules for merging dependency dexpreopt.config files (see 111 [make/core/dex_preopt_config_merger.py]) 112 113 - generate build rules for dexpreopt_gen invocation 114 115 - generate build rules for executing dexpreopt.sh scripts 116 117- At Ninja phase (during `m`): 118 - generate dexpreopt.config files 119 120 - execute dexpreopt_gen rules (generate dexpreopt.sh scripts) 121 122 - execute dexpreopt.sh scripts (this runs the actual dexpreopt) 123 124The Make/Kati phase adds all the necessary dependencies that trigger 125dexpreopt_gen and dexpreopt.sh rules. The real dexpreopt command (dex2oat 126invocation that will be executed to AOT-compile a package) is in the 127dexpreopt.sh script, which is generated close to the end of the build. 128 129### Indirect build rules 130 131The process described above for Make packages involves "indirect build rules", 132i.e. build rules that are generated not at the time when the build system is 133created (which is a small step at the very beginning of the build triggered with 134`m nothing`), but at the time when the actual build is done (`m` phase). 135 136Some build systems, such as Make, allow modifications of the build graph during 137the build. Other build systems, such as Soong, have a clear separation into the 138first "generation phase" (this is when build rules are created) and the second 139"build phase" (this is when the build rules are executed), and they do not allow 140modifications of the dependency graph during the second phase. The Soong 141approach is better from performance standpoint, because with the Make approach 142there are no guarantees regarding the time of the build --- recursive build 143graph modfications continue until fixpoint. However the Soong approach is also 144more restictive, as it can only generate build rules from the information that 145is passed to the build system via global configuration, Android.bp files or 146encoded in the Go code. Any other information (such as the contents of the Java 147manifest files) are not accessible and cannot be used to generate build rules. 148 149Hence the need for the "indirect build rules": during the generation phase only 150stubs of the build rules are generated, and the real rules are generated by the 151stub rules during the build phase (and executed immediately). Note that the 152build system still has to add all the necessary dependencies during the 153generation phase, because it will not be possible to change build order during 154the build phase. 155 156Indirect buils rules are used in a couple of places in dexpreopt: 157 158- [soong/scripts/manifest_check.py]: first to extract targetSdkVersion from the 159 manifest, and later to extract `<uses-library/>` tags from the manifest and 160 compare them to the uses-library list known to the build system 161 162- [soong/scripts/construct_context.py]: to trim compatibility libraries in CLC 163 164- [make/core/dex_preopt_config_merger.py]: to merge information from 165 dexpreopt.config files for uses-library dependencies into the dependent's 166 dexpreopt.config file (mostly the CLC) 167 168- autogenerated dexpreopt.sh scripts: to call dexpreopt_gen 169 170### Consistency check - manifest_check.py 171 172Because the information from the manifests has to be duplicated in the 173Android.bp/Android.mk files, there is a danger that it may get out of sync. To 174guard against that, the build system generates a rule that verifies 175uses-libraries: checks the metadata in the build files against the contents of a 176manifest. The manifest can be available as a source file, or as part of a 177prebuilt APK. 178 179The check is implemented in [soong/scripts/manifest_check.py]. 180 181It is possible to turn off the check globally for a product by setting 182`PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true` in a product makefile, or for a 183particular build by setting `RELAX_USES_LIBRARY_CHECK=true`. 184 185### Compatibility libraries - construct_context.py 186 187Compatibility libraries are libraries that didn’t exist prior to a certain SDK 188version (say, `N`), but classes in them were in the bootclasspath jars, etc., 189and in version `N` they have been separated into a standalone uses-library. 190Compatibility libraries should only be in the CLC of an app if its 191`targetSdkVersion` in the manifest is less than `N`. 192 193Currently compatibility libraries only affect apps (but not other libraries). 194 195The build system cannot see `targetSdkVersion` of an app at the time it 196generates dexpreopt build rules, so it doesn't know whether to add compatibility 197libaries to CLC or not. As a workaround, the build system includes all 198compatibility libraries regardless of the app version, and appends some extra 199logic to the dexpreopt rule that will extract `targetSdkVersion` from the 200manifest and filter CLC based on that version during Ninja stage of the build, 201immediately before executing the dexpreopt command (see the 202soong/scripts/construct_context.py script). 203 204As of the time of writing (January 2022), there are the following compatibility 205libraries: 206 207- org.apache.http.legacy (SDK 28) 208- android.hidl.base-V1.0-java (SDK 29) 209- android.hidl.manager-V1.0-java (SDK 29) 210- android.test.base (SDK 30) 211- android.test.mock (SDK 30) 212 213### Manifest fixer 214 215Sometimes uses-library tags are missing from the source manifest of a 216library/app. This may happen for example if one of the transitive dependencies 217of the library/app starts using another uses-library, and the library/app's 218manifest isn't updated to include it. 219 220Soong can compute some of the missing uses-library tags for a given library/app 221automatically as SDK libraries in the transitive dependency closure of the 222library/app. The closure is needed because a library/app may depend on a static 223library that may in turn depend on an SDK library (possibly transitively via 224another library). 225 226Not all uses-library tags can be computed in this way, because some of the 227uses-library dependencies are not SDK libraries, or they are not reachable via 228transitive dependency closure. But when possible, allowing Soong to calculate 229the manifest entries is less prone to errors and simplifies maintenance. For 230example, consider a situation when many apps use some static library that adds a 231new uses-library dependency -- all the apps will have to be updated. That is 232difficult to maintain. 233 234There is also a manifest merger, because sometimes the final manifest of an app 235is merged from a few dependency manifests, so the final manifest installed on 236devices contains a superset of uses-library tags of the source manifest of the 237app. 238 239 240[make/core/dex_preopt.mk]: https://cs.android.com/android/platform/superproject/+/main:build/make/core/dex_preopt.mk 241[make/core/dex_preopt_config.mk]: https://cs.android.com/android/platform/superproject/+/main:build/make/core/dex_preopt_config.mk 242[make/core/dex_preopt_config_merger.py]: https://cs.android.com/android/platform/superproject/+/main:build/make/core/dex_preopt_config_merger.py 243[make/core/dex_preopt_odex_install.mk]: https://cs.android.com/android/platform/superproject/+/main:build/make/core/dex_preopt_odex_install.mk 244[soong/dexpreopt]: https://cs.android.com/android/platform/superproject/+/main:build/soong/dexpreopt 245[soong/dexpreopt/class_loader_context.go]: https://cs.android.com/android/platform/superproject/+/main:build/soong/dexpreopt/class_loader_context.go 246[soong/dexpreopt/config.go]: https://cs.android.com/android/platform/superproject/+/main:build/soong/dexpreopt/config.go 247[soong/dexpreopt/dexpreopt.go]: https://cs.android.com/android/platform/superproject/+/main:build/soong/dexpreopt/dexpreopt.go 248[soong/java]: https://cs.android.com/android/platform/superproject/+/main:build/soong/java 249[soong/java/app.go:deps]: https://cs.android.com/android/platform/superproject/+/main:build/soong/java/app.go?q=%22func%20\(u%20*usesLibrary\)%20deps%22 250[soong/java/app.go:verifyUsesLibraries]: https://cs.android.com/android/platform/superproject/+/main:build/soong/java/app.go?q=%22func%20\(u%20*usesLibrary\)%20verifyUsesLibraries%22 251[soong/java/bootclasspath_fragment.go]: https://cs.android.com/android/platform/superproject/+/main:build/soong/java/bootclasspath_fragment.go 252[soong/java/dexpreopt.go]: https://cs.android.com/android/platform/superproject/+/main:build/soong/java/dexpreopt.go 253[soong/java/dexpreopt_bootjars.go]: https://cs.android.com/android/platform/superproject/+/main:build/soong/java/dexpreopt_bootjars.go 254[soong/java/dexpreopt_config.go]: https://cs.android.com/android/platform/superproject/+/main:build/soong/java/dexpreopt_config.go 255[soong/java/java.go:addCLCFromDep]: https://cs.android.com/android/platform/superproject/+/main:build/soong/java/java.go?q=%22func%20addCLCfromDep%22 256[soong/java/platform_bootclasspath.go]: https://cs.android.com/android/platform/superproject/+/main:build/soong/java/platform_bootclasspath.go 257[soong/scripts/construct_context.py]: https://cs.android.com/android/platform/superproject/+/main:build/soong/scripts/construct_context.py 258[soong/scripts/manifest_check.py]: https://cs.android.com/android/platform/superproject/+/main:build/soong/scripts/manifest_check.py 259