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>
98 lines
5.6 KiB
C++
98 lines
5.6 KiB
C++
// SPDX-License-Identifier: LGPL-3.0-only
|
|
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
|
|
|
export module Crafter.Build:Platform;
|
|
import std;
|
|
namespace fs = std::filesystem;
|
|
|
|
namespace Crafter {
|
|
struct Configuration;
|
|
struct CommandResult {
|
|
std::int32_t exitCode = 0;
|
|
std::string output;
|
|
bool crashed = false;
|
|
bool timedOut = false;
|
|
std::int32_t signal = 0;
|
|
};
|
|
// An exclusive lock on the file `path` (created if missing), held from
|
|
// construction to destruction. An flock, so it keeps out the other
|
|
// threads of this process (each opens the file anew) and other
|
|
// crafter-build processes alike. The OS drops it when the descriptor
|
|
// closes, process death included, so an interrupted build never leaves a
|
|
// stale lock; the descriptor is close-on-exec, so the tools a holder
|
|
// spawns don't keep it past its release.
|
|
class FileLock {
|
|
std::int32_t fd = -1;
|
|
public:
|
|
explicit FileLock(const fs::path& path);
|
|
~FileLock();
|
|
FileLock(const FileLock&) = delete;
|
|
FileLock& operator=(const FileLock&) = delete;
|
|
};
|
|
std::string BuildStdPcm(const Configuration& config, fs::path stdPcm);
|
|
fs::path GetCacheDir();
|
|
CommandResult RunCommandChecked(std::string_view command);
|
|
// A compile / link / archive step. Success is the command's exit status,
|
|
// never whether it printed anything: a warning does not stop the compiler
|
|
// from writing a valid object, and warnings are only emitted when a
|
|
// translation unit is actually recompiled — so keying failure off the
|
|
// output made the same unchanged source pass or fail depending on whether
|
|
// its object happened to be up to date.
|
|
//
|
|
// Returns an empty string on success (any warnings go to the user via
|
|
// Progress::Diagnostic), otherwise the compiler's diagnostics. Callers
|
|
// therefore keep the `if (!result.empty())` shape they already had.
|
|
std::string RunBuildCommand(std::string_view command);
|
|
export CommandResult RunCommandWithTimeout(std::string_view command, std::chrono::seconds timeout);
|
|
// Runs `command` through sh with this process's own stdin, stdout and
|
|
// stderr, so its output appears as it is produced and it can prompt.
|
|
// Every helper above captures instead, which suits a compile step and
|
|
// not a command that runs for half an hour. Returns the exit code, or
|
|
// 128+signal when the command was killed; a nonzero exit is the caller's
|
|
// to judge and never throws. While it runs, Ctrl-C goes to the command
|
|
// and not to crafter-build, which then sees it as exit code 130.
|
|
// Project scripts reach this through ScriptContext::Run.
|
|
std::int32_t RunCommandAttached(std::string_view command);
|
|
// `s` as one single-quoted sh word, safe to splice into a command line
|
|
// whatever it contains: paths with spaces, arguments a user typed.
|
|
export std::string ShellQuote(std::string_view s);
|
|
std::string GetBaseCommand(const Configuration& config);
|
|
export Configuration LoadProject(const fs::path& projectFile, std::span<const std::string_view> args);
|
|
// A dependency the project file currently running its CrafterBuildProject
|
|
// declared with `#pragma crafter dependency`, so it can also be wired
|
|
// into the build graph (a test that links the same module, say) without
|
|
// spelling the source twice. `name` is the dependency's Configuration
|
|
// name or any module it exports — whatever the project file `import`s.
|
|
// Throws when no such dependency was declared, or when called from
|
|
// anywhere other than inside LoadProject.
|
|
export Configuration* ProjectDependency(std::string_view name);
|
|
// True while the project file crafter-build was pointed at runs its
|
|
// CrafterBuildProject, and when no project file is running at all; false
|
|
// inside a dependency's project file (a `#pragma crafter dependency`,
|
|
// GitProject or LocalProject target). Wire anything only the project's
|
|
// own build or test run needs behind it. The case that forced it: a
|
|
// library's test links crafter-build itself, so when crafter-build loads
|
|
// that library as a dependency, the test's GitProject loads crafter-build's
|
|
// project file in turn and the two recurse until the cycle guard throws.
|
|
export bool IsTopLevelProject();
|
|
// Resolves the directory holding distributed runtime assets (Crafter.Build
|
|
// module sources, wasi-runtime/, etc). Honors CRAFTER_BUILD_HOME; otherwise
|
|
// derives <prefix>/share/crafter-build from the running executable's path.
|
|
export fs::path GetCrafterBuildHome();
|
|
// Newest mtime among the prerequisites a compiler recorded in `depFile`
|
|
// (clang's `-MD -MF <artifact>.d`). This is how a build step learns which
|
|
// headers its source pulled in: the module scanner only reads `import`
|
|
// lines, so an `#include`d file is invisible to it until the compiler
|
|
// reports what it actually opened.
|
|
//
|
|
// Returns file_time_type::max() when freshness cannot be proven — no
|
|
// depfile (nothing has compiled this artifact since depfiles were emitted,
|
|
// including every object left over from an older crafter-build) or a
|
|
// prerequisite that no longer exists. Callers compare the result against
|
|
// their artifact's mtime, so max() reads as "rebuild".
|
|
fs::file_time_type NewestPrerequisite(const fs::path& depFile);
|
|
// Wildcard name matching ('*', '?') shared by the test and lint verbs.
|
|
bool MatchGlob(std::string_view glob, std::string_view name);
|
|
// Empty `globs` matches everything.
|
|
bool MatchAny(std::span<const std::string> globs, std::string_view name);
|
|
}
|