@@ -3294,25 +3294,11 @@ prepare_build(bool print_fingerprint,
32943294 }
32953295 };
32963296
3297- // Stage a dep's source files into a fresh directory, rewriting their
3298- // module / import declarations against `rename`. Used by the multi-
3299- // version mangling fallback (Level 1) so two cross-major copies of
3300- // the same package can coexist with distinct module names.
3301- //
3302- // Headers (referenced via `[build].include_dirs`) are NOT staged —
3303- // those keep pointing at the original install dir via absolutized
3304- // include paths.
3305- auto stage_with_rewrite = [](const std::filesystem::path& srcRoot,
3306- const std::filesystem::path& dstRoot,
3307- const mcpp::manifest::Manifest& depManifest,
3308- const std::map<std::string, std::string>& rename)
3309- -> std::expected<void , std::string>
3297+ auto package_source_files = [](
3298+ const std::filesystem::path& srcRoot,
3299+ const mcpp::manifest::Manifest& depManifest)
3300+ -> std::expected<std::set<std::filesystem::path>, std::string>
33103301 {
3311- std::error_code ec;
3312- std::filesystem::create_directories (dstRoot, ec);
3313- if (ec) return std::unexpected (std::format (
3314- " stage: cannot create '{}': {}" , dstRoot.string (), ec.message ()));
3315-
33163302 // Resolve the source globs against the original root, falling
33173303 // back to the convention default if the manifest didn't set any.
33183304 std::vector<std::string> globs = depManifest.modules .sources ;
@@ -3338,8 +3324,32 @@ prepare_build(bool print_fingerprint,
33383324 " stage: no source files found under '{}' (globs={})" ,
33393325 srcRoot.string (), globs.size ()));
33403326 }
3327+ return sourceFiles;
3328+ };
33413329
3342- for (auto const & f : sourceFiles) {
3330+ // Stage a dep's source files into a fresh directory, rewriting their
3331+ // module / import declarations against `rename`. Used by the multi-
3332+ // version mangling fallback (Level 1) so two cross-major copies of
3333+ // the same package can coexist with distinct module names.
3334+ //
3335+ // Headers (referenced via `[build].include_dirs`) are NOT staged —
3336+ // those keep pointing at the original install dir via absolutized
3337+ // include paths.
3338+ auto stage_with_rewrite = [&](const std::filesystem::path& srcRoot,
3339+ const std::filesystem::path& dstRoot,
3340+ const mcpp::manifest::Manifest& depManifest,
3341+ const std::map<std::string, std::string>& rename)
3342+ -> std::expected<void , std::string>
3343+ {
3344+ std::error_code ec;
3345+ std::filesystem::create_directories (dstRoot, ec);
3346+ if (ec) return std::unexpected (std::format (
3347+ " stage: cannot create '{}': {}" , dstRoot.string (), ec.message ()));
3348+
3349+ auto sources = package_source_files (srcRoot, depManifest);
3350+ if (!sources) return std::unexpected (sources.error ());
3351+
3352+ for (auto const & f : *sources) {
33433353 auto rel = std::filesystem::relative (f, srcRoot, ec);
33443354 if (ec) return std::unexpected (std::format (
33453355 " stage: cannot relativize '{}': {}" , f.string (), ec.message ()));
@@ -3361,6 +3371,29 @@ prepare_build(bool print_fingerprint,
33613371 return {};
33623372 };
33633373
3374+ auto declared_modules_for = [&](const std::filesystem::path& srcRoot,
3375+ const mcpp::manifest::Manifest& depManifest)
3376+ -> std::expected<std::vector<std::string>, std::string>
3377+ {
3378+ auto sources = package_source_files (srcRoot, depManifest);
3379+ if (!sources) return std::unexpected (sources.error ());
3380+ std::vector<std::string> modules;
3381+ for (auto const & file : *sources) {
3382+ std::ifstream is (file);
3383+ if (!is) return std::unexpected (std::format (
3384+ " mangle: cannot read '{}'" , file.string ()));
3385+ std::stringstream buf; buf << is.rdbuf ();
3386+ for (auto & name : mcpp::pm::declared_module_roots (buf.str ())) {
3387+ if (std::ranges::find (modules, name) == modules.end ())
3388+ modules.push_back (std::move (name));
3389+ }
3390+ }
3391+ if (modules.empty ()) return std::unexpected (std::format (
3392+ " mangle: package '{}' declares no named C++ module to rewrite" ,
3393+ depManifest.package .name ));
3394+ return modules;
3395+ };
3396+
33643397 // Stage 2a — feature-activated optional dependencies. Defined as local
33653398 // lambdas (NOT file-scope functions): keeping their std::map instantiations
33663399 // inside this implementation unit avoids polluting the exported module BMI,
@@ -3612,14 +3645,22 @@ prepare_build(bool print_fingerprint,
36123645 spec.version ));
36133646 }
36143647
3615- // Module names in the source files use the dep's full
3616- // [package].name (e.g. "mcpplibs.cmdline"), not the
3617- // namespaced-subtable shortName. Use that for the
3618- // rename key so the rewriter actually matches what the
3619- // .cppm sources declare.
3620- const std::string moduleName = secondaryManifest.package .name ;
3621- std::string mangled =
3622- mcpp::pm::mangle_name (moduleName, spec.version );
3648+ // Module names are authored API and are not required to
3649+ // mirror package identity. Discover every provided module
3650+ // root from the secondary's source text, then rewrite the
3651+ // same map in both the secondary and its consumer.
3652+ auto moduleNames = declared_modules_for (
3653+ secondaryRoot, secondaryManifest);
3654+ if (!moduleNames) return std::unexpected (moduleNames.error ());
3655+ std::map<std::string, std::string> rename;
3656+ for (auto const & module : *moduleNames) {
3657+ rename.emplace (module ,
3658+ mcpp::pm::mangle_name (module , spec.version ));
3659+ }
3660+ const auto & moduleName = moduleNames->front ();
3661+ const auto & mangledModule = rename.at (moduleName);
3662+ const std::string mangledPackage = mcpp::pm::mangle_name (
3663+ key.shortName , spec.version );
36233664
36243665 // Stage layout:
36253666 // <root>/target/.mangled/<consumerPkg>/<dep>__<version>/ ← rewritten secondary source
@@ -3629,10 +3670,9 @@ prepare_build(bool print_fingerprint,
36293670 auto stageBase = *root / " target" / " .mangled"
36303671 / consumerManifest.package .name ;
36313672 auto secStage = stageBase
3632- / std::format (" {}__{}" , moduleName , spec.version );
3673+ / std::format (" {}__{}" , key. shortName , spec.version );
36333674 auto consumerStage = stageBase / " __self__" ;
36343675
3635- std::map<std::string, std::string> rename{ {moduleName, mangled} };
36363676 if (auto r = stage_with_rewrite (secondaryRoot, secStage,
36373677 secondaryManifest, rename); !r)
36383678 return std::unexpected (r.error ());
@@ -3649,11 +3689,10 @@ prepare_build(bool print_fingerprint,
36493689 // exact (ns, mangled) pair dedup cleanly. The original
36503690 // primary entry (it->second) is untouched.
36513691 auto stagedManifest = secondaryManifest;
3652- // Update [package].name to the mangled module name so
3653- // the modgraph validator (which checks "exported module
3654- // must be prefixed by package name") accepts the
3655- // rewritten sources.
3656- stagedManifest.package .name = mangled;
3692+ // Give the staged package a distinct atomic identity too;
3693+ // authored module names remain independent and are carried
3694+ // exclusively by the rename map above.
3695+ stagedManifest.package .name = mangledPackage;
36573696 if (stagedManifest.package .namespace_ .empty ()) {
36583697 stagedManifest.package .namespace_ = key.ns .empty ()
36593698 ? std::string (mcpp::pm::kDefaultNamespace ) : key.ns ;
@@ -3673,7 +3712,7 @@ prepare_build(bool print_fingerprint,
36733712 std::make_unique<mcpp::manifest::Manifest>(std::move (stagedManifest)));
36743713 dep_cache_identities.push_back ({
36753714 .indexName = cache_index_name (key.ns ),
3676- .packageName = mangled ,
3715+ .packageName = mangledPackage ,
36773716 .version = spec.version ,
36783717 .sourceKind = " version" ,
36793718 });
@@ -3682,7 +3721,7 @@ prepare_build(bool print_fingerprint,
36823721 recordDependencyEdge (item.consumerDepIndex , depPackageIndex, spec);
36833722 auto linkFlagsAdded = propagateLinkFlags (secStage, *dep_manifests.back ());
36843723
3685- ResolvedKey mangledKey{key.ns , mangled };
3724+ ResolvedKey mangledKey{key.ns , mangledPackage };
36863725 resolved[mangledKey] = ResolvedRecord{
36873726 .version = spec.version ,
36883727 .constraint = item.originalConstraint ,
@@ -3695,7 +3734,8 @@ prepare_build(bool print_fingerprint,
36953734
36963735 mcpp::ui::info (" Mangled" ,
36973736 std::format (" {} v{} ↔ v{} → {} (cross-major fallback)" ,
3698- moduleName, it->second .version , spec.version , mangled));
3737+ moduleName, it->second .version , spec.version ,
3738+ mangledModule));
36993739 continue ;
37003740 }
37013741
0 commit comments