In order to expose system specialized outputs, aside from those already handled explicitly, I often found myself doing this ( especially in a repl ):
( builtins.getFlake "foo" ).helper.${system} { ... };
# or
( builtins.getFlake "foo" ).helper { inheret system; ... };
# or
( builtins.getFlake "foo" ).helper system { ... };
I found that having both an attribute for each system and a function was really convenient, and started writing flake outputs with __functor
members to accept system
as an arg.
With this in mind I wrote up a convenient helper lib that I wanted to share
# Example Usage:
# nix-repl> add = curryDefaultSystems ( system:
# { x, y }: builtins.trace system ( x + y )
# )
#
# nix-repl> add { x = 1; y = 2; }
# { __functor = <lambda>;
# aarch64-darwin = 3; trace: aarch64-darwin
# aarch64-linux = 3; trace: aarch64-linux
# i686-linux = 3; trace: i686-linux
# x86_64-darwin = 3; trace: x86_64-darwin
# x86_64-linux = 3; trace: x86_64-linux
# }
#
# nix-repl> ( add { x = 1; y = 2; } ) "x86_64-linux"
# trace: x86_64-linux
# 3
{ utils ? builtins.getFlake "github:numtide/flake-utils" }:
let
inherit (utils.lib) eachSystemMap defaultSystems;
currySystems = supportedSystems: fn: args: let
fas = builtins.functionArgs fn;
callAs = system: fn ( { inherit system; } // args );
callV = system: fn system args;
apply = if ( fas == {} ) then callV else if ( fas ? system ) then callAs
else throw "provided function cannot accept system as an arg";
sysAttrs = eachSystemMap supportedSystems apply;
curried = { __functor = self: system: self.${system}; };
in sysAttrs // curried;
curryDefaultSystems = currySystems defaultSystems;
funkSystems = supportedSystems: fn: let
fas = builtins.functionArgs fn;
callAs = system: fn { inherit system; };
callV = system: fn system;
apply = if ( fas == {} ) then callV else if ( fas ? system ) then callAs
else throw "provided function cannot accept system as an arg";
sysAttrs = eachSystemMap supportedSystems apply;
curried = { __functor = self: system: self.${system}; };
in sysAttrs // curried;
funkDefaultSystems = funkSystems defaultSystems;
in {
inherit currySystems curryDefaultSystems;
inherit funkSystems funkDefaultSystems;
} /* End `attrsets.nix' */
Here are two examples of the pattern “in place” without the polymorphism, these can be rewritten with the helpers:
outputs = {...}: let ... in {
# Wrappers for Pandoc, Makeinfo, and NixOS module options' generators.
# Multiple helper functions in an attribute set, which share
# top-level arguments.
docgen = ( eachDefaultSystemMap ( system: import ./pkgs/docgen {
inherit (nixpkgs.legacyPackages.${system}) pandoc texinfo;
} ) ) // { __functor = docgenSelf: system: docgenSelf.${system}; };
# A single function, which stashes system specific args
# and accepts remaining ones.
tarutil = ( eachDefaultSystemMap ( system:
{ compressor ? nixpkgs.legacyPackages.${system}.gzip }:
import ./pkgs/build-support/trivial/tar.nix {
inherit system compressor;
inherit (nixpkgs.legacyPackages.${system}) gnutar;
} ) ) // { __functor = tarSelf: system: tarSelf.${system}; };
}
In a REPL or legacy Nix files I can use either style depending on what’s more ergonomic:
nix-repl> :a map ( x: x.x86_64-linux ) {
inherit ( builtins.getFlake "foo" ) bar baz;
}
nix-repl> bar {...}
nix-repl> # or
nix-repl> ( builtins.getFlake "foo" ).quux {
inheret (somePkg) system buildInputs version;
}
You may be thinking at this point: “if this guy thinks this is a novel idea, wait till he hears about overlays
”; which is a fair point. I was previously using overlays for cases like this; but counterpoint - this is hideous and is slower to eval:
nix-repl> ( ( builtins.getFlake "nixpkgs" ).overlays = [
( builtins.getFlake "foo" ).overlays.default
] ).quux { inheret (somePkg) builtInputs version; }
So yeah, I’m aware that this isn’t “fixing a missing feature”; but it sure is convenient!