> Documentation version: 3. [HTML page](environment.html)

<a id="pin-the-tooling-environment"></a>

# Pin the tooling environment

` Blueprint.roc ` declares every tool a contributor and CI need. ` Blueprint.lock `
pins them. Both are committed. The ` blueprint ` command from
[roc-blueprint](https://github.com/lukewilliamboswell/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.

Roc packages can use ` Programs `, ` Fetch ` and ` Expect ` with no Nix source.
Follow [the complete Roc package adoption guide](pure-package.md#adopt-a-pure-package-without-nix). Native
platforms can declare Nix sources for their host toolchains and sandboxed builds.

<a id="risk-tooling-selection"></a>

## Risks and controls for tooling selection

A contributor and CI can run different tool versions, or a new compiler can
break the scripts needed to diagnose the product. Keep tooling identity explicit
and separate from the compiler under test. Blueprint recipes and locks select
the tooling bytes; local source scripts declare the corresponding compiler.
Verify configuration and run the same named tasks locally and in CI. Changing a
lock is an admission decision, not a harmless cache refresh. The lock establishes
selected identities; it does not establish that the selected upstream code is
free of defects.

<a id="declare-the-environment"></a>

## Declare the environment

``` roc
app [config] { pf: platform "<roc-blueprint release URL>" }

config = [
	Name("roc-example"),
	Packages("nix", From(NixPackages("github:NixOS/nixpkgs/nixos-unstable"))),
	Input("roc", "github:roc-lang/roc-overlay"),
	Environment(
		"dev",
		[
			Overlays(["roc"]),
			Command("roc-stable", "nix#rocpkgs.nightly-<date>-<commit>"),
			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(["nix#coreutils", "nix#gh", "nix#git", "nix#unzip", "nix#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"])]),
]
```

In this example, ` Packages ` declares its Nix package source,
` Input ` declares the flake input and ` Overlays ` selects its role in this environment.
Source-qualified names such as ` nix#gh ` select the declared package source.
Each setting has one job:

- **` Command("roc-stable", ...) `**: Names the exact nightly release used for tooling.
  The compiler comes from [roc-overlay](https://github.com/roc-lang/roc-overlay),
  which packages recorded nightlies under their release tags. ` Command ` exposes
  that one release as ` roc-stable ` without adding the tool’s own executables, so
  the environment provides no bare ` roc `.

- **` RocPackages `**: Declares the content-addressed Roc bundles to prepare before
  scripts start. For tasks and shells, Blueprint downloads missing bundles and
  uses ` roc unbundle ` to verify their URL hashes before publishing them into Roc’s
  package cache; these task-only bundles have no entries in ` Blueprint.lock `.
  Sandboxed builds instead take their bundles from provider-locked sources. List
  every bundle, including dependencies of dependencies;
  ` roc-stable deps scripts/check_all.roc ` prints 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 or ` go `. 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, package sources, fetched programs and bundle sources used by
sandboxed builds. Task-only bundle identities remain in their committed URLs.

Commit the generated lock after review. Derived files and generated Nix stay in
` .blueprint/ `, which the repository’s ` .gitignore ` lists.

<a id="get-the-blueprint-binary"></a>

## Get ` blueprint `

Blueprint needs Nix only for Nix-sourced tools and sandboxed builds. For
those environments install [Nix](https://nixos.org/download) first. There are 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.

<a id="with-nix"></a>

### With Nix

Nix can run or install ` blueprint ` straight from the release tag, with nothing
to download or verify by hand:

``` sh
# 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>
```

Nix builds the command from tagged source on first use.

<a id="as-a-release-binary"></a>

### 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 `](actions.md#setup-blueprint),
which checks it against a reviewed SHA-256 on every run. Nobody builds the CLI
from source in CI.

<a id="what-the-command-needs"></a>

### What the command needs

The binary needs neither Python nor Roc installed. It embeds its fixed
configuration compiler, the nightly its release notes record. On first use it
installs the embedded archive into its content-addressed store and verifies the
recorded hash before extraction. It reuses verified bytes on later runs and
repairs a damaged archive from its embedded copy. Bootstrapping this compiler
needs no GitHub request; project tools and packages can still need downloads.
A compatible explicit ` ROC ` override takes precedence, then a compatible host
` roc `. An override must match the compiler required by the Blueprint binary.

` blueprint compiler-version ` reports the bundled compiler tag without evaluating
a project, installing a compiler or writing a cache. It describes Blueprint
itself even when a host override is selected. The report does not require the
repository’s ` roc-stable ` pin to match: update that pin separately after testing
the scripts that use it.

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](overview.md#three-compilers-three-jobs).

<a id="work-locally"></a>

## Work locally

A contributor needs ` blueprint `, Nix when their selected tools/builds use it,
and a current nightly installed
as ` roc `, the way Roc is normally installed. Nothing else has to be set up.

``` sh
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.

<a id="use-the-environment-in-ci"></a>

## Use the environment in CI

Every job that runs repository scripts sets up Blueprint first, then installs
the latest nightly, then calls a task:

1. Set up the verified ` blueprint ` binary, requesting Nix when needed, with
   [` actions/setup-blueprint `](actions.md#setup-blueprint).

2. Install the latest nightly as ` roc ` with the official ` roc-lang/setup-roc `
   action; see [Install the nightly compiler](actions.md#install-the-nightly-compiler).

3. Run ` blueprint run <task> `.


Choose runners for which the selected release has reviewed binaries in
` actions/setup-blueprint/releases.json `, and verify that the declared tools also
support those systems. Cross-compiling a host in
[the builder job](platforms.md#the-builder-job) does not establish that it
runs on the target.

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](platforms.md#keep-rebuild-selection-narrow-and-complete).

