Check libsignal Updates #232
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Automatically check for new libsignal releases and create PR with all updates | |
| # | |
| # This workflow: | |
| # 1. Runs daily (or manually) to check for new libsignal releases | |
| # 2. Compares with current upstream dependency tag in rust/Cargo.toml | |
| # 3. If newer version found: | |
| # - Updates rust/Cargo.toml with new libsignal tag | |
| # - Runs cargo update to update Cargo.lock | |
| # - Regenerates FRB bindings (make codegen) | |
| # - Updates CHANGELOG.md with AI-generated entry (GitHub Models) | |
| # - Creates a PR with all changes | |
| name: Check libsignal Updates | |
| on: | |
| schedule: | |
| # Run daily at 09:00 UTC | |
| - cron: '0 9 * * *' | |
| workflow_dispatch: | |
| inputs: | |
| force_update: | |
| description: 'Force update even if version is the same' | |
| required: false | |
| default: false | |
| type: boolean | |
| target_version: | |
| description: 'Specific version to update to (leave empty for latest)' | |
| required: false | |
| default: '' | |
| type: string | |
| jobs: | |
| check-updates: | |
| runs-on: ubuntu-latest | |
| # All writes (checkout push, PR creation, branch push) go through the App | |
| # token; the default GITHUB_TOKEN needs no write access here. | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Generate GitHub App Token | |
| id: app-token | |
| uses: actions/create-github-app-token@v3 | |
| with: | |
| app-id: ${{ vars.APP_ID }} | |
| private-key: ${{ secrets.APP_PRIVATE_KEY }} | |
| - uses: actions/checkout@v7 | |
| with: | |
| token: ${{ steps.app-token.outputs.token }} | |
| - name: Setup FVM and Flutter | |
| uses: ./.github/actions/setup-fvm | |
| - name: Check for updates | |
| id: check | |
| env: | |
| TARGET_VERSION: ${{ inputs.target_version }} | |
| FORCE_UPDATE: ${{ inputs.force_update }} | |
| run: | | |
| # Build arguments | |
| ARGS="--ci" | |
| if [ -n "$TARGET_VERSION" ]; then | |
| # Manual input is interpolated into ARGS and reaches a shell before | |
| # the Dart checker can validate it. Reject anything that is not the | |
| # exact upstream tag form here as well. | |
| if [[ ! "$TARGET_VERSION" =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$ ]]; then | |
| echo "::error::target_version must use the exact vX.Y.Z form." | |
| exit 2 | |
| fi | |
| ARGS="$ARGS --version $TARGET_VERSION" | |
| fi | |
| if [ "$FORCE_UPDATE" = "true" ]; then | |
| ARGS="$ARGS --force" | |
| fi | |
| echo "Running: make check-new-libsignal-version ARGS=\"$ARGS\"" | |
| # `|| true` is unavoidable, and the exit code is unusable as a signal: | |
| # the checker exits 1 for "update available", and GNU make collapses any | |
| # non-zero recipe status into its own exit 2 (verified: a recipe exiting | |
| # 1 makes `make` exit 2), so 2 cannot be told apart from a crash. | |
| make check-new-libsignal-version ARGS="$ARGS" || true | |
| # What can tell them apart is the outputs file: the checker writes it | |
| # before signalling, and not at all when it throws. Without this gate a | |
| # broken checker (rate limit, API change, network) looks exactly like | |
| # "already up to date" and the workflow stays green forever while | |
| # silently never opening another update PR. | |
| if ! grep -q '^needs_update=' "$GITHUB_OUTPUT"; then | |
| echo "::error::Update checker wrote no result — it failed before reporting. Check the step log above; do NOT read this as 'up to date'." | |
| exit 1 | |
| fi | |
| echo "Check completed. Outputs:" | |
| cat $GITHUB_OUTPUT || true | |
| # Idempotency gate: if an open PR for this exact version already exists, | |
| # do nothing. Without this, every scheduled run regenerates the update | |
| # (cargo update + AI changelog are not byte-deterministic) and | |
| # force-pushes the PR branch — adding daily noise commits and wiping any | |
| # manual commits pushed to the PR. force_update bypasses the gate. | |
| - name: Check for existing update PR | |
| id: existing | |
| if: steps.check.outputs.needs_update == 'true' | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| LATEST_VERSION: ${{ steps.check.outputs.latest_version }} | |
| FORCE_UPDATE: ${{ inputs.force_update }} | |
| run: | | |
| BRANCH="update-libsignal-${LATEST_VERSION}" | |
| OPEN_PRS=$(gh pr list --head "$BRANCH" --state open --json number --jq 'length') | |
| if [ "$OPEN_PRS" != "0" ] && [ "$FORCE_UPDATE" != "true" ]; then | |
| echo "skip=true" >> $GITHUB_OUTPUT | |
| echo "::notice::Open update PR for branch $BRANCH already exists — skipping to avoid force-pushing over it." | |
| else | |
| echo "skip=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Update rust/Cargo.toml | |
| id: cargo-toml | |
| if: steps.check.outputs.needs_update == 'true' && steps.existing.outputs.skip != 'true' | |
| env: | |
| TARGET_VERSION: ${{ inputs.target_version }} | |
| FORCE_UPDATE: ${{ inputs.force_update }} | |
| run: | | |
| # Build arguments for update | |
| ARGS="--update --ci" | |
| if [ -n "$TARGET_VERSION" ]; then | |
| # Manual input is interpolated into ARGS and reaches a shell before | |
| # the Dart checker can validate it. Reject anything that is not the | |
| # exact upstream tag form here as well. | |
| if [[ ! "$TARGET_VERSION" =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$ ]]; then | |
| echo "::error::target_version must use the exact vX.Y.Z form." | |
| exit 2 | |
| fi | |
| ARGS="$ARGS --version $TARGET_VERSION" | |
| fi | |
| if [ "$FORCE_UPDATE" = "true" ]; then | |
| ARGS="$ARGS --force" | |
| fi | |
| echo "Running: make check-new-libsignal-version ARGS=\"$ARGS\"" | |
| if make check-new-libsignal-version ARGS="$ARGS"; then | |
| echo "success=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "success=false" >> $GITHUB_OUTPUT | |
| echo "::error::Failed to update rust/Cargo.toml" | |
| fi | |
| # Records the libsignal dependency change (libsignal Highlight + Changed). | |
| # Does NOT touch the libsignal_frb crate version — that is bumped later at | |
| # release time (`make release-frb`), which also stamps the frb Highlight. | |
| - name: Update CHANGELOG.md | |
| id: changelog | |
| if: steps.check.outputs.needs_update == 'true' && steps.existing.outputs.skip != 'true' | |
| env: | |
| AI_MODELS_TOKEN: ${{ secrets.AI_MODELS_TOKEN }} | |
| LATEST_VERSION: ${{ steps.check.outputs.latest_version }} | |
| CURRENT_VERSION: ${{ steps.check.outputs.current_version }} | |
| run: | | |
| if make update-changelog ARGS="--version $LATEST_VERSION --from $CURRENT_VERSION --ci"; then | |
| echo "success=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "success=false" >> $GITHUB_OUTPUT | |
| echo "::warning::AI changelog update failed. Please update CHANGELOG.md manually." | |
| fi | |
| - name: Setup Rust | |
| id: setup-rust | |
| if: steps.check.outputs.needs_update == 'true' && steps.existing.outputs.skip != 'true' | |
| continue-on-error: true | |
| uses: ./.github/actions/setup-rust | |
| - name: Setup protoc | |
| id: setup-protoc | |
| if: steps.check.outputs.needs_update == 'true' && steps.existing.outputs.skip != 'true' | |
| continue-on-error: true | |
| uses: ./.github/actions/setup-protoc | |
| - name: Update Cargo.lock | |
| id: cargo-lock | |
| if: steps.check.outputs.needs_update == 'true' && steps.existing.outputs.skip != 'true' | |
| run: | | |
| if make rust-update; then | |
| echo "success=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "success=false" >> $GITHUB_OUTPUT | |
| echo "::warning::Failed to update Cargo.lock" | |
| fi | |
| # The codegen binary is not preinstalled on runners; without this step | |
| # `make codegen` fails with exit 127 on every run. Pinned so CI and | |
| # local runs generate identical bindings (cached by rust-cache). | |
| - name: Install FRB codegen | |
| if: steps.check.outputs.needs_update == 'true' && steps.existing.outputs.skip != 'true' | |
| continue-on-error: true | |
| run: make setup-frb-codegen | |
| - name: Get Dart dependencies | |
| if: steps.check.outputs.needs_update == 'true' && steps.existing.outputs.skip != 'true' | |
| continue-on-error: true | |
| run: make get | |
| - name: Regenerate FRB bindings | |
| id: codegen | |
| if: steps.check.outputs.needs_update == 'true' && steps.existing.outputs.skip != 'true' | |
| run: | | |
| if make codegen; then | |
| echo "success=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "success=false" >> $GITHUB_OUTPUT | |
| echo "::warning::FRB codegen failed. libsignal API may have changed." | |
| fi | |
| - name: Create Pull Request | |
| if: steps.check.outputs.needs_update == 'true' && steps.existing.outputs.skip != 'true' | |
| uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 | |
| with: | |
| token: ${{ steps.app-token.outputs.token }} | |
| sign-commits: true | |
| commit-message: | | |
| chore(deps): update libsignal to ${{ steps.check.outputs.latest_version }} | |
| title: "Update libsignal to ${{ steps.check.outputs.latest_version }}" | |
| body: | | |
| ## Automated libsignal Update | |
| This PR updates the libsignal dependency version. | |
| ### Version Change | |
| | Component | Old | New | | |
| |-----------|-----|-----| | |
| | libsignal | ${{ steps.check.outputs.current_version }} | ${{ steps.check.outputs.latest_version }} | | |
| ${{ steps.check.outputs.is_prerelease == 'true' && '> **Pre-release**: This is a release candidate. Consider waiting for stable release unless you need specific features.' || '' }} | |
| ${{ steps.cargo-toml.outputs.success == 'false' && '> **Critical**: Failed to update rust/Cargo.toml. Manual update required.' || '' }} | |
| ${{ steps.cargo-lock.outputs.success == 'false' && '> **Warning**: Failed to update Cargo.lock. Run `make rust-update` manually.' || '' }} | |
| ${{ steps.codegen.outputs.success == 'false' && '> **Error**: FRB codegen failed. libsignal API may have changed. Manual intervention required.' || '' }} | |
| ${{ steps.changelog.outputs.success == 'false' && '> **Warning**: AI changelog update failed. Please update CHANGELOG.md manually.' || '' }} | |
| > **Note**: This PR does NOT bump the `libsignal_frb` crate version and does NOT build native binaries. It only updates the libsignal dependency. Cut a native release later with `make release-frb` (stage 1), then the Dart release (stage 2). See CLAUDE.md → Release Flow. | |
| ### Files Updated Automatically | |
| - `rust/Cargo.toml` — ${{ steps.cargo-toml.outputs.success == 'true' && 'libsignal dependency tags only (the `libsignal_frb` crate version is intentionally NOT bumped here)' || '**NOT UPDATED** (update failed)' }} | |
| - `rust/Cargo.lock` — ${{ steps.cargo-lock.outputs.success == 'true' && 'updated dependencies' || '**NOT UPDATED** (cargo update failed)' }} | |
| - `lib/src/rust/` — ${{ steps.codegen.outputs.success == 'true' && 'regenerated FRB bindings' || '**NOT UPDATED** (codegen failed, manual update required)' }} | |
| - `CHANGELOG.md` — ${{ steps.changelog.outputs.success == 'true' && 'AI-generated entry (GitHub Models)' || '**NOT UPDATED** (AI failed, manual update required)' }} | |
| - `README.md` — ${{ steps.cargo-toml.outputs.success == 'true' && 'version badge' || '**NOT UPDATED**' }} | |
| - `.copier-answers.yml` — ${{ steps.cargo-toml.outputs.success == 'true' && 'upstream_version updated' || '**NOT UPDATED**' }} | |
| - `CLAUDE.md` — ${{ steps.cargo-toml.outputs.success == 'true' && 'example in documentation' || '**NOT UPDATED**' }} | |
| ### Release Notes | |
| See [libsignal ${{ steps.check.outputs.latest_version }} release notes](${{ steps.check.outputs.release_url }}) | |
| ### Before Merge | |
| ${{ steps.cargo-toml.outputs.success == 'false' && '1. **UPDATE rust/Cargo.toml** — automatic update failed, update manually' || '' }} | |
| ${{ steps.cargo-lock.outputs.success == 'false' && '1. **RUN `make rust-update`** — Cargo.lock update failed' || '' }} | |
| 1. ${{ steps.codegen.outputs.success == 'false' && '**FIX FRB BINDINGS** — codegen failed, check libsignal API changes' || 'Review FRB bindings — check if API changes require Dart code updates' }} | |
| 2. **Review CHANGELOG.md** — ${{ steps.changelog.outputs.success == 'true' && 'verify AI-generated entry is accurate' || '**ADD CHANGELOG ENTRY MANUALLY**' }} | |
| ### After Merge | |
| 1. **Run tests locally** (optional, CI will run them): | |
| ```bash | |
| make test | |
| ``` | |
| 2. This update accumulates on `main` with no new native binary. When | |
| you are ready to ship, run the two-stage release: | |
| - **Stage 1 — native crate:** `make release-frb ARGS="--version X.Y.Z"` | |
| (bumps `libsignal_frb`, tags `libsignal_frb-X.Y.Z`, builds binaries) | |
| - **Stage 2 — Dart package:** bump `pubspec.yaml`, move CHANGELOG | |
| `[Unreleased]` → version, tag `vX.Y.Z` (release-package skill) | |
| --- | |
| *This PR was created automatically by the libsignal update checker.* | |
| branch: update-libsignal-${{ steps.check.outputs.latest_version }} | |
| delete-branch: true | |
| labels: | | |
| dependencies | |
| automated | |
| ${{ steps.check.outputs.is_prerelease == 'true' && 'pre-release' || '' }} | |
| ${{ steps.cargo-toml.outputs.success == 'false' && 'cargo-toml-failed' || '' }} | |
| ${{ steps.cargo-lock.outputs.success == 'false' && 'cargo-lock-failed' || '' }} | |
| ${{ steps.codegen.outputs.success == 'false' && 'codegen-failed' || '' }} | |
| ${{ steps.changelog.outputs.success == 'false' && 'changelog-needed' || '' }} | |
| - name: Summary | |
| env: | |
| LATEST_VERSION: ${{ steps.check.outputs.latest_version }} | |
| CURRENT_VERSION: ${{ steps.check.outputs.current_version }} | |
| IS_PRERELEASE: ${{ steps.check.outputs.is_prerelease }} | |
| NEEDS_UPDATE: ${{ steps.check.outputs.needs_update }} | |
| SKIP_EXISTING: ${{ steps.existing.outputs.skip }} | |
| CARGO_TOML_OK: ${{ steps.cargo-toml.outputs.success }} | |
| CARGO_LOCK_OK: ${{ steps.cargo-lock.outputs.success }} | |
| CODEGEN_OK: ${{ steps.codegen.outputs.success }} | |
| CHANGELOG_OK: ${{ steps.changelog.outputs.success }} | |
| run: | | |
| if [ "$SKIP_EXISTING" = "true" ]; then | |
| echo "## Update PR Already Open" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "An open PR for **${LATEST_VERSION}** already exists — nothing to do." >> $GITHUB_STEP_SUMMARY | |
| echo "Re-run with \`force_update\` to regenerate it (this force-pushes the PR branch)." >> $GITHUB_STEP_SUMMARY | |
| elif [ "$NEEDS_UPDATE" = "true" ]; then | |
| echo "## Update Available" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Property | Value |" >> $GITHUB_STEP_SUMMARY | |
| echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| Current libsignal | ${CURRENT_VERSION} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| New libsignal | ${LATEST_VERSION} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Pre-release | ${IS_PRERELEASE} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Cargo.toml | ${CARGO_TOML_OK} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Cargo.lock | ${CARGO_LOCK_OK} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| FRB Codegen | ${CODEGEN_OK} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| AI Changelog | ${CHANGELOG_OK} |" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "A pull request has been created for review." >> $GITHUB_STEP_SUMMARY | |
| if [ "$CARGO_TOML_OK" = "false" ]; then | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "> **Critical**: Failed to update rust/Cargo.toml. Manual update required." >> $GITHUB_STEP_SUMMARY | |
| fi | |
| if [ "$CARGO_LOCK_OK" = "false" ]; then | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "> **Warning**: Failed to update Cargo.lock. Run \`make rust-update\` manually." >> $GITHUB_STEP_SUMMARY | |
| fi | |
| if [ "$CODEGEN_OK" = "false" ]; then | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "> **Error**: FRB codegen failed. libsignal API may have changed. Manual intervention required." >> $GITHUB_STEP_SUMMARY | |
| fi | |
| if [ "$CHANGELOG_OK" = "false" ]; then | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "> **Warning**: AI changelog update failed. Please update CHANGELOG.md manually." >> $GITHUB_STEP_SUMMARY | |
| fi | |
| else | |
| echo "## Up to Date" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "Current version **${CURRENT_VERSION}** is the latest." >> $GITHUB_STEP_SUMMARY | |
| fi |