Resolve the target side once, and let every translation unit read it #117
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: | |
| 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. | |
| OPENKAL_BRANCH: 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. | |
| "$MCPP" build --dev | |
| BUILT=$(find target -type f \( -name 'mcpp' -o -name 'mcpp.exe' \) | head -1) | |
| [ -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: 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' | |
| - 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 | |
| - 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 the last two scripts need | |
| run: | | |
| set -euo pipefail | |
| sudo apt-get update -qq && sudo apt-get install -y -qq qemu-user | |
| "$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; 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 | |
| [ "$fail" = 0 ] || exit 1 |