From 759a13c9cfe8ca764087e1eca90cbcf30d99e279 Mon Sep 17 00:00:00 2001 From: kylebd99 Date: Sun, 27 Sep 2026 14:48:12 -0400 Subject: [PATCH] Harden the sysimage workflow The ubuntu-latest build on main died when the runner VM shut down during PackageCompiler's final compile, most likely out of memory. - Add a 24 GB swap file on Linux runners before building. - Log memory, swap and disk every minute during the build; step logs stream live, so this survives a runner dying. - Skip the build when the release already has this platform's image. scripts/sysimage/info.py resolves the environment and prints the image name, which takes minutes where a build takes hours. - Add a `publish` input to manual runs, so the build can be tested without publishing a release. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/sysimage.yml | 46 +++++++++++++++++++++++++++++++++- scripts/sysimage/info.py | 32 +++++++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 scripts/sysimage/info.py diff --git a/.github/workflows/sysimage.yml b/.github/workflows/sysimage.yml index fa246cb..206ccf8 100644 --- a/.github/workflows/sysimage.yml +++ b/.github/workflows/sysimage.yml @@ -5,6 +5,11 @@ name: Sysimage # environment. Users download it with `pixi run fetch-sysimage`. on: workflow_dispatch: + inputs: + publish: + description: Publish the images to the release (off to only test the build) + type: boolean + default: true push: branches: - main @@ -36,10 +41,49 @@ jobs: - uses: prefix-dev/setup-pixi@v0.10.0 with: pixi-version: v0.72.2 + # Resolving the Julia environment is needed to name the image anyway, and + # takes minutes where a build takes hours, so check the release first. + - name: Check for a published image + id: info + env: + GH_TOKEN: ${{ github.token }} + run: | + pixi run python scripts/sysimage/info.py + . <(grep -E '^(tag|name)=' "$GITHUB_OUTPUT" | tail -2) + if gh release view "$tag" --json assets --jq '.assets[].name' 2>/dev/null \ + | grep -qxF "$name"; then + echo "$name is already published in $tag; skipping the build." + echo "exists=true" >> "$GITHUB_OUTPUT" + fi + # PackageCompiler's final compile needs more memory than the hosted runners + # have (the ubuntu-latest VM died without it), so give Linux swap on /mnt. + - name: Add swap + if: runner.os == 'Linux' && steps.info.outputs.exists != 'true' + run: | + sudo fallocate -l 24G /mnt/swapfile + sudo chmod 600 /mnt/swapfile + sudo mkswap /mnt/swapfile + sudo swapon /mnt/swapfile + free -h - name: Build sysimage id: build - run: pixi run build-sysimage + if: steps.info.outputs.exists != 'true' + run: | + # Log memory and disk every minute. Step logs stream live, so this + # survives even if the runner VM dies mid-build. + ( + while sleep 60; do + case "$RUNNER_OS" in + Linux) free -m | awk '/Mem|Swap/ {printf "[resources] %s used=%sMB free=%sMB ", $1, $3, $4}' ;; + macOS) memory_pressure | grep -i "free percentage" | sed 's/^/[resources] /' | tr '\n' ' ' ;; + esac + df -h . | awk 'NR==2 {print "disk avail=" $4}' + done + ) & + pixi run build-sysimage - name: Publish to release + # pushes to main always publish; manual runs only when asked to + if: steps.info.outputs.exists != 'true' && (github.event_name == 'push' || inputs.publish) env: GH_TOKEN: ${{ github.token }} TAG: ${{ steps.build.outputs.tag }} diff --git a/scripts/sysimage/info.py b/scripts/sysimage/info.py new file mode 100644 index 0000000..4219922 --- /dev/null +++ b/scripts/sysimage/info.py @@ -0,0 +1,32 @@ +"""Print the sysimage key, release tag and asset name for this environment. + +In GitHub Actions they are also written to ``$GITHUB_OUTPUT`` as ``key``, +``tag`` and ``name``. +""" + +import os +import sys +from pathlib import Path + +HERE = Path(__file__).resolve().parent +sys.path.insert(0, str(HERE)) + +from _load import sysimage # noqa: E402 + + +def main() -> None: + key = sysimage.current_key() + info = { + "key": key, + "tag": sysimage.release_tag(key), + "name": sysimage.image_name(key), + } + for k, v in info.items(): + print(f"{k}={v}") + if github_output := os.environ.get("GITHUB_OUTPUT"): + with open(github_output, "a") as f: + f.writelines(f"{k}={v}\n" for k, v in info.items()) + + +if __name__ == "__main__": + main()