Developer Environment¶
The repository includes a portable POSIX setup script for Linux and BSD development hosts:
It installs the toolchain and development packages needed for the current Meson build, then creates or reconfigures the default build directory.
Supported Hosts¶
Merovingian is C++26, so the host needs a recent toolchain: Clang ≥ 18
(-std=c++26) or GCC ≥ 14 with the matching C++ standard library, plus
Meson ≥ 1.1.0. Older distributions ship a pre-C++26 compiler and cannot build
the project — see platform-support.md for the minimum
version of each Tier 1 platform and for the static-tarball path for older hosts.
The script supports these package managers:
- Linux:
apt,dnf,zypper,pacman,apk - BSD:
pkg,pkg_add,pkgin
The package set intentionally avoids Boost. It installs C++ toolchains, Meson,
Ninja, pkg-config/pkgconf, Git, Python, Perl, Bison, Flex, M4, OpenSSL,
LibSodium, PostgreSQL client headers, Catch2, clang tooling, and cppcheck.
Merovingian pins libcurl, SQLite, Catch2, and yyjson through committed Meson
wraps so the default build path does not depend on host copies of those
libraries. OpenSSL, LibSodium, and PostgreSQL libpq are resolved from
operating-system packages so security updates can be delivered by the host
package manager. yyjson is used for strict JSON
parsing; its wrap exposes third-party headers as system includes so
warning-as-error still applies to project code rather than inline C headers
from the dependency checkout. C++ sources include a project-owned adapter rather
than yyjson.h directly.
CI runs clang-tidy on changed C++ translation units with parallel per-file log
groups and timeouts. Headers are analyzed transitively through the Meson compile
database instead of being invoked as standalone clang-tidy inputs.
The unsafe source gate is a Bash script and must be run with bash, matching
CI, because it relies on strict shell options and portable grep behavior.
OpenSSL is the selected TLS provider and is intentionally resolved through the
host package manager. The Linux, BSD, and WSL wrappers still run Meson with
--wrap-mode=forcefallback by default, but the top-level OpenSSL, LibSodium,
and libpq dependencies disallow fallback so clean builds use OS-provided shared
libraries for those boundaries.
Dependency reviews live under docs/dependencies/; new direct dependencies
should add or update a review there before they are wired into Meson.
Line Endings¶
.gitattributes checks out every text file with LF on every platform
(* text=auto eol=lf), whatever core.autocrlf is set to; only the
Windows-only *.ps1 and *.cmd scripts check out with CRLF (ADR-0158). The
tree is built and tested under Linux, WSL or a BSD, tests read unit files,
scripts and sources as text, and packages ship files straight from the
checkout, so a CRLF working copy fails tests and would ship broken files.
A checkout made before 0.12.27 keeps whatever endings it was written with, since Git rewrites a file only when it changes. Re-check it out once (commit or set aside your work first):
git ls-files --eol then shows w/lf for everything but those two script
types. tests/tooling/test_line_endings.py (Meson test line-endings-tooling)
checks the attributes; it skips itself where Git cannot read the checkout, such
as a worktree created by Windows Git and opened from WSL, whose .git file
names a C:/ path.
Git Hooks¶
Install the repository Git hooks from the project root:
The pre-commit hook runs the unsafe C/C++ source gate, the Catch2 BDD style
gate, the unit/conformance test registration gate, and a staged-file check that
requires project changes to include both CHANGELOG.md and a relevant
docs/*.md update. Set MEROVINGIAN_SKIP_CHANGE_DOC_CHECK=1 only for reviewed
non-behavioural maintenance commits.
The project also carries a Codex PostToolUse hook in .codex/hooks.json.
After Codex edit/write tool calls, it runs .codex/hooks/clang_format_after_edit.py
to apply clang-format -i to edited C and C++ source files. Codex requires
project-local command hooks to be reviewed and trusted before they run.
Linux and BSD CI builds run through the same wrappers intended for local use:
Ubuntu/Debian WSL hosts should first run:
build-wsl.sh defaults to the build-wsl directory and contains NTFS-specific
workarounds. In particular, automake's depcomp bootstrap fails on
NTFS-backed filesystems; the script automatically detects a stale curl
subproject (one configured without --disable-dependency-tracking) and wipes
it so Meson re-configures curl with the correct options. It also compares the
extracted subprojects/curl-<version>/meson.build against the committed curl
packagefile and re-extracts curl when that copy is stale, including after
--clean wipes the build directory. Pass --clean to wipe the entire build
directory and start from scratch. The wrapper also stages an executable
make shim under the WSL home directory cache before Meson setup so
external_project builds do not depend on executable-bit metadata for scripts
stored under /mnt/c. The staged shim is rewritten with LF line endings so
its #!/bin/sh shebang remains executable on Linux.
From Windows, use the launcher wrappers instead of typing the full wsl.exe
command manually:
build-wsl.cmd forwards its arguments to scripts/build-wsl.ps1, and the
PowerShell bridge enters the default WSL distro unless -Distro is provided,
then runs sh ./scripts/build-wsl.sh from the repository root. This keeps
Windows-triggered builds on the same WSL build path as an interactive Linux
shell.
The WSL setup script installs the compiler, linker, Perl/Bison/Flex toolchain needed by the wrapped third-party sources, OpenSSL, LibSodium, PostgreSQL client headers, Catch2, clang tooling, cppcheck, and a current Meson/Ninja virtual environment. The virtual environment is used because the distribution Meson in Ubuntu 24.04 is too old to configure the repo's C++26 build.
Project-owned headers should use quoted includes. Use the rewrite helper to convert angle-bracket Merovingian includes in source trees:
Check mode reports pending rewrites without modifying files:
Common Usage¶
Preview the commands without installing packages:
Install packages and configure the default build directory:
Check an existing machine without installing packages:
Use a dedicated build directory:
Skip package installation when dependencies are already managed externally:
Unified Build Script¶
build.py is the single entry point for configuring, compiling, and testing
on every supported platform. It delegates to the shell scripts in scripts/
and handles Meson setup, compilation, and testing in one step.
# Linux
python build.py linux
# BSD
python build.py bsd
# WSL (Windows Subsystem for Linux)
python build.py wsl
# AddressSanitizer + UBSan through WSL
python build.py wsl --builddir build-asan --buildtype debug --sanitize address,undefined
# ThreadSanitizer through WSL
python build.py wsl --builddir build-tsan --buildtype debug --sanitize thread
Packaging targets¶
python build.py deb # Ubuntu/Debian .deb package
python build.py rpm # Fedora RPM package
python build.py pkg # FreeBSD .pkg package
python build.py static # Portable static Linux tarball (musl)
CI also produces distro-specific packages via dedicated scripts:
sh scripts/build-deb.sh # Debian .deb (also used by deb CI job)
sh scripts/build-rhel-rpm.sh # RHEL-compatible RPM (AlmaLinux 10)
sh scripts/build-opensuse-rpm.sh # OpenSUSE Tumbleweed RPM
sh scripts/build-openbsd-pkg.sh # OpenBSD .tgz (run inside OpenBSD)
sh scripts/build-netbsd-pkg.sh # NetBSD .tgz (run inside NetBSD)
Common development options¶
# Change build directory
python build.py linux --builddir build-dev
# Use a hardened build profile
python build.py linux --profile hardened
# Use the named sanitizer profile on WSL (defaults to address,undefined)
python build.py wsl --profile sanitizer
# Skip tests (compile only)
python build.py wsl --no-tests
# Wipe and reconfigure (WSL only)
python build.py wsl --clean
# Specify WSL distro
python build.py wsl --distro Ubuntu-24.04
# Preview commands without running them
python build.py linux --dry-run
# Set up build directory without compiling
python build.py linux --setup-only
# Compile without running tests or reconfiguring
python build.py linux --compile-only
Run python build.py --help for the full option reference.
build.py internally calls the shell scripts described in the Build Wrappers
section below. Use the scripts directly only when you need script-specific
options not exposed by build.py.
Build Wrappers¶
Use the Linux wrapper on a native Linux shell to configure, compile, and test with a C++26-capable Clang toolchain:
The default build directory is build, with CC=clang and CXX=clang++.
Override these when using another compiler:
Inside WSL use the dedicated WSL wrapper instead:
The default build directory is build-wsl. The script automatically handles
NTFS-specific issues (see the WSL section above). Pass --clean to force a
full rebuild.
From Windows, .\build-wsl.cmd is the matching entrypoint; it forwards all
arguments to scripts/build-wsl.ps1, which then invokes scripts/build-wsl.sh
inside the default WSL distro unless -Distro overrides it.
All wrappers use Meson forcefallback mode by default so pinned dependency
wraps are used even when the host has system copies installed. OpenSSL,
LibSodium, and PostgreSQL libpq are excluded from that policy and are resolved
from the host package manager.
Meson launches repository shell-based source gates through sh, which keeps
WSL builds on /mnt/c independent of Windows executable-bit metadata.
Smoke tests that need Unix file modes create their fixtures under the WSL
temporary directory instead of the Windows-mounted build directory. This keeps
the server's permission checks enabled while avoiding DrvFS chmod failures.
The smoke-test Meson file keeps shared shell fragments as single expressions so
the Ubuntu 24.04 Meson parser accepts them during setup.
Clang 22 builds keep -Werror enabled, but suppress the narrow
-Wc2y-extensions diagnostic because Catch2 uses __COUNTER__ for test
registration.
BSD Notes¶
FreeBSD and HardenedBSD use pkg. OpenBSD uses pkg_add. NetBSD prefers pkgin and falls back to pkg_add when available.
Some BSD releases ship a base compiler and package LLVM separately. The script installs the packaged LLVM toolchain so clang-format, clang-tidy, and current C++ compiler support are available for project checks.
scripts/reject-unsafe.sh (the pre-commit safety gate) needs a grep with -P. The BSD base greps
have none, so install GNU grep (the ggrep package; the gate uses it automatically). Without it the
gate refuses to run rather than passing without checking. Its tooling test skips its rule tests on
such a host and checks only that refusal.
FreeBSD, OpenBSD, and NetBSD are Tier 1 platforms: each builds and runs the full
test suite per pull request through scripts/build-bsd.sh on a CI VM. See
platform-support.md for the full support-tier matrix and
per-platform hardening posture.
Documentation Site¶
The documents under docs/ are published to
https://omg-software.github.io/Merovingian/ with MkDocs Material, configured
by mkdocs.yml in the repository root. The
documentation site workflow
builds the site with --strict on every branch and pull request and keeps the
rendered site as the docs-site workflow artifact; only main deploys to
Pages (ADR-0101).
To build and preview it locally with the same pinned dependencies CI uses:
python3 -m venv .venv-docs
.venv-docs/bin/pip install --require-hashes -r requirements-docs.txt
.venv-docs/bin/mkdocs build --strict
.venv-docs/bin/mkdocs serve
What the site expects of each branch:
- A new document needs a navigation entry. Add it to
navinmkdocs.yml, or toexclude_docsif it should not be published.docs-site-tooling(tests/tooling/test_docs_site.py) fails otherwise. ADR records are the exception: the ADR index links to them. - Link as you would in the repository. Relative links to files outside
docs/(source, workflows, moduleAGENTS.mdfiles) are rewritten byscripts/mkdocs_site_hooks.pyto open on GitHub at the commit the site was built from. Links intodocs/matrix-v1.19-spec/open the same section of the published v1.19 specification. - Broken links fail the build. A link whose target exists nowhere in the repository, or an anchor that does not exist on the target page, is a strict-mode warning.
- The changelog page is
CHANGELOG.md. The hooks publish it aschangelog.md; do not create that file underdocs/. - The theme is OMG Software's. Colours live in
docs/stylesheets/extra.css, not in Material palette names; the site opens dark, uses system fonts and loads nothing from another origin (ADR-0223).docs-site-toolingchecks text and link contrast in both schemes.
Security Posture¶
The setup script does not weaken local package manager policy, add third-party package repositories, install Boost, or fetch dependencies outside the operating-system package manager. Use --dry-run during review to record the exact package command before running it on hardened hosts.