Skip to content

2026.9.8.1: a package's host modules are ordered by what they import, and the design document aligned with what shipped #2013

2026.9.8.1: a package's host modules are ordered by what they import, and the design document aligned with what shipped

2026.9.8.1: a package's host modules are ordered by what they import, and the design document aligned with what shipped #2013

Workflow file for this run

name: ci-macos
# macOS CI for mcpp — validates LLVM/Clang as the default macOS toolchain.
# Tests the full xlings → LLVM → C++23 import std pipeline on macOS ARM64.
#
# The e2e suite runs in PARALLEL in ci-macos-e2e.yml (same setup via
# .github/actions/setup-macos-llvm), not tacked onto this job.
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
workflow_dispatch:
concurrency:
group: ci-macos-${{ github.ref }}
cancel-in-progress: true
jobs:
macos-xlings-llvm:
name: macOS ARM64 — xlings LLVM end-to-end
runs-on: macos-15
timeout-minutes: 30
# NOTE: no MCPP_VERBOSE here — keep this job's output shape identical to
# ci-macos-e2e.yml, which asserts mcpp's default quiet output (48/53).
steps:
- uses: actions/checkout@v4
- name: System info
run: |
uname -a
sw_vers
xcrun --show-sdk-path
echo "SDK: $(xcrun --show-sdk-version)"
- uses: ./.github/actions/setup-macos-llvm
- name: Inspect LLVM package structure
run: |
echo "=== bin/ ==="
ls "$LLVM_ROOT/bin/" | grep -E "^(clang|llvm|lld|ld)" | head -20
echo "=== lib/ ==="
ls "$LLVM_ROOT/lib/" 2>/dev/null | head -10
echo "=== share/libc++/ ==="
find "$LLVM_ROOT" -name "std.cppm" -o -name "std.compat.cppm" 2>/dev/null
echo "=== clang++.cfg ==="
cat "$LLVM_ROOT/bin/clang++.cfg" 2>/dev/null || echo "(no cfg file)"
echo "=== Target triple ==="
"$CXX" -dumpmachine
echo "=== Module manifest ==="
"$CXX" -print-library-module-manifest-path 2>/dev/null || echo "(not available)"
- name: Test — non-module C++23 compilation
run: |
WORK=$(mktemp -d)
cd "$WORK"
cat > main.cpp << 'EOF'
#include <iostream>
#include <format>
int main() {
std::cout << std::format("Hello from LLVM on macOS! clang {}", __clang_version__) << std::endl;
return 0;
}
EOF
"$CXX" -std=c++23 -o hello main.cpp
./hello
- name: Test — import std (two-stage module compilation)
run: |
WORK=$(mktemp -d)
cd "$WORK"
# Find std.cppm
STD_CPPM=$(find "$LLVM_ROOT" -name "std.cppm" -path "*/libc++/*" | head -1)
if [ -z "$STD_CPPM" ]; then
echo "::error::std.cppm not found in LLVM package"
find "$LLVM_ROOT" -name "*.cppm" 2>/dev/null
exit 1
fi
echo "std.cppm at: $STD_CPPM"
echo "=== Step 1: Precompile std module ==="
mkdir -p pcm.cache
"$CXX" -std=c++23 -Wno-reserved-module-identifier \
--precompile "$STD_CPPM" -o pcm.cache/std.pcm
echo "=== Step 2: Compile std.pcm → std.o ==="
"$CXX" -std=c++23 -Wno-reserved-module-identifier \
pcm.cache/std.pcm -c -o std.o
echo "=== Step 3: Compile main.cpp with import std ==="
cat > main.cpp << 'EOF'
import std;
int main() {
std::println("C++23 import std works on macOS via xlings LLVM!");
return 0;
}
EOF
"$CXX" -std=c++23 -fmodule-file=std=pcm.cache/std.pcm -c main.cpp -o main.o
echo "=== Step 4: Link ==="
"$CXX" main.o std.o -o hello_modules
echo "=== Step 5: Run ==="
./hello_modules
- name: Test — import std.compat
run: |
WORK=$(mktemp -d)
cd "$WORK"
STD_CPPM=$(find "$LLVM_ROOT" -name "std.cppm" -path "*/libc++/*" | head -1)
STD_COMPAT_CPPM=$(find "$LLVM_ROOT" -name "std.compat.cppm" -path "*/libc++/*" | head -1)
if [ -z "$STD_COMPAT_CPPM" ]; then
echo "::warning::std.compat.cppm not found, skipping"
exit 0
fi
echo "std.compat.cppm at: $STD_COMPAT_CPPM"
mkdir -p pcm.cache
# Build std first
"$CXX" -std=c++23 -Wno-reserved-module-identifier \
--precompile "$STD_CPPM" -o pcm.cache/std.pcm
"$CXX" -std=c++23 -Wno-reserved-module-identifier \
pcm.cache/std.pcm -c -o std.o
# Build std.compat (depends on std)
"$CXX" -std=c++23 -Wno-reserved-module-identifier \
-fmodule-file=std=pcm.cache/std.pcm \
--precompile "$STD_COMPAT_CPPM" -o pcm.cache/std.compat.pcm
"$CXX" -std=c++23 -Wno-reserved-module-identifier \
-fmodule-file=std=pcm.cache/std.pcm \
pcm.cache/std.compat.pcm -c -o std.compat.o
cat > main.cpp << 'EOF'
import std.compat;
#include <cstdio>
int main() {
printf("std.compat works on macOS! %s\n", "success");
return 0;
}
EOF
"$CXX" -std=c++23 \
-fmodule-file=std=pcm.cache/std.pcm \
-fmodule-file=std.compat=pcm.cache/std.compat.pcm \
-c main.cpp -o main.o
"$CXX" main.o std.o std.compat.o -o compat_test
./compat_test
- name: Test — multi-module project
run: |
WORK=$(mktemp -d)
cd "$WORK"
STD_CPPM=$(find "$LLVM_ROOT" -name "std.cppm" -path "*/libc++/*" | head -1)
mkdir -p pcm.cache
# Build std
"$CXX" -std=c++23 -Wno-reserved-module-identifier \
--precompile "$STD_CPPM" -o pcm.cache/std.pcm
"$CXX" -std=c++23 -Wno-reserved-module-identifier \
pcm.cache/std.pcm -c -o std.o
# User module: greeter
cat > greeter.cppm << 'EOF'
export module greeter;
import std;
export namespace greeter {
std::string hello(std::string_view name) {
return std::format("Hello, {}! (from macOS module)", name);
}
}
EOF
"$CXX" -std=c++23 -fmodule-file=std=pcm.cache/std.pcm \
--precompile greeter.cppm -o pcm.cache/greeter.pcm
"$CXX" -std=c++23 -fmodule-file=std=pcm.cache/std.pcm \
pcm.cache/greeter.pcm -c -o greeter.o
# Main
cat > main.cpp << 'EOF'
import std;
import greeter;
int main() {
std::println("{}", greeter::hello("mcpp"));
return 0;
}
EOF
"$CXX" -std=c++23 \
-fmodule-file=std=pcm.cache/std.pcm \
-fmodule-file=greeter=pcm.cache/greeter.pcm \
-c main.cpp -o main.o
"$CXX" main.o greeter.o std.o -o multimod
./multimod
- name: Validate mcpp probe logic expectations
run: |
echo "=== Verifying mcpp's assumptions ==="
echo "1. -print-sysroot returns empty (mcpp falls back to xcrun):"
result=$("$CXX" -print-sysroot 2>/dev/null || true)
if [ -z "$result" ]; then
echo " PASS: empty (xcrun fallback needed)"
else
echo " INFO: $result"
fi
echo "2. xcrun --show-sdk-path works:"
xcrun --show-sdk-path && echo " PASS"
echo "3. -dumpmachine returns darwin triple:"
triple=$("$CXX" -dumpmachine)
echo " $triple"
echo "$triple" | grep -q "darwin" && echo " PASS: contains 'darwin'"
echo "4. libc++ module manifest discoverable:"
manifest=$("$CXX" -print-library-module-manifest-path 2>/dev/null || true)
if [ -n "$manifest" ] && [ -f "$manifest" ]; then
echo " PASS: $manifest"
echo " Content:"
cat "$manifest" | head -20
else
echo " INFO: manifest not via flag, using fallback path"
find "$LLVM_ROOT/share/libc++" -name "*.cppm" 2>/dev/null && echo " PASS: fallback exists"
fi
echo "5. llvm-ar available:"
ls "$LLVM_ROOT/bin/llvm-ar" && echo " PASS"
echo "6. clang-scan-deps available:"
ls "$LLVM_ROOT/bin/clang-scan-deps" && echo " PASS" || echo " WARN: not found"
- name: Validate install.sh platform detection
run: |
uname_s=$(uname -s)
uname_m=$(uname -m)
echo "Platform: ${uname_s}-${uname_m}"
case "${uname_s}-${uname_m}" in
Darwin-arm64) echo "PASS: would select darwin-arm64" ;;
Darwin-x86_64) echo "PASS: would select darwin-x86_64" ;;
*) echo "FAIL: unexpected platform"; exit 1 ;;
esac
- name: Build mcpp from source (self-host)
run: |
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
"$MCPP" build
- name: Unit + integration tests via `mcpp test`
run: |
# Use freshly-built mcpp (has --mirror support)
MCPP=$(find target -path "*/bin/mcpp" | head -1)
MCPP=$(cd "$(dirname "$MCPP")" && pwd)/$(basename "$MCPP")
"$MCPP" self config --mirror GLOBAL
"$MCPP" test
- name: Forensics — test-binary link + load state (on failure)
if: failure()
run: |
BIN=$(find target -path "*/bin/test_manifest" | head -1)
echo "binary: $BIN"
[ -n "$BIN" ] || exit 0
echo "--- otool -L ---"; otool -L "$BIN" || true
echo "--- rpaths ---"; otool -l "$BIN" | grep -A2 LC_RPATH || true
echo "--- statically embedded libc++? ---"
# `nm ... 2>/dev/null | grep -c` USED TO PRINT "0 (good)" WHEN `nm`
# ITSELF FAILED. A count of zero and a tool that never ran produce the
# same number, and the word "good" is then a claim nothing supports.
# This is diagnostic output rather than a gate, which makes it worse
# rather than better: a human reads it and believes it.
if nm "$BIN" > /tmp/nm.out 2> /tmp/nm.err; then
echo "$(grep -cE 'T __ZNSt3__1' /tmp/nm.out) libc++ symbols (0 = none embedded)"
else
echo "nm failed, so this measurement says nothing: $(head -1 /tmp/nm.err)"
fi
echo "--- direct run ---"
set +e
"$BIN" > run.out 2>&1
echo "exit=$?"
head -20 run.out
sleep 5
echo "--- newest crash report (termination) ---"
CR=$(ls -t "$HOME/Library/Logs/DiagnosticReports"/*.ips 2>/dev/null | head -1)
if [ -n "$CR" ]; then
python3 - "$CR" <<'PY'
import json, sys
lines = open(sys.argv[1]).read().splitlines()
meta = json.loads(lines[0]); body = json.loads("\n".join(lines[1:]))
print("proc:", meta.get("app_name"), "| exc:", body.get("exception", {}))
print("termination:", body.get("termination", {}))
t = [th for th in body.get("threads", []) if th.get("triggered")]
for fr in (t[0].get("frames", [])[:12] if t else []):
print(" ", fr.get("imageIndex"), fr.get("symbol", fr.get("imageOffset")))
imgs = body.get("usedImages", [])
for i, im in enumerate(imgs[:12]):
print("img", i, im.get("path"))
PY
else
echo "none"
fi
- name: "Toolchain: LLVM — build mcpp (self-host)"
run: |
MCPP=$(find target -path "*/bin/mcpp" | head -1)
MCPP=$(cd "$(dirname "$MCPP")" && pwd)/$(basename "$MCPP")
test -x "$MCPP"
cp "$MCPP" /tmp/mcpp-fresh
MCPP=/tmp/mcpp-fresh
"$MCPP" toolchain default "llvm@${MCPP_LLVM_VER}"
"$MCPP" clean
"$MCPP" build
"$MCPP" --version
# GRAPHICS ON THIS HOST, BUILD ONLY, AND THAT IS THE WHOLE CLAIM.
#
# This runner has no Vulkan device, so what is asserted is what this
# platform decides: that the shader compiler THIS platform uses -- the
# rule declares `xim:shaderc` here and `xim:glslang` on Linux -- produces
# both SPIR-V headers, and that the Vulkan half compiles and links
# against the loader package. Running it is the Linux job's criterion,
# where a software device (`xim:mesa-lavapipe`) is published and the two
# legs' pixels are compared.
#
# The example is otherwise built only on Linux (`build_examples.sh` runs
# there), which is exactly the shape this change exists to remove: the
# half of a lane written for a host is the half that host never
# exercises.
- name: "Graphics: the offscreen example builds on this host"
shell: bash
run: |
set -e
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
cd "$GITHUB_WORKSPACE/examples/10-graphics/offscreen"
# The toolchain is NAMED rather than inherited: leaving it to whatever
# a neighbouring step happened to select makes this step's subject
# depend on step order, which is not a property anybody reads.
"/tmp/mcpp-fresh" build --toolchain "llvm@${MCPP_LLVM_VER}"
for f in triangle_vert triangle_frag; do
d="target/.build-mcpp/out/spirv"
test -f "$d/$f.h" || { echo "missing $d/$f.h"; exit 1; }
# THE MAGIC IS NOT ALWAYS IN THE HEADER, AND THAT IS THE POINT OF
# THIS JOB. The rule chooses the shader compiler this platform
# publishes -- glslang on Linux, glslc here -- and the two split
# the declaration differently: glslang writes a complete `const
# uint32_t ...[] = {...}`, glslc an initialiser list the rule
# declares around, so the words land in `<stem>.inc`. An assertion
# naming only the header is an assertion about ONE compiler, which
# is exactly the shape this step exists to catch.
# ONE FILE AT A TIME, because `grep -qs a b` exits 2 when `b`
# does not exist -- even on a match in `a`, and even with `-s`,
# which suppresses the message and not the status. Written as one
# grep over both names, this criterion fails whenever the route
# that produces only a header is taken, which is a failure about
# the criterion and not about the shader.
found=""
for g in "$d/$f.h" "$d/$f.inc"; do
[ -f "$g" ] && grep -q '0x07230203' "$g" && found=1
done
[ -n "$found" ] \
|| { echo "$f carries no SPIR-V magic in either $f.h or $f.inc"; exit 1; }
done
echo "ok: both shader stages compiled and the Vulkan half linked"
# AND NOW IT RUNS, WHICH IS A DIFFERENT CLAIM FROM THE ONE ABOVE.
#
# A build asserts that the shader compiler this platform publishes works
# and that the Vulkan half links. It cannot assert that the loader hands
# this program a device, and on macOS that is the interesting half:
# MoltenVK is a PORTABILITY driver, which the loader does not give to
# `vkEnumeratePhysicalDevices` unless the instance asked for portability
# enumeration. A program written against native drivers therefore finds no
# device here and reports it as "this machine has no GPU".
#
# This step is what turns that into a red build rather than a plausible
# message. It depends on the portability enumeration in this PR: without
# it the run reaches zero devices even though everything installed
# correctly.
#
# `VK_DRIVER_FILES` rather than an ICD search directory: the package is in
# the xlings store, not in `/usr/local/share/vulkan/icd.d`, and naming the
# file is the one form that does not depend on where the loader looks.
- name: "Graphics: the offscreen example RUNS on MoltenVK"
shell: bash
run: |
set -e
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
# NOT installed from here. The example declares `xim:moltenvk` under
# `cfg(macos)`, so the build above already provisioned it, and looking
# for the ICD without installing anything is what asserts that the
# declaration works. An `xlings install` here would make this step
# pass whether the manifest named the driver or not.
icd=$(find "${MCPP_HOME:-$HOME/.mcpp}/registry/data/xpkgs/xim-x-moltenvk" \
"$HOME/.xlings/data/xpkgs/xim-x-moltenvk" \
-name 'MoltenVK_icd.json' -print -quit 2>/dev/null || true)
[ -n "$icd" ] || { echo "no MoltenVK ICD in either store"; exit 1; }
echo "ICD: $icd"
cd "$GITHUB_WORKSPACE/examples/10-graphics/offscreen"
out=$(VK_DRIVER_FILES="$icd" "/tmp/mcpp-fresh" run 2>&1) || { echo "$out"; exit 1; }
echo "$out"
# The program asserts the corners and the centre itself and exits
# non-zero on either. What CI adds is that the run reached a DEVICE:
# a portability driver that the loader declined to show would leave
# the program reporting no device, which is the failure this step
# exists for. The name is not compared to a fixed string because it is
# the host's GPU and differs by runner; that it is non-empty and the
# centre pixel is opaque is what distinguishes reaching a device from
# not.
echo "$out" | grep -qE 'centre pixel: \([0-9]+, [0-9]+, [0-9]+, 255\)' \
|| { echo "no centre pixel was reported: the run reached no device"; exit 1; }
echo "ok: MoltenVK enumerated and the image was rendered on it"
# Integration: the mcpp built from THIS PR's source (the self-host binary,
# $MCPP = /tmp/mcpp-fresh) builds & runs a real external C++ project —
# xlings (openxlings/xlings ships its own mcpp.toml).
- name: "Integration: mcpp builds & runs xlings (openxlings/xlings)"
env:
XLINGS_NON_INTERACTIVE: '1'
run: |
MCPP=/tmp/mcpp-fresh # the freshly self-hosted binary built from this PR
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
# THIS IS THE STEP THE RETRY WAS MEASURED ON: a macOS runner
# answered `Could not resolve host: github.com` after 30s of
# resolver timeout, failing a PR check before a single line of
# mcpp had run. See .github/tools/git_clone_retry.sh.
"$GITHUB_WORKSPACE/.github/tools/git_clone_retry.sh" \
--depth 1 --recurse-submodules \
https://github.com/openxlings/xlings /tmp/xlings-src
cd /tmp/xlings-src
"$MCPP" self config --mirror GLOBAL
"$MCPP" build
"$MCPP" run