diff --git a/implementations/Crafter.Build-Clang.cpp b/implementations/Crafter.Build-Clang.cpp index 1de2264..4965488 100644 --- a/implementations/Crafter.Build-Clang.cpp +++ b/implementations/Crafter.Build-Clang.cpp @@ -572,6 +572,36 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map shaderSeen; + std::function copyDepShaders = [&](Configuration* dep) { + if (!shaderSeen.insert(dep).second) return; + fs::path depDir = dep->BinDir(); + for (const Shader& shader : dep->shaders) { + fs::path src = depDir / shader.path.filename().replace_extension("spv"); + if (!fs::exists(src)) continue; + fs::path dest = outputDir / src.filename(); + if (!fs::exists(dest) || fs::last_write_time(src) > fs::last_write_time(dest)) { + fs::copy_file(src, dest, fs::copy_options::overwrite_existing); + } + } + for (Configuration* sub : dep->dependencies) copyDepShaders(sub); + }; + for (Configuration* dep : config.dependencies) copyDepShaders(dep); + } catch (const fs::filesystem_error& e) { + for(std::thread& thread : threads) thread.join(); + return {e.what(), false, {}}; + } + } + if(repack.load()) { buildResult.repack = true; } diff --git a/implementations/Crafter.Build-Shader.cpp b/implementations/Crafter.Build-Shader.cpp index 0880e88..8a8d5c8 100644 --- a/implementations/Crafter.Build-Shader.cpp +++ b/implementations/Crafter.Build-Shader.cpp @@ -54,7 +54,8 @@ namespace Crafter { } bool Shader::Check(const fs::path& outputDir) const { - return fs::exists((outputDir/path.filename()).generic_string()+".spv") && fs::last_write_time(path.generic_string()+".glsl") < fs::last_write_time((outputDir/path.filename()).generic_string()+".spv"); + fs::path spv = outputDir / path.filename().replace_extension("spv"); + return fs::exists(spv) && fs::last_write_time(path) < fs::last_write_time(spv); } std::string Shader::Compile(const fs::path& outputDir) const { EShLanguage glslangType = ToEShLanguage(type); diff --git a/tests/ShaderDep/ShaderDep.cpp b/tests/ShaderDep/ShaderDep.cpp new file mode 100644 index 0000000..0bf8551 --- /dev/null +++ b/tests/ShaderDep/ShaderDep.cpp @@ -0,0 +1,58 @@ +/* +Crafter® Build +Copyright (C) 2026 Catcrafts® +Catcrafts.net + +LGPL-3.0-only. + +Verifies that when an executable consumes a library that registers a shader, +the resulting .spv ships in the executable's bin dir alongside the binary — +not just in the lib's own bin dir, where the runtime exe wouldn't find it. +*/ + +import std; +import Crafter.Build; +#include "../_shared/TestUtil.h" +namespace fs = std::filesystem; +using namespace TestUtil; +using namespace Crafter; + +int main() { + try { + fs::path src = fs::current_path() / "tests" / "ShaderDep" / "inner"; + Configuration cfg = LoadFixture("ShaderDep", src); + + auto br = BuildOnce(cfg); + if (!br.result.empty()) { + std::println(std::cerr, "build failed:\n{}", br.result); + return 1; + } + + fs::path libSpv = cfg.dependencies[0]->BinDir() / "triangle.spv"; + fs::path appSpv = cfg.BinDir() / "triangle.spv"; + + if (!fs::exists(libSpv)) { + std::println(std::cerr, "lib .spv missing at {}", libSpv.string()); + return 1; + } + if (!fs::exists(appSpv)) { + std::println(std::cerr, "exe .spv missing at {}", appSpv.string()); + return 1; + } + + std::ifstream f(appSpv, std::ios::binary); + unsigned char magic[4] = {}; + f.read(reinterpret_cast(magic), 4); + if (magic[0] != 0x03 || magic[1] != 0x02 || magic[2] != 0x23 || magic[3] != 0x07) { + std::println(std::cerr, + "SPIR-V magic mismatch in exe-side copy: got {:#04x} {:#04x} {:#04x} {:#04x}", + magic[0], magic[1], magic[2], magic[3]); + return 1; + } + + return 0; + } catch (const std::exception& e) { + std::println(std::cerr, "test exception: {}", e.what()); + return 1; + } +} diff --git a/tests/ShaderDep/inner/lib/ShaderLib.cppm b/tests/ShaderDep/inner/lib/ShaderLib.cppm new file mode 100644 index 0000000..f5b1bc1 --- /dev/null +++ b/tests/ShaderDep/inner/lib/ShaderLib.cppm @@ -0,0 +1,3 @@ +export module ShaderLib; + +export int Identify() { return 0; } diff --git a/tests/ShaderDep/inner/lib/triangle.glsl b/tests/ShaderDep/inner/lib/triangle.glsl new file mode 100644 index 0000000..e1abf19 --- /dev/null +++ b/tests/ShaderDep/inner/lib/triangle.glsl @@ -0,0 +1,13 @@ +#version 450 + +layout(location = 0) out vec3 color; + +void main() { + vec2 positions[3] = vec2[]( + vec2( 0.0, -0.5), + vec2( 0.5, 0.5), + vec2(-0.5, 0.5) + ); + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); + color = vec3(1.0, 0.0, 0.0); +} diff --git a/tests/ShaderDep/inner/main.cpp b/tests/ShaderDep/inner/main.cpp new file mode 100644 index 0000000..2b7338b --- /dev/null +++ b/tests/ShaderDep/inner/main.cpp @@ -0,0 +1,4 @@ +import std; +import ShaderLib; + +int main() { return Identify(); } diff --git a/tests/ShaderDep/inner/project.cpp b/tests/ShaderDep/inner/project.cpp new file mode 100644 index 0000000..5ffb058 --- /dev/null +++ b/tests/ShaderDep/inner/project.cpp @@ -0,0 +1,33 @@ +import std; +import Crafter.Build; +namespace fs = std::filesystem; +using namespace Crafter; + +extern "C" Configuration CrafterBuildProject(std::span) { + static auto ShaderLib = std::make_unique(); + ShaderLib->path = "./lib/"; + ShaderLib->name = "ShaderLib"; + ShaderLib->outputName = "ShaderLib"; + ShaderLib->target = "x86_64-pc-linux-gnu"; + ShaderLib->type = ConfigurationType::LibraryStatic; + { + std::array ifaces = { "ShaderLib" }; + std::array impls = {}; + ShaderLib->GetInterfacesAndImplementations(ifaces, impls); + } + ShaderLib->shaders.emplace_back("./lib/triangle.glsl", "main", ShaderType::Vertex); + + Configuration app; + app.path = "./"; + app.name = "shader-dep-app"; + app.outputName = "shader-dep-app"; + app.target = "x86_64-pc-linux-gnu"; + app.type = ConfigurationType::Executable; + app.dependencies = { ShaderLib.get() }; + { + std::array ifaces = {}; + std::array impls = { "main" }; + app.GetInterfacesAndImplementations(ifaces, impls); + } + return app; +} diff --git a/tests/ShaderDep/project.cpp b/tests/ShaderDep/project.cpp new file mode 100644 index 0000000..045d72d --- /dev/null +++ b/tests/ShaderDep/project.cpp @@ -0,0 +1,20 @@ +import std; +import Crafter.Build; +namespace fs = std::filesystem; +using namespace Crafter; + +extern "C" Configuration CrafterBuildProject(std::span) { + Configuration cfg; + cfg.path = "tests/ShaderDep/"; + cfg.name = "ShaderDep"; + cfg.outputName = "ShaderDep"; + cfg.target = "x86_64-pc-linux-gnu"; + cfg.type = ConfigurationType::Executable; + cfg.dependencies = { ParentLib("crafter.build-lib") }; + cfg.linkFlags.push_back("-Wl,--export-dynamic"); + cfg.linkFlags.push_back("-ldl"); + std::array ifaces = {}; + std::array impls = { "ShaderDep" }; + cfg.GetInterfacesAndImplementations(ifaces, impls); + return cfg; +}