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/.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..14eff519 100644 --- a/scripts/releaseWorkflow.test.ts +++ b/scripts/releaseWorkflow.test.ts @@ -151,6 +151,69 @@ 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 -- + // `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