Skip to content

Commit 8e00a18

Browse files
authored
2026.9.30.2: a selected workspace member declares as the root, the clang MSVC row states its compiler version (#746), and a shared member is compiled once per configuration (#747)
The release canary of v2026.9.30.2 on mcpp-language-server stopped the release before any build job ran. This change fixes that defect, mcpp#746, and a test gap, for release as 2026.9.30.2. - A selected workspace member declares as the root. The workspace plan's virtual root declares only its members, and the rules that grant the root's own declarations a privilege read its edges alone, so a rooted workspace's own package, or a member selected with -p, lost the position it held when planned as its own root. PrepareState::declaredByRoot now answers for the root and each selected member, and the kind and reference clashes, the git lock, the identity write-back, edge linkage, direct target-side candidates and the index refresh ask it. Two selected members that disagree about one dependency's checkout or link form are refused, naming both (e2e 850). - mcpp#746: the version of the toolset's cl.exe, read from its VS_FIXEDFILEINFO as clang reads it, is passed as -fms-compatibility-version with the toolset words, so it is on every command and in every key (e2e 760, unit tests). - e2e 851 states that a member two configurations use is compiled once in each, with the union of one configuration's features.
1 parent 87cc232 commit 8e00a18

23 files changed

Lines changed: 811 additions & 54 deletions

‎.agents/docs/2026-09-30-build-wall-time-progress-count-and-hang-plan.md‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,6 +1018,42 @@ resolved as stated.
10181018
held only `/usr/bin:/bin`. The test's `PATH` now holds System32, as every
10191019
Windows `PATH` does, and as the other Windows tests with a restricted `PATH`
10201020
do.
1021+
- **The release canary on mcpp-language-server.** Its
1022+
commit of 07:44 overrides `openkal-linux` by `path` in its root package,
1023+
against the `version` request of `openkal-musl`, which the rule "the root's
1024+
declaration wins" settles. 2026.9.28.3 builds it; 2026.9.29.1 to 2026.9.30.2
1025+
refuse it ("Pick one"): the virtual root of the workspace plan declares
1026+
only its members, and the rule read the virtual root's edges alone. The
1027+
same reading was inventoried across the planner; a selected member now
1028+
declares as the root for the kind and reference clashes, the git lock, the
1029+
identity write-back, `linkage` on its dependency edges, the target-side
1030+
candidates it names directly, and the index refresh. The refusal to mangle
1031+
the root's sources stays with the root: a member reaches it only when two
1032+
members pin two versions of one package, which one plan builds by mangling
1033+
and which each member built on its own before. Two selected members that
1034+
disagree about one dependency's checkout (its kind or its reference) or its
1035+
link form are refused, naming both. An independent review of the change
1036+
found three defects before it was pushed, each fixed: the arguments of the
1037+
link-form refusal were out of order, two selected members pointing one
1038+
dependency at two directories were only warned about, and link-form
1039+
conflicts were compared by the short key, which two packages can share.
1040+
e2e 850 states each; every criterion fails on 2026.9.30.2 as released
1041+
before the fix.
1042+
- **mcpp#746, e2e 760 on Windows.** A job on an older runner image restored
1043+
the sandbox cache that a job on a newer image had saved; clang refused the
1044+
std module compiled for `msvc19.51.36260` against a translation unit
1045+
compiled for `msvc19.51.36257`. The key of the clang MSVC row held the
1046+
toolset directory, and a directory name does not determine `cl.exe`'s
1047+
build. The version of the toolset's `cl.exe`, read from its
1048+
`VS_FIXEDFILEINFO` as clang reads it, is now passed as
1049+
`-fms-compatibility-version` with the toolset words, so it is on every
1050+
command and in every key.
1051+
- **A shared member across two configurations (e2e 851).** A member that two
1052+
members of different configurations use is compiled once in each build
1053+
directory; in one configuration it is compiled once with the union of the
1054+
features its consumers ask for, and a consumer's own flags stay out of it.
1055+
The behaviour was correct; the criterion was absent (e2e 833 builds a
1056+
member of another standard that uses no shared member).
10211057

10221058
## 8. Appendix: readings
10231059

‎.agents/docs/2026-09-30-build-wall-time-verify.sh‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,24 @@ if git clone -q --depth 1 https://github.com/openxlings/xlings.git xlings > s8-c
158158
else fail "8 xlings from its main branch" ../s8.log; fi
159159
else skip "8 xlings could not be cloned"; fi
160160

161+
# ── 9. CHANGE: a rooted workspace's own path override wins ────────────────
162+
# The shape of the release canary on mcpp-language-server: the workspace's
163+
# own package declares `framework` by `path`, a library it uses asks for it by
164+
# `git`. 2026.9.30.1 refuses it ("Pick one").
165+
rm -rf "$W/s9"; mkdir -p "$W/s9/fw/src" "$W/s9/libg/src" "$W/s9/ws/src"; cd "$W/s9"
166+
git init -q fw && git -C fw config user.email t@l && git -C fw config user.name t
167+
printf '[package]\nname = "framework"\nversion = "0.1.0"\n\n[build]\nsources = ["src/*.c"]\n\n[targets.framework]\nkind = "lib"\n' > fw/mcpp.toml
168+
printf 'int framework_marker(void) { return 101; }\n' > fw/src/framework.c
169+
git -C fw add -A && git -C fw commit -qm A && rev=$(git -C fw rev-parse HEAD)
170+
printf 'int framework_marker(void) { return 199; }\n' > fw/src/framework.c
171+
printf '[package]\nname = "libg"\nversion = "0.1.0"\n\n[build]\nsources = ["src/*.c"]\n\n[targets.libg]\nkind = "lib"\n\n[dependencies.framework]\ngit = "%s"\nrev = "%s"\n' "$W/s9/fw" "$rev" > libg/mcpp.toml
172+
printf 'extern int framework_marker(void);\nint libg_marker(void) { return framework_marker(); }\n' > libg/src/libg.c
173+
printf '[package]\nname = "app"\nversion = "0.1.0"\n\n[dependencies]\nframework = { path = "%s" }\nlibg = { path = "%s" }\n\n[targets.app]\nkind = "bin"\nmain = "src/main.cpp"\n\n[workspace]\nmembers = ["."]\n' "$W/s9/fw" "$W/s9/libg" > ws/mcpp.toml
174+
printf '#include <cstdio>\nextern "C" int libg_marker(void);\nint main() { std::printf("%%d\\n", libg_marker()); }\n' > ws/src/main.cpp
175+
if (cd ws && "$MCPP" build > ../s9.log 2>&1) && [ "$("$(bin_of ws/target app)")" = 199 ]; then
176+
pass "9 CHANGE: a rooted workspace's own path override wins over a library's git declaration"
177+
else fail "9 CHANGE: the rooted workspace's override" s9.log; fi
178+
161179
echo
162180
echo "RESULT: $passes passed, $fails failed, $skips skipped (mcpp $VER)"
163181
[ "$fails" -eq 0 ]

‎CHANGELOG.md‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,27 @@ of 10.5 s; a build with nothing to do is unchanged at 0.05 s.
5454
BMI after that compile, so a fresh build could compile the consumer first and
5555
fail with `failed to read compiled module`. The stage edge of such a BMI now
5656
waits for those BMIs (e2e 849).
57+
- **A selected workspace member declares as the root.** Since 2026.9.29.1 a
58+
workspace is planned from a virtual root that declares only its members, and
59+
the rules that grant the root's own declarations a privilege read that
60+
root's edges alone. A rooted workspace's own package, and a member selected
61+
with `-p`, again hold the position each held when planned as its own root:
62+
its `path` or `git` override of a dependency that another package requests
63+
by another kind wins (the release canary on mcpp-language-server, whose root
64+
package overrides `openkal-linux` by `path`, was refused with "Pick one");
65+
`linkage` on its dependency edges is honoured; the identity its declarations
66+
adopt is written back for the lock names; and its registry dependencies are
67+
considered for the index refresh. Two selected members that disagree about
68+
one dependency's checkout (its kind or its reference) or its link form are
69+
refused, naming both (e2e 850).
70+
- **The clang MSVC row states the compiler version (mcpp#746).** The version
71+
of the toolset's `cl.exe`, read from its file version as clang reads it, is
72+
passed as `-fms-compatibility-version` on every command and enters every
73+
key. Without it the driver chose the version itself and wrote it into each
74+
BMI, and a std module compiled under one runner image's `cl.exe` was served
75+
to another image whose `cl.exe` differed under the same toolset directory
76+
name (`std.pcm was compiled for ... msvc19.51.36260 ... msvc19.51.36257`).
77+
A clang build for `*-windows-msvc` rebuilds once after the upgrade.
5778

5879
### Changed
5980

‎docs/05-dependencies.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,18 @@ disagrees with another dependency, with neither being the root, is never
174174
settled by guessing which one was declared first — that is exactly the
175175
"accident of queue order" this section replaces.
176176

177+
**In a workspace (mcpp 2026.9.30.2+).** The root is the package a command
178+
builds: a rooted workspace's own package, and each member the command selects
179+
(every member under `--workspace`, `X` under `-p X`). Each holds the root's
180+
position for its own declarations, as it did when every member was planned as
181+
its own root, so a member's `path` override of a dependency that another
182+
package requests by `version` wins, with the warning above. Two selected
183+
members that declare one identity by two kinds, or by two references of one
184+
kind, are refused, naming both: one configuration builds one checkout of a
185+
package. The same holds for `linkage`
186+
on a selected member's dependency edges, which is honoured, and refused when
187+
two selected members ask for two forms of one package.
188+
177189
### The identity of a `path` or `git` dependency (mcpp 2026.9.14.2+)
178190

179191
A `path` or `git` dependency is the package its manifest declares, whatever key

‎docs/07-workspace.md‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,18 @@ member that several members use is compiled once.
435435
that member's commands. Editing them recompiles that member and what
436436
imports it; the build directory stays the same.
437437
- **Features.** `--features f` activates `f` in each selected member that
438-
declares it, and is refused when no selected member declares it.
438+
declares it, and is refused when no selected member declares it. A package
439+
that several members of one configuration use is compiled once, with the
440+
union of the features they ask for; a package that members of two
441+
configurations use is compiled once in each.
442+
- **The root's declarations.** Each selected member declares as the root did
443+
when it was planned alone: its `path` or `git` override of a dependency wins
444+
over another package's declaration, `linkage` on its dependency edges is
445+
honoured, and its registry dependencies are considered for the index
446+
refresh (2026.9.30.2+). Two selected members that disagree about one
447+
dependency's checkout (its kind or its reference) or its link form are
448+
refused, naming both. See
449+
[05 — When two declarations of one dependency disagree](05-dependencies.md#when-two-declarations-of-one-dependency-disagree).
439450
- **Hooks.** The `[hooks]` of every selected member run around the build, in
440451
member order.
441452
- **Resources.** A member's `[resources]` and `windows_code_page` are

‎docs/zh/05-dependencies.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,14 @@ sdk = { version = "1.0", visibility = "private" }
160160
冲突、且都不是根的情形,绝不会靠猜哪个先被声明来解决 —— 那正是本节要替换掉
161161
的"队列顺序的意外"。
162162

163+
**工作区中(mcpp 2026.9.30.2+)。** 根是命令所构建的包:带根包工作区自己的包,
164+
以及命令选中的每个成员(`--workspace` 下是全部成员,`-p X` 下是 `X`)。每个都
165+
以根的位置对待自己的声明,与每个成员各自作为根规划时一致;因此成员用 `path`
166+
覆盖另一个包以 `version` 请求的依赖时,成员的声明胜出,并给出上文的警告。两个
167+
被选中的成员以不同种类、或以同一种类的不同引用声明同一身份时被拒绝,并点名双方:
168+
一个配置只构建一个包的一份 checkout。被选中成员依赖边上的 `linkage` 同理:它会被采纳;两个被选中成员
169+
为同一个包要求两种形态时被拒绝。
170+
163171
### `path` 或 `git` 依赖的身份(mcpp 2026.9.14.2+)
164172

165173
`path` 或 `git` 依赖就是它的 manifest 所声明的那个包,与指向它的键无关。来源

‎docs/zh/07-workspace.md‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,12 @@ mcpp test --workspace --workspace-timeout 1800 # whole fan-out (default 0 = no
403403
- **编译参数。** 成员的 `cflags`、`cxxflags`、`ldflags` 与 defines 作用于该成员自己的命令。
404404
修改它们会重新编译该成员以及导入它的单元,构建目录保持不变。
405405
- **Feature。** `--features f` 在每个声明了 `f` 的被选成员中启用它;没有被选成员声明它时,
406-
命令被拒绝。
406+
命令被拒绝。同一配置中被多个成员使用的包只编译一次,feature 取它们请求的并集;被两个
407+
配置的成员使用的包在每个配置中各编译一次。
408+
- **根的声明。** 每个被选成员都像单独规划时的根那样声明依赖:它对某个依赖的 `path` 或
409+
`git` 覆盖胜过其他包的声明,它依赖边上的 `linkage` 被采纳,它的注册表依赖参与索引刷新
410+
的判断(2026.9.30.2+)。两个被选成员对同一依赖的 checkout(种类或引用)或链接形态意见不一
411+
时被拒绝,并点名双方。见 [05 —— 同一依赖两条声明冲突的处理](05-dependencies.md#同一依赖两条声明冲突的处理)。
407412
- **Hooks。** 每个被选成员的 `[hooks]` 按成员顺序在构建前后运行。
408413
- **资源。** 成员的 `[resources]` 与 `windows_code_page` 按该成员的目录与 include 目录编译,
409414
只嵌入该成员自己的程序与共享库(2026.9.29.2+)。

‎modules/toolchain-model/src/linkmodel.cppm‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ struct ToolchainLinkModel {
8080
std::filesystem::path msvcToolsDir;
8181
std::filesystem::path winSdkRoot;
8282
std::string winSdkVersion;
83+
std::string msvcCompilerVersion;
8384

8485
bool clangDriver = false; // clang: -isystem headers; gcc: -idirafter
8586
// gcc: -idirafter (…#include_next), -B/-L only
@@ -117,6 +118,15 @@ struct ToolchainLinkModel {
117118
// and the SDK always travel together: given only the toolset, the driver
118119
// stops reading %INCLUDE% and takes the registry's newest SDK instead.
119120
// Read by the compile line, the link line and the cache key alike.
121+
//
122+
// THE COMPILER VERSION TRAVELS WITH THEM (mcpp#746). Without
123+
// `-fms-compatibility-version` the driver reads it from a cl.exe it
124+
// locates itself and writes it into every BMI's target
125+
// (`x86_64-pc-windows-msvc19.51.36260`), and a BMI whose version differs
126+
// from the importer's is refused. Two runner images held different cl.exe
127+
// builds under one toolset directory name, so the directory, which was
128+
// the key's only view of the toolset, served one image's std module to
129+
// the other. Said here, the version is on every command and in every key.
120130
std::vector<std::string> msvc_driver_tokens(const PathEscape& esc) const {
121131
std::vector<std::string> out;
122132
if (msvcToolsDir.empty()) return out;
@@ -128,6 +138,8 @@ struct ToolchainLinkModel {
128138
out.push_back("-Xmicrosoft-windows-sdk-version");
129139
out.push_back(winSdkVersion);
130140
}
141+
if (!msvcCompilerVersion.empty())
142+
out.push_back("-fms-compatibility-version=" + msvcCompilerVersion);
131143
return out;
132144
}
133145

@@ -405,6 +417,7 @@ ToolchainLinkModel resolve_link_model(const Toolchain& tc) {
405417
lm.msvcToolsDir = tc.msvcToolsDir;
406418
lm.winSdkRoot = tc.windowsSdkRoot;
407419
lm.winSdkVersion = tc.windowsSdkVersion;
420+
lm.msvcCompilerVersion = tc.msvcCompilerVersion;
408421
}
409422
return lm;
410423
}

‎modules/toolchain-model/src/model.cppm‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,10 @@ struct Toolchain {
279279
// the cl.exe row carries the same answer in its own path and envOverrides.
280280
std::filesystem::path msvcToolsDir; // <vs>/VC/Tools/MSVC/<v>
281281
std::string msvcToolsVersion; // "14.44.35207"
282+
// The version of that toolset's cl.exe ("19.44.35211"), from its file
283+
// version: what clang would otherwise read from the same file itself, and
284+
// what a toolset directory name does not determine (mcpp#746).
285+
std::string msvcCompilerVersion;
282286
std::string msvcOrigin; // "system" | "managed"
283287
std::string msvcProduct; // for the one printed line
284288
std::filesystem::path windowsSdkRoot; // <kits>/10 or the payload

0 commit comments

Comments
 (0)