Have you ever installed something using Homebrew and noticed the command's output updating seemingly unrelated packages? This recently happened to me, and it piqued my curiosity. As a result, I embarked on a journey to understand how package dependencies work within the Homebrew universe. This post is a friendly guide to what I've discovered, which I hope will help others navigate the complex web of package dependencies.
Setting the Scene
I installed bear, a utility that generates compilation databases for the C programming language, and surprisingly, the output indicated an update for teleport, a completely unrelated package I use to connect to our Kubernetes clusters at work. Here's the command I used:
brew install bearI quickly realized that teleport couldn't possibly be a direct dependency of bear. This led me to hypothesize that they might share common dependencies. But how could I confirm this? Well, this turned out to be a fantastic opportunity to learn some new commands.
The Investigation
I learned about the brew uses command, which is a helpful tool for exploring what packages are using a particular package. I tested this command by checking what depends on openssl@1.1, assuming a large number of packages would. Here's what I found:
brew uses --recursive --installed openssl@1.1apr-util exa gnutls libgit2 nmap python@3.11 tmate
autojump ffmpeg grpc libpq node rtmpdump tmux
bear freetds krb5 libssh openldap ruby unbound
borgbackup global libevent libssh2 php srt yt-dlp
curl gnupg libfido2 llvm pspg teleportInterestingly, both bear and teleport depend on openssl@1.1. If bear and teleport share the same dependencies but require different versions, Homebrew will try to resolve this conflict.
Finding Common Ground
I then sought to understand how to use the diff command on Linux to show similarities, not differences. Despite trying many approaches, I found a more effective command that was new to me: comm. The man page for comm reads:
man commcomm – select or reject lines common to two files
The following options are available:
-1 Suppress printing of column 1, lines only in file1.
-2 Suppress printing of column 2, lines only in file2.Let's use comm to compare the dependencies between bear and teleport:
comm -12 <(brew deps bear) <(brew deps teleport)This produces the following output:
c-ares
ca-certificates
openssl@1.1During the bear installation, I noticed that c-ares was updated, and Homebrew provided a hint that I had previously overlooked:
==> Upgrading 2 dependents of upgraded formulae:
Disable this behaviour by setting HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
node 19.8.1 -> 20.2.0, teleport 12.1.5 -> 13.0.3Furthermore, teleport depends on node (which also depends on c-ares), so this could be another reason for the update.
Digging Deeper
Wanting to understand why teleport depends on c-ares, I delved further:
brew deps teleport --treeOutput:
teleport
├── libfido2
│ ├── libcbor
│ └── openssl@1.1
│ └── ca-certificates
└── node
├── brotli
├── c-ares
├── icu4c
├── libnghttp2
├── libuv
└── openssl@1.1
└── ca-certificatesIt turns out that node depends on c-ares, and teleport depends on node. This interdependency was the key!
Conclusion
Through this exploration, I acquired valuable tools that will undoubtedly come in handy for troubleshooting Homebrew package dependencies in the future. If you ever find yourself puzzled by unexpected package updates in Homebrew, I encourage you to use these commands to unravel the mysteries of package dependencies. Remember, a deeper understanding of your tools can only make you a more effective problem-solver. Happy brewing!
Image: Markus Spiske via Unsplash
