{
description = "Improved types and functionality for using Git in Radicle";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/release-25.11";
crane = {
url = "github:ipetkov/crane";
};
git-hooks = {
url = "github:cachix/git-hooks.nix";
inputs.nixpkgs.follows = "nixpkgs";
};
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs = {
nixpkgs.follows = "nixpkgs";
};
};
flake-utils.url = "github:numtide/flake-utils";
advisory-db = {
url = "github:rustsec/advisory-db";
flake = false;
};
};
nixConfig = {
keepOutputs = true;
};
outputs = {
self,
nixpkgs,
crane,
fenix,
flake-utils,
advisory-db,
rust-overlay,
...
} @ inputs:
flake-utils.lib.eachDefaultSystem (system: let
pname = "radicle-git";
pkgs = import nixpkgs {
inherit system;
overlays = [(import rust-overlay)];
};
inherit (pkgs) lib;
msrv = let
msrv = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).workspace.package.rust-version;
in rec {
toolchain = pkgs.rust-bin.stable.${msrv}.default;
craneLib = (crane.mkLib pkgs).overrideToolchain toolchain;
commonArgs = mkCommonArgs craneLib;
};
rustup = rec {
toolchain = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
craneLib = (crane.mkLib pkgs).overrideToolchain toolchain;
commonArgs = mkCommonArgs craneLib;
};
srcFilters = path: type:
# Allow data/git-platinum.tgz
(lib.hasSuffix "\.tgz" path)
||
# Default filter from crane (allow .rs files)
(rustup.craneLib.filterCargoSources path type);
src = lib.cleanSourceWith {
src = ./.;
filter = srcFilters;
};
basicArgs = {
inherit src;
pname = "radicle-git";
version = "0.1.0";
strictDeps = true;
};
# Common arguments can be set here to avoid repeating them later
mkCommonArgs = craneLib:
basicArgs
// {
# Build *just* the cargo dependencies, so we can reuse
# all of that work (e.g. via cachix) when running in CI
cargoArtifacts = craneLib.buildDepsOnly basicArgs;
nativeBuildInputs = with pkgs; [
git
];
buildInputs = lib.optionals pkgs.stdenv.buildPlatform.isDarwin (with pkgs; [
darwin.apple_sdk.frameworks.Security
libiconv
]);
};
buildCrate = rust: name: let
# Test crates live under `{crate}/t` but are called `{crate}-t`.
# Check if it is a test crate, and safely remove the suffix and build the Cargo.toml path.
isTest = lib.hasSuffix "-test" name;
pname = lib.removeSuffix "-test" name;
cargoToml = src + "/${pname}${lib.optionalString isTest "/t"}/Cargo.toml";
in
rust.craneLib.buildPackage (rust.commonArgs
// {
inherit (rust.craneLib.crateNameFromCargoToml {inherit cargoToml;}) pname version;
cargoExtraArgs = "-p ${pname}";
doCheck = false;
});
buildCrates = {
rust ? rustup,
prefix ? "",
}:
builtins.listToAttrs (map
(name: lib.nameValuePair (prefix + name) (buildCrate rust name))
[
"radicle-std-ext"
"radicle-git-ext"
"radicle-git-ext-test"
"radicle-surf"
"radicle-surf-test"
]);
in {
# Formatter
formatter = pkgs.alejandra;
checks =
(buildCrates {
rust = msrv;
prefix = "msrv-";
})
// {
pre-commit-check = inputs.git-hooks.lib.${system}.run {
src = ./.;
settings.rust.check.cargoDeps = pkgs.rustPlatform.importCargoLock {lockFile = ./Cargo.lock;};
default_stages = [
"pre-commit"
"pre-push"
];
hooks = {
alejandra.enable = true;
codespell = {
enable = true;
entry = "${lib.getExe pkgs.codespell} -w";
types = ["text"];
};
rustfmt = {
enable = true;
fail_fast = true;
packageOverrides.rustfmt = rustup.toolchain;
};
cargo-check = {
enable = true;
name = "cargo check";
after = ["rustfmt"];
fail_fast = true;
};
cargo-doc = let
# We wrap `cargo` in order to set an environment variable that
# gives us a non-zero exit on warning.
command =
pkgs.writeShellScript
"cargo"
"RUSTDOCFLAGS='--deny warnings' ${lib.getExe' rustup.toolchain "cargo"} $@";
in {
enable = true;
name = "cargo doc";
after = ["rustfmt"];
fail_fast = true;
entry = "${command} doc --workspace --all-features --no-deps";
files = "\\.rs$";
pass_filenames = false;
};
clippy = {
enable = true;
name = "cargo clippy";
stages = ["pre-push"]; # Only pre-push, because it takes a while.
settings = {
allFeatures = true;
denyWarnings = true;
};
packageOverrides = {
cargo = rustup.toolchain;
clippy = rustup.toolchain;
};
};
shellcheck.enable = true;
};
};
# Run clippy (and deny all warnings) on the crate source,
# again, reusing the dependency artifacts from above.
#
# Note that this is done as a separate derivation so that
# we can block the CI if there are issues here, but not
# prevent downstream consumers from building our crate by itself.
doc = rustup.craneLib.cargoDoc rustup.commonArgs;
deny = rustup.craneLib.cargoDeny rustup.commonArgs;
fmt = rustup.craneLib.cargoFmt basicArgs;
audit = rustup.craneLib.cargoAudit {
inherit src advisory-db;
};
# Run tests with cargo-nextest
nextest = rustup.craneLib.cargoNextest (rustup.commonArgs
// {
partitions = 1;
partitionType = "count";
nativeBuildInputs = [
# git is required so the sandbox can access it.
pkgs.git
];
# Ensure dev is used since we rely on env variables being
# set in tests.
buildPhase = ''
export CARGO_PROFILE=dev;
'';
});
};
packages = buildCrates {};
devShells.default = rustup.craneLib.devShell {
# Extra inputs can be added here; cargo and rustc are provided by default.
packages = [
pkgs.cargo-deny
pkgs.cargo-msrv
pkgs.cargo-nextest
pkgs.cargo-semver-checks
pkgs.cargo-watch
pkgs.ripgrep
pkgs.rust-analyzer
];
};
});
}