fix: rebuild and relink on what changed, submodules at their revisions, and mingw found where it is
Some checks failed
Flatpak / flatpak (push) Failing after 1m12s
Some checks failed
Flatpak / flatpak (push) Failing after 1m12s
- Shaders record the files glslang opened (a depfile) and their settings
(source path, stage, entry point, defines, include directories) in the
build directory, and rebuild when an included file or a setting changes,
not only the shader's own source.
- An executable relinks when its link command changed, not only when an
object is newer: a link flag in project.cpp, or a dependency's library
that moved (an ExternalDependency built into a new cache directory). The
command is recorded beside the objects; its flags come from ordered sets,
so the order the dependency threads finish in can't change it.
- An external's newest artifact counts its libDirs and versioned .so names.
- Submodules are checked out at the revisions the fetched commit records,
from the URLs its .gitmodules gives, after every clone, switch, checkout
and pull, so a pinned commit builds against its own submodules, and one
moved by hand is put back; with no .gitmodules, git isn't asked.
- Each external clone's fetch and build hold a file lock: parallel builds
pulled into one clone at once ("cannot lock ref").
- mingw:
- -stdlib=libstdc++ always;
- the std module's source is found through libstdc++.modules.json in the
mingw toolchain's own library directories (the generic lib/ holds the
host's Linux one), asking the compiler once a run;
- a toolchain that isn't there is a build error saying where it looked,
not std::terminate;
- CRAFTER_MINGW_DIR names the prefix, the directory holding
x86_64-w64-mingw32/ and lib/gcc/;
- inside the Flatpak the host's /usr mingw is used.
- RunScript's exit-status check reads as the one range it is.
- Tests: ConcurrentExternalFetch, ExternalArtifactTime, GitSubmodules,
LinkFlagsChange, MingwMissing, ShaderIncludeChange; ShaderCompile and
ToolchainResolve extended.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
959c65dbb2
commit
653dd0bb9b
26 changed files with 1075 additions and 109 deletions
|
|
@ -8,6 +8,7 @@ module;
|
|||
#include "../lib/DirStackFileIncluder.h"
|
||||
module Crafter.Build:Shader_impl;
|
||||
import :Shader;
|
||||
import :Platform;
|
||||
import std;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
|
|
@ -31,6 +32,39 @@ namespace {
|
|||
}
|
||||
return EShLangVertex;
|
||||
}
|
||||
|
||||
// A path as a Make prerequisite, escaped the way NewestPrerequisite reads it.
|
||||
std::string MakeEscaped(const std::string& path) {
|
||||
std::string escaped;
|
||||
for (const char c : path) {
|
||||
if (c == ' ' || c == '\t' || c == '#') escaped += '\\';
|
||||
if (c == '$') escaped += '$';
|
||||
escaped += c;
|
||||
}
|
||||
return escaped;
|
||||
}
|
||||
|
||||
std::string ReadFile(const fs::path& file) {
|
||||
std::ifstream stream(file, std::ios::binary);
|
||||
return std::string(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>());
|
||||
}
|
||||
|
||||
// What a compile's output depends on besides files' contents, one line
|
||||
// each: two entries of one source differ here, and an #include searched
|
||||
// for from another source directory or in other directories can find
|
||||
// another file.
|
||||
std::string Settings(const Crafter::Shader& shader, std::span<const fs::path> includeDirs) {
|
||||
std::string settings = std::format("source {}\nstage {}\nentry {}\n", fs::absolute(shader.path).string(), static_cast<int>(shader.type), shader.entrypoint);
|
||||
for (const std::string& define : shader.defines) settings += std::format("define {}\n", define);
|
||||
for (const fs::path& dir : includeDirs) settings += std::format("include {}\n", dir.string());
|
||||
return settings;
|
||||
}
|
||||
|
||||
// Where Compile records, for Check, what the output in the bin dir was
|
||||
// built from: beside the configuration's objects, as their depfiles are.
|
||||
fs::path Record(const Crafter::Shader& shader, const fs::path& buildDir, std::string_view kind) {
|
||||
return buildDir / std::format("{}.{}", shader.OutputName().string(), kind);
|
||||
}
|
||||
}
|
||||
|
||||
namespace Crafter {
|
||||
|
|
@ -45,11 +79,25 @@ namespace Crafter {
|
|||
name.replace_extension(variant.empty() ? std::string("spv") : std::format("{}.spv", variant));
|
||||
return name;
|
||||
}
|
||||
bool Shader::Check(const fs::path& outputDir) const {
|
||||
fs::path spv = outputDir / OutputName();
|
||||
return fs::exists(spv) && fs::last_write_time(path) < fs::last_write_time(spv);
|
||||
bool Shader::Check(const fs::path& outputDir, const fs::path& buildDir, std::span<const fs::path> includeDirs) const {
|
||||
std::error_code ec;
|
||||
const fs::file_time_type built = fs::last_write_time(outputDir / OutputName(), ec);
|
||||
if (ec) return false;
|
||||
// The source and every file it #included, as the last compile
|
||||
// recorded them: without a record, or with an included file since
|
||||
// deleted, NewestPrerequisite reads max(), which is stale.
|
||||
if (std::max(fs::last_write_time(path), NewestPrerequisite(Record(*this, buildDir, "d"))) >= built) return false;
|
||||
return ReadFile(Record(*this, buildDir, "settings")) == Settings(*this, includeDirs);
|
||||
}
|
||||
std::string Shader::Compile(const fs::path& outputDir, std::span<const fs::path> includeDirs) const {
|
||||
std::string Shader::Compile(const fs::path& outputDir, const fs::path& buildDir, std::span<const fs::path> includeDirs) const {
|
||||
// The records go first, so a compile that fails leaves the output
|
||||
// stale, whatever it wrote.
|
||||
const fs::path depFile = Record(*this, buildDir, "d");
|
||||
const fs::path settingsFile = Record(*this, buildDir, "settings");
|
||||
std::error_code removed;
|
||||
fs::remove(depFile, removed);
|
||||
fs::remove(settingsFile, removed);
|
||||
|
||||
EShLanguage glslangType = ToEShLanguage(type);
|
||||
glslang::InitializeProcess();
|
||||
|
||||
|
|
@ -157,6 +205,25 @@ namespace Crafter {
|
|||
if (!spvLog.empty()) {
|
||||
return fail("SPIR-V codegen reported issues", std::move(spvLog));
|
||||
}
|
||||
|
||||
// What Check compares the output against next time: the source and
|
||||
// the files it included, as absolute paths so that reading them back
|
||||
// doesn't depend on the directory a build runs from, and the settings.
|
||||
std::ofstream dep(depFile, std::ios::binary);
|
||||
dep << MakeEscaped(fs::absolute(outputDir / filename).string()) << ": " << MakeEscaped(fs::absolute(path).string());
|
||||
for (const std::string& included : includeDir.getIncludedFiles()) {
|
||||
dep << " \\\n " << MakeEscaped(fs::absolute(included).string());
|
||||
}
|
||||
dep << '\n';
|
||||
dep.close();
|
||||
std::ofstream settings(settingsFile, std::ios::binary);
|
||||
settings << Settings(*this, includeDirs);
|
||||
settings.close();
|
||||
if (!dep || !settings) {
|
||||
fs::remove(depFile, removed);
|
||||
fs::remove(settingsFile, removed);
|
||||
return fail("failed to record what the shader was built from", buildDir.string());
|
||||
}
|
||||
glslang::FinalizeProcess();
|
||||
return {};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue