@@ -56,6 +56,7 @@ import mcpp.manifest;
5656import mcpp.modgraph.scanner;
5757import mcpp.toolchain.detect;
5858import mcpp.toolchain.fingerprint;
59+ import mcpp.toolchain.linkmodel;
5960import mcpp.toolchain.triple;
6061import mcpp.freestanding.target; // the flags a bare-metal triple implies
6162
@@ -97,6 +98,36 @@ struct BuildAxes {
9798 // Measured on exactly that upgrade. Empty for hosted targets, so nothing
9899 // else's key moves.
99100 std::vector<std::string> targetImpliedFlags;
101+ // ⭐⭐ THE HEADER SET THE DRIVER IS POINTED AT — not the driver.
102+ //
103+ // Everything else on this axis describes the COMPILER. Nothing described
104+ // the LIBRARY it compiles against, and the two are separately installed:
105+ // one clang payload sits above whichever `xim:glibc` and
106+ // `xim:linux-headers` the home happens to carry.
107+ //
108+ // ⚠️ AND `driverIdentity` CANNOT COVER IT, BY DESIGN.
109+ // `normalize_driver_output` deliberately strips `/home/`, `/tmp/` and
110+ // `/var/` paths out of `clang --version`, which is what lets one entry be
111+ // shared between two homes. Correct — and it means two homes carrying the
112+ // same clang above DIFFERENT glibc payloads produce the same
113+ // `driver_identity`, the same key, and one entry serving two header sets.
114+ //
115+ // Measured (mcpp#514 §B, third direction): two host builds resolving glibc
116+ // 2.39 and 2.44 hit one entry, and the mixed BMIs then crash the clang
117+ // frontend outright — SIGSEGV in
118+ // `ASTReader::FindExternalVisibleDeclsByName` during deserialization —
119+ // rather than producing a readable diagnostic. BMIs carry no cross-check
120+ // against each other.
121+ //
122+ // ⭐ THE `std` CACHE ON THE SAME MACHINE HAS ALWAYS HAD THIS. Its identity
123+ // folds in `std_build_commands`, the whole command line, `-isystem` rows
124+ // included. Two caches, one machine, two notions of "the same inputs"; the
125+ // dependency one was the short one.
126+ //
127+ // Store-relative, for the same reason the include dirs below are: the key
128+ // must survive a different MCPP_HOME, and what matters is WHICH payloads,
129+ // not where the home is.
130+ std::vector<std::string> targetHeaderSet;
100131 std::string stdlibId;
101132 std::string stdlibVersion;
102133 // B
@@ -163,7 +194,8 @@ BuildAxes build_axes(const mcpp::toolchain::Toolchain& tc,
163194 const mcpp::manifest::Manifest& rootManifest,
164195 std::string_view cppStandardFlag,
165196 const std::vector<std::string>& dialectFlags,
166- std::string_view macosDeploymentTarget);
197+ std::string_view macosDeploymentTarget,
198+ const std::filesystem::path& storeRoot = {});
167199
168200// Axes E from one PackageRoot. `storeRoot` is stripped off absolute include
169201// dirs so the key survives a different MCPP_HOME (the payload paths are
@@ -217,6 +249,7 @@ nlohmann::json to_json(const BuildAxes& b, const PackageAxes& p) {
217249 {" driver_identity" , b.driverIdentity },
218250 {" target_triple" , b.targetTriple },
219251 {" target_implied_flags" , b.targetImpliedFlags },
252+ {" target_header_set" , b.targetHeaderSet },
220253 {" stdlib" , b.stdlibId },
221254 {" stdlib_version" , b.stdlibVersion },
222255 };
@@ -264,6 +297,7 @@ std::string key_hex(const BuildAxes& b, const PackageAxes& p) {
264297 put (s, " driver" , b.driverIdentity );
265298 put (s, " triple" , b.targetTriple );
266299 put_list (s, " targetflags" , b.targetImpliedFlags );
300+ put_list (s, " hdrset" , b.targetHeaderSet );
267301 put (s, " stdlib" , b.stdlibId );
268302 put (s, " stdlibv" , b.stdlibVersion );
269303 // B
@@ -302,7 +336,8 @@ BuildAxes build_axes(const mcpp::toolchain::Toolchain& tc,
302336 const mcpp::manifest::Manifest& rootManifest,
303337 std::string_view cppStandardFlag,
304338 const std::vector<std::string>& dialectFlags,
305- std::string_view macosDeploymentTarget)
339+ std::string_view macosDeploymentTarget,
340+ const std::filesystem::path& storeRoot)
306341{
307342 BuildAxes b;
308343 b.compilerId = std::string (tc.compiler_name ());
@@ -349,6 +384,36 @@ BuildAxes build_axes(const mcpp::toolchain::Toolchain& tc,
349384 b.stdlibId = tc.stdlibId ;
350385 b.stdlibVersion = tc.stdlibVersion ;
351386
387+ // ⭐ THE HEADER SET, TAKEN FROM THE RESOLVERS THAT PRODUCE THE COMMAND
388+ // LINE — not derived a second time.
389+ //
390+ // `mcpp.toolchain.linkmodel` is already the single answer to "where does
391+ // this toolchain's C library and libc++ come from"; every consumer of a
392+ // compile line reads it. Asking it here means the key describes the header
393+ // set that will actually be used, and cannot drift from it.
394+ //
395+ // ⚠️ Store-relative. The absolute form names this machine's home, and the
396+ // whole point of `normalize_driver_output` stripping paths is that one
397+ // entry can serve two homes carrying the same payloads. Leaving these
398+ // absolute would undo that; folding them in relative distinguishes
399+ // DIFFERENT payloads while still sharing between homes.
400+ {
401+ const auto storeStr = storeRoot.generic_string ();
402+ const mcpp::toolchain::PathEscape relativize =
403+ [&](const std::filesystem::path& p) -> std::string {
404+ auto str = p.generic_string ();
405+ if (!storeStr.empty () && str.starts_with (storeStr))
406+ return " <store>" + str.substr (storeStr.size ());
407+ return str;
408+ };
409+ for (auto & t : mcpp::toolchain::resolve_clang_driver (tc)
410+ .compile_tokens (relativize))
411+ b.targetHeaderSet .push_back (t);
412+ for (auto & t : mcpp::toolchain::resolve_link_model (tc)
413+ .compile_tokens (relativize))
414+ b.targetHeaderSet .push_back (t);
415+ }
416+
352417 b.cppStandard = rootManifest.package .standard ;
353418 b.cppStandardFlag = std::string (cppStandardFlag);
354419 b.dialectFlags = dialectFlags;
0 commit comments