Build & test
Everything runs through just.
just --list shows the recipes.
just # build + check, the defaultjust build # build the crates and regenerate the C headersjust test # every suite: Rust, C++, Pythonjust check # every linter CI runsjust format # format everythingWhat you need
Section titled “What you need”| For | Tool |
|---|---|
| The crates and the C headers | Rust (stable + nightly for fmt), cbindgen |
| The C++ suite | a C++17 compiler; optionally mingw-w64 for the Windows cross-build |
| The Python suite | uv (the recipe creates the venv itself) |
| The documentation site | Node and pnpm; doxygen and a nightly Rust toolchain for the reference; a Playwright browser for diagrams (pnpm --dir docs exec playwright install chromium) |
The Windows cross-build is skipped with a message if x86_64-w64-mingw32-g++ is absent, so it is optional.
The generated C artifacts
Section titled “The generated C artifacts”c/fznso_types.h and c/fznso_solver_template.c are generated by cbindgen from rust/fznso-types and rust/fznso-solver-template.
To change what a C consumer sees, change the Rust doc comment or type it comes from, then run just build.
just check-headers confirms the result still compiles as both C and C++.
The prose in each generated file’s preamble comes from the header = """…""" strings in cbindgen_types.toml and cbindgen_template.toml.
Testing against a real solver
Section titled “Testing against a real solver”The suites drive a throwaway example solver by default. Point the C++ consumer at your own library to check it against the full interface:
just test-cpp./target/test_consumer /path/to/libmysolver.soRun the Rust suite under miri before changing anything that touches handles, casts or lifetimes:
just miriThis is not optional diligence.
The interface passes handles as raw pointers that each side casts back to its own type, and every bug in that class has passed the ordinary test suite first.
rust/fznso/tests/abi.rs drives the generated entry points directly so miri covers them too; miri cannot dlopen, so tests/dylib.rs is skipped under it.
The documentation site
Section titled “The documentation site”just docs # build the site, including the generated referencejust docs-serve # live reload while writingTwo parts of the site are generated and must not be edited in place:
docs/src/content/docs/registry/comes fromdocs/registry/*.toml. Edit the TOML.docs/src/content/docs/reference/comes from the code, via rustdoc JSON, Doxygen XML and griffe. Edit the doc comment.
Both run from pnpm gen, which dev and build invoke for you; the dumps they read come from just docs-api.
Diagrams are written as Mermaid in a ```mermaid block and rendered to inline SVG at build time, so a page ships no JavaScript to draw one and the labels stay searchable text.
That needs a browser: pnpm --dir docs exec playwright install chromium, once.
Doxygen and the Python toolchain are optional: a missing one skips its pages with a warning, so the prose stays workable without them.
Writing documentation
Section titled “Writing documentation”One rule, and it is the reason the site exists:
Every fact has one home. Everywhere else links to it.
| Kind of fact | Lives in |
|---|---|
| A rule of the protocol | a page under docs/src/content/docs/spec/ |
| The contract of one function, type or field | its doc comment — the reference is generated from it, never hand-written |
| A common name | docs/registry/*.toml |
| Example code | a real file under rust/fznso/examples/, cpp/tests/ or python/tests/, imported with ?raw |
If you find yourself pasting a paragraph into a second file, that is the signal to put it in the spec and link instead.
CI enforces the links, so a reference that stops resolving is a build failure rather than a dead end for a reader: cargo doc runs with -D warnings, and scripts/check-links.mjs walks the built site checking every internal link and anchor.