ci: PR shard profile (x86 and Windows whole, arm64 x2)

_test.yml takes a shard_profile input: "standard" (the default; dry run and
release pass nothing and keep ubuntu x3 / windows x2, byte-identical
matrices) or "pr", which pr.yml now uses: ubuntu x86 and Windows run as one
job each and only arm64, the slowest leg, splits in two. A PR runs few legs
and every shard re-pays its setup, so fewer, longer jobs cost less. An
unknown profile is an error. The wiring contract pins both the default and
PR's use of the profile.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
Martin Vogel committed 2026-09-30 23:30:57 +02:00
1 parent a8d5a8154f
commit ae5bb0bdea
3 files changed
+33 -10

No files matched your search

+20 -5
View File
@@ -16,6 +16,10 @@ on:
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'
type: boolean
default: false
shard_profile:
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)'
type: string
default: standard
lanes:
description: 'PR lane selection (scripts/ci/select-lanes.sh JSON list): only those legs and jobs run; "all" = every one (dry run, release)'
type: string
@@ -44,6 +48,7 @@ jobs:
env:
BROAD: ${{ inputs.broad_platforms }}
SHARDS: ${{ inputs.shard_suites }}
SHARD_PROFILE: ${{ inputs.shard_profile }}
LANES: ${{ inputs.lanes }}
run: |
# Every CORE leg is tagged with the lane (scripts/ci/select-lanes.sh)
@@ -113,14 +118,24 @@ jobs:
# shards); Windows gets 2 (every extra shard re-pays ~5 min of
# MSYS2 setup); macOS stays at 1 — mac legs are not the critical
# path and mac runner concurrency ceilings are the tightest.
# The pr profile shards less: a PR runs few legs and every shard
# re-pays its setup, so x86 and Windows run whole and only arm64,
# the slowest leg (~72 min unsharded), splits in two.
case "$SHARD_PROFILE" in
standard) N_X86=3; N_ARM=3; N_WIN=2 ;;
pr) N_X86=1; N_ARM=2; N_WIN=1 ;;
*) echo "::error::unknown shard_profile '$SHARD_PROFILE'"; exit 1 ;;
esac
if [ "$SHARDS" = "true" ]; then
UNIX=$(jq -c '[.[] | . as $e
| (if ($e.os | startswith("ubuntu")) then 3 else 1 end) as $n
UNIX=$(jq -c --argjson x86 "$N_X86" --argjson arm "$N_ARM" '[.[] | . as $e
| (if ($e.os | startswith("ubuntu")) then
(if ($e.os | endswith("-arm")) then $arm else $x86 end)
else 1 end) as $n
| range(1; $n + 1) as $i
| $e + {shard: "\($i)/\($n)"}]' <<< "$UNIX")
WIN=$(jq -c '[.[] | . as $e
| range(1; 3) as $i
| $e + {shard: "\($i)/2"}]' <<< "$WIN")
WIN=$(jq -c --argjson n "$N_WIN" '[.[] | . as $e
| range(1; $n + 1) as $i
| $e + {shard: "\($i)/\($n)"}]' <<< "$WIN")
fi
echo "unix={\"include\":$UNIX}" >> "$GITHUB_OUTPUT"
echo "windows={\"include\":$WIN}" >> "$GITHUB_OUTPUT"
+5 -3
View File
@@ -53,10 +53,12 @@ jobs:
# Perf assertions are timing-sensitive on shared runners; they stay in
# dry runs and releases where a flaky red does not block a merge.
skip_perf: true
# Iteration speed: split each C-suite leg across parallel shard jobs
# (ubuntu x3, windows x2; coverage re-proven every run by the
# shard-completeness job). Dry runs and releases shard identically.
# Iteration speed: split C-suite legs across parallel shard jobs
# (coverage re-proven every run by the shard-completeness job). The PR
# profile shards only arm64 (x2); x86 and Windows run whole, since each
# shard re-pays its setup. Dry runs and releases keep x3 / x2.
shard_suites: true
shard_profile: pr
# ── Which lanes does this PR need? The changed files go through
# scripts/ci/select-lanes.sh (tier, lanes, full, reasons); docs / CI /
+8 -2
View File
@@ -3,8 +3,9 @@
# are selected at all.
#
# 1. dry-run.yml and release.yml pass no `lanes` (and no shard profile) to
# _test.yml / _lint.yml / _security.yml, whose inputs default to "all":
# those runs execute every job exactly as before lane selection.
# _test.yml / _lint.yml / _security.yml, whose inputs default to "all"
# (and "standard"): those runs execute every job, sharded, exactly as
# before lane selection. Only pr.yml asks for the PR shard profile.
# 2. pr.yml feeds the selector's lanes to every reusable workflow, runs the
# docs-only contracts job, and hands ci-ok both the selection and the
# run's job list (actions: read) -- ci-ok needs `contracts` but never the
@@ -62,6 +63,9 @@ for caller in ("dry-run.yml", "release.yml"):
require(not re.search(r"(?m)^\s+(lanes|shard_profile):", body),
f"{caller} job {job} passes a lane selection to {called.group(1)}; "
f"dry runs and releases must run every lane")
m = re.search(r"(?ms)^ shard_profile:\n(.*?)(?=^ \S|^\S|\Z)", text("_test.yml"))
require(m is not None and re.search(r"(?m)^ default: standard$", m.group(1)) is not None,
"_test.yml must declare a shard_profile input defaulting to standard")
for callee in REUSABLE:
src = text(callee)
m = re.search(r"(?ms)^ lanes:\n(.*?)(?=^ \S|^\S|\Z)", src)
@@ -83,6 +87,8 @@ for key in ("tier", "full", "lanes"):
for job in ("security", "lint", "test"):
require("lanes: ${{ needs.changes.outputs.lanes }}" in pr.get(job, ""),
f"pr.yml {job} must pass the selected lanes")
require(re.search(r"(?m)^ shard_profile: pr$", pr.get("test", "")) is not None,
"pr.yml test must use the PR shard profile")
require("scripts/test.sh --contracts-only" in pr.get("contracts", ""),
"pr.yml contracts job must run scripts/test.sh --contracts-only")
ci_ok = pr.get("ci-ok", "")