Okay. We aren’t actually done with Chapter 5 yet. We got through the bulk of it, but we still have a few short sections to go.
5.2. Generators
Generators are functions that create file formats from nix data structures, e. g. for configuration files. There are generators available for:
INI
,JSON
andYAML
Well, as a matter of fact, looking at lib.generators
, it seems that there are generators for:
toGitINI
toINI
toJSON
toKeyValue
toPlist
toPretty
toYAML
Some of those are self-explanatory… but not all of them. What is GitINI
? I have to turn to the source:
/* * Generate a git-config file from an attrset. * * It has two major differences from the regular INI format: * * 1. values are indented with tabs * 2. sections can have sub-sections
Okay, sure.
I don’t really know what a “key-value” is either. I guess =
separated? Or it’s configurable? I dunno. Doesn’t matter.
What is toPretty
?
/* Pretty print a value, akin to `builtins.trace`. * Should probably be a builtin as well. */
A lot of these functions have those weird misaligned asterisks. But who cares? We got pretty-printing! Can it work??
nix-repl> lib.generators.toPretty { x = 1; }
error: 'toPretty' at /nix/store/mi0xpwzl81c7dgpr09qd67knbc24xab5-nixpkgs-21.05pre274251.f5f6dc053b1/nixpkgs/lib/generators.nix:202:14
called with unexpected argument 'x', at (string):1:1
Umm okay hmm. The documentation doesn’t say anything about… how to actually use this function. But reading the source, it seems…
nix-repl> lib.generators.toPretty { multiline = true; } { x = 1; }
"{\n x = 1;\n}"
So close. It, you know, it did pretty print the value, I guess. In a way that is completely useless to me.
Sigh. So… still searching, then.
Returning to the manual:
All generators follow a similar call interface:
generatorName configFunctions data
, whereconfigFunctions
is an attrset of user-defined functions that format nested parts of the content.
Okay; it did immediately describe how to call generator functions.
Nothing super interesting here, and the section is over.
5.3. Debugging Nix Expressions
This section just says “there are functions in lib.debug
for debugging.” That’s not… we already saw that. The manual already said that. This is not a useful section.
5.4. prefer-remote-fetch
overlay
prefer-remote-fetch
is an overlay that download sources on remote builder. This is useful when the evaluating machine has a slow upload while the builder can fetch faster directly from the source. To use it, put the following snippet as a new overlay:self: super: (super.prefer-remote-fetch self super)
Okay… I haven’t actually used overlays yet, so I have to stop and remind myself how they work. So this overlay is a function that takes the entire Nixpkgs set, and returns a new Nixpkgs set. Right? Which means prefer-remote-fetch
is the name of a function in the top-level of Nixpkgs that takes two arguments: the “final” package set, and the “current” package set. In other words, there is an overlay-shaped function in the top-level Nixpkgs set.
Okay. So it looks kind of goofy, but the point-free equivalent would be much more hairy, so we just write it out. Fair enough.
And in fact yes; I remembered how this worked correctly:
nix-repl> pkgs.prefer-remote-fetch
«lambda @ /nix/store/mi0xpwzl81c7dgpr09qd67knbc24xab5-nixpkgs-21.05pre274251.f5f6dc053b1/nixpkgs/pkgs/build-support/prefer-remote-fetch/default.nix:13:1»
I don’t think I’ve seen a kebab-cased attribute before. I didn’t even realize that was a valid identifier. I love kebab-case. It’s a shame that Nix seems to use camelCase for everything else.
Okay, so I am curious how this works. I have not actually used a remote builder (yet), but this feels… magic? Not in a good way? Like I have no idea how this would be implemented. Let’s take a peek.
$ cat ~/src/nixpkgs/pkgs/build-support/prefer-remote-fetch/default.nix
# An overlay that download sources on remote builder.
# This is useful when the evaluating machine has a slow
# upload while the builder can fetch faster directly from the source.
# Usage: Put the following snippet in your usual overlay definition:
#
# self: super:
# (super.prefer-remote-fetch self super)
# Full configuration example for your own account:
#
# $ mkdir ~/.config/nixpkgs/overlays/
# $ echo 'self: super: super.prefer-remote-fetch self super' > ~/.config/nixpkgs/overlays/prefer-remote-fetch.nix
#
self: super: {
fetchurl = args: super.fetchurl (args // { preferLocalBuild = false; });
fetchgit = args: super.fetchgit (args // { preferLocalBuild = false; });
fetchhg = args: super.fetchhg (args // { preferLocalBuild = false; });
fetchsvn = args: super.fetchsvn (args // { preferLocalBuild = false; });
fetchipfs = args: super.fetchipfs (args // { preferLocalBuild = false; });
}
Aha. So it just monkeypatches all the fetch*
functions to insist preferLocalBuild = false
. Okay. Not so magic, then.
I used to write a bunch of Objective-C code, and in Objective-C monkeypatching is called swizzling, which is a much more whimsical term and I wish it had become standardized but here we are.
I realize that we’re just modifying a set here and these are functions not methods so neither term actually applies but whatever you get what I’m saying.
Short section.
5.5. pkgs.nix-gitignore
pkgs.nix-gitignore
is a function that acts similarly tobuiltins.filterSource
but also allows filtering with the help of the gitignore format.
More kebab-case!
There are two subsections here that describe how to use it.
The intro said – and I am quoting – “pkgs.nix-gitignore
is a function,” but it seems that it’s not, it’s a set that contains a bunch of functions.
Okay. Seems useful, for, you know, packaging stuff.
Nothing for hgignore
! But since we still can’t actually install Mercurial with Nix I am not surprised by that. No idea when or if 2.3.11 is ever going to come out.
okay we really did it this time
That’s the end! We read Chapter 5.
But… there are still a lot more functions that we didn’t talk about.
Like, if I just look at lib
, and filter all its values just for the sets, and exclude the ones we’ve already talked about…
cli = { ... };
customisation = { ... };
fetchers = { ... };
filesystem = { ... };
fixedPoints = { ... };
kernel = { ... };
licenses = { ... };
maintainers = { ... };
mergeAttrBy = { ... };
meta = { ... };
misc = { ... };
modules = { ... };
platforms = { ... };
There are more! There are a lot more.
Of course, not all of those are actually function namespaces. licenses
, maintainers
, platforms
, etc… these are just collections of values. mergeAttrBy
seems to be a cutesy sort of collection of specific merge functions? What is this?
Ah, okay – there’s a function mergeAttrByFunc
which allows you to merge two sets by calling a specific function for each attribute. So if I’m reading this right:
nix-repl> myMerge = lib.mergeAttrByFunc {
small = lib.trivial.min;
large = lib.trivial.max;
}
nix-repl> myMerge { small = 10; large = 10; }
{ small = 20; large = 20; }
error: attempt to call something which is not a function but a set, at (string):1:1
Okay, no; it’s a much weirder API:
nix-repl> lib.mergeAttrByFunc {
mergeAttrBy = {
small = lib.trivial.min;
large = lib.trivial.max;
};
small = 10;
large = 10;
}
{ small = 20; large = 20; }
{ large = 20; mergeAttrBy = { ... }; small = 10; }
That’s just… why is it intrusive like that. That’s so gross. Anyway mergeAttrBy
is a function of the mergeAttrBy
shape that provides sane default merge values for derivation attributes – concatenates the buildInputs
, tries to concatenate the shell scripts as strings – although I feel like there might be some weird edge case here; I don’t know if you can just conjoin two lines of shell like that safely in all cases. It’s shell. There’s gotta be a case where that’s not okay, right?
This is not important.
Anyway, these functions are defined in a file called deprecated.nix
, so… yeah. I dunno what the non-deprecated way to do this is.
Oh, look!
/* deprecated: For historical reasons, imap has an index starting at 1. But for consistency with the rest of the library we want an index starting at zero. */ imap = imap1;
Well that explains why Nix’s imap
is explicitly called imap0
. Huh!
But anyway, about a non-deprecated mergeAttrByFunc
… I dunno. It’s a pretty simple helper to define yourself. The fact that it doesn’t exist in lib.attrsets
makes me think it is not really useful in practice. And lib.attrsets.zipWithFunc
is a more general version (merges an arbitrary number of sets, not just two; merge function is determined by a function call instead of a set lookup). I dunno. Whatever. Question answered.
Which means all of these:
cli = { ... };
customisation = { ... };
fetchers = { ... };
filesystem = { ... };
fixedPoints = { ... };
kernel = { ... };
meta = { ... };
misc = { ... };
modules = { ... };
Are lib
function namespace-alikes that are not documented in the manual.
So… let’s dive in, I guess.
The source actually seems really well commented, so I’m going to just start there.
Starting with cli
, there are only two functions – toGNUCommandLine
and toGNUCommandLineShell
. There’s an example in the source:
cli.toGNUCommandLine {} { data = builtins.toJSON { id = 0; }; X = "PUT"; retry = 3; retry-delay = null; url = [ "https://example.com/foo" "https://example.com/bar" ]; silent = false; verbose = true; } => [ "-X" "PUT" "--data" "{\"id\":0}" "--retry" "3" "--url" "https://example.com/foo" "--url" "https://example.com/bar" "--verbose" ]
And toGNUCommandLineShell
just calls lib.strings.escapeShellArgs
on the return value. Easy.
lib.customization
is the home of:
callPackageWith
callPackagesWith
extendDerivation
hydraJob
makeOverridable
makeScope
makeScopeWithSplicing
overrideDerivation
What’s hydraJob
?
/* Strip a derivation of all non-essential attributes, returning only those needed by hydra-eval-jobs. Also strictly evaluate the result to ensure that there are no thunks kept alive to prevent garbage collection. */
Huh. I don’t know what hydra-eval-jobs
is. I hope to find out more.
What are scopes? On makeScope
:
/* Make a set of packages with a common scope. All packages called with the provided `callPackage' will be evaluated with the same arguments. Any package in the set may depend on any other. The `overrideScope'` function allows subsequent modification of the package set in a consistent way, i.e. all packages in the set will be called with the overridden packages. The package sets may be hierarchical: the packages in the set are called with the scope provided by `newScope' and the set provides a `newScope' attribute which can form the parent scope for later package sets. */
So this is like… a way to group packages that have common dependencies, that all need to share the same dependencies? I think I need to see a concrete example of this.
I see it used in such files as php-packages.nix
, emacs-packages.nix
, ocaml-packages.nix
, etc, as well as files like gnome-3/default.nix
and kde/default.nix
. And qt
stuff… so like basically what you’d expect. Packages that exist in a particular “ecosystem.”
makeScopeWithSplicing
has this to say for itself:
/* Like [makeScope], but aims to support cross compilation. It's still ugly, but hopefully it helps a little bit. */
A humble comment.
Man, we still have a lot to get through.
nix-repl> :p lib.fetchers
{ proxyImpureEnvVars = [ "http_proxy" "https_proxy" "ftp_proxy" "all_proxy" "no_proxy" ]; }
That one was easy. I assume this is a top-level thing like this so that it can be easily modified in an overlay or whatever.
nix-repl> :p lib.filesystem
{
haskellPathsInDir = «lambda @ .../nixpkgs/lib/filesystem.nix:6:23»;
listFilesRecursive = «lambda @ .../nixpkgs/lib/filesystem.nix:50:24»;
locateDominatingFile = «lambda @ .../nixpkgs/lib/filesystem.nix:28:26»;
}
Well that’s a little weird.
# haskellPathsInDir : Path -> Map String Path
# A map of all haskell packages defined in the given path,
# identified by having a cabal file with the same name as the
# directory itself.
# locateDominatingFile : RegExp
# -> Path
# -> Nullable { path : Path;
# matches : [ MatchResults ];
# }
# Find the first directory containing a file matching 'pattern'
# upward from a given 'file'.
# Returns 'null' if no directories contain a file matching 'pattern'.
# listFilesRecursive: Path -> [ Path ]
#
# Given a directory, return a flattened list of all files within it recursively.
Weird. haskellPathsInDir
is used in exactly one place: pkgs/build-support/emacs/buffer.nix
:
# nix-buffer function for a project with a bunch of haskell packages
# in one directory
haskellMonoRepo = { ... }:
Shrug emoticon.
locateDominatingFile
is used in exactly zero places. And listFilesRecursive
is… also used in exactly zero places. Huh.
What’s the policy for deprecating or removing dead code? Like presumably people could be depending on these functions, in their own private package repositories. But surely… surely Nixpkgs does remove dead code? Sometimes? It’s not an append-only thing?
I don’t know.
nix-repl> :p lib.fixedPoints
{
composeExtensions = «lambda @ .../nixpkgs/lib/fixed-points.nix:75:5»;
composeManyExtensions = «lambda @ .../nixpkgs/lib/lists.nix:52:20»;
converge = «lambda @ .../nixpkgs/lib/fixed-points.nix:32:14»;
extends = «lambda @ .../nixpkgs/lib/fixed-points.nix:69:13»;
fix = «lambda @ .../nixpkgs/lib/fixed-points.nix:19:9»;
fix' = «lambda @ .../nixpkgs/lib/fixed-points.nix:25:10»;
makeExtensible = «lambda @ .../nixpkgs/lib/fixed-points.nix:109:48»;
makeExtensibleWithCustomName = «lambda @ .../nixpkgs/lib/fixed-points.nix:109:34»;
}
The implementation of fix
matches that of Haskell:
fix = f: let x = f x; in x;
So if I’m following correctly, this won’t “recurse” until a fixed-point is reached; this just calls the function with the output of itself, which will resolve any self-references to the argument.
But uhh how does that work if the argument is a plain old value? I haven’t really thought about this. I have never actually used fix
, just been vaguely mystified by it in the past.
To dig in a little bit, I try to write:
nix-repl> lib.fixedPoints.fix (x: if x % 2 == 0 then x / 2 else x + 1)
error: syntax error, unexpected $undefined, expecting THEN, at (string):1:30
But that does not work with a very mysterious error message. Is… is %
not defined?
nix-repl> 1 % 2
error: syntax error, unexpected $undefined, expecting $end, at (string):1:3
Huh. Okay. Fine.
nix-repl> builtins.mod
error: attribute 'mod' missing, at (string):1:1
nix-repl> builtins.modulo
error: attribute 'modulo' missing, at (string):1:1
Ugh okay I have to pull up the documentation to find that it’s lib.trivial.mod
:
nix-repl> lib.fixedPoints.fix (x: if lib.trivial.mod x 2 == 0 then x / 2 else x + 1)
error: infinite recursion encountered, at /nix/store/mi0xpwzl81c7dgpr09qd67knbc24xab5-nixpkgs-21.05pre274251.f5f6dc053b1/nixpkgs/lib/trivial.nix:225:20
Okay. I take a step back and think about this. It makes sense; I was not reading this correctly at all.
I was thinking of the function Nix calls converge
: gimme a function, gimme an input, apply until it returns a value. That’s what I was trying to write.
But that’s not fix
. fix
actually pulls a value out of a function from nothing. There is no “seed” that it’s starting with.
So how can it call the function in the first place?
Well, because laziness: it can call the function with like… a “placeholder,” sort of – a thunk. And that value will not actually be evaluated until you ask for it. So you just can’t ask for it until the function itself has returned: you have to ensure that the argument is not evaluated eagerly. I think, anyway. Who knows if I’m getting this right. So I think I can say:
nix-repl> lib.fixedPoints.fix (x: [ x ])
[ [ ... ] ]
And that’s just fine. It’s the list that contains itself. nix repl
is even smart enough to let me print this “infinite” value without blowing up my screen.
nix-repl> :p lib.fixedPoints.fix (x: [ x ])
[ [ «repeated» ] ]
Cool.
But I can’t say:
nix-repl> lib.fixedPoints.fix (x: x)
error: infinite recursion encountered, at (string):1:25
Because the function I passed to fix
tries to evaluate its argument before it returns.
Or, no; that’s not quite right: the function doesn’t try to evaluate its argument; nix repl
tries to evaluate it. All the evaluation is “pull-based.” And when nix repl
tries to pull the value out, it finds an infinite loop.
nix-repl> infiniteValue = lib.fixedPoints.fix (x: x)
nix-repl> infiniteValue
error: infinite recursion encountered, at (string):1:26
But similarly, I’m sure I can’t say:
nix-repl> lib.fixedPoints.fix (x: builtins.seq x [ x ])
error: infinite recursion encountered, at undefined position
It’s pretty nice that Nix can detect this instead of just hanging! Thanks, Nix.
Okay; this makes sense to me. Does this make sense to you? I feel like this is maybe weird. I wish the Nix manual had spent some time more talking about laziness. I feel like most people are probably not coming to Nix with much experience with this concept.
Anyway, moving past fix
: there’s something called fix'
that I don’t really understand.
# A variant of `fix` that records the original recursive attribute set in the
# result. This is useful in combination with the `extends` function to
# implement deep overriding. See pkgs/development/haskell-modules/default.nix
# for a concrete example.
fix' = f: let x = f x // { __unfix__ = f; }; in x;
I do not think my brain can handle the concrete example, so I do no such thing, but make a note so that maybe I will come back to this later.
We see extends
, as previously mentioned, which appears to be the machinery that “overlays” are implemented in terms of – a similar “self: super:
” interface.
composeExtensions
composes functions of the self: super:
shape. makeExtensible
just adds an extend
function to a set, which is the correct partial application of exend
.
Okay. Makes sense. Machinery for overriding, basically.
nix-repl> :p lib.kernel
{
freeform = «lambda @ .../nixpkgs/lib/kernel.nix:14:14»;
module = {
optional = false;
tristate = "m";
};
no = {
optional = false;
tristate = "n";
};
option = «lambda @ .../nixpkgs/lib/kernel.nix:8:12»;
whenHelpers = «lambda @ .../nixpkgs/lib/kernel.nix:19:17»;
yes = {
optional = false;
tristate = "y";
};
}
Your guess is as good as mine.
The first comment in kernel.nix
is:
# Keeping these around in case we decide to change this horrible implementation :)
And whenHelpers
is described as:
/*
Common patterns/legacy used in common-config/hardened/config.nix
*/
???
Look at uses, it seems like this is actually used for configuring the Linux kernel? Interesting. Sort of feels like lib
is this giant monolithic catch-all instead of individual things having their own helpers. At least in some cases. I’m sure that’s not a fair generalization to make, considering that I am only looking at lib
.
nix-repl> :p lib.meta
{
addMetaAttrs = «lambda @ .../nixpkgs/lib/meta.nix:15:18»;
appendToName = «lambda @ .../nixpkgs/lib/meta.nix:40:18»;
dontDistribute = «lambda @ .../nixpkgs/lib/meta.nix:21:20»;
hiPrio = «lambda @ .../nixpkgs/lib/meta.nix:15:28»;
hiPrioSet = «lambda @ .../nixpkgs/lib/meta.nix:69:15»;
lowPrio = «lambda @ .../nixpkgs/lib/meta.nix:15:28»;
lowPrioSet = «lambda @ .../nixpkgs/lib/meta.nix:59:16»;
mapDerivationAttrset = «lambda @ .../nixpkgs/lib/meta.nix:46:26»;
platformMatch = «lambda @ .../nixpkgs/lib/meta.nix:84:19»;
setName = «lambda @ .../nixpkgs/lib/meta.nix:27:13»;
setPrio = «lambda @ .../nixpkgs/lib/meta.nix:50:13»;
updateName = «lambda @ .../nixpkgs/lib/meta.nix:35:16»;
}
Okay; I assume these all do the trivial things, calling, like, derivation.overrideAttrs
the way I’d expect.
No! In fact, looking at the source:
addMetaAttrs = newAttrs: drv:
drv // { meta = (drv.meta or {}) // newAttrs; };
They’re just doing the even-more-trivial thing. Modifying it as a set. Huh! So there’d be a disagreement between, like, derivation.meta
and derivation.drvAttrs.meta
. Which I guess never matters? Hopefully?
Okay the next one is pretty fun.
We have lib.misc
, and it contains a bunch of stuff, and I’m not going to talk about all the stuff.
I assumed this would be a different set of “miscellaneous functions” than the “miscellaneous functions” in lib.trivial
. But it’s not. lib.misc
contains all the functions defined in lib/deprecated.nix
. Which really makes me think that it’s all the deprecated functions. But it’s not called lib.deprecated
: that would be crazy. It’s called misc
. Oooookay. I’m not going to dig into those because I am assuming they’re all deprecated.
Lastly we have lib.modules
, which is massive.
There is no real description or high-level summary in modules.nix
of what these functions are or what they do, but I think that I’m gathering from various comments hidden deep in the file that this is a NixOS thing?
I google “nixos modules” and get a hit on the wiki:
Modules are files combined by NixOS to produce the full system configuration. A module contains a Nix expression. It declares options for other modules to define (give a value). It processes them and defines options declared in other modules.
lib.modules
seems to contain the machinery for doing that. So I’m just going to skip all of that.
And we’re done! We read Chapter 5; we did all the extra credit. Next time it looks like we’ll get to learn about stdenv
. I’m excited.
- Why is
prefer-remote-fetch
kebab-cased? - Is it safe to get rid of
locateDominatingFile
(and other unusedlib
functions)? - What’s with
fix'
and the__unfix__
attribute? Should I care about this? - What is
hydra-eval-jobs
? - Where is
lib.warn
defined? - Why is
lib.misc
not calledlib.deprecated
? - Why is
lib.trivial
not calledlib.misc
?