Thanks for helping improve basic-cli.
CI uses a pinned Roc nightly from roc-lang/nightlies.
For local work, use the nightly-tag pinned in the CI workflow,
matching the generated ABI. Download the archive for that tag and your operating
system from roc-lang/nightlies releases.
We are committed to providing a friendly, safe, and welcoming environment for all. See the Code of Conduct for details.
Check the compiler available locally:
roc versionTo install the pinned nightly locally, extract the downloaded archive and add
the directory containing the roc executable to your PATH.
With Nix's nix-command and flakes features enabled, the flake provides the
pinned Roc nightly, the Rust toolchain and cross-compilation standard
libraries, Zig, Python, Valgrind (Linux only), and the documentation preview
server on supported Linux and macOS systems. Enter it with:
nix developTo load the shell automatically, install direnv and
hook it into your shell. The checked-in .envrc uses direnv's use flake;
install nix-direnv as well if
your direnv version does not provide it. Then simply approve .envrc once:
direnv allowThe lock file pins every flake input. The Roc nightly is pinned on top of that,
by release tag in flake.nix, and must name the same nightly the workflows
pin so the shell and CI cannot drift apart; see
Updating Roc Glue. The tags come from
roc-overlay, which mirrors the
official roc-lang/nightlies
binaries. After editing the tag, refresh that input so the new nightly is
recorded:
nix flake update roc-overlayBumping channel in rust-toolchain.toml similarly needs
nix flake update rust-overlaywhenever the requested version is newer than the manifests in the locked
rust-overlay.
CI pins a specific nightly so the compiler and committed host ABI glue cannot drift independently. When updating the nightly pin in the workflows:
- Update
flake.nixto the samerocpkgsrelease tag, then runnix flake update roc-overlay. - Run
./ci/regenerate_glue.shto refreshsrc/roc_platform_abi.rs. - Reconcile
src/lib.rsif generated names or layouts changed. - Run
cargo checkand./scripts/test.py.
For changes to the Nix release outputs, run nix flake check. This builds and
runs an application against the pinned published platform inside the Nix sandbox;
see the Nix consumer documentation.
Release-tooling regression tests run with:
python3 -m unittest scripts/test_check_release_version.py scripts/test_check_version_bump.py scripts/test_update_nix_release.pyThe release workflow bounds roc bump to 120 seconds and saves its output in
.release/bump-output.txt. It currently warns on comparison errors and timeouts:
the compiler still rejects the public InternalHttp.TransportErr alias in the
0.22.2 predecessor. Switch to --mode require once that comparison works.
Run the full local check before opening release or CI-facing changes:
./scripts/test.pySet ROC to test with a specific compiler without changing PATH:
ROC=/path/to/roc ./scripts/test.pyThe default command builds and bundles the native host, serves the bundle from localhost, then formats, checks, tests, builds, and runs every example. Process input, environment, fixtures, helper servers, exit codes, and separate stdout and stderr assertions work on Unix and Windows.
The data in scripts/test_spec.json is the source of truth for the test matrix.
Every example must have exactly one entry. Set its enabled flag to false
to skip the app, or set a stage flag to false under stages or
platforms.windows to skip only a broken stage without changing the runner.
Add named objects to an app's cases array to run the same compiled binary
with different arguments, stdin, environment, fixtures, helper servers,
expected exit codes, or output assertions. happy is only a naming convention;
an app can have any number of successful and failing cases.
CI separates source validation, cross-target compilation, and native execution.
Every example is compiled for each target declared in platform/main.roc:
x64mac, arm64mac, x64win, x64musl, and arm64musl. Target-specific
binary artifacts are then downloaded and executed on matching native runners;
this includes arm64musl, which runs on an arm64 Linux runner.
The operations can also be run independently:
./scripts/test.py --operation validate
./scripts/test.py --operation build --target x64musl --artifact-dir dist/example-binaries
./scripts/test.py --operation run --target x64musl --artifact-dir dist/example-binariesOn Linux, run those native artifacts under Valgrind with the same cases and output assertions used by CI:
./scripts/test.py --operation run --target x64musl --artifact-dir dist/example-binaries --valgrindThe static musl allocator is invisible to Valgrind's heap accounting: a report of zero heap allocations alone does not prove cleanup. On x86_64 glibc Linux, run the additional ownership check used by CI:
ROC=/path/to/pinned/roc python3 scripts/test_resource_lifetimes.pyIt creates an isolated diagnostic platform under target/, uses glibc so
Valgrind observes allocations, and rejects memory errors and definite/indirect
leaks. It also tracks file descriptors. The fixtures in
tests/resource-lifetimes/ verify port release, stream EOF, shared aliases, command reuse, and early returns; Rust tests verify
exactly-once native finalization and child reaping. Runtime reactor descriptors
and reachable service state can remain until process exit.
For faster local iterations when the platform host is already built:
./scripts/test.py --no-buildBuild and validation operations bundle the current platform and temporarily rewrite example headers to use its localhost URL. Checked-in examples may therefore keep using the latest published release URL while local work and pull requests exercise the WIP platform.
The Rust host ABI is generated from platform/main.roc using Roc's RustGlue.roc generator:
./ci/regenerate_glue.sh
./ci/regenerate_glue.sh --checkCommit src/roc_platform_abi.rs with any platform API change and the matching Rust host updates.
The script defaults to a sibling ../roc checkout. Override paths when needed:
ROC=../roc/zig-out/bin/roc ROC_SRC=../roc ./ci/regenerate_glue.shUse ci/regenerate_glue.sh --check separately when reviewing platform ABI changes; CI intentionally treats the committed Rust glue as the host ABI source of truth.
Do not edit generated glue by hand.
Every checked-in example should pass roc check, roc test, and roc build
with the current nightly.
Examples are executable documentation for representative, realistic workflows; they are not intended to exhaustively exercise every public API function.
Examples should include a top-level main! annotation. When the full platform error row would distract from the example, map low-level errors into a small example-domain error or use _ for the error type. Prefer postfix ?, infix ?, or ?? for effect results instead of ignoring them.
HTTP examples use Roc's builtin Json parser directly through Http.get!.
Examples that are intentionally kept out of CI while an API or compiler blocker is tracked use the .todoroc extension and must include a TODO comment with a GitHub issue link. Rename them back to .roc only after they check and build with the current nightly.
Generate platform docs from the platform entrypoint:
ROC_DOCS_URL_ROOT=/basic-cli/main roc docs --output=generated-docs platform/main.rocThe documentation entrypoint is platform/main.roc, matching the package that
applications consume.
To preview generated docs locally:
cd generated-docs
simple-http-server --nocache --indexThe release workflow attaches docs.tar.gz, updates checked-in examples to the
new bundle URL, and opens a follow-up PR for those source changes. It also
reconstructs the versioned documentation site from release assets and deploys
the validated docs immediately. The Pages workflow performs the same
reconstruction and adds freshly generated main docs without committing
generated documentation to the repository.