feat(ci): build and publish the Flatpak

One job replaces the tarball pipeline. On a pull request it builds the
Flatpak with flatpak-builder, which runs the bootstrap, the self-host
build and the test suite inside the sandbox, and smoke-tests the result
with `crafter-build --version` and the hello-world example. On a push to
master it also bundles the build, promotes it into the live repo as the
`beta` branch and refreshes the rolling `latest` prerelease. On a tag
X.Y.Z it promotes as `stable`, refuses to proceed unless the metainfo's
newest release is that version, and publishes a release whose notes are
that entry's description.

The job runs on the `flatpak` runner label: the privileged forgejo-runner
on the Hetzner box, with the Sdk install, the flatpak-builder state and
the repo's web root as volumes. The state volume is what keeps a normal
run short: flatpak-builder rebuilds the LLVM and WASI modules only when
their sources change. The live repo is canonical and never built into;
builds are promoted with build-commit-from, then build-update-repo signs
and prunes it and generates static deltas.

The musl job, the per-march tarballs, the toolchain packages the runner
used to install and the PKGBUILD go: the Flatpak is the one artifact.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Jorijn van der Graaf 2026-10-02 22:36:55 +02:00
commit ccfd607870
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
3 changed files with 152 additions and 225 deletions

View file

@ -17,7 +17,7 @@ crafter-build is a C++26 modules build system. A project is described by a `proj
| `wasi-runtime/` | `runtime.js` and `index.html.in`, shipped for browser targets. |
| `lib/` | Vendored third-party headers (nlohmann/json, glslang's DirStackFileIncluder). Do not edit. |
| `share/crafter-build/`, `bin/`, `build/`, `lib/*.a` | Generated and gitignored. |
| `PKGBUILD`, `.forgejo/workflows/ci.yaml` | Arch package and the Forgejo Actions CI. |
| `packaging/flatpak/`, `.forgejo/workflows/ci.yaml` | The Flatpak manifest with its metainfo, repo and ref files, and the Forgejo Actions CI that builds and publishes it. |
Partitions and what they own:
@ -116,7 +116,7 @@ These were decided deliberately. Do not undo them without the maintainer's say-s
1. **Configuration is C++.** No JSON, YAML, TOML or DSL for project, runner or lint configuration. Lint rules are C++ callbacks; there is no clang-format or clang-tidy integration and no separate "format rule" type. A rule that calls `ctx.SetContent` is a transform, and that is the whole mechanism.
2. **Per-project artifacts go in `<project>/build` and `<project>/bin`. Only cross-project things go in `~/.cache/crafter.build/`:** the std PCM and Crafter.Build PCMs per target and march, and external clones keyed by their spec hash.
3. **Sources ship, PCMs build locally.** Packages install `*.cppm` under `share/crafter-build/`. Nothing target-baked ships except the executable itself, and that defaults to a portable `-march` in `PKGBUILD` and CI.
3. **Sources ship, PCMs build locally.** The Flatpak installs `*.cppm` under `/app/share/crafter-build/`. Nothing target-baked ships except the executable and the toolchain beside it, and the executable is built for a portable `-march`.
4. **crafter-build is a monolithic executable with `--export-dynamic`.** `project.so` resolves `Crafter::` symbols from the running binary at dlopen time. There is no Windows host; `x86_64-w64-mingw32` is a cross-compile target for user projects only.
5. **Convention over configuration.** Lead with the zero-config path. Env var, then CLI flag, then full `project.cpp` are the escalation, not the entry point.
6. **One skip mechanism** (runner probe plus exit 77) and **one `TestRunner` shape** (template fields with `{bin}`, `{bundle}` and similar placeholders; `Cmd(...)` covers any wrapper). Do not add a special-case runner class or a hardcoded "skip on this OS" branch.
@ -131,9 +131,9 @@ These were decided deliberately. Do not undo them without the maintainer's say-s
- Subject line: `type(scope): imperative summary`, with `feat`, `fix`, `refactor`, `docs` or `test`; the scope (`lint`, `incremental`, ...) is optional. Body: what was wrong, why, and why the fix has this shape, wrapped at about 75 columns. Read `git log` for the voice.
- Separate commits for the fix, its test and the README change when each stands on its own.
- Update the README in the same PR when user-visible behaviour changes.
- `format`, `lint` and `test` green before pushing. CI runs the bootstrap and the test suite on PRs and publishes release archives on `master`.
- `format`, `lint` and `test` green before pushing. CI builds the Flatpak and runs the suite inside it on PRs, publishes the `beta` branch of the Catcrafts repo on every push to `master`, and the `stable` branch plus a release on an `X.Y.Z` tag.
- Never commit `bin/`, `build/`, `share/crafter-build/`, `lib/*.a` or fixture build outputs (all gitignored), and never edit the vendored files in `lib/`.
- Keep CI's `CRAFTER_BUILD_MARCH=x86-64-v2` for the bootstrap: the runner's CPU cannot execute v3 code, and the v3 and v4 artifacts are produced from the v2 binary.
- Keep `CRAFTER_BUILD_MARCH=x86-64-v2` in the manifest for the shipped binary: it has to run on any x86_64 machine. User projects still default to `native`.
## Security considerations
@ -141,9 +141,15 @@ These were decided deliberately. Do not undo them without the maintainer's say-s
- Unpinned dependencies `git pull` on every invocation, so builds and tests need network. A missing or private repo surfaces as a clone failure before anything is built.
- `--runner=cmd:<command>` executes the named wrapper around every test binary. Wrappers come from `--runner=` or `CRAFTER_BUILD_RUNNER_*`; do not hardcode hosts, wrappers or credentials in `project.cpp` or tests.
- The linter loads `libclang` at runtime by name. `CRAFTER_BUILD_LIBCLANG` pins an exact path and must fail loudly when wrong, never fall back to another library.
- CI force-pushes the rolling `latest` tag and publishes a prerelease on every push to `master` with the workflow's own token. Do not add secrets to the workflow or echo the environment in it.
- CI force-pushes the rolling `latest` tag and publishes a prerelease on every push to `master` with the workflow's own token, and signs the Flatpak repo with the key in the `FLATPAK_GPG_PRIVATE_KEY` secret. Do not add other secrets to the workflow or echo the environment in it.
- Tests write only under `fs::temp_directory_path()` and the fixture's own `bin/` and `build/`. Keep it that way.
## CI and publishing
`.forgejo/workflows/ci.yaml` runs on the `flatpak` runner label: the privileged forgejo-runner on the Hetzner box (bubblewrap inside the job container needs it). Its `config.yml` allows three volumes, which the workflow mounts: `crafter-flatpak-system` (the Sdk install at `/var/lib/flatpak`), `crafter-flatpak-builder` (flatpak-builder state and ccache, so the LLVM and WASI modules are rebuilt only when their sources change) and `/var/www/flatpak`, the web root Caddy serves as `https://flatpak.catcrafts.net/`. Secrets: `FLATPAK_GPG_PRIVATE_KEY` (armored, no passphrase) and `FLATPAK_GPG_KEY_ID`. The live repo at `/var/www/flatpak/repo` is canonical; builds are promoted into it with `flatpak build-commit-from`, never built into it.
To release: add a `<release>` to `packaging/flatpak/net.catcrafts.CrafterBuild.metainfo.xml` (its description becomes the release notes), merge, and push the tag `X.Y.Z`. CI refuses a tag that does not match the newest entry. Local builds: `flatpak-builder --user --force-clean --ccache --repo=repo build-dir packaging/flatpak/net.catcrafts.CrafterBuild.yaml`, then `flatpak-builder --run build-dir <manifest> crafter-build --version`.
## Gotchas
- A `#pragma crafter dependency` target is rebuilt on every load on purpose: clang crashes rather than erroring when it imports a BMI whose own imported PCM was rebuilt underneath it, and the depfile check is what catches a regenerated host cache. Do not optimise the rebuild away.