Skip to content

Commit b8ddb55

Browse files
authored
2026.9.29.2: a workspace plan reads what a member states about itself from the member (#739)
The plan's root in a workspace plan is a virtual root that holds the values shared by the whole graph. Five statements a member makes about itself were read from that root in 2026.9.29.1 and were therefore empty, and a member's product directory lacked the shared libraries its libraries need (.agents/docs/2026-09-29-workspace-build-graph-design.md, section 17.1). - A member's relative [indices].path is anchored at the member when the member is loaded (e2e 120). - The std module is built when a selected member's target entries import std (e2e 836 M1). - Each selected member's [resources] and windows_code_page are compiled against the member's directory and include directories, under res/<member>/, and embedded into the member's images only (e2e 837). A quoted include or resource file in a script is found through the include directories, as the resource compiler finds it. - Every graph-built shared library of a member's closure is placed in the member's product directory (e2e 835 L3). - [build] linkage is a root-position value. - A member's unknown mcpp: capability is refused, and its cfg() and schema warnings are reported, as a root's (e2e 836 M2, M3). - Version 2026.9.29.2. Refs #734.
1 parent e7b3218 commit b8ddb55

21 files changed

Lines changed: 974 additions & 418 deletions

‎.agents/docs/2026-09-29-workspace-build-graph-design.md‎

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -435,8 +435,10 @@ it.
435435

436436
**Configurations.** The selected members are grouped by their effective
437437
root-position values: the toolchain request, `[package] standard`,
438-
`dialect_cxxflags`, `cxx_runtime`, `dependency_linkage`, the target and the
439-
profile, each after workspace inheritance and the command line. Each group is
438+
`dialect_cxxflags`, `cxx_runtime`, `linkage`, `dependency_linkage`, the target,
439+
the profile and the indices, each after workspace inheritance and the command
440+
line. A relative path a member writes (an `[indices].path`) is anchored at the
441+
member before the values are compared. Each group is
440442
one plan with one virtual root, whose root-position values are the group's.
441443
Two groups that resolve to one directory (two spellings of one toolchain) are
442444
built one after the other in it.
@@ -538,3 +540,36 @@ Readings with the implementation (Linux, llvm 22.1.8):
538540
mcpp's own repository, a rooted workspace, builds itself with the new engine
539541
(full build, gcc 16.1.0, 115 s), and its module tests run through member plans
540542
(`mcpp test -p graph`, `-p versioning`).
543+
544+
### 17.1 What a member states about itself (2026.9.29.2)
545+
546+
The inventory of §15 classified the sites that treat `packages[0]` as the
547+
root. It did not cover the sites that read the root's manifest (`state.m`,
548+
`plan.manifest`) for a value the package owns; under a virtual root those read
549+
an empty value. The mcpp-index sweep of 2026.9.29.1 found two of them, and an
550+
inventory of every such read (about 170 sites) found the rest. Each is now
551+
read from the member, or made a value of the plan:
552+
553+
| Statement | 2026.9.29.1 | Now | Criterion |
554+
|---|---|---|---|
555+
| a member's relative `[indices].path` | resolved against the workspace root | anchored at the member when the member is loaded; two spellings of one tree are one configuration | e2e 120, unit `IndexPathsAreAnchoredWhereTheyWereWritten` |
556+
| `import std` in a member's test entries | not seen (the root's targets only) | the entries of the root and of every selected member | e2e 836 M1 |
557+
| a member's `[resources]`, `windows_code_page` | the virtual root's (empty) | per member: its directory and include directories, `res/<member>/`, its images only | e2e 837 |
558+
| `[build] linkage` | not copied to the virtual root | a root-position value | unit `RootPositionValuesSeparateMembers` |
559+
| unknown `mcpp:` capability, cfg() and schema warnings | not reported for a member | refused and reported for each selected member | e2e 836 M2, M3 |
560+
| the shared libraries of a member's closure | only those the member's units link were placed | every graph-built shared library of the closure, with its aliases, as §15 states | e2e 835 L3 |
561+
562+
Each criterion fails on 2026.9.29.1 and passes on 2026.9.29.2. The resource
563+
case also showed a defect of every build: a quoted `#include` in a script was
564+
tracked beside the script although the resource compiler finds it through the
565+
include directories, so a script that relied on them could not be built. The
566+
scan now resolves as the compiler does (unit
567+
`InputsAreResolvedThroughTheIncludeDirectories`).
568+
569+
The last row is not a read of the root's manifest: a root's program sits in
570+
`bin/` beside every graph-built library, so no build before the workspace plan
571+
needed the transitive set named. The index member `wayland` (libffi under
572+
libwayland-client) showed it.
573+
574+
The rule the inventory missed is general: a virtual root answers for the plan,
575+
and a read of the root's manifest is correct only for a value of the plan.

‎CHANGELOG.md‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,43 @@
44
> Each `## [<version>]` section is that release's notes. Entries are written in English
55
> from 2026.9.28.3 on; earlier entries remain as written.
66

7+
## [2026.9.29.2] - 2026-09-29
8+
9+
This release corrects what a workspace plan reads from its members. The plan's
10+
root is a virtual root that holds the values shared by the whole graph; five
11+
statements that a member makes about itself were read from that root in
12+
2026.9.29.1 and were therefore empty, and a member's product directory lacked
13+
the shared libraries that its libraries need. The mcpp-index sweep of
14+
2026.9.29.1 found the first two.
15+
16+
### Fixed
17+
18+
- **A member's own relative `[indices].path` is anchored at the member.** It
19+
was resolved against the workspace root, so a member that declares its own
20+
index (47 members of mcpp-index do) found no package. Two members that name
21+
one tree with different relative paths are now one configuration (e2e 120).
22+
- **A member whose tests import `std` is tested with the std module built.**
23+
The decision read the entry files of the root's targets only, and the virtual
24+
root has none; a member whose only sources are its tests failed to compile
25+
them (e2e 836).
26+
- **A member's `[resources]` and `windows_code_page` reach its own images.**
27+
Each selected member's resources are compiled against the member's directory
28+
and include directories, under `res/<member>/`, and embedded into the
29+
member's programs and shared libraries only (e2e 837). A quoted `#include`
30+
or resource file in a script is now found through the include directories as
31+
the resource compiler finds it, in every build.
32+
- **A member's product directory holds every graph-built shared library of its
33+
closure.** Only the libraries a member's own units link were placed, so a
34+
library that another library needs (libffi under libwayland-client) was
35+
missing and the program did not start (e2e 835 L3).
36+
- **`[build] linkage` is a value of the plan.** It chooses the C runtime that
37+
every object is compiled against, so members that differ in it are separate
38+
configurations, and the plan takes it from its members.
39+
- **A member's manifest is checked as a root's.** An unknown capability under
40+
the reserved `mcpp:` prefix is refused, and a cfg() predicate mcpp cannot
41+
evaluate and the other schema warnings are reported, for each selected
42+
member (e2e 836).
43+
744
## [2026.9.29.1] - 2026-09-29
845

946
This release builds a workspace as one graph per configuration. The selected

‎docs/07-workspace.md‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -421,9 +421,11 @@ member that several members use is compiled once.
421421

422422
- **Configurations.** Members are built in one graph when they share their
423423
toolchain request, target, C++ standard, `dialect_cxxflags`, C++ runtime,
424-
profile and the other `[build]` values that apply to a whole graph. Members
425-
that differ in one of them are built in separate graphs, at the same time,
426-
sharing the command's jobs.
424+
`linkage`, profile, indices and the other `[build]` values that apply to a
425+
whole graph. Members that differ in one of them are built in separate
426+
graphs, at the same time, sharing the command's jobs. A relative path a
427+
member writes, such as its own `[indices]` path, is read from the member's
428+
directory.
427429
- **Selection.** `--workspace`, and a virtual root without `-p`, select every
428430
member. `-p X`, and a command run in X's directory, plan X and what X
429431
reaches. The two share the build directory, so `mcpp build --workspace`
@@ -436,6 +438,9 @@ member that several members use is compiled once.
436438
declares it, and is refused when no selected member declares it.
437439
- **Hooks.** The `[hooks]` of every selected member run around the build, in
438440
member order.
441+
- **Resources.** A member's `[resources]` and `windows_code_page` are
442+
compiled against the member's directory and include directories and embedded
443+
into that member's programs and shared libraries only (2026.9.29.2+).
439444
- **No-op builds.** A command repeated with nothing changed is answered by one
440445
check per configuration, without planning.
441446
- **Module names.** Members built in one graph share one module namespace:

‎docs/zh/07-workspace.md‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -392,9 +392,10 @@ mcpp test --workspace --workspace-timeout 1800 # whole fan-out (default 0 = no
392392
对工作空间的命令把成员放在一起规划:被选中的成员及其全部依赖构成一张构建图,只有一个
393393
`build.ninja`,被多个成员使用的成员只编译一次。
394394

395-
- **配置。** 工具链请求、目标、C++ 标准、`dialect_cxxflags`、C++ 运行时、配置档,以及其他
396-
作用于整张图的 `[build]` 值都相同的成员在同一张图中构建。其中任一项不同的成员在各自的图中
397-
构建,这些图同时进行,共享命令的并行任务数。
395+
- **配置。** 工具链请求、目标、C++ 标准、`dialect_cxxflags`、C++ 运行时、`linkage`、配置档、
396+
索引,以及其他作用于整张图的 `[build]` 值都相同的成员在同一张图中构建。其中任一项不同的成员
397+
在各自的图中构建,这些图同时进行,共享命令的并行任务数。成员写下的相对路径(例如它自己的
398+
`[indices]` 路径)按该成员的目录解析。
398399
- **选择。** `--workspace`,以及虚拟工作空间根下不带 `-p` 的命令,选中全体成员;`-p X` 与在
399400
X 的目录中执行的命令规划 X 及其所依赖的一切。两者共用构建目录,因此先执行
400401
`mcpp build --workspace` 再执行 `mcpp build -p X` 不编译任何内容;只有当某个包在两条命令中
@@ -404,6 +405,8 @@ mcpp test --workspace --workspace-timeout 1800 # whole fan-out (default 0 = no
404405
- **Feature。** `--features f` 在每个声明了 `f` 的被选成员中启用它;没有被选成员声明它时,
405406
命令被拒绝。
406407
- **Hooks。** 每个被选成员的 `[hooks]` 按成员顺序在构建前后运行。
408+
- **资源。** 成员的 `[resources]` 与 `windows_code_page` 按该成员的目录与 include 目录编译,
409+
只嵌入该成员自己的程序与共享库(2026.9.29.2+)。
407410
- **无事可做的构建。** 在没有任何改动时重复执行的命令,每个配置只做一次检查,不重新规划。
408411
- **模块名。** 在同一张图中构建的成员共享一个模块命名空间:两个成员各自提供同名模块时,不能在
409412
同一条 `--workspace` 命令中构建;分别用 `-p` 构建。

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

‎modules/versioning/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.9.29.1";
34+
inline constexpr std::string_view MCPP_VERSION = "2026.9.29.2";
3535

3636
} // namespace mcpp

‎src/build/ninja_backend.cppm‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2751,6 +2751,11 @@ std::string emit_ninja_string(const BuildPlan& plan, std::string* placements) {
27512751
escape_ninja_path(ru.output),
27522752
escape_ninja_path(ru.source),
27532753
implicit.empty() ? std::string{} : " |" + implicit));
2754+
if (!ru.flags.empty() && ru.flags != plan.rcFlags) {
2755+
std::string rcf;
2756+
for (auto const& f : ru.flags) { rcf += ' '; rcf += shell_quote_arg(f); }
2757+
append(std::format(" rcflags ={}\n", rcf));
2758+
}
27542759
}
27552760
if (!plan.resourceUnits.empty()) append("\n");
27562761

‎src/build/plan.cppm‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,13 @@ struct ResourceUnit {
176176
// (verified against llvm-rc 22.1.8: /I, /D, no dependency output), so these
177177
// come from a text scan plus `[resources].extra-inputs`.
178178
std::vector<std::filesystem::path> implicitInputs;
179+
// The directories the script's `#include`s are searched in: its package's
180+
// directory and include_dirs. A workspace plan compiles the resources of
181+
// several members, each against its own package.
182+
std::vector<std::filesystem::path> includeDirs;
183+
// The resource compiler's flags for this script. `BuildPlan::rcFlags` are
184+
// the first unit's; a unit whose flags differ states its own.
185+
std::vector<std::string> flags;
179186
};
180187

181188
struct RuntimeCapabilityProvider {
@@ -3011,6 +3018,30 @@ make_plan(const mcpp::manifest::Manifest& manifest,
30113018
}
30123019
units.push_back(std::move(lu));
30133020
}
3021+
// Every graph-built shared library of the closure is placed beside
3022+
// the member's images, not only those its units link: a library that
3023+
// another shared library needs (libffi under libwayland-client) is
3024+
// found there, as it is in `bin/` beside a root's program.
3025+
const bool loadsLibraries = std::ranges::any_of(units, [](const LinkUnit& u) {
3026+
return u.kind != LinkUnit::StaticLibrary;
3027+
});
3028+
for (auto i : closureIdx) {
3029+
if (!loadsLibraries) break;
3030+
auto it = sharedTargetsByPackage.find(i);
3031+
if (it == sharedTargetsByPackage.end()) continue;
3032+
for (auto t : it->second) {
3033+
auto const& dep = sharedDepTargets[t];
3034+
std::vector<std::filesystem::path> files{dep.output};
3035+
for (auto const& alias : runtime_aliases_for_target(dep.target, naming))
3036+
files.push_back(alias);
3037+
for (auto const& f : files) {
3038+
if (f.parent_path() == productDir) continue;
3039+
const bool seen = std::ranges::any_of(group.placements,
3040+
[&](auto const& pl) { return pl.source == f; });
3041+
if (!seen) group.placements.push_back({f, place(f)});
3042+
}
3043+
}
3044+
}
30143045
// A program the closure ships through `artifacts` (mcpp#711) is linked
30153046
// once, at `bin/`, and placed beside the member's programs, where a
30163047
// program that launches it looks for it.

‎src/build/prepare/config.cpp‎

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -794,7 +794,8 @@ bool is_std_module(std::string_view name) {
794794

795795
bool graph_or_targets_import_std(const mcpp::modgraph::Graph& graph,
796796
const mcpp::manifest::Manifest& manifest,
797-
const std::filesystem::path& projectRoot) {
797+
const std::filesystem::path& projectRoot,
798+
const std::vector<mcpp::modgraph::PackageRoot>& packages) {
798799
for (auto& u : graph.units) {
799800
for (auto& req : u.requires_) {
800801
if (is_std_module(req.logicalName))
@@ -804,16 +805,26 @@ bool graph_or_targets_import_std(const mcpp::modgraph::Graph& graph,
804805

805806
// Some target entry files can be added to the plan after the package scan.
806807
// Check them here so std BMI setup matches what make_plan will compile: they
807-
// are read by the same scan_entry_file make_plan reads them with.
808-
const auto extTable = mcpp::extension_table_for(manifest.buildConfig.moduleExtensions,
809-
manifest.buildConfig.deviceExtensions);
810-
for (auto& t : manifest.targets) {
811-
if (t.main.empty()) continue;
812-
const auto entry = mcpp::modgraph::scan_entry_file(projectRoot / t.main,
813-
manifest.package.name, extTable);
814-
for (auto const& req : entry.requires_)
815-
if (is_std_module(req.logicalName)) return true;
816-
}
808+
// are read by the same scan_entry_file make_plan reads them with. The
809+
// packages whose targets make_plan compiles are the root and, in a
810+
// workspace plan, every selected member (a member whose only sources are
811+
// its tests is the case the root alone misses).
812+
auto targets_import_std = [](const mcpp::manifest::Manifest& m,
813+
const std::filesystem::path& root) {
814+
const auto extTable = mcpp::extension_table_for(m.buildConfig.moduleExtensions,
815+
m.buildConfig.deviceExtensions);
816+
for (auto& t : m.targets) {
817+
if (t.main.empty()) continue;
818+
const auto entry = mcpp::modgraph::scan_entry_file(root / t.main,
819+
m.package.name, extTable);
820+
for (auto const& req : entry.requires_)
821+
if (is_std_module(req.logicalName)) return true;
822+
}
823+
return false;
824+
};
825+
if (targets_import_std(manifest, projectRoot)) return true;
826+
for (auto const& pkg : packages)
827+
if (pkg.selectedMember && targets_import_std(pkg.manifest, pkg.root)) return true;
817828
return false;
818829
}
819830

0 commit comments

Comments
 (0)