Skip to content

ci: 引导用的 mcpp 自带一份 xlings,而那份早于一次索引移动 #43

ci: 引导用的 mcpp 自带一份 xlings,而那份早于一次索引移动

ci: 引导用的 mcpp 自带一份 xlings,而那份早于一次索引移动 #43

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:
MCPP_VERSION: 2026.8.26.2
XLINGS_VERSION: v2026.8.27.1
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
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
- 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:
MCPP_VERSION: 2026.8.26.2
XLINGS_VERSION: v2026.8.27.1
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
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
- 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