// 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; // A std module rebuilt in place has to recompile the units that import it. // The std PCM is rebuilt in its directory whenever its source changes under // the same compiler and flags: a libc++ update, or mingw-w64's under the // same prefix. A .cpp unit's depfile listed only its source and headers, // because the driver records imported BMIs for --precompile alone, so such // a unit kept its object and the next build only relinked it. // // The project is one main.cpp that imports std. Its object is dated before // std.pcm and after its source, as a std module rebuilt after the object // leaves them, and the next build has to compile it again. XDG_CACHE_HOME keeps the std PCM out of the real // cache, whose other users the change of date would rebuild. extern "C" int setenv(const char* name, const char* value, int overwrite); namespace { std::int32_t failures = 0; void Check(bool cond, std::string_view msg) { if (!cond) { std::println(std::cerr, "FAIL: {}", msg); ++failures; } } BuildResult BuildOnce(Configuration& cfg) { std::unordered_map> depResults; std::mutex depMutex; return Build(cfg, depResults, depMutex); } } int main() { const fs::path scratch = fs::temp_directory_path() / "crafter-build-std-pcm-rebuild"; std::error_code ec; fs::remove_all(scratch, ec); fs::create_directories(scratch); const fs::path project = scratch / "project"; fs::copy(fs::current_path() / "tests" / "StdPcmRebuild" / "fixture", project, fs::copy_options::recursive); setenv("XDG_CACHE_HOME", (scratch / "cache").c_str(), 1); Configuration cfg; cfg.path = project; cfg.name = "rebuilt"; cfg.outputName = "rebuilt"; cfg.type = ConfigurationType::Executable; std::array ifaces = {}; std::array impls = { "main" }; cfg.GetInterfacesAndImplementations(ifaces, impls); BuildResult built = BuildOnce(cfg); Check(built.result.empty(), std::format("the project builds: {}", built.result)); const fs::path object = cfg.BuildDir() / "main_impl.o"; const fs::path stdPcm = StdPcmDir(cfg) / "std.pcm"; Check(fs::exists(object) && fs::exists(stdPcm), std::format("{} and {} exist", object.string(), stdPcm.string())); if (!fs::exists(object) || !fs::exists(stdPcm)) return 1; const fs::file_time_type first = fs::last_write_time(object); built = BuildOnce(cfg); Check(built.result.empty(), std::format("an idle build succeeds: {}", built.result)); Check(fs::last_write_time(object) == first, "and compiles nothing again"); // The source before the object and the object before std.pcm, so that // nothing but the std module makes the object stale. const fs::file_time_type older = fs::last_write_time(stdPcm) - std::chrono::seconds(10); fs::last_write_time(project / "main.cpp", older - std::chrono::seconds(10)); fs::last_write_time(object, older); built = BuildOnce(cfg); Check(built.result.empty(), std::format("the build after the std module succeeds: {}", built.result)); Check(fs::last_write_time(object) > older, "a unit importing a std module newer than its object is compiled again"); fs::remove_all(scratch, ec); if (failures > 0) { std::println(std::cerr, "{} assertions failed", failures); return 1; } return 0; }