From 2bdbc87549fd24255c9c14a2f36060f916bb11a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Bj=C3=A4reholt?= Date: Thu, 17 Sep 2026 14:25:48 +0200 Subject: [PATCH 1/4] ci(release): nightly build against the latest master of every submodule A true nightly: pull only, nothing committed or published. Catches integration breakage between the submodules' masters before anyone bumps the bundle, instead of at bump time. - new cron 0 3 * * * (the weekly dev-release cron is unchanged), plus a workflow_dispatch input submodules=pinned|latest for on-demand runs - nightly-submodules: an always-run resolver job that, on the nightly cron or submodules=latest, moves every submodule (recursively) to its upstream master and reports whether anything moved relative to the committed pointers; skips the build entirely when nothing did - the four build jobs need it and, on nightly, run only when something moved; each checks out exactly the resolver's SHAs so every leg tests the same tips even minutes apart - build-tauri relocks src-tauri/Cargo.lock to the moved aw-server-rust revision so Tauri really builds latest and check_tauri_server.py still holds (installs a Rust toolchain for that step; the action is idempotent with the job's later setup) - preflight/create-tag are excluded from the nightly cron and from submodules=latest, so a nightly can never mint a dev prerelease --- .github/workflows/release.yml | 178 +++++++++++++++++++++++++++++++++- 1 file changed, 173 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 00659308d..efb2221ee 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,9 +9,23 @@ name: Release on: schedule: + # Weekly dev prerelease (goes through preflight/create-tag). - cron: '0 12 * * 4' + # Nightly integration build: every submodule moved to its upstream + # master, build + test, nothing committed or published. Skipped when + # no submodule moved since the pinned tree. Identified below via + # github.event.schedule == '0 3 * * *'. + - cron: '0 3 * * *' workflow_dispatch: inputs: + submodules: + description: 'pinned = build the committed submodule pointers; latest = move every submodule to its upstream master first (nothing is committed)' + required: false + type: choice + options: + - pinned + - latest + default: pinned release_line: description: 'Release line to prerelease from' required: true @@ -43,7 +57,11 @@ permissions: jobs: preflight: name: Pre-flight checks - if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + # Never mint a dev release from the nightly cron or from a dispatch that + # moved submodules off their pinned pointers. + if: >- + (github.event_name == 'schedule' && github.event.schedule != '0 3 * * *') + || (github.event_name == 'workflow_dispatch' && inputs.submodules != 'latest') runs-on: ubuntu-latest concurrency: group: dev-release @@ -252,13 +270,80 @@ jobs: echo "The tag-triggered build jobs in this workflow will now build artifacts and create/update the draft prerelease." } >> "$GITHUB_STEP_SUMMARY" + nightly-submodules: + # Always runs (cheap) so the build jobs' `needs:` never short-circuits + # them. Only does real work on the nightly cron or `submodules=latest`: + # resolves every submodule, recursively, to its upstream master and + # reports whether anything moved relative to the committed pointers. + # Build jobs check out exactly these SHAs so every leg tests the same + # tips even when they start minutes apart. Nothing is committed. + name: Resolve latest submodule tips (nightly) + runs-on: ubuntu-latest + outputs: + latest: ${{ steps.resolve.outputs.latest }} + moved: ${{ steps.resolve.outputs.moved }} + shas: ${{ steps.resolve.outputs.shas }} + steps: + - uses: actions/checkout@v7 + with: + submodules: 'recursive' + fetch-depth: 1 + + - name: Resolve submodule tips + id: resolve + env: + LATEST: ${{ (github.event_name == 'schedule' && github.event.schedule == '0 3 * * *') || inputs.submodules == 'latest' }} + run: | + set -euo pipefail + if [ "$LATEST" != "true" ]; then + { echo "latest=false"; echo "moved=false"; echo "shas="; } >> "$GITHUB_OUTPUT" + echo "Pinned submodule build; nothing to resolve." + exit 0 + fi + # Single quotes are deliberate: `git submodule foreach` expands + # $displaypath itself, once per submodule. + # shellcheck disable=SC2016 + tips='echo "$(git rev-parse HEAD) $displaypath"' + before=$(git submodule foreach --recursive --quiet "$tips") + # Move each submodule (and nested submodules) to the tip of its + # upstream default branch. awatcher is third-party but is included: + # a nightly is exactly where its drift should surface. + git submodule update --init --recursive --remote --depth 1 2>&1 | tail -20 + after=$(git submodule foreach --recursive --quiet "$tips") + if [ "$before" = "$after" ]; then + moved=false + echo "All submodules already at their upstream tips; nightly build skipped." + else + moved=true + echo "Submodules moved:" + diff <(echo "$before") <(echo "$after") | sed 's/^/ /' || true + fi + { + echo "latest=true" + echo "moved=$moved" + echo "shas<> "$GITHUB_OUTPUT" + { + echo "### Nightly submodule tips" + echo '```' + echo "$after" + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + research-edition-checks: name: Research Edition — patch and packaging smoke tests # Run on every PR and push so drifted submodule pins fail at review time, # not when Erik pushes the annotated release tag. The patcher is stdlib-only # so no poetry/build step is needed; only submodules (real source files) and # pytest (for the unit-test fixture suite). - if: github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' + needs: nightly-submodules + # Normal triggers as before; additionally the nightly cron, but only when + # a submodule actually moved off its committed pointer. + if: >- + github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' + || (needs.nightly-submodules.outputs.latest == 'true' && needs.nightly-submodules.outputs.moved == 'true') runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v7 @@ -266,6 +351,19 @@ jobs: submodules: 'recursive' fetch-depth: 1 + - name: Use latest submodule tips (nightly / submodules=latest) + if: needs.nightly-submodules.outputs.latest == 'true' + env: + SUBMODULE_SHAS: ${{ needs.nightly-submodules.outputs.shas }} + run: | + set -euo pipefail + printf '%s\n' "$SUBMODULE_SHAS" | while read -r sha path; do + [ -n "$path" ] || continue + git -C "$path" fetch -q --depth 1 origin "$sha" + git -C "$path" checkout -q "$sha" + done + git submodule status --recursive + - name: Set up Python uses: actions/setup-python@v7 with: @@ -297,7 +395,12 @@ jobs: build-qt: name: Build Qt artifacts - if: github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' + needs: nightly-submodules + # Normal triggers as before; additionally the nightly cron, but only when + # a submodule actually moved off its committed pointer. + if: >- + github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' + || (needs.nightly-submodules.outputs.latest == 'true' && needs.nightly-submodules.outputs.moved == 'true') runs-on: ${{ matrix.os }} continue-on-error: ${{ matrix.experimental }} env: @@ -343,6 +446,19 @@ jobs: submodules: 'recursive' fetch-depth: 0 + - name: Use latest submodule tips (nightly / submodules=latest) + if: needs.nightly-submodules.outputs.latest == 'true' + env: + SUBMODULE_SHAS: ${{ needs.nightly-submodules.outputs.shas }} + run: | + set -euo pipefail + printf '%s\n' "$SUBMODULE_SHAS" | while read -r sha path; do + [ -n "$path" ] || continue + git -C "$path" fetch -q origin "$sha" + git -C "$path" checkout -q "$sha" + done + git submodule status --recursive + - name: Set RELEASE run: | echo "RELEASE=${{ startsWith(github.ref_name, 'v') || github.ref_name == 'master' }}" >> "$GITHUB_ENV" @@ -622,7 +738,12 @@ jobs: build-qt-manylinux-2-28: name: Build Qt artifacts (manylinux_2_28 — glibc 2.28 ABI floor) - if: github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' + needs: nightly-submodules + # Normal triggers as before; additionally the nightly cron, but only when + # a submodule actually moved off its committed pointer. + if: >- + github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' + || (needs.nightly-submodules.outputs.latest == 'true' && needs.nightly-submodules.outputs.moved == 'true') runs-on: ubuntu-22.04 container: image: quay.io/pypa/manylinux_2_28_x86_64 @@ -650,6 +771,19 @@ jobs: submodules: 'recursive' fetch-depth: 0 + - name: Use latest submodule tips (nightly / submodules=latest) + if: needs.nightly-submodules.outputs.latest == 'true' + env: + SUBMODULE_SHAS: ${{ needs.nightly-submodules.outputs.shas }} + run: | + set -euo pipefail + printf '%s\n' "$SUBMODULE_SHAS" | while read -r sha path; do + [ -n "$path" ] || continue + git -C "$path" fetch -q origin "$sha" + git -C "$path" checkout -q "$sha" + done + git submodule status --recursive + # Configure git safe.directory immediately after checkout so that every # subsequent step — including the version probe below — can read the full # history. Deferring this step past the first `git describe` call causes @@ -889,7 +1023,12 @@ jobs: build-tauri: name: Build Tauri artifacts - if: github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' + needs: nightly-submodules + # Normal triggers as before; additionally the nightly cron, but only when + # a submodule actually moved off its committed pointer. + if: >- + github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' + || (needs.nightly-submodules.outputs.latest == 'true' && needs.nightly-submodules.outputs.moved == 'true') runs-on: ${{ matrix.os }} continue-on-error: ${{ matrix.experimental }} env: @@ -945,6 +1084,19 @@ jobs: submodules: "recursive" fetch-depth: 0 + - name: Use latest submodule tips (nightly / submodules=latest) + if: needs.nightly-submodules.outputs.latest == 'true' + env: + SUBMODULE_SHAS: ${{ needs.nightly-submodules.outputs.shas }} + run: | + set -euo pipefail + printf '%s\n' "$SUBMODULE_SHAS" | while read -r sha path; do + [ -n "$path" ] || continue + git -C "$path" fetch -q origin "$sha" + git -C "$path" checkout -q "$sha" + done + git submodule status --recursive + - name: Set environment variables run: | echo "RELEASE=${{ startsWith(github.ref_name, 'v') || github.ref_name == 'master' }}" >> "$GITHUB_ENV" @@ -979,6 +1131,22 @@ jobs: with: python-version: ${{ matrix.python_version }} + # On a nightly the aw-server-rust submodule has moved but aw-tauri's + # Cargo.lock still pins the old revision; relock it so Tauri really builds + # the latest server and the verification below still holds. Needs cargo, + # which this job only installs later — the toolchain action is idempotent. + - name: Set up Rust for the nightly relock + if: needs.nightly-submodules.outputs.latest == 'true' + uses: dtolnay/rust-toolchain@stable + + - name: Relock aw-tauri Cargo.lock to the latest aw-server-rust (nightly) + if: needs.nightly-submodules.outputs.latest == 'true' + run: | + set -euo pipefail + sha=$(git -C aw-server-rust rev-parse HEAD) + (cd aw-tauri/src-tauri && cargo update -p aw-server --precise "$sha") + git -C aw-tauri diff --stat -- src-tauri/Cargo.lock + - name: Verify Tauri and Qt embed the same aw-server-rust revision # Tauri embeds the Git revision in its Cargo.lock, independently of # the top-level submodule used by Qt. Check the actual build inputs; From 97742d7a652a1fc7772e94bc415aeb853db8ab9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Bj=C3=A4reholt?= Date: Thu, 17 Sep 2026 17:13:00 +0200 Subject: [PATCH 2/4] =?UTF-8?q?ci(release):=20address=20review=20=E2=80=94?= =?UTF-8?q?=20pin=20toolchain,=20cheap=20resolver,=20nested=20init,=20excl?= =?UTF-8?q?ude=20third-party=20awatcher?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - pin dtolnay/rust-toolchain to the commit the file already uses elsewhere - resolver job checks out without submodules; initialises them only in latest mode, so ordinary pushes/PRs no longer pay a recursive clone - consumers run two passes (direct, then `submodule update --init`, then nested) so a parent revision that introduces a new nested submodule cannot make `git -C ` fail - the nightly moves only first-party submodules; awatcher (third-party) stays at its pinned revision so unreviewed upstream code never runs in jobs that carry Tauri signing secrets --- .github/workflows/release.yml | 107 +++++++++++++++++++++++++--------- 1 file changed, 81 insertions(+), 26 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index efb2221ee..a2a2beb55 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -284,9 +284,11 @@ jobs: moved: ${{ steps.resolve.outputs.moved }} shas: ${{ steps.resolve.outputs.shas }} steps: + # No submodules here: on ordinary pushes and PRs this job exits at the + # guard below, and a recursive submodule clone would be paid for nothing + # on every build. Submodules are initialised only in latest mode. - uses: actions/checkout@v7 with: - submodules: 'recursive' fetch-depth: 1 - name: Resolve submodule tips @@ -300,15 +302,20 @@ jobs: echo "Pinned submodule build; nothing to resolve." exit 0 fi + git submodule update --init --recursive --depth 1 2>&1 | tail -3 # Single quotes are deliberate: `git submodule foreach` expands # $displaypath itself, once per submodule. # shellcheck disable=SC2016 tips='echo "$(git rev-parse HEAD) $displaypath"' before=$(git submodule foreach --recursive --quiet "$tips") - # Move each submodule (and nested submodules) to the tip of its - # upstream default branch. awatcher is third-party but is included: - # a nightly is exactly where its drift should surface. - git submodule update --init --recursive --remote --depth 1 2>&1 | tail -20 + # Move each first-party submodule (and its nested submodules) to the + # tip of its upstream default branch. awatcher is third-party and is + # left at its pinned revision: a nightly must not execute unreviewed + # third-party code in jobs that carry signing secrets. + # shellcheck disable=SC2016 + firstparty=$(git submodule --quiet foreach 'echo $sm_path' | grep -vx awatcher) + # shellcheck disable=SC2086 + git submodule update --init --recursive --remote --depth 1 -- $firstparty 2>&1 | tail -20 after=$(git submodule foreach --recursive --quiet "$tips") if [ "$before" = "$after" ]; then moved=false @@ -357,11 +364,23 @@ jobs: SUBMODULE_SHAS: ${{ needs.nightly-submodules.outputs.shas }} run: | set -euo pipefail - printf '%s\n' "$SUBMODULE_SHAS" | while read -r sha path; do - [ -n "$path" ] || continue - git -C "$path" fetch -q --depth 1 origin "$sha" - git -C "$path" checkout -q "$sha" - done + # Two passes: direct submodules first, then initialise whatever + # nested submodules the moved parents declare (a new parent revision + # may introduce one that the pinned checkout never had), then nested. + pass() { + printf '%s\n' "$SUBMODULE_SHAS" | while read -r sha path; do + [ -n "$path" ] || continue + case "$1" in + direct) case "$path" in */*) continue ;; esac ;; + nested) case "$path" in */*) ;; *) continue ;; esac ;; + esac + git -C "$path" fetch -q --depth 1 origin "$sha" + git -C "$path" checkout -q "$sha" + done + } + pass direct + git submodule update --init --recursive --depth 1 + pass nested git submodule status --recursive - name: Set up Python @@ -452,11 +471,23 @@ jobs: SUBMODULE_SHAS: ${{ needs.nightly-submodules.outputs.shas }} run: | set -euo pipefail - printf '%s\n' "$SUBMODULE_SHAS" | while read -r sha path; do - [ -n "$path" ] || continue - git -C "$path" fetch -q origin "$sha" - git -C "$path" checkout -q "$sha" - done + # Two passes: direct submodules first, then initialise whatever + # nested submodules the moved parents declare (a new parent revision + # may introduce one that the pinned checkout never had), then nested. + pass() { + printf '%s\n' "$SUBMODULE_SHAS" | while read -r sha path; do + [ -n "$path" ] || continue + case "$1" in + direct) case "$path" in */*) continue ;; esac ;; + nested) case "$path" in */*) ;; *) continue ;; esac ;; + esac + git -C "$path" fetch -q origin "$sha" + git -C "$path" checkout -q "$sha" + done + } + pass direct + git submodule update --init --recursive + pass nested git submodule status --recursive - name: Set RELEASE @@ -777,11 +808,23 @@ jobs: SUBMODULE_SHAS: ${{ needs.nightly-submodules.outputs.shas }} run: | set -euo pipefail - printf '%s\n' "$SUBMODULE_SHAS" | while read -r sha path; do - [ -n "$path" ] || continue - git -C "$path" fetch -q origin "$sha" - git -C "$path" checkout -q "$sha" - done + # Two passes: direct submodules first, then initialise whatever + # nested submodules the moved parents declare (a new parent revision + # may introduce one that the pinned checkout never had), then nested. + pass() { + printf '%s\n' "$SUBMODULE_SHAS" | while read -r sha path; do + [ -n "$path" ] || continue + case "$1" in + direct) case "$path" in */*) continue ;; esac ;; + nested) case "$path" in */*) ;; *) continue ;; esac ;; + esac + git -C "$path" fetch -q origin "$sha" + git -C "$path" checkout -q "$sha" + done + } + pass direct + git submodule update --init --recursive + pass nested git submodule status --recursive # Configure git safe.directory immediately after checkout so that every @@ -1090,11 +1133,23 @@ jobs: SUBMODULE_SHAS: ${{ needs.nightly-submodules.outputs.shas }} run: | set -euo pipefail - printf '%s\n' "$SUBMODULE_SHAS" | while read -r sha path; do - [ -n "$path" ] || continue - git -C "$path" fetch -q origin "$sha" - git -C "$path" checkout -q "$sha" - done + # Two passes: direct submodules first, then initialise whatever + # nested submodules the moved parents declare (a new parent revision + # may introduce one that the pinned checkout never had), then nested. + pass() { + printf '%s\n' "$SUBMODULE_SHAS" | while read -r sha path; do + [ -n "$path" ] || continue + case "$1" in + direct) case "$path" in */*) continue ;; esac ;; + nested) case "$path" in */*) ;; *) continue ;; esac ;; + esac + git -C "$path" fetch -q origin "$sha" + git -C "$path" checkout -q "$sha" + done + } + pass direct + git submodule update --init --recursive + pass nested git submodule status --recursive - name: Set environment variables @@ -1137,7 +1192,7 @@ jobs: # which this job only installs later — the toolchain action is idempotent. - name: Set up Rust for the nightly relock if: needs.nightly-submodules.outputs.latest == 'true' - uses: dtolnay/rust-toolchain@stable + uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 # master - name: Relock aw-tauri Cargo.lock to the latest aw-server-rust (nightly) if: needs.nightly-submodules.outputs.latest == 'true' From 16cc23a2f8691f60ef252e60de1b8afeb1c6dce1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Bj=C3=A4reholt?= Date: Thu, 17 Sep 2026 17:19:31 +0200 Subject: [PATCH 3/4] ci(release): keep direct submodule tips through nested init; trust submodule worktrees in the manylinux container Review findings (Codex): - `git submodule update --init --recursive` at the top level checks out the superproject-recorded revisions, undoing the direct-tip pass; every latest-mode job silently built pinned code. Initialise nested submodules from inside each direct submodule instead. - In the manylinux container the step ran before the safe.directory workaround and `git -C ` runs inside worktrees the global entry does not cover. Move it after, with a step-scoped safe.directory=* via GIT_CONFIG_*. --- .github/workflows/release.yml | 40 +++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a2a2beb55..83284e904 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -379,7 +379,10 @@ jobs: done } pass direct - git submodule update --init --recursive --depth 1 + # Initialise nested submodules from *inside* each direct submodule: + # a top-level `git submodule update --recursive` would check out + # the superproject-recorded (pinned) revisions and undo pass direct. + git submodule --quiet foreach 'git submodule update --init --recursive -q' pass nested git submodule status --recursive @@ -486,7 +489,10 @@ jobs: done } pass direct - git submodule update --init --recursive + # Initialise nested submodules from *inside* each direct submodule: + # a top-level `git submodule update --recursive` would check out + # the superproject-recorded (pinned) revisions and undo pass direct. + git submodule --quiet foreach 'git submodule update --init --recursive -q' pass nested git submodule status --recursive @@ -802,9 +808,22 @@ jobs: submodules: 'recursive' fetch-depth: 0 + # Configure git safe.directory immediately after checkout so that every + # subsequent step — including the version probe below — can read the full + # history. Deferring this step past the first `git describe` call causes + # the version probe to fall back to v0.0.0.dev-unknown. + - name: Configure git safe directory + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + - name: Use latest submodule tips (nightly / submodules=latest) if: needs.nightly-submodules.outputs.latest == 'true' env: + # The container runs as root over a runner-owned checkout; the + # global safe.directory above covers the workspace only, not the + # submodule worktrees this step runs git inside of. Step-scoped. + GIT_CONFIG_COUNT: 1 + GIT_CONFIG_KEY_0: safe.directory + GIT_CONFIG_VALUE_0: '*' SUBMODULE_SHAS: ${{ needs.nightly-submodules.outputs.shas }} run: | set -euo pipefail @@ -823,17 +842,13 @@ jobs: done } pass direct - git submodule update --init --recursive + # Initialise nested submodules from *inside* each direct submodule: + # a top-level `git submodule update --recursive` would check out + # the superproject-recorded (pinned) revisions and undo pass direct. + git submodule --quiet foreach 'git submodule update --init --recursive -q' pass nested git submodule status --recursive - # Configure git safe.directory immediately after checkout so that every - # subsequent step — including the version probe below — can read the full - # history. Deferring this step past the first `git describe` call causes - # the version probe to fall back to v0.0.0.dev-unknown. - - name: Configure git safe directory - run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - - name: Set RELEASE run: | echo "RELEASE=${{ startsWith(github.ref_name, 'v') || github.ref_name == 'master' }}" >> "$GITHUB_ENV" @@ -1148,7 +1163,10 @@ jobs: done } pass direct - git submodule update --init --recursive + # Initialise nested submodules from *inside* each direct submodule: + # a top-level `git submodule update --recursive` would check out + # the superproject-recorded (pinned) revisions and undo pass direct. + git submodule --quiet foreach 'git submodule update --init --recursive -q' pass nested git submodule status --recursive From 65fa749d9ae6007a7746cd86377149705cc930e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Bj=C3=A4reholt?= Date: Fri, 18 Sep 2026 13:07:37 +0200 Subject: [PATCH 4/4] ci(release): keep nightly latest-submodule builds out of the dev-release gate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review finding (Bob's reviewer): preflight judges master by the names of its check-runs, and a nightly run's build jobs carried the same names as the push build against the same SHA — a red nightly on unreviewed submodule tips would have cancelled the weekly dev prerelease although the pinned tree was healthy. Latest-mode jobs are now named "... [latest submodules]" and the gate excludes that marker and the resolver job. Also refuse to run the resolver's `submodule update` with an empty first-party list, which would have moved every submodule, awatcher included. --- .github/workflows/release.yml | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 83284e904..0879a952c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -180,7 +180,10 @@ jobs: --jq '.check_suite_id' 2>/dev/null || echo "0") # Auxiliary workflow jobs are not CI signals; exclude them so their - # skipped or failed conclusions do not block the release. + # skipped or failed conclusions do not block the release. Nightly + # latest-submodule builds run against the same master SHA and are + # named "… [latest submodules]": a red nightly says nothing about + # the pinned tree that a dev release ships, so they are excluded too. conclusions=$(gh api "repos/${GITHUB_REPOSITORY}/commits/${head_sha}/check-runs" \ --paginate \ --slurp 2>/dev/null | jq -r --arg suite "$current_suite_id" ' @@ -188,7 +191,8 @@ jobs: | select( .app.slug == "github-actions" and ((.check_suite.id | tostring) != $suite) and - (.name | test("^(Dependabot|Auto-merge|greeting|Pre-flight checks|Create dev release tag)$") | not) + (.name | test("^(Dependabot|Auto-merge|greeting|Pre-flight checks|Create dev release tag|Resolve latest submodule tips \\(nightly\\))$") | not) and + (.name | test("\\[latest submodules\\]") | not) ) | .conclusion] | unique @@ -313,7 +317,10 @@ jobs: # left at its pinned revision: a nightly must not execute unreviewed # third-party code in jobs that carry signing secrets. # shellcheck disable=SC2016 - firstparty=$(git submodule --quiet foreach 'echo $sm_path' | grep -vx awatcher) + firstparty=$(git submodule --quiet foreach 'echo $sm_path' | grep -vx awatcher || true) + # An empty list would drop the `--` argument and move *every* + # submodule, awatcher included; refuse instead. + if [ -z "$firstparty" ]; then echo "no first-party submodules to move" >&2; exit 1; fi # shellcheck disable=SC2086 git submodule update --init --recursive --remote --depth 1 -- $firstparty 2>&1 | tail -20 after=$(git submodule foreach --recursive --quiet "$tips") @@ -340,7 +347,7 @@ jobs: } >> "$GITHUB_STEP_SUMMARY" research-edition-checks: - name: Research Edition — patch and packaging smoke tests + name: Research Edition — patch and packaging smoke tests${{ needs.nightly-submodules.outputs.latest == 'true' && ' [latest submodules]' || '' }} # Run on every PR and push so drifted submodule pins fail at review time, # not when Erik pushes the annotated release tag. The patcher is stdlib-only # so no poetry/build step is needed; only submodules (real source files) and @@ -416,7 +423,7 @@ jobs: run: python3 -m pytest scripts/tests/test_generate_latest_json.py scripts/tests/test_configure_tauri_release.py -q build-qt: - name: Build Qt artifacts + name: Build Qt artifacts${{ needs.nightly-submodules.outputs.latest == 'true' && ' [latest submodules]' || '' }} needs: nightly-submodules # Normal triggers as before; additionally the nightly cron, but only when # a submodule actually moved off its committed pointer. @@ -774,7 +781,7 @@ jobs: path: dist/activitywatch-*.* build-qt-manylinux-2-28: - name: Build Qt artifacts (manylinux_2_28 — glibc 2.28 ABI floor) + name: Build Qt artifacts (manylinux_2_28 — glibc 2.28 ABI floor)${{ needs.nightly-submodules.outputs.latest == 'true' && ' [latest submodules]' || '' }} needs: nightly-submodules # Normal triggers as before; additionally the nightly cron, but only when # a submodule actually moved off its committed pointer. @@ -1080,7 +1087,7 @@ jobs: path: dist/activitywatch-*.* build-tauri: - name: Build Tauri artifacts + name: Build Tauri artifacts${{ needs.nightly-submodules.outputs.latest == 'true' && ' [latest submodules]' || '' }} needs: nightly-submodules # Normal triggers as before; additionally the nightly cron, but only when # a submodule actually moved off its committed pointer.