ci: run nix flake check with crane and a cached Nix store

Move fmt, clippy, tests, cargo package verification and the offline mock
smoke into flake checks built with crane, so the crate dependency build is
its own derivation that is reused until Cargo.lock changes. CI runs a single
nix flake check and restores /nix/store from the GitHub Actions cache via
nix-community/cache-nix-action, replacing the rustup + cargo test job.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013cfWL2GVg97oLfkNvPLaci
This commit is contained in:
Claude 2026-10-02 12:57:26 +00:00
commit f1ac1b94bb
No known key found for this signature in database
4 changed files with 110 additions and 62 deletions

View file

@ -6,44 +6,11 @@ on:
pull_request:
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo + target
uses: swatinem/rust-cache@v2
- name: Check formatting
run: cargo fmt --check
- name: Clippy
run: cargo clippy -- -D warnings
- name: Test
run: cargo test
- name: Publish ownership snapshot tests
run: scripts/publish-ownership-snapshot-test.sh
- name: Package
run: cargo package --locked
- name: Install Smoke
run: |
root="$(mktemp -d)"
cargo install --path . --locked --root "$root"
"$root/bin/idx" version
scripts/live-smoke.sh --bin "$root/bin/idx" --no-build --mode mock
# Everything that compiles Rust runs as a `nix flake check` derivation
# (fmt, clippy, tests, package verify, build, offline smoke). Crane splits
# the crate dependency build into its own derivation, and the Nix store is
# restored from the GitHub Actions cache, so unchanged derivations are
# substituted instead of rebuilt.
nix:
runs-on: ubuntu-latest
@ -52,14 +19,24 @@ jobs:
uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@v30
uses: cachix/install-nix-action@v31
- name: Build default package
run: nix build .#default
- name: Restore and save Nix store
uses: nix-community/cache-nix-action@v7
with:
primary-key: nix-${{ runner.os }}-${{ hashFiles('flake.nix', 'flake.lock', 'Cargo.lock', 'Cargo.toml') }}
restore-prefixes-first-match: nix-${{ runner.os }}-
gc-max-store-size-linux: 8G
- name: Flake check
run: nix flake check -L
- name: Run default app
run: nix run .#default -- version
- name: Publish ownership snapshot tests
run: scripts/publish-ownership-snapshot-test.sh
- name: Build ownership publisher
run: |
nix build .#ownership-publisher

18
flake.lock generated
View file

@ -38,7 +38,8 @@
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay"
"rust-overlay": "rust-overlay",
"crane": "crane"
}
},
"rust-overlay": {
@ -75,6 +76,21 @@
"repo": "default",
"type": "github"
}
},
"crane": {
"locked": {
"lastModified": 1790899423,
"narHash": "sha256-THGtz65UGJU+sonZidRboyCMk+9DclpMF7pwL4uDV4M=",
"owner": "ipetkov",
"repo": "crane",
"rev": "47b6b27ed9a3a9181415e4367d0c30ab2a0e0250",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
}
},
"root": "root",

View file

@ -4,13 +4,14 @@
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
flake-utils.url = "github:numtide/flake-utils";
crane.url = "github:ipetkov/crane";
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, flake-utils, rust-overlay }:
outputs = { self, nixpkgs, flake-utils, crane, rust-overlay }:
flake-utils.lib.eachDefaultSystem (system:
let
overlays = [ (import rust-overlay) ];
@ -20,34 +21,43 @@
rustToolchain = pkgs.rust-bin.stable.latest.default.override {
extensions = [ "rust-src" "rust-analyzer" ];
};
rustPlatform = pkgs.makeRustPlatform {
cargo = rustToolchain;
rustc = rustToolchain;
};
craneLib = (crane.mkLib pkgs).overrideToolchain (_: rustToolchain);
runtimeDeps = with pkgs; [
curl-impersonate
mupdf
];
idxPackage = rustPlatform.buildRustPackage {
# Only the crate itself (manifests, src/, tests/), so doc or script
# edits do not invalidate the Rust derivations.
rustFiles = [
./Cargo.toml
./Cargo.lock
./src
./tests
];
src = lib.fileset.toSource {
root = ./.;
fileset = lib.fileset.unions rustFiles;
};
commonArgs = {
inherit src;
pname = cargoManifest.package.name;
version = cargoManifest.package.version;
src = lib.cleanSource ./.;
cargoLock = {
lockFile = ./Cargo.lock;
strictDeps = true;
nativeBuildInputs = with pkgs; [ pkg-config ];
buildInputs = with pkgs; [ openssl ];
};
# Dependency-only build keyed on Cargo.toml/Cargo.lock: reused from the
# Nix store (or binary cache) until the dependency graph changes.
cargoArtifacts = craneLib.buildDepsOnly commonArgs;
idxPackage = craneLib.buildPackage (commonArgs // {
inherit cargoArtifacts;
doCheck = false;
nativeBuildInputs = with pkgs; [
makeWrapper
pkg-config
];
buildInputs = with pkgs; [
openssl
];
nativeBuildInputs = commonArgs.nativeBuildInputs ++ [ pkgs.makeWrapper ];
postInstall = ''
wrapProgram "$out/bin/idx" \
--prefix PATH : "${lib.makeBinPath runtimeDeps}"
'';
};
});
# Self-contained ownership snapshot publisher for scheduled hosts
# (e.g. a NixOS systemd timer): no checkout, `nix develop` or cargo
# build at run time. Wraps the repo scripts with a pinned `idx`.
@ -103,9 +113,53 @@
apps.default = {
type = "app";
program = "${idxPackage}/bin/idx";
meta.description = "idx CLI";
};
checks = {
default = idxPackage;
ownership-publisher = ownershipPublisher;
fmt = craneLib.cargoFmt { inherit src; };
clippy = craneLib.cargoClippy (commonArgs // {
inherit cargoArtifacts;
cargoClippyExtraArgs = "--all-targets -- -D warnings";
});
test = craneLib.cargoTest (commonArgs // {
inherit cargoArtifacts;
# Ownership discover tests point IDX_CURL_IMPERSONATE_BIN at plain
# `curl` against a local fixture server.
nativeCheckInputs = [ pkgs.curl ];
});
# `cargo package` verification: the published crate (per the
# `include` list in Cargo.toml) must build on its own.
package = craneLib.mkCargoDerivation (commonArgs // {
inherit cargoArtifacts;
pnameSuffix = "-package";
src = lib.fileset.toSource {
root = ./.;
fileset = lib.fileset.unions (rustFiles ++ [
./LICENSE
./README.md
]);
};
buildPhaseCargoCommand = "cargo package --locked --offline --allow-dirty";
doInstallCargoArtifacts = false;
installPhaseCommand = "touch $out";
});
smoke-mock = pkgs.runCommand "idx-smoke-mock"
{ nativeBuildInputs = with pkgs; [ bash coreutils findutils gnugrep gnused gawk ]; }
''
cp -r ${./scripts} scripts
chmod -R u+w scripts
patchShebangs scripts
# The mock provider reads fixtures relative to the working dir.
mkdir -p tests
cp -r ${./tests/fixtures} tests/fixtures
export HOME="$TMPDIR"
${idxPackage}/bin/idx version
bash scripts/live-smoke.sh --bin ${idxPackage}/bin/idx --no-build --mode mock
touch "$out"
'';
};
checks.default = idxPackage;
checks.ownership-publisher = ownershipPublisher;
devShells.default = pkgs.mkShell {
inputsFrom = [ idxPackage ];

1
result-1 Symbolic link
View file

@ -0,0 +1 @@
/nix/store/jh7j89lzmrvvhm44dv96znzlf522p2ac-idx-cli-test-0.2.4