From f2ed7225bfb8a195789b49b8662934eb0674c88b Mon Sep 17 00:00:00 2001 From: PathGao Date: Tue, 11 Aug 2026 19:52:52 +0800 Subject: [PATCH 1/2] ci: key the cargo cache on the runner image, not just on "Linux" Moving the Ubuntu would otherwise have restored the wrong cache silently. rust-cache's key ends in `runner.os`, which is `Linux` for both 22.04 and 24.04. Read off two runs of the same job, one per Ubuntu, it is identical: v0-rust-linux-build-test-Linux-x64-e8b3ee54-a2c94f3a ^^^^^ the image is not in here So master's next run on 24.04 would have restored a target/ built on 22.04 as a full match. Cargo fingerprints do not track system libraries: webkit2gtk-sys's build script probes the distribution's headers and caches the result, and that result would have been reused against a different distribution's, with nothing to notice. `$ImageOS` is what the runner calls its own image -- ubuntu22, ubuntu24, macos15. Keying on it invalidates on this move and on the next one, including the ones `macos-latest` and `windows-latest` make with no commit at all. Read from the runner rather than written down, so it is not a fourth thing to remember. The previous keys become unreachable and expire on the usual seven-day-unused or least-recently-used path. One extra set of ~4.45 GB until they do, against the 10 GB limit that no longer holds the npm caches. Checked by mutation: dropping IMAGE_OS from the key fails the new assertion and nothing else. The first attempt at that check reported a pass because bash ate the `${{ }}` before python saw it and the file was never mutated -- the same shape as `continue-on-error`, a check that cannot fail looking exactly like a check that passed. Redone with the edit asserted before the run. 976 pass. Co-Authored-By: Claude Opus 5 --- .github/workflows/test.yml | 7 +++++++ .github/workflows/test_build.yml | 16 +++++++++++++++- scripts/releaseWorkflow.test.ts | 23 +++++++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b6a7e2b2..1b379949 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -81,10 +81,17 @@ jobs: # against a 10 GB per-repository limit shared with ~110 npm caches, and # GitHub evicts least-recently-used -- so unbounded per-branch copies # would push out the very entries they are meant to reuse. + - name: name the runner image for the cache key + run: echo "IMAGE_OS=$ImageOS" >> "$GITHUB_ENV" + - name: cache rust dependencies uses: Swatinem/rust-cache@v2 with: workspaces: src-tauri + # See the note in test_build.yml: rust-cache keys on `runner.os`, so + # ubuntu-22.04 and ubuntu-24.04 share a cache and a build script's + # probe of the wrong distribution's headers gets reused. + key: ${{ env.IMAGE_OS }} save-if: ${{ github.ref == 'refs/heads/master' }} - name: install dependencies (ubuntu only) diff --git a/.github/workflows/test_build.yml b/.github/workflows/test_build.yml index de044067..9d877973 100644 --- a/.github/workflows/test_build.yml +++ b/.github/workflows/test_build.yml @@ -140,11 +140,25 @@ jobs: # Writing only on master pins the total at one set. Pull requests restore # from master and save nothing, which is also what makes the push trigger # above worth its runner time: it is the only writer. + # rust-cache's key ends `-Linux-x64`: `runner.os`, not the image. A cache + # written on ubuntu-22.04 restores onto ubuntu-24.04 as a full match -- + # measured, `v0-rust-linux-build-test-Linux-x64-e8b3ee54-a2c94f3a` on both + # -- and cargo fingerprints do not track system libraries, so the + # webkit2gtk-sys build script's probe of jammy's headers would be reused + # against noble's without anything noticing. + # + # `$ImageOS` is `ubuntu22`/`ubuntu24`/`macos15`, so this invalidates the + # next runner move too, including the ones `-latest` makes without a + # commit. Named once, from the runner itself, rather than written down. + - name: name the runner image for the cache key + run: echo "IMAGE_OS=$ImageOS" >> "$GITHUB_ENV" + shell: bash + - name: cache rust dependencies uses: Swatinem/rust-cache@v2 with: workspaces: src-tauri - key: ${{ matrix.os-name }} + key: ${{ matrix.os-name }}-${{ env.IMAGE_OS }} save-if: ${{ github.ref == 'refs/heads/master' }} # Keyed on which entry this is, not on which Ubuntu it happens to be. diff --git a/scripts/releaseWorkflow.test.ts b/scripts/releaseWorkflow.test.ts index ec3623d9..e5d6d0fc 100644 --- a/scripts/releaseWorkflow.test.ts +++ b/scripts/releaseWorkflow.test.ts @@ -151,6 +151,29 @@ test('a step asks which matrix entry it is, not which runner it landed on', () = } }); +test('a cargo cache cannot outlive the runner image that filled it', () => { + // rust-cache's key ends `-Linux-x64`: `runner.os`, not the image. Measured on + // two runs of the same job, one per Ubuntu, the key was identical -- + // `v0-rust-linux-build-test-Linux-x64-e8b3ee54-a2c94f3a` -- so moving the + // runner would have restored a jammy-built target/ into a noble build as a + // full match. Cargo fingerprints do not track system libraries: the + // webkit2gtk-sys build script's probe of the wrong distribution's headers + // would have been reused with nothing to notice. + // + // `$ImageOS` comes from the runner, so this also covers the image changing + // under `macos-latest` and `windows-latest`, which happens without a commit. + for (const [name, source] of [ + ['test.yml', testWorkflow], + ['test_build.yml', testBuildWorkflow], + ] as const) { + const cacheStep = sliceFrom(source, 'uses: Swatinem/rust-cache@v2'); + const key = /^\s*key:\s*(.+)$/m.exec(cacheStep)?.[1]; + assert.ok(key, `${name} caches cargo without a key naming the runner image`); + assert.match(key, /env\.IMAGE_OS/, `${name}'s cache key does not change when the runner image does`); + assert.match(source, /IMAGE_OS=\$ImageOS/, `${name} never reads $ImageOS into the environment`); + } +}); + test('the updater feed publishes the keys an installed Markpad can ask for', () => { // tauri-plugin-updater tries `{os}-{arch}-{installer}` and then `{os}-{arch}`, // and only tries the first when the binary knows its own bundle type. Ours From bd9992bd81b2dfd563b58ff41bd5bbf3449a0832 Mon Sep 17 00:00:00 2001 From: PathGao <42336971+PathGao@users.noreply.github.com> Date: Tue, 11 Aug 2026 20:26:48 +0800 Subject: [PATCH 2/2] ci: install the six Linux packages a pull request proved the build needs (#581) Stacked on ci/one-linux-runner. build.yml asked apt for sixteen packages; test_build.yml asks for six and compiles the same tree and bundles the same three formats. Nothing said which was right, because build.yml only runs on workflow_dispatch -- its apt list is the one part of the Linux build no pull request has ever exercised. Six of the ten extras apt installs regardless: libwebkit2gtk-4.1-dev depends on libwebkit2gtk-4.1-0, libjavascriptcoregtk-4.1-dev, gir1.2-webkit2-4.1 and libgtk-3-dev, and those pull libjavascriptcoregtk-4.1-0 and gir1.2-javascriptcoregtk-4.1 behind them. The other four are libxcb1-dev, libxcb-render0-dev, libxcb-shape0-dev and libxcb-xfixes0-dev, plus xdg-utils -- none of which appear in Tauri v2's Debian/Ubuntu prerequisites. The evidence is not that reasoning. Run 31487547674 built the .deb, the .rpm and the AppImage on ubuntu-24.04 -- the release's own runner, after the change below this one -- with these six and nothing else, and uploaded 101 MB of bundles. Making the lists identical is the point rather than making one shorter. Both workflows compile and bundle, so any difference between them is a release depending on something nothing tested. An assertion holds them together, and holds test.yml to the same list minus the bundlers, since it compiles but never bundles. Checked by mutation in both directions: adding a package to build.yml fails it, and removing one from test.yml fails it. 977 pass. Co-authored-by: PathGao Co-authored-by: Claude Opus 5 --- .github/workflows/build.yml | 31 ++++++++++++------------- scripts/releaseWorkflow.test.ts | 40 +++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7f8d6f46..e57c3a4b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -197,29 +197,26 @@ jobs: - name: Install Linux dependencies if: matrix.os == 'linux' - # One list, one apt call. This was two `apt-get install` invocations - # naming 21 packages between them, five of them in both -- - # libappindicator3-dev, librsvg2-dev, patchelf, rpm and - # libwebkit2gtk-4.1-dev. Same 16 packages, said once. + # The same list test_build.yml installs, which is what makes it a + # check: a pull request now compiles and bundles against exactly the + # packages a release does. An assertion holds the two together. + # + # It was sixteen. Six of those apt installs anyway -- + # libwebkit2gtk-4.1-dev depends on libwebkit2gtk-4.1-0, + # libjavascriptcoregtk-4.1-dev, gir1.2-webkit2-4.1 and libgtk-3-dev, + # and those pull the remaining two -- and four libxcb *-dev packages + # and xdg-utils appear in no Tauri v2 prerequisite list. Run + # 31487547674 built the .deb, the .rpm and the AppImage on + # ubuntu-24.04 with the six below and nothing else. run: | sudo apt-get update sudo apt-get install -y \ - gir1.2-javascriptcoregtk-4.1 \ - gir1.2-webkit2-4.1 \ - libappindicator3-dev \ libgtk-3-dev \ - libjavascriptcoregtk-4.1-0 \ - libjavascriptcoregtk-4.1-dev \ - librsvg2-dev \ - libwebkit2gtk-4.1-0 \ libwebkit2gtk-4.1-dev \ - libxcb1-dev \ - libxcb-render0-dev \ - libxcb-shape0-dev \ - libxcb-xfixes0-dev \ + libappindicator3-dev \ + librsvg2-dev \ patchelf \ - rpm \ - xdg-utils + rpm - name: Install Frontend Dependencies run: npm ci diff --git a/scripts/releaseWorkflow.test.ts b/scripts/releaseWorkflow.test.ts index e5d6d0fc..14eff519 100644 --- a/scripts/releaseWorkflow.test.ts +++ b/scripts/releaseWorkflow.test.ts @@ -151,6 +151,46 @@ test('a step asks which matrix entry it is, not which runner it landed on', () = } }); +/** The packages an `apt-get install -y` line asks for, sorted. */ +function aptPackages(source: string): string[] { + const line = /apt-get install -y((?:[^\n]*\\\n)*[^\n]*)/.exec(source)?.[1] ?? ''; + return line + .split(/[\s\\]+/) + .filter(Boolean) + .sort(); +} + +test('a release installs the dependencies a pull request proved it can build with', () => { + // build.yml only runs on workflow_dispatch, so its apt list is the one part + // of the Linux build no pull request exercises -- it asked for sixteen + // packages against test_build.yml's six, and nothing said which was right. + // Six of the ten extras apt installs anyway as dependencies of + // libwebkit2gtk-4.1-dev; the four libxcb *-dev packages and xdg-utils are in + // no Tauri v2 prerequisite list. Run 31487547674 built the .deb, the .rpm and + // the AppImage on ubuntu-24.04 with six. + // + // One list rather than a shorter one is the point. Both workflows compile and + // bundle, so a difference between them is a release depending on something + // nothing tested — which is what this was. + assert.deepEqual( + aptPackages(sliceFrom(workflow, 'Install Linux dependencies')), + aptPackages(sliceFrom(testBuildWorkflow, 'install dependencies (ubuntu only)')), + 'the release build and the pull request build install different Linux packages', + ); + + // test.yml compiles but never bundles, so it needs everything above except + // the bundlers. Asserted as a subset rather than as its own list, so adding a + // dependency in one place cannot leave it behind. + const bundlers = new Set(['rpm']); + assert.deepEqual( + aptPackages(sliceFrom(testWorkflow, 'install dependencies (ubuntu only)')), + aptPackages(sliceFrom(testBuildWorkflow, 'install dependencies (ubuntu only)')).filter( + (p) => !bundlers.has(p), + ), + 'test.yml has drifted from the list the builds use', + ); +}); + test('a cargo cache cannot outlive the runner image that filled it', () => { // rust-cache's key ends `-Linux-x64`: `runner.os`, not the image. Measured on // two runs of the same job, one per Ubuntu, the key was identical --