This document is to propose a less awkward way to do flake evaluation.
- Embedded system in attr paths (e.g.
packages.x86_64-linux.default
) - Hard to combine non-system specific items with system specific items (e.g. overlays vs packages)
- "Diamond dependency issues" when bringing libraries from many flakes (each has an opinionated dependency tree)
- Make overlays a hard requirement for composition
- Split up outputs, some specific to a system, and other are more generalized (e.g. overlays, NixOS modules)
{
description = "Example better flake";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/unstable";
};
overlays = {
default = import ./nix/overlay.nix;
};
# Attrset of modules
nixosModules = import ./nixos;
nixosConfigurations = { self }: {
default = self.inputs.nixpkgs.lib.nixosSystem {
modules = [ self.nixosModules.default ];
};
};
pkgsConfig = { allowUnfree = true; cudaSupport = true; };
pkgsOverlays = { self, overlays }: [
self.inputs.foo.overlays.default
self.overlays.default
];
# Optional explicit import of nixpkgs, allows for further customization
pkgsForSystem = { self, system }:
import self.inputs.nixpkgs {
inherit system;
config = self.pkgsConfig;
overlays = self.pkgsOverlays;
};
outputs = { pkgs, ... }: with pkgs; {
formatter = nixpkgs-fmt;
checks.default = myPackage;
packages.default = myPackage;
# Instead of using { type = app; program = <store path>;}, just use <store path>
app.default = myPackage;
app.other = "${myPackage}/bin/other-bin";
devShells.default = mkShell {
inputsFrom = [
myPackage
];
nativeBuildInputs = [
cmake-lint
] ++ pkgs.lib.optionals stdenv.isLinux [
gdb
];
};
};
# These can just import a whole directory from a path
templates.default = ./templates/default;
}
{
description = "Minimal better flake";
inputs.nixpkgs.url = "github:nixos/nixpkgs/unstable";
# Overlay defines myPackage
overlays.default = final: prev: {
myPackage = final.callPackage ./package.nix { };
};
outputs = { pkgs }: with pkgs; {
packages.default = myPackage;
devShells.default = mkShell { inputsFrom = [ myPackage ]; };
};
}
The evaluation of these results would roughly be:
# The ? here is that these could be optionally imported if defined
flake = let
# Initial import with unknown attrs defined, inputs would also need to be resolved as part of this "import"
raw = import ./flake.nix;
# Evaluate enough to get a nixpkgs import
pkgsConfig = if raw ? pkgsConfig then pkgsConfig { self = raw; } else { };
pkgsOverlays = if raw ? pkgsOverlays then pkgsOverlays { self = raw; } else [ ];
# Replace pkgsConfig and pkgsOverlays functions with their arguments applied results
callRawFlake = callPackageWith (raw // { inherit pkgsConfig pkgsOverlays; self = raw; });
mkFlake = {
self,
pkgsConfig,
pkgsOverlays,
# Could be defined by user, but if not, do an expected import
pkgsForSystem ? { self, system }: import self.inputs.nixpkgs { config = pkgsConfig; overlays = pkgsOverlays;);
}: let
pkgs = pkgsForSystem { inherit self; system = builtins.getCurrentSystem; };
# TODO: nixosConfigurations and other attrs would optinally need to be called if they're functions and present
in raw.outputs pkgs // { inherit pkgsConfig pkgsOverlays; };
# Create self fixed point, and pass as flake output
final = raw // (callRawFlake mkFlake) // { self = final; };
in
final
To change the system, usage of the --system argument would be made
nix eval --system aarch64-linux .#myPackages.drvPath
- Embedded system in attr paths (e.g.
packages.x86_64-linux.default
)- Make system selection implicit in the structure, passed through --system flag
- Hard to combine non-system specific items with system specific items (e.g. overlays vs packages)
- non-system specific items now have their own dedicated top-level attribute.
- "Diamond dependency issues" when bringing libraries from many flakes (each has an opinionated dependency tree)
- Usage of overlays to give a single pkgs instance avoids the "1,000 glibc" and diamnond dependency issues.