An agent working on a crafter-build project reads its AGENTS.md, and learns nothing there about the libraries the project builds against unless someone copied that in by hand. This repository shows the cost: the code style section of its AGENTS.md restates what Crafter.LintRules enforces, and goes stale whenever the rules change. A library now ships those notes itself, as AGENTS.consumer.md beside its project.cpp; nothing in its project.cpp changes. When a consumer has an AGENTS.md beside its own project.cpp, `crafter-build` and `crafter-build test` keep a section in it, between two marker comments, holding its dependencies' notes. The section is appended the first time and rewritten in place after that. The file is written only when the section changes, and an AGENTS.md is never created. lint, format, run and clean leave it alone. The update runs after loading, which has just fetched every dependency, and before compiling, so a build that fails still has the notes. cfg.dependencyNotes picks the dependencies. Transitive, the default, follows `dependencies` all the way down from the project's own configurations, its tests and its `#pragma crafter dependency` tooling; Direct stops at the first loaded project on each path; None removes the section. A dependency's own pragma tooling never counts: it is about how that dependency is built, not how it is used, and being no `dependencies` edge it is never reached. Nor are a dependency's tests: nothing builds them for the consumer, and their configurations can point at a local their project file has since moved out of. The walk has to know which Configurations belong to another project, and where that project lives, and cfg.path cannot say: only the Configuration GitProject and LocalProject return is rebased, so a dependency's internal configurations keep "./", which resolves to the consumer. The loader records it instead, LoadedProjectOf beside the project cache, and keeps what the root's pragmas resolved to past the frame LoadProject pops, PragmaDependencies. Neither is exported. The markers count only as whole lines outside fenced code blocks, so documentation can quote them. Unbalanced markers and a document ending inside a code block leave the file alone with a warning: guessing which part of someone's file is generated would overwrite their text, and missing the section would add another one on every build. A note that would break the section when it is read back, through a marker line of its own or a code block it never closes, is left out with a warning. The file is replaced through a temporary renamed onto the symlink's target, so a linked AGENTS.md stays a link and a write cut short leaves the old file rather than part of the new one. The code is a new partition, Crafter.Build:Agents, listed in build.sh, CrafterBuildModules, project.cpp and the umbrella. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
116 lines
5.5 KiB
C++
116 lines
5.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::string error;
|
|
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);
|
|
// 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);
|
|
ExternalBuildResult BuildExternal(const ExternalDependency& dep, std::string_view target, std::atomic<bool>& cancelled);
|
|
|
|
// 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
|
|
// forwarded verbatim to the dep's CrafterBuildProject — typically you
|
|
// pass through the parent's args so target/march/debug propagate.
|
|
struct GitProjectSpec {
|
|
GitSource source;
|
|
fs::path projectFile = "project.cpp";
|
|
std::vector<std::string> args;
|
|
};
|
|
|
|
// Clones the spec's git URL into the per-project external cache (keyed by
|
|
// url+branch+commit+args+projectFile), recursively LoadProject's the
|
|
// remote's project.cpp, and returns a stable Configuration* you can drop
|
|
// into cfg.dependencies. The Configuration is owned by an internal cache
|
|
// for the lifetime of the build; identical specs share one Configuration.
|
|
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; // forwarded to dep's CrafterBuildProject
|
|
};
|
|
|
|
// 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);
|
|
}
|
|
|
|
namespace Crafter {
|
|
// Where GitProject or LocalProject loaded a Configuration from. The
|
|
// loader keeps this beside the cache that owns the Configuration, since
|
|
// the Configuration itself cannot say: only the one they return has its
|
|
// path rebased, and the configurations a dependency's project file makes
|
|
// for itself keep a path such as "./", which resolves against the
|
|
// consumer's directory.
|
|
struct LoadedProject {
|
|
fs::path projectFile; // absolute
|
|
std::string url; // the git source; empty for a LocalProject
|
|
};
|
|
// The LoadedProject behind a Configuration that GitProject or
|
|
// LocalProject returned; nullopt for any other: the project crafter-build
|
|
// was pointed at, and every configuration a project file makes itself.
|
|
std::optional<LoadedProject> LoadedProjectOf(const Configuration& cfg);
|
|
}
|