Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I2d3a5a9d745630f5da305664f0bdc66e6a6a6964
9 KiB
Hacking Pinakes
Pinakes is a lot of things. It also plans to be a lot of things. One of those things, as you might have noticed, is complete. To be complete in features, to be complete in documentation and to be complete in hackability. This document covers, very comprehensively, how you may:
- Develop Pinakes
- Build Pinakes
- Contribute to Pinakes
for developers as well as:
- Distribute Pinakes
for Pinakes maintainers and packagers.
Development Environment
Pinakes uses Nix with flakes for pure, reproducible developer environments. All of the build dependencies that you need, from Rust to GTK and webkit, are provided in the devshell. In addition to not requiring system libraries, we'd like to actively discourage you from relying on system libraries.
Direnv may be preferred by some users as an alternative to nix develop.
# Enter the dev shell
$ nix develop
# Or with direnv (recommended)
$ direnv allow
Nix is the only supported way to get all dependencies. Distro package managers may work for the core crates but are not supported for the UI.
Building Pinakes
Pinakes is a Cargo workspace with multiple crates targeting multiple aspects of
the projects. The user-facing crates are provided in packages/*.
pinakes-server and pinakes-tui can be built normally with cargo while the
UI crate, i.e., pinakes-ui must be built with the Dioxus CLI (dx) so
that the SCSS stylesheets are compiled correctly. Everything else should work
perfectly fine with the standard cargo build.
To make the things a little bit more convenient, this project uses Just as its command runner and provides a few recipes for common tasks.
# Build all crates (server + TUI + UI)
$ just build-all
# Build individual components
$ just build-server # pinakes-server (HTTP API)
$ just build-tui # pinakes-tui (terminal UI)
$ just build-ui # pinakes-ui (Dioxus desktop/web UI, requires dx)
The dependencies required for the recipes, including Just itself, is provided by the Nix devshell. You may also run the cargo equivalents of those commands:
# Core crates
$ cargo build -p pinakes-server
$ cargo build -p pinakes-tui
# The UI crate must be built with dx, not cargo.
$ dx build -p pinakes-ui
Release builds follow the same pattern, but with --release. Nothing else
should be required for the most part.
Running Pinakes
For the time being, UI clients require the server to be running before they can function. Offline sync is planned, but likely come later alongside cross-device sync. All UIs connect to the server over HTTP.
# 1. Copy and edit the example config (first time only)
$ cp pinakes.example.toml pinakes.toml
# 2. Start the server (defaults to 127.0.0.1:3000)
$ cargo run -p pinakes-server -- pinakes.toml
# 3a. Run the TUI client
$ cargo run -p pinakes-tui
# 3b. Run the desktop/web UI
$ cargo run -p pinakes-ui
# Connect TUI to a non-default server address
$ cargo run -p pinakes-tui -- --server http://localhost:3000
The GUI reads PINAKES_SERVER_URL if set; otherwise it assumes
http://127.0.0.1:3000. You may change the host and port if you require.
Testing
Pinakes boasts, or well, attempts to boast a very comprehensive testing suite to
ensure no regressions are introduced while in development. For fast parallel
test execution, we use cargo-nextest and while you are recommended to use it
over cargo test both should be supported. For CI and package tests, only
cargo-nextest is supported.
# Run all workspace tests (preferred)
$ just test
# Equivalent without Just
$ cargo nextest run --workspace
# Tests for a single crate
$ cargo nextest run -p pinakes-core
$ cargo nextest run -p pinakes-server
# Run a specific test by name and show output
$ cargo test -p pinakes-core -- test_name --nocapture
Linting and Formatting
Formatting
There is a treewide formatter provided within the flake.nix that invokes the
recommended formatter tooling across the various filetypes, including database
migrations and Markdown sources. While a just fmt recipe is provided, it will
not cover most of the files. Still, you may format the Rust sources as such:
# Format all code
$ just fmt # or: cargo fmt
# Check formatting without modifying files
$ cargo fmt --check
Linting
For now lints only cover Rust sources, and are provided by the Clippy tool. You
may, as usual, invoke it with just lint.
# Run Clippy
$ just lint # or: cargo clippy --workspace
# Treat Clippy warnings as errors (used in CI)
$ cargo clippy --workspace -- -D warnings # `-D warnings` is recommended
All Clippy warnings, besides some of the really annoying ones or false
positives, must be resolved at the source. Do not use #[allow(...)] or
#[expect(...)] to silence warnings unless there is a documented reason.
It may be okay, at times, to suppress lints but you should focus on resolving
them rather than suppressing them. If suppressing, prefer #[expect] and
provide the reason parameter.
Generating API Documentation
The REST API documentation is generated from OpenAPI annotations in
pinakes-server using cargo xtask:
# Generate the API documentation at docs/api/
$ just docs # or: cargo xtask docs
This writes:
docs/api.md- Index linking all generated filesdocs/api/<tag>.md- One Markdown file per API tagdocs/api/openapi.json- Full OpenAPI 3.0 specification
Please do not edit files under docs/api/ by hand; they are regenerated on each
run.
Code Style
There are many conventions that are in play within the Pinakes codebase. You'll get used and adjust to most of them naturally, but here are some of the general conventions written down for convenience.
General
- Make illegal states unrepresentable; ıse ADTs and newtype wrappers to encode constraints in the type system. Parse and validate at system boundaries (user input, external APIs); trust internal types everywhere else.
- This one is kind of obvious, but please use the
tracingcrate with structured fields. Do not useprintln!oreprintln!in library or server code. - All storage operations must be implemented for both the SQLite and PostgreSQL backends. Neither backend is optional.
Naming is kind of obvious, but I'd like to remind you that we use snake_case
for functions and variables, PascalCase for types and traits,
SCREAMING_SNAKE_CASE for constants. There is not much else to it.
Error Handling
-
unwrap()andexpect()are banned in non-test code. Use?,ok_or_else,match, orif letinstead.- The only accepted exception is when a failure is truly impossible (e.g., a
hardcoded regex that is known-valid at compile time). In that case, add
#[expect(clippy::expect_used, reason = "...")]with a clear explanation.
- The only accepted exception is when a failure is truly impossible (e.g., a
hardcoded regex that is known-valid at compile time). In that case, add
-
Wrap storage errors with the
db_ctxhelper so error messages include the operation and relevant ID:stmt.execute(params).map_err(db_ctx("insert_media", &media_id))?;
Disallowed Types and APIs
Pinakes uses hashers from rustc_hash, i.e., FxHashMap and FxHashSet over
the std equivalents due to their faster nature without random state. This is
enforced by clippy.
As Pinakes uses Rust 1.90+, the now-deprecated once_cell crate is also banned.
Use the stdlib equivalents:
once_cell::unsync::OnceCell->std::cell::OnceCellonce_cell::sync::OnceCell->std::sync::OnceLockonce_cell::unsync::Lazy->std::cell::LazyCellonce_cell::sync::Lazy->std::sync::LazyLock
Contributing
- Fork the repository and create a feature branch.
- Enter the dev shell:
nix develop(ordirenv allow). - Make your changes; keep commits focused and descriptive. We use scoped commits.
- Run
just fmtandjust lintbefore opening a pull request. - Run
just testto verify nothing is broken. - Open a pull request against
main. Describe what you changed and why.
For significant changes, consider opening an issue first to discuss the approach.
Packaging and Distribution
Pinakes is released under EUPL v1.2. For distributable release builds, please
respect the license. You may create release builds for the user-facing crates
using cargo build --release for server and TUI components. Use dx instead
for the GUI:
# Server
$ cargo build --release -p pinakes-server
# TUI
$ cargo build --release -p pinakes-tui
# GUI
$ dx build --release -p pinakes-ui
The resulting binaries are self-contained except for system libraries required
by the UI (GTK3, libsoup, webkit2gtk). Database migrations run automatically on
first server startup via the refinery crate; no manual migration step is
needed.
SQLite (the default backend) requires no external database server. PostgreSQL
deployments need the pg_trgm extension enabled on the target database. It is
generally advisable to document those requirements for the users, even though
they do not require additional packaging steps.