I don’t feel like I know enough about Nix to manage my own build dependencies with it, but I do feel like I know enough to use Nix as a simple package manager. I use Homebrew on my laptop, and at this point I definitely know more about Nix than I do about Homebrew.1
Why would I switch away from Homebrew? Mostly because I think it will be educational. I don’t really have anything against Homebrew. It works. It has a lot of the packages I want. It does break more often than I’d like – not, like, often, but any time I go more than a year or so without using a particular computer I know that I’m going to return to some bizarre Ruby error that I have to spend half an hour figuring out before I can do anything.
And there’s no way that Nix would ever play me like that.
Homebrew doesn’t seem to distinguish between “packages I want to have installed” and “packages I have to have installed because they are dependencies of other packages.”
I remember this bothering me before. It’s bothering me a lot right now, as I try to get rid of it.
When I type brew list
to see installed packages, I get like hundreds of entries – only a couple dozen of which I actually remember installing. I have to go through it by hand to figure out what I actually want to have.
Here’s what I find:
cabal-install
curl
fzf
git
haskell-stack
hexedit
htop
hugo
imagemagick
jq
mercurial
nasm
nmap
node
nodenv
opam
openssl
pandoc
pngcrush
pv
pyenv
python@2
python@3.9
rbenv
ripgrep
sqlite
terminal-notifier
tmux
tree
xz
yarn
yasm
zsh
33 packages. Not that many! I’m a little surprised. But I haven’t really done any development on this computer in a few years. Whatever I don’t need to justify myself to you.
Let’s try to nix-env -iA nixpkgs.*
them.
$ nix-env -iA nixpkgs.{cabal-install,curl,fzf,git,haskell-stack,...}
And… it worked? Mostly?
I am kind of surprised to report that almost all of these just worked. Some of the packages had different names – node
is called nodejs
, and python
is python2
and python3
, for example – but otherwise it was super smooth. The only missing packages are nodenv
and pyenv
– which seems fair. If you’re using Nix, you don’t need those.
Well, no, that’s not true: you might want to make something that works for people who aren’t using Nix, so you still want to distribute a .python-version
file and test that it does the right thing or whatever. It’s still a useful package.
And, oddly, rbenv
is here… hmm. Maybe pyenv
and nodenv
are just missing, and not philosophically boycotted.
I’m kind of surprised that terminal-notifier
is in here: I thought that was a macOS-specific thing. Maybe it is, and I am not the only person using Nix on macOS.
But yeah, that went very smoothly. Everything mostly just worked. Great experience no notes.
Oh, except for one little thing:
$ nix-env -iA nixpkgs.mercurial
installing 'mercurial-5.6'
these paths will be fetched (3.45 MiB download, 14.95 MiB unpacked):
/nix/store/7x5s4k57cw0a8nldypmm1y5f763k01kl-mercurial-5.6
copying path '/nix/store/7x5s4k57cw0a8nldypmm1y5f763k01kl-mercurial-5.6' from 'https://cache.nixos.org'...
Assertion failed: (size_ < capacity_), function push_back, file src/libexpr/attr-set.hh, line 54.
[1] 1968 abort nix-env -iA nixpkgs.mercurial
Oh dear. Mercurial is not some weird obscure package, either. It’s… it’s a pretty big one. I find a GitHub issue opened (at time of writing) 20 days ago:
https://github.com/NixOS/nixpkgs/issues/112465
Yikes. Yikes. Okay. So that’s not great.
I am using the “unstable” Nixpkgs channel, but I didn’t choose to live life on the edge. That’s just the default thing that you get when you install Nix!
So… well, yep, I guess that’s a pretty well-named channel. But 20 days of instability is pretty surprising. I have no idea what the error means, or what I would do to fix it. I’m not even trying to build it! I just want to download it from the cache!
So I cannot get rid of Homebrew completely.2 But I uninstall everything I can, and my brew list
is now just:
autoconf
mercurial
nodenv
pkg-config
python@3.9
sqlite
xz
gdbm
node-build
openssl@1.1
pyenv
readline
tcl-tk
Everything left there is a dependency of mercurial
, nodenv
, or pyenv
. That is the closure of those packages, if you will. Did I do it? Did I use the words?
zsh
is a little interesting, since I use that as my login shell. So I’ll have to figure that out.3
How do I print out the path to a Nix package? I want to see the path to zsh
. This whole time I’ve just been doing which foo
, but which zsh
gives me /bin/zsh
, so I need to figure out the right way.
I look through nix --help
and try a promising command:
$ nix path-info nixpkgs.zsh
/nix/store/bainh95kdn1h0gy5rdayjj7qq15a6q46-zsh-5.8
Neat. I add that to /etc/shells
so I can use the installation from Nix.
Except… hmm. That’s not gonna work very well for me, is it? If I ever upgrade zsh
, I’m going to have a new hash, and it will eventually be garbage-collected, and I will have no login shell.
To be safe, I add it as a GC root. And I guess… I’ll have to manually chsh
any time I upgrade zsh
? Okay. Seems… tolerable. Definitely not great. But how often can zsh
change? No way there will be some critical security vulnerability and I’ll forget that I ever did this.
But alright. I did the thing. I migrated. But that was the easy part.
homebrew, as she is played
There’s something that I’ve wanted to do from the very beginning of this series: write – for myself, mostly – a translation table between Homebrew commands and Nix commands. I thought that by the end of the manual I would be able to do this no problem. But that is not actually the case…
I look through zsh_history
to see what brew
commands I use most frequently. I only have ten thousand total history entries, most of which are not brew
, so this is just a sampling of recent usage. But here’s what I see (after filtering out typos and commands that no longer exist):
138 info
135 uninstall
108 install
63 search
41 upgrade
22 list
19 update
15 outdated
7 link
7 doctor
5 leaves
5 cleanup
4 untap
3 tap
3 reinstall
Wait. brew leaves
? You can list just installed packages? I actually googled this, and couldn’t find it. Boo. I guess that past Ian knew this though, at some point.
Huh. Okay. I take it back. I’m sorry I doubted you, Homebrew.
Anyway: the command I use the most often is info
. And… wow.
Off to a bad start.
I don’t know what the Nix equivalent of brew info
is.
Umm.
Hmm.
Welp! That’s not great. Nothing in nix --help
sounds plausible – no nix show
or nix info
or anything. nix-env -q
sounds sort of maybe like it could do this – I’m looking to do a type of query. man nix-env
tells me about --meta
:
$ nix-env -qaA nixpkgs.git --meta
git-2.30.0
Cool cool thanks? Alright. Oh. I should have kept reading:
This option is only available with
--xml
or--json
.
But it’s not an error if you pass it anyway! It’s just silently not what you want! Gross.
$ nix-env -qaA nixpkgs.git --meta --json
{
"nixpkgs.git": {
"name": "git-2.30.0",
"pname": "git",
"version": "2.30.0",
"system": "x86_64-darwin",
"meta": {
"available": true,
"broken": false,
"changelog": "https://raw.githubusercontent.com/git/git/2.30.0/Documentation/RelNotes/2.30.0.txt",
"description": "Distributed version control system",
"homepage": "https://git-scm.com/",
"insecure": false,
"license": {
"deprecated": true,
"fullName": "GNU General Public License v2.0",
"spdxId": "GPL-2.0",
"url": "https://spdx.org/licenses/GPL-2.0.html"
},
"longDescription": "Git, a popular distributed version control system designed to\nhandle very large projects with speed and efficiency.\n",
"maintainers": [ /* ... */ ],
"name": "git-2.30.0",
"outputsToInstall": [
"out"
],
"platforms": [ /* ... */ ],
"position": "/nix/store/q9vdjh5mh5f2cypycbqxdiwls44b1n5i-nixpkgs-21.05pre272788.870dbb751f4/nixpkgs/pkgs/applications/version-management/git-and-tools/git/default.nix:335",
"unfree": false,
"unsupported": false
}
}
}
Okay. That’s… way too much stuff.
Let’s compare that to brew info git
:
$ brew info git
git: stable 2.30.1 (bottled), HEAD
Distributed revision control system
https://git-scm.com
Not installed
From: https://github.com/Homebrew/homebrew-core/blob/HEAD/Formula/git.rb
License: GPL-2.0-only
==> Dependencies
Required: gettext ✘, pcre2 ✘
==> Options
--HEAD
Install HEAD version
==> Caveats
The Tcl/Tk GUIs (e.g. gitk, git-gui) are now in the `git-gui` formula.
==> Analytics
install: 216,142 (30 days), 554,354 (90 days), 2,371,732 (365 days)
install-on-request: 213,566 (30 days), 547,255 (90 days), 2,290,105 (365 days)
build-error: 0 (30 days)
Huh. That actually also looks like way too much stuff.
So it’s worth asking what I care about here. Why do I use brew info
? I ponder this for a moment, and come up with a few reasons:
- to check if a specific package name is the actual thing I want before I install it (i.e.
cabal
vscabal-install
) - to read the package’s description (usually because I encountered some unfamiliar command in a blog post somewhere: this is a very fast way to find out what it is)
- to see what version of the package Homebrew has (especially in the case of compilers and development tools, where I care deeply about the exact version I’m using)
Those are the only reasons I can think of. So honestly the first two lines of brew info
’s output cover 100% of what I want.
I don’t think I ever really realized that. Turning back to Nix, how can I get those two lines?
$ nix-env -qaA nixpkgs.git --description
git-2.30.0 Distributed version control system
Huh. Maybe… that’s all I need? It’s pretty darn close. And it’s only 190% more characters to type than brew info git
!
I see there is a meta.longDescription
. I would weakly prefer something like this:
$ nix-env -qaA "nixpkgs.$1" --json \
| jq -r '.[] | .name + " " + .meta.description,
"",
(.meta.longDescription | rtrimstr("\n"))'
git-2.30.0 Distributed version control system
Git, a popular distributed version control system designed to
handle very large projects with speed and efficiency.
So I add this to my script directory as sd nix info
, to reduce wear on my fingertips.
Alright. Let’s move on: uninstall
and install
. I know this:
$ nix-env -iA nixpkgs.git
$ nix-env -e git
Except that the string “git” means a different thing when you use -iA
than it does when you use -e
.4 You install packages by their “Nixpkgs attribute name” or whatever, but you uninstall them by their “name.” You can install by “name” but it takes 30 seconds so you will never do that. There is no way to uninstall by “attribute.”
I understand why this is, now: the attribute path in Nixpkgs is not an intrinsic part of the package. The package doesn’t know that it’s called nixpkgs.git
. You could evaluate nixpkgs.git
and find its store path, but that wouldn’t work in the case that nixpkgs.git
has changed since you installed it (because of nix-channel --update
, say, or because you renamed the channel, or whatever).
So like… this is a weird gross problem that Homebrew doesn’t have.
Or does it? Have you ever tried to uninstall a package that has been removed from Homebrew? Or renamed? Or to uninstall a package that was installed with an older version of Homebrew and now you’re getting weird Ruby errors because the format of casks has changed in a backwards-incompatible way?
You haven’t? Oh. Yeah; to be fair it is pretty rare.
As a side note, I really don’t like that nix-env -e foo
just silently does nothing of you have a typo, or mix up the two kinds of “name”:
$ nix-env -e foo
$ echo $?
0
But I guess… it’s unlikely to cause real problems? It was slightly confusing while I was building and installing my own derivations to “practice” Nix, but I don’t think it would actually be confusing in real life.
brew search git
is nix search git
– nix
wins this one hands down. Here’s brew search git
:
$ (time brew search git) | head
==> Formulae
bagit
bash-git-prompt
bit-git
cgit
digitemp
easy-git
git
git-absorb
git-annex
brew search git 6.46s user 3.82s system 58% cpu 17.704 total
And our challenger:
$ (time nix search git) | head
warning: using cached results; pass '-u' to update the cache
* nixpkgs.act (act-0.2.20)
Run your GitHub Actions locally
* nixpkgs.argocd (argocd-1.8.5)
Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes
* nixpkgs.axoloti (axoloti-1.0.12-2)
Sketching embedded digital audio algorithms. To fix permissions of the Axoloti USB device node, add a similar udev rule to <literal>services.udev.extraRules</literal>: <literal>SUBSYSTEM=="usb", ATTR{idVendor}=="16c0", ATTR{idProduct}=="0442", OWNER="someuser", GROUP="somegroup"</literal>
* nixpkgs.bat (bat-0.17.1)
nix search git 0.90s user 0.05s system 89% cpu 1.063 total
Much more useful output – I don’t need to run brew info
on all of those to see what they are, and because it’s locally cached, it’s much faster – at the cost of having to invalidate it myself, with which I am totally fine.
Alright. brew upgrade
is nix-env -u
. Pretty easy.5
Where are we now?
138 ✓ info
135 ✓ uninstall
108 ✓ install
63 ✓ search
41 ✓ upgrade
22 list
19 update
15 outdated
7 link
7 doctor
5 leaves
5 cleanup
4 untap
3 tap
3 reinstall
Thanks.
brew list
is… I don’t know.
I want to say nix-env -q
, but I now know that that’s actually equivalent to brew leaves
. That’s really what I want when I say brew list
, though.
It’s nice seeing dependencies as well, I guess, but sort of useless without seeing why they’re there. So I guess:
$ nix-store --query --tree -u ~/.nix-profile/ | head
/nix/store/5cmxacs3grxnyqcc8a58hqg0i2w8xza3-user-environment
+---/nix/store/0drrznwp316knss36m177bk3r3pgj31i-xz-5.2.5-bin
| +---/nix/store/5y6lcfjghk5kbv4782vi7w79vz60gsyn-bash-4.4-p23
| | +---/nix/store/5y6lcfjghk5kbv4782vi7w79vz60gsyn-bash-4.4-p23 [...]
| +---/nix/store/awgjr2jdyr2gqhkd8nb03yh8bzcg1zci-xz-5.2.5
| +---/nix/store/awgjr2jdyr2gqhkd8nb03yh8bzcg1zci-xz-5.2.5 [...]
+---/nix/store/1s7qdbi6fprms0hljsx9rq2fwr3fz1gi-nix-2.3.10-man
+---/nix/store/298mjs1y9r3yxri5sjhsqakkzv0arcnx-stack-2.5.1.1
| +---/nix/store/7r6hlpv7z1myj2nn4lvxmhga3q9s3k8x-libffi-3.3
| | +---/nix/store/7r6hlpv7z1myj2nn4lvxmhga3q9s3k8x-libffi-3.3 [...]
Is sort of… anything? This is a stretch. I’m going to call it nix-env -q
and leave it at that.
brew update
is nix-channel --update
. No; there’s no nix-channel -u
, insanely.
And it seems like there’s no good equivalent of brew outdated
. I like running brew outdated
as a nice way to just see what packages are under active development. I like to read the release notes of, like, git
before I upgrade it. There’s nix-env -u --dry-run
…
$ nix-env -u --dry-run
(dry run; not doing anything)
upgrading 'python3-3.8.7' to 'python3-3.10.0a4'
these derivations will be built:
/nix/store/sfkdnpn3klqb7iymg520m33c3pp7zjk0-python3-3.10.0a4.drv
these paths will be fetched (17.86 MiB download, 17.92 MiB unpacked):
/nix/store/01h4dkp0nyrsw0zawmasxawhfik474ma-Python-3.10.0a4.tar.xz
/nix/store/9kwzs3pplms8sijf55sdryypzvic4x1s-python-3.x-distutils-C++.patch
/nix/store/cxnlnh40dvw6ipfq4f9sdba0z2l291iz-python-setup-hook.sh
/nix/store/glhx09dkz4h6b6gvs42j7nsn2jv7bn3m-readline-6.3p08-dev
/nix/store/l2gg10pmpcj7awz93yk4q2n5is5y9kjl-nuke-references
Compare that to:
$ brew outdated
node-build (4.9.28) < 4.9.31
openssl@1.1 (1.1.1i) < 1.1.1j
pyenv (1.2.22) < 1.2.23
python@3.9 (3.9.1_8) < 3.9.2_1
Homebrew wins this one by… a lot. Maybe there’s a way to get better output out of nix
, but I can’t really figure anything out myself.
I think that’s all the like… commands that I use on the regular. We’re into weirder territory now:
138 ✓ info
135 ✓ uninstall
108 ✓ install
63 ✓ search
41 ✓ upgrade
22 ✓ list
19 ✓ update
15 ✓ outdated
7 link
7 doctor
5 leaves
5 cleanup
4 untap
3 tap
3 reinstall
I don’t know what brew link
does. I think it’s a command that I run when Homebrew tells me to and usually means that something has gone very wrong. Maybe? I don’t know.
I love brew doctor
. It’s always spewing dozens of problems for me to ignore. I think it’s useful when you’re installing Homebrew for the first time and getting it set up, but I don’t think it’s ever useful ever again. I love warnings like this:
Warning: Unbrewed static libraries were found in /usr/local/lib.
If you didn't put them there on purpose they could cause problems when
building Homebrew formulae, and may need to be deleted.
Unexpected static libraries:
/usr/local/lib/libcord.a
/usr/local/lib/libgc.a
man wouldn’t it be neat if there were a package manager that could isolate builds such that
I am surprised to see that there is actually nix doctor
:
$ nix doctor
Store uri: local
error: not an absolute path: './node_modules'
Ooookay. So just as useful, I guess?
I think it’s trying to complain that I have ./node_modules/.bin
in my PATH
? Which I do so I can invoke my devDependencies
when I use npm
. It’s never caused a problem before. I don’t know if this is a problem that nix doctor
is trying to report or if it’s an error that nix doctor
encountered that broke it. So I temporarily remove that from my PATH
to re-run it:
$ nix doctor
Store uri: local
Okay? Still no idea. It would be nice if it told me what it was doing – what checks I had passed – so that I could feel good about myself. Like when you run that SSL test thing and it gives you an A+. There’s nothing for you to do, but it feels good to hear, you know?
brew cleanup
is basically nix-collect-garbage -d
. Did you know you needed to manually collect garbage in Homebrew? I remember the first time I ever ran that and it freed up like 8 GB of old cached files or whatever. Good times.
brew tap
and brew untap
are basically nix-channel --add
and nix-channel --remove
, if I understand them correctly.
And then brew reinstall
, which in Nix is just nix-env -i
again. I think. Or just uninstall/reinstall. Honestly: not a very important command. I probably have only ever run it out of desperation, and it probably has never fixed the problem I was having.
Okay! And that’s how to use Nix instead of Homebrew.
It’s not as nice! In a lot of ways! And you can’t install hg
!
I might make more helpers as time goes on so that I can just type sd nix install
or sd nix outdated
or whatever, and paper over some of the major UI problems that way. sd nix info
has already been pretty nice to have, in the time between writing this post and publishing it.
Maybe every Nix user ever ends up doing this, and that’s why the UI is, you know, the way that it is.
Anyway, I’m going to try using Nix as my package manager for a little while and see what happens. I will report back if anything exciting happens.
- Is there a better way to see outdated packages?
- How should I install my login shell with Nix?
- What happens if two packages produce outputs with the same name? Like what if I install
/nix/store/foo-1.0/bin/foo
and/nix/store/bar-1.0/bin/foo
– what ends up in~/.nix-profile/bin/foo
?
-
Of course, I’ve never needed to know anything about Homebrew to use Homebrew, because Homebrew just kinda works most of the time. ↩︎
-
I also use lots of Homebrew casks, and I don’t bother trying to replace those in Nix – that’s why you don’t see
emacs
in that list. So I wouldn’t get to uninstall Homebrew anyway. how dare you suggest that i don’t useemacs
↩︎ -
No way did I only think of this after uninstalling my current login shell and having to scramble to get a working terminal again so I could reinstall it. I definitely thought of it ahead of time because I’m very conscientious. ↩︎
-
Yes, I used
-e
instead of--uninstall
. I came up with a mnemonic to remember the short version: “-i
before-e
.” Cute, right? ↩︎ -
How naïve I was. Yes,
nix-env -u
exists. And it might even work, for some packages. But if you’re using the Nixpkgs repository,nix-env -u
is basically useless. See My first package upgrade, in which I first encounter its failings, How to install Python, in which I learn why it doesn’t work, and Ambiguous packages, in which I discover just how deeply broken it really is. Or see Setting up a declarative user environment for a simple alternative that works the way you want it to, and that I have been using without issue since writing that post. ↩︎