Skip to content

Commit cc5234c

Browse files
committed
fix: keep workspace executables independent of sibling shared targets
Retain a member's own module and implementation objects when it also produces a shared target. Keep declared external shared dependencies and test independent executable linkage with the sibling library removed.
1 parent 6e3cb97 commit cc5234c

2 files changed

Lines changed: 123 additions & 6 deletions

File tree

‎src/build/plan.cppm‎

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3087,9 +3087,12 @@ make_plan(const mcpp::manifest::Manifest& manifest,
30873087
"'{}', which target '{}' of this build also produces",
30883088
t.name, owner, lu.output.generic_string(), other.targetName));
30893089

3090+
// A sibling shared target is another image of this member's
3091+
// objects, not a dependency of its executable. Only another
3092+
// package's shared target supplies its implementation by link.
30903093
for (auto const& cu : plan.compileUnits) {
30913094
if (!closure.contains(cu.packageName)) continue;
3092-
if (sharedDepPackages.contains(cu.packageName)) continue;
3095+
if (cu.packageName != owner && sharedDepPackages.contains(cu.packageName)) continue;
30933096
if (mcpp::links_unconditionally(cu.kind)) lu.objects.push_back(cu.object);
30943097
}
30953098
if (!t.main.empty() && lu.kind != LinkUnit::StaticLibrary) {
@@ -3147,7 +3150,7 @@ make_plan(const mcpp::manifest::Manifest& manifest,
31473150
const bool entryDefinesMain = lu.entryMain && source_defines_main(*lu.entryMain);
31483151
for (auto const& cu : plan.compileUnits) {
31493152
if (!closure.contains(cu.packageName)) continue;
3150-
if (sharedDepPackages.contains(cu.packageName)) continue;
3153+
if (cu.packageName != owner && sharedDepPackages.contains(cu.packageName)) continue;
31513154
if (!is_implementation_source(cu.kind)) continue;
31523155
if (lu.entryMain && cu.source == *lu.entryMain) continue;
31533156
if (entryFilesAcrossTargets.contains(cu.source)) continue;
@@ -3159,11 +3162,12 @@ make_plan(const mcpp::manifest::Manifest& manifest,
31593162
}
31603163
if (lu.kind != LinkUnit::StaticLibrary) {
31613164
const auto before = lu.implicitInputs.size();
3162-
// The shared libraries the closure's packages link, and no
3163-
// other member's.
3165+
// The member still links its declared shared dependencies
3166+
// even when it also produces a shared target of its own.
31643167
for (auto i : closureIdx)
3165-
if (!sharedDepPackages.contains(qualified_package_name(packages[i].manifest))
3166-
&& !placedInImage.contains(qualified_package_name(packages[i].manifest)))
3168+
if (i == mi
3169+
|| (!sharedDepPackages.contains(qualified_package_name(packages[i].manifest))
3170+
&& !placedInImage.contains(qualified_package_name(packages[i].manifest))))
31673171
append_direct_shared_deps(lu, i);
31683172
// The graph-built shared libraries this unit loads are placed
31693173
// beside it.
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
#!/usr/bin/env bash
2+
# requires:
3+
# 880 -- mcpp#760: a member with shared and bin targets builds an independent
4+
# executable from its own implementation, not from its sibling shared library.
5+
#
6+
# A module interface, a separate implementation unit and a static dependency
7+
# must all reach the executable. Another member still consumes the shared
8+
# target. Both --workspace and an explicit member selection keep this rule.
9+
set -e
10+
source "$(dirname "$0")/_host_path.sh"
11+
12+
TMP=$(mktemp -d)
13+
trap 'rm -rf "$TMP"' EXIT
14+
fail() { echo "FAIL: $1"; shift; for f in "$@"; do echo "--- $f ---"; cat "$f" 2>/dev/null; done; exit 1; }
15+
MCPP="${MCPP:-mcpp}"
16+
17+
# Share the installed tool payloads, but keep the build and package caches
18+
# private. The fixture has no registry package dependencies; the default host
19+
# toolchain is resolved through the existing registry, as in the E2E runner.
20+
REGISTRY_HOST=$(host_path "${MCPP_HOME:-$HOME/.mcpp}/registry")
21+
export MCPP_HOME="$TMP/mcpp-home"
22+
mkdir -p "$MCPP_HOME" "$TMP/ws"
23+
cat > "$MCPP_HOME/config.toml" <<EOF
24+
[xlings]
25+
home = "$REGISTRY_HOST"
26+
EOF
27+
28+
EXE=""
29+
case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) EXE=".exe" ;; esac
30+
bin_of() { find target -path '*/bin/*' -name "$1$EXE" -type f | head -1; }
31+
32+
cd "$TMP/ws"
33+
cat > mcpp.toml <<'EOF'
34+
[workspace]
35+
members = ["dual", "client", "support"]
36+
EOF
37+
mkdir -p common/src dual/src client/src support/src
38+
cat > support/mcpp.toml <<'EOF'
39+
[package]
40+
name = "support"
41+
version = "0.1.0"
42+
43+
[targets.support]
44+
kind = "shared"
45+
EOF
46+
printf 'export module t880_support;\nexport int delta() { return 1; }\n' > support/src/support.cppm
47+
cat > common/mcpp.toml <<'EOF'
48+
[package]
49+
name = "common"
50+
version = "0.1.0"
51+
52+
[targets.common]
53+
kind = "lib"
54+
EOF
55+
printf 'export module t880_base;\nexport int base_value() { return 41; }\n' > common/src/base.cppm
56+
cat > dual/mcpp.toml <<'EOF'
57+
[package]
58+
name = "dual"
59+
version = "0.1.0"
60+
61+
[dependencies]
62+
common = { path = "../common" }
63+
support = { path = "../support" }
64+
65+
[targets.dual_dll]
66+
kind = "shared"
67+
68+
[targets.dual]
69+
kind = "bin"
70+
main = "src/main.cpp"
71+
EOF
72+
printf 'export module t880_dual;\nexport int interface_value() { return 1; }\nexport int answer();\n' > dual/src/dual.cppm
73+
printf 'module t880_dual;\nimport t880_base;\nimport t880_support;\nint answer() { return base_value() + delta(); }\n' > dual/src/impl.cpp
74+
printf 'import t880_dual;\nint main() { return answer() == 42 && interface_value() == 1 ? 0 : 1; }\n' > dual/src/main.cpp
75+
cat > client/mcpp.toml <<'EOF'
76+
[package]
77+
name = "client"
78+
version = "0.1.0"
79+
80+
[dependencies]
81+
dual = { path = "../dual" }
82+
83+
[targets.client]
84+
kind = "bin"
85+
main = "src/main.cpp"
86+
EOF
87+
cp dual/src/main.cpp client/src/main.cpp
88+
89+
"$MCPP" build --workspace > workspace.log 2>&1 || fail "the workspace did not build" workspace.log
90+
dual=$(bin_of dual)
91+
client=$(bin_of client)
92+
[ -n "$dual" ] && [ -n "$client" ] || fail "a member's executable is missing" workspace.log
93+
"$dual" || fail "the owner's executable did not run"
94+
"$client" || fail "the shared-library consumer did not run"
95+
96+
# Remove every placement of the sibling library. A client that imports it must
97+
# fail, while the owner's executable must still run: mere success with both
98+
# products beside it would also accept the import-library workaround for #760.
99+
mkdir hidden
100+
n=0
101+
while IFS= read -r library; do
102+
n=$((n + 1))
103+
mv "$library" "hidden/$n"
104+
done < <(find target -path '*/bin/*' \( -name '*dual_dll.dll' -o -name '*dual_dll.so*' -o -name '*dual_dll.dylib' \))
105+
[ "$n" -gt 0 ] || fail "the sibling shared library was not built" workspace.log
106+
"$dual" || fail "the owner's executable depends on its sibling shared library"
107+
if "$client" > client.log 2>&1; then
108+
fail "the external consumer did not use the shared library" client.log
109+
fi
110+
111+
"$MCPP" build -p dual > selected.log 2>&1 || fail "the selected member did not build" selected.log
112+
"$(bin_of dual)" || fail "the selected member's executable did not run"
113+
echo "PASS: 880_a_members_executable_keeps_its_own_implementation"

0 commit comments

Comments
 (0)