Contrast with pacman on Arch, which is incredibly fast at nearly every operation.
Unfortunately I’ve found all the package managers for macOS to be lacking.
brew is slow, but has packages for most things and generally up to date.
fink packages seem a bit out dated last I checked. At least the ones I cared about.
MacPorts is also slow and somewhat out of date.
pkgsrc doesn’t have Apple M1 binary packages available yet.
I haven’t tried Nix.
Sometimes I’m tempted to try my hand at writing my own package manager for macOS, but getting a critical mass of packaged software seems quite hard.
Some new package manager that's a mix of pkgsrc, brew, and MacPorts would be awesome. Especially if portable and could somehow also replace nvm, rbenv, pythonenv, and things of that nature.
Nix evaluation can be slow sometimes, but installation is generally relatively fast, since everything in Nix is fully parallelizable. It's generally several times faster than apt, for installs that involve multiple large downloads, for example, and historically it's also faster than pacman.[1] (I think this is probably no longer the case with Pacman 6.0, which finally parallelizes downloads. In that case Nix evaluation time probably dominates.)
I imagine what you're identifying as slow is searching through Nix packages. On the old/stable release of Nix, you use something like `nix-env -qaP` for this, and that requires evaluation of every package in Nix, and can take a long time. `nix search`, however, is extremely fast on the unstable releases of Nix.
A Nix search using the old method is extremely slow, > 7 seconds:
I ~ time nix-env -f '<nixpkgs>' -qaP libreoffice
libreoffice libreoffice-7.0.4.2
libreoffice-still libreoffice-7.0.4.2
libreoffice-still-unwrapped libreoffice-7.0.4.2
libreoffice-fresh libreoffice-7.1.2.2
libreoffice-fresh-unwrapped libreoffice-7.1.2.2
libreoffice-qt libreoffice-7.1.2.2
________________________________________________________
Executed in 7.31 secs fish external
usr time 6.79 secs 335.00 micros 6.79 secs
sys time 0.52 secs 67.00 micros 0.52 secs
A Nix search using the new method, which leverages cached evaluation of Nixpkgs, takes about half a second: nix-shell -p nixUnstable --run fish
time nix search --experimental-features 'nix-command flakes' nixpkgs libreoffice
<snip>
* legacyPackages.x86_64-linux.libreoffice (7.0.4.2)
Comprehensive, professional-quality productivity suite, a variant of openoffice.org
<snip>
________________________________________________________
Executed in 576.30 millis fish external
usr time 561.00 millis 252.00 micros 560.75 millis
sys time 16.07 millis 55.00 micros 16.01 millis
And installation, in either case, is pretty fast. It takes a few second to install, for example, Libreoffice on my system, with the vast majority of the time taken up being the download: time nix-env -f '<nixpkgs>' -iA libreoffice
installing 'libreoffice-7.0.4.2'
these paths will be fetched (153.59 MiB download, 574.57 MiB unpacked):
<snip>
copying path '/nix/store/8yq9h91bsbgi62dhvh4xfqxvhq58b5y4-libreoffice-7.0.4.2' from 'https://cache.nixos.org'...
building '/nix/store/nhmf4ak414lrzy3387kbsx8brm8hckds-user-environment.drv'...
created 68 symlinks in user environment
________________________________________________________
Executed in 21.32 secs fish external
usr time 1.34 secs 0.00 micros 1.34 secs
sys time 0.43 secs 521.00 micros 0.42 secs
(I tried for a bit to get Linuxbrew installed so I could test the same functionality with it, but Homebrew isn't portable enough to work on NixOS, since it hardcodes a ton of command and interpreter paths to files that don't exist on some distros.)—
1: https://michael.stapelberg.ch/posts/2019-08-17-linux-package...
Now that I'm on my Ubuntu system, I can easily test Nix and Homebrew side-by-side.
To make it fair, I've removed almost everything I have installed with Nix, as well as Homebrew. Interestingly, Homebrew is using ~2x as much disk space, and listing my installed packages (none) with Homebrew takes ~30x as long as with Nix:
~
du -hsxc /nix/store /home/linuxbrew
238M /nix/store
584M /home/linuxbrew
822M total
~
time nix profile list
________________________________________________________
Executed in 19.88 millis fish external
usr time 11.21 millis 174.00 micros 11.04 millis
sys time 11.18 millis 141.00 micros 11.04 millis
~
time sudo -H nix profile list
0 flake:nixpkgs#legacyPackages.x86_64-linux.nixFlakes github:NixOS/nixpkgs/b2f2d04f01b3de3abad0dd9bc48ad7a3ee32bbc6#legacyPackages.x86_64-linux.nixFlakes /nix/store/g4xva7x46h5bqqh811n12dzjc7325x21-nix-2.4pre20210326_dd77f71
1 flake:nixpkgs#legacyPackages.x86_64-linux.starship github:NixOS/nixpkgs/b2f2d04f01b3de3abad0dd9bc48ad7a3ee32bbc6#legacyPackages.x86_64-linux.starship /nix/store/5n36vabqg701l9nji30m1r004zcy4q0w-starship-0.51.0
________________________________________________________
Executed in 29.91 millis fish external
usr time 27.30 millis 380.00 micros 26.92 millis
sys time 4.72 millis 283.00 micros 4.43 millis
~
time brew list
==> Formulae
________________________________________________________
Executed in 881.81 millis fish external
usr time 527.24 millis 411.00 micros 526.83 millis
sys time 173.15 millis 298.00 micros 172.85 millis
Homebrew takes about twice as long to search for packages related to Libreoffice (results omitted to HN won't tell me this post is too long): ~
time nix search nixpkgs libreoffice >/dev/null
________________________________________________________
Executed in 554.37 millis fish external
usr time 537.39 millis 434.00 micros 536.95 millis
sys time 16.44 millis 289.00 micros 16.15 millis
~
time brew search libreoffice >/dev/null
________________________________________________________
Executed in 1.33 secs fish external
usr time 578.16 millis 0.00 micros 578.16 millis
sys time 124.57 millis 367.00 micros 124.20 millis
I had some trouble looking for large packages to install with Homebrew and Nix for comparison. It seems that GUI apps (Libreoffice, Firefox, etc.) tend to be packaged exclusively as casks for Homebrew, which means they don't exist on Linux. So instead, I selected a handful of packages from the Linux formulas list[1] that I figured would pull in a substantive dependency closure. Apparently there's no bottle for Crystal, so it took a very long time to install because it had to compile from source. Prior to that, though, installation crashed because of a ulimits issue when ‘pouring the bottle’ for LLVM: ~
time brew install imagemagick bat feh crystal python mruby
==> Downloading https://ghcr.io/v2/linuxbrew/core/zlib/manifests/1.2.11
######################################################################## 100.0%
==> Downloading https://ghcr.io/v2/linuxbrew/core/zlib/blobs/sha256:6f604280b9e056da163b81cef3076b2e39bbd2b8d3cc8c36d27c19c472c760a4
==> Downloading from https://pkg-containers.githubusercontent.com/ghcr1/blobs/sha256:6f604280b9e056da163b81cef3076b2e39bbd2b8d3cc8c36d27c19c472c760a4?se=20
######################################################################## 100.0%
==> Downloading https://ghcr.io/v2/linuxbrew/core/libpng/manifests/1.6.37
######################################################################## 100.0%
==> Downloading https://ghcr.io/v2/linuxbrew/core/libpng/blobs/sha256:c5d219b27780c730af50693110ed2c79e50200d3595c7e395998fe3950354ec9
==> Downloading from https://pkg-containers.githubusercontent.com/ghcr1/blobs/sha256:c5d219b27780c730af50693110ed2c79e50200d3595c7e395998fe3950354ec9?se=20
######################################################################## 100.0%
==> Downloading https://ghcr.io/v2/linuxbrew/core/bzip2/manifests/1.0.8-1
######################################################################## 100.0%
<snip>
==> Installing crystal dependency: llvm@9
==> Pouring llvm@9--9.0.1_5.x86_64_linux.bottle.tar.gz
Error: Too many open files @ rb_sysopen - /home/linuxbrew/.linuxbrew/Cellar/llvm@9/9.0.1_5/bin/llvm-jitlink
________________________________________________________
Executed in 600.02 secs fish external
usr time 203.24 secs 479.00 micros 203.24 secs
sys time 70.80 secs 313.00 micros 70.80 secs
I decided to re-run the brew install command, but skip Crystal, which only took a few more seconds: ~
time brew install imagemagick bat feh python mruby
<snip>
==> Installing mruby
==> Pouring mruby--3.0.0.x86_64_linux.bottle.tar.gz
/home/linuxbrew/.linuxbrew/Cellar/mruby/3.0.0: 323 files, 14.7MB
________________________________________________________
Executed in 20.18 secs fish external
usr time 9.56 secs 0.00 micros 9.56 secs
sys time 3.06 secs 761.00 micros 3.06 secs
I left Crystal in when I tested Nix, since I knew it would be no problem, and Nix was done in ~36 seconds, meaning Homebrew ~17x the time, skipping one package entirely.
After the two install operations, Homebrew was using several more gigabytes of storage than Nix:
du -hsxc /nix/store /home/linuxbrew
2.0G /nix/store
4.9G /home/linuxbrew
6.8G total
I'll reply to this comment with a ‘warmed-up’ test of installing a single additional package, as well. But it seems pretty clear already that it's misleading at best to characterize Homebrew as faster than Nix.—
Installing one package with Homebrew, where the whole dependency closure is bottled, no building from source:
~
time brew install graphicsmagick
==> Downloading https://ghcr.io/v2/linuxbrew/core/libxfixes/manifests/5.0.3
######################################################################## 100.0%
==> Downloading https://ghcr.io/v2/linuxbrew/core/libxfixes/blobs/sha256:3aaba846570380f7dbf5cf7f93280866318a7730c9c5fd38fd1ad275aec037bd
<snip>
==> Pouring freeglut--3.2.1_5.x86_64_linux.bottle.tar.gz
/home/linuxbrew/.linuxbrew/Cellar/freeglut/3.2.1_5: 20 files, 1.3MB
==> Installing graphicsmagick dependency: jasper
==> Pouring jasper--2.0.32.x86_64_linux.bottle.tar.gz
/home/linuxbrew/.linuxbrew/Cellar/jasper/2.0.32: 40 files, 1.1MB
==> Installing graphicsmagick
==> Pouring graphicsmagick--1.3.36.x86_64_linux.bottle.tar.gz
/home/linuxbrew/.linuxbrew/Cellar/graphicsmagick/1.3.36: 497 files, 14.7MB
________________________________________________________
Executed in 362.04 secs fish external
usr time 56.32 secs 603.00 micros 56.32 secs
sys time 20.00 secs 383.00 micros 20.00 secs
Installing the same package with Nix: ~
time nix profile install nixpkgs#graphicsmagick
________________________________________________________
Executed in 11.58 secs fish external
usr time 701.80 millis 500.00 micros 701.30 millis
sys time 310.06 millis 319.00 micros 309.74 millis
So in terms of time, they're not really in the same ballpark. Nix is operating in ‘wait a few seconds’ time, and Homebrew is operating in ‘go make yourself a cup of coffee and come back’ time.And after running `brew cleanup` and `brew autoremove`, then `nix-collect-garbage` and `nix-store --optimize`:
~
du -hsxc /nix/store /home/linuxbrew
1.9G /nix/store
3.9G /home/linuxbrew
5.8G total
Listing installed packages now: ~
time brew list
==> Formulae
aom fontconfig imagemagick libelf libmpc libx11 libxshmfence mesa-glu python@3.9 xinput
bat freeglut imath libev libomp libxau libxslt mpdecimal readline xmlto
binutils freetype imlib2 libexif libpciaccess libxcb libxt mpfr rtmpdump xorgproto
bison gcc isl libffi libpng libxdamage libxv mruby ruby xz
brotli gdbm jasper libgcrypt libpthread-stubs libxdmcp libxvmc ncurses shared-mime-info zlib
bzip2 gettext jbig2dec libgpg-error libsm libxext libxxf86vm nghttp2 sqlite zstd
c-ares ghostscript jemalloc libheif libssh2 libxfixes libyaml openexr unzip
curl giflib jpeg libice libtiff libxi little-cms2 openjpeg util-linux
docbook glib krb5 libidn libtool libxinerama llvm openldap wayland
docbook-xsl gmp libde265 libidn2 libunistring libxml2 lm-sensors openssl@1.1 wayland-protocols
expat gnu-getopt libdrm liblqr libva libxrandr m4 pcre webp
feh graphicsmagick libedit libmetalink libvdpau libxrender mesa pkg-config x265
________________________________________________________
Executed in 580.23 millis fish external
usr time 478.15 millis 0.00 micros 478.15 millis
sys time 108.59 millis 759.00 micros 107.83 millis
~
time nix profile list
0 flake:nixpkgs#legacyPackages.x86_64-linux.imagemagick github:NixOS/nixpkgs/b2f2d04f01b3de3abad0dd9bc48ad7a3ee32bbc6#legacyPackages.x86_64-linux.imagemagick /nix/store/5ps3bydsjiqgycbzg2cxkwyz50m4rga7-imagemagick-7.0.11-6
1 flake:nixpkgs#legacyPackages.x86_64-linux.bat github:NixOS/nixpkgs/b2f2d04f01b3de3abad0dd9bc48ad7a3ee32bbc6#legacyPackages.x86_64-linux.bat /nix/store/0lyhwrmf3nbadavaq3ixfndhiaw3b86l-bat-0.18.0
2 flake:nixpkgs#legacyPackages.x86_64-linux.feh github:NixOS/nixpkgs/b2f2d04f01b3de3abad0dd9bc48ad7a3ee32bbc6#legacyPackages.x86_64-linux.feh /nix/store/sy0xxs6lbvwhidg5dlj6ckwik2mn3b6l-feh-3.6.3
3 flake:nixpkgs#legacyPackages.x86_64-linux.crystal github:NixOS/nixpkgs/b2f2d04f01b3de3abad0dd9bc48ad7a3ee32bbc6#legacyPackages.x86_64-linux.crystal /nix/store/fbnk8kwbllsn2m60d43y2cfqpwbw5pk4-crystal-1.0.0
4 flake:nixpkgs#legacyPackages.x86_64-linux.python github:NixOS/nixpkgs/b2f2d04f01b3de3abad0dd9bc48ad7a3ee32bbc6#legacyPackages.x86_64-linux.python /nix/store/pp0fp5zds7wqhyx6zqnnxl2aqzf4a51r-python-2.7.18
5 flake:nixpkgs#legacyPackages.x86_64-linux.graphicsmagick github:NixOS/nixpkgs/b2f2d04f01b3de3abad0dd9bc48ad7a3ee32bbc6#legacyPackages.x86_64-linux.graphicsmagick /nix/store/fm7fa16blb4xadsxxagxy0vfmhrjqns0-graphicsmagick-1.3.36
________________________________________________________
Executed in 25.18 millis fish external
usr time 18.24 millis 452.00 micros 17.79 millis
sys time 9.17 millis 279.00 micros 8.89 millis
Nix's output is a mess, visually, and that'll change in the future. But note that it doesn't bother me with the whole dependency closure, and instead tracks what I actually wanted to install.Finally, a removal test. I'll uninstall everything but graphicsmagick, with both package managers.
time brew remove imagemagick bat feh python mruby
Error: Refusing to uninstall /home/linuxbrew/.linuxbrew/Cellar/python@3.9/3.9.5
because it is required by freeglut, glib, graphicsmagick, jasper, libheif, liblqr, llvm, mesa, mesa-glu and shared-mime-info, which are currently installed.
You can override this and force removal with:
brew uninstall --ignore-dependencies imagemagick bat feh python mruby
Oops. A second try, since Homebrew put me in dependency hell because it doesn't know the difference between what's installed as a dependency and what I explicitly asked it to install (I thought this functionality was needed to implement `brew autoremove`??). ~
time brew remove --ignore-dependencies imagemagick bat feh python mruby
Uninstalling /home/linuxbrew/.linuxbrew/Cellar/imagemagick/7.0.11-13_2... (805 files, 31.4MB)
Uninstalling /home/linuxbrew/.linuxbrew/Cellar/bat/0.18.1... (12 files, 5.5MB)
Uninstalling /home/linuxbrew/.linuxbrew/Cellar/feh/3.7... (26 files, 716.9KB)
Uninstalling /home/linuxbrew/.linuxbrew/Cellar/python@3.9/3.9.5... (2,650 files, 76.9MB)
Uninstalling /home/linuxbrew/.linuxbrew/Cellar/mruby/3.0.0... (323 files, 14.7MB)
________________________________________________________
Executed in 1.11 secs fish external
usr time 874.06 millis 420.00 micros 873.64 millis
sys time 224.91 millis 261.00 micros 224.65 millis
~
time brew autoremove
<snip>
Uninstalling /home/linuxbrew/.linuxbrew/Cellar/libgpg-error/1.42... (48 files, 1.8MB)
________________________________________________________
Executed in 3.35 secs fish external
usr time 2.51 secs 424.00 micros 2.51 secs
sys time 0.82 secs 261.00 micros 0.82 secs
Nice and fast. :)Now for Nix:
~
time begin
nix profile remove '.\*'
nix profile install nixpkgs#graphicsmagick
end
________________________________________________________
Executed in 2.99 secs fish external
usr time 499.04 millis 871.00 micros 498.17 millis
sys time 126.67 millis 527.00 micros 126.14 millis
Not as fast, because I garbage-collected the evaluation of Nixpkgs itself, so it fetched and re-evaluated that. (Also I did this in a weird way because the UI for the `nix profile` command is incomplete and still kinda clunky.)I also need to clean up with Nix:
~
time nix-collect-garbage -d
removing old generations of profile /nix/var/nix/profiles/per-user/pxc/profile
removing generation 49
<snip>
deleting '/nix/store/znvl3v02hy8ffkhwc6m9z1q5pj8j6ppj-setup-hook.sh'
deleting '/nix/store/trash'
deleting unused links...
note: currently hard linking saves -1.14 MiB
188 store paths deleted, 1574.20 MiB freed
________________________________________________________
Executed in 1.23 secs fish external
usr time 14.30 millis 160.00 micros 14.14 millis
sys time 10.70 millis 96.00 micros 10.60 millis
And after cleanup, the usage:
~
du -hsxc /nix/store /home/linuxbrew
412M /nix/store
3.2G /home/linuxbrew
3.6G total
Is there a cleanup command I've missed for Homebrew?Performance considerations aren't really among my main concerns when it comes to package management, so I didn't mention them in my original post about what's ‘wrong’ with Homebrew. But I'd have to add it now.
basing this reply on my past experience with pkgsrc (in a netbsd or linux environments though) as long as you can point pkgsrc to a decent compiler and it can complete its bootstrap phase, it'll likely work.
Homebrew's biggest issue is that it isn't a package manager for MacOS. You can interpret that however you want, but it's versatility is extremely limited compared to "full-fat" package managers like apt and pacman. At the end of the day, all of the MacOS package managers fit together to perform separate stop-gap solutions, where none of them really fell that great to use in the first place. Hell, pacman is so good that it's one of the core reasons I'll probably never end up fully switching my workflow to MacOS.
I don’t like that it changes the rules on file ownership in /opt and /usr/local.
And it’s petty but I really don’t appreciate the beer brewing metaphor. It doesn’t fit and it feels forced. Giving the project a searchable name is fine but cutesy names are irritating when I have real work to do. Just name the tool and especially commands for what they do.
I say this as a person who loves beer.
No, I didn’t RTFM; I’m sure that would help, but I mostly just avoid homebrew when I can, and that’s worked out OK.
Think of it as an installer fetcher and runner, as opposed to a collection of integrated and uniformly built packages.
It's great, because downloading and running installers manually by clicking around sucks. But it's totally separate from the rest of Homebrew, which is a monorepo of build recipes targeting a separate base system, similar in that basic way to things like pkgsrc and FreeBSD Ports.
Want a Reuben? That's:
plumber tile rye beef-corned
plumber grout saurkraut russian
Also since this is in the context of making a sandwich a bunch of options to 'plumber' mention things like toaster oven wattage and temperature. set PLUMBER_PREHEAT=350
set PLUMBER_BACKEND=toaster
This is all kind of a pain. Let's use pipes. Except for some reason whoever defined this uses thousand island instead of russian dressing and expects the sandwich on a plate. plumber pipe reuben --russian --to-goThat said, every time I've used it, I've been struck by design flaws that make Homebrew feel incomplete or immature compared to most Linux system package managers. Some examples:
• The last time I used it, Homebrew didn't even support recursive uninstallation of orphaned dependencies, guaranteeing the accrual of entire packages of cruft over time. This was apparently finally fixed (in the crummy way that apt-get, the worst of all the Linux system package management tools, handles it) only in 2020 [1].
• A secondary package manager taking over all of /usr/local is quite presumptous and not portable at all
• Homebrew's combination of an assumption of a single-user setup with the choice of installing to global directories in the FHS is awful, and imo it's straight up deceptive to claim that Homebrew's default setup meaningfully supports ‘unprivileged package installation’
• `brew cask` is useful, but the cleavage between it and the rest of Homebrew is awkward, and it's more like an installer-fetcher à la Chocolatey than a real package management system
• Homebrew's packages are built with a relatively low degree of isolation from the base system's libraries and toolchain, so it's not unusual for a macOS upgrade to break installed Homebrew packages, and the build outputs and bottles associated with a package are given per-macOS release. (To be fair, the former is a pretty common problem for all kinds of software on macOS, as well.)
• I really don't like Linuxbrew's abuse of /home, which you have to stick with if you want to use bottles (binary caches)
• Homebrew just doesn't support managing dependencies on libraries in some languages (e.g., Perl) at all [2]— even though it includes packages that depend on them— which fails to meet my bare minimum expectations for a general-purpose package manager. In practice, this means that you have to install additional, language-specific package managers to use Homebrew; you have to manage dependencies in them yourself; and Homebrew packages require you to install these dependencies in system-wide locations, polluting your whole operating system (a potential source of dependency hell, as well as a practical nuisance) for their sake. The latter also, of course, undercuts the unprivileged installation claim that Homebrew touts.
To be fair to Homebrew, it's been a while since I used a Mac on a daily basis, and I expect that Homebrew has improved in some ways in the meantime. I know for certain that it's improved, to reiterate an example given earlier, with respect to some, if not all (i.e., not those managed externally) orphaned dependencies. And Homebrew also has some strengths, namely its very slick and orderly command-line interface.
But the real reasons I try to use Homebrew as little as possible aren't about Homebrew being _bad_ so much as my preference for tools with other strengths (which require a design different from Homebrew's to implement). On macOS, I try to use Nixpkgs and nix-darwin as much as possible because I prefer things like:
• declarative configuration rather than imperative state management
• fully isolated builds, more predictable solutions
• using a single package management tool for libraries and apps of all languages
• building .apps from source like any other package
• atomicity of upgrades
• freedom to roll back configurations/installations at any time
• ability to mix and match packages from different releases, modify dependencies for one project without affecting another
• never installing language interpreters or libraries globally, except for login shells
I don't really want to use anything that doesn't have those features, and for that on macOS, Nix is the only game in town. Homebrew is a pile of state to manage, and I'd rather have intentions to declare than a pile of state to manage. That issue isn't unique to Homebrew, of course.
Pkgsrc doesn't share some of Homebrew's (in my view) other defects, like its abuse of the FHS on Linux, single-user design/bad permissions configuration, building many packages directly on top of macOS system libraries that change from OS release to OS release when it doesn't have to, or the expectation that library dependencies for some languages will be managed manually/externally (and in global directories, to top it off), so that's what makes Pkgsrc interesting to me as an alternative to Homebrew, even though Nix will still always be my first-line choice on macOS.
—
1: https://github.com/Homebrew/brew/commit/0fa417706a2143dbec1e...
sh <(curl -L https://nixos.org/nix/install) --darwin-use-unencrypted-nix-store-volume
I don't remember whether I actually did this on my system. I think I manually created an additional volume for /nix, but encrypted it. That approach is also now described in the manual, should you prefer it.[2](Unencrypted should be fine anyway, as the Nix store is designed/assumed to be public (world-readable). Packages and tools in the ecosystem never put secrets there.)
—
1: https://nixos.org/manual/nix/stable/#sect-macos-installation
2: https://nixos.org/manual/nix/stable/#sect-macos-installation...