Skip to content

Commit 982fc73

Browse files
committed
2026.9.29.4: a program shipped through artifacts links with its own closure's line in a workspace plan
The validation project's post-release build of 2026.9.29.3 stopped at the link of obj/gpp_updater/Updater.exe with undefined references to Qt and bit7z symbols: the libraries are stated by the updater package's build program, and the program was linked with the plan's line, which pools the dependencies' link flags and not a member's. - In a workspace plan each program shipped through artifacts has a link group of its own closure (LinkGroup::linkOnly): the closure's link line and runtime contract, and no placement, since the members that ship the program place it and its runtime files. - e2e 838 states it with a library the helper's build program links (mcpp::link_lib); it fails on 2026.9.29.3 with "undefined reference" at obj/helper/helper and passes here. - Version 2026.9.29.4; design document section 17.1.
1 parent c73725b commit 982fc73

7 files changed

Lines changed: 143 additions & 8 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
@@ -559,6 +559,7 @@ read from the member, or made a value of the plan:
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 |
561561
| 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 |
562+
| the link line of a program shipped through `artifacts` (2026.9.29.4) | the plan's line, which pools the dependencies' flags and not a member's, so a library its package's build program states was missing | a link group of its own closure that places nothing (`LinkGroup::linkOnly`) | e2e 838 |
562563

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

‎CHANGELOG.md‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,23 @@
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.4] - 2026-09-29
8+
9+
This release links a program that a workspace member ships through
10+
`artifacts` with the link line of its own package's closure. The validation
11+
project's post-release build of 2026.9.29.3 found it.
12+
13+
### Fixed
14+
15+
- **A program shipped through `artifacts` links with its own closure's line in
16+
a workspace plan.** It was linked with the plan's line, which pools the
17+
dependencies' link flags and not a member's; a library that the program's
18+
package states through its build program (`mcpp::link_lib`) was therefore
19+
missing, and the link failed with undefined references (e2e 838). The
20+
program now has a link group of its own that holds its closure's line and
21+
runtime contract and places nothing, since the members that ship the program
22+
place it.
23+
724
## [2026.9.29.3] - 2026-09-29
825

926
This release completes the runtime placement of a program that a workspace

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

3636
} // namespace mcpp

‎src/build/ninja_backend.cppm‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1224,6 +1224,7 @@ std::string emit_ninja_string(const BuildPlan& plan, std::string* placements) {
12241224
std::vector<BuildPlan::DeployFile> placedFiles;
12251225
if (!plan.manifest.package.virtualRoot) placedFiles = deployFiles;
12261226
for (std::size_t g = 0; g < groupFlags.size(); ++g) {
1227+
if (plan.linkGroups[g].linkOnly) continue;
12271228
for (auto const& d : groupFlags[g].runtimeDeploy) placedFiles.push_back(d);
12281229
for (auto const& pl : plan.linkGroups[g].placements)
12291230
placedFiles.push_back({{pl.source}, pl.dest});
@@ -2844,12 +2845,16 @@ std::string emit_ninja_string(const BuildPlan& plan, std::string* placements) {
28442845
// deps), so other targets are unaffected.
28452846
//
28462847
// 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.
2848+
// program; see `placedFiles`), and the group of a program shipped
2849+
// through `artifacts` places none either, so such a program waits for
2850+
// none: it is placed in the members' product directories with their
2851+
// deploy sets.
28502852
std::string orderOnly;
28512853
if (lu.kind == LinkUnit::Binary || lu.kind == LinkUnit::TestBinary) {
2852-
if (lu.linkGroup >= 0 || !plan.manifest.package.virtualRoot)
2854+
const bool placesDeploy = lu.linkGroup >= 0
2855+
? !plan.linkGroups[static_cast<std::size_t>(lu.linkGroup)].linkOnly
2856+
: !plan.manifest.package.virtualRoot;
2857+
if (placesDeploy)
28532858
for (auto const& d : uflags.runtimeDeploy)
28542859
orderOnly += " " + escape_ninja_path(d.dest);
28552860
if (lu.linkGroup >= 0)

‎src/build/plan.cppm‎

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,11 @@ struct BuildPlan {
533533
// The graph-built shared libraries the member's units load, placed
534534
// beside them (a hard link where the file system allows, §5.3).
535535
std::vector<Placement> placements;
536+
// The group of a program shipped through `artifacts`: it holds the
537+
// link line and the runtime contract of the program's own closure,
538+
// and places nothing, since the members that ship the program place
539+
// it and its runtime files. `member` is empty.
540+
bool linkOnly = false;
536541
};
537542
std::vector<LinkGroup> linkGroups;
538543
};
@@ -2725,9 +2730,9 @@ make_plan(const mcpp::manifest::Manifest& manifest,
27252730
r.target.name, owner, lu.output.generic_string(), other.targetName));
27262731

27272732
std::set<std::string> closure{owner};
2733+
std::set<std::size_t> seen{r.packageIndex};
27282734
{
27292735
std::vector<std::size_t> work{r.packageIndex};
2730-
std::set<std::size_t> seen{r.packageIndex};
27312736
while (!work.empty()) {
27322737
const auto i = work.back(); work.pop_back();
27332738
if (auto it = directPackageDeps.find(i); it != directPackageDeps.end())
@@ -2795,6 +2800,23 @@ make_plan(const mcpp::manifest::Manifest& manifest,
27952800
lu.objects.push_back(cu.object);
27962801
}
27972802
append_shared_deps_for_linked_objects(lu);
2803+
// In a workspace plan the plan's own line pools the dependencies'
2804+
// flags and not a member's, so the program links with its closure's
2805+
// line, as a member's program does, in a group that places nothing.
2806+
if (manifest.package.virtualRoot) {
2807+
BuildPlan::LinkGroup group;
2808+
group.linkOnly = true;
2809+
group.productDir = lu.output.parent_path();
2810+
group.ldflags = packages[0].linkUsage.ldflags;
2811+
for (auto i : seen)
2812+
for (auto const& f : packages[i].linkUsage.ldflags)
2813+
group.ldflags.push_back(f);
2814+
std::vector<mcpp::modgraph::PackageRoot> closurePackages;
2815+
for (auto i : seen) closurePackages.push_back(packages[i]);
2816+
derive_runtime(closurePackages, group.productDir, group);
2817+
lu.linkGroup = static_cast<int>(plan.linkGroups.size());
2818+
plan.linkGroups.push_back(std::move(group));
2819+
}
27982820
plan.linkUnits.push_back(std::move(lu));
27992821
}
28002822

@@ -3082,7 +3104,7 @@ make_plan(const mcpp::manifest::Manifest& manifest,
30823104
// its path as a file (a program of the workspace's own package named as a
30833105
// member, say).
30843106
for (auto const& g : plan.linkGroups) {
3085-
if (g.productDir == std::filesystem::path("bin")) continue;
3107+
if (g.linkOnly || g.productDir == std::filesystem::path("bin")) continue;
30863108
for (auto const& u : plan.linkUnits) {
30873109
bool clash = u.output == g.productDir;
30883110
for (auto const& a : u.runtimeAliases) clash = clash || a == g.productDir;
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#!/usr/bin/env bash
2+
# requires: elf
3+
# 838_a_program_shipped_through_artifacts_links_its_own_closure.sh --
4+
# workspace design 2026-09-29 §15 and §17.1.
5+
#
6+
# A program a member ships through `artifacts` is linked with the link line of
7+
# its own package's closure. Its package's build program states a library the
8+
# program needs (`mcpp::link_lib`); in a workspace plan that statement belongs
9+
# to the package, not to the plan, and 2026.9.29.3 linked the program with
10+
# the plan's line, so the library was missing ("undefined reference").
11+
#
12+
# A1 `mcpp build --workspace` links the program shipped through `artifacts`;
13+
# A2 the program in the shipping member's product directory runs;
14+
# A3 the program as its own member's product runs too.
15+
set -e
16+
17+
TMP=$(mktemp -d)
18+
trap "rm -rf $TMP" EXIT
19+
cd "$TMP"
20+
fail() { echo "FAIL: $1"; shift; for f in "$@"; do echo "--- $f ---"; cat "$f" 2>/dev/null; done; exit 1; }
21+
MCPP="${MCPP:-mcpp}"
22+
23+
# A library the helper links through its build program, built as an archive
24+
# by a project of its own and copied into the helper's package.
25+
mkdir -p vend/src
26+
cat > vend/mcpp.toml <<'EOF'
27+
[package]
28+
name = "vend"
29+
version = "0.1.0"
30+
31+
[targets.vend]
32+
kind = "lib"
33+
EOF
34+
printf 'extern "C" int vend_v() { return 7; }\n' > vend/src/vend.cpp
35+
(cd vend && "$MCPP" build > ../vend.log 2>&1) || fail "the archive project did not build" vend.log
36+
archive=$(find vend/target -name 'libvend.a' | head -1)
37+
[ -n "$archive" ] || fail "no libvend.a" vend.log
38+
39+
mkdir -p ws && cd ws
40+
cat > mcpp.toml <<'EOF'
41+
[workspace]
42+
members = ["gui", "helper"]
43+
EOF
44+
mkdir -p gui/src helper/src helper/vendor/lib
45+
cp "../$archive" helper/vendor/lib/
46+
cat > helper/mcpp.toml <<'EOF'
47+
[package]
48+
name = "helper"
49+
version = "0.1.0"
50+
51+
[targets.helper]
52+
kind = "bin"
53+
main = "src/main.cpp"
54+
EOF
55+
cat > helper/build.mcpp <<'EOF'
56+
import mcpp;
57+
int main() {
58+
mcpp::link_search("vendor/lib");
59+
mcpp::link_lib("vend");
60+
return 0;
61+
}
62+
EOF
63+
printf 'extern "C" int vend_v();\nint main() { return vend_v() == 7 ? 0 : 1; }\n' > helper/src/main.cpp
64+
cat > gui/mcpp.toml <<'EOF'
65+
[package]
66+
name = "gui"
67+
version = "0.1.0"
68+
69+
[dependencies.helper]
70+
path = "../helper"
71+
artifacts = ["helper"]
72+
73+
[targets.app]
74+
kind = "bin"
75+
main = "src/main.cpp"
76+
EOF
77+
printf 'int main() { return 0; }\n' > gui/src/main.cpp
78+
79+
# A1
80+
"$MCPP" build --workspace > b.log 2>&1 || fail "A1: the workspace build failed" b.log
81+
82+
# A2, A3
83+
shipped=$(find target -path '*/bin/gui/helper' -type f | head -1)
84+
[ -n "$shipped" ] || fail "A2: bin/gui/helper is missing" b.log
85+
"$shipped" || fail "A2: bin/gui/helper did not run"
86+
own=$(find target -path '*/bin/helper/helper' -type f | head -1)
87+
[ -n "$own" ] || fail "A3: bin/helper/helper is missing" b.log
88+
"$own" || fail "A3: bin/helper/helper did not run"
89+
90+
echo "PASS: 838_a_program_shipped_through_artifacts_links_its_own_closure"

0 commit comments

Comments
 (0)