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>
104 lines
3.8 KiB
C++
104 lines
3.8 KiB
C++
// SPDX-License-Identifier: LGPL-3.0-only
|
|
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
|
|
|
module Crafter.Build:Script_impl;
|
|
import std;
|
|
import :Script;
|
|
import :Clang;
|
|
import :Platform;
|
|
import :Progress;
|
|
namespace fs = std::filesystem;
|
|
using namespace Crafter;
|
|
|
|
namespace {
|
|
std::string JoinNames(std::span<const Script> scripts) {
|
|
std::string out;
|
|
for (const Script& script : scripts) {
|
|
if (!out.empty()) out += ", ";
|
|
out += script.name;
|
|
}
|
|
return out;
|
|
}
|
|
}
|
|
|
|
std::int32_t ScriptContext::Run(std::string_view command) const {
|
|
// Echoed by default, unlike a compile step: a script's commands are the
|
|
// only account of what it is doing to the machine.
|
|
if (Progress::GetVerbosity() != Progress::Verbosity::Quiet) std::println("$ {}", command);
|
|
return RunCommandAttached(command);
|
|
}
|
|
|
|
std::int32_t Crafter::RunScript(Configuration& projectCfg, const RunScriptOptions& opts) {
|
|
// Loading the project may have left an in-place status line behind.
|
|
Progress::Clear();
|
|
|
|
const std::span<const Script> scripts = projectCfg.scripts;
|
|
if (scripts.empty()) {
|
|
std::println(std::cerr,
|
|
R"msg(No scripts defined.
|
|
Add them in project.cpp before returning the Configuration:
|
|
|
|
cfg.scripts.push_back({{"docs", [](Crafter::ScriptContext& ctx) {{
|
|
return ctx.Run("python3 tools/gen-docs.py");
|
|
}}}});
|
|
|
|
`crafter-build run docs [args...]` then runs it and exits with what it
|
|
returned; ctx.args holds the arguments.)msg");
|
|
return 1;
|
|
}
|
|
|
|
// Checked for every script on every run, the list included, so a bad
|
|
// declaration is reported the first time anyone uses `run` and not only
|
|
// when that one name is asked for.
|
|
std::unordered_set<std::string_view> seen;
|
|
for (const Script& script : scripts) {
|
|
if (script.name.empty() || script.name.starts_with('-')) {
|
|
std::println(std::cerr, "script name '{}' cannot be run: a name must not be empty or start with '-'", script.name);
|
|
return 1;
|
|
}
|
|
// Running the first or the last of two would both be a guess, and
|
|
// the usual cause is a pasted line whose name was never changed.
|
|
if (!seen.insert(script.name).second) {
|
|
std::println(std::cerr, "script '{}' is defined twice", script.name);
|
|
return 1;
|
|
}
|
|
if (!script.run) {
|
|
std::println(std::cerr, "script '{}' has no callback", script.name);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (opts.listOnly || !opts.named) {
|
|
for (const Script& script : scripts) std::println("{}", script.name);
|
|
return 0;
|
|
}
|
|
|
|
const auto found = std::ranges::find(scripts, opts.name, &Script::name);
|
|
if (found == scripts.end()) {
|
|
std::println(std::cerr, "No script named '{}'. Scripts: {}", opts.name, JoinNames(scripts));
|
|
return 1;
|
|
}
|
|
|
|
ScriptContext ctx{
|
|
.project = projectCfg,
|
|
// lexically_normal: fs::absolute keeps a "./" component.
|
|
.projectDir = opts.projectFile.empty() ? fs::absolute(projectCfg.path).lexically_normal() : opts.projectFile.parent_path(),
|
|
.args = opts.args,
|
|
};
|
|
try {
|
|
const std::int32_t code = found->run(ctx);
|
|
if (code >= 0 && code <= 255) return code;
|
|
std::println(std::cerr, "script '{}' returned {}, which is not an exit status (0..255); exiting with 1", found->name, code);
|
|
return 1;
|
|
} catch (const std::exception& e) {
|
|
// Never let a script's exception unwind across the project.so
|
|
// boundary and out of Run: report it as this script's failure.
|
|
Progress::Clear();
|
|
std::println(std::cerr, "script '{}' failed: {}", found->name, e.what());
|
|
return 1;
|
|
} catch (...) {
|
|
Progress::Clear();
|
|
std::println(std::cerr, "script '{}' failed: it threw something that isn't a std::exception", found->name);
|
|
return 1;
|
|
}
|
|
}
|