Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Development workflow

The backlog is the sole list of future work and priorities. Issues, claims, and local PR reviews live in the shared issues.db; the repository’s AGENTS.md and CLAUDE.md describe the worktree and review workflow.

Keep implementation, reference contracts, and executable lessons aligned in the same change. Reference pages describe implemented rules and current limits; proposals preserve rationale and alternatives. Update a backlog entry when its scope changes or completes.

Native tests should check observable output, diagnostics, evaluation order, mutation, ownership, borrowing, and cleanup. Allocating operations need failure and partial-state cleanup checks. Use runtime allocation checks, sanitizers, or Miri where they exercise the changed boundary.

Build a complete local installation

Install just, a stable Rust toolchain with its rust-docs component, Python 3, and the native C compiler/linker and binutils (readelf, nm). From the checkout, run:

just                       # same as just build
just compiler              # compiler only
just engine dataframe      # one engine and its redistribution notices
just engine linalg
just test                  # all-features workspace suite, with a timing report
just docs                  # all three published-site checks
just ci                    # every CI check this Linux host can run
just release-check         # just ci plus the Docker release build and smoke tests
just --list

The default builds release plenty and plentyc, then both optional engines, sequentially. The first engine builds are substantial; later builds reuse Cargo artifacts. target/release/ contains both executables, both shared libraries, and dataframe-notices/ and linalg-notices/ with checksummed metadata and licenses for --dist. Missing Rust license files can be installed with rustup component add rust-docs. Cargo may need network access for uncached registry and pinned Git dependencies.

Add the checkout’s absolute target/release directory to PATH. For Fish:

fish_add_path /path/to/plenty/target/release

Rebuilding updates this installation; moving the checkout or cleaning its artifacts removes it. The compiler recipe explicitly selects target/, even if CARGO_TARGET_DIR is set. Engine caches stay under target/dataframe-engine/ and target/linalg-engine/. Ordinary cargo build --release --locked remains compiler-only. Existing Python engine build commands remain available for custom output paths and job counts.

These are local host builds, not portable release archives. Local packaging retains architecture, SONAME, export, dependency and RPATH checks, but records the engine’s actual required glibc symbol version rather than enforcing 2.28. It also records build_kind: local and whether the checkout is dirty (excluding the shared issues register); this is provenance information, not an attestation. The compiler and generated applications can require newer system libraries too. The explicit --local-install packaging mode does not produce release archives or enforce the portable GLIBC/GCC symbol baseline; the release workflow never selects it. Portable releases keep the strict baseline audit.

Do not run builds against the same installation concurrently. Recipes stop on the first failure; the complete installation is not a transaction. Notices are staged before replacement, and --dist rejects a library whose checksum no longer matches its notices after an interrupted update. Re-run the recipe to repair that installation. Existing notices directories are generated output and are replaced, not a location for application-owned files.

Check timing reports

just test runs the full all-features workspace suite, including doctests, with compilation timed separately from test execution. It saves Markdown and JSON reports under a unique target/check-timings/suite-*/ directory, and retains Cargo’s compilation HTML under target/cargo-timings/. Output stays on the terminal and the command returns failure if compilation or tests fail. The execution phase includes Cargo’s short freshness check and doctest builds; this is command-level attribution, not a compiler or per-test profiler.

python3 -B scripts/check_timings.py suite --profile ci
python3 -B scripts/check_timings.py report target/check-timings/suite-CURRENT/timings.json --baseline target/check-timings/suite-EARLIER/timings.json

just ci also saves each invocation’s step logs, steps.tsv, timings.json and timings.md under target/local-ci/run-*/, including failed steps. It continues to later checks after a failure. These timings use Bash’s integer seconds; the suite wrapper uses a monotonic clock. Both add negligible work at command boundaries and retain separate runs for comparison.

Direct cargo test remains available but does not save this report. Reports record the revision and host; note profile, cache warmth, competing builds, Cargo lock waits and environment overrides when comparing runs. JSON command rows record the suite’s actual Cargo arguments. Measurements observe ordinary work; they do not reserve the host or claim controlled benchmark results.

For CI job/step reports and historical comparisons, see CI timings.

Dependency versions

The compiler executable and runtime share a tracked workspace Cargo.lock. Fresh checkouts and worktrees use its dependency versions; CI builds and tests with --locked so a stale or missing lockfile fails instead of resolving a new dependency graph. Use --locked for local validation too.

Update dependencies deliberately with cargo update, and review the resulting lockfile changes with the code that needs them. Changes to Cranelift or regalloc2 require rerunning object-identity checks and affected compiler performance comparisons. Record the dependency versions with benchmark results so results from different dependency graphs are not compared as if only Plenty changed.

Build profiles and CI

The workspace uses Rust edition 2024. Development and release builds disable incremental compilation; workspace code keeps line tables for backtraces, while dependencies have no debug information. See CI and repository configuration for the build profiles, jobs, caches, timings, and source highlighting.

For the local CI-profile full suite, run:

cargo nextest run --locked --workspace --all-features --cargo-profile ci

Running tests

cargo test --locked --workspace --all-features runs the full suite: the tests CI runs in shards under nextest, plus the doctests. When several workers share a machine, add -j 3 -- --test-threads=3.

CI’s lint job also gates cargo fmt --all -- --check, cargo clippy --locked --workspace --all-targets -- -D warnings, and python3 -B scripts/check_links.py book/src on stable Rust. Run them before committing.

The integration tests in tests/test_*.rs are modules of one test binary, tests/integration.rs, so all of them share one pool of test threads and the binary is linked once. Select one file’s tests by module, or one test by path:

cargo test --locked --test integration test_std_url::
cargo test --locked --test integration -- --exact test_std_url::rfc_3986_examples_split_into_components

Cargo builds separately for each set of --workspace and feature flags, so add the full suite’s flags to reuse its build. List a new test file in tests/integration.rs; every_test_file_is_listed fails until it is. Editing any test file recompiles the whole binary.

Most tests compile a Plenty program and link it with cc. The workspace’s .cargo/config.toml sets COMPILER_PATH for the processes Cargo starts, so where mold is installed as /usr/libexec/mold/ld, cc links these programs with mold, several times faster than GNU ld. Elsewhere cc uses its default linker. CI installs mold for this. The setting does not change the compiler’s default cc driver or the Rust linker; COMPILER_PATH= cargo test ... links with the system default. Tests must not depend on one linker’s output, such as the exact symbol table. The file also sets PLENTY_LINKER_THREADS=1, so each test’s link runs mold on one thread (Native linking): many links run at once, and mold’s default thread per core made them compete for the cores. PLENTY_LINKER_THREADS= cargo test ... restores mold’s default.

The same file points PLENTY_CACHE_DIR at target/plenty-cache, so every test run of a worktree reuses the compiled-function cache from the runs before it and never writes to ~/.cache. A fresh cache must not change any result: PLENTY_CACHE=off cargo test ... runs without it, and a test that exercises the cache uses a directory of its own.

cargo-nextest is optional locally; CI uses it. cargo nextest run --locked --workspace --all-features runs each test in its own process and does not run doctests (cargo test --locked --workspace --all-features --doc does). Its -j sets test threads, not build jobs: when sharing a machine, use --build-jobs 3 --test-threads 3. Because each test is its own process, a static lock in a test file no longer keeps two tests apart: tests that must not overlap belong to a test group in .config/nextest.toml, as the timing tests of tests/test_compile_scaling.rs do. --profile ci is CI’s profile: it runs every test after a failure and writes target/nextest/ci/junit.xml.

Runnable documentation

tests/test_tutorial.rs reads tutorial and design/ pages in SUMMARY.md order. A plenty fence has an output fence and runs both through compile-and-run and an explicitly compiled binary. A plenty fragment fence is an explanatory excerpt, such as a signature shape, and is not run. A plenty-error fence has an error substring and must fail without executing effects. A plenty-file companion module remains on the same page as its example. Every book page must appear in SUMMARY.md. The examples run as 16 tests in parallel, each taking every 16th example in reading order; a failure names the example’s page and line.

book/theme/plenty-highlight.js registers the highlight.js grammars for these fences: Plenty source for plenty, plenty fragment, plenty-error and plenty-file, compiler diagnostics with highlighted `code` for error, and plain text for output. book/theme/plenty-highlight.css colours the classes the bundled themes leave unstyled. Its keyword, literal, type and built-in lists follow the compiler; tests/test_book_highlighting.rs fails when a reserved word or lower-case prelude name is missing from them, or when they name one the compiler no longer defines.

Proposal fences are illustrative and are not executed by the tutorial suite. Use text fences for proposed syntax; check implemented behavior with a complete program or an executable lesson.

Before handing off changes to book content, navigation, assets, configuration, or docs build/check scripts, run the same checks as documentation CI:

python3 -B scripts/check_links.py book/src
python3 -B -m unittest discover -s scripts -p 'test_*.py'
python3 -B scripts/build_docs_site.py

Use the mdBook version pinned in book/releases.json; see Documentation site for installation. Record the complete-site result in the PR. If a check is blocked, state which one and why rather than reporting documentation validation as complete. The assembled-site check includes generated chapter and print links and version navigation; a plain mdbook build is only a preview, not a substitute.

For moved or changed executable lessons, also run cargo test --locked --test integration test_tutorial::. Prose-only changes need the listed docs checks, not a compiler rebuild.

The link checker uses Python’s standard library. It checks local file targets in inline links, images, and reference definitions, ignoring code and comments. It does not check remote URLs, HTML links, or heading anchors. To check only a changed area, pass a directory or Markdown file relative to book/src, for example python3 -B scripts/check_links.py book/src proposals.

Documentation site

Install the pinned tools and preview the book locally:

rustup toolchain install 1.99.0 --profile minimal
cargo +1.99.0 install mdbook --version 0.5.4 --locked
mdbook serve book --open

python3 -B scripts/build_book.py builds the checkout into book/book; python3 -B scripts/build_docs_site.py builds and checks the complete versioned site in book/site. Do not use mdbook test for Plenty fences; the tutorial integration tests execute them.

See documentation deployment for site previews, publication, permissions and recovery, and documentation versions for release admission and retention.

Reverse-proxy tests

Run the nginx and Caddy integration tests when needed; they require Docker and curl:

PLENTY_DOCKER_TESTS=1 cargo test --locked --test integration test_std_http_server_proxy::

Without the variable, these tests report a skip and pass. With it, a missing Docker daemon or curl fails them. See reverse-proxy tests for container cleanup and CI image maintenance.

Quick reproductions

The compiler expects a complete program with a main entry point. Put probe statements inside it; for allocation or printing, propagate failures:

def main() -> Result[(), AllocError | IoError]:
    xs = [1, 2, 3]?
    print(xs)?
    Ok(())

An infallible body can use def main() -> ():. The fragment wrapper in tests/support/mod.rs is test fixture construction. For repeated probes, build once with cargo build --release, then use target/release/plenty FILE from that worktree; rebuild when compiler or runtime sources change.

Measure compilation cost when changing checking or lowering; the architecture guide describes reproducible performance evidence. The opt-in compiler profiler subdivides lowering, ownership checks and native compilation. Use compile_bench --workload check|scale|aot to isolate a workload, and --no-warmup when the report should contain only the requested repetitions. Normal tests never start a profiling session automatically.