Skip to content

Commit 89ac910

Browse files
committed
fix(link): 命令长度上限从架构上消掉,不再补第八个洞 (2026.8.5.4)
mcpp-index 的 opencv-module 在 windows 上 LNK1170 —— 这是同一族缺陷的**第七次**。 架构分析见 .agents/docs/2026-08-06-command-length-architecture.md。 前七次:#247(CreateProcess 32 KiB)、#261 两处(cmd.exe 8191)、#274(argv 50781 字符,失败是裸 127)、#344(POSIX MAX_ARG_STRLEN 128 KiB)、2026.8.5.3(link.exe 响应文件单行 128 KiB)、本次。共同形状: - **发现方式永远是崩溃**,且崩在构建最后一步(本次:编译完 356 秒之后); - **失败不可归因** —— 三种报错谁都不说是哪条边; - **触发者从来不是「写了很长的命令」**,而是无关改动:#344 修缓存正确性、#274 改 错误粒度、本次是**把 CI 的 pin 抬过 2026.8.3.4**。 每次修完都在注释里写「构建系统不该有靠崩溃才发现的规模上限」,然后换个地方再犯。 ── 为什么现在才出现 ────────────────────────────────────────────────────── #344 给每个依赖的对象加了一层包目录(缓存正确性要求),路径因此变长 —— 同一条边 在 linux 上从 56 840 涨到 161 687 字节。而 mcpp-index 的 CI 一直 pin 在 **2026.8.3.3**,正好是那之前一版,windows 腿从没用长路径链接过。抬 pin 才第一次撞到。 ── P1:让长度不再是变量 ───────────────────────────────────────────────── 2026.8.5.3 把 mcpp**自己**写的响应文件改成按行分隔。但 clang 作为 driver 时会 **再生成一个**响应文件转发给链接器,那个是单行的 —— 我们改不到它。所以 windows 上的 clang 链接改用 `-fuse-ld=lld`。 **不是 workaround**:lld 用 LLVM 的 tokenizer 解析响应文件,**没有单行上限**,消掉 的是一整类而不是把数字调大;路径也缩不短(那层包目录正是 #344 需要的);而且 **linux 与 macOS 早就在用 lld**,windows 是唯一还在用系统链接器、也是唯一有单行 上限的平台 —— 这是消除平台不一致。原生 cl.exe 保持 link.exe(那里响应文件是我们 自己写的,2026.8.5.3 已覆盖)。 ── P2:上限进表(新模块 cmdlimits.cppm)────────────────────────────────── 根因是命令构造层对「这条命令要穿过哪些通道、各自上限多少」一无所知,而这份知识 只在注释和 CHANGELOG 里 —— 是「同一决策 N 处推导」的镜像:**一个关键约束在零处 被表达**。 表里除字节数还记**症状**:这一族最贵的从来不是修,是**认出**。 `Argument list too long` / `LNK1170` / 裸 `127` 三种表现毫无共同点。新增通道必须 回答「你的上限是多少」,与 directives::kTable 里「Scope 是必填字段」同一手法。 ── P3:计划期拦截并指名道姓 ──────────────────────────────────────────── 生成完 build.ninja 统一扫描,超限时报出边名/通道/实测字节/上限/解法/文档路径, 且发生在**还没编译任何东西**的时候。 实施中纠正两处判断: - 校验点选「manifest 生成后统一扫描」而非「逐 emit site 插桩」—— 后者在新增一种边 时没人会想起来加,而那正是前七次的漏法; - **phony 必须排除**,否则会误报到 **#274 的修复本身**:它为解决 argv 超限,正是把 几千个目标收进一条 phony 聚合边,而 phony 根本没有 command。走 rspfile 的规则 同样豁免。判据是「rule 有 command 且不走 rspfile」。 ── 测试 ──────────────────────────────────────────────────────────────── 单测 test_cmdlimits 8 条:每个通道都在表里、数字是实测的那些(MAX_ARG_STRLEN 是 32 页,不是谁都会先想到的 2 MiB ARG_MAX)、每条都记了症状与解法、诊断含边名/字节/ 上限/解法/文档路径。 **没做超限 e2e**:P1 之后本地造不出自然超限的边,要造只能人为破坏 rspfile 规则, 那测的是被破坏的代码而不是真实路径。windows 的真实验证由 mcpp-index CI 承担。 全量单测 58/58;mcpp 自身 351 条边零误报。 ── 其他 ──────────────────────────────────────────────────────────────── 内带 xlings 升到 2026.8.5.2,.github/ 下 16 处 pin 由 check_version_pins.sh 校验同步。 (校验脚本当场抓到 7 处不同步,包括带 v 前缀的 5 处;并拦下了我一次误改 —— 全局 替换差点把它自己注释里的**历史记录** available: 2026.8.5.1 也改掉。)
1 parent 9d4995e commit 89ac910

17 files changed

Lines changed: 541 additions & 22 deletions
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
# 命令长度:把「靠崩溃发现的规模上限」从架构上消掉
2+
3+
> 状态:**已实施(2026.8.5.4)**
4+
> 触发:mcpp-index 的 `opencv-module` 在 windows 上 `LNK1170`,这是同一族缺陷的**第七次**
5+
> 涉及:`src/build/ninja_backend.cppm``src/build/flags.cppm`、新模块 `src/build/cmdlimits.cppm`
6+
7+
---
8+
9+
## 0. 为什么这次不该再补一个洞
10+
11+
同一族缺陷,七次:
12+
13+
| # | 版本 | 谁超了 | 撞的是什么上限 | 当时的修法 |
14+
|---|---|---|---|---|
15+
| 1 | #247 | 链接命令内联 `$in`,数千对象 | Windows `CreateProcess` **32 KiB** | windows 的链接规则改走 rspfile |
16+
| 2 | #261 | scan 规则经 shell 重定向 → 被 `cmd /c` 包裹 | `cmd.exe` **8191** | 改用 `clang-scan-deps -o`,去掉包裹 |
17+
| 3 | #261 | 编译/扫描内联无界 `-I` 列表 | 同上 | windows 的这些规则也改走 rspfile |
18+
| 4 | #274 | `mcpp test` 的显式 ninja 目标集(FFmpeg 2281 个单元 → argv **50 781** 字符) | `cmd.exe` **8191**,失败是**裸 127** | 目标集改成 phony 聚合边 |
19+
| 5 | #344 | 对象路径变长(每依赖多一层包目录),同一条边 56 840 → **161 687** 字节 | POSIX `MAX_ARG_STRLEN` **128 KiB**(ninja 用 `sh -c`,整条命令是一个 argv 项) | 链接/归档**全平台**改走 rspfile |
20+
| 6 | 2026.8.5.3 | rspfile 把所有对象写在**一行** | `link.exe` 响应文件**单行 128 KiB** | `rspfile_content = $in_newline` |
21+
| 7 | 本次 | clang driver 读完我们的 rspfile,**又生成一个单行的**给 link.exe | 同上 | ← 本文档 |
22+
23+
七次的共同形状:
24+
25+
- **发现方式永远是崩溃**,而且崩在**构建的最后一步**(第 7 次:编译 356 秒之后)。
26+
- **失败不可归因**:`posix_spawn: Argument list too long` 不说哪条边;`LNK1170` 不说哪个 target;`cmd /c` 那次是裸 `127`,ninja 和 mcpp 都没机会打印任何东西。
27+
- **触发者从来不是"写了很长的命令"**,而是一个看似无关的改动:#344 是修缓存正确性,#274 是改错误报告粒度,本次是**把 CI 的 pin 从 2026.8.3.3 抬到 2026.8.5.x**
28+
29+
每次修完,都在注释里写下"构建系统不该有一个靠崩溃才发现的规模上限"——然后下一次换个地方再犯。
30+
31+
**所以问题不在任何一个上限,而在于:命令构造层对「这条命令要经过哪些通道、每个通道的上限是多少」一无所知,而这份知识只存在于注释和 CHANGELOG 里。**
32+
33+
## 1. 根因:三条结构性缺陷
34+
35+
### R1. 构造层与执行通道之间没有契约
36+
37+
`ninja_backend` 负责拼命令,但一条命令实际要穿过的通道是:
38+
39+
```
40+
mcpp 拼出的规则文本
41+
→ ninja 展开(可能内联,可能写 rspfile)
42+
→ 进程创建(CreateProcess / posix_spawn / sh -c)
43+
→ 工具自身(driver 可能再写一个 rspfile 转发给 linker)
44+
→ 最终工具(link.exe / lld / ar)
45+
```
46+
47+
每一层都有自己的上限,**而且互不相同**。构造层不知道自己产出的东西会经过哪几层,于是「加一层包目录」这种改动无法被任何机制提醒。
48+
49+
这与本仓库反复付过学费的「同一决策在 N 处推导」是同一类问题的镜像:**一个关键约束在零处被表达**
50+
51+
### R2. 上限是叙述,不是数据
52+
53+
mcpp 已经有成熟的表驱动范式:
54+
55+
- `CommandDialect` —— 一个 flag 怎么拼(gnu / msvc)
56+
- `BmiTraits` —— BMI 的形态与引用方式
57+
- `directives::kTable` —— build.mcpp 的指令(一行一条指令,解析/缓存/落盘全由该行驱动)
58+
59+
唯独「执行通道 → 上限」没有表。它散落在七处注释里,每处只讲自己那次。没有任何地方能回答「windows 上一条链接命令的可用预算是多少」。
60+
61+
### R3. 校验发生在运行期,而且是别人的运行期
62+
63+
上限是在 **ninja 执行边****link.exe 解析文件** 时才撞上的。那时:
64+
65+
- 已经花掉了全部编译时间;
66+
- 报错的是别人的程序,信息里没有 mcpp 的上下文(哪个 target、哪个包、多少个对象);
67+
- mcpp 没有介入的机会。
68+
69+
而 mcpp **在生成 build.ninja 时就完全知道**每条边的输入个数与路径长度。校验点选错了。
70+
71+
## 2. 设计
72+
73+
三条原则,对应三条根因。
74+
75+
### P1. 让长度不再是变量(结构性消除 > 阈值调大)
76+
77+
凡是可能随项目规模**无界增长**的载荷(对象列表、include 列表、库列表),必须满足:
78+
79+
1. 走响应文件,不进命令行;
80+
2. 响应文件**按行分隔**;
81+
3. 下游工具对响应文件**没有单行上限**
82+
83+
第 3 条是本次新增的认识,也是前六次都没覆盖到的:**我们控制不了 driver 再生成的那个文件**。唯一的解法是让最终工具不带这个限制。
84+
85+
因此:**windows 上的 clang 链接改用 `-fuse-ld=lld`**
86+
87+
> 这不是"换个工具绕过去"。理由有三:
88+
> - lld 通过 LLVM 的 tokenizer 解析响应文件,**没有单行上限**——是消掉一整类,不是把某个数字调大;
89+
> - 路径本身缩不短:per-package 那层目录正是 #344 需要的,其余是源码树自己的结构;
90+
> - **linux 与 macOS 早就在用 lld**(`kLinkDriverFlags`)。windows 是唯一还在用系统链接器的平台,也是唯一有单行上限的。这是**消除平台不一致**,不是新增特例。
91+
>
92+
> 原生 cl.exe(`isMsvcDialect`)保持 link.exe:那条路径上响应文件是 mcpp 自己写的,2026.8.5.3 已经修好。
93+
94+
### P2. 剩余上限必须是表里的数据
95+
96+
新模块 `src/build/cmdlimits.cppm`,把执行通道与其上限写成一张表:
97+
98+
```cpp
99+
enum class Channel {
100+
NinjaArgv, // ninja 直接创建进程
101+
PosixShell, // ninja 的 `sh -c "<整条命令>"`:整条是一个 argv 项
102+
CmdWrapper, // `cmd /c`(#261 起已在全仓绝迹,留在表里以防复活)
103+
RspContent, // 响应文件总量
104+
RspLine, // 响应文件单行
105+
};
106+
107+
struct Limit {
108+
Channel channel;
109+
std::size_t bytes;
110+
std::string_view where; // 谁施加的
111+
std::string_view symptom; // 撞上时用户会看到什么
112+
std::string_view remedy; // 怎么消掉
113+
};
114+
```
115+
116+
表里同时记录**症状**——因为这一族缺陷最贵的部分从来不是修,而是**认出**它。`Argument list too long`、`LNK1170`、裸 `127` 这三种表现毫无共同点,下一次遇到第四种时,表能把人直接指到这里。
117+
118+
新增一个执行通道时,**必须在表里回答"你的上限是多少"**,否则加不进来——与 `directives::kTable` 里「Scope 是必填字段」同一个手法:把一个容易忘的问题变成结构上绕不过去的字段。
119+
120+
### P3. 在计划期校验,并且指名道姓
121+
122+
`ninja_backend` 发射每条边时,已经持有该边的全部输入。因此:
123+
124+
- 估算该边在**每个它会穿过的通道**上的字节数;
125+
- 与表比对;
126+
- 超限时:**能自动降级就降级**(例如内联 → rspfile),**不能降级就报错**,并给出 target 名、通道、实测字节数、上限、以及表里的 remedy。
127+
128+
关键是**报错时机**:在 `mcpp build` 刚开始、还没编译任何东西的时候,而不是 356 秒之后。
129+
130+
## 3. 实施步骤
131+
132+
| 步 | 内容 | 状态 |
133+
|---|---|---|
134+
| 1 | `-fuse-ld=lld` 用于 windows clang 链接(P1) | ✅ `flags.cppm` |
135+
| 2 | 新模块 `cmdlimits.cppm`:通道表 + 预算/判定/诊断(P2) | ✅ |
136+
| 3 | `ninja_backend` 生成 build.ninja 后统一校验(P3) | ✅ |
137+
| 4 | 单测锁住表与诊断 | ✅ `tests/unit/test_cmdlimits.cpp`(8 条) |
138+
139+
### 实施中修正的两处判断
140+
141+
**(a) 校验点不在「发射每条边」,而在「manifest 生成之后统一扫描」。** 逐点插桩要改每个 emit site,而**新增一种边时没人会想起来加**——这正是前七次的漏法。改为扫描已生成的 manifest:新边当天就被覆盖。
142+
143+
**(b) `phony` 必须排除,否则会误报到 #274 的修复本身。** 一条 `build` 行长 ≠ 命令长:`phony` 根本没有 command。而 #274 为解决 argv 超限,正是把几千个目标收进一条 phony 聚合边——不排除的话,新校验会把那条边报成超限,把解法当成问题。走 rspfile 的规则同样豁免(命令里只有 `@$out.rsp`)。
144+
145+
判据因此是:**该边的 rule 有 command,且不走 rspfile** → 它的输入会进命令行 → 校验。
146+
147+
## 4. 验证
148+
149+
- **步 1**:mcpp-index 的 `opencv-module` / `opencv-module-dnn` 在 windows 上通过。这是当前唯一已知能触发的真实场景——本地无法复现(需要 windows + 那个规模的依赖图)。
150+
- **步 2–3**:单测 `test_cmdlimits`(8 条)锁住表与诊断——每个通道都在表里、数字是实测的那些、每条都记了症状与解法、诊断里含边名/实测字节/上限/解法/文档路径。**没有做超限的 e2e**:P1 之后本地已经造不出自然超限的边(要造只能人为破坏 rspfile 规则,那测的是被破坏的代码而非真实路径)。
151+
- 回归:全量单测 58/58;链接相关 e2e(28 / 47 / 86 / 07 / 148 / 190)绿;mcpp 自身 351 条边**零误报**。
152+
153+
## 5. 明确不做
154+
155+
- **不缩短对象路径**。per-package 那层是 #344 的正确性要求,缩回去就是拿正确性换长度。
156+
- **不给"最大项目规模"设一个文档化的数字**。P1 的目标是让这个数字不存在;凡是还存在的,进表并在计划期校验。
157+
- **不改 `cmd /c`**。#261 起它已在全仓 ninja 规则中绝迹,表里保留一行只是为了它某天复活时有人认得出。
158+
- **本次不动原生 cl.exe 路径**。那里的响应文件是 mcpp 自己写的,2026.8.5.3 已覆盖。

.github/actions/bootstrap-mcpp/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ inputs:
2525
# `package.name`, so one of the two was simply unreachable — and which one
2626
# depended on the machine, which is why CI failed on `compat:lua` on
2727
# Windows and `mcpplibs.capi:lua` on Linux. Never pin below that.
28-
default: '2026.8.5.1'
28+
default: '2026.8.5.2'
2929
cache-target:
3030
description: also restore/save target/ (build artifacts + BMIs)
3131
required: false

.github/actions/setup-macos-llvm/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ inputs:
1515
# Floor imposed by the index, not a routine bump — see
1616
# .github/actions/bootstrap-mcpp/action.yml for why 0.4.69 is required
1717
# (two packages named `lua` in one repo need openxlings/xlings#381).
18-
default: '2026.8.5.1'
18+
default: '2026.8.5.2'
1919

2020
runs:
2121
using: composite

.github/workflows/bootstrap-macos.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
# Dormant (workflow_dispatch only), but kept in step with the rest —
1818
# check_version_pins.sh holds it there. Floor: 0.4.69, below which the
1919
# index cannot resolve two packages that share a short name.
20-
XLINGS_VERSION: '2026.8.5.1'
20+
XLINGS_VERSION: '2026.8.5.2'
2121
steps:
2222
- uses: actions/checkout@v4
2323

.github/workflows/ci-fresh-install.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ jobs:
152152
env:
153153
XLINGS_NON_INTERACTIVE: '1'
154154
run: |
155-
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.8.5.1
155+
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.8.5.2
156156
echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH"
157157
158158
- name: Install mcpp and config mirror
@@ -292,7 +292,7 @@ jobs:
292292

293293
- name: Install xlings + mcpp
294294
run: |
295-
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.8.5.1
295+
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.8.5.2
296296
# Deliberately NOT writing to $GITHUB_PATH here. On container
297297
# images that declare no PATH in their config (opensuse/
298298
# tumbleweed), appending a single dir to GITHUB_PATH makes the
@@ -363,7 +363,7 @@ jobs:
363363
# (older ones carry minos=15 and refuse to start).
364364
# v0.4.51+: in-process sha256 — this image has no sha256sum
365365
# binary, so pinned fetches failed before it.
366-
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.8.5.1
366+
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.8.5.2
367367
echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH"
368368
369369
- name: Install mcpp and config mirror

.github/workflows/ci-linux-e2e.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ jobs:
123123
124124
- name: Bootstrap xlings + released mcpp
125125
run: |
126-
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.8.5.1
126+
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.8.5.2
127127
export PATH="$HOME/.xlings/subos/current/bin:$PATH"
128128
xlings update
129129
xlings install mcpp -y -g

.github/workflows/cross-build-test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ jobs:
118118
# release assets were uploaded in a broken state (records present,
119119
# blobs missing → 404 on GET); re-uploaded clean. The stale-INDEX
120120
# half is handled by the marker-clear below.
121-
XLINGS_VERSION: '2026.8.5.1'
121+
XLINGS_VERSION: '2026.8.5.2'
122122
run: |
123123
tarball="xlings-${XLINGS_VERSION}-linux-x86_64.tar.gz"
124124
curl -fsSL -o "/tmp/${tarball}" \
@@ -255,7 +255,7 @@ jobs:
255255
- name: Bootstrap mcpp via xlings
256256
env:
257257
XLINGS_NON_INTERACTIVE: '1'
258-
XLINGS_VERSION: '2026.8.5.1'
258+
XLINGS_VERSION: '2026.8.5.2'
259259
run: |
260260
tarball="xlings-${XLINGS_VERSION}-linux-x86_64.tar.gz"
261261
curl -fsSL -o "/tmp/${tarball}" \

.github/workflows/release.yml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ jobs:
9696
# Pin xlings to a known-good version. The upstream install
9797
# script always grabs `latest` (no version override), so we
9898
# download + self-install manually to avoid broken releases.
99-
XLINGS_VERSION: '2026.8.5.1'
99+
XLINGS_VERSION: '2026.8.5.2'
100100
run: |
101101
if [ ! -x "$HOME/.xlings/subos/default/bin/xlings" ]; then
102102
tarball="xlings-${XLINGS_VERSION}-linux-x86_64.tar.gz"
@@ -288,7 +288,7 @@ jobs:
288288
- name: Bootstrap mcpp via xlings
289289
env:
290290
XLINGS_NON_INTERACTIVE: '1'
291-
XLINGS_VERSION: '2026.8.5.1'
291+
XLINGS_VERSION: '2026.8.5.2'
292292
run: |
293293
tarball="xlings-${XLINGS_VERSION}-linux-x86_64.tar.gz"
294294
curl -fsSL -o "/tmp/${tarball}" \
@@ -358,11 +358,11 @@ jobs:
358358
# below are pinned to the same version as XLINGS_VERSION; they are
359359
# NOT interpolated from it, so check_version_pins.sh scans for them
360360
# explicitly (they were absent from the old lock-step comment).
361-
XLA="xlings-2026.8.5.1-linux-aarch64.tar.gz"
361+
XLA="xlings-2026.8.5.2-linux-aarch64.tar.gz"
362362
if curl -fsSL -o "/tmp/$XLA" \
363-
"https://github.com/openxlings/xlings/releases/download/v2026.8.5.1/$XLA"; then
363+
"https://github.com/openxlings/xlings/releases/download/v2026.8.5.2/$XLA"; then
364364
tar -xzf "/tmp/$XLA" -C /tmp
365-
XLBIN=$(find /tmp/xlings-2026.8.5.1-linux-aarch64 -path '*/bin/xlings' -type f | head -1)
365+
XLBIN=$(find /tmp/xlings-2026.8.5.2-linux-aarch64 -path '*/bin/xlings' -type f | head -1)
366366
if [ -n "$XLBIN" ]; then
367367
mkdir -p "$STAGING/$WRAPPER/registry/bin"
368368
cp "$XLBIN" "$STAGING/$WRAPPER/registry/bin/xlings"
@@ -440,7 +440,7 @@ jobs:
440440
- name: Bootstrap mcpp via xlings
441441
env:
442442
XLINGS_NON_INTERACTIVE: '1'
443-
XLINGS_VERSION: '2026.8.5.1'
443+
XLINGS_VERSION: '2026.8.5.2'
444444
run: |
445445
if [ ! -x "$HOME/.xlings/subos/default/bin/xlings" ]; then
446446
WORK=$(mktemp -d)
@@ -622,7 +622,7 @@ jobs:
622622
shell: bash
623623
env:
624624
XLINGS_NON_INTERACTIVE: '1'
625-
XLINGS_VERSION: '2026.8.5.1'
625+
XLINGS_VERSION: '2026.8.5.2'
626626
run: |
627627
# Captured before the `cd` below, in POSIX form: this step never
628628
# returns to the workspace, and GITHUB_WORKSPACE is a backslash

.xlings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"workspace": {
3-
"mcpp": "2026.8.5.2"
3+
"mcpp": "2026.8.5.3"
44
}
55
}

CHANGELOG.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,32 @@
33
> 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。
44
> 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)
55
6+
## [2026.8.5.4] — 2026-08-06
7+
8+
命令长度这一族缺陷的**第七次**,这次不再补洞。架构分析见 `.agents/docs/2026-08-06-command-length-architecture.md`
9+
10+
### 修复
11+
12+
- **windows 上的 clang 链接改用 lld,消掉最后一个随对象数增长的上限。** 2026.8.5.3 把 mcpp**自己**写的响应文件改成按行分隔,但 clang 作为 driver 时会**再生成一个**响应文件转发给链接器,而那个是单行的 —— 我们改不到它。mcpp-index 的 `opencv-module` 因此在 2026.8.5.3 上仍然 `LNK1170`,而且是在编译完 356 秒之后。
13+
14+
**为什么现在才出现**:#344 给每个依赖的对象加了一层包目录(缓存正确性的要求),路径因此变长——同一条边在 linux 上从 56 840 涨到 161 687 字节。而 mcpp-index 的 CI 一直 pin 在 **2026.8.3.3**,正好是那之前一版,所以它的 windows 腿从没用长路径链接过。**把 pin 抬上来才第一次撞到**
15+
16+
**为什么这不是 workaround**:lld 用 LLVM 的 tokenizer 解析响应文件,**没有单行上限** —— 消掉的是一整类,不是把某个数字调大;路径也缩不短(那层包目录正是 #344 需要的,其余是源码树自身结构)。而且 **linux 与 macOS 早就在用 lld**,windows 是唯一还在用系统链接器、也是唯一有单行上限的平台 —— 这是消除平台不一致。原生 cl.exe 保持 link.exe:那条路径上响应文件是我们自己写的。
17+
18+
### 改进
19+
20+
- **命令长度预算现在是一张表,不是七处注释(`src/build/cmdlimits.cppm`)。** 前七次每次都在注释里写下「构建系统不该有靠崩溃才发现的规模上限」,然后换个地方再犯。根因是:命令构造层对「这条命令要穿过哪些通道、每个通道上限多少」**一无所知**,而这份知识只存在于注释与 CHANGELOG 里 —— 是「同一决策在 N 处推导」的镜像:**一个关键约束在零处被表达**
21+
22+
表里除了字节数,还记**症状**:这一族最贵的从来不是修,而是**认出**`posix_spawn: Argument list too long``LNK1170`、cmd.exe 的裸 `127` 三种表现毫无共同点,下次遇到第四种时表能直接把人指过来。新增执行通道必须回答「你的上限是多少」,与 `directives::kTable` 里「Scope 是必填字段」同一个手法。
23+
24+
- **超限改为在计划期拦下并指名道姓。** 以前是 ninja 或 link.exe 在构建末尾崩,而报错的是别人的程序,信息里没有 mcpp 的上下文(哪条边、哪个包、多少对象)。现在生成完 `build.ninja` 就统一扫描,超限时报出**边名、通道、实测字节、上限、解法、文档路径**,且发生在还没编译任何东西的时候。
25+
26+
两处判断在实施中被纠正:校验点选在「manifest 生成后统一扫描」而不是「逐个 emit site 插桩」——后者在新增一种边时没人会想起来加,而那正是前七次的漏法;`phony` 必须排除,否则会误报到 **#274 的修复本身**(它为解决 argv 超限,正是把几千个目标收进一条 phony 聚合边,而 phony 根本没有 command)。
27+
28+
### 其他
29+
30+
- 内带 xlings 升到 **2026.8.5.2**(`.github/` 下 16 处 pin 由 `check_version_pins.sh` 机器校验同步)。
31+
632
## [2026.8.5.3] — 2026-08-05
733

834
### 修复

0 commit comments

Comments
 (0)