P A R T . . . O N E

> Generally everything you're saying about Linux package management vs Homebrew is the opposite of my experience. A coworker uses Linux for his machine and apt's packages always seem out of date or he has problems installing things, whereas Homebrew always just works.

A TALE OF TWO HOMEBREWS: the package management tools

I think it would be helpful here to distinguish Homebrew-as-package-manager ( https://github.com/Homebrew/brew ) from Homebrew-as-repository ( https://github.com/Homebrew/homebrew-core ).

As a technology or stack of technologies for building, installing, upgrading, and configuring software, Homebrew can be compared to package management systems on Linux distros. In my opinion, the package management system is the heart of a Linux distribution. Two distros are essentially interchangeable if they share repositories and a package manager; they're the same system twice, each time with a different desktop environment installed by default. And with a little courage and good ole apt, you can even convert from Ubuntu to Linux Mint! Here, I don't think Homebrew (in particular `brew`) stands up very well.

For example, Homebrew doesn't handle orphaned packages. When a package is installed, it drags in all its dependencies. But when the package is uninstalled, they are left behind. In the Linux world, different tools handle this problem differently. The `aptitude` command-line interface for apt tries to remove all orphaned dependencies every time you install, uninstall, or upgrade. Apt users (Debian, Ubuntu, Linux Mint, PopOS, all their remixes and many more) can also use `apt-get autoremove` to clean up any orphaned dependencies they may have left over. Gentoo users can remove orphaned dependencies with `emerge --depclean`. Fedora users can run `dnf autoremove`. NixOS and GuixSD have a garbage collector for packages.

By contrast, if you run `brew install cowsay` and then `brew uninstall cowsay`, everything that Homebrew installed just to put `cowsay` on your system is left behind. You're accruing ‘cruft’ with each package installation. And sometimes you might then find out that some package you were compiling got a different library version than you expected, and you can't for the life of you figure out why you even have that version of that library installed. It appears at a glance that Homebrew lacks any solution for removing orphaned dependencies after the fact, as with `apt autoremove`, but there is a third-party extension for removing hopefully-obsolete dependencies at uninstall time. It gives you a `brew rmtree` subcommand.

The first thing you'll notice on its GitHub page is a great big warning ( https://github.com/beeftornado/homebrew-rmtree#warning ). One thing this warning reveals is that Homebrew doesn't distinguish between packages that the user has chosen to install and packages that just happen to be present on the system. This is the reason that `brew rmtree` might remove command-line tools that you actually use-- because nothing _else_ you have installed depends on them, even though they're a package you explicitly told `brew` to install, because you wanted it. The author's workaround is a command-line flag `--ignore`, which allows you to list packages you want preserved, presumably after you notice that they got removed when you didn't want them to. The best user experience I can imagine for this feature is aliasing or wrapping the command with a list of your ‘preserved packages’, so that `brew rmtree` becomes safe to use with respect to them.

Not only is this not a problem with the package manager of any decent Linux distro, in addition to tracking which packages were installed on purpose (enabling the automatic removal of unused dependencies in a way that is actually safe to use), Linux package managers also offer a whole range of powerful and sensible tools for handling the trickier cases. On Sabayon Linux, for example, the Entropy package management system provides (vs. the `--ignore` flag on a third-party extension we get with Homebrew, one command-line argument you can use at install time only) a system of package masking, whereby one can specify ranges of versions, prohibit or require that particular packages be bleeding edge releases or that they come from the battle-tried stable repositories, and so on. On openSUSE, with `zypper`, each repository has a priority (they are all the same priority by default), which users can modify. By prioritizing your repositories, you can control the versions of specific pieces of software but still receive security updates. You can also pin packages at specific version or range of versions. In addition, Zypper associates each package with a `vendor`, to make better sense of choosing packages from different sources. This is really nice if, for example, you want your base system to be a stable distro release, but you want Firefox and all of its dependencies to come from a repository that houses the latest stable Firefox builds. At the same time, you may want to install the latest version of your favorite multimedia player, but retain the codecs from your base system, or a repository that ships codecs with the greatest compatibility. Many Linux distros provide excellent tools for easily managing things like this.

To return to Homebrew, on that side we have `brew pin`, which allows you to tell Homebrew not to automatically update a particular package along the way to doing other things you tell it to do in the future. For repository management, Homebrew has the tap system, which offers no real priority but a `brew tap pin` command whose documentation contains the following caveat:

‘Do note that pinned taps are prioritized only when the formula name is directly given by you, i.e. it will not influence formulae automatically installed as dependencies.’

so it does not effect a systemwide change (and presumably no mechanism for doing so is provided). Besides only affecting part of the system, the only function of `brew tap pin` is ‘to prioritize a tap over homebrew/core’ ( https://docs.brew.sh/Taps ). So we get no way to control the relative priorities of taps at all.

Linux distros have been hacking on their package management tools for ages and ages, as core components of entire operating systems. The tools are usually written in C and operate on little databases. They've equipped these systems to roll out configuration changes (after backing up the old ones), safely migrate users to equivalent packages when old forks die, stop and start services, optionally guide users through configuration, install bootloaders, recalculate font caches, tell the desktop environment to reindex its application shortcuts for search. And they do these things in ways that make it possible to disable the ‘hooks’ associated with these packages, (or re-run them whenever), and to lint these packages for all kinds of standards. Macros in the package definition formats make it possible for a single package definition to be usable for multiple Linux distributions, even including different operating system kernels. Homebrew only recently got ‘bottles’, which are a non-integrated mechanism of providing caches of prebuilt binaries for Homebrew's source-based packages; many Linux distros, on the other hand, have been distributing updates as compressed binary deltas between the currently installed version and the latest installed versions, for years.

Distros are working on deep, deep aspects of package quality, as well, in a way that I haven't seen as much outside of those projects. Debian, for example, has been doing a ton of work with upstream projects to make their builds deterministic, so that Debian packages can be built so reliably that one can usually expect bit-for-bit reproducibility. NixOS and GuixSD have pioneered and developed an approach to package management which draws upon the principles of functional programming in order to facilitate exhaustive, declarative configuration and provide guarantees portability, determinism, and the ability to roll back upgrades and installations at will. openSUSE builds every one of their packages by allocating a fresh, empty virtual machine and installing only the dependencies specified in the package, to make it impossible for their packages to have any missing, hidden, or broken dependencies.
And on Linux systems, package managers are tightly integrated into the system in a way that Homebrew will likely never be, not for any lack of expertise or effort on the part of Homebrew developers, but because the choice is ultimately not up to them. On a GNU+Linux system, the same package management tool is used for system upgrades as for application installation, and there's a lot of power, convenience, and predictability which comes from that.