- C++ 96.2%
- JavaScript 2.8%
- Shell 0.5%
- Batchfile 0.5%
| implementations | ||
| interfaces | ||
| lib | ||
| tests | ||
| .gitignore | ||
| build.cmd | ||
| build.sh | ||
| LICENSE | ||
| PKGBUILD | ||
| project.cpp | ||
| README.md | ||
Crafter Build
A C++26 modules build system. Project descriptions are written in C++ — no JSON, no Lua, no embedded DSL. You write a project.cpp that constructs a Configuration and returns it; Crafter Build compiles, loads, and executes it.
Status
- Linux: working end-to-end, packageable as a distro package. Verified on Arch Linux via PKGBUILD in a fresh container.
- Windows: bootstrap builds and runs; cross-EXE/DLL symbol-resolution work in progress.
Quick start (Linux)
Bootstrap requires clang, cmake, git, lld, and libc++.
./build.sh # produces bin/crafter-build
CRAFTER_BUILD_HOME=$PWD/build ./bin/crafter-build # rebuild via project.cpp
For distro-packaged installs, crafter-build finds its modules at <prefix>/share/crafter-build/ automatically — no env var required.
To build the system as a distro package on Arch:
makepkg -si # uses CRAFTER_BUILD_MARCH=x86-64-v3 by default for portability
Writing a project.cpp
Crafter Build loads a project.cpp from the current directory. The file exports one function that returns a populated Configuration:
import std;
import Crafter.Build;
namespace fs = std::filesystem;
using namespace Crafter;
extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> args) {
Configuration cfg;
cfg.path = "./";
cfg.name = "myapp";
cfg.outputName = "myapp";
cfg.target = "x86_64-pc-linux-gnu";
cfg.type = ConfigurationType::Executable;
std::array<fs::path, 1> ifaces = { "interfaces/Hello" };
std::array<fs::path, 2> impls = { "implementations/Hello", "implementations/main" };
cfg.GetInterfacesAndImplementations(ifaces, impls);
return cfg;
}
Run crafter-build in that directory; outputs land at bin/myapp-<target>-<march>/myapp and intermediates at build/myapp-<target>-<march>/.
Dependencies
Three kinds, all fetched/built incrementally:
Cross-project Crafter sub-projects — point cfg.dependencies at another Configuration*. Cross-project module imports (import OtherProject;) are tracked per-import: only the modules that actually import a changed dep rebuild.
External git+cmake deps — for things like glslang. Declare in cfg.externalDependencies:
ExternalDependency& glslang = cfg.externalDependencies.emplace_back();
glslang.name = "glslang";
glslang.source.url = "https://github.com/KhronosGroup/glslang.git";
glslang.source.branch = "main";
glslang.builder = ExternalBuilder::CMake;
glslang.options = { "-DENABLE_OPT=OFF" };
glslang.includeDirs = { "" };
glslang.libs = { "SPIRV", "glslang", "OSDependent" };
Clones live at ~/.cache/crafter.build/external/<name>-<hash>/, keyed by (url, branch, commit, options) so different projects sharing the same dep with the same spec reuse the clone and cmake build, while projects with diverging specs each get their own cache entry. The clone is resilient to partial-clone recovery and CMake reconfigure is triggered automatically on options changes.
Header-only git deps — same as cmake deps but builder = ExternalBuilder::None. Just clones and propagates -I flags.
Install layout
<prefix>/bin/crafter-build # the executable
<prefix>/share/crafter-build/*.cppm # module sources (rebuilt on user machine on first run)
~/.cache/crafter.build/<target>-<march>/ # std.pcm + Crafter.Build*.pcm, built locally
Sources ship instead of PCMs because libc++ ABI varies between machines — the user's machine builds its own PCMs against its own libc++ on first run (~6-8s one-time cost), making the install resilient to libc++ version differences across distros.
Build incrementality
Per-import precise tracking for both within-project and cross-project module dependencies:
- Touch
lib/Hello.cppm→ only consumers ofHellorebuild. - Touch
lib/Other.cppm→ only consumers ofOtherrebuild. - External CMake dep produces fresh
.afiles → whole project rebuilds (deliberately coarse — cmake-dep changes are rare).
Diamond deps (A → {B, C}; B → X; C → X) build X exactly once via a std::shared_future<BuildResult> cache.
Architecture
- Modules:
Crafter.Build:Shader/:Platform/:Interface/:Implementation/:External/:Clangpartitions, re-exported by theCrafter.Buildumbrella. - Build process: parallel — interface PCMs, implementation
.ofiles, external dep clones, dep-config recursive builds, and shader compilations all spawn threads, sync at well-defined join points. LoadProjectcompilesproject.cppto<project>/build/project.soanddlopens it. The host exe is linked with-Wl,--export-dynamicso the project's undefined symbols resolve against the running binary.
Compatibility / portability
- Linux: x86_64. Tested on Arch in a clean container.
- Windows: bootstrap works (build.cmd produces working exe); the
LoadProjectpath needs the in-progress DLL+launcher refactor to support cross-EXE/DLL symbol resolution.
The CRAFTER_BUILD_MARCH and CRAFTER_BUILD_MTUNE env vars override the default -march=native / -mtune=native for distro CI builds:
CRAFTER_BUILD_MARCH=x86-64-v3 CRAFTER_BUILD_MTUNE=generic ./build.sh
Roadmap
- Windows DLL+launcher refactor (in progress)
- Configuration inheritance (
extends:-style merging from base configs) - Test runner (dlopen-based test loading)
- Target-triple auto-detection (
clang -print-target-triple) - Prefer system glslang when available, fall back to git+cmake
- Order-preserving deduplication of
BuildResult.libs(currentunordered_setdoesn't preserve link order — only matters for cyclic static libs)
License
LGPL-3.0-only. See LICENSE.