docs(openkal): record the layering correction that produced 0.2 #901
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-linux-e2e | |
| # The e2e suite (tests/e2e/run_all.sh) split out of ci-linux.yml so it runs in | |
| # PARALLEL with the build/unit/toolchain jobs instead of tacked on after them, | |
| # and SHARDED across two runners on top of that. Both workflows share the same | |
| # cache lineage (mcpp sandbox + xlings + target/), so each shard restores a warm | |
| # build and the only added wall-clock vs. the inline version is one extra warm | |
| # `mcpp build` per runner. | |
| # | |
| # Paired workflows: ci-linux.yml (build + unit + toolchain legs + integration), | |
| # ci-macos.yml / ci-macos-e2e.yml, ci-windows.yml / ci-windows-e2e.yml. | |
| on: | |
| push: | |
| branches: [ main ] | |
| pull_request: | |
| branches: [ main ] | |
| workflow_dispatch: | |
| concurrency: | |
| group: ci-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| e2e: | |
| name: e2e ${{ matrix.shard }}/2 (linux x86_64, self-host) | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 45 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| shard: [1, 2] | |
| env: | |
| # Round-robin slice of tests/e2e (see run_all.sh). Two shards halve the | |
| # suite's wall-clock; the split is computed on the full file list, so a | |
| # test's shard does not move when host capabilities differ. | |
| E2E_SHARD: ${{ matrix.shard }}/2 | |
| MCPP_HOME: /home/runner/.mcpp | |
| # NOTE: do NOT force MCPP_VERBOSE here. The e2e suite includes tests that | |
| # assert mcpp's DEFAULT (quiet) output — e.g. 48_build_error_output and | |
| # 53_namespaced_cache_label — which forced verbose would break. Verbose is | |
| # set only in the fresh-install workflows (cold bootstrap, no such asserts). | |
| # A specific test that needs verbose passes `--verbose` itself. | |
| steps: | |
| - uses: actions/checkout@v4 | |
| # Same cache lineage as ci-linux.yml so this job lands on a warm | |
| # toolchain/sandbox instead of re-installing it. | |
| - uses: ./.github/actions/bootstrap-mcpp | |
| - name: Configure mirror + Build mcpp from source (self-host) | |
| run: | | |
| 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 | |
| - name: E2E suite | |
| # Per-test 600s timeout lives in tests/e2e/run_all.sh and identifies | |
| # WHICH test hung; this caps the whole suite so a hang fails fast. | |
| timeout-minutes: 25 | |
| run: | | |
| # Point the e2e runner at the freshly-built binary, not the | |
| # bootstrap one. Tests cd into mktemp -d, so $MCPP must be | |
| # absolute or the relative path breaks under the temp cwd. | |
| # The RELEASED mcpp this job bootstrapped from, kept for e2e 252: the | |
| # claim that an older client can still build against a package the PR | |
| # produces is only worth making if something checks it against a real | |
| # old binary. Captured before $MCPP is repointed at the fresh build. | |
| export MCPP_BOOT="$MCPP" | |
| MCPP=$(realpath "$(find target -type f -name mcpp -printf '%T@ %p\n' | sort -rn | head -1 | cut -d' ' -f2)") | |
| test -x "$MCPP" | |
| export MCPP | |
| # Tests that set MCPP_HOME to a fresh tmpdir need an xlings to | |
| # bootstrap from; surface the xlings binary installed above. | |
| export MCPP_VENDORED_XLINGS="$XLINGS_BIN" | |
| test -x "$MCPP_VENDORED_XLINGS" | |
| # GitHub-hosted runners are outside CN; keep CI toolchain downloads on | |
| # the global mirror while mcpp's default remains CN for fresh local | |
| # sandboxes. E2E tests with their own MCPP_HOME read this variable. | |
| export MCPP_E2E_TOOLCHAIN_MIRROR=GLOBAL | |
| "$MCPP" self config --mirror "$MCPP_E2E_TOOLCHAIN_MIRROR" | |
| "$MCPP" self config | |
| # Pin the global default so test 28 (default-toolchain path) gets a | |
| # deterministic GNU answer instead of an auto-install pick. | |
| # | |
| # Installed explicitly, not assumed. `toolchain default` requires the | |
| # toolchain to be present, and nothing in this job puts it there: | |
| # building mcpp above uses whatever first-run picks (llvm). It worked | |
| # only because the sandbox cache happened to carry a gcc some earlier | |
| # run had installed -- so the moment the cache key changed (an xlings | |
| # pin bump moves the whole prefix), every e2e shard failed with | |
| # `gcc@16.1.0 is not installed` before running a single test. | |
| # Warm runs re-install nothing; this costs a lookup. | |
| "$MCPP" toolchain install gcc 16.1.0 | |
| "$MCPP" toolchain default gcc@16.1.0 | |
| # Warm musl once so fresh-home e2e tests inherit the payload. | |
| "$MCPP" toolchain install gcc 16.1.0-musl | |
| bash tests/e2e/run_all.sh | |
| # ────────────────────────────────────────────────────────────────── | |
| # Bare metal: the one chain the sharded suite above cannot be trusted | |
| # to exercise. | |
| # | |
| # tests/e2e/130_freestanding_riscv_build_and_run.sh declares | |
| # `# requires: qemu-riscv`, which is legitimately absent on the macOS and | |
| # Windows runners — so it must be a SOFT token, and a soft token means the | |
| # test skips in silence on a Linux runner that lost qemu too. That is the | |
| # exact shape this repository has been burned by twice (65_* never ran at | |
| # all; ten pack e2e skipped on two platforms), and no token can tell the two | |
| # cases apart. | |
| # | |
| # So the guard lives here, where it can be exact: install qemu, run the one | |
| # test, and assert its PASS line appeared. A skip fails this job. | |
| # ────────────────────────────────────────────────────────────────── | |
| baremetal: | |
| name: bare-metal e2e (riscv64-none-elf, qemu) | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 40 | |
| env: | |
| MCPP_HOME: /home/runner/.mcpp | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: ./.github/actions/bootstrap-mcpp | |
| - name: Build mcpp from source (self-host) | |
| run: | | |
| 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 | |
| - name: Install the emulator (xim:qemu-riscv) | |
| run: | | |
| # ⚠️ BOTH homes. The shim on PATH dispatches against whichever home | |
| # owns it, and `mcpp run` runs the runner through that shim — so an | |
| # emulator installed only in the ambient xlings home answers | |
| # "xlings: 'qemu-system-riscv64' is not installed" when mcpp asks. | |
| # Measured: the job installed it once, the shim resolved, and the | |
| # run still failed. | |
| "$XLINGS_BIN" install xim:qemu-riscv -y | |
| XLINGS_HOME="${MCPP_HOME:-$HOME/.mcpp}/registry" \ | |
| "$XLINGS_BIN" install xim:qemu-riscv -y | |
| # Assert it is reachable AND runnable BEFORE the tests. Without this | |
| # the capability probe simply would not add `qemu-riscv` and the | |
| # tests would skip — which is what this job exists to prevent. | |
| command -v qemu-system-riscv64 | |
| qemu-system-riscv64 --version | head -1 | |
| # The target sysroot, into the home MCPP uses. Test 131's BSP | |
| # declares it as an `[xlings] deps` entry and finds it through | |
| # `xpkg_dir`; installed into the ambient xlings home instead, the | |
| # test would SKIP and the seam would go unexercised. | |
| XLINGS_HOME="${MCPP_HOME:-$HOME/.mcpp}/registry" \ | |
| "$XLINGS_BIN" install xim:picolibc-riscv -y | |
| test -d "${MCPP_HOME:-$HOME/.mcpp}/registry/data/xpkgs/xim-x-picolibc-riscv" | |
| - name: Bare-metal e2e | |
| timeout-minutes: 25 | |
| run: | | |
| MCPP=$(realpath "$(find target -type f -name mcpp -printf '%T@ %p\n' | sort -rn | head -1 | cut -d' ' -f2)") | |
| test -x "$MCPP" | |
| export MCPP | |
| export MCPP_VENDORED_XLINGS="$XLINGS_BIN" | |
| export MCPP_E2E_TOOLCHAIN_MIRROR=GLOBAL | |
| "$MCPP" self config --mirror "$MCPP_E2E_TOOLCHAIN_MIRROR" | |
| # llvm is the toolchain a freestanding target pins; install it | |
| # explicitly rather than relying on whatever the sandbox cache holds. | |
| "$MCPP" toolchain install llvm 22.1.8 | |
| # Run the two scripts DIRECTLY rather than through run_all.sh. | |
| # They are standalone (they take $MCPP and nothing else), run_all.sh | |
| # accepts no filter — it would run the whole 250-test suite here for | |
| # two tests — and, more to the point, run_all.sh exits 0 on a skip. | |
| # Invoked directly, a skip is visible: the script either prints its | |
| # PASS line or it does not. | |
| for t in tests/e2e/130_freestanding_riscv_build_and_run.sh \ | |
| tests/e2e/131_freestanding_bsp_supplies_everything.sh \ | |
| tests/e2e/132_freestanding_test_and_artifacts.sh \ | |
| tests/e2e/133_freestanding_std_subset.sh; do | |
| echo "=== $t ===" | |
| bash "$t" 2>&1 | tee "$(basename "$t").log" | |
| rc=${PIPESTATUS[0]} | |
| [ "$rc" = "0" ] || { echo "$t failed (exit $rc)"; exit 1; } | |
| done | |
| # The assertion this job exists for: both tests RAN. Each has an | |
| # early `exit 0` for a missing capability, so a zero exit code alone | |
| # does not distinguish "passed" from "skipped". | |
| grep -q 'PASS: freestanding riscv64 build + run' \ | |
| 130_freestanding_riscv_build_and_run.sh.log || { | |
| echo "130 (engine chain) skipped on the runner that must run it"; exit 1; } | |
| grep -q 'PASS: BSP supplies the sysroot' \ | |
| 131_freestanding_bsp_supplies_everything.sh.log || { | |
| echo "131 (ecosystem chain) skipped on the runner that must run it"; exit 1; } | |
| grep -q 'PASS: bare-metal mcpp test names its failure' \ | |
| 132_freestanding_test_and_artifacts.sh.log || { | |
| echo "132 (test + artifacts) skipped on the runner that must run it"; exit 1; } | |
| grep -q 'PASS: the freestanding std subset' \ | |
| 133_freestanding_std_subset.sh.log || { | |
| echo "133 (std subset) skipped on the runner that must run it"; exit 1; } | |
| # ────────────────────────────────────────────────────────────────── | |
| # Hermetic (no host toolchain): the ONLY environment class that | |
| # faithfully reproduces issue #195. Standard runners ship gcc + | |
| # libc6-dev, so a sandbox toolchain that leaks to the host's CRT | |
| # still links "green" there; this container has no compiler and no | |
| # host Scrt1.o, so any leak fails loudly. Builds PR code with the | |
| # bootstrap mcpp, then runs the llvm flow end-to-end. | |
| # ────────────────────────────────────────────────────────────────── | |
| hermetic: | |
| name: hermetic e2e (no host toolchain, container) | |
| runs-on: ubuntu-24.04 | |
| container: debian:stable-slim | |
| timeout-minutes: 60 | |
| env: | |
| XLINGS_NON_INTERACTIVE: '1' | |
| steps: | |
| - name: Install base utilities (NO compiler) | |
| run: | | |
| apt-get update -qq | |
| apt-get install -y -qq curl ca-certificates git xz-utils unzip | |
| # The whole point of this job: no host toolchain, no host CRT. | |
| ! command -v gcc | |
| ! command -v cc | |
| test ! -e /usr/lib/x86_64-linux-gnu/Scrt1.o | |
| test ! -e /usr/lib/gcc | |
| - uses: actions/checkout@v4 | |
| # Payload cache (downloads only — the container still has no host | |
| # toolchain, which is the property under test). | |
| - name: Cache mcpp sandbox payloads | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.mcpp | |
| key: mcpp-hermetic-${{ hashFiles('mcpp.toml') }} | |
| restore-keys: | | |
| mcpp-hermetic- | |
| - name: Bootstrap xlings + released mcpp | |
| run: | | |
| curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.8.17.2 | |
| export PATH="$HOME/.xlings/subos/current/bin:$PATH" | |
| xlings update | |
| xlings install mcpp -y -g | |
| MCPP_BOOT="$HOME/.xlings/subos/current/bin/mcpp" | |
| "$MCPP_BOOT" --version | |
| "$MCPP_BOOT" self config --mirror GLOBAL | |
| echo "MCPP_BOOT=$MCPP_BOOT" >> "$GITHUB_ENV" | |
| - name: Build PR mcpp from source (sandbox gcc only) | |
| run: | | |
| "$MCPP_BOOT" build | |
| MCPP=$(realpath "$(find target -type f -name mcpp -printf '%T@ %p\n' | sort -rn | head -1 | cut -d' ' -f2)") | |
| test -x "$MCPP" | |
| "$MCPP" --version | |
| echo "MCPP=$MCPP" >> "$GITHUB_ENV" | |
| - name: "issue #195 reproduction: manifest llvm toolchain, fresh" | |
| run: | | |
| cd "$(mktemp -d)" | |
| "$MCPP" new hello195 | |
| cd hello195 | |
| printf '\n[toolchain]\nlinux = "llvm@22.1.8"\n' >> mcpp.toml | |
| printf 'import std;\nint main() { std::println("hello {}", 195); return 0; }\n' > src/main.cpp | |
| "$MCPP" run | |
| - name: Hermetic llvm e2e subset | |
| run: | | |
| export PATH="$HOME/.xlings/subos/current/bin:$PATH" | |
| export MCPP | |
| bash tests/e2e/86_llvm_hermetic_link.sh | |
| bash tests/e2e/37_llvm_import_std.sh |