120 lines
4.7 KiB
Markdown
120 lines
4.7 KiB
Markdown
+++
|
||
date = '2025-04-06T14:49:03+02:00'
|
||
draft = false
|
||
title = 'Why I do not use flake-utils'
|
||
tags = ['nix']
|
||
+++
|
||
|
||
I have been using [Nix](https://nixos.org/) for a while now. Around a year ago, I switched everything from the [servicepoint](https://git.berlin.ccc.de/servicepoint/servicepoint) library to my [machine configuration](https://git.berlin.ccc.de/vinzenz/nixos-configuration) over to flakes.
|
||
|
||
For me the biggest advantage flakes bring is not additional functionality. Instead, they are an easier and semi-standardized way to do what you could before.
|
||
|
||
When learning flakes, you often see [flake-utils]( https://github.com/numtide/flake-utils) being used. With it, you can shorten your flakes by not having to specify everything per system.
|
||
|
||
_Edit_: The patterns described here as well as `flake-parts` are also compared in [Practical Nix flake anatomy](https://vtimofeenko.com/posts/practical-nix-flake-anatomy-a-guided-tour-of-flake.nix/#flake-utils-flakeparts-etc).
|
||
|
||
## Without anything
|
||
|
||
```nix
|
||
{
|
||
description = "Flake utils demo - without flakes";
|
||
|
||
outputs = { self, nixpkgs, flake-utils }:
|
||
{
|
||
packages."x86_64-linux" = rec {
|
||
hello = pkgs.hello;
|
||
default = hello;
|
||
}
|
||
packages."aarch64-linux" = rec {
|
||
hello = pkgs.hello;
|
||
default = hello;
|
||
}
|
||
|
||
# more systems ...
|
||
}
|
||
}
|
||
```
|
||
|
||
## With flake-utils
|
||
|
||
```nix
|
||
{
|
||
description = "Flake utils demo";
|
||
|
||
inputs.flake-utils.url = "github:numtide/flake-utils";
|
||
|
||
outputs = { self, nixpkgs, flake-utils }:
|
||
flake-utils.lib.eachDefaultSystem (system:
|
||
let pkgs = nixpkgs.legacyPackages.${system}; in
|
||
{
|
||
packages = rec {
|
||
hello = pkgs.hello;
|
||
default = hello;
|
||
};
|
||
}
|
||
);
|
||
}
|
||
```
|
||
|
||
## With function in flake
|
||
|
||
To make a long story short, here is what I usually do instead:
|
||
|
||
```nix
|
||
{
|
||
description = "forAllSystems demo";
|
||
|
||
outputs = { self, nixpkgs }:
|
||
let
|
||
supported-systems = [ # [1]
|
||
"x86_64-linux"
|
||
"aarch64-linux"
|
||
];
|
||
forAllSystems =
|
||
f:
|
||
nixpkgs.lib.genAttrs supported-systems /* [2] */ (
|
||
system:
|
||
f rec {
|
||
pkgs = nixpkgs.legacyPackages.${system}; # [3]
|
||
inherit system;
|
||
}
|
||
);
|
||
in
|
||
{
|
||
packages = forAllSystems ({ pkgs, ... }: rec {
|
||
hello = pkgs.hello;
|
||
default = default;
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
That’s definitely more code! - Yes, but it also includes more information in the flake, while getting rid of an external dependency.
|
||
While more code can be intimidating for beginners, it actually helps remove a barrier to understanding how the flake works in this case.
|
||
For me, it wasn’t a problem to ignore boilerplate like this at first, slowly learning more language features until I finally understood everything.
|
||
|
||
At **[1]**, the supported systems are specified. I personally use `x86_64-linux` and `aarch64-linux`, but I also usually support `x86_64-darwin` and `aarch64-darwin` in public projects.
|
||
If you want to support any system, you can use [`nixpkgs.lib.system.flake-exposed`](https://github.com/NixOS/nixpkgs/blob/374e6bcc403e02a35e07b650463c01a52b13a7c8/lib/systems/default.nix#L58) at **[2]** instead of defining your own list.
|
||
|
||
### Example variations
|
||
|
||
Because the definition is right inside the flake, you can tweak what gets passed to the function.
|
||
By doing that, you have to explicitly add the new identifier to each part that uses it, instead of having a global `let` binding that is implicitly used.
|
||
`system` being available here is another bonus, as otherwise this would require duplicate `let`s everywhere.
|
||
|
||
An example for how to do it is already right there: at **[3]**, `pkgs` is provided.
|
||
Some real-world usages I wrote or encountered:
|
||
|
||
- [servicepoint-life flake](https://git.berlin.ccc.de/vinzenz/servicepoint-life/src/commit/5f5e10d39f09f4d60b4301e76f0158636afee6d1/flake.nix): passes in initialized `naersk` instance and a shorthand to refer to packages defined in the flake itself
|
||
- [RedoxOS development flake](https://gitlab.redox-os.org/redox-os/redox/-/blob/cb34b9bd862f46729c0082c37a41782a3b1319c3/flake.nix#L38): uses it to pass a custom rust-toolchain
|
||
- [my NixOS flake](https://git.berlin.ccc.de/vinzenz/nixos-configuration/src/commit/3d27f554015ece713fa22fe829d711b430d6bbda/flake.nix): uses something similar for per-host config
|
||
|
||
Another possible tweak: You may want to define separate supported systems for each output.
|
||
This is useful, for example, if the target environment you're developing for cannot support a development shell.
|
||
|
||
## Conclusion
|
||
|
||
For me, the trade-offs are worth it, as they provide greater transparency and control over the flake configuration.
|
||
For you, this may be different, in which case keep using it!
|
||
Ultimately, it's also matter of personal preference.
|