Skip to content

ADFA-5068: Fix javac-services dependency scope without breaking AGP's consistency check - #1643

Draft
davidschachterADFA wants to merge 3 commits into
task/ADFA-5053-lazy-load-javac-carrierfrom
task/ADFA-5068-javac-services-dep-scope
Draft

ADFA-5068: Fix javac-services dependency scope without breaking AGP's consistency check#1643
davidschachterADFA wants to merge 3 commits into
task/ADFA-5053-lazy-load-javac-carrierfrom
task/ADFA-5068-javac-services-dep-scope

Conversation

@davidschachterADFA

@davidschachterADFAdavidschachterADFA commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

Stacked on #1638 (ADFA-5053) -- this needs its java-compiler-carrier module and ADR 0012.

Follow-up from ADFA-5053's APK size audit (ADFA-5068). javac-services declared kotlin-stdlib, guava, :common, and :logger as implementation even though all four are resident, leaking androidx.core's ~2.87MB of dead resources plus duplicate guava/kotlin-stdlib bytecode into java-compiler-carrier.apk. Switching those to compileOnly broke AGP's compileClasspath.shouldResolveConsistentlyWith(runtimeClasspath) check one level up in java-compiler-impl, once :common's androidx graph stopped incidentally anchoring a high-enough version against AGP's own low-pinned viewbinding injection.

  • Relocated ReflectUtils (the one :common symbol javac-services actually uses -- VMUtils/ILogger already lived in :shared/:logger) from :common to :shared.
  • javac-services: common.kotlin/projects.common/projects.logger -> compileOnly(shared)/compileOnly(logger)/compileOnly(kotlin); dropped the guava line entirely (confirmed dead -- javac-services' own code never references it).
  • Added a constraints {} block in java-compiler-impl pinning androidx.annotation/kotlin-stdlib/org.jetbrains:annotations to the versions already used elsewhere in the project. A constraint only rescopes an edge already reachable via AGP's injected viewbinding, so it doesn't reintroduce the duplication this fix removes -- just a version bump on a few KB of annotation classes.
  • Documented the pattern in ADR 0012 (Negative/costs) as a third worked example of the resident-vs-isolated compileOnly rule.

Verified

From-scratch java-compiler-carrier:assembleV8Release:

  • APK: 32.4MB -> 10.2MB
  • resources.arsc: 2.27MB -> 40 bytes (empty; all androidx.core res/ entries gone)
  • dex: 9.1+8.1+10.5+2.6MB across 5 files -> 8.4+2.1MB across 2

Test plan

  • :shared:test, :subprojects:javac-services:testV8DebugUnitTest, :lsp:java-compiler-impl:testV8DebugUnitTest, :common:testV8DebugUnitTest all green
  • Clean :subprojects:java-compiler-carrier:assembleV8Release succeeds and byte sizes confirmed above
  • spotlessApply clean

🤖 Generated with Claude Code

… consistency check
javac-services declared kotlin-stdlib, guava, :common, and :logger as
implementation even though all are resident, leaking androidx.core's
2.87MB of dead resources and duplicate guava/kotlin-stdlib bytecode
into java-compiler-carrier.apk. Switching those to compileOnly broke
AGP's compileClasspath.shouldResolveConsistentlyWith(runtimeClasspath)
check one level up in java-compiler-impl: removing :common's implicit
implementation edge meant nothing but AGP's own low-pinned viewbinding
was left providing androidx.annotation/kotlin-stdlib/org.jetbrains:
annotations on the runtime classpath, while compileOnly deps (sora-
editor, appcompat, material, lsp:indexing) still demanded much higher
versions on the compile side.
Relocated ReflectUtils (the one :common symbol javac-services actually
uses; VMUtils and ILogger already lived in :shared/:logger) there, and
added a constraints{} block in java-compiler-impl pinning the three
conflicting artifacts to the versions already used everywhere else in
the project -- harmonizing both classpaths without re-adding a real
dependency edge. guava was dropped outright: javac-services' own code
never references it directly.
Verified via a from-scratch java-compiler-carrier release build:
32.4MB -> 10.2MB, resources.arsc 2.27MB -> 40 bytes (empty), dex
9.1+8.1+10.5+2.6MB across 5 files -> 8.4+2.1MB across 2.

@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.

Comment threadsubprojects/javac-services/build.gradle.kts Outdated
Comment threadsubprojects/javac-services/build.gradle.kts
Comment threaddocs/adr/0012-lazy-load-javac-via-dexclassloader.md Outdated
@hal-eisen-adfa
hal-eisen-adfa marked this pull request as draft August 21, 2026 18:19
Moving ReflectUtilsTest to :shared dropped all nine of its tests from CI:
jacocoAggregateReport depended only on each subproject's testV8DebugUnitTest,
and a plain java-library has `test`. Sonarqube reaches unit tests solely through
that task, so the ADFA-4649 stripFinalModifier regression they guard has been
unguarded since the move -- and :plugin-api and :logger were already invisible
the same way. The aggregate now falls back to `test` where there is no
testV8DebugUnitTest, and collects the flavorless class dirs and
build/jacoco/test.exec so the coverage is reported, not merely executed.
Verified: :shared:test, :logger:test and :plugin-api:test now appear in the
task graph, and the nine tests pass.
javac-services declares compileOnly(libs.androidx.annotation). Three sources
import it, and dropping implementation(projects.common) removed the last
declared provider; it compiled only because viewBinding dragged in
androidx.annotation:1.0.0. That module has no layouts, so viewBinding is off
there now, which makes the dependency real rather than incidental.
The guava comment claimed the carrier no longer duplicates it. It does:
:subprojects:java-compiler-carrier's v8ReleaseRuntimeClasspath still reaches
com.google.guava:guava through :build-deps:google-java-format and
javaparser-symbol-solver-core, neither of which goes via :common. Comment
corrected to say so, and to say that keeping guava out means deciding about
those two edges -- google-java-format needs it at runtime -- rather than a scope
change.
ADR 0012 said viewbinding is unconditionally injected by AGP. It is injected by
our own convention plugin. That correction suggested a cheaper fix than the
constraints block, so I tried it: with viewBinding off in both modules and the
constraints removed, v8Release still fails on androidx.annotation:{strictly
1.0.0}, because the pin arrives through the runtime graph from the modules that
still enable viewBinding. The constraints stay, the ADR now records both the
correct attribution and why the cheaper fix needs to be project-wide.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…5068-javac-services-dep-scope
This PR is stacked on #1638, not on stage. Merging stage directly gave
five conflicts -- the same five #1638 had, because they are the same
relocations -- and would have meant resolving them a second time and
renumbering the shared ADR independently, leaving the two branches with
different answers to the same question.
Merging its actual base instead resolves cleanly: zero conflicts, and
the ADR arrives already renumbered to 0015 with stage's 0011-0014 in
place.
Verified: :lsp:java-compiler-impl and :app compile, the java LSP tests
pass, spotlessCheck is clean.
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