Cross-system Flakes: What's your favorite `forAllSystems`?

Sorry if this is the wrong category but I’m posting this as a discussion rather than help. I’m just interested in peoples opinions.
I have seen multiple different implementations of forAllSystems used in flakes meant to run on different OSs/Architectures. Here are a few:

forAllSystems = f:
      builtins.listToAttrs (map (system: {
          name = system;
          value = f system;
        })
        systems);
# or
forAllSystems = nixpkgs.lib.genAttrs systems;

There is, of course, the flake-utils implementation:

 eachSystem = eachSystemOp (
    # Merge outputs for each system.
    f: attrs: system:
    let
      ret = f system;
    in
    builtins.foldl' (
      attrs: key:
      attrs
      // {
        ${key} = (attrs.${key} or { }) // {
          ${system} = ret.${key};
        };
      }
    ) attrs (builtins.attrNames ret)
  );

Some people create pkgs directly (e.g., here):

forAllSystems = f: nixpkgs.lib.genAttrs allSystems (system: f {
        pkgs = nixpkgs.legacyPackages.${system};
      })

Do you know any other versions? What is your favorite and why?

3 Likes

I just use

      supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" ];
      forAllSystems = f:
        nixpkgs.lib.genAttrs supportedSystems (system: f system);

because it’s short and basic and doesn’t need another input just for this and was what was in the template I copied initially and has just stayed there since without having ever been thought about again until now

7 Likes

I use the partial application form:

forAllSystems = with nixpkgs; (lib.genAttrs lib.systems.flakeExposed);
3 Likes

Fair, thank you for thinking about it now! I’ve been trying to wrap my head around how this differs from forAllSystems = nixpkgs.lib.genAttrs supportedSystems; and I think it is an eta-reduction of your code.

A much simplified case in nix would be:

nix-repl> concatA = x: x + "A"

nix-repl> concatA2 = (f: concatA) (g: f g)

nix-repl> concatA "B"
"BA"

nix-repl> concatA2 "B"
"BA"

Or slightly closer to the code

nix-repl> forAll = inputs.nixpkgs.lib.genAttrs ["a" "b"]

nix-repl> forAll (x: x)
{
  a = "a";
  b = "b";
}

nix-repl> forAll2 = f: forAll (s: f s)

nix-repl> forAll2 (x: x)
{
  a = "a";
  b = "b";
}

I’m new to functional programming so this is fascinating.

Interesting! TIL about flakeExposed. Thank you!

1 Like

This article on the topic was also nice.

3 Likes

I prefer this one.

1 Like

In a flake that doesn’t rely on nixpkgs, this is surely the best option. Adding in nixpkgs for this would be a +50 MB dependency.

flake-utils is just bad in terms of not-stopping people from adding in system where it shouldn’t be added, a common footgun. IMO that repo should be archived considering they already made a better replacement (blueprint).

2 Likes

Interesting. I didn’t know about blueprint and hadn’t considered the dependency on Nixpkgs. Is there a way to import just nixpkgs built in’s without the whole 50mb?

There’s GitHub - nix-community/nixpkgs.lib: nixpkgs lib for cheap instantiation [maintainer=@github-action] (with initial help from @blaggacao) to lighten the load but it’s still a new flake for a couple functions…

1 Like

Thank you! Yeah, probably not worth to import just for that but nonetheless can help with inspiration writing nix. :slight_smile:
In the meantime I found another version using builtins.getAttr

    forAllSystems = f:
      nixpkgs.lib.genAttrs systems (
        system:
          f (builtins.getAttr system nixpkgs.legacyPackages)
      );

Or without nixpkgs dependency in the generation

    forAllSystems = f:
      builtins.listToAttrs (map (system: {
          name = system;
          value = f (builtins.getAttr system nixpkgs.legacyPackages);
        })
        systems);

This is a verbose way to write nixpkgs.legacyPackages.${system}.

1 Like

I like this for simple situations.

packages = builtins.mapAttrs (system: pkgs: {
...
}) inputs.nixpkgs.legacyPackages;
2 Likes

default.nix and with import <nixpkgs> { }; ... are pretty good

1 Like