perf(3168): the deploy path stops compiling test binaries

crane's `buildDepsOnly` defaults `doCheck = true`, which adds
`--all-targets` and a `cargo test --no-run` — so the dep cache compiles
every dev-dependency and test harness in the tree. The checks need
exactly that. `workspaceBuild` never runs a test and never links one, and
was sitting on the same cache.

Split into two, one per audience. Not a duplication of something shared:
CI evaluates against hyperhive's own nixpkgs pin while a host overrides
it — security patches cannot wait on a lock bump upstream — so the two
closures already differ and neither substitutes for the other. Giving
each its own cache therefore costs nobody a second build; it stops the
deploy compiling artifacts only CI consumes.

Both still build from crane's dummy source, so both hashes key on
Cargo.toml/Cargo.lock: a code edit rebuilds neither, a dependency or
toolchain bump rebuilds both. That bump is the recurring cost this
targets — on a host overriding nixpkgs for security patches, it is every
patch.

⚠️ The first build after this lands is cold: the derivation hashes move.
This commit is contained in:
atlas 2026-08-11 21:52:27 +02:00 committed by mara
commit d7b12e8c48
2 changed files with 47 additions and 4 deletions

View file

@ -1,5 +1,5 @@
# All flake package outputs. Imported per system from flake.nix; the
# shared rust build wiring (cleanSrc / cargoArtifacts /
# shared rust build wiring (cleanSrc / cargoArtifactsBinOnly /
# nativeBuildInputs) comes in via `rust` (see ../rust.nix).
{
pkgs,
@ -10,7 +10,7 @@
}:
let
inherit (pkgs) lib;
inherit (rust) cleanSrc cargoArtifacts nativeBuildInputs;
inherit (rust) cleanSrc cargoArtifactsBinOnly nativeBuildInputs;
docsAttrs = import ../docs {
inherit pkgs self;
@ -43,7 +43,7 @@ let
};
# ONE compile of the whole workspace (every bin, sharing the
# prebuilt `cargoArtifacts` dep cache). The per-bin packages below
# prebuilt `cargoArtifactsBinOnly` dep cache). The per-bin packages below
# are cheap copy-extractors over this, so workspace lib crates
# (hive-sh4re, hive-claude, …) compile exactly once instead of once
# per bin derivation.
@ -52,9 +52,16 @@ let
# (carries the hyperhive-assets build input for the prompt-template
# assertions in hive-agent::prompt::tests). Keeping them out of this
# derivation means a prompt edit doesn't bust the cargo cache.
#
# …and the dep cache underneath is the one WITHOUT test targets. This
# derivation has never run a test — `doCheck = false` below says so —
# but it used to sit on a cache that compiled every dev-dependency and
# test harness anyway, work whose only consumers are the checks. A
# deploy paid for it on every toolchain or dependency change.
workspaceBuild = craneLib.buildPackage {
src = cleanSrc;
inherit cargoArtifacts nativeBuildInputs;
cargoArtifacts = cargoArtifactsBinOnly;
inherit nativeBuildInputs;
pname = "hyperhive-workspace";
version = "0.1.0";
doCheck = false;

View file

@ -45,6 +45,23 @@ rec {
# `inherit cargoArtifacts;` so a workspace-only edit doesn't rebuild
# deps. All consumers use the same `cleanSrc` so the input hash
# stays consistent across the chain.
# Two dep caches, one per audience, and the split is not premature:
# crane's `buildDepsOnly` defaults `doCheck = true`, which adds
# `--all-targets` to the check and a `cargo test --no-run`, so the cache
# compiles every dev-dependency and test harness in the tree. The CHECKS
# need exactly that. A DEPLOY never runs a test binary and never links
# one, so on the deploy path that work is compiled and thrown away.
#
# They are separate rather than shared because they were never actually
# shared: CI evaluates against hyperhive's own nixpkgs pin, while a host
# overrides it (security patches cannot wait on a lock bump upstream),
# so the two closures differ and neither substitutes for the other. One
# cache per audience therefore costs nobody a second build — it just
# stops the deploy paying for artifacts only CI consumes.
#
# ⚠️ `buildDepsOnly` builds from `mkDummySrc`, so both hashes key on
# Cargo.toml/Cargo.lock rather than on `.rs` files: a code edit rebuilds
# neither, a dependency or toolchain change rebuilds both.
cargoArtifacts = craneLib.buildDepsOnly {
src = cleanSrc;
# Workspace Cargo.toml is virtual (no `[package].name`), so crane
@ -56,4 +73,23 @@ rec {
version = "0.1.0";
inherit nativeBuildInputs;
};
# The deploy path's cache: same deps, no test targets. `doCheck = false`
# drops crane's `--all-targets` and its `cargo test --no-run`, so
# dev-dependencies and test harnesses are never compiled here.
#
# Only `packages` consume this. Anything that needs to *run* a test —
# `checks.cargo-test` — or lint one — `checks.clippy --all-targets` —
# takes `cargoArtifacts` above instead, and would rebuild what it needs
# if it were pointed here by mistake.
cargoArtifactsBinOnly = craneLib.buildDepsOnly {
src = cleanSrc;
# Distinct pname so the two are told apart in build logs and store
# paths; a shared name would make the useful question ("which cache
# is this rebuild?") unanswerable at a glance.
pname = "hyperhive-workspace-bin";
version = "0.1.0";
doCheck = false;
inherit nativeBuildInputs;
};
}