Blueprint.roc declares every tool a contributor and CI need. Blueprint.lock
pins them. Both are committed. The blueprint command from
roc-blueprint realises that
environment, the same way on a contributor’s machine and on every CI runner.
The environment holds the tooling compiler, the tools the scripts call, and the native toolchains a platform host or a test host needs. It does not hold the package compiler, which is never pinned.
Declare the environment
app [config] { pf: platform "<roc-blueprint release URL>" }
config = [
Name("roc-example"),
Overlay("roc", "github:roc-lang/roc-overlay"),
Environment(
"dev",
[
Overlays(["roc"]),
Command("roc-stable", "rocpkgs.nightly-2026-10-09-258ab27"),
RocPackages([
"https://github.com/roc-lang/basic-cli/releases/download/<version>/<hash>.tar.zst",
"https://github.com/roc-lang/http/releases/download/<version>/<hash>.tar.zst",
]),
Tools(["coreutils", "gh", "git", "unzip", "zip"]),
],
),
Shell("default", [Use("dev")]),
Task("check", [Use("dev"), Run(["scripts/check_all.roc"])]),
Task("bundle", [Use("dev"), Run(["scripts/build_bundle.roc"])]),
Task("test-bundle", [Use("dev"), Run(["scripts/test_bundle.roc"])]),
Task("package-examples", [Use("dev"), Run(["scripts/package_examples.roc"])]),
Task("package-docs", [Use("dev"), Run(["scripts/package_docs.roc"])]),
Task("test-published", [Use("dev"), Run(["scripts/test_published.roc"])]),
]
Each setting has one job:
Command("roc-stable", …)-
Names the exact nightly release used for tooling. The compiler comes from roc-overlay, which packages recorded nightlies under their release tags.
Commandexposes that one release asroc-stablewithout adding the tool’s own executables, so the environment provides no bareroc. RocPackages-
Locks the Roc bundles the scripts depend on, so a script resolves them without downloading. List every bundle, including dependencies of dependencies;
roc-stable deps scripts/check_all.rocprints them. Blueprint does not read package headers and cannot add a missing one. Tools-
Every program a script starts, and every toolchain a build needs: for example
pandoc,zig, a Rust toolchain orgo. A tool that a script calls and the environment does not declare is a defect, because CI and contributors then run different versions. Task-
A named entry point. CI calls tasks, never script paths, so the workflow and a contributor run the same command.
The two files divide the work. Blueprint.roc states which release is the
tooling compiler and which tools are needed, where a reviewer can read it.
Blueprint.lock pins the overlay revision that supplies that release’s download
URL and hash, the package source, and each locked bundle.
Nothing that blueprint generates is committed. Its generated Nix files stay in
.blueprint/, which the repository’s .gitignore lists.
Get blueprint
Blueprint needs Nix on the machine, so install Nix
first. There are then two ways to get the blueprint command. Whichever you
use, take the same roc-blueprint release as the platform URL in the
repository’s Blueprint.roc header.
With Nix
Nix can run or install blueprint straight from the release tag, with nothing
to download or verify by hand:
# run it once, without installing
nix run github:lukewilliamboswell/roc-blueprint/<version> -- run check
# or install it into your profile
nix profile install github:lukewilliamboswell/roc-blueprint/<version>
This is the simplest route for a contributor. Nix builds the command from the tagged source the first time, which takes longer than a download, and the flake provides it for x86_64 Linux, arm64 Linux and Apple Silicon macOS.
Blueprint does not run on Intel macOS. nixpkgs no longer supports that system,
so there is no environment for Blueprint to realise there and no
blueprint-x86_64-darwin binary is released.
As a release binary
Each roc-blueprint release also attaches prebuilt blueprint-<system> binaries
and a blueprint.sha256 file. Download the one for your system, check it
against the published SHA-256, make it executable and put it on PATH.
CI always uses the release binary, through
actions/setup-blueprint,
which checks it against a reviewed SHA-256 on every run. Nobody builds the CLI
from source in CI.
What the command needs
The binary needs Nix on the machine. It needs neither Python nor Roc installed.
To evaluate Blueprint.roc it uses a roc on PATH when that is exactly the
nightly the binary was built with; otherwise it fetches that nightly itself from
a roc-overlay revision pinned inside the binary. The ROC environment variable
overrides this choice and must name that same nightly.
This compiler is separate from roc-stable for a reason: Blueprint is written
in Roc and must run before the environment it describes exists. See
Three compilers, three jobs.
A release’s binary must be used with that release’s platform URL in the
Blueprint.roc header. The two move together; see
Upgrade Blueprint.
Work locally
A contributor needs Nix, the blueprint command, and a current nightly installed
as roc, the way Roc is normally installed. Nothing else has to be set up.
blueprint run check # run a declared task in the pinned environment
blueprint shell # or enter the environment and run scripts directly
blueprint tasks # list the declared tasks
blueprint is the outer command. Run it from an ordinary shell: there is no
nix develop step first and no nested shell.
Inside the environment roc-stable is the pinned compiler, and the
contributor’s own roc remains visible for package work.
Use the environment in CI
Every job that runs repository scripts sets up Blueprint first, then installs the latest nightly, then calls a task:
-
Set up Nix and the verified
blueprintbinary withactions/setup-blueprint. -
Install the latest nightly as
rocwith the officialroc-lang/setup-rocaction; see Install the nightly compiler. -
Run
blueprint run <task>.
A matrix names only runners Blueprint supports: x86_64 and arm64 Linux, and Apple Silicon macOS. It cannot include an Intel macOS runner. A platform can still cross-compile a host for that target in the builder job, but no job runs the repository’s scripts there.
The job needs no cache account and no token for this. A Nix cache is an
optimisation a repository may add; a cache that requires id-token: write
widens a test job’s token and must be an explicit choice.
A change to Blueprint.lock changes the tools every build uses. Where a
repository fingerprints native build inputs, Blueprint.lock is one of the
fingerprinted files; see
Keep rebuild selection narrow and complete.