Nightly Bump #163
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
| name: Nightly Bump | |
| on: | |
| schedule: | |
| - cron: '0 2 * * *' # 2 AM UTC daily | |
| workflow_dispatch: # allow manual trigger | |
| concurrency: | |
| group: nightly-bump | |
| cancel-in-progress: true | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| issues: write | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Nightly: bump every submodule to its latest upstream default branch and push | |
| # the result to the throwaway `nightly/bump-candidate` branch. The reusable | |
| # `tests-and-doctests` pipeline then runs the nix tests + doc-tests against that | |
| # candidate state. ONLY when every test passes is the candidate promoted to | |
| # `nightly/bump` and its PR opened/refreshed — so `nightly/bump` and its PR | |
| # always point at a bumped state that is known-green. | |
| # | |
| # On failure: the candidate branch is left for inspection, `nightly/bump` and its | |
| # PR are untouched, and the logos-core team is pinged via the failure tracking | |
| # issue (notify-team) from the test pipeline. | |
| # | |
| # The exact same pipeline can be run manually against any branch — see | |
| # .github/workflows/tests-and-doctests.yml (Actions tab → "Tests and Doc-tests" | |
| # → Run workflow on your branch). That's the way to bump some modules in a | |
| # branch and test the whole workspace state of that branch. | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| jobs: | |
| bump: | |
| name: Bump submodules and stage candidate | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 60 | |
| permissions: | |
| contents: write | |
| outputs: | |
| changed: ${{ steps.check.outputs.changed }} | |
| date: ${{ steps.meta.outputs.date }} | |
| repos: ${{ steps.check.outputs.repos }} | |
| sha: ${{ steps.stage.outputs.sha }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Initialize submodules | |
| run: | | |
| git config --global url."https://github.com/".insteadOf "git@github.com:" | |
| git submodule update --init --depth 1 --jobs 4 | |
| - name: Bump submodules to latest | |
| run: | | |
| # Set origin/HEAD for each submodule so --remote resolves the correct | |
| # default branch (master or main) rather than always assuming master. | |
| git submodule foreach --quiet ' | |
| git remote set-head origin --auto 2>/dev/null || true | |
| ' | |
| git submodule update --remote --depth 1 --jobs 4 | |
| - name: Compute date | |
| id: meta | |
| run: echo "date=$(date -u +%Y-%m-%d)" >> "$GITHUB_OUTPUT" | |
| - name: Check for changes | |
| id: check | |
| run: | | |
| if git diff --quiet; then | |
| echo "changed=false" >> "$GITHUB_OUTPUT" | |
| echo "No submodule updates found." >> "$GITHUB_STEP_SUMMARY" | |
| else | |
| echo "changed=true" >> "$GITHUB_OUTPUT" | |
| changed="" | |
| while IFS= read -r line; do | |
| [[ -z "$line" ]] && continue | |
| repo=$(echo "$line" | sed 's|^repos/||') | |
| changed="$changed $repo" | |
| done < <(git diff --name-only -- repos/) | |
| changed=$(echo "$changed" | xargs) | |
| echo "repos=$changed" >> "$GITHUB_OUTPUT" | |
| echo "## Updated repos" >> "$GITHUB_STEP_SUMMARY" | |
| for r in $changed; do echo "- \`$r\`" >> "$GITHUB_STEP_SUMMARY"; done | |
| fi | |
| - name: Install Nix | |
| if: steps.check.outputs.changed == 'true' | |
| # Nix 2.35.1. Must stay >= 2.26: below that, `--override-input` DISCARDS the | |
| # overridden input's own lock and resolves its children from the parent's stale | |
| # lock, so a doc-test that overrides an input can silently build months-old deps | |
| # and still report green. v27 shipped 2.22 and did exactly that. | |
| uses: cachix/install-nix-action@v31.11.0 | |
| with: | |
| extra_nix_config: | | |
| experimental-features = nix-command flakes | |
| - name: Setup Cachix | |
| if: steps.check.outputs.changed == 'true' | |
| uses: cachix/cachix-action@v15 | |
| with: | |
| name: logos-co | |
| authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' | |
| - name: Add workspace tools to PATH | |
| if: steps.check.outputs.changed == 'true' | |
| run: echo "${{ github.workspace }}/scripts" >> "$GITHUB_PATH" | |
| - name: Sync dep-graph | |
| if: steps.check.outputs.changed == 'true' | |
| run: ws sync-graph --quiet | |
| - name: Update flake.lock | |
| if: steps.check.outputs.changed == 'true' | |
| run: | | |
| updated_inputs="" | |
| for repo in ${{ steps.check.outputs.repos }}; do | |
| if grep -qE "^[[:space:]]*${repo}([[:space:]]*=|\.)" flake.nix; then | |
| echo "Updating flake input: $repo" | |
| nix flake lock --update-input "$repo" | |
| updated_inputs="$updated_inputs $repo" | |
| else | |
| echo "Skipping $repo (not a top-level flake input)" | |
| fi | |
| done | |
| if [[ -n "$updated_inputs" ]]; then | |
| echo "## Updated flake.lock inputs" >> "$GITHUB_STEP_SUMMARY" | |
| for r in $updated_inputs; do echo "- \`$r\`" >> "$GITHUB_STEP_SUMMARY"; done | |
| else | |
| echo "No matching flake inputs to update." >> "$GITHUB_STEP_SUMMARY" | |
| fi | |
| # Push the bumped workspace to the throwaway `nightly/bump-candidate` branch | |
| # so the reusable test pipeline can check it out. Nothing consumes this | |
| # branch except that pipeline; `nightly/bump` and its PR are only advanced | |
| # to this state after every test passes (see the promote job). | |
| - name: Stage candidate branch | |
| id: stage | |
| if: steps.check.outputs.changed == 'true' | |
| run: | | |
| branch="nightly/bump-candidate" | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git checkout -b "$branch" | |
| git add repos/ nix/dep-graph.nix flake.nix flake.lock | |
| git commit -m "$(printf '%s\n\n%s\n' \ | |
| "chore: nightly submodule bump ${{ steps.meta.outputs.date }}" \ | |
| "Updated: ${{ steps.check.outputs.repos }}")" | |
| git push --force -u origin "$branch" | |
| echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" | |
| # Run the nix tests + doc-tests against the staged candidate state, publish the | |
| # reports under nightly/<date>/, and notify the logos-core team on any failure. | |
| # No PR comment here: the PR is created/refreshed only after this passes, in the | |
| # promote job below. | |
| tests-and-doctests: | |
| needs: bump | |
| if: ${{ needs.bump.outputs.changed == 'true' }} | |
| uses: ./.github/workflows/tests-and-doctests.yml | |
| with: | |
| ref: nightly/bump-candidate | |
| report-base: nightly/${{ needs.bump.outputs.date }} | |
| notify-team: true | |
| secrets: inherit | |
| # Promote the tested-green candidate to `nightly/bump` and open/refresh its PR. | |
| # Gated on the WHOLE test pipeline succeeding (result == 'success'), so | |
| # `nightly/bump` and its PR never advance to a state that failed tests. If any | |
| # test fails this job is skipped and `nightly/bump`/its PR stay as they were. | |
| promote: | |
| name: Promote candidate to nightly/bump | |
| needs: | |
| - bump | |
| - tests-and-doctests | |
| if: ${{ needs.bump.outputs.changed == 'true' && needs.tests-and-doctests.result == 'success' }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Promote candidate and open/refresh PR | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| DATE: ${{ needs.bump.outputs.date }} | |
| REPOS: ${{ needs.bump.outputs.repos }} | |
| SHA: ${{ needs.bump.outputs.sha }} | |
| run: | | |
| branch="nightly/bump" | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| # Fast-forward `nightly/bump` to the exact commit that passed tests. | |
| git fetch origin nightly/bump-candidate | |
| git push --force origin "$SHA:refs/heads/$branch" | |
| owner="${GITHUB_REPOSITORY%%/*}" | |
| repo="${GITHUB_REPOSITORY#*/}" | |
| report="https://${owner}.github.io/${repo}/nightly/${DATE}/" | |
| title="chore: nightly submodule bump ${DATE}" | |
| body="$(printf '%s\n' \ | |
| '## Nightly Submodule Bump' \ | |
| '' \ | |
| 'Bumped all submodules to their latest upstream default branch.' \ | |
| '' \ | |
| '### Updated repos' \ | |
| "$(for r in ${REPOS}; do echo "- \`$r\`"; done)" \ | |
| '' \ | |
| '### Checks' \ | |
| '✅ All nix tests + doc-tests passed against this state in the linked' \ | |
| '**Tests and Doc-tests** pipeline before this branch/PR was updated.' \ | |
| '' \ | |
| "Full doc-test report index: ${report}" \ | |
| '' \ | |
| 'Auto-generated by the nightly bump workflow.')" | |
| existing=$(gh pr list --head "$branch" --state open --json number --jq '.[0].number' 2>/dev/null || true) | |
| if [[ -n "$existing" ]]; then | |
| gh pr edit "$existing" --title "$title" --body "$body" | |
| echo "Updated PR #$existing" | |
| else | |
| gh pr create --head "$branch" --title "$title" --body "$body" | |
| echo "Created PR for $branch" | |
| fi |