override-utilsThis project implements an ergonomic interface for overriding Nixpkgs implemented in pure Nix. I also wrote a blog post which provides more context about this project: Ergonomic overrides for Nixpkgs.
The inspiration for this project is a blog post I wrote: The hard part of type-checking Nix. In that post I noted that one major source of usability issues was Nixpkgs:
The real issue with Nix isn’t the lack of a type checker. The absence of a type-checker is problematic, but in my view this is a symptom of a larger issue.
The fundamental problem that plagues all type-checking attempts for Nix is that nobody actually uses Nix the language at any significant scale. Instead, the community has adopted two sub-languages embedded within Nix for programming “in the large”:
Nixpkgs overlays
This is an embedded language that simulates object-oriented programming with inheritance / late binding / dynamic scope (depending on how you think about it)
NixOS modules
This is an embedded language that roughly emulates Terraform
This project addresses part of that problem by providing a simpler way to create Nixpkgs overrides/overlays with fewer footguns and better error messages.
For example, instead of writing this:
final: prev: {
nix-serve-ng = prev.nix-serve-ng.overrideAttrs (old: {
patches = (old.patches or []) ++ [ ./lix-compat.patch ];
});
}
… you can now write this:
final: override {
nix-serve-ng.overrideAttrs.patches = append [ ./lix-compat.patch ];
}
Example flake usage:
{ inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
inputs.override-utils.url = "github:Gabriella439/override-utils";
outputs = { nixpkgs, override-utils, ... }:
let
utils = override-utils.lib;
overlay = final: utils.override {
nix-serve-ng.overrideAttrs.patches =
utils.append [ ./lix-compat.patch ];
};
inherit (nixpkgs) lib;
in
{ packages = lib.genAttrs lib.systems.flakeExposed (system:
nixpkgs.legacyPackages."${system}".appendOverlays [ overlay ]
);
};
}
This repository provides a default.nix which uses flake-compat to export the
same outputs as as flake.nix.
You can create an override function using override. For example, this
creates a new function that sets the foo attribute to 2:
override {
foo = set 2;
}
The result of override is a function that you can directly invoke on an
existing attribute set, like this:
nix-repl> :print override { foo = set 2; } { bar = 3; }
{ bar = 3; foo = 2; }
You need to specify an operation (like set) for each attribute you modify. If
you omit the operation:
override {
foo = 1;
}
… then you will get an error message like this one:
nix-repl> :print override { foo = 1; } { }
…
override-utils: No operation specified
You wrote something like this:
override {
foo =
1;
}
… but you need to specify an operation like this:
override {
foo =
set 1;
━━━
}
Overrides can be nested:
override {
foo.bar = set 2;
}
… and they won't clobber existing parent attributes if already present:
nix-repl> :print override { foo.bar = set 2; } { foo.baz = 3; }
{ foo = { bar = 2; baz = 3; }; }
Without override, you would have to write something like this to avoid
clobbering the parent attribute:
prev: {
foo = prev.foo // { bar = 2; };
}
… but then that breaks if the foo attribute does not already exist,
whereas override handles the parent attribute's absence gracefully:
nix-repl> :print override { foo.bar = set 2; } { }
{ foo = { bar = 2; }; }
You can modify an existing value using modify, which is the most general
interface for modifying values:
nix-repl> :print override { foo = modify ({ input }: input + 3); } { foo = 2; }
{ foo = 5; }
The modify operation lets you specify a default value if the attribute is not
present:
nix-repl> :print override { foo = modify ({ input ? 0 }: input + 3); } { }
{ foo = 3; }
nix-repl> :print override { foo = modify ({ input ? 0 }: input + 3); } { foo = 2; }
{ foo = 5; }
However, you'll more commonly use a helper operation, like add:
nix-repl> :print override { foo = add 3; } { }
{ foo = 3; }
nix-repl> :print override { foo = add 3; } { foo = 2; }
{ foo = 5; }
For the full list of supported operations, see the Operations section below.
You can transform each element in a list using the "*" attribute:
nix-repl> :print override { foo."*" = add 1; } { foo = [ 2 3 5 ]; }
{ foo = [ 3 4 6 ]; }
nix-repl> :print override { foo."*" = add 1; } { }
{ foo = [ ]; }
… or you can transform a specific element in a list using the element's index:
nix-repl> :print override { foo."0" = set 7; } { foo = [ 2 3 5 ]; }
{ foo = [ 7 3 5 ]; }
Similarly, you can transform every attribute in an attribute set using the "<name>" attribute:
nix-repl> :print override { "<name>" = add 1; } { x = 2; y = 3; }
{ x = 3; y = 4; }
Finally, all of the .override* methods in Nixpkgs are supported, meaning
that you can write something like this:
override {
nix-serve-ng.overrideAttrs.patches = append [ ./lix-compat.patch ];
}
… and that is the same thing as if you had written:
prev: {
nix-serve-ng = prev.nix-serve-ng.overrideAttrs (old: {
patches = (old.patches or []) + [ ./lix-compat.patch ];
});
}
You can combine and nest all of these features arbitrarily. For example:
nix-repl> old = { projects = [ { } { name = "OS"; } { name = "pkgs"; } ]; }
nix-repl> :print override { projects."*".name = prefix "Nix"; } old
{ projects = [ { name = "Nix"; } { name = "NixOS"; } { name = "Nixpkgs"; } ]; }
Or if you want something more realistic, here's an example overlay from one of
my own projects that patches servant-multipart-client.cabal just for GHCJS:
final: override {
haskell.packages.ghcjs.override.overrides = set (hfinal: override {
servant-multipart-client.overrideCabal.postPatch = append
''
sed -i 's/ .*<0.19//' servant-multipart-client.cabal
'';
});
}
To do the same thing without this package you would have to write:
final: prev: {
haskell = prev.haskell // {
packages = prev.haskell.packages // {
ghcjs = prev.haskell.packages.ghcjs.override (old: {
overrides = hfinal: hprev: {
servant-multipart-client =
final.haskell.lib.overrideCabal
hprev.servant-multipart-client
(old: {
postPatch = (old.postPatch or "") +
''
sed -i 's/ .*<0.19//' servant-multipart-client.cabal
'';
});
};
});
};
};
}
If you want to create an overlay instead of an override function, the only difference is that you write:
final: override {
…
}
In other words, you prefix overrides with a function argument representing
the final attribute set.
For example, if you were to write something like this:
final: override {
x = add 1;
y = final.x;
}
… that would generate the same overlay as this:
final: prev: {
x = prev.x + 1;
y = final.x;
}
add
Add a number to the existing value (or to 0 if missing):
nix-repl> :print override { foo = add 2; } { }
{ foo = 2; }
nix-repl> :print override { foo = add 3; } { foo = 2; }
{ foo = 5; }
subtract
Subtract a number from the existing value (or from 0 if missing)
nix-repl> :print override { foo = subtract 3; } { }
{ foo = -3; }
nix-repl> :print override { foo = subtract 3; } { foo = 2; }
{ foo = -1; }
prepend
Prepend a list before the existing value (or before [] if missing)
nix-repl> :print override { foo = prepend [ 2 3 ]; } { }
{ foo = [ 3 5 ]; }
nix-repl> :print override { foo = prepend [ 2 3 ]; } { foo = [ 5 ]; }
{ foo = [ 3 5 2 ]; }
append
Append a list after the existing value (or after [] if missing)
nix-repl> :print override { foo = append [ 3 5 ]; } { }
{ foo = [ 3 5 ]; }
nix-repl> :print override { foo = append [ 3 5 ]; } { foo = [ 2 ]; }
{ foo = [ 2 3 5 ]; }
prefix
Add a string prefix before the existing value (or before "" if missing)
nix-repl> :print override { foo = prefix "Nix"; } { }
{ foo = "Nix"; }
nix-repl> :print override { foo = prefix "Nix"; } { foo = "OS"; }
{ foo = "NixOS"; }
suffix
Add a string suffix after the existing value (or after "" if missing)
nix-repl> :print override { foo = suffix "OS"; } { }
{ foo = "OS"; }
nix-repl> :print override { foo = suffix "OS"; } { foo = "Nix"; }
{ foo = "NixOS"; }
prefixWith
Add a string prefix before the existing value (or before "" if missing) with
a separator:
nix-repl> :print override { foo = prefixWith "." "before"; } { }
{ foo = "before"; }
nix-repl> :print override { foo = prefixWith "." "before"; } { foo = "after"; }
{ foo = "before.after"; }
suffixWith
Add a string suffix after the existing value (or after "" if missing) with a
separator:
nix-repl> :print override { foo = suffixWith "." "after"; } { }
{ foo = "after"; }
nix-repl> :print override { foo = suffixWith "." "after"; } { foo = "before"; }
{ foo = "before.after"; }
prefixWords
Add words before the existing value (or before "" if missing), inserting a
space in between
prefixWords = prefixWith " "
nix-repl> override { foo = prefixWords "hello"; } { }
{ foo = "hello"; }
nix-repl> override { foo = prefixWords "hello"; } { foo = "world"; }
{ foo = "hello world"; }
suffixWords
Add words after the existing value (or after "" if missing), inserting a
space in between
suffixWords = suffixWith " "
nix-repl> override { foo = suffixWords "world"; } { }
{ foo = "world"; }
nix-repl> override { foo = suffixWords "world"; } { foo = "hello"; }
{ foo = "hello world"; }
prefixLines
Prepend lines before the existing value (or before "" if missing)
prefixLines = prefixWith "\n"
nix-repl> :print override { foo = prefixLines "hello"; } { }
{ foo = "hello"; }
nix-repl> :print override { foo = prefixLines "hello"; } { foo = "world"; }
{ foo = "hello\nworld"; }
suffixLines
Append lines after the existing value (or after "" if missing), inserting
a newline in between
suffixLines = suffixWith "\n"
nix-repl> :print override { foo = suffixLines "world"; } { }
{ foo = "world"; }
nix-repl> :print override { foo = suffixLines "world"; } { foo = "hello"; }
{ foo = "hello\nworld"; }
7 commits
1 commits
Nix
100.0%
override-utilsThis project implements an ergonomic interface for overriding Nixpkgs implemented in pure Nix. I also wrote a blog post which provides more context about this project: Ergonomic overrides for Nixpkgs.
The inspiration for this project is a blog post I wrote: The hard part of type-checking Nix. In that post I noted that one major source of usability issues was Nixpkgs:
The real issue with Nix isn’t the lack of a type checker. The absence of a type-checker is problematic, but in my view this is a symptom of a larger issue.
The fundamental problem that plagues all type-checking attempts for Nix is that nobody actually uses Nix the language at any significant scale. Instead, the community has adopted two sub-languages embedded within Nix for programming “in the large”:
Nixpkgs overlays
This is an embedded language that simulates object-oriented programming with inheritance / late binding / dynamic scope (depending on how you think about it)
NixOS modules
This is an embedded language that roughly emulates Terraform
This project addresses part of that problem by providing a simpler way to create Nixpkgs overrides/overlays with fewer footguns and better error messages.
For example, instead of writing this:
final: prev: {
nix-serve-ng = prev.nix-serve-ng.overrideAttrs (old: {
patches = (old.patches or []) ++ [ ./lix-compat.patch ];
});
}
… you can now write this:
final: override {
nix-serve-ng.overrideAttrs.patches = append [ ./lix-compat.patch ];
}
Example flake usage:
{ inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
inputs.override-utils.url = "github:Gabriella439/override-utils";
outputs = { nixpkgs, override-utils, ... }:
let
utils = override-utils.lib;
overlay = final: utils.override {
nix-serve-ng.overrideAttrs.patches =
utils.append [ ./lix-compat.patch ];
};
inherit (nixpkgs) lib;
in
{ packages = lib.genAttrs lib.systems.flakeExposed (system:
nixpkgs.legacyPackages."${system}".appendOverlays [ overlay ]
);
};
}
This repository provides a default.nix which uses flake-compat to export the
same outputs as as flake.nix.
You can create an override function using override. For example, this
creates a new function that sets the foo attribute to 2:
override {
foo = set 2;
}
The result of override is a function that you can directly invoke on an
existing attribute set, like this:
nix-repl> :print override { foo = set 2; } { bar = 3; }
{ bar = 3; foo = 2; }
You need to specify an operation (like set) for each attribute you modify. If
you omit the operation:
override {
foo = 1;
}
… then you will get an error message like this one:
nix-repl> :print override { foo = 1; } { }
…
override-utils: No operation specified
You wrote something like this:
override {
foo =
1;
}
… but you need to specify an operation like this:
override {
foo =
set 1;
━━━
}
Overrides can be nested:
override {
foo.bar = set 2;
}
… and they won't clobber existing parent attributes if already present:
nix-repl> :print override { foo.bar = set 2; } { foo.baz = 3; }
{ foo = { bar = 2; baz = 3; }; }
Without override, you would have to write something like this to avoid
clobbering the parent attribute:
prev: {
foo = prev.foo // { bar = 2; };
}
… but then that breaks if the foo attribute does not already exist,
whereas override handles the parent attribute's absence gracefully:
nix-repl> :print override { foo.bar = set 2; } { }
{ foo = { bar = 2; }; }
You can modify an existing value using modify, which is the most general
interface for modifying values:
nix-repl> :print override { foo = modify ({ input }: input + 3); } { foo = 2; }
{ foo = 5; }
The modify operation lets you specify a default value if the attribute is not
present:
nix-repl> :print override { foo = modify ({ input ? 0 }: input + 3); } { }
{ foo = 3; }
nix-repl> :print override { foo = modify ({ input ? 0 }: input + 3); } { foo = 2; }
{ foo = 5; }
However, you'll more commonly use a helper operation, like add:
nix-repl> :print override { foo = add 3; } { }
{ foo = 3; }
nix-repl> :print override { foo = add 3; } { foo = 2; }
{ foo = 5; }
For the full list of supported operations, see the Operations section below.
You can transform each element in a list using the "*" attribute:
nix-repl> :print override { foo."*" = add 1; } { foo = [ 2 3 5 ]; }
{ foo = [ 3 4 6 ]; }
nix-repl> :print override { foo."*" = add 1; } { }
{ foo = [ ]; }
… or you can transform a specific element in a list using the element's index:
nix-repl> :print override { foo."0" = set 7; } { foo = [ 2 3 5 ]; }
{ foo = [ 7 3 5 ]; }
Similarly, you can transform every attribute in an attribute set using the "<name>" attribute:
nix-repl> :print override { "<name>" = add 1; } { x = 2; y = 3; }
{ x = 3; y = 4; }
Finally, all of the .override* methods in Nixpkgs are supported, meaning
that you can write something like this:
override {
nix-serve-ng.overrideAttrs.patches = append [ ./lix-compat.patch ];
}
… and that is the same thing as if you had written:
prev: {
nix-serve-ng = prev.nix-serve-ng.overrideAttrs (old: {
patches = (old.patches or []) + [ ./lix-compat.patch ];
});
}
You can combine and nest all of these features arbitrarily. For example:
nix-repl> old = { projects = [ { } { name = "OS"; } { name = "pkgs"; } ]; }
nix-repl> :print override { projects."*".name = prefix "Nix"; } old
{ projects = [ { name = "Nix"; } { name = "NixOS"; } { name = "Nixpkgs"; } ]; }
Or if you want something more realistic, here's an example overlay from one of
my own projects that patches servant-multipart-client.cabal just for GHCJS:
final: override {
haskell.packages.ghcjs.override.overrides = set (hfinal: override {
servant-multipart-client.overrideCabal.postPatch = append
''
sed -i 's/ .*<0.19//' servant-multipart-client.cabal
'';
});
}
To do the same thing without this package you would have to write:
final: prev: {
haskell = prev.haskell // {
packages = prev.haskell.packages // {
ghcjs = prev.haskell.packages.ghcjs.override (old: {
overrides = hfinal: hprev: {
servant-multipart-client =
final.haskell.lib.overrideCabal
hprev.servant-multipart-client
(old: {
postPatch = (old.postPatch or "") +
''
sed -i 's/ .*<0.19//' servant-multipart-client.cabal
'';
});
};
});
};
};
}
If you want to create an overlay instead of an override function, the only difference is that you write:
final: override {
…
}
In other words, you prefix overrides with a function argument representing
the final attribute set.
For example, if you were to write something like this:
final: override {
x = add 1;
y = final.x;
}
… that would generate the same overlay as this:
final: prev: {
x = prev.x + 1;
y = final.x;
}
add
Add a number to the existing value (or to 0 if missing):
nix-repl> :print override { foo = add 2; } { }
{ foo = 2; }
nix-repl> :print override { foo = add 3; } { foo = 2; }
{ foo = 5; }
subtract
Subtract a number from the existing value (or from 0 if missing)
nix-repl> :print override { foo = subtract 3; } { }
{ foo = -3; }
nix-repl> :print override { foo = subtract 3; } { foo = 2; }
{ foo = -1; }
prepend
Prepend a list before the existing value (or before [] if missing)
nix-repl> :print override { foo = prepend [ 2 3 ]; } { }
{ foo = [ 3 5 ]; }
nix-repl> :print override { foo = prepend [ 2 3 ]; } { foo = [ 5 ]; }
{ foo = [ 3 5 2 ]; }
append
Append a list after the existing value (or after [] if missing)
nix-repl> :print override { foo = append [ 3 5 ]; } { }
{ foo = [ 3 5 ]; }
nix-repl> :print override { foo = append [ 3 5 ]; } { foo = [ 2 ]; }
{ foo = [ 2 3 5 ]; }
prefix
Add a string prefix before the existing value (or before "" if missing)
nix-repl> :print override { foo = prefix "Nix"; } { }
{ foo = "Nix"; }
nix-repl> :print override { foo = prefix "Nix"; } { foo = "OS"; }
{ foo = "NixOS"; }
suffix
Add a string suffix after the existing value (or after "" if missing)
nix-repl> :print override { foo = suffix "OS"; } { }
{ foo = "OS"; }
nix-repl> :print override { foo = suffix "OS"; } { foo = "Nix"; }
{ foo = "NixOS"; }
prefixWith
Add a string prefix before the existing value (or before "" if missing) with
a separator:
nix-repl> :print override { foo = prefixWith "." "before"; } { }
{ foo = "before"; }
nix-repl> :print override { foo = prefixWith "." "before"; } { foo = "after"; }
{ foo = "before.after"; }
suffixWith
Add a string suffix after the existing value (or after "" if missing) with a
separator:
nix-repl> :print override { foo = suffixWith "." "after"; } { }
{ foo = "after"; }
nix-repl> :print override { foo = suffixWith "." "after"; } { foo = "before"; }
{ foo = "before.after"; }
prefixWords
Add words before the existing value (or before "" if missing), inserting a
space in between
prefixWords = prefixWith " "
nix-repl> override { foo = prefixWords "hello"; } { }
{ foo = "hello"; }
nix-repl> override { foo = prefixWords "hello"; } { foo = "world"; }
{ foo = "hello world"; }
suffixWords
Add words after the existing value (or after "" if missing), inserting a
space in between
suffixWords = suffixWith " "
nix-repl> override { foo = suffixWords "world"; } { }
{ foo = "world"; }
nix-repl> override { foo = suffixWords "world"; } { foo = "hello"; }
{ foo = "hello world"; }
prefixLines
Prepend lines before the existing value (or before "" if missing)
prefixLines = prefixWith "\n"
nix-repl> :print override { foo = prefixLines "hello"; } { }
{ foo = "hello"; }
nix-repl> :print override { foo = prefixLines "hello"; } { foo = "world"; }
{ foo = "hello\nworld"; }
suffixLines
Append lines after the existing value (or after "" if missing), inserting
a newline in between
suffixLines = suffixWith "\n"
nix-repl> :print override { foo = suffixLines "world"; } { }
{ foo = "world"; }
nix-repl> :print override { foo = suffixLines "world"; } { foo = "hello"; }
{ foo = "hello\nworld"; }
7 commits
1 commits
Nix
100.0%