fix: key the host PCM cache on source content, add clean, hash project args

Three follow-ons to the stale-build report, all cases of an identity not
capturing something that changes the output.

The host PCM cache under <cache>/crafter.build/<target>-<march>/ is shared by
every crafter-build on the machine, and freshness was a per-file mtime
comparison. That cannot tell "this PCM is newer than my source" from "this PCM
was built from different sources that happen to be newer", so a package install
and a working checkout — or two checkouts of different versions — silently
compiled their project.cpp against each other's declarations. Invalidation now
keys on a stamp over the bytes of every module source, which also covers the
case one file's mtime never could: the cached PCMs import each other, so a
change to :Interface invalidates :Clang's PCM with Crafter.Build-Clang.cppm
untouched.

Project args ApplyStandardArgs does not itself interpret are now folded into
VariantId. Such a flag typically decides what gets compiled or bundled — the
report's example is --no-webgpu dropping entries from cfg.files — and without it
both settings shared one bin dir and interleaved their outputs there, leaving a
bundle matching neither. Sorted and deduplicated so flag order doesn't split the
cache, and inherited by test Configurations.

`crafter-build clean` removes the project's bin/ and build/ trees. It
deliberately does not load project.cpp: cleaning is most often reached when
something is already wrong, and a clean that first needs the project to compile
is useless exactly then.
This commit is contained in:
catbot 2026-07-30 17:43:09 +00:00
commit e8fde57582
9 changed files with 439 additions and 15 deletions

View file

@ -83,6 +83,31 @@ int main() {
Check(!q.Get("--other=").has_value(), "ArgQuery::Get returns nullopt for absent prefix");
}
// Flags ApplyStandardArgs doesn't itself interpret are collected into
// cfg.projectArgs, which feeds VariantId — a project flag that changes what
// gets compiled has to move the output directory with it.
{
Configuration cfg;
std::array<std::string_view, 5> raw = {
"--debug", "--no-webgpu", "--target=aarch64-linux-gnu", "--feature=fancy", "bare",
};
ApplyStandardArgs(cfg, raw);
std::vector<std::string> expected = { "--feature=fancy", "--no-webgpu", "bare" };
Check(cfg.projectArgs == expected, std::format("only unrecognised args are collected, sorted; got [{}]", std::format("{}", std::views::join_with(cfg.projectArgs, std::string(", ")) | std::ranges::to<std::string>())));
}
// Order and repetition must not perturb the set — otherwise the same build
// spelled two ways would land in two directories.
{
Configuration a;
Configuration b;
std::array<std::string_view, 2> forward = { "--alpha", "--beta" };
std::array<std::string_view, 3> reversed = { "--beta", "--alpha", "--beta" };
ApplyStandardArgs(a, forward);
ApplyStandardArgs(b, reversed);
Check(a.projectArgs == b.projectArgs, "projectArgs is order- and duplicate-insensitive");
}
if (Failures > 0) {
std::println(std::cerr, "{} assertions failed", Failures);
return 1;