Skip to content

keg: skip linking some symlinks that point to other kegs - #23558

Merged
cho-m merged 1 commit into
mainfrom
keg-skip-symlinks-to-other-keg
Aug 18, 2026
Merged

keg: skip linking some symlinks that point to other kegs#23558
cho-m merged 1 commit into
mainfrom
keg-skip-symlinks-to-other-keg

Conversation

@cho-m

@cho-m cho-m commented Aug 18, 2026

Copy link
Copy Markdown
Member

Specifically symlinks that point to another keg via the opt path using the same relative path. These would have previously been a conflict linking the same file.

This can be used by split formulae to help resolve the path of relative files, e.g. Flang expecting LLVM's LTO library at ../lib/<libname>.

We've used other workarounds before like reversing the symlink so we resolve the path via Homebrew prefix; however, this is less ideal as the wrong file can be found if a user runs brew unlink/link. Also has lower security as an unrelated formula could inject something into the same location unlike our sandboxed kegs.

This change does not allow symlink to Cellar path as it is fragile and should never be used in formulae.


Mainly want this for the LLVM case as I've been working on LLVM 23 update and am trying to fix up some LLVM <-> Flang interaction.

  • On macOS, we use a toolchain file that sets -lto_library. This works, but can't remove the default arg so there is always going to be a warning on invalid path
  • On Linux, we use reversed symlink Flang keg --> Homebrew prefix --> LLVM keg. The problem here is brew unlink llvm will break it and even worse is brew unlink llvm && brew link llvm@20 would cause wrong LTO library to be found as path is unversioned.

There are other scenarios like improving qt formula layout to work even when user brew unlinks Qt6 formulae.


  • Have you followed our Contributing guidelines?
  • Have you checked for other open Pull Requests for the same change?
  • Have you explained what your changes do? Performance claims (e.g. "this is faster") must include Hyperfine benchmarks.
  • Have you explained why you'd like these changes included, not just what they do?
  • For bug fixes, have you given step-by-step brew commands to reproduce the bug?
  • Have you written new tests (excluding integration tests)? Here's an example.
  • Have you successfully run brew lgtm (style, typechecking and tests) locally?

  • I did not use AI/LLM to create this PR, or I disclosed the tool/model below and reviewed its output; I did not attribute commits to AI and will answer maintainer questions and review comments myself without AI/LLM.

Specifically symlinks that point to another keg via the opt path using
the same relative path. These would have previously been a conflict
linking the same file.

This can be used by split formulae to help resolve the path of relative
files, e.g. Flang expecting LLVM's LTO library at `../lib/<libname>`.

We've used other workarounds before like reversing the symlink so we
resolve the path via Homebrew prefix; however, this is less ideal as
the wrong file can be found if a user runs brew unlink/link. Also has
lower security as an unrelated formula could inject something into
the same location unlike our sandboxed kegs.

This change does not allow symlink to Cellar path as it is fragile and
should never be used in formulae.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates Keg#link to skip linking certain symlinks that would otherwise conflict when they point (via HOMEBREW_PREFIX/opt) to a file that’s already linked from another keg at the same relative path, enabling split-formula layouts (e.g. LLVM/Flang) without fragile Cellar-path symlinks.

Changes:

  • Teach Keg#link_dir to prune entries when the source resolves to an opt-based path at the same relative location.
  • Extend keg_spec with coverage for “same relative path” vs “different relative path” and a Cellar-path conflict case.
  • Adjust the test keg helper to support suffixing filenames to avoid cross-keg link conflicts in specs.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
Library/Homebrew/keg.rb Adds pruning logic in link_dir to skip linking certain opt-based symlinks that would otherwise conflict.
Library/Homebrew/test/keg_spec.rb Adds regression tests for cross-keg opt symlink handling and updates the test helper to support unique filenames.
Suppressed comments (2)

Library/Homebrew/keg.rb:822

  • This prune rule is intended to apply only to symlinks pointing into another keg via HOMEBREW_PREFIX/opt, but the current check will also match a symlink pointing to this keg’s own opt_record. That would skip linking a file that may not otherwise exist at the destination. Restrict this to symlinks and exclude the current keg’s opt_record.
        # Skip symlinks where the source is located in another keg at the same
        # relative path. Split formulae (e.g. llvm + flang) can use these when
        # their binaries need to find files at a specific relative path.
        Find.prune if resolved_src.fnmatch?("#{HOMEBREW_PREFIX}/opt/*/#{relative_src}", File::FNM_PATHNAME)

Library/Homebrew/test/keg_spec.rb:225

  • Same as above: normalise resolved_path before comparing it to an absolute path, otherwise .. segments in the joined path can make the equality check fail.
        keg.link
        expect((HOMEBREW_PREFIX/"lib"/filename2).resolved_path).to eq keg/"lib"/filename2
      end

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Comment thread Library/Homebrew/keg.rb
Comment thread Library/Homebrew/test/keg_spec.rb

@MikeMcQuaid MikeMcQuaid left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@cho-m
cho-m added this pull request to the merge queue Aug 18, 2026
Merged via the queue into main with commit a2005e5 Aug 18, 2026
48 checks passed
@cho-m
cho-m deleted the keg-skip-symlinks-to-other-keg branch August 18, 2026 23:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants