Skip to content

Commit 1cb54ff

Browse files
authored
feat: support package runtime shared dependencies
Support package-owned link flags, shared dependency artifacts, platform mcpp overlays, Objective-C source handling, and local index cache hygiene for package dependency workflows.
1 parent 56307ad commit 1cb54ff

15 files changed

Lines changed: 895 additions & 32 deletions

src/build/compile_commands.cppm

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ namespace mcpp::build {
3434
namespace {
3535

3636
bool is_c_source(const std::filesystem::path& src) {
37-
return src.extension() == ".c";
37+
auto ext = src.extension();
38+
return ext == ".c" || ext == ".m";
3839
}
3940

4041
// Split a flag string into individual tokens AND un-escape ninja-style

src/build/ninja_backend.cppm

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,8 @@ std::filesystem::path mcpp_exe_path() {
133133
}
134134

135135
bool is_c_source(const std::filesystem::path& src) {
136-
return src.extension() == ".c";
136+
auto ext = src.extension();
137+
return ext == ".c" || ext == ".m";
137138
}
138139

139140
std::string ltrim_copy(std::string_view s) {
@@ -360,15 +361,15 @@ std::string emit_ninja_string(const BuildPlan& plan) {
360361
}
361362

362363
append("rule cxx_link\n");
363-
append(" command = $cxx $in -o $out $ldflags\n");
364+
append(" command = $cxx $in -o $out $ldflags $unit_ldflags\n");
364365
append(" description = LINK $out\n\n");
365366

366367
append("rule cxx_archive\n");
367368
append(" command = $ar rcs $out $in\n");
368369
append(" description = AR $out\n\n");
369370

370371
append("rule cxx_shared\n");
371-
append(" command = $cxx -shared $in -o $out $ldflags\n");
372+
append(" command = $cxx -shared $in -o $out $ldflags $unit_ldflags\n");
372373
append(" description = SHARED $out\n\n");
373374

374375
if (dyndep) {
@@ -609,7 +610,17 @@ std::string emit_ninja_string(const BuildPlan& plan) {
609610
rule = "cxx_shared";
610611
break;
611612
}
612-
append(std::format("build {} : {}{}\n", escape_ninja_path(lu.output), rule, ins));
613+
std::string implicit;
614+
for (auto& input : lu.implicitInputs) {
615+
implicit += " " + escape_ninja_path(input);
616+
}
617+
618+
std::string out_line = std::format("build {} : {}{}{}\n",
619+
escape_ninja_path(lu.output), rule, ins,
620+
implicit.empty() ? std::string{} : " |" + implicit);
621+
if (auto flags = join_flags(lu.linkFlags); !flags.empty())
622+
out_line += " unit_ldflags =" + flags + "\n";
623+
append(std::move(out_line));
613624
}
614625
append("\n");
615626

src/build/plan.cppm

Lines changed: 196 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export module mcpp.build.plan;
88
import std;
99
import mcpp.manifest;
1010
import mcpp.modgraph.graph;
11+
import mcpp.modgraph.scanner;
1112
import mcpp.toolchain.detect;
1213
import mcpp.toolchain.fingerprint;
1314
import mcpp.platform;
@@ -17,6 +18,7 @@ export namespace mcpp::build {
1718
struct CompileUnit {
1819
std::filesystem::path source;
1920
std::filesystem::path object; // relative to plan.outputDir
21+
std::string packageName;
2022
std::vector<std::filesystem::path> localIncludeDirs;
2123
std::vector<std::string> packageCflags;
2224
std::vector<std::string> packageCxxflags;
@@ -28,6 +30,8 @@ struct LinkUnit {
2830
std::string targetName;
2931
enum Kind { Binary, StaticLibrary, SharedLibrary, TestBinary } kind = Binary;
3032
std::vector<std::filesystem::path> objects; // relative to plan.outputDir
33+
std::vector<std::filesystem::path> implicitInputs; // relative to plan.outputDir
34+
std::vector<std::string> linkFlags; // per-link edge flags
3135
std::filesystem::path output; // relative to plan.outputDir
3236
std::optional<std::filesystem::path> entryMain; // src path of main.cpp for bin
3337
};
@@ -55,6 +59,7 @@ BuildPlan make_plan(const mcpp::manifest::Manifest& manifest,
5559
const mcpp::toolchain::Fingerprint& fp,
5660
const mcpp::modgraph::Graph& graph,
5761
const std::vector<std::size_t>& topoOrder,
62+
const std::vector<mcpp::modgraph::PackageRoot>& packages,
5863
const std::filesystem::path& projectRoot,
5964
const std::filesystem::path& outputDir,
6065
const std::filesystem::path& stdBmiPath,
@@ -82,13 +87,80 @@ std::string object_filename_for(const std::filesystem::path& src) {
8287
return stem + (src.extension() == ".cppm" ? ".m.o" : ".o");
8388
}
8489

90+
std::string qualified_package_name(const mcpp::manifest::Manifest& manifest) {
91+
if (!manifest.package.namespace_.empty()
92+
&& manifest.package.name.starts_with(manifest.package.namespace_ + ".")) {
93+
return manifest.package.name;
94+
}
95+
if (manifest.package.namespace_.empty()) return manifest.package.name;
96+
return manifest.package.namespace_ + "." + manifest.package.name;
97+
}
98+
99+
std::vector<std::string> dependency_name_candidates(
100+
const std::string& depName,
101+
const mcpp::manifest::DependencySpec& spec)
102+
{
103+
std::vector<std::string> out;
104+
auto push = [&](std::string value) {
105+
if (value.empty()) return;
106+
if (std::find(out.begin(), out.end(), value) == out.end())
107+
out.push_back(std::move(value));
108+
};
109+
110+
push(depName);
111+
if (!spec.shortName.empty()) push(spec.shortName);
112+
if (!spec.namespace_.empty() && !spec.shortName.empty()) {
113+
push(spec.namespace_ + "." + spec.shortName);
114+
}
115+
return out;
116+
}
117+
118+
std::filesystem::path target_output(const mcpp::manifest::Target& t) {
119+
if (t.kind == mcpp::manifest::Target::Library) {
120+
return std::filesystem::path("bin") /
121+
std::format("{}{}{}", mcpp::platform::lib_prefix, t.name,
122+
mcpp::platform::static_lib_ext);
123+
}
124+
if (t.kind == mcpp::manifest::Target::SharedLibrary) {
125+
return std::filesystem::path("bin") /
126+
std::format("{}{}{}", mcpp::platform::lib_prefix, t.name,
127+
mcpp::platform::shared_lib_ext);
128+
}
129+
return std::filesystem::path("bin") /
130+
std::format("{}{}", t.name, mcpp::platform::exe_suffix);
131+
}
132+
133+
bool is_implementation_source(const std::filesystem::path& source) {
134+
auto ext = source.extension();
135+
return ext == ".cpp" || ext == ".cc" || ext == ".cxx" || ext == ".c" || ext == ".m";
136+
}
137+
138+
std::vector<std::string> shared_library_link_flags(const mcpp::manifest::Target& t) {
139+
std::vector<std::string> flags;
140+
if constexpr (mcpp::platform::is_windows) {
141+
flags.push_back(target_output(t).generic_string());
142+
} else {
143+
flags.push_back("-L" + target_output(t).parent_path().generic_string());
144+
if constexpr (mcpp::platform::supports_rpath) {
145+
if constexpr (mcpp::platform::is_macos) {
146+
flags.push_back("-Wl,-rpath,@loader_path");
147+
} else {
148+
flags.push_back("-Wl,-rpath,'$$ORIGIN'");
149+
}
150+
}
151+
flags.push_back("-l" + t.name);
152+
}
153+
return flags;
154+
}
155+
85156
} // namespace
86157

87158
BuildPlan make_plan(const mcpp::manifest::Manifest& manifest,
88159
const mcpp::toolchain::Toolchain& tc,
89160
const mcpp::toolchain::Fingerprint& fp,
90161
const mcpp::modgraph::Graph& graph,
91162
const std::vector<std::size_t>& topoOrder,
163+
const std::vector<mcpp::modgraph::PackageRoot>& packages,
92164
const std::filesystem::path& projectRoot,
93165
const std::filesystem::path& outputDir,
94166
const std::filesystem::path& stdBmiPath,
@@ -122,6 +194,7 @@ BuildPlan make_plan(const mcpp::manifest::Manifest& manifest,
122194
auto& u = graph.units[idx];
123195
CompileUnit cu;
124196
cu.source = u.path;
197+
cu.packageName = u.packageName;
125198
cu.localIncludeDirs = u.localIncludeDirs;
126199
cu.packageCflags = u.packageCflags;
127200
cu.packageCxxflags = u.packageCxxflags;
@@ -163,6 +236,116 @@ BuildPlan make_plan(const mcpp::manifest::Manifest& manifest,
163236
entryFilesAcrossTargets.insert(projectRoot / t.main);
164237
}
165238
}
239+
for (auto const& p : packages) {
240+
for (auto const& t : p.manifest.targets) {
241+
if (!t.main.empty()) {
242+
entryFilesAcrossTargets.insert(p.root / t.main);
243+
}
244+
}
245+
}
246+
247+
struct SharedDepTarget {
248+
std::size_t packageIndex = 0;
249+
std::string packageName;
250+
mcpp::manifest::Target target;
251+
std::filesystem::path output;
252+
};
253+
std::vector<SharedDepTarget> sharedDepTargets;
254+
std::set<std::string> sharedDepPackages;
255+
std::map<std::size_t, std::vector<std::size_t>> sharedTargetsByPackage;
256+
std::map<std::string, std::size_t, std::less<>> packageIndexByName;
257+
for (std::size_t i = 0; i < packages.size(); ++i) {
258+
auto const& p = packages[i];
259+
packageIndexByName[qualified_package_name(p.manifest)] = i;
260+
packageIndexByName[p.manifest.package.name] = i;
261+
}
262+
263+
for (std::size_t i = 1; i < packages.size(); ++i) {
264+
auto const& p = packages[i];
265+
auto qname = qualified_package_name(p.manifest);
266+
for (auto const& t : p.manifest.targets) {
267+
if (t.kind != mcpp::manifest::Target::SharedLibrary) continue;
268+
sharedDepPackages.insert(qname);
269+
const auto targetIndex = sharedDepTargets.size();
270+
sharedDepTargets.push_back(SharedDepTarget{
271+
.packageIndex = i,
272+
.packageName = qname,
273+
.target = t,
274+
.output = target_output(t),
275+
});
276+
sharedTargetsByPackage[i].push_back(targetIndex);
277+
}
278+
}
279+
280+
std::map<std::size_t, std::vector<std::size_t>> directPackageDeps;
281+
for (std::size_t i = 0; i < packages.size(); ++i) {
282+
for (auto const& [depName, spec] : packages[i].manifest.dependencies) {
283+
for (auto const& candidate : dependency_name_candidates(depName, spec)) {
284+
auto it = packageIndexByName.find(candidate);
285+
if (it == packageIndexByName.end() || it->second == i) continue;
286+
auto& deps = directPackageDeps[i];
287+
if (std::find(deps.begin(), deps.end(), it->second) == deps.end())
288+
deps.push_back(it->second);
289+
break;
290+
}
291+
}
292+
}
293+
294+
auto append_direct_shared_deps = [&](LinkUnit& lu, std::size_t packageIndex) {
295+
auto depsIt = directPackageDeps.find(packageIndex);
296+
if (depsIt == directPackageDeps.end()) return;
297+
for (auto depIndex : depsIt->second) {
298+
auto targetsIt = sharedTargetsByPackage.find(depIndex);
299+
if (targetsIt == sharedTargetsByPackage.end()) continue;
300+
for (auto targetIndex : targetsIt->second) {
301+
auto const& dep = sharedDepTargets[targetIndex];
302+
lu.implicitInputs.push_back(dep.output);
303+
auto flags = shared_library_link_flags(dep.target);
304+
lu.linkFlags.insert(lu.linkFlags.end(), flags.begin(), flags.end());
305+
}
306+
}
307+
};
308+
309+
auto append_shared_deps_for_linked_objects = [&](LinkUnit& lu) {
310+
std::set<std::size_t> linkedPackages;
311+
linkedPackages.insert(0);
312+
for (auto& cu : plan.compileUnits) {
313+
if (sharedDepPackages.contains(cu.packageName)) continue;
314+
auto it = packageIndexByName.find(cu.packageName);
315+
if (it == packageIndexByName.end()) continue;
316+
linkedPackages.insert(it->second);
317+
}
318+
319+
for (auto packageIndex : linkedPackages) {
320+
append_direct_shared_deps(lu, packageIndex);
321+
}
322+
};
323+
324+
auto append_package_objects = [&](LinkUnit& lu, const std::string& packageName) {
325+
for (auto& cu : plan.compileUnits) {
326+
if (cu.packageName != packageName) continue;
327+
if (cu.source.extension() == ".cppm") {
328+
lu.objects.push_back(cu.object);
329+
}
330+
}
331+
for (auto& cu : plan.compileUnits) {
332+
if (cu.packageName != packageName) continue;
333+
if (!is_implementation_source(cu.source)) continue;
334+
if (lu.entryMain && cu.source == *lu.entryMain) continue;
335+
if (entryFilesAcrossTargets.contains(cu.source)) continue;
336+
lu.objects.push_back(cu.object);
337+
}
338+
};
339+
340+
for (auto const& dep : sharedDepTargets) {
341+
LinkUnit lu;
342+
lu.targetName = dep.target.name;
343+
lu.kind = LinkUnit::SharedLibrary;
344+
lu.output = dep.output;
345+
append_package_objects(lu, dep.packageName);
346+
append_direct_shared_deps(lu, dep.packageIndex);
347+
plan.linkUnits.push_back(std::move(lu));
348+
}
166349

167350
// 4. Link units (one per [targets.X])
168351
// When any TestBinary target exists, skip Binary/Library/SharedLibrary
@@ -179,29 +362,24 @@ BuildPlan make_plan(const mcpp::manifest::Manifest& manifest,
179362
lu.targetName = t.name;
180363
if (t.kind == mcpp::manifest::Target::Library) {
181364
lu.kind = LinkUnit::StaticLibrary;
182-
lu.output = std::filesystem::path("bin") /
183-
std::format("{}{}{}", mcpp::platform::lib_prefix, t.name,
184-
mcpp::platform::static_lib_ext);
365+
lu.output = target_output(t);
185366
} else if (t.kind == mcpp::manifest::Target::SharedLibrary) {
186367
lu.kind = LinkUnit::SharedLibrary;
187-
lu.output = std::filesystem::path("bin") /
188-
std::format("{}{}{}", mcpp::platform::lib_prefix, t.name,
189-
mcpp::platform::shared_lib_ext);
368+
lu.output = target_output(t);
190369
} else if (t.kind == mcpp::manifest::Target::TestBinary) {
191370
lu.kind = LinkUnit::TestBinary;
192-
lu.output = std::filesystem::path("bin") /
193-
std::format("{}{}", t.name, mcpp::platform::exe_suffix);
371+
lu.output = target_output(t);
194372
if (!t.main.empty()) lu.entryMain = projectRoot / t.main;
195373
} else {
196374
lu.kind = LinkUnit::Binary;
197-
lu.output = std::filesystem::path("bin") /
198-
std::format("{}{}", t.name, mcpp::platform::exe_suffix);
375+
lu.output = target_output(t);
199376
if (!t.main.empty()) lu.entryMain = projectRoot / t.main;
200377
}
201378

202379
// Include all module units' objects (they may be needed at runtime via global init).
203380
// For binary target, also include main.cpp's object if main is present.
204381
for (auto& cu : plan.compileUnits) {
382+
if (sharedDepPackages.contains(cu.packageName)) continue;
205383
if (cu.source.extension() == ".cppm") {
206384
lu.objects.push_back(cu.object);
207385
}
@@ -212,6 +390,7 @@ BuildPlan make_plan(const mcpp::manifest::Manifest& manifest,
212390
CompileUnit main_cu;
213391
main_cu.source = *lu.entryMain;
214392
main_cu.object = std::filesystem::path("obj") / object_filename_for(*lu.entryMain);
393+
main_cu.packageName = qualified_package_name(manifest);
215394

216395
// We didn't scan main.cpp earlier (it's not in scanner output unless globbed in).
217396
// Best-effort: scan its imports here.
@@ -251,13 +430,18 @@ BuildPlan make_plan(const mcpp::manifest::Manifest& manifest,
251430
// file registered as another target's entryMain (each binary's main()
252431
// is exclusive to that binary).
253432
for (auto& cu : plan.compileUnits) {
254-
auto ext = cu.source.extension();
255-
if (ext != ".cpp" && ext != ".cc" && ext != ".cxx" && ext != ".c") continue;
433+
if (sharedDepPackages.contains(cu.packageName)) continue;
434+
if (!is_implementation_source(cu.source)) continue;
256435
if (lu.entryMain && cu.source == *lu.entryMain) continue; // own entry: already added above
257436
if (entryFilesAcrossTargets.contains(cu.source)) continue; // foreign entry: skip
258437
lu.objects.push_back(cu.object);
259438
}
260439

440+
if (lu.kind == LinkUnit::Binary || lu.kind == LinkUnit::TestBinary
441+
|| lu.kind == LinkUnit::SharedLibrary) {
442+
append_shared_deps_for_linked_objects(lu);
443+
}
444+
261445
plan.linkUnits.push_back(std::move(lu));
262446
}
263447

0 commit comments

Comments
 (0)