the chain reaches a consumer, and the review of how it got there #648
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: openkal cross-build (3 hosts × 3 targets) | |
| # WHAT THIS WORKFLOW ASSERTS, AND WHY IT IS A MATRIX RATHER THAN A ROW. | |
| # | |
| # `cross-build-test.yml` verifies the crosses served by a PAYLOAD: a toolchain | |
| # whose driver has exactly one target. There the host and the target are joined | |
| # — `x86_64-w64-mingw32-g++` is the Windows cross and nothing else — so one row | |
| # per supported combination is the honest shape. | |
| # | |
| # openkal changes the shape of the question. The target side — the C library, | |
| # the C++ runtime, the platform's own implementation — is a set of PACKAGES in | |
| # the dependency graph, and the compiler is an ordinary retargetable clang. The | |
| # claim that follows is that N hosts × N targets collapses to N implementations | |
| # plus one tool: **the machine doing the building stops being a variable.** | |
| # | |
| # THAT IS A CLAIM, AND CLAIMS OF THIS SHAPE HAVE BEEN WRONG IN THIS | |
| # REPOSITORY. Reaching PE from a Linux host needed four separate repairs, and | |
| # adding the other two hosts found seven more — every one of them a decision | |
| # that had been keyed on which machine was building rather than on which machine | |
| # the output was for: | |
| # | |
| # the link line's three host-shaped branches, only one of which carried | |
| # `--target=`; the `std` module command's Windows branch, which dropped the | |
| # package's own include paths; `cd X && …` not changing the drive in cmd.exe; | |
| # the artefact-format test matching LLVM's `apple` rather than mcpp's `macos`; | |
| # the C++ runtime contract naming a library to link when one was already in | |
| # the objects; `-nostdinc` missing so a host SDK header could be found; and | |
| # `-lgcc` naming GCC's runtime on a link whose compiler is clang. | |
| # | |
| # None of those was visible from one host. So the matrix is the test. | |
| # | |
| # ── The shape ────────────────────────────────────────────────────────────── | |
| # | |
| # THREE build jobs, one per host, each producing THREE artefacts — nine builds. | |
| # THREE run jobs, one per system, each executing the artefact FOR that system | |
| # produced by ALL THREE hosts. | |
| # | |
| # build on Linux build on macOS build on Windows | |
| # run Linux | |
| # run macOS | |
| # run Windows | |
| # | |
| # The diagonal is an ordinary native build. The six off-diagonal cells are | |
| # the claim, and they are what a single-host workflow cannot reach. | |
| # | |
| # THE RUN JOBS INSTALL NOTHING — not mcpp, not a compiler, not a C runtime. | |
| # A program above openkal carries its C library, its C++ runtime and its | |
| # unwinder; what remains is the operating system it was built for. If a | |
| # toolchain step is ever added to one of them because "the program needs it", | |
| # that is the finding rather than the fix. | |
| # | |
| # AND THE ASSERTION IS ON THE OUTPUT, NOT THE EXIT STATUS. The program prints | |
| # four lines, and `unwound: true` is the one a link cannot fake: it says a | |
| # destructor ran while an exception was being carried out of a frame, which | |
| # means the unwinder found this image's own frame descriptions. | |
| on: | |
| pull_request: | |
| workflow_dispatch: | |
| inputs: | |
| openkal_ref: | |
| description: "Branch of the openkal packages to build against (empty = main)" | |
| required: false | |
| default: "" | |
| concurrency: | |
| group: ci-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| # No mcpp or xlings version here. `bootstrap-mcpp` owns both, and a second | |
| # statement of them is a second thing to keep in step — the pin check | |
| # (.github/tools/check_version_pins.sh) enforces the ones that exist and would | |
| # not know about a copy in this file. | |
| XLINGS_NON_INTERACTIVE: '1' | |
| # The branch of the openkal packages this change is verified against. | |
| # | |
| # IT WAS `feat/openkal-closure` UNTIL 2026-08-25, LONG AFTER THAT BRANCH | |
| # MERGED. A fixed name here is a pin nobody is reminded to move: the comment | |
| # said "when they are on `main` this becomes `main`" and the moment for that | |
| # passed without anyone reading it again. Every run since was verifying this | |
| # engine against a tree the ecosystem had left behind — and the two | |
| # regressions found today both hid behind exactly this shape, a pin that | |
| # keeps a check green by keeping it out of date. | |
| # | |
| # IT IS OVERRIDABLE FOR ONE RUN, AND THAT IS THE OTHER HALF OF A PROTOCOL | |
| # THIS REPOSITORY ALREADY HAS ONE HALF OF. The ecosystem repositories build | |
| # against an mcpp PR branch through `MCPP_SOURCE_REF`, so an engine change | |
| # is measured against them before it merges. The reverse was hard-coded to | |
| # `main`, which makes a change that REQUIRES a coordinated ecosystem commit | |
| # unverifiable until after that commit lands --- and unmergeable until then, | |
| # since this job is the one that fails. | |
| # | |
| # `__CYGWIN__`'s withdrawal is the case that showed it: this job builds | |
| # `openkal-llvm-runtime@main`, whose installed header read only the borrowed | |
| # name, so the engine's own CI reproduced the ordering constraint as a red | |
| # cell. The constraint is real and the cell is correct --- the packages must | |
| # publish first --- but verifying the engine BEFORE that publish needs this | |
| # input. Left empty, nothing changes. | |
| OPENKAL_BRANCH: ${{ github.event.inputs.openkal_ref || 'main' }} | |
| jobs: | |
| build: | |
| name: build 3 targets on ${{ matrix.host }} | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: 120 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - { host: linux, runner: ubuntu-24.04 } | |
| - { host: macos, runner: macos-14 } | |
| - { host: windows, runner: windows-2022 } | |
| defaults: | |
| run: | |
| shell: bash | |
| steps: | |
| - uses: actions/checkout@v4 | |
| # THE REPOSITORY'S OWN BOOTSTRAP, NOT A SECOND ONE. | |
| # | |
| # This job first wrote its own: fetch xlings, then | |
| # `xlings install mcpp@<version>`. It failed on the very first run: | |
| # | |
| # xlings: version '2026.8.17.1' not found for 'mcpp' | |
| # available: 2026.8.19.4 | |
| # | |
| # `.xlings.json` at this repository's root pins the mcpp that BUILDS mcpp, | |
| # and that pin does not move when mcpp is released — so it names a version | |
| # the index no longer carries, and a bare install inside the checkout | |
| # obeys the pin rather than the argument. `bootstrap-mcpp` already knows | |
| # this (it runs `install_pinned_mcpp.sh`), works on all three systems, and | |
| # shares the cache lineage every other job lands on. | |
| # | |
| # ⇒ Two bootstraps would be two things to keep correct, and the second one | |
| # was wrong within a day of being written. | |
| - uses: ./.github/actions/bootstrap-mcpp | |
| # THE mcpp UNDER TEST. Everything after this uses the binary this step | |
| # produces; the bootstrapped one above is only what compiles it. | |
| - name: Build the mcpp in this pull request | |
| run: | | |
| set -euo pipefail | |
| export MCPP_VENDORED_XLINGS="$XLINGS_BIN" | |
| "$XLINGS_BIN" config --mirror GLOBAL 2>/dev/null || true | |
| "$MCPP" self config --mirror GLOBAL 2>/dev/null || true | |
| # `--dev` and not `--release`. What is under test is a set of | |
| # decisions about compile and link flags; an optimisation level | |
| # changes none of them and a release self-build is most of the budget | |
| # of a job on a two-core runner. | |
| # REMOVE ANY mcpp ALREADY UNDER `target/` FIRST, so that after the | |
| # build there is exactly ONE and it is the one this step produced. | |
| # | |
| # `target/` is a RESTORED CACHE whose key hashes `src/**`. A source | |
| # change misses the key, `restore-keys` hands back the nearest older | |
| # tree anyway — that is the point of the layered restore — and the | |
| # build then writes its output into a NEW `<triple>/<fingerprint>/` | |
| # directory beside the ones already there. `find … | head -1` returns | |
| # whichever directory the walk reaches first, which is not the newest | |
| # and is not this build's. | |
| # | |
| # MEASURED, LOCALLY AND ON CI. On this developer's own tree the | |
| # command picks `007bc0f2b78f7fa5` while the build just wrote | |
| # `148448dec6c4a7a7`. On CI it picked a stale binary whose ELF | |
| # interpreter names a glibc payload the runner no longer has: | |
| # | |
| # …/bin/mcpp: cannot execute: required file not found (exit 127) | |
| # | |
| # — which reads as a broken build of THIS commit and is nothing of the | |
| # kind. Two runs, same stale fingerprint, after seven green ones: the | |
| # trigger is simply a source change large enough to leave two | |
| # directories in the restored tree. | |
| # | |
| # mtime CANNOT BE THE CRITERION HERE. A cache restore writes every | |
| # file at extraction time, so "newest" is meaningless on exactly the | |
| # tree where this goes wrong — `ls -t | head -1` has picked a stale | |
| # fingerprint in this repository before. Absence can't be faked: | |
| # delete them, and whatever exists afterwards was linked just now. | |
| # | |
| # The objects and BMIs stay cached — this costs one link. | |
| find target -type f \( -name 'mcpp' -o -name 'mcpp.exe' \) -delete 2>/dev/null || true | |
| "$MCPP" build --dev | |
| # EXACTLY ONE, not "at least one". Two would mean the delete above | |
| # missed a tree and the ambiguity this step exists to remove is back. | |
| # | |
| # NO `mapfile`. This job also runs on macos, whose `/bin/bash` is | |
| # 3.2 and has no such builtin — a bashism here would fail on one row | |
| # of the matrix for a reason unrelated to what the row tests. | |
| find target -type f \( -name 'mcpp' -o -name 'mcpp.exe' \) > /tmp/mcpp-built.txt | |
| COUNT=$(wc -l < /tmp/mcpp-built.txt | tr -d ' ') | |
| if [ "$COUNT" -ne 1 ]; then | |
| echo "::error::expected exactly one built mcpp, found $COUNT" | |
| sed 's/^/ /' /tmp/mcpp-built.txt | |
| exit 1 | |
| fi | |
| BUILT=$(cat /tmp/mcpp-built.txt) | |
| BUILT=$(cd "$(dirname "$BUILT")" && pwd)/$(basename "$BUILT") | |
| echo "MCPP_UNDER_TEST=$BUILT" >> "$GITHUB_ENV" | |
| "$BUILT" --version | |
| - name: Select the toolchain the openkal packages ask for | |
| run: | | |
| set -euo pipefail | |
| # Install, then select. `toolchain default` names one and does not | |
| # fetch it. | |
| "$MCPP_UNDER_TEST" self config --mirror GLOBAL 2>/dev/null || true | |
| "$MCPP_UNDER_TEST" toolchain install llvm 22.1.8 | |
| "$MCPP_UNDER_TEST" toolchain default 'llvm@22.1.8' | |
| # WHAT THIS RUNNER ACTUALLY PROVIDES FOR THE mingw TARGET. On a Windows | |
| # host `openkal-windows`'s build program generates no import libraries | |
| # ("the system's own are present"), so `-lntdll` and its neighbours are | |
| # found by lld only where the host, the sandbox or the payload puts | |
| # them. This job was green with one sandbox lineage and red with the | |
| # next (2026-09-13, PR #629, same sources, same image), which is the | |
| # signature of a dependency on cached state nobody declared. The lines | |
| # below say where the libraries come from, so the next such reading is | |
| # diagnosed from the log rather than from a bisect over caches. | |
| - name: What this host provides for x86_64-w64-windows-gnu | |
| if: matrix.host == 'windows' | |
| run: | | |
| echo "PATH=$PATH" | tr ':' '\n' | head -40 | |
| ls "${MCPP_HOME:-$HOME/.mcpp}/registry/data/xpkgs" 2>/dev/null || echo "(no xpkgs dir)" | |
| CLANG=$(ls "${MCPP_HOME:-$HOME/.mcpp}"/registry/data/xpkgs/xim-x-llvm/22.1.8/bin/clang++.exe 2>/dev/null | head -1) | |
| echo "clang=$CLANG" | |
| [ -n "$CLANG" ] && "$CLANG" --target=x86_64-w64-windows-gnu -print-search-dirs | |
| [ -n "$CLANG" ] && "$CLANG" --target=x86_64-w64-windows-gnu -print-file-name=libntdll.a | |
| [ -n "$CLANG" ] && "$CLANG" --target=x86_64-w64-windows-gnu -print-file-name=libkernel32.a | |
| command -v x86_64-w64-mingw32-gcc gcc 2>/dev/null || true | |
| - name: The program — one source, three targets | |
| run: | | |
| set -euo pipefail | |
| # THE SEVENTH CALL SITE. `git_clone_retry.sh` was written because a | |
| # runner's DNS hiccup is not a red build, and its own note counts | |
| # "six call sites, one failure mode" — this workflow was not among | |
| # them, and the mode duly arrived here. Measured on this job, | |
| # 2026-08-25: | |
| # | |
| # fatal: unable to access '…/openkal-llvm-runtime/': | |
| # Could not resolve host: github.com | |
| # | |
| # One name that did not resolve ended a 120-minute job in its first | |
| # minute, beside a real failure it had nothing to do with. | |
| "$GITHUB_WORKSPACE/.github/tools/git_clone_retry.sh" \ | |
| --quiet --depth 1 -b "$OPENKAL_BRANCH" \ | |
| https://github.com/mcpplibs/openkal-llvm-runtime "$RUNNER_TEMP/okl" | |
| cd "$RUNNER_TEMP/okl/examples/same-source" | |
| mkdir -p "$RUNNER_TEMP/out" | |
| # The three HOSTED targets. Bare metal is verified by | |
| # `openkal-llvm-runtime`'s own CI under qemu; it has no runner here to | |
| # execute on, and a build-only cell in a workflow whose point is | |
| # running would be the weaker claim. | |
| for t in x86_64-linux-gnu aarch64-macos x86_64-windows-gnu; do | |
| rm -rf target | |
| "$MCPP_UNDER_TEST" build --target "$t" | |
| a=$(find target -type f \( -name 'openkal-same-source' -o -name 'openkal-same-source.exe' \) | head -1) | |
| [ -n "$a" ] || { echo "::error::$t produced no artefact on ${{ matrix.host }}"; exit 1; } | |
| case "$t" in | |
| x86_64-windows-gnu) cp "$a" "$RUNNER_TEMP/out/windows.exe" ;; | |
| aarch64-macos) cp "$a" "$RUNNER_TEMP/out/macos" ;; | |
| *) cp "$a" "$RUNNER_TEMP/out/linux" ;; | |
| esac | |
| echo "${{ matrix.host }} → $t : $(ls -l "$a" | awk '{print $5}') bytes" | |
| done | |
| # THE ONLY CRITERION THE `builtins` TOKEN HAS, AND WHY IT IS HERE. | |
| # | |
| # `[c-abi] builtins = "iso"` states that the C library supplies the ISO | |
| # functions and no vendor extensions. On Darwin targets clang's loop | |
| # idiom recogniser rewrites a constant-pattern fill into a call to | |
| # `memset_pattern16`, an Apple libc extension no such library carries. | |
| # That call is produced by the code generator, so it appears in no `-D` | |
| # and in no preprocessor dump --- and `mcpp.toolchain.cenv` verifies its | |
| # tokens by comparing a `-dM` dump. The token this mechanism emitted was | |
| # therefore a silent no-op for the whole of its first life, and this | |
| # step exists because no other kind of check could have reported it. | |
| # | |
| # LEG 1 IS THE DENOMINATOR, AND IT IS NOT DECORATION. At `-O0` the pass | |
| # does not run, and `-ffreestanding` implies `-fno-builtin`; an | |
| # assertion that only reads "the symbol is absent" therefore passes in | |
| # several worlds where nothing was measured. Leg 1 compiles with no flag | |
| # at all and fails if the symbol does NOT appear. | |
| # | |
| # LEG 2 PINS THE DEFECT ITSELF. `-fno-builtin-memset_pattern16` is | |
| # accepted in silence and changes nothing: `-fno-builtin-<fn>` is matched | |
| # against clang's builtin table, and `memset_pattern16` is an LLVM | |
| # TargetLibraryInfo libfunc rather than a clang builtin. The toolchain is | |
| # pinned in this job, so the reading is stable. Should a later pin make | |
| # leg 2 fail, clang has gained the narrower behaviour, and `cenv` can | |
| # emit the narrower flag and recover the 1.8 per cent `-fno-builtin` | |
| # costs. | |
| # | |
| # LEG 3 IS THE ENGINE: the same idiom, over the openkal stack, for | |
| # `aarch64-macos`, built by the mcpp under test. Measured on the token | |
| # this step was written for, the two readings are | |
| # | |
| # -fno-builtin-memset_pattern16 1 reference, and the link fails | |
| # -fno-builtin 0 references, and it links | |
| # | |
| # so a regression here reports itself at the link before the assertion | |
| # is reached. The assertion covers the remaining case, in which some | |
| # layer happens to supply the symbol and the link succeeds anyway. | |
| # | |
| # `-O2` IS PER PACKAGE RATHER THAN `--release`. The idiom pass does not | |
| # run at the dev profile's `-O0`, and a release build would compile the | |
| # runtime a second time in a second profile for no reading. | |
| - name: builtins = "iso" withdraws the Apple pattern fill | |
| run: | | |
| set -euo pipefail | |
| BIN="${MCPP_HOME:-$HOME/.mcpp}/registry/data/xpkgs/xim-x-llvm/22.1.8/bin" | |
| # Not `ls ... | head -1`: with `pipefail` the absent candidate's | |
| # exit status ends the step before the guard below is reached. | |
| pick() { # $1..$n = candidate paths; prints the first executable one | |
| for c in "$@"; do | |
| if [ -x "$c" ]; then printf '%s' "$c"; return 0; fi | |
| done | |
| return 1 | |
| } | |
| CLANG="$(pick "$BIN/clang" "$BIN/clang.exe")" \ | |
| || { echo "::error::no clang under $BIN"; exit 1; } | |
| NM="$(pick "$BIN/llvm-nm" "$BIN/llvm-nm.exe")" \ | |
| || { echo "::error::no llvm-nm under $BIN"; exit 1; } | |
| # Inside the clone, so the dependency is named by a relative path. | |
| # `$RUNNER_TEMP` is a backslash path on the Windows host and a TOML | |
| # string would read its separators as escapes. | |
| W="$RUNNER_TEMP/okl/examples/builtins-probe" | |
| rm -rf "$W"; mkdir -p "$W/src" | |
| # The one shape the idiom recogniser rewrites. The element type is | |
| # `int` and not `char` because a byte-repeating value becomes | |
| # `memset`, which every C library has. | |
| cat > "$W/src/main.cpp" <<'PROBE' | |
| extern "C" void fill(int* a, long n) { | |
| for (long i = 0; i < n; ++i) a[i] = 0x01020304; | |
| } | |
| int main() { | |
| static int buf[64]; | |
| fill(buf, 64); | |
| return buf[0] == 0x01020304 ? 0 : 1; | |
| } | |
| PROBE | |
| cat > "$W/mcpp.toml" <<'PROJECT' | |
| [package] | |
| name = "openkal-builtins-probe" | |
| version = "0.1.0" | |
| [build] | |
| cxxflags = ["-O2"] | |
| [dependencies] | |
| openkal-llvm-runtime = { path = "../.." } | |
| [toolchain] | |
| default = "llvm@22.1.8" | |
| PROJECT | |
| # THE READER ASKS FOR UNDEFINED SYMBOLS, NOT FOR BYTES. | |
| # | |
| # This was `grep -ac memset_pattern16`, on the reasoning that the name | |
| # is in the object's string table and a byte match needs no tool. It | |
| # reads 1/0 correctly with GNU grep and read 0 for all three legs on | |
| # the macOS host, where grep is BSD: the object is binary, and what | |
| # `-a` promises about that differs between the two. Leg 1 is what | |
| # reported it. `llvm-nm` is in the payload beside the clang already | |
| # being used, and answers the question this step is actually asking. | |
| refs() { "$NM" -u "$1" 2>/dev/null | grep -c memset_pattern16 || true; } | |
| T=--target=arm64-apple-macos14.0 | |
| "$CLANG" $T -O2 -c "$W/src/main.cpp" -o "$W/bare.o" | |
| "$CLANG" $T -O2 -fno-builtin-memset_pattern16 -c "$W/src/main.cpp" -o "$W/narrow.o" | |
| "$CLANG" $T -O2 -fno-builtin -c "$W/src/main.cpp" -o "$W/blunt.o" | |
| echo "no flag : $(refs "$W/bare.o")" | |
| echo "-fno-builtin-memset_pattern16 : $(refs "$W/narrow.o")" | |
| echo "-fno-builtin : $(refs "$W/blunt.o")" | |
| [ "$(refs "$W/bare.o")" = 1 ] || { echo "::error::leg 1: the probe no longer triggers the idiom, so legs 2 and 3 measure nothing"; exit 1; } | |
| [ "$(refs "$W/narrow.o")" = 1 ] || { echo "::error::leg 2: clang now honours -fno-builtin-memset_pattern16, and mcpp.toolchain.cenv can emit the narrower token"; exit 1; } | |
| [ "$(refs "$W/blunt.o")" = 0 ] || { echo "::error::leg 2: -fno-builtin no longer withdraws the pattern fill"; exit 1; } | |
| (cd "$W" && "$MCPP_UNDER_TEST" build --target aarch64-macos) | |
| obj=$(find "$W/target" -name 'main.o' | head -1) | |
| [ -n "$obj" ] || { echo "::error::leg 3 produced no object to read"; exit 1; } | |
| echo "engine, aarch64-macos over openkal: $(refs "$obj")" | |
| [ "$(refs "$obj")" = 0 ] || { echo "::error::leg 3: builtins = \"iso\" did not withdraw memset_pattern16"; exit 1; } | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: openkal-built-on-${{ matrix.host }} | |
| path: ${{ runner.temp }}/out/ | |
| if-no-files-found: error | |
| run: | |
| name: run 3 builds on ${{ matrix.system }} | |
| needs: build | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: 15 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - { system: linux, runner: ubuntu-24.04, file: linux } | |
| - { system: macos, runner: macos-14, file: macos } | |
| - { system: windows, runner: windows-2022, file: windows.exe } | |
| defaults: | |
| run: | |
| shell: bash | |
| steps: | |
| # NO checkout AND NO toolchain. This job is the claim: a program built | |
| # above openkal needs the operating system it was built for and nothing | |
| # else. Anything installed here would weaken what a pass means. | |
| - uses: actions/download-artifact@v4 | |
| with: { pattern: openkal-built-on-*, path: art } | |
| - name: The same program, from all three build hosts | |
| run: | | |
| set -euo pipefail | |
| fail=0 | |
| for host in linux macos windows; do | |
| bin="art/openkal-built-on-$host/${{ matrix.file }}" | |
| echo "──────── built on $host, running on ${{ matrix.system }} ────────" | |
| if [ ! -f "$bin" ]; then | |
| echo "::error::$bin is missing"; fail=1; continue | |
| fi | |
| # The executable bit does not survive an artefact upload. | |
| chmod +x "$bin" || true | |
| # arm64 macOS refuses an unsigned image, so the signature is | |
| # asserted before the run: a failure here is "the linker did not | |
| # ad-hoc sign it", which is a different repair from "it crashed". | |
| if [ "${{ matrix.system }}" = "macos" ]; then | |
| codesign -dv "$bin" 2>&1 | grep -q 'adhoc\|Signature' \ | |
| || { echo "::error::built on $host: no code signature"; fail=1; continue; } | |
| fi | |
| if ! "./$bin" > out.log 2>&1; then | |
| echo "::error::built on $host: it did not run"; cat out.log; fail=1; continue | |
| fi | |
| cat out.log | |
| ok=1 | |
| grep -q 'sorted: 2 4 7' out.log || ok=0 | |
| grep -q 'caught: 42' out.log || ok=0 | |
| # The line a link cannot fake. | |
| grep -q 'unwound: true' out.log || ok=0 | |
| grep -q 'import std over openkal: ok' out.log || ok=0 | |
| [ "$ok" = 1 ] || { echo "::error::built on $host: wrong output"; fail=1; } | |
| done | |
| [ "$fail" = 0 ] || exit 1 | |
| echo "three builds, one system, same four lines" | |
| # ────────────────────────────────────────────────────────────────── | |
| # The e2e scripts that BUILD the openkal ecosystem, on a runner that | |
| # has what they ask for. | |
| # | |
| # THEY WERE WRITTEN AND THEY WERE NEVER RUN. `285`–`289` declare | |
| # `# requires: llvm`, and the linux e2e shards report | |
| # | |
| # Detected capabilities: elf unix-shell fresh-sandbox gcc | |
| # patchelf pack symlink python3 … | |
| # | |
| # — no `llvm`, on either shard, because the shard workflow never | |
| # installs one. `run_all.sh` exits 0 on a skip, so the suite stayed | |
| # green while the five tests measuring this ecosystem did not run. | |
| # | |
| # run_all.sh's own note says why no token can fix this: a hard-requires | |
| # cannot tell "this runner is misconfigured" from "this platform | |
| # legitimately lacks the capability". The guard has to know which | |
| # runner it is, so it lives in the job — install the capability, then | |
| # assert each script's PASS line actually appeared. Same shape as | |
| # ci-linux-e2e.yml's `baremetal` job, for the same reason. | |
| # ────────────────────────────────────────────────────────────────── | |
| ecosystem-e2e: | |
| name: openkal e2e (the scripts, on a runner that has llvm) | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 90 | |
| defaults: | |
| run: | |
| shell: bash | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: ./.github/actions/bootstrap-mcpp | |
| - name: Build the mcpp in this pull request | |
| run: | | |
| set -euo pipefail | |
| export MCPP_VENDORED_XLINGS="$XLINGS_BIN" | |
| "$XLINGS_BIN" config --mirror GLOBAL 2>/dev/null || true | |
| "$MCPP" self config --mirror GLOBAL 2>/dev/null || true | |
| "$MCPP" build --dev | |
| # NEWEST BY MTIME, NOT FIRST BY DIRECTORY ORDER. `target/` holds one | |
| # directory per fingerprint and the runner restores a cache of it, so | |
| # `find … | head -1` can return a binary an earlier run left behind. | |
| # Measured: it reported the right VERSION STRING — the stale copy was | |
| # built from an earlier push of this same release — while missing the | |
| # last two commits, so one new test passed and two failed for reasons | |
| # that were nowhere in the source. | |
| BUILT=$(find target -type f -name 'mcpp' -printf '%T@ %p\n' \ | |
| | sort -rn | head -1 | cut -d' ' -f2) | |
| [ -n "$BUILT" ] || { echo "::error::mcpp did not build"; exit 1; } | |
| BUILT=$(cd "$(dirname "$BUILT")" && pwd)/$(basename "$BUILT") | |
| echo "MCPP_UNDER_TEST=$BUILT" >> "$GITHUB_ENV" | |
| "$BUILT" --version | |
| - name: Install what the scripts declare | |
| run: | | |
| set -euo pipefail | |
| "$MCPP_UNDER_TEST" self config --mirror GLOBAL 2>/dev/null || true | |
| # Both, and both are load-bearing: 285 and 291's first half build | |
| # with gcc (a backend running ON a platform, keeping the payload's | |
| # C library), 286-289 and 291's second half with llvm (the whole | |
| # stack from the graph, where openkal-llvm-runtime IS libc++). | |
| "$MCPP_UNDER_TEST" toolchain install gcc 16.1.0 | |
| "$MCPP_UNDER_TEST" toolchain install llvm 22.1.8 | |
| # THE EMULATORS, OR TWO OF THE SIX MEASURE HALF OF WHAT THEY SAY. | |
| # | |
| # 287 and 288 both end by RUNNING what they built — an aarch64 binary and | |
| # a riscv64 machine image — and both degrade to a SKIP when no emulator | |
| # is here. Measured on this job's first run: 288 printed | |
| # | |
| # SKIP no riscv64 machine emulator here — linking is not booting | |
| # | |
| # and still reached its OK line, so the PASS-line assertion below would | |
| # have called that covered. Linking is not booting, as the script itself | |
| # says. | |
| # | |
| # BOTH homes, for the reason ci-linux-e2e.yml's baremetal job records: | |
| # the shim on PATH dispatches against whichever home owns it, so an | |
| # emulator installed only in the ambient one answers "not installed" when | |
| # mcpp asks. | |
| - name: Install the emulators, and this host's mingw-w64 (#662) | |
| run: | | |
| set -euo pipefail | |
| # The runner image carries third-party apt lists (Google Chrome | |
| # among them) that this job does not use, and a transient | |
| # `Hash Sum mismatch` on one of them fails the whole update -- which | |
| # killed two cross-build jobs in setup, before a single byte was | |
| # compiled. Dropping the lists this job has no use for is what makes | |
| # the step's failure mean something about this job. | |
| # | |
| # BY CONTENT, NOT BY FILENAME. The first attempt removed | |
| # `google-chrome.list` and the update failed on the same URL: on | |
| # ubuntu-24.04 the runner writes deb822 `.sources` files, so the | |
| # name was a guess and the guess was wrong. | |
| sudo grep -rlE 'dl[.]google[.]com|packages[.]microsoft[.]com' \ | |
| /etc/apt/sources.list.d/ 2>/dev/null | xargs -r sudo rm -f | |
| # `mingw-w64` (the distro package, NOT `mingw-cross-gcc`) is what | |
| # 738's `mingw-host-headers` capability probes for | |
| # (`/usr/x86_64-w64-mingw32/include`). The assertion it gates -- | |
| # clang's own driver no longer searches the host once a graph | |
| # package supplies the target's C library -- has no discriminating | |
| # power without a host copy to have leaked in in the first place; | |
| # this is the one runner in the fleet that installs it for that | |
| # reason (run_all.sh's capability probe, and the design doc's note | |
| # on `# requires:` gates CI never satisfies on its own). | |
| sudo apt-get update -qq && sudo apt-get install -y -qq qemu-user mingw-w64 | |
| "$XLINGS_BIN" install xim:qemu-riscv -y | |
| XLINGS_HOME="${MCPP_HOME:-$HOME/.mcpp}/registry" \ | |
| "$XLINGS_BIN" install xim:qemu-riscv -y | |
| # Reachable AND runnable, asserted before the tests: without this the | |
| # scripts would simply skip and say so in a line nobody reads. | |
| qemu-aarch64 --version | head -1 | |
| "$XLINGS_BIN" run qemu-system-riscv64 --version 2>/dev/null | head -1 \ | |
| || command -v qemu-system-riscv64 | |
| - name: The scripts | |
| run: | | |
| set -euo pipefail | |
| export MCPP="$MCPP_UNDER_TEST" | |
| export MCPP_VENDORED_XLINGS="$XLINGS_BIN" | |
| # Directly rather than through run_all.sh: it accepts no filter, and | |
| # it exits 0 on a skip — which is the condition this job exists to | |
| # detect. | |
| fail=0 | |
| for t in tests/e2e/285_*.sh tests/e2e/286_*.sh tests/e2e/287_*.sh \ | |
| tests/e2e/288_*.sh tests/e2e/289_*.sh tests/e2e/291_*.sh \ | |
| tests/e2e/292_*.sh tests/e2e/293_*.sh tests/e2e/294_*.sh \ | |
| tests/e2e/738_*.sh; do | |
| echo "=== $t ===" | |
| bash "$t" 2>&1 | tee "$(basename "$t").log" || true | |
| rc=${PIPESTATUS[0]} | |
| [ "$rc" = "0" ] || { echo "::error::$t failed (exit $rc)"; fail=1; } | |
| done | |
| [ "$fail" = 0 ] || exit 1 | |
| - name: Each one RAN | |
| run: | | |
| set -euo pipefail | |
| # THE ASSERTION THIS JOB EXISTS FOR. A zero exit code cannot | |
| # distinguish "passed" from "skipped" — every one of these scripts | |
| # has an early `exit 0` for a capability or an arrangement it did | |
| # not find. The PASS line can. | |
| check() { | |
| grep -qF "$2" "$1".log || { | |
| echo "::error::$1 did not run to its conclusion on the runner that must run it" | |
| tail -5 "$1".log 2>/dev/null | sed 's/^/ /' | |
| return 1 | |
| } | |
| echo " ok $1" | |
| } | |
| fail=0 | |
| check 285_kernel_abi_from_graph_keeps_the_payload_c_library.sh \ | |
| "OK: a graph-supplied kernel interface leaves the payload's C library reachable" || fail=1 | |
| check 286_the_openkal_stack_still_builds.sh \ | |
| "OK: the openkal stack builds, links statically and runs" || fail=1 | |
| check 287_the_openkal_stack_crosses_to_aarch64.sh \ | |
| "OK: the openkal stack crosses to aarch64, supplies its atomics helpers and runs" || fail=1 | |
| # AND IT REACHED THE PARTS THAT NEED A TOOL. Both of 287's last two | |
| # assertions degrade to a SKIP, and the OK line prints either way. | |
| check 287_the_openkal_stack_crosses_to_aarch64.sh \ | |
| "LSE instructions out of" || fail=1 | |
| check 287_the_openkal_stack_crosses_to_aarch64.sh \ | |
| "it runs under qemu-aarch64" || fail=1 | |
| check 288_the_openkal_stack_on_a_machine_with_no_os.sh \ | |
| "OK: openkal runs on a machine with no operating system and no C library" || fail=1 | |
| # 288's name says "runs"; without this it can print that line | |
| # having only linked. | |
| check 288_the_openkal_stack_on_a_machine_with_no_os.sh \ | |
| "it boots" || fail=1 | |
| check 289_one_host_reaches_every_openkal_target.sh \ | |
| "OK: one host reached" || fail=1 | |
| check 291_dynamic_linkage_is_refused_only_when_the_c_library_is_the_graphs.sh \ | |
| "OK: the C library decides whether 'dynamic' can be honoured" || fail=1 | |
| check 292_a_package_that_names_a_layer_does_not_lose_the_targets_compiler.sh \ | |
| "OK: naming a layer changes the system, not the compiler that emits the target" || fail=1 | |
| check 293_the_requested_target_and_the_resolved_one_name_one_os.sh \ | |
| "OK: the requested target and the resolved one name one operating system" || fail=1 | |
| check 294_the_list_answers_what_can_be_built_not_what_has_a_payload.sh \ | |
| "OK: the list answers what can be built, not what has a payload" || fail=1 | |
| check 738_a_graph_supplied_target_closes_the_hosts_own_search.sh \ | |
| "PASS: 738 a graph-supplied target closes the host's own search" || fail=1 | |
| [ "$fail" = 0 ] || exit 1 |