Skip to content

本包从哪儿编,与本包发布什么,现在是两件事 #107

本包从哪儿编,与本包发布什么,现在是两件事

本包从哪儿编,与本包发布什么,现在是两件事 #107

Workflow file for this run

name: CI
# What this workflow asserts.
#
# The claim this package makes is that a C library ported once onto openkal runs
# above every implementation of the specification, so the claim is tested by
# running the same two programs on three systems and comparing what they produce
# against what the system's own tools produce.
#
# posix a program that opens a file by an absolute name, reads a
# variable, measures an interval, starts another program and waits
# for it --- thirty-two observations, none of which the program
# contains an implementation of
# wordcount the same counts as the system's own `wc'
#
# Two compiler families, gcc and llvm. The third of the three mcpp offers is
# absent from this package and the reason is recorded in the manifest: musl's
# four remaining assembly definitions are in an object format that toolchain
# does not assemble, so the question that toolchain would answer is not one this
# package can ask.
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.26.2
XLINGS_VERSION: v2026.8.17.2
XLINGS_NON_INTERACTIVE: '1'
jobs:
programs:
name: ${{ matrix.name }}
runs-on: ${{ matrix.os }}
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
include:
- { name: 'linux, gcc', os: ubuntu-24.04, toolchain: 'gcc@16.1.0', target: '' }
- { name: 'linux, llvm', os: ubuntu-24.04, toolchain: 'llvm@22.1.8', target: '' }
- { name: 'macos, llvm', os: macos-14, toolchain: 'llvm@20.1.7', target: '' }
- { name: 'windows, gcc', os: windows-2022, toolchain: 'gcc@16.1.0', target: 'x86_64-windows-gnu' }
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
- name: The specification and the implementation for this system
run: |
bash tools/working-trees.sh '${{ github.head_ref || github.ref_name }}' \
'${{ matrix.target }}'
- 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
"$env:USERPROFILE\.xlings\subos\current\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
- name: Install mcpp
run: |
xlings update
# ⚠️ 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
# ⚠️⚠️ 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
- name: Select the toolchain
run: |
spec='${{ matrix.toolchain }}'
mcpp toolchain install "${spec%@*}" "${spec#*@}"
mcpp toolchain default "$spec"
mcpp toolchain list
- name: The library builds
run: |
extra=''
[ -n '${{ matrix.target }}' ] && extra='--target ${{ matrix.target }}'
mcpp build $extra
# ⭐ AN OPTIONAL INTERFACE IS REFERENCED WEAKLY, WHICH IS A PROPERTY OF
# THE OBJECT AND NOT OF ANY ONE LINK.
#
# `openkal.random` is optional, and clause 6.1 expresses an
# implementation that does not provide it as the absence of a link-time
# definition. This dispatcher is linked into every program, so a strong
# reference to `kal_random_fill` makes an optional interface mandatory:
# every program over a backend without it fails to link, whether or not
# it wanted a random byte.
#
# ⚠️ THAT DEFECT SHIPPED, AND NOTHING HERE SAW IT. It was found one
# repository downstream, by a bare-metal program in openkal-llvm-runtime
# linking over openkal-opensbi:
#
# ld.lld: error: undefined symbol: kal_random_fill
#
# Reproducing it here would need a bare-metal toolchain and a backend
# that declines the interface. The symbol class needs neither, and states
# the invariant more directly than a link would: `w` for the optional
# one, `U` for a required one beside it.
- name: An optional interface is referenced weakly
if: runner.os == 'Linux'
run: |
obj="$(find target -name okm_syscall.o | head -1)"
test -n "$obj" || { echo "::error::okm_syscall.o was not built"; exit 1; }
nm="$(command -v llvm-nm || command -v nm)"
"$nm" "$obj" > syms.txt
# The control: a required interface must still be a strong reference,
# so that a backend failing to provide one is still a link error.
grep -qE '^ *U kal_time_sleep$' syms.txt \
|| { echo "::error::kal_time_sleep is not an undefined strong reference — the check below proves nothing"
grep kal_time syms.txt; exit 1; }
grep -qE '^ *w kal_random_fill$' syms.txt \
|| { echo "::error::kal_random_fill is not a weak reference; an optional interface has been made mandatory"
grep kal_random syms.txt; exit 1; }
echo " ok kal_random_fill is weak, kal_time_sleep is strong"
# ⭐⭐ ASKING WHETHER A STREAM IS A TERMINAL GETS THE RIGHT ANSWER.
#
# musl's `isatty' asks with TIOCGWINSZ; this port answered only TCGETS,
# so every `isatty' returned 0 — for a real terminal as readily as for a
# pipe. Nothing failed: `std::print' simply never took its terminal path,
# and a program deciding on colour or on line buffering decided wrongly
# and in silence.
#
# ⭐ THE CRITERION IS A RELATION, NOT A VALUE. `isatty` under a pipe and
# under a pseudo-terminal must DIFFER, and must differ the same way the
# system's own C library does. A test asserting "0 in a pipe" alone would
# have passed throughout the defect.
- name: Asking whether a stream is a terminal is answered, not refused
if: runner.os == 'Linux' && matrix.target == ''
run: |
d="$(mktemp -d)"; mkdir -p "$d/src"
cat > "$d/mcpp.toml" <<TOML
[package]
name = "isattyprobe"
version = "0.1.0"
[dependencies]
openkal-musl = { path = "$PWD" }
[targets.isattyprobe]
kind = "bin"
main = "src/main.c"
[build]
cxx_runtime = "host-coupled"
TOML
sed -i 's/^ //' "$d/mcpp.toml"
printf '#include <unistd.h>\n#include <stdio.h>\nint main(void){ printf("%%d\\n", isatty(1)); return 0; }\n' > "$d/src/main.c"
( cd "$d" && mcpp build --toolchain '${{ matrix.toolchain }}' )
bin="$(find "$d/target" -name isattyprobe -type f | 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
# port look broken.
printf '#include <unistd.h>\n#include <stdio.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) — this check would prove nothing"
exit 1; }
port_pipe="$("$bin" | cat | tr -d '\r')"
port_tty="$(script -qec "$bin" /dev/null | tr -d '\r' | head -1)"
echo " control: pipe=$ctrl_pipe tty=$ctrl_tty port: pipe=$port_pipe tty=$port_tty"
[ "$port_pipe" = "$ctrl_pipe" ] && [ "$port_tty" = "$ctrl_tty" ] \
|| { echo "::error::isatty over this port disagrees with the system's own C library"; exit 1; }
echo " ok isatty answers the same as the system's own C library"
# ⭐⭐ THE INTERNAL OVERLAY STOPS AT THIS PACKAGE'S BOUNDARY.
#
# musl reaches its own declarations through `src/include`, whose headers
# define `hidden`, `weak` and `weak_alias` — names that mean something
# only to musl's own sources. This package publishes the path it is built
# from, so every consumer used to see them too, and which consumer broke
# on which name was found one at a time (openkal-musl#13).
#
# `[build] private_include_dirs` (mcpp 2026.8.27.1) says which entries of
# `include_dirs` stop here. This asserts the DIRECTORY is absent from a
# consumer's command line — not that one macro no longer collides, which
# would go green again the moment the package patched that macro while
# the leak stayed.
- name: What this package is built from is not what it publishes
working-directory: examples/cross-hello
run: |
extra=''
[ -n '${{ matrix.target }}' ] && extra='--target ${{ matrix.target }}'
mcpp build --toolchain '${{ matrix.toolchain }}' $extra
test -s compile_commands.json \
|| { echo "::error::no compile_commands.json — nothing to check"; exit 1; }
# ⚠️⚠️ PER ROW, NOT OVER THE FILE. This example depends on the
# package by path, so THE PROVIDER'S OWN ROWS ARE IN THIS SAME FILE
# and they carry the overlay legitimately — that is what "private,
# not unused" means. A `grep` over the whole file cannot tell the two
# apart and would call the package's own build a leak.
#
# ⚠️ Separators are normalised because the Windows runner writes
# `…\musl\src\include`, and a check that silently stops matching on
# one platform is a check that platform does not have.
norm='(.arguments // (.command | split(" "))) | join(" ") | gsub("\\\\"; "/")'
jq -r ".[] | select((.file | gsub(\"\\\\\\\\\"; \"/\")) | test(\"examples/cross-hello\")) | $norm" \
compile_commands.json > consumer.txt
jq -r ".[] | select((.file | gsub(\"\\\\\\\\\"; \"/\")) | test(\"examples/cross-hello\") | not) | $norm" \
compile_commands.json > provider.txt
# ⚠️ DENOMINATORS ON BOTH SIDES. With no consumer row every absence
# below is vacuously true; with no provider row the control is.
cons="$(wc -l < consumer.txt)"; prov="$(wc -l < provider.txt)"
echo " rows: consumer=$cons provider=$prov"
[ "$cons" -ge 1 ] && [ "$prov" -ge 1 ] \
|| { echo "::error::consumer=$cons provider=$prov — nothing was checked"; exit 1; }
bad=0
for d in musl/src/include musl/src/internal musl-generated/internal; do
if grep -q -- "$d" consumer.txt; then
echo "::error::the internal overlay '$d' reached a consumer"
bad=1
fi
# The other half of the same key: private is not the same as
# dropped. musl's own sources must still reach their declarations.
grep -q -- "$d" provider.txt \
|| { echo "::error::'$d' is on nobody's command line — private_include_dirs withheld it from this package too"
bad=1; }
done
# And something PUBLIC must still cross the boundary, or this check
# would pass for a build that published nothing at all.
grep -q -- 'port/include' consumer.txt \
|| { echo "::error::no public include directory reached the consumer — the check above proves nothing"
exit 1; }
[ "$bad" = 0 ] || exit 1
echo " ok the internal overlay stops here; the public headers do not"
# A program above this package names one package. It does not name
# openkal, it does not name an implementation, and it says nothing about
# the platform.
- name: The posix probe
working-directory: examples/posix
run: |
extra=''
[ -n '${{ matrix.target }}' ] && extra='--target ${{ matrix.target }}'
mcpp build $extra
binary="$(find target -type f \( -name 'posix' -o -name 'posix.exe' \) | head -1)"
# A watchdog, because a program that does not return is as much a
# failure as one that returns wrongly, and the job would otherwise
# spend its whole timeout finding that out. Written out rather than
# taken from `timeout', which two of the three systems have and one
# does not.
watch() { # watch <seconds> <command>...
local seconds="$1"; shift
"$@" & local pid=$!
( sleep "$seconds"; kill -9 "$pid" 2> /dev/null ) & local guard=$!
wait "$pid"; local status=$?
kill "$guard" 2> /dev/null || true
return $status
}
if watch 120 sh -c "\"$binary\" > run.log 2>&1"; then
cat run.log
else
status=$?
echo "--- what the program printed before it stopped (status $status) ---"
cat run.log
# A program that stopped and a program that did not return need
# different questions asked of them, and "exit code 139" and "the
# job timed out" answer neither. The debugger is for the first; a
# stack sample of a program that is still running is for the second,
# and a debugger asked to run a program that hangs hangs with it.
if [ "$status" -eq 137 ]; then
echo "--- it did not return; where it was ---"
"$binary" > /dev/null 2>&1 & hung=$!
sleep 5
if command -v sample > /dev/null 2>&1; then
sample "$hung" 3 -mayDie 2>&1 | head -80 || true
elif command -v eu-stack > /dev/null 2>&1; then
eu-stack -p "$hung" 2>&1 | head -60 || true
fi
kill -9 "$hung" 2> /dev/null || true
elif command -v lldb > /dev/null 2>&1; then
watch 90 lldb --batch -o run \
-k 'thread backtrace all' -k 'register read' -k quit \
-- "$binary" > crash.log 2>&1 || true
cat crash.log
elif command -v gdb > /dev/null 2>&1; then
watch 90 gdb -batch -ex run -ex 'bt' --args "$binary" > crash.log 2>&1 || true
cat crash.log
fi
exit 1
fi
# Both directions: that the program reported, and that nothing it
# observed failed to hold. Asserting only the first would pass for a
# program that printed its failures.
grep -qE '^-- failures: 0 --$' run.log
! grep -q '^FAIL:' run.log
- name: The same program, built the ordinary way, as a control
run: |
# ⚠️ THIS STEP EXISTS BECAUSE IT ALREADY EARNED ITS PLACE.
#
# The cross-built copy failed on a machine of the other system, and the
# obvious reading was a defect in this port or in the way it was
# cross-built. A native build of the SAME source reproduced it here ---
# which said it was neither, and the defect was in the program's own
# assertion macro, which evaluated its condition twice and so closed a
# stream that was already closed.
#
# Two failures that look alike are told apart by building the same
# source two ways, and that is cheap enough to do every time.
# ⚠️ The target has to be named here as it is everywhere else in this
# job: one system needs it, and a step that omitted it built for that
# system's other toolchain and failed at the link with names kernel32
# supplies. Measured, on the first run of this step.
extra=''
[ -n '${{ matrix.target }}' ] && extra='--target ${{ matrix.target }}'
cd examples/cross-hello
mcpp build $extra
binary="$(find target -type f \( -name 'cross-hello' -o -name 'cross-hello.exe' \) | head -1)"
"./$binary" 2>&1 | tee out.log
grep -q -- '-- failures: 0 --' out.log
- name: The counts agree with the system's own
working-directory: examples/wordcount
run: |
printf 'alpha beta\ngamma\n' > sample.txt
expected="$(wc < sample.txt | tr -s ' ' | sed 's/^ //')"
extra=''
[ -n '${{ matrix.target }}' ] && extra='--target ${{ matrix.target }}'
mcpp build $extra
binary="$(find target -type f \( -name 'wordcount' -o -name 'wordcount.exe' \) | head -1)"
WORDCOUNT_VERBOSE=1 "$binary" sample.txt 2>&1 | tee run.log
counts="$(sed -n 's/^lines \([0-9]*\) words \([0-9]*\) bytes \([0-9]*\)$/\1 \2 \3/p' run.log)"
test -n "$counts" || { echo "the program printed no count" >&2; exit 1; }
echo "wordcount: $counts"
echo "wc: $expected"
test "$counts" = "$expected"
# The variable was consulted, and the interval was measured.
grep -q 'elapsed .* nanoseconds' run.log
# ---------------------------------------------------------------------------
# The measurement musl/PATCHES.md records, made from a machine that is not the
# system it is about.
#
# It is a job rather than a note because the claim it supports --- that a
# program for that system can be built, and linked, without any file that
# system supplies --- is one that a change to this port could silently undo.
# The two numbers it asserts are zero indirect symbols and exactly two names.
cross-macos:
name: cross-link for the other system, from this one
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: The implementation for that system
run: |
branch='${{ github.head_ref || github.ref_name }}'
git clone --quiet https://github.com/mcpplibs/openkal-macos.git ../openkal-macos
git -C ../openkal-macos checkout --quiet "origin/$branch" 2>/dev/null || true
git clone --quiet https://github.com/mcpplibs/openkal.git ../openkal
git -C ../openkal checkout --quiet "origin/$branch" 2>/dev/null || true
- name: Install LLVM
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq clang lld llvm
# ld64.lld is the Mach-O driver of the same binary; Ubuntu's package
# installs it under a version suffix and without the plain name.
v=$(ls /usr/lib/llvm-*/bin/ld64.lld 2>/dev/null | sort -V | tail -1)
test -n "$v" || { echo "::error::this image has no ld64.lld"; exit 1; }
d=$(dirname "$v")
echo "LD64=$v" >> "$GITHUB_ENV"
# The tools are installed under a version suffix and the probe asks for
# them by their plain names, so the directory that has both spellings
# is named rather than assumed.
echo "NM=$d/llvm-nm" >> "$GITHUB_ENV"
echo "$d" >> "$GITHUB_PATH"
"$d/llvm-nm" --version | head -1
clang --version | head -1
- name: Zero indirect symbols, and two names
run: |
bash tools/probe-cross-macos.sh aarch64
bash tools/probe-cross-macos.sh x86_64
# ⚠️ LINKING IS NOT RUNNING, AND THIS IS WHERE THAT IS ADMITTED.
#
# Everything above happens on a machine that is not the system in
# question, and everything above is about the objects and the link. Whether
# the result STARTS is a different question, and it cannot be asked here:
# that system's 64-bit ARM variant refuses to start an image that is not
# signed, and the signature can only be made on a machine of that system.
#
# So the artifact is carried there. The job that receives it is the only
# place in this workflow where the claim becomes "a program built here
# runs there" rather than "a program built here links".
- name: A program for that system, built here
run: |
bash tools/cross-build-macos.sh aarch64 examples/cross-hello/src/main.c cross-hello-arm64
file cross-hello-arm64
- uses: actions/upload-artifact@v4
with:
name: cross-hello-arm64
path: cross-hello-arm64
retention-days: 1
# ---------------------------------------------------------------------------
# The other half of the claim, on a machine of that system.
cross-macos-run:
name: what was cross-built starts, on that system
needs: cross-macos
runs-on: macos-14
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: cross-hello-arm64
- name: Sign it, because this system will not start it otherwise
run: |
chmod +x cross-hello-arm64
# Ad hoc: the identity is `-', which asserts nothing about who made
# the program and is exactly what this system requires before it will
# start one on this architecture. A program that is distributed needs
# more; a program that is checked needs this.
codesign -s - --force cross-hello-arm64
codesign -dv cross-hello-arm64 2>&1 | head -3
- name: It starts, and the C library beneath it came up
run: |
./cross-hello-arm64 | tee run.log
grep -q -- '-- failures: 0 --' run.log