Skip to content

Commit 7735387

Browse files
fix: 六处「答案已记录、做决定的代码不读它」+ 明确拒绝 system 工具链 (#538)
* fix: six defects where a recorded answer never reached the decision (2026.8.30.2) Analysis, measurements and design: `.agents/docs/2026-08-30-issues-527-529-535-537-analysis-and-design.md`. Two families. A record exists and the code that decides does not read it; or the runtime search path mixes what was declared with what this machine happens to have installed. The host-dependence policy that settles every severity here is stated once in that document's §1.2 and in docs/03: mcpp and everything the ecosystem publishes depend on no host, a user's own project may and that choice is theirs to guarantee, and the boundary between a warning and a refusal is whether the result builds and runs. `mcpp test` re-derived an unchanged answer on every invocation (#529). Both post-link ELF passes were written with a read-back keyed on the artifact's stat, and `prepare_build` rewrites `resolution.json` from a fresh json object at the start of every run, carrying neither record — so each invocation began by deleting the memo its own backend was about to look for. The records move to `.mcpp-runtime-verdicts.json`, which survives, and `resolution.json` keeps publishing a copy, which is the shape `sync_resolution_verdict` already had. Pruning now asks whether the artifact left the DISK rather than whether it left the current command's plan: `build` and `test` share one output directory with different link-unit sets and were deleting each other's verdicts. The record's invalidation key gains the SubOS farm stamp and MCPP_ALLOW_HOST_LIBS, because making a memo durable creates a staleness obligation that did not exist while the answer was recomputed. Measured on ten link units, all warm: `mcpp test` after a `mcpp build` 3.15s -> 0.36s. A new source file in a `path` dependency was invisible to the fast path, which swept only the project being built. `mcpp build` printed `Finished dev in 0.00s` and the module was never compiled. Content edits were caught, but by the post-link snapshot rather than by the sweep. This is #359's shape in a directory that fix did not reach, and workspace members depend on each other by `path`, so it is not an edge case. The dependency source roots are recorded in `.build_cache` and swept, manifest included. `[toolchain] system` with a `build.mcpp` died as `posix_spawnp('') failed` (#527 Bug 1). The resolved compiler path was in `tc->binaryPath` the whole time and was not handed to the build.mcpp closure. This fills an unset variable; it adds no host capability, and the same manifest without a build.mcpp already built. The host-dependence warning states the cost once per build and names the xim route. `standard = 26` — the spelling #527 uses in three of its own examples — was silently ignored, because the key is documented as a string and `get_string` returns nothing for a bare integer. Both spellings are now accepted. `[workspace.package]` and `[workspace.build]` (#527 Bug 2, RFC 3). The workspace root's `[build]` reached no member. Scalars are inherited when the member did not DECLARE the key, vectors append workspace-first, and "declared" is recorded by both parse paths rather than inferred by comparing against the default — a member pinning `standard = "c++23"` under a c++26 workspace must keep it, and that is the same bytes as the default. This is the precondition the cpp20 design doc's §9-Q3 wrote down and deferred. Both inheritance sites now call one function. `allow_host_libs` is refused there: it turns a correctness gate off, and a root able to set it once would disable it for members added later by someone who never read that file. A dependency declaring a standard above the graph's is now reported instead of silently discarded, degraded and promoted by --strict, and scoped to manifests the author controls: every index descriptor with an mcpp segment declares `language` (782 of 782 measured locally, 756 of 774 being C libraries carrying a boilerplate "c++23"), so declaredness does not mean authorship there. A dialect-class flag in `cxxflags` that never reaches the `import std` prebuild is refused before compiling, naming `dialect_cxxflags`. Read from the effective flag set, so a profile or target block is covered too; silent when nothing in the graph imports std, and scoped to the root package because a dependency carrying the flag may legitimately not import std at all. Tests: tests/e2e/321..326, each with the negative case that keeps the check honest. Docs: 03, 05, 06 in both languages. * fix: refuse `[toolchain] system`; only mcpp-managed toolchains build The host-dependence rule is not uniform across axes, and the split is the point rather than an inconsistency. THE TOOLCHAIN IS MCPP'S OWN CONTRACT. Everything mcpp promises — that `import std` is available, that the runtime closure is computable, that two machines and CI produce the same build — is a statement about a compiler mcpp resolved and can identify. A compiler taken from PATH makes every one of those unverifiable, so `[toolchain] … = "system"` is refused rather than warned about. `msvc@system` is the single exception and is a different spelling: it names a FAMILY whose installation mcpp locates, on the one platform where the compiler cannot be redistributed. THE LIBRARIES A PROGRAM LINKS ARE THE PROGRAM'S BUSINESS. A project may link a host library or its own `.so`; mcpp names the supported route — declare the provider so it resolves from mcpp-index, contribute the package if the index does not carry it yet — and does not refuse while the result builds and runs. The developer owns the artifact and guarantees it. This replaces the previous commit's treatment of #527 Bug 1, which filled in the resolved compiler path and warned. The crash it removed was real — `posix_spawnp('') failed (error 2)` as soon as the project had a build.mcpp — but a refusal that arrives as a crash three layers down is not a policy, it is a bug wearing one. The refusal now fires during toolchain resolution, before anything tries to compile the build program, and says what to write instead. Three existing tests referenced the escape hatch and each needed a different answer: 14_toolchain_fallback asserted only that `system` did NOT produce "no toolchain configured". That predicate stays satisfied by any other error, so the test went on passing while its stated intent inverted — a negative-only assertion cannot tell "it worked" from "it failed differently". Both halves are checked now. 293_…_name_one_os used `system` to point a Linux compiler at a Windows target. The refusal fires first, so the test began taking its skip branch — and its own header says a skip there has to be earned or the test cannot see a revert. The refusal is now an accepted PASS branch with its own reason, because the invariant holds by a stronger mechanism: that door is closed entirely. 105_asm_sources_nasm genuinely unaffected; its broken-MCPP_HOME bootstrap error still fires first. Verified, not assumed. 325 is rewritten accordingly, and asserts the refusal reaches the user before the build program starts, that it fires for the environment side channel too, that it names the msvc@system exception and the library axis, and — the denominator — that a project with no `[toolchain]` at all still builds. `mcpp.diag`'s host-route helper is reverted: with the toolchain axis refusing rather than warning, and the library-provenance work not in this change, it had no consumer. Shipping an unread field is the defect this branch is about. Also fixes the version constant: `modules/versioning/src/version.cppm` is the second source of truth `check_version_pins.sh` enforces, and CI caught it — that mismatch is what failed e2e on all three platforms and the Windows `SubsystemContracts.TheBinaryVersionMatchesTheRootManifest`. * docs: the host-dependence rule is per axis; second person out of the reference doc The design document argued a single boundary — "does it build and run" — for every host dependency, and D15 followed it to "warn, do not refuse". §1.2 is corrected to state the rule per axis: the toolchain is mcpp's contract and is refused, the libraries a program links are the program's own and stay a warning. §7, §9, §10, §11.7, §12 and the review record follow. The measurement behind the earlier conclusion was right and stays in the document — `[toolchain] system` does build a project using `import std`. What was wrong was carrying it across an axis boundary, which is the failure this document keeps finding from the other side. Also drops a second-person sentence from docs/06 that check_docs_style.sh refuses in a reference doc. * fix(workspace): anchor an inherited include_dirs to the workspace root Found by re-reading the merge, not by a failure. `[workspace.build] include_dirs = ["shared/inc"]` was prepended to each member verbatim, so every member resolved it against its OWN directory — looking for `<member>/shared/inc` for a directory that lives at `<workspace>/shared/inc`. This is #224 for a new key. `[indices].path` and `[workspace.dependencies] path` are anchored to the workspace root for exactly this reason, and a third relative-path key that skipped it fails as a missing header three members deep, naming neither the manifest that declared it nor the root it was written against. Anchored rather than refused: `expandIncludeDirs` already accepts an absolute include directory, so the anchored form needs nothing downstream. 321 now includes a header from the workspace root, so the anchoring has an assertion rather than a comment. * test(293): the new refusal branch must not exit early The ecosystem job runs this file and checks that each test "ran to its conclusion" — an assertion that exists so an early exit cannot masquerade as a pass. My branch for the toolchain refusal did `exit 0`, which skipped half two entirely: four correct cross builds that this file also guards. CI caught it; a local run did not, because locally the exit code is all a caller sees. The branch now records that half one is settled and lets the script continue. The "names both systems" assertion is asked only of the OS-mismatch refusal — the toolchain refusal is a different sentence about a different decision, one that never resolved a target at all, and demanding both triples from it would be asserting on the wrong object. --------- Co-authored-by: speak-agent <x.d2learn.org@gmail.com>
1 parent adc7077 commit 7735387

25 files changed

Lines changed: 4042 additions & 172 deletions

‎.agents/docs/2026-08-30-issues-527-529-535-537-analysis-and-design.md‎

Lines changed: 1749 additions & 0 deletions
Large diffs are not rendered by default.

‎CHANGELOG.md‎

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,95 @@
33
> 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。
44
> 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)。
55
6+
## [2026.8.30.2] — 2026-08-30
7+
8+
六处缺陷,来自 #527 / #529 的分析,外加一处在实现 review 时挖出来、没有人报过的。
9+
它们分属两族:**记录存在而做决定的代码不读它**,以及**运行期搜索路径把「声明的」
10+
和「这台机器碰巧装了的」混在一起**。
11+
12+
完整分析、量化与设计见
13+
[`.agents/docs/2026-08-30-issues-527-529-535-537-analysis-and-design.md`](.agents/docs/2026-08-30-issues-527-529-535-537-analysis-and-design.md)。
14+
15+
> **host 依赖的规则按轴分,而这个分叉是刻意的。** **工具链属于 mcpp 的契约**:
16+
> `import std` 可用、闭包可计算、同一份构建在别的机器和 CI 上一致,都是关于
17+
> 「一个 mcpp 解析得出、叫得出名字的编译器」的陈述,所以 `[toolchain] = "system"`
18+
> 被**明确拒绝**(`msvc` 是唯一例外)。**而程序链接哪些库是程序自己的事**:
19+
> 工程可以链 host 的库或自己的 `.so`,mcpp 说明代价并指出 mcpp-index 那条路,但不拒绝。
20+
21+
### 修复
22+
23+
- **`mcpp test` 每次调用都在重算一份没有变化的答案(#529)。** 两个 post-link ELF
24+
pass 都写了读回优化(stat 没变 ⇒ 复用上次判定),而 `prepare_build` 每次调用都用
25+
一个**全新的 json 对象**重写 `resolution.json` —— 那里面没有这两条记录。于是每次
26+
调用一开始,就把自己后端待会儿要找的备忘录删掉了。
27+
28+
记录改存进 `.mcpp-runtime-verdicts.json`(它本来就活得过 `prepare_build`),
29+
`resolution.json` 继续发布一份副本 —— 这正是 `sync_resolution_verdict` 已有的
30+
形态。同时:
31+
- 剪枝的判据从「不在本次 plan 里」改为「产物已不在磁盘上」。`mcpp build` 与
32+
`mcpp test` 共用一个输出目录而 link unit 集合不同,前一个判据让两条命令互删
33+
对方的记录;
34+
- 记录的失效键纳入 SubOS farm 的 `.xlings.json` 时间戳与 `MCPP_ALLOW_HOST_LIBS`
35+
—— 让备忘录持久化,就产生了一条以前不存在的正确性义务。
36+
37+
实测(10 个 link unit,每个 11.5MB,全热):
38+
39+
| | 之前 | 之后 |
40+
|---|---|---|
41+
| `mcpp build -p <member>` | 0.70s | 0.32s |
42+
| `mcpp test -p <member>`(紧接 build) | 3.15s | **0.36s** |
43+
| `mcpp test -p <member>`(连续) | 1.95s | 0.40s |
44+
45+
- **`path` 依赖里新增一个源文件,fast path 看不见。** 陈旧性扫描只覆盖被构建的那个
46+
工程,于是 `mcpp build` 报 `Finished dev in 0.00s`,而那个模块从没编译过。内容改动
47+
之所以还能被抓到,靠的不是扫描,是 ninja 重链后的事后放弃。这是 #359 那条形态
48+
("glob 输入变了而现存文件的 mtime 一个没动")在它当年没有覆盖到的目录里。
49+
workspace 成员之间就是 `path` 依赖,所以这不是边角情况。
50+
51+
- **`[toolchain] system` 现在被明确拒绝,而不再崩溃(#527 Bug 1)。**
52+
它此前配合 `build.mcpp` 会死在 `posix_spawnp('') failed (error 2)` —— 一条以崩溃形式
53+
出现的"拒绝"不是政策,是穿着政策外衣的 bug。
54+
55+
**mcpp 只用它自己管理的工具链构建。** `PATH` 上的编译器无法被识别、无法被复现,于是
56+
`import std` 可用性、运行期闭包、"同一份构建在另一台机器上"全都不再是 mcpp 能承诺的
57+
东西。拒绝消息给出该写什么、去哪看可选项,并点明 `msvc` 是**唯一例外**
58+
(它点名的是一个族,mcpp 定位其安装),同时说明**host 库是另一条轴,不在拒绝之列**。
59+
60+
- **`standard = 26`(不带引号)被静默忽略。** 键被文档写成字符串,而 `get_string` 对
61+
裸整数返回空,于是工程按默认档位编译、零诊断。#527 自己的三处示例就是这么写的。
62+
两种拼法现在都接受。
63+
64+
### 新增
65+
66+
- **`[workspace.package]` 与 `[workspace.build]`(#527 Bug 2 / RFC 3)。** workspace 根
67+
的 `[build]` 此前完全没有传给成员;现在标量按「成员**声明过**就成员优先」继承,
68+
向量按 workspace 在前追加。
69+
70+
「声明过」是**解析时记录的事实**,不是与默认值比较得出的推断 —— 成员在
71+
`standard = 26` 的 workspace 下刻意写 `standard = "c++23"` 必须保住,而那与默认值
72+
同为一串字节。这正是 cpp20 设计文档 §9-Q3 记下的前置条件。
73+
74+
`allow_host_libs` 明确不可继承:它关掉的是某个具体产物的检查,workspace 根设一次
75+
就等于替所有后来加入的成员也关掉了。`[workspace.package]` / `[workspace.build]` 里
76+
不认识的键会被**拒绝**而不是忽略。
77+
78+
没有 `[workspace.target.<triple>]`:根里普通的 `[target.<triple>]` 本来就按 triple
79+
被成员继承,为同一能力再加一种拼法只增加接口面。
80+
81+
- **依赖声明了高于当前图的标准时会说出来。** C++ 模块图只有一个标准,依赖自己的
82+
`standard` 不生效 —— 这是对的;缺的是它一直不说。degraded 级别(`--strict` 提升),
83+
且**只对工程作者自己拥有的 manifest 生效**:索引里带 mcpp 段的描述符 782 个全都声明了
84+
`language`,其中 756/774 是 `import_std = false` 的 C 库带的样板值,信任「声明过」会
85+
让 c++20 的根工程对着整个索引报警。
86+
87+
- **方言标志没进 `import std` 预编译时,在编译前拒绝。** `[build] cxxflags` 里的
88+
`-fno-exceptions` / `-fno-rtti` 会到达每个 TU 却到不了 std BMI 预编译,于是每个
89+
importer 都在 mcpp 生成的文件里失败,而报错只讲机制不讲那个键。现在提前拒绝并指出
90+
`dialect_cxxflags`。读的是**生效后**的标志集合(`[build]` / `[profile.*]` /
91+
`[target.…]` 都算),并且在图中没有 `import std` 时不触发。
92+
93+
这两个标志仍然**不自动提升**:依赖可以合法地不同意,消费者无权替它决定。
94+
695
## [2026.8.29.1] — 2026-08-29
796

897
构建规则以普通包分发的机制自 2026.8.5.1 就能用,而**规范**一直没有:一个规则包

‎docs/03-toolchains.md‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,55 @@ Pinned toolsets coexist with each other and with a system Visual Studio.
305305
> that may have been intended. (The family-less `[toolchain] … = "system"` — the PATH
306306
> compiler — is a separate and deliberate escape hatch, and is unaffected.)
307307
308+
### `[toolchain] … = "system"` — refused
309+
310+
**mcpp builds only with toolchains it manages.** A compiler taken from `PATH` is
311+
not supported, and the configuration is refused rather than warned about:
312+
313+
```
314+
error: [toolchain] linux = "system" is not supported: mcpp builds only with
315+
toolchains it manages.
316+
A compiler taken from PATH cannot be identified or reproduced, so
317+
`import std` availability, the runtime closure and "the same build on
318+
another machine" all stop being things mcpp can promise.
319+
Name one instead — mcpp installs it on first use:
320+
321+
[toolchain]
322+
linux = "gcc@16.1.0"
323+
324+
or set a machine default with `mcpp toolchain default gcc@16.1.0`, and
325+
see `mcpp toolchain list` for what is available.
326+
```
327+
328+
`msvc@system` is **the one exception** and is a different spelling: it names a
329+
*family* whose installation mcpp locates and identifies, on the one platform
330+
where the compiler cannot be redistributed. See the section above.
331+
332+
#### Why the toolchain and the libraries get different answers
333+
334+
mcpp's rule about host dependence is not uniform across axes, and the split is
335+
deliberate:
336+
337+
- **mcpp itself, and everything the mcpp ecosystem publishes, depends on no
338+
host.** Toolchains and payloads come through xlings — the xim index or
339+
mcpp-index. This is what makes a build reproducible across machines and Linux
340+
distributions.
341+
- **The toolchain is part of that contract, so it is not the project's to take
342+
from the host.** Everything mcpp promises — `import std` availability, a
343+
computable runtime closure, the same build on a teammate's machine and in CI
344+
— is a statement about a compiler mcpp resolved and can name. A `PATH`
345+
compiler makes all of it unverifiable, which is why this one is a refusal.
346+
- **The libraries a program links are the program's own business.** A project
347+
may link a host library or its own `.so`. mcpp says what that costs and names
348+
the supported route — declare the provider so it resolves from mcpp-index, and
349+
if the index does not carry it yet, contributing the package is the path — but
350+
it does not refuse, as long as the result builds and runs. The developer owns
351+
the artifact and guarantees it.
352+
353+
A build that provably *cannot* run stays an error on either axis: a runtime
354+
closure that cannot be satisfied is refused, because the artifact will not
355+
start. See [binary distribution](12-binary-distribution.md).
356+
308357
### `msvc@system` — the machine's own Visual Studio
309358

310359
mcpp locates and identifies an installed Visual Studio / Build Tools; it never

‎docs/05-mcpp-toml.md‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,47 @@ Two properties worth knowing:
7272
If the sources `import std;` at a level the resolved toolchain does not provide the `std`
7373
module for, mcpp fails before compiling and names both the toolchain and the project level.
7474

75+
Both spellings of the value are accepted: `standard = "c++26"` and `standard = 26`.
76+
77+
When a **dependency declares a level above the graph's**, mcpp says so before compiling
78+
rather than letting it fail somewhere inside that dependency's sources. See
79+
[workspace §4.2](06-workspace.md).
80+
81+
#### Dialect flags and the `import std` BMI
82+
83+
Some flags change what the standard library's headers declare, so the precompiled `import std`
84+
BMI has to be built with them too. That is what `[build] dialect_cxxflags` is for: it is
85+
applied to the std BMI prebuild, the module scan **and** every translation unit in the graph,
86+
including dependencies.
87+
88+
```toml
89+
[build]
90+
dialect_cxxflags = ["-fno-exceptions"]
91+
```
92+
93+
mcpp promotes a few flags into that channel automatically when it finds them in `cxxflags`
94+
(`-freflection`, `-fchar8_t`, `-D_GLIBCXX_USE_CXX11_ABI=…`) — a graph that mixes those is
95+
ill-formed anyway, so no dependency can hold a different opinion about them.
96+
97+
`-fno-exceptions` and `-fno-rtti` are **not** promoted, because a dependency can legitimately
98+
disagree: they remove a language facility the dependency may use, and the consumer cannot make
99+
that choice on its behalf. Left in `cxxflags` they reach every TU and not the prebuild, so the
100+
build cannot succeed — mcpp refuses it before compiling and names the key:
101+
102+
```
103+
error: `-fno-exceptions` changes the language dialect, but the `import std` BMI is
104+
precompiled without it, so every importing translation unit will fail with
105+
"language dialect differs".
106+
Declare it as a dialect flag instead:
107+
108+
[build]
109+
dialect_cxxflags = ["-fno-exceptions"]
110+
```
111+
112+
The check reads the **effective** flags, so it fires for the same flag written in
113+
`[profile.<name>] cxxflags` or in a `[target.…]` block. It does not fire when nothing in the
114+
graph imports `std`, where the flag is an ordinary per-unit option that works.
115+
75116
### 2.2 `[targets.<name>]` — Build Targets
76117

77118
```toml

‎docs/06-workspace.md‎

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,102 @@ linkage = "static"
151151
default = "llvm@20.1.7"
152152
```
153153

154+
### 4.1 `[workspace.package]` and `[workspace.build]`
155+
156+
Package metadata and build flags shared by every member are declared once at the
157+
workspace root:
158+
159+
```toml
160+
[workspace]
161+
members = ["libs/core", "libs/http", "apps/server"]
162+
163+
[workspace.package]
164+
standard = 26 # or "c++26"; both spellings are accepted
165+
version = "0.4.2"
166+
license = "Apache-2.0"
167+
authors = ["example"]
168+
169+
[workspace.build]
170+
cxxflags = ["-Wall", "-Wextra"]
171+
dialect_cxxflags = ["-fno-exceptions"]
172+
```
173+
174+
A member then declares only what is its own:
175+
176+
```toml
177+
[package]
178+
name = "core"
179+
# standard, version, license and authors are inherited;
180+
# [workspace.build] cxxflags are inherited
181+
```
182+
183+
**The merge rule.**
184+
185+
| kind | rule |
186+
|---|---|
187+
| scalars (`standard`, `version`, `license`, `c_standard`, `linkage`, …) | the member wins **when it declared the key**; otherwise the workspace value applies |
188+
| vectors (`cxxflags`, `ldflags`, `defines`, `dialect_cxxflags`, `include_dirs`, …) | append, **workspace first** — so a member's own flag comes later on the command line, where it wins |
189+
| `[workspace.dependencies]` | explicit opt-in per dependency, `x.workspace = true` (§3) |
190+
191+
"Declared" means the key was written, not that its value differs from the
192+
default. A member that deliberately pins `standard = "c++23"` under a
193+
`[workspace.package] standard = 26` keeps c++23; a member that says nothing gets
194+
c++26. Those two are the same value and opposite intents, which is why the
195+
distinction is recorded rather than inferred.
196+
197+
Scalars and vectors are inherited **implicitly**, without a per-key opt-in. The
198+
drift a workspace exists to prevent is a member that forgot to opt in, so
199+
inheritance is the default and overriding is what has to be stated.
200+
Dependencies keep their explicit opt-in because a dependency is an edge in the
201+
resolution graph: inheriting one implicitly would change what a member resolves
202+
without its own manifest naming it.
203+
204+
**`version` may be omitted by a member** when `[workspace.package]` supplies it.
205+
It remains required overall — a member with neither is refused, naming both the
206+
member and the workspace key that would have supplied it.
207+
208+
**Not everything is inheritable.** `[workspace.build] allow_host_libs` is
209+
refused. It disables the hermetic-link check for a specific artifact, and a
210+
workspace root able to set it once would disable that check for members added
211+
later by someone who never read the root manifest. Keys that describe *how to
212+
build* are inheritable; keys that describe *which safety check not to run* stay
213+
with the package whose artifact it is. Any other unknown key in
214+
`[workspace.package]` / `[workspace.build]` is refused too, rather than ignored:
215+
a key that is silently dropped from a table whose whole purpose is propagation
216+
produces a workspace that looks configured and is not.
217+
218+
**There is no `[workspace.target.<triple>]`.** A plain `[target.<triple>]` block
219+
in the workspace root is already inherited by every member, per triple, with the
220+
member winning. A second spelling for the same capability would be surface with
221+
no function.
222+
223+
### 4.2 One standard for the whole module graph
224+
225+
A C++ module graph has exactly one standard: BMIs are not compatible across
226+
levels, so the root package's `standard` is applied to every package in the
227+
graph, including dependencies. A dependency's own `standard` is not applied.
228+
229+
When a dependency **declares** a level higher than the graph is built at, mcpp
230+
reports it before compiling:
231+
232+
```
233+
warning: dependency `render` declares standard = "c++26", and this graph is
234+
built at c++23
235+
impact: a C++ module graph has one standard, so the dependency's declaration
236+
is not applied and its sources are compiled at the graph's level
237+
hint: raise the consumer's standard to "c++26", or declare it once for
238+
every member:
239+
240+
[workspace.package]
241+
standard = "c++26"
242+
```
243+
244+
This is a warning rather than an error — such a build usually succeeds, and it
245+
is promoted to an error by `--strict`. It is reported only for manifests the
246+
project author controls (the root package, workspace members, and `path`
247+
dependencies): a package resolved from an index carries a `standard` written by
248+
a descriptor generator rather than by the person reading the message.
249+
154250
## 5. Build Commands
155251

156252
### 5.1 Building & testing from the Workspace Root

‎docs/zh/03-toolchains.md‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,47 @@ pinned toolset 之间、以及与系统 Visual Studio 之间都可以共存。
284284
> `[toolchain] … = "system"` —— 即 PATH 上的编译器 —— 是另一套、也是有意保留的
285285
> 逃生口,不受影响。)
286286
287+
### `[toolchain] … = "system"` —— 拒绝
288+
289+
**mcpp 只用它自己管理的工具链构建。** `PATH` 上现成的编译器不受支持,该配置会被**拒绝**,
290+
而不是提示:
291+
292+
```
293+
error: [toolchain] linux = "system" is not supported: mcpp builds only with
294+
toolchains it manages.
295+
A compiler taken from PATH cannot be identified or reproduced, so
296+
`import std` availability, the runtime closure and "the same build on
297+
another machine" all stop being things mcpp can promise.
298+
Name one instead — mcpp installs it on first use:
299+
300+
[toolchain]
301+
linux = "gcc@16.1.0"
302+
303+
or set a machine default with `mcpp toolchain default gcc@16.1.0`, and
304+
see `mcpp toolchain list` for what is available.
305+
```
306+
307+
`msvc@system` 是**唯一的例外**,而且是另一种拼法:它点名的是一个**族**,mcpp 负责定位并识别
308+
其安装 —— 那是唯一一个编译器不能被重新分发的平台。见上一节。
309+
310+
#### 为什么工具链与库得到的答案不同
311+
312+
mcpp 对 host 依赖的规则并不是各条轴统一的,这个分叉是刻意的:
313+
314+
- **mcpp 自身、以及 mcpp 生态发布的一切,都不依赖任何 host。** 工具链与 payload 都经由
315+
xlings 获得 —— xim 索引或 mcpp-index。这正是构建能跨机器、跨 Linux 发行版复现的原因。
316+
- **工具链属于这份契约,所以它不是工程可以从 host 拿的东西。** mcpp 承诺的每一件事 ——
317+
`import std` 可用、运行期闭包可计算、同一份构建在同事机器上和 CI 里一致 —— 都是关于
318+
**一个 mcpp 解析出来、叫得出名字的编译器**的陈述。`PATH` 上的编译器让这些全部无法核验,
319+
这就是这一条是拒绝的原因。
320+
- **程序链接哪些库,是程序自己的事。** 工程可以链 host 的库,也可以链自己的 `.so`。mcpp 会
321+
说明这样做的代价,并指出受支持的路径 —— 声明该 provider 让它从 mcpp-index 解析;索引尚未
322+
收录时,**把包贡献进 mcpp-index** 就是那条路 —— 但只要结果能构建、能运行,就不强行拒绝。
323+
产物是开发者的,由他保证。
324+
325+
而"证明跑不起来"的构建在两条轴上都仍然是错误:运行期闭包不可满足时会被拒绝,因为产物根本
326+
起不来。见[二进制分发](12-binary-distribution.md)。
327+
287328
### `msvc@system` —— 机器自己的 Visual Studio
288329

289330
mcpp 只负责定位并识别已安装的 Visual Studio / Build Tools,**从不**安装、

0 commit comments

Comments
 (0)