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
39 changes: 37 additions & 2 deletions .agents/docs/2026-09-29-workspace-build-graph-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -435,8 +435,10 @@ it.

**Configurations.** The selected members are grouped by their effective
root-position values: the toolchain request, `[package] standard`,
`dialect_cxxflags`, `cxx_runtime`, `dependency_linkage`, the target and the
profile, each after workspace inheritance and the command line. Each group is
`dialect_cxxflags`, `cxx_runtime`, `linkage`, `dependency_linkage`, the target,
the profile and the indices, each after workspace inheritance and the command
line. A relative path a member writes (an `[indices].path`) is anchored at the
member before the values are compared. Each group is
one plan with one virtual root, whose root-position values are the group's.
Two groups that resolve to one directory (two spellings of one toolchain) are
built one after the other in it.
Expand Down Expand Up @@ -538,3 +540,36 @@ Readings with the implementation (Linux, llvm 22.1.8):
mcpp's own repository, a rooted workspace, builds itself with the new engine
(full build, gcc 16.1.0, 115 s), and its module tests run through member plans
(`mcpp test -p graph`, `-p versioning`).

### 17.1 What a member states about itself (2026.9.29.2)

The inventory of §15 classified the sites that treat `packages[0]` as the
root. It did not cover the sites that read the root's manifest (`state.m`,
`plan.manifest`) for a value the package owns; under a virtual root those read
an empty value. The mcpp-index sweep of 2026.9.29.1 found two of them, and an
inventory of every such read (about 170 sites) found the rest. Each is now
read from the member, or made a value of the plan:

| Statement | 2026.9.29.1 | Now | Criterion |
|---|---|---|---|
| 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` |
| `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 |
| 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 |
| `[build] linkage` | not copied to the virtual root | a root-position value | unit `RootPositionValuesSeparateMembers` |
| unknown `mcpp:` capability, cfg() and schema warnings | not reported for a member | refused and reported for each selected member | e2e 836 M2, M3 |
| 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 |

Each criterion fails on 2026.9.29.1 and passes on 2026.9.29.2. The resource
case also showed a defect of every build: a quoted `#include` in a script was
tracked beside the script although the resource compiler finds it through the
include directories, so a script that relied on them could not be built. The
scan now resolves as the compiler does (unit
`InputsAreResolvedThroughTheIncludeDirectories`).

The last row is not a read of the root's manifest: a root's program sits in
`bin/` beside every graph-built library, so no build before the workspace plan
needed the transitive set named. The index member `wayland` (libffi under
libwayland-client) showed it.

The rule the inventory missed is general: a virtual root answers for the plan,
and a read of the root's manifest is correct only for a value of the plan.
37 changes: 37 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,43 @@
> Each `## [<version>]` section is that release's notes. Entries are written in English
> from 2026.9.28.3 on; earlier entries remain as written.

## [2026.9.29.2] - 2026-09-29

This release corrects what a workspace plan reads from its members. The plan's
root is a virtual root that holds the values shared by the whole graph; five
statements that 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 that its libraries need. The mcpp-index sweep of
2026.9.29.1 found the first two.

### Fixed

- **A member's own relative `[indices].path` is anchored at the member.** It
was resolved against the workspace root, so a member that declares its own
index (47 members of mcpp-index do) found no package. Two members that name
one tree with different relative paths are now one configuration (e2e 120).
- **A member whose tests import `std` is tested with the std module built.**
The decision read the entry files of the root's targets only, and the virtual
root has none; a member whose only sources are its tests failed to compile
them (e2e 836).
- **A member's `[resources]` and `windows_code_page` reach its own images.**
Each selected member's resources are compiled against the member's directory
and include directories, under `res/<member>/`, and embedded into the
member's programs and shared libraries only (e2e 837). A quoted `#include`
or resource file in a script is now found through the include directories as
the resource compiler finds it, in every build.
- **A member's product directory holds every graph-built shared library of its
closure.** Only the libraries a member's own units link were placed, so a
library that another library needs (libffi under libwayland-client) was
missing and the program did not start (e2e 835 L3).
- **`[build] linkage` is a value of the plan.** It chooses the C runtime that
every object is compiled against, so members that differ in it are separate
configurations, and the plan takes it from its members.
- **A member's manifest is checked as a root's.** An unknown capability under
the reserved `mcpp:` prefix is refused, and a cfg() predicate mcpp cannot
evaluate and the other schema warnings are reported, for each selected
member (e2e 836).

## [2026.9.29.1] - 2026-09-29

This release builds a workspace as one graph per configuration. The selected
Expand Down
11 changes: 8 additions & 3 deletions docs/07-workspace.md
Original file line number Diff line number Diff line change
Expand Up @@ -421,9 +421,11 @@ member that several members use is compiled once.

- **Configurations.** Members are built in one graph when they share their
toolchain request, target, C++ standard, `dialect_cxxflags`, C++ runtime,
profile and the other `[build]` values that apply to a whole graph. Members
that differ in one of them are built in separate graphs, at the same time,
sharing the command's jobs.
`linkage`, profile, indices and the other `[build]` values that apply to a
whole graph. Members that differ in one of them are built in separate
graphs, at the same time, sharing the command's jobs. A relative path a
member writes, such as its own `[indices]` path, is read from the member's
directory.
- **Selection.** `--workspace`, and a virtual root without `-p`, select every
member. `-p X`, and a command run in X's directory, plan X and what X
reaches. The two share the build directory, so `mcpp build --workspace`
Expand All @@ -436,6 +438,9 @@ member that several members use is compiled once.
declares it, and is refused when no selected member declares it.
- **Hooks.** The `[hooks]` of every selected member run around the build, in
member order.
- **Resources.** A member's `[resources]` and `windows_code_page` are
compiled against the member's directory and include directories and embedded
into that member's programs and shared libraries only (2026.9.29.2+).
- **No-op builds.** A command repeated with nothing changed is answered by one
check per configuration, without planning.
- **Module names.** Members built in one graph share one module namespace:
Expand Down
9 changes: 6 additions & 3 deletions docs/zh/07-workspace.md
Original file line number Diff line number Diff line change
Expand Up @@ -392,9 +392,10 @@ mcpp test --workspace --workspace-timeout 1800 # whole fan-out (default 0 = no
对工作空间的命令把成员放在一起规划:被选中的成员及其全部依赖构成一张构建图,只有一个
`build.ninja`,被多个成员使用的成员只编译一次。

- **配置。** 工具链请求、目标、C++ 标准、`dialect_cxxflags`、C++ 运行时、配置档,以及其他
作用于整张图的 `[build]` 值都相同的成员在同一张图中构建。其中任一项不同的成员在各自的图中
构建,这些图同时进行,共享命令的并行任务数。
- **配置。** 工具链请求、目标、C++ 标准、`dialect_cxxflags`、C++ 运行时、`linkage`、配置档、
索引,以及其他作用于整张图的 `[build]` 值都相同的成员在同一张图中构建。其中任一项不同的成员
在各自的图中构建,这些图同时进行,共享命令的并行任务数。成员写下的相对路径(例如它自己的
`[indices]` 路径)按该成员的目录解析。
- **选择。** `--workspace`,以及虚拟工作空间根下不带 `-p` 的命令,选中全体成员;`-p X` 与在
X 的目录中执行的命令规划 X 及其所依赖的一切。两者共用构建目录,因此先执行
`mcpp build --workspace` 再执行 `mcpp build -p X` 不编译任何内容;只有当某个包在两条命令中
Expand All @@ -404,6 +405,8 @@ mcpp test --workspace --workspace-timeout 1800 # whole fan-out (default 0 = no
- **Feature。** `--features f` 在每个声明了 `f` 的被选成员中启用它;没有被选成员声明它时,
命令被拒绝。
- **Hooks。** 每个被选成员的 `[hooks]` 按成员顺序在构建前后运行。
- **资源。** 成员的 `[resources]` 与 `windows_code_page` 按该成员的目录与 include 目录编译,
只嵌入该成员自己的程序与共享库(2026.9.29.2+)。
- **无事可做的构建。** 在没有任何改动时重复执行的命令,每个配置只做一次检查,不重新规划。
- **模块名。** 在同一张图中构建的成员共享一个模块命名空间:两个成员各自提供同名模块时,不能在
同一条 `--workspace` 命令中构建;分别用 `-p` 构建。
Expand Down
2 changes: 1 addition & 1 deletion mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mcpp"
version = "2026.9.29.1"
version = "2026.9.29.2"
description = "Modern C++ build & package management tool"
license = "Apache-2.0"
authors = ["mcpp-community"]
Expand Down
2 changes: 1 addition & 1 deletion modules/versioning/src/version.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@ import std;

export namespace mcpp {

inline constexpr std::string_view MCPP_VERSION = "2026.9.29.1";
inline constexpr std::string_view MCPP_VERSION = "2026.9.29.2";

} // namespace mcpp
5 changes: 5 additions & 0 deletions src/build/ninja_backend.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -2751,6 +2751,11 @@ std::string emit_ninja_string(const BuildPlan& plan, std::string* placements) {
escape_ninja_path(ru.output),
escape_ninja_path(ru.source),
implicit.empty() ? std::string{} : " |" + implicit));
if (!ru.flags.empty() && ru.flags != plan.rcFlags) {
std::string rcf;
for (auto const& f : ru.flags) { rcf += ' '; rcf += shell_quote_arg(f); }
append(std::format(" rcflags ={}\n", rcf));
}
}
if (!plan.resourceUnits.empty()) append("\n");

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

struct RuntimeCapabilityProvider {
Expand Down Expand Up @@ -3011,6 +3018,30 @@ make_plan(const mcpp::manifest::Manifest& manifest,
}
units.push_back(std::move(lu));
}
// Every graph-built shared library of the closure is placed beside
// the member's images, not only those its units link: a library that
// another shared library needs (libffi under libwayland-client) is
// found there, as it is in `bin/` beside a root's program.
const bool loadsLibraries = std::ranges::any_of(units, [](const LinkUnit& u) {
return u.kind != LinkUnit::StaticLibrary;
});
for (auto i : closureIdx) {
if (!loadsLibraries) break;
auto it = sharedTargetsByPackage.find(i);
if (it == sharedTargetsByPackage.end()) continue;
for (auto t : it->second) {
auto const& dep = sharedDepTargets[t];
std::vector<std::filesystem::path> files{dep.output};
for (auto const& alias : runtime_aliases_for_target(dep.target, naming))
files.push_back(alias);
for (auto const& f : files) {
if (f.parent_path() == productDir) continue;
const bool seen = std::ranges::any_of(group.placements,
[&](auto const& pl) { return pl.source == f; });
if (!seen) group.placements.push_back({f, place(f)});
}
}
}
// A program the closure ships through `artifacts` (mcpp#711) is linked
// once, at `bin/`, and placed beside the member's programs, where a
// program that launches it looks for it.
Expand Down
33 changes: 22 additions & 11 deletions src/build/prepare/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -794,7 +794,8 @@ bool is_std_module(std::string_view name) {

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

// Some target entry files can be added to the plan after the package scan.
// Check them here so std BMI setup matches what make_plan will compile: they
// are read by the same scan_entry_file make_plan reads them with.
const auto extTable = mcpp::extension_table_for(manifest.buildConfig.moduleExtensions,
manifest.buildConfig.deviceExtensions);
for (auto& t : manifest.targets) {
if (t.main.empty()) continue;
const auto entry = mcpp::modgraph::scan_entry_file(projectRoot / t.main,
manifest.package.name, extTable);
for (auto const& req : entry.requires_)
if (is_std_module(req.logicalName)) return true;
}
// are read by the same scan_entry_file make_plan reads them with. The
// packages whose targets make_plan compiles are the root and, in a
// workspace plan, every selected member (a member whose only sources are
// its tests is the case the root alone misses).
auto targets_import_std = [](const mcpp::manifest::Manifest& m,
const std::filesystem::path& root) {
const auto extTable = mcpp::extension_table_for(m.buildConfig.moduleExtensions,
m.buildConfig.deviceExtensions);
for (auto& t : m.targets) {
if (t.main.empty()) continue;
const auto entry = mcpp::modgraph::scan_entry_file(root / t.main,
m.package.name, extTable);
for (auto const& req : entry.requires_)
if (is_std_module(req.logicalName)) return true;
}
return false;
};
if (targets_import_std(manifest, projectRoot)) return true;
for (auto const& pkg : packages)
if (pkg.selectedMember && targets_import_std(pkg.manifest, pkg.root)) return true;
return false;
}

Expand Down
Loading
Loading