97 lines
4 KiB
C++
97 lines
4 KiB
C++
|
|
// 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
|
||
|
|
// <build>/.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;
|
||
|
|
}
|