Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
420 changes: 420 additions & 0 deletions .agents/docs/2026-08-25-the-two-layer-predicate-family.md

Large diffs are not rendered by default.

23 changes: 21 additions & 2 deletions .github/tools/mirror_res.sh
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,9 @@ done
# The final completeness gate below still does FULL GETs.
probe() { # host_path asset → 0 iff the object serves bytes
local code
code=$(curl -fsSL -o /dev/null -w '%{http_code}' -r 0-0 -L "$1" 2>/dev/null)
code=$(curl -fsS -o /dev/null -w '%{http_code}' -r 0-0 -L \
--retry 3 --retry-all-errors --retry-delay 2 --max-time 60 \
"$1" 2>/dev/null)
[[ "$code" == 200 || "$code" == 206 ]]
}

Expand Down Expand Up @@ -285,7 +287,24 @@ hosts=()
[[ "${GTC_ENABLED:-0}" == 1 ]] && hosts+=("gitcode.com/$GTC_DST")
for host in "${hosts[@]}"; do
for a in "${ASSETS[@]}"; do
code=$(curl -fsSL -o /dev/null -w '%{http_code}' -L "https://${host}/releases/download/${VER}/${a}" 2>/dev/null || echo ERR)
# ⚠️ RETRIED, BECAUSE A MIRROR CAN ANSWER 502 FOR AN ASSET IT HOLDS.
# v2026.8.25.1 failed here twice: every one of the 16 assets reported
# "already mirrored, skipping", and the gate then failed one of them on a
# single 502 from GitCode's edge. Fetched by hand a minute later it was
# 5,772,395 bytes with the published sha256 — the file was never missing.
#
# `--retry-all-errors` and not `--retry`: plain `--retry` covers transient
# HTTP codes but not the transport-layer failures this path also sees, and
# this repository has paid for that distinction before (ci-curl-52).
#
# ⚠️ `|| echo ERR` APPENDS, it does not replace — `-f` makes curl exit
# non-zero on 502 while `-w` has already written the code, so the variable
# read `502ERR` and the log could not be grepped for a status. Substituted
# only when curl printed nothing at all.
code=$(curl -fsS -o /dev/null -w '%{http_code}' -L \
--retry 3 --retry-all-errors --retry-delay 3 --max-time 120 \
"https://${host}/releases/download/${VER}/${a}" 2>/dev/null)
[[ -n "$code" ]] || code=ERR
echo " $code https://${host}/releases/download/${VER}/${a}"
[[ "$code" == 200 ]] || { rc=1; echo "[mirror] FAIL: missing/unverified: https://${host}/releases/download/${VER}/${a}" >&2; }
done
Expand Down
19 changes: 17 additions & 2 deletions .github/workflows/openkal-cross.yml
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,15 @@ jobs:
"$XLINGS_BIN" config --mirror GLOBAL 2>/dev/null || true
"$MCPP" self config --mirror GLOBAL 2>/dev/null || true
"$MCPP" build --dev
BUILT=$(find target -type f -name 'mcpp' | head -1)
# ⚠️ NEWEST BY MTIME, NOT FIRST BY DIRECTORY ORDER. `target/` holds one
# directory per fingerprint and the runner restores a cache of it, so
# `find … | head -1` can return a binary an earlier run left behind.
# Measured: it reported the right VERSION STRING — the stale copy was
# built from an earlier push of this same release — while missing the
# last two commits, so one new test passed and two failed for reasons
# that were nowhere in the source.
BUILT=$(find target -type f -name 'mcpp' -printf '%T@ %p\n' \
| sort -rn | head -1 | cut -d' ' -f2)
[ -n "$BUILT" ] || { echo "::error::mcpp did not build"; exit 1; }
BUILT=$(cd "$(dirname "$BUILT")" && pwd)/$(basename "$BUILT")
echo "MCPP_UNDER_TEST=$BUILT" >> "$GITHUB_ENV"
Expand Down Expand Up @@ -337,7 +345,8 @@ jobs:
# detect.
fail=0
for t in tests/e2e/285_*.sh tests/e2e/286_*.sh tests/e2e/287_*.sh \
tests/e2e/288_*.sh tests/e2e/289_*.sh tests/e2e/291_*.sh; do
tests/e2e/288_*.sh tests/e2e/289_*.sh tests/e2e/291_*.sh \
tests/e2e/292_*.sh tests/e2e/293_*.sh tests/e2e/294_*.sh; do
echo "=== $t ==="
bash "$t" 2>&1 | tee "$(basename "$t").log" || true
rc=${PIPESTATUS[0]}
Expand Down Expand Up @@ -383,4 +392,10 @@ jobs:
"OK: one host reached" || fail=1
check 291_dynamic_linkage_is_refused_only_when_the_c_library_is_the_graphs.sh \
"OK: the C library decides whether 'dynamic' can be honoured" || fail=1
check 292_a_package_that_names_a_layer_does_not_lose_the_targets_compiler.sh \
"OK: naming a layer changes the system, not the compiler that emits the target" || fail=1
check 293_the_requested_target_and_the_resolved_one_name_one_os.sh \
"OK: the requested target and the resolved one name one operating system" || fail=1
check 294_the_list_answers_what_can_be_built_not_what_has_a_payload.sh \
"OK: the list answers what can be built, not what has a payload" || fail=1
[ "$fail" = 0 ] || exit 1
142 changes: 142 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,148 @@
> 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。
> 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)。

## [2026.8.25.2] — 2026-08-25

### 修复

- **⭐⭐ 一个包声明「我供给哪一层」,让裸机目标丢掉了唯一能产出它的编译器。**

实测,三行清单就够:

```toml
provides = ["mcpp:kernel-abi=openkal"]
```
```
$ mcpp build --target riscv64-none-elf
Resolved gcc@16.1.0 → riscv64-none-elf → …/bin/g++
g++: error: unrecognized argument in option '-mabi=lp64d'
g++: error: unrecognized command-line option '--target=riscv64-none-elf'
```

`graphSuppliesSystem` 是一个跨 kernel-abi ∪ c-abi 的 OR,它取消目标行的编译器
pin。**对宿主行这是对的**——那一行指的是「哪个载荷供给这个目标的 C 库」,图供给
了就用不上它。**对裸机行不是**:表里自己写着「pin 是 llvm,因为 clang/lld 按构造
就是交叉编译器」——宿主 g++ 根本发不出 `riscv64-none-elf`,图里有什么都不改变
这件事。

于是 pin 分成两类,而这个区分**在读取那一行的同一处**记下,不在决定点重新推导。

与 2026.8.25.1 修的四条同型:**一个跨两层的谓词,决定了一件不取决于这两层的事**。
这是第五条。

⚠️ 发现方式:2026.8.25.1 发布后重钉七个下游 pin PR,openkal-opensbi 红在
`g++: unrecognized`。它在 2026.8.24.6 那轮红在**同一条**,所以既非 25.1 引入,
也非 25.1 修掉——是同一跨度里的遗留。

### 测试

- e2e 292,两向断言:声明一层之后裸机目标仍解析到同一个编译器(先建立基线,
否则分不清「修好了」和「这台机器没有 llvm」);以及宿主行**不得**顶掉项目自己
选的工具链——不加区分地永不取消 pin 也能让前一半通过,而那正是这个谓词当初要
防的替换。

## [2026.8.25.2] — 2026-08-25

一个谓词族的收尾。`2026.8.25.1` 修了其中四条,本次修余下三条,并补上让它们
存活至今的两个 CI 空洞。完整分析见
[`.agents/docs/2026-08-25-the-two-layer-predicate-family.md`](.agents/docs/2026-08-25-the-two-layer-predicate-family.md)。

### 修复

- **⭐⭐ 图供给了 C 库,不等于目标平台的 SDK 不再需要。**

```
kernel-abi openkal (openkal-macos@0.3.4, graph)
c-abi musl (openkal-musl@0.3.5, graph)
ld64.lld: error: library not found for -lSystem
ld64.lld: error: undefined symbol: clock_gettime_nsec_np
```

macOS 分支给链接线加 `-isysroot`,它自己的注释写明了为什么(「否则 ld64.lld
会死在 library not found for -lSystem」);而 80 行之后的图分支把整条 `f.ld`
换掉,`-isysroot` 随之消失——**注释预言的那个失败,由它下面的代码造成**。

⭐ **Linux 上二者恰好重合而 Darwin 上不重合**:Linux 的内核接口是一条指令
(`syscall`),所以自足的 musl 真的替换了一切;Darwin 的内核接口**本身就是一个
库**(libSystem),所以 Mach-O 链接无论 libc 从哪来都要 SDK。新增
`platformAnchor`:写一次、读一次,两个分支不可能对「什么该活下来」有分歧。

- **⭐⭐ 请求的目标与解析出的目标必须是同一个操作系统。**

```
Target x86_64-windows-gnu → x86_64-unknown-linux-gnu
src/stream.cpp:68:9: error: 'GetFileType' was not declared in this scope
```

一行里两个操作系统,而没有任何提示。交叉载荷缺席时解析回落到宿主编译器,
Windows 源码被按 Linux 编译,失败在一百行之后——报出的是一个 Win32 函数名,
不是做出这个决定的那一处。openkal-uefi 撞在链接器上:
`ld: unrecognized option '--subsystem'`。

⭐ **报告里早就有证据,现在对它下断言**,而不是把问题重新推导一遍。范围刻意
只取 OS:`x86_64-windows-gnu → x86_64-w64-windows-gnu` 的差异正是这一行要报告
的归一化,拿整个三元组比会拒掉每一次正确的交叉构建。

- **⭐⭐ 「首次运行」那条路把 `--target` 丢了。**

```
First run no toolchain configured — installing gcc@16.1.0 (glibc, native ABI)
Resolved gcc@16.1.0 → …/xim-x-gcc/16.1.0/bin/g++ ← 路径里没有目标
```

同一条命令在已有工具链的机器上是
`Resolved gcc@16.1.0 → x86_64-windows-gnu → …/mingw-cross-gcc/…`。这条分支回答
的是「这台机器没有工具链,给它一个」,答案是一份**宿主**载荷;
`overrides.target_triple` 在这条路上**从未被读取**。而载荷解析那条路上
`autoInstall=true` **本来就在**,只是没被走到。

⭐ 修法不是加条件,而是让首次运行**汇入**那条已经会处理目标的路径。上面那条
「同一个操作系统」的不变量因此有了配套:守卫让错配变成一句拒绝,汇入让本来就能
服务的目标不再走到那句拒绝。

⚠️ **这一处的第一版是无限递归,而我的注释写着「深度为一」。** 闸放在了分支之外
(必须放外面:它上面那段 Windows 代码自己会设置 target),而标志没有任何人复位。
本机看不见——这一格只在「首次运行 + 交叉目标」出现,而开发机永远不是首次运行。
抓到它的是两条 CI,症状还不同:生态仓库上 `Resolved` 打四遍后 **exit 139
(SIGSEGV,爆栈)**,mcpp 自己的 `bare Windows` 上 `First run` 反复打印后 exit 1。
现在的闸结构上不可能循环,标志在**调用之前**置位。

- **`toolchain list` 漏掉了本机能构建的目标。**

它用 `host_can_serve`(问的是「有没有预制载荷」)去回答「能不能构建」。实测
Linux 上 `x86_64-windows-musl` 不在列表里,而同一台机器能产出真正的 PE32+。

⚠️ **而不是每一行缺席都是这样**:`x86_64-windows-msvc` 与 `aarch64-macos` 在
Linux 上缺席是**对的**,MSVC 与 macOS SDK 是宿主专有的,依赖替代不了。判据不
需要新字段——**一行若指向本宿主装得上的编译器,那它缺的只是系统,而系统可以
由图供给**。第三种状态:`via dependency graph`。

- **镜像完整性门:一个没有重试的 GET 判掉整条发布。**

`2026.8.25.1` 的发布红了两次,两次都报 16 个资产「already mirrored」,然后因
其中一个的 502 判失败。手工抓下来:5,772,395 字节,sha256 与发布的校验和逐位
相同。补 `--retry-all-errors`(不是 `--retry`,后者盖不住这条路径也会遇到的
传输层错误);并修掉 `502ERR` 拼接(`|| echo ERR` 是追加不是替换)。

### 测试

- **e2e 292/293/294**,每条两向断言,且**都在修复前的二进制上验证过会失败**:
292 声明一层后裸机目标仍解析到同一编译器 + 宿主行不得顶掉项目自己的工具链;
293 拒绝跨 OS 的解析 + 四个正确交叉目标零误伤;294 列出图供给的目标 + 宿主
专有的仍然缺席。

- **⚠️ `285`–`289` 此前一条都没在 CI 跑过。** 它们声明 `# requires: llvm`,而两个
linux e2e shard 的能力行里没有 `llvm`,`run_all.sh` 在 skip 时退 0。新增
`openkal-cross.yml` 的 `ecosystem-e2e`:装 gcc + llvm + 两个模拟器,直跑六条,
**逐条断言 PASS 行**,并对 287/288 **额外断言运行阶段那一行**(实测它们会降级
成 SKIP 而 OK 行照印)。

- **判据的「否」与「没测成」同读数**:一次会话里我自己新写的六条 e2e 有四条犯了
它(宿主 objdump 反汇编外架构得零指令零报错、`readelf` 读不了的文件输出零行、
CI 自己的 PATH 本就以 subos/bin 开头)。判据一律带分母
(`7 LSE instructions out of 148906`),工具取自产生该产物的工具链。

## [2026.8.25.1] — 2026-08-25

### 修复
Expand Down
18 changes: 16 additions & 2 deletions docs/03-toolchains.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,22 @@ Available toolchains (run `mcpp toolchain install <family> <version>`):
```

`*` marks the default pair. The Targets block is the live view of the target
vocabulary: `installed` payloads, `available` targets this host can install,
and `planned` targets that are registered but not yet shipped.
vocabulary, in four statuses:

| Status | Meaning | What to do next |
|---|---|---|
| `installed` | a payload here already produces it | nothing |
| `available` | a payload exists for this host | `mcpp toolchain install` |
| `via dependency graph` | the compiler is here; the target's system is not, and packages can supply it | depend on an implementation of the target's kernel interface and C library |
| `planned` | registered in the vocabulary, not yet shipped | — |

⚠️ **A target absent from this block cannot be built here at all** — and that
is a narrower statement than it used to be. Until mcpp 2026.8.25.2 the block
listed only what a payload served, so a target whose system comes from a
dependency graph was missing while the same host produced real artefacts for
it. `x86_64-windows-msvc` and `aarch64-macos` remain absent on a Linux host,
correctly: MSVC and the macOS SDK are host-only and no dependency substitutes
for them.

## Windows PE via MinGW-w64 (`x86_64-windows-gnu`, no Visual Studio required)

Expand Down
17 changes: 14 additions & 3 deletions docs/zh/03-toolchains.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,20 @@ Available toolchains (run `mcpp toolchain install <family> <version>`):
llvm 20.1.7
```

`*` 标记当前的默认对。Targets 块是 target 词汇表的实时视图:`installed`
为已装的链,`available` 为本宿主可安装的 target,`planned` 为已登记但尚未
发布的 target。
`*` 标记当前的默认对。Targets 块是 target 词汇表的实时视图,共四种状态:

| 状态 | 含义 | 下一步做什么 |
|---|---|---|
| `installed` | 本机已有的载荷就能产出它 | 无 |
| `available` | 本宿主存在可装的载荷 | `mcpp toolchain install` |
| `via dependency graph` | 编译器在本机,而目标的系统不在,由包供给 | 依赖一个实现该目标内核接口与 C 库的包 |
| `planned` | 已登记在词表中,尚未发布 | — |

⚠️ **不在这个块里的 target,在本机根本构建不了**——而这句话现在比以前更窄。
mcpp 2026.8.25.2 之前,这个块只列载荷能服务的那些,于是「系统来自依赖图」的
target 缺席,而同一台机器能为它产出真实的产物。`x86_64-windows-msvc` 与
`aarch64-macos` 在 Linux 宿主上仍然缺席,这是**对的**:MSVC 与 macOS SDK 是宿主
专有的,依赖替代不了。

## Windows PE 之 MinGW-w64(`x86_64-windows-gnu`,无需 Visual Studio)

Expand Down
2 changes: 1 addition & 1 deletion mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mcpp"
version = "2026.8.25.1"
version = "2026.8.25.2"
description = "Modern C++ build & package management tool"
license = "Apache-2.0"
authors = ["mcpp-community"]
Expand Down
48 changes: 47 additions & 1 deletion src/build/flags.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -1270,6 +1270,19 @@ CompileFlags compute_flags(const BuildPlan& plan) {
return f;
}

// ⭐ THE PART OF THE LINK LINE THAT BELONGS TO THE TARGET, NOT THE PAYLOAD.
//
// Empty on every platform whose kernel interface is an instruction rather
// than a library — Linux's is `syscall`, so a self-contained libc from the
// dependency graph really does replace everything the payload contributed.
// Darwin's interface IS a library (`libSystem`), so a Mach-O link needs the
// SDK whoever supplies libc.
//
// Written by the macOS branch below and read by the graph replacement after
// it: one value, written once, so the two cannot disagree about what
// survives a replacement. Declared here rather than inside either, because
// the ordering between them is the whole point.
std::string platformAnchor;
if constexpr (mcpp::platform::is_windows) {
if (isMsvcDialect) {
// Native cl.exe: link.exe does the link (SeparateLinker). Search
Expand Down Expand Up @@ -1374,6 +1387,23 @@ CompileFlags compute_flags(const BuildPlan& plan) {
std::string macos_sdk;
if (auto sdk = mcpp::platform::macos::sdk_path())
macos_sdk = " -isysroot " + escape_path(*sdk);
// ⭐⭐ AND KEPT, BECAUSE THE GRAPH BRANCH BELOW REPLACES THIS LINE.
//
// `-isysroot` and the deployment floor describe the TARGET OS. Every
// other token here describes the payload — which is exactly what a
// graph-supplied C library replaces — so when that replacement runs it
// must carry these two across. It did not, and the failure was the one
// the comment above predicts, on a stack whose C library came from the
// graph (measured 2026-08-25, openkal-musl on macos-14):
//
// ld64.lld: error: library not found for -lSystem
// ld64.lld: error: undefined symbol: clock_gettime_nsec_np
//
// A Mach-O link needs the SDK whoever supplies libc, because on Darwin
// the platform interface IS a library. Linux needs no equivalent: its
// kernel interface is an instruction, so a self-contained libc from the
// graph really does replace everything.
platformAnchor = macos_sdk + version_min;
f.ld = std::format("{}{}{} -fuse-ld=lld{}{}{}{}", full_static,
b_flag, macos_sdk, version_min, link_intent_ld,
user_ldflags, link_extra);
Expand Down Expand Up @@ -1514,7 +1544,23 @@ CompileFlags compute_flags(const BuildPlan& plan) {
if (plan.toolchain.compiler == mcpp::toolchain::CompilerId::Clang)
graphLd += " -fuse-ld=lld";

f.ld = std::format("{}{}{}{}{}", full_static, graphLd,
// ⭐⭐ AND THE TARGET'S OWN ANCHOR SURVIVES THE REPLACEMENT.
//
// Everything this branch rebuilds describes the PAYLOAD — its `-B`, its
// startup objects, its loader — and a graph-supplied C library is
// exactly what replaces those. `platformAnchor` is the part that does
// not: on Darwin it is `-isysroot <SDK>` plus the deployment floor,
// which describe the target OS and are needed however libc arrives.
//
// Measured 2026-08-25 without it, on openkal-musl over openkal-macos:
//
// ld64.lld: error: library not found for -lSystem
// ld64.lld: error: undefined symbol: clock_gettime_nsec_np
//
// — the failure the macOS branch's own comment predicts, caused by this
// branch discarding the line that prevents it. Empty everywhere else,
// so no other target's link line moves.
f.ld = std::format("{}{}{}{}{}{}", full_static, graphLd, platformAnchor,
link_intent_ld, user_ldflags, link_extra);
f.ldC = f.ld; // no C++ runtime token on this line

Expand Down
Loading
Loading