docs: scripts in the README

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Jorijn van der Graaf 2026-10-06 18:29:32 +02:00
commit 631c60892a
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB

View file

@ -256,6 +256,50 @@ Suppression is comment-based :
// lint-disable-file — all rules, whole file
```
## Scripts
A project has chores that are not part of building it: packaging, installing, publishing, generating documentation. A script gives one a name. Scripts are C++ in `project.cpp` like everything else, a name and a callback pushed onto `cfg.scripts` before returning the `Configuration`:
```cpp
cfg.scripts.push_back({"docs", [](Crafter::ScriptContext& ctx) {
return ctx.Run("python3 tools/gen-docs.py");
}});
```
```bash
crafter-build run # list the project's scripts
crafter-build run docs # run one
crafter-build run docs --open api # everything after the name is handed to the script
```
The callback returns the exit status, and `crafter-build run` exits with it. `ctx.Run(command)` runs a shell command with the terminal attached, so its output appears as it is produced and it can ask questions, and returns the command's exit code (128 plus the signal number when it was killed). A script with several steps stops at the first failure by returning that code:
```cpp
cfg.scripts.push_back({"package", [](Crafter::ScriptContext& ctx) {
const std::string dist = Crafter::ShellQuote((ctx.projectDir / "dist").string());
if (const std::int32_t code = ctx.Run(std::format("mkdir -p {}", dist)); code != 0) return code;
return ctx.Run(std::format("tar -czf {}/myapp.tar.gz -C {} .", dist, Crafter::ShellQuote(ctx.project.BinDir().string())));
}});
```
`ScriptContext` gives each script:
| Member | Meaning |
|---|---|
| `project` | the `Configuration` that `project.cpp` returned. Read the project through this; a `cfg` captured by reference is gone by the time a script runs |
| `projectDir` | absolute directory of the project file |
| `args` | everything typed after the script's name, untouched |
| `Run(command)` | run a shell command with the terminal attached and return its exit code |
Things to know:
- Everything after the script's name belongs to the script, including what looks like an option of crafter-build (`--help`, `-v`). Options and project args for crafter-build go before the name: `crafter-build --debug run package`.
- `run` loads `project.cpp` but does not build the project. A script that needs the build output runs after `crafter-build`.
- Commands run in the directory crafter-build was started from. Build paths from `ctx.projectDir`, and quote anything that is not a literal with `Crafter::ShellQuote`.
- Only the scripts of the project you run crafter-build in are available. A dependency's scripts are not.
- Inside the Flatpak, commands run in its sandbox: the host's files are visible, but the tools on `PATH` are the ones the Flatpak ships (`git`, `cmake`, `python3`, `tar`, ...), not everything installed on the host.
- A return value outside 0 to 255 is not an exit status and becomes 1. So does a script that throws, with the exception's message printed.
## Examples
[examples/](examples/) contains progressively larger self-contained projects: