Develop with Nix
The dev shell that generates the bazelrc files the rest of the build assumes, and what is actually inside it.
Who this is for: anyone who wants the toolchain CI uses, and anyone
working on LRE, which needs it. What you'll have at
the end: the shell, the five generated bazelrc files it produces, and an
accurate list of what it does and does not contain. Time: twenty
minutes, mostly download.
The flake in the repository root is where almost all of NativeLink's tooling
lives. Entering the shell is not just a convenience: the shell hook is what
writes the bazelrc fragments that .bazelrc pulls in with try-import,
and try-import means their absence is silent. A build outside the shell is
not a broken build; it is a differently configured one.
Install Nix
The next-gen Nix installer is the easiest path:
curl -sSfL https://artifacts.nixos.org/nix-installer | sh -s -- install --enable-flakesThe --enable-flakes flag turns on the nix-command and flakes
experimental features, which the repository's flake needs.
Three things that go wrong on first install
-
nixnot found after installing. Restart your terminal, or source the profile script:. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh -
experimental Nix feature 'nix-command' is disabled. You used the standard installer. Add this to~/.config/nix/nix.conf:experimental-features = nix-command flakes -
Disk space. Toolchains, compiler binaries and build artifacts are large. Have 15 to 20 GB free before the first
nix develop.
Enter the dev shell
git clone git@github.com:yourusername/nativelink
cd nativelink
nix developFirst run downloads the toolchain, which takes a few minutes. After that,
entering is close to instant; everything is content-addressed in
/nix/store.
Install `direnv` and stop thinking about this
CONTRIBUTING.md recommends it strongly, and the reason is that the shell
has to be re-entered after every branch switch or change to a .nix file.
direnv does that for you:
nix profile install nixpkgs#direnv
# add the hook for your shell: https://direnv.net/docs/hook.html
# then, in the repo: direnv allowTo check the environment is active: env | grep NIX should print several
*NIX_* variables.
What entering the shell generates
This is the part that matters and the part that is invisible. The shell hook writes five things into your working tree:
| Generated file | What it does |
|---|---|
.pre-commit-config.yaml | Symlink enabling the repo's pre-commit hooks |
lre.bazelrc | Points Bazel at the LRE toolchains |
nativelink.bazelrc | Gives your builds access to NativeLink's read-only cache |
nixos.bazelrc | Adds the required NixOS binary paths (NixOS only) |
darwin.bazelrc | Configures Darwin libs and frameworks (macOS only) |
.bazelrc picks all four bazelrc fragments up with try-import, so
outside the shell Bazel silently proceeds without them. That is why a build
can succeed outside Nix and still not match CI.
What is in the shell
The package list is in devShells.default. Broadly:
Rust and build: a bazel wrapper around Bazelisk (it unsets TMPDIR
first), the pinned stable Rust toolchain from the LRE overlay,
rust-analyzer, buck2, python3, git, pre-commit, git-cliff.
Web: bun, nodejs_22, lychee, the Playwright driver. This is what
lets you work on the docs without installing anything.
Infrastructure: kubectl, kubernetes-helm, kustomize, kubectx,
kind, cilium-cli, fluxcd, pulumi, skopeo, dive, cosign,
trivy, awscli2, google-cloud-sdk, docker-client, tektoncd-cli,
go.
Docs and prose: vale, which is the style linter the docs are checked
against.
Repo-specific helpers: the toolchain generators, the local image
builders, local-image-test, and the LRE clang.
Some things you might expect are not here
No protoc is provided; the generated Rust is checked into
nativelink-proto/genproto/ and nothing invokes protoc at build time.
No prebuilt nativelink binary is in the shell; build it with Bazel
or Cargo, or run nix run . which builds and runs the flake's default
package. And CONTRIBUTING.md still advises installing Clang separately;
the shell does now ship lre.clang, so try without first, and fall back to
nix profile install nixpkgs#clang if a C++ toolchain error says
otherwise.
Running things inside the shell
Everything from the Bazel and
Cargo workflows works unchanged. The shell guarantees
the versions, and supplies the bazelrc fragments those workflows assume.
The two commands worth knowing that only exist here:
pre-commit run -a # the full pre-commit suite
nix build .#nativelinkCoverageForHost # HTML coverage report in ./resultLRE and worker images
The flake builds the LRE worker images rather than exposing an LRE shell. The ones you are likely to want:
nix build .#nativelink-worker-lre-cc
nix build .#nativelink-worker-lre-rs
nix build .#nativelink-worker-lre-javalre.bazelrc, generated on entering the shell, is what points a local Bazel
build at the matching toolchains. See LRE for what the
scheme is doing and why the /nix/store paths have to match on both sides.
When Nix isn't worth it
For a typo fix, a doc tweak or a single unit test, the
Cargo workflow is faster to get into and nothing will
go wrong. Reach for Nix when you are touching the build graph, working on
LRE, chasing a difference between your machine and CI, or working on the
docs, since vale and bun both come from here.
Common questions
With a working shell, the next question is where your change goes. Twelve crates, a strict layering, and six places the first guess is wrong.