From 4151a0e2a324a2500c21eeb02bfd747c4046c2a5 Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 29 Sep 2026 12:04:17 +0800 Subject: [PATCH] 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. --- ...2026-09-29-workspace-build-graph-design.md | 1 + CHANGELOG.md | 19 +++++++++ mcpp.toml | 2 +- modules/versioning/src/version.cppm | 2 +- src/build/ninja_backend.cppm | 10 ++++- src/build/plan.cppm | 16 +++++++- ...orkspace_is_one_graph_per_configuration.sh | 39 ++++++++++++++++--- 7 files changed, 79 insertions(+), 10 deletions(-) diff --git a/.agents/docs/2026-09-29-workspace-build-graph-design.md b/.agents/docs/2026-09-29-workspace-build-graph-design.md index d84af43c..9682eae9 100644 --- a/.agents/docs/2026-09-29-workspace-build-graph-design.md +++ b/.agents/docs/2026-09-29-workspace-build-graph-design.md @@ -558,6 +558,7 @@ read from the member, or made a value of the plan: | `[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 | +| 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 | 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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 24faa9fb..f4d89d74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,25 @@ > Each `## []` 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.3] - 2026-09-29 + +This release completes the runtime placement of a program that a workspace +member ships through `artifacts`. The validation project's post-release build +of 2026.9.29.2 found it. + +### Fixed + +- **A program shipped through `artifacts` no longer waits for a runtime file + that a workspace plan never places.** Its link edge depended on the plan's + own deploy set, which a workspace plan does not place (`bin/` holds products + only), so a workspace whose members declare runtime files stopped with + "missing and no known rule to make it" (e2e 833 G9). +- **The runtime files of such a program are beside it.** A member's runtime + set includes the closure of every package whose program the member ships + through `artifacts`, so the program finds its own runtime files in the + member's product directory, as it did beside a root's program in `bin/` + (e2e 833 G9). + ## [2026.9.29.2] - 2026-09-29 This release corrects what a workspace plan reads from its members. The plan's diff --git a/mcpp.toml b/mcpp.toml index 13293072..a7bebe4e 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,6 +1,6 @@ [package] name = "mcpp" -version = "2026.9.29.2" +version = "2026.9.29.3" description = "Modern C++ build & package management tool" license = "Apache-2.0" authors = ["mcpp-community"] diff --git a/modules/versioning/src/version.cppm b/modules/versioning/src/version.cppm index a863d91a..a1dae570 100644 --- a/modules/versioning/src/version.cppm +++ b/modules/versioning/src/version.cppm @@ -31,6 +31,6 @@ import std; export namespace mcpp { -inline constexpr std::string_view MCPP_VERSION = "2026.9.29.2"; +inline constexpr std::string_view MCPP_VERSION = "2026.9.29.3"; } // namespace mcpp diff --git a/src/build/ninja_backend.cppm b/src/build/ninja_backend.cppm index 40fc6f96..7f3eb68f 100644 --- a/src/build/ninja_backend.cppm +++ b/src/build/ninja_backend.cppm @@ -2842,10 +2842,16 @@ std::string emit_ninja_string(const BuildPlan& plan, std::string* placements) { // action populated after the first plan), relinked a program whose // link inputs had not changed. Empty on RPATH platforms (no *.dll // deps), so other targets are unaffected. + // + // A workspace plan places no deploy set of its own (its root has no + // program; see `placedFiles`), so a unit outside the link groups, a + // program shipped through `artifacts`, waits for none: it is placed in + // the members' product directories with their deploy sets. std::string orderOnly; if (lu.kind == LinkUnit::Binary || lu.kind == LinkUnit::TestBinary) { - for (auto const& d : uflags.runtimeDeploy) - orderOnly += " " + escape_ninja_path(d.dest); + if (lu.linkGroup >= 0 || !plan.manifest.package.virtualRoot) + for (auto const& d : uflags.runtimeDeploy) + orderOnly += " " + escape_ninja_path(d.dest); if (lu.linkGroup >= 0) for (auto const& pl : plan.linkGroups[static_cast(lu.linkGroup)].placements) orderOnly += " " + escape_ninja_path(pl.dest); diff --git a/src/build/plan.cppm b/src/build/plan.cppm index dc6a3642..298aa2e2 100644 --- a/src/build/plan.cppm +++ b/src/build/plan.cppm @@ -2863,9 +2863,23 @@ make_plan(const mcpp::manifest::Manifest& manifest, for (auto i : closureIdx) for (auto const& f : packages[i].linkUsage.ldflags) group.ldflags.push_back(f); + // The runtime files are those of the closure and of every package a + // program the closure ships through `artifacts` reaches: the program + // is placed beside the member's programs and loads its own runtime + // there, as it did beside a root's program in `bin/`. { + std::set runtimeIdx = closureIdx; + std::vector work; + for (auto const& [i, j] : artifactEdges) + if (closureIdx.contains(i) && runtimeIdx.insert(j).second) work.push_back(j); + while (!work.empty()) { + const auto i = work.back(); work.pop_back(); + if (auto it = directPackageDeps.find(i); it != directPackageDeps.end()) + for (auto j : it->second) + if (runtimeIdx.insert(j).second) work.push_back(j); + } std::vector closurePackages; - for (auto i : closureIdx) closurePackages.push_back(packages[i]); + for (auto i : runtimeIdx) closurePackages.push_back(packages[i]); derive_runtime(closurePackages, productDir, group); } const int groupIndex = static_cast(plan.linkGroups.size()); diff --git a/tests/e2e/833_a_workspace_is_one_graph_per_configuration.sh b/tests/e2e/833_a_workspace_is_one_graph_per_configuration.sh index caf33e10..01074c06 100755 --- a/tests/e2e/833_a_workspace_is_one_graph_per_configuration.sh +++ b/tests/e2e/833_a_workspace_is_one_graph_per_configuration.sh @@ -18,7 +18,11 @@ # G7 no member directory receives a build directory, and `clean --stale` # removes the ones members held before, keeping `target/.build-mcpp/`; # G8 a program a member ships through `artifacts` is beside that member's -# program, in its product directory. +# program, in its product directory; +# G9 that program's own runtime files are beside it there, and its link +# waits for no runtime file of another member (2026.9.29.2 made it wait +# for cli's, which a workspace plan places only in bin/cli/, so the +# build stopped with "missing and no known rule to make it"). set -e TMP=$(mktemp -d) @@ -61,6 +65,14 @@ EOF done printf '#include \nimport shared_core;\nint main() { std::printf("%%d\\n", core_v() + 1); return 0; }\n' > cli/src/main.cpp printf '#include \nimport shared_core;\nint main() { std::printf("%%d\\n", core_v() + 2); return 0; }\n' > gui/src/main.cpp +mkdir -p cli/data helper/data +printf 'cli\n' > cli/data/cli.dat +printf 'helper\n' > helper/data/helper.dat +cat >> cli/mcpp.toml <<'EOF' + +[runtime] +deploy_files = ["data/cli.dat"] +EOF cat >> gui/mcpp.toml <<'EOF' [dependencies.helper] @@ -75,6 +87,9 @@ version = "0.1.0" [targets.helper] kind = "bin" main = "src/main.cpp" + +[runtime] +deploy_files = ["data/helper.dat"] EOF printf 'int main() { return 0; }\n' > helper/src/main.cpp @@ -130,15 +145,26 @@ other=$(echo "$dirs" | grep -v "^$main_dir$") [ -f "$main_dir/bin/gui/helper$EXE" ] || fail "G8: bin/gui/helper is missing" ws.log [ -f "$main_dir/bin/helper/helper$EXE" ] || fail "G8: bin/helper/helper is missing" ws.log +# G9: the build above succeeded with a runtime file in cli's closure; the +# program gui ships has its runtime file beside it, and cli's is cli's. +[ -f "$main_dir/bin/gui/helper.dat" ] || fail "G9: bin/gui/helper.dat is missing" ws.log +[ -f "$main_dir/bin/cli/cli.dat" ] || fail "G9: bin/cli/cli.dat is missing" ws.log +[ ! -e "$main_dir/bin/gui/cli.dat" ] || fail "G9: cli's runtime file is in gui's product directory" ws.log + # G1: the core module is compiled once, in the one graph. log="$main_dir/.ninja_log" n=$(grep -c 'core\.m\.o' "$log" || true) [ "$n" = 1 ] || fail "G1: core's object was built $n times" "$log" -# G2: `-p cli` after `--workspace` compiles nothing. -before=$(wc -l < "$log") +# G2: `-p cli` after `--workspace` compiles and links nothing. Counted over +# objects, module interfaces and cli's program: the placement of cli's runtime +# file is one staging edge whose list names the selection, so a plan of +# another selection copies the file again. +built() { awk -F'\t' 'NR > 1 { print $4 }' "$log" \ + | grep -cE '\.(o|obj|gcm|pcm|ifc)$|^bin/cli/app' || true; } +before=$(built) "$MCPP" build -p cli > p.log 2>&1 || fail "G2: -p cli failed" p.log -after=$(wc -l < "$log") +after=$(built) [ "$before" = "$after" ] || fail "G2: -p cli recompiled after --workspace" "$log" # G3: an edit to gui's flags recompiles gui only. @@ -150,7 +176,10 @@ cxxflags = ["-DGUI_EXTRA=1"] EOF before=$(wc -l < "$log") "$MCPP" build --workspace > g3.log 2>&1 || fail "G3: the build after the edit failed" g3.log -tail -n +$((before + 1)) "$log" | cut -f4 > g3.edges +# Objects, module interfaces and programs; the staging of runtime files after +# a plan of another selection is not a rebuild (see G2). +tail -n +$((before + 1)) "$log" | cut -f4 \ + | grep -E '\.(o|obj|gcm|pcm|ifc)$|/app(\.exe)?$' > g3.edges || true grep -q 'gui' g3.edges || fail "G3: gui was not rebuilt after its flags changed" g3.edges if grep -Eq 'core|/cli/|_cli|ns1|ns2' g3.edges; then fail "G3: an edit to gui's flags rebuilt another member" g3.edges