fix: give the mingw target a sysroot that holds mingw-w64 alone #44

Merged
jorijnvdgraaf merged 12 commits from claude/issue-43 into master 2026-10-31 22:35:49 +00:00
2 changed files with 98 additions and 0 deletions
Showing only changes of commit 0e69e71b11 - Show all commits

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>
Jorijn van der Graaf 2026-10-31 22:59:23 +01:00
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB

View file

@ -258,6 +258,7 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
cfg.AddTest("GitSubmodules").Dependencies({ CrafterBuildLib.get() }); cfg.AddTest("GitSubmodules").Dependencies({ CrafterBuildLib.get() });
cfg.AddTest("LinkFlagsChange").Dependencies({ CrafterBuildLib.get() }); cfg.AddTest("LinkFlagsChange").Dependencies({ CrafterBuildLib.get() });
cfg.AddTest("ExternalArtifactTime").Dependencies({ CrafterBuildLib.get() }); cfg.AddTest("ExternalArtifactTime").Dependencies({ CrafterBuildLib.get() });
cfg.AddTest("ExternalReconfigure").Dependencies({ CrafterBuildLib.get() });
cfg.AddTest("ConcurrentExternalFetch").Dependencies({ CrafterBuildLib.get() }); cfg.AddTest("ConcurrentExternalFetch").Dependencies({ CrafterBuildLib.get() });
cfg.AddTest("SharedExternalFetch").Dependencies({ CrafterBuildLib.get() }); cfg.AddTest("SharedExternalFetch").Dependencies({ CrafterBuildLib.get() });
cfg.AddTest("StandardArgs").Dependencies({ CrafterBuildLib.get() }); cfg.AddTest("StandardArgs").Dependencies({ CrafterBuildLib.get() });

View 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;
}