Merge branch 'claude/dependency-notes': a library's AGENTS.consumer.md in its consumers' AGENTS.md
All checks were successful
Flatpak / flatpak (push) Successful in 12m18s

Brings in dependency notes: a crafter-build project that ships an
AGENTS.consumer.md beside its project.cpp has it kept in a marked
section of each consumer's AGENTS.md, refreshed by `crafter-build` and
`crafter-build test`, chosen by cfg.dependencyNotes (Transitive,
Direct, None). The new partition is Crafter.Build:Agents; the loader
records where each GitProject and LocalProject came from and what a
project file's pragmas resolved to.

The branch started before master's four external-fetch commits. Both
touch External.cpp, the Clang partition, project.cpp, README.md and
AGENTS.md, but in different places, so the merge needed no hand
reconciliation; the merged tree passes build.sh, the self-host build,
format --check and the test suite.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jorijn van der Graaf 2026-10-31 05:05:07 +01:00
commit cc0a6aed8c
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
29 changed files with 1238 additions and 19 deletions

View file

@ -324,6 +324,10 @@ ExternalBuildResult Crafter::ExternalOutputs(const ExternalDependency& dep, std:
namespace {
std::mutex ProjectCacheMutex;
std::unordered_map<std::string, std::unique_ptr<Configuration>> ProjectCache;
// Keyed by the Configurations ProjectCache owns, which live as long as
// the process does, so the pointers never dangle. Guarded by
// ProjectCacheMutex as well.
std::unordered_map<const Configuration*, LoadedProject> LoadedProjects;
// Run the dep's project.cpp from its own directory so its relative paths
// (cfg.path = "./", interfaces/, lib/, ...) resolve against the dep
@ -353,6 +357,36 @@ namespace {
for (Shader& s : cfg.shaders) s.path = abs(s.path);
return cfg;
}
// Into ProjectCache under `fullHash`, with `loaded` recorded beside it.
// When another thread stored the same spec first, its Configuration
// wins and `cfg` is dropped.
Configuration* StoreProject(std::string fullHash, Configuration cfg, LoadedProject loaded) {
std::lock_guard lock(ProjectCacheMutex);
if (auto it = ProjectCache.find(fullHash); it != ProjectCache.end()) {
return it->second.get();
}
auto stored = std::make_unique<Configuration>(std::move(cfg));
Configuration* ptr = stored.get();
ProjectCache.emplace(std::move(fullHash), std::move(stored));
LoadedProjects.emplace(ptr, std::move(loaded));
return ptr;
}
// `url` as it names a repository: without user info, which is where a
// token goes, and without trailing slashes or ".git", so specs that only
// spell the repository differently name the same one.
std::string RepositoryKey(std::string_view url) {
std::string key(url);
if (const std::size_t scheme = key.find("://"); scheme != std::string::npos) {
const std::size_t host = scheme + 3;
const std::size_t at = key.rfind('@', key.find('/', host));
if (at != std::string::npos && at >= host) key.erase(host, at + 1 - host);
}
while (key.ends_with('/')) key.pop_back();
if (key.ends_with(".git")) key.resize(key.size() - 4);
return key;
}
}
Configuration* Crafter::GitProject(const GitProjectSpec& spec) {
@ -405,14 +439,13 @@ Configuration* Crafter::GitProject(const GitProjectSpec& spec) {
std::vector<std::string_view> argViews(spec.args.begin(), spec.args.end());
Configuration cfg = LoadProjectFromRoot(projectPath, cloneDir, argViews);
std::lock_guard lock(ProjectCacheMutex);
if (auto it = ProjectCache.find(fullHash); it != ProjectCache.end()) {
return it->second.get();
}
auto stored = std::make_unique<Configuration>(std::move(cfg));
Configuration* ptr = stored.get();
ProjectCache.emplace(std::move(fullHash), std::move(stored));
return ptr;
const fs::path projectInRepo = spec.projectFile.lexically_normal();
const fs::path below = projectInRepo.parent_path();
return StoreProject(std::move(fullHash), std::move(cfg), {
.projectFile = projectPath.lexically_normal(),
.name = below.empty() ? name : std::format("{}/{}", name, below.generic_string()),
.identity = std::format("git|{}|{}", RepositoryKey(spec.source.url), projectInRepo.generic_string()),
});
}
fs::path Crafter::GitFetch(const GitSource& source) {
@ -467,12 +500,15 @@ Configuration* Crafter::LocalProject(const LocalProjectSpec& spec) {
std::vector<std::string_view> argViews(spec.args.begin(), spec.args.end());
Configuration cfg = LoadProjectFromRoot(canonical, depRoot, argViews);
std::lock_guard lock(ProjectCacheMutex);
if (auto it = ProjectCache.find(fullHash); it != ProjectCache.end()) {
return it->second.get();
}
auto stored = std::make_unique<Configuration>(std::move(cfg));
Configuration* ptr = stored.get();
ProjectCache.emplace(std::move(fullHash), std::move(stored));
return ptr;
return StoreProject(std::move(fullHash), std::move(cfg), {
.projectFile = canonical,
.name = depRoot.filename().string(),
.identity = std::format("local|{}", canonical.string()),
});
}
std::optional<LoadedProject> Crafter::LoadedProjectOf(const Configuration& cfg) {
std::lock_guard lock(ProjectCacheMutex);
if (auto it = LoadedProjects.find(&cfg); it != LoadedProjects.end()) return it->second;
return std::nullopt;
}