Skip to content

Keep outdated :no_linkage build dependencies - #24290

Merged
MikeMcQuaid merged 3 commits into
Homebrew:mainfrom
HaraldNordgren:keep-no-linkage-build-dependencies
Oct 19, 2026
Merged

MikeMcQuaid merged 3 commits into
Homebrew:mainfrom
HaraldNordgren:keep-no-linkage-build-dependencies

Conversation

@HaraldNordgren

@HaraldNordgren HaraldNordgren commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Follow-up to #24273, which lets source builds keep an outdated build dependency when its installed compatibility_version matches the current formula.

That works for most build dependencies, but not for one that a formula only runs while its compatibility_version is bumped for linking reasons. The common case is llvm used as a compiler: llvm bumps compatibility_version with each major version because libLLVM's ABI changes. That matters for formulae that link libLLVM, like rust, but not for one that only compiles with clang.

This lets formulae tag such build dependencies :no_linkage, which already declares that the formula doesn't link against the dependency, and keeps them when outdated regardless of compatibility_version. Everything else from that change still applies: nothing is kept with --build-bottle, and a kept build dependency is still upgraded if this install would incompatibly upgrade something it was built against.

How I got here

On my MacBook Air (macOS 12.7.6, Xcode 14.2, so no bottles), brew install ccache started by rebuilding llvm and spent three hours on it before I cancelled. Keeping compatible build dependencies avoided that rebuild for ruby's rust build dependency, but ccache itself needs llvm to build on Xcode 14 (Homebrew/homebrew-core#319277). With llvm declared as a build dependency, brew rebuilds llvm 23 first: my llvm 22.1.8 has compatibility_version 1 and the formula has 2, even though llvm 22's clang compiles ccache fine. With this change and that ccache PR, which declares depends_on "llvm" => [:build, :no_linkage], brew kept llvm 22.1.8 and built ccache 4.14.1_2 from source in about six minutes.

brew test ccache and brew audit --strict ccache pass.

Changes

  • FormulaInstaller: when building from source, keep an outdated :no_linkage build dependency if it's installed and opt-linked, whatever its compatibility_version. Other build dependencies still need a matching compatibility_version.
  • Document this in the :no_linkage DSL docs and the Formula Cookbook.
  • Add a spec for keeping an outdated :no_linkage build dependency with a different compatibility_version.

  • 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 brew benchmark results.
  • 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.

Claude Code (Claude Opus 5.5) helped trace the dependency resolution code and write the change, docs and spec. So far it's verified with brew style on the changed files and by installing ccache on my macOS 12 machine with this branch and the linked ccache PR, which kept llvm 22 instead of rebuilding it. brew typecheck and the formula_installer specs haven't run locally yet.


Summary by CodeRabbit

  • Bug Fixes
    • Source builds now handle :no_linkage build dependencies without unnecessarily requiring a compatible installed version. Dependencies may be skipped when they are not linked or have missing options; other build dependencies continue to follow compatibility-version checks.
  • Documentation
    • Clarified that :no_linkage build dependencies can use an outdated installed version during source builds.
    • Added guidance on keeping declarations consistent within a spec and platform, with differences allowed across stable, HEAD, and platforms.

@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.

Potentially happy to merge this but more questions again. Would be good to add dedicated tests for this and the last PR to verify functionality doesn't change when building bottles.

Comment thread Library/Homebrew/formula_installer.rb
@HaraldNordgren

Copy link
Copy Markdown
Contributor Author

Potentially happy to merge this but more questions again. Would be good to add dedicated tests for this and the last PR to verify functionality doesn't change when building bottles.

Happy to hear!

Yes, tests are good! You argued here (#15927) that testing was not feasible, would you say it's more possible to add here, and/or for the code in my previous one: #24273.

@MikeMcQuaid

Copy link
Copy Markdown
Member

You argued here (#15927) that testing was not feasible

Sorry, I can't see where I said that, can you quote me?

would you say it's more possible to add here

add here but include for the previous one. I'm thinking of more in formula_installer_spec to just exercise the old behaviour remains when using --build-bottle etc.

@HaraldNordgren

Copy link
Copy Markdown
Contributor Author

Sorry, I can't see where I said that, can you quote me?

Here: #15927 (comment)

add here but include for the previous one. I'm thinking of more in formula_installer_spec to just exercise the old behaviour remains when using --build-bottle etc.

Makes sense, I'll take a look (likely tomorrow because I need some family time 😃)

@MikeMcQuaid

Copy link
Copy Markdown
Member

Sorry, I can't see where I said that, can you quote me?

Here: #15927 (comment)

Ah. You can ignore anything I said about testing pre-AI 😉

@HaraldNordgren
HaraldNordgren marked this pull request as ready for review October 17, 2026 18:57
@coderabbitai

coderabbitai Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 2002f579-ad57-4c82-a414-55e160da0aaf
📥 Commits

Reviewing files that changed from the base of the PR and between 2513d69 and ecc49c6.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: ab0f0a2d-940c-4647-b20d-dcf7251c1891
📥 Commits

Reviewing files that changed from the base of the PR and between 013821d and 2513d69.

📒 Files selected for processing: 4
  • Library/Homebrew/formula.rb
  • Library/Homebrew/formula_installer.rb
  • Library/Homebrew/test/formula_installer_spec.rb
  • docs/Formula-Cookbook.md

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

📝 Walkthrough

Build dependency expansion now handles :no_linkage dependencies separately from compatibility-version checks. Documentation describes the dependency declaration rules, and installer specs cover the updated conditions.

Changes: Build dependency expansion

Layer / File(s) Summary
no_linkage dependency rules
Library/Homebrew/formula_installer.rb, Library/Homebrew/formula.rb, docs/Formula-Cookbook.md
FormulaInstaller skips compatibility-version checks for :no_linkage build dependencies. Documentation describes use of outdated installed versions during source builds and declaration consistency across specs and platforms.
Dependency expansion specs
Library/Homebrew/test/formula_installer_spec.rb
Specs cover compatibility-version cases and :no_linkage dependency handling, including bottle builds, opt-link status, missing options, and the :build tag.

Priority: ⬇️ Low

Merge Risk: ⚪ Minimal · up to 2513d

No actionable merge-blocking issue is established. Run the outstanding typecheck and installer specs as normal checks before merging.

Security architecture risk: 🔵 Low · up to 2513d

The change is narrowly scoped and preserves bottle-build exclusions and incompatible-dependency upgrade checks. It broadens reliance on already installed build tools. No new security attack path was established, but artifact identity and stability under concurrent mutation remain incompletely verified.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The direct exposure change is reuse of additional already installed build tools and their recorded runtime dependencies during eligible source builds. The inspected change does not establish a new remote entrypoint, tenant boundary, credential grant, or privilege escalation path.

Trust Boundaries and Controls

  • observed — The compatibility exception requires an explicit no_linkage declaration on a build dependency. Required-option checks, opt-path existence, receipt availability, bottle-build exclusion, and incompatible-upgrade reconciliation remain controls on retention. Compatibility-version checking is not replaced by a security-version check.

Resilience and Maintainability Implications

  • observed — Installation locks the target and declared recursive dependencies before recomputing its dependency plan, and releases locks on exceptions. This is counterevidence against unrestricted concurrent modification by cooperating installers, but does not prove stability for receipt-only dependencies or external filesystem writers.

Hardening Proposals

  • proposed — Consider explicitly validating that the retained opt target and the keg supplying runtime-dependency metadata identify the same installed artifact. This would strengthen identity assurance without treating compatibility_version as a security-patch guarantee.
Pre-merge checks: Passed 4
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: keeping outdated build dependencies marked :no_linkage.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@HaraldNordgren

Copy link
Copy Markdown
Contributor Author

@MikeMcQuaid Tests added!

- A build dependency that is only run, like `llvm` used as a compiler,
  isn't built against, so its `compatibility_version` doesn't matter.
  Requiring a match makes e.g. `ccache` on Xcode 14 rebuild `llvm` 23
  even though the installed `llvm` 22 can compile it.
- Let formulae tag such build dependencies `:no_linkage`, which
  already means the formula doesn't link against them, and keep them
  when outdated. The dependencies they were built against are still
  checked.
- Check that `--build-bottle`, with or without `--build-from-source`,
  still upgrades outdated build dependencies under both keep rules: a
  matching `compatibility_version` (Homebrew#24273) and `:no_linkage`.
- Also check that a build dependency's own dependencies are upgraded
  when building a bottle, and that `:no_linkage` alone doesn't keep an
  outdated runtime dependency.
- Check that the old behaviour stays for an outdated build dependency
  with a different `compatibility_version`, an outdated runtime
  dependency with a matching one and a keg whose install receipt lists
  no runtime dependencies.
- Check that a `:no_linkage` build dependency is still upgraded if it
  isn't opt-linked or is missing required options.
@HaraldNordgren
HaraldNordgren force-pushed the keep-no-linkage-build-dependencies branch from 2513d69 to ecc49c6 Compare October 18, 2026 09:33

@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!

@MikeMcQuaid
MikeMcQuaid added this pull request to the merge queue Oct 19, 2026
Merged via the queue into Homebrew:main with commit 327a67b Oct 19, 2026
52 checks passed
@HaraldNordgren
HaraldNordgren deleted the keep-no-linkage-build-dependencies branch October 19, 2026 14:33
HaraldNordgren added a commit to HaraldNordgren/homebrew-core that referenced this pull request Oct 19, 2026
- ccache 4.14 uses C++20 `std::ranges` algorithms that Apple clang 14
  (Xcode 14.2 and older) doesn't provide, so build with `llvm` there,
  like `mysql@8.4` and others do. Declaring `llvm` also puts `llvm-ar`
  on the build `PATH`, which CMake needs for `ENABLE_IPO`.
- ccache only uses llvm's clang as a compiler and doesn't link against
  it, so mark it `:no_linkage`. Since Homebrew/brew#24290, an installed
  but outdated `llvm` is then kept, not rebuilt.
HaraldNordgren added a commit to HaraldNordgren/homebrew-core that referenced this pull request Oct 19, 2026
- ccache 4.14 uses C++20 `std::ranges` algorithms that Apple clang 14
  (Xcode 14.2 and older) doesn't provide, so build with `llvm` there,
  like `mysql@8.4` and others do. Declaring `llvm` also puts `llvm-ar`
  on the build `PATH`, which CMake needs for `ENABLE_IPO`.
- ccache only uses llvm's clang as a compiler and doesn't link against
  it, so mark it `:no_linkage`. Since Homebrew/brew#24290, an installed
  but outdated `llvm` is then kept, not rebuilt.
- Hide `llvm` from the `PATH` again for the tests. Its `llvm-profdata`
  can't read the profiles Apple clang 14 writes, so `test.profiling`
  and `test.profiling_clang` fail. Without it they're skipped, as on
  newer Xcode.
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.

2 participants