Crafter.Build/interfaces/Crafter.Build-External.cppm
Jorijn van der Graaf 84d9d66649
All checks were successful
Flatpak / flatpak (pull_request) Successful in 7m27s
docs: correct what the sibling-args docs promise
The README suggested giving one sibling a list without a flag only the
project reads, so that the sibling would not get a build directory of
its own. When another path loads the same sibling with the full list,
that filter is what gives it the second directory: two builds of one
project, both in the program (#41). It now says to build such a list
once and give it to every sibling. It also says the default is read
when the spec is made, so a spec made at namespace scope, on another
thread or in a script forwards nothing, and that cfg.projectArgs, the
part ApplyStandardArgs did not interpret, is no substitute: it drops
the target and the build type. The comments on ProjectArgs() and on
Configuration::projectArgs say the same.

The spec comment named --local as a flag the default carries down a
chain of siblings. Through a GitProject hop it breaks: the clone has no
checkouts beside it. The GitProject comment said the clone is keyed by
the args; only the Configuration is, and every arg list builds in the
one clone. AGENTS.md now notes that a stale harness gives no symptom
for a changed default member initialiser: project.cpp silently gets the
old default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:11:41 +01:00

141 lines
7.5 KiB
C++

// SPDX-License-Identifier: LGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
export module Crafter.Build:External;
import std;
namespace fs = std::filesystem;
export namespace Crafter {
struct Configuration;
struct GitSource {
std::string url;
std::string branch;
std::string commit;
};
enum class ExternalBuilder {
None,
CMake,
};
struct ExternalDependency {
std::string name;
GitSource source;
ExternalBuilder builder = ExternalBuilder::None;
std::vector<std::string> options;
std::vector<fs::path> includeDirs;
// Extra library search paths (each becomes a -L flag), interpreted
// relative to the CMake build dir. Currently only honoured for
// ExternalBuilder::CMake. The CMake builder always adds its
// top-level build dir as a -L; libDirs is for projects (msquic,
// others) whose CMakeLists set LIBRARY_OUTPUT_DIRECTORY to a subdir
// of the build tree.
std::vector<fs::path> libDirs;
std::vector<std::string> libs;
};
struct ExternalBuildResult {
std::vector<std::string> compileFlags;
std::vector<std::string> linkFlags;
fs::file_time_type latestArtifact = fs::file_time_type::min();
};
// Where this dependency's clone lives in the cache. A pure function of the
// declaration and target — BuildExternal derives its own working directory
// through this, so a caller that only needs to know where the headers ended
// up cannot drift from where they actually are.
fs::path ExternalCloneDir(const ExternalDependency& dep, std::string_view target);
// Where BuildExternal leaves this dependency: the CMake build directory,
// or the clone itself when it is only fetched. Pure, like ExternalCloneDir.
// Build() keys a pass's record of the work on it rather than on the clone,
// so a declaration that shares the clone but builds it with CMake is not
// handed the outcome of a fetch alone.
fs::path ExternalBuildDir(const ExternalDependency& dep, std::string_view target);
// The -I flags this dependency contributes, from its declared includeDirs.
// Also pure, which is what lets the linter's AST layer parse a source that
// includes an external's headers without running a build to find out where
// they are. The paths only resolve once something has fetched the clone.
std::vector<std::string> ExternalIncludeFlags(const ExternalDependency& dep, std::string_view target);
// Fetches the clone and, for ExternalBuilder::CMake, configures and builds
// it. Empty on success, otherwise what failed. A lock on the clone is held
// throughout, so another crafter-build, or another build pass in this one,
// works on it before or after this call and never during it. Not
// cancellable once started: Build() hands the outcome to every
// configuration of the pass that declares the dependency, and a run cut
// short would leave them a clone nobody fetched.
std::string BuildExternal(const ExternalDependency& dep, std::string_view target);
// What a configuration that declares `dep` compiles and links with once
// BuildExternal has run: the -I flags and, for CMake, the -L, -rpath and -l
// flags and the newest library. Per declaration, because two that share a
// clone can still differ in includeDirs, libDirs and libs.
ExternalBuildResult ExternalOutputs(const ExternalDependency& dep, std::string_view target);
// The args the project file now running its CrafterBuildProject was
// loaded with: everything that function receives, --target=, --debug and
// the other standard flags included. Not cfg.projectArgs, which holds
// only what ApplyStandardArgs left uninterpreted: `.args =
// cfg.projectArgs` would drop the target and the build type. Empty
// everywhere else, because it reads the stack LoadProject keeps per
// thread, as IsTopLevelProject does: outside a project file, on any
// thread but the one running it, once CrafterBuildProject has returned
// (a script runs later), and before it is called, which covers the
// file's static initialisers and its `#pragma crafter dependency`
// pre-pass. The specs below default their args to it. Defined beside
// LoadProject, but declared here so those defaults can see it: importing
// :Platform into this partition would make the exported `struct
// Configuration;` above a redeclaration of :Platform's unexported one,
// which clang rejects.
std::vector<std::string> ProjectArgs();
// Specification for a sibling crafter-build project to fetch and depend on.
// GitSource picks the revision: leave branch + commit empty for the
// remote's default branch HEAD (sloppy but convenient), set branch to
// track a branch tip, or set commit to pin to an immutable SHA. args are
// what the dep's CrafterBuildProject receives. They default to this
// project's own, read when the spec is made, so --target=, --march=,
// --debug and the project's own flags reach every sibling down a chain
// without being passed. Set them to forward something else: {} for
// nothing, or a list built from this project's args, such as one without
// a flag only this project reads. Give such a list to every sibling: a
// project that two paths load with two different lists is built twice,
// and the program ends up with both builds.
struct GitProjectSpec {
GitSource source;
fs::path projectFile = "project.cpp";
std::vector<std::string> args = ProjectArgs();
};
// Clones the spec's git URL into the external cache, recursively
// LoadProject's the remote's project.cpp, and returns a stable
// Configuration* you can drop into cfg.dependencies. The clone is keyed by
// url+branch+commit+projectFile alone, so every arg list builds in the
// same working tree, each into variant directories of its own. The
// Configuration is owned by an internal cache for the lifetime of the
// build, keyed by the clone and the args: identical specs share one.
Configuration* GitProject(const GitProjectSpec& spec);
// Specification for a local sibling crafter-build project (e.g. an
// example folder depending on its parent project, or a workspace where
// multiple projects live side-by-side without going through git).
struct LocalProjectSpec {
fs::path projectFile; // relative to cwd or absolute
std::vector<std::string> args = ProjectArgs(); // as GitProjectSpec::args
};
// Same as GitProject but for a local on-disk project — no fetch, no
// cache copy. Resolves projectFile to its canonical absolute path and
// recursively LoadProject's it. Returns a stable Configuration* owned
// by the same internal cache as GitProject.
Configuration* LocalProject(const LocalProjectSpec& spec);
// Clone (or update) the given git source into the shared external
// cache and return the path to the cloned working tree. Intended for
// example projects that need large asset bundles which shouldn't
// live in the parent repo — push the returned path (or specific
// files inside it) into `cfg.assets`. Reuses the same cache root as
// ExternalDependency / GitProject. Caching key = url + branch + commit;
// identical calls reuse the existing clone. Pin `source.commit` to a
// SHA for reproducible, offline-after-first-fetch builds.
fs::path GitFetch(const GitSource& source);
}