The Linux development environment is defined in flake.nix and flake.lock.
It supports x86-64 and AArch64. The host and both devcontainer variants use the
same tools: Rust, Node.js 24, pnpm, Python with cocotb, Verilator, C/C++ build
tools, mold, cargo-insta, mbx, and the Mergify CLI (mergify) for stacked pull
requests. Rust reads rust-toolchain.toml. Corepack selects
pnpm from the nearest package.json with a packageManager field, so pnpm
updates do not depend on the version available in nixpkgs. The first use of a
version requires network access to download it into the user's Corepack cache;
subsequent uses can run offline. No corepack enable step is needed.
Install Nix with nix-command and flakes enabled, then run:
nix develop
pnpm install --frozen-lockfile
cargo test --lockedFor scripts, use nix develop --command <command>, for example:
nix develop --command pnpm run build:napi
nix develop --command cargo test --locked -p celox-vpi --test cocotb_e2e \
cocotb_drives_an_attached_native_flip_flop -- --exact --ignoredThe shell sets CELOX_COCOTB_PYTHON to its Python with cocotb and Z3_BIN
to its Z3 for the lydite solver tests. It includes
wasm32-unknown-unknown for browser Rust builds. The separate NAPI WASI
cross-build still needs the WASI target and SDK used by CI; these are not
part of this native development shell.
Native builds in the shell use mold through the Nix compiler wrapper to reduce
link time. This applies to both supported Linux architectures and the devcontainer
shells. No global RUSTFLAGS or repository-wide Cargo linker setting is needed,
so WebAssembly builds and builds outside this environment keep their own linker
configuration. Re-enter the shell after updating the flake to activate mold.
For automatic activation, install direnv and nix-direnv once:
nix profile install nixpkgs#direnv nixpkgs#nix-direnv
mkdir -p ~/.config/direnv
printf '\n%s\n' 'source "$HOME/.nix-profile/share/nix-direnv/direnvrc"' >> ~/.config/direnv/direnvrc
printf '\n%s\n' 'eval "$(direnv hook bash)"' >> ~/.bashrc
eval "$(direnv hook bash)"
direnv allowFor zsh, replace the two Bash hook commands with:
printf '\n%s\n' 'eval "$(direnv hook zsh)"' >> ~/.zshrc
eval "$(direnv hook zsh)"The checked-in .envrc loads the flake through nix-direnv. Review and allow it
again when it changes. Launch editors from the activated shell, or use an
editor's direnv integration, so subprocesses get the same tools.
The devShell directs global npm installs (including CLI self-updates) to
~/.local/share/npm and adds its bin directory to PATH. Set
NPM_CONFIG_PREFIX before entering the shell to use another writable location.
The devcontainer configures the same location for its user. Do not use sudo or
try to change permissions in /nix/store.
After changing the flake, re-enter nix develop or run direnv reload.
An already-running shell can apply the same settings immediately:
export NPM_CONFIG_PREFIX="$HOME/.local/share/npm"
export PATH="$NPM_CONFIG_PREFIX/bin:$PATH"
npm install -g @openai/codex
hash -rInside the development environment, cargo is an mbx shim. This also applies
to Cargo invoked by pnpm or rust-analyzer. The real Cargo and Rust toolchain
remain on PATH; no global mbx setup or Cargo configuration is modified.
mbx doctor may still warn that global setup is missing: the shell uses a
scoped Cargo symlink. Check readlink -f "$(command -v cargo)" inside the
shell; it should resolve to the Nix-provided mbx binary.
mbx keeps reading your per-user configuration. On the existing development
host, ~/mbx is a mounted Btrfs filesystem and ~/.config/mbx/config.toml
already selects its cache directory. The flake does not override it.
Machine-specific absolute paths do not belong in flake.nix or .mbx.toml.
On another Linux host, create a writable directory on your chosen local
filesystem and add this to ~/.config/mbx/config.toml (use your actual absolute
path; TOML does not expand $HOME):
cache_dir = "/home/YOUR_USER/mbx/cache"
# Optional limits; choose these for your disk and workload.
[gc]
max_total_size = "50GiB"
[target]
max_age = "30d"Alternatively, export MBX_CACHE_DIR=/your/mounted/filesystem/cache before
entering the shell. This takes precedence over the user configuration.
Managed targets default to a directory inside the cache, so cache blobs and
restored outputs stay on the same filesystem. Avoid setting CARGO_TARGET_DIR,
Cargo's build.target-dir, or a separate MBX_TARGET_ROOT: explicit Cargo
targets bypass managed placement, and another filesystem prevents reflinks.
mbx doctor
cargo check --locked -p celox-state-layout
readlink -f target
mbx gc --dry-runFor a new checkout, the first build creates target as a symlink into the
managed cache. If a real target/ already exists, mbx asks interactively before
replacing its outputs; non-interactive builds leave it alone. Stop active
builds before migrating. To discard old build outputs explicitly, use
cargo clean and remove an empty target/ if necessary, then build again.
Do not delete the shared cache to migrate one checkout.
Btrfs is optional: mbx copies when reflinks are unavailable, so caching and
collection still work, but shared outputs can take more physical space.
Verify both cache and managed-target reflinks with mbx doctor. GC budgets
use logical bytes and are not hard limits on physical disk consumption.
If an existing writable Btrfs mount is available, use a directory on it.
Otherwise, an administrator can provision a dedicated filesystem or a loopback
image once, mount it persistently through /etc/fstab, and give your user
ownership of a cache directory. Normal builds and reflinks need no sudo.
Neither the flake nor devcontainer mounts a block filesystem.
For the existing loopback setup, check the mount before building:
findmnt --target "$HOME/mbx"
df -h "$HOME/mbx"The filesystem type should be btrfs, not the parent filesystem. A loopback
image consumes space on its backing filesystem too; monitor both. Nix's
/nix/store and its garbage collection are separate from mbx's storage budget.
Both .devcontainer/default and .devcontainer/with-claude directly use the
Ubuntu 24.04 devcontainer base image, install Nix, and build .#dev-tools
from the same lockfile. The resulting profile is on the
editor's PATH, and Bash loads the full devShell environment. Tool installation
failures stop setup. Optional assistant installations retain their existing
opt-in behavior and are outside the pinned development toolchain.
Before opening the container, ensure $HOME/mbx exists on the Docker host
and, if using Btrfs, that it is mounted. Both variants bind that directory to
/home/vscode/mbx and set
MBX_CACHE_DIR=/home/vscode/mbx/container-cache. The container cache and managed
targets are on the same host filesystem, without granting mount privileges to
the container. Docker Desktop or remote filesystem sharing may not preserve
reflink support; mbx doctor inside the container is the definitive check.
The host directory must be writable by the container user (normally the
devcontainer aligns its UID with the local user).
For a different host path, edit the mbx mounts entry in your chosen
devcontainer.json. You can use source=${localEnv:CELOX_MBX_ROOT} and set
CELOX_MBX_ROOT in the environment that launches VS Code / the devcontainer
CLI. Keep the destination and MBX_CACHE_DIR unchanged. With a remote Docker
daemon, the source must exist on that daemon's host.
Host and container paths differ. A target symlink created on the host may
point outside the container, and vice versa. Prefer separate checkouts for
host and container builds. Keep their cache roots separate as configured:
mbx collects targets whose checkout paths no longer exist, and each environment
sees different absolute paths. Worktrees within one environment still share
its cache. Both cache roots use the same Btrfs filesystem.
If reusing one checkout, stop builds and remove only the target symlink
(unlink target) before switching environments, then let mbx recreate it.
Never use recursive deletion on the symlink's resolved cache directory.
After creating the container, run mbx doctor and a build in its terminal.
Re-run bash .devcontainer/post-create.sh after changing the flake/lockfile,
then restart terminals and reload the editor to refresh its tool profile.
nix flake update
nix flake check
nix fmt -- --check flake.nix
nix develop --command bash -c 'rustc --version; node --version; pnpm --version; mbx doctor'Commit flake.lock together with environment changes. When updating Rust,
update the rust-overlay revision in flake.nix and regenerate flake.lock
with nix flake update rust-overlay if its locked revision predates the release.
Renovate groups the explicitly pinned rust-overlay revision with Rust version
updates in one PR. The mbx input is updated by the workflow described below;
other Nix inputs remain manually managed. The CI lint job
also evaluates the locked flake for both Linux architectures and builds its
Rust toolchain check without allowing lockfile updates, so a Rust update that
is not supported by the pinned overlay fails CI before merge.
When updating pnpm, update packageManager in package.json and the pnpm
lockfile as needed; no Nix version or hash update is required. Verify with
nix develop --command pnpm --version (also from a workspace package directory).
The Mergify CLI is defined in nix/mergify-cli.nix from upstream's prebuilt
release binaries, the only unfree package the flake allows. To update it, set
its version and replace both Linux hashes with the SRI form of the release's
SHA256SUMS entries (nix hash convert --hash-algo sha256 --to sri <hex>).
mbx comes from tignear/nix-packages.
Run nix flake update nix-packages to update its pinned package definition.
The Update mbx workflow checks that input daily at 05:37 UTC and can also
be dispatched manually. It validates the updated flake and mbx executable,
then opens or updates automation/update-mbx against the default branch.
Only flake.lock is included in the PR; other flake inputs stay pinned.
The workflow uses the existing release GitHub App credentials so its PR
triggers normal CI. The daily and manual runs also build and run the
committed mbx package on both x86_64 and ARM64 Linux. Updates are proposed as PRs for review;
the workflow does not merge them automatically. Upstream package-definition
updates can produce a PR even when the mbx version is unchanged.
The shell pins development tools, not the complete host OS or all build inputs. Use the committed Cargo/pnpm lockfiles and locked dependency installation for repeatable builds. Existing CI workflows continue to test their platform matrix independently of the Linux devShell.