Skip to content

Commit 9256260

Browse files
committed
chore: bump version to 2026.8.17.1, and record what §1–§4 actually became
The design doc gains a status section per item, including the two places the PLAN was corrected by the implementation and the one half of §4 that was deliberately not done: - `dist::Format` now reads the target triple before falling back to the host. Everything but MinGW used to ask the host, which made a Windows contract unassertable on the Linux runner where most of this gets reviewed. It only ADDS answers, so no existing build changes. - `force_bundle` had to reach the PE system list too. ELF always worked that way; making the PE exclusion unconditional would have turned an explicitly written decision into decoration. - The ELF closure still runs the artifact. `ldd` hands back RESOLVED PATHS while `DT_NEEDED` gives only names, and turning names into paths means reimplementing the loader's search order ($ORIGIN, DT_RPATH before LD_LIBRARY_PATH before DT_RUNPATH before ld.so.cache, hwcaps). Rewriting that under a correct, e2e-covered path is more risk than it buys — and the cost is stated rather than left to be discovered: cross-ARCHITECTURE ELF packing is still unsupported, which is the second limit §4.1 names. `mcpp self doctor` reports the Windows SDK PER ORIGIN. One unlabelled line was the same "one question, two answerers" shape this axis exists to close: a user reading it would believe it applied to their pinned build, and it did not.
1 parent ad2e446 commit 9256260

5 files changed

Lines changed: 110 additions & 18 deletions

File tree

.agents/docs/2026-08-16-windows-toolchain-three-axes-design.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,48 @@ LD_TRACE_LOADED_OBJECTS=1 '<binary>'
298298
299299
---
300300
301+
## 6.5 落地状态(2026-08-17,mcpp 2026.8.17.1)
302+
303+
**§1 / §2 / §3 / §4 全部实现,§2.4 明确不做。** 逐条对应:
304+
305+
| 条目 | 状态 | 落点 |
306+
|---|---|---|
307+
| §1 Origin 建模 | ✅ | `enum class Origin`(`model.cppm`)—— 放在数据模型里,让 spec 侧(`registry`)与已定位编译器侧(`msvc`)指的是**同一根轴** |
308+
| §1 拒绝 `gcc@system` | ✅ | `parse_toolchain_spec`,错误里同时给出 pin 与 PATH 逃生口两种写法 |
309+
| §1 解析一次 | ✅ | `prepare` 原先把同一个字符串**解析两遍**、各自下结论;现在一次,`origin_of()` 分发 |
310+
| §1 消重复:`resolve_managed_msvc` | ✅ | `registry.cppm`。"payload 在哪、为什么不能用 fetcher 的 `root`"两份手写实现,而理由只写在其中一份里 |
311+
| §1 消重复:sysroot 谓词 | ✅ | `needs_linux_sysroot_payloads()`。两份**不等价**,而其中一份的注释声称它们互为镜像 —— 少的是 PE 那一项 |
312+
| §1 解析链注释 | ✅ | 一张按 `TcOrigin` 枚举名写的表。原先两处、分别声称 3 步和 4 步,合起来点到 9 个输入里的 5 个,还互相矛盾 |
313+
| §2.2 SDK 按来源分流 | ✅ | `msvc::resolve_sdk_for()`;受管来源忽略 `WindowsSdkDir`/`WindowsSdkVersion` 并打印 `note:` |
314+
| §2.2 无 SDK payload 时 | ✅ | 退回机器 SDK 并**说出来**(可用 > 失败,但不可复现这件事必须留痕) |
315+
| §2.3 `ucrt@` 身份 | ✅ | `bind_windows_ucrt()`,进 `runtimeContractHash`;`runtime_provider()` 取代各处 `starts_with("glibc@")` |
316+
| §2.4 manifest SDK 键 | ⛔ **不做** | 理由见该节 |
317+
| §3.3 PE `toolchain-coupled` | ✅ | `dist::Mechanism::deployToolchainRuntime` → `CompileFlags::toolchainRuntimeDeploy` → ninja `stage_file` 边 |
318+
| §3.3 排除 debug CRT | ✅ | 判据只有一处(`vc_redist_dir()`),拷贝那步复用它而不是另立一条按名字的规则 |
319+
| §4 静态读闭包 | ✅ | `mcpp.pack.binfmt`:ELF `DT_NEEDED`、PE 导入表 **+ 延迟导入表** |
320+
| §4 跨 OS 打包 | ✅ | Linux 上给 Windows 产物打 zip;`mcpp.pack.zip` 自己写压缩包(没有哪个 zip 工具在每个宿主上都存在) |
321+
| §4.3 `pack` 读 Contract | ✅ | `cxx_runtime` 决定 toolchain runtime 目录进不进搜索集;与 `--mode` 矛盾时拒绝 |
322+
323+
**两处对方案本身的修正**,来自实现过程:
324+
325+
1. **`dist::Format` 改为先看 target triple、再退回宿主。** 原先除 MinGW 外一律
326+
问宿主,于是"Windows 上的契约"在 Linux runner 上**根本无法断言** —— 而这轮
327+
大部分 review 就发生在 Linux runner 上。只**新增**答案:说不出 OS 的 triple
328+
仍走原来那条推导,现有构建一个都不变。
329+
2. **`force_bundle` 在 PE 上也要能覆盖系统表。** ELF 上一直如此;PE 上第一版把
330+
系统排除写成了无条件的,那会让一个明确写下来的决定变成装饰。
331+
332+
**§4 未做的一半,以及理由。** ELF 闭包**仍然**通过运行产物取得
333+
(`LD_TRACE_LOADED_OBJECTS`),`binfmt` 的 `DT_NEEDED` 读取只用于识别与诊断。
334+
原因不是懒:`ldd` 交回的是**已解析的路径**,而 `DT_NEEDED` 只有名字,把名字变
335+
成路径要重新实现 loader 的搜索规则(`DT_RPATH` → `LD_LIBRARY_PATH` →
336+
`DT_RUNPATH` → `ld.so.cache` → 默认目录,加上 `$ORIGIN` 展开与 hwcaps 子目录)。
337+
在一条**已经正确、且被 e2e 覆盖**的路径上重写这个,风险远大于收益。
338+
**代价要说清楚:跨架构的 ELF 打包(x86_64 上给 aarch64 产物打包)仍然不支持**,
339+
而这正是 §4.1 指出的第二个限制。PE 那条没有既有实现,所以它从一开始就是静态的。
340+
341+
---
342+
301343
## 7. 落地顺序
302344
303345
| # | 项 | 规模 | 依赖 |

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 = "2026.8.16.3"
3+
version = "2026.8.17.1"
44
description = "Modern C++ build & package management tool"
55
license = "Apache-2.0"
66
authors = ["mcpp-community"]

src/build/prepare.cppm

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1294,18 +1294,36 @@ prepare_build(bool print_fingerprint,
12941294
// resolution / fingerprinting.
12951295
fold_build_defines_into_flags(m->buildConfig);
12961296

1297-
// msvc@system: located on the machine, never resolved through xim
1298-
// packages. mcpp does not install the machine's Visual Studio.
1297+
// ORIGIN, RESOLVED ONCE.
12991298
//
1300-
// A VERSIONED msvc spec is a different origin and takes the xim path
1301-
// below — which is the whole point: what the manifest says is what gets
1299+
// The spec used to be parsed TWICE from the same string a dozen lines
1300+
// apart — once to ask "is this msvc@system", once to get the package —
1301+
// and each call site drew its own conclusions from the result. Two parses
1302+
// of one string is two places for the answer to differ, which is the shape
1303+
// §1 of the three-axes design is about: a platform special case whose cost
1304+
// is paid at every site that has to know about it.
1305+
//
1306+
// `Origin::SystemMsvc` is located on the machine and never resolved
1307+
// through an xim package — mcpp does not install the machine's Visual
1308+
// Studio. `Origin::Managed` is everything else, including a VERSIONED
1309+
// msvc spec, and that is the point: what the manifest says is what gets
13021310
// used, on every machine, instead of whatever this one happens to have.
1303-
bool tcSpecIsMsvc = false;
1304-
if (tcSpec.has_value()) {
1305-
if (auto s = mcpp::toolchain::parse_toolchain_spec(*tcSpec);
1306-
s && mcpp::toolchain::is_system_toolchain(*s))
1307-
tcSpecIsMsvc = true;
1311+
std::optional<mcpp::toolchain::ToolchainSpec> parsedSpec;
1312+
auto tcOriginAxis = mcpp::toolchain::Origin::Managed;
1313+
if (tcSpec.has_value() && *tcSpec != "system") {
1314+
// A parse FAILURE is not the same as an unparseable spec being
1315+
// absent: `gcc@system` now fails here by name (see
1316+
// parse_toolchain_spec), and swallowing that would put the error back
1317+
// where it used to happen — somewhere else, saying something else.
1318+
auto s = mcpp::toolchain::parse_toolchain_spec(*tcSpec);
1319+
if (!s) return std::unexpected(std::format(
1320+
"[toolchain].{} = '{}': {}", kCurrentPlatform, *tcSpec, s.error()));
1321+
parsedSpec = std::move(*s);
1322+
tcOriginAxis = mcpp::toolchain::origin_of(*parsedSpec);
13081323
}
1324+
const bool tcSpecIsMsvc =
1325+
parsedSpec && tcOriginAxis == mcpp::toolchain::Origin::SystemMsvc;
1326+
13091327
if (tcSpecIsMsvc) {
13101328
if (!mcpp::platform::is_windows) {
13111329
return std::unexpected(std::format(
@@ -1319,9 +1337,9 @@ prepare_build(bool print_fingerprint,
13191337
mcpp::ui::info("Resolved", std::format(
13201338
"msvc@system → msvc {} ({})",
13211339
inst->display_version(), inst->clPath.string()));
1322-
} else if (tcSpec.has_value() && *tcSpec != "system") {
1323-
auto spec = mcpp::toolchain::parse_toolchain_spec(*tcSpec);
1324-
if (!spec || spec->version.empty()) {
1340+
} else if (parsedSpec) {
1341+
auto spec = parsedSpec;
1342+
if (spec->version.empty()) {
13251343
return std::unexpected(std::format(
13261344
"[toolchain].{} = '{}' is invalid; expected '<pkg>@<version>'",
13271345
kCurrentPlatform, *tcSpec));

src/doctor.cppm

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,13 +133,45 @@ export int doctor_report() {
133133
warn("msvc not detected — run `mcpp toolchain default msvc` for "
134134
"setup guidance (mcpp does not install MSVC)");
135135
}
136-
// Windows SDK (native cl.exe builds need its UCRT/um headers).
136+
// Windows SDK. REPORTED PER ORIGIN, because it is chosen per origin
137+
// (design §2.2): `msvc@system` searches the machine, a pinned toolset
138+
// takes the payload installed with it. One unlabelled line here would
139+
// be the same "one question, two answerers" shape the SDK axis exists
140+
// to close — a user reading it would believe it applied to their
141+
// pinned build, and it does not.
137142
if (auto sdk = mcpp::toolchain::msvc::find_windows_sdk()) {
138-
ok(std::format("Windows SDK {} at {}", sdk->version,
143+
ok(std::format("Windows SDK (msvc@system) {} at {}", sdk->version,
139144
sdk->root.string()));
140145
} else {
141-
warn("no Windows SDK found — native msvc builds will fail "
142-
"(install the 'Windows 11 SDK' VS component)");
146+
warn("no Windows SDK found for msvc@system — native builds with "
147+
"the machine's Visual Studio will fail (install the "
148+
"'Windows 11 SDK' VS component)");
149+
}
150+
{
151+
std::error_code sdkEc;
152+
auto msvcRoot = mcpp::home::root()
153+
/ "registry" / "data" / "xpkgs" / "xim-x-msvc";
154+
for (auto& v : std::filesystem::directory_iterator(msvcRoot, sdkEc)) {
155+
if (!v.is_directory(sdkEc)) continue;
156+
auto ver = v.path().filename().string();
157+
auto inst = mcpp::toolchain::msvc::installation_at(
158+
v.path(), ver, /*identifyVersion=*/false);
159+
if (!inst) continue;
160+
// The SAME resolution a build performs, not a second one
161+
// shaped like it: what doctor prints is what the user will
162+
// believe, so the two must not be able to disagree.
163+
auto choice = mcpp::toolchain::msvc::resolve_sdk_for(inst->clPath);
164+
if (choice.sdk) {
165+
ok(std::format("Windows SDK (msvc@{}) {} at {}",
166+
ver, choice.sdk->version,
167+
choice.sdk->root.string()));
168+
} else {
169+
warn(std::format(
170+
"msvc@{} has no usable Windows SDK — reinstall it to "
171+
"pull its `xim:windows-sdk` dependency", ver));
172+
}
173+
if (!choice.note.empty()) warn(choice.note);
174+
}
143175
}
144176

145177
mcpp::ui::status("Checking", "mingw (xim:mingw-gcc)");

src/version.cppm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,6 @@ import std;
3131

3232
export namespace mcpp {
3333

34-
inline constexpr std::string_view MCPP_VERSION = "2026.8.16.3";
34+
inline constexpr std::string_view MCPP_VERSION = "2026.8.17.1";
3535

3636
} // namespace mcpp

0 commit comments

Comments
 (0)