// 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 CMake dependency configured with other inputs than this build's (another // compiler or --sysroot, the flags crafter-build injects, which it records in // /.crafter-options) has to be built again from nothing. It was // reconfigured in place, and CMake does not recompile what a changed // --sysroot compiled (the Makefiles generator writes it into build.make, // which make does not compare) and keeps the check results of the old // configure. A mingw dependency built by a release that gave clang the // host's /usr as the sysroot (issue #43) kept its objects reading // __libc_single_threaded after the update. // // The dependency is a local repository with a one-file C library. A file // planted in its build directory stands for what the old configure left, // and the recorded inputs are rewritten the way another crafter-build would // have left them. Everything is local; XDG_CACHE_HOME keeps the clone out of // the real cache. 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; } } void Git(const fs::path& dir, std::string_view args) { const std::string command = std::format("git -C {} {}", ShellQuote(dir.string()), args); auto result = RunCommandWithTimeout(command, std::chrono::seconds(60)); if (result.exitCode != 0) throw std::runtime_error(std::format("{} failed: {}", command, result.output)); } void Write(const fs::path& file, std::string_view text) { std::ofstream(file, std::ios::binary | std::ios::trunc) << text; } } int main() { const fs::path scratch = fs::temp_directory_path() / "crafter-build-external-reconfigure"; fs::remove_all(scratch); fs::create_directories(scratch); setenv("XDG_CACHE_HOME", (scratch / "cache").c_str(), 1); setenv("GIT_CONFIG_NOSYSTEM", "1", 1); setenv("GIT_CONFIG_GLOBAL", "/dev/null", 1); for (const char* name : { "GIT_AUTHOR_NAME", "GIT_COMMITTER_NAME" }) setenv(name, "crafter-build test", 1); for (const char* name : { "GIT_AUTHOR_EMAIL", "GIT_COMMITTER_EMAIL" }) setenv(name, "test@example.invalid", 1); const fs::path repo = scratch / "reconf"; fs::create_directories(repo); Write(repo / "lib.c", "int Reconfigured(void) { return 1; }\n"); Write(repo / "CMakeLists.txt", "cmake_minimum_required(VERSION 3.16)\n" "project(reconf C)\n" "add_library(reconf STATIC lib.c)\n"); Git(repo, "init -q -b main"); Git(repo, "add ."); Git(repo, "commit -q -m reconf"); ExternalDependency dep; dep.name = "reconf"; dep.source.url = std::format("file://{}", repo.string()); dep.builder = ExternalBuilder::CMake; dep.libs = { "reconf" }; std::string error = BuildExternal(dep, HostTarget()); Check(error.empty(), std::format("the dependency builds: {}", error)); const fs::path build = ExternalBuildDir(dep, HostTarget()); const fs::path leftover = build / "left-by-the-old-configure"; Write(leftover, ""); error = BuildExternal(dep, HostTarget()); Check(error.empty(), std::format("and builds again: {}", error)); Check(fs::exists(leftover), "with the same inputs its build directory is kept"); Write(build / ".crafter-options", " -DCMAKE_SYSROOT=/usr\n"); error = BuildExternal(dep, HostTarget()); Check(error.empty(), std::format("configured with other inputs, it builds: {}", error)); Check(!fs::exists(leftover), "from an empty build directory"); Check(fs::exists(build / "libreconf.a"), "and the library is built again"); std::error_code ec; fs::remove_all(scratch, ec); if (failures > 0) { std::println(std::cerr, "{} assertions failed", failures); return 1; } return 0; }