Crafter.Build/tests/StdPcmRebuild/main.cpp

85 lines
3.6 KiB
C++
Raw Permalink Normal View History

// 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<fs::path, std::shared_future<BuildResult>> 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<fs::path, 0> ifaces = {};
std::array<fs::path, 1> 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;
}