CI #96
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: CI | |
| # What this workflow asserts. | |
| # | |
| # A C++ 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. The | |
| # claim here is therefore not "it builds" but "a program above it does the | |
| # things a C++ program does", and the one that settles it is an exception thrown | |
| # across frames and caught --- because that is the path a build proves nothing | |
| # about. Everything else this package could check, a runtime that was linked but | |
| # never worked would also satisfy. | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| workflow_dispatch: | |
| inputs: | |
| mcpp_ref: | |
| description: "Branch of mcpp-community/mcpp to build and test against (empty = the released pin)" | |
| required: false | |
| default: "" | |
| env: | |
| MCPP_SOURCE_REF: ${{ github.event.inputs.mcpp_ref || vars.MCPP_SOURCE_REF }} | |
| MCPP_VERSION: 2026.8.27.1 | |
| XLINGS_VERSION: v2026.8.17.2 | |
| XLINGS_NON_INTERACTIVE: '1' | |
| jobs: | |
| runtime: | |
| name: build the runtime and run what stands above it (${{ matrix.toolchain }}) | |
| runs-on: ubuntu-24.04 | |
| # ⚠️ Raised for the self-build step below, which is temporary. See the note | |
| # there: when mcpp#486 ships, that step goes and so does this. | |
| timeout-minutes: 90 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| toolchain: ['llvm@22.1.8'] | |
| defaults: | |
| run: | |
| shell: bash | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install xlings and mcpp | |
| run: | | |
| # --retry-all-errors and not --retry alone: the first covers a | |
| # transient HTTP status and a timeout, and what this step meets is a | |
| # failure of the transport. Observed in this ecosystem as | |
| # `curl: (35) Recv failure: Connection reset by peer', thirteen | |
| # seconds into a job, before anything was built. | |
| curl -fsSL --retry 3 --retry-all-errors --retry-delay 2 \ | |
| https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh \ | |
| | bash -s "$XLINGS_VERSION" | |
| echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH" | |
| - name: Install mcpp | |
| run: bash tools/install-mcpp.sh | |
| # ⭐ THE ENGINE EVERY STEP BELOW WILL USE, COMPARED AGAINST THE ONE BUILT. | |
| # | |
| # The step above appends a directory to GITHUB_PATH, which governs the | |
| # steps that follow it, so that step cannot observe its own effect. Whether | |
| # the appended spelling is one the runner accepts is a property of the | |
| # runner and differs between hosts. Left unasserted, a cross-validation run | |
| # on a host that ignores it builds this ecosystem with the released engine | |
| # and reports the result as though the change under review had been tested. | |
| - name: The engine on PATH is the one under review | |
| run: | | |
| set -euo pipefail | |
| if [ -z "${MCPP_UNDER_REVIEW:-}" ]; then | |
| echo " no source reference: this run tests $(mcpp --version)" | |
| exit 0 | |
| fi | |
| on_path=$(mcpp --version | awk '{print $2}') | |
| if [ "$on_path" != "$MCPP_UNDER_REVIEW" ]; then | |
| echo "::error::PATH resolves mcpp $on_path, and the build under review is $MCPP_UNDER_REVIEW" | |
| echo " the directory appended to GITHUB_PATH did not take effect on this host" | |
| command -v mcpp | |
| exit 1 | |
| fi | |
| echo " every step below runs $on_path, built from $MCPP_SOURCE_REF" | |
| - name: Select the toolchain | |
| run: | | |
| spec='${{ matrix.toolchain }}' | |
| mcpp toolchain install "${spec%@*}" "${spec#*@}" | |
| mcpp toolchain default "$spec" | |
| # THE C LIBRARY THIS RUNTIME IS CONFIGURED FOR, AS WRITTEN ON THE BRANCH | |
| # UNDER TEST RATHER THAN AS PUBLISHED. The reasoning is in the script. | |
| - name: The stack, as written on this branch | |
| run: bash tools/branch-graph.sh '${{ github.head_ref || github.ref_name }}' | |
| - name: The runtime builds | |
| run: mcpp build | |
| # ⚠️ THE ONE OBSERVATION A BUILD CANNOT MAKE. | |
| # | |
| # An unwinder that cannot find the program's frame descriptions compiles, | |
| # links, and runs every path that does not throw. Measured while this | |
| # package was written: the whole example above printed its first line and | |
| # then `libc++abi: terminating due to uncaught exception', with | |
| # _Unwind_Backtrace walking zero frames --- and nothing named the cause. | |
| # So the throw is the check, and the destructor beside it is what says the | |
| # unwind was correct rather than merely non-fatal. | |
| # ⭐ THE UNWINDER, AND EVERY POSITION `__config_site` DECLARES. | |
| # | |
| # The throw and the destructor beside it are what say the runtime is | |
| # really there. The rest of the probe asks whether the CLAIMS this package | |
| # makes about the environment beneath are true: `std::filesystem` over | |
| # openkal.fs, `std::random_device` over openkal.random, and the two | |
| # operations openkal has no atom for --- which must be REFUSED rather than | |
| # ignored, and which a probe checking only the supported ones would pass | |
| # for a port that silently accepted them. | |
| - name: A C++ program above it throws, catches, and uses what is declared | |
| run: | | |
| cd examples/cxx && mcpp run 2>&1 | tee out.log | |
| grep -q -- '-- failures: 0 --' out.log | |
| ! grep -q '^FAIL:' out.log | |
| - name: import std above it | |
| run: | | |
| cd examples/import-std && mcpp run 2>&1 | tee out.log | |
| grep -q 'import std above openkal: 2 4 7' out.log | |
| # WHAT __config_site DECLARES AND WHAT THE PORT BENEATH PROVIDES ARE | |
| # RECONCILED HERE, RATHER THAN BEING CHECKED ONCE AND ASSUMED AFTERWARDS. | |
| # | |
| # `__config_site` is this package's statement about the environment it was | |
| # configured for, and the 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 0 --- 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, | |
| # so the declaration is now true and this step is what keeps it true. | |
| # | |
| # THE CRITERION IS A RELATION AND NOT A VALUE. Asserting "a terminal is | |
| # detected" would need a terminal; asserting "a pipe is not" would pass | |
| # throughout the defect. What must hold is that the two DIFFER, and that | |
| # they differ the way the system's own C library says they do. | |
| - name: What the runtime declares is what the port provides | |
| run: | | |
| set -euo pipefail | |
| d="$(mktemp -d)"; mkdir -p "$d/src" | |
| cat > "$d/mcpp.toml" <<TOML | |
| [package] | |
| name = "termdecl" | |
| version = "0.1.0" | |
| [dependencies] | |
| openkal-llvm-runtime = { path = "$PWD" } | |
| [targets.termdecl] | |
| kind = "bin" | |
| main = "src/main.cpp" | |
| TOML | |
| sed -i 's/^ //' "$d/mcpp.toml" | |
| cat > "$d/src/main.cpp" <<'CPP' | |
| #include <cstdio> | |
| #include <unistd.h> | |
| // The declaration this step exists to reconcile. If the package stops | |
| // claiming a terminal, the program says so rather than failing to | |
| // compile: the point is to compare the claim against the behaviour, | |
| // and a build error would compare nothing. | |
| int main() { | |
| #if defined(_LIBCPP_HAS_TERMINAL) && _LIBCPP_HAS_TERMINAL | |
| std::printf("declared=1 isatty=%d\n", isatty(1)); | |
| #else | |
| std::printf("declared=0 isatty=%d\n", isatty(1)); | |
| #endif | |
| } | |
| CPP | |
| sed -i 's/^ //' "$d/src/main.cpp" | |
| ( cd "$d" && mcpp build ) | |
| bin="$(find "$d/target" -type f -name termdecl | head -1)" | |
| test -n "$bin" || { echo "::error::the probe did not build"; exit 1; } | |
| # The control: the system's own C library, through the same harness. | |
| # Without it a `script` that fails to allocate a pty would make the | |
| # runtime look wrong. | |
| printf '#include <stdio.h>\n#include <unistd.h>\nint main(void){ printf("%%d\\n", isatty(1)); return 0; }\n' > "$d/ctrl.c" | |
| cc "$d/ctrl.c" -o "$d/ctrl" | |
| ctrl_pipe="$("$d/ctrl" | cat | tr -d '\r')" | |
| ctrl_tty="$(script -qec "$d/ctrl" /dev/null | tr -d '\r' | head -1)" | |
| [ "$ctrl_pipe" = 0 ] && [ "$ctrl_tty" = 1 ] \ | |
| || { echo "::error::the harness cannot tell a pty from a pipe (control gave $ctrl_pipe/$ctrl_tty)" | |
| exit 1; } | |
| out_pipe="$("$bin" | cat | tr -d '\r')" | |
| out_tty="$(script -qec "$bin" /dev/null | tr -d '\r' | head -1)" | |
| echo " control: pipe=$ctrl_pipe tty=$ctrl_tty" | |
| echo " runtime: $out_pipe / $out_tty" | |
| declared="${out_pipe#declared=}"; declared="${declared%% *}" | |
| pipe_v="${out_pipe##*isatty=}" | |
| tty_v="${out_tty##*isatty=}" | |
| if [ "$declared" = 1 ]; then | |
| # The claim is that a terminal can be detected, so the two must | |
| # differ and must differ as the system's own library does. | |
| [ "$pipe_v" = "$ctrl_pipe" ] && [ "$tty_v" = "$ctrl_tty" ] \ | |
| || { echo "::error::the runtime declares _LIBCPP_HAS_TERMINAL but the port answers $pipe_v/$tty_v where the system answers $ctrl_pipe/$ctrl_tty" | |
| exit 1; } | |
| echo " ok the declaration holds: a terminal is distinguished from a pipe" | |
| else | |
| # The claim is that it cannot. Then it must not appear to: a | |
| # declaration of 0 beside a working isatty is also a drift, and the | |
| # remedy is to raise the declaration rather than leave it stale. | |
| [ "$tty_v" = "$ctrl_tty" ] \ | |
| && { echo "::error::the runtime declares no terminal support while the port detects one; the declaration is stale" | |
| exit 1; } | |
| echo " ok the declaration holds: no terminal support is claimed and none is present" | |
| fi | |
| # ⭐⭐ THE SAME PROGRAM ON A MACHINE WITH NO OPERATING SYSTEM. | |
| # | |
| # Everything above this step runs on a host, and a host has a C library, a | |
| # C++ runtime and an unwinder already installed. A program that reaches | |
| # one of them by mistake still works — so those steps can pass without | |
| # having exercised this package's own copies at all. | |
| # | |
| # There is nothing here to reach. The C library is openkal-musl, the | |
| # standard library and the unwinder are this package's, and beneath them | |
| # is firmware whose whole interface is `ecall`. ⇒ This is the step that | |
| # cannot go green by accident, which is why the design document makes it | |
| # the acceptance criterion rather than one more row. | |
| # | |
| # ⚠️ The assertion is on the OUTPUT and not on the exit status: firmware | |
| # that never reaches the payload exits zero, and so does a payload whose | |
| # console writes go nowhere. | |
| - name: Install the emulator | |
| if: matrix.toolchain == 'llvm@22.1.8' | |
| run: | | |
| xlings install xim:qemu-riscv -y | |
| XLINGS_HOME="$HOME/.mcpp/registry" xlings install xim:qemu-riscv -y | |
| - name: The same source on bare metal, with exceptions | |
| if: matrix.toolchain == 'llvm@22.1.8' | |
| run: | | |
| set -euo pipefail | |
| cd examples/same-source | |
| Q=$(ls -d "$HOME"/.mcpp/registry/data/xpkgs/xim-x-qemu-riscv/*/bin/qemu-system-riscv64 | head -1) | |
| # ⚠️ Anchored on the BARE NAME, so a checkout that already carries a | |
| # path is left alone rather than getting a path inside a path. The | |
| # manifest is required to carry the bare name; the note beside it says | |
| # why, and this is the step that relies on it. | |
| grep -q '"qemu-system-riscv64"' mcpp.toml \ | |
| || { echo "::error::the manifest no longer carries the bare emulator name"; exit 1; } | |
| sed -i "s|\"qemu-system-riscv64\"|\"$Q\"|" mcpp.toml | |
| # ⚠️ `--target riscv64-none-elf`, AND THE FLAG IS THE WHOLE STEP. | |
| # | |
| # This manifest carries no `[build] target`, so a bare `mcpp run` | |
| # builds for the HOST and passes — the four lines below appear either | |
| # way, which is the manifest's own claim and not an accident. A step | |
| # named "on bare metal" that omits the flag therefore reports green | |
| # over a host build and has never once reached OpenSBI. Measured | |
| # 2026-08-24: the artefact was `target/x86_64-linux-gnu/…`. | |
| mcpp run --target riscv64-none-elf 2>&1 | tee out.log | |
| # The emulator's own banner, so a run that never left the host cannot | |
| # satisfy this step by printing the four application lines. | |
| grep -q 'Boot HART' out.log # firmware ran; this is OpenSBI | |
| grep -q 'sorted: 2 4 7' out.log # containers + algorithms + the allocator | |
| grep -q 'caught: 42' out.log # the unwinder found the handler | |
| grep -q 'unwound: true' out.log # ⭐ and ran a destructor on the way | |
| grep -q 'import std over openkal: ok' out.log | |
| # ⭐⭐ AND THE SAME SOURCE ON THIS MACHINE, WHICH IS WHAT MAKES THE STEP | |
| # ABOVE A DEMONSTRATION RATHER THAN AN ILLUSTRATION. | |
| # | |
| # Nothing is edited between the two commands — no `#if`, no second | |
| # directory, no second source. The only thing that differs is the memory | |
| # layout, and `build.mcpp` states it behind a condition on the target OS | |
| # because that is a statement about the machine. | |
| - name: The same source, on this machine, over openkal-linux | |
| if: matrix.toolchain == 'llvm@22.1.8' | |
| run: | | |
| set -euo pipefail | |
| cd examples/same-source && mcpp run 2>&1 | tee host.log | |
| grep -q 'import std over openkal: ok' host.log | |
| # The four lines are the same four lines. | |
| # | |
| # ⚠️ `tr -d '\r'` ON BOTH SIDES, AND IT IS NOT COSMETIC. The bare-metal | |
| # run reaches the console through an emulated 16550 UART, and a serial | |
| # console terminates lines with CRLF; the native run does not. Without | |
| # this the diff reports four differing lines whose visible text is | |
| # identical, which reads as a failure of the claim being tested rather | |
| # than of the transport carrying it. The assertion is about what the | |
| # program printed, not about how the bytes arrived. | |
| diff <(grep -E '^(sorted|caught|unwound|import std over openkal):' out.log | tr -d '\r') \ | |
| <(grep -E '^(sorted|caught|unwound|import std over openkal):' host.log | tr -d '\r') | |
| # ⭐⭐ AND THE SAME SOURCE FOR TWO MACHINES THIS ONE IS NOT. | |
| # | |
| # The two steps above prove the source does not know which machine it is | |
| # for. These prove the BUILD does not need to be on it: one Linux host | |
| # produces a PE and a Mach-O, and the jobs below run them on the real | |
| # thing with nothing installed. | |
| # | |
| # ⚠️ THE ARTEFACT IS THE ARGUMENT, WHICH IS WHY THOSE JOBS INSTALL NOTHING. | |
| # Not mcpp, not a compiler, not a C runtime — the program carries its C | |
| # library, its C++ runtime and its unwinder, and what remains is the | |
| # operating system it was built for. A run that needed a redistributable | |
| # installed first would be demonstrating something weaker. | |
| - name: The same source, built here for Windows and for macOS | |
| if: matrix.toolchain == 'llvm@22.1.8' | |
| run: | | |
| set -euo pipefail | |
| cd examples/same-source | |
| mkdir -p "$RUNNER_TEMP/cross" | |
| for t in x86_64-windows-gnu aarch64-macos; do | |
| rm -rf target | |
| mcpp build --target "$t" | |
| a=$(find target -type f \( -name 'openkal-same-source' -o -name '*.exe' \) | head -1) | |
| [ -n "$a" ] || { echo "::error::$t produced no artefact"; exit 1; } | |
| echo "$t → $(file -b "$a")" | |
| cp "$a" "$RUNNER_TEMP/cross/" | |
| done | |
| # ⚠️ The format is asserted here rather than left to the run jobs. A | |
| # run that fails tells you the program did not work; this tells you | |
| # what was produced, and the two failures need different fixes. | |
| file "$RUNNER_TEMP/cross/openkal-same-source.exe" | grep -q 'PE32+ executable' | |
| file "$RUNNER_TEMP/cross/openkal-same-source" | grep -q 'Mach-O 64-bit arm64' | |
| - uses: actions/upload-artifact@v4 | |
| if: matrix.toolchain == 'llvm@22.1.8' | |
| with: | |
| name: cross-artifacts | |
| path: ${{ runner.temp }}/cross/ | |
| if-no-files-found: error | |
| # --------------------------------------------------------------------------- | |
| # ⭐⭐ THE ACCEPTANCE CRITERION FOR PORTABILITY OF THE ARTEFACT. | |
| # | |
| # A cross build that produces a well-formed file proves the compiler was told | |
| # the right target. It does not prove the program runs, and every difference | |
| # this ecosystem has had to find on these two platforms — the loader-bootstrapped | |
| # thread-local, the unwinder's search for its own tables, the personality | |
| # routine — links successfully and fails at run time. | |
| # | |
| # ⚠️ These jobs deliberately have NO toolchain steps. If one is ever added | |
| # because "the program needs it", that is the finding, not the fix. | |
| run-on-windows: | |
| name: the artefact built on Linux runs on Windows | |
| needs: runtime | |
| runs-on: windows-2022 | |
| timeout-minutes: 10 | |
| defaults: | |
| run: | |
| shell: bash | |
| steps: | |
| - uses: actions/download-artifact@v4 | |
| with: { name: cross-artifacts, path: art } | |
| - name: It runs, and it unwinds | |
| run: | | |
| set -euo pipefail | |
| ./art/openkal-same-source.exe 2>&1 | tee out.log | |
| grep -q 'sorted: 2 4 7' out.log | |
| grep -q 'caught: 42' out.log | |
| # ⭐ The line a link cannot fake: a destructor ran during the unwind, | |
| # so libunwind found `.eh_frame` by reading the image rather than by | |
| # asking the operating system to enumerate modules. | |
| grep -q 'unwound: true' out.log | |
| grep -q 'import std over openkal: ok' out.log | |
| run-on-macos: | |
| name: the artefact built on Linux runs on macOS | |
| needs: runtime | |
| runs-on: macos-14 | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/download-artifact@v4 | |
| with: { name: cross-artifacts, path: art } | |
| - name: It runs, and it unwinds | |
| run: | | |
| set -euo pipefail | |
| # ⚠️ The executable bit does not survive an artefact upload. | |
| chmod +x art/openkal-same-source | |
| # ⚠️ AND THE SIGNATURE DOES. arm64 macOS refuses an unsigned image, so | |
| # this is asserted before the run: a failure here is "the linker did | |
| # not ad-hoc sign it", which is a different repair from "the program | |
| # crashed". | |
| codesign -dv art/openkal-same-source 2>&1 | grep -q 'adhoc\|Signature' | |
| # ⚠️ A CRASH IS DIAGNOSED HERE RATHER THAN GUESSED AT LATER. This is | |
| # the first time an artefact of this stack has run on this system, and | |
| # the interesting failures — the entry point's assumptions about what | |
| # the kernel hands it, the thread pointer, the two borrowed names — | |
| # all look identical from outside: `Segmentation fault: 11`. | |
| # One CI cycle that prints a backtrace is worth several that do not. | |
| if ! ./art/openkal-same-source > out.log 2>&1; then | |
| echo "--- how far the loader got ---" | |
| # ⚠️ To a file, then read. Piping into `tail` interleaves dyld's | |
| # output with the shell's own report of the signal, and the last | |
| # lines — the ones that say which initializer was running — are the | |
| # ones that get lost. | |
| DYLD_PRINT_INITIALIZERS=1 ./art/openkal-same-source > dyld.log 2>&1 || true | |
| tail -40 dyld.log | |
| echo "--- it did not run; what the debugger saw ---" | |
| # ⭐ `lr` IS THE DATUM. A jump to address 0 leaves no frame to | |
| # unwind, so `bt` says only "frame #0: 0x0" — which is the symptom | |
| # restated. The link register still holds the return address of | |
| # whoever made that call, and `image lookup` turns it into a name. | |
| # ⚠️ `-k` AND NOT `-o`. In `--batch` lldb abandons the remaining | |
| # `-o` commands after the first one that errors, and reading | |
| # register state at a PC of 0 errors — so the three commands that | |
| # would have said something never ran. `-k` is the list lldb | |
| # executes WHEN THE PROCESS CRASHES, which is the case at hand. | |
| lldb --batch \ | |
| -k 'register read pc lr sp fp x8 x9 x16 x17' \ | |
| -k 'image lookup --address $lr' \ | |
| -k 'thread backtrace all' \ | |
| -k 'image list -o -f' \ | |
| -o run \ | |
| -- ./art/openkal-same-source 2>&1 | tail -70 || true | |
| echo "--- the image's own dependencies ---" | |
| otool -L art/openkal-same-source || true | |
| otool -l art/openkal-same-source | grep -A4 LC_MAIN || true | |
| cat out.log | |
| exit 1 | |
| fi | |
| cat out.log | |
| grep -q 'sorted: 2 4 7' out.log | |
| grep -q 'caught: 42' out.log | |
| grep -q 'unwound: true' out.log | |
| grep -q 'import std over openkal: ok' out.log | |
| # --------------------------------------------------------------------------- | |
| # ⭐⭐ THE HOST DIMENSION — THE HALF OF THE CLAIM THE JOBS ABOVE DO NOT TOUCH. | |
| # | |
| # Everything above builds on Linux. That establishes "one host reaches every | |
| # target" and leaves open the thing an N×N matrix would otherwise have to | |
| # enumerate: whether the HOST matters. The scheme's answer is that it does not | |
| # — the target side is a set of packages and the compiler is a retargetable | |
| # clang, so N hosts × N targets collapses to N implementations plus one tool. | |
| # | |
| # ⚠️ THAT IS A CLAIM, AND CLAIMS OF THIS SHAPE HAVE BEEN WRONG HERE BEFORE: | |
| # the Linux host needed four separate repairs before it reached PE, and every | |
| # one of them was invisible until a build was actually run. Two more hosts | |
| # cost two jobs; asserting the collapse without running them costs a paragraph | |
| # and proves nothing. | |
| # | |
| # Each host builds all four targets and RUNS the one that is itself, which is | |
| # the same criterion the Linux job applies to itself. | |
| host-dimension: | |
| name: ${{ matrix.host }} host reaches every target | |
| needs: runtime | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: 120 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - { host: macOS, runner: macos-14, native: aarch64-macos } | |
| - { host: Windows, runner: windows-2022, native: x86_64-windows-gnu } | |
| defaults: | |
| run: | |
| shell: bash | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install xlings (Unix) | |
| if: runner.os != 'Windows' | |
| run: | | |
| # --retry-all-errors and not --retry alone: the first covers a | |
| # transient HTTP status and a timeout, and what this step meets is a | |
| # failure of the transport. Observed in this ecosystem as | |
| # `curl: (35) Recv failure: Connection reset by peer', thirteen | |
| # seconds into a job, before anything was built. | |
| curl -fsSL --retry 3 --retry-all-errors --retry-delay 2 \ | |
| https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh \ | |
| | bash -s "$XLINGS_VERSION" | |
| echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH" | |
| - name: Install xlings (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| irm https://d2learn.org/xlings-install.ps1.txt | iex | |
| "$env:USERPROFILE\.xlings\subos\current\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append | |
| - name: Install mcpp | |
| run: bash tools/install-mcpp.sh | |
| # ⭐ THE ENGINE EVERY STEP BELOW WILL USE, COMPARED AGAINST THE ONE BUILT. | |
| # | |
| # The step above appends a directory to GITHUB_PATH, which governs the | |
| # steps that follow it, so that step cannot observe its own effect. Whether | |
| # the appended spelling is one the runner accepts is a property of the | |
| # runner and differs between hosts. Left unasserted, a cross-validation run | |
| # on a host that ignores it builds this ecosystem with the released engine | |
| # and reports the result as though the change under review had been tested. | |
| - name: The engine on PATH is the one under review | |
| run: | | |
| set -euo pipefail | |
| if [ -z "${MCPP_UNDER_REVIEW:-}" ]; then | |
| echo " no source reference: this run tests $(mcpp --version)" | |
| exit 0 | |
| fi | |
| on_path=$(mcpp --version | awk '{print $2}') | |
| if [ "$on_path" != "$MCPP_UNDER_REVIEW" ]; then | |
| echo "::error::PATH resolves mcpp $on_path, and the build under review is $MCPP_UNDER_REVIEW" | |
| echo " the directory appended to GITHUB_PATH did not take effect on this host" | |
| command -v mcpp | |
| exit 1 | |
| fi | |
| echo " every step below runs $on_path, built from $MCPP_SOURCE_REF" | |
| # ⭐ THE SAME ENGINE AND THE SAME STACK AS THE LINUX JOB, FROM A DIFFERENT | |
| # HOST. This job had neither: it installed the released engine and | |
| # resolved this ecosystem from the index, so a change spanning these | |
| # repositories was validated on one host of three and reported as | |
| # validated everywhere. | |
| - name: Select the toolchain | |
| run: | | |
| set -euo pipefail | |
| # ⚠️ INSTALL, THEN SELECT. `toolchain default` names a toolchain and | |
| # does not fetch one, so selecting an absent payload fails with | |
| # `llvm@22.1.8 is not installed` — measured on both rows of this job. | |
| mcpp toolchain install llvm 22.1.8 | |
| mcpp toolchain default 'llvm@22.1.8' | |
| - name: The stack, as written on this branch | |
| run: bash tools/branch-graph.sh '${{ github.head_ref || github.ref_name }}' | |
| - name: Every target, from this host | |
| run: | | |
| set -euo pipefail | |
| cd examples/same-source | |
| for t in x86_64-linux-gnu x86_64-windows-gnu aarch64-macos riscv64-none-elf; do | |
| rm -rf target | |
| mcpp 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 this host"; exit 1; } | |
| echo "$t → $(file -b "$a" 2>/dev/null || echo built)" | |
| done | |
| # ⭐ And the one that is this machine, run rather than inspected — the same | |
| # criterion the Linux job holds itself to, applied from a different host. | |
| - name: The artefact for this host runs on it | |
| run: | | |
| set -euo pipefail | |
| cd examples/same-source | |
| rm -rf target | |
| mcpp run --target '${{ matrix.native }}' 2>&1 | tee out.log | |
| grep -q 'sorted: 2 4 7' out.log | |
| grep -q 'caught: 42' out.log | |
| grep -q 'unwound: true' out.log | |
| grep -q 'import std over openkal: ok' out.log |