Skip to content

Commit e1fe32d

Browse files
committed
Merge remote-tracking branch 'origin/main' into codex/pr387-merge-check
# Conflicts: # tests/unit/test_compile_commands.cpp
2 parents f283884 + 8625c45 commit e1fe32d

20 files changed

Lines changed: 1971 additions & 64 deletions

.agents/docs/2026-08-08-machine-readable-output-protocol-design.md

Lines changed: 411 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# 机器可读输出协议 —— 拆分实施计划
2+
3+
> 设计:`2026-08-08-machine-readable-output-protocol-design.md`(对 RFC #379 的核对与修正)
4+
> 已定:未知格式走 **stderr + rc=2**;`pack --format` **声明为例外**
5+
6+
## 顺序与理由
7+
8+
设计文档 §4.5 把 CDB 引号缺陷排在阶段 1 之前,理由是「协议做得再好,也救不了一个内容
9+
本身就坏掉的出口」。照此排:
10+
11+
```
12+
W0 CDB 引号(§4.5) —— 今天就在坏,且与协议正交,先修
13+
W1 stdout 归属(阶段 0) —— 未知选项/未知值统一 stderr + rc=2
14+
W2 mcpp.wire 模块(阶段 1) —— envelope + destructive + --protocol-version
15+
W3 --format 归一(阶段 2) —— --json 永久别名,入口一处归一
16+
W4 接入(阶段 3a) —— self env / xpkg parse / cache list
17+
```
18+
19+
W4 之后的 `metadata` / `--configure-only`(阶段 3b/3c)**不在本次范围** —— 它们是新增
20+
能力而非契约统一,且依赖 #372 的拆分结论。
21+
22+
## W0 —— CDB 的 `arguments` 带 shell 引号
23+
24+
**File:** `src/build/compile_commands.cppm`
25+
26+
- [ ] **Step 1** 红:带空格路径 + llvm 的单测,断言
27+
① 任何 token 不以引号开头/结尾;② 带空格的路径是**一个** token
28+
- [ ] **Step 2** 跑,确认 FAIL(今天两条都不满足)
29+
- [ ] **Step 3**`split_flags` 认引号。**顺序陷阱**:ninja`$ ` 反转义必须发生在
30+
引号****,否则被引号包住的空格先把 token 切断 —— 那正是现在的 bug
31+
- [ ] **Step 4** 绿
32+
- [ ] **Step 5** Commit
33+
34+
判据不能写成「clangd 能用了」——在不含空格的路径上恒真,正是它至今没被发现的原因。
35+
36+
## W1 —— stdout 归属
37+
38+
**Files:** `src/cli.cppm``src/main.cpp`(+ 依赖 `mcpplibs.cmdline` 的处置)
39+
40+
- [ ] **Step 1**:e2e 断言未知**选项**与未知****在通道(stderr)与退出码(2)上一致
41+
- [ ] **Step 2** 跑,FAIL(今天:未知选项 → stdout/rc=1;未知值 → stderr/rc=2)
42+
- [ ] **Step 3** 实现。`mcpplibs.cmdline:127``std::println` 写 stdout,在依赖里 ——
43+
**本次不改依赖**,改为 mcpp 侧接管 `ParseResult`:不走 `App::run()` 的内建错误
44+
打印,自己输出到 stderr 并返回 2
45+
- [ ] **Step 4** 绿
46+
- [ ] **Step 5** Commit
47+
48+
## W2 —— `mcpp.wire`(独立模块)
49+
50+
**New:** `src/wire.cppm`
51+
52+
- [ ] **Step 1** 单测:envelope 形状(`schemaVersion` / `kind` / `destructive` /
53+
`mcpp.version` / `mcpp.protocol{min,max}` / `data` / `diagnostics`)
54+
- [ ] **Step 2** 实现。`Diagnostic/Position/Range/Severity` 与 envelope 构造从 #372
55+
`src/ide/model.cppm``src/ide/snapshot.cppm` 提升(设计文档 §5-B)
56+
- [ ] **Step 3** `mcpp --protocol-version`:输出 `{min,max}` **加命令 → destructive 静态表**
57+
(设计 §2.3 —— untrusted 门要在执行前知道)
58+
- [ ] **Step 4** golden fixture,且**反向验证过**(改字段名要变红,设计 §4)
59+
- [ ] **Step 5** Commit
60+
61+
## W3 —— `--format` 归一
62+
63+
**Files:** `src/cli.cppm`(入口归一)、各 `cmd_*.cppm`
64+
65+
- [ ] **Step 1** 单测:`--json``--format json` 产出**逐字节相同**
66+
- [ ] **Step 2/3** 入口一处 `--json``--format json`;核心只见 `--format`
67+
- [ ] **Step 4** 绿;`--json` 永久保留、**不打 deprecation 警告**
68+
- [ ] **Step 5** Commit
69+
70+
`pack --format tar|dir` 不动(已定为例外),文档写明。
71+
72+
## W4 —— 接入 envelope
73+
74+
**Files:** `cmd_self.cppm`(新增 `self env --format json`)、`cmd_xpkg.cppm``cmd_cache.cppm`
75+
76+
- [ ] **Step 1** 每个 `kind` 一个 golden fixture(反向验证过)
77+
- [ ] **Step 2** `self env` 覆盖 mcpp-vscode#8 §2.4:MCPP_HOME / registry / xlings home /
78+
index repos / default toolchain
79+
- [ ] **Step 3** `xpkg parse` / `cache list` 包进 envelope(`schemaVersion` 随之而来)
80+
- [ ] **Step 4** 绿
81+
- [ ] **Step 5** Commit
82+
83+
## 平台特化的去处
84+
85+
按要求:平台差异进 `src/platform/`,协议本体独立 `.cppm`
86+
87+
- 协议本体 → **`src/wire.cppm`**(新模块,不依赖任何命令)
88+
- 路径/引号的平台差异 → 已在 `src/platform/`(`env::path_list_separator` 等);W0 的
89+
引号识别是**平台无关**的(引号本身两平台都要剥),不新增平台分支
90+
91+
## 交付
92+
93+
- 全部并入 **PR #385**(与 docs 一起)
94+
- 版本已是 2026.8.8.3(刚发布)⇒ 本次**跳过版本 bump**,除非 CI 要求
95+
- xlings pin 已是 2026.8.8.1(索引最新)⇒ 无需再抬

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

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,44 @@ jobs:
118118
git fetch -q --depth 1 origin "$ref"
119119
git checkout -q FETCH_HEAD
120120
echo "self-hosting $repo @ $ref"
121+
# The clone's .xlings.json declares `workspace.mcpp` — the BOOTSTRAP
122+
# pin, hand-maintained and deliberately lagging the newest release.
123+
# It is scoped to the working directory and beats anything installed,
124+
# so every `mcpp` below resolved the bootstrap version, which the
125+
# fresh-install steps above never installed:
126+
#
127+
# [error] xlings: version '2026.8.6.2' not found for 'mcpp'
128+
# [error] available: 2026.8.8.2
129+
#
130+
# "newest release != bootstrap pin" is the NORMAL state, so this step
131+
# failed on every run from the moment the two diverged — and it is
132+
# weekly, so nothing pointed at it. This step tests the freshly
133+
# installed RELEASED binary against a source tree; the bootstrap pin
134+
# has no standing in that question. install_released_mcpp.sh removes
135+
# it for exactly this reason on the x86_64 legs (its point 1); this
136+
# leg was written separately and never got it.
137+
rm -f .xlings.json
121138
mcpp self config --mirror GLOBAL 2>/dev/null || true
122139
mcpp build --target aarch64-linux-musl
123140
# Absolute: it is used again after `cd /tmp/xlings-src` below.
124141
m=$(find "$PWD/target/aarch64-linux-musl" -type f -path '*/bin/mcpp' | head -1)
125142
file "$m" | grep -q "ARM aarch64" || { echo "expected aarch64 mcpp"; exit 1; }
126143
"$m" --version
144+
# MCPP_HOME must be carried over explicitly: mcpp derives it from the
145+
# BINARY's location, so a binary sitting in /tmp/mcpp-src/target would
146+
# otherwise adopt an empty home and re-bootstrap the whole ecosystem
147+
# instead of reusing what the fresh-install steps above provisioned.
148+
#
149+
# Resolved HERE, before the cd below, and that placement is load-
150+
# bearing: `mcpp` is the shim, so it obeys whatever workspace pin the
151+
# CURRENT DIRECTORY carries — and the xlings checkout declares one too
152+
# (`workspace.mcpp = 2026.8.6.1`, its own bootstrap). Run from there,
153+
# this resolves a version nothing installed, MCPP_HOME comes back
154+
# empty, and the step dies on the guard below instead of on the real
155+
# cause. /tmp/mcpp-src has had its pin removed above, so ask from here.
156+
export MCPP_HOME=$(mcpp self env | awk -F'= *' '/^MCPP_HOME/{print $2; exit}')
157+
echo "reusing MCPP_HOME=$MCPP_HOME"
158+
test -d "$MCPP_HOME" || { echo "could not determine MCPP_HOME"; exit 1; }
127159
git clone --depth 1 https://github.com/openxlings/xlings /tmp/xlings-src
128160
cd /tmp/xlings-src
129161
# "$m", not `mcpp`: the just-built binary is the code under review,
@@ -132,14 +164,6 @@ jobs:
132164
# records, one line further down. It surfaced the same way: a fix for
133165
# an aarch64-only failure in exactly this build could not be
134166
# validated here, because the binary running it predated the fix.
135-
#
136-
# MCPP_HOME must be carried over explicitly: mcpp derives it from the
137-
# BINARY's location, so a binary sitting in /tmp/mcpp-src/target would
138-
# otherwise adopt an empty home and re-bootstrap the whole ecosystem
139-
# instead of reusing what the fresh-install steps above provisioned.
140-
export MCPP_HOME=$(mcpp self env | awk -F'= *' '/^MCPP_HOME/{print $2; exit}')
141-
echo "reusing MCPP_HOME=$MCPP_HOME"
142-
test -d "$MCPP_HOME" || { echo "could not determine MCPP_HOME"; exit 1; }
143167
"$m" build --target aarch64-linux-musl
144168
x=$(find target/aarch64-linux-musl -type f -path '*/bin/xlings' | head -1)
145169
file "$x" | grep -q "ARM aarch64" || { echo "expected aarch64 xlings"; exit 1; }
@@ -162,6 +186,13 @@ jobs:
162186
# is the only place #295 can actually be reproduced.
163187
- name: Build current mcpp source for native regression tests
164188
run: |
189+
# Third site of the same pin, and the reason to remove it here too:
190+
# this job installs `xlings install mcpp` (bare = latest) and NOTHING
191+
# else, so the bootstrap version the checkout pins is never on this
192+
# runner. Obeying the pin here does not select an older builder — it
193+
# selects one that does not exist. Same removal as the self-host step
194+
# above, same reason as install_released_mcpp.sh point 1.
195+
rm -f .xlings.json
165196
mcpp build --target aarch64-linux-musl
166197
self=$(find target/aarch64-linux-musl -type f -path '*/bin/mcpp' | head -1)
167198
test -x "$self"

CHANGELOG.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,50 @@
33
> 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。
44
> 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)
55
6+
## [2026.8.8.4] — 2026-08-08
7+
8+
机器可读输出有契约了。设计与实测见
9+
`.agents/docs/2026-08-08-machine-readable-output-protocol-design.md`,用户文档见
10+
`docs/11-machine-output.md`
11+
12+
### 新增
13+
14+
- **`--format json`:统一的输出信封(`mcpp.wire`)。** 覆盖 `self env``xpkg parse`
15+
`cache list`,含 `schemaVersion` / `kind` / `kindVersion` / `effects` / `data` /
16+
`diagnostics`。信封与 kind 的版本**分开** —— 单一全局版本号会让给 `mcpp.env` 加一个
17+
字段推高客户端为 `mcpp.xpkg` 读到的版本。
18+
19+
- **`mcpp --protocol-version`** 回本 build 支持的信封版本、各 kind 版本,以及
20+
**命令 → 效应**的静态表。它是优化而非地基:在它出现之前的每个 mcpp 上,它自己就是
21+
未知选项,失败与成功同走 stdout —— 所以客户端的判据只能是**正向识别**(stdout 解析
22+
得出 `schemaVersion` + `kind`)。这条写进了文档的第一节。
23+
24+
- **效应集合,不是 `destructive` 布尔。** 实测:全新 `MCPP_HOME``xpkg parse`
25+
`cache list` 什么都不建,`self env` 建 6 项。布尔分不开「mcpp 给自己做初始化」和
26+
「执行工作区里的代码」,而 IDE 的 untrusted 门只在乎后者。
27+
28+
- **`mcpp self env --format json` 走独立只读路径。** 不调 `load_or_init`:客户端问
29+
「东西在哪」不该成为把东西放到那儿的原因。全新 home 上返回完整路径 +
30+
`initialized: false`,创建项数 0(人类路径不变,仍会初始化)。
31+
32+
### 修复
33+
34+
- **CDB 的 `arguments` 带着 shell 引号。** 消费者(clangd)逐字 exec 它,不经 shell,
35+
于是带引号的 token 不是 flag 而是不存在的文件名。Windows 上每个带路径的 flag 都中招
36+
(`shell_quote_arg` 的触发集含反斜杠);带空格的路径上更糟 —— token 在引号**内部**
37+
被切断,一个参数变成两个,其中一个带着永不闭合的开引号。
38+
39+
- **usage 错误不再进 stdout。** 未知选项过去打在 stdout、rc=1、stderr 为空,于是
40+
`mcpp cache list --format json | jq` 拿到的是人类文本。现在未知选项与不支持的值
41+
统一 stderr + rc=2,stdout 一字不写。
42+
43+
### 兼容
44+
45+
- **`--json` 永久保留它的 payload,不打 deprecation 警告。** 拼写兼容不等于 payload
46+
兼容:`cache list --json` 顶层是 `{root, entries}` 且本仓库 e2e 已断言。两种拼写由
47+
同一来源产出,不会漂移。
48+
- `pack --format tar|dir` 是产物形态而非输出格式,**声明为本协议的例外**
49+
650
## [2026.8.8.3] — 2026-08-08
751

852
### 修复

0 commit comments

Comments
 (0)