From 1b0b287629e6e3b6d8eb6182db72b28fd0510574 Mon Sep 17 00:00:00 2001 From: _ov4 Date: Sun, 18 Oct 2026 05:41:23 +0000 Subject: fix(ci): publish nightly assets only after all builds succeed Co-Authored-By: Claude Opus 5.5 --- .github/workflows/nightly-build.yml | 105 ++++++++++++++++++++---------------- AGENTS/ARCH.md | 24 +++++---- README.md | 2 +- 3 files changed, 76 insertions(+), 55 deletions(-) diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index 0bbb358..049683c 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -21,27 +21,8 @@ defaults: shell: bash jobs: - clean: - name: Clear nightly attachments - runs-on: ubuntu-24.04 - steps: - - name: Delete all existing release assets - run: | - latest_sha=$(gh api "repos/$GH_REPO/git/ref/heads/main" --jq '.object.sha') - if [[ "$latest_sha" != "$GITHUB_SHA" ]]; then - echo "This push has been superseded; leaving the release untouched." - exit 1 - fi - release_id=$(gh api "repos/$GH_REPO/releases/tags/$CPSC_NIGHTLY_TAG" --jq '.id') - gh api --paginate "repos/$GH_REPO/releases/$release_id/assets?per_page=100" \ - --jq '.[].id' > "$RUNNER_TEMP/nightly-asset-ids" - while IFS= read -r asset_id; do - gh api --method DELETE "repos/$GH_REPO/releases/assets/$asset_id" - done < "$RUNNER_TEMP/nightly-asset-ids" - compiler: name: Linux x86_64 compiler - needs: clean runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v7.0.1 @@ -122,17 +103,15 @@ jobs: done - name: Upload the compiler package - run: | - latest_sha=$(gh api "repos/$GH_REPO/git/ref/heads/main" --jq '.object.sha') - if [[ "$latest_sha" != "$GITHUB_SHA" ]]; then - echo "This push has been superseded; skipping upload." - exit 0 - fi - gh release upload "$CPSC_NIGHTLY_TAG" build/cpsc-*-Linux-x86_64.tar.xz --clobber + uses: actions/upload-artifact@v7.0.2 + with: + name: nightly-linux + path: build/cpsc-*-Linux-x86_64.tar.xz + if-no-files-found: error + retention-days: 1 macos: name: macOS arm64 compiler - needs: clean runs-on: macos-15 steps: - uses: actions/checkout@v7.0.1 @@ -231,17 +210,15 @@ jobs: done < <(otool -L "$RUNNER_TEMP/nightly-smoke.out" | tail -n +2 | awk '{print $1}') - name: Upload the macOS package - run: | - latest_sha=$(gh api "repos/$GH_REPO/git/ref/heads/main" --jq '.object.sha') - if [[ "$latest_sha" != "$GITHUB_SHA" ]]; then - echo "This push has been superseded; skipping upload." - exit 0 - fi - gh release upload "$CPSC_NIGHTLY_TAG" build/cpsc-*-Darwin-arm64.tar.xz --clobber + uses: actions/upload-artifact@v7.0.2 + with: + name: nightly-macos + path: build/cpsc-*-Darwin-arm64.tar.xz + if-no-files-found: error + retention-days: 1 windows: name: Windows x86_64 compiler - needs: clean runs-on: windows-2025 steps: - uses: actions/checkout@v7.0.1 @@ -372,17 +349,15 @@ jobs: } - name: Upload the Windows package - run: | - latest_sha=$(gh api "repos/$GH_REPO/git/ref/heads/main" --jq '.object.sha') - if [[ "$latest_sha" != "$GITHUB_SHA" ]]; then - echo "This push has been superseded; skipping upload." - exit 0 - fi - gh release upload "$CPSC_NIGHTLY_TAG" build/cpsc-*-Windows-*.zip --clobber + uses: actions/upload-artifact@v7.0.2 + with: + name: nightly-windows + path: build/cpsc-*-Windows-*.zip + if-no-files-found: error + retention-days: 1 plugin: name: VS Code extension - needs: clean runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v7.0.1 @@ -431,10 +406,50 @@ jobs: PY - name: Upload the VSIX + uses: actions/upload-artifact@v7.0.2 + with: + name: nightly-plugin + path: plugin/*.vsix + if-no-files-found: error + retention-days: 1 + + publish: + name: Update nightly attachments + needs: [compiler, macos, windows, plugin] + runs-on: ubuntu-24.04 + steps: + - name: Download all release packages + uses: actions/download-artifact@v8 + with: + pattern: nightly-* + path: release-packages + + - name: Validate and publish all release packages run: | + shopt -s nullglob + linux=(release-packages/nightly-linux/cpsc-*-Linux-x86_64.tar.xz) + macos=(release-packages/nightly-macos/cpsc-*-Darwin-arm64.tar.xz) + windows=(release-packages/nightly-windows/cpsc-*-Windows-*.zip) + plugin=(release-packages/nightly-plugin/*.vsix) + for count in "${#linux[@]}" "${#macos[@]}" "${#windows[@]}" "${#plugin[@]}"; do + if [[ "$count" -ne 1 ]]; then + echo "Expected exactly one package for each of the four builds; leaving the release untouched." >&2 + exit 1 + fi + done + packages=("${linux[@]}" "${macos[@]}" "${windows[@]}" "${plugin[@]}") + for package in "${packages[@]}"; do + test -s "$package" + done latest_sha=$(gh api "repos/$GH_REPO/git/ref/heads/main" --jq '.object.sha') if [[ "$latest_sha" != "$GITHUB_SHA" ]]; then - echo "This push has been superseded; skipping upload." + echo "This push has been superseded; leaving the release untouched." exit 0 fi - gh release upload "$CPSC_NIGHTLY_TAG" plugin/*.vsix --clobber + release_id=$(gh api "repos/$GH_REPO/releases/tags/$CPSC_NIGHTLY_TAG" --jq '.id') + gh api --paginate "repos/$GH_REPO/releases/$release_id/assets?per_page=100" \ + --jq '.[].id' > "$RUNNER_TEMP/nightly-asset-ids" + while IFS= read -r asset_id; do + gh api --method DELETE "repos/$GH_REPO/releases/assets/$asset_id" + done < "$RUNNER_TEMP/nightly-asset-ids" + gh release upload "$CPSC_NIGHTLY_TAG" "${packages[@]}" --clobber diff --git a/AGENTS/ARCH.md b/AGENTS/ARCH.md index 2e122a5..0ffe875 100644 --- a/AGENTS/ARCH.md +++ b/AGENTS/ARCH.md @@ -264,15 +264,17 @@ existing terminator), and an explicit `llvm::Triple` for the embedded target. ## Nightly builds `.github/workflows/nightly-build.yml` runs on every push to `main`, without path -filters. A shared concurrency group cancels superseded runs. The cleanup job -requires the existing `nightly-build` release and deletes all uploaded assets, -including paginated results, before either build starts. It never creates or -deletes the release and preserves its tag, title and notes. GitHub's generated +filters. A shared concurrency group cancels superseded runs. A final publish job +requires all four build jobs to succeed before updating the existing `nightly-build` +release. Any build or check failure leaves all previous attachments untouched. +The publish job deletes all old uploaded assets, including paginated results, +then uploads the four new packages. It never creates or deletes the release +and preserves its tag, title and notes. GitHub's generated source downloads remain available. The Linux compiler and plugin jobs use Ubuntu 24.04 x86_64; the macOS compiler job uses a macOS 15 arm64 runner, and Windows uses Windows Server 2025 x86_64. -All four check out the push SHA and run independently after cleanup. All three +All four check out the push SHA and run independently. All three compiler jobs use LLVM/Clang/LLD 23, Python (build-time), Release and EMBEDDED settings, Pandoc, and the existing `release` target. Linux obtains its toolchain from apt.llvm.org; macOS uses Homebrew's LLVM 23 and builds matching static LLD with the helper above. Windows uses `msys2/setup-msys2@v2` with MINGW64, @@ -284,13 +286,17 @@ packaged binary and checks native generation plus `.ll` output without tool look Windows system directories, excluding MSYS2 and LLVM. The macOS check also verifies arm64 and rejects non-system dynamic libraries. The plugin uses Node.js 22, `npm ci --ignore-scripts` and -`npm run package`, then checks VSIX contents. Each job uploads directly after -its own checks pass; failure in one does not block the others. Compiler jobs enable +`npm run package`, then checks VSIX contents. Each job retains its checked package +as a separate Actions artifact for one day, failing if the package is missing. +Compiler jobs enable BUILD_TESTING, install Perl for the tests and run all CTest cases with the push SHA as seed before their package smoke checks and uploads. Failed tests retain inputs and logs as Actions artifacts for 14 days; test summaries appear even on failure. See TESTING.md. -Cleanup and uploads check main's current SHA to avoid modifying nightly for a -superseded push. The workflow uses `GITHUB_TOKEN` with `contents: write`. +The publish job downloads only the current run's `nightly-*` artifacts and checks +that exactly one nonempty package exists for each build before any release API +calls. It then checks main's current SHA to avoid modifying nightly for a +superseded push. Release upload failures do not roll back deleted assets. +The workflow uses `GITHUB_TOKEN` with `contents: write`. Compiler package names retain the existing commit-based version; the VSIX uses `plugin/package.json`'s version. Before committing workflow changes, run actionlint, simulate release API calls, and smoke-test the packages locally; diff --git a/README.md b/README.md index 0854012..3fd72ac 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A compiler for CIE Pseudocode (A Level 9618 Computer Science) [Compiler tests](tests/README.md) -Download the Linux x86_64, Windows x86_64 or macOS 15+ Apple Silicon compiler package and VS Code extension from [Nightly Build](https://github.com/ovfor4/cie-pseudocode-compiler/releases/tag/nightly-build). The compiler uses EMBEDDED mode and needs no LLVM installation. Each push to `main` removes the previous attachments before rebuilding all four packages; each package appears when its build and checks finish. +Download the Linux x86_64, Windows x86_64 or macOS 15+ Apple Silicon compiler package and VS Code extension from [Nightly Build](https://github.com/ovfor4/cie-pseudocode-compiler/releases/tag/nightly-build). The compiler uses EMBEDDED mode and needs no LLVM installation. Each push to `main` builds all four packages in parallel. Release attachments are updated only after all four builds and their checks succeed; any build or check failure keeps all previous attachments. Details can be found here: [Pseudocode Guide for Teachers](https://www.cambridgeinternational.org/Images/697401-2026-pseudocode-guide-for-teachers.pdf) -- cgit v1.3.1-4-g156e