1616 description : ' Split each C-suite leg across parallel shard jobs (ubuntu x3, windows x2); off = one job per leg, byte-identical to the pre-shard topology'
1717 type : boolean
1818 default : false
19+ shard_profile :
20+ description : ' Shard counts when shard_suites is on: standard = ubuntu x3, windows x2 (dry run, release); pr = ubuntu x86 x1, ubuntu arm64 x2, windows x1 (fewer jobs re-paying setup)'
21+ type : string
22+ default : standard
23+ lanes :
24+ description : ' PR lane selection (scripts/ci/select-lanes.sh JSON list): only those legs and jobs run; "all" = every one (dry run, release)'
25+ type : string
26+ default : all
27+ test_selection :
28+ description : ' PR test selection (smart CI): the artifact the select-tests job uploaded; the test-unix and test-windows legs run only its tests. Empty = every test (dry run, release, and any PR the selection does not narrow)'
29+ type : string
30+ default : ' '
1931
2032permissions :
2133 contents : read
@@ -30,18 +42,26 @@ jobs:
3042 outputs :
3143 unix : ${{ steps.set.outputs.unix }}
3244 windows : ${{ steps.set.outputs.windows }}
45+ tsan : ${{ steps.set.outputs.tsan }}
46+ has_unix : ${{ steps.set.outputs.has_unix }}
47+ has_windows : ${{ steps.set.outputs.has_windows }}
48+ has_tsan : ${{ steps.set.outputs.has_tsan }}
3349 steps :
3450 - name : Compute matrices
3551 id : set
3652 env :
3753 BROAD : ${{ inputs.broad_platforms }}
3854 SHARDS : ${{ inputs.shard_suites }}
55+ SHARD_PROFILE : ${{ inputs.shard_profile }}
56+ LANES : ${{ inputs.lanes }}
3957 run : |
58+ # Every CORE leg is tagged with the lane (scripts/ci/select-lanes.sh)
59+ # it serves; the tag is what lane selection filters on below.
4060 CORE_UNIX='[
41- {"os":"ubuntu-latest","cc":"gcc","cxx":"g++"},
42- {"os":"ubuntu-24.04-arm","cc":"gcc","cxx":"g++"},
43- {"os":"macos-14","cc":"cc","cxx":"c++"},
44- {"os":"macos-15-intel","cc":"cc","cxx":"c++"}
61+ {"os":"ubuntu-latest","cc":"gcc","cxx":"g++","lane":"unix-x86" },
62+ {"os":"ubuntu-24.04-arm","cc":"gcc","cxx":"g++","lane":"unix-arm64" },
63+ {"os":"macos-14","cc":"cc","cxx":"c++","lane":"unix-macos14" },
64+ {"os":"macos-15-intel","cc":"cc","cxx":"c++","lane":"unix-macos-intel" }
4565 ]'
4666 # Broad matrix legs are REQUIRED gates (no optional/continue-on-error
4767 # escape hatches): the release dry run must be all-green, every platform.
5979 # interception crashed (interception_win: unhandled instruction). With
6080 # the native ARM64 toolchain ASan instruments native ARM64 code, so it
6181 # is a real (non-optional) gate, not a tolerated emulated-flake.
62- CORE_WIN='[{"os":"windows-latest","msystem":"CLANG64","pkg":"x86_64"}]'
82+ CORE_WIN='[{"os":"windows-latest","msystem":"CLANG64","pkg":"x86_64","lane":"windows" }]'
6383 BROAD_WIN='[{"os":"windows-2025","msystem":"CLANG64","pkg":"x86_64"},{"os":"windows-11-arm","msystem":"CLANGARM64","pkg":"aarch64"}]'
6484 if [ "$BROAD" = "true" ]; then
6585 UNIX=$(jq -cn --argjson a "$CORE_UNIX" --argjson b "$BROAD_UNIX" '$a + $b')
6888 UNIX=$(jq -cn --argjson a "$CORE_UNIX" '$a')
6989 WIN=$(jq -cn --argjson a "$CORE_WIN" '$a')
7090 fi
91+ # The ThreadSanitizer legs (test-tsan), tagged the same way.
92+ TSAN='[
93+ {"os":"ubuntu-latest","cc":"clang","cxx":"clang++","lane":"tsan-x86"},
94+ {"os":"ubuntu-24.04-arm","cc":"clang","cxx":"clang++","lane":"tsan-arm"},
95+ {"os":"macos-14","cc":"cc","cxx":"c++","lane":"tsan-mac"}
96+ ]'
97+ # Lane selection. "all" (dry run, release) keeps every leg and drops
98+ # the tags, so those matrices -- and their job names -- stay exactly
99+ # as before lane selection. A PR keeps only its selected legs, and
100+ # the tag stays in the job name, where ci-ok reads it back. An
101+ # untagged leg could never be selected: that is a configuration
102+ # error here, never a leg silently dropped.
103+ if [ "$LANES" = "all" ]; then
104+ UNIX=$(jq -c 'map(del(.lane))' <<< "$UNIX")
105+ WIN=$(jq -c 'map(del(.lane))' <<< "$WIN")
106+ TSAN=$(jq -c 'map(del(.lane))' <<< "$TSAN")
107+ else
108+ for M in "$UNIX" "$WIN" "$TSAN"; do
109+ jq -e 'all(.[]; has("lane"))' <<< "$M" > /dev/null || { echo "::error::untagged test leg in $M"; exit 1; }
110+ done
111+ UNIX=$(jq -c --argjson sel "$LANES" 'map(select(.lane | IN($sel[])))' <<< "$UNIX")
112+ WIN=$(jq -c --argjson sel "$LANES" 'map(select(.lane | IN($sel[])))' <<< "$WIN")
113+ TSAN=$(jq -c --argjson sel "$LANES" 'map(select(.lane | IN($sel[])))' <<< "$TSAN")
114+ fi
71115 # Suite sharding (opt-in via the shard_suites input): each platform's
72116 # C-suite run is split across N runner jobs. CBM_TEST_SHARD="i/N"
73117 # slices deterministically inside run-tests-parallel.sh; each
@@ -78,20 +122,37 @@ jobs:
78122 # shards); Windows gets 2 (every extra shard re-pays ~5 min of
79123 # MSYS2 setup); macOS stays at 1 — mac legs are not the critical
80124 # path and mac runner concurrency ceilings are the tightest.
125+ # The pr profile shards less: a PR runs few legs and every shard
126+ # re-pays its setup, so x86 and Windows run whole and only arm64,
127+ # the slowest leg (~72 min unsharded), splits in two.
128+ case "$SHARD_PROFILE" in
129+ standard) N_X86=3; N_ARM=3; N_WIN=2 ;;
130+ pr) N_X86=1; N_ARM=2; N_WIN=1 ;;
131+ *) echo "::error::unknown shard_profile '$SHARD_PROFILE'"; exit 1 ;;
132+ esac
81133 if [ "$SHARDS" = "true" ]; then
82- UNIX=$(jq -c '[.[] | . as $e
83- | (if ($e.os | startswith("ubuntu")) then 3 else 1 end) as $n
134+ UNIX=$(jq -c --argjson x86 "$N_X86" --argjson arm "$N_ARM" '[.[] | . as $e
135+ | (if ($e.os | startswith("ubuntu")) then
136+ (if ($e.os | endswith("-arm")) then $arm else $x86 end)
137+ else 1 end) as $n
84138 | range(1; $n + 1) as $i
85139 | $e + {shard: "\($i)/\($n)"}]' <<< "$UNIX")
86- WIN=$(jq -c '[.[] | . as $e
87- | range(1; 3 ) as $i
88- | $e + {shard: "\($i)/2 "}]' <<< "$WIN")
140+ WIN=$(jq -c --argjson n "$N_WIN" '[.[] | . as $e
141+ | range(1; $n + 1 ) as $i
142+ | $e + {shard: "\($i)/\($n) "}]' <<< "$WIN")
89143 fi
90144 echo "unix={\"include\":$UNIX}" >> "$GITHUB_OUTPUT"
91145 echo "windows={\"include\":$WIN}" >> "$GITHUB_OUTPUT"
146+ echo "tsan={\"include\":$TSAN}" >> "$GITHUB_OUTPUT"
147+ # GitHub rejects an empty matrix, so a job whose legs were all
148+ # deselected is skipped through these flags instead.
149+ echo "has_unix=$(jq 'length > 0' <<< "$UNIX")" >> "$GITHUB_OUTPUT"
150+ echo "has_windows=$(jq 'length > 0' <<< "$WIN")" >> "$GITHUB_OUTPUT"
151+ echo "has_tsan=$(jq 'length > 0' <<< "$TSAN")" >> "$GITHUB_OUTPUT"
92152
93153 test-unix :
94154 needs : setup-matrix
155+ if : ${{ needs.setup-matrix.outputs.has_unix == 'true' }}
95156 strategy :
96157 fail-fast : false
97158 matrix : ${{ fromJSON(needs.setup-matrix.outputs.unix) }}
@@ -160,12 +221,20 @@ jobs:
160221 ccache-test-${{ matrix.os }}-${{ matrix.cc }}-${{ github.ref }}-
161222 ccache-test-${{ matrix.os }}-${{ matrix.cc }}-
162223
224+ - name : Fetch the test selection
225+ if : ${{ inputs.test_selection != '' }}
226+ uses : actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
227+ with :
228+ name : ${{ inputs.test_selection }}
229+ path : ${{ runner.temp }}/test-selection
230+
163231 - name : Test
164232 run : scripts/test.sh CC=${{ matrix.cc }} CXX=${{ matrix.cxx }}
165233 env :
166234 CBM_TEST_SHARD : ${{ matrix.shard }}
167235 CBM_TEST_LEG : ${{ matrix.os }}-${{ matrix.cc }}
168236 CBM_SKIP_PERF : ${{ inputs.skip_perf && '1' || '' }}
237+ CBM_TEST_SELECTION_DIR : ${{ inputs.test_selection != '' && format('{0}/test-selection', runner.temp) || '' }}
169238 CCACHE_DIR : ${{ github.workspace }}/.ccache
170239 CCACHE_MAXSIZE : 1500M
171240
@@ -205,6 +274,7 @@ jobs:
205274 # inode/flock behavior and Windows handle/link behavior are each gating,
206275 # without adding Node/Go provisioning to every sanitizer/container leg.
207276 test-package-wrappers :
277+ if : ${{ inputs.lanes == 'all' || contains(fromJSON(inputs.lanes), 'pkg-wrappers') }}
208278 strategy :
209279 fail-fast : false
210280 matrix :
@@ -245,7 +315,10 @@ jobs:
245315 # parallel suite — catches what an older compiler-rt can miss. Same
246316 # canonical scripts/test.sh wave as every other leg.
247317 test-diag :
248- runs-on : ubuntu-latest
318+ if : ${{ inputs.lanes == 'all' || contains(fromJSON(inputs.lanes), 'diag') }}
319+ # Pinned for the same reason as the lint jobs: the pinned diagnostic
320+ # toolchain below installs from llvm-toolchain-noble-21.
321+ runs-on : ubuntu-24.04
249322 timeout-minutes : 120
250323 steps :
251324 - uses : actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
@@ -308,6 +381,7 @@ jobs:
308381 # list both venues now share. Zero use-of-uninitialized-value findings were
309382 # reported across the lane on either architecture.
310383 test-msan :
384+ if : ${{ inputs.lanes == 'all' || contains(fromJSON(inputs.lanes), 'msan') }}
311385 runs-on : ubuntu-latest
312386 timeout-minutes : 180
313387 steps :
@@ -332,6 +406,7 @@ jobs:
332406 # so this leg is the ordinary ASan suite built with that toolchain and run
333407 # with detect_leaks=1. Mirrors the local `make -f Makefile.cbm test-lsan`.
334408 test-lsan-macos :
409+ if : ${{ inputs.lanes == 'all' || contains(fromJSON(inputs.lanes), 'lsan-macos') }}
335410 runs-on : macos-latest
336411 timeout-minutes : 120
337412 steps :
@@ -370,13 +445,11 @@ jobs:
370445 key : ccache-lsan-${{ github.ref }}-${{ github.sha }}
371446
372447 test-tsan :
448+ needs : setup-matrix
449+ if : ${{ needs.setup-matrix.outputs.has_tsan == 'true' }}
373450 strategy :
374451 fail-fast : false
375- matrix :
376- include :
377- - { os: ubuntu-latest, cc: clang, cxx: clang++ }
378- - { os: ubuntu-24.04-arm, cc: clang, cxx: clang++ }
379- - { os: macos-14, cc: cc, cxx: c++ }
452+ matrix : ${{ fromJSON(needs.setup-matrix.outputs.tsan) }}
380453 runs-on : ${{ matrix.os }}
381454 timeout-minutes : 120
382455 steps :
@@ -429,6 +502,7 @@ jobs:
429502
430503 test-windows :
431504 needs : setup-matrix
505+ if : ${{ needs.setup-matrix.outputs.has_windows == 'true' }}
432506 strategy :
433507 fail-fast : false
434508 matrix : ${{ fromJSON(needs.setup-matrix.outputs.windows) }}
@@ -479,6 +553,13 @@ jobs:
479553 ccache-test-${{ matrix.os }}-${{ matrix.msystem }}-${{ github.ref }}-
480554 ccache-test-${{ matrix.os }}-${{ matrix.msystem }}-
481555
556+ - name : Fetch the test selection
557+ if : ${{ inputs.test_selection != '' }}
558+ uses : actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
559+ with :
560+ name : ${{ inputs.test_selection }}
561+ path : ${{ runner.temp }}/test-selection
562+
482563 - name : Test
483564 shell : msys2 {0}
484565 # AddressSanitizer is unavailable on native ARM64 Windows (LLVM ships no
@@ -506,6 +587,7 @@ jobs:
506587 CBM_TEST_SHARD : ${{ matrix.shard }}
507588 CBM_TEST_LEG : ${{ matrix.os }}-${{ matrix.msystem }}
508589 CBM_SKIP_PERF : ${{ inputs.skip_perf && '1' || '' }}
590+ CBM_TEST_SELECTION_DIR : ${{ inputs.test_selection != '' && format('{0}/test-selection', runner.temp) || '' }}
509591 CCACHE_DIR : ${{ github.workspace }}/.ccache
510592 CCACHE_MAXSIZE : 1500M
511593
@@ -562,8 +644,12 @@ jobs:
562644 with :
563645 pattern : shard-manifest-*
564646 path : manifests
647+ # A PR with no test leg selected uploads no manifest; the script
648+ # accepts that only when the selection really holds no leg.
565649 - name : Verify cross-shard union per leg
566- run : repo/scripts/ci/verify-shard-union.sh manifests
650+ env :
651+ LANES : ${{ inputs.lanes }}
652+ run : repo/scripts/ci/verify-shard-union.sh manifests --lanes "$LANES"
567653
568654 # Windows product-surface regression guards. Distinct from test-windows above
569655 # (the sanitizer C suite): these drive a real product binary + external UI pack
@@ -574,6 +660,7 @@ jobs:
574660 # CLI arguments being mangled by the narrow-argv main() (#423/#20, fixed by the
575661 # wide-argv entrypoint that reads GetCommandLineW).
576662 test-windows-guards :
663+ if : ${{ inputs.lanes == 'all' || contains(fromJSON(inputs.lanes), 'windows-guards') }}
577664 runs-on : windows-latest
578665 timeout-minutes : 60
579666 steps :
0 commit comments