Skip to content

Commit 4151a0e

Browse files
committed
2026.9.29.3: a program shipped through artifacts in a workspace plan waits for no plan-level runtime file, and its own runtime files are beside it
The validation project's post-release build of 2026.9.29.2 stopped with "'bin/translations/qt_gppcli_en.qm', needed by 'obj/gpp_updater/Updater.exe', missing and no known rule to make it". - The link edge of a unit outside the link groups (a program shipped through artifacts) no longer takes the plan's deploy set as order-only inputs in a workspace plan: the virtual root has no program, and nothing places that set. - A member's runtime set includes the closure of every package whose program the member ships through artifacts, so the program's own runtime files are in the member's product directory, as they were beside a root's program. - e2e 833 G9 states both; G2 and G3 count compile and link outputs, since a plan of another selection stages the runtime files again. - Version 2026.9.29.3; design document section 17.1.
1 parent b8ddb55 commit 4151a0e

7 files changed

Lines changed: 79 additions & 10 deletions

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,7 @@ read from the member, or made a value of the plan:
558558
| `[build] linkage` | not copied to the virtual root | a root-position value | unit `RootPositionValuesSeparateMembers` |
559559
| unknown `mcpp:` capability, cfg() and schema warnings | not reported for a member | refused and reported for each selected member | e2e 836 M2, M3 |
560560
| 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+
| the runtime files of a program shipped through `artifacts` (2026.9.29.3) | its link waited for the plan's deploy set, which a workspace plan does not place; its own runtime files were not in the member's directory | the link waits for no plan-level file; a member's runtime set includes the closures its `artifacts` edges reach | e2e 833 G9 |
561562

562563
Each criterion fails on 2026.9.29.1 and passes on 2026.9.29.2. The resource
563564
case also showed a defect of every build: a quoted `#include` in a script was

‎CHANGELOG.md‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,25 @@
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.3] - 2026-09-29
8+
9+
This release completes the runtime placement of a program that a workspace
10+
member ships through `artifacts`. The validation project's post-release build
11+
of 2026.9.29.2 found it.
12+
13+
### Fixed
14+
15+
- **A program shipped through `artifacts` no longer waits for a runtime file
16+
that a workspace plan never places.** Its link edge depended on the plan's
17+
own deploy set, which a workspace plan does not place (`bin/` holds products
18+
only), so a workspace whose members declare runtime files stopped with
19+
"missing and no known rule to make it" (e2e 833 G9).
20+
- **The runtime files of such a program are beside it.** A member's runtime
21+
set includes the closure of every package whose program the member ships
22+
through `artifacts`, so the program finds its own runtime files in the
23+
member's product directory, as it did beside a root's program in `bin/`
24+
(e2e 833 G9).
25+
726
## [2026.9.29.2] - 2026-09-29
827

928
This release corrects what a workspace plan reads from its members. The plan's

‎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.2"
3+
version = "2026.9.29.3"
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.2";
34+
inline constexpr std::string_view MCPP_VERSION = "2026.9.29.3";
3535

3636
} // namespace mcpp

‎src/build/ninja_backend.cppm‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2842,10 +2842,16 @@ std::string emit_ninja_string(const BuildPlan& plan, std::string* placements) {
28422842
// action populated after the first plan), relinked a program whose
28432843
// link inputs had not changed. Empty on RPATH platforms (no *.dll
28442844
// deps), so other targets are unaffected.
2845+
//
2846+
// A workspace plan places no deploy set of its own (its root has no
2847+
// program; see `placedFiles`), so a unit outside the link groups, a
2848+
// program shipped through `artifacts`, waits for none: it is placed in
2849+
// the members' product directories with their deploy sets.
28452850
std::string orderOnly;
28462851
if (lu.kind == LinkUnit::Binary || lu.kind == LinkUnit::TestBinary) {
2847-
for (auto const& d : uflags.runtimeDeploy)
2848-
orderOnly += " " + escape_ninja_path(d.dest);
2852+
if (lu.linkGroup >= 0 || !plan.manifest.package.virtualRoot)
2853+
for (auto const& d : uflags.runtimeDeploy)
2854+
orderOnly += " " + escape_ninja_path(d.dest);
28492855
if (lu.linkGroup >= 0)
28502856
for (auto const& pl : plan.linkGroups[static_cast<std::size_t>(lu.linkGroup)].placements)
28512857
orderOnly += " " + escape_ninja_path(pl.dest);

‎src/build/plan.cppm‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2863,9 +2863,23 @@ make_plan(const mcpp::manifest::Manifest& manifest,
28632863
for (auto i : closureIdx)
28642864
for (auto const& f : packages[i].linkUsage.ldflags)
28652865
group.ldflags.push_back(f);
2866+
// The runtime files are those of the closure and of every package a
2867+
// program the closure ships through `artifacts` reaches: the program
2868+
// is placed beside the member's programs and loads its own runtime
2869+
// there, as it did beside a root's program in `bin/`.
28662870
{
2871+
std::set<std::size_t> runtimeIdx = closureIdx;
2872+
std::vector<std::size_t> work;
2873+
for (auto const& [i, j] : artifactEdges)
2874+
if (closureIdx.contains(i) && runtimeIdx.insert(j).second) work.push_back(j);
2875+
while (!work.empty()) {
2876+
const auto i = work.back(); work.pop_back();
2877+
if (auto it = directPackageDeps.find(i); it != directPackageDeps.end())
2878+
for (auto j : it->second)
2879+
if (runtimeIdx.insert(j).second) work.push_back(j);
2880+
}
28672881
std::vector<mcpp::modgraph::PackageRoot> closurePackages;
2868-
for (auto i : closureIdx) closurePackages.push_back(packages[i]);
2882+
for (auto i : runtimeIdx) closurePackages.push_back(packages[i]);
28692883
derive_runtime(closurePackages, productDir, group);
28702884
}
28712885
const int groupIndex = static_cast<int>(plan.linkGroups.size());

‎tests/e2e/833_a_workspace_is_one_graph_per_configuration.sh‎

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@
1818
# G7 no member directory receives a build directory, and `clean --stale`
1919
# removes the ones members held before, keeping `target/.build-mcpp/`;
2020
# G8 a program a member ships through `artifacts` is beside that member's
21-
# program, in its product directory.
21+
# program, in its product directory;
22+
# G9 that program's own runtime files are beside it there, and its link
23+
# waits for no runtime file of another member (2026.9.29.2 made it wait
24+
# for cli's, which a workspace plan places only in bin/cli/, so the
25+
# build stopped with "missing and no known rule to make it").
2226
set -e
2327

2428
TMP=$(mktemp -d)
@@ -61,6 +65,14 @@ EOF
6165
done
6266
printf '#include <cstdio>\nimport shared_core;\nint main() { std::printf("%%d\\n", core_v() + 1); return 0; }\n' > cli/src/main.cpp
6367
printf '#include <cstdio>\nimport shared_core;\nint main() { std::printf("%%d\\n", core_v() + 2); return 0; }\n' > gui/src/main.cpp
68+
mkdir -p cli/data helper/data
69+
printf 'cli\n' > cli/data/cli.dat
70+
printf 'helper\n' > helper/data/helper.dat
71+
cat >> cli/mcpp.toml <<'EOF'
72+
73+
[runtime]
74+
deploy_files = ["data/cli.dat"]
75+
EOF
6476
cat >> gui/mcpp.toml <<'EOF'
6577
6678
[dependencies.helper]
@@ -75,6 +87,9 @@ version = "0.1.0"
7587
[targets.helper]
7688
kind = "bin"
7789
main = "src/main.cpp"
90+
91+
[runtime]
92+
deploy_files = ["data/helper.dat"]
7893
EOF
7994
printf 'int main() { return 0; }\n' > helper/src/main.cpp
8095

@@ -130,15 +145,26 @@ other=$(echo "$dirs" | grep -v "^$main_dir$")
130145
[ -f "$main_dir/bin/gui/helper$EXE" ] || fail "G8: bin/gui/helper is missing" ws.log
131146
[ -f "$main_dir/bin/helper/helper$EXE" ] || fail "G8: bin/helper/helper is missing" ws.log
132147

148+
# G9: the build above succeeded with a runtime file in cli's closure; the
149+
# program gui ships has its runtime file beside it, and cli's is cli's.
150+
[ -f "$main_dir/bin/gui/helper.dat" ] || fail "G9: bin/gui/helper.dat is missing" ws.log
151+
[ -f "$main_dir/bin/cli/cli.dat" ] || fail "G9: bin/cli/cli.dat is missing" ws.log
152+
[ ! -e "$main_dir/bin/gui/cli.dat" ] || fail "G9: cli's runtime file is in gui's product directory" ws.log
153+
133154
# G1: the core module is compiled once, in the one graph.
134155
log="$main_dir/.ninja_log"
135156
n=$(grep -c 'core\.m\.o' "$log" || true)
136157
[ "$n" = 1 ] || fail "G1: core's object was built $n times" "$log"
137158

138-
# G2: `-p cli` after `--workspace` compiles nothing.
139-
before=$(wc -l < "$log")
159+
# G2: `-p cli` after `--workspace` compiles and links nothing. Counted over
160+
# objects, module interfaces and cli's program: the placement of cli's runtime
161+
# file is one staging edge whose list names the selection, so a plan of
162+
# another selection copies the file again.
163+
built() { awk -F'\t' 'NR > 1 { print $4 }' "$log" \
164+
| grep -cE '\.(o|obj|gcm|pcm|ifc)$|^bin/cli/app' || true; }
165+
before=$(built)
140166
"$MCPP" build -p cli > p.log 2>&1 || fail "G2: -p cli failed" p.log
141-
after=$(wc -l < "$log")
167+
after=$(built)
142168
[ "$before" = "$after" ] || fail "G2: -p cli recompiled after --workspace" "$log"
143169

144170
# G3: an edit to gui's flags recompiles gui only.
@@ -150,7 +176,10 @@ cxxflags = ["-DGUI_EXTRA=1"]
150176
EOF
151177
before=$(wc -l < "$log")
152178
"$MCPP" build --workspace > g3.log 2>&1 || fail "G3: the build after the edit failed" g3.log
153-
tail -n +$((before + 1)) "$log" | cut -f4 > g3.edges
179+
# Objects, module interfaces and programs; the staging of runtime files after
180+
# a plan of another selection is not a rebuild (see G2).
181+
tail -n +$((before + 1)) "$log" | cut -f4 \
182+
| grep -E '\.(o|obj|gcm|pcm|ifc)$|/app(\.exe)?$' > g3.edges || true
154183
grep -q 'gui' g3.edges || fail "G3: gui was not rebuilt after its flags changed" g3.edges
155184
if grep -Eq 'core|/cli/|_cli|ns1|ns2' g3.edges; then
156185
fail "G3: an edit to gui's flags rebuilt another member" g3.edges

0 commit comments

Comments
 (0)