Skip to content

Commit 7102abb

Browse files
committed
fix: resolve the openkal index and runtime revisions once per run
The four build jobs of openkal-cross.yml each cloned `main` of the index and of the runtime of their own accord, at four different moments on four runners that become available at four different times, and the run jobs then asserted that all four had built the same sources. On 2026-10-13 that assertion failed for a reason the change under test had nothing to do with: run 38399400601 started at 12:43 UTC, its build jobs at 12:52:08 through 13:01:33, and mcpp-index@main took four commits inside that window (21b9a77a 12:56:12, d3a328c8 13:04:42, cfb808bc 13:10:06, 0340de61 13:17:58), so the four clones straddled pushes and the run jobs reported `index sources differ`. Resolve both refs once, in a new `revisions` job, and have every host materialise the commit it names. OPENKAL_BRANCH keeps its meaning: an override still verifies exactly what it named, now as one commit rather than as whatever the branch had become when each runner woke up. The run jobs keep their cross-host comparison; it is what proves a host used the pin rather than re-resolving for itself. git_clone_retry.sh grows the two primitives that needs: `--rev <ref> <url>`, which prints the commit a ref names without downloading the tree, and `--commit <sha> <url> <dir>`, which materialises exactly that commit, both under the retry the helper already exists for. Writing a SHA into the workflow instead would be the pin-that-keeps-a-check-green-by-keeping-it- out-of-date that the file itself warns about.
1 parent b5630e2 commit 7102abb

2 files changed

Lines changed: 194 additions & 17 deletions

File tree

‎.github/tools/git_clone_retry.sh‎

Lines changed: 110 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
# and their attention, a slow true red costs 50 seconds.
3939
#
4040
# Usage: git_clone_retry.sh [git clone args...] <url> <dir>
41+
# git_clone_retry.sh --rev <ref> <url> # print the commit it names
42+
# git_clone_retry.sh --commit <sha> <url> <dir> # that commit, exactly
4143
# Env: GIT_CLONE_ATTEMPTS (default 4), GIT_CLONE_BACKOFF (default "5 15 30")
4244
set -euo pipefail
4345

@@ -50,28 +52,121 @@ read -r -a BACKOFF <<< "${GIT_CLONE_BACKOFF:-5 15 30}"
5052
# different reason than the first and the log stops making sense.
5153
dest="${*: -1}"
5254

55+
# `cmd || rc=$?`, NOT `if cmd; then …; fi; rc=$?`.
56+
#
57+
# After an `if`, `$?` is the status of the IF STATEMENT — which is 0 when the
58+
# body did not run. Written that way this script exits 0 on a clone that never
59+
# succeeded, i.e. it turns a hard failure into a green step with a missing
60+
# checkout. Caught by asserting the exit code in the helper's own test rather
61+
# than by reading the output, which looked correct.
62+
#
63+
# ONE COPY OF IT, for all three verbs below. They back off over the same
64+
# sequence and give up in the same words, so the retry is a property of the
65+
# helper rather than of whichever verb was written last.
66+
failed() { # $1 = attempt number (1-based), $2 = exit status of that attempt
67+
if (( $1 == ATTEMPTS )); then
68+
echo "git_clone_retry: giving up after $ATTEMPTS attempts (last exit $2)" >&2
69+
exit "$2"
70+
fi
71+
delay="${BACKOFF[$(( $1 - 1 < ${#BACKOFF[@]} ? $1 - 1 : ${#BACKOFF[@]} - 1 ))]}"
72+
echo "git_clone_retry: attempt $1/$ATTEMPTS failed (exit $2); retrying in ${delay}s" >&2
73+
sleep "$delay"
74+
}
75+
76+
# ── TWO MORE VERBS, AND WHY THEY ARE IN THIS FILE ──────────────────────────
77+
#
78+
# `--rev` and `--commit` exist because "clone this branch" stopped being one
79+
# act the moment four jobs needed it. Every `build` job of `openkal-cross.yml`
80+
# cloned `main` of the index and of the runtime of its own accord, on runners
81+
# that become available at different moments — the macOS leg waits in a queue
82+
# the others do not — and the `run` job then asserted that all four had built
83+
# the same sources, and failed the run when they had not. On 2026-10-13 that
84+
# assertion was right to fail and the change under test had nothing to do with
85+
# it: the run started at 12:43 UTC, its build jobs at 12:52:08 through
86+
# 13:01:33, and mcpp-index@main took four commits between 12:56:12 and
87+
# 13:17:58 — so clones taken minutes apart straddled pushes, and the run job
88+
# reported `index sources differ`.
89+
#
90+
# The repair is to resolve the ref ONCE, at the start of the run, and have
91+
# every host materialise that commit. Writing the SHA into the workflow would
92+
# be the pin nobody is reminded to move that `openkal-cross.yml` warns about in
93+
# its own header, so the resolution is a job — and these are the two primitives
94+
# it needs: ask what a ref names, and get exactly that commit.
95+
96+
# `--rev <ref> <url>`: print the commit the ref names.
97+
#
98+
# `ls-remote` rather than clone-and-rev-parse: what the caller wants is one
99+
# number, and the tree behind it is tens of megabytes on both of the
100+
# repositories this is pointed at.
101+
if [[ "${1:-}" == "--rev" ]]; then
102+
[ "$#" -eq 3 ] || { echo "git_clone_retry: usage: --rev <ref> <url>" >&2; exit 2; }
103+
ref="$2"; url="$3"
104+
# A bare name is a BRANCH. `refs/…` and `HEAD` are taken as written, so a
105+
# tag or a fully qualified ref can be asked for too.
106+
case "$ref" in
107+
refs/*|HEAD) want="$ref" ;;
108+
*) want="refs/heads/$ref" ;;
109+
esac
110+
for (( i = 1; i <= ATTEMPTS; i++ )); do
111+
rc=0
112+
# stderr is NOT captured: what git says about a URL it cannot reach is
113+
# the diagnostic, and it is the message a plain clone would have shown.
114+
listing=$(git ls-remote "$url" "$want") || rc=$?
115+
if (( rc == 0 )); then
116+
sha=$(printf '%s\n' "$listing" | awk -v w="$want" '$2 == w { print $1; exit }')
117+
# No such ref is an ANSWER, not a blip: the host was reached and
118+
# said the ref is not there, so retrying only delays the report.
119+
[ -n "$sha" ] || { echo "git_clone_retry: $url has no $want" >&2; exit 1; }
120+
printf '%s\n' "$sha"
121+
exit 0
122+
fi
123+
failed "$i" "$rc"
124+
done
125+
fi
126+
127+
# `--commit <sha> <url> <dir>`: materialise exactly that commit, and nothing
128+
# the branch did afterwards.
129+
#
130+
# NOT `git clone --branch <sha>`: that argument takes a branch or a tag, and a
131+
# commit is neither. `init` + `fetch --depth 1 origin <sha>` is the shape that
132+
# names a commit, and however far the branch moves after the resolution, it
133+
# costs one commit's tree once.
134+
if [[ "${1:-}" == "--commit" ]]; then
135+
[ "$#" -eq 4 ] || { echo "git_clone_retry: usage: --commit <sha> <url> <dir>" >&2; exit 2; }
136+
sha="$2"; url="$3" dest="$4"
137+
[[ "$sha" =~ ^[0-9a-f]{40}$ ]] \
138+
|| { echo "git_clone_retry: --commit wants a 40-hex commit, got '$sha'" >&2; exit 2; }
139+
for (( i = 1; i <= ATTEMPTS; i++ )); do
140+
rc=0
141+
{
142+
git -c init.defaultBranch=main init --quiet "$dest" &&
143+
git -C "$dest" remote add origin "$url" &&
144+
git -C "$dest" fetch --quiet --depth 1 origin "$sha" &&
145+
git -C "$dest" checkout --quiet --detach FETCH_HEAD
146+
} || rc=$?
147+
if (( rc == 0 )); then
148+
exit 0
149+
fi
150+
# Nothing a failed attempt made survives it, for the reason the clone
151+
# path gives below about its partial clone: a tree half-made by this
152+
# attempt is not a retry, it is a second failure with a different cause.
153+
# And a caller that gives up should not find a directory claiming to be
154+
# the checkout it asked for.
155+
rm -rf -- "$dest"
156+
failed "$i" "$rc"
157+
done
158+
fi
159+
53160
for (( i = 1; i <= ATTEMPTS; i++ )); do
54-
# `cmd || rc=$?`, NOT `if cmd; then …; fi; rc=$?`.
55-
#
56-
# After an `if`, `$?` is the status of the IF STATEMENT — which is 0 when
57-
# the body did not run. Written that way this script exits 0 on a clone that
58-
# never succeeded, i.e. it turns a hard failure into a green step with a
59-
# missing checkout. Caught by asserting the exit code in the helper's own
60-
# test rather than by reading the output, which looked correct.
61161
rc=0
62162
git clone "$@" || rc=$?
63163
if (( rc == 0 )); then
64164
exit 0
65165
fi
66-
if (( i == ATTEMPTS )); then
67-
echo "git_clone_retry: giving up after $ATTEMPTS attempts (last exit $rc)" >&2
68-
exit "$rc"
69-
fi
70-
if [[ -n "$dest" && "$dest" != -* && -e "$dest" ]]; then
166+
# Nothing to remove on the last attempt: there is no retry to prepare for.
167+
if (( i < ATTEMPTS )) && [[ -n "$dest" && "$dest" != -* && -e "$dest" ]]; then
71168
echo "git_clone_retry: removing partial '$dest' before retrying" >&2
72169
rm -rf -- "$dest"
73170
fi
74-
delay="${BACKOFF[$(( i - 1 < ${#BACKOFF[@]} ? i - 1 : ${#BACKOFF[@]} - 1 ))]}"
75-
echo "git_clone_retry: attempt $i/$ATTEMPTS failed (exit $rc); retrying in ${delay}s" >&2
76-
sleep "$delay"
171+
failed "$i" "$rc"
77172
done

‎.github/workflows/openkal-cross.yml‎

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ name: openkal cross-build (4 hosts × 3 targets)
4444
# Three cells are native. The nine crosses include every output built on Linux
4545
# ARM64, and are what a single-host workflow cannot reach.
4646
#
47+
# A `revisions` JOB RUNS FIRST, before any of them, and resolves the index and
48+
# the runtime branches to one commit each. The four build jobs materialise
49+
# those commits, because "the four hosts built the same sources" — the claim
50+
# the run jobs assert — needs one revision to be about. Four independent
51+
# clones of a moving `main` are not that, and on 2026-10-13 they were not one
52+
# revision (run 38399400601). See the job.
53+
#
4754
# THE RUN JOBS INSTALL NOTHING — not mcpp, not a compiler, not a C runtime.
4855
# A program above openkal carries its C library, its C++ runtime and its
4956
# unwinder; what remains is the operating system it was built for. If a
@@ -93,8 +100,75 @@ env:
93100
OPENKAL_BRANCH: ${{ github.event.inputs.openkal_ref || 'main' }}
94101

95102
jobs:
103+
# ── THE TWO REVISIONS EVERY HOST BUILDS AGAINST, RESOLVED ONCE ───────────
104+
#
105+
# FOUR JOBS CLONING ONE BRANCH ARE NOT FOUR COPIES OF ONE REVISION. The build
106+
# matrix cloned `main` of the index and of the runtime of its own accord, on
107+
# runners that become available at different moments — the macOS leg waits in
108+
# a queue the others do not — and the `run` job below then asserted that all
109+
# four had built the same sources.
110+
#
111+
# THAT ASSERTION WAS RIGHT AND IT WENT RED FOR A REASON NO HOST CAUSED. On
112+
# 2026-10-13, run 38399400601, all three `run` jobs failed with
113+
#
114+
# index sources differ
115+
#
116+
# — so the four hosts did record different revisions. The run started at
117+
# 12:43 UTC and its build jobs at 12:52:08, 12:53:44, 12:58:20 and 13:01:33
118+
# UTC, and they do not clone at the same point in their own step sequence —
119+
# the macOS leg builds this commit's mcpp first — while mcpp-index@main took
120+
# four commits inside that window: 21b9a77a at 12:56:12, d3a328c8 at 13:04:42,
121+
# cfb808bc at 13:10:06, 0340de61 at 13:17:58. Two clones straddling one push
122+
# is all it takes, and the reading arrives 40 minutes later, in a job that
123+
# built nothing.
124+
#
125+
# THE ALTERNATIVE — pinning a SHA into this file — is the failure
126+
# `OPENKAL_BRANCH` above is annotated with: a pin nobody is reminded to move,
127+
# which keeps a check green by keeping it out of date. So the refs are
128+
# resolved here, once, and every host materialises the commit named below.
129+
# `OPENKAL_BRANCH` keeps its meaning: a run that overrides it still verifies
130+
# exactly what it named, now as one commit rather than as whatever the branch
131+
# had become when each runner woke up.
132+
#
133+
# The `run` job keeps its comparison. It is no longer a race against a moving
134+
# branch; it is what proves each host used the pin rather than re-resolving
135+
# for itself.
136+
revisions:
137+
name: the index and runtime revisions this run builds
138+
runs-on: ubuntu-24.04
139+
timeout-minutes: 10
140+
outputs:
141+
index: ${{ steps.resolve.outputs.index }}
142+
runtime: ${{ steps.resolve.outputs.runtime }}
143+
defaults:
144+
run:
145+
shell: bash
146+
steps:
147+
- uses: actions/checkout@v4
148+
149+
- name: Resolve both refs, once
150+
id: resolve
151+
run: |
152+
set -euo pipefail
153+
RETRY="$GITHUB_WORKSPACE/.github/tools/git_clone_retry.sh"
154+
index=$("$RETRY" --rev main https://github.com/mcpp-community/mcpp-index)
155+
runtime=$("$RETRY" --rev "$OPENKAL_BRANCH" \
156+
https://github.com/mcpplibs/openkal-llvm-runtime)
157+
# The `run` job's comparison already refuses a SHA that is not 40 hex;
158+
# this refuses to PIN one, so a mis-resolution fails here, in 10
159+
# seconds, rather than as four builds of four different trees.
160+
for commit in "$index" "$runtime"; do
161+
[[ "$commit" =~ ^[0-9a-f]{40}$ ]] \
162+
|| { echo "::error::not a commit: '$commit'"; exit 1; }
163+
done
164+
echo "mcpp-index@main is $index"
165+
echo "openkal-llvm-runtime@$OPENKAL_BRANCH is $runtime"
166+
echo "index=$index" >> "$GITHUB_OUTPUT"
167+
echo "runtime=$runtime" >> "$GITHUB_OUTPUT"
168+
96169
build:
97170
name: build 3 targets on ${{ matrix.host }}
171+
needs: revisions
98172
runs-on: ${{ matrix.runner }}
99173
timeout-minutes: 120
100174
strategy:
@@ -238,6 +312,9 @@ jobs:
238312
command -v x86_64-w64-mingw32-gcc gcc 2>/dev/null || true
239313
240314
- name: The program — one source, three targets
315+
env:
316+
INDEX_SHA: ${{ needs.revisions.outputs.index }}
317+
RUNTIME_SHA: ${{ needs.revisions.outputs.runtime }}
241318
run: |
242319
set -euo pipefail
243320
# THE SEVENTH CALL SITE. `git_clone_retry.sh` was written because a
@@ -251,11 +328,16 @@ jobs:
251328
#
252329
# One name that did not resolve ended a 120-minute job in its first
253330
# minute, beside a real failure it had nothing to do with.
331+
#
332+
# BOTH CLONES NOW NAME A COMMIT. `--depth 1 -b main` was four jobs
333+
# describing a moving branch as one revision; see the `revisions` job
334+
# for the run that paid for it.
254335
"$GITHUB_WORKSPACE/.github/tools/git_clone_retry.sh" \
255-
--quiet --depth 1 -b "$OPENKAL_BRANCH" \
336+
--commit "$RUNTIME_SHA" \
256337
https://github.com/mcpplibs/openkal-llvm-runtime "$RUNNER_TEMP/okl"
257338
"$GITHUB_WORKSPACE/.github/tools/git_clone_retry.sh" \
258-
--quiet --depth 1 https://github.com/mcpp-community/mcpp-index "$RUNNER_TEMP/openkal-index"
339+
--commit "$INDEX_SHA" \
340+
https://github.com/mcpp-community/mcpp-index "$RUNNER_TEMP/openkal-index"
259341
export MCPP_OPENKAL_INDEX="$RUNNER_TEMP/openkal-index"
260342
cd "$RUNNER_TEMP/okl/examples/same-source"
261343
mkdir -p "$RUNNER_TEMP/out"

0 commit comments

Comments
 (0)