2026-07-23 01:24:42 +02:00
|
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
|
|
|
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
2026-04-27 07:04:42 +02:00
|
|
|
|
test runner, cross-target runners, lib/exe split
- subprocess-isolated test runner (replaces V1 dlopen-RunTest);
Pass/Fail/Crash/Timeout/Skipped outcomes via :Test partition
- TestRunner abstraction with command templates: Local, Ssh,
SshWin (cmd.exe-shell), QemuUser, FromEnv; probe-based skip
when runner unreachable
- transitive PCM-path propagation in Build(); resolveImport
walks deps recursively; depResults cache keyed by PcmDir()
so per-target builds don't collide
- cfg.sysroot threaded through BuildStdPcm + base compile/link
command (enables aarch64 cross via Arch Linux ARM rootfs)
- lib + exe split: project.cpp defines crafterBuildLib
(LibraryStatic) + crafterBuildExe (Executable depending on
it); build.sh produces lib/libcrafter-build.a alongside
bin/crafter-build for downstream static-link consumers
- Windows DLL+launcher: CRAFTER_API macro, /EXPORT flag for
project.dll's CrafterBuildProject; Crafter::Run as the real
entry point with main.cpp as a thin wrapper
- 18 tests: HelloWorld/WithModule/Defines/CrossProjectModule/
Diamond × (Linux + sshwin:winvm), plus Incremental,
BuildError, Libraries, RunnerClassification, QemuUser,
SshRunner, WindowsViaSsh, CrossArchAarch64
- single ./bin/crafter-build test runs everything; Windows
variants skip gracefully if winvm SSH alias unreachable
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-27 22:32:19 +02:00
|
|
|
module;
|
|
|
|
|
#include "Crafter.Build-Api.h"
|
2026-04-27 07:04:42 +02:00
|
|
|
export module Crafter.Build:External;
|
|
|
|
|
import std;
|
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
|
|
|
|
|
|
export namespace Crafter {
|
2026-04-30 02:20:19 +02:00
|
|
|
struct Configuration;
|
|
|
|
|
|
2026-04-27 07:04:42 +02:00
|
|
|
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;
|
2026-05-06 03:59:19 +02:00
|
|
|
// 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;
|
2026-04-27 07:04:42 +02:00
|
|
|
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();
|
|
|
|
|
};
|
|
|
|
|
|
feat(lint): no-char-pointer reads the AST, retiring the interop denylist
The rule was `\bchar\s*\*` over the text minus a substring denylist — argv,
getenv, setenv, dlerror, c_str, .data(, reinterpret_cast, extern ". Every entry
was a patch for one interop site, the list could only grow as libraries
arrived, and each entry disabled the rule for the whole LINE it appeared on.
It now walks declarations and asks the question the denylist was approximating:
whose header dictates this spelling? A declaration with C language linkage, or
one whose initialiser binds to an entity declared outside the project root, is
somebody else's API and keeps its spelling. getenv, c_str and friends are
exempt because of where they are declared, not because they are named here, so
a new external library needs no new entry.
Two bugs found while testing this, both of which had made the rule silently
pass over the entire repository:
clang_getCursorLanguage cannot be used to detect extern "C". Its default answer
is CXLanguage_C for a plain function, variable or parameter even in a C++
translation unit, so isExternC was true almost everywhere and exempted
everything. Replaced by tracking CXCursor_LinkageSpec depth during the walk,
reading the extent text to tell extern "C" from extern "C++".
Attributing any foreign reference in a subtree to the enclosing declaration was
too broad: a function that merely touched libc++ somewhere in its body would
exempt its own signature. Narrowed to initialiser contexts — a variable, field
or parameter — which is where a binding to a foreign API actually occurs.
Also: functions now carry their RESULT type rather than the whole function
type, since the parameters arrive as their own declarations and would otherwise
be reported twice. main's parameters are exempt structurally, its signature
being fixed by the language rather than chosen here.
Two sites keep an explicit lint-disable, both Crafter::Run taking main's argv
verbatim. That is two visible, reasoned suppressions in place of a denylist
that silently disabled the rule for every line mentioning one of eight tokens.
ExternalCloneDir and ExternalIncludeFlags are now exposed from :External, so a
source that includes an external dependency's headers can be parsed without
running a build to discover where they are. BuildExternal derives its own
working directory through the same function, so the two cannot drift.
Crafter.Build-Shader.cpp needed this to parse at all.
A file with no compile command — project.cpp, which LoadProject builds with its
own flags — is not a translation unit of the build graph, so AST rules skip it
the way a rule self-filters by extension. That is distinct from a file that
should have parsed and did not, which stays an error.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 23:46:32 +02:00
|
|
|
// 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.
|
|
|
|
|
CRAFTER_API 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.
|
|
|
|
|
CRAFTER_API std::vector<std::string> ExternalIncludeFlags(const ExternalDependency& dep, std::string_view target);
|
2026-07-23 01:24:42 +02:00
|
|
|
CRAFTER_API ExternalBuildResult BuildExternal(const ExternalDependency& dep, std::string_view target, std::atomic<bool>& cancelled);
|
2026-04-30 02:20:19 +02:00
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
|
CRAFTER_API 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.
|
|
|
|
|
CRAFTER_API Configuration* LocalProject(const LocalProjectSpec& spec);
|
2026-05-19 00:50:06 +02:00
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
|
CRAFTER_API fs::path GitFetch(const GitSource& source);
|
2026-04-27 07:04:42 +02:00
|
|
|
}
|