This commit is contained in:
parent
7abbdf503b
commit
c6b0bbaf25
1 changed files with 11 additions and 11 deletions
22
README.md
22
README.md
|
|
@ -1,6 +1,6 @@
|
|||
# Crafter Build
|
||||
|
||||
A C++26 modules build system. Project descriptions are written in C++ with 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.
|
||||
A C++26 modules build system. Project descriptions are written in C++ with 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.
|
||||
|
||||
## Install
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
|
|||
}
|
||||
```
|
||||
|
||||
Run `crafter-build` in that directory; outputs land at `bin/myapp-<target>-<march>-<mtune>-<hash>/myapp` and intermediates at `build/myapp-<target>-<march>-<mtune>-<hash>/`. The hash covers everything that changes what is compiled (build type, defines, flags, sysroot, the compiler itself), so two such builds never share a directory. After a compiler update, a new crafter-build release included, the first build starts a fresh directory and recompiles; `crafter-build clean` removes the ones left behind. Add `-r` to run the produced executable straight after a successful build.
|
||||
Run `crafter-build` in that directory, outputs land at `bin/myapp-<target>-<march>-<mtune>-<hash>/myapp` and intermediates at `build/myapp-<target>-<march>-<mtune>-<hash>/`. The hash covers everything that changes what is compiled (build type, defines, flags, sysroot, the compiler itself), so two such builds never share a directory. After a compiler update, a new crafter-build release included, the first build starts a fresh directory and recompiles. `crafter-build clean` removes the ones left behind. Add `-r` to run the produced executable straight after a successful build.
|
||||
|
||||
## Dependencies
|
||||
|
||||
|
|
@ -75,7 +75,7 @@ Configuration* sibling = LocalProject({ .projectFile = "../Crafter.Math/project.
|
|||
cfg.dependencies = { math };
|
||||
```
|
||||
|
||||
Clones share the external cache and identical specs share one `Configuration`. An unpinned source is `git pull`ed on every invocation; set `.commit` for reproducible, offline-after-first-fetch builds.
|
||||
Clones share the external cache and identical specs share one `Configuration`. An unpinned source is `git pull`ed on every invocation, set `.commit` for reproducible, offline-after-first-fetch builds.
|
||||
|
||||
A project loaded this way runs its `CrafterBuildProject` as a dependency, and `IsTopLevelProject()` is `false` while it does (`true` in the project crafter-build was pointed at, and outside any project file). Wire test-only dependencies behind it: a library whose test links crafter-build itself would otherwise load crafter-build's `project.cpp` from inside crafter-build and recurse.
|
||||
|
||||
|
|
@ -112,7 +112,7 @@ Test conventions:
|
|||
|
||||
- **Exit codes**: `0` = ✅ pass, nonzero = ❌ fail, **`77`** = ⏭ skipped (autoconf convention; use it for runtime opt-outs like "tool not on PATH").
|
||||
- Each test runs in its own subprocess — a segfault doesn't take the runner down.
|
||||
- Default timeout is 60 s per test; runs in parallel up to `hardware_concurrency()`.
|
||||
- Default timeout is 60 s per test, runs in parallel up to `hardware_concurrency()`.
|
||||
- Stdout+stderr is captured and printed atomically on completion. Failed/crashed/timed-out tests also write to `build/test-logs/<name>.log`.
|
||||
- A broken fixture `project.cpp` is reported as a single failed test, not a fatal error — other tests still run.
|
||||
|
||||
|
|
@ -130,7 +130,7 @@ crafter-build test --timeout=5 # override per-t
|
|||
|
||||
Outcomes: ✅ pass, ❌ fail, 💥 crash (with signal name), ⏱ timeout, ⏭ skipped. The runner exits 0 only if every non-skipped test passed.
|
||||
|
||||
`--target=` filters the run to tests whose `cfg.target` matches; default is the host triple. Per-target tests parse `--target=...` from their own args span and build for it; tests with hardcoded targets (e.g. host-only meta-tests) are filtered out under non-matching `--target=`.
|
||||
`--target=` filters the run to tests whose `cfg.target` matches, default is the host triple. Per-target tests parse `--target=...` from their own args span and build for it, tests with hardcoded targets (e.g. host-only meta-tests) are filtered out under non-matching `--target=`.
|
||||
|
||||
### Cross-target test runners
|
||||
|
||||
|
|
@ -145,9 +145,9 @@ TestRunner::Cmd("wine") // local exec via prefix co
|
|||
|
||||
For elaborate setups (rsync, ssh ControlMaster, env vars on the spawn, `QEMU_LD_PREFIX` for sysroots), construct `TestRunner` directly — `copy`/`exec`/`cleanup`/`probe` are public string fields with `{bundle}`, `{remote_bundle}`, `{bin}`, `{bin_name}`, `{args}` placeholders.
|
||||
|
||||
For cross-arch builds, set `cfg.sysroot` on the `Configuration` to point at a sysroot for the target. `BuildStdPcm` and the compile/link commands both consume it: `--sysroot=<path>` is passed to clang, and the std module is precompiled against the sysroot's libc++ headers. The module source has to come from the same libc++ release as those headers, so it is taken from `<sysroot>/usr/share/libc++/v1/std.cppm` when the sysroot ships it, else from the toolchain when both are the same major release, else from the sources crafter-build carries for other releases (`share/crafter-build/libc++-modules/<major>/`; `crafter-build --version` lists them). Alpine is the common case for the last two: its `libc++-dev` has the headers and no module source. When nothing matches, the build stops with one line naming both releases; installing that release's `share/libc++/v1` directory into the sysroot resolves it. The cheapest aarch64 sysroot is an extracted [Arch Linux ARM rootfs](http://os.archlinuxarm.org/) plus `libc++` + `libc++abi` + `gcc` installed into it via `pacman --root <rootfs> --config <alarm-pacman.conf> --dbpath <rootfs>/var/lib/pacman -Sy …` (point pacman at ALARM's mirrors via explicit config to avoid pulling host-arch packages).
|
||||
For cross-arch builds, set `cfg.sysroot` on the `Configuration` to point at a sysroot for the target. `BuildStdPcm` and the compile/link commands both consume it: `--sysroot=<path>` is passed to clang, and the std module is precompiled against the sysroot's libc++ headers. The module source has to come from the same libc++ release as those headers, so it is taken from `<sysroot>/usr/share/libc++/v1/std.cppm` when the sysroot ships it, else from the toolchain when both are the same major release, else from the sources crafter-build carries for other releases (`share/crafter-build/libc++-modules/<major>/`, `crafter-build --version` lists them). Alpine is the common case for the last two: its `libc++-dev` has the headers and no module source. When nothing matches, the build stops with one line naming both releases, installing that release's `share/libc++/v1` directory into the sysroot resolves it. The cheapest aarch64 sysroot is an extracted [Arch Linux ARM rootfs](http://os.archlinuxarm.org/) plus `libc++` + `libc++abi` + `gcc` installed into it via `pacman --root <rootfs> --config <alarm-pacman.conf> --dbpath <rootfs>/var/lib/pacman -Sy …` (point pacman at ALARM's mirrors via explicit config to avoid pulling host-arch packages).
|
||||
|
||||
For `--target=x86_64-w64-mingw32` (Windows from Linux), the pipeline cross-compiles as long as `mingw-w64` is installed. Pair it with `--runner=cmd:wine` to run the result. Only the MinGW ABI is supported; there is no MSVC target and no Windows host. `-fuse-ld=lld` is auto-injected on every link, and `-lstdc++exp` is auto-linked for the mingw target so user projects don't need to declare those flags.
|
||||
For `--target=x86_64-w64-mingw32` (Windows from Linux), the pipeline cross-compiles as long as `mingw-w64` is installed. Pair it with `--runner=cmd:wine` to run the result. Only the MinGW ABI is supported, there is no MSVC target and no Windows host. `-fuse-ld=lld` is auto-injected on every link, and `-lstdc++exp` is auto-linked for the mingw target so user projects don't need to declare those flags.
|
||||
|
||||
### Configuring runners
|
||||
|
||||
|
|
@ -176,7 +176,7 @@ local
|
|||
cmd:<command> # cmd:wine, cmd:qemu-aarch64, cmd:valgrind, ...
|
||||
```
|
||||
|
||||
Each runner carries a `probe` command (e.g. `which wine`); if it fails, every test using that runner is reported as ⏭ skipped and the rest of the suite continues. Skipped tests count toward the summary but don't fail the suite (`AllPassed()` returns true on skip-only runs).
|
||||
Each runner carries a `probe` command (e.g. `which wine`), if it fails, every test using that runner is reported as ⏭ skipped and the rest of the suite continues. Skipped tests count toward the summary but don't fail the suite (`AllPassed()` returns true on skip-only runs).
|
||||
|
||||
### Linking the parent project's library
|
||||
|
||||
|
|
@ -203,7 +203,7 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view>)
|
|||
|
||||
## Linting & formatting
|
||||
|
||||
Lint rules are C++ in `project.cpp` with no `.lintrc`, no YAML, and no built-in rules. A rule is a named callback that runs once per source file; register it on the `Configuration` before returning:
|
||||
Lint rules are C++ in `project.cpp` with no `.lintrc`, no YAML, and no built-in rules. A rule is a named callback that runs once per source file, register it on the `Configuration` before returning:
|
||||
|
||||
```cpp
|
||||
cfg.AddLintRule("no-tabs", [](Crafter::LintContext& ctx) {
|
||||
|
|
@ -244,7 +244,7 @@ Lint findings print compiler-style — `file:line: warning: message [rule]` —
|
|||
| `Extension()` | file extension, e.g. `".cppm"` |
|
||||
| `CommentStripped()` | `content` with comments and string/char literal bodies blanked to spaces — newlines kept, so line numbers stay valid (raw strings not recognized yet) |
|
||||
| `Report(line, msg)` | record a finding (`line == 0` for whole-file) |
|
||||
| `SetContent(str)` | replace the file's content — makes the rule a transform; `lines` re-splits, `CommentStripped()` re-derives. Build the new string, call once at the end; don't assign `content` directly |
|
||||
| `SetContent(str)` | replace the file's content — makes the rule a transform, `lines` re-splits, `CommentStripped()` re-derives. Build the new string, call once at the end, don't assign `content` directly |
|
||||
|
||||
Suppression is comment-based :
|
||||
|
||||
|
|
@ -275,7 +275,7 @@ This project is licensed under the GNU Lesser General Public License, version 3
|
|||
|
||||
The example code under [examples/](examples/) is licensed under the MIT license (see [examples/LICENSE](examples/LICENSE)), so it can be freely copied into your own projects.
|
||||
|
||||
The [lib/](lib/) directory contains vendored third-party code (nlohmann/json, glslang's DirStackFileIncluder) that remains under its own permissive licenses; see the headers of those files.
|
||||
The [lib/](lib/) directory contains vendored third-party code (nlohmann/json, glslang's DirStackFileIncluder) that remains under its own permissive licenses, see the headers of those files.
|
||||
|
||||
## Copyright
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue