At this point I should know better than to install postgres via homebrew, but unwanted/unasked-for upgrades is not a feature I'm looking for in a package manager.
What's your experience with other package managers for macOS? Currently looking into macports & nix and not sure which one to choose...
---
Homebrew upgrades dependencies and dependents of those dependencies (which, admittedly, can feel like unrelated) on installation and upgrade. As mentioned in other comments, you can customise this behaviour with `HOMEBREW_NO_INSTALL_UPGRADE` or `HOMEBREW_NO_AUTO_UPDATE`.
Homebrew does this because the alternative is sometimes breaking things. An example:
- you want to install `virtualenv` that depends on `python@3.10`
- the binary package for `virtualenv` you want requires the newest `python@3.10`
- this upgrades `python@3.10` on installation
Now, Homebrew has a choice. Either we upgrade _everything_ that depends on `python@3.10` that you have installed or we knowingly break some of the things you have installed that depend on `python@3.10`. We choose the safer option by default.
The more time left between updating/upgrading, the more likely to have more dependencies updated which requires more dependents to be updated.
---
Regardless, I appreciate this is a problem and we're still figuring out potential solutions for this problem. A reminder that we're a volunteer run project so it's not always as easy as we'd like it to be to get these changes out quickly.
Perhaps in situations like this Homebrew could alert the user "Hey, this package hasn't been updated in a while and 40 other packages depend on it so this is going to take a while. Press enter to confirm or cancel and use an alternative method (HOMEBREW_NO_INSTALL_UPGRADE) to just upgrade the one package. Caveat - this may break things."
My only point (and a source of confusion) here is that I never anticipated the cascading effect that installing a single package can have on seemingly unrelated parts of the system. I've been using homebrew for many years and as far as I remember, the only time anything broke like this was when I explicitly ran update/upgrade.
I'm going to try HOMEBREW_NO_INSTALL_UPGRADE/HOMEBREW_NO_AUTO_UPDATE — it does seem like a more intuitive default, but again, I don't know anything about how homebrew works under the hood.
Thanks again!
Personally, I have moved to downloading large things (Python, Postgres by your example) as standalone apps, and only use Homebrew for small libraries and CLI tools. I think that makes more sense.
You can download all of the Mac Python versions you want from python.org and you can get Postgres from postgresapp.com which provides a nice toolbar interface to start and shutdown servers of various versions.
With homebrew, I never know how many packages will be updated and how long it will take, so sometimes when I just need a small utility, suddenly it'll update a bunch of packages and it takes 45 minutes.
It's a UI problem.
brew outdated will list outdated app/dependencies that need to be updated. If you want to know the app's dependencies, use the info argument. For example brew info ffmpeg will list the information about FFmpeg and list the dependencies that it uses along with status indication if the dependencies is installed or not.
Am I missing something here? I am not sure if it is a UI problem since Homebrew have those functions with a few arguments here and there. Look like you are using a utility that have so much dependencies that it needs or you have habit of using brew upgrade which will prompt to upgrade everything expect pinned apps/dependencies.
Yes. The principle of least astonishment.
If Postgres has an unversioned dependency to PackageX, the only time I might expect Postgres to be updated automatically is if I update PackageX across a major version boundary, risking breaking changes. Even then, I'm not sure I'd want that behavior by default - I'd at least like to be prompted - since often this still won't break anything, or it will likely cause less damage than updating every package with a dependency to PackageX. And if Postgres breaks after I update PackageX I can easily reason about what just happened. The alternative can be incredibly hard to troubleshoot because so many changes have occurred.
Overall, Homebrew feels far too aggressive here, and I think the result, for people who have some understanding of Homebrew behavior, is that they're afraid to touch anything.
Homebrew doesn't meaningfully have versioned or unversioned dependencies, because it doesn't incorporate any notion of version into its dependency solver. When you declare a dependency on `python@3.9`, you're not telling `brew` that ‘I need a copy of the package `python` which satisfies the constraint “version is 3.9.x”.’ Homebrew just treats `python@3.9` as the name of a package which is a whole copy of python, and it can exist alongside other, similar packages like `python@3.8` and `python@3.10`.
Homebrew only incorporates this kind of `@` syntax for select packages of which maintainers have elected to maintain multiple versions. By my count, there are 81 such packages in homebrew-core, and the following is a complete list of all of those which appear in a `depends_on` declaration:
antlr
atkmm
autoconf
cairomm
db
elasticsearch
erlang
gcc
ghc
glibmm
go
gradle
guile
hdf5
headers
helm
isl
libfuse
llvm
lua
mbedtls
mysql
node
numpy
openexr
openjdk
openssl
pangomm
php
proj
pyqt
pyside
python
qt
ruby
swig
synth
tbb
vtk
wxwidgets
So when you look at a Postgres formula in the Homebrew repos and see that it declares an ‘unversioned’ dependency on openldap, for example[1], that does not mean that the formula author has declared ‘any version is fine’, i.e., it does not mean that the dependency is unversioned. It just means that the dependency's version constraints are totally unspecified except implicitly as ‘the only version of that package which is simultaneously present in the glorious monorepo of build recipes’.This is a feature it shares with some source-based package management systems, like Nix and Guix. Those package managers solve this problem by basically vendorizing everything, so installing a new package never upgrades anything else you have installed.
Other source-based package management systems, like Portage and Pkgsrc, have extended their model to include some handling of version constraints, but they also sometimes declare dependencies without specifying a version. That seems dubious to me, since in reality there pretty much always are version constraints that ought to be specified there, but maybe those two projects are mature enough that they've hammered out the problematic cases by now.
—
https://github.com/Homebrew/homebrew-core/blob/master/Formul...
That said, I did notice recently the same effects as the OP. I.e. I try to install something small and it depends on something like python, which triggers "upgrade the whole universe" loop, which breaks a ton of things (e.g. python upgrade doesn't carry over installed modules, but does change current version - which breaks many local python scripts since their dependency modules are now in the old version). Auto-cleanup tends to break a lot of things too - especially bad for commonly-used things like openssl or ICU which commonly are linked as shared libs.
I think there are settings for changing all of this, but you only find out once you've already been bit. Maybe it's worth considering making it somehow explicit - e.g. ask the user "do you really want to upgrade the whole universe" before going in? In many cases, I'm completely fine with using python 3.7 even if python 3.10 is already out. And most of the tools that depend on python would be fine with it too. It would be nicer if low-friction mode would be the default.
> Now, Homebrew has a choice. Either we upgrade _everything_ that depends on `python@3.10` that you have installed or we knowingly break some of the things you have installed that depend on `python@3.10`. We choose the safer option by default.
A look at the formulae in homebrew-core shows that Homebrew basically doesn't consider versions in its dependency management at all, so formula are just using the ‘@x.y.z’ in place of all manner of version constraints. So you get wild stuff like this line here[1]:
depends_on "curl" # curl >= 7.55 is required
wat.Of course the recursive update policy is going to be weirdly painful for some users! Homebrew doesn't even attempt to encode some very basic aspects of dependency, so now (a) you can't know what you're going to break if you do upgrade a common dependency and (b) you can't know when you can afford not to upgrade a dependency shared by a package you're about to install and the already-installed system. The choice you outline above is one that is not faced by most package managers, because they don't make this mistake. The naive wheel reinvention with Homebrew is so eternally disappointing, and it explains a lot of the pain users experience with it.
> Homebrew upgrades dependencies and dependents of those dependencies (which, admittedly, can feel like unrelated)
One relatively non-disruptive thing you might be able to do to make this behavior less surprising to users is (offer a way to?) print the dependency tree for package installations/upgrades that pull in upgrades of their ‘siblings’. You'd probably want to just do it in a textual way, but this project seems to model the kind of logic you'd want for printing dependency trees with Homebrew as it exists.[2]
—
1: https://github.com/Homebrew/homebrew-core/blob/master/Formul...
2: https://github.com/martido/homebrew-graph/blob/master/cmd/br...
What’s the difference in what Homebrew is doing from what normally happens with something like Pacman or Apt? My impression is that “core” binaries, like Python might be, don’t get updated as often as they would on Homebrew, and perhaps those dependency chains are less frequent. Is it because (e.g.) python versions change rarely on linux package managers? Or something about how the binaries are compiled?
Homebrew is a source-based package manager, where packages are built from a single shared source of build recipes, and distribution of software happens by distributing the whole collection of recipes. Support for a binary cache is added through the identification of build recipes with the hashes of archives of binaries.
APT is what's called a binary package management system. While build recipes can be stored in a central repository, they don't have to be, as binary package management systems typically identify two kinds of packages: source packages and binary packages, which can be distributed separately. (Some repositories may not even include source packages.) Source packages contain metadata like build recipes, but things like archives of the upstream source code, patches, and sometimes helper scripts that might be usd in building. Binary packages include the actual built artifacts; they're installable packages.
In the source-based package management world, if packages can be added to the main collection through external sources, this is typically done by pretending that those packages were embedded in (some particular version of) the main repository of build recipes. Collections of packages that are ‘injected’ into the main package collection like this are typically called ‘overlays’.
In binary package management systems, an external package is not expected to have access to some copy of a complete collection of build recipes from some ‘master’ repository. Instead, source packages are distributed individually using the same mechanisms as binary packages. This means that the global namespace of terms which refer to packages can't be navigated implicitly at build time like on source-based systems, the identification of actual source code and build instructions with some package name happens both at build time and at install time. And in both cases, explicit metadata (package names, package versions, equivalency classes, lists of conflicting packages, etc.) is used to determine what versions of dependencies get pulled in.
As a result, binary package management systems always include dependency solving at install time, and usually include some explicit version constraint for every single dependency declared in a package's build recipe. This means they have the resources to look at what's going to be installed, what's already installed, and say ‘oh, the new installee requires Python <= 3.10.4, and since your installed packages together only need Python >= 3.10.2 and you already have python-3.10.3 installed, we don't have to touch Python to install this new package unless you ask us to’.
Pacman is somewhat atypical. The build system is essentially source-based and monolithic (there's no source package format) and the AUR is essentially an overlay, but the high-level CLI tool acts like a binary packager and uses the language of repositories to describe dealing with multiple package sources. Pacman's dependency resolution behavior is also unusually unreliable, and probably closer to what `brew` does than what `apt` does. Pacman has the resources to describe and test version constraints, both in its build system and in its CLI tools, but they're underutilized in a way that's more typical of source-based package management systems than binary ones. Homebrew is similar in that such constraints are radically underutilized (they're totally absent).
The main difference between Homebrew's behavior and Pacman's in this instance is that by default, just `pacman -S` doesn't update pacman's collection of package definitions (pacman -Sy does) and Homebrew always tries to install from the latest build recipes.
I imagine that Pacman's capacity for handling version constraints means that the story with `pacman` will usually be a bit better than with `brew`, but that otherwise `brew install` will be similar to `pacman -Sy` and `HOMEBREW_NO_INSTALL_UPGRADE=1 brew install` will be similar to `pacman -S`. (Pacman also sometimes makes the opposite tradeoff that Howell describes for Homebrew sometimes; it's not unheard of for installing new software or updating software using Pacman to break installed software.)
The irony is that poster was trying to install a solution to this very problem (virtualenv).
The broader problem is that, at this point, python is effectively its own OS. Coupled with macOS being schizophrenic between "We want to provide a posix environment for users" and "No user should ever look under the hood, so no need to tell them about all the duct taped bits."
Which brew attempts to bridge. Admirably, but never perfectly.
Obligatory: https://xkcd.com/1987/
As a result, Python packaging has an utterly insane design that it can't evolve its way out of.
Problems that have proven more or less intractable for resolution by evolutionary change:
- total lack of static metadata— you must download a source package and attempt to build it in order to determine what its actual dependencies are
- relatedly, package setup scripts are way too powerful and can do literally anything, which makes automating Python packaging a nightmare
- any possible vendorization is deeply limited by the fact that Python processes can't include multiple versions of a single library
- the behavior of all Python packaging tools relies on the implicit state of PyPI, forcing a high degree of non-determinism unless you go to great lengths to take snapshots of PyPI
- native dependencies (i.e., dependencies on C libraries) are either just totally undeclared, or packaged abuse setup.py in order to manually compile them in an ad-hoc, unmanaged fashion
There's no way out of this that doesn't involve breaking things for Python developers who are, whether out of shortsightedness or resigned pragmatism, relying on or working around the many, various bad practices that the Python packaging 'system' currently allows.This is why programmatically generating Python packages for general-purpose package managers like Homebrew ranges (depending on the resources made available by the package manager's design) ranges from impossible to stupidly complex and computationally expensive to kinda works but has no hope of reliability/correctness/completeness.
AFAIK homebrew cannot automatically modify $PATH before starting a program such that $PATH points to the correct dependencies for that program. If you have multiple versions of some dependency installed, it is up to you to run `brew switch` before running your program to manually set the correct versions for each dependency.
Thanks for all the work you put in homebrew, I really appreciate it. Just upgraded to Monte Rey a few days ago and I was kind of fearful that some of the things that I needed where not going to work... nop, every singe package that I've needed so far was installed successfully.
Thanks!
My life became much better after setting this.
I wouldn't be averse to a HOMEBREW_AUTO_UPDATE_INTERVAL=180d either, or HOMEBREW_AUTO_UPDATE_ASK=y .
I think we need a new package manager since its not the fault of homebrew that many packages have hardcoded stuff in there.
Anyway, I just wanted to say you guys are doing a great job!
1. Nix has very limited support for GUI applications of the kind that you'd find in `brew cask`. Not no support, some of them are there, but limited support compared to brew. If you use brew as an out-of-the-box install-everything setup like me, you'll not be able to replicate that easily with Nix.
2. Nix can't currently build Swift applications because of some Xcode something I didn't fully understand.
3. I found a small number of packages that are broken on Nix on my aarch64 / M1 MacBook Air (and marked broken, so when you try to install it complains) and a small number that are broken without being marked broken. In specific, I think R was one that surprised me -- this is a major programming language and although the build requirements are onerous (it's a mix of C and Fortran), I would think it would work.
That being said the actual process of installing, updating, and upgrading seems much faster and dramatically less shitty than homebrew, and so I recommend migrating stuff off of brew anyway.
For anyone who remembers the year 2000 and having to compile everything from scratch, Homebrew is brilliant - but there are complexities and quirks. For Python I’d go with pyenv. Postgres - well, watch your step and maybe use Docker instead.
I'm reaching the tipping point to adopt Nix, after seeing Nix as an alternative for supporting project after project. The unstated subtext always seems to be "Nix is what the smart kids are using, but it's harder to learn."
Haskell? Same religion. But the joke is that one needs a PhD in category theory to use Haskell at all, and package management has long been a Haskell sore point, yet Nix hasn't taken over.
The Lean Theorem Prover?
https://leanprover.github.io/lean4/doc/make/nix.html
A century ago, Hilbert's program to formalize and automate mathematics was stopped in its tracks by developments in logic such as Gödel's incompleteness theorems. There is a new push involving many leading mathematicians, using the Lean theorem prover. While some imagine computers replacing people, a better model is how a chess grandmaster checks their analysis using computers. Even as the experience of observing computer Go games leads to moments of amazement, as if observing an alien tournament, the role of a human to appreciate and generalize is preserved. Primates have long used tools; our future is to coevolve with machines.
Dependency hell and Gödel's incompleteness are cut of the same fabric. This is where I first read about Nix flakes.
That said, I'm sticking to Homebrew. Not really sure about this auto-upgrading, maybe it can be deactivated, on the other hand probably it's a good way to keep packages updated.
Sure, you will eventually need to use the Nix language if you want to have complex and reusable configurations, but this is actually not needed for beginners! To install packages, you need the Nix package manager (with channels already setup) and use `nix-env` to install for the current user. You can just search for the package in https://search.nixos.org/packages and use the command directly.
If you want to have user configurations, use home-manager, just modify the example to add other options, you seldom need complicated Nix expressions unless you need to build a package by yourself.
When you really need to do so, you may want to have a look at other's build script, and make sure you understand everything in the build process, including compile dependencies and runtime dependencies, environment variables, etc. You may want to read Nix Pills at some point, but maybe you can get away with reading just the relevant section (incomplete knowledge, but usually enough to get the work done).
Indeed, I've been using Nix for many years (on Ubuntu, NixOS and macOS), and I now find the apt-get style 'considered harmful'.
Instead, I define a single Nix derivation ("package") which provides everything I want in its 'bin/' directory, e.g.
with nixpkgs;
buildEnv {
name = "my-macbook-programs";
paths = [ bash gimp /* etc */ ];
}
The nix-build command creates a symlink called `result`, so I put `/path/to/my/git/repo/result/bin` in my $PATH.Removing Nix is easier on operating systems where root is root (normal Unix-likes without a lot of filesystem-related security policies enabled). Nix only uses a separate APFS volume on macOS because Apple disabled writing to /.
The way that affects the uninstall process should be documented.
I spotted some people who've recently worked on Nix's install scripts and uninstall procedure elsewhere on the thread. You can file an issue if you think they'll miss your comment :)
I still run Homebrew for a couple of services and casks, but I'm down to less than 20 packages still installed that way.
There have been a few points of friction (which I'm used to as a polyglot with lots of active projects), but the community has been very helpful with ironing those out.
I use the following Nix function to install GUI applications https://github.com/cmacrae/.nixpkgs/blob/d4b51eb414b0edaffae...
It doesn't work for everything, but I use it at the moment for Amethyst, DBeaver, DockerDesktop, Emacs, Firefox, iTerm2, Postman, Slack and VNCViewer.
Yes, the update prompts get annoying (auto-updaters don't work since everything's read-only). I use niv to update things, e.g.
$ niv update firefox -v94.0
That will:- Update firefox's version field in nix/sources.json
- Update its url field based on its template (for Firefox that's "https://ftp.mozilla.org/pub/firefox/releases/<version>/mac-E...")
- Fetch that file to the Nix store
- Update firefox's sha256 field to match that file's
Running nix-build will create a new system package, and the nix/sources.json changes can be committed to git.
One question: How do you properly install things like R on the M1 Macs, then? Do you use a 'backup' package manager?
Homebrew is old and it needs to be replaced. It has served its time.
You are more than welcome to build your own alternative. Homebrew is open source and volunteer run. I'm sure there is a very valid reason requests like this get shot down.
No wonder open source maintainers hate dealing with people like you.
The maintainer did close they issue but they didn't lock the thread. If there was a very valid reason the request got shot down, it went unstated afaict. (Fair enough if they just don't allow feature requests on the issue tracker, I guess)
Counterpoint: I've spent serious time (years) with, oh, six or seven package managers, in a couple decades of using package managers. More if you count Flatpack and stuff like that.
Brew is my favorite, all-around. Package selection is incredible. Stability is excellent. It's a little slow and I think defaulting to updating the package list damn near every time one invokes the command is a really weird choice, but that's configurable.
My second favorite? Docker :-)
Third would probably be portage (from Gentoo), I guess. Then apt/dpkg.
Worst I've used is easily Macports, and it's not even close—however, I switched years ago because it broke constantly under normal usage, on my machine, so for all I know it's great now—and I've never forgiven RPM-based distros for all the time I lost to RPM hell over the years, and for going so long with worse CLI tools than apt.
Void's seems very nice, but I've not spent enough time with it to feel the rough edges.
apt and dpkg are way behind dnf and rpm these days, both in terms of the structure of packages and the interfaces of the high-level CLI tools
What do you like best about Homebrew?
And Homebrew does have a progress bar for each app. The bar is for the downloading but other than that, I don't think it need a progress bar for installtion since they are quick to install than using the GUI installation.
Winget also lacks most of the core functionality of a package manager, including knowing how to uninstall anything at all. But uninstallation will never work reliably on winget anyway, as long as winget an installer wrangler, because installer wranglers inherit unreliable and bespoke per-package installation/uninstallation procedures from existing Windows apps. After a year and a half in public, Winget is still just Chocolatey but faster and much worse in every other respect.
There's basically nothing on offer on Windows if you want a package manager instead of just some automation wrapping executable installers. (Scoop is the closest thing, and it's sort of just now swinging back from the verge of death after months in limbo.) The fact that Microsoft announced a project and called it a package manager has done more or less nothing to change that.
Pacman, apt, portage, etc have much more stable interfaces. But the workarounds for my edge cases in Homebrew from a year ago don't work anymore. Even knowing what is an edgecase or not is a problem.
Most of these issues would be fine if the interface changed as slowly and consistently as Linux package managers.
You shouldn't use the system package manager on Ubuntu or Red Hat or whatever the way you're using Brew, either, unless you're exactly matched to your deployment target—same distro, same version, and you don't upgrade locally unless the servers are getting upgraded—and are scripting your local package installations and upgrades with the same script you use to deploy on the server.
Homebrew's response to criticism seems to be to continually narrow the field of users and the scope of their project, and tell users to use something else.
People are only frustrated because they found it to be so helpful.
But I suppose that it had to mature, or something like that.
I find it much faster now, which is quite funny to me because back when Homebrew was new (and a lot less opinionated) it was much faster than MacPorts, especially with the bottles (binary packages) feature.
Today, MacPorts has binary packages, doesn't randomly force incredibly slow updates when I just want to quickly install something or run a package search, has entirely opt-in telemetry through installing a package (mpstats) and still lets me customise packages (through the variants system, which is more powerful than the with --with-feature system Homebrew seems to have abandoned).
The only pain point I've found is upgrading to new macOS releases, though I've often found just reinstalling MacPorts itself will get me going again in a pinch without having to take inventory of my installed ports and reinstall everything from scratch.
I have been much happier with Macports than with Homebrew (which I try every other year to see if it’s improved).
> The only pain point I've found is upgrading to new macOS releases, though I've often found just reinstalling MacPorts itself will get me going again in a pinch without having to take inventory of my installed ports and reinstall everything from scratch.
I used to use the commands suggested on the Macports website (save the list of installed packages to a file; update OS; install Macports; re-install ports). But since a couple of upgrades I have decided to use this as an opportunity to get rid of ports I don’t actually need.
I tried to do a clean install on my 2012 MBP to get a feel for what would break. Unfortunately the 2012 is out of service and Big Sur kept powering down randomly. It runs Ubuntu 20.04 just fine tho.
I think OSes shouldn't atrophy. So I'm super-testing TimeMachine.
In contrast to how it used to be, Homebrew now seems designed for people that only want popular bleeding edge packages, and nobody else. If I just wanted to install google-chrome, sublime, and skype, I would use the app store. I used to use Homebrew to install more obscure packages that were useful to me, like Arduino, Processing, and some specialized bluetooth tools.
It is especially frustrating because there are good reasons to not want bleeding edge. OSX ages pretty well, and new versions of macOS often remove important features. As long as you have security updates, I think it is reasonable if you run an older version of macOS. For some of us, Catalina and Big Sur were pretty painful, especially if they are a liability for how you make money. Losing total-terminal and total-spaces were a big blow to my workflow, and not at all simple to work around, as well as certain accessibility software.
Homebrew being so aggressive about updating itself compounds many other issues it has. For one thing, major changes are made frequently, and it is very aggressive about pruning packages and only supporting recent version of macOS.
So, I have lost count of the number of times Homebrew has broken itself.
Homebrew will go out of its way to search through its history and tell you a package has been removed, but gives you no easy way to install them. Homebrew could make it fairly easy to revert updates, since so much of it is in a git repo, but it doesn't.
I wanted to install an SNES emulator, and I was using an install of macOS from maybe two years earlier, and Homebrew installed a version that was incompatible with my OS. Since it was a cask, I don't think anything depended on it. I had to actually search through the Homebrew repo history and check out that specific formula, since nobody had a tap for it.
Unless you are near the center of the bell curve, you don't matter. And it is a shame, because I really think that it wouldn't impose a significant cost to support more of us, and substantially improve its UX.
Homebrew used to be a powerful repository of most macOS software. Some of its less used packages could be unreliable, sure, but that was the case regardless of where I got them. Now it is morphing into an App Store. This is part of why I gave up on macOS two years ago and went to Linux full time. I can't rely on Homebrew.
In my case, it was in 2017, and I remember clearly what happened. Homebrew alerted me about the most recent compatible formula, and what commit it was removed in a week earlier. I was then forced to unshallow the Homebrew repo, revert that commit, and then I could install it. I remember I had a good reason for not updating my OS, and I'm sure it was less than two years old.
It was incredibly frustrating, and took a lot more effort than it was worth. Homebrew already had the ability to find the last good version, it would not have been hard to automate it. There were no taps at the time, I had no choice but to do it manually.
The whole reason that I used Homebrew to begin with in 2014 was to help mitigate these problems.
I think there has been a cultural shift away from being more useful for a larger base of users, to being optimal and "clean".
The least it could do is confirm before it makes irreversible and sometimes breaking changes to your system. I don't see any good reason to delete your distfiles after potentially installing an incompatible package. And compared to most Linux based package managers, Homebrew makes big changes to its interface frequently, which makes it a headache to keep up with. Just look at how much casks have changed in the last two years.
Anyway, when I felt I could no longer rely on Homebrew, that was the last straw for me dumping macOS.
I thought the story was that Apple had a problem with GNU licensing, and that was the reason everything got locked to ~2007 software, forcing everyone who wants to use bash and GNU utils to resort to a package manager.
Yeah… googling it, people have long been saying GPL v3 is what Apple has a problem with:
Is it the act of being fastidious regarding everything under '/usr'? If so, count me in.
One of my biggest gripes with the aged Linux install ritual is having to keep the source bundle forever in case I ever decide to uninstall. Why? Because the autoconf makefile is the only rule that cleans up all the bins, docs, and shared files that were sprayed into the OS. Building from source needs an uninstall fix, but no one seems to care. (Or I simply don't understand how w/o keeping the build folder forever... Help?)
export HOMEBREW_NO_AUTO_UPDATE=1It makes sense, but then I don’t do full upgrades and get delayed just like OP. I live with it.
Macports has this appearance of being 'dated', and not the cool kid compared to Homebrew. But it doesn't have an opinion on what version of software you can install. Want postgres 9.4? Go ahead and install it. Want some ancient version of some other library? That's fine.
Dealing with custom port files and repositories isn't quite as easy, I suppose, but that's not really much different to setting up a debian repo in that it's a use case you won't encounter nearly as often as just installing stuff.
I also used nixpkgs at one point, many moons ago. That was also nice but not the most practical on a Macbook with 128GB storage, when you also have Docker, npm/ruby dependencies, and your native Mac software in the mix.
Fortunately Homebrew has lots of helper scripts to upgrade DB’s like Postgres. You probably saw some scroll back with a command to upgrade from 12 to 13 or 13-14 etc. It will even download and install an old version in order to upgrade safely.
I understand the frustration but what is the alternative? Pinning every dependency? The reason so many of your packages were updated was likely because a core dependency was upgraded like OpenSSL.
This can be encapsulated with things like docker, but the "I'll globally, arbitrarily update everything to the bleeding edge" strategy isn't friendly for building and maintaining software.
It's confusing, but we'll get there. You'll need/want:
- nix, the tool/build system.
- nixpkgs, the world's most up-to-date and underfunded package repository.
- nix-darwin, bringing a NixOS experience to Darwin. Not perfect, but pretty damn great.
And optionally,
- home-manager, a nix-based opinionated tool to manage dotfiles and other stuff in your home directory.
And finally, some things to avoid and experimental features to use:
- do not ever use `nix env -i` to install things ala homebrew, apt, etc. This is a trap. You don't want this.
- use nix flakes. Sure, it's experimental, but it's what you _do_ want. Reproducible, version-pinned builds.
- if you use direnv, or are familiar with it, check out nix-direnv, which is like direnv on crack. Instead of managing your packages globally, manage them per workspace or project.
I see "nix-env --install" as a gateway drug to using Nix. It's effortless, and already easy to see benefits from using Nix (like you can easily get the same set of packages on different OSs).
Rather, it's worth explaining why it's bad to use. The footgun I ran into was that I'd caused a messy state by installing something and forgetting I'd installed it.
Maybe nix flakes and home-manager and nix-darwin are really cool.. but they're also much harder to use, and surely overwhelming for someone who just wants a way to install packages. I think it's like telling someone to learn Kubernetes when they're wondering how to serve the website they wrote. -- I think it's easier to appreciate these once you've gotten your feet wet with nix.
My biggest problem is that it's not version-controlled. I don't use home-manager, and (still) haven't looked into Nix flakes.
Instead, I put `export PATH="/path/to/my/repo/result/bin:$PATH"` in my ~/.bashrc, and inside that repo I have a default.nix file:
buildEnv {
name = "my-packages";
paths = [ foo bar baz ... ];
}
Running `nix-build` will update the result/bin symlink to point at the new applications.But yeah I agree, it's fine to use `nix-env -iA <package>` or whatever. It's fine when you're getting started, and it's fine for a slightly more persistent way of trying things out than `nix-shell`.
I've been using `nix env -i` without any issues till now, but I'll explore nix flakes.
What your link describes though looks better. I’ll give it a try, thanks.
In every case they were removing features that were working just fine, and in the process creating work for me. Got fed up, switched to macports (after checking out nix), and so far things are back to the way I like it.
The trick of disabling auto updates doesn't really work when you do want things to be updated. Sometimes there are conflicts and the usual path was to upgrade everything.
Not that it matters: it seems that the usual "upgrade everything" from 2 years ago has changed into "delete every folder and reinstall everything from scratch".
Also, a few years ago, updating everything or reinstalling lots of things from scratch was fast. Now, it requires much more time due to the massive number of dependencies every single package has.
Also, the maintainers have a super weird aversion to everything that is "too simple". I've seen packages rejected because "the build process was too simple and there are no dependencies". The excuse was that "users can build it themselves". This is not for silly stuff random people wrote on weekend, this is for 20 year old tools used in production written in C.
I can't say for certain why or how it got worse for other people, but for me it started when they doubled down on dynamic linking. That's why installing takes 5x the time it used to, takes more disk space, breaks all the time and makes organising your tools a fucking mess.
As for me: I've replaced most of my Brew usage with Docker (for most development tools) or with static binaries compiled weekly using Github Actions (for cli tools like youtube-dl, etc). I'm also using download links straight from language websites (Rust, Ruby, Python, DotNet, Haskell).
wtf? What could be the possible motivation for this? Is their build farm really straining or something?
Can you point me to this example?
> I'm also using download links straight from language websites (Rust, Ruby, Python, DotNet, Haskell).
this sounds downright medieval to me, like why even use macOS for development if the software management tools are so bad that that's what you're doing
Because it's only one tool that really sucks: Brew. For the languages I specified, it works quite well. Even using C++ with vendored dependencies works better than average in macOS for me.
For most of those languages I mentioned, Brew already offloads the real installation and updating of the language itself to a language manager (via rvm/rbenv, nvm, pyenv, rustup, whatever), so it doesn't make a difference, except without Brew I get a more stable, officially supported, environment.
Also, DotNet was a Cask until a few months ago, so they effectively were just downloading something from the official website just like me.
- Use the workflow_dispatch type so it can be manually triggered. Or schedule if you want it to run periodically.
- Install dependencies like automake/cmake and libraries using brew (I cheat here a bit)
- Clone the repository
- Perform the steps for building a static build
- Use actions/upload-artifact@v2 to upload the executable.
It's simple, although to get it right with some programs. The advantage is that with one click you can download it, put in your path, and it won't need special paths.Here's an example with jq: https://pastebin.com/gHu8dTgQ
Honestly the best option is asking maintainers of each program to add a static build to their projects in order to make their projects able to run without installing dependencies. Problem is, some maintainers are very anti-static-compiling.
I'm a bit wary on relying on it now. It's a shame, because it's a really useful tool (and I actually like the auto-update).
I’ve seen homebrew overwriting system files and thoroughly breaking machines (that’s no longer that easy, but more thanks to Apple).
Haven’t tried many other package managers (Fink, when G3 was bleeding edge), but MacPorts has the feel of an exquisitely well engineered tool.
https://github.com/Homebrew/brew/blob/7d31a70373edae4d8e78d9...
Since then i moved to manage everything that needs to stay frozen by either a versioned formula/cask (python@3.9 and so on) or use brew to install a version manager (asdf,pyenv,pipx) and then use that. Results are much better once I did that
Although versioned casks/formula exists you will still get minor versions - python@3.9 will update from 3.9.0 to 3.9.1 is that ever exists. so while completely protecting you from upgrades it does minimize the the risk
Also, there is really no need to install virtualenv directly from brew. A very bad practice. Of course doesn't change the fact that brew did things that are unrelated to what you asked of it
I've seen countless people trying a new technology/tool, and overselling it as 'amazing' and super easy.
Seeing a post like yours helps to keep the discussion grounded in reality, with tools that looks nice on paper but have practical downsides as well.
Finding your sea legs is rough, and IME people who completely deny this are few and far between. The reward is a pretty impressive lever.
I set up a shell.nix file on my projects, and together with direnv the environment switches with the right dependencies for each project.
Shameless plug, I wrote about this recently on medium: https://link.medium.com/cYkKMtdHRkb
Parts of it are getting easier, and it has an exceptionally smart and helpful community. But it's not smooth and pretty and easy to pick up like Homebrew is. (And that does suck)
I found it much more helpful to find people's own Nix setups and learn from them, by the way. Mine is at https://github.com/Smaug123/nix-dotfiles - like all such things, it's a WIP, and I'm definitely still a noob, but bits of it may be able to help.
I recommend using Nix alongside a couple other systems so you have escape hatches.
- Nix
- pkgsrc or MacPorts ‘just in case’ they're more convenient for some package for any reason
- Homebrew, exclusively to use for Casks (which are really a separate system from the rest of Homebrew)
To get the most out of Nix, you'll need to take some time to really learn it. For managing services like postgres, it's nicest to use Nix-Darwin (a module system for managing services and programs, including automatic startup via LaunchD) but if you want to always keep the same Postgres with Nix-Darwin, you'll have to learn how to pin packages.If you use Nix with Flakes, it'll feel a lot faster (much faster than Homebrew). If you use old-school Nix, it'll be slow if you call some deprecated commands.
It is rough around the edges as some packages are missing if I remember right.
For instance: arkade get kubectl@v1.22.1 yq helm faas-cli
It's not got anywhere near the catalog of brew, and doesn't compile software, or help you find lib-xyz for your Yubikey, but it is really fast and has a growing community behind it.
It works on MacOS, Linux, Windows and arm hosts to determine the correct download URL and pull in a binary.
[1] https://github.com/alexellis/arkade
Contributions are welcome.
Use Docker, probably. Homebrew is for your tools, not your dependencies. Is the system you're deploying to a Mac with Homebrew? Do all your collaborators use Macs with Homebrew? If the answer to either is no, why were you trying to use Homebrew for that in the first place, even if it were otherwise good for that? Would you use Homebrew to install Node modules for your project? Python packages? No? When why are you letting it define which version of PostgreSQL this project depends on?
Because right now https://brew.sh leads with "The Missing Package Manager for macOS", which would lead anyone to think it does the same as a package manager for Linux, and they're _full_ of developer dependency packages.
It's not about project philosophy, it's about using the right tool for the job. Now, since people keep running into this problem, maybe they ought to put a warning about that right up top in a big banner on the site, I dunno, but it's not specific to Homebrew.
This is not because there's anything really wrong with them for what they're designed to do, but they install packages globally, and different projects may want different versions of the same thing.
And for any cli tool that's also mission-critical, it can be run within Docker so I can pin it or keep it bleeding edge more easily.
For the important stuff, performance and ease of installation can take a side seat to stability.
If I had to use a Mac my solutions would be to alway use Docker, a virtual machine, or a remote dev environment of some kind.
Granted I don't work much with compiled languages anymore so I don't need to setup some complicated tool chain, but when I need to do that I usually go for MSYS2 or WSL when it has to be Linux.
Guess I'll give that machine another go now.
apt/dpkg and any other package manager that scatters files all over the filesystem is just an absolute disaster that--if you do a lot of R&D tinkering--eventually renders a computer unusable after a few years. That's what happened to my first WSL instance; no apt command would work anymore because it had broken its own dependencies.
That was your experience. Mine is that after using apt for a lot of R&D tinkering for 3 years, everything runs fine. apt is global. Sometimes that's great and that's what you want. Sometimes it's not. Another way to keep that in mind is to think as apt as "bad at doing cleanup".
The thing is that you have to have a separation between things that you want on your system all the time, and things that you use for one or more projects. For the first, apt is perfect. For the second, usually that's where I use tools like nvm for node versions, languages package managers like npm, and also docker (which I use a lot for development databases).
For a backend JS project, I would install nvm, npm and docker globally, and then for the project I would use node through nvm with a .nvmrc, install packages locally with npm and use a package.json and package-lock.json, and run the development database with docker through a npm command, usually the latest PostgreSQL version. The link between the backend and database would be made with environment variables, because that's also what I use in production.
The difference between the global (apt) scope and the project (the rest) scope might seem like a failure of apt, but I personally see it as the difference between global and local scope in programming: both have their use, we separate them for a reason and we don't put the same things in them.
This is the craziest claim that I've ever heard about package management, I think
I would love to hear a more complete story
Homebrew is one of the slowest package managers I've ever used.
Ports have a long history from the BSD Unices and Macports keeps that ethos. It installs neatly in /opt and doesn't screw around with MacOS except in the approved ways (eg for Java and Python frameworks).
I still use Macports and it's had everything I've needed and when I hit bugs during the Big Sur beta, they were fixed quickly and directly, while still complying with Apple's beta policies.
I've dabbled with Nix, but it's a bit
too* prescriptive for me.I've tried the nix.dev installation + nix-darwin instructions 4 or 5 times but never make it very far.
Seems like I should generally prefer a multi-user install? Does nix-darwin play well with that?
As I supposed to `sudo -i nix-channel --add / update` or not use sudo? Is unstable the generally recommended channel? Do I use sudo with nix-darwin, since it's supposed to be kind of like NixOS / system-wide?
And then I usually give up, count my blessings for homebrew, and resolve to never again waste another 2 days trying to figure out nix.
Rinse and repeat in 1 month.
On my macOS computer, I don't have a multi-user install. Never had issues with it (but I also only just use one user account for everything).
Just tried running the nix-darwin installer, and it sets up some extra users as part of that anyway.
> Am I supposed to `sudo -i nix-channel --add / update` or not use sudo? Is unstable the generally recommended channel? Do I use sudo with nix-darwin, since it's supposed to be kind of like NixOS / system-wide?
Once you've got nix installed, in general you don't want to "sudo nix ...". Unstable is fine; but if things break, it's easy to rollback changes with Nix, so that you can use the versions which worked.
There's no need to start out with wrangling with nix-darwin. Just starting out with "nix-env --install --attr nixpkgs.<whatever>" will be closest to how homebrew is used.
Been running with homebrew for at least a decade since, and I cannot recall any negative encounter other than the hassle of grabbing xcode command line tools when I upgrade a major version of Mac OS, which is a minor detail.
I get the appeal of MacPorts & a dedicated separate package path but it caused all sorts of problems for me v. the integrated approach of homebrew.
O there was another negative now I remember -- had to uninstall brew version of MacVim & install manually. But that not a big issue either & typically, all of the brew packages are CLI only for me.
[1]: <https://rubenerd.com/using-netbsds-pkgsrc-everywhere-i-can/>
[2]: <https://news.ycombinator.com/item?id=27293108>
[3]: <http://www.pkgsrc.org/>
I wasn't able to immediately find any issue tracker for pkgsrc packages, and it seems they're a "mailing list and IRC" type setup, which if true means it definitely isn't for me
https://www.perkin.org.uk/pages/pkgsrc-binary-packages-for-o...
https://github.com/joyent/pkgsrc
Now, maybe if I go trawling through mailing lists or (shudder) IRC I can find out what the status is, but that's beyond my level of energy expenditure for some niche packaging system
The answer to your question is that searching years of email threads is not the same UX as navigating to a dedicated page for managing the lifecycle of a (bug|feature) that tracks when I can expect a new, or working, version of -- in this case awscli-v2 -- to show up, or (if reasonable) that I can indicate to others that I'm willing to take responsibility for doing the work to package it, in order to avoid duplicated effort
Plus, again for me, I don't need more email traffic in my life
Even on a system like that, many times when I interact with package managers like conda, it ends up compiling some huge package from scratch. I consider that a failure.
I instructed you, brew to install something. Not to spend an eternity autoupdating things.
Now, it's unusable.
export HOMEBREW_NO_AUTO_UPDATE=1
in my zshnev so long ago that I forgot about it, and just enjoyed a great product from a great community.Is it a bad default? Sure, probably, spooky-action-at-a-distance is not great. But it's just a default, if you don't like it, change it, one line of shell config is simpler switching distros.
1. Python has built in virtual environment support. python -m venv should do the trick. 2. Use docker for running Postgres, MySQL etc with some directory mounted for data.
EDIT: Personal PC is running NixOS not Windows.
Some other downsides:
- Nix installations can take time. Nix packages can either build from source, or download from a binary cache. If it's not in the cache, it will build from source.
- Nix can take up a significant of storage space.
However, e.g. Nix is amazing at rolling back package installs; and installing a package with nix won't ever break another package installed by nix.
If you like the phrases "pure/immutable", "declarative", "functional programming", etc. then you'll like Nix. -- If those words don't excite you, then you'll not like Nix.
HOMEBREW_NO_INSTALL_UPGRADE
If set, brew install will not automatically upgrade installed but outdated formulae
https://docs.brew.sh/Manpage#HOMEBREW_NO_INSTALL_UPGRADE (that's a fake hyperlink cause they don't put named anchors on their <li> for some reason)updated with an actual hyperlink: https://github.com/Homebrew/brew/blob/3.3.2/docs/Manpage.md?...
A "breaking things without this example": - you want to install something that depends on `readline` - the binary package for the thing you want requires the latest `readline` - this upgrades `readline` on installation
Now, we have a choice. Either we upgrade _everything_ that depends on `readline` that you have installed or we knowingly break some of the things you have installed that depend on `readline`. We choose the safer option by default.
If you leave it a long time between updating: you are more likely to have more dependencies updated which requires more dependents to be updated.
I don't want to have to keep updating brew just to keep from having to wait ever-increasing amounts of time when I want to install a new package or upgrade a specific one. I never have these issues with macports, and won't have them with nix.
Macports primarily for system level dep. use this whenever packages available.
Fall back to homebrew when not available on Macports. This includes brew cask which is unique.
This dual setup may requires you to carefully separating the 2 when installing and updating. I do this by installing brew in custom location (not /use/local) which is now the default on ARM. Also some shell functions that selectively control the environment (essentially I use an environment that only sees port to update port, vice versa.)
Lastly, conda for dev environments, mostly for Python stack. But it is also useful for other stuffs (conda is a cross platform package manager and in this sense it is similar to brew which also support Linux.) Obviously selections is not as broad comparing to brew.
On computing facilities, I used to use linuxbrew to install some “system” dependencies that’s missing, including zsh, mosh, tmux, tree, etc. But obviously I cannot install in the recommended path by brew, which means compiles from source. And it is not robust (and they also say this is not a supported pattern.) I now use conda for this and is very robust (conda uses a hack to install precompiled binaries to arbitrary prefix.)
xcode-select --install
That said, MacPorts is often opinionated, e.g. they package certain versions of some software and not others.I didn't dig into the process for a user to add their own ports, or share new ports with a team, in order to more fully compare the two packagers, but that ticket stood out to me
I apologize if "puritan" was too emotionally loaded of a word that hurt people's feelings -- I didn't think "strict" was the right word but maybe I should have used "opinionated?"
Use Homebrew only for tools and not for critical services like databases and use a cloud server or local virtualization like VMware Fusion (which is free these days for personal use) to run services because major distro packages have far more eyes to check for stability issues.
I never understand people trying to run every parts of the development environment through a local machine's third party package managers which would forbid anyone else being able to check your work right then as you're only running it as localhost, not to mention the power can be down at any moment you're not using your machine and you can't even switch machine yourself to work from easily.
And then when deploying, you'll be running it on Linux anyway which would likely bring little consistency issues.
Do yourself a favor, either spend $5/mo for a cloud Linux and run your stuff remotely or run a locally virtualized Linux and stop using third party package managers no one is using to run on production.
And you can happily keep Homebrew and also install Homebrew on Linux to have unified tool sets on your Mac and Linux CLI tools.
Personally, I find that the whole Homebrew "theme" where every command/thing is named after something alcohol/brewing-related (cask, bottle, formulae, tap, keg, etc) is a huge turnoff.
I hate it, but Anaconda might work too, so long as it's not a corporate laptop where you need a license.
- Homebrew is rolling - always was and never claimed otherwise.
- OTOH for key packages (say databases) it has available versioned bottles. for postgresql one have:
- postgresql@10
- postgresql@12
- postgresql@9.4
- postgresql@9.6
- postgresql (points to latest)
- postgresql@11
- postgresql@13
- postgresql@9.5
- Finally, no one is forced to update everything at all.
run `brew outdated` and pick whatever you want updated then `brew update foo`...In short, none of what happened to the OP is due to inherent flaws in homebrew... QED.
why are you being this hostile while being completely wrong?
It is my first choice since a while and if it's not there I go to homebrew
Check [2] https://pkgsrc.se/ before, to see if the availability and the 'freshness' of packages fits your needs.
edit:
[3] http://pkgsrc.joyent.com/install-on-osx/ if you don't want to compile and have binaries instead.
Try:
python -m venv
To fix the very real issue you describe, I would like to see a --isolate option for mission critical packages. All the dependencies would be contained in a separate, package specific directory within /opt or /local. The rest of the dependency management could happen without affecting the isolated package.
- Python already includes the venv module, you don't need to install anything, just run `python -m venv <venv_dir>`
- You should manage multiple versions with pyenv instead of brew. Pyenv can be installed with brew.
not optimal, but sadly necessary.
Here are some arguments why it may be worth it this time :)
EDIT: for me this is a minor issue. i'll continue using `brew` as it does the job quite well
If you forget to include the extensions, you can add them all later.
Also, I'm 100% certain all it did was update the formulae (similar to apt-get update, NOT apt-get upgrade).
I've been using homebrew for years and this is the default behavior, so unless you purposefully set it up to upgrade everything this is PEBKAC.
AFAIK the only time it upgrades everything is when you do a blanket `brew upgrade`. And if you're pinned to something like postgresql@10 it will stay at that major version.
python -m venv /my/directory
I’ve never had any problem with homebrew in over a decade’s usage. But yes, don’t use it for python development tooling: use pyenv and python -m venv and pip for that.brew install pyenv pyenv install 3.9.7 cd ~/myproject pyenv local 3.9.7 python3.9 -m venv --upgrade-deps .venv source ./.venv/bin/activate
You're off to the races. (I think I got that --upgrade-deps flag right.)
Pyenv builds all of my python versions. Pipx installs cli tools in their own env.
Precommit is also great for much more than just pre-commit hooks. It can install tools used for hooks in their own cache, no need to manage venvs yourself.
Doing a `python[V.v] -m venv …` and then sourcing `activate` is conceptually much simpler and doesn’t affect any other terminal sessions you may have open.
Just wondering.
My experience has been that very, very rarely is any installation as straightforward as "curl && ./configure && make PREFIX=whatever install", partially due to everyone in the known universe thinking they need some awesome new build system, partially due to unknown dependencies and their versions, partially due to it usually needing some patches because they only build on the author's machine, and all of that is just noise when I just want to have something running
The value of Homebrew to me are the constantly bumped formulae descriptors, definitely, 100% NOT the "brew" frontend for it. So, I've found a nice middle-ground by switching Homebrew into developer mode and neutering its bottle system and using "--build-from-source"
This isn't necessarily to lobby you, as much as "for your consideration" and to raise awareness for others that one need not throw the baby out with the bathwater just because the brew frontend is user-hostle
Aye, This is particularly impacting when the build system itself needs to be eh built.
> The value of Homebrew to me are the constantly bumped formulae descriptors, definitely, 100% NOT the "brew" frontend for it. [...] and using "--build-from-source"
I hadn't thought about that and I suspect you are right. Although I lack recent exposure to it and I can certainly recall getting very annoyed by it, I have to say that my experience with Gentoo's Portage/emerge is by far the least unpleasant as dealing with package managers goes.
I think I started using homebrew some 4+ years ago. I have no idea what a cask, tap, pour, or whatever is. At first things "just worked" and it was great.
Then came the couple of afternoons where I had an important project to finish, and I had to upgrade X. I think once it was matplotlib (for which I had to upgrade pip? ... I can't remember) or something silly like that. Everything broke, I had to reinstall all my python packages (that's when I moved to pyenv), and I did not finish my work on time. There was probably an easier way, but I was (am) a newb and I didn't know how.
I remember brew was one of the first command line utilities I ever used and thought, "woah, that was easy _and_ cool!", but I no longer think I am the target audience.
Someone might say the onus is on me to learn more about the tools I use, and they are probably right. I need to know my .tmux.conf or .zshrc or whatever to get the most out of those, too, and sometimes I like to dive in. For the most part, I just want to Get On With My Work and be oblivious.
If you could provide some guidance, I would genuinely be grateful. For a newbie like myself, when I see comments like "just set HOMEBREW_NO_AUTO_UPDATE=1" and then back and forth on why that does and doesn't work, I decide I don't have time for this today and cross my fingers I don't need to brew install anything in the near future. And if I do, I will probably avoid it (because it will take, I don't know, hours?).
However! You shouldn't be using your system package manager to install Python dependencies! As you know, since you moved to pyenv. But I don't think it's fair to blame homebrew for getting you into trouble when doing that; one also wouldn't use apt to manage python packages.
In fact if you look at this thread, many supposed problems with homebrew seem to be in fact problems with python package management. It's the same with all languages isn't it -- ruby and rbenv and gem, node and nvm and npm, rust and cargo. As soon as you start actually installing 3rd-party dependencies, you're better off using something language-specific to install the interpreter / compiler / toolchain, better than using homebrew or apt etc.
Macports is a tougher sell for an average user today simply because the disparity in popularity means macports library is smaller and older. But macports is engineered more correctly.
This was my biggest complaint about Homebrew. Does it mean it was finally fixed?