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:
parent
13697cd026
commit
e8fde57582
9 changed files with 439 additions and 15 deletions
79
tests/CleanProject/main.cpp
Normal file
79
tests/CleanProject/main.cpp
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
|
||||
import std;
|
||||
import Crafter.Build;
|
||||
namespace fs = std::filesystem;
|
||||
using namespace Crafter;
|
||||
|
||||
// `crafter-build clean` exists so the fix for a stale build is discoverable
|
||||
// rather than folklore. It must not need the project to load: the state you most
|
||||
// want to clear is one where the build is already broken.
|
||||
namespace {
|
||||
std::int32_t Failures = 0;
|
||||
|
||||
void Check(bool cond, std::string_view msg) {
|
||||
if (!cond) {
|
||||
std::println(std::cerr, "FAIL: {}", msg);
|
||||
++Failures;
|
||||
}
|
||||
}
|
||||
|
||||
fs::path StageProject(std::string_view leaf) {
|
||||
fs::path root = fs::temp_directory_path() / std::format("crafter-build-clean-{}", leaf);
|
||||
fs::remove_all(root);
|
||||
fs::create_directories(root / "bin" / "app-x86_64-pc-linux-gnu-native-native-0badf00d");
|
||||
fs::create_directories(root / "build" / "app-x86_64-pc-linux-gnu-native-native-0badf00d");
|
||||
std::ofstream(root / "bin" / "app-x86_64-pc-linux-gnu-native-native-0badf00d" / "app") << "binary";
|
||||
std::ofstream(root / "build" / "app-x86_64-pc-linux-gnu-native-native-0badf00d" / "main_impl.o") << "object";
|
||||
std::ofstream(root / "keep-me.txt") << "untouched";
|
||||
return root;
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
{
|
||||
// A project file that could never compile: clean has to work anyway.
|
||||
fs::path root = StageProject("broken");
|
||||
fs::path projectFile = root / "project.cpp";
|
||||
std::ofstream(projectFile) << "this is not valid C++ at all\n";
|
||||
|
||||
std::vector<fs::path> removed = CleanProject(projectFile);
|
||||
Check(removed.size() == 2, std::format("both trees reported removed, got {}", removed.size()));
|
||||
Check(!fs::exists(root / "bin"), "bin/ is gone");
|
||||
Check(!fs::exists(root / "build"), "build/ is gone");
|
||||
Check(fs::exists(root / "keep-me.txt"), "unrelated files in the project root are left alone");
|
||||
Check(fs::exists(projectFile), "the project file itself is left alone");
|
||||
}
|
||||
|
||||
{
|
||||
// Idempotent, and quiet about it — nothing to remove is not an error.
|
||||
fs::path root = StageProject("twice");
|
||||
fs::path projectFile = root / "project.cpp";
|
||||
std::ofstream(projectFile) << "\n";
|
||||
|
||||
CleanProject(projectFile);
|
||||
std::vector<fs::path> second = CleanProject(projectFile);
|
||||
Check(second.empty(), "a second clean reports nothing removed");
|
||||
}
|
||||
|
||||
{
|
||||
// Resolved relative to the project file, not the cwd, so --project=
|
||||
// cleans the project it names.
|
||||
fs::path root = StageProject("elsewhere");
|
||||
fs::path projectFile = root / "project.cpp";
|
||||
std::ofstream(projectFile) << "\n";
|
||||
fs::path cwdBin = fs::current_path() / "bin";
|
||||
bool cwdBinExisted = fs::exists(cwdBin);
|
||||
|
||||
CleanProject(projectFile);
|
||||
Check(!fs::exists(root / "bin"), "the named project's bin/ is gone");
|
||||
Check(fs::exists(cwdBin) == cwdBinExisted, "the cwd's bin/ is untouched");
|
||||
}
|
||||
|
||||
if (Failures > 0) {
|
||||
std::println(std::cerr, "{} assertions failed", Failures);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Loading…
Reference in a new issue