diff --git a/.github/workflows/ci-target-matrix.yml b/.github/workflows/ci-target-matrix.yml index 3e6e0455..6f1c1873 100644 --- a/.github/workflows/ci-target-matrix.yml +++ b/.github/workflows/ci-target-matrix.yml @@ -309,6 +309,19 @@ jobs: export MCPP="$MCPP_UNDER_TEST" export MCPP_VENDORED_XLINGS="$XLINGS_BIN" "$MCPP" self config --mirror GLOBAL 2>/dev/null || true + # ⚠️⚠️ 编译器轴跟着**声明**走,不跟着「这台机器上装了什么」走。 + # + # 实测 2026-08-26,同一个提交:PR 上这个 job 绿,合入 main 后红 —— 那 + # 次 windows-2022 恢复出来的缓存里多了一个 `gcc@16.1.0`,扫描产出 24 + # 格而期望表声明 16 格,八格全部报成「表里没有这一格」。缓存里有什么 + # 不是这个仓库声明了什么,而判据必须是后者。 + # + # 与上一步取自同一列,所以「装它」和「扫它」不可能各说各话。 + MATRIX_COMPILERS="$(awk -F'\t' -v h='${{ matrix.host }}' \ + 'NF>=11 && $2==h {print $4}' tests/matrix/expected.tsv \ + | sort -u | tr '\n' ' ')" + export MATRIX_COMPILERS + echo "declared compiler axis: ${MATRIX_COMPILERS:-}" # ⚠️ 两种体系各自成表。scan 把 mode 写进第一列,而比对必须按 mode 分开 # 做 —— 拿一种体系的测量去比整张表,另一种的每一行都会被报成「没跑到」。 bash tests/matrix/scan.sh payload > measured-payload.tsv diff --git a/CHANGELOG.md b/CHANGELOG.md index 87e11ac6..fed795b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -99,6 +99,33 @@ 测陈述它。⚠️ 这条缺陷是**读出来的**:它需要一台没有工具链的 Windows 机器,而 `config.toml` 的 sha256 判据跑在已配好的环境里,两条分支一条都到不了。 +### 示例 + +- **⭐ `examples/06-openkal-cross` 现在是四个目标,而且不再写 `[toolchain]`。** + + 第四行 `--target aarch64-linux` 正是这次修好的那一条 —— 在此之前它补全成 + `aarch64-linux-gnu`(registered but not supported)并拒绝。同时删掉了那个 + `[toolchain] default = "llvm@22.1.8"` 段:`openkal-llvm-runtime` 自己声明 + `requires = ["mcpp:compiler=llvm"]`,mcpp 现在读它。实测在一台全局默认为 gcc 的 + 机器上,四个目标全部解析出 `llvm@22.1.8`,而 `~/.mcpp/config.toml` 一字未改。 + + ⚠️ 顺带修掉一处失效的钉:该示例的依赖下界还写着 `0.1.1`,而那个版本的 + compiler-rt builtins 在 aarch64 上编不过。这个示例**不被任何 CI 构建**,所以它 + 钉住的版本过期了也没有任何东西会说话。 + +### CI + +- **⚠️⚠️ 目标矩阵的编译器轴跟着「装了什么」走,于是缓存能决定判据。** + + 实测:同一个提交,PR 的 `scan (windows-x86_64)` 绿,合入 main 后同一个 job 红 —— + 那次 runner 恢复出来的缓存里多了一个 `gcc@16.1.0`,扫描产出 24 格而期望表为这台 + 宿主声明的是 16 格,八格全部报成「表里没有这一格」。 + + ⭐ **期望表是一份声明**,编译器轴现在跟着它走(`MATRIX_COMPILERS`,由 workflow + 从 expected.tsv 的同一列算出,与「装哪些」那一步同源,所以两者不可能各说各话)。 + 装着却不在声明里的版本写到 stderr —— 一次没跑的测量和一次通过的测量,在退出码上 + 没有区别。 + ### 兼容性 ⭐ **没有任何一次原本成功的构建换了行为。** 图声明编译器的情形里,原来的结局是 diff --git a/examples/06-openkal-cross/README.md b/examples/06-openkal-cross/README.md index bf9d2ca2..d845a84c 100644 --- a/examples/06-openkal-cross/README.md +++ b/examples/06-openkal-cross/README.md @@ -1,15 +1,21 @@ # 06 — One Source, Several Machines -A program that asks each machine what it is, built for three targets from any +A program that asks each machine what it is, built for four targets from any host without being edited. ```bash mcpp run # this machine -mcpp build --target x86_64-linux # Linux, from any host -mcpp build --target aarch64-macos # macOS, from any host -mcpp build --target x86_64-windows-gnu # Windows, from any host +mcpp build --target x86_64-linux # Linux x86_64, from any host +mcpp build --target aarch64-linux # Linux aarch64, from any host +mcpp build --target aarch64-macos # macOS aarch64, from any host +mcpp build --target x86_64-windows # Windows x86_64, from any host ``` +⭐ Every one of them declines the third segment. The `aarch64-linux` line could +not be written before 2026.8.26.2: it completed to `aarch64-linux-gnu`, a row +registered but not supported, and refused — while `aarch64-linux-musl` built. +A request that names no C library now resolves to a row that exists. + `src/main.cpp` contains no preprocessor directive and no branch on a target name. What differs between the builds is which packages the dependency graph resolved. @@ -18,20 +24,36 @@ resolved. ```toml [dependencies] -openkal-llvm-runtime = "0.1.1" +openkal-llvm-runtime = "0.1.3" +``` + +That is the whole of it. One dependency supplies the compiler runtime and the +C++ runtime, and depends in turn on a C library, which depends on whichever +implementation of the platform interface matches the target being built. One +line therefore selects three of the five target-side layers. -[toolchain] -default = "llvm@22.1.8" +⭐⭐ **And the fourth — the compiler — without the manifest naming it.** There +used to be a `[toolchain] default = "llvm@22.1.8"` here. It is gone, because +`openkal-llvm-runtime` declares `requires = ["mcpp:compiler=llvm"]` — a C++ +runtime is configured for one compiler family and records that in the headers it +ships — and since 2026.8.26.2 mcpp reads that and takes it: + +``` + Resolved llvm@22.1.8 → aarch64-linux-musl → …/xim-x-llvm/22.1.8/bin/clang++ + required by openkal-llvm-runtime@0.1.3 + (`requires = ["mcpp:compiler=llvm"]`), not your gcc@16.1.0 + — this project only ``` -One dependency supplies the compiler runtime and the C++ runtime, and depends in -turn on a C library, which depends on whichever implementation of the platform -interface matches the target being built. One line therefore selects three of -the five target-side layers. +⚠️ **"this project only" is load-bearing.** Nothing is written — not +`~/.mcpp/config.toml`, not this manifest. A requirement is a property of the +package that states it, so it decides this build and no other. A project that +wants a different compiler still writes one here, and that statement outranks +the graph (docs/03). -The toolchain line names a compiler and nothing else. Where the headers, the C -library, the C++ runtime and the platform implementation come from is not stated -in the manifest at all; the build reports what it resolved: +Where the headers, the C library, the C++ runtime and the platform +implementation come from is not stated in the manifest at all; the build reports +what it resolved: ``` Target x86_64-windows-gnu → x86_64-w64-windows-gnu diff --git a/examples/06-openkal-cross/mcpp.toml b/examples/06-openkal-cross/mcpp.toml index 828f4ab1..db23cda4 100644 --- a/examples/06-openkal-cross/mcpp.toml +++ b/examples/06-openkal-cross/mcpp.toml @@ -1,25 +1,41 @@ [package] name = "portable-report" version = "0.1.0" -description = "One source, three hosted targets, over openkal" +description = "One source, four hosted targets, over openkal" # There is no `[build] target` here, and its absence is what the directory is # for. The target is given on the command line so that the same file can be -# built three ways without being edited. +# built four ways without being edited. [dependencies] # One line selects three of the five target-side layers. `openkal-llvm-runtime` # supplies the compiler runtime and the C++ runtime, and depends in turn on # `openkal-musl` for the C library, which depends on whichever implementation of # openkal matches the target being built. -openkal-llvm-runtime = "0.1.1" +openkal-llvm-runtime = "0.1.3" -[toolchain] -# A compiler, and nothing else. Where the headers, the C library, the C++ -# runtime and the platform implementation come from is not stated in this file; -# it follows from the dependency above and is resolved after the graph exists. -# The build reports what it resolved. -default = "llvm@22.1.8" +# ⭐⭐ AND IT SELECTS THE FOURTH — THE COMPILER — WITHOUT THIS FILE SAYING SO. +# +# There used to be a `[toolchain] default = "llvm@22.1.8"` here, with a comment +# explaining that it named "a compiler, and nothing else". It is gone, and the +# example is more accurate for it: `openkal-llvm-runtime` declares +# `requires = ["mcpp:compiler=llvm"]`, because a C++ runtime is configured for +# one compiler family and records that configuration in the headers it ships. +# +# Since 2026.8.26.2 mcpp reads that and takes it. Measured on a machine whose +# global default is gcc, all four targets: +# +# Resolved llvm@22.1.8 → aarch64-linux-musl → …/xim-x-llvm/22.1.8/bin/clang++ +# required by openkal-llvm-runtime@0.1.3 +# (`requires = ["mcpp:compiler=llvm"]`), not your gcc@16.1.0 +# — this project only +# +# ⚠️ "this project only" is load-bearing. Nothing is written: not +# `~/.mcpp/config.toml`, not this file. A requirement is a property of the +# package that states it, so it decides this build and no other. +# +# A project that wants a different compiler still writes one here, and that +# statement outranks the graph — see docs/03. # ⚠️ NO BARE-METAL TARGET SECTION, AND ITS ABSENCE IS DELIBERATE. # diff --git a/examples/06-openkal-cross/src/main.cpp b/examples/06-openkal-cross/src/main.cpp index 12cdd153..3dc2017d 100644 --- a/examples/06-openkal-cross/src/main.cpp +++ b/examples/06-openkal-cross/src/main.cpp @@ -1,13 +1,20 @@ -// One source, three machines, and a program that asks each of them what it is. +// One source, four machines, and a program that asks each of them what it is. // // mcpp run this machine -// mcpp build --target x86_64-linux Linux, any host -// mcpp build --target aarch64-macos macOS, any host -// mcpp build --target x86_64-windows Windows, any host +// mcpp build --target x86_64-linux Linux x86_64, any host +// mcpp build --target aarch64-linux Linux aarch64, any host +// mcpp build --target aarch64-macos macOS aarch64, any host +// mcpp build --target x86_64-windows Windows x86_64, any host +// +// ⭐ EVERY ONE OF THEM DECLINES THE THIRD SEGMENT, AND THE FOURTH LINE COULD +// NOT BE WRITTEN UNTIL 2026.8.26.2. `aarch64-linux` used to complete to +// `aarch64-linux-gnu` — a row registered but not supported — and refuse, while +// `aarch64-linux-musl` built. A request that names no C library now resolves to +// a row that exists; see docs/16. // // Nothing below is conditional on a platform. There is no preprocessor // directive in this file, and no branch on a target name. What differs between -// the three builds is which packages the dependency graph resolved, and the +// the four builds is which packages the dependency graph resolved, and the // only trace of that difference in the source is that the program ASKS about // capabilities instead of assuming them. // diff --git a/tests/matrix/scan.sh b/tests/matrix/scan.sh index 93d4b0fb..c53f98c2 100755 --- a/tests/matrix/scan.sh +++ b/tests/matrix/scan.sh @@ -111,7 +111,32 @@ targets() { printf '%s' "$LIST" | jq_r '.data.targets[].target' | sort -u; } # # ⚠️ 但收窄必须说出来。被丢掉的版本写到 stderr —— 一次没跑的测量和一次通过的 # 测量,在退出码上没有区别。 +# +# ⚠️⚠️ 而「装了哪些」不是「这台宿主声明了哪些」,把前者当成后者会让缓存决定判据。 +# +# 实测 2026-08-26,同一个提交:PR 的 `scan (windows-x86_64)` 绿,合入 main 后同一 +# 个 job 红 —— 因为那次 runner 恢复出来的缓存里多了一个 `gcc@16.1.0`,扫描于是产出 +# 24 格,而期望表为这台宿主声明的是 16 格,八格全部报成「表里没有这一格」。 +# +# 这台宿主装了什么随缓存变(297 的具名跳过就是围着这个事实写的),而**期望表是一 +# 份声明**。让编译器轴跟着声明走,这一整类假红就消失了:`MATRIX_COMPILERS` 由 +# workflow 从 expected.tsv 自己那一列算出来传进来。 +# +# ⭐ 不传时退回「装了什么扫什么」,因为本机跑没有期望表可依。 compilers() { + if [ -n "${MATRIX_COMPILERS:-}" ]; then + # ⚠️ 收窄仍然要说出来。装着却不在声明里的那些,写到 stderr —— 一次没跑的 + # 测量和一次通过的测量,在退出码上没有区别。 + printf '%s' "$LIST" | jq_r '.data.toolchains[] | .family + "@" + .version' \ + | while IFS= read -r have; do + case " $MATRIX_COMPILERS " in + *" $have "*) ;; + *) echo "scan: 略过 $have —— 本宿主的期望表没有声明它" >&2 ;; + esac + done + printf '%s\n' $MATRIX_COMPILERS + return + fi printf '%s' "$LIST" | jq_r '.data.toolchains[] | .family + "@" + .version' \ | awk -F@ '{ if (seen[$1]++) print "scan: 略过 " $0 " —— 每族只取最新" > "/dev/stderr" else print $0 }'