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 }} 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 - 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 - 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 }} 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: 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}') - 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 }} 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.ps1', '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: 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 }} 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/*.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; 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" gh release upload "$CPSC_NIGHTLY_TAG" "${packages[@]}" --clobber