Skip to content

Commit f6a50ce

Browse files
committed
A member's product directory holds every graph-built shared library of its closure
1 parent 34920cb commit f6a50ce

4 files changed

Lines changed: 62 additions & 6 deletions

File tree

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,7 @@ read from the member, or made a value of the plan:
557557
| 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 |
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 |
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 |
560561

561562
Each criterion fails on 2026.9.29.1 and passes on 2026.9.29.2. The resource
562563
case also showed a defect of every build: a quoted `#include` in a script was
@@ -565,5 +566,10 @@ include directories, so a script that relied on them could not be built. The
565566
scan now resolves as the compiler does (unit
566567
`InputsAreResolvedThroughTheIncludeDirectories`).
567568

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+
568574
The rule the inventory missed is general: a virtual root answers for the plan,
569575
and a read of the root's manifest is correct only for a value of the plan.

‎CHANGELOG.md‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
This release corrects what a workspace plan reads from its members. The plan's
1010
root is a virtual root that holds the values shared by the whole graph; five
1111
statements that a member makes about itself were read from that root in
12-
2026.9.29.1 and were therefore empty. The mcpp-index sweep of 2026.9.29.1 found
13-
the first two.
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.
1415

1516
### Fixed
1617

@@ -28,6 +29,10 @@ the first two.
2829
member's programs and shared libraries only (e2e 837). A quoted `#include`
2930
or resource file in a script is now found through the include directories as
3031
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).
3136
- **`[build] linkage` is a value of the plan.** It chooses the C runtime that
3237
every object is compiled against, so members that differ in it are separate
3338
configurations, and the plan takes it from its members.

‎src/build/plan.cppm‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3018,6 +3018,30 @@ make_plan(const mcpp::manifest::Manifest& manifest,
30183018
}
30193019
units.push_back(std::move(lu));
30203020
}
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+
}
30213045
// A program the closure ships through `artifacts` (mcpp#711) is linked
30223046
// once, at `bin/`, and placed beside the member's programs, where a
30233047
// program that launches it looks for it.

‎tests/e2e/835_a_shared_library_is_placed_by_a_link.sh‎

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010
#
1111
# L1 each program runs from its product directory;
1212
# L2 the library in the two product directories is one file (one inode),
13-
# or, where links are not possible, two copies with equal content.
13+
# or, where links are not possible, two copies with equal content;
14+
# L3 a shared library that another shared library of the closure needs is
15+
# placed too (2026.9.29.1 placed only the libraries a member's own units
16+
# link, so a program whose library needs libffi did not start).
1417
set -e
1518

1619
TMP=$(mktemp -d)
@@ -21,18 +24,30 @@ MCPP="${MCPP:-mcpp}"
2124

2225
cat > mcpp.toml <<'EOF'
2326
[workspace]
24-
members = ["shlib", "p1", "p2"]
27+
members = ["base", "shlib", "p1", "p2"]
2528
EOF
26-
mkdir -p shlib/src p1/src p2/src
29+
mkdir -p base/src shlib/src p1/src p2/src
30+
cat > base/mcpp.toml <<'EOF'
31+
[package]
32+
name = "base"
33+
version = "0.1.0"
34+
35+
[targets.base]
36+
kind = "shared"
37+
EOF
38+
printf 'export module basis;\nexport int base_v() { return 6; }\n' > base/src/base.cppm
2739
cat > shlib/mcpp.toml <<'EOF'
2840
[package]
2941
name = "shlib"
3042
version = "0.1.0"
3143
44+
[dependencies]
45+
base = { path = "../base" }
46+
3247
[targets.shlib]
3348
kind = "shared"
3449
EOF
35-
printf 'export module shlib;\nexport int shared_v() { return 7; }\n' > shlib/src/shlib.cppm
50+
printf 'export module shlib;\nimport basis;\nexport int shared_v() { return base_v() + 1; }\n' > shlib/src/shlib.cppm
3651
for p in p1 p2; do
3752
cat > $p/mcpp.toml <<EOF
3853
[package]
@@ -71,4 +86,10 @@ if [ "$ia" != "$ib" ]; then
7186
echo "note: the placements are copies on this file system"
7287
fi
7388

89+
# L3
90+
for p in p1 p2; do
91+
[ -n "$(find "$dir/$p" -maxdepth 1 -name 'libbase.so*' | head -1)" ] \
92+
|| fail "L3: libbase, which libshlib needs, is not placed in bin/$p/" b.log
93+
done
94+
7495
echo "PASS: 835_a_shared_library_is_placed_by_a_link"

0 commit comments

Comments
 (0)