-
-
Notifications
You must be signed in to change notification settings - Fork 284
ci: Automate JNI branch sync on release #3593
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7f76f03
210600a
08af791
b942d0e
13b6d00
27246f8
03884a1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| # This workflow is only needed for the maintenance of the jni 0.15.x branch. | ||
| # See https://github.com/getsentry/sentry-dart/issues/3373 | ||
| # This can be removed in v10 when we can support jni 0.15.x in the main branch. | ||
|
|
||
| name: Sync JNI Branch | ||
|
|
||
| on: | ||
| release: | ||
| types: [published] | ||
|
|
||
| env: | ||
| JNI_BRANCH: deps/jni-0.15.x | ||
|
|
||
| concurrency: | ||
| group: sync-jni-branch | ||
| cancel-in-progress: false | ||
|
|
||
| jobs: | ||
| sync: | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: write | ||
| pull-requests: write | ||
| issues: write | ||
| timeout-minutes: 30 | ||
| if: ${{ !github.event.release.prerelease }} | ||
|
|
||
| steps: | ||
| - name: Get auth token | ||
| id: token | ||
| uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 | ||
| with: | ||
| app-id: ${{ vars.SENTRY_RELEASE_BOT_CLIENT_ID }} | ||
| private-key: ${{ secrets.SENTRY_RELEASE_BOT_PRIVATE_KEY }} | ||
|
|
||
| - name: Determine release tag | ||
| id: release | ||
| env: | ||
| RELEASE_TAG: ${{ github.event.release.tag_name }} | ||
| run: | | ||
| # Validate tag format to prevent script injection | ||
| if [[ ! "$RELEASE_TAG" =~ ^[0-9]+\.[0-9]+\.[0-9]+.*$ ]]; then | ||
| echo "::error::Invalid tag format: $RELEASE_TAG" | ||
| exit 1 | ||
| fi | ||
| echo "tag=$RELEASE_TAG" >> $GITHUB_OUTPUT | ||
| echo "Release tag: $RELEASE_TAG" | ||
|
|
||
| - name: Checkout | ||
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | ||
| with: | ||
| token: ${{ steps.token.outputs.token }} | ||
| fetch-depth: 0 | ||
| ref: ${{ env.JNI_BRANCH }} | ||
|
|
||
| - name: Setup Java | ||
| uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 | ||
| with: | ||
| distribution: 'temurin' | ||
| java-version: '17' | ||
|
|
||
| - name: Setup Flutter | ||
| uses: subosito/flutter-action@fd55f4c5af5b953cc57a2be44cb082c8f6635e8e # pin@v2.21.0 | ||
|
|
||
| - name: Configure git | ||
| run: | | ||
| git config user.name 'Sentry Github Bot' | ||
| git config user.email 'bot+github-bot@sentry.io' | ||
|
|
||
| - name: Create sync branch | ||
| id: branch | ||
| env: | ||
| TAG: ${{ steps.release.outputs.tag }} | ||
| run: | | ||
| SYNC_BRANCH="sync/jni-$TAG" | ||
| git checkout -b "$SYNC_BRANCH" | ||
| echo "name=$SYNC_BRANCH" >> $GITHUB_OUTPUT | ||
|
|
||
| - name: Merge release tag | ||
| id: merge | ||
| continue-on-error: true | ||
| env: | ||
| TAG: ${{ steps.release.outputs.tag }} | ||
| BRANCH: ${{ env.JNI_BRANCH }} | ||
| run: | | ||
| git merge "$TAG" --no-edit \ | ||
| -m "Merge release $TAG into $BRANCH" | ||
|
|
||
| - name: Regenerate JNI bindings | ||
| id: regenerate | ||
| if: steps.merge.outcome == 'success' | ||
| continue-on-error: true | ||
| working-directory: packages/flutter | ||
| run: | | ||
| scripts/safe-regenerate-jni.sh 2>&1 | tee /tmp/jni-regen.log | ||
buenaflor marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| - name: Commit regenerated bindings | ||
| if: steps.merge.outcome == 'success' && steps.regenerate.outcome == 'success' | ||
| env: | ||
| TAG: ${{ steps.release.outputs.tag }} | ||
| run: | | ||
| git add packages/flutter/lib/src/native/java/binding.dart | ||
| if ! git diff --cached --quiet; then | ||
| git commit -m "chore: regenerate JNI bindings for $TAG" | ||
| fi | ||
|
|
||
| - name: Push and create PR | ||
| if: steps.merge.outcome == 'success' && steps.regenerate.outcome == 'success' | ||
| env: | ||
| GH_TOKEN: ${{ steps.token.outputs.token }} | ||
| TAG: ${{ steps.release.outputs.tag }} | ||
| BRANCH: ${{ env.JNI_BRANCH }} | ||
| SYNC_BRANCH: ${{ steps.branch.outputs.name }} | ||
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | ||
| run: | | ||
| git push -u origin "$SYNC_BRANCH" | ||
| { | ||
| echo "## Summary" | ||
| echo "Automated sync of the JNI compatibility branch with release \`$TAG\`." | ||
| echo "This PR merges the release tag into \`$BRANCH\` and regenerates JNI bindings." | ||
| echo "" | ||
| echo "## Checklist" | ||
| echo "- [ ] Verify JNI bindings compile successfully" | ||
| echo "- [ ] Verify tests pass" | ||
| echo "" | ||
| echo "> This PR was auto-generated by the [sync-jni-branch]($RUN_URL) workflow." | ||
| } > /tmp/pr-body.md | ||
| gh pr create \ | ||
| --base "$BRANCH" \ | ||
| --title "Sync JNI branch with release $TAG" \ | ||
| --body-file /tmp/pr-body.md | ||
|
|
||
| - name: Create issue on failure | ||
| if: steps.merge.outcome == 'failure' || steps.regenerate.outcome == 'failure' | ||
| env: | ||
| GH_TOKEN: ${{ steps.token.outputs.token }} | ||
| TAG: ${{ steps.release.outputs.tag }} | ||
| BRANCH: ${{ env.JNI_BRANCH }} | ||
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | ||
| MERGE_OUTCOME: ${{ steps.merge.outcome }} | ||
| run: | | ||
| if [ "$MERGE_OUTCOME" = "failure" ]; then | ||
| FAILED_STEP="Merge release tag" | ||
| else | ||
| FAILED_STEP="Regenerate JNI bindings" | ||
| fi | ||
| LOG_TAIL="$(tail -100 /tmp/jni-regen.log 2>/dev/null || echo 'No log output available')" | ||
| { | ||
| echo "## JNI Branch Sync Failure" | ||
| echo "The automated sync of the JNI compatibility branch failed for release \`$TAG\`." | ||
| echo "" | ||
| echo "**Failed step:** $FAILED_STEP" | ||
| echo "**Workflow run:** $RUN_URL" | ||
| echo "**Target branch:** \`$BRANCH\`" | ||
| echo "" | ||
| echo "## Error Logs (last 100 lines)" | ||
| echo '```' | ||
| echo "$LOG_TAIL" | ||
| echo '```' | ||
| echo "" | ||
| echo "## Manual Remediation" | ||
| echo "1. Checkout the JNI branch: \`git checkout $BRANCH\`" | ||
| echo "2. Merge the release tag: \`git merge $TAG\`" | ||
| echo "3. Run: \`cd packages/flutter && scripts/safe-regenerate-jni.sh\`" | ||
| echo "4. Resolve any issues manually" | ||
| echo "5. Commit and push the changes" | ||
| } > /tmp/issue-body.md | ||
| gh issue create \ | ||
| --title "JNI branch sync failed for release $TAG" \ | ||
| --body-file /tmp/issue-body.md | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
|
|
||
| # Safe JNI binding regeneration script. | ||
| # | ||
| # Tries the normal regeneration first (flutter build apk + jnigen). | ||
| # If that fails (e.g. binding.dart is incompatible with a new jni version), | ||
| # falls back to compiling only the Java/Kotlin plugin code via Gradle, | ||
| # creating a stub libs.jar, and running jnigen directly — bypassing | ||
| # Dart compilation entirely. | ||
| # | ||
| # This script is only needed for the maintenance of the jni 0.15.x branch. | ||
| # See https://github.com/getsentry/sentry-dart/issues/3373 | ||
| # This can be removed in v10 when we can support jni 0.15.x in the main branch. | ||
|
|
||
| cd "$(dirname "$0")/../" | ||
|
|
||
| binding_path="lib/src/native/java/binding.dart" | ||
|
|
||
| # Use fvm if available, otherwise bare commands. | ||
| if command -v fvm &> /dev/null; then | ||
| FLUTTER="fvm flutter" | ||
| DART="fvm dart" | ||
| else | ||
| FLUTTER="flutter" | ||
| DART="dart" | ||
| fi | ||
|
|
||
| echo "=== Attempting normal JNI binding regeneration ===" | ||
|
|
||
| # The normal path: build APK (compiles Dart + Java), then run jnigen. | ||
| set +e | ||
| ( | ||
| cd example | ||
| $FLUTTER build apk | ||
| ) | ||
| normal_build=$? | ||
| set -e | ||
|
|
||
| if [ $normal_build -eq 0 ]; then | ||
| $DART run jnigen --config ffi-jni.yaml | ||
| $DART format "$binding_path" | ||
| echo "=== Normal regeneration succeeded ===" | ||
| exit 0 | ||
| fi | ||
|
|
||
| echo "" | ||
| echo "=== Normal regeneration failed (exit code $normal_build), attempting fallback ===" | ||
| echo "" | ||
|
|
||
| # The normal path failed, likely because the existing binding.dart | ||
| # doesn't compile with the current jni package version. | ||
| # | ||
| # Fallback strategy: | ||
| # 1. Clean Gradle build artifacts | ||
| # 2. Compile only the sentry_flutter plugin's Java/Kotlin code via Gradle | ||
| # (this does NOT require Dart to compile) | ||
| # 3. Create a stub libs.jar so jnigen's Gradle classpath resolution succeeds | ||
| # (libs.jar normally contains compiled Dart code from flutter build apk, | ||
| # but jnigen only needs the Java classes, not the Dart code) | ||
| # 4. Run jnigen directly to regenerate binding.dart | ||
| # 5. Verify the result by building the full APK | ||
|
|
||
| echo "Step 1: Cleaning Gradle build artifacts..." | ||
| cd example/android | ||
| ./gradlew clean | ||
| cd ../.. | ||
|
|
||
| echo "Step 2: Compiling sentry_flutter plugin Java/Kotlin code..." | ||
| cd example/android | ||
| ./gradlew :sentry_flutter:bundleLibCompileToJarRelease | ||
| cd ../.. | ||
|
|
||
| echo "Step 3: Creating stub libs.jar..." | ||
| # jnigen's Gradle classpath includes libs.jar (normally produced by flutter build apk). | ||
| # It must be a valid jar but doesn't need to contain Dart compiled classes — | ||
| # jnigen only reads Java classes from the Sentry Android SDK jars and Maven caches. | ||
| stub_dir=$(mktemp -d) | ||
| mkdir -p example/build/app/intermediates/flutter/release | ||
| jar cf example/build/app/intermediates/flutter/release/libs.jar -C "$stub_dir" . | ||
| rm -rf "$stub_dir" | ||
|
|
||
| echo "Step 4: Running jnigen..." | ||
| $DART run jnigen --config ffi-jni.yaml | ||
|
|
||
| echo "Step 5: Formatting generated binding..." | ||
| $DART format "$binding_path" | ||
|
|
||
| echo "Step 6: Verifying by building example APK..." | ||
| set +e | ||
| ( | ||
| cd example | ||
| $FLUTTER build apk | ||
| ) | ||
| verify_result=$? | ||
| set -e | ||
|
|
||
| if [ $verify_result -ne 0 ]; then | ||
| echo "" | ||
| echo "=== ERROR: Regenerated bindings do not compile ===" | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe work logging the error code or failure logs.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. afaict the errors should be visible in the logs when running flutter build apk so we dont need to log more here |
||
| echo "=== Manual intervention is needed ===" | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo "" | ||
| echo "=== Fallback regeneration succeeded ===" | ||
| exit 0 | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I remember you mentioning that some files need to be deleted or commented out before re-generation, is this still true?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
safe-regenerate-jni.sh handles this now - it's not strictly needed anymore since the script will create a stub so it still compiles