Skip to content

Commit 5c8d966

Browse files
committed
fix: preserve shared-library owner link configuration
1 parent 8d9bde2 commit 5c8d966

4 files changed

Lines changed: 282 additions & 37 deletions

File tree

‎src/build/plan.cppm‎

Lines changed: 65 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2537,35 +2537,6 @@ make_plan(const mcpp::manifest::Manifest& manifest,
25372537
}
25382538
};
25392539

2540-
for (auto const& dep : sharedDepTargets) {
2541-
LinkUnit lu;
2542-
lu.targetName = dep.target.name;
2543-
lu.package = dep.packageName;
2544-
lu.kind = LinkUnit::SharedLibrary;
2545-
lu.dependencyOwned = true;
2546-
lu.output = dep.output;
2547-
lu.importLibrary = import_library_for(dep.target, naming);
2548-
if (msvcTarget && !dep.target.windowsAutoExport && !dep.target.exportPatterns.empty())
2549-
return std::unexpected(exports_without_discovery(dep.target, dep.packageName));
2550-
if (msvcTarget && dep.target.windowsAutoExport && !lu.importLibrary.empty())
2551-
lu.defFile = std::filesystem::path("bin") / (dep.target.name + ".def");
2552-
lu.soname = dep.target.soname;
2553-
lu.exportPatterns = dep.target.exportPatterns;
2554-
lu.runtimeAliases = runtime_aliases_for_target(dep.target, naming);
2555-
lu.loaderTagFlag = loader_tag_flag(lu.kind);
2556-
append_package_objects(lu, dep.packageName);
2557-
append_direct_shared_deps(lu, dep.packageIndex);
2558-
if (auto it = staticsByImagePackage.find(dep.packageIndex);
2559-
it != staticsByImagePackage.end()) {
2560-
for (auto staticIndex : it->second) {
2561-
append_package_objects(
2562-
lu, qualified_package_name(packages[staticIndex].manifest));
2563-
append_direct_shared_deps(lu, staticIndex);
2564-
}
2565-
}
2566-
plan.linkUnits.push_back(std::move(lu));
2567-
}
2568-
25692540
// THE PROGRAMS A CONSUMER SHIPS FROM ITS DEPENDENCIES (mcpp#711).
25702541
//
25712542
// An edge `x = { ..., artifacts = ["updater"] }` asks for the dependency's
@@ -2615,6 +2586,71 @@ make_plan(const mcpp::manifest::Manifest& manifest,
26152586
}
26162587
}
26172588
}
2589+
// 共享映像先于工作区成员创建,但链接配置仍属于它自己的包。
2590+
// 同包的多个 DLL 共用配置组;只生成链接配置,不重复安排产物部署。
2591+
std::map<std::size_t, int> sharedLinkGroups;
2592+
for (auto const& dep : sharedDepTargets) {
2593+
LinkUnit lu;
2594+
lu.targetName = dep.target.name;
2595+
lu.package = dep.packageName;
2596+
lu.kind = LinkUnit::SharedLibrary;
2597+
lu.dependencyOwned = true;
2598+
lu.output = dep.output;
2599+
lu.importLibrary = import_library_for(dep.target, naming);
2600+
if (msvcTarget && !dep.target.windowsAutoExport && !dep.target.exportPatterns.empty())
2601+
return std::unexpected(exports_without_discovery(dep.target, dep.packageName));
2602+
if (msvcTarget && dep.target.windowsAutoExport && !lu.importLibrary.empty())
2603+
lu.defFile = std::filesystem::path("bin") / (dep.target.name + ".def");
2604+
lu.soname = dep.target.soname;
2605+
lu.exportPatterns = dep.target.exportPatterns;
2606+
lu.runtimeAliases = runtime_aliases_for_target(dep.target, naming);
2607+
lu.loaderTagFlag = loader_tag_flag(lu.kind);
2608+
if (!sharedLinkGroups.contains(dep.packageIndex)) {
2609+
std::set<std::size_t> closure = {dep.packageIndex};
2610+
std::vector<std::size_t> pending = {dep.packageIndex};
2611+
while (!pending.empty()) {
2612+
const auto i = pending.back();
2613+
pending.pop_back();
2614+
if (auto edges = directPackageDeps.find(i); edges != directPackageDeps.end()) {
2615+
for (auto j : edges->second) {
2616+
if (artifactEdges.contains({i, j}) || packages[j].buildTimeOnly) continue;
2617+
if (closure.insert(j).second) pending.push_back(j);
2618+
}
2619+
}
2620+
}
2621+
BuildPlan::LinkGroup group;
2622+
group.linkOnly = true;
2623+
group.productDir = lu.output.parent_path();
2624+
// 虚拟根只含工作区配置;普通消费者的私有链接参数不能进入依赖 DLL。
2625+
if (manifest.package.virtualRoot) group.ldflags = packages[0].linkUsage.ldflags;
2626+
group.ldflags.insert(group.ldflags.end(), packages[dep.packageIndex].linkUsage.ldflags.begin(),
2627+
packages[dep.packageIndex].linkUsage.ldflags.end());
2628+
std::vector<mcpp::modgraph::PackageRoot> closurePackages;
2629+
closurePackages.push_back(packages[dep.packageIndex]);
2630+
for (auto i : closure) {
2631+
if (i == dep.packageIndex) continue;
2632+
group.ldflags.insert(group.ldflags.end(), packages[i].linkUsage.ldflags.begin(),
2633+
packages[i].linkUsage.ldflags.end());
2634+
closurePackages.push_back(packages[i]);
2635+
}
2636+
derive_runtime(closurePackages, group.productDir, group);
2637+
sharedLinkGroups[dep.packageIndex] = static_cast<int>(plan.linkGroups.size());
2638+
plan.linkGroups.push_back(std::move(group));
2639+
}
2640+
lu.linkGroup = sharedLinkGroups.at(dep.packageIndex);
2641+
append_package_objects(lu, dep.packageName);
2642+
append_direct_shared_deps(lu, dep.packageIndex);
2643+
if (auto it = staticsByImagePackage.find(dep.packageIndex);
2644+
it != staticsByImagePackage.end()) {
2645+
for (auto staticIndex : it->second) {
2646+
append_package_objects(
2647+
lu, qualified_package_name(packages[staticIndex].manifest));
2648+
append_direct_shared_deps(lu, staticIndex);
2649+
}
2650+
}
2651+
plan.linkUnits.push_back(std::move(lu));
2652+
}
2653+
26182654
// Reached through a non-artifact edge from the root (its dependencies,
26192655
// dev- and build-dependencies included), versus reached only through an
26202656
// artifact edge. Only the second set is withheld from the root's images,

‎src/build/prepare/plan.cpp‎

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -422,14 +422,11 @@ static std::expected<void, std::string> step13_link_forms(PrepareState& state, B
422422
}
423423

424424
static std::expected<void, std::string> step13_make_plan(PrepareState& state, BuildContext& ctx) {
425-
// A workspace plan links each member's closure with that closure's own
426-
// flags (workspace design 2026-09-29 §15): each package's link flags,
427-
// its build program's included, with search paths made absolute against
428-
// the package, as the root's pooled list holds them outside a workspace.
429-
if (state.workspacePlan())
430-
for (std::size_t i = 1; i < state.packages.size(); ++i)
431-
state.packages[i].linkUsage.ldflags = normalized_dependency_ldflags(
432-
state.packages[i].root, state.packages[i].manifest.buildConfig.ldflags);
425+
// 每个映像都读取所属包的链接快照,包括普通 path 依赖中的 DLL。
426+
// 脚本执行后统一刷新,并把搜索路径解析到声明它的包,不能只更新工作区成员。
427+
for (std::size_t i = 1; i < state.packages.size(); ++i)
428+
state.packages[i].linkUsage.ldflags = normalized_dependency_ldflags(
429+
state.packages[i].root, state.packages[i].manifest.buildConfig.ldflags);
433430
auto planResult = mcpp::build::make_plan(*state.m, *state.tc, state.fp, state.scan.graph, state.report.topoOrder,
434431
state.packages, *state.root, ctx.outputDir,
435432
state.stdBmiPath, state.stdObjectPath, state.storeRoots);
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
#!/usr/bin/env bash
2+
# requires: unix-shell
3+
# DLL 的脚本库与静态依赖的链接选项必须在该 DLL 自己的链接边生效。
4+
set -euo pipefail
5+
MCPP="${MCPP:-mcpp}"
6+
TMP=$(mktemp -d)
7+
trap 'rm -rf "$TMP"' EXIT
8+
fail() { echo "FAIL: $1"; cat "$2"; exit 1; }
9+
cd "$TMP"
10+
mkdir -p owner/src helper/src app/src standalone/src
11+
cat > mcpp.toml <<'EOF'
12+
[workspace]
13+
members = ["owner", "app"]
14+
EOF
15+
cat > owner/mcpp.toml <<'EOF'
16+
[package]
17+
name = "owner"
18+
version = "0.1.0"
19+
[build]
20+
sources = ["src/*.c"]
21+
[dependencies]
22+
helper = { path = "../helper" }
23+
[targets.owner]
24+
kind = "shared"
25+
EOF
26+
cat > owner/build.mcpp <<'EOF'
27+
import std;
28+
import mcpp;
29+
int main() {
30+
if (std::string_view(mcpp::target_os()) == "windows") {
31+
mcpp::link_lib("bcrypt");
32+
} else {
33+
mcpp::link_lib("m");
34+
}
35+
}
36+
EOF
37+
cat > helper/mcpp.toml <<'EOF'
38+
[package]
39+
name = "helper"
40+
version = "0.1.0"
41+
[build]
42+
sources = ["src/*.c"]
43+
EOF
44+
cat > helper/build.mcpp <<'EOF'
45+
import std;
46+
import mcpp;
47+
int main() {
48+
if (std::string_view(mcpp::target_os()) == "windows") {
49+
mcpp::link_lib("userenv");
50+
mcpp::link_lib("delayimp");
51+
mcpp::link_flag("-Wl,/DELAYLOAD:userenv.dll");
52+
} else {
53+
mcpp::link_lib("m");
54+
}
55+
}
56+
EOF
57+
cat > helper/src/helper.c <<'EOF'
58+
#ifdef _WIN32
59+
#include <windows.h>
60+
#include <userenv.h>
61+
int helper_value(void) {
62+
DWORD length = 0;
63+
GetProfilesDirectoryW(NULL, &length);
64+
return length != 0;
65+
}
66+
#else
67+
#include <math.h>
68+
int helper_value(void) { volatile double x = 0.0; return cos(x) == 1.0; }
69+
#endif
70+
EOF
71+
cat > owner/src/owner.c <<'EOF'
72+
int helper_value(void);
73+
#ifdef _WIN32
74+
#include <windows.h>
75+
#include <bcrypt.h>
76+
__declspec(dllexport) int owner_value(void) {
77+
unsigned char byte;
78+
return BCryptGenRandom(NULL, &byte, 1, BCRYPT_USE_SYSTEM_PREFERRED_RNG) == 0
79+
&& helper_value() ? 42 : 0;
80+
}
81+
#else
82+
#include <math.h>
83+
int owner_value(void) {
84+
volatile double x = 0.0;
85+
return sin(x) == 0.0 && helper_value() ? 42 : 0;
86+
}
87+
#endif
88+
EOF
89+
for name in app standalone; do
90+
cat > "$name/mcpp.toml" <<EOF
91+
[package]
92+
name = "$name"
93+
version = "0.1.0"
94+
[dependencies]
95+
owner = { path = "../owner" }
96+
[targets.$name]
97+
kind = "bin"
98+
main = "src/main.c"
99+
EOF
100+
printf 'int owner_value(void);\nint main(void) { return owner_value() == 42 ? 0 : 1; }\n' > "$name/src/main.c"
101+
done
102+
for mode in workspace member; do
103+
args=(--workspace)
104+
[ "$mode" != member ] || args=(-p app)
105+
"$MCPP" build "${args[@]}" > "$mode.log" 2>&1 || fail "$mode lost the DLL's link configuration" "$mode.log"
106+
exe=$(find target -type f \( -name app -o -name app.exe \) -path '*/bin/*' | head -1)
107+
[ -n "$exe" ] || fail "$mode built no app" "$mode.log"
108+
"$exe" || fail "$mode app could not use its shared library" "$mode.log"
109+
done
110+
"$MCPP" build -p owner > owner.log 2>&1 || fail "selecting only the DLL owner lost its flags" owner.log
111+
cd standalone
112+
"$MCPP" build > standalone.log 2>&1 || fail "a path dependency lost its DLL link configuration" standalone.log
113+
exe=$(find target -type f \( -name standalone -o -name standalone.exe \) -path '*/bin/*' | head -1)
114+
[ -n "$exe" ] || fail "the ordinary path consumer built no program" standalone.log
115+
"$exe" || fail "the ordinary path consumer could not call the DLL" standalone.log
116+
echo "ok: shared images use their owners' and static dependencies' link configuration"
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#include <gtest/gtest.h>
2+
3+
import std;
4+
import mcpp.build.plan;
5+
import mcpp.build.ninja;
6+
import mcpp.manifest;
7+
import mcpp.modgraph.scanner;
8+
import mcpp.toolchain.model;
9+
10+
namespace {
11+
12+
mcpp::modgraph::PackageRoot package(const std::filesystem::path& root,
13+
std::string name, std::string flag) {
14+
mcpp::modgraph::PackageRoot p;
15+
p.root = root / name;
16+
p.manifest.package.name = std::move(name);
17+
p.manifest.package.version = "0.1.0";
18+
p.linkUsage.ldflags = {std::move(flag)};
19+
return p;
20+
}
21+
22+
} // namespace
23+
24+
// DLL 的链接配置属于它的包,不能从消费者或其它工作区成员借来。
25+
TEST(SharedLinkUsage, OwnerAndDependencyFlagsReachEverySharedImage) {
26+
using mcpp::manifest::DependencySpec;
27+
using mcpp::manifest::Target;
28+
for (const bool workspace : {false, true}) {
29+
for (const auto triple : {"x86_64-linux-gnu", "x86_64-pc-windows-msvc"}) {
30+
const auto root = std::filesystem::temp_directory_path() / "mcpp-shared-link-usage";
31+
auto app = package(root, "app", "-lconsumer_only");
32+
auto owner = package(root, "owner", "-lowner_only");
33+
auto helper = package(root, "helper", "-lhelper_only");
34+
auto unrelated = package(root, "unrelated", "-lunrelated_only");
35+
owner.manifest.runtimeConfig.linkIntent.linkLibraryDirs = {"owner-lib"};
36+
owner.manifest.runtimeConfig.linkIntent.runtimeSearchDirs = {"owner-runtime"};
37+
owner.manifest.runtimeConfig.requirements.push_back({.kind = "soname", .value = "owner-plugin"});
38+
owner.manifest.runtimeConfig.artifacts.push_back({.role = "plugin", .path = "owner-plugin"});
39+
owner.manifest.runtimeConfig.provides = {"owner-capability"};
40+
helper.manifest.runtimeConfig.linkIntent.libraries = {"helper-runtime-lib"};
41+
unrelated.manifest.runtimeConfig.linkIntent.linkLibraryDirs = {"unrelated-lib"};
42+
app.manifest.package.virtualRoot = workspace;
43+
if (workspace) app.linkUsage.ldflags.clear();
44+
app.manifest.dependencies["owner"] = DependencySpec{.path = "../owner"};
45+
app.manifest.dependencies["unrelated"] = DependencySpec{.path = "../unrelated"};
46+
owner.manifest.dependencies["helper"] = DependencySpec{.path = "../helper"};
47+
owner.manifest.targets.push_back({.name = "owner_a", .kind = Target::SharedLibrary});
48+
owner.manifest.targets.push_back({.name = "owner_b", .kind = Target::SharedLibrary});
49+
owner.selectedMember = workspace;
50+
owner.memberProducts = "owner";
51+
std::vector<mcpp::modgraph::PackageRoot> packages = {app, owner, helper, unrelated};
52+
mcpp::toolchain::Toolchain tc;
53+
tc.compiler = mcpp::toolchain::CompilerId::Clang;
54+
tc.targetTriple = triple;
55+
const auto plan = mcpp::build::make_plan(app.manifest, tc, {}, {}, {}, packages,
56+
app.root, root / "target", {}, {});
57+
ASSERT_TRUE(plan.has_value()) << plan.error();
58+
std::size_t measured = 0;
59+
for (const auto& unit : plan->linkUnits) {
60+
if (unit.package != "owner") continue;
61+
++measured;
62+
ASSERT_GE(unit.linkGroup, 0) << unit.targetName << " " << triple;
63+
const auto& group = plan->linkGroups.at(static_cast<std::size_t>(unit.linkGroup));
64+
EXPECT_TRUE(group.linkOnly);
65+
EXPECT_TRUE(group.placements.empty());
66+
EXPECT_NE(std::ranges::find(group.ldflags, "-lowner_only"), group.ldflags.end());
67+
EXPECT_NE(std::ranges::find(group.ldflags, "-lhelper_only"), group.ldflags.end());
68+
EXPECT_EQ(std::ranges::find(group.ldflags, "-lconsumer_only"), group.ldflags.end());
69+
EXPECT_EQ(std::ranges::find(group.ldflags, "-lunrelated_only"), group.ldflags.end());
70+
EXPECT_EQ(group.linkIntent.linkLibraryDirs,
71+
std::vector<std::filesystem::path>{owner.root / "owner-lib"});
72+
EXPECT_EQ(group.runtimeLibraryDirs,
73+
std::vector<std::filesystem::path>{owner.root / "owner-runtime"});
74+
EXPECT_EQ(group.linkIntent.libraries, std::vector<std::string>{"helper-runtime-lib"});
75+
EXPECT_EQ(group.runtimeDlopenLibs, std::vector<std::string>{"owner-plugin"});
76+
ASSERT_EQ(group.runtimeArtifacts.size(), 1u);
77+
EXPECT_EQ(group.runtimeArtifacts.front().path, owner.root / "owner-plugin");
78+
ASSERT_EQ(group.runtimeProviders.size(), 1u);
79+
EXPECT_EQ(group.runtimeProviders.front().capability, "owner-capability");
80+
}
81+
EXPECT_EQ(measured, 2u);
82+
const auto ninja = mcpp::build::emit_ninja_string(*plan);
83+
for (const auto& unit : plan->linkUnits) {
84+
if (unit.package != "owner") continue;
85+
const auto start = ninja.find("build " + unit.output.generic_string());
86+
ASSERT_NE(start, std::string::npos);
87+
const auto next = ninja.find("\nbuild ", start + 1);
88+
const auto edge = ninja.substr(start, next - start);
89+
EXPECT_NE(edge.find("-lowner_only"), std::string::npos) << edge;
90+
EXPECT_NE(edge.find("-lhelper_only"), std::string::npos) << edge;
91+
EXPECT_EQ(edge.find("-lconsumer_only"), std::string::npos) << edge;
92+
EXPECT_EQ(edge.find("-lunrelated_only"), std::string::npos) << edge;
93+
}
94+
}
95+
}
96+
}

0 commit comments

Comments
 (0)