I’m building a flake.nix
repo (called nullkomma), which I would like to reuse in a bunch of other repos (the “caller” repos). I might also let the caller repos add stuff on top.
It’s very important to me that the caller repos have absolutely minimal boilerplate. In my experience, having a bunch of copy/pasted (or git subtree merge
) files causes the caller repos to quickly diverge from the DevOps standard way of doing things (here, nullkomma), requiring manual, painful upgrades.
So ideally, I’d like a flake which looks like this in the caller repos (pseudocode):
{
description = "some-caller-repo";
inputs = {
nullkomma.url = "https://flakehub.com/f/dataheld/nullkomma/0.1.*";
};
outputs = {
nullkomma,
self
} {
nullkomma.do-it-the-normal-way
}
In addition, I need to copy/paste in the .gitignore
and .envrc
(for nix-direnv). I can live with that.
However, using just flakes, my flake.nix
is considerably longer, repetitive and abstraction-leaking (see below).
My question is: Do I need something like flake-parts or are there nix/flake-native ways of increasing reuse/composability that I’m missing?
In general, what’s the idiomatic/recommended way to achieve reusability & composability with nix flakes? Strong opinions welcome .
Here's the `flake.nix` inside the **caller repo**, with comments on what feels repetitive to me.
For better legibility, I’ve cut out most of the stuff and only kept the formatter and check for formatting.
{
description = "some-caller-repo";
inputs = {
# I get that I might need to declare nixpkgs for *this* (caller) flake...
nixpkgs = {
url = "https://flakehub.com/f/NixOS/nixpkgs";
follows = "nullkomma/nixpkgs";
};
flake-utils = {
url = "https://flakehub.com/f/numtide/flake-utils";
follows = "nullkomma/flake-utils";
};
nullkomma.url = "https://flakehub.com/f/dataheld/nullkomma/0.1.*";
};
outputs = {
flake-utils,
nixpkgs,
nullkomma,
self,
...
}: let
universalOutputs = {
schemas = nullkomma.schemas;
};
systemOutputs = flake-utils.lib.eachDefaultSystem (
system: let
pkgs = nixpkgs.legacyPackages.${system};
# this is where it gets repetetive ...
nullkommaDevShell = nullkomma.devShells.${system}.default;
shellInputs = nullkommaDevShell.nativeBuildInputs or nullkommaDevShell.buildInputs or [];
in {
checks = {
formatting = pkgs.stdenv.mkDerivation {
# this is where it gets bad;
# I'm building treefmt-nix upstream in nullkomma,
# but could not think of an easier way to reduce it as a *check* here
# since that will always require self/src,
name = "check-format";
buildInputs = [ pkgs.treefmt ] ++ shellInputs;
phases = [ "checkPhase" "installPhase" ];
# this seems bad to me because that `treefmt --ci` is the command
# is a concern *internal* to nullkomma,
# and should not leak into the caller repo's flake
checkPhase = ''
output=$(treefmt --ci)
'';
# all of this boilerplate already exists, presumably,
# inside treefmt-nix, but I couldn't find a way to reuse it here
# without just repeating all the treefmt-nix calls from nullkomma
installPhase = ''
mkdir -p $out
echo "Test passed" > $out/result
'';
};
};
devShells.default = pkgs.mkShell {
# that seems ok ... but why do I even need it in the first place?
inputsFrom = [ nullkomma.devShells.${system}.default ];
};
formatter = nullkomma.formatter.${system};
}
);
in
universalOutputs // systemOutputs;
}
For completeness sake, this is the upstream nullkomma flake.nix
:
{
description = "nullkomma";
inputs = {
nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/0.2411.*";
# keep-sorted start
fh.url = "https://flakehub.com/f/DeterminateSystems/fh/0.1.*";
flake-checker.url = "https://flakehub.com/f/DeterminateSystems/flake-checker/0.2.*";
flake-iter.url = "https://flakehub.com/f/DeterminateSystems/flake-iter/0.1.*";
flake-schemas.url = "https://flakehub.com/f/DeterminateSystems/flake-schemas/0.1.*";
flake-utils.url = "https://flakehub.com/f/numtide/flake-utils/0.1.*";
format.url = "path:./format";
# keep-sorted end
};
outputs = {
nixpkgs,
# keep-sorted start
fh,
flake-checker,
flake-iter,
flake-schemas,
flake-utils,
format,
# keep-sorted end
self,
...
}: let
universalOutputs = {
schemas = flake-schemas.schemas;
};
systemOutputs = flake-utils.lib.eachDefaultSystem (
system: let
pkgs = nixpkgs.legacyPackages.${system};
in {
checks = {
format-check = format.packages.${system}.format-check self;
flake-check = flake-checker.packages.${system}.default;
};
devShells.default = pkgs.mkShell {
packages = [
# keep-sorted start
fh.packages.${system}.default
flake-checker.packages.${system}.default
flake-iter.packages.${system}.default
format.packages.${system}.default
pkgs.git
pkgs.gnumake
pkgs.nixd
# keep-sorted end
];
};
formatter = format.packages.${system}.default;
}
);
in
universalOutputs // systemOutputs;
}