test: a CMake external configured with other inputs is built from nothing
ExternalReconfigure builds a one-file C library from a local repository as a CMake external, then plants a file in its build directory to stand for what an old configure left. Built again with the same inputs, the directory is kept. With its recorded inputs rewritten the way another crafter-build's configure would have left them, the next build has to start from an empty directory and build the library again. Before the fix the planted file survived the reconfigure. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
56b44f616a
commit
0e69e71b11
2 changed files with 98 additions and 0 deletions
|
|
@ -258,6 +258,7 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
|
|||
cfg.AddTest("GitSubmodules").Dependencies({ CrafterBuildLib.get() });
|
||||
cfg.AddTest("LinkFlagsChange").Dependencies({ CrafterBuildLib.get() });
|
||||
cfg.AddTest("ExternalArtifactTime").Dependencies({ CrafterBuildLib.get() });
|
||||
cfg.AddTest("ExternalReconfigure").Dependencies({ CrafterBuildLib.get() });
|
||||
cfg.AddTest("ConcurrentExternalFetch").Dependencies({ CrafterBuildLib.get() });
|
||||
cfg.AddTest("SharedExternalFetch").Dependencies({ CrafterBuildLib.get() });
|
||||
cfg.AddTest("StandardArgs").Dependencies({ CrafterBuildLib.get() });
|
||||
|
|
|
|||
97
tests/ExternalReconfigure/main.cpp
Normal file
97
tests/ExternalReconfigure/main.cpp
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
// 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;
|
||||
}
|
||||
Loading…
Reference in a new issue