Link Conflicts Break Keg Installation and Pollute Install (and other linking issues)
Trying to install libomp and llvm at the same time revealed a whole host of issues with linking.
This is a huge amount of different related issues/improvements, and I'm super busy with school right now but if I have time I can hopefully open some PRs
Roughly in order of severity, the issues are:
1. Link conflicts will brick a formula's installation (even though the keg itself was installed fine).
This is the actual critical bug that can really break things.
# Finds all symlinks pointing to the llvm keg (as well as the keg folder itself)
alias list-llvm='ls -la /opt/zerobrew/prefix/* | grep llvm | wc -l'
list-llvm # 0
zb install libomp
zb install llvm # fails due to a link conflict with libomp
list-llvm # 432!so the ~432 links that get created by zb for llvm before the linking conflict occurs (out of 834 total for a proper installation) are left behind.
Even though /opt/zerobrew/prefix/Cellar/llvm exists, the linking failure incorrectly causes zb to not register the keg, not showing up in zb list, and not able to be uninstalled:
zb uninstall llvm
==> Uninstalling llvm...
Error: Failed to uninstall llvm: formula 'llvm' is not installed
error: formula 'llvm' is not installedthis is poses further problems because if I want to install something else, say, clang-format, this happens:
zb install clang-format
==> Installing clang-format...
==> Resolving dependencies (1 packages)...
clang-format 21.1.8
==> Downloading and installing...
Note: This package can't be installed with zerobrew.
Error: link conflict at '/opt/zerobrew/prefix/bin/clang-format'
...So a link conflict happens with a link from a keg that zb doesn't even know exists! This would be super hard to debug, as the install fails, but the conflicting links are erroneously not cleaned up.
zb currently has no way to clean up a botched install, either, as there's no way to see what files a formula is responsible for (see issue 4). The only way I could know my llvm install was cleaned properly was to manually delete the files or to uninstall the conflict, then install llvm to register it with zb, and then uninstall it to properly clean up all of the links.
When homebrew has a link conflict, it's more graceful:
...
Error: The `brew link` step did not complete successfully
The formula built, but is not symlinked into /opt/homebrew
Could not symlink bin/clusterdb
Target /opt/homebrew/bin/clusterdb
is a symlink belonging to libpq. You can unlink it:
brew unlink libpq
To force the link and overwrite all conflicting files:
brew link --overwrite postgresql@14
To list all files that would be deleted:
brew link --overwrite postgresql@14 --dry-run
Possible conflicting files are:
/opt/homebrew/bin/clusterdb -> /opt/homebrew/Cellar/libpq/18.1/bin/clusterdb
...Proposed fix
- For this issue, zb needs to register the keg as installed before the linking step. First complete the installation, ie installing
/opt/zerobrew/prefix/Cellar/{package}, and then failing the linking step creates an error in the linking step only, allowing the installation to complete like normal. - Either by checking all to-be-created links before linking, or rolling back all created links after finding a conflict, linking failures should be all-or-none by default to match homebrew's behavior, so if one link conflicts, they all fail. Homebrew has other commands to control this behavior better which zb in my opinion would greatly benefit from (see issue 4).
- zb should also track which links are owned by which keg, note how homebrew displays which formula owns the conflicting link. This goes along with the link/unlink commands (see issue 4).
2. Not respecting keg-only formulas
Homebrew already has a mechanism for reducing linking conflicts, where some formulas are marked as "keg-only", meaning no links are created. For example, libpq has this line in its formula:
keg_only "it conflicts with PostgreSQL"Which causes a helpful message to be printed with the formula's caveat (see issue 5). This would also have fixed the libomp/llvm issue, as libomp is marked as keg-only.
3. Not respecting keg-only for versioned formulas
Different versions of the same package are marked with the line:
keg_only :versioned_formulaas the same package will obviously create the same links. For example, when installing postgresql@{14,15}, homebrew gives the following helpful error with its caveat:
postgresql@15 is keg-only, which means it was not symlinked into /opt/homebrew,
because this is an alternate version of another formula.Currently, installing postgresql14/15 on zb, the first install will succeed as normal, but the second will brick like with llvm, not registering in zb's list of installed formulas and requiring manually deletion
4. Lacking features to debug installations
Dealing with packages with conflicting links is also made a lot easier by some of homebrew's tooling.
brew link/unlink {formula} is obviously important for managing links, and can also be passed the --dry-run flag to just show which links will be created/destroyed, also super helpful for seeing which links a package is responsible for. The --overwrite flag is also important for this command as seen above, to manage kegs with conflicting links.
brew list {package} will show which files a particular formula has created, also very helpful for debugging/managing installations.
As previously mentioned, these two require tracking which installed files/links a keg is responsible for, which makes fixing lots of issues much easier.
brew info {package} will, along with printing dependencies, also print caveats for a formula.
5. Not displaying caveats on install
Homebrew deals with conflicting links by simply not allowing the installed formula to create any links (either by the formula being marked keg-only or checking for conflicts on install). This prevents linking issues, but makes using that installed package difficult. Caveats contain important information to help resolve this. For instance, when conflicting postgresql versions are installed, the following is appended to the caveat (for fish shell):
postgresql@15 is keg-only, which means it was not symlinked into /opt/homebrew,
because this is an alternate version of another formula.
If you need to have postgresql@15 first in your PATH, run:
fish_add_path /opt/homebrew/opt/postgresql@15/bin
For compilers to find postgresql@15 you may need to set:
set -gx LDFLAGS "-L/opt/homebrew/opt/postgresql@15/lib"
set -gx CPPFLAGS "-I/opt/homebrew/opt/postgresql@15/include"
For pkgconf to find postgresql@15 you may need to set:
set -gx PKG_CONFIG_PATH "/opt/homebrew/opt/postgresql@15/lib/pkgconfig"(see caveats.rb) This information is necessary if you want to still use a keg-only formula without digging for the install location manually.
Source: lucasgelfond/zerobrew