Skip to content

Latest commit

 

History

History
258 lines (196 loc) · 10.1 KB

CONTRIBUTING.md

File metadata and controls

258 lines (196 loc) · 10.1 KB

Contributing

Thanks for helping improve basic-webserver. Questions and early design discussions are welcome in the Roc Zulip chat.

Before making a change

Read design.md first. It is the authoritative description of the platform's intended architecture, ownership boundaries, supported scope, and non-goals. The implementation is still moving toward that design, so existing code is not evidence that a conflicting architecture is intentional.

Keep application policy in Roc, durable mutable state in SQLite or an external service, and bounded transport or operating-system resources in typed host subsystems. If a request conflicts with the design, call out the conflict instead of adding a workaround or silently expanding the platform's scope.

design.md records enduring what and why. Put implementation plans, migration status, and temporary constraints in code, focused documentation, issues, or pull requests.

Prefer simple solutions and a single source of truth where practical. Keep repository automation in scripts/ and write it in portable Python; extend an existing entry point when the responsibility fits, and add a new script only for a distinct reusable workflow.

Prerequisites

  • A recent Zig-based roc compiler on PATH. Build it from a Roc source checkout with zig build roc. The old Rust-based compiler is unsupported.
  • The Rust toolchain declared in rust-toolchain.toml. Rustup selects it automatically. Bundling also needs its rust-docs component, which provides the standard library notices.
  • Python 3. Repository automation uses only the Python standard library.
  • Native build tools for your operating system. Cross-compiling the musl hosts uses Zig 0.17.0. Windows host builds require Python 3.11 or newer, Zig 0.17.0, the Windows SDK, and the MSVC build tools used by Rust's MSVC target; see Windows host inputs.

The exact Roc nightly used for development, CI, and releases is recorded in .roc-version, and every examples/ manifest repeats it in its roc: entry. The CI action in .github/actions/setup-roc installs that version, and python scripts/test.py --operation validate fails when a manifest drifts from it. Move both to another nightly with:

python scripts/update_roc_version.py nightly-2026-08-13-2fdd90e

A scheduled workflow, update_roc_nightly.yml, runs that script for the latest nightly, pushes the result to the update-roc-nightly branch, runs CI with that nightly, and opens a pull request reporting whether it passed.

Build and run locally

Build the native host library:

python scripts/build.py

Then run an example:

roc examples/health.roc

The server listens on http://127.0.0.1:8000 by default. Build one explicit target with python scripts/build.py --target TARGET, or every target buildable from the current host with python scripts/build.py --all. Linux builds the two musl targets; macOS builds the macOS and musl targets. The x64win host inputs must be built on Windows.

Verify a change

The normal local checks are:

cargo fmt --all -- --check
cargo test --locked
python scripts/test.py

Run python scripts/build.py first whenever the Rust host has changed or the native host library is absent.

scripts/test.py validates its own harness, formats and checks Roc sources, runs Roc tests, builds every active examples/*.roc application, and executes the cases in scripts/test_spec.json. Normal server cases use the real HTTP listener, including HTTP/2 coverage. The runner uses only Python's standard library and applies the same expectations on Linux, macOS, and Windows.

Files ending in .todoroc are intentionally skipped migration backlog. Active .roc examples must pass the suite on every supported operating system.

For a focused application build after building the host:

roc build examples/health.roc

CI also checks every Linux example under Valgrind Memcheck. On x86-64 Linux with Valgrind installed, run the same lane with:

python scripts/test.py --operation memcheck

Windows host inputs

Build the Windows host on Windows x64 with Zig 0.17.0 on PATH (or selected with ZIG) and Rust's notices and LLVM tools installed with rustup component add rust-docs llvm-tools-preview. The Rust target remains x86_64-pc-windows-msvc; zig cc builds ring, SQLite, and zstd with that ABI and the SDK headers through scripts/zig_cc.py, so a separate clang-cl installation is unnecessary. All Windows build steps use the same version-checked Zig. The compiler adapter is copied to a path derived from the compiler's path and SHA-256 and both adapter files' hashes, so changing these inputs invalidates Cargo's cached native builds.

Run python scripts/build.py --target x64win, followed by python -m unittest scripts.test_windows_runtime. The build generates the MSVC startup archive and UCRT/Win32 import libraries, combines them into windows_support.lib, then collects the actual Cargo and Rust notices. python scripts/windows_runtime.py --check verifies the library and notice inventories, and bundling repeats that check. Release artifacts must transfer the entire platform/targets/x64win/ directory, including PROVENANCE.json and licenses/.

The runtime uses the UCRT supplied with Windows 10 and 11. Vendored Roc sources are checked against native/windows/UPSTREAM.json; update the recorded source revision, hashes, and original license together when changing them. New native dependencies or license expressions need review before adding them to the producer's accepted set.

Add or change examples

Keep examples realistic. Prefer adding a case to an existing useful example, or add a new example that demonstrates a real platform use case instead of a test-only Roc application.

Every active examples/*.roc application must have exactly one entry in scripts/test_spec.json. Platform-specific skips are exceptional: each skip must include a concrete reason and a GitHub tracking issue URL. The validator rejects platform-specific expected results.

Generated Rust glue

Changes to the hosted or provides blocks in platform/main.roc require regenerating src/roc_platform_abi.rs. The compiler and RustGlue.roc must come from the matching revision recorded in scripts/regenerate_glue.py.

ROC_SRC=/path/to/roc python scripts/regenerate_glue.py
ROC_SRC=/path/to/roc python scripts/regenerate_glue.py --check

ROC_GLUE_SPEC=/path/to/RustGlue.roc can be used instead of ROC_SRC.

Third-party notices

THIRD_PARTY_LICENSES.md ships in every bundle. Everything after its marker comment is generated, so do not edit that part by hand. Regenerate it after changing Cargo.lock, rust-toolchain.toml or a prebuilt input under platform/targets/:

python scripts/third_party_notices.py

The script runs cargo metadata for every Roc target and follows normal dependencies, which excludes build and dev dependencies. For each linked package it reproduces every license file in the package source, along with the copyright comments at the top of its source files. A license expression outside the reviewed set in the script stops it, as does a package with no license file unless the script maps it to another package from the same repository. CI and the release workflow run it with --check and fail when the committed file is stale.

Prebuilt inputs such as musl's libc.a and crt1.o, LLVM's libunwind.a and Roc's libSystem.tbd are recorded in licenses/native-inputs.json with their SHA-256, origin and license text in licenses/. Every committed file under platform/targets/ must be recorded there. When replacing one, record where the new file came from and its license before regenerating.

Generate API documentation

Generate and serve the same platform API documentation published for main:

roc docs platform/main.roc --serve

Open the local URL printed by Roc. The default output directory without --serve is generated-docs/.

Generated documentation is not committed. Each release attaches a docs.tar.gz asset holding the documentation for that version, and the published site is rebuilt from those assets plus freshly generated main documentation:

python scripts/restore_release_docs.py temp_docs

This needs an authenticated GitHub CLI (gh auth login).

The www directory holds documentation for releases up to 0.16.0, which predate the release asset and cannot be changed retroactively. It is copied into the published site alongside the restored archives, and is scheduled for deletion once 0.20.0 is released.

Benchmarking

Use the unified benchmark entrypoint for substituted-transport server invariants, allocation reports, real HTTP/SSE/SQLite load, process measurements, and before/after comparisons:

python scripts/benchmark.py check
python scripts/benchmark.py measure \
  --suite http \
  --label before \
  --output target/benchmarks/before.jsonl

See docs/benchmarking.md for the real-versus-simulated evidence boundary, thousand-stream SSE runs, scenario controls, recorded metrics, and comparison workflow. Local timing and memory values are indicative and are never CI thresholds.

Release validation

Source validation without building runtime artifacts is available separately:

python scripts/test.py --operation validate

Release CI builds all target host inputs, creates one platform bundle, has each compiler host cross-build every target from that bundle, and runs every independently produced artifact set on its native target.

After all declared target inputs have been assembled under platform/targets, create a release-format package with:

python scripts/bundle.py --output-dir dist

The bundler validates target completeness and Roc's transitive dependency size limit, then includes LICENSE, THIRD_PARTY_LICENSES.md and the Rust standard library notices. The release workflow assembles Windows, macOS, and Linux inputs before invoking it.