// 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's newest library decides when its consumers are stale // (BuildExternal's latestArtifact). Only files directly in the CMake build // directory named *.a or *.so were looked at, so msquic's // bin/Release/libmsquic.so.2, in one of the dependency's libDirs and with a // version after the .so, never counted: a rebuilt msquic left everything // built against the old one in place. // // The dependency here is a local repository whose CMake project puts a // versioned shared library in bin/Release, declared as a libDir. 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-artifact-time"; 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 / "versioned"; fs::create_directories(repo); Write(repo / "lib.c", "int Versioned(void) { return 2; }\n"); Write(repo / "CMakeLists.txt", "cmake_minimum_required(VERSION 3.16)\n" "project(versioned C)\n" "add_library(versioned SHARED lib.c)\n" "set_target_properties(versioned PROPERTIES VERSION 2.0.0 SOVERSION 2\n" " LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/Release)\n"); Git(repo, "init -q -b main"); Git(repo, "add ."); Git(repo, "commit -q -m versioned"); ExternalDependency dep; dep.name = "versioned"; dep.source.url = std::format("file://{}", repo.string()); dep.builder = ExternalBuilder::CMake; dep.libDirs = { "bin/Release" }; dep.libs = { "versioned" }; std::atomic cancelled = false; const ExternalBuildResult result = BuildExternal(dep, HostTarget(), cancelled); Check(result.error.empty(), std::format("the dependency builds: {}", result.error)); const fs::path library = ExternalCloneDir(dep, HostTarget()) / "build" / "bin" / "Release" / "libversioned.so.2.0.0"; Check(fs::exists(library), std::format("the library is where its CMake project puts it: {}", library.string())); if (fs::exists(library)) { Check(result.latestArtifact == fs::last_write_time(library), "its newest artifact is the versioned library in the libDir"); } std::error_code ec; fs::remove_all(scratch, ec); if (failures > 0) { std::println(std::cerr, "{} assertions failed", failures); return 1; } return 0; }