Crafter.Build/tests/Cuda/Cuda.cpp

94 lines
3.6 KiB
C++
Raw Normal View History

2026-04-29 03:52:08 +02:00
/*
Crafter® Build
Copyright (C) 2026 Catcrafts®
Catcrafts.net
LGPL-3.0-only.
End-to-end CUDA build through the V2 pipeline:
the inner project.cpp registers a .cu translation unit via cfg.cuda, and
crafter.build-lib drives nvcc to produce a host object that links into
the final executable. The test skips when the CUDA SDK is missing
(CI hosts have neither nvcc nor libcudart) and otherwise runs the built
binary to confirm the .cu's host-callable entry returns through the link.
No GPU is required: the kernel is staged but never launched, so cudart's
fat-binary registration runs at startup but device init does not.
*/
import std;
import Crafter.Build;
#include "../_shared/TestUtil.h"
namespace fs = std::filesystem;
using namespace TestUtil;
using namespace Crafter;
namespace {
bool ToolPresent(std::string_view name) {
return std::system(std::format("which {} > /dev/null 2>&1", name).c_str()) == 0;
}
}
int main() {
try {
if (!ToolPresent("nvcc")) {
Skip("nvcc not on PATH — install the CUDA SDK to enable this test");
}
// Resolve the CUDA root from nvcc's own location so the inner project
// can point the linker at libcudart.so without a hardcoded path.
// Standard SDK layout puts nvcc at <root>/bin/nvcc and cudart at
// <root>/lib64; if either assumption is broken we skip rather than
// fail, since that means the install is non-standard.
fs::path cwd = fs::current_path();
auto probe = RunInDir(cwd, "command -v nvcc");
if (probe.exitCode != 0 || probe.output.empty()) {
Skip("nvcc not on PATH");
}
std::string nvccPath = probe.output;
while (!nvccPath.empty() && (nvccPath.back() == '\n' || nvccPath.back() == '\r')) {
nvccPath.pop_back();
}
fs::path real = fs::canonical(nvccPath);
fs::path cudaLibDir = real.parent_path().parent_path() / "lib64";
if (!fs::exists(cudaLibDir / "libcudart.so")) {
Skip(std::format("libcudart.so not found at {} — incomplete CUDA SDK install",
cudaLibDir.string()));
}
::setenv("CRAFTER_CUDA_LIBDIR", cudaLibDir.string().c_str(), 1);
fs::path src = cwd / "tests" / "Cuda" / "inner";
Configuration cfg = LoadFixture("Cuda", src);
fs::path work = fs::current_path();
auto br = BuildOnce(cfg);
if (!br.result.empty()) {
std::println(std::cerr, "build failed:\n{}", br.result);
return 1;
}
fs::path artifact = work / "bin" / "cuda-test-x86_64-pc-linux-gnu-native" / "cuda-test";
if (!fs::exists(artifact)) {
std::println(std::cerr, "expected artifact missing at {}", artifact.string());
return 1;
}
// libcudart.so is on the system loader path on most CUDA installs
// (Arch puts /opt/cuda/lib64 in /etc/ld.so.conf.d), but other distros
// don't, so set LD_LIBRARY_PATH explicitly for the run.
auto run = RunInDir(work, std::format(
"LD_LIBRARY_PATH='{}' '{}'", cudaLibDir.string(), artifact.string()));
if (run.exitCode != 0) {
std::println(std::cerr, "artifact exited nonzero (rc={}):\n{}", run.exitCode, run.output);
return 1;
}
if (run.output != "answer=42\n") {
std::println(std::cerr, "output mismatch:\n expected: \"answer=42\\n\"\n got: {:?}", run.output);
return 1;
}
return 0;
} catch (const std::exception& e) {
std::println(std::cerr, "test exception: {}", e.what());
return 1;
}
}