fixes
All checks were successful
CI / build-test-release (push) Successful in 15m14s

This commit is contained in:
Jorijn van der Graaf 2026-05-02 21:08:51 +02:00
commit d7a9c85ea6
11 changed files with 183 additions and 43 deletions

View file

@ -247,6 +247,29 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
BuildResult buildResult;
// glslang #include search paths for every shader compiled in this
// configuration: each transitive (incl. self) buildFiles entry's parent
// dir, or the entry itself if it points at a directory. Collected once
// up front so the per-shader threads can capture the resulting span by
// reference. No file copy involved — glslang reads the includes in
// place from the dep's source tree.
std::vector<fs::path> shaderIncludeDirs;
{
std::unordered_set<std::string> seenDirs;
std::unordered_set<const Configuration*> seenCfg;
std::function<void(const Configuration*)> collect = [&](const Configuration* c) {
if (!seenCfg.insert(c).second) return;
for (const fs::path& bf : c->buildFiles) {
fs::path dir = fs::is_directory(bf) ? bf : bf.parent_path();
if (seenDirs.insert(dir.string()).second) {
shaderIncludeDirs.push_back(std::move(dir));
}
}
for (const Configuration* sub : c->dependencies) collect(sub);
};
collect(&config);
}
std::vector<std::thread> threads;
threads.reserve(config.shaders.size() + 1 + config.interfaces.size() + config.implementations.size());
@ -254,11 +277,11 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
std::atomic<bool> buildCancelled{false};
for (const Shader& shader : config.shaders) {
if (shader.Check(outputDir)) continue;
threads.emplace_back([&shader, &outputDir, &buildError, &buildCancelled]() {
threads.emplace_back([&shader, &outputDir, &shaderIncludeDirs, &buildError, &buildCancelled]() {
Progress::Task task(std::format("Compiling shader {}", shader.path.filename().string()));
if (buildCancelled.load(std::memory_order_relaxed)) return;
std::string result = shader.Compile(outputDir);
std::string result = shader.Compile(outputDir, shaderIncludeDirs);
if (result.empty()) return;
bool expected = false;
@ -272,42 +295,28 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
Progress::Task task(std::format("Copying files for {}", config.name));
if (buildCancelled.load(std::memory_order_relaxed)) return;
try {
for (const fs::path& additionalFile : config.files) {
for (const fs::path& additionalFile : config.files) {
fs::path destination = outputDir / additionalFile.filename();
if (fs::is_directory(additionalFile)) {
for (const auto& entry : fs::recursive_directory_iterator(additionalFile)) {
const fs::path& sourcePath = entry.path();
// Compute relative path inside the directory
fs::path relativePath = fs::relative(sourcePath, additionalFile);
fs::path destPath = destination / relativePath;
if (entry.is_directory()) {
// Ensure directory exists in destination
if (!fs::exists(destPath)) {
fs::create_directories(destPath);
}
if (!fs::exists(destPath)) fs::create_directories(destPath);
} else if (entry.is_regular_file()) {
// Ensure parent directory exists
fs::create_directories(destPath.parent_path());
if (!fs::exists(destPath)) {
fs::copy_file(sourcePath, destPath);
}
else if (fs::last_write_time(sourcePath) > fs::last_write_time(destPath)) {
} else if (fs::last_write_time(sourcePath) > fs::last_write_time(destPath)) {
fs::copy_file(sourcePath, destPath, fs::copy_options::overwrite_existing);
}
}
}
} else {
// Handle regular file
if (!fs::exists(destination)) {
fs::copy_file(additionalFile, destination);
}
else if (fs::last_write_time(additionalFile) > fs::last_write_time(destination)) {
} else if (fs::last_write_time(additionalFile) > fs::last_write_time(destination)) {
fs::copy_file(additionalFile, destination, fs::copy_options::overwrite_existing);
}
}
@ -572,30 +581,60 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
return {buildError, false, {}};
}
// Ship .spv files from transitive deps' bin dirs alongside the executable
// so consumers find shaders next to the binary at runtime. Only an exe is
// a deployment unit; intermediate libs don't need to forward shaders since
// the exe walks all transitive deps. Runs outside the repack gate because
// the relink mtime check above only watches .so/.dll/.a, so a shader-only
// change in a dep wouldn't trigger repack but still needs the new .spv
// copied across.
// Ship runtime artifacts from transitive deps' bin dirs alongside the
// executable: compiled .spv files (cfg.shaders) and asset files/dirs
// (cfg.files). The lib already mirrored these into its own bin dir
// during its build, but a consumer exe loads them from its own dir at
// runtime. Only an exe is a deployment unit; intermediate libs don't
// need to forward since the exe walks all transitive deps. Runs outside
// the repack gate because the relink mtime check above only watches
// .so/.dll/.a, so a shader/file-only change in a dep wouldn't trigger
// repack but still needs the new artifact copied across.
// (cfg.buildFiles use a different mechanism — they're exposed to shader
// compiles as #include search paths in place, no copy.)
if (config.type == ConfigurationType::Executable) {
try {
std::unordered_set<Configuration*> shaderSeen;
std::function<void(Configuration*)> 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)) {
auto copyTree = [](const fs::path& src, const fs::path& dest) {
if (fs::is_directory(src)) {
for (const auto& entry : fs::recursive_directory_iterator(src)) {
fs::path rel = fs::relative(entry.path(), src);
fs::path destPath = dest / rel;
if (entry.is_directory()) {
if (!fs::exists(destPath)) fs::create_directories(destPath);
} else if (entry.is_regular_file()) {
fs::create_directories(destPath.parent_path());
if (!fs::exists(destPath)) {
fs::copy_file(entry.path(), destPath);
} else if (fs::last_write_time(entry.path()) > fs::last_write_time(destPath)) {
fs::copy_file(entry.path(), destPath, fs::copy_options::overwrite_existing);
}
}
}
} else {
if (!fs::exists(dest)) {
fs::copy_file(src, dest);
} else if (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);
std::unordered_set<Configuration*> seen;
std::function<void(Configuration*)> forwardDepArtifacts = [&](Configuration* dep) {
if (!seen.insert(dep).second) return;
fs::path depBinDir = dep->BinDir();
for (const Shader& shader : dep->shaders) {
fs::path src = depBinDir / shader.path.filename().replace_extension("spv");
if (!fs::exists(src)) continue;
copyTree(src, outputDir / src.filename());
}
for (const fs::path& additionalFile : dep->files) {
fs::path src = depBinDir / additionalFile.filename();
if (!fs::exists(src)) continue;
copyTree(src, outputDir / additionalFile.filename());
}
for (Configuration* sub : dep->dependencies) forwardDepArtifacts(sub);
};
for (Configuration* dep : config.dependencies) forwardDepArtifacts(dep);
} catch (const fs::filesystem_error& e) {
for(std::thread& thread : threads) thread.join();
return {e.what(), false, {}};

View file

@ -317,6 +317,7 @@ namespace {
for (fs::path& p : cfg.cFiles) p = abs(p);
for (fs::path& p : cfg.cuda) p = abs(p);
for (fs::path& p : cfg.files) p = abs(p);
for (fs::path& p : cfg.buildFiles) p = abs(p);
for (Shader& s : cfg.shaders) s.path = abs(s.path);
return cfg;
}

View file

@ -57,7 +57,7 @@ namespace Crafter {
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 {
std::string Shader::Compile(const fs::path& outputDir, std::span<const fs::path> includeDirs) const {
EShLanguage glslangType = ToEShLanguage(type);
glslang::InitializeProcess();
@ -98,6 +98,9 @@ namespace Crafter {
shader.setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_4);
DirStackFileIncluder includeDir;
includeDir.pushExternalLocalDirectory(path.parent_path().generic_string());
for (const fs::path& dir : includeDirs) {
includeDir.pushExternalLocalDirectory(dir.generic_string());
}
if (!shader.parse(GetDefaultResources(), 100, false, messages, includeDir)) {
return fail("GLSL parse failed", std::string(shader.getInfoLog()) + shader.getInfoDebugLog());