Skip to content

perf(instrumentation): Inject manifest metadata at build time - #1401

Closed
romtsn wants to merge 6 commits into
mainfrom
perf/java-531-build-time-manifest
Closed

perf(instrumentation): Inject manifest metadata at build time#1401
romtsn wants to merge 6 commits into
mainfrom
perf/java-531-build-time-manifest

Conversation

@romtsn

@romtsn romtsn commented Aug 13, 2026

Copy link
Copy Markdown
Member

📜 Description

Parse resolved io.sentry.* entries from the merged Android manifest and inject
their typed values into ManifestMetadataReader during bytecode instrumentation.
The optimization uses the existing runtimeOptimizations.enabled flag and only
runs with sentry-android-core 8.54.0 or newer.

Resource references, unresolved placeholders, and unsupported SDK versions keep
using the existing runtime manifest lookup.

Companion SDK PR: getsentry/sentry-java#5963

💡 Motivation and Context

Sentry Android initialization currently retrieves application metadata through
PackageManager and lazily unparcels its Bundle. Resolving manifest metadata at
build time removes both operations from the startup path.

Refs JAVA-531

💚 How did you test it?

  • ./gradlew spotlessApply
  • Parser and ASM visitor unit tests
  • Integration tests for resolved values and resource-reference fallback
  • Validated all 98 supported manifest keys in a real app in injected and fallback modes
  • Confirmed in Perfetto that both startup getApplicationInfo calls disappear when injected
  • Compared 24 cold starts per mode; direct-map init median improved from 32.987 ms to 30.835 ms

📝 Checklist

  • I reviewed the submitted code
  • I added tests to verify the changes
  • I updated the docs if needed
  • No breaking changes

🔮 Next steps

Merge the SDK support in getsentry/sentry-java#5963 before releasing this optimization.

Parse resolved Sentry manifest metadata and inject it into compatible Android SDK versions so startup avoids PackageManager and Bundle unparceling. Fall back to runtime lookup when values cannot be resolved safely.

Refs JAVA-531
Co-Authored-By: Codex <noreply@openai.com>
@linear-code

linear-code Bot commented Aug 13, 2026

Copy link
Copy Markdown

JAVA-531

Refs JAVA-531
Co-Authored-By: Codex <noreply@openai.com>
Refs JAVA-531
Co-Authored-By: Codex <noreply@openai.com>
@romtsn
romtsn marked this pull request as ready for review August 13, 2026 11:45
romtsn and others added 2 commits August 13, 2026 15:19
Use android:resource so newer AGP versions cannot resolve the fixture before the merged manifest reaches the parser.

Refs JAVA-531
Co-Authored-By: OpenAI Codex <noreply@openai.com>
Prevent AGP dependency instrumentation from reusing manifest metadata injected for another app while keeping older SDK transforms cacheable.

Refs JAVA-531
Co-Authored-By: OpenAI Codex <noreply@openai.com>
Generate one manifest metadata cache key from the per-build modules service so repeated Gradle input queries stay stable and configuration-cache restores receive a fresh key.

Refs JAVA-531
Co-Authored-By: OpenAI Codex <noreply@openai.com>

@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 0d32407. Configure here.

)
params.buildTimeMetadataCacheKey.setDisallowChanges(
params.buildTimeMetadataEnabled.map {
if (it) runtimeModulesService.get().buildTimeMetadataCacheKey else ""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shared cache key reuses injected metadata

High Severity

buildTimeMetadataCacheKey now comes from SentryModulesService, a single registerIfAbsent build service shared by every project and variant. The isolation key is therefore identical across apps and flavors, so AGP can reuse ManifestMetadataReader bytecode injected for a different variant. Product flavors and debug/release often have different io.sentry.* metadata, including DSNs.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0d32407. Configure here.

MANIFEST_METADATA_READER_NAME ->
ManifestMetadataClassVisitor(
instrumentationContext.apiVersion.get(),
nextClassVisitor,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: The buildTimeMetadata() function is called twice without caching. A transient failure on the second call will crash the build due to an unchecked null.
Severity: MEDIUM

Suggested Fix

Cache the result of the buildTimeMetadata() call within the factory instance. The result from the first invocation in isInstrumentable should be stored and reused in createClassVisitor. This avoids re-reading the file from disk and eliminates the race condition where a transient I/O error could cause a build crash.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location:
plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/SentrySdkOptimizationClassVisitorFactory.kt#L56

Potential issue: The `buildTimeMetadata()` function, which reads and parses the merged
manifest file, is called independently in `isInstrumentable` and `createClassVisitor`
without caching the result. The Android Gradle Plugin (AGP) guarantees that if
`isInstrumentable` returns true, `createClassVisitor` will be called. However, if the
first file read succeeds but the second one fails due to a transient I/O error, the
`checkNotNull(buildTimeMetadata())` call in `createClassVisitor` will throw an
`IllegalStateException`, causing the build to crash. This creates a potential for rare,
hard-to-diagnose build failures.

Also affects:

  • plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/SentrySdkOptimizationClassVisitorFactory.kt:69~72

@romtsn

romtsn commented Aug 13, 2026

Copy link
Copy Markdown
Member Author

Superseded by a generated app-class design. The replacement makes the SDK bytecode rewrite identical for every app and puts variant-specific manifest values in a normally generated app class, avoiding synthetic cache keys and cross-app transform reuse.

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

1 participant