Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

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

## 8. Appendix: readings

Expand Down
18 changes: 18 additions & 0 deletions .agents/docs/2026-09-30-build-wall-time-verify.sh
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,24 @@ if git clone -q --depth 1 https://github.com/openxlings/xlings.git xlings > s8-c
else fail "8 xlings from its main branch" ../s8.log; fi
else skip "8 xlings could not be cloned"; fi

# ── 9. CHANGE: a rooted workspace's own path override wins ────────────────
# The shape of the release canary on mcpp-language-server: the workspace's
# own package declares `framework` by `path`, a library it uses asks for it by
# `git`. 2026.9.30.1 refuses it ("Pick one").
rm -rf "$W/s9"; mkdir -p "$W/s9/fw/src" "$W/s9/libg/src" "$W/s9/ws/src"; cd "$W/s9"
git init -q fw && git -C fw config user.email t@l && git -C fw config user.name t
printf '[package]\nname = "framework"\nversion = "0.1.0"\n\n[build]\nsources = ["src/*.c"]\n\n[targets.framework]\nkind = "lib"\n' > fw/mcpp.toml
printf 'int framework_marker(void) { return 101; }\n' > fw/src/framework.c
git -C fw add -A && git -C fw commit -qm A && rev=$(git -C fw rev-parse HEAD)
printf 'int framework_marker(void) { return 199; }\n' > fw/src/framework.c
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
printf 'extern int framework_marker(void);\nint libg_marker(void) { return framework_marker(); }\n' > libg/src/libg.c
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
printf '#include <cstdio>\nextern "C" int libg_marker(void);\nint main() { std::printf("%%d\\n", libg_marker()); }\n' > ws/src/main.cpp
if (cd ws && "$MCPP" build > ../s9.log 2>&1) && [ "$("$(bin_of ws/target app)")" = 199 ]; then
pass "9 CHANGE: a rooted workspace's own path override wins over a library's git declaration"
else fail "9 CHANGE: the rooted workspace's override" s9.log; fi

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

### Changed

Expand Down
12 changes: 12 additions & 0 deletions docs/05-dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,18 @@ disagrees with another dependency, with neither being the root, is never
settled by guessing which one was declared first — that is exactly the
"accident of queue order" this section replaces.

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

### The identity of a `path` or `git` dependency (mcpp 2026.9.14.2+)

A `path` or `git` dependency is the package its manifest declares, whatever key
Expand Down
13 changes: 12 additions & 1 deletion docs/07-workspace.md
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,18 @@ member that several members use is compiled once.
that member's commands. Editing them recompiles that member and what
imports it; the build directory stays the same.
- **Features.** `--features f` activates `f` in each selected member that
declares it, and is refused when no selected member declares it.
declares it, and is refused when no selected member declares it. A package
that several members of one configuration use is compiled once, with the
union of the features they ask for; a package that members of two
configurations use is compiled once in each.
- **The root's declarations.** Each selected member declares as the root did
when it was planned alone: its `path` or `git` override of a dependency wins
over another package's declaration, `linkage` on its dependency edges is
honoured, and its registry dependencies are considered for the index
refresh (2026.9.30.2+). Two selected members that disagree about one
dependency's checkout (its kind or its reference) or its link form are
refused, naming both. See
[05 — When two declarations of one dependency disagree](05-dependencies.md#when-two-declarations-of-one-dependency-disagree).
- **Hooks.** The `[hooks]` of every selected member run around the build, in
member order.
- **Resources.** A member's `[resources]` and `windows_code_page` are
Expand Down
8 changes: 8 additions & 0 deletions docs/zh/05-dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,14 @@ sdk = { version = "1.0", visibility = "private" }
冲突、且都不是根的情形,绝不会靠猜哪个先被声明来解决 —— 那正是本节要替换掉
的"队列顺序的意外"。

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

### `path` 或 `git` 依赖的身份(mcpp 2026.9.14.2+)

`path` 或 `git` 依赖就是它的 manifest 所声明的那个包,与指向它的键无关。来源
Expand Down
7 changes: 6 additions & 1 deletion docs/zh/07-workspace.md
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,12 @@ mcpp test --workspace --workspace-timeout 1800 # whole fan-out (default 0 = no
- **编译参数。** 成员的 `cflags`、`cxxflags`、`ldflags` 与 defines 作用于该成员自己的命令。
修改它们会重新编译该成员以及导入它的单元,构建目录保持不变。
- **Feature。** `--features f` 在每个声明了 `f` 的被选成员中启用它;没有被选成员声明它时,
命令被拒绝。
命令被拒绝。同一配置中被多个成员使用的包只编译一次,feature 取它们请求的并集;被两个
配置的成员使用的包在每个配置中各编译一次。
- **根的声明。** 每个被选成员都像单独规划时的根那样声明依赖:它对某个依赖的 `path` 或
`git` 覆盖胜过其他包的声明,它依赖边上的 `linkage` 被采纳,它的注册表依赖参与索引刷新
的判断(2026.9.30.2+)。两个被选成员对同一依赖的 checkout(种类或引用)或链接形态意见不一
时被拒绝,并点名双方。见 [05 —— 同一依赖两条声明冲突的处理](05-dependencies.md#同一依赖两条声明冲突的处理)。
- **Hooks。** 每个被选成员的 `[hooks]` 按成员顺序在构建前后运行。
- **资源。** 成员的 `[resources]` 与 `windows_code_page` 按该成员的目录与 include 目录编译,
只嵌入该成员自己的程序与共享库(2026.9.29.2+)。
Expand Down
13 changes: 13 additions & 0 deletions modules/toolchain-model/src/linkmodel.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ struct ToolchainLinkModel {
std::filesystem::path msvcToolsDir;
std::filesystem::path winSdkRoot;
std::string winSdkVersion;
std::string msvcCompilerVersion;

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

Expand Down Expand Up @@ -405,6 +417,7 @@ ToolchainLinkModel resolve_link_model(const Toolchain& tc) {
lm.msvcToolsDir = tc.msvcToolsDir;
lm.winSdkRoot = tc.windowsSdkRoot;
lm.winSdkVersion = tc.windowsSdkVersion;
lm.msvcCompilerVersion = tc.msvcCompilerVersion;
}
return lm;
}
Expand Down
4 changes: 4 additions & 0 deletions modules/toolchain-model/src/model.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,10 @@ struct Toolchain {
// the cl.exe row carries the same answer in its own path and envOverrides.
std::filesystem::path msvcToolsDir; // <vs>/VC/Tools/MSVC/<v>
std::string msvcToolsVersion; // "14.44.35207"
// The version of that toolset's cl.exe ("19.44.35211"), from its file
// version: what clang would otherwise read from the same file itself, and
// what a toolset directory name does not determine (mcpp#746).
std::string msvcCompilerVersion;
std::string msvcOrigin; // "system" | "managed"
std::string msvcProduct; // for the one printed line
std::filesystem::path windowsSdkRoot; // <kits>/10 or the payload
Expand Down
Loading
Loading