xref: /aosp_15_r20/build/soong/dexpreopt/DEXPREOPT_IMPLEMENTATION.md (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
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