Skip to content

perf(instrumentation): [Generated metadata 1] Generate class availability - #1404

Merged
romtsn merged 6 commits into
mainfrom
perf/generated-sdk-metadata-availability
Aug 24, 2026
Merged

perf(instrumentation): [Generated metadata 1] Generate class availability#1404
romtsn merged 6 commits into
mainfrom
perf/generated-sdk-metadata-availability

Conversation

@romtsn

@romtsn romtsn commented Aug 13, 2026

Copy link
Copy Markdown
Member

Generate an app-owned Java source containing SDK integration class availability from the variant runtime classpath. The ASM transform now emits a stable symbolic call to that generated class and no longer carries variant-specific inputs.

This moves dependency resolution into a cacheable source-generation task, avoiding runtime classpath resolution during Gradle configuration and keeping the transform isolated and configuration-cache compatible.

The SDK-side availability field shipped in sentry-java#5875. Older SDK versions are detected and left unchanged, retaining reflection as the fallback.

@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 01c33f8. Configure here.

@romtsn
romtsn force-pushed the perf/generated-sdk-metadata-availability branch from 01c33f8 to d6cc1ee Compare August 17, 2026 09:57

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

looks good! main concern would be the configuration cache issue if it is an issue!

AVAILABILITY_FIELD,
MAP_DESCRIPTOR,
)
visitor.visitLdcInsn(className)

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.

what did lines 114-131 do and why don't we need them anymore?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It used to instrument like this:

static Map<String, Object> classAvailability;

static {
  classAvailability = new HashMap();
  classAvailability.put(..., ...)
  ...
}

now it's just this

static Map<String, Object> classAvailability = /* this is injected by SAGP */ SentryGeneratedBuildTimeOptions.getClassAvailability(); 

after we've switched to generating SentryGeneratedBuildTimeOptions in the plugin.

.toSet()
val availability = resolveClassAvailability(modules)
val sourceFile = File(output.get().asFile, GENERATED_CLASS_PATH)
sourceFile.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.

if this is properly marked as an output directory we don't need to do this.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

i tested this - it's marked as @OutputDirectory, however, that one only creates the root dir automatically but not the subdirs, so we still need mkdirs() here

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.

ah so only output is created but not sourceFile ? that makes sense.

GenerateSentryBuildTimeOptionsTask::class.java,
) { task ->
task.moduleIds.set(
configurationProvider.map { configuration ->

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 can't tell if this eagerly resolves the configuration. if so, we should avoid that as resolving the configuration would mean resolving all the dependencies at configuration time.
also we should check if this is configuration cache compatible since I know that the configuration object can't be serialized in to the configuration cache.
i think itd be worth adding an integration test for this!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

configurations.named() and the following map remain lazy, and our unit test verifies registration does not resolve the runtime classpath. I’ve also made the configuration-cache integration test explicitly assert this task runs and is up-to-date when the cache is reused.

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.

nice!

@@ -0,0 +1,7 @@
package io.sentry.android.core

object SentryGeneratedBuildTimeOptions {

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.

since we're generating java, should this file be in java as well?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

i think it's fine to keep it in Kotlin since the generated bytecode matches that of java anyway (thanks to @JvmStatic)


companion object {
private const val GENERATED_CLASS_PATH =
"io/sentry/android/core/SentryGeneratedBuildTimeOptions.java"

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.

just a note: mixing kotlin and java sources in the same module drastically slows down compilation speed. not saying we should do anything about it but just noting it here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

that's a great point I haven't thought of! I guess there's no an easy enough way for us to identify if the module contains only kotlin or only java files and generate respectively. I guess i'd keep it in mind until someone raises a real build time overhead concern

romtsn and others added 6 commits August 24, 2026 14:53
Move runtime classpath resolution into a cacheable generated-source task and keep the SDK ASM transform parameterless. This avoids configuration-time artifact resolution while preserving reflection fallback for unknown classes.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
Add Compose UI to the real SDK fixture so replay's optional Compose references do not obscure the release-build coverage.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
Ignore unmatched project variants while collecting external module coordinates for generated SDK availability metadata.

Co-Authored-By: Codex <noreply@openai.com>
Avoid uploading a source-context bundle containing only Sentry's generated build-time options class.

Co-Authored-By: Codex <noreply@openai.com>
Use Gradle's directory property API for the generated source and compare normalized paths when excluding it from source bundles. Explicitly cover the generation task in the configuration-cache integration test.

Co-Authored-By: Codex <noreply@openai.com>
@romtsn
romtsn force-pushed the perf/generated-sdk-metadata-availability branch from a965436 to e193d84 Compare August 24, 2026 13:00
@romtsn
romtsn changed the base branch from perf/generated-sdk-metadata to main August 24, 2026 13:01
@romtsn
romtsn merged commit c3570fa into main Aug 24, 2026
23 checks passed
@romtsn
romtsn deleted the perf/generated-sdk-metadata-availability branch August 24, 2026 15:25
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