Skip to content

feat(gradle-plugin): upload native debug symbols via symbol-sets upload - #660

Open
cat-ph wants to merge 10 commits into
mainfrom
cat/gradle-native-symbol-upload
Open

feat(gradle-plugin): upload native debug symbols via symbol-sets upload#660
cat-ph wants to merge 10 commits into
mainfrom
cat/gradle-native-symbol-upload

Conversation

@cat-ph

@cat-ph cat-ph commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

💡 Motivation and Context

Native crash capture (#659) needs the app's .so debug symbols uploaded at build time, the same way the plugin already uploads ProGuard mappings. This registers an uploadPostHogNativeSymbols<Variant> task that points posthog-cli symbol-sets upload (>= 0.7.32) at the variant's merged native libs intermediates: the unstripped libraries as built, before AGP strips them for packaging. The CLI scans the directory, uploads every library carrying debug info and a GNU build id, and triages the rest with guidance.

Wiring choices:

  • Registered for every variant, independent of minification: native symbolication is orthogonal to JVM obfuscation, so the task also exists for non-minified builds.
  • Automatic upload is an explicit posthog { uploadNativeSymbols.set(true) } opt-in and skips debuggable variants; the per-variant task remains directly invocable. It scans the complete merged native library directory, covering NDK builds, jniLibs, and libraries from dependencies.
  • Skipped via onlyIf when the variant has no .so files at all.
  • Reuses the existing CLI discovery, credential, and dotenv plumbing (PostHogCliExecTask), so tasks.withType<PostHogCliExecTask> configuration applies to it like the mapping upload.

💚 How did you test it?

  • All 12 committed functional cases pass against AGP 8.0.2 and 8.9.1, covering merged-library dependencies, opt-in/out behavior, source bundling, debuggable-variant exclusion, and configuration-cache smoke coverage. A corrected local release-hook configuration-cache scenario fails on the previous head (962b0a1) for both AGP versions because Gradle cannot serialize the captured task, and passes on ecc097a; a corrected real assembleRelease failure also skips upload on the current head.
  • Latest-head device/devbox E2E: assembleDebug skipped automatic upload, while bundleRelease ran uploadPostHogNativeSymbolsRelease exactly once. A fresh ELF plus its exact DWARF source tree was uploaded with CLI 0.10.0; the symbol set was stored without failure and marked used by Cymbal.
  • On an API 35 ARM64 emulator, a real SIGSEGV produced one distinct $exception carrying that debug reference. Cymbal resolved the JNI, C++, and inline frames at lines 19, 12, and 8 with bundled latest-v5 source context. Only an actual Play Console upload/distribution remains untested.

📝 Checklist

  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the docs if needed. (docs PR is staged separately, gated on release)
  • No breaking change or entry added to the changelog.

If releasing new changes

  • Ran pnpm changeset to generate a changeset file

@marandaneto

marandaneto commented Aug 5, 2026

Copy link
Copy Markdown
Member

we should add a config to enable|disable this in PostHogCliExecTask (or PostHogUploadNativeSymbolsTask not sure) since debug symbols are quite heavy and not all apps (the majority) dont need this
also, we should also provide another config to upload debug symbols w or w/o sources
i think both should be disabled by default

@marandaneto

Copy link
Copy Markdown
Member

not sure if this task is under isDebuggable already but we should not upload for debug builds

a known gap is that we still wont be able to symbolicate OS frames, which is ok for now

@marandaneto

Copy link
Copy Markdown
Member

left a few comments, the overall approach does whats supposed to do, noice!

cat-ph added 4 commits August 5, 2026 17:32
…task

With the directory untracked, outputs.upToDateWhen marked the task UP-TO-DATE after a native rebuild that kept the same app version, so new build ids were never uploaded.
Replaces the externalNativeBuild heuristic, which missed jniLibs and dependency-packaged libraries, with a posthog { uploadNativeSymbols } extension flag, off by default.
…ariants

includeNativeSymbolSources passes --include-source to the CLI, off by default. The automatic hook now excludes debuggable variants so day-to-day debug builds don't upload heavy unoptimized symbol sets; explicit invocation still works for any variant. ApplicationVariant.debuggable is unavailable on the AGP 8.0.x baseline, so the flag is resolved from the finalized DSL build type.
@cat-ph

cat-ph commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

not sure if this task is under isDebuggable already but we should not upload for debug builds

great catch, updated this too!

@cat-ph

cat-ph commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

thanks @marandaneto for the thorough review 💙 updated! 🏓

@cat-ph
cat-ph marked this pull request as ready for review August 6, 2026 14:30
@cat-ph
cat-ph requested a review from a team as a code owner August 6, 2026 14:30
@cat-ph
cat-ph requested review from a team, ablaszkiewicz and hpouillot August 6, 2026 14:31
@greptile-apps

greptile-apps Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor
Prompt To Fix All With AI
### Issue 1
posthog-android-gradle-plugin/src/main/kotlin/com/posthog/android/PostHogAndroidGradlePlugin.kt:114
**Finalizer uploads after failed builds**

When an opted-in assemble, bundle, or install task fails after producing merged native libraries, `hookWithAssembleTasks` still runs the upload through `finalizedBy`, causing symbols for an artifact that was never successfully built to be uploaded and allowing an upload error to obscure the original failure.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "feat(gradle-plugin): add source bundling..." | Re-trigger Greptile

@posthog

posthog Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

🦔 ReviewHog reviewed this pull request

Nothing worth raising this time, so here's a calming picture instead:

Someone relaxing in a sunny garden

…ails

Finalizers run even when the build they finalize fails, which would upload symbols for an artifact that was never built and let upload errors obscure the original failure. Applies to the mapping upload too, which shares the hook.
@cat-ph
cat-ph requested a review from marandaneto August 6, 2026 22:16
// invocations are unaffected: an unexecuted anchor carries no failure.
this@hookWithAssembleTasks.configure {
onlyIf("the finalized build succeeded") {
anchors.none { anchor -> anchor.get().state.failure != null }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

blocking: Preserve Gradle configuration-cache compatibility

This execution-time predicate closes over anchors and resolves each TaskProvider with get(), causing configuration-cache storage to fail with cannot serialize object of type DefaultTask. This affects opted-in native-symbol uploads and the shared mapping-upload hook. Please keep the condition provider-safe without resolving Task instances during execution and add configuration-cache coverage.

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.

fixed, ty! added a PostHogTaskFailureTracker

Comment thread posthog-android-gradle-plugin/build.gradle.kts

@marandaneto marandaneto left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

left one blocker for config cache otherwise lgtm

cat-ph added 2 commits August 7, 2026 15:46
The onlyIf predicate resolved Task instances at execution time, which the configuration cache cannot serialize. Failures are now tracked by task path in a build service fed by task-completion events, and a functional test pins configuration-cache store and reuse.
cat-ph added 2 commits August 7, 2026 15:56
…sts observe the hooked path

The failed-build test wired its failing task at configuration time, before AGP registers variant tasks, so the build failed during configuration and the assertions passed vacuously; it now wires in afterEvaluate and asserts the merge succeeded and the failure was post-merge. The configuration-cache test ran the explicit debug task, which never attaches the finalizers or the failure-tracker predicate; it now runs the hooked release path with configuration-cache problems failing the build, asserting store, upload success, and reuse.
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