fix(incremental): key build outputs on the compiler

A project built with one clang kept its outputs when crafter-build
started using another. The output directory is named by VariantId(),
which hashed every input that changes what is compiled except the
compiler itself, and staleness is decided by mtimes, which a compiler
update leaves alone. A PCM can only be read by the clang release that
wrote it, so after the bundled LLVM moved from 22 to 23 the first build
found nothing stale, and the first edit failed with "module file
Greeter.pcm uses an older format that is no longer supported" until the
project was cleaned by hand. A project with a `#pragma crafter
dependency` did not get that far: its project file imports the
dependency's PCM, so it failed to load, and for a git dependency that
PCM lives in the cache, where `crafter-build clean` does not reach. The
same happens natively when the distro updates clang.

The toolchain's key now goes into the hash, as the host PCM cache was
already keyed on it. An update starts an empty variant directory and
rebuilds into it; the previous one stays until `clean`, as after any
other change of variant, and two crafter-build versions can work in one
checkout without disturbing each other. VariantId() gains an overload
taking the toolchain so the dependence can be tested in-process, the
way ResolveToolchain and SysrootStdModule take theirs.

External cmake builds are not covered: their directory is fixed per
clone and keyed on the compiler's path, not its release. Objects of two
clang releases link together on the host, so nothing fails there, but
they are not rebuilt either.

Verified by replaying the update: three projects built with the 2.0.0
Flatpak, then built, edited and run with this one without cleaning,
then built with 2.0.0 again.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Jorijn van der Graaf 2026-10-03 20:09:22 +02:00
commit b5a366d060
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB

View file

@ -7,6 +7,7 @@ import :Shader;
import :Interface;
import :Implementation;
import :External;
import :Toolchain;
namespace fs = std::filesystem;
export namespace Crafter {
@ -488,10 +489,21 @@ 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) 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 {
// (type, debug, sysroot, defines, compileFlags, 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()); }
// The variant under an explicit toolchain; VariantId() is this for
// the running one. The compiler is compile state like any flag: a
// PCM can only be read by the clang release that wrote it, and
// staleness is decided by mtimes, which a compiler update does not
// touch. Before the toolchain was hashed, a project built with clang
// 22 kept its PCMs when crafter-build moved to clang 23, and the
// first edit afterwards failed with "module file ... uses an older
// format that is no longer supported" until the project was cleaned.
// Keyed here, an update starts an empty directory and rebuilds; the
// old one stays until `crafter-build clean`.
std::string VariantId(const Toolchain& toolchain) const {
std::string compileKey;
compileKey += std::to_string(static_cast<std::int32_t>(type));
compileKey += '|';
@ -521,6 +533,8 @@ export namespace Crafter {
compileKey += "|A:";
compileKey += a;
}
compileKey += "|T:";
compileKey += toolchain.Key();
std::size_t configHash = std::hash<std::string>{}(compileKey);
return std::format("{}-{}-{}-{}-{:08x}", name, target, march, mtune, configHash);
}