Skip to content

ADFA-3604: Re-enable R8 shrinking with keep rules for kotlin-analysis-api - #1596

Merged
davidschachterADFA merged 4 commits into
stagefrom
chore/ADFA-3604-analyze-dex-code-bloat
Jul 30, 2026
Merged

ADFA-3604: Re-enable R8 shrinking with keep rules for kotlin-analysis-api#1596
davidschachterADFA merged 4 commits into
stagefrom
chore/ADFA-3604-analyze-dex-code-bloat

Conversation

@davidschachterADFA

@davidschachterADFAdavidschachterADFA commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • ADFA-3604 asked us to investigate why classes*.dex exceeds 108 MB. The single biggest contributor turned out to be the bundled Kotlin Analysis API jar (used for in-editor Kotlin code intelligence): 43,951 classes / ~18.4 MB, ~42% of all dex bytecode.
  • R8 shrinking is already wired on for :app release (isMinifyEnabled = isAppModule), but app/proguard-rules.pro had a blanket -dontshrink added in ADFA-996 after shrinking broke release-build project init — neutering shrinking entirely ever since.
  • This PR removes -dontshrink and adds targeted -keep rules to subprojects/kotlin-analysis-api/consumer-rules.pro for every class the jar loads reflectively via IntelliJ plugin XML descriptors (Core.xml, CoreImpl.xml, JavaPsiPlugin.xml, analysis-api/*.xml) and META-INF/servicesServiceLoader entries — the paths R8 can't trace from bytecode reachability alone.

Trial results

Local release build (:app:assembleV8Release), before vs after:

BeforeAfter
Dex files164
Total size119.4 MB28.8 MB
Total classes104,03324,853
org.jetbrains.kotlin.* (analysis API)43,951 classes / 18.4 MB4,818 classes / 2.28 MB

Spot-checked R8's usage.txt and the output dex directly: MainActivity, OnboardingActivity, CrashHandlerActivity, and the Kotlin LSP classes are all still present. The com.itsaky.androidide.* entries R8 removed are unused synthetic R$/BuildConfig classes and dead private members, not core app code.

Risk

ADFA-996's prior shrink attempt failed at runtime (release-build project init), not at build time — static analysis and a build-succeeds check don't rule that out here. No ARM device was available to install and exercise the shrunk APK before opening this PR, particularly Kotlin autocomplete/diagnostics (the exact feature the new keep rules protect).

Test plan

  • Install a release build from this branch on a physical ARM device
  • Verify app launches and onboarding/main flow works
  • Open a Kotlin file and verify autocomplete and diagnostics work (go-to-definition is not implemented for Kotlin files in this codebase yet -- see PR comment)
  • Confirm no crashes during project init/sync

🤖 Generated with Claude Code

…-api
Removes the blanket -dontshrink added in ADFA-996, which neutered R8
shrinking entirely after it broke release-build project init. Adds
targeted keep rules for every class the bundled kotlin-analysis-api jar
loads reflectively via IntelliJ plugin XML descriptors and
ServiceLoader entries, since those are the paths R8 can't trace from
bytecode reachability alone.
Cuts release dex from 119.4 MB / 104,033 classes to 28.8 MB / 24,853
classes in a trial build. Not yet verified on a physical device.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@claudeclaudeBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.

Tip: disable this comment in your organization's Code Review settings.

@coderabbitai

coderabbitaiBot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

R8 shrinking is re-enabled for the application, while consumer rules preserve Kotlin analysis and IntelliJ classes instantiated through plugin descriptors and ServiceLoader.

Changes

R8 preservation configuration

Layer / File(s)Summary
Kotlin analysis consumer keep rules
subprojects/kotlin-analysis-api/consumer-rules.pro
Documents and adds generated keep rules for IntelliJ, Kotlin analysis API, FIR, decompiler, and UAST classes loaded reflectively or through ServiceLoader.
Application shrinking enablement
app/proguard-rules.pro
Removes the global -dontshrink directive so R8 can shrink unused classes.

Estimated code review effort: 2 (Simple) | ~15 minutes

Possibly related PRs

Suggested reviewers:hal-eisen-adfa

Poem

I’m a little rabbit guarding the keep,
While R8 trims classes in its sleep.
Plugin names sparkle, services stay bright,
Kotlin analysis hops through the night.
Shrink on, keep safe—what a fine feat! 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Title check✅ PassedThe title clearly summarizes the main change: re-enabling R8 shrinking with targeted keep rules for kotlin-analysis-api.
Description check✅ PassedThe description is directly related to the changeset and accurately explains the shrink/keep-rule changes and rationale.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/ADFA-3604-analyze-dex-code-bloat

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
subprojects/kotlin-analysis-api/consumer-rules.pro (1)

9-360: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the maintenance/refresh process for this keep-rule list.

This file replaces a blanket -dontshrink with ~350 hand-enumerated FQCN keep rules. That's the right tradeoff for size, but it's brittle against future upgrades of the bundled Kotlin Analysis API / IntelliJ platform jars: if a class referenced here is renamed or removed upstream, R8 will typically just drop the now-unresolvable keep rule (or warn) rather than fail the build, so breakage would only surface at runtime via ServiceConfigurationError/ClassNotFoundException — exactly the kind of regression the PR flags as still needing physical-device validation.

Consider adding a short note (here or in the ticket) on how this list was derived (e.g., scanning META-INF/services/* and plugin XML descriptors) and how to regenerate/diff it on future dependency bumps, so staleness is caught deliberately rather than discovered in the field.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@subprojects/kotlin-analysis-api/consumer-rules.pro` around lines 9 - 360,
Document how the hand-maintained keep-rule list was generated, including
scanning META-INF/services entries and plugin XML descriptors, and describe the
steps to regenerate and diff it after Kotlin Analysis API or IntelliJ platform
dependency upgrades. Add this maintenance note near the keep rules or in the
associated tracking documentation, referencing the enumerated FQCN rules as the
maintained artifact.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@subprojects/kotlin-analysis-api/consumer-rules.pro`:
- Around line 9-360: Document how the hand-maintained keep-rule list was
generated, including scanning META-INF/services entries and plugin XML
descriptors, and describe the steps to regenerate and diff it after Kotlin
Analysis API or IntelliJ platform dependency upgrades. Add this maintenance note
near the keep rules or in the associated tracking documentation, referencing the
enumerated FQCN rules as the maintained artifact.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3b2ba7a8-0354-4e65-9b01-6a35a8ec8d9c

📥 Commits

Reviewing files that changed from the base of the PR and between 2e606b7 and 8e63ad8.

📒 Files selected for processing (2)
  • app/proguard-rules.pro
  • subprojects/kotlin-analysis-api/consumer-rules.pro
💤 Files with no reviewable changes (1)
  • app/proguard-rules.pro

Review feedback on #1596: the 352-entry keep-rule list had no documented
provenance. Adds the exact extraction/parsing steps used to generate it
and how to regenerate and diff it after a Kotlin Analysis API or
IntelliJ platform dependency bump.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@davidschachterADFA

Copy link
Copy Markdown
CollaboratorAuthor

On-device verification (debug build, chore/ADFA-3604-analyze-dex-code-bloat, Samsung Galaxy Note20 Ultra / SM-N986U, arm64-v8a):

  • Built and installed :app:assembleV8Debug, launched cleanly, no crashes.
  • Created a new Kotlin project from the "Empty Activity" template — KotlinLanguageServer: Kotlin project initialized logged with no ClassNotFoundException/NoClassDefFoundError anywhere in the analysis pipeline.
  • Opened MainActivity.kt: Kotlin syntax highlighting rendered correctly.
  • Diagnostics engine ran live and surfaced an inline error tooltip.
  • Triggered autocomplete (identifier.) — popup correctly showed real member suggestions (equals, hashCode) resolved through the Kotlin analysis API, confirming the completion pipeline survives R8 shrinking with the new keep rules.

This was a debug build rather than release, so it doesn't fully replace the release-build device test already noted in the PR description, but it's a second independent confirmation that the kotlin-analysis-api keep rules hold up for project init, diagnostics, and completion. Go-to-definition check still to follow.

@davidschachterADFA

Copy link
Copy Markdown
CollaboratorAuthor

Follow-up on the go-to-definition item: reopened the same test project (debug build) and long-pressed a resolved symbol (Bundle in an import) to bring up the code-actions menu. For this Kotlin file it only offered Comment line / Uncomment line / Organize imports / Surround with try/catch / Implement members — no "Go to definition".

Checked why: GoToDefinitionAction (lsp/java/src/main/java/com/itsaky/androidide/lsp/java/actions/common/GoToDefinitionAction.kt) extends BaseJavaCodeAction and is only registered in the Java LSP's action menu. The Kotlin LSP's action set (lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/) has AddImportAction, ImplementMembersAction, NullSafetyAction, OrganizeImportsAction, SurroundWithTryCatchAction — no go-to-definition action. The backend capability does exist (KotlinLanguageServer.findDefinition() is implemented and IDEEditor.findDefinition() calls through to it), but no UI action wires it up for Kotlin files.

So this is a pre-existing gap in the product (Kotlin go-to-definition was never hooked up to the editor UI), not something this PR's R8/keep-rule changes affect either way — there's no existing behavior here for the keep rules to have broken. Updated the test-plan checkbox to reflect that autocomplete and diagnostics were verified for Kotlin, and noted go-to-definition separately rather than leave it looking untested or falsely checked.

@davidschachterADFA

davidschachterADFA commented Jul 29, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Filed ADFA-4923 to track the missing Kotlin go-to-definition action.

@davidschachterADFA
davidschachterADFA merged commit 3262149 into stageJul 30, 2026
4 checks passed
@davidschachterADFA
davidschachterADFA deleted the chore/ADFA-3604-analyze-dex-code-bloat branch July 30, 2026 02:10
hal-eisen-adfa added a commit that referenced this pull request Aug 15, 2026
* ADFA-5156: Roll back R8 shrinking to unbreak plugins
Restores the blanket -dontshrink that ADFA-3604 (#1596) removed on
2026-07-29. This is a temporary rollback to restore plugin functionality;
a targeted fix follows.
Plugins are loaded parent-first through a stock DexClassLoader
(PluginLoader.kt:92-116, parent passed at PluginManager.kt:603), so every
kotlin.** class a plugin references resolves from the IDE's dex, not from
the ~1058 stdlib classes the plugin bundles. R8 cannot see plugin call
sites, so it strips every stdlib member the IDE itself does not call. The
net effect is that a plugin can only call the subset of the Kotlin standard
library that the IDE also calls; anything else throws NoSuchMethodError at
runtime. Sketch to UI fails on every image load with
"No static method maxOrNull([F)Ljava/lang/Float; in class ArraysKt".
-dontobfuscate and -dontoptimize were already set, so restoring -dontshrink
reduces R8 to a pass-through and returns the release build to the
configuration shipped before ADFA-3604. isMinifyEnabled and
isShrinkResources are deliberately left alone, keeping resource shrinking
and the build wiring unchanged.
Verified by dex-scanning both APKs (baseline pulled from a release install
on Samsung RFCT704HEAL):
kotlin/kotlinx method declarations 30,669 -> 45,371
ArraysKt/CollectionsKt/MapsKt/
FilesKt/SequencesKt facades absent -> present
10 sketch-to-ui stdlib call sites all stripped -> all present
CompletableJob$DefaultImpls.plus stripped -> present
Sketch to UI now loads an image and completes detection on-device with no
NoSuchMethodError in logcat.
APK size: 659,307,160 -> 706,829,817 bytes (+47.5 MB, +7.2%).
Note: R8 was buying less than #1596 advertised. That PR measured dex at
28.8 MB / 24,853 classes, but the shipped APK is 85 MB / 78,757 classes --
the URGENT follow-ups (#1609, #1610) added -dontoptimize plus a set of keep
rules that clawed most of it back.
* ADFA-5156: Add R8 plugin-impact analysis tooling
The ADFA-5156 failure mode is invisible at build time -- assemblePlugin is
green, the manifest is fine, the .cgp is correct, and only on-device
execution of a specific code path reveals that R8 stripped a stdlib member
the plugin needs. These scripts make it measurable from build artifacts
instead.
scripts/r8-plugin-impact/
README.md what the bug is, how to run, how to read output,
the known false positives, and the ADFA-5156
baseline numbers to measure future builds against
dex-dump.sh extract + dexdump an APK or .cgp
analyze-plugin-impact.py simulate parent-first resolution of every
kotlin.*/kotlinx.* call site in each plugin's own
code against two host dexes and diff the verdicts
Three subcommands: impact (the before/after table), explain-method (trace one
resolution chain, showing where it leaves the APK), explain-absent (inspect
fall-throughs for the split-brain shape that caused the original bug).
Documents three traps that produce wrong conclusions if analysis is done ad
hoc, all of which bit during this investigation:
- Methods inherited from the Android boot classpath read as missing, because
java.util.* is not in the APK. Eight such false positives are enumerated.
- Kotlin multifile facades (ArraysKt, StringsKt) declare nothing themselves;
they extend a part class whose underscore count varies
(StringsKt__StringsKt vs ArraysKt___ArraysKt). Checking a facade directly
always fails.
- D8 build-time synthetics ($$ExternalSyntheticBackport0 and friends) never
exist in the host and always show as absent.
Stdlib-only Python, no third-party dependencies. Run with
uv run --no-project.
* ADFA-5156: Apply spotless formatting to plugin-impact scripts
* ADFA-5156: Point plugin-impact baseline at the deployed plugin set
The first baseline measured a local folder of .cgp files that turned out to
be a pre-rename snapshot -- 5 stale filenames and 3 plugins missing. Re-runs
against the artifact from the last update-libs.yml deploy (26 plugins, 4,261
call sites) and documents how to obtain that artifact, so the next person
does not measure the wrong set. Conclusion is unchanged: zero regressions,
67 real failures to 0.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@davidschachterADFA@hal-eisen-adfa