From 8f33b7f9313ebf600f4efd7c650e15e05a1de134 Mon Sep 17 00:00:00 2001 From: isabel Date: Thu, 9 Jan 2025 16:25:09 +0000 Subject: [PATCH] feat(content): i'm not mad, i'm disapointed --- content/im-not-mad-im-disapointed.md | 279 +++++++++++++++++++++++++++ 1 file changed, 279 insertions(+) create mode 100644 content/im-not-mad-im-disapointed.md diff --git a/content/im-not-mad-im-disapointed.md b/content/im-not-mad-im-disapointed.md new file mode 100644 index 0000000..59dac40 --- /dev/null +++ b/content/im-not-mad-im-disapointed.md @@ -0,0 +1,279 @@ +--- +title: I'm not mad, I'm disappointed +description: A short rant about the state of your nix code +date: 09/01/2025 +tags: + - meta + - nix +--- + +## Introduction + +When I see another friend of mine start using nix my eyes glisten with the +knowledge there is still hope for their lost soul yet. But then I observe their +copy-pasted code and see all the bad practices everywhere. *Sighhhh*. Time to +explain to you everything wrong about what you're doing in your nix code. Make +sure to pay attention and make some notes because I don't want to explain it +again. + +### `system` and `lib.nixosSystem` + +Often I see people passing the `system` argument to `lib.nixosSystem`. However, +you should not do this! This is beacuse "legacy input" and will straight up be +ignored beacuse of your autogenerated `hardware-configuration.nix` file, which +will set system for you. So to rectify this we can instead set +`nixpkgs.hostPlatform` to the desired system. + +```nix +{ + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + + outputs = inputs: { + nixosConfigurations = { + # this host contains our `system` antipattern, which we want to avoid + myBadHost = inputs.nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ ./hardware-configuration.nix ]; + }; + + # this is our fixed host, notice how it uses `nixpkgs.hostPlatform` + myGoodHost = inputs.nixpkgs.lib.nixosSystem { + modules = [ + ./hardware-configuration.nix + { nixpkgs.hostPlatform = "x86_64-linux"; } + ]; + }; + }; + }; +} +``` + +For further reading about this topic please consult +[nixpkgs@332e603/flake.nix#L54](https://github.com/NixOS/nixpkgs/blob/332e6030634ce7701496564d4c70ae8209919931/flake.nix#L54). + +To such I am usually asked "But I want to be able to track other nixpkgs +revisions or inputs, then don't I have to? Or set a specialArg or something?". +NO! Almost never do you want to use specialArgs. SpecialArgs are great but +often overused for things that the module system was designed to do better. Now +to answer the main question, you can still do that. Allow me to introduce you +to `pkgs.stdenv.hostPlatform.system` and its alias `pkgs.system`. So now we can +do something more like the following: + +```nix +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + # notice our new nixpkgs input that is pinned to a stable version + nixpkgs-stable.url = "github:NixOS/nixpkgs/nixos-24.05"; + }; + + # v this here is destructuring the inputs, and the `@` will allow us to refer to `inputs` as a whole + outputs = { nixpkgs, ... } @ inputs: { + nixosConfigurations.mySystem = inputs.nixpkgs.lib.nixosSystem { + # notice we are now we are now passing the inputs as a specialArg + specialArgs = { + inherit inputs; + + # do NOT add `system` as a specialArg + system = "x86_64-linux"; + }; + + modules = [ + # instead notice how we can use `pkgs.system` to refer to the system output from nixpkgs 24.05 + # and we didn't need to add `system` as a specialArg + ({ pkgs, inputs, ... }: { + environment.systemPackages = [ + inputs.nixpkgs-stable.legacyPackages.${pkgs.system}.nil + ]; + + # this didn't change from our previous example + nixpkgs.hostPlatform = "x86_64-linux"; + }) + ]; + }; + }; +} +``` + +Overall, `system` being an input to `lib.nixosSystem` was so irritating me so +much that I made several bluesky posts complaining about it. And went on to +make [easy-hosts](https://github.com/isabelroses/easy-hosts) as an abstraction +around making systems for your systems that will prevent you from making these +mistakes, and will force you to use the module system more. + +### system dependent overlays + +I sadly am not kidding. These are real. I have seen people do this. + +First off, overlays should not be consumed with a system like +`overlays.x86_64-linux` or `overlays.x86_64-linux.default`. Please stop +yourself if you ever get here. Instead, overlays should be consumed like +`source.overlays.name`, to archive that consider this template: + +```nix +{ + # inputs if you need them + + outputs = inputs: { + overlays = let + pkgs = inputs.nixpkgs.legacyPackages.x86_64-linux; + in { + # these are all incorrect variations of the same code, that i have seen + # or would be generate by some kind of code + x86_64-linux = final: prev: { + hello = pkgs.callPackage ./default.nix { }; + }; + + x86_64-linux.default = final: prev: { + hello = pkgs.callPackage ./default.nix { }; + }; + + default.x86_64-linux = final: prev: { + hello = pkgs.callPackage ./default.nix { }; + }; + + # notice in this example we removed any refrence to `pkgs` and insted we use `prev` + # this means that the overlay is not system dependent + default = final: prev: { + hello = prev.callPackage ./default.nix { }; + }; + }; + + # any additional outputs go here + }; +} +``` + +Notice how here I used `prev` instead of `pkgs` or anything alike because we do +not need to know what the system is here. And if you do wish to refer to the +system recall how earlier I spoke of `pkgs.system` well `prev` like `pkgs` will +also expose `system`, so you can use `prev.system`. + +### Stop importing nixpkgs + +All the time I see people writing some form of the following: + +```nix +{ + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + + outputs = { nixpkgs, ... }: let + pkgs = import nixpkgs { }; + # or + pkgs = import nixpkgs { system = "aarch64-linux"; }; + in { + # imagine the rest of the file + }; +} +``` + +Both of these `pkgs` are bad. Importing `nixpkgs` without a system will use +`builtins.currentSystem` which is impure, this means that it needs to use +information that may not be reproducable like your system type. And the second +`pkgs` is explicit importing `nixpkgs` with `system`, which is not as +preferable but not strictly speaking "bad". Instead, consider writing: + +```nix +{ + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + + outputs = { nixpkgs, ... }: let + system = "aarch64-linux"; + pkgs = nixpkgs.legacyPackages.${system}; + in { + packages.${system}.default = pkgs.callPackage ./default.nix {}; + }; +} +``` + +You may also recall my earlier posts titled [Experimenting with +nix](/blog/experimenting-with-nix-7) and or [Dev +dependencies](https://isabelroses.com/blog/nix-shells-8), where I refer to a +function called `forAllSystems` to improve upon this pattern further. For the +sake of completion I will bring one of those examples back and use it to +generate the system based outputs of packages. + +```nix +{ + inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + + outputs = { nixpkgs, ... }: let + forAllSystems = + function: + nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed ( + system: function nixpkgs.legacyPackages.${system} + ); + in { + packages = forAllSystems (pkgs: { + default = pkgs.callPackage ./default.nix { }; + }); + }; +} +``` + +You may however have noticed this won't work if you need to access unfree or +broken packages. In which case I would recommend modifying the function to +align with the following: + +```nix +forAllSystems = + function: + nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed ( + system: function ( + import nixpkgs { + inherit system; + config.allowUnfree = true; + } + ) + ); +``` + +The key difference in the above example is that we now are importing `nixpkgs` +and as such we now have access to `nixpkgs.config` and `nixpkgs.overlays` if +you so wish to use those. + +This brings me onto my final point, + +### Stop using flake-utils + +flake-utils is often an extra dependency added for no good reason. The main +purpose for using it more modernly is its overrideable systems, they do this +through an input from the [nix-systems](https://github.com/nix-systems) GitHub +org. But more typically no one uses it for that, in fact most people use +flake-utils for its ability to create system dependent outputs. This is what +leads to the issues with `system` dependent `overlays`. But recall, I've +already covered all of what you need to make your own flake-utils, through the +usage of the `forAllSystems` function. + +However, some of you may still want those overrideable systems. In which case +please consider writing this flake like this instead: + +```nix +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + systems.url = "github:nix-systems/default"; + }; + + outputs = { nixpkgs, systems, ... }: let + forAllSystems = + function: + nixpkgs.lib.genAttrs (import systems) ( + system: function nixpkgs.legacyPackages.${system} + ); + in { + packages = forAllSystems (pkgs: { + default = pkgs.callPackage ./default.nix { }; + }); + + overlays.default = final: prev: { + hello = prev.callPackage ./default.nix { }; + }; + }; +} +``` + +## Conclusion + +I was never mad at you, I was just disappointed. But now you know better, so go +forth and write better nix code. -- 2.51.2