Files
scriptc/.github/workflows/ci.yml
T
Chris Tate 3bf0d0b9a5 Give the sanitized emitter job swap and a larger stack
The sanitized native emitter peaks near 13 GB while it emits the
compiler and then compiles the 348 MB module with clang under
AddressSanitizer, which together exceed the 16 GB runner. Back the
rebuild-emit job with a 24 GiB swap file and lift its stack limit,
since the instrumented emitter's recursion can exceed 8 MiB.
2026-10-11 16:45:12 -05:00

792 lines
33 KiB
YAML

name: CI
on:
push:
branches: [main]
pull_request:
concurrency:
group: ci-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
# GitHub recommends stack.base.ref == base.ref for the lowest unmerged PR:
# https://docs.github.com/en/pull-requests/how-tos/merge-and-close-pull-requests/optimizing-ci-for-stacked-pull-requests
# Only trusted, same-repository upper layers defer full CI. Forks, ordinary
# PRs, pushes and incomplete metadata always retain full validation.
validation_scope:
name: Select CI scope
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions: {}
outputs:
full: ${{ steps.scope.outputs.full }}
steps:
- id: scope
# Read GitHub's event directly; do not run scripts from a PR checkout.
run: |
node <<'NODE'
const { appendFileSync, readFileSync } = require('node:fs');
const event = JSON.parse(readFileSync(process.env.GITHUB_EVENT_PATH, 'utf8'));
const pr = event.pull_request;
const repository = event.repository?.full_name;
const sameRepository = typeof repository === 'string' && repository.length > 0 &&
pr?.head?.repo?.full_name === repository && pr?.base?.repo?.full_name === repository;
const trustedAuthor = ['OWNER', 'MEMBER', 'COLLABORATOR'].includes(pr?.author_association);
const stackBase = pr?.stack?.base?.ref;
const prBase = pr?.base?.ref;
// Position 1 can already be merged; compare the current base branches.
const upperLayer = process.env.GITHUB_EVENT_NAME === 'pull_request' &&
sameRepository && trustedAuthor && typeof stackBase === 'string' && stackBase.length > 0 &&
typeof prBase === 'string' && prBase.length > 0 && stackBase !== prBase;
const full = !upperLayer;
appendFileSync(process.env.GITHUB_OUTPUT, `full=${full}\n`);
console.log(full ? 'Run full CI' : 'Defer full CI until this PR becomes the lowest unmerged layer');
NODE
code_quality:
name: lint and format
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 11.1.3
- uses: actions/setup-node@v4
with:
node-version-file: .node-version
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm lint
- run: pnpm format:check
- name: Code quality and CI policy contracts
run: pnpm test tests/harness/code-quality.test.ts tests/harness/ci-stack-scope.test.ts
env:
SCRIPTC_TEST_WORKERS: "1"
- name: Verify mixed-source generated AST getters
run: pnpm generate:ts7-ast --check
native_macos:
needs: validation_scope
if: ${{ needs.validation_scope.outputs.full == 'true' }}
name: build (macOS arm64 native packages)
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 11.1.3
- uses: actions/setup-node@v4
with:
node-version-file: .node-version
cache: pnpm
- run: pnpm install --frozen-lockfile
- uses: ./.github/actions/native-packages
with:
target: darwin-arm64
- name: Preserve executable permissions in shared packages
run: >-
tar -czf "$RUNNER_TEMP/native-darwin-arm64.tar.gz"
packages/llvm-darwin-arm64/bin
packages/runtime-darwin-arm64/artifacts
packages/runtime-darwin-arm64/runtime-pack.json
- uses: actions/upload-artifact@v4
with:
name: native-darwin-arm64
path: ${{ runner.temp }}/native-darwin-arm64.tar.gz
retention-days: 1
tests:
needs: [validation_scope, native_macos]
if: ${{ needs.validation_scope.outputs.full == 'true' }}
name: test (${{ matrix.flavor }}, ${{ matrix.shard }}/10)
# arm64 macOS: same clang/libc the differential + number-formatting
# oracles were verified against.
runs-on: macos-15
strategy:
fail-fast: false
matrix:
flavor: [plain, san]
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
env:
# Capped workers: the shared runner's cores oversubscribe under the
# corpus compile fan-out, and vitest's worker RPC times out ("Timeout
# calling onTaskUpdate") with every test green — the same contention
# artifact the harness README documents for local parallel suites.
SCRIPTC_TEST_WORKERS: "4"
# san = ASan + RC audit over the same suite; anything but "1" is plain.
SCRIPTC_SAN: ${{ matrix.flavor == 'san' && '1' || '' }}
# Within-file partition for the large case-driven harnesses
# (differential, llvm-differential, npm, server, Test262, coverage, and
# Vercel E2E): partition stable case names into slice i of 10
# (tests/harness/shard.ts). The ten shards' unions run every case
# exactly once. vitest's own --shard is FILE-granular and cannot split
# these monster files (differential.test.ts alone is ~9 min
# sequential), hence the case-sharded steps below.
SCRIPTC_TEST_SHARD: ${{ matrix.shard }}/10
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 11.1.3
- uses: actions/setup-node@v4
with:
node-version-file: .node-version
cache: pnpm
- run: pnpm install --frozen-lockfile
- uses: actions/download-artifact@v4
with:
name: native-darwin-arm64
path: ${{ runner.temp }}
- run: tar -xzf "$RUNNER_TEMP/native-darwin-arm64.tar.gz"
- run: pnpm build
# Start the corpus immediately beside a serial side queue. One shared
# worker budget covers both processes; file and case shard axes stay
# separate so every fixture still runs exactly once in each lane.
- name: Tests (corpus and integration suites)
run: node scripts/ci-test.mjs
native_linux:
needs: validation_scope
if: ${{ needs.validation_scope.outputs.full == 'true' }}
name: build (Linux x64 native packages)
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 11.1.3
- uses: actions/setup-node@v4
with:
node-version-file: .node-version
cache: pnpm
- uses: ./.github/actions/llvm-linux
- run: pnpm install --frozen-lockfile
- uses: ./.github/actions/zig
- uses: ./.github/actions/native-packages
with:
target: linux-x64-gnu
- name: Preserve executable permissions in shared packages
run: >-
tar -czf "$RUNNER_TEMP/native-linux-x64-gnu.tar.gz"
packages/llvm-linux-x64-gnu/bin
packages/runtime-linux-x64-gnu/artifacts
packages/runtime-linux-x64-gnu/runtime-pack.json
- uses: actions/upload-artifact@v4
with:
name: native-linux-x64-gnu
path: ${{ runner.temp }}/native-linux-x64-gnu.tar.gz
retention-days: 1
effects:
needs: [validation_scope, native_linux]
if: ${{ needs.validation_scope.outputs.full == 'true' }}
name: test (Linux, ${{ matrix.flavor }}, Effect ${{ matrix.shard }}/16)
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
flavor: [plain, san]
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
env:
# One compiler at a time leaves room for its 8 GiB heap, checker and
# LLVM on a 16 GB runner. Parallelism comes from independent shards.
# Both package versions retain every Node/native comparison in both
# flavors; Effect 3 plain also retains its release optimizer coverage.
SCRIPTC_TEST_WORKERS: "1"
SCRIPTC_EFFECT_TEST_CONCURRENCY: "1"
SCRIPTC_TEST_SHARD: ${{ matrix.shard }}/16
SCRIPTC_SAN: ${{ matrix.flavor == 'san' && '1' || '' }}
ASAN_OPTIONS: detect_leaks=0
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 11.1.3
- uses: actions/setup-node@v4
with:
node-version-file: .node-version
cache: pnpm
- run: pnpm install --frozen-lockfile
- uses: actions/download-artifact@v4
with:
name: native-linux-x64-gnu
path: ${{ runner.temp }}
- run: tar -xzf "$RUNNER_TEMP/native-linux-x64-gnu.tar.gz"
- run: pnpm test tests/harness/effect.test.ts tests/harness/effect4.test.ts
# Build each instrumented seed once. Downstream contracts retain the same
# compiler and source artifacts without repeating seed construction.
bootstrap_seed:
needs: validation_scope
if: ${{ needs.validation_scope.outputs.full == 'true' }}
name: build (Linux, ${{ matrix.flavor }}, bootstrap seed)
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
flavor: [plain, san]
env:
SCRIPTC_SAN: ${{ matrix.flavor == 'san' && '1' || '' }}
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 11.1.3
- uses: actions/setup-node@v4
with:
node-version-file: .node-version
cache: pnpm
- uses: ./.github/actions/llvm-linux
- run: pnpm install --frozen-lockfile
- uses: ./.github/actions/zig
- uses: ./.github/actions/native-packages
with:
target: linux-x64-gnu
- uses: ./.github/actions/native-packages
with:
target: wasm32-wasi
helper: "false"
- run: pnpm build
- name: Prepare bootstrap seed
run: node scripts/bootstrap-seed.mjs
env:
SCRIPTC_BOOTSTRAP_SEED_DIRECTORY: ${{ runner.temp }}/native-bootstrap
- name: Archive seed and compiler artifacts
run: tar -C "$RUNNER_TEMP" -cf "$RUNNER_TEMP/bootstrap-seed.tar" native-bootstrap
- uses: actions/upload-artifact@v4
with:
name: bootstrap-seed-${{ matrix.flavor }}-${{ github.sha }}
path: ${{ runner.temp }}/bootstrap-seed.tar
retention-days: 1
if-no-files-found: error
bootstrap:
needs: [validation_scope, bootstrap_seed]
if: ${{ needs.validation_scope.outputs.full == 'true' }}
name: test (Linux, ${{ matrix.flavor }}, native bootstrap, ${{ matrix.phase }})
# These contracts are portable. Standard Linux runners have enough memory
# for LLVM optimization alongside the compiler that produces its input.
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
# The sanitized self-rebuild is split at the IR boundary: one job
# lowers the compiler and compares its IR with the seed's; the other
# emits, links and probes the next generation from the seed's IR.
include:
- { flavor: plain, phase: commands }
- { flavor: plain, phase: rebuild }
- { flavor: san, phase: commands }
- { flavor: san, phase: rebuild-frontend }
- { flavor: san, phase: rebuild-emit }
env:
SCRIPTC_TEST_WORKERS: "1"
SCRIPTC_SAN: ${{ matrix.flavor == 'san' && '1' || '' }}
SCRIPTC_BOOTSTRAP_PHASE: ${{ matrix.phase }}
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 11.1.3
- uses: actions/setup-node@v4
with:
node-version-file: .node-version
cache: pnpm
- uses: ./.github/actions/llvm-linux
- run: pnpm install --frozen-lockfile
- uses: ./.github/actions/zig
# Rebuilds use the relocated seed's packaged helper and runtimes.
- uses: ./.github/actions/native-packages
if: matrix.phase == 'commands'
with:
target: linux-x64-gnu
- uses: ./.github/actions/native-packages
if: matrix.phase == 'commands'
with:
target: wasm32-wasi
helper: "false"
- run: pnpm build
- uses: actions/download-artifact@v4
with:
name: bootstrap-seed-${{ matrix.flavor }}-${{ github.sha }}
path: ${{ runner.temp }}
- name: Restore bootstrap seed
run: tar -C "$RUNNER_TEMP" -xf "$RUNNER_TEMP/bootstrap-seed.tar"
# The sanitized emitter peaks near 13 GB while it emits the compiler,
# then clang compiles the 348 MB module under AddressSanitizer. Together
# they exceed the runner's 16 GB of memory, so back them with swap.
- name: Enlarge swap for the sanitized emitter
if: matrix.phase == 'rebuild-emit'
run: |
sudo swapoff -a
sudo rm -f /mnt/swapfile
sudo fallocate -l 24G /mnt/swapfile
sudo chmod 600 /mnt/swapfile
sudo mkswap /mnt/swapfile
sudo swapon /mnt/swapfile
free -h
- name: Native bootstrap and package installation checks
# The instrumented emitter's recursion can exceed the default 8 MiB stack.
run: |
if [ "$SCRIPTC_BOOTSTRAP_PHASE" = rebuild-emit ]; then ulimit -s unlimited; fi
node scripts/ci-native-bootstrap.mjs
env:
SCRIPTC_BOOTSTRAP_SEED_DIRECTORY: ${{ runner.temp }}/native-bootstrap
SCRIPTC_BOOTSTRAP_PACKAGE_DIR: ${{ runner.temp }}/native-cli-package
# A real host-clang/glibc build, not the zig cross-target lane: catches
# Linux feature-test macro omissions at compile time and separate-libm
# omissions/order errors at link time.
linux_host_clang:
needs: validation_scope
if: ${{ needs.validation_scope.outputs.full == 'true' }}
name: test (linux host clang/glibc)
runs-on: ubuntu-24.04
timeout-minutes: 10
env:
# The verdict must come from a live clang invocation, never a cached
# binary copied from a previous build.
SCRIPTC_NO_CACHE: "1"
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 11.1.3
- uses: actions/setup-node@v4
with:
node-version-file: .node-version
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm build
- name: Host clang and sanitizer build regressions
run: >-
pnpm test packages/compiler/test/cc-driver.test.ts
packages/compiler/src/native/sanitizer.test.ts
--testNamePattern "host-native clang static build|sanitized vendor archives"
# The TS7 parity sweep exercises the compiler frontend, so it can run
# alongside the Linux LLVM/runtime package build instead of following it.
ts7_parity:
needs: validation_scope
if: ${{ needs.validation_scope.outputs.full == 'true' }}
name: test (TypeScript 7 parity)
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 11.1.3
- uses: actions/setup-node@v4
with:
node-version-file: .node-version
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm build
- name: TypeScript 7 full parity sweep
run: pnpm test:ts7
llvm_artifacts_linux_x64:
needs: validation_scope
if: ${{ needs.validation_scope.outputs.full == 'true' }}
name: test (Linux x64 LLVM artifacts, no clang)
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 11.1.3
- uses: actions/setup-node@v4
with:
node-version-file: .node-version
cache: pnpm
- uses: ./.github/actions/llvm-linux
- run: pnpm install --frozen-lockfile
- uses: ./.github/actions/zig
- uses: ./.github/actions/native-packages
with:
target: linux-x64-gnu
- name: Build and verify Linux x64 musl release packages
run: |
pnpm --filter @scriptc/llvm-linux-x64-musl build:native
CC=zig AR=zig pnpm --filter @scriptc/runtime-linux-x64-musl build:native
helper=packages/llvm-linux-x64-musl/bin/scriptc-llvm-codegen
readelf -l "$helper" > "$RUNNER_TEMP/musl-helper-program-headers"
if grep -q INTERP "$RUNNER_TEMP/musl-helper-program-headers"; then
echo "musl helper must be a static executable" >&2
exit 1
fi
docker run --rm --volume "$GITHUB_WORKSPACE:/work:ro" alpine:3.22 \
/work/$helper version --format=json | node -e '
const value = JSON.parse(require("node:fs").readFileSync(0, "utf8"));
if (value.default_target !== "x86_64-unknown-linux-musl" || !value.supported_targets?.includes("wasm32-unknown-wasi")) process.exit(1);
'
node packages/runtime-linux-x64-musl/scripts/verify.mjs
- uses: ./.github/actions/native-packages
with:
target: wasm32-wasi
helper: "false"
- run: pnpm build
- name: Linux helper object format and no-clang output contract
run: |
PATH="$RUNNER_TEMP/traps:$PATH"
mkdir -p "$RUNNER_TEMP/traps"
for tool in clang cc gcc ar ld; do printf '#!/bin/sh\nexit 97\n' > "$RUNNER_TEMP/traps/$tool"; chmod +x "$RUNNER_TEMP/traps/$tool"; done
node packages/cli/dist/main.js build tests/corpus/001-hello.ts --emit=obj -o "$RUNNER_TEMP/hello.o"
file "$RUNNER_TEMP/hello.o" | grep 'ELF 64-bit LSB relocatable, x86-64'
- name: Linux helper/runtime-pack executable differential smoke
env:
SCRIPTC_NO_CACHE: "1"
SCRIPTC_LEGACY_C_PIPELINE: "0"
run: |
node packages/cli/dist/main.js build tests/corpus/001-hello.ts -o "$RUNNER_TEMP/hello"
test "$("$RUNNER_TEMP/hello")" = 'hello world'
- name: Linux release runtime reachability and debug payload
run: pnpm test tests/harness/runtime-tree-shaking.test.ts
- name: Debian 12 glibc 2.36 runtime-pack smoke
run: |
docker run --rm --volume "$GITHUB_WORKSPACE:/work:ro" --workdir /work node:24-bookworm-slim sh -ec '
apt-get update
apt-get install --yes --no-install-recommends clang
SCRIPTC_NO_CACHE=1 SCRIPTC_LEGACY_C_PIPELINE=0 node packages/cli/dist/main.js build tests/corpus/001-hello.ts -o /tmp/hello
test "$(/tmp/hello)" = "hello world"
'
- name: WASI helper object/runtime-pack smoke
run: pnpm test packages/cli/test/wasi-runtime-pack.test.ts tests/harness/wasm-library.test.ts
llvm_artifacts_macos_arm64:
needs: [validation_scope, native_macos]
if: ${{ needs.validation_scope.outputs.full == 'true' }}
name: test (macOS arm64 LLVM artifacts, no clang, ${{ matrix.shard }}/5)
runs-on: macos-15
# Shard 1 additionally runs package/install and link-parity contracts
# before its LLVM differential slice. The slice itself can take ~10
# minutes on a cold hosted runner, so 20 minutes cancels an otherwise
# healthy job before the differential completes.
timeout-minutes: 35
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4, 5]
env:
# The helper differential uses the harness's stable per-case sharding,
# keeping each macOS job below its timeout while the matrix union still
# exercises every LLVM-tier program.
SCRIPTC_TEST_SHARD: ${{ matrix.shard }}/5
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 11.1.3
- uses: actions/setup-node@v4
with:
node-version-file: .node-version
cache: pnpm
- run: pnpm install --frozen-lockfile
- uses: actions/download-artifact@v4
with:
name: native-darwin-arm64
path: ${{ runner.temp }}
- run: tar -xzf "$RUNNER_TEMP/native-darwin-arm64.tar.gz"
- run: pnpm build
- name: No-clang assembly/object contract
if: matrix.shard == 1
run: pnpm test packages/cli/test/native-output.test.ts
- name: Helper diagnostics and object/link parity
if: matrix.shard == 1
run: >-
pnpm test
packages/compiler/test/native-codegen-integration.test.ts
packages/compiler/src/backend/runtime-pack.test.ts
packages/cli/test/native-link-info.test.ts
packages/cli/test/runtime-pack.test.ts
tests/harness/native-object-example.test.ts
- name: LLVM release/dev differential (${{ matrix.shard }}/5)
env:
SCRIPTC_TEST_WORKERS: "4"
run: pnpm test tests/harness/llvm-differential.test.ts
- name: Packaged helper contract
if: matrix.shard == 1
run: |
TARBALL=$(pnpm --dir packages/llvm-darwin-arm64 pack --pack-destination "$RUNNER_TEMP" --silent)
node scripts/verify-llvm-package.mjs "$RUNNER_TEMP/$(basename "$TARBALL")"
- name: Packed npm installation smoke
if: matrix.shard == 1
env:
# Release optimization is covered by the compiler bootstrap jobs.
# This lane verifies packaging and installation with a faster seed.
SCRIPTC_NATIVE_OPTIMIZATION: dev
run: |
pnpm --filter @scriptc/cli-darwin-arm64 build:native
pnpm --dir packages/runtime pack --pack-destination "$RUNNER_TEMP" --silent
pnpm --dir packages/runtime-darwin-arm64 pack --pack-destination "$RUNNER_TEMP" --silent
pnpm --dir packages/compiler pack --pack-destination "$RUNNER_TEMP" --silent
pnpm --dir packages/cli-darwin-arm64 pack --pack-destination "$RUNNER_TEMP" --silent
pnpm --dir packages/cli pack --pack-destination "$RUNNER_TEMP" --silent
PREFIX="$RUNNER_TEMP/installed-scriptc"
npm install --prefix "$PREFIX" --no-audit --no-fund \
"$RUNNER_TEMP/scriptc-runtime-$(node -p "require('./packages/runtime/package.json').version").tgz" \
"$RUNNER_TEMP/scriptc-runtime-darwin-arm64-$(node -p "require('./packages/runtime-darwin-arm64/package.json').version").tgz" \
"$RUNNER_TEMP/scriptc-llvm-darwin-arm64-$(node -p "require('./packages/llvm-darwin-arm64/package.json').version").tgz" \
"$RUNNER_TEMP/scriptc-compiler-$(node -p "require('./packages/compiler/package.json').version").tgz" \
"$RUNNER_TEMP/scriptc-cli-darwin-arm64-$(node -p "require('./packages/cli-darwin-arm64/package.json').version").tgz" \
"$RUNNER_TEMP/scriptc-$(node -p "require('./packages/cli/package.json').version").tgz"
PATH="" "$PREFIX/node_modules/.bin/scriptc" build tests/corpus/001-hello.ts \
--emit=obj -o "$RUNNER_TEMP/installed.o"
file "$RUNNER_TEMP/installed.o" | grep 'Mach-O 64-bit object arm64'
PATH="" "$PREFIX/node_modules/.bin/scriptc" build tests/corpus/001-hello.ts \
--print=native-link-info -o "$RUNNER_TEMP/installed-link.o" \
> "$RUNNER_TEMP/installed-link.json"
node examples/native-object/link.mjs cc \
"$RUNNER_TEMP/installed-link.json" "$RUNNER_TEMP/installed-program"
test "$("$RUNNER_TEMP/installed-program")" = 'hello world'
llvm_artifacts_macos_x64:
needs: validation_scope
if: ${{ needs.validation_scope.outputs.full == 'true' }}
name: test (macOS x64 LLVM artifacts, no clang)
runs-on: macos-15-intel
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 11.1.3
- uses: actions/setup-node@v4
with:
node-version-file: .node-version
cache: pnpm
- run: pnpm install --frozen-lockfile
- uses: ./.github/actions/native-packages
with:
target: darwin-x64
- run: pnpm build
- name: Intel Mach-O no-clang object contract
run: |
mkdir -p "$RUNNER_TEMP/traps"
for tool in clang cc gcc ar ld; do printf '#!/bin/sh\nexit 97\n' > "$RUNNER_TEMP/traps/$tool"; chmod +x "$RUNNER_TEMP/traps/$tool"; done
PATH="$RUNNER_TEMP/traps:$PATH" node packages/cli/dist/main.js build tests/corpus/001-hello.ts --emit=obj -o "$RUNNER_TEMP/hello.o"
file "$RUNNER_TEMP/hello.o" | grep 'Mach-O 64-bit object x86_64'
- name: Intel helper/runtime-pack executable smoke
env:
SCRIPTC_NO_CACHE: "1"
SCRIPTC_LEGACY_C_PIPELINE: "0"
run: |
node packages/cli/dist/main.js build tests/corpus/001-hello.ts -o "$RUNNER_TEMP/hello"
test "$($RUNNER_TEMP/hello)" = 'hello world'
# Exercises the supported Windows GNU target and the built CLI end to end:
# TS7 must open its synthetic project, ambient files must resolve across
# slash styles, and the default executable must use the .exe suffix.
windows_cli:
name: test (windows CLI smoke)
needs: [validation_scope, llvm_artifacts_windows_x64]
if: ${{ needs.validation_scope.outputs.full == 'true' }}
runs-on: windows-latest
timeout-minutes: 15
env:
SCRIPTC_NO_CACHE: "1"
SCRIPTC_CC: zigcc
SCRIPTC_TARGET: x86_64-windows-gnu
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/zig
- uses: pnpm/action-setup@v4
with:
version: 11.1.3
- uses: actions/setup-node@v4
with:
node-version-file: .node-version
cache: pnpm
- name: Install dependencies
shell: pwsh
run: |
pnpm install --frozen-lockfile
if ($LASTEXITCODE -ne 0) {
Write-Host "pnpm exited without a diagnostic; retrying once"
pnpm install --frozen-lockfile
}
- uses: actions/download-artifact@v4
with:
name: windows-native-packages
path: packages
- run: pnpm build
- name: Windows path regressions
run: pnpm exec vitest run packages/compiler/test/ts7/program-adapter.test.ts packages/compiler/test/ts7/resolver-parity.test.ts packages/compiler/src/native/toolchain.test.ts packages/cli/test/paths.test.ts
- name: Windows Sandbox path regression
run: >-
pnpm exec vitest run tests/harness/worktree-files.test.ts
--testNamePattern "workspace reset keeps Linux Sandbox paths POSIX"
- name: Windows scriptc run smoke
shell: pwsh
run: |
zig version
$output = & node packages/cli/dist/main.js run tests/corpus/001-hello.ts --backend llvm
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
$actual = ($output -join "`n").Trim()
if ($actual -ne "hello world") {
throw "unexpected scriptc output: $actual"
}
- name: Windows PE subsystem selection
env:
SCRIPTC_NO_CACHE: "0"
run: pnpm exec vitest run packages/cli/test/windows-subsystem.test.ts
- name: Windows utility runtime linking
run: pnpm exec vitest run packages/compiler/test/util-runtime-linking.test.ts
- name: Windows filesystem links differential
run: >-
pnpm exec vitest run tests/harness/differential.test.ts tests/harness/project-config.test.ts
--testNamePattern "node24-fs-links|filesystem links"
- name: Windows CA-store differential
shell: pwsh
run: |
$nodeOutput = & node tests/corpus/2557-tls-ca-store.ts
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
$nativeOutput = & node packages/cli/dist/main.js run tests/corpus/2557-tls-ca-store.ts --backend llvm
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
$expected = ($nodeOutput -join "`n").Trim()
$actual = ($nativeOutput -join "`n").Trim()
if ($actual -ne $expected) {
throw "Windows CA-store output differed:`nNODE:`n$expected`nNATIVE:`n$actual"
}
- name: Windows CA EKU policy
run: >-
pnpm exec vitest run packages/compiler/test/cc-driver.test.ts
--testNamePattern "Windows CA roots honor"
llvm_artifacts_windows_x64:
needs: validation_scope
if: ${{ needs.validation_scope.outputs.full == 'true' }}
name: test (Windows x64 MSVC LLVM artifacts)
# The official LLVM 22 Windows archives are MSVC-built and the helper
# selects CMake's VS 2022 generator. `windows-latest` can move to an
# image without that generator, so retain the hosted VS 2022 contract.
runs-on: windows-2022
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 11.1.3
- uses: actions/setup-node@v4
with:
node-version-file: .node-version
cache: pnpm
- name: Install pinned LLVM development distribution
shell: pwsh
run: |
# windows-2022 already provides CMake, Ninja, VS 2022, and 7-Zip.
# Use the complete archive because it supplies LLVM's exported CMake
# targets and static libraries. Windows tar.exe takes 25+ minutes to
# materialize it; two-stage 7-Zip extraction takes seconds.
$archive = "$env:RUNNER_TEMP\clang+llvm-22.1.8-x86_64-pc-windows-msvc.tar.xz"
$llvm = "$env:RUNNER_TEMP\llvm-22.1.8"
$expanded = "$env:RUNNER_TEMP\llvm-expanded"
$sevenZip = "$env:ProgramFiles\7-Zip\7z.exe"
if (-not (Test-Path $sevenZip)) {
choco install 7zip --no-progress -y
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
}
if (-not (Test-Path $sevenZip)) { throw "7-Zip is unavailable" }
curl.exe -fL --retry 3 -o $archive https://github.com/llvm/llvm-project/releases/download/llvmorg-22.1.8/clang%2Bllvm-22.1.8-x86_64-pc-windows-msvc.tar.xz
& $sevenZip x -y "-o$expanded" $archive
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
& $sevenZip x -y "-o$expanded" "$expanded\clang+llvm-22.1.8-x86_64-pc-windows-msvc.tar"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
Move-Item "$expanded\clang+llvm-22.1.8-x86_64-pc-windows-msvc" $llvm
echo "LLVM_DIR=$llvm\lib\cmake\llvm" >> $env:GITHUB_ENV
echo "$llvm\bin" >> $env:GITHUB_PATH
- run: pnpm install --frozen-lockfile
- name: Build Windows helper
run: pnpm --filter @scriptc/llvm-win32-x64-msvc build:native
- uses: ./.github/actions/zig
- name: Build Windows runtime pack
run: pnpm --filter @scriptc/runtime-win32-x64-msvc build:native
- run: pnpm build
- name: Windows COFF no-clang object contract
shell: pwsh
run: |
node packages/cli/dist/main.js build tests/corpus/001-hello.ts --emit=obj -o "$env:RUNNER_TEMP\hello.obj"
$bytes = [System.IO.File]::ReadAllBytes("$env:RUNNER_TEMP\hello.obj")
if ($bytes[0] -ne 0x64 -or $bytes[1] -ne 0x86) { throw "expected AMD64 COFF object" }
- uses: actions/upload-artifact@v4
with:
name: windows-native-packages
path: |
packages/llvm-win32-x64-msvc/bin
packages/runtime-win32-x64-msvc/artifacts
packages/runtime-win32-x64-msvc/runtime-pack.json
retention-days: 1
mobile_runtime_packs:
needs: validation_scope
if: ${{ needs.validation_scope.outputs.full == 'true' }}
name: test (${{ matrix.runtime }})
strategy:
fail-fast: false
matrix:
include:
- runtime: runtime-ios-arm64
runner: macos-15
- runtime: runtime-ios-simulator-arm64
runner: macos-15
- runtime: runtime-android-arm64
runner: ubuntu-24.04
runs-on: ${{ matrix.runner }}
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/mobile-runtime-pack
with:
runtime: ${{ matrix.runtime }}
# Retain the pre-matrix "test" check for branch protection and badges.
# Require this check on the stack base. Upper layers must not pass before
# full validation, so untested groups cannot merge. Merge bottom-up.
test:
needs: [validation_scope, code_quality, native_macos, tests, native_linux, effects, bootstrap_seed, bootstrap, linux_host_clang, ts7_parity, llvm_artifacts_linux_x64, llvm_artifacts_macos_arm64, llvm_artifacts_macos_x64, windows_cli, llvm_artifacts_windows_x64, mobile_runtime_packs]
# Run even after failed or cancelled lanes: skipped required jobs allow merging.
if: ${{ always() }}
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: All lanes green
run: |
test "${{ needs.validation_scope.result }}" = success
test "${{ needs.code_quality.result }}" = success
case "${{ needs.validation_scope.outputs.full }}" in
true)
test "${{ needs.native_macos.result }}" = success
test "${{ needs.tests.result }}" = success
test "${{ needs.native_linux.result }}" = success
test "${{ needs.effects.result }}" = success
test "${{ needs.bootstrap_seed.result }}" = success
test "${{ needs.bootstrap.result }}" = success
test "${{ needs.linux_host_clang.result }}" = success
test "${{ needs.ts7_parity.result }}" = success
test "${{ needs.llvm_artifacts_linux_x64.result }}" = success
test "${{ needs.llvm_artifacts_macos_arm64.result }}" = success
test "${{ needs.llvm_artifacts_macos_x64.result }}" = success
test "${{ needs.windows_cli.result }}" = success
test "${{ needs.llvm_artifacts_windows_x64.result }}" = success
test "${{ needs.mobile_runtime_packs.result }}" = success
;;
false)
echo "::notice::Full CI is deferred until this PR becomes the lowest unmerged layer. Merge the stack from the bottom up."
exit 1
;;
*)
echo "Invalid CI scope"
exit 1
;;
esac