@@ -1387,7 +1387,12 @@ make_plan(const mcpp::manifest::Manifest& manifest,
13871387 // match would file the member's sources under the outer package. Index 0 is
13881388 // the root project; `packages.size()` means "outside every known root",
13891389 // which is treated as root-owned and never cached.
1390- auto owner_of = [&](const std::filesystem::path& src) -> std::size_t {
1390+ //
1391+ // `container_of` keeps the distinction `owner_of` folds away: no package
1392+ // contains the source at all. The object address of a source outside its
1393+ // declaring package needs it (see `outside_prefix` below).
1394+ auto container_of = [&](const std::filesystem::path& src)
1395+ -> std::optional<std::size_t > {
13911396 std::size_t best = 0 ;
13921397 std::size_t bestLen = 0 ;
13931398 bool found = false ;
@@ -1399,7 +1404,11 @@ make_plan(const mcpp::manifest::Manifest& manifest,
13991404 auto len = packages[p].root .generic_string ().size ();
14001405 if (!found || len > bestLen) { best = p; bestLen = len; found = true ; }
14011406 }
1402- return found ? best : 0 ;
1407+ if (!found) return std::nullopt ;
1408+ return best;
1409+ };
1410+ auto owner_of = [&](const std::filesystem::path& src) -> std::size_t {
1411+ return container_of (src).value_or (0 );
14031412 };
14041413
14051414 std::set<std::filesystem::path> scannedSources;
@@ -1469,6 +1478,47 @@ make_plan(const mcpp::manifest::Manifest& manifest,
14691478 return pkg.empty () ? safe
14701479 : std::filesystem::path (sanitize (pkg)) / safe;
14711480 };
1481+ // A SOURCE OUTSIDE ITS DECLARING PACKAGE IS ADDRESSED BY WHERE IT IS, NOT
1482+ // BY HOW FAR IT IS FROM THAT PACKAGE (mcpp#641, item 3).
1483+ //
1484+ // `relPath` is relative to the package whose manifest or build program
1485+ // named the source. When a build program names a file under another
1486+ // package's root, mirroring that relPath spells one `__up` per directory
1487+ // between the two roots, so the address grew with a distance that has
1488+ // nothing to do with the file. Reported on windows-2022: a host tool's
1489+ // `.ddi` reached 271 characters, 90 of them this mirror, and `mcpp dyndep`
1490+ // could not open it.
1491+ //
1492+ // The address is instead the owning package's slug under a `__pkg`
1493+ // component, followed by the path inside that package; a source that no
1494+ // package contains is filed under `__ext/<hash of its directory>`. Both
1495+ // are downward and shell-safe by construction, and the marker components
1496+ // keep them apart from the declaring package's own mirrored directories.
1497+ // A source inside its declaring package does not reach this function, so
1498+ // its address is unchanged.
1499+ auto escapes_declaring_package = [](const std::filesystem::path& relPath) {
1500+ if (relPath.empty ()) return false ;
1501+ if (relPath.is_absolute () || relPath.has_root_name ()) return true ;
1502+ return *relPath.begin () == " .." ;
1503+ };
1504+ auto outside_prefix = [&](const std::filesystem::path& src)
1505+ -> std::filesystem::path {
1506+ if (auto c = container_of (src)) {
1507+ std::error_code ec;
1508+ auto rel = std::filesystem::relative (src, packages[*c].root , ec);
1509+ if (!ec && !rel.empty ())
1510+ return std::filesystem::path (" __pkg" )
1511+ / sanitize (qualified_package_name (packages[*c].manifest ))
1512+ / safe_object_prefix ({}, rel.parent_path ());
1513+ }
1514+ // The identity of a directory, not its spelling on this code page
1515+ // (see the path narrowing rule in mcpp-contributing).
1516+ const auto dir = src.parent_path ().lexically_normal ().generic_u8string ();
1517+ const auto digest = mcpp::toolchain::hash_string (std::string_view (
1518+ reinterpret_cast <const char *>(dir.data ()), dir.size ()));
1519+ return std::filesystem::path (" __ext" ) / digest.substr (0 , 8 );
1520+ };
1521+
14721522 // mcpp#233/#240/#344: the single source of truth for a compile unit's
14731523 // object addresses — scanned units AND the synthesized entry main go
14741524 // through here, so neither the link input nor the cache address can
@@ -1483,12 +1533,20 @@ make_plan(const mcpp::manifest::Manifest& manifest,
14831533 std::size_t owner) -> ObjectAddress
14841534 {
14851535 const auto fname = object_filename_for (src, objExt);
1536+ const bool escapes = escapes_declaring_package (relPath);
14861537 if (owner == 0 ) {
14871538 // Root project: never cached, historical layout preserved.
1488- if (rootBasenameCount[fname] > 1 )
1539+ if (rootBasenameCount[fname] > 1 ) {
1540+ if (escapes)
1541+ return { std::filesystem::path (" obj" )
1542+ / (pkg.empty () ? std::filesystem::path{}
1543+ : std::filesystem::path (sanitize (pkg)))
1544+ / outside_prefix (src) / fname,
1545+ {} };
14891546 return { std::filesystem::path (" obj" )
14901547 / safe_object_prefix (pkg, relPath.parent_path ()) / fname,
14911548 {} };
1549+ }
14921550 return { std::filesystem::path (" obj" ) / fname, {} };
14931551 }
14941552
@@ -1498,7 +1556,8 @@ make_plan(const mcpp::manifest::Manifest& manifest,
14981556 auto mirrored = safe_object_prefix ({}, relPath.parent_path ()) / fname;
14991557
15001558 ObjectAddress addr;
1501- addr.object = std::filesystem::path (" obj" ) / slug / mirrored;
1559+ addr.object = std::filesystem::path (" obj" ) / slug
1560+ / (escapes ? outside_prefix (src) / fname : mirrored);
15021561
15031562 // The cache address additionally has to be MACHINE-independent: another
15041563 // machine computes the same key and reads the same entry. A relPath
0 commit comments