This is it. We’ve reached the end. After a month and a half of reading the Nixpkgs manual in excruciating detail, I have finally defeated it.
Or, I’m about to defeat it. We’re about to defeat it. We’re going to do this together.
Chapter 17. Quick Start to Adding a Package
At this point I feel that I’ve… kind of got a pretty good handle on this. Step 1: write a default.nix
file. Step 2: put it somewhere reasonable. Step 3: add it to the giant monolithic list of every package in the universe.
And, yeah. That’s pretty much what the manual says.
It then gives a list of 9 packages to serve as good examples for writing said default.nix
files.
Aha!
You can use
nix-prefetch-url
url
to get the SHA-256 hash of source distributions. There are similar commands asnix-prefetch-git
andnix-prefetch-hg
available innix-prefetch-scripts
package.
I was just wondering about that. Now I have my answer.
A list of schemes for
mirror://
URLs can be found inpkgs/build-support/fetchurl/mirrors.nix
.
Wha? What are mirror://
URLs?
It doesn’t say, but it links to the code, so let’s take a look.
And there is… no explanation of this. I have never encountered a mirror://
URL, although ⌘F tells me that the Nixpkgs manual did use some of them (without explanation) in the section on packaging Perl programs.
It just says:
# Mirrors for mirror://site/filename URIs, where "site" is
# "sourceforge", "gnu", etc.
And then there’s a big long list of attributes like this:
# SourceForge.
sourceforge = [
"https://downloads.sourceforge.net/"
"https://prdownloads.sourceforge.net/"
"https://heanet.dl.sourceforge.net/sourceforge/"
"https://surfnet.dl.sourceforge.net/sourceforge/"
"https://dfn.dl.sourceforge.net/sourceforge/"
"https://osdn.dl.sourceforge.net/sourceforge/"
"https://kent.dl.sourceforge.net/sourceforge/"
];
# CPAN mirrors.
cpan = [
"https://cpan.metacpan.org/"
"https://cpan.perl.org/"
"http://backpan.perl.org/" # for old releases
];
So I’m assuming – this is not explained in the code or the manual – that there is some Nix machinery in fetchurl
to try these lists of mirrors, when you use the mirror
“protocol.”
But there is also one mysterious one – at the very top of the file:
# Content-addressable Nix mirrors.
hashedMirrors = [
"http://tarballs.nixos.org"
];
I don’t really know what that’s about. It certainly seems different than all the others.
But I remember hearing – I don’t remember reading this in the manual, or in some random blog post, or what – about some sort of mechanism that fetchurl
can use to transparently mirror sources, as a way to make Nix builds reproducible even as The Internet changes out from under us.
Aha! Grepping my blog reveals the answer. The Nix manual mentioned it, in passing, in the documentation for a configuration value called hashed-mirrors
.
So presumably this is just the default value, and I can configure my own hashed-mirrors
with all the sources that my software depends on. Which I like!
I wonder how I can set that up.
I also wonder if hashed-mirrors
is like… a fallback? Or a preferred cache, like a CDN situation? I would guess that it’s just a fallback. But maybe not? Since you’d only use it in the case that you aren’t using a substitute. And Nix is paying for that bandwidth already. Anyway, if I had my own private version, I would definitely want it to be the primary source. Hmm. I wonder if I can configure that.
Anyway. Yeah. That’s how you contribute. And then you open a PR. Seems straightforward.
Chapter 18. Coding conventions
This is a chapter about conventions.
It begins with… syntax. Like, where to put your braces. How many spaces to use. Stuff like that. Stuff that, in 2021, actual humans are still spending actual human effort on. Stuff that a computer can– okay. I won’t go on a rant about autoformatters. Or linters, at the very least – that’s not even a controversial ask, is it? Anyway. Definitely bigger fish to fry here.
I’m not going to read all of these detailed instructions for how to format source code because it’s, you know, life is too short.
Actually, I will rant a little bit, because I thought of something almost interesting to say about this.
One of the beautiful things about Nix is that it takes “build instructions” – documents written for humans – and turns them into expressions written for computers.
Consider, for example, this document. It is a document that describes how to build the music programming environment SuperCollider. It is a lot of words. It contains instructions for human people to follow, if they want to build SuperCollider for themselves. Many of the words are just about how to install Qt.
Now consider this alternative. That’s the Nix expression to build SuperCollider.
The ability to lift “instructions for humans” into “instructions for computers” is one of the great superpowers that Nix gives you.
I don’t mean to pick on SuperCollider; that’s just something I happened to be playing with recently, and I thought its build instructions made a very good Nix case study. And I recognize that I’m not being fair, really: the official instructions contain a lot of information about conditional compilation of different features, which the Nix expression does not support.
So I will pick on myself instead:
This project relies on
cairo
andpango
. If you don’t have them already, you can install them with Homebrew like this:$ brew install cairo --without-x11 $ brew install pango --without-x11
Afterwards, I’m pretty sure you need
gtk2hs-buildtools
for something.$ cabal install gtk2hs-buildtools
Make sure you don’t install that into a sandbox! It adds some binaries that you need. So make sure
~/.cabal/bin
is on yourPATH
.Afterwards, we do haskell things:
$ cabal sandbox init
We have to install the Haskell
pango
bindings separately becauseclang
can’t build it. Look, don’t ask me.$ cabal install pango -j --with-gcc=gcc-4.8
Then we install the rest:
$ cabal install -j --only-dependencies
Those are the build instructions I wrote once for a little toy project designed to waste company time and money. Are the instructions correct? Complete? I have no idea. It’s not like I tested them. How could I? It worked on my machine. Did I capture every dependency? I don’t know. The language I used there is certainly not reassuring.
Anyway, we’re in part 35 of a series about learning Nix. So I don’t really think I need to convince you of the value of actually repeatable build instructions.
Now consider this document:
Function formal arguments are written as:
{ arg1, arg2, arg3 }:
but if they don’t fit on one line they’re written as:
{ arg1, arg2, arg3 , arg4, ... , # Some comment... argN }:
It’s just as infuriating, right? As opposed to:
$ nix fmt
Or whatever.
Okay. End of rant.
The manual then describes package naming conventions. Which begins with the wonderful:
The key words must, must not, required, shall, shall not, should, should not, recommended, may, and optional in this section are to be interpreted as described in RFC 2119. Only emphasized words are to be interpreted in this way.
You know you’re in for a good time whenever someone links to RFC 2119.
This is followed by a couple simple rules for naming packages. No uppercase letters. Versions start with numbers. I dunno. I thought I could be snarky about the naming police intensely overspecifying every little detail, but there are only actually six different rules.
One of them is:
If a package is not a release but a commit from a repository, then the version part of the name must be the date of that (fetched) commit. The date must be in “
YYYY-MM-DD
” format. Also append “unstable
” to the name - e.g., “pkgname-unstable-2014-09-23
”.
Which seems odd to me. I dunno. Kinda arbitrary, whether a commit is “a release” or just “a commit.”
Anyway, then we describe the layout of the Nixpkgs repository. What folders to put stuff in. I don’t really know the exact difference between a tool/
and an application/
, but I could probably distinguish between them pretty well in practice. I dunno. servers/
is for both HTTP servers and X.org, which is very funny to me.
18.4. Fetching Sources
Right now there is only one fetcher which has mirroring support and that is
fetchurl
.
Aha. So the hashed-mirrors
thingy doesn’t work with fetchgit
? Or fetchFromGitHub
? That seems… problematic.
Or, no… it seems fetchFromGitHub
is implemented with fetchurl
, not fetchgit
– it fetches a source archive over HTTP; it doesn’t fetch via the git protocol directly. Okay; that’s good to know.
The manual gives an example here of how to use the nix-prefetch-scripts
:
Find the value to put as
sha256
by runningnix run -f '<nixpkgs>' nix-prefetch-github -c nix-prefetch-github --rev 1f795f9f44607cc5bec70d1300150bfefcef2aae NixOS nix
or
nix-prefetch-url --unpack https://github.com/NixOS/nix/archive/1f795f9f44607cc5bec70d1300150bfefcef2aae.tar.gz.
I tried running that first one:
$ nix run -f '<nixpkgs>' nix-prefetch-github -c nix-prefetch-github --rev 1f795f9f44607cc5bec70d1300150bfefcef2aae NixOS nix
[4 copied (1.2 MiB), 0.3 MiB DL]
{
"owner": "NixOS",
"repo": "nix",
"rev": "1f795f9f44607cc5bec70d1300150bfefcef2aae",
"sha256": "1i2yxndxb6yc9l6c99pypbd92lfq5aac4klq7y2v93c9qvx2cgpc",
"fetchSubmodules": true
}
Pretty neat!
This is my first encounter with nix run
. Well, since the quick start guide, anyway.
Let’s try to dissect that a little. From nix run --help
:
Usage: nix run <FLAGS>... <INSTALLABLES>...
-f, --file <FILE> evaluate FILE rather than the default
-c, --command <COMMAND> <ARGS> command and arguments to be executed; defaults to 'bash'
So we provide one INSTALLABLE
, nix-prefetch-github
. Presumably that’s the name of a top-level attribute:
$ sd nix info nix-prefetch-github
nix-prefetch-github-4.0.3 Prefetch sources from github
Indeed. There is also:
$ sd nix info nix-prefetch-scripts
nix-prefetch-scripts Collection of all the nix-prefetch-* scripts which may be used to obtain source hashes
But I don’t try to run that one.
Then the --file
we pass is <nixpkgs>
. Although this doesn’t look like a “file,” remember that it is – to Nix, <nixpkgs>
is another way to write the path /Users/ian/.nix-defexpr/channels/nixpkgs
, which is a directory with a default.nix
file.
And then -c
is the command to run with that INSTALLABLE
on our PATH
, I guess. I assume it just adds /nix/store/xxx-nix-prefetch-github-4.0.3/bin
to the PATH
and then tries to execute that command?
Let’s find out.
$ nix run -f '<nixpkgs>' nix-prefetch-github -c env | grep '^PATH='
PATH=/nix/store/y1c2zbzirfhv29ix4hdsxp6ksnxl2q3q-python3.8-nix-prefetch-github-4.0.3/bin:...
Indeed. Okay. Cool.
18.5. Obtaining source hash
We just learned how to do this, but now there’s a whole section on how to do this? In more detail, but I’m not sure that any of the extra detail is necessary, given the above example.
I learn how to turn a hexadecimal hash into a Base32 hash.
You can convert between formats with
nix-hash
, for example:nix-hash --type sha256 --to-base32 HASH
Okay. I think that’s the first time I’ve seen nix-hash
.
It then says “if all else fails, put a fake hash and run some command and copy the error,” and provides examples of well-formed but incorrect hashes: lib.fakeSha256
and friends. Huh!
Actually really it says don’t do this, because downloading a package is not a very good way to find out what the hash should be, because man in the middle attacks.
It adds this mystifying explanation:
https://
URLs are not secure in method 5. When obtaining hashes with fake hash method, TLS checks are disabled. So refetch source hash from several different networks to exclude MITM scenario. Alternatively, use fake hash method to make Nix error, but instead of extracting hash from error, extracthttps://
URL and prefetch it with method 1.
…what? Why on earth would TLS checks be disabled when fetching sources with a known fake hash? What? This must be… wrong, right? What is this trying to say?
I don’t know. I am upset and afraid.
Chapter 19. Submitting changes
Alright. Now we know how to make changes. Now we learn how to submit them.
This is… instructions for how to make good commits? Update release notes. How to test stuff. Good information, I guess? Link to contribution guidelines. How to mark security releases and get them out sooner. “There is a pull request template.”
This is interesting:
Sandboxing is not enabled by default in Nix due to a small performance hit on each build. In pull requests for nixpkgs people are asked to test builds with sandboxing enabled (see
Tested using sandboxing
in the pull request template) because in https://nixos.org/hydra/ sandboxing is also used.
It then tells me how to enable sandboxes. By editing my global /etc/nix/nix.conf
? That seems dumb. If I just wanna test one derivation… there’s no --sandbox
flag I can pass to nix-build
, or something? Come on.
If you are updating a package’s version, you can use
nixpkgs-review
to make sure all packages that depend on the updated package still compile correctly. Thenixpkgs-review
utility can look for and build all dependencies either based on uncommited changes with thewip
option or specifying a github pull request number.nix run nixpkgs.nixpkgs-review -c nixpkgs-review pr 12345
Pretty neat!
Also, that’s much nicer looking than nix run -f '<nixpkgs>'
. How does that work, then? Does nix run
use ~/.nix-defexpr
? I attempt to answer empirically:
$ nix run nixpkgs.hello -c hello
Hello, world!
$ nix run ianpkgs.hello -c hello
error: attribute 'ianpkgs' in selection path 'ianpkgs.hello' not found
Hmm. Maybe not. Or I just broke my ianpkgs
thing that I set up forever ago.
Yes, okay. It seems I did break it, at some point:
$ nix-env -iA ianpkgs.hello
error: expression does not evaluate to a derivation (or a set or list of those)
I fix it – my default.nix
wasn’t fully applying callPackage
– and try again:
$ nix run ianpkgs.hello -c hello
error: attribute 'ianpkgs' in selection path 'ianpkgs.hello' not found
So, no. I thought there was a chance that it was just a bad error message, but it seems it is not using ~/.nix-defexpr
at all.
So… what is it using? nix run --help
doesn’t explain this. It does say:
To start a shell providing youtube-dl from your 'nixpkgs' channel:
$ nix run nixpkgs.youtube-dl
But… what? Is it literally defaulting to ~/.nixpkgs-defexpr/channels
? That would be insane.
I have no idea.
I guess I have to look in the source.
Let’s see… src/nix/run.cc
does not document the -f
flag, but it seems to be an “installable” command. That leads me to installables.cc
.
Which is sort of inscrutable. But it seems to rely on NIX_PATH
elements, and duplicate some subset of the nix-env
directory-traversing thing?
$ echo $NIX_PATH
/Users/ian/.nix-defexpr/channels:/Users/ian/.nix-defexpr/channels
Why is that my NIX_PATH
? Because apparently I’ve sourced my ~/.zshenv
since I started this shell session, and every time I do that, it adds an entry here.
$ source ~/.zshenv
$ echo $NIX_PATH
/Users/ian/.nix-defexpr/channels:/Users/ian/.nix-defexpr/channels:/Users/ian/.nix-defexpr/channels
How am I to express my feelings, with only the Latin alphabet at my disposal? I feel as though I must turn, in these trying times, to Unicode’s Supplemental Symbols and Pictographs block: 🤦♀️
Anyway, I find this issue by looking at the blame for installables.cc
, which… does nothing to give me confidence in… anything. My takeaway is: no one knows how this works, and no one knows how this is supposed to work, because no one has decided how this is supposed to work, and that’s what you get for using nix run
when it’s marked experimental.
Yeah; I have no idea here. Let’s do some experiments:
$ NIX_PATH=ianpkgs=$HOME/.nix-defexpr/ianpkgs nix run ianpkgs.hello -c hello
error: file 'nixpkgs' was not found in the Nix search path (add it using $NIX_PATH or -I), at /Users/ian/scratch/default.nix:1:13
Ah. Right. That’s on me: I import <nixpkgs>
in my default.nix
.
$ NIX_PATH=ianpkgs=$HOME/.nix-defexpr/ianpkgs:nixpkgs=$HOME/.nix-defexpr/channels/nixpkgs nix run ianpkgs.hello -c hello
Hello, Nix!
Okay. So that works. I guess. So this… treats the first component as a NIX_PATH
element?
I don’t know.
I guess the -f '<nixpkgs>'
version – while quite a mouthful – at least I could explain how it worked.
Anyway, moving on: description of branches. Where to put your PR. Simple stuff.
Chapter 20. Reviewing contributions
Be nice. Be polite.
GitHub provides reactions as a simple and quick way to provide feedback to pull requests or any comments. The thumb-down reaction should be used with care and if possible accompanied with some explanation so the submitter has directions to improve their contribution.
I heartily approve of this language.
Some examples of common tasks, and how to do them. Seems pretty nice. Updating packages. Adding new ones. Not a lot to say about this one.
Chapter 21. Contributing to this documentation
Now we’re talking.
$ cd /path/to/nixpkgs/doc $ nix-shell [nix-shell]$ make
I try it out, and just get a massive pile of error messages.
Starts like this:
pandoc preface.chapter.md -t docbook \
--top-level-division=chapter \
--extract-media=media \
--lua-filter=/diagram-generator.lua \
-f markdown+smart \
| cat > preface.chapter.xml
Error running filter /diagram-generator.lua:
/diagram-generator.lua: openBinaryFile: does not exist (No such file or directory)
Ends like this:
^
manual.xml:40: element include: XInclude error : could not load contributing/contributing-to-documentation.chapter.xml, and no fallback was found
make: *** [Makefile:77: manual-full.xml] Error 1
With hundreds of similar error messages along the way.
If you experience problems, run
make debug
to help understand the docbook errors.
Okay. This gives me less output…
[nix-shell:~/src/nixpkgs/doc]$ make debug
nix-shell --run "xmloscopy --docbook5 ./manual.xml ./manual-full.xml"
trace: lib.crossLists is deprecated, use lib.cartesianProductOfSets instead
trace: `mkStrict' is obsolete; use `mkOverride 0' instead.
trace: `lib.nixpkgsVersion` is deprecated, use `lib.version` instead!
trace: warning: lib.readPathsFromFile is deprecated, use a list instead
trace: Warning: `showVal` is deprecated and will be removed in the next release, please use `traceSeqN`
trace: lib.zip is deprecated, use lib.zipAttrsWith instead
Feasability check on files without xincludes...
A document is feasibly valid if it could be transformed into a valid
document by inserting any number of attributes and child elements
anywhere in the tree.
This is equivalent to pretending every element is optional.
This option may be useful while a document is still under
construction.
This option also disables checking that references are valid.
./release-notes.xml: OK
./preface.chapter.xml: Not feasibly valid:
Line 1:
make: *** [Makefile:8: debug] Error 1
I mean, it’s not… wrong, I guess.
[nix-shell:~/src/nixpkgs/doc]$ cat preface.chapter.xml
It’s just an empty file. Which is, in fact, not feasibly valid.
After making modifications to the manual, it’s important to build it before committing. You can do that as follows:
$ cd /path/to/nixpkgs/doc $ nix-shell [nix-shell]$ make clean [nix-shell]$ nix-build .
Okay. I’ll bite.
I ran this, and it… succeeded? Just fine?
If the build succeeds, the manual will be in
./result/share/doc/nixpkgs/manual.html
.
And indeed. That worked. Very different CSS – I’m so used to way the manual looks, after all these months, that it’s sort of jarring to see it using these alternative styles – but yeah, it’s all here.
So… I have no idea why it said to run make
, when, in fact, the secret is to run nix-build
. That’s… that’s extremely confusing.
Do you think this is a test? This is like, if you want to make contributions to the Nixpkgs documentation, the first thing you need to do is fix the documentation for how to make documentation contributions?
Ugh.
Well, one day, maybe. I read the whole Nixpkgs manual. Although I feel like I have a lot more to say about the Nix manual than the Nixpkgs manual, it’s not impossible that I’ll be back here some day, fixing typos or something at least.
For now? Farewell, Nixpkgs manual. You were… a lot longer and a lot harder to read than the Nix manual, if we’re being honest with each other.
- How do
hashed-mirrors
work? - Why does Nix… disable TLS when fetching sources? What?
- How do I enable the sandbox for a single build?