When core library desugaring is enabled, the R8 path appends the prebuilt java8_legacy_dex from the toolchain to the final dexes wholesale. There is no shrinking step at all, so every APK bundles the entire j$.* runtime regardless of how much of it the app actually uses.
AGP instead runs an R8 -> TraceReferences -> L8 pipeline: trace which j$.* APIs the optimized app actually reaches, shrink the desugared library down to those with L8, and append the result. So compared to a Gradle build of the same app, the Bazel APK carries a lot of dead j$.* code.
Numbers from our app (nio desugar config): total dex size went from ~16 MiB to ~14 MiB with shrinking, i.e. roughly 2 MiB of unused j$.* code was being shipped.
We're running a patch that reproduces the AGP pipeline: Dolfik1@031d22f
Notes on the patch:
- TraceReferences is used to produce the L8 keep rules because R8 9.x removed
--desugared-lib-pg-conf-output, so R8 can no longer emit them itself. TraceReferences is the R8-sanctioned replacement and what AGP uses.
- TraceReferences needs
--lib to be the same library set R8 saw (android.jar plus the app's neverlink/compile-only jars), not android.jar alone. If a j$.* member is only reachable through a dependency class missing from --lib, resolution fails and that keep rule is silently dropped, which would surface as runtime NoClassDefFoundError instead of a build error.
- L8 runs with
-dontobfuscate. AGP also obfuscates the desugared library and surfaces a mapping for retrace; the current rules_android path appends a D8-built (unminified) dex, so keeping names readable matches existing behavior. Matching AGP's obfuscation + mapping would be a follow-up.
--min-api is set to the binary's effective min SDK instead of whatever the prebuilt java8_legacy_dex was baked with.
- New toolchain attrs: l8, tracereferences, desugar_jdk_libs_all, desugared_jdk_libs_jar.
Happy to turn this into a PR with tests if you'd take it. Main design question is whether you'd want the shrinking pipeline gated behind a flag or as the new default for the R8 path.
When core library desugaring is enabled, the R8 path appends the prebuilt
java8_legacy_dexfrom the toolchain to the final dexes wholesale. There is no shrinking step at all, so every APK bundles the entire j$.* runtime regardless of how much of it the app actually uses.AGP instead runs an R8 -> TraceReferences -> L8 pipeline: trace which j$.* APIs the optimized app actually reaches, shrink the desugared library down to those with L8, and append the result. So compared to a Gradle build of the same app, the Bazel APK carries a lot of dead j$.* code.
Numbers from our app (nio desugar config): total dex size went from ~16 MiB to ~14 MiB with shrinking, i.e. roughly 2 MiB of unused j$.* code was being shipped.
We're running a patch that reproduces the AGP pipeline: Dolfik1@031d22f
Notes on the patch:
--desugared-lib-pg-conf-output, so R8 can no longer emit them itself. TraceReferences is the R8-sanctioned replacement and what AGP uses.--libto be the same library set R8 saw (android.jar plus the app's neverlink/compile-only jars), not android.jar alone. If a j$.* member is only reachable through a dependency class missing from --lib, resolution fails and that keep rule is silently dropped, which would surface as runtime NoClassDefFoundError instead of a build error.-dontobfuscate. AGP also obfuscates the desugared library and surfaces a mapping for retrace; the current rules_android path appends a D8-built (unminified) dex, so keeping names readable matches existing behavior. Matching AGP's obfuscation + mapping would be a follow-up.--min-apiis set to the binary's effective min SDK instead of whatever the prebuilt java8_legacy_dex was baked with.Happy to turn this into a PR with tests if you'd take it. Main design question is whether you'd want the shrinking pipeline gated behind a flag or as the new default for the R8 path.