All checks were successful
CI / build-test-release (pull_request) Successful in 11m50s
Two crafter-build invocations sharing XDG_CACHE_HOME used to clobber each other's writes to <cache>/<target>-<march>/std.pcm and the Crafter.Build-*.pcm modules: each LoadProject path wrote directly to the final path, so a reader could see a half-written file and die with "malformed or corrupted precompiled file: 'can't skip to bit X from Y'" (issue #14). Every BuildStdPcm / EnsureCrafterBuildPcms write now goes via <final>.tmp.<pid>.<seq> and atomic-renames into place; concurrent writers always see either the old or the new file, never torn bytes. The mingw-on- Linux std.cppm copy is per-PID for the same reason. Adds a regression test (ConcurrentCacheRace) that races four LoadProject() calls against a cold scratch cache — reproduces the race 5/5 without the fix and passes 5/5 with it. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
119 lines
5.8 KiB
C++
119 lines
5.8 KiB
C++
import std;
|
|
import Crafter.Build;
|
|
namespace fs = std::filesystem;
|
|
using namespace Crafter;
|
|
|
|
extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> args) {
|
|
std::vector<std::string> depArgs(args.begin(), args.end());
|
|
Configuration* math = GitProject({
|
|
.source = { "https://forgejo.catcrafts.net/Catcrafts/Crafter.Math.git" },
|
|
.args = depArgs,
|
|
});
|
|
Configuration* asset = GitProject({
|
|
.source = { "https://forgejo.catcrafts.net/Catcrafts/Crafter.Asset.git" },
|
|
.args = depArgs,
|
|
});
|
|
|
|
static auto crafterBuildLib = std::make_unique<Configuration>();
|
|
crafterBuildLib->path = "./";
|
|
crafterBuildLib->name = "crafter.build-lib";
|
|
crafterBuildLib->outputName = "crafter-build";
|
|
ApplyStandardArgs(*crafterBuildLib, args);
|
|
// Windows builds (native msvc via build.cmd or cross-compiled mingw from
|
|
// Linux) need a DLL + import lib + launcher exe so LoadProject can
|
|
// compile project.cpp against a stable ABI boundary. Linux is monolithic.
|
|
crafterBuildLib->type = (crafterBuildLib->target == "x86_64-w64-mingw32" || crafterBuildLib->target == "x86_64-pc-windows-msvc")
|
|
? ConfigurationType::LibraryDynamic
|
|
: ConfigurationType::LibraryStatic;
|
|
|
|
crafterBuildLib->dependencies = { math, asset };
|
|
crafterBuildLib->defines.push_back({"CRAFTER_BUILD_HAS_ASSET", ""});
|
|
{
|
|
std::array<fs::path, 10> interfaces = {
|
|
"interfaces/Crafter.Build",
|
|
"interfaces/Crafter.Build-Shader",
|
|
"interfaces/Crafter.Build-Platform",
|
|
"interfaces/Crafter.Build-Interface",
|
|
"interfaces/Crafter.Build-Implementation",
|
|
"interfaces/Crafter.Build-External",
|
|
"interfaces/Crafter.Build-Clang",
|
|
"interfaces/Crafter.Build-Test",
|
|
"interfaces/Crafter.Build-Progress",
|
|
"interfaces/Crafter.Build-Asset",
|
|
};
|
|
std::array<fs::path, 9> implementations = {
|
|
"implementations/Crafter.Build-Shader",
|
|
"implementations/Crafter.Build-Platform",
|
|
"implementations/Crafter.Build-Interface",
|
|
"implementations/Crafter.Build-Implementation",
|
|
"implementations/Crafter.Build-External",
|
|
"implementations/Crafter.Build-Clang",
|
|
"implementations/Crafter.Build-Test",
|
|
"implementations/Crafter.Build-Progress",
|
|
"implementations/Crafter.Build-Asset",
|
|
};
|
|
crafterBuildLib->GetInterfacesAndImplementations(interfaces, implementations);
|
|
}
|
|
ExternalDependency& glslang = crafterBuildLib->externalDependencies.emplace_back();
|
|
glslang.name = "glslang";
|
|
glslang.source.url = "https://github.com/KhronosGroup/glslang.git";
|
|
glslang.source.branch = "main";
|
|
glslang.builder = ExternalBuilder::CMake;
|
|
glslang.options = { "-DENABLE_OPT=OFF" };
|
|
// mingw cross-build: skip the standalone executable. We only consume the
|
|
// libraries, and glslang.exe pulls in libgcc_eh which needs pthread that
|
|
// mingw-w64 doesn't link by default.
|
|
if (crafterBuildLib->target == "x86_64-w64-mingw32") {
|
|
glslang.options.push_back("-DENABLE_GLSLANG_BINARIES=OFF");
|
|
}
|
|
glslang.includeDirs = { "" };
|
|
glslang.libs = { "SPIRV", "GenericCodeGen", "glslang", "OSDependent", "MachineIndependent", "glslang-default-resource-limits" };
|
|
|
|
Configuration cfg;
|
|
cfg.path = "./";
|
|
cfg.name = "crafter.build-exe";
|
|
cfg.outputName = "crafter-build";
|
|
ApplyStandardArgs(cfg, args);
|
|
cfg.type = ConfigurationType::Executable;
|
|
cfg.dependencies = { crafterBuildLib.get() };
|
|
{
|
|
std::array<fs::path, 0> interfaces = {};
|
|
std::array<fs::path, 1> implementations = { "implementations/main" };
|
|
cfg.GetInterfacesAndImplementations(interfaces, implementations);
|
|
}
|
|
if (cfg.target == "x86_64-pc-linux-gnu") {
|
|
cfg.linkFlags.push_back("-Wl,--export-dynamic");
|
|
cfg.linkFlags.push_back("-ldl");
|
|
}
|
|
if (cfg.target == "x86_64-w64-mingw32" || cfg.target == "x86_64-pc-windows-msvc") {
|
|
// winsock for the -r wasm port probe (bind/WSAStartup).
|
|
crafterBuildLib->linkFlags.push_back("-lws2_32");
|
|
}
|
|
|
|
// Self-tests link the local crafter-build library and exercise it in
|
|
// process. The harness (whichever crafter-build invokes `test`) compiles
|
|
// these against the *installed* share/crafter-build .cppm files, then
|
|
// links each test exe against crafterBuildLib built from the local
|
|
// sources — so the code under test is whatever's in this checkout.
|
|
// Mirrors how downstream consumers link their own libraries into tests.
|
|
if (cfg.target == "x86_64-pc-linux-gnu") {
|
|
cfg.AddTest("HelloWorld").Dependencies({ crafterBuildLib.get() });
|
|
cfg.AddTest("StaticLib").Dependencies({ crafterBuildLib.get() });
|
|
cfg.AddTest("ModuleInterface").Dependencies({ crafterBuildLib.get() });
|
|
cfg.AddTest("DependencyLink").Dependencies({ crafterBuildLib.get() });
|
|
cfg.AddTest("ShaderCompile").Dependencies({ crafterBuildLib.get() });
|
|
cfg.AddTest("StandardArgs").Dependencies({ crafterBuildLib.get() });
|
|
cfg.AddTest("TestRunnerSpec").Dependencies({ crafterBuildLib.get() });
|
|
cfg.AddTest("VariantId").Dependencies({ crafterBuildLib.get() });
|
|
cfg.AddTest("WasiBrowserRuntime").Dependencies({ crafterBuildLib.get() });
|
|
cfg.AddTest("RunSingleTestExit").Dependencies({ crafterBuildLib.get() });
|
|
// LoadProject dlopens the synthesized project.so, which references
|
|
// Crafter:: symbols (HostTarget, Configuration ctors) that have to be
|
|
// visible from the test exe — same wiring crafter-build itself uses
|
|
// for project.so.
|
|
cfg.AddTest("ConcurrentCacheRace").Dependencies({ crafterBuildLib.get() })
|
|
.LinkFlag("-Wl,--export-dynamic").LinkFlag("-ldl");
|
|
}
|
|
|
|
return cfg;
|
|
}
|