Skip to content

eachSystem in flake example makes it harder to understand #505

@trespaul

Description

@trespaul

When trying to adapt the current flake example from the README to their own project, readers have to parse out the custom (unusual?) eachSystem as well as other parts of the example that are specific to a particular usage. The extra input dependency is also unnecessary, and those unfamiliar with it (e.g., me) may have to go look up the systems repo just to figure out what the function they just pasted into their code is doing.

eachSystem is not the simplest and requires a bit of thinking to understand for those who haven't done much nix-lang wrangling or higher-ordered function algebra. (Even for the more experienced, it's not clear what benefit it is to have to pass a pkgs parameter when the correct nixpkgs can be deduced from the system.)

As demonstration, to mentally parse the value of the formatter attribute, one has to think through the following:

nixpkgs.lib.genAttrs [ "x86_64-linux" "etc" ] (
  system:
  (
    pkgs:
    (nixpkgs.lib.genAttrs [ "x86_64-linux" "etc" ] (
      system: (pkgs: treefmt-nix.lib.evalModule pkgs ./treefmt.nix) nixpkgs.legacyPackages.${system}
    )).${pkgs.system}.config.build.wrapper
  )
    nixpkgs.legacyPackages.${system}
);

to come to the conclusion that the wrapper is evaluated with pkgs and the config, on which the formatter and the checker can then be accessed.

I may not be the best nixer in the world, but I've been using nix for several years now and it still took me at least half an hour of rotating functions in my mind to adapt this to my own flake.

(Not to mention that to get the system's treefmtEval, the example gets system from pkgs — which, by the way, gives the warning that 'system' has been renamed to/replaced by 'stdenv.hostPlatform.system' — which is gotten from system, which is already passed as a parameter!)

Describe the solution you'd like

For each individual system, the necessary code simplifies to

(treefmt-nix.lib.evalModule nixpkgs.legacyPackages.${system} ./treefmt.nix
).config.build.wrapper;

With this, it's clear that what is necessary to produce the formatter is to apply treefmt-nix.lib.evalModule to the appropriate pkgs and the repo languages config, which results in an attrset on which .config.build.wrapper is the wrapped formatter and .config.build.check is the checker. Having to make a wrapper where the inner argument needs an outer variable is still not the clearest UX, but that makes it even more necessary for the example to be as clear as possible.

For example:

{
  inputs.treefmt-nix.url = "github:numtide/treefmt-nix";

  outputs = { self, nixpkgs, systems, treefmt-nix }:
    let
      # substitute your preferred way of handling `system`
      eachSystem = nixpkgs.lib.genAttrs [ "x86_64-linux" "etc" ];

      # for each system, evaluate treefmt-nix with pkgs and your configuration
      treefmtEval = eachSystem (system:
        treefmt-nix.lib.evalModule nixpkgs.legacyPackages.${system} ./treefmt.nix
      );
    in
    {
      # for `nix fmt`, use `.config.build.wrapper`
      formatter = eachSystem (system:
        treefmtEval.${system}.config.build.wrapper
      );

      # for `nix flake check`, use `.config.build.check` and apply it to the flake
      checks = eachSystem (system: {
        formatting = treefmtEval.${system}.config.build.check self;
      });
    };
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions