Skip to content

Commit b842278

Browse files
authored
ci: race tests/lints/images in parallel; gate publishing on the green verdict (#82)
Push-to-image latency is the target: the release build legs produce the image artifacts in parallel with ci from the first second (both start warm off the shared deps cache), and ci itself fans back out into three independent lanes (x86 tests, arm tests, arm lints) plus the node addon — wall time is the slowest lane, not a staged pipeline. Shipping moves to the gate instead: tag-time publish/promote/registry jobs require the tagged commit's ci run to have concluded green (fail closed if missing). nix-checks.sh takes an optional attr subset so lanes build only their slice; every lane still builds deps first and publishes the binary cache immediately.
1 parent a3003f5 commit b842278

3 files changed

Lines changed: 96 additions & 38 deletions

File tree

.github/workflows/ci.yml

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -124,27 +124,35 @@ jobs:
124124
path: .win-green-marker
125125
key: windows-green-${{ needs.windows-gate.outputs.hash }}
126126

127-
# One staged pipeline per arch, everything on a single runner: deps are
128-
# built and published to the binary cache FIRST (a later stage failing
129-
# never costs the next run its warm deps), then the source-keyed
130-
# build/test/lint work, then the cheap fan-out (image, node addon) reuses
131-
# the already-hot store instead of paying setup+restore on fresh runners.
132-
pipeline:
133-
name: ${{ matrix.system }} pipeline
127+
# Everything races in parallel off the shared binary cache: each lane
128+
# restores, builds its deps closures, publishes the cache immediately
129+
# (no-op unless the key is new), then runs only its slice of the checks.
130+
# Image artifacts are produced by the release workflow's build legs,
131+
# which start on the same push in parallel with all of this; publishing
132+
# is what gates on green, not building.
133+
nix-ci:
134+
name: ${{ matrix.lane }}
134135
strategy:
135136
fail-fast: false
136137
matrix:
137138
include:
138-
- runner: ubuntu-24.04
139+
- lane: x86 tests
140+
runner: ubuntu-24.04
139141
system: x86_64-linux
140142
deps: .#checks.x86_64-linux.deps
141-
image: dockerImage
142-
- runner: ubuntu-24.04-arm
143+
attrs: nextest native-smoke
144+
- lane: arm tests
145+
runner: ubuntu-24.04-arm
146+
system: aarch64-linux
147+
deps: .#checks.aarch64-linux.deps
148+
attrs: nextest lambda-tests native-smoke
149+
- lane: arm lints
150+
runner: ubuntu-24.04-arm
143151
system: aarch64-linux
144152
deps: .#checks.aarch64-linux.deps .#checks.aarch64-linux.wasm-deps
145-
image: lambdaImage
153+
attrs: clippy fmt no-node-spawn wasm-check
146154
runs-on: ${{ matrix.runner }}
147-
timeout-minutes: 60
155+
timeout-minutes: 45
148156
steps:
149157
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
150158
# No standalone nix installer: the composite runs it with the
@@ -154,27 +162,29 @@ jobs:
154162
- name: deps (dependency closures)
155163
run: nix build --no-link ${{ matrix.deps }}
156164
# Publish immediately and from any branch: the composite no-ops
157-
# unless the exact key is new, so this fires once per input rotation.
165+
# unless the exact key is new, so this fires once per input rotation
166+
# (lanes sharing a key race; first save wins, the rest skip).
158167
- uses: ./.github/actions/nix-store-cache/save
159168
continue-on-error: true
160-
- name: build + checks
161-
run: bash ci/nix-checks.sh ${{ matrix.system }}
162-
# Fan-out: cheap on the hot store. Skipped on main pushes, where the
163-
# release workflow builds the real thing from the same cache.
164-
- name: image smoke (${{ matrix.image }})
165-
if: github.event_name != 'push'
166-
run: nix build --no-link .#${{ matrix.image }}
169+
- name: checks (${{ matrix.attrs }})
170+
run: bash ci/nix-checks.sh ${{ matrix.system }} ${{ matrix.attrs }}
171+
172+
node-addon:
173+
name: node addon
174+
if: github.event_name != 'push'
175+
runs-on: ubuntu-latest
176+
timeout-minutes: 20
177+
steps:
178+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
167179
- uses: ./.github/actions/rust-setup
168-
if: github.event_name != 'push'
169180
with:
170181
# Pure consumer of the cache the release napi linux legs save from
171182
# main pushes (same shared-key); PR-scoped saves would be quota
172183
# waste.
173184
workspaces: crate/abgen-node
174185
shared-key: abgen-node
175186
save-if: "false"
176-
- name: node addon (build + smoke)
177-
if: github.event_name != 'push'
187+
- name: abgen-node (build + smoke)
178188
working-directory: crate/abgen-node
179189
run: |
180190
npm ci --no-audit --no-fund
@@ -187,4 +197,3 @@ jobs:
187197
# crate/abgen-node now that the step runs there.
188198
bash ../../ci/check-glibc-floor.sh 2.34 \
189199
target/release/libabgen_node.so
190-

.github/workflows/release.yml

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,38 @@ jobs:
701701
path: lambdaImage.tar.gz
702702
if-no-files-found: error
703703

704+
# Building races ahead of the test verdict; SHIPPING is what gates on it.
705+
# A tag's commit already ran ci when it landed on main — this waits for
706+
# that verdict and fails closed if it is missing or red.
707+
ci-green:
708+
name: ci green gate
709+
if: startsWith(github.ref, 'refs/tags/')
710+
runs-on: ubuntu-24.04
711+
timeout-minutes: 40
712+
permissions:
713+
actions: read
714+
contents: read
715+
steps:
716+
- name: wait for the commit's ci verdict
717+
env:
718+
GH_TOKEN: ${{ github.token }}
719+
run: |
720+
set -euo pipefail
721+
for _ in $(seq 1 40); do
722+
run="$(gh api "/repos/$GITHUB_REPOSITORY/actions/workflows/ci.yml/runs?head_sha=$GITHUB_SHA&per_page=1" \
723+
--jq '"\(.workflow_runs[0].status // "none") \(.workflow_runs[0].conclusion // "")"')"
724+
status="${run%% *}"; conclusion="${run#* }"
725+
[ "$conclusion" = "success" ] && exit 0
726+
if [ "$status" = "completed" ]; then
727+
echo "ci for $GITHUB_SHA concluded: ${conclusion:-none}" >&2
728+
exit 1
729+
fi
730+
echo "ci for $GITHUB_SHA: $status — waiting"
731+
sleep 60
732+
done
733+
echo "timed out waiting for the ci verdict" >&2
734+
exit 1
735+
704736
promote:
705737
name: promote prebuilt targets
706738
needs: build-id
@@ -797,11 +829,15 @@ jobs:
797829

798830
image:
799831
name: ghcr image
800-
needs: build-id
801-
# Tags always run (they push); otherwise only when no prebuilt artifact
802-
# exists yet — a non-tag run with a prebuilt would skip every step and
803-
# burn a runner fetching an artifact it discards.
804-
if: startsWith(github.ref, 'refs/tags/') || needs.build-id.outputs.image_docker == ''
832+
needs: [build-id, ci-green]
833+
# Tags always run (they push) but only with a green ci verdict for the
834+
# commit; otherwise only when no prebuilt artifact exists yet — a
835+
# non-tag run with a prebuilt would skip every step and burn a runner
836+
# fetching an artifact it discards.
837+
if: >-
838+
!cancelled() && needs.build-id.result == 'success' &&
839+
((startsWith(github.ref, 'refs/tags/') && needs.ci-green.result == 'success') ||
840+
(!startsWith(github.ref, 'refs/tags/') && needs.build-id.outputs.image_docker == ''))
805841
runs-on: ubuntu-24.04
806842
permissions:
807843
contents: read
@@ -875,9 +911,13 @@ jobs:
875911
876912
lambda-image:
877913
name: ECR lambda image
878-
needs: build-id
879-
# Same gate as the ghcr image: tags push, non-tag runs only build.
880-
if: startsWith(github.ref, 'refs/tags/') || needs.build-id.outputs.image_lambda == ''
914+
needs: [build-id, ci-green]
915+
# Same gate as the ghcr image: tags push (green ci only), non-tag runs
916+
# only build.
917+
if: >-
918+
!cancelled() && needs.build-id.result == 'success' &&
919+
((startsWith(github.ref, 'refs/tags/') && needs.ci-green.result == 'success') ||
920+
(!startsWith(github.ref, 'refs/tags/') && needs.build-id.outputs.image_lambda == ''))
881921
runs-on: ubuntu-24.04-arm
882922
permissions:
883923
contents: read
@@ -980,11 +1020,12 @@ jobs:
9801020
9811021
napi-publish:
9821022
name: publish @dcl/abgen-node
983-
needs: [build-id, napi]
1023+
needs: [build-id, napi, ci-green]
9841024
runs-on: ubuntu-24.04
9851025
if: >-
9861026
!cancelled() &&
9871027
startsWith(github.ref, 'refs/tags/v') && inputs.dry_run != true &&
1028+
needs.ci-green.result == 'success' &&
9881029
(needs.napi.result == 'success' ||
9891030
(needs.napi.result == 'skipped' && needs.build-id.outputs.napi_promote == 'true'))
9901031
permissions:
@@ -1046,11 +1087,12 @@ jobs:
10461087
npm publish --access public
10471088
10481089
publish:
1049-
needs: [build, promote]
1090+
needs: [build, promote, ci-green]
10501091
runs-on: ubuntu-24.04
10511092
if: >-
10521093
!cancelled() &&
10531094
startsWith(github.ref, 'refs/tags/v') && inputs.dry_run != true &&
1095+
needs.ci-green.result == 'success' &&
10541096
needs.build.result != 'failure' && needs.build.result != 'cancelled' &&
10551097
needs.promote.result != 'failure' && needs.promote.result != 'cancelled'
10561098
permissions:

ci/nix-checks.sh

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,17 @@
22

33
set -euo pipefail
44

5-
system="${1:?usage: nix-checks.sh <system>}"
6-
7-
names="$(nix eval --raw ".#checks.${system}" \
8-
--apply 'checks: builtins.concatStringsSep "\n" (builtins.attrNames checks)')"
5+
system="${1:?usage: nix-checks.sh <system> [attr...]}"
6+
shift || true
7+
8+
# With attr names, build only that subset (lanes split the check set across
9+
# parallel jobs); without, build every attr the system carries.
10+
if [ $# -gt 0 ]; then
11+
names="$(printf '%s\n' "$@")"
12+
else
13+
names="$(nix eval --raw ".#checks.${system}" \
14+
--apply 'checks: builtins.concatStringsSep "\n" (builtins.attrNames checks)')"
15+
fi
916

1017
[ -n "$names" ] || { echo "no checks for ${system}" >&2; exit 1; }
1118

0 commit comments

Comments
 (0)