Skip to content

0.5.0 --- follow openkal 0.11 #57

0.5.0 --- follow openkal 0.11

0.5.0 --- follow openkal 0.11 #57

Workflow file for this run

name: CI
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 }}
jobs:
build:
name: boots under OVMF as BOOTX64.EFI
runs-on: ubuntu-24.04
timeout-minutes: 40
env:
# The specification is taken from the branch of the same name where one
# exists, so a change spanning both repositories is reviewed as a whole.
OPENKAL_BRANCH: ${{ github.head_ref || github.ref_name }}
MCPP_VERSION: 2026.8.27.1
XLINGS_VERSION: v2026.8.17.2
XLINGS_NON_INTERACTIVE: '1'
steps:
- uses: actions/checkout@v4
- name: Install xlings
run: |
curl -fsSL 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: |
# ⚠️ A LOOP, BECAUSE ONE `xlings update` CAN RETURN A STALE INDEX
# WITHOUT SAYING SO.
#
# The index is published as an artifact behind a pointer, and that
# pointer propagates asynchronously after a version bump is merged.
# Measured on release day: an update run four minutes after the merge
# printed `index updated`, and the install then failed with
#
# package 'mcpp@<ver>' not found in the synced index
# (xim@artifact:<an older sha>, ...), synced 0 seconds ago
#
# Nothing had gone wrong. The update fetched the PREVIOUS artifact,
# and "synced 0 seconds ago" describes when it was fetched rather than
# what it contains — which is why the message reads as freshness.
#
# So this is not a retry around flakiness; it is the wait that a
# single update does not perform. A pin naming a version that was
# never published still fails, after the last attempt, and says which
# of the two situations it is.
for attempt in 1 2 3 4 5 6; do
xlings update > /dev/null 2>&1 || true
if # ⚠️ THE PIN MAY NAME THE RELEASE THIS RUN IS VALIDATING, which does
# not exist yet — that is the whole point of MCPP_SOURCE_REF. Bootstrap
# from whatever the index has; the step below replaces it with the
# build under review, and the pin is what an ordinary run tests.
if [ -n "${MCPP_SOURCE_REF:-}" ]; then
xlings install mcpp -y -g
else
xlings install "mcpp@$MCPP_VERSION" -y -g
fi; then break; fi
if [ "$attempt" = 6 ]; then
echo "::error::mcpp@$MCPP_VERSION never appeared in the index (6 attempts over 5 minutes). If it was just released, the pointer has not propagated; if the pin names a version that was never published, it never will."
exit 1
fi
echo "the index has not caught up yet (attempt $attempt of 6); waiting 60s"
sleep 60
done
# ⚠️⚠️ TRANSITION: GIVE THE BOOTSTRAP THE glibc ITS BINDING NAMES.
#
# `xim:glibc`'s `latest` moved from `2.44` to `2.44.2`. A payload
# directory is named after the version a request RESOLVED to, while a
# RuntimeBinding carries the version that was DECLARED — and the xlings
# a released mcpp vendors into its own sandbox still declares `2.44`.
# So a clean machine installs `2.44.2`, the toolchain fixup asks for
# `2.44`, and the build stops before anything is compiled:
#
# error: selected RuntimeBinding glibc@2.44 requires payload
# '…/xpkgs/xim-x-glibc/2.44', but it is not installed
#
# ⚠️ On every NEW machine and on none that already existed, which is why
# it is invisible from a developer's own. Measured on `main` as readily
# as on any branch — the index records the same failure verbatim in
# `pkgs/g/glibc.lua` and states the rule it broke: "The index is DATA
# and the client is a PROGRAM: the consumer ships first."
#
# ⭐ REMOVE THIS once a released mcpp resolves it. `mcpp 2026.8.27.1`
# accepts an installed payload whose version REFINES the requested one
# (`payload_dir_for_version`), so a bootstrap from it needs nothing
# here. Until then the missing payload is simply installed.
if [ -x "$HOME/.mcpp/registry/bin/xlings" ]; then
XLINGS_HOME="$HOME/.mcpp/registry" XLINGS_NON_INTERACTIVE=1 \
"$HOME/.mcpp/registry/bin/xlings" install glibc@2.44 -y -g \
>/dev/null 2>&1 || true
echo "glibc payloads present: $(ls "$HOME/.mcpp/registry/data/xpkgs/xim-x-glibc" 2>/dev/null | tr '\n' ' ')"
fi
mcpp --version
mcpp self config --mirror GLOBAL
# ⭐⭐ CROSS-VALIDATION: BUILD THE mcpp UNDER REVIEW AND USE THAT ONE.
#
# Empty in the ordinary run, so this job keeps testing the RELEASED
# mcpp the pin above names. Set it — `workflow_dispatch` input, or the
# repository variable — and the same job runs against that source.
#
# ⚠️ THIS EXISTS BECAUSE THE ORDER USED TO BE WRONG. Several mcpp
# releases went out green and only then turned this ecosystem red: the
# engine's own CI cannot see a defect that appears only in a real
# dependency graph, and this repository could not see the engine until
# it had been published. Validating before the release closes that gap.
#
# The released mcpp installed just above is the bootstrap that compiles
# it; mcpp builds itself and there is no other compiler for it here.
if [ -n "${MCPP_SOURCE_REF:-}" ]; then
src="$RUNNER_TEMP/mcpp-src"
[ -d "$src" ] || git clone --quiet --depth 1 \
--branch "$MCPP_SOURCE_REF" \
https://github.com/mcpp-community/mcpp.git "$src"
# ⚠️ THE CLONE'S OWN WORKSPACE PIN MUST NOT DECIDE WHICH mcpp
# BUILDS IT. `.xlings.json` at mcpp's root pins the mcpp that
# compiles mcpp, and that pin does not move when mcpp is released —
# so a build inside the checkout obeys it and tries to install a
# version the index may no longer carry:
#
# [error] xlings: version '2026.8.17.1' not found for 'mcpp'
# available: 2026.8.25.1
#
# What is wanted here is the source compiled by the mcpp installed
# above, which is exactly what removing the file leaves.
rm -f "$src/.xlings.json"
( cd "$src" && mcpp build --release )
# ⚠️ BOTH SPELLINGS, AND NO `-perm`. The matrix reaches Windows and
# macOS runners too: on Windows the artefact is `mcpp.exe`, and
# `-perm -u+x` is not a question that filesystem answers the way this
# expects. Measured: `Finished release [optimized] in 173.44s`
# followed by "mcpp did not build" — the build had succeeded and the
# search was looking for the wrong name.
#
# `$src` is a FRESH clone each run, so `target/` holds exactly what
# this step just built; `-printf` would be the safer form on a cached
# tree and is a GNU extension this must not use.
built=$(find "$src/target" -type f \
\( -name mcpp -o -name mcpp.exe \) | head -1)
[ -n "$built" ] || { echo "::error::mcpp did not build from $MCPP_SOURCE_REF"; exit 1; }
echo "$(cd "$(dirname "$built")" && pwd)" >> "$GITHUB_PATH"
# ⚠️ Reported, because a PATH entry that does not win looks exactly
# like one that does until something built with the wrong engine.
echo "under review: $("$built" --version) (from $MCPP_SOURCE_REF)"
fi
# ⚠️⚠️ A JOB THAT RESOLVES THE PUBLISHED SPECIFICATION CANNOT REVIEW A
# CHANGE TO ONE.
#
# Another step in this file already clones the specification and
# substitutes it, and that made the arrangement look complete. It was not:
# the steps below reached `openkal' BY VERSION, so a version under review
# --- which by definition is not published --- failed them with
#
# E_NOT_FOUND: package 'compat.openkal@0.9.0' not found in the
# synced index ... the index is current, so this name is either
# wrong or not published yet
#
# ⭐⭐ THE UNIT IS THE JOB, NOT THE REPOSITORY. Measured 2026-08-28 across
# the eight repositories of this ecosystem while one change spanned all of
# them: seven jobs in three repositories had this shape, and every one of
# those repositories ALSO had a job that substituted correctly --- which is
# what made the gap invisible to a check done a repository at a time.
#
# These jobs are green on `main` and can only be green there, because
# there the published version is the one under test. That is the property
# that makes the defect silent: it is not a check that fails, it is a
# check that cannot run at the only time it would have something to say.
- name: Point at the specification's working tree
run: |
set -euo pipefail
git clone --quiet https://github.com/mcpplibs/openkal .spec
if git -C .spec rev-parse --verify --quiet "origin/$OPENKAL_BRANCH" > /dev/null; then
git -C .spec checkout --quiet "origin/$OPENKAL_BRANCH"
echo "the specification is at $OPENKAL_BRANCH"
else
echo "the specification has no $OPENKAL_BRANCH; its default branch is used"
fi
# ⚠️ EVERY MANIFEST THAT NAMES IT, AND BY THE SAME FORM. mcpp refuses
# a graph in which one package reaches a dependency by version and
# another by path, so substituting only the root leaves the build
# refusing for a second reason instead of the first.
# ⚠️ NOT `sed -i'. This step runs on macOS and on Windows too, and
# BSD sed requires an argument to -i that GNU sed refuses. A temporary
# file is the spelling that holds on all three.
subst() { # subst <file> <relative-path-to-the-specification>
sed "s|^openkal = .*$|openkal = { path = \"$2\" }|" "$1" > "$1.next"
mv "$1.next" "$1"
grep -q "path = \"$2\"" "$1" \
|| { echo "::error::$1 was not substituted"; exit 1; }
}
subst mcpp.toml .spec
subst examples/hello/mcpp.toml ../../.spec
- name: Install OVMF and qemu
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends ovmf qemu-system-x86
- name: The example is a real EFI application
run: |
set -euo pipefail
cd examples/hello
mcpp build
EXE=$(find target -name "hello.exe" | head -1)
# ⚠️ THE TOOLCHAIN THAT BUILT THE IMAGE, NOT LLVM.
#
# This target resolves to mingw-cross-gcc, so on a machine that has
# never built anything else there is no LLVM to borrow `llvm-readobj`
# from — the previous version of this step globbed for one and died on
# `ls: cannot access .../xim-x-llvm/*/bin/llvm-readobj`, AFTER the
# build had succeeded. The verification tool has to come from the same
# place as the compiler, or the check is only available on machines
# that happen to have another one.
OD=$(ls -d "$HOME"/.mcpp/registry/data/xpkgs/xim-x-mingw-cross-gcc/*/bin/x86_64-w64-mingw32-objdump | head -1)
test -x "$OD"
# ⚠️ Subsystem 10 is what makes firmware load it. A Windows console
# subsystem would build, link, and never be executed by UEFI.
# Measured spelling from this objdump: `Subsystem 0000000a (EFI application)`.
"$OD" -p "$EXE" | grep -qE 'Subsystem[[:space:]]+0000000a'
# And no import table: a DLL dependency cannot be satisfied by firmware.
if "$OD" -p "$EXE" | grep -qi "DLL Name"; then
"$OD" -p "$EXE" | grep -i -A2 "DLL Name"; echo "unexpected DLL import"; exit 1
fi
- name: It boots under OVMF and prints
run: |
set -euo pipefail
cd examples/hello
EXE=$(find target -name "hello.exe" | head -1)
mkdir -p esp/EFI/BOOT && cp "$EXE" esp/EFI/BOOT/BOOTX64.EFI
cp /usr/share/OVMF/OVMF_VARS_4M.fd vars.fd
timeout 90 qemu-system-x86_64 -machine q35 -m 256 -display none \
-serial file:uefi.log \
-drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \
-drive if=pflash,format=raw,file=vars.fd \
-drive format=raw,file=fat:rw:esp -net none || true
cat uefi.log | tr -d "\\000" | head -40
grep -q "hello from openkal over UEFI" uefi.log
grep -q "pool ok" uefi.log
# ---------------------------------------------------------------------------
# The cross-compilation is performed FROM three systems, not only from Linux.
#
# Every target in this repository is a cross target, which makes the host a
# separate axis from the target: the compiler, the target C library and the
# emulator are payloads mcpp resolves for whichever system it is running on.
# A package that has only ever been built from Linux is a package whose
# consumers must use Linux, and nothing in these sources says so.
#
# ⚠️ A TOOLCHAIN AXIS IS ABSENT HERE, AND THAT IS MEASURED RATHER THAN
# ASSUMED. The row for a bare-metal triple names its compiler, and the
# command-line override does not displace it: `--toolchain gcc@16.1.0` on a
# `riscv64-none-elf` build resolves llvm@22.1.8 regardless. A matrix over
# compiler families would therefore run the same compiler on every row and
# report coverage it does not have. The toolchain axis belongs where the
# choice is real — openkal, whose declarations are compiled by three families
# on three systems.
#
# ⚠️ BUILD ONLY, AND DELIBERATELY. Behaviour is asserted once, above, under an
# emulator. Booting the same image from three systems would be a statement
# about the emulator rather than about this package, and "the image does what
# the README says" does not become more true for having been observed from
# macOS.
portability:
name: cross-builds from ${{ matrix.os }}
runs-on: ${{ matrix.os }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
os: [macos-14, windows-2022]
defaults:
run:
shell: bash
env:
# The specification is taken from the branch of the same name where one
# exists, so a change spanning both repositories is reviewed as a whole.
OPENKAL_BRANCH: ${{ github.head_ref || github.ref_name }}
MCPP_VERSION: 2026.8.27.1
XLINGS_VERSION: v2026.8.17.2
XLINGS_NON_INTERACTIVE: '1'
steps:
- uses: actions/checkout@v4
- name: Install xlings (Unix)
if: runner.os != 'Windows'
run: |
curl -fsSL 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
# The installer amends the user's environment; no later step in this
# job reads it back, so the directory is named here.
"$env:USERPROFILE\.xlings\subos\current\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
- name: Install mcpp
run: |
# ⚠️ A LOOP, BECAUSE ONE `xlings update` CAN RETURN A STALE INDEX
# WITHOUT SAYING SO.
#
# The index is published as an artifact behind a pointer, and that
# pointer propagates asynchronously after a version bump is merged.
# Measured on release day: an update run four minutes after the merge
# printed `index updated`, and the install then failed with
#
# package 'mcpp@<ver>' not found in the synced index
# (xim@artifact:<an older sha>, ...), synced 0 seconds ago
#
# Nothing had gone wrong. The update fetched the PREVIOUS artifact,
# and "synced 0 seconds ago" describes when it was fetched rather than
# what it contains — which is why the message reads as freshness.
#
# So this is not a retry around flakiness; it is the wait that a
# single update does not perform. A pin naming a version that was
# never published still fails, after the last attempt, and says which
# of the two situations it is.
for attempt in 1 2 3 4 5 6; do
xlings update > /dev/null 2>&1 || true
if # ⚠️ THE PIN MAY NAME THE RELEASE THIS RUN IS VALIDATING, which does
# not exist yet — that is the whole point of MCPP_SOURCE_REF. Bootstrap
# from whatever the index has; the step below replaces it with the
# build under review, and the pin is what an ordinary run tests.
if [ -n "${MCPP_SOURCE_REF:-}" ]; then
xlings install mcpp -y -g
else
xlings install "mcpp@$MCPP_VERSION" -y -g
fi; then break; fi
if [ "$attempt" = 6 ]; then
echo "::error::mcpp@$MCPP_VERSION never appeared in the index (6 attempts over 5 minutes). If it was just released, the pointer has not propagated; if the pin names a version that was never published, it never will."
exit 1
fi
echo "the index has not caught up yet (attempt $attempt of 6); waiting 60s"
sleep 60
done
# ⚠️⚠️ TRANSITION: GIVE THE BOOTSTRAP THE glibc ITS BINDING NAMES.
#
# `xim:glibc`'s `latest` moved from `2.44` to `2.44.2`. A payload
# directory is named after the version a request RESOLVED to, while a
# RuntimeBinding carries the version that was DECLARED — and the xlings
# a released mcpp vendors into its own sandbox still declares `2.44`.
# So a clean machine installs `2.44.2`, the toolchain fixup asks for
# `2.44`, and the build stops before anything is compiled:
#
# error: selected RuntimeBinding glibc@2.44 requires payload
# '…/xpkgs/xim-x-glibc/2.44', but it is not installed
#
# ⚠️ On every NEW machine and on none that already existed, which is why
# it is invisible from a developer's own. Measured on `main` as readily
# as on any branch — the index records the same failure verbatim in
# `pkgs/g/glibc.lua` and states the rule it broke: "The index is DATA
# and the client is a PROGRAM: the consumer ships first."
#
# ⭐ REMOVE THIS once a released mcpp resolves it. `mcpp 2026.8.27.1`
# accepts an installed payload whose version REFINES the requested one
# (`payload_dir_for_version`), so a bootstrap from it needs nothing
# here. Until then the missing payload is simply installed.
if [ -x "$HOME/.mcpp/registry/bin/xlings" ]; then
XLINGS_HOME="$HOME/.mcpp/registry" XLINGS_NON_INTERACTIVE=1 \
"$HOME/.mcpp/registry/bin/xlings" install glibc@2.44 -y -g \
>/dev/null 2>&1 || true
echo "glibc payloads present: $(ls "$HOME/.mcpp/registry/data/xpkgs/xim-x-glibc" 2>/dev/null | tr '\n' ' ')"
fi
mcpp --version
mcpp self config --mirror GLOBAL
# ⭐⭐ CROSS-VALIDATION: BUILD THE mcpp UNDER REVIEW AND USE THAT ONE.
#
# Empty in the ordinary run, so this job keeps testing the RELEASED
# mcpp the pin above names. Set it — `workflow_dispatch` input, or the
# repository variable — and the same job runs against that source.
#
# ⚠️ THIS EXISTS BECAUSE THE ORDER USED TO BE WRONG. Several mcpp
# releases went out green and only then turned this ecosystem red: the
# engine's own CI cannot see a defect that appears only in a real
# dependency graph, and this repository could not see the engine until
# it had been published. Validating before the release closes that gap.
#
# The released mcpp installed just above is the bootstrap that compiles
# it; mcpp builds itself and there is no other compiler for it here.
if [ -n "${MCPP_SOURCE_REF:-}" ]; then
src="$RUNNER_TEMP/mcpp-src"
[ -d "$src" ] || git clone --quiet --depth 1 \
--branch "$MCPP_SOURCE_REF" \
https://github.com/mcpp-community/mcpp.git "$src"
# ⚠️ THE CLONE'S OWN WORKSPACE PIN MUST NOT DECIDE WHICH mcpp
# BUILDS IT. `.xlings.json` at mcpp's root pins the mcpp that
# compiles mcpp, and that pin does not move when mcpp is released —
# so a build inside the checkout obeys it and tries to install a
# version the index may no longer carry:
#
# [error] xlings: version '2026.8.17.1' not found for 'mcpp'
# available: 2026.8.25.1
#
# What is wanted here is the source compiled by the mcpp installed
# above, which is exactly what removing the file leaves.
rm -f "$src/.xlings.json"
( cd "$src" && mcpp build --release )
# ⚠️ BOTH SPELLINGS, AND NO `-perm`. The matrix reaches Windows and
# macOS runners too: on Windows the artefact is `mcpp.exe`, and
# `-perm -u+x` is not a question that filesystem answers the way this
# expects. Measured: `Finished release [optimized] in 173.44s`
# followed by "mcpp did not build" — the build had succeeded and the
# search was looking for the wrong name.
#
# `$src` is a FRESH clone each run, so `target/` holds exactly what
# this step just built; `-printf` would be the safer form on a cached
# tree and is a GNU extension this must not use.
built=$(find "$src/target" -type f \
\( -name mcpp -o -name mcpp.exe \) | head -1)
[ -n "$built" ] || { echo "::error::mcpp did not build from $MCPP_SOURCE_REF"; exit 1; }
echo "$(cd "$(dirname "$built")" && pwd)" >> "$GITHUB_PATH"
# ⚠️ Reported, because a PATH entry that does not win looks exactly
# like one that does until something built with the wrong engine.
echo "under review: $("$built" --version) (from $MCPP_SOURCE_REF)"
fi
# ⚠️⚠️ A JOB THAT RESOLVES THE PUBLISHED SPECIFICATION CANNOT REVIEW A
# CHANGE TO ONE.
#
# Another step in this file already clones the specification and
# substitutes it, and that made the arrangement look complete. It was not:
# the steps below reached `openkal' BY VERSION, so a version under review
# --- which by definition is not published --- failed them with
#
# E_NOT_FOUND: package 'compat.openkal@0.9.0' not found in the
# synced index ... the index is current, so this name is either
# wrong or not published yet
#
# ⭐⭐ THE UNIT IS THE JOB, NOT THE REPOSITORY. Measured 2026-08-28 across
# the eight repositories of this ecosystem while one change spanned all of
# them: seven jobs in three repositories had this shape, and every one of
# those repositories ALSO had a job that substituted correctly --- which is
# what made the gap invisible to a check done a repository at a time.
#
# These jobs are green on `main` and can only be green there, because
# there the published version is the one under test. That is the property
# that makes the defect silent: it is not a check that fails, it is a
# check that cannot run at the only time it would have something to say.
- name: Point at the specification's working tree
run: |
set -euo pipefail
git clone --quiet https://github.com/mcpplibs/openkal .spec
if git -C .spec rev-parse --verify --quiet "origin/$OPENKAL_BRANCH" > /dev/null; then
git -C .spec checkout --quiet "origin/$OPENKAL_BRANCH"
echo "the specification is at $OPENKAL_BRANCH"
else
echo "the specification has no $OPENKAL_BRANCH; its default branch is used"
fi
# ⚠️ EVERY MANIFEST THAT NAMES IT, AND BY THE SAME FORM. mcpp refuses
# a graph in which one package reaches a dependency by version and
# another by path, so substituting only the root leaves the build
# refusing for a second reason instead of the first.
# ⚠️ NOT `sed -i'. This step runs on macOS and on Windows too, and
# BSD sed requires an argument to -i that GNU sed refuses. A temporary
# file is the spelling that holds on all three.
subst() { # subst <file> <relative-path-to-the-specification>
sed "s|^openkal = .*$|openkal = { path = \"$2\" }|" "$1" > "$1.next"
mv "$1.next" "$1"
grep -q "path = \"$2\"" "$1" \
|| { echo "::error::$1 was not substituted"; exit 1; }
}
subst mcpp.toml .spec
- name: The EFI application cross-builds
run: |
# ⚠️ TWICE, AND THE FIRST IS ALLOWED TO FAIL — every row of this
# matrix is a machine that has never targeted this triple, which is
# precisely where mcpp's lazy install of the target C library shows.
# The first build compiles sources needing its headers before they
# exist; the second has them. A single build here reported a
# portability failure that was really a cold-machine one.
mcpp build || true
mcpp build