name: Nightly build on: push: branches: [main] permissions: contents: write concurrency: group: nightly-build cancel-in-progress: true env: GH_TOKEN: ${{ github.token }} GH_REPO: ${{ github.repository }} CPSC_NIGHTLY_TAG: nightly-build defaults: run: shell: bash jobs: compiler: name: Linux x86_64 compiler runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v7.0.1 with: ref: ${{ github.sha }} fetch-depth: 0 persist-credentials: false - name: Install LLVM 23 and packaging dependencies run: | curl -fsSL https://apt.llvm.org/llvm-snapshot.gpg.key \ | gpg --dearmor \ | sudo tee /usr/share/keyrings/cpsc-llvm.gpg > /dev/null printf '%s\n' \ 'deb [signed-by=/usr/share/keyrings/cpsc-llvm.gpg] https://apt.llvm.org/noble/ llvm-toolchain-noble-23 main' \ | sudo tee /etc/apt/sources.list.d/cpsc-llvm.list > /dev/null sudo apt-get update sudo apt-get install -y --no-install-recommends \ build-essential cmake clang-23 llvm-23-dev lld-23 \ libclang-23-dev liblld-23-dev libpolly-23-dev perl \ zlib1g-dev libzstd-dev libncurses-dev libedit-dev pandoc \ libwebkit2gtk-4.1-dev libayatana-appindicator3-dev \ libxdo-dev librsvg2-dev patchelf - name: Build the embedded release package run: | cmake -S . -B build \ -DCMAKE_BUILD_TYPE=Release -DCPSC_RUN_MODE=EMBEDDED \ -DBUILD_TESTING=ON -DCPSC_TEST_SEED="$GITHUB_SHA" \ -DCMAKE_C_COMPILER=clang-23 -DCMAKE_CXX_COMPILER=clang++-23 \ -DLLVM_DIR=/usr/lib/llvm-23/lib/cmake/llvm cmake --build build --parallel 4 cmake --build build --target release --parallel 4 - name: Test compiler semantics, differential output and diagnostics id: compiler-tests run: ctest --test-dir build --output-on-failure --parallel 2 - name: Summarize compiler tests if: ${{ !cancelled() && (steps.compiler-tests.outcome == 'success' || steps.compiler-tests.outcome == 'failure') }} run: perl tests/summarize.pl build/test-results - name: Retain failed compiler test inputs and logs if: ${{ failure() && steps.compiler-tests.outcome == 'failure' }} uses: actions/upload-artifact@v7.0.2 with: name: compiler-tests-${{ runner.os }}-${{ runner.arch }}-${{ github.sha }} path: | build/test-results/ build/Testing/Temporary/ retention-days: 14 - name: Smoke test the packaged compiler run: | packages=(build/cpsc-*-Linux-x86_64.tar.xz) test "${#packages[@]}" -eq 1 mkdir -p "$RUNNER_TEMP/cpsc-smoke" tar -xJf "${packages[0]}" -C "$RUNNER_TEMP/cpsc-smoke" package_name=$(basename "${packages[0]}" .tar.xz) package_dir="$RUNNER_TEMP/cpsc-smoke/$package_name" test -x "$package_dir/cpsc" test -f "$package_dir/install.sh" test -f "$package_dir/LICENSE" test -f "$package_dir/doc/cpsc.1" test -f "$package_dir/doc/language/cpsc-language-overview.7" printf '%s\n' 'OUTPUT "nightly smoke"' > "$RUNNER_TEMP/nightly-smoke.cps" output=$(env -i PATH=/usr/bin:/bin "$package_dir/cpsc" "$RUNNER_TEMP/nightly-smoke.cps" -r) test "$output" = 'nightly smoke' env -i PATH=/nonexistent "$package_dir/cpsc" "$RUNNER_TEMP/nightly-smoke.cps" output=$("$RUNNER_TEMP/nightly-smoke.out") test "$output" = 'nightly smoke' env -i PATH=/nonexistent "$package_dir/cpsc" "$RUNNER_TEMP/nightly-smoke.cps" -S grep -q '^define i32 @main' "$RUNNER_TEMP/nightly-smoke.ll" for binary in "$package_dir/cpsc" "$RUNNER_TEMP/nightly-smoke.out"; do while IFS= read -r library; do case "$library" in libc.so.*|libm.so.*|libmvec.so.*|ld-linux*.so.*) ;; *) echo "Unexpected runtime dependency: $library" >&2; exit 1 ;; esac done < <(readelf -d "$binary" | sed -n 's/.*Shared library: \[\(.*\)\].*/\1/p') done rm -rf "$RUNNER_TEMP/install-home" "$RUNNER_TEMP/cpsc-install" mkdir -p "$RUNNER_TEMP/install-home" HOME="$RUNNER_TEMP/install-home" "$package_dir/install.sh" --prefix "$RUNNER_TEMP/cpsc-install" test -x "$RUNNER_TEMP/cpsc-install/bin/cpsc" test -f "$RUNNER_TEMP/cpsc-install/share/cpsc/man/man1/cpsc.1" test -f "$RUNNER_TEMP/cpsc-install/share/cpsc/man/man7/cpsc-language-overview.7" output=$(env -i PATH=/usr/bin:/bin "$RUNNER_TEMP/cpsc-install/bin/cpsc" "$RUNNER_TEMP/nightly-smoke.cps" -r) test "$output" = 'nightly smoke' - uses: actions/setup-node@v7.1.0 with: node-version: '22' - uses: dtolnay/rust-toolchain@stable - name: Compile and check the Linux desktop app without publishing it working-directory: bundle run: | npm ci --ignore-scripts npm run prepare:compiler npm run tauri -- build --no-bundle cargo test --release --locked --manifest-path src-tauri/Cargo.toml perl scripts/smoke-package.pl src-tauri/target/release - name: Upload the compiler package 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 runs-on: macos-15 steps: - uses: actions/checkout@v7.0.1 with: ref: ${{ github.sha }} fetch-depth: 0 persist-credentials: false - name: Install LLVM 23 and packaging dependencies run: | test "$(uname -m)" = arm64 brew update brew install llvm@23 pandoc zlib zstd ncurses perl brew upgrade llvm@23 llvm_prefix=$(brew --prefix llvm@23) test -x "$llvm_prefix/bin/clang++" "$llvm_prefix/bin/llvm-nm" --defined-only --demangle \ "$llvm_prefix/lib/libclangDriver.a" > "$RUNNER_TEMP/clang-driver-symbols.txt" if ! grep -F 'clang::driver::tools::AMDGPU::setArchNameInTriple(' "$RUNNER_TEMP/clang-driver-symbols.txt"; then echo 'LLVM static archive is incomplete; install a Homebrew bottle with the duplicate-member fix.' >&2 exit 1 fi - name: Identify the static LLD toolchain id: lld-cache-key run: | llvm_prefix=$(brew --prefix llvm@23) llvm_version=$("$llvm_prefix/bin/llvm-config" --version) { printf '%s\n' "${ImageVersion:?}" "$llvm_prefix" "$llvm_version" "$PWD" brew list --versions llvm@23 xcrun --show-sdk-path xcrun --show-sdk-version xcrun --show-sdk-build-version shasum -a 256 "$llvm_prefix/bin/clang" "$llvm_prefix/lib/libclangDriver.a" \ "$llvm_prefix"/lib/libLLVM*.dylib } > "$RUNNER_TEMP/static-lld-toolchain.txt" fingerprint=$(shasum -a 256 "$RUNNER_TEMP/static-lld-toolchain.txt" | cut -d ' ' -f 1) echo "fingerprint=$fingerprint" >> "$GITHUB_OUTPUT" echo "path=build/static-lld-$llvm_version" >> "$GITHUB_OUTPUT" - name: Cache the matching static LLD build uses: actions/cache@v6.1.0 with: path: ${{ steps.lld-cache-key.outputs.path }} key: ${{ runner.os }}-${{ runner.arch }}-static-lld-v1-${{ steps.lld-cache-key.outputs.fingerprint }}-${{ hashFiles('tools/build-static-lld.sh') }} - name: Build matching static LLD libraries run: | sh tools/build-static-lld.sh "$(brew --prefix llvm@23)" "$PWD/build" 3 - name: Build the embedded release package run: | llvm_prefix=$(brew --prefix llvm@23) zlib_prefix=$(brew --prefix zlib) zstd_prefix=$(brew --prefix zstd) ncurses_prefix=$(brew --prefix ncurses) cmake -S . -B build \ -DCMAKE_BUILD_TYPE=Release -DCPSC_RUN_MODE=EMBEDDED \ -DBUILD_TESTING=ON -DCPSC_TEST_SEED="$GITHUB_SHA" \ -DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_DEPLOYMENT_TARGET=15.0 \ -DCMAKE_OSX_SYSROOT="$(xcrun --show-sdk-path)" \ -DCMAKE_C_COMPILER="$llvm_prefix/bin/clang" \ -DCMAKE_CXX_COMPILER="$llvm_prefix/bin/clang++" \ -DLLVM_DIR="$llvm_prefix/lib/cmake/llvm" \ -DCMAKE_PREFIX_PATH="$llvm_prefix;$zlib_prefix;$zstd_prefix;$ncurses_prefix" cmake --build build --parallel 3 cmake --build build --target release --parallel 3 - name: Test compiler semantics, differential output and diagnostics id: compiler-tests run: ctest --test-dir build --output-on-failure --parallel 2 - name: Summarize compiler tests if: ${{ !cancelled() && (steps.compiler-tests.outcome == 'success' || steps.compiler-tests.outcome == 'failure') }} run: perl tests/summarize.pl build/test-results - name: Retain failed compiler test inputs and logs if: ${{ failure() && steps.compiler-tests.outcome == 'failure' }} uses: actions/upload-artifact@v7.0.2 with: name: compiler-tests-${{ runner.os }}-${{ runner.arch }}-${{ github.sha }} path: | build/test-results/ build/Testing/Temporary/ retention-days: 14 - name: Smoke test the packaged compiler run: | packages=(build/cpsc-*-Darwin-arm64.tar.xz) test "${#packages[@]}" -eq 1 mkdir -p "$RUNNER_TEMP/cpsc-smoke" tar -xJf "${packages[0]}" -C "$RUNNER_TEMP/cpsc-smoke" package_name=$(basename "${packages[0]}" .tar.xz) package_dir="$RUNNER_TEMP/cpsc-smoke/$package_name" test -x "$package_dir/cpsc" test -f "$package_dir/install.sh" test -f "$package_dir/LICENSE" test -f "$package_dir/doc/cpsc.1" test -f "$package_dir/doc/language/cpsc-language-overview.7" test "$(lipo -archs "$package_dir/cpsc")" = arm64 while IFS= read -r library; do case "$library" in /usr/lib/*|/System/Library/*|/Library/Apple/System/Library/*) ;; *) echo "Unexpected runtime dependency: $library" >&2; exit 1 ;; esac done < <(otool -L "$package_dir/cpsc" | tail -n +2 | awk '{print $1}') printf '%s\n' 'OUTPUT "nightly smoke"' > "$RUNNER_TEMP/nightly-smoke.cps" output=$(env -i PATH=/usr/bin:/bin "$package_dir/cpsc" "$RUNNER_TEMP/nightly-smoke.cps" -r) test "$output" = 'nightly smoke' env -i PATH=/nonexistent "$package_dir/cpsc" "$RUNNER_TEMP/nightly-smoke.cps" output=$("$RUNNER_TEMP/nightly-smoke.out") test "$output" = 'nightly smoke' env -i PATH=/nonexistent "$package_dir/cpsc" "$RUNNER_TEMP/nightly-smoke.cps" -S grep -q '^define i32 @main' "$RUNNER_TEMP/nightly-smoke.ll" while IFS= read -r library; do case "$library" in /usr/lib/*|/System/Library/*|/Library/Apple/System/Library/*) ;; *) echo "Unexpected program dependency: $library" >&2; exit 1 ;; esac done < <(otool -L "$RUNNER_TEMP/nightly-smoke.out" | tail -n +2 | awk '{print $1}') rm -rf "$RUNNER_TEMP/install-home" "$RUNNER_TEMP/cpsc-install" mkdir -p "$RUNNER_TEMP/install-home" HOME="$RUNNER_TEMP/install-home" "$package_dir/install.sh" --prefix "$RUNNER_TEMP/cpsc-install" test -x "$RUNNER_TEMP/cpsc-install/bin/cpsc" test -f "$RUNNER_TEMP/cpsc-install/share/cpsc/man/man1/cpsc.1" test -f "$RUNNER_TEMP/cpsc-install/share/cpsc/man/man7/cpsc-language-overview.7" output=$(env -i PATH=/usr/bin:/bin "$RUNNER_TEMP/cpsc-install/bin/cpsc" "$RUNNER_TEMP/nightly-smoke.cps" -r) test "$output" = 'nightly smoke' - uses: actions/setup-node@v7.1.0 with: node-version: '22' - uses: dtolnay/rust-toolchain@stable - name: Cache desktop Rust dependencies uses: Swatinem/rust-cache@v2.9.3 with: workspaces: bundle/src-tauri -> target cache-on-failure: true - name: Build and test the macOS desktop package working-directory: bundle run: | npm ci --ignore-scripts npm run prepare:compiler npm run package cargo test --release --locked --manifest-path src-tauri/Cargo.toml - name: Smoke test the macOS desktop archive run: | packages=(bundle/artifacts/cpsc-bundle-*-Darwin-arm64.tar.xz) test "${#packages[@]}" -eq 1 smoke_dir="$RUNNER_TEMP/cpsc-bundle-smoke" mkdir -p "$smoke_dir" tar -xJf "${packages[0]}" -C "$smoke_dir" perl bundle/scripts/smoke-package.pl "$smoke_dir/CPSC Bundle.app" test "$(lipo -archs "$smoke_dir/CPSC Bundle.app/Contents/MacOS/cpsc-bundle")" = arm64 test "$(lipo -archs "$smoke_dir/CPSC Bundle.app/Contents/MacOS/cpsc")" = arm64 - name: Upload the macOS desktop package uses: actions/upload-artifact@v7.0.2 with: name: nightly-bundle-macos path: bundle/artifacts/cpsc-bundle-*-Darwin-arm64.tar.xz if-no-files-found: error retention-days: 1 - name: Upload the macOS package 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 runs-on: windows-2025 steps: - uses: actions/checkout@v7.0.1 with: ref: ${{ github.sha }} fetch-depth: 0 persist-credentials: false - name: Install LLVM 23 and build dependencies uses: msys2/setup-msys2@v2 with: msystem: MINGW64 update: true install: >- curl git perl mingw-w64-x86_64-cmake mingw-w64-x86_64-make mingw-w64-x86_64-clang mingw-w64-x86_64-clang-tools-extra mingw-w64-x86_64-llvm mingw-w64-x86_64-lld mingw-w64-x86_64-libffi mingw-w64-x86_64-libxml2 mingw-w64-x86_64-libiconv mingw-w64-x86_64-zlib mingw-w64-x86_64-zstd - name: Download Pandoc for Windows shell: msys2 {0} run: | mkdir -p build/deps/packages curl -fL --retry 3 \ -o build/deps/packages/pandoc-3.12.1-windows-x86_64.zip \ https://github.com/jgm/pandoc/releases/download/3.12.1/pandoc-3.12.1-windows-x86_64.zip - name: Build the embedded release package shell: msys2 {0} run: | test -x /c/Strawberry/perl/bin/perl.exe cmake -S . -B build -G "MinGW Makefiles" \ -DCMAKE_BUILD_TYPE=Release -DCPSC_RUN_MODE=EMBEDDED \ -DBUILD_TESTING=ON -DCPSC_TEST_SEED="$GITHUB_SHA" \ -DPERL_EXECUTABLE="C:/Strawberry/perl/bin/perl.exe" \ -DCMAKE_C_COMPILER="$(cygpath -m /mingw64/bin/clang.exe)" \ -DCMAKE_CXX_COMPILER="$(cygpath -m /mingw64/bin/clang++.exe)" \ -DLLVM_DIR="$(cygpath -m /mingw64/lib/cmake/llvm)" \ -DCMAKE_PREFIX_PATH="$(cygpath -m /mingw64)" cmake --build build --parallel 4 cmake --build build --target release --parallel 4 - name: Check Windows runtime dependencies shell: msys2 {0} run: | llvm-readobj --coff-imports build/cpsc.exe > build/windows-imports.txt imports=$(sed -n 's/^ Name: //p' build/windows-imports.txt | tr -d '\r') test -n "$imports" windows_root=$(cygpath -u "$SYSTEMROOT") while IFS= read -r library; do echo "Runtime dependency: $library" case "${library,,}" in api-ms-win-*.dll|ext-ms-win-*.dll) ;; *) if [[ ! -f "$windows_root/System32/$library" ]]; then echo "Unexpected runtime dependency: $library" >&2 exit 1 fi ;; esac done <<< "$imports" - name: Test compiler semantics, differential output and diagnostics id: compiler-tests shell: msys2 {0} run: ctest --test-dir build --output-on-failure --parallel 2 - name: Summarize compiler tests if: ${{ !cancelled() && (steps.compiler-tests.outcome == 'success' || steps.compiler-tests.outcome == 'failure') }} shell: msys2 {0} run: perl tests/summarize.pl build/test-results - name: Retain failed compiler test inputs and logs if: ${{ failure() && steps.compiler-tests.outcome == 'failure' }} uses: actions/upload-artifact@v7.0.2 with: name: compiler-tests-${{ runner.os }}-${{ runner.arch }}-${{ github.sha }} path: | build/test-results/ build/Testing/Temporary/ retention-days: 14 - name: Smoke test the packaged compiler shell: pwsh run: | $packages = @(Get-ChildItem -Path build/cpsc-*-Windows-*.zip -File) if ($packages.Count -ne 1) { throw 'Expected one Windows compiler package' } $smokeDir = Join-Path $env:RUNNER_TEMP 'cpsc-smoke' Expand-Archive -LiteralPath $packages[0].FullName -DestinationPath $smokeDir $packageDir = Join-Path $smokeDir $packages[0].BaseName foreach ($file in @('cpsc.exe', 'install.cmd', 'LICENSE', 'doc/cpsc.html', 'doc/language/cpsc-language-overview.html')) { if (-not (Test-Path -LiteralPath (Join-Path $packageDir $file) -PathType Leaf)) { throw "Missing package file: $file" } } $source = Join-Path $env:RUNNER_TEMP 'nightly-smoke.cps' Set-Content -LiteralPath $source -Value 'OUTPUT "nightly smoke"' -Encoding ascii $savedPath = $env:PATH try { $env:PATH = "$env:SystemRoot\System32;$env:SystemRoot" $output = & (Join-Path $packageDir 'cpsc.exe') $source -r if ($LASTEXITCODE -ne 0) { throw "Compiler smoke test failed: $LASTEXITCODE" } if (($output -join "`n") -cne 'nightly smoke') { throw "Unexpected output: $output" } $native = Join-Path $env:RUNNER_TEMP 'nightly-smoke.exe' & (Join-Path $packageDir 'cpsc.exe') $source -o $native if ($LASTEXITCODE -ne 0) { throw "Native compilation failed: $LASTEXITCODE" } $output = & $native if ($LASTEXITCODE -ne 0) { throw "Native program failed: $LASTEXITCODE" } if (($output -join "`n") -cne 'nightly smoke') { throw "Unexpected native output: $output" } & (Join-Path $packageDir 'cpsc.exe') $source -S if ($LASTEXITCODE -ne 0) { throw "IR compilation failed: $LASTEXITCODE" } $ir = [System.IO.Path]::ChangeExtension($source, '.ll') if (-not (Select-String -LiteralPath $ir -Pattern '^define i32 @main' -Quiet)) { throw 'Missing LLVM IR main function' } } finally { $env:PATH = $savedPath } - name: Smoke test the Windows package installer shell: cmd run: | set "PKGZIP=" for %%F in (build\cpsc-*-Windows-*.zip) do set "PKGZIP=%%~nF" if not defined PKGZIP exit /b 1 set "PKGDIR=%RUNNER_TEMP%\cpsc-smoke\%PKGZIP%" set "DEST=%RUNNER_TEMP%\cpsc install (smoke)" if exist "%DEST%" rmdir /s /q "%DEST%" call "%PKGDIR%\install.cmd" --no-such-option if not errorlevel 1 exit /b 1 call "%PKGDIR%\install.cmd" --prefix "%DEST%" if errorlevel 1 exit /b 1 call "%PKGDIR%\install.cmd" --prefix "%DEST%" if errorlevel 1 exit /b 1 if not exist "%DEST%\cpsc.exe" exit /b 1 if not exist "%DEST%\doc\cpsc.html" exit /b 1 if not exist "%DEST%\doc\language\cpsc-language-overview.html" exit /b 1 if not exist "%DEST%\doc\language\keyword\cpsc-keyword-if.html" exit /b 1 if not exist "%APPDATA%\Microsoft\Windows\Start Menu\Programs\cpsc\cpsc Help.lnk" exit /b 1 reg query HKCU\Environment /v Path | find /i "%DEST%" if errorlevel 1 exit /b 1 reg query HKCU\Environment /v Path | find /i "%DEST%;%DEST%" if not errorlevel 1 exit /b 1 "%DEST%\cpsc.exe" "%RUNNER_TEMP%\nightly-smoke.cps" -r | find "nightly smoke" if errorlevel 1 exit /b 1 - name: Install the Windows build tree with CMake shell: msys2 {0} run: | prefix="$(cygpath -m "$RUNNER_TEMP")/cpsc cmake install" cmake --install build --prefix "$prefix" test -f "$prefix/cpsc.exe" test -f "$prefix/doc/cpsc.html" - uses: actions/setup-node@v7.1.0 with: node-version: '22' - uses: dtolnay/rust-toolchain@stable - name: Cache desktop Rust dependencies uses: Swatinem/rust-cache@v2.9.3 with: workspaces: bundle/src-tauri -> target cache-on-failure: true - name: Build and test the Windows desktop package working-directory: bundle run: | npm ci --ignore-scripts npm run prepare:compiler npm run package cargo test --release --locked --manifest-path src-tauri/Cargo.toml - name: Smoke test the portable Windows application shell: pwsh run: | $packages = @(Get-ChildItem -Path bundle/artifacts/cpsc-bundle-*-Windows-x86_64.exe -File) if ($packages.Count -ne 1) { throw 'Expected one portable Windows executable' } & C:\Strawberry\perl\bin\perl.exe bundle/scripts/smoke-package.pl $packages[0].FullName if ($LASTEXITCODE -ne 0) { throw 'Desktop package smoke test failed' } - name: Upload the Windows desktop package uses: actions/upload-artifact@v7.0.2 with: name: nightly-bundle-windows path: bundle/artifacts/cpsc-bundle-*-Windows-x86_64.exe if-no-files-found: error retention-days: 1 - name: Upload the Windows package 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 runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v7.0.1 with: ref: ${{ github.sha }} fetch-depth: 0 persist-credentials: false - uses: actions/setup-node@v7.1.0 with: node-version: '22' - name: Install development dependencies working-directory: plugin run: npm ci --ignore-scripts - name: Check, test and package the extension working-directory: plugin run: npm run package - name: Check VSIX contents run: | python3 - <<'PY' from pathlib import Path from zipfile import ZipFile packages = list(Path("plugin").glob("*.vsix")) assert len(packages) == 1, "Expected one VSIX package" with ZipFile(packages[0]) as package: names = set(package.namelist()) required = { "extension/package.json", "extension/src/extension.js", "extension/src/indentation.js", "extension/src/lexical.js", "extension/src/brackets.js", "extension/src/runner.js", "extension/syntaxes/cie-pseudocode.tmLanguage.json", "extension/language-configuration.json", "extension/readme.md", "extension/LICENSE.txt", "extension/ThirdPartyNotices.md", } assert required <= names, f"Missing VSIX files: {required - names}" excluded = {"AGENTS", "AGENTS.md", "test", "node_modules"} assert not any(excluded.intersection(Path(name).parts) for name in names), "Unexpected development files in VSIX" 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/cpsc-plugin-*.vsix) bundle_macos=(release-packages/nightly-bundle-macos/cpsc-bundle-*-Darwin-arm64.tar.xz) bundle_windows=(release-packages/nightly-bundle-windows/cpsc-bundle-*-Windows-x86_64.exe) for count in "${#linux[@]}" "${#macos[@]}" "${#windows[@]}" "${#plugin[@]}" "${#bundle_macos[@]}" "${#bundle_windows[@]}"; do if [[ "$count" -ne 1 ]]; then echo "Expected exactly one of each of the six packages; leaving the release untouched." >&2 exit 1 fi done packages=("${linux[@]}" "${macos[@]}" "${windows[@]}" "${plugin[@]}" "${bundle_macos[@]}" "${bundle_windows[@]}") for package in "${packages[@]}"; do test -s "$package" done v='([0-9]{8}-[0-9]{6})-([0-9a-f]{7,40})' version_of() { local name name=$(basename "$1") if [[ ! "$name" =~ $2 ]]; then echo "Unexpected package name: $name; leaving the release untouched." >&2 exit 1 fi printf '%s %s' "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" } linux_v=$(version_of "${linux[0]}" "^cpsc-$v-Linux-x86_64\\.tar\\.xz\$") macos_v=$(version_of "${macos[0]}" "^cpsc-$v-Darwin-arm64\\.tar\\.xz\$") windows_v=$(version_of "${windows[0]}" "^cpsc-$v-Windows-[^/]+\\.zip\$") plugin_v=$(version_of "${plugin[0]}" "^cpsc-plugin-$v\\.vsix\$") bundle_macos_v=$(version_of "${bundle_macos[0]}" "^cpsc-bundle-$v-Darwin-arm64\\.tar\\.xz\$") bundle_windows_v=$(version_of "${bundle_windows[0]}" "^cpsc-bundle-$v-Windows-x86_64\\.exe\$") if [[ "$linux_v" != "$macos_v" || "$linux_v" != "$windows_v" ]]; then echo "Compiler packages have different versions; leaving the release untouched." >&2 exit 1 fi if [[ "$bundle_macos_v" != "$bundle_windows_v" ]]; then echo "Bundle packages have different versions; leaving the release untouched." >&2 exit 1 fi 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 0 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" upload="$RUNNER_TEMP/nightly-upload" mkdir -p "$upload" latest_files=() for package in "${packages[@]}"; do name=$(basename "$package") [[ "$name" =~ $v ]] latest_files+=("$upload/${name/${BASH_REMATCH[0]}/latest}") cp "$package" "${latest_files[-1]}" done read -r compiler_time compiler_commit <<< "$linux_v" read -r plugin_time plugin_commit <<< "$plugin_v" read -r bundle_time bundle_commit <<< "$bundle_macos_v" printf '{\n "compiler": { "time": "%s", "commit": "%s" },\n "plugin": { "time": "%s", "commit": "%s" },\n "bundle": { "time": "%s", "commit": "%s" }\n}\n' \ "$compiler_time" "$compiler_commit" "$plugin_time" "$plugin_commit" "$bundle_time" "$bundle_commit" \ > "$upload/version.json" gh release upload "$CPSC_NIGHTLY_TAG" "${latest_files[@]}" --clobber gh release upload "$CPSC_NIGHTLY_TAG" "$upload/version.json" --clobber