@@ -8,6 +8,7 @@ export module mcpp.build.plan;
88import std;
99import mcpp.manifest;
1010import mcpp.modgraph.graph;
11+ import mcpp.modgraph.scanner;
1112import mcpp.toolchain.detect;
1213import mcpp.toolchain.fingerprint;
1314import mcpp.platform;
@@ -17,6 +18,7 @@ export namespace mcpp::build {
1718struct 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
87158BuildPlan 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