Skip to content

Commit e84e8fb

Browse files
committed
docs(release): 0.0.102 — changelog, manifest reference, batch-ledger invariants
Version bump plus the three documentation surfaces this batch owes: - CHANGELOG entry covering #261 / #257 / #258 / #254 and what was explicitly left out (#259, and the unknown-key policy split into #263). - docs/05-mcpp-toml.md: the new conditional keys, and — more usefully than a key list — WHY the set is what it is. A conditional section accepts exactly the additive build inputs; linkage/target/profile knobs are excluded because they are inputs to target selection or need override semantics. Includes the per-OS removal idiom and the "off-target entries do not exist, so they cannot warn" rule. - Batch ledger gains a section 6 for cross-batch invariants: the existing "one decision, one derivation" rule with #254/#258 recorded as instances, and the new "failures must be audible" rule with its rationale — three of this batch's four issues were the same silent-degradation shape, in a code base that already practiced the discipline elsewhere but had never made it an invariant new code inherits.
1 parent 0364eb4 commit e84e8fb

5 files changed

Lines changed: 86 additions & 4 deletions

File tree

‎.agents/docs/2026-07-19-issues-230-243-batch-ledger-and-architecture-assessment.md‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,33 @@
117117
- 每落地一个 issue:更新 §1 对应行的状态 + commit,并在 CHANGELOG 追加(带 `#nnn`)。
118118
- 凡"根因在仓外 / 转后续"的,§1 标 ⛔ 并附外部 issue 链接,**不得**在本仓用 band-aid 标记已修。
119119
- 本批次的发布仍走既定链路:release 四平台 → 镜像 xlings-res(gh+gtc)→ xim-pkgindex 索引 → bump bootstrap pin;随后 mcpplibs #79 CI pin 升级解阻塞(#240 是 #79 的合并门)。
120+
121+
---
122+
123+
## 6. 架构不变量(跨批次,持续生效)
124+
125+
### 6.1 同一决策不许两处推导(§5.2 起,持续)
126+
127+
已命中的实例:#242 的 feature 请求集解析×激活两处不自洽;#253 的 per-OS `features`;**0.0.102 新增两例**——
128+
- **#254**:平台选择在 xpkg 通道按宿主编译期常量推导,在 manifest 通道按 resolved target 推导。native 构建 host == target 使两者巧合一致,缺陷因此对三平台 CI 完全不可见。收敛手段是**类型化**(`mcpp.platform.axis`),让"传错轴"成为编译错误而非运行期巧合。
129+
- **#258**:"哪些构建输入可被条件化"在 cfg 轴与 feature 轴各推导一遍,且答案不同。收敛手段是把该集合提炼为**类型**(`BuildInputs`)而非一张手工维护的键表——表会与结构体漂移,类型不会。
130+
131+
判据:当一个决策需要在两处表达时,优先找一个**类型**来承载它,其次才是把两处调用同一个函数。
132+
133+
### 6.2 失败必须响,不许静默降级(0.0.102 新立)
134+
135+
> 任何"因为条件不满足而少做一步"的分支,必须要么返回错误,要么经 `mcpp::diag::degraded` 上报;
136+
> `log::debug` / `log::verbose` **不算**用户可见。
137+
> 丢弃 `std::expected` 返回值(`(void)expr` 或不接收)在本仓视为缺陷。
138+
139+
由来:0.0.102 批次的四条 issue 里有三条的实质是同一失效模式——引擎遇到没准备好的情况时安静地少做一点事。#257 在 clang 上不发 depfile(构建"成功",产物用陈旧 BMI);#258 在条件段静默丢弃 `flags`(构建"成功",规则从未生效);#259 的 sysroot 兜底是死代码且返回值被 `(void)` 丢弃(安装"成功",二进制不能 exec)。
140+
141+
值得注意的是本仓在**别处**早已把这条纪律做得很好(xpkg 未知键 → `xpkgUnknownKeys` + did-you-mean;`scan_overrides` 零命中 → 硬错;死 glob → 告警并点名归属 feature)。纪律一直存在,只是没有被提升为跨模块不变量,于是新代码没有默认继承它。
142+
143+
`diag::degraded` 的 `impact` 参数是这条不变量的执行机制:它强制作者写出"用户会因此遭遇什么"。上述三个 bug 当年缺的恰好就是这句话。
144+
145+
### 6.3 code review checklist 增补
146+
147+
- 每个 `if (cond) return;` / `continue` / `catch` 静默吞掉的分支:用户看得见吗?
148+
- 每个 `std::expected` 返回值:被检查了吗?`(void)` 丢弃需要理由。
149+
- 新增一个"可条件化 / 可传播 / 可覆盖"的字段时:它进的是哪个**类型**?该类型是否已经表达了这个语义?

‎CHANGELOG.md‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,31 @@
33
> 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。
44
> 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)。
55
6+
## [0.0.102] — 2026-07-22
7+
8+
> 四条 issue 一个批次:#261 windows 命令行长度、#257 clang 依赖跟踪缺口、#258 条件段表达力、#254 host/target 双轴不自洽。贯穿主线是**同一决策不许两处推导**,以及本批次新立的第二条不变量**失败必须响,不许静默降级**。设计见 `.agents/docs/2026-07-22-v0.0.102-batch-254-261-design.md`,issue 裁决见 `.agents/docs/2026-07-22-issue-triage-254-261.md`。
9+
10+
### 修复
11+
12+
- **windows scan-deps 撞 8191 字符上限**(#261):clang 扫描规则此前用 shell 重定向产出 P1689 JSON,ninja 在 windows 走 CreateProcess 不解释 `>`,于是整条命令被 `cmd /c` 包裹——而 cmd.exe 的命令行上限是 8191,只有 CreateProcess 的 1/4。改用 `clang-scan-deps -o`(LLVM 17+,两个自带工具链均实测可用),重定向与包裹一并消失,与 GCC 的 `-fdeps-file=`、MSVC 的 `/scanDependencies` 三条分支形态统一。`cmd /c` 自此在全仓 ninja 规则中绝迹。
13+
- **windows 编译/扫描命令行无长度兜底**(#261):`local_include_flags()` 每依赖一个 `-I`、无上界,而 `cxx_module`/`cxx_object`/`c_object`/`asm_object`/`cxx_scan` 全部内联该载荷;扫描命令包住编译命令,只是恰好先炸。把 #247 给链接规则的 response file 推广到所有携带无界载荷的规则(gcc/clang 驱动与 cl.exe 均展开 `@file`,clang-scan-deps 会把 `--` 之后的 `@file` 透传给驱动;NASM 因为拼写是 `-@ file` 而排除)。顺带把 `escape_flag_path` 改用 `generic_string()`——这是 #247 那个坑往下一层:response file 内容按 GNU 文法分词,反斜杠是转义符,windows 的 `-I` 路径一旦离开命令行就会丢分隔符。POSIX 逐字节不变。
14+
- **clang 路径 purview `#include` 不触发重建**(#257):编辑模块接口 purview 内 `#include` 的文件后,构建静默复用陈旧 BMI,导入方对着旧接口编译——最差的一类失效,错误结果看起来像一次正常的增量构建。根因是 0.0.97 把两个决策捆在一个谓词里:**是否发 depfile** 与 **是否剥离 GCC `-fmodules` 附加的 reversed make-rules**。实测两个自带工具链,clang 的 module-TU depfile 就是一条普通规则(`x.o: x.cppm ops.inc`),没有任何需要过滤的东西——当年那道闸在防一个不存在的形状,代价是把与它捆在一起的正确性契约一起关掉了。现拆为 `posixDepfile`(所有 POSIX 非 MSVC)与 `needsGnuModuleFilter`(仅 GCC)。同时补上同一处不对称的另一半:`c_object`/`asm_object` 此前在**任何**工具链上都没有 depfile。windows 非 MSVC(mingw gcc / 托管 clang)仍无 depfile(GCC 过滤器依赖 awk),但现在经 `diag::degraded` 明确上报影响,不再静默。
15+
- **xpkg per-OS 段按宿主而非目标拼接**(#254):per-OS 段的 sources/flags/deps 与 xpm 版本/资产表全部按 `xpkg_platform` 这个**编译期宿主常量**选取,而 `[target.'cfg(...)']` 按**解析后的目标**求值——同一决策两处推导。交叉编译时依赖包因此被拼进宿主那条腿。原生构建 host == target 恰好掩盖了它,所以三平台 CI 从未发现。
16+
17+
### 新增
18+
19+
- **`mcpp::diag` 统一降级/告警通道**:`degraded()` 强制填写 `impact`——作者必须回答"用户会因此遭遇什么",而这正是每一个静默降级 bug 当年缺的那句话。记录按整条载荷去重、报告即渲染(告警与 `ui::status` 的交织顺序、以及提前返回前已报内容都不变),`--strict` 提升策略收敛到单点(此前在 `prepare.cppm` 里 copy-paste 了 5 处)。告警内容首次可单测。`prepare.cppm` 的 8 处裸 `println(stderr, "warning: ...")` 已迁入;渲染仍走 `ui::warning`,输出逐字节不变。
20+
- **`[target.'cfg(...)'.build]` 支持 per-glob `flags` 与 `include_dirs`**(#258,xpkg `target_cfg` 同步):此前 `sources` 可按 OS 条件化而 `flags` 不能,于是一份覆盖三 OS 的 manifest 必须让每个 OS 都看到另外两个 OS 的 flag 条目,而它们按构造必然零命中。vendored-opencv 移植为此付出 **703 个 stub 文件**(唯一用途是给 windows TU 制造 OS-唯一路径好让全局 flag 表 key 上去)、32 条指向它们的 glob,以及每次构建 ~23 条结构性死 glob 告警。条件条目追加在 base 之后,GNU last-wins 使**撤销**可表达(windows 需要 `-UHAVE_UNISTD_H` 对抗 base 的 `-D`,而无条件覆盖层做不到——`-U` 反条目自身也需要按 OS 条件化,递归回同一缺口)。死 glob 告警由**结构**消除:不匹配当前 OS 的条目根本不进 `globFlags`,与 #253 在 feature 轴上的做法同构;issue 里提的 `optional = true` 逃生口**明确不做**,那会成为第二套抑制机制。
21+
- **`mcpp.platform.axis`:host / target 两轴成为不同类型**(#254):互不可转换,且都不能由裸字符串构造;API 声明自己要哪一轴,调用点必须**指名**自己给的是哪一轴。`synthesize_from_xpkg_lua` 的平台参数去掉宿主默认值改为必填——静默默认正是这个 bug 得以存活的方式。三段 triple 与 xpkg 的词汇差异("macos" vs "macosx")收进 `TargetPlatform::for_os`。分类:依赖包 manifest 合成与版本/资产选择走 **target**;工具链版本列举走 **host**(其注释本就写明了理由);`--all-os` lint 走刻意起得别扭的 `for_lint_of()`。
22+
- **`BuildInputs` 类型**(#258 的架构面):cfg 轴与 feature 轴此前各自手挑可条件化的字段子集,**且挑得不一样**(cfg 拿 cflags/cxxflags/ldflags/sources,feature 拿 sources/defines/flags)。"哪些构建输入可被条件化"这一决策被提炼成一个**类型**而非一张要靠人记得更新的表。入选判据两条:合并语义是**追加**;消费点在条件合并**之后**。据此排除选型轴(`target` 用来*选定* triple,而谓词要靠该 triple 求值——构造性循环)与策略标量(需 last-wins,是另一场设计);`generatedFiles` 因为是写磁盘的**副作用动作**而非输入、`featureDefines` 因为是沿 Public 边**传播的接口贡献**而非私有构建输入,各自按范畴排除。`BuildConfig` 以**继承**方式承载(该字段在 ~150 处被读,嵌套只会是 150 次无收益的机械改动),`ConditionalConfig` 则**嵌套**——保证需要落在那里。合并收敛为单一 `append(BuildInputs&, const BuildInputs&)`。
23+
- **#256 clang 模块运算符模板 canary + 文档**:mcpp 侧无缺陷(`--precompile` 成功,崩溃在 clang 前端的导入侧),但 mcpp 自带 LLVM,且该 hazard 正落在本项目推荐的模块包模式上。文档给出可操作规则(导出的运算符模板,所有模板参数应由**第一个实参**绑定;否则改为对整个推导操作数类型建模)与可直接套用的改写范式。e2e 150 是**状态** canary 而非通过/失败契约:期望表记录 LLVM 20-22 崩、18-19 正常,一旦现实与期望不符即失败——未来某次 clang bump **修好**它与**再弄坏**它同样是需要被看见的信号。
24+
25+
### 备注
26+
27+
- 新增不变量写入批次总账:**任何"因条件不满足而少做一步"的分支,必须要么返回错误,要么经 `diag::degraded` 上报;`log::debug`/`log::verbose` 不算用户可见;丢弃 `std::expected` 返回值视为缺陷。**
28+
- e2e 新增 148(64 个 include dir 的宽 include 表,POSIX 验内联形态、windows 验 response file)、149(条件段 per-glob flags 四象限:命中生效/覆盖 base 的 removal/非命中零告警/真死 glob 仍告警)、150(#256 canary);118 去掉 `# requires: gcc` 并加 clang 腿。
29+
- **未包含**:#259(根因在 xlings 侧的 dep 静默丢弃,调查结论已发 issue 评论;mcpp 侧另发现 sysroot 兜底是死代码——`resolve_xpkg_path("xim:glibc")` 缺版本必然失败且两处都丢弃返回值——一并另行安排);manifest 未知键策略五处不一致([#263](https://github.com/mcpp-community/mcpp/issues/263),有兼容性面,与 #258 无依赖)。
30+
631
## [0.0.101] — 2026-07-20
732

833
> #253:feature 模型两缺口收口——per-feature per-glob `flags` + per-OS `features` 语义锁定,解锁 compat.opencv `dnn` off-linux 腿并消除 feature-off 构建的死 glob 告警。设计见 `.agents/docs/2026-07-20-issue-253-feature-flags-and-per-os-features-design.md`。

‎docs/05-mcpp-toml.md‎

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -376,12 +376,39 @@ when you need arch/env conditions or combinators.
376376
- **Keys**: `dependencies` / `dev-dependencies` / `build-dependencies`, and
377377
`build` with `cflags` / `cxxflags` / `ldflags` / `sources` (mcpp 0.0.95+ —
378378
conditional source globs, e.g. gating `src/x86/**/*.asm` behind
379-
`cfg(arch = "x86_64")`; `!`-exclusion globs work here too).
379+
`cfg(arch = "x86_64")`; `!`-exclusion globs work here too), plus `flags` and
380+
`include_dirs` / `include_dirs_after` (mcpp 0.0.102+).
381+
- **What `build` accepts is exactly the set of *additive build inputs*** — the
382+
things that combine by appending and are consumed after the predicate is
383+
evaluated. `linkage`, `target`, and the profile knobs are deliberately not
384+
among them: they are *inputs to* target selection (conditioning `target` on
385+
a predicate evaluated against `target` is circular), or they need
386+
override-rather-than-append semantics.
380387
- **Evaluated against the resolved target** — the `--target` triple for a cross
381388
build, otherwise the host. So a native Linux build never even *downloads* a
382389
`[target.windows]` dependency.
383390
- **Precedence**: an exact-triple table wins over a `cfg`/alias table; multiple
384-
matching predicate tables have their flags concatenated.
391+
matching predicate tables have their flags concatenated. Conditional entries
392+
are appended **after** the unconditional `[build]` ones, so under GNU
393+
"last flag wins" a conditional rule overrides a broader unconditional one.
394+
That is what makes a per-OS **removal** expressible:
395+
396+
```toml
397+
[build]
398+
flags = [{ glob = "third_party/zlib/**", defines = ["HAVE_UNISTD_H=1"] }]
399+
400+
# clang-MSVC has no <unistd.h>: undo the base define, add the windows one.
401+
[target.'cfg(windows)'.build]
402+
flags = [{ glob = "third_party/zlib/**",
403+
defines = ["NO_FSEEKO"], cflags = ["-UHAVE_UNISTD_H"] }]
404+
```
405+
406+
- **A conditional `flags` entry that does not match the current target does not
407+
exist at all**, so it cannot produce a "glob matched no source file" warning.
408+
One manifest can therefore carry all three OSes' flag tables without any of
409+
them generating noise on the other two — the same way an inactive feature's
410+
entries simply are not there. A zero-hit glob in the *unconditional* table
411+
still warns, because there it is a real defect.
385412
- **`toolchain` / `linkage` are exact-triple only** — they describe one specific
386413
cross target, so put them under `[target.<triple>]` (above), not under a bare
387414
alias or `cfg(...)`.

‎mcpp.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mcpp"
3-
version = "0.0.101"
3+
version = "0.0.102"
44
description = "Modern C++ build & package management tool"
55
license = "Apache-2.0"
66
authors = ["mcpp-community"]

‎src/toolchain/fingerprint.cppm‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import mcpp.toolchain.detect;
1818

1919
export namespace mcpp::toolchain {
2020

21-
inline constexpr std::string_view MCPP_VERSION = "0.0.101";
21+
inline constexpr std::string_view MCPP_VERSION = "0.0.102";
2222

2323
struct FingerprintInputs {
2424
Toolchain toolchain;

0 commit comments

Comments
 (0)