Skip to content

Commit aa891f8

Browse files
committed
feat: MCPP_TARGET_REQUESTED,以及 openkal 的 3×3 交叉验证工作流
── 1. MCPP_TARGET_REQUESTED —— 「有没有被指名一个目标」 ──────── MCPP_TARGET 回答「这是给哪台机器的」,没给 --target 时用宿主填上,那对那个问题是 对的。但它回答不了平台包必须问的另一个问题:**这次构建有没有被指向一个目标**。 ⚠️ 两者不同,即使三元组相等:在 arm64 Mac 上跑 mcpp build --target aarch64-macos 指的就是宿主那台机器,而目标侧仍然来自图 —— 于是本工具不往链接线上放任何系统 SDK,而知道这个系统的那个包是唯一能点名一个的东西。同一台机器上的本机构建拿得到 SDK,不需要包供给任何东西。 实测(openkal-macos 试图用手头能拿到的东西判断这件事): · 用宿主判 → 交叉对,而在 Mac 上 --target aarch64-macos 错 (library not found for -lSystem) · 用 MCPP_TARGET 判 → 交叉对,本机构建错,因为它**从不为空** (undefined symbol: wcslen / strtoul / __error —— 包里三个名字的 stub 遮蔽了厂商那份完整的) ⭐ 旧版 mcpp 两个都不设,而那对它是**正确**的答案:它没有「目标侧来自图」这回事, 系统永远在链接线上,包不该供给任何东西。 ── 2. .github/workflows/openkal-cross.yml —— 3 宿主 × 3 目标 ── cross-build-test.yml 验证的是由**载荷**伺候的交叉:驱动只有一个目标,宿主和目标是 绑在一起的,所以一行一种组合是诚实的形状。 openkal 把问题的形状改了:目标侧是依赖图里的一组包,编译器是普通的可重定向 clang。 由此得出的断言是 N 宿主 × N 目标塌缩成 N 个实现加一个工具 —— **构建的那台机器不再 是一个变量**。 ⚠️ 而这个形状的断言在本仓库错过。从 Linux 宿主到达 PE 需要四处分别的修复,加上另外 两台宿主又找到七处,每一处都是「按哪台机器在构建」而不是「按输出给哪台机器」分的 决定 —— 链接行的三支、std 模块命令的 Windows 分支、cmd.exe 的 cd 不换盘符、格式 判据匹配 LLVM 的 apple 而不是 mcpp 的 macos、契约在运行时已在对象里时仍去点名一个 库、缺 -nostdinc、-lgcc 在 clang 的链接上。没有一处是从一台宿主看得见的。 ⇒ 三个构建 job(每个产三个产物,共九个);三个运行 job(每个执行**三个宿主**为它 产出的那一个)。对角线是普通的本机构建,六个非对角格才是那个断言。 ⚠️ 运行的三个 job 什么都不装 —— 不装 mcpp、不装编译器、不装 C 运行时。判据是 unwound: true:链接骗不出「析构函数在异常被带出栈帧时跑到了」。
1 parent 19e5fcd commit aa891f8

2 files changed

Lines changed: 255 additions & 0 deletions

File tree

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
name: openkal cross-build (3 hosts × 3 targets)
2+
3+
# ⭐⭐ WHAT THIS WORKFLOW ASSERTS, AND WHY IT IS A MATRIX RATHER THAN A ROW.
4+
#
5+
# `cross-build-test.yml` verifies the crosses served by a PAYLOAD: a toolchain
6+
# whose driver has exactly one target. There the host and the target are joined
7+
# — `x86_64-w64-mingw32-g++` is the Windows cross and nothing else — so one row
8+
# per supported combination is the honest shape.
9+
#
10+
# openkal changes the shape of the question. The target side — the C library,
11+
# the C++ runtime, the platform's own implementation — is a set of PACKAGES in
12+
# the dependency graph, and the compiler is an ordinary retargetable clang. The
13+
# claim that follows is that N hosts × N targets collapses to N implementations
14+
# plus one tool: **the machine doing the building stops being a variable.**
15+
#
16+
# ⚠️ THAT IS A CLAIM, AND CLAIMS OF THIS SHAPE HAVE BEEN WRONG IN THIS
17+
# REPOSITORY. Reaching PE from a Linux host needed four separate repairs, and
18+
# adding the other two hosts found seven more — every one of them a decision
19+
# that had been keyed on which machine was building rather than on which machine
20+
# the output was for:
21+
#
22+
# the link line's three host-shaped branches, only one of which carried
23+
# `--target=`; the `std` module command's Windows branch, which dropped the
24+
# package's own include paths; `cd X && …` not changing the drive in cmd.exe;
25+
# the artefact-format test matching LLVM's `apple` rather than mcpp's `macos`;
26+
# the C++ runtime contract naming a library to link when one was already in
27+
# the objects; `-nostdinc` missing so a host SDK header could be found; and
28+
# `-lgcc` naming GCC's runtime on a link whose compiler is clang.
29+
#
30+
# None of those was visible from one host. So the matrix is the test.
31+
#
32+
# ── The shape ──────────────────────────────────────────────────────────────
33+
#
34+
# THREE build jobs, one per host, each producing THREE artefacts — nine builds.
35+
# THREE run jobs, one per system, each executing the artefact FOR that system
36+
# produced by ALL THREE hosts.
37+
#
38+
# build on Linux build on macOS build on Windows
39+
# run Linux ✓ ✓ ✓
40+
# run macOS ✓ ✓ ✓
41+
# run Windows ✓ ✓ ✓
42+
#
43+
# ⭐ The diagonal is an ordinary native build. The six off-diagonal cells are
44+
# the claim, and they are what a single-host workflow cannot reach.
45+
#
46+
# ⚠️ THE RUN JOBS INSTALL NOTHING — not mcpp, not a compiler, not a C runtime.
47+
# A program above openkal carries its C library, its C++ runtime and its
48+
# unwinder; what remains is the operating system it was built for. If a
49+
# toolchain step is ever added to one of them because "the program needs it",
50+
# that is the finding rather than the fix.
51+
#
52+
# ⚠️ AND THE ASSERTION IS ON THE OUTPUT, NOT THE EXIT STATUS. The program prints
53+
# four lines, and `unwound: true` is the one a link cannot fake: it says a
54+
# destructor ran while an exception was being carried out of a frame, which
55+
# means the unwinder found this image's own frame descriptions.
56+
57+
on:
58+
pull_request:
59+
workflow_dispatch:
60+
61+
concurrency:
62+
group: ci-${{ github.workflow }}-${{ github.ref }}
63+
cancel-in-progress: true
64+
65+
env:
66+
# ⚠️ The version that BOOTSTRAPS the build of this repository's mcpp. What is
67+
# under test is the mcpp that comes out, which is why every step below uses
68+
# the built binary rather than this one.
69+
MCPP_VERSION: 2026.8.19.4
70+
XLINGS_VERSION: v2026.8.17.2
71+
XLINGS_NON_INTERACTIVE: '1'
72+
# The branch of the openkal packages this change is verified against. They
73+
# move together with it; when they are on `main` this becomes `main`.
74+
OPENKAL_BRANCH: feat/openkal-closure
75+
76+
jobs:
77+
build:
78+
name: build 3 targets on ${{ matrix.host }}
79+
runs-on: ${{ matrix.runner }}
80+
timeout-minutes: 120
81+
strategy:
82+
fail-fast: false
83+
matrix:
84+
include:
85+
- { host: linux, runner: ubuntu-24.04 }
86+
- { host: macos, runner: macos-14 }
87+
- { host: windows, runner: windows-2022 }
88+
defaults:
89+
run:
90+
shell: bash
91+
steps:
92+
- uses: actions/checkout@v4
93+
94+
- name: Install xlings (Unix)
95+
if: runner.os != 'Windows'
96+
run: |
97+
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh \
98+
| bash -s "$XLINGS_VERSION"
99+
echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH"
100+
101+
- name: Install xlings (Windows)
102+
if: runner.os == 'Windows'
103+
shell: pwsh
104+
run: |
105+
irm https://d2learn.org/xlings-install.ps1.txt | iex
106+
"$env:USERPROFILE\.xlings\subos\current\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
107+
108+
- name: Install the bootstrap mcpp
109+
run: |
110+
# ⚠️ A LOOP, because one `xlings update` can return a stale index
111+
# without saying so: the index is published behind a pointer that
112+
# propagates asynchronously, and "synced 0 seconds ago" describes when
113+
# it was fetched rather than what it contains.
114+
for attempt in 1 2 3 4 5 6; do
115+
xlings update > /dev/null 2>&1 || true
116+
if xlings install "mcpp@$MCPP_VERSION" -y -g; then break; fi
117+
if [ "$attempt" = 6 ]; then
118+
echo "::error::mcpp@$MCPP_VERSION never appeared in the index"; exit 1
119+
fi
120+
sleep 60
121+
done
122+
mcpp self config --mirror GLOBAL
123+
124+
# ⭐ THE mcpp UNDER TEST. Everything after this uses the binary this step
125+
# produces; the bootstrap one above is only what compiles it.
126+
- name: Build the mcpp in this pull request
127+
run: |
128+
set -euo pipefail
129+
# ⚠️ `--dev` and not `--release`. What is under test is a set of
130+
# decisions about compile and link flags; an optimisation level
131+
# changes none of them and a release self-build is most of the budget
132+
# of a job on a two-core runner.
133+
mcpp build --dev
134+
BUILT=$(find target -type f \( -name 'mcpp' -o -name 'mcpp.exe' \) | head -1)
135+
[ -n "$BUILT" ] || { echo "::error::mcpp did not build"; exit 1; }
136+
echo "$(cd "$(dirname "$BUILT")" && pwd)" >> "$GITHUB_PATH"
137+
"$BUILT" --version
138+
139+
- name: Select the toolchain the openkal packages ask for
140+
run: |
141+
# ⚠️ Install, then select. `toolchain default` names one and does not
142+
# fetch it.
143+
mcpp toolchain install llvm 22.1.8
144+
mcpp toolchain default 'llvm@22.1.8'
145+
146+
- name: The program — one source, three targets
147+
run: |
148+
set -euo pipefail
149+
git clone --quiet --depth 1 -b "$OPENKAL_BRANCH" \
150+
https://github.com/mcpplibs/openkal-llvm-runtime "$RUNNER_TEMP/okl"
151+
cd "$RUNNER_TEMP/okl/examples/same-source"
152+
mkdir -p "$RUNNER_TEMP/out"
153+
# ⚠️ The three HOSTED targets. Bare metal is verified by
154+
# `openkal-llvm-runtime`'s own CI under qemu; it has no runner here to
155+
# execute on, and a build-only cell in a workflow whose point is
156+
# running would be the weaker claim.
157+
for t in x86_64-linux-gnu aarch64-macos x86_64-windows-gnu; do
158+
rm -rf target
159+
mcpp build --target "$t"
160+
a=$(find target -type f \( -name 'openkal-same-source' -o -name 'openkal-same-source.exe' \) | head -1)
161+
[ -n "$a" ] || { echo "::error::$t produced no artefact on ${{ matrix.host }}"; exit 1; }
162+
case "$t" in
163+
x86_64-windows-gnu) cp "$a" "$RUNNER_TEMP/out/windows.exe" ;;
164+
aarch64-macos) cp "$a" "$RUNNER_TEMP/out/macos" ;;
165+
*) cp "$a" "$RUNNER_TEMP/out/linux" ;;
166+
esac
167+
echo "${{ matrix.host }} → $t : $(ls -l "$a" | awk '{print $5}') bytes"
168+
done
169+
170+
- uses: actions/upload-artifact@v4
171+
with:
172+
name: openkal-built-on-${{ matrix.host }}
173+
path: ${{ runner.temp }}/out/
174+
if-no-files-found: error
175+
176+
run:
177+
name: run 3 builds on ${{ matrix.system }}
178+
needs: build
179+
runs-on: ${{ matrix.runner }}
180+
timeout-minutes: 15
181+
strategy:
182+
fail-fast: false
183+
matrix:
184+
include:
185+
- { system: linux, runner: ubuntu-24.04, file: linux }
186+
- { system: macos, runner: macos-14, file: macos }
187+
- { system: windows, runner: windows-2022, file: windows.exe }
188+
defaults:
189+
run:
190+
shell: bash
191+
steps:
192+
# ⚠️ NO checkout AND NO toolchain. This job is the claim: a program built
193+
# above openkal needs the operating system it was built for and nothing
194+
# else. Anything installed here would weaken what a pass means.
195+
- uses: actions/download-artifact@v4
196+
with: { pattern: openkal-built-on-*, path: art }
197+
198+
- name: The same program, from all three build hosts
199+
run: |
200+
set -euo pipefail
201+
fail=0
202+
for host in linux macos windows; do
203+
bin="art/openkal-built-on-$host/${{ matrix.file }}"
204+
echo "──────── built on $host, running on ${{ matrix.system }} ────────"
205+
if [ ! -f "$bin" ]; then
206+
echo "::error::$bin is missing"; fail=1; continue
207+
fi
208+
# ⚠️ The executable bit does not survive an artefact upload.
209+
chmod +x "$bin" || true
210+
# ⚠️ arm64 macOS refuses an unsigned image, so the signature is
211+
# asserted before the run: a failure here is "the linker did not
212+
# ad-hoc sign it", which is a different repair from "it crashed".
213+
if [ "${{ matrix.system }}" = "macos" ]; then
214+
codesign -dv "$bin" 2>&1 | grep -q 'adhoc\|Signature' \
215+
|| { echo "::error::built on $host: no code signature"; fail=1; continue; }
216+
fi
217+
if ! "./$bin" > out.log 2>&1; then
218+
echo "::error::built on $host: it did not run"; cat out.log; fail=1; continue
219+
fi
220+
cat out.log
221+
ok=1
222+
grep -q 'sorted: 2 4 7' out.log || ok=0
223+
grep -q 'caught: 42' out.log || ok=0
224+
# ⭐ The line a link cannot fake.
225+
grep -q 'unwound: true' out.log || ok=0
226+
grep -q 'import std over openkal: ok' out.log || ok=0
227+
[ "$ok" = 1 ] || { echo "::error::built on $host: wrong output"; fail=1; }
228+
done
229+
[ "$fail" = 0 ] || exit 1
230+
echo "three builds, one system, same four lines"

src/build/build_program.cppm

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,31 @@ contract_env(const fs::path& root, const fs::path& outDir, const BuildProgramEnv
317317
std::vector<std::pair<std::string, std::string>> e;
318318
auto hostT = mcpp::toolchain::triple::host_triple().str();
319319
e.emplace_back("MCPP_TARGET", env.targetTriple.empty() ? hostT : env.targetTriple);
320+
// ⭐⭐ THE SAME VALUE UNFILLED — EMPTY WHEN NOBODY NAMED A TARGET.
321+
//
322+
// `MCPP_TARGET` above answers "which machine is this for", and filling it
323+
// in with the host is right for that question. It cannot answer a different
324+
// one that a platform package has to ask: **was this build POINTED at a
325+
// target**, or is it an ordinary native build?
326+
//
327+
// The two are not the same even when the triples are equal. `mcpp build
328+
// --target aarch64-macos` on an arm64 Mac names the same machine the host
329+
// is, and yet it is the graph that supplies the target side — so this tool
330+
// puts no system SDK on the link, and the package that knows the system is
331+
// the only thing that can name one. A native build on the same machine gets
332+
// the SDK and needs nothing from the package.
333+
//
334+
// ⚠️ Measured 2026-08-23, `openkal-macos` trying to decide this from what
335+
// was available. From the host: right for the cross, wrong for
336+
// `--target aarch64-macos` ON a Mac (`library not found for -lSystem`).
337+
// From `MCPP_TARGET`: right for the cross, wrong for the native build,
338+
// because it is never empty (`undefined symbol: wcslen`, `strtoul`, … —
339+
// the package's three-name stub had shadowed the vendor's complete one).
340+
//
341+
// ⭐ An older mcpp sets neither, and that is the correct answer for it:
342+
// it has no graph-supplied target side, so the system is always on the
343+
// link and a package should supply nothing.
344+
e.emplace_back("MCPP_TARGET_REQUESTED", env.targetTriple);
320345
// Convenience splits of the resolved target (Cargo CARGO_CFG_TARGET_*
321346
// parity): parsed ONCE here through the canonical triple parser so every
322347
// build.mcpp stops hand-splitting MCPP_TARGET. MCPP_TARGET_ENV is "" when

0 commit comments

Comments
 (0)