Repository navigation
969 lines (946 loc) · 42.9 KB
/
Copy pathci.yml
File metadata and controls
969 lines (946 loc) · 42.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
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
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.
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
# 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
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
# 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.
- name: Tests (corpus and integration suites)
run: |
ulimit -c 0
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
- 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
# 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
- 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
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
- 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]
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
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, 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
# 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, 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
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, 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"
- 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, 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,
bootstrap_seed,
bootstrap,
linux_host_clang,
ts7_parity,
checker_identity,
llvm_artifacts_linux_x64,
llvm_artifacts_macos_arm64,
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 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_macos_arm64 "${{ needs.llvm_artifacts_macos_arm64.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