fix: key the std module's directory on everything it is compiled with

StdPcmDir named the directory by target, march and toolchain, then
grew a case per target for the rest: the wasm flags, and with the first
fix for #43 the mingw prefix. Every other flag BuildStdPcm passes went
unkeyed, so configurations that differed only there took turns
rebuilding one std PCM in place, under the PCMs built against it: a
project with -mtune=generic against project.cpp's -mtune=native on every
run, or two sysroots for one target. And the mingw prefix was keyed only
when there was one, so a build without a prefix landed in the directory
an older release had filled with a CRAFTER_MINGW_DIR=/usr build, and
relinked its objects.

StdPcmFlags is now the one source of the flags BuildStdPcm compiles
with, and StdPcmKey names the directory by target, march and toolchain
followed by a hash of those flags and, for the mingw target, of the
prefix behind its --sysroot. VariantId hashes StdPcmKey in place of the
mingw prefix: units do not notice a std module that moved directories,
so a build has to move with it. Every std PCM directory and build
directory moves once, as after a compiler update; the old ones stay
until `clean` and the cache is cleared by hand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jorijn van der Graaf 2026-10-31 22:58:44 +01:00
commit 5d25495621
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
4 changed files with 114 additions and 65 deletions

View file

@ -392,6 +392,15 @@ export namespace Crafter {
};
struct Configuration;
// The name StdPcmDir gives the directory of this configuration's std
// module under `toolchain`: target, march and the toolchain's key, which
// say what it is, then a hash of everything else it is compiled with,
// each flag BuildStdPcm passes (StdPcmFlags) and for the mingw target the
// prefix behind its --sysroot. Two configurations that would compile the
// std module differently never share a directory, so neither rebuilds it
// under the PCMs the other compiled against it. Declared ahead of
// Configuration, whose VariantId keys on it.
std::string StdPcmKey(const Configuration& config, const Toolchain& toolchain);
// What a Script's callback is handed when `crafter-build run <name>`
// starts it.
@ -580,7 +589,8 @@ export namespace Crafter {
void AddAstLintRule(std::string name, std::function<void(LintContext&)> check);
// Suffix that uniquely identifies this Configuration's compile state.
// target+march+mtune are spelled out for readability; the rest
// (type, debug, sysroot, defines, compileFlags, the toolchain)
// (type, debug, sysroot, defines, compileFlags, the std module, the
// toolchain)
// collapse into a short hash so two Configurations sharing a path but
// with different compile state can't clobber each other's outputs.
std::string VariantId() const { return VariantId(GetToolchain()); }
@ -628,17 +638,15 @@ export namespace Crafter {
compileKey += "|B:";
compileKey += WasmBaselineFlags;
}
// The mingw-w64 prefix decides what the mingw target compiles
// against, and until issue #43 clang got it as the sysroot
// itself, which put the host's own headers on the include path.
// Unkeyed, the release that fixed it kept the objects compiled
// against glibc's headers: the build after the update only
// relinked them, and failed to link as before. No prefix keeps
// the id as it was, since clang's own discovery has not changed.
if (target == "x86_64-w64-mingw32" && !toolchain.mingwPrefix.empty()) {
compileKey += "|M:";
compileKey += toolchain.mingwPrefix.string();
}
// The std module the units import. Its PCMs and objects record
// it by path, and units do not notice it moving: when the mingw
// prefix stopped being clang's sysroot as is (issue #43), the
// std module was rebuilt and the objects compiled against the
// host's glibc headers were only relinked, failing as before. A
// std module that moves directories, because anything it is
// compiled with changed, moves the build with it.
compileKey += "|S:";
compileKey += StdPcmKey(*this, toolchain);
// Sorted by ApplyStandardArgs, so flag order on the command line
// doesn't split the cache.
for (const std::string& a : projectArgs) {
@ -673,10 +681,9 @@ export namespace Crafter {
std::vector<std::string> requires_;
};
// Directory holding the std module PCM for this Configuration. Keyed on
// target+march because a BMI is only loadable by a TU with matching target
// features, and suffixed with the wasm flags (WasmBaselineFlags, then the
// variant's) for the same reason.
// Directory holding the std module PCM for this Configuration,
// GetCacheDir() / StdPcmKey(config, GetToolchain()): a BMI is only
// loadable by a unit compiled the way it was.
fs::path StdPcmDir(const Configuration& config);
// Where Build leaves this Configuration's linked artifact: the executable
@ -805,4 +812,9 @@ namespace Crafter {
// compile command and of the std module's, which have to agree flag for
// flag.
std::string WasmCompileFlags(const Configuration& config);
// The flags BuildStdPcm compiles the std module with, after the base
// command and before the source, each with a leading space. StdPcmKey
// hashes the same string, so the command and the directory it writes to
// cannot disagree about what decides the BMI.
std::string StdPcmFlags(const Configuration& config);
}