- subprocess-isolated test runner (replaces V1 dlopen-RunTest); Pass/Fail/Crash/Timeout/Skipped outcomes via :Test partition - TestRunner abstraction with command templates: Local, Ssh, SshWin (cmd.exe-shell), QemuUser, FromEnv; probe-based skip when runner unreachable - transitive PCM-path propagation in Build(); resolveImport walks deps recursively; depResults cache keyed by PcmDir() so per-target builds don't collide - cfg.sysroot threaded through BuildStdPcm + base compile/link command (enables aarch64 cross via Arch Linux ARM rootfs) - lib + exe split: project.cpp defines crafterBuildLib (LibraryStatic) + crafterBuildExe (Executable depending on it); build.sh produces lib/libcrafter-build.a alongside bin/crafter-build for downstream static-link consumers - Windows DLL+launcher: CRAFTER_API macro, /EXPORT flag for project.dll's CrafterBuildProject; Crafter::Run as the real entry point with main.cpp as a thin wrapper - 18 tests: HelloWorld/WithModule/Defines/CrossProjectModule/ Diamond × (Linux + sshwin:winvm), plus Incremental, BuildError, Libraries, RunnerClassification, QemuUser, SshRunner, WindowsViaSsh, CrossArchAarch64 - single ./bin/crafter-build test runs everything; Windows variants skip gracefully if winvm SSH alias unreachable Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
201 lines
8.2 KiB
C++
201 lines
8.2 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) {
|
|
bool debug = false;
|
|
for (std::string_view arg : args) {
|
|
if (arg == "--debug") debug = true;
|
|
}
|
|
|
|
static auto crafterBuildLib = std::make_unique<Configuration>();
|
|
crafterBuildLib->path = "./";
|
|
crafterBuildLib->name = "crafter.build-lib";
|
|
crafterBuildLib->outputName = "crafter-build";
|
|
crafterBuildLib->target = "x86_64-pc-linux-gnu";
|
|
crafterBuildLib->type = ConfigurationType::LibraryStatic;
|
|
crafterBuildLib->debug = debug;
|
|
{
|
|
std::array<fs::path, 8> 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",
|
|
};
|
|
std::array<fs::path, 7> 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",
|
|
};
|
|
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" };
|
|
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";
|
|
cfg.target = "x86_64-pc-linux-gnu";
|
|
cfg.type = ConfigurationType::Executable;
|
|
cfg.debug = debug;
|
|
cfg.dependencies = { crafterBuildLib.get() };
|
|
{
|
|
std::array<fs::path, 0> interfaces = {};
|
|
std::array<fs::path, 1> implementations = { "implementations/main" };
|
|
cfg.GetInterfacesAndImplementations(interfaces, implementations);
|
|
}
|
|
cfg.linkFlags.push_back("-Wl,--export-dynamic");
|
|
cfg.linkFlags.push_back("-ldl");
|
|
|
|
// ----- Single-driver, multi-target tests (each appears once per (target, runner)). -----
|
|
|
|
// Lifetime holders for per-target lib Configurations referenced by Tests.
|
|
static std::vector<std::unique_ptr<Configuration>> testLibPool;
|
|
|
|
struct TargetRunner {
|
|
std::string target;
|
|
TestRunner runner;
|
|
std::vector<std::string> extraLinkFlags;
|
|
};
|
|
std::vector<TargetRunner> targets = {
|
|
{ "x86_64-pc-linux-gnu", TestRunner::Local(), {} },
|
|
{ "x86_64-w64-mingw32", TestRunner::SshWin("winvm", "C:/temp/crafter-tests"), {"-lstdc++exp"} },
|
|
};
|
|
|
|
auto addPerTarget = [&](std::string name, auto buildOne) {
|
|
for (auto& tr : targets) {
|
|
Test t;
|
|
t.config.name = name;
|
|
t.config.target = tr.target;
|
|
t.config.type = ConfigurationType::Executable;
|
|
t.config.linkFlags.push_back("-fuse-ld=lld");
|
|
for (auto& f : tr.extraLinkFlags) t.config.linkFlags.push_back(f);
|
|
buildOne(t, tr.target);
|
|
t.runner = tr.runner;
|
|
cfg.tests.push_back(std::move(t));
|
|
}
|
|
};
|
|
|
|
addPerTarget("HelloWorld", [](Test& t, std::string_view) {
|
|
t.config.path = "tests/fixtures/hello-world/";
|
|
t.config.outputName = "hello";
|
|
std::array<fs::path, 0> ifaces = {};
|
|
std::array<fs::path, 1> impls = { "main" };
|
|
t.config.GetInterfacesAndImplementations(ifaces, impls);
|
|
});
|
|
|
|
addPerTarget("WithModule", [](Test& t, std::string_view) {
|
|
t.config.path = "tests/fixtures/with-module/";
|
|
t.config.outputName = "hello-mod";
|
|
std::array<fs::path, 1> ifaces = { "interfaces/Greeter" };
|
|
std::array<fs::path, 1> impls = { "main" };
|
|
t.config.GetInterfacesAndImplementations(ifaces, impls);
|
|
});
|
|
|
|
addPerTarget("Defines", [](Test& t, std::string_view) {
|
|
t.config.path = "tests/fixtures/defines/";
|
|
t.config.outputName = "defines-app";
|
|
std::array<fs::path, 0> ifaces = {};
|
|
std::array<fs::path, 1> impls = { "main" };
|
|
t.config.GetInterfacesAndImplementations(ifaces, impls);
|
|
t.config.defines.push_back({"CRAFTER_TEST_FOO", "42"});
|
|
});
|
|
|
|
addPerTarget("CrossProjectModule", [&](Test& t, std::string_view target) {
|
|
auto fooLib = std::make_unique<Configuration>();
|
|
fooLib->path = "tests/fixtures/cross-project/lib/";
|
|
fooLib->name = std::format("Foo-cross-project-{}", target);
|
|
fooLib->outputName = "Foo";
|
|
fooLib->target = std::string(target);
|
|
fooLib->type = ConfigurationType::LibraryStatic;
|
|
std::array<fs::path, 1> libIfaces = { "Foo" };
|
|
std::array<fs::path, 0> libImpls = {};
|
|
fooLib->GetInterfacesAndImplementations(libIfaces, libImpls);
|
|
Configuration* fooLibPtr = fooLib.get();
|
|
testLibPool.push_back(std::move(fooLib));
|
|
|
|
t.config.path = "tests/fixtures/cross-project/";
|
|
t.config.outputName = "cross-app";
|
|
t.config.dependencies = { fooLibPtr };
|
|
std::array<fs::path, 0> mainIfaces = {};
|
|
std::array<fs::path, 1> mainImpls = { "main" };
|
|
t.config.GetInterfacesAndImplementations(mainIfaces, mainImpls);
|
|
});
|
|
|
|
auto makeDiamondLib = [&](std::string_view dir, std::string_view modName,
|
|
std::string_view target, std::span<Configuration*> deps) {
|
|
auto lib = std::make_unique<Configuration>();
|
|
lib->path = std::format("tests/fixtures/diamond/{}/", dir);
|
|
lib->name = std::format("{}-diamond-{}", modName, target);
|
|
lib->outputName = std::string(modName);
|
|
lib->target = std::string(target);
|
|
lib->type = ConfigurationType::LibraryStatic;
|
|
lib->dependencies.assign(deps.begin(), deps.end());
|
|
std::array<fs::path, 1> ifaces = { fs::path(modName) };
|
|
std::array<fs::path, 0> impls = {};
|
|
lib->GetInterfacesAndImplementations(ifaces, impls);
|
|
return lib;
|
|
};
|
|
|
|
addPerTarget("Diamond", [&](Test& t, std::string_view target) {
|
|
auto X = makeDiamondLib("X", "X", target, {});
|
|
Configuration* xDeps[] = { X.get() };
|
|
auto B = makeDiamondLib("B", "B", target, xDeps);
|
|
auto C = makeDiamondLib("C", "C", target, xDeps);
|
|
Configuration* mainDeps[] = { B.get(), C.get() };
|
|
|
|
t.config.path = "tests/fixtures/diamond/";
|
|
t.config.outputName = "diamond-app";
|
|
t.config.dependencies.assign(mainDeps, mainDeps + 2);
|
|
std::array<fs::path, 0> ifaces = {};
|
|
std::array<fs::path, 1> impls = { "main" };
|
|
t.config.GetInterfacesAndImplementations(ifaces, impls);
|
|
|
|
testLibPool.push_back(std::move(X));
|
|
testLibPool.push_back(std::move(B));
|
|
testLibPool.push_back(std::move(C));
|
|
});
|
|
|
|
// ----- Outer-driver tests (still need their own driver source for multi-step
|
|
// logic or meta-runner behavior). -----
|
|
|
|
auto addOuterDriverTest = [&](std::string name) {
|
|
Test t;
|
|
t.config.path = "./";
|
|
t.config.name = name;
|
|
t.config.outputName = name;
|
|
t.config.target = "x86_64-pc-linux-gnu";
|
|
t.config.type = ConfigurationType::Executable;
|
|
std::array<fs::path, 0> empty = {};
|
|
std::array<fs::path, 1> impls = { fs::path("tests") / name };
|
|
t.config.GetInterfacesAndImplementations(empty, impls);
|
|
cfg.tests.push_back(std::move(t));
|
|
};
|
|
|
|
addOuterDriverTest("Incremental");
|
|
addOuterDriverTest("BuildError");
|
|
addOuterDriverTest("Libraries");
|
|
addOuterDriverTest("RunnerClassification");
|
|
addOuterDriverTest("QemuUser");
|
|
addOuterDriverTest("SshRunner");
|
|
addOuterDriverTest("CrossArchAarch64");
|
|
addOuterDriverTest("WindowsViaSsh");
|
|
|
|
return cfg;
|
|
}
|