mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-31 07:23:06 +08:00
1247 lines
55 KiB
YAML
1247 lines
55 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
# Nightly full macOS sweep. Pushes to main run no macOS jobs, and pull requests run them only when they change
|
|
# darwin code (scripts/ci-lanes.mjs).
|
|
schedule:
|
|
- cron: "17 7 * * *"
|
|
# A manual run, for example on a release candidate commit. It runs every lane; macos_suite selects the complete
|
|
# suite on macOS, as the nightly sweep runs it, or the darwin suite that pull requests run.
|
|
workflow_dispatch:
|
|
inputs:
|
|
macos_suite:
|
|
description: The test suite to run on macOS
|
|
type: choice
|
|
options: [full, darwin]
|
|
default: full
|
|
scope:
|
|
description: Every lane, or only the shape overlay validation (shape_overlay)
|
|
type: choice
|
|
options: [all, shape_overlay]
|
|
default: all
|
|
|
|
concurrency:
|
|
# A new push to a pull request cancels its previous run. Every push to main runs to completion in a group of
|
|
# its own commit, so each merged commit gets a result (releases require a green run on their commit). The
|
|
# nightly sweep and manual runs neither cancel nor are cancelled by main pushes.
|
|
group: ci-${{ github.event_name == 'schedule' && 'schedule' || github.event_name == 'workflow_dispatch' && format('dispatch-{0}', github.run_id) || github.event.pull_request.number || github.sha }}
|
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
|
|
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.
|
|
// A PR into the default branch is the lowest unmerged layer of any
|
|
// stack, whatever the stack metadata names as its base.
|
|
const defaultBranch = event.repository?.default_branch;
|
|
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 &&
|
|
prBase !== defaultBranch;
|
|
// A manual run of the shape overlay validation alone skips the other lanes.
|
|
const shapeOnly = process.env.GITHUB_EVENT_NAME === 'workflow_dispatch' &&
|
|
event.inputs?.scope === 'shape_overlay';
|
|
const full = !upperLayer && !shapeOnly;
|
|
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
|
|
|
|
# Which expensive lanes a pull request needs (scripts/ci-lanes.mjs, run from the base commit against the pull
|
|
# request's diff, so a pull request cannot change its own selection). Pushes to main run every lane except macos,
|
|
# whose jobs the nightly sweep runs on main. The nightly sweep and manual runs run every lane.
|
|
lanes:
|
|
needs: validation_scope
|
|
if: ${{ needs.validation_scope.outputs.full == 'true' }}
|
|
name: Select CI lanes
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 5
|
|
permissions:
|
|
contents: read
|
|
outputs:
|
|
linux: ${{ steps.lanes.outputs.linux }}
|
|
macos: ${{ steps.lanes.outputs.macos }}
|
|
effects: ${{ steps.lanes.outputs.effects }}
|
|
bootstrap: ${{ steps.lanes.outputs.bootstrap }}
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
if: github.event_name == 'pull_request'
|
|
with:
|
|
fetch-depth: 0
|
|
ref: ${{ github.event.pull_request.head.sha }}
|
|
- id: lanes
|
|
env:
|
|
EVENT: ${{ github.event_name }}
|
|
BASE: ${{ github.event.pull_request.base.sha }}
|
|
HEAD: ${{ github.event.pull_request.head.sha }}
|
|
run: |
|
|
every() { printf 'linux=true\nmacos=true\neffects=true\nbootstrap=true\n' | tee -a "$GITHUB_OUTPUT"; }
|
|
if [ "$EVENT" = push ]; then
|
|
printf 'linux=true\nmacos=false\neffects=true\nbootstrap=true\n' | tee -a "$GITHUB_OUTPUT"
|
|
elif [ "$EVENT" != pull_request ]; then
|
|
every
|
|
elif git show "$BASE:scripts/ci-lanes.mjs" > "$RUNNER_TEMP/ci-lanes.mjs" 2> /dev/null; then
|
|
node "$RUNNER_TEMP/ci-lanes.mjs" --base "$BASE" --head "$HEAD" --github-output
|
|
else
|
|
echo "The base commit has no scripts/ci-lanes.mjs: every lane"
|
|
every
|
|
fi
|
|
|
|
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
|
|
- name: Verify the generated IR field readers
|
|
run: pnpm generate:ir-fields --check
|
|
- name: Verify the generated IR codec
|
|
run: pnpm generate:ir-codec --check
|
|
|
|
native_macos:
|
|
needs: [validation_scope, lanes]
|
|
if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.macos == '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, lanes, native_linux]
|
|
if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.linux == 'true' }}
|
|
name: test (Linux, ${{ matrix.flavor }}, ${{ matrix.shard }}/20)
|
|
# The full suite runs on Linux x64, whose hosted runners start without the
|
|
# macOS queue. tests_macos keeps the darwin clang/libc oracles.
|
|
runs-on: ubuntu-24.04
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
flavor: [plain, san]
|
|
# Twenty shards keep each job near six minutes of tests (the corpus
|
|
# programs are balanced by measured cost, tests/harness/shard.ts).
|
|
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
|
|
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' || '' }}
|
|
# Match macOS, where Apple ASan has no LeakSanitizer: the RC audit owns
|
|
# leak detection, including intentional abandonment. Plain shards also
|
|
# build some sanitized probes, so this applies to both flavors. The
|
|
# san shards' case-sharded corpus turns LeakSanitizer on
|
|
# (scripts/ci-test-plan.mjs, tests/harness/lsan.supp).
|
|
ASAN_OPTIONS: detect_leaks=0
|
|
# 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 20
|
|
# (tests/harness/shard.ts). The twenty 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 }}/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
|
|
- uses: ./.github/actions/llvm-linux
|
|
- 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 build
|
|
# Node island tests (`// @requires-libnode` corpus programs and
|
|
# node-island.test.ts) load Node from a libnode pack, and fail rather
|
|
# than skip in this lane when none is configured.
|
|
- uses: ./.github/actions/libnode
|
|
# Ubuntu pipes core dumps to apport, and RLIMIT_CORE does not limit a
|
|
# piped dump. The kernel then streams every page of the runtime's
|
|
# reserved allocator address space through the pipe, so each expected
|
|
# abort outlives its test timeout. Write cores to files instead, where
|
|
# the limit below applies.
|
|
- name: Disable piped core dumps
|
|
run: sudo sysctl -w kernel.core_pattern=core
|
|
# 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.
|
|
# The tests run in a transient scope capped below the runner's 16 GB, so
|
|
# the runner itself keeps memory: past 15 GB of RAM the scope swaps, as
|
|
# the whole runner did before, and when RAM and swap run out the kernel
|
|
# kills the scope's largest process. A sanitized shard peaks near 16 GB,
|
|
# and the sanitized self-hosting native-frontend build alone needs 15 GB
|
|
# (its clang compile of the frontend takes 10.5 GB).
|
|
# OOMPolicy=continue leaves the rest of the scope running, so vitest
|
|
# reports the killed worker and the job log names the test. Node sizes
|
|
# its default heap from the memory it may use, which the scope lowers
|
|
# below 4 GB; the workers keep the 4 GB heap they had on the bare
|
|
# runner.
|
|
- name: Tests (corpus and integration suites)
|
|
run: |
|
|
ulimit -c 0
|
|
if [ -d /run/systemd/system ]; then
|
|
exec sudo --preserve-env systemd-run --scope --quiet -p MemoryMax=15G -p OOMPolicy=continue \
|
|
--uid="$(id -u)" --gid="$(id -g)" -- env PATH="$PATH" HOME="$HOME" NODE_OPTIONS="--max-old-space-size=4096" node scripts/ci-test.mjs
|
|
fi
|
|
node scripts/ci-test.mjs
|
|
|
|
tests_macos:
|
|
needs: [validation_scope, lanes, native_macos]
|
|
if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.macos == 'true' }}
|
|
name: test (macOS, ${{ matrix.flavor }}, ${{ (github.event_name != 'schedule' && inputs.macos_suite != 'full') && 'darwin ' || '' }}${{ matrix.shard }}/${{ (github.event_name != 'schedule' && inputs.macos_suite != 'full') && '3' || '10' }})
|
|
# arm64 macOS: same clang/libc the differential + number-formatting
|
|
# oracles were verified against. Pull requests that change darwin code and
|
|
# manual runs with macos_suite set to darwin run the darwin suite
|
|
# (scripts/ci-test-plan.mjs) in three slices; the nightly schedule and
|
|
# other manual runs run the complete suite in ten. The complete suite on
|
|
# every push needed more macOS runners than the repository has.
|
|
runs-on: macos-15
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
flavor: [plain, san]
|
|
shard: ${{ fromJSON((github.event_name != 'schedule' && inputs.macos_suite != 'full') && '[1, 2, 3]' || '[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]') }}
|
|
env:
|
|
# See the Linux tests job for the worker cap and case partitioning.
|
|
SCRIPTC_TEST_WORKERS: "4"
|
|
SCRIPTC_SAN: ${{ matrix.flavor == 'san' && '1' || '' }}
|
|
SCRIPTC_TEST_SHARD: ${{ matrix.shard }}/${{ (github.event_name != 'schedule' && inputs.macos_suite != 'full') && '3' || '10' }}
|
|
SCRIPTC_CI_SUITE: ${{ (github.event_name != 'schedule' && inputs.macos_suite != 'full') && 'darwin' || 'full' }}
|
|
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
|
|
# Node island tests load Node from a libnode pack, and fail rather
|
|
# than skip on macOS CI when none is configured.
|
|
- uses: ./.github/actions/libnode
|
|
- name: Tests (${{ (github.event_name != 'schedule' && inputs.macos_suite != 'full') && 'darwin suite' || 'corpus and integration suites' }})
|
|
run: node scripts/ci-test.mjs
|
|
|
|
native_linux:
|
|
needs: [validation_scope, lanes]
|
|
if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.linux == '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, lanes, native_linux]
|
|
if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.effects == '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
|
|
|
|
# The sanitized three.js nodes build alone: beside the san corpus's other
|
|
# workers it took 860 to more than 1200 seconds of its shard
|
|
# (SCRIPTC_THREE_SANITIZED_NODES_JOB in scripts/ci-test-plan.mjs). It keeps
|
|
# the corpus's LeakSanitizer settings.
|
|
three_nodes_san:
|
|
needs: [validation_scope, lanes, native_linux]
|
|
if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.linux == 'true' }}
|
|
name: test (Linux, san, three.js nodes)
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 40
|
|
env:
|
|
SCRIPTC_SAN: "1"
|
|
ASAN_OPTIONS: detect_leaks=1
|
|
LSAN_OPTIONS: suppressions=${{ github.workspace }}/tests/harness/lsan.supp:print_suppressions=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/three.test.ts -t "'nodes' runs statically through LLVM on 'native'"
|
|
|
|
# Shape inference's overlay (opt-in, SCRIPTC_SHAPE_OVERLAY=1) over the
|
|
# suites of real packages: nightly and on manual runs only (scope
|
|
# shape_overlay runs it alone), outside the `test` roll-up. Every case
|
|
# must build and run as in the default build. It builds its own native
|
|
# packages, so it does not depend on the other lanes.
|
|
shape_overlay_native:
|
|
if: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }}
|
|
name: build (Linux x64 native packages, shape overlay)
|
|
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-shape-overlay
|
|
path: ${{ runner.temp }}/native-linux-x64-gnu.tar.gz
|
|
retention-days: 1
|
|
|
|
shape_overlay:
|
|
needs: [shape_overlay_native]
|
|
if: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }}
|
|
name: test (Linux, shape overlay, ${{ matrix.suite }} ${{ matrix.shard }}/8)
|
|
runs-on: ubuntu-24.04
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
suite: [npm, npm-static, three, effect]
|
|
shard: [1, 2, 3, 4, 5, 6, 7, 8]
|
|
env:
|
|
SCRIPTC_SHAPE_OVERLAY: "1"
|
|
SCRIPTC_TEST_WORKERS: "1"
|
|
SCRIPTC_EFFECT_TEST_CONCURRENCY: "1"
|
|
SCRIPTC_TEST_SHARD: ${{ matrix.shard }}/8
|
|
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-shape-overlay
|
|
path: ${{ runner.temp }}
|
|
- run: tar -xzf "$RUNNER_TEMP/native-linux-x64-gnu.tar.gz"
|
|
# The npm suites' Node island cases load Node from a libnode pack, and
|
|
# fail rather than skip in this lane when none is configured.
|
|
- uses: ./.github/actions/libnode
|
|
if: matrix.suite == 'npm' || matrix.suite == 'npm-static'
|
|
- name: Run the suite with the overlay on
|
|
run: |
|
|
case "${{ matrix.suite }}" in
|
|
npm) pnpm test tests/harness/npm-shape-overlay.test.ts ;;
|
|
npm-static) pnpm test tests/harness/npm-static.test.ts ;;
|
|
three) pnpm test tests/harness/three.test.ts ;;
|
|
effect) pnpm test tests/harness/effect.test.ts tests/harness/effect4.test.ts ;;
|
|
esac
|
|
|
|
# Build each instrumented seed once. Downstream contracts retain the same
|
|
# compiler and source artifacts without repeating seed construction.
|
|
bootstrap_seed:
|
|
needs: [validation_scope, lanes]
|
|
if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.bootstrap == '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
|
|
# The sanitized seed compiles the compiler's 700 MB module with one clang
|
|
# process under AddressSanitizer. It peaks near 14 GB while the Node
|
|
# process that emitted the module still holds about 6 GB. Together they
|
|
# exceed the runner's 16 GB of memory, so back them with swap.
|
|
- name: Enlarge swap for the sanitized seed
|
|
if: matrix.flavor == 'san'
|
|
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: 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, lanes, bootstrap_seed]
|
|
if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.bootstrap == '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, lanes]
|
|
if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.linux == '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, lanes]
|
|
if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.linux == '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
|
|
|
|
# tsc-ts, the default checker, and tsgo (SCRIPTC_CHECKER=tsgo) serve the same TypeScript 7 API. Every corpus
|
|
# program must compile to the same LLVM IR, with the same diagnostics, with either checker.
|
|
checker_identity:
|
|
needs: [validation_scope, lanes]
|
|
if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.linux == 'true' }}
|
|
name: test (tsgo and tsc-ts give the same IR)
|
|
runs-on: ubuntu-24.04
|
|
# The two corpus compiles run back to back and take close to 30 minutes on a
|
|
# slow hosted runner, so a 30-minute limit cancels otherwise healthy runs.
|
|
timeout-minutes: 60
|
|
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: Corpus LLVM IR with tsgo and with tsc-ts (release)
|
|
run: pnpm test:checker-identity --optimization release
|
|
- name: Corpus LLVM IR with tsgo and with tsc-ts (dev)
|
|
run: pnpm test:checker-identity --optimization dev
|
|
|
|
llvm_artifacts_linux_x64:
|
|
needs: [validation_scope, lanes, native_linux]
|
|
if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.linux == 'true' }}
|
|
name: test (Linux x64 LLVM artifacts, no clang)
|
|
runs-on: ubuntu-24.04
|
|
# The glibc helper and runtime pack come from native_linux, as the macOS
|
|
# artifact jobs take theirs from native_macos. The musl packages and the
|
|
# WASI smoke run in llvm_artifacts_linux_x64_musl and
|
|
# llvm_artifacts_linux_x64_wasi: together with a rebuild of the glibc
|
|
# packages, which a runtime change forces, the one job took 25 to 32
|
|
# minutes.
|
|
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/zig
|
|
- 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 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"
|
|
'
|
|
|
|
llvm_artifacts_linux_x64_musl:
|
|
needs: [validation_scope, lanes, native_linux]
|
|
if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.linux == 'true' }}
|
|
name: test (Linux x64 LLVM artifacts, no clang, musl)
|
|
runs-on: ubuntu-24.04
|
|
# The static musl helper and runtime pack, restored from the
|
|
# native-packages cache when their inputs are unchanged: about 9 minutes
|
|
# when both are built. The runtime build emits its units through the
|
|
# host's glibc helper (runtime-unit.mjs), which comes from native_linux.
|
|
timeout-minutes: 25
|
|
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: actions/download-artifact@v4
|
|
with:
|
|
name: native-linux-x64-gnu
|
|
path: ${{ runner.temp }}
|
|
- run: tar -xzf "$RUNNER_TEMP/native-linux-x64-gnu.tar.gz"
|
|
- uses: ./.github/actions/native-packages
|
|
with:
|
|
target: linux-x64-musl
|
|
- name: Verify the Linux x64 musl release helper
|
|
run: |
|
|
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);
|
|
'
|
|
|
|
llvm_artifacts_linux_x64_wasi:
|
|
needs: [validation_scope, lanes, native_linux]
|
|
if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.linux == 'true' }}
|
|
name: test (Linux x64 LLVM artifacts, no clang, wasm32)
|
|
runs-on: ubuntu-24.04
|
|
# The wasm32-wasi runtime pack (restored from the native-packages cache
|
|
# when unchanged; its units go through native_linux's glibc helper) and
|
|
# the WASI smoke tests, which take 6 to 9 minutes.
|
|
timeout-minutes: 25
|
|
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: actions/download-artifact@v4
|
|
with:
|
|
name: native-linux-x64-gnu
|
|
path: ${{ runner.temp }}
|
|
- run: tar -xzf "$RUNNER_TEMP/native-linux-x64-gnu.tar.gz"
|
|
- uses: ./.github/actions/native-packages
|
|
with:
|
|
target: wasm32-wasi
|
|
helper: "false"
|
|
- run: pnpm build
|
|
- 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, lanes, native_macos]
|
|
if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.macos == 'true' }}
|
|
name: test (macOS arm64 LLVM artifacts, no clang, ${{ matrix.shard }}/5)
|
|
runs-on: macos-15
|
|
# Each LLVM differential slice takes 30 to 35 minutes on a hosted runner.
|
|
# The package/install and link-parity contracts run in their own job
|
|
# (llvm_artifacts_macos_arm64_contracts), so no slice carries them.
|
|
timeout-minutes: 50
|
|
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
|
|
# Node island tests load Node from a libnode pack, and fail rather
|
|
# than skip on macOS CI when none is configured.
|
|
- uses: ./.github/actions/libnode
|
|
- name: Node island harness
|
|
if: matrix.shard == 1
|
|
run: pnpm test tests/harness/node-island packages/libnode-common
|
|
- name: LLVM release/dev differential (${{ matrix.shard }}/5)
|
|
env:
|
|
SCRIPTC_TEST_WORKERS: "4"
|
|
run: pnpm test tests/harness/llvm-differential.test.ts
|
|
|
|
llvm_artifacts_macos_arm64_contracts:
|
|
needs: [validation_scope, lanes, native_macos]
|
|
if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.macos == 'true' }}
|
|
name: test (macOS arm64 LLVM artifacts, no clang, contracts)
|
|
runs-on: macos-15
|
|
# The no-clang object, helper parity, packaged helper and packed npm
|
|
# installation contracts: about 12 minutes, which used to run in front
|
|
# of shard 1's differential slice and took that job to 45-50 minutes.
|
|
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
|
|
- 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
|
|
run: pnpm test packages/cli/test/native-output.test.ts
|
|
- name: Helper diagnostics and object/link parity
|
|
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: Packaged helper contract
|
|
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
|
|
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, lanes]
|
|
if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.macos == 'true' }}
|
|
name: test (macOS x64 LLVM artifacts, no clang)
|
|
runs-on: macos-15-intel
|
|
# The Node island tests take most of it.
|
|
timeout-minutes: 45
|
|
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
|
|
# Node island tests load Node from a libnode pack, and fail rather
|
|
# than skip on macOS CI when none is configured.
|
|
- uses: ./.github/actions/libnode
|
|
- name: Node islands (Intel)
|
|
run: >-
|
|
pnpm test tests/harness/node-island packages/libnode-common
|
|
&& pnpm test tests/harness/differential.test.ts -t ' (68[0-9][0-9]|7661|7662)-'
|
|
env:
|
|
SCRIPTC_TEST_WORKERS: "3"
|
|
- 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, lanes, llvm_artifacts_windows_x64]
|
|
if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.linux == '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"
|
|
# The Node host checks with tsc-ts, the default, over the named pipe it
|
|
# serves on Windows, and with tsgo.
|
|
- name: Windows scriptc run smoke
|
|
shell: pwsh
|
|
run: |
|
|
zig version
|
|
foreach ($checker in @("tsc-ts", "tsgo")) {
|
|
$env:SCRIPTC_CHECKER = $checker
|
|
$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 with ${checker}: $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, lanes]
|
|
if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.linux == '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, lanes]
|
|
if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.linux == 'true' }}
|
|
name: test (${{ matrix.runtime }})
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
runtime: [runtime-android-arm64]
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 20
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: ./.github/actions/mobile-runtime-pack
|
|
with:
|
|
runtime: ${{ matrix.runtime }}
|
|
|
|
# The iOS runtime packs build on macOS runners, so they run in the macos lane.
|
|
mobile_runtime_packs_ios:
|
|
needs: [validation_scope, lanes]
|
|
if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.macos == 'true' }}
|
|
name: test (${{ matrix.runtime }})
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
runtime: [runtime-ios-arm64, runtime-ios-simulator-arm64]
|
|
runs-on: macos-15
|
|
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,
|
|
lanes,
|
|
code_quality,
|
|
native_macos,
|
|
tests,
|
|
tests_macos,
|
|
native_linux,
|
|
effects,
|
|
three_nodes_san,
|
|
bootstrap_seed,
|
|
bootstrap,
|
|
linux_host_clang,
|
|
ts7_parity,
|
|
checker_identity,
|
|
llvm_artifacts_linux_x64,
|
|
llvm_artifacts_linux_x64_musl,
|
|
llvm_artifacts_linux_x64_wasi,
|
|
llvm_artifacts_macos_arm64,
|
|
llvm_artifacts_macos_arm64_contracts,
|
|
llvm_artifacts_macos_x64,
|
|
windows_cli,
|
|
llvm_artifacts_windows_x64,
|
|
mobile_runtime_packs,
|
|
mobile_runtime_packs_ios,
|
|
]
|
|
# 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.lanes.result }}" = success
|
|
# A lane job passes when it succeeded, or when it was skipped because its lane is off.
|
|
lane() { [ "$2" = success ] || { [ "$3" = false ] && [ "$2" = skipped ]; } || { echo "$1: $2 (lane $3)"; exit 1; }; }
|
|
lane native_macos "${{ needs.native_macos.result }}" "${{ needs.lanes.outputs.macos }}"
|
|
lane tests "${{ needs.tests.result }}" "${{ needs.lanes.outputs.linux }}"
|
|
lane tests_macos "${{ needs.tests_macos.result }}" "${{ needs.lanes.outputs.macos }}"
|
|
lane native_linux "${{ needs.native_linux.result }}" "${{ needs.lanes.outputs.linux }}"
|
|
lane effects "${{ needs.effects.result }}" "${{ needs.lanes.outputs.effects }}"
|
|
lane three_nodes_san "${{ needs.three_nodes_san.result }}" "${{ needs.lanes.outputs.linux }}"
|
|
lane bootstrap_seed "${{ needs.bootstrap_seed.result }}" "${{ needs.lanes.outputs.bootstrap }}"
|
|
lane bootstrap "${{ needs.bootstrap.result }}" "${{ needs.lanes.outputs.bootstrap }}"
|
|
lane linux_host_clang "${{ needs.linux_host_clang.result }}" "${{ needs.lanes.outputs.linux }}"
|
|
lane ts7_parity "${{ needs.ts7_parity.result }}" "${{ needs.lanes.outputs.linux }}"
|
|
lane checker_identity "${{ needs.checker_identity.result }}" "${{ needs.lanes.outputs.linux }}"
|
|
lane llvm_artifacts_linux_x64 "${{ needs.llvm_artifacts_linux_x64.result }}" "${{ needs.lanes.outputs.linux }}"
|
|
lane llvm_artifacts_linux_x64_musl "${{ needs.llvm_artifacts_linux_x64_musl.result }}" "${{ needs.lanes.outputs.linux }}"
|
|
lane llvm_artifacts_linux_x64_wasi "${{ needs.llvm_artifacts_linux_x64_wasi.result }}" "${{ needs.lanes.outputs.linux }}"
|
|
lane llvm_artifacts_macos_arm64 "${{ needs.llvm_artifacts_macos_arm64.result }}" "${{ needs.lanes.outputs.macos }}"
|
|
lane llvm_artifacts_macos_arm64_contracts "${{ needs.llvm_artifacts_macos_arm64_contracts.result }}" "${{ needs.lanes.outputs.macos }}"
|
|
lane llvm_artifacts_macos_x64 "${{ needs.llvm_artifacts_macos_x64.result }}" "${{ needs.lanes.outputs.macos }}"
|
|
lane windows_cli "${{ needs.windows_cli.result }}" "${{ needs.lanes.outputs.linux }}"
|
|
lane llvm_artifacts_windows_x64 "${{ needs.llvm_artifacts_windows_x64.result }}" "${{ needs.lanes.outputs.linux }}"
|
|
lane mobile_runtime_packs "${{ needs.mobile_runtime_packs.result }}" "${{ needs.lanes.outputs.linux }}"
|
|
lane mobile_runtime_packs_ios "${{ needs.mobile_runtime_packs_ios.result }}" "${{ needs.lanes.outputs.macos }}"
|
|
;;
|
|
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
|