Skip to content

fix(instrumentation): Resolve class availability at task time - #1402

Closed
sentry-junior[bot] wants to merge 14 commits into
mainfrom
fix/runtime-opt-config-time-resolution
Closed

fix(instrumentation): Resolve class availability at task time#1402
sentry-junior[bot] wants to merge 14 commits into
mainfrom
fix/runtime-opt-config-time-resolution

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Fixes the config-time *RuntimeClasspath resolution warning reported in #1399 after the runtime optimization work in #1375.

Problem

AGP snapshots instrumentation @Inputs during configuration. The availability map was derived from configuration.incoming.resolutionResult, so enabling runtimeOptimizations resolved the variant runtime classpath too early.

Fix

  • Add ResolveSdkClassAvailabilityTask, following the existing deps-report pattern (artifactsFor(...).resolvedArtifacts).
  • Resolve modules and write a deterministic availability properties file at task execution time.
  • Pass that file as an @InputFile to SentrySdkOptimizationClassVisitorFactory so dependency changes still invalidate instrumentation.
  • Keep tracing on the existing afterResolve + SentryModulesService path; remove the modules Provider return from collectModules that forced config-time resolution.

Test plan

  • ./gradlew -p plugin-build test --tests ResolveSdkClassAvailabilityTaskTest --tests SentryModulesCollectorTest --tests LoadClassClassVisitorTest
  • ./gradlew -p plugin-build spotlessApply compileKotlin
  • CI / sample assemble (no Android SDK in this environment)
  • Confirm warning gone with runtimeOptimizations enabled

Refs #1399

Requested by roman.

--

View Junior Session [Sentry]

sentry-junior Bot and others added 4 commits August 13, 2026 12:10
Move SDK runtime-optimization classpath resolution onto a dedicated
task output so AGP no longer resolves *RuntimeClasspath while
snapshotting instrumentation inputs during configuration.

Refs #1399

Co-Authored-By: Roman Zavarnitsyn <roman.zavarnitsyn@sentry.io>
Co-Authored-By: Roman Zavarnitsyn <roman.zavarnitsyn@sentry.io>
Co-Authored-By: Roman Zavarnitsyn <roman.zavarnitsyn@sentry.io>
Assert the #1399 warning stays gone under --warning-mode all, and add a
unit mutation oracle that register() leaves runtimeClasspath unresolved.

Co-Authored-By: Roman Zavarnitsyn <roman.zavarnitsyn@sentry.io>
@romtsn
romtsn marked this pull request as ready for review August 13, 2026 15:08
sentry-junior Bot and others added 2 commits August 13, 2026 15:17
Derive module ids from resolutionResult.allComponents via a lazy
Provider so presence matches the pre-#1402 graph path without
config-time resolution, including modules without android-classes.
Co-Authored-By: Roman Zavarnitsyn <roman.zavarnitsyn@sentry.io>

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

leaving you my comments from my partially completed review


val availability = resolveClassAvailability(modules)
val output = outputFile.get().asFile
output.parentFile?.mkdirs()

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.

we dont need this, gradle will create the directory marked as OutputFile

Suggested change
output.parentFile?.mkdirs()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done — dropped the manual mkdirs().

val output = outputFile.get().asFile
output.parentFile?.mkdirs()
// Write manually so the file stays deterministic for build caching (no Properties timestamp).
output.bufferedWriter().use { writer ->

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.

can you add a comment here or somewhere as to what we expect the file to look like?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

added above the writer:

// Deterministic one-entry-per-line properties (no java.util.Properties timestamp header):
//   androidx.core.view.ScrollingView=true
//   timber.log.Timber=false

val modulesService =
if (runtimeOptimizationsEnabled || tracingInstrumentationEnabled) {

// Runtime optimizations resolve the classpath via a task output so AGP does not force

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.

I'm not sure I fully understood this comment. Isn't this an input not an output?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yep — for the ASM visitor it's an @InputFile. the point of the task is only to produce that file at execution time so AGP doesn't force config-time resolution while snapshotting params. reworded the comment to call that out explicitly.

sentry-junior Bot and others added 2 commits August 13, 2026 16:03
Drop manual OutputFile mkdirs, document the properties format, and
explain that the task output is the instrumentation input.
Add a unit glue test from task file through visitor injection, and an
integration test that checks the availability properties plus injected
LoadClass bytecode after assemble with a supporting SDK.

Co-Authored-By: Roman Zavarnitsyn <roman.zavarnitsyn@sentry.io>
Comment thread plugin-build/src/test/kotlin/io/sentry/android/gradle/TestUtils.kt
Require the classAvailability injection marker when locating LoadClass
under app/build so the E2E assertion cannot pick an uninstrumented
library jar from walk order. Also search the TestKit transform cache
where AsmClassesTransform writes dependency outputs.
CI showed the finder could return a stale instrumented jar from the
shared TestKit transform cache (timber=false) even when this build's
availability file had timber=true. Filter candidates by the expected
map and prefer fresher ASM outputs.
Nested @InputFile visitor params are not reliable for dependency jars:
AGP instruments them via isolated AsmClassesTransform, which does not
promote nested file inputs into transform inputs/dependencies. CI showed
the resolve task wrote timber=true while every instrumented LoadClass
candidate had an empty or timber=false map.

Restore a serializable @input MapProperty and populate it from the
resolve task output so workers receive the availability values without
config-time classpath resolution.

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4a568a8. Configure here.

Comment thread plugin-build/src/main/kotlin/io/sentry/android/gradle/AndroidComponentsConfig.kt Outdated
TaskProvider.map { task -> task.outputFile.get() } can read the file
before the resolve task runs and snapshot an empty map into the ASM
worker. Wire classAvailability from
availabilityTask.flatMap { outputFile }.map(::readClassAvailability)
so Gradle tracks the produced file and only reads it after execution.
AsmClassesTransform isolates nested visitor @input MapProperty values when
dependency jars are consumed. Isolation cannot query a task-mapped provider
until the producing task has completed. Make merge/dex/transform consumers
depend on resolveSentrySdkClassAvailability so the map is ready.
AsmClassesTransform isolation cannot query task-mapped nested @input
MapProperty values (even after dependsOn ordered the resolve task).
Restore RegularFileProperty @InputFile wiring and keep merge/dex/
transform consumers depending on the resolve task so the file exists
when workers run.
@romtsn

romtsn commented Aug 13, 2026

Copy link
Copy Markdown
Member

Superseded by the generated app-class design. Class availability will be produced as normal generated app code, so runtime classpath resolution stays at task execution without manually ordering internal AGP transform consumers by task name.

@romtsn romtsn closed this Aug 13, 2026
Sign up for free to 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