Skip to content

Commit da51fdb

Browse files
Reconcile what __config_site declares with what the port provides (#7)
* Reconcile what __config_site declares with what the port provides `__config_site` is this package's statement about the environment it was configured for, and that environment is openkal-musl. A statement that drifts from what the port actually provides does not fail to build and does not fail to link. It produces a program that takes a path the environment cannot support, and reports nothing. `_LIBCPP_HAS_TERMINAL` is the position where that happened. It gates `std::__is_posix_terminal`, which is `isatty(fileno(stream))` and nothing else. The port answered `isatty` with TCGETS while musl asks with TIOCGWINSZ, so every `isatty` returned zero --- for a real terminal as readily as for a pipe --- and `std::print` never took its terminal path. Nothing failed; a program deciding on colour or on line buffering decided wrongly and in silence. THE REMEDY WAS TO FIX THE PORT RATHER THAN TO WITHDRAW THE DECLARATION. The design document proposed setting the position to zero, which would have been a correct description of a broken port and would have left every program above it without a facility the environment can in fact provide. openkal-musl now answers the question musl asks, so the declaration is true, and this step is what keeps it true rather than leaving it to be checked once and assumed afterwards. The criterion is a relation and not a value. Asserting that a terminal is detected would need a terminal; asserting that a pipe is not would have passed throughout the defect. What must hold is that the two DIFFER, and that they differ the way the system's own C library does --- which is why a control built with the host compiler runs through the same harness. The step also fails in the other direction. A declaration of zero beside a working `isatty` is drift of the same kind, and the remedy there is to raise the declaration rather than to leave it stale. The declared versions move in step with openkal 0.8 and openkal-musl 0.4. * ci: validate against the engine under review, as every other repository does This repository had no MCPP_SOURCE_REF mechanism while the other seven in the ecosystem did. An engine change could therefore be validated against seven repositories and not against the one that carries a C++ standard library. That is the repository where a change to how a target side is assembled shows first. A standard library is not portable in the sense a program is: it is configured for one C library and compiled against that library's headers, so a change in which headers reach a translation unit reaches this package before it reaches anything above it. The mechanism is the one the other repositories use, so that a reader who knows one knows all of them: empty in an ordinary run, in which case the released pin is what is tested; set through the workflow_dispatch input or the repository variable, in which case the engine is built from that source and put ahead on the path. The version it reports is echoed, because a path entry that does not win looks exactly like one that does until something is built with the wrong engine. * ci: test against the C library as written on this branch, not as published This package declares openkal-musl by version, which is what a published manifest must say. A change spanning the two repositories cannot be tested that way: the version named does not exist in the index until the other half is released, and the run fails with E_NOT_FOUND: package 'openkal-musl@0.4.0' not found in the synced index which reads as a mistake in this manifest and is nothing of the kind. Every other repository in this ecosystem solves it by substituting a working tree. This one now does the same, for both halves of the stack beneath it: the C library at this branch where it has one, and the specification that C library reaches, so that what is tested is the stack as written rather than a mixture of one branch and one release. The substitution is asserted rather than assumed. One that matched nothing would leave the manifest naming a version, the resolver would fetch a published C library, and the run would report on that one while appearing to report on this branch --- which is the failure this whole mechanism exists to prevent, arriving by a different route. * ci: substitute the whole chain, not its first two links The substitution reached the C library and the specification and stopped there. openkal-musl also names a BACKEND, whose version moves with the same change, so the run failed one link further down for the same reason it had failed at the top: E_NOT_FOUND: package 'openkal-linux@0.6.0' not found in the synced index Every dependency that moves with a change spanning these repositories is now substituted, and each substitution is asserted rather than assumed --- one that matched nothing would leave a manifest naming a published version, and the run would report on that one while appearing to report on this branch. ⚠️ That the first attempt stopped two links down is worth recording. A chain is substituted correctly only when it is followed to its end, and 'I substituted the dependency' is not the same statement as 'nothing in the graph still names a version that does not exist'. * ci: discover the backends to substitute rather than listing them openkal-musl names a backend per target --- linux, macos, windows and opensbi, each conditional --- and every one of their versions moves with a change that spans these repositories. Each left unsubstituted fails the same way, one link further down: E_NOT_FOUND: package 'openkal-linux@0.6.0' (the host build) E_NOT_FOUND: package 'openkal-opensbi@0.2.0' (the bare-metal one) I named the first and the second appeared. The commit that named the first said "a chain is substituted correctly only when it is followed to its end", and then followed it by hand to one entry short of the end, which is the ordinary outcome of a list written from memory. The set is now read out of the manifest, so it cannot fall behind what that manifest declares. ⭐ AND THE LAST CHECK IS THE ONE THAT WOULD HAVE CAUGHT BOTH: after every substitution, nothing anywhere in the graph may still name a version. That is a property of the whole graph rather than of the entries someone remembered, and it fails at the substitution rather than at a resolver error further on, where the message names a package instead of naming the omission. * ci: apply the branch substitution and the engine under review to every job The host-dimension job resolved this ecosystem from the index and installed the released engine. Both were wrong for a change that spans these repositories, and the first announced itself: E_NOT_FOUND: package 'compat.openkal-musl@0.4.0' not found in the synced index, synced 0 seconds ago The second would not have. A manifest key the engine does not recognise is accepted rather than refused, so that job would have built manifests written for an unreleased engine using a released one, and the semantics the new key asks for would simply be absent from the result. The remedy is not a second copy of the procedure. Two copies that must agree are two copies that will not, which is how one job came to have neither. Both are now scripts that each job calls: tools/install-mcpp.sh the engine this run is judged by tools/branch-graph.sh the working trees of this ecosystem, in place of the versions the manifests name Three consequences follow from the extraction. The substituted paths are now relative. An absolute path names a directory of one machine; a manifest carrying one has been committed in this ecosystem and published, and every consumer resolving it was handed a path that exists nowhere. Relative paths cannot express that mistake. They are also the only form that works unchanged on all three hosts, since `pwd` under MSYS reports `/d/a/...`. Measured before adoption: a dependency named by a relative path resolves against the manifest that contains it rather than against the working directory, at three levels of nesting, and the substituted graph of six repositories resolves with no absolute path anywhere in it. In-place editing no longer uses `sed -i`. BSD sed, which is macOS's, reads the argument after -i as a backup suffix, so the command that edits a file on Linux consumes the next expression on macOS --- and this script now runs on macOS. The engine on PATH is compared against the engine that was built. GITHUB_PATH governs the steps that follow, so the step that appends cannot observe its own effect, and whether the appended spelling is one the runner accepts is a property of the runner. Unasserted, a cross-validation run on a host that ignored it would build with the released engine and report the change under review as tested. * Test against the released engine mcpp 2026.8.27.1 is published and the index's latest points at it, so the pin moves to it and the repository variable MCPP_SOURCE_REF is cleared. What this repository tests is now the engine a user installs rather than a branch of it. The manifests are unchanged: they already name the versions this round publishes, and this repository's continuous integration substitutes working trees for them, so the change spanning these repositories is still tested as written. --------- Co-authored-by: speak-agent <x.d2learn.org@gmail.com>
1 parent 739b3bd commit da51fdb

5 files changed

Lines changed: 391 additions & 23 deletions

File tree

.github/workflows/ci.yml

Lines changed: 168 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,15 @@ on:
1515
branches: [main]
1616
pull_request:
1717
workflow_dispatch:
18+
inputs:
19+
mcpp_ref:
20+
description: "Branch of mcpp-community/mcpp to build and test against (empty = the released pin)"
21+
required: false
22+
default: ""
1823

1924
env:
20-
MCPP_VERSION: 2026.8.26.2
25+
MCPP_SOURCE_REF: ${{ github.event.inputs.mcpp_ref || vars.MCPP_SOURCE_REF }}
26+
MCPP_VERSION: 2026.8.27.1
2127
XLINGS_VERSION: v2026.8.17.2
2228
XLINGS_NON_INTERACTIVE: '1'
2329

@@ -45,24 +51,43 @@ jobs:
4551
echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH"
4652
4753
- name: Install mcpp
54+
run: bash tools/install-mcpp.sh
55+
56+
# ⭐ THE ENGINE EVERY STEP BELOW WILL USE, COMPARED AGAINST THE ONE BUILT.
57+
#
58+
# The step above appends a directory to GITHUB_PATH, which governs the
59+
# steps that follow it, so that step cannot observe its own effect. Whether
60+
# the appended spelling is one the runner accepts is a property of the
61+
# runner and differs between hosts. Left unasserted, a cross-validation run
62+
# on a host that ignores it builds this ecosystem with the released engine
63+
# and reports the result as though the change under review had been tested.
64+
- name: The engine on PATH is the one under review
4865
run: |
49-
for attempt in 1 2 3 4 5 6; do
50-
xlings update > /dev/null 2>&1 || true
51-
if xlings install "mcpp@$MCPP_VERSION" -y -g; then break; fi
52-
if [ "$attempt" = 6 ]; then
53-
echo "::error::mcpp@$MCPP_VERSION never appeared in the index"; exit 1
54-
fi
55-
sleep 60
56-
done
57-
mcpp --version
58-
mcpp self config --mirror GLOBAL
66+
set -euo pipefail
67+
if [ -z "${MCPP_UNDER_REVIEW:-}" ]; then
68+
echo " no source reference: this run tests $(mcpp --version)"
69+
exit 0
70+
fi
71+
on_path=$(mcpp --version | awk '{print $2}')
72+
if [ "$on_path" != "$MCPP_UNDER_REVIEW" ]; then
73+
echo "::error::PATH resolves mcpp $on_path, and the build under review is $MCPP_UNDER_REVIEW"
74+
echo " the directory appended to GITHUB_PATH did not take effect on this host"
75+
command -v mcpp
76+
exit 1
77+
fi
78+
echo " every step below runs $on_path, built from $MCPP_SOURCE_REF"
5979
6080
- name: Select the toolchain
6181
run: |
6282
spec='${{ matrix.toolchain }}'
6383
mcpp toolchain install "${spec%@*}" "${spec#*@}"
6484
mcpp toolchain default "$spec"
6585
86+
# THE C LIBRARY THIS RUNTIME IS CONFIGURED FOR, AS WRITTEN ON THE BRANCH
87+
# UNDER TEST RATHER THAN AS PUBLISHED. The reasoning is in the script.
88+
- name: The stack, as written on this branch
89+
run: bash tools/branch-graph.sh '${{ github.head_ref || github.ref_name }}'
90+
6691
- name: The runtime builds
6792
run: mcpp build
6893

@@ -85,6 +110,102 @@ jobs:
85110
cd examples/import-std && mcpp run 2>&1 | tee out.log
86111
grep -q 'import std above openkal: 2 4 7' out.log
87112
113+
# WHAT __config_site DECLARES AND WHAT THE PORT BENEATH PROVIDES ARE
114+
# RECONCILED HERE, RATHER THAN BEING CHECKED ONCE AND ASSUMED AFTERWARDS.
115+
#
116+
# `__config_site` is this package's statement about the environment it was
117+
# configured for, and the environment is openkal-musl. A statement that
118+
# drifts from what the port actually provides does not fail to build and
119+
# does not fail to link: it produces a program that takes a path the
120+
# environment cannot support, and reports nothing.
121+
#
122+
# `_LIBCPP_HAS_TERMINAL` is the position where that happened. It gates
123+
# `std::__is_posix_terminal`, which is `isatty(fileno(stream))` and nothing
124+
# else. The port answered `isatty` with TCGETS while musl asks with
125+
# TIOCGWINSZ, so every `isatty` returned 0 --- for a real terminal as
126+
# readily as for a pipe --- and `std::print` never took its terminal path.
127+
# Nothing failed; a program deciding on colour or on line buffering decided
128+
# wrongly and in silence.
129+
#
130+
# The remedy was to fix the port rather than to withdraw the declaration,
131+
# so the declaration is now true and this step is what keeps it true.
132+
#
133+
# THE CRITERION IS A RELATION AND NOT A VALUE. Asserting "a terminal is
134+
# detected" would need a terminal; asserting "a pipe is not" would pass
135+
# throughout the defect. What must hold is that the two DIFFER, and that
136+
# they differ the way the system's own C library says they do.
137+
- name: What the runtime declares is what the port provides
138+
run: |
139+
set -euo pipefail
140+
d="$(mktemp -d)"; mkdir -p "$d/src"
141+
cat > "$d/mcpp.toml" <<TOML
142+
[package]
143+
name = "termdecl"
144+
version = "0.1.0"
145+
[dependencies]
146+
openkal-llvm-runtime = { path = "$PWD" }
147+
[targets.termdecl]
148+
kind = "bin"
149+
main = "src/main.cpp"
150+
TOML
151+
sed -i 's/^ //' "$d/mcpp.toml"
152+
cat > "$d/src/main.cpp" <<'CPP'
153+
#include <cstdio>
154+
#include <unistd.h>
155+
// The declaration this step exists to reconcile. If the package stops
156+
// claiming a terminal, the program says so rather than failing to
157+
// compile: the point is to compare the claim against the behaviour,
158+
// and a build error would compare nothing.
159+
int main() {
160+
#if defined(_LIBCPP_HAS_TERMINAL) && _LIBCPP_HAS_TERMINAL
161+
std::printf("declared=1 isatty=%d\n", isatty(1));
162+
#else
163+
std::printf("declared=0 isatty=%d\n", isatty(1));
164+
#endif
165+
}
166+
CPP
167+
sed -i 's/^ //' "$d/src/main.cpp"
168+
( cd "$d" && mcpp build )
169+
bin="$(find "$d/target" -type f -name termdecl | head -1)"
170+
test -n "$bin" || { echo "::error::the probe did not build"; exit 1; }
171+
172+
# The control: the system's own C library, through the same harness.
173+
# Without it a `script` that fails to allocate a pty would make the
174+
# runtime look wrong.
175+
printf '#include <stdio.h>\n#include <unistd.h>\nint main(void){ printf("%%d\\n", isatty(1)); return 0; }\n' > "$d/ctrl.c"
176+
cc "$d/ctrl.c" -o "$d/ctrl"
177+
ctrl_pipe="$("$d/ctrl" | cat | tr -d '\r')"
178+
ctrl_tty="$(script -qec "$d/ctrl" /dev/null | tr -d '\r' | head -1)"
179+
[ "$ctrl_pipe" = 0 ] && [ "$ctrl_tty" = 1 ] \
180+
|| { echo "::error::the harness cannot tell a pty from a pipe (control gave $ctrl_pipe/$ctrl_tty)"
181+
exit 1; }
182+
183+
out_pipe="$("$bin" | cat | tr -d '\r')"
184+
out_tty="$(script -qec "$bin" /dev/null | tr -d '\r' | head -1)"
185+
echo " control: pipe=$ctrl_pipe tty=$ctrl_tty"
186+
echo " runtime: $out_pipe / $out_tty"
187+
188+
declared="${out_pipe#declared=}"; declared="${declared%% *}"
189+
pipe_v="${out_pipe##*isatty=}"
190+
tty_v="${out_tty##*isatty=}"
191+
192+
if [ "$declared" = 1 ]; then
193+
# The claim is that a terminal can be detected, so the two must
194+
# differ and must differ as the system's own library does.
195+
[ "$pipe_v" = "$ctrl_pipe" ] && [ "$tty_v" = "$ctrl_tty" ] \
196+
|| { echo "::error::the runtime declares _LIBCPP_HAS_TERMINAL but the port answers $pipe_v/$tty_v where the system answers $ctrl_pipe/$ctrl_tty"
197+
exit 1; }
198+
echo " ok the declaration holds: a terminal is distinguished from a pipe"
199+
else
200+
# The claim is that it cannot. Then it must not appear to: a
201+
# declaration of 0 beside a working isatty is also a drift, and the
202+
# remedy is to raise the declaration rather than leave it stale.
203+
[ "$tty_v" = "$ctrl_tty" ] \
204+
&& { echo "::error::the runtime declares no terminal support while the port detects one; the declaration is stale"
205+
exit 1; }
206+
echo " ok the declaration holds: no terminal support is claimed and none is present"
207+
fi
208+
88209
# ⭐⭐ THE SAME PROGRAM ON A MACHINE WITH NO OPERATING SYSTEM.
89210
#
90211
# Everything above this step runs on a host, and a host has a C library, a
@@ -345,23 +466,49 @@ jobs:
345466
"$env:USERPROFILE\.xlings\subos\current\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
346467
347468
- name: Install mcpp
469+
run: bash tools/install-mcpp.sh
470+
471+
# ⭐ THE ENGINE EVERY STEP BELOW WILL USE, COMPARED AGAINST THE ONE BUILT.
472+
#
473+
# The step above appends a directory to GITHUB_PATH, which governs the
474+
# steps that follow it, so that step cannot observe its own effect. Whether
475+
# the appended spelling is one the runner accepts is a property of the
476+
# runner and differs between hosts. Left unasserted, a cross-validation run
477+
# on a host that ignores it builds this ecosystem with the released engine
478+
# and reports the result as though the change under review had been tested.
479+
- name: The engine on PATH is the one under review
348480
run: |
349-
for attempt in 1 2 3 4 5 6; do
350-
xlings update > /dev/null 2>&1 || true
351-
if xlings install "mcpp@$MCPP_VERSION" -y -g; then break; fi
352-
if [ "$attempt" = 6 ]; then
353-
echo "::error::mcpp@$MCPP_VERSION never appeared in the index"
354-
exit 1
355-
fi
356-
sleep 60
357-
done
358-
mcpp self config --mirror GLOBAL
481+
set -euo pipefail
482+
if [ -z "${MCPP_UNDER_REVIEW:-}" ]; then
483+
echo " no source reference: this run tests $(mcpp --version)"
484+
exit 0
485+
fi
486+
on_path=$(mcpp --version | awk '{print $2}')
487+
if [ "$on_path" != "$MCPP_UNDER_REVIEW" ]; then
488+
echo "::error::PATH resolves mcpp $on_path, and the build under review is $MCPP_UNDER_REVIEW"
489+
echo " the directory appended to GITHUB_PATH did not take effect on this host"
490+
command -v mcpp
491+
exit 1
492+
fi
493+
echo " every step below runs $on_path, built from $MCPP_SOURCE_REF"
494+
495+
# ⭐ THE SAME ENGINE AND THE SAME STACK AS THE LINUX JOB, FROM A DIFFERENT
496+
# HOST. This job had neither: it installed the released engine and
497+
# resolved this ecosystem from the index, so a change spanning these
498+
# repositories was validated on one host of three and reported as
499+
# validated everywhere.
500+
- name: Select the toolchain
501+
run: |
502+
set -euo pipefail
359503
# ⚠️ INSTALL, THEN SELECT. `toolchain default` names a toolchain and
360504
# does not fetch one, so selecting an absent payload fails with
361505
# `llvm@22.1.8 is not installed` — measured on both rows of this job.
362506
mcpp toolchain install llvm 22.1.8
363507
mcpp toolchain default 'llvm@22.1.8'
364508
509+
- name: The stack, as written on this branch
510+
run: bash tools/branch-graph.sh '${{ github.head_ref || github.ref_name }}'
511+
365512
- name: Every target, from this host
366513
run: |
367514
set -euo pipefail

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,9 @@ mcpp.lock
1212
# remove a file that is ALREADY TRACKED. This repository had two: one at the
1313
# root and one under `examples/import-std/`. The first cleanup found only the
1414
# root one, because the scan it used anchored the path at the beginning.
15+
16+
# The working trees the workflow substitutes for published dependencies.
17+
.musl/
18+
.spec/
19+
.impl/
20+
.openkal-*/

mcpp.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
namespace = "mcpplibs"
33
name = "openkal-llvm-runtime"
4-
version = "0.1.3"
4+
version = "0.2.0"
55
description = "LLVM's C++ runtime libraries — libc++, libc++abi and libunwind — configured for openkal-musl rather than for a host C library."
66
license = "Apache-2.0"
77
authors = ["mcpplibs"]
@@ -198,7 +198,7 @@ sources = [
198198
cflags = ["-DDISABLE_AARCH64_FMV=1"]
199199

200200
[dependencies]
201-
openkal-musl = "0.3.5"
201+
openkal-musl = "0.4.0"
202202

203203
[build]
204204
cxx_standard = "c++23"

tools/branch-graph.sh

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
#!/usr/bin/env bash
2+
#
3+
# THE ECOSYSTEM AS WRITTEN ON A BRANCH, RATHER THAN AS PUBLISHED.
4+
#
5+
# This package declares openkal-musl by version, which is what a published
6+
# manifest must say. A change that spans the two repositories cannot be tested
7+
# that way: the version named here does not exist in the index until the other
8+
# half is released, and the run fails with
9+
#
10+
# E_NOT_FOUND: package 'openkal-musl@<version>' not found in the synced index
11+
#
12+
# --- which reads as a mistake in this manifest and is nothing of the kind. The
13+
# remedy is to put the working trees in place of the versions, for the whole
14+
# graph rather than for its first edge.
15+
#
16+
# ⚠️ WHY THIS IS A SCRIPT AND NOT A STEP. It was a step, in one job of two, and
17+
# the other job resolved from the index and failed exactly as above the moment
18+
# the versions moved. Two copies of a procedure that must agree are two copies
19+
# that will not; one file called twice cannot drift.
20+
#
21+
# ⚠️ THE SUBSTITUTED PATHS ARE RELATIVE, AND DELIBERATELY SO.
22+
#
23+
# An absolute path names a directory of one machine. A manifest carrying one has
24+
# been committed in this ecosystem and published, and every consumer resolving
25+
# it was handed a path that exists nowhere. Relative paths cannot express that
26+
# mistake. They are also the only form that works unchanged on all three hosts:
27+
# `pwd` under MSYS reports `/d/a/...`, which is not a path the engine resolves,
28+
# so an absolute form would need a Windows-only conversion here.
29+
#
30+
# ⚠️ `sed -i` IS NOT PORTABLE. BSD sed, which is macOS's, reads the argument
31+
# after -i as a backup suffix; the same command that edits a file on Linux
32+
# consumes the next expression on macOS. In-place editing goes through a
33+
# temporary file below for that reason.
34+
set -euo pipefail
35+
36+
branch="${1:-}"
37+
[ -n "$branch" ] || { echo "usage: ${0##*/} <branch>" >&2; exit 2; }
38+
39+
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
40+
cd "$root"
41+
42+
# Clones a repository of this ecosystem, preferring the branch under test when
43+
# that repository has one of the same name.
44+
fetch() {
45+
local repo="$1" dir="$2"
46+
rm -rf "$dir"
47+
git clone --quiet "https://github.com/mcpplibs/$repo.git" "$dir"
48+
if git -C "$dir" rev-parse --verify --quiet "origin/$branch" > /dev/null; then
49+
git -C "$dir" checkout --quiet "origin/$branch"
50+
printf ' %-22s %s %s\n' "$repo" "$branch" "$(git -C "$dir" rev-parse --short HEAD)"
51+
else
52+
printf ' %-22s default branch %s (it has no %s)\n' \
53+
"$repo" "$(git -C "$dir" rev-parse --short HEAD)" "$branch"
54+
fi
55+
}
56+
57+
# In-place editing that both GNU and BSD sed perform identically.
58+
edit() {
59+
local file="$1" expr="$2"
60+
sed -E "$expr" "$file" > "$file.substituted"
61+
mv "$file.substituted" "$file"
62+
}
63+
64+
echo "the stack under test:"
65+
fetch openkal-musl .musl
66+
fetch openkal .spec
67+
68+
manifests=(mcpp.toml .musl/mcpp.toml)
69+
70+
# ⚠️ EVERY BACKEND THE C LIBRARY NAMES, DISCOVERED RATHER THAN LISTED.
71+
#
72+
# openkal-musl names a backend per target --- linux, macos, windows, opensbi ---
73+
# each conditional on the target it serves. Their versions all move with a change
74+
# that spans these repositories, so each one left unsubstituted fails the same
75+
# way, one link further down:
76+
#
77+
# E_NOT_FOUND: package 'openkal-linux@0.6.0' (the host build)
78+
# E_NOT_FOUND: package 'openkal-opensbi@0.2.0' (the bare-metal one)
79+
#
80+
# The first was fixed by naming it, and the second appeared. A list written by
81+
# hand is a list that is one entry short, so the set is read out of the manifest.
82+
for backend in $(grep -oE '^openkal-[a-z]+ = \{ version' .musl/mcpp.toml | cut -d' ' -f1); do
83+
fetch "$backend" ".$backend"
84+
85+
# The backend reaches the specification too, by whatever form its own
86+
# manifest uses. From <root>/.<backend>/ the specification is ../.spec.
87+
edit ".$backend/mcpp.toml" 's|^openkal = .*$|openkal = { path = "../.spec" }|'
88+
89+
edit .musl/mcpp.toml \
90+
"s|^$backend = \\{ version = \"[^\"]*\"(.*)\$|$backend = { path = \"../.$backend\"\\1|"
91+
grep -q "path = \"../.$backend\"" .musl/mcpp.toml \
92+
|| { echo "::error::$backend was not substituted"; exit 1; }
93+
94+
manifests+=(".$backend/mcpp.toml")
95+
done
96+
97+
edit .musl/mcpp.toml 's|^openkal = .*$|openkal = { path = "../.spec" }|'
98+
edit mcpp.toml 's|^openkal-musl = .*$|openkal-musl = { path = "./.musl" }|'
99+
100+
# The substitution is asserted rather than assumed. One that matched nothing
101+
# would leave the manifest naming a version, the resolver would fetch a
102+
# published C library, and the run would report on that one while appearing to
103+
# report on this branch.
104+
grep -q 'path = "./.musl"' mcpp.toml \
105+
|| { echo "::error::the C library substitution matched nothing"; exit 1; }
106+
grep -q 'path = "../.spec"' .musl/mcpp.toml \
107+
|| { echo "::error::the specification substitution matched nothing"; exit 1; }
108+
109+
# ⭐ THE CHECK THAT WOULD HAVE CAUGHT EVERY FAILURE ABOVE AT ITS FIRST OCCURRENCE:
110+
# nothing anywhere in the substituted graph still names a version. The three
111+
# defects this file records were each found by a build failing one link further
112+
# down than the last; this asks the whole graph at once.
113+
if grep -nE '^openkal[a-z-]* = ("|\{ version)' "${manifests[@]}"; then
114+
echo "::error::something in the graph still names a version rather than a tree"
115+
exit 1
116+
fi
117+
118+
echo "the whole stack names working trees; nothing in it names a version"

0 commit comments

Comments
 (0)