Skip to content

Core library desugaring: the desugared library is appended to the APK unshrunk #533

Description

@Dolfik1

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions