Merge remote-tracking branch 'origin/main' into stricter-brew-wrappers

This commit is contained in:
Carlo Cabrera 2025-08-18 18:41:19 +08:00 committed by Carlo Cabrera
commit 145c65d811
No known key found for this signature in database
GPG Key ID: C74D447FC549A1D0
285 changed files with 4931 additions and 2917 deletions

View File

@ -9,15 +9,19 @@ Please follow these guidelines when contributing:
### Required Before Each Commit
- Run `brew typecheck` to verify types are declared correctly using Sorbet.
Individual files/directories cannot be checked.
`brew typecheck` is fast enough to just be run globally every time.
- Run `brew style --fix` to lint code formatting using RuboCop.
Individual files can be checked/fixed by passing them as arguments.
Individual files can be checked/fixed by passing them as arguments e.g. `brew style --fix Library/Homebrew/cmd/reinstall.rb``
- Run `brew tests --online` to ensure that RSpec unit tests are passing (although some online tests may be flaky so can be ignored if they pass on a rerun).
Individual test files can be passed with `--only` e.g. to test `Library/Homebrew/cmd/reinstall.rb` with `Library/Homebrew/test/cmd/reinstall_spec.rb` run `brew tests --only=cmd/reinstall`.
### Development Flow
- Write new code (using Sorbet `sig` type signatures and `typed: strict` files whenever possible)
- Write new tests (avoid more than one `:integration_test` per file for speed)
- Write new code (using Sorbet `sig` type signatures and `typed: strict` for new files, but never for RSpec/test/`*_spec.rb` files)
- Write new tests (avoid more than one `:integration_test` per file for speed).
Use only one `expect` assertion per test.
- Keep comments minimal; prefer self-documenting code through strings, variable names, etc. over more comments.
## Repository Structure
@ -34,7 +38,10 @@ Please follow these guidelines when contributing:
## Key Guidelines
1. Follow Ruby best practices and idiomatic patterns
2. Maintain existing code structure and organisation
3. Write unit tests for new functionality. Use one assertion per test where possible.
4. Document public APIs and complex logic. Suggest changes to the `docs/` folder when appropriate
1. Follow Ruby and Bash best practices and idiomatic patterns.
2. Maintain existing code structure and organisation.
3. Write unit tests for new functionality.
4. Document public APIs and complex logic.
5. Suggest changes to the `docs/` folder when appropriate
6. Follow software principles such as DRY and YAGNI.
7. Keep diffs as minimal as possible.

View File

@ -17,13 +17,6 @@ updates:
- "*"
allow:
- dependency-type: all
cooldown:
default-days: 1
semver-major-days: 14
semver-minor-days: 7
semver-patch-days: 1
include:
- "*"
- package-ecosystem: devcontainers
directory: "/"
multi-ecosystem-group: all
@ -31,10 +24,6 @@ updates:
- "*"
allow:
- dependency-type: all
cooldown:
default-days: 1
include:
- "*"
- package-ecosystem: docker
directory: "/"
multi-ecosystem-group: all
@ -49,10 +38,6 @@ updates:
- "*"
allow:
- dependency-type: all
cooldown:
default-days: 1
include:
- "*"
- package-ecosystem: pip
directories:
- "/Library/Homebrew/formula-analytics/"
@ -61,11 +46,4 @@ updates:
- "*"
allow:
- dependency-type: all
cooldown:
default-days: 1
semver-major-days: 14
semver-minor-days: 7
semver-patch-days: 1
include:
- "*"

View File

@ -43,7 +43,7 @@ jobs:
- name: Install tools
run: brew install actionlint shellcheck zizmor
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
@ -87,13 +87,13 @@ jobs:
security-events: write
steps:
- name: Download SARIF file
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
name: results.sarif
path: results.sarif
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5
uses: github/codeql-action/upload-sarif@df559355d593797519d70b90fc8edd5db049e7a2 # v3.29.9
with:
sarif_file: results.sarif
category: zizmor

View File

@ -34,7 +34,7 @@ jobs:
test-bot: true
- name: Cache Bundler RubyGems
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: ${{ steps.set-up-homebrew.outputs.gems-path }}
key: ${{ runner.os }}-rubygems-${{ steps.set-up-homebrew.outputs.gems-hash }}

View File

@ -22,12 +22,12 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4
uses: github/codeql-action/init@df559355d593797519d70b90fc8edd5db049e7a2 # v3.29.9
with:
languages: ruby
config: |
@ -35,4 +35,4 @@ jobs:
- Library/Homebrew/vendor
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4
uses: github/codeql-action/analyze@df559355d593797519d70b90fc8edd5db049e7a2 # v3.29.9

View File

@ -34,7 +34,7 @@ jobs:
merge: ${{ steps.attributes.outputs.merge }}
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 0
persist-credentials: false
@ -160,7 +160,7 @@ jobs:
arch: "arm64"
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 0
persist-credentials: false
@ -188,7 +188,7 @@ jobs:
echo "push=$(jq --raw-output "${filter}" <<<"${PUSH}")" >>"${GITHUB_OUTPUT}"
- name: Log in to GitHub Packages (github-actions[bot])
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
with:
registry: ghcr.io
username: github-actions[bot]
@ -223,7 +223,7 @@ jobs:
- name: Log in to GitHub Packages (BrewTestBot)
if: fromJSON(steps.attributes.outputs.push)
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
with:
registry: ghcr.io
username: BrewTestBot
@ -273,20 +273,20 @@ jobs:
cache-binary: false
- name: Download Docker image digests
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
path: ${{ runner.temp }}/digests
pattern: digest-${{ matrix.version }}-*
merge-multiple: true
- name: Log in to Docker Hub
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
with:
username: brewtestbot
password: ${{ secrets.HOMEBREW_BREW_DOCKER_TOKEN }}
- name: Log in to GitHub Packages (BrewTestBot)
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
with:
registry: ghcr.io
username: BrewTestBot

View File

@ -31,7 +31,7 @@ jobs:
test-bot: false
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
@ -52,7 +52,7 @@ jobs:
run: vale docs/
- name: Install Ruby
uses: ruby/setup-ruby@472790540115ce5bd69d399a020189a8c87d641f # v1.247.0
uses: ruby/setup-ruby@829114fc20da43a41d27359103ec7a63020954d4 # v1.255.0
with:
bundler-cache: true
working-directory: docs
@ -70,7 +70,7 @@ jobs:
run: ../script/generate-api-samples.rb --template
- name: Cache HTML Proofer
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: tmp/.htmlproofer
key: ${{ runner.os }}-htmlproofer

View File

@ -81,7 +81,7 @@ jobs:
run: rm -f "${RUNNER_TEMP}/${TEMPORARY_CERTIFICATE_FILE}"
- name: Checkout another Homebrew to brew subdirectory
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
path: brew
fetch-depth: 0
@ -162,7 +162,7 @@ jobs:
name: macos-15-arm64
steps:
- name: Download installer from GitHub Actions
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
name: "${{ needs.build.outputs.installer_path }}"
@ -215,7 +215,7 @@ jobs:
contents: write
steps:
- name: Download installer from GitHub Actions
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
name: "${{ needs.build.outputs.installer_path }}"

View File

@ -36,14 +36,14 @@ jobs:
test-bot: false
- name: Checkout Homebrew/rubydoc.brew.sh
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
repository: Homebrew/rubydoc.brew.sh
path: rubydoc
persist-credentials: false
- name: Install Ruby
uses: ruby/setup-ruby@472790540115ce5bd69d399a020189a8c87d641f # v1.247.0
uses: ruby/setup-ruby@829114fc20da43a41d27359103ec7a63020954d4 # v1.255.0
with:
bundler-cache: true
working-directory: rubydoc

View File

@ -50,7 +50,7 @@ jobs:
signing_key: ${{ secrets.BREWTESTBOT_SSH_SIGNING_KEY }}
- name: Cache Bundler RubyGems
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: ${{ steps.set-up-homebrew.outputs.gems-path }}
key: ${{ runner.os }}-rubygems-${{ steps.set-up-homebrew.outputs.gems-hash }}

View File

@ -43,7 +43,7 @@ jobs:
echo "target=${target}" >> "$GITHUB_OUTPUT"
echo "source=${source}" >> "$GITHUB_OUTPUT"
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 1
persist-credentials: true

View File

@ -40,7 +40,7 @@ jobs:
test-bot: false
- name: Cache Bundler RubyGems
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: ${{ steps.set-up-homebrew.outputs.gems-path }}
key: ${{ runner.os }}-rubygems-syntax-${{ steps.set-up-homebrew.outputs.gems-hash }}
@ -53,7 +53,7 @@ jobs:
run: brew install shellcheck shfmt
- name: Cache style cache
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: ~/.cache/Homebrew/style
key: syntax-style-cache-${{ github.sha }}
@ -92,7 +92,7 @@ jobs:
test-bot: true
- name: Cache Bundler RubyGems
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: ${{ steps.set-up-homebrew.outputs.gems-path }}
key: ${{ runner.os }}-rubygems-tap-syntax-${{ steps.set-up-homebrew.outputs.gems-hash }}
@ -102,7 +102,7 @@ jobs:
run: brew install-bundler-gems --groups=style
- name: Cache style cache
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: ~/.cache/Homebrew/style
key: tap-syntax-style-cache-${{ github.sha }}
@ -266,7 +266,7 @@ jobs:
test-bot: false
- name: Cache Bundler RubyGems
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: ${{ steps.set-up-homebrew.outputs.gems-path }}
key: ${{ matrix.runs-on }}-tests-rubygems-${{ steps.set-up-homebrew.outputs.gems-hash }}
@ -281,7 +281,7 @@ jobs:
run: mkdir tests
- name: Cache parallel tests log
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: tests
key: ${{ runner.os }}-${{ matrix.test-flags }}-parallel_runtime_rspec-${{ github.sha }}
@ -505,7 +505,7 @@ jobs:
- name: Cache Homebrew Bundler RubyGems
id: cache
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: ${{ steps.set-up-homebrew.outputs.gems-path }}
key: ${{ runner.os }}-rubygems-${{ steps.set-up-homebrew.outputs.gems-hash }}

View File

@ -92,7 +92,7 @@ jobs:
fi
- name: Generate push token
uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6
uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1
id: app-token
if: github.event_name == 'workflow_dispatch'
with:

View File

@ -96,6 +96,11 @@ Layout/ArgumentAlignment:
Layout/CaseIndentation:
EnforcedStyle: end
# currently bugged and as relevant/urgent in taps:
# https://github.com/rubocop/rubocop/issues/14443
Layout/EmptyLinesAfterModuleInclusion:
Enabled: false
# significantly less indentation involved; more consistent
Layout/FirstArrayElementIndentation:
EnforcedStyle: consistent

View File

@ -17,7 +17,7 @@ GEM
bindata (~> 2)
erubi (1.13.1)
hana (1.3.7)
json (2.13.0)
json (2.13.2)
json_schemer (2.4.0)
bigdecimal
hana (~> 1.3)
@ -32,7 +32,7 @@ GEM
minitest (5.25.5)
netrc (0.11.0)
parallel (1.27.0)
parallel_tests (5.3.1)
parallel_tests (5.4.0)
parallel
parser (3.3.9.0)
ast (~> 2.4.1)
@ -55,7 +55,7 @@ GEM
logger
prism (>= 1.3.0)
redcarpet (3.6.1)
regexp_parser (2.10.0)
regexp_parser (2.11.2)
require-hooks (0.2.2)
rexml (3.4.1)
rspec (3.13.1)
@ -79,7 +79,7 @@ GEM
rspec-support (3.13.4)
rspec_junit_formatter (0.6.0)
rspec-core (>= 2, < 4, != 2.12.0)
rubocop (1.78.0)
rubocop (1.79.2)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
@ -87,7 +87,7 @@ GEM
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.9.3, < 3.0)
rubocop-ast (>= 1.45.1, < 2.0)
rubocop-ast (>= 1.46.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 4.0)
rubocop-ast (1.46.0)
@ -106,7 +106,7 @@ GEM
rubocop-sorbet (0.10.5)
lint_roller
rubocop (>= 1.75.2)
ruby-lsp (0.25.0)
ruby-lsp (0.26.1)
language_server-protocol (~> 3.17.0)
prism (>= 1.2, < 2.0)
rbs (>= 3, < 5)
@ -118,22 +118,22 @@ GEM
docile (~> 1.1)
simplecov-html (~> 0.11)
simplecov_json_formatter (~> 0.1)
simplecov-cobertura (2.1.0)
simplecov-cobertura (3.0.0)
rexml
simplecov (~> 0.19)
simplecov-html (0.13.2)
simplecov_json_formatter (0.1.4)
simpleidn (0.2.3)
sorbet (0.5.12357)
sorbet-static (= 0.5.12357)
sorbet-runtime (0.5.12357)
sorbet-static (0.5.12357-aarch64-linux)
sorbet-static (0.5.12357-universal-darwin)
sorbet-static (0.5.12357-x86_64-linux)
sorbet-static-and-runtime (0.5.12357)
sorbet (= 0.5.12357)
sorbet-runtime (= 0.5.12357)
spoom (1.7.5)
sorbet (0.5.12401)
sorbet-static (= 0.5.12401)
sorbet-runtime (0.5.12401)
sorbet-static (0.5.12401-aarch64-linux)
sorbet-static (0.5.12401-universal-darwin)
sorbet-static (0.5.12401-x86_64-linux)
sorbet-static-and-runtime (0.5.12401)
sorbet (= 0.5.12401)
sorbet-runtime (= 0.5.12401)
spoom (1.7.6)
erubi (>= 1.10.0)
prism (>= 0.28.0)
rbi (>= 0.3.3)

View File

@ -4,6 +4,7 @@
require "api/analytics"
require "api/cask"
require "api/formula"
require "api/internal"
require "base64"
module Homebrew
@ -26,7 +27,7 @@ module Homebrew
api_url = "#{HOMEBREW_API_DEFAULT_DOMAIN}/#{endpoint}"
output = Utils::Curl.curl_output("--fail", api_url)
end
raise ArgumentError, "No file found at #{Tty.underline}#{api_url}#{Tty.reset}" unless output.success?
raise ArgumentError, "No file found at: #{Tty.underline}#{api_url}#{Tty.reset}" unless output.success?
cache[endpoint] = JSON.parse(output.stdout, freeze: true)
rescue JSON::ParserError
@ -151,6 +152,30 @@ module Homebrew
json.except("variations")
end
sig { params(download_queue: T.nilable(DownloadQueue), stale_seconds: Integer).void }
def self.fetch_api_files!(download_queue: nil, stale_seconds: Homebrew::EnvConfig.api_auto_update_secs.to_i)
if Homebrew::EnvConfig.use_internal_api?
Homebrew::API::Internal.fetch_formula_api!(download_queue:, stale_seconds:)
Homebrew::API::Internal.fetch_cask_api!(download_queue:, stale_seconds:)
else
Homebrew::API::Formula.fetch_api!(download_queue:, stale_seconds:)
Homebrew::API::Formula.fetch_tap_migrations!(download_queue:, stale_seconds:)
Homebrew::API::Cask.fetch_api!(download_queue:, stale_seconds:)
Homebrew::API::Cask.fetch_tap_migrations!(download_queue:, stale_seconds:)
end
end
sig { void }
def self.write_names_and_aliases
if Homebrew::EnvConfig.use_internal_api?
Homebrew::API::Internal.write_formula_names_and_aliases
Homebrew::API::Internal.write_cask_names
else
Homebrew::API::Formula.write_names_and_aliases
Homebrew::API::Cask.write_names
end
end
sig { params(names: T::Array[String], type: String, regenerate: T::Boolean).returns(T::Boolean) }
def self.write_names_file!(names, type, regenerate:)
names_path = HOMEBREW_CACHE_API/"#{type}_names.txt"
@ -162,6 +187,20 @@ module Homebrew
false
end
sig { params(aliases: T::Hash[String, String], type: String, regenerate: T::Boolean).returns(T::Boolean) }
def self.write_aliases_file!(aliases, type, regenerate:)
aliases_path = HOMEBREW_CACHE_API/"#{type}_aliases.txt"
if !aliases_path.exist? || regenerate
aliases_text = aliases.map do |alias_name, real_name|
"#{alias_name}|#{real_name}"
end
aliases_path.write(aliases_text.join("\n"))
return true
end
false
end
sig {
params(json_data: T::Hash[String, T.untyped])
.returns([T::Boolean, T.any(String, T::Array[T.untyped], T::Hash[String, T.untyped])])
@ -202,6 +241,69 @@ module Homebrew
Tap.fetch(org, repo)
end
sig { returns(T::Array[String]) }
def self.formula_names
if Homebrew::EnvConfig.use_internal_api?
Homebrew::API::Internal.formula_arrays.keys
else
Homebrew::API::Formula.all_formulae.keys
end
end
sig { returns(T::Hash[String, String]) }
def self.formula_aliases
if Homebrew::EnvConfig.use_internal_api?
Homebrew::API::Internal.formula_aliases
else
Homebrew::API::Formula.all_aliases
end
end
sig { returns(T::Hash[String, String]) }
def self.formula_renames
if Homebrew::EnvConfig.use_internal_api?
Homebrew::API::Internal.formula_renames
else
Homebrew::API::Formula.all_renames
end
end
sig { returns(T::Hash[String, String]) }
def self.formula_tap_migrations
if Homebrew::EnvConfig.use_internal_api?
Homebrew::API::Internal.formula_tap_migrations
else
Homebrew::API::Formula.tap_migrations
end
end
sig { returns(T::Array[String]) }
def self.cask_tokens
if Homebrew::EnvConfig.use_internal_api?
Homebrew::API::Internal.cask_hashes.keys
else
Homebrew::API::Cask.all_casks.keys
end
end
sig { returns(T::Hash[String, String]) }
def self.cask_renames
if Homebrew::EnvConfig.use_internal_api?
Homebrew::API::Internal.cask_renames
else
Homebrew::API::Cask.all_renames
end
end
sig { returns(T::Hash[String, String]) }
def self.cask_tap_migrations
if Homebrew::EnvConfig.use_internal_api?
Homebrew::API::Internal.cask_tap_migrations
else
Homebrew::API::Cask.tap_migrations
end
end
end
sig { params(block: T.proc.returns(T.untyped)).returns(T.untyped) }

View File

@ -14,18 +14,25 @@ module Homebrew
DEFAULT_API_FILENAME = "cask.jws.json"
sig { returns(String) }
def self.api_filename
return DEFAULT_API_FILENAME unless ENV.fetch("HOMEBREW_USE_INTERNAL_API", false)
"cask.#{SimulateSystem.current_tag}.jws.json"
end
private_class_method :cache
sig { params(token: String).returns(T::Hash[String, T.untyped]) }
def self.fetch(token)
Homebrew::API.fetch "cask/#{token}.json"
sig { params(name: String).returns(T::Hash[String, T.untyped]) }
def self.cask_json(name)
fetch_cask_json! name if !cache.key?("cask_json") || !cache.fetch("cask_json").key?(name)
cache.fetch("cask_json").fetch(name)
end
sig { params(name: String, download_queue: T.nilable(DownloadQueue)).void }
def self.fetch_cask_json!(name, download_queue: nil)
endpoint = "cask/#{name}.json"
json_cask, updated = Homebrew::API.fetch_json_api_file endpoint, download_queue: download_queue
return if download_queue
json_cask = JSON.parse((HOMEBREW_CACHE_API/endpoint).read) unless updated
cache["cask_json"] ||= {}
cache["cask_json"][name] = json_cask
end
sig { params(cask: ::Cask::Cask, download_queue: T.nilable(Homebrew::DownloadQueue)).returns(Homebrew::API::SourceDownload) }
@ -64,7 +71,7 @@ module Homebrew
sig { returns(Pathname) }
def self.cached_json_file_path
HOMEBREW_CACHE_API/api_filename
HOMEBREW_CACHE_API/DEFAULT_API_FILENAME
end
sig {
@ -72,7 +79,7 @@ module Homebrew
.returns([T.any(T::Array[T.untyped], T::Hash[String, T.untyped]), T::Boolean])
}
def self.fetch_api!(download_queue: nil, stale_seconds: Homebrew::EnvConfig.api_auto_update_secs.to_i)
Homebrew::API.fetch_json_api_file api_filename, stale_seconds:, download_queue:
Homebrew::API.fetch_json_api_file DEFAULT_API_FILENAME, stale_seconds:, download_queue:
end
sig {

View File

@ -14,18 +14,25 @@ module Homebrew
DEFAULT_API_FILENAME = "formula.jws.json"
sig { returns(String) }
def self.api_filename
return DEFAULT_API_FILENAME unless ENV.fetch("HOMEBREW_USE_INTERNAL_API", false)
"internal/formula.#{SimulateSystem.current_tag}.jws.json"
end
private_class_method :cache
sig { params(name: String).returns(T::Hash[String, T.untyped]) }
def self.fetch(name)
Homebrew::API.fetch "formula/#{name}.json"
def self.formula_json(name)
fetch_formula_json! name if !cache.key?("formula_json") || !cache.fetch("formula_json").key?(name)
cache.fetch("formula_json").fetch(name)
end
sig { params(name: String, download_queue: T.nilable(DownloadQueue)).void }
def self.fetch_formula_json!(name, download_queue: nil)
endpoint = "formula/#{name}.json"
json_formula, updated = Homebrew::API.fetch_json_api_file endpoint, download_queue: download_queue
return if download_queue
json_formula = JSON.parse((HOMEBREW_CACHE_API/endpoint).read) unless updated
cache["formula_json"] ||= {}
cache["formula_json"][name] = json_formula
end
sig { params(formula: ::Formula, download_queue: T.nilable(Homebrew::DownloadQueue)).returns(Homebrew::API::SourceDownload) }
@ -63,7 +70,7 @@ module Homebrew
sig { returns(Pathname) }
def self.cached_json_file_path
HOMEBREW_CACHE_API/api_filename
HOMEBREW_CACHE_API/DEFAULT_API_FILENAME
end
sig {
@ -71,7 +78,7 @@ module Homebrew
.returns([T.any(T::Array[T.untyped], T::Hash[String, T.untyped]), T::Boolean])
}
def self.fetch_api!(download_queue: nil, stale_seconds: Homebrew::EnvConfig.api_auto_update_secs.to_i)
Homebrew::API.fetch_json_api_file api_filename, stale_seconds:, download_queue:
Homebrew::API.fetch_json_api_file DEFAULT_API_FILENAME, stale_seconds:, download_queue:
end
sig {
@ -147,13 +154,8 @@ module Homebrew
def self.write_names_and_aliases(regenerate: false)
download_and_cache_data! unless cache.key?("formulae")
return unless Homebrew::API.write_names_file!(all_formulae.keys, "formula", regenerate:)
(HOMEBREW_CACHE_API/"formula_aliases.txt").open("w") do |file|
all_aliases.each do |alias_name, real_name|
file.puts "#{alias_name}|#{real_name}"
end
end
Homebrew::API.write_names_file!(all_formulae.keys, "formula", regenerate:)
Homebrew::API.write_aliases_file!(all_aliases, "formula", regenerate:)
end
end
end

View File

@ -0,0 +1,177 @@
# typed: strict
# frozen_string_literal: true
require "cachable"
require "api"
require "api/source_download"
require "download_queue"
require "formula_stub"
module Homebrew
module API
# Helper functions for using the JSON internal API.
module Internal
extend Cachable
private_class_method :cache
sig { returns(String) }
def self.formula_endpoint
"internal/formula.#{SimulateSystem.current_tag}.jws.json"
end
sig { returns(String) }
def self.cask_endpoint
"internal/cask.#{SimulateSystem.current_tag}.jws.json"
end
sig { params(name: String).returns(Homebrew::FormulaStub) }
def self.formula_stub(name)
return cache["formula_stubs"][name] if cache.key?("formula_stubs") && cache["formula_stubs"].key?(name)
stub_array = formula_arrays[name]
raise "No formula stub found for #{name}" unless stub_array
stub = Homebrew::FormulaStub.new(
name: name,
pkg_version: PkgVersion.parse(stub_array[0]),
rebuild: stub_array[1],
sha256: stub_array[2],
)
cache["formula_stubs"] ||= {}
cache["formula_stubs"][name] = stub
stub
end
sig {
params(download_queue: T.nilable(Homebrew::DownloadQueue), stale_seconds: Integer)
.returns([T::Hash[String, T.untyped], T::Boolean])
}
def self.fetch_formula_api!(download_queue: nil, stale_seconds: Homebrew::EnvConfig.api_auto_update_secs.to_i)
json_contents, updated = (Homebrew::API.fetch_json_api_file formula_endpoint, stale_seconds:, download_queue:)
[T.cast(json_contents, T::Hash[String, T.untyped]), updated]
end
sig {
params(download_queue: T.nilable(Homebrew::DownloadQueue), stale_seconds: Integer)
.returns([T::Hash[String, T.untyped], T::Boolean])
}
def self.fetch_cask_api!(download_queue: nil, stale_seconds: Homebrew::EnvConfig.api_auto_update_secs.to_i)
json_contents, updated = (Homebrew::API.fetch_json_api_file cask_endpoint, stale_seconds:, download_queue:)
[T.cast(json_contents, T::Hash[String, T.untyped]), updated]
end
sig { returns(T::Boolean) }
def self.download_and_cache_formula_data!
json_contents, updated = fetch_formula_api!
cache["formula_stubs"] = {}
cache["formula_aliases"] = json_contents["aliases"]
cache["formula_renames"] = json_contents["renames"]
cache["formula_tap_migrations"] = json_contents["tap_migrations"]
cache["formula_arrays"] = json_contents["formulae"]
updated
end
private_class_method :download_and_cache_formula_data!
sig { returns(T::Boolean) }
def self.download_and_cache_cask_data!
json_contents, updated = fetch_cask_api!
cache["cask_stubs"] = {}
cache["cask_renames"] = json_contents["renames"]
cache["cask_tap_migrations"] = json_contents["tap_migrations"]
cache["cask_hashes"] = json_contents["casks"]
updated
end
private_class_method :download_and_cache_cask_data!
sig { params(regenerate: T::Boolean).void }
def self.write_formula_names_and_aliases(regenerate: false)
download_and_cache_formula_data! unless cache.key?("formula_arrays")
Homebrew::API.write_names_file!(formula_arrays.keys, "formula", regenerate:)
Homebrew::API.write_aliases_file!(formula_aliases, "formula", regenerate:)
end
sig { params(regenerate: T::Boolean).void }
def self.write_cask_names(regenerate: false)
download_and_cache_cask_data! unless cache.key?("cask_hashes")
Homebrew::API.write_names_file!(cask_hashes.keys, "cask", regenerate:)
end
sig { returns(T::Hash[String, [String, Integer, T.nilable(String)]]) }
def self.formula_arrays
unless cache.key?("formula_arrays")
updated = download_and_cache_formula_data!
write_formula_names_and_aliases(regenerate: updated)
end
cache["formula_arrays"]
end
sig { returns(T::Hash[String, String]) }
def self.formula_aliases
unless cache.key?("formula_aliases")
updated = download_and_cache_formula_data!
write_formula_names_and_aliases(regenerate: updated)
end
cache["formula_aliases"]
end
sig { returns(T::Hash[String, String]) }
def self.formula_renames
unless cache.key?("formula_renames")
updated = download_and_cache_formula_data!
write_formula_names_and_aliases(regenerate: updated)
end
cache["formula_renames"]
end
sig { returns(T::Hash[String, String]) }
def self.formula_tap_migrations
unless cache.key?("formula_tap_migrations")
updated = download_and_cache_formula_data!
write_formula_names_and_aliases(regenerate: updated)
end
cache["formula_tap_migrations"]
end
sig { returns(T::Hash[String, T::Hash[String, T.untyped]]) }
def self.cask_hashes
unless cache.key?("cask_hashes")
updated = download_and_cache_cask_data!
write_cask_names(regenerate: updated)
end
cache["cask_hashes"]
end
sig { returns(T::Hash[String, String]) }
def self.cask_renames
unless cache.key?("cask_renames")
updated = download_and_cache_cask_data!
write_cask_names(regenerate: updated)
end
cache["cask_renames"]
end
sig { returns(T::Hash[String, String]) }
def self.cask_tap_migrations
unless cache.key?("cask_tap_migrations")
updated = download_and_cache_cask_data!
write_cask_names(regenerate: updated)
end
cache["cask_tap_migrations"]
end
end
end
end

View File

@ -4,18 +4,27 @@
class BottleSpecification
RELOCATABLE_CELLARS = [:any, :any_skip_relocation].freeze
sig { returns(T.nilable(Tap)) }
attr_accessor :tap
attr_reader :collector, :root_url_specs, :repository
attr_reader :collector
sig { returns(T::Hash[String, T.untyped]) }
attr_reader :root_url_specs
sig { returns(String) }
attr_reader :repository
sig { void }
def initialize
@rebuild = 0
@repository = Homebrew::DEFAULT_REPOSITORY
@collector = Utils::Bottles::Collector.new
@root_url_specs = {}
@rebuild = T.let(0, Integer)
@repository = T.let(Homebrew::DEFAULT_REPOSITORY, String)
@collector = T.let(Utils::Bottles::Collector.new, Utils::Bottles::Collector)
@root_url_specs = T.let({}, T::Hash[String, T.untyped])
@root_url = T.let(nil, T.nilable(String))
end
sig { params(val: Integer).returns(T.nilable(Integer)) }
sig { params(val: Integer).returns(Integer) }
def rebuild(val = T.unsafe(nil))
val.nil? ? @rebuild : @rebuild = val
end

View File

@ -95,10 +95,7 @@ begin
require "api/cask"
download_queue = Homebrew::DownloadQueue.new
stale_seconds = 86400 # 1 day
Homebrew::API::Formula.fetch_api!(download_queue:, stale_seconds:)
Homebrew::API::Formula.fetch_tap_migrations!(download_queue:, stale_seconds:)
Homebrew::API::Cask.fetch_api!(download_queue:, stale_seconds:)
Homebrew::API::Cask.fetch_tap_migrations!(download_queue:, stale_seconds:)
Homebrew::API.fetch_api_files!(download_queue:, stale_seconds:)
begin
download_queue.fetch
ensure

View File

@ -678,8 +678,8 @@ else
then
message="Please update your system curl or set HOMEBREW_CURL_PATH to a newer version.
Minimum required version: ${HOMEBREW_MINIMUM_CURL_VERSION}
Your curl version: ${curl_name_and_version##* }
Your curl executable: $(type -p "${HOMEBREW_CURL}")"
Your curl version: ${curl_name_and_version##* }
Your curl executable: $(type -p "${HOMEBREW_CURL}")"
if [[ -z ${HOMEBREW_CURL_PATH} ]]
then
@ -706,8 +706,8 @@ Your curl executable: $(type -p "${HOMEBREW_CURL}")"
then
message="Please update your system Git or set HOMEBREW_GIT_PATH to a newer version.
Minimum required version: ${HOMEBREW_MINIMUM_GIT_VERSION}
Your Git version: ${major}.${minor}.${micro}.${build}
Your Git executable: $(unset git && type -p "${HOMEBREW_GIT}")"
Your Git version: ${major}.${minor}.${micro}.${build}
Your Git executable: $(unset git && type -p "${HOMEBREW_GIT}")"
if [[ -z ${HOMEBREW_GIT_PATH} ]]
then
HOMEBREW_FORCE_BREWED_GIT="1"
@ -1067,6 +1067,9 @@ then
fi
unset SUDO
# Remove internal variables
unset HOMEBREW_INTERNAL_ALLOW_PACKAGES_FROM_PATHS
if [[ -n "${HOMEBREW_BASH_COMMAND}" ]]
then
# source rather than executing directly to ensure the entire file is read into

View File

@ -227,7 +227,10 @@ class Build
end
begin
ENV.delete("HOMEBREW_FORBID_PACKAGES_FROM_PATHS")
# Undocumented opt-out for internal use.
# We need to allow formulae from paths here due to how we pass them through.
ENV["HOMEBREW_INTERNAL_ALLOW_PACKAGES_FROM_PATHS"] = "1"
args = Homebrew::Cmd::InstallCmd.new.args
Context.current = args.context
@ -268,7 +271,7 @@ rescue Exception => e # rubocop:disable Lint/RescueException
error_hash["output"] = e.output
end
error_pipe.puts error_hash.to_json
error_pipe.close
error_pipe&.puts error_hash.to_json
error_pipe&.close
exit! 1
end

View File

@ -8,6 +8,7 @@ module Cask
# Abstract superclass for all artifacts.
class AbstractArtifact
extend T::Helpers
abstract!
include Comparable

View File

@ -389,7 +389,7 @@ module Cask
end
def uninstall_pkgutil(*pkgs, command: nil, **_)
ohai "Uninstalling packages with sudo; the password may be necessary:"
ohai "Uninstalling packages with `sudo` (which may request your password)..."
pkgs.each do |regex|
::Cask::Pkg.all_matching(regex, command).each do |pkg|
puts pkg.package_id

View File

@ -35,7 +35,7 @@ module Cask
private
def run_installer(command: nil, verbose: false, **_options)
ohai "Running installer for #{cask} with sudo; the password may be necessary."
ohai "Running installer for #{cask} with `sudo` (which may request your password)..."
unless path.exist?
pkg = path.relative_path_from(cask.staged_path)
pkgs = Pathname.glob(cask.staged_path/"**"/"*.pkg").map { |path| path.relative_path_from(cask.staged_path) }

View File

@ -56,6 +56,9 @@ module Cask
(target.realpath == source.realpath || target.realpath.to_s.start_with?("#{cask.caskroom_path}/"))
opoo "#{message}; overwriting."
Utils.gain_permissions_remove(target, command:)
elsif (formula = conflicting_formula)
opoo "#{message} from formula #{formula}; skipping link."
return
else
raise CaskError, "#{message}."
end
@ -69,11 +72,32 @@ module Cask
return unless target.symlink?
ohai "Unlinking #{self.class.english_name} '#{target}'"
if (formula = conflicting_formula)
odebug "#{target} is from formula #{formula}; skipping unlink."
return
end
Utils.gain_permissions_remove(target, command:)
end
sig { params(command: T.class_of(SystemCommand)).void }
def create_filesystem_link(command); end
# Check if the target file is a symlink that originates from a formula
# with the same name as this cask, indicating a potential conflict
sig { returns(T.nilable(String)) }
def conflicting_formula
if target.symlink? && target.exist? &&
(match = target.realpath.to_s.match(%r{^#{HOMEBREW_CELLAR}/(?<formula>[^/]+)/}o))
match[:formula]
end
rescue => e
# If we can't determine the realpath or any other error occurs,
# don't treat it as a conflicting formula file
odebug "Error checking for conflicting formula file: #{e}"
nil
end
end
end
end

View File

@ -3,6 +3,8 @@
require "cask/denylist"
require "cask/download"
require "cask/installer"
require "cask/quarantine"
require "digest"
require "livecheck/livecheck"
require "source_location"
@ -498,20 +500,32 @@ module Cask
return if url.nil?
return if !cask.tap.official? && !signing?
return if cask.deprecated? && cask.deprecation_reason != :unsigned
return if cask.deprecated? && cask.deprecation_reason != :fails_gatekeeper_check
unless Quarantine.available?
odebug "Quarantine support is not available, skipping signing audit"
return
end
odebug "Auditing signing"
is_in_skiplist = cask.tap&.audit_exception(:signing_audit_skiplist, cask.token)
extract_artifacts do |artifacts, tmpdir|
is_container = artifacts.any? { |a| a.is_a?(Artifact::App) || a.is_a?(Artifact::Pkg) }
artifacts.each do |artifact|
next if artifact.is_a?(Artifact::Binary) && is_container == true
any_signing_failure = artifacts.any? do |artifact|
next false if artifact.is_a?(Artifact::Binary) && is_container == true
artifact_path = artifact.is_a?(Artifact::Pkg) ? artifact.path : artifact.source
path = tmpdir/artifact_path.relative_path_from(cask.staged_path)
unless Quarantine.detect(path)
odebug "#{path} does not have quarantine attributes, skipping signing audit"
next false
end
result = case artifact
when Artifact::Pkg
system_command("spctl", args: ["--assess", "--type", "install", path], print_stderr: false)
@ -521,21 +535,17 @@ module Cask
system_command("gktool", args: ["scan", path], print_stderr: false)
when Artifact::Binary
# Shell scripts cannot be signed, so we skip them
next if path.text_executable?
next false if path.text_executable?
system_command("codesign", args: ["--verify", "-R=notarized", "--check-notarization", path],
print_stderr: false)
system_command("codesign", args: ["--verify", "-R=notarized", "--check-notarization", path],
print_stderr: false)
else
add_error "Unknown artifact type: #{artifact.class}", location: url.location
end
if result.success? && cask.deprecated? && cask.deprecation_reason == :unsigned
add_error "Cask is deprecated as unsigned but artifacts are signed!"
end
next if cask.deprecated? && cask.deprecation_reason == :unsigned
next if result.success?
next false if result.success?
next true if cask.deprecated? && cask.deprecation_reason == :fails_gatekeeper_check
next true if is_in_skiplist
add_error <<~EOS, location: url.location
Signature verification failed:
@ -543,7 +553,21 @@ module Cask
macOS on ARM requires software to be signed.
Please contact the upstream developer to let them know they should sign and notarize their software.
EOS
true
end
return if any_signing_failure
add_error "Cask is in the signing audit skiplist, but does not need to be skipped!" if is_in_skiplist
return unless cask.deprecated?
return if cask.deprecation_reason != :fails_gatekeeper_check
add_error <<~EOS
Cask is deprecated because it failed Gatekeeper checks but all artifacts now pass!
Remove the deprecate/disable stanza or update the deprecate/disable reason.
EOS
end
end
@ -619,6 +643,11 @@ module Cask
.extract_nestedly(to: @tmpdir, verbose: false)
end
# Process rename operations after extraction
# Create a temporary installer to process renames in the audit directory
temp_installer = Installer.new(@cask)
temp_installer.process_rename_operations(target_dir: @tmpdir)
# Set the flag to indicate that extraction has occurred.
@artifacts_extracted = T.let(true, T.nilable(TrueClass))
@ -640,10 +669,20 @@ module Cask
extract_artifacts do |artifacts, tmpdir|
is_container = artifacts.any? { |a| a.is_a?(Artifact::App) || a.is_a?(Artifact::Pkg) }
artifacts.each do |artifact|
next if !artifact.is_a?(Artifact::App) && !artifact.is_a?(Artifact::Binary)
next if artifact.is_a?(Artifact::Binary) && is_container
mentions_rosetta = cask.caveats.include?("requires Rosetta 2")
requires_intel = cask.depends_on.arch&.any? { |arch| arch[:type] == :intel }
artifacts_to_test = artifacts.filter do |artifact|
next false if !artifact.is_a?(Artifact::App) && !artifact.is_a?(Artifact::Binary)
next false if artifact.is_a?(Artifact::Binary) && is_container
true
end
next if artifacts_to_test.blank?
any_requires_rosetta = artifacts_to_test.any? do |artifact|
artifact = T.cast(artifact, T.any(Artifact::App, Artifact::Binary))
path = tmpdir/artifact.source.relative_path_from(cask.staged_path)
result = case artifact
@ -665,7 +704,7 @@ module Cask
end
# binary stanza can contain shell scripts, so we just continue if lipo fails.
next unless result.success?
next false unless result.success?
odebug "Architectures: #{result.merged_output}"
@ -675,17 +714,17 @@ module Cask
next
end
supports_arm = result.merged_output.include?("arm64")
mentions_rosetta = cask.caveats.include?("requires Rosetta 2")
requires_intel = cask.depends_on.arch&.any? { |arch| arch[:type] == :intel }
result.merged_output.exclude?("arm64") && result.merged_output.include?("x86_64")
end
if supports_arm && mentions_rosetta
add_error "Artifacts do not require Rosetta 2 but the caveats say otherwise!",
location: url.location
elsif !supports_arm && !mentions_rosetta && !requires_intel
add_error "Artifacts require Rosetta 2 but this is not indicated by the caveats!",
if any_requires_rosetta
if !mentions_rosetta && !requires_intel
add_error "At least one artifact requires Rosetta 2 but this is not indicated by the caveats!",
location: url.location
end
elsif mentions_rosetta
add_error "No artifacts require Rosetta 2 but the caveats say otherwise!",
location: url.location
end
end
end
@ -896,6 +935,20 @@ module Cask
add_error error, location: url.location if error
end
sig { void }
def audit_forgejo_prerelease_version
return if (url = cask.url).nil?
odebug "Auditing Forgejo prerelease"
user, repo = get_repo_data(%r{https?://codeberg\.org/([^/]+)/([^/]+)/?.*}) if online?
return if user.nil? || repo.nil?
tag = SharedAudits.forgejo_tag_from_url(url.to_s)
tag ||= cask.version
error = SharedAudits.forgejo_release(user, repo, tag, cask:)
add_error error, location: url.location if error
end
sig { void }
def audit_github_repository_archived
# Deprecated/disabled casks may have an archived repository.
@ -928,6 +981,23 @@ module Cask
add_error "GitLab repo is archived", location: url.location if metadata["archived"]
end
sig { void }
def audit_forgejo_repository_archived
return if cask.deprecated? || cask.disabled?
return if (url = cask.url).nil?
user, repo = get_repo_data(%r{https?://codeberg\.org/([^/]+)/([^/]+)/?.*}) if online?
return if user.nil? || repo.nil?
metadata = SharedAudits.forgejo_repo_data(user, repo)
return if metadata.nil?
return unless metadata["archived"]
add_error "Forgejo repository is archived since #{metadata["archived_at"]}",
location: url.location
end
sig { void }
def audit_github_repository
return unless new_cask?
@ -970,6 +1040,20 @@ module Cask
add_error error, location: url.location if error
end
sig { void }
def audit_forgejo_repository
return unless new_cask?
return if (url = cask.url).nil?
user, repo = get_repo_data(%r{https?://codeberg\.org/([^/]+)/([^/]+)/?.*})
return if user.nil? || repo.nil?
odebug "Auditing Forgejo repo"
error = SharedAudits.forgejo(user, repo)
add_error error, location: url.location if error
end
sig { void }
def audit_denylist
return unless cask.tap

View File

@ -60,13 +60,14 @@ module Cask
source: T.nilable(String),
tap: T.nilable(Tap),
loaded_from_api: T::Boolean,
api_source: T.nilable(T::Hash[String, T.untyped]),
config: T.nilable(Config),
allow_reassignment: T::Boolean,
loader: T.nilable(CaskLoader::ILoader),
block: T.nilable(T.proc.bind(DSL).void),
).void
}
def initialize(token, sourcefile_path: nil, source: nil, tap: nil, loaded_from_api: false,
def initialize(token, sourcefile_path: nil, source: nil, tap: nil, loaded_from_api: false, api_source: nil,
config: nil, allow_reassignment: false, loader: nil, &block)
@token = token
@sourcefile_path = sourcefile_path
@ -74,6 +75,7 @@ module Cask
@tap = tap
@allow_reassignment = allow_reassignment
@loaded_from_api = loaded_from_api
@api_source = api_source
@loader = loader
# Sorbet has trouble with bound procs assigned to instance variables:
# https://github.com/sorbet/sorbet/issues/6843
@ -91,6 +93,9 @@ module Cask
sig { returns(T::Boolean) }
def loaded_from_api? = @loaded_from_api
sig { returns(T.nilable(T::Hash[String, T.untyped])) }
attr_reader :api_source
# An old name for the cask.
sig { returns(T::Array[String]) }
def old_tokens
@ -407,8 +412,8 @@ module Cask
private_constant :HASH_KEYS_TO_SKIP
def to_hash_with_variations
if loaded_from_api? && !Homebrew::EnvConfig.no_install_from_api?
return api_to_local_hash(Homebrew::API::Cask.all_casks[token].dup)
if loaded_from_api? && (json_cask = api_source) && !Homebrew::EnvConfig.no_install_from_api?
return api_to_local_hash(json_cask.dup)
end
hash = to_h

View File

@ -18,6 +18,7 @@ module Cask
module ILoader
extend T::Helpers
interface!
sig { abstract.params(config: T.nilable(Config)).returns(Cask) }
@ -28,6 +29,7 @@ module Cask
class AbstractContentLoader
include ILoader
extend T::Helpers
abstract!
sig { returns(String) }
@ -306,8 +308,8 @@ module Cask
return if Homebrew::EnvConfig.no_install_from_api?
return unless ref.is_a?(String)
return unless (token = ref[HOMEBREW_DEFAULT_TAP_CASK_REGEX, :token])
if !Homebrew::API::Cask.all_casks.key?(token) &&
!Homebrew::API::Cask.all_renames.key?(token)
if Homebrew::API.cask_tokens.exclude?(token) &&
!Homebrew::API.cask_renames.key?(token)
return
end
@ -336,6 +338,7 @@ module Cask
cask_options = {
loaded_from_api: true,
api_source: json_cask,
sourcefile_path: @sourcefile_path,
source: JSON.pretty_generate(json_cask),
config:,

View File

@ -19,6 +19,7 @@ require "cask/dsl/container"
require "cask/dsl/depends_on"
require "cask/dsl/postflight"
require "cask/dsl/preflight"
require "cask/dsl/rename"
require "cask/dsl/uninstall_postflight"
require "cask/dsl/uninstall_preflight"
require "cask/dsl/version"
@ -81,6 +82,7 @@ module Cask
:language,
:name,
:os,
:rename,
:sha256,
:staged_path,
:url,
@ -162,6 +164,7 @@ module Cask
@on_system_block_min_os = T.let(nil, T.nilable(MacOSVersion))
@os = T.let(nil, T.nilable(String))
@os_set_in_block = T.let(false, T::Boolean)
@rename = T.let([], T::Array[DSL::Rename])
@sha256 = T.let(nil, T.nilable(T.any(Checksum, Symbol)))
@sha256_set_in_block = T.let(false, T::Boolean)
@staged_path = T.let(nil, T.nilable(Pathname))
@ -343,6 +346,28 @@ module Cask
end
end
# Renames files after extraction.
#
# This is useful when the downloaded file has unpredictable names
# that need to be normalized for proper artifact installation.
#
# ### Example
#
# ```ruby
# rename "RØDECaster App*.pkg", "RØDECaster App.pkg"
# ```
#
# @api public
sig {
params(from: String,
to: String).returns(T::Array[DSL::Rename])
}
def rename(from = T.unsafe(nil), to = T.unsafe(nil))
return @rename if from.nil?
@rename << DSL::Rename.new(T.must(from), T.must(to))
end
# Sets the cask's version.
#
# ### Example
@ -600,6 +625,9 @@ module Cask
raise ArgumentError, "more than one of replacement, replacement_formula and/or replacement_cask specified!"
end
# odeprecate: remove this remapping when the :unsigned reason is removed
because = :fails_gatekeeper_check if because == :unsigned
if replacement
odeprecated(
"deprecate!(:replacement)",
@ -626,6 +654,9 @@ module Cask
raise ArgumentError, "more than one of replacement, replacement_formula and/or replacement_cask specified!"
end
# odeprecate: remove this remapping when the :unsigned reason is removed
because = :fails_gatekeeper_check if because == :unsigned
if replacement
odeprecated(
"disable!(:replacement)",

View File

@ -0,0 +1,52 @@
# typed: strict
# frozen_string_literal: true
module Cask
class DSL
# Class corresponding to the `rename` stanza.
class Rename
sig { returns(String) }
attr_reader :from, :to
sig { params(from: String, to: String).void }
def initialize(from, to)
@from = from
@to = to
end
sig { params(staged_path: Pathname).void }
def perform_rename(staged_path)
return unless staged_path.exist?
# Find files matching the glob pattern
matching_files = if @from.include?("*")
staged_path.glob(@from)
else
[staged_path.join(@from)].select(&:exist?)
end
return if matching_files.empty?
# Rename the first matching file to the target path
source_file = matching_files.first
return if source_file.nil?
target_file = staged_path.join(@to)
# Ensure target directory exists
target_file.dirname.mkpath
# Perform the rename
source_file.rename(target_file.to_s) if source_file.exist?
end
sig { returns(T::Hash[Symbol, String]) }
def pairs
{ from:, to: }
end
sig { returns(String) }
def to_s = pairs.inspect
end
end
end

View File

@ -36,6 +36,8 @@ module Cask
def self.info(cask, args:)
puts get_info(cask)
return unless cask.tap.core_cask_tap?
require "utils/analytics"
::Utils::Analytics.cask_output(cask, args:)
end

View File

@ -125,6 +125,7 @@ module Cask
Caskroom.ensure_caskroom_exists
extract_primary_container
process_rename_operations
save_caskfile
rescue => e
purge_versioned_files
@ -292,6 +293,19 @@ on_request: true)
Quarantine.propagate(from: primary_container.path, to:)
end
sig { params(target_dir: T.nilable(Pathname)).void }
def process_rename_operations(target_dir: nil)
return if @cask.rename.empty?
working_dir = target_dir || @cask.staged_path
odebug "Processing rename operations in #{working_dir}"
@cask.rename.each do |rename_operation|
odebug "Renaming #{rename_operation.from} to #{rename_operation.to}"
rename_operation.perform_rename(working_dir)
end
end
sig { params(predecessor: T.nilable(Cask)).void }
def install_artifacts(predecessor: nil)
already_installed_artifacts = []

View File

@ -40,11 +40,12 @@ module Cask
end
private_class_method :swift_target_args
sig { returns(Symbol) }
sig { returns([Symbol, T.nilable(String)]) }
def self.check_quarantine_support
odebug "Checking quarantine support"
if xattr.nil? || !system_command(xattr, args: ["-h"], print_stderr: false).success?
check_output = nil
status = if xattr.nil? || !system_command(xattr, args: ["-h"], print_stderr: false).success?
odebug "There's no working version of `xattr` on this system."
:xattr_broken
elsif swift.nil?
@ -55,21 +56,41 @@ module Cask
args: [*swift_target_args, QUARANTINE_SCRIPT],
print_stderr: false)
case api_check.exit_status
exit_status = api_check.exit_status
check_output = api_check.merged_output.to_s.strip
error_output = api_check.stderr.to_s.strip
case exit_status
when 2
odebug "Quarantine is available."
:quarantine_available
when 1
# Swift script ran but failed (likely due to CLT issues)
odebug "Swift quarantine script failed: #{error_output}"
if error_output.include?("does not exist") || error_output.include?("No such file")
:swift_broken_clt
elsif error_output.include?("compiler") || error_output.include?("SDK")
:swift_compilation_failed
else
:swift_runtime_error
end
when 127
# Command not found or execution failed
odebug "Swift execution failed with exit status 127"
:swift_not_executable
else
odebug "Unknown support status"
:unknown
odebug "Swift returned unexpected exit status: #{exit_status}"
:swift_unexpected_error
end
end
[status, check_output]
end
sig { returns(T::Boolean) }
def self.available?
@status ||= check_quarantine_support
@quarantine_support ||= check_quarantine_support
@status == :quarantine_available
@quarantine_support[0] == :quarantine_available
end
def self.detect(file)

View File

@ -25,7 +25,7 @@ module Cask
full_paths = remove_nonexistent(paths)
return if full_paths.empty?
ohai "Changing ownership of paths required by #{@cask} with sudo; the password may be necessary."
ohai "Changing ownership of paths required by #{@cask} with `sudo` (which may request your password)..."
@command.run!("/usr/sbin/chown", args: ["-R", "--", "#{user}:#{group}", *full_paths],
sudo: true)
end

View File

@ -40,6 +40,7 @@ module Cask
attr_reader :tag, :branch, :revision, :only_path, :verified
extend Forwardable
def_delegators :uri, :path, :scheme, :to_s
# Creates a `url` stanza.

View File

@ -171,7 +171,7 @@ module Homebrew
stable = formula.stable
if resource_name == "patch"
patch_hashes = stable&.patches&.filter_map { _1.resource.version if _1.external? }
patch_hashes = stable&.patches&.filter_map { T.cast(_1, ExternalPatch).resource.version if _1.external? }
return true unless patch_hashes&.include?(Checksum.new(version.to_s))
elsif resource_name && stable && (resource_version = stable.resources[resource_name]&.version)
return true if resource_version != version

View File

@ -207,13 +207,13 @@ module Homebrew
if formula_path.exist? ||
(!Homebrew::EnvConfig.no_install_from_api? &&
!CoreTap.instance.installed? &&
Homebrew::API::Formula.all_formulae.key?(path.basename.to_s))
Homebrew::API.formula_names.include?(path.basename.to_s))
paths << formula_path
end
if cask_path.exist? ||
(!Homebrew::EnvConfig.no_install_from_api? &&
!CoreCaskTap.instance.installed? &&
Homebrew::API::Cask.all_casks.key?(path.basename.to_s))
Homebrew::API.cask_tokens.include?(path.basename.to_s))
paths << cask_path
end

View File

@ -10,6 +10,7 @@ module Homebrew
module Cmd
class Deps < AbstractCommand
include DependenciesHelpers
cmd_args do
description <<~EOS
Show dependencies for <formula>. When given multiple formula arguments,
@ -87,8 +88,8 @@ module Homebrew
sig { override.void }
def run
raise UsageError, "`brew deps --os=all` is not supported" if args.os == "all"
raise UsageError, "`brew deps --arch=all` is not supported" if args.arch == "all"
raise UsageError, "`brew deps --os=all` is not supported." if args.os == "all"
raise UsageError, "`brew deps --arch=all` is not supported." if args.arch == "all"
os, arch = T.must(args.os_arch_combinations.first)
eval_all = args.eval_all?

View File

@ -11,6 +11,7 @@ module Homebrew
module Cmd
class FetchCmd < AbstractCommand
include Fetch
FETCH_MAX_TRIES = 5
cmd_args do

View File

@ -12,6 +12,7 @@ module Homebrew
module Cmd
class GistLogs < AbstractCommand
include Install
cmd_args do
description <<~EOS
Upload logs for a failed build of <formula> to a new Gist. Presents an

View File

@ -159,9 +159,9 @@ module Homebrew
case obj
when Formula
Utils::Analytics.formula_output(obj, args:)
Utils::Analytics.formula_output(obj, args:) if obj.core_formula?
when Cask::Cask
Utils::Analytics.cask_output(obj, args:)
Utils::Analytics.cask_output(obj, args:) if obj.tap.core_cask_tap?
when FormulaOrCaskUnavailableError
Utils::Analytics.output(filter: obj.name, args:)
else
@ -380,6 +380,8 @@ module Homebrew
ohai "Caveats", caveats_string
end
return unless formula.core_formula?
Utils::Analytics.formula_output(formula, args:)
end

View File

@ -181,7 +181,7 @@ module Homebrew
# `build.rb`. Instead, `hide_from_man_page` and don't do anything with
# this argument here.
# This odisabled should stick around indefinitely.
odisabled "brew install --env", "`env :std` in specific formula files"
odisabled "`brew install --env`", "`env :std` in specific formula files"
end
args.named.each do |name|

View File

@ -25,27 +25,27 @@ module Homebrew
If `sudo` is passed, operate on `/Library/LaunchDaemons` or `/usr/lib/systemd/system` (started at boot).
Otherwise, operate on `~/Library/LaunchAgents` or `~/.config/systemd/user` (started at login).
[`sudo`] `brew services` [`list`] (`--json`) (`--debug`):
[`sudo`] `brew services` [`list`] [`--json`] [`--debug`]:
List information about all managed services for the current user (or root).
Provides more output from Homebrew and `launchctl`(1) or `systemctl`(1) if run with `--debug`.
[`sudo`] `brew services info` (<formula>|`--all`|`--json`):
[`sudo`] `brew services info` (<formula>|`--all`) [`--json`]:
List all managed services for the current user (or root).
[`sudo`] `brew services run` (<formula>|`--all`|`--file=`):
[`sudo`] `brew services run` (<formula>|`--all`) [`--file=`]:
Run the service <formula> without registering to launch at login (or boot).
[`sudo`] `brew services start` (<formula>|`--all`|`--file=`):
[`sudo`] `brew services start` (<formula>|`--all`) [`--file=`]:
Start the service <formula> immediately and register it to launch at login (or boot).
[`sudo`] `brew services stop` (`--keep`) (`--no-wait`|`--max-wait=`) (<formula>|`--all`):
[`sudo`] `brew services stop` [`--keep`] [`--no-wait`|`--max-wait=`] (<formula>|`--all`):
Stop the service <formula> immediately and unregister it from launching at login (or boot),
unless `--keep` is specified.
[`sudo`] `brew services kill` (<formula>|`--all`):
Stop the service <formula> immediately but keep it registered to launch at login (or boot).
[`sudo`] `brew services restart` (<formula>|`--all`|`--file=`):
[`sudo`] `brew services restart` (<formula>|`--all`) [`--file=`]:
Stop (if necessary) and start the service <formula> immediately and register it to launch at login (or boot).
[`sudo`] `brew services cleanup`:
@ -58,15 +58,16 @@ module Homebrew
flag "--max-wait=",
description: "Wait at most this many seconds for `stop` to finish stopping a service. " \
"Defaults to 60. Set this to zero (0) seconds to wait indefinitely."
switch "--all",
description: "Run <subcommand> on all services."
switch "--json",
description: "Output as JSON."
switch "--no-wait",
description: "Don't wait for `stop` to finish stopping the service."
switch "--keep",
description: "When stopped, don't unregister the service from launching at login (or boot)."
switch "--all",
description: "Run <subcommand> on all services."
switch "--json",
description: "Output as JSON."
conflicts "--all", "--file"
conflicts "--max-wait=", "--no-wait"
named_args %w[list info run start stop kill restart cleanup]
@ -112,7 +113,7 @@ module Homebrew
]
if no_named_formula_commands.include?(subcommand)
raise UsageError, "The `#{subcommand}` subcommand does not accept a formula argument!" if formulae.present?
raise UsageError, "The `#{subcommand}` subcommand does not accept the --all argument!" if args.all?
raise UsageError, "The `#{subcommand}` subcommand does not accept the `--all` argument!" if args.all?
end
if args.file
@ -122,22 +123,22 @@ module Homebrew
*Homebrew::Services::Commands::Restart::TRIGGERS,
]
if file_commands.exclude?(subcommand)
raise UsageError, "The `#{subcommand}` subcommand does not accept the --file= argument!"
elsif args.all?
raise UsageError,
"The `#{subcommand}` subcommand does not accept the --all and --file= arguments at the same time!"
raise UsageError, "The `#{subcommand}` subcommand does not accept the `--file=` argument!"
end
end
unless Homebrew::Services::Commands::Stop::TRIGGERS.include?(subcommand)
raise UsageError, "The `#{subcommand}` subcommand does not accept the --keep argument!" if args.keep?
raise UsageError, "The `#{subcommand}` subcommand does not accept the --no-wait argument!" if args.no_wait?
raise UsageError, "The `#{subcommand}` subcommand does not accept the `--keep` argument!" if args.keep?
if args.no_wait?
raise UsageError, "The `#{subcommand}` subcommand does not accept the `--no-wait` argument!"
end
if args.max_wait
raise UsageError, "The `#{subcommand}` subcommand does not accept the --max-wait= argument!"
raise UsageError, "The `#{subcommand}` subcommand does not accept the `--max-wait=` argument!"
end
end
opoo "The --all argument overrides provided formula argument!" if formulae.present? && args.all?
opoo "The `--all` argument overrides provided formula argument!" if formulae.present? && args.all?
targets = if args.all?
if subcommand == "start"

View File

@ -134,10 +134,7 @@ module Homebrew
end
# Check if we can parse the JSON and do any Ruby-side follow-up.
unless Homebrew::EnvConfig.no_install_from_api?
Homebrew::API::Formula.write_names_and_aliases
Homebrew::API::Cask.write_names
end
Homebrew::API.write_names_and_aliases unless Homebrew::EnvConfig.no_install_from_api?
Homebrew.failed = true if ENV["HOMEBREW_UPDATE_FAILED"]
return if Homebrew::EnvConfig.disable_load_formula?

View File

@ -126,7 +126,7 @@ module Homebrew
sig { override.void }
def run
if args.build_from_source? && args.named.empty?
raise ArgumentError, "--build-from-source requires at least one formula"
raise ArgumentError, "`--build-from-source` requires at least one formula"
end
formulae, casks = args.named.to_resolved_formulae_to_casks

View File

@ -40,7 +40,7 @@ class CompilerFailure
# Non-Apple compilers are in the format fails_with compiler => version
if spec.is_a?(Hash)
compiler, major_version = spec.first
raise ArgumentError, "The hash `fails_with` syntax only supports GCC" if compiler != :gcc
raise ArgumentError, "The `fails_with` hash syntax only supports GCC" if compiler != :gcc
type = compiler
# so fails_with :gcc => '7' simply marks all 7 releases incompatible

View File

@ -3,7 +3,7 @@
#
# - For changes to a command under `COMMANDS` or `DEVELOPER COMMANDS` sections):
# - Find the source file in `Library/Homebrew/[dev-]cmd/<command>.{rb,sh}`.
# - For `.rb` files, edit the `<command>_args` method.
# - For `.rb` files, edit the `cmd_args` block.
# - For `.sh` files, edit the top comment, being sure to use the line prefix
# `#:` for the comments to be recognized as documentation. If in doubt,
# compare with already documented commands.

View File

@ -3,7 +3,7 @@
#
# - For changes to a command under `COMMANDS` or `DEVELOPER COMMANDS` sections):
# - Find the source file in `Library/Homebrew/[dev-]cmd/<command>.{rb,sh}`.
# - For `.rb` files, edit the `<command>_args` method.
# - For `.rb` files, edit the `cmd_args` block.
# - For `.sh` files, edit the top comment, being sure to use the line prefix
# `#:` for the comments to be recognized as documentation. If in doubt,
# compare with already documented commands.

View File

@ -3,7 +3,7 @@
#
# - For changes to a command under `COMMANDS` or `DEVELOPER COMMANDS` sections):
# - Find the source file in `Library/Homebrew/[dev-]cmd/<command>.{rb,sh}`.
# - For `.rb` files, edit the `<command>_args` method.
# - For `.rb` files, edit the `cmd_args` block.
# - For `.sh` files, edit the top comment, being sure to use the line prefix
# `#:` for the comments to be recognized as documentation. If in doubt,
# compare with already documented commands.

View File

@ -1,11 +1,11 @@
{
"licenseListVersion": "5dbff5c",
"licenseListVersion": "3.27.0",
"exceptions": [
{
"reference": "https://spdx.org/licenses/389-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/389-exception.json",
"referenceNumber": 33,
"referenceNumber": 36,
"name": "389 Directory Server Exception",
"licenseExceptionId": "389-exception",
"seeAlso": [
@ -17,7 +17,7 @@
"reference": "https://spdx.org/licenses/Asterisk-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/Asterisk-exception.json",
"referenceNumber": 52,
"referenceNumber": 16,
"name": "Asterisk exception",
"licenseExceptionId": "Asterisk-exception",
"seeAlso": [
@ -29,7 +29,7 @@
"reference": "https://spdx.org/licenses/Asterisk-linking-protocols-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/Asterisk-linking-protocols-exception.json",
"referenceNumber": 75,
"referenceNumber": 22,
"name": "Asterisk linking protocols exception",
"licenseExceptionId": "Asterisk-linking-protocols-exception",
"seeAlso": [
@ -40,7 +40,7 @@
"reference": "https://spdx.org/licenses/Autoconf-exception-2.0.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/Autoconf-exception-2.0.json",
"referenceNumber": 60,
"referenceNumber": 2,
"name": "Autoconf exception 2.0",
"licenseExceptionId": "Autoconf-exception-2.0",
"seeAlso": [
@ -52,7 +52,7 @@
"reference": "https://spdx.org/licenses/Autoconf-exception-3.0.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/Autoconf-exception-3.0.json",
"referenceNumber": 1,
"referenceNumber": 51,
"name": "Autoconf exception 3.0",
"licenseExceptionId": "Autoconf-exception-3.0",
"seeAlso": [
@ -63,7 +63,7 @@
"reference": "https://spdx.org/licenses/Autoconf-exception-generic.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/Autoconf-exception-generic.json",
"referenceNumber": 59,
"referenceNumber": 73,
"name": "Autoconf generic exception",
"licenseExceptionId": "Autoconf-exception-generic",
"seeAlso": [
@ -77,7 +77,7 @@
"reference": "https://spdx.org/licenses/Autoconf-exception-generic-3.0.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/Autoconf-exception-generic-3.0.json",
"referenceNumber": 48,
"referenceNumber": 40,
"name": "Autoconf generic exception for GPL-3.0",
"licenseExceptionId": "Autoconf-exception-generic-3.0",
"seeAlso": [
@ -88,7 +88,7 @@
"reference": "https://spdx.org/licenses/Autoconf-exception-macro.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/Autoconf-exception-macro.json",
"referenceNumber": 15,
"referenceNumber": 63,
"name": "Autoconf macro exception",
"licenseExceptionId": "Autoconf-exception-macro",
"seeAlso": [
@ -101,7 +101,7 @@
"reference": "https://spdx.org/licenses/Bison-exception-1.24.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/Bison-exception-1.24.json",
"referenceNumber": 69,
"referenceNumber": 56,
"name": "Bison exception 1.24",
"licenseExceptionId": "Bison-exception-1.24",
"seeAlso": [
@ -112,7 +112,7 @@
"reference": "https://spdx.org/licenses/Bison-exception-2.2.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/Bison-exception-2.2.json",
"referenceNumber": 19,
"referenceNumber": 28,
"name": "Bison exception 2.2",
"licenseExceptionId": "Bison-exception-2.2",
"seeAlso": [
@ -123,7 +123,7 @@
"reference": "https://spdx.org/licenses/Bootloader-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/Bootloader-exception.json",
"referenceNumber": 68,
"referenceNumber": 65,
"name": "Bootloader Distribution Exception",
"licenseExceptionId": "Bootloader-exception",
"seeAlso": [
@ -134,7 +134,7 @@
"reference": "https://spdx.org/licenses/CGAL-linking-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/CGAL-linking-exception.json",
"referenceNumber": 34,
"referenceNumber": 74,
"name": "CGAL Linking Exception",
"licenseExceptionId": "CGAL-linking-exception",
"seeAlso": [
@ -146,7 +146,7 @@
"reference": "https://spdx.org/licenses/Classpath-exception-2.0.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/Classpath-exception-2.0.json",
"referenceNumber": 66,
"referenceNumber": 67,
"name": "Classpath exception 2.0",
"licenseExceptionId": "Classpath-exception-2.0",
"seeAlso": [
@ -158,7 +158,7 @@
"reference": "https://spdx.org/licenses/CLISP-exception-2.0.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/CLISP-exception-2.0.json",
"referenceNumber": 4,
"referenceNumber": 10,
"name": "CLISP exception 2.0",
"licenseExceptionId": "CLISP-exception-2.0",
"seeAlso": [
@ -169,7 +169,7 @@
"reference": "https://spdx.org/licenses/cryptsetup-OpenSSL-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/cryptsetup-OpenSSL-exception.json",
"referenceNumber": 55,
"referenceNumber": 46,
"name": "cryptsetup OpenSSL exception",
"licenseExceptionId": "cryptsetup-OpenSSL-exception",
"seeAlso": [
@ -185,7 +185,7 @@
"reference": "https://spdx.org/licenses/Digia-Qt-LGPL-exception-1.1.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/Digia-Qt-LGPL-exception-1.1.json",
"referenceNumber": 2,
"referenceNumber": 48,
"name": "Digia Qt LGPL Exception version 1.1",
"licenseExceptionId": "Digia-Qt-LGPL-exception-1.1",
"seeAlso": [
@ -196,7 +196,7 @@
"reference": "https://spdx.org/licenses/DigiRule-FOSS-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/DigiRule-FOSS-exception.json",
"referenceNumber": 25,
"referenceNumber": 53,
"name": "DigiRule FOSS License Exception",
"licenseExceptionId": "DigiRule-FOSS-exception",
"seeAlso": [
@ -207,7 +207,7 @@
"reference": "https://spdx.org/licenses/eCos-exception-2.0.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/eCos-exception-2.0.json",
"referenceNumber": 31,
"referenceNumber": 61,
"name": "eCos exception 2.0",
"licenseExceptionId": "eCos-exception-2.0",
"seeAlso": [
@ -218,7 +218,7 @@
"reference": "https://spdx.org/licenses/erlang-otp-linking-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/erlang-otp-linking-exception.json",
"referenceNumber": 78,
"referenceNumber": 13,
"name": "Erlang/OTP Linking Exception",
"licenseExceptionId": "erlang-otp-linking-exception",
"seeAlso": [
@ -231,7 +231,7 @@
"reference": "https://spdx.org/licenses/Fawkes-Runtime-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/Fawkes-Runtime-exception.json",
"referenceNumber": 47,
"referenceNumber": 21,
"name": "Fawkes Runtime Exception",
"licenseExceptionId": "Fawkes-Runtime-exception",
"seeAlso": [
@ -242,7 +242,7 @@
"reference": "https://spdx.org/licenses/FLTK-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/FLTK-exception.json",
"referenceNumber": 3,
"referenceNumber": 66,
"name": "FLTK exception",
"licenseExceptionId": "FLTK-exception",
"seeAlso": [
@ -253,7 +253,7 @@
"reference": "https://spdx.org/licenses/fmt-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/fmt-exception.json",
"referenceNumber": 16,
"referenceNumber": 77,
"name": "fmt exception",
"licenseExceptionId": "fmt-exception",
"seeAlso": [
@ -265,7 +265,7 @@
"reference": "https://spdx.org/licenses/Font-exception-2.0.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/Font-exception-2.0.json",
"referenceNumber": 32,
"referenceNumber": 35,
"name": "Font exception 2.0",
"licenseExceptionId": "Font-exception-2.0",
"seeAlso": [
@ -276,7 +276,7 @@
"reference": "https://spdx.org/licenses/freertos-exception-2.0.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/freertos-exception-2.0.json",
"referenceNumber": 44,
"referenceNumber": 23,
"name": "FreeRTOS Exception 2.0",
"licenseExceptionId": "freertos-exception-2.0",
"seeAlso": [
@ -287,7 +287,7 @@
"reference": "https://spdx.org/licenses/GCC-exception-2.0.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/GCC-exception-2.0.json",
"referenceNumber": 6,
"referenceNumber": 14,
"name": "GCC Runtime Library exception 2.0",
"licenseExceptionId": "GCC-exception-2.0",
"seeAlso": [
@ -299,7 +299,7 @@
"reference": "https://spdx.org/licenses/GCC-exception-2.0-note.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/GCC-exception-2.0-note.json",
"referenceNumber": 11,
"referenceNumber": 20,
"name": "GCC Runtime Library exception 2.0 - note variant",
"licenseExceptionId": "GCC-exception-2.0-note",
"seeAlso": [
@ -310,7 +310,7 @@
"reference": "https://spdx.org/licenses/GCC-exception-3.1.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/GCC-exception-3.1.json",
"referenceNumber": 77,
"referenceNumber": 25,
"name": "GCC Runtime Library exception 3.1",
"licenseExceptionId": "GCC-exception-3.1",
"seeAlso": [
@ -321,7 +321,7 @@
"reference": "https://spdx.org/licenses/Gmsh-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/Gmsh-exception.json",
"referenceNumber": 35,
"referenceNumber": 26,
"name": "Gmsh exception",
"licenseExceptionId": "Gmsh-exception",
"seeAlso": [
@ -332,7 +332,7 @@
"reference": "https://spdx.org/licenses/GNAT-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/GNAT-exception.json",
"referenceNumber": 18,
"referenceNumber": 69,
"name": "GNAT exception",
"licenseExceptionId": "GNAT-exception",
"seeAlso": [
@ -343,7 +343,7 @@
"reference": "https://spdx.org/licenses/GNOME-examples-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/GNOME-examples-exception.json",
"referenceNumber": 49,
"referenceNumber": 76,
"name": "GNOME examples exception",
"licenseExceptionId": "GNOME-examples-exception",
"seeAlso": [
@ -355,19 +355,18 @@
"reference": "https://spdx.org/licenses/GNU-compiler-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/GNU-compiler-exception.json",
"referenceNumber": 41,
"referenceNumber": 58,
"name": "GNU Compiler Exception",
"licenseExceptionId": "GNU-compiler-exception",
"seeAlso": [
"https://sourceware.org/git?p\u003dbinutils-gdb.git;a\u003dblob;f\u003dlibiberty/unlink-if-ordinary.c;h\u003de49f2f2f67bfdb10d6b2bd579b0e01cad0fd708e;hb\u003dHEAD#l19",
"https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/powerpc/lib/crtsavres.S?h\u003dv6.16-rc6#n34"
"https://sourceware.org/git?p\u003dbinutils-gdb.git;a\u003dblob;f\u003dlibiberty/unlink-if-ordinary.c;h\u003de49f2f2f67bfdb10d6b2bd579b0e01cad0fd708e;hb\u003dHEAD#l19"
]
},
{
"reference": "https://spdx.org/licenses/gnu-javamail-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/gnu-javamail-exception.json",
"referenceNumber": 72,
"referenceNumber": 64,
"name": "GNU JavaMail exception",
"licenseExceptionId": "gnu-javamail-exception",
"seeAlso": [
@ -378,7 +377,7 @@
"reference": "https://spdx.org/licenses/GPL-3.0-389-ds-base-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/GPL-3.0-389-ds-base-exception.json",
"referenceNumber": 67,
"referenceNumber": 18,
"name": "GPL-3.0 389 DS Base Exception",
"licenseExceptionId": "GPL-3.0-389-ds-base-exception",
"seeAlso": []
@ -387,7 +386,7 @@
"reference": "https://spdx.org/licenses/GPL-3.0-interface-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/GPL-3.0-interface-exception.json",
"referenceNumber": 76,
"referenceNumber": 78,
"name": "GPL-3.0 Interface Exception",
"licenseExceptionId": "GPL-3.0-interface-exception",
"seeAlso": [
@ -398,7 +397,7 @@
"reference": "https://spdx.org/licenses/GPL-3.0-linking-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/GPL-3.0-linking-exception.json",
"referenceNumber": 51,
"referenceNumber": 68,
"name": "GPL-3.0 Linking Exception",
"licenseExceptionId": "GPL-3.0-linking-exception",
"seeAlso": [
@ -409,7 +408,7 @@
"reference": "https://spdx.org/licenses/GPL-3.0-linking-source-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/GPL-3.0-linking-source-exception.json",
"referenceNumber": 50,
"referenceNumber": 62,
"name": "GPL-3.0 Linking Exception (with Corresponding Source)",
"licenseExceptionId": "GPL-3.0-linking-source-exception",
"seeAlso": [
@ -421,7 +420,7 @@
"reference": "https://spdx.org/licenses/GPL-CC-1.0.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/GPL-CC-1.0.json",
"referenceNumber": 54,
"referenceNumber": 33,
"name": "GPL Cooperation Commitment 1.0",
"licenseExceptionId": "GPL-CC-1.0",
"seeAlso": [
@ -433,7 +432,7 @@
"reference": "https://spdx.org/licenses/GStreamer-exception-2005.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/GStreamer-exception-2005.json",
"referenceNumber": 7,
"referenceNumber": 4,
"name": "GStreamer Exception (2005)",
"licenseExceptionId": "GStreamer-exception-2005",
"seeAlso": [
@ -444,7 +443,7 @@
"reference": "https://spdx.org/licenses/GStreamer-exception-2008.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/GStreamer-exception-2008.json",
"referenceNumber": 56,
"referenceNumber": 41,
"name": "GStreamer Exception (2008)",
"licenseExceptionId": "GStreamer-exception-2008",
"seeAlso": [
@ -455,7 +454,7 @@
"reference": "https://spdx.org/licenses/harbour-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/harbour-exception.json",
"referenceNumber": 63,
"referenceNumber": 59,
"name": "harbour exception",
"licenseExceptionId": "harbour-exception",
"seeAlso": [
@ -466,7 +465,7 @@
"reference": "https://spdx.org/licenses/i2p-gpl-java-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/i2p-gpl-java-exception.json",
"referenceNumber": 36,
"referenceNumber": 9,
"name": "i2p GPL+Java Exception",
"licenseExceptionId": "i2p-gpl-java-exception",
"seeAlso": [
@ -477,7 +476,7 @@
"reference": "https://spdx.org/licenses/Independent-modules-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/Independent-modules-exception.json",
"referenceNumber": 13,
"referenceNumber": 45,
"name": "Independent Module Linking exception",
"licenseExceptionId": "Independent-modules-exception",
"seeAlso": [
@ -488,7 +487,7 @@
"reference": "https://spdx.org/licenses/KiCad-libraries-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/KiCad-libraries-exception.json",
"referenceNumber": 23,
"referenceNumber": 44,
"name": "KiCad Libraries Exception",
"licenseExceptionId": "KiCad-libraries-exception",
"seeAlso": [
@ -499,7 +498,7 @@
"reference": "https://spdx.org/licenses/LGPL-3.0-linking-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/LGPL-3.0-linking-exception.json",
"referenceNumber": 42,
"referenceNumber": 32,
"name": "LGPL-3.0 Linking Exception",
"licenseExceptionId": "LGPL-3.0-linking-exception",
"seeAlso": [
@ -512,7 +511,7 @@
"reference": "https://spdx.org/licenses/libpri-OpenH323-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/libpri-OpenH323-exception.json",
"referenceNumber": 70,
"referenceNumber": 19,
"name": "libpri OpenH323 exception",
"licenseExceptionId": "libpri-OpenH323-exception",
"seeAlso": [
@ -523,7 +522,7 @@
"reference": "https://spdx.org/licenses/Libtool-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/Libtool-exception.json",
"referenceNumber": 22,
"referenceNumber": 71,
"name": "Libtool Exception",
"licenseExceptionId": "Libtool-exception",
"seeAlso": [
@ -535,7 +534,7 @@
"reference": "https://spdx.org/licenses/Linux-syscall-note.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/Linux-syscall-note.json",
"referenceNumber": 9,
"referenceNumber": 37,
"name": "Linux Syscall Note",
"licenseExceptionId": "Linux-syscall-note",
"seeAlso": [
@ -546,7 +545,7 @@
"reference": "https://spdx.org/licenses/LLGPL.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/LLGPL.json",
"referenceNumber": 14,
"referenceNumber": 24,
"name": "LLGPL Preamble",
"licenseExceptionId": "LLGPL",
"seeAlso": [
@ -557,7 +556,7 @@
"reference": "https://spdx.org/licenses/LLVM-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/LLVM-exception.json",
"referenceNumber": 28,
"referenceNumber": 72,
"name": "LLVM Exception",
"licenseExceptionId": "LLVM-exception",
"seeAlso": [
@ -568,7 +567,7 @@
"reference": "https://spdx.org/licenses/LZMA-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/LZMA-exception.json",
"referenceNumber": 45,
"referenceNumber": 30,
"name": "LZMA exception",
"licenseExceptionId": "LZMA-exception",
"seeAlso": [
@ -579,7 +578,7 @@
"reference": "https://spdx.org/licenses/mif-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/mif-exception.json",
"referenceNumber": 65,
"referenceNumber": 29,
"name": "Macros and Inline Functions Exception",
"licenseExceptionId": "mif-exception",
"seeAlso": [
@ -592,7 +591,7 @@
"reference": "https://spdx.org/licenses/mxml-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/mxml-exception.json",
"referenceNumber": 71,
"referenceNumber": 38,
"name": "mxml Exception",
"licenseExceptionId": "mxml-exception",
"seeAlso": [
@ -604,7 +603,7 @@
"reference": "https://spdx.org/licenses/Nokia-Qt-exception-1.1.html",
"isDeprecatedLicenseId": true,
"detailsUrl": "https://spdx.org/licenses/Nokia-Qt-exception-1.1.json",
"referenceNumber": 64,
"referenceNumber": 47,
"name": "Nokia Qt LGPL exception 1.1",
"licenseExceptionId": "Nokia-Qt-exception-1.1",
"seeAlso": [
@ -615,7 +614,7 @@
"reference": "https://spdx.org/licenses/OCaml-LGPL-linking-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/OCaml-LGPL-linking-exception.json",
"referenceNumber": 61,
"referenceNumber": 42,
"name": "OCaml LGPL Linking Exception",
"licenseExceptionId": "OCaml-LGPL-linking-exception",
"seeAlso": [
@ -626,7 +625,7 @@
"reference": "https://spdx.org/licenses/OCCT-exception-1.0.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/OCCT-exception-1.0.json",
"referenceNumber": 21,
"referenceNumber": 17,
"name": "Open CASCADE Exception 1.0",
"licenseExceptionId": "OCCT-exception-1.0",
"seeAlso": [
@ -637,7 +636,7 @@
"reference": "https://spdx.org/licenses/OpenJDK-assembly-exception-1.0.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/OpenJDK-assembly-exception-1.0.json",
"referenceNumber": 40,
"referenceNumber": 11,
"name": "OpenJDK Assembly exception 1.0",
"licenseExceptionId": "OpenJDK-assembly-exception-1.0",
"seeAlso": [
@ -648,7 +647,7 @@
"reference": "https://spdx.org/licenses/openvpn-openssl-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/openvpn-openssl-exception.json",
"referenceNumber": 53,
"referenceNumber": 3,
"name": "OpenVPN OpenSSL Exception",
"licenseExceptionId": "openvpn-openssl-exception",
"seeAlso": [
@ -660,7 +659,7 @@
"reference": "https://spdx.org/licenses/PCRE2-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/PCRE2-exception.json",
"referenceNumber": 62,
"referenceNumber": 12,
"name": "PCRE2 exception",
"licenseExceptionId": "PCRE2-exception",
"seeAlso": [
@ -671,7 +670,7 @@
"reference": "https://spdx.org/licenses/polyparse-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/polyparse-exception.json",
"referenceNumber": 27,
"referenceNumber": 54,
"name": "Polyparse Exception",
"licenseExceptionId": "polyparse-exception",
"seeAlso": [
@ -682,7 +681,7 @@
"reference": "https://spdx.org/licenses/PS-or-PDF-font-exception-20170817.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/PS-or-PDF-font-exception-20170817.json",
"referenceNumber": 74,
"referenceNumber": 43,
"name": "PS/PDF font exception (2017-08-17)",
"licenseExceptionId": "PS-or-PDF-font-exception-20170817",
"seeAlso": [
@ -693,7 +692,7 @@
"reference": "https://spdx.org/licenses/QPL-1.0-INRIA-2004-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/QPL-1.0-INRIA-2004-exception.json",
"referenceNumber": 24,
"referenceNumber": 50,
"name": "INRIA QPL 1.0 2004 variant exception",
"licenseExceptionId": "QPL-1.0-INRIA-2004-exception",
"seeAlso": [
@ -705,7 +704,7 @@
"reference": "https://spdx.org/licenses/Qt-GPL-exception-1.0.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/Qt-GPL-exception-1.0.json",
"referenceNumber": 20,
"referenceNumber": 34,
"name": "Qt GPL exception 1.0",
"licenseExceptionId": "Qt-GPL-exception-1.0",
"seeAlso": [
@ -716,7 +715,7 @@
"reference": "https://spdx.org/licenses/Qt-LGPL-exception-1.1.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/Qt-LGPL-exception-1.1.json",
"referenceNumber": 29,
"referenceNumber": 39,
"name": "Qt LGPL exception 1.1",
"licenseExceptionId": "Qt-LGPL-exception-1.1",
"seeAlso": [
@ -727,7 +726,7 @@
"reference": "https://spdx.org/licenses/Qwt-exception-1.0.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/Qwt-exception-1.0.json",
"referenceNumber": 10,
"referenceNumber": 79,
"name": "Qwt exception 1.0",
"licenseExceptionId": "Qwt-exception-1.0",
"seeAlso": [
@ -738,7 +737,7 @@
"reference": "https://spdx.org/licenses/romic-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/romic-exception.json",
"referenceNumber": 79,
"referenceNumber": 6,
"name": "Romic Exception",
"licenseExceptionId": "romic-exception",
"seeAlso": [
@ -754,7 +753,7 @@
"reference": "https://spdx.org/licenses/RRDtool-FLOSS-exception-2.0.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/RRDtool-FLOSS-exception-2.0.json",
"referenceNumber": 57,
"referenceNumber": 7,
"name": "RRDtool FLOSS exception 2.0",
"licenseExceptionId": "RRDtool-FLOSS-exception-2.0",
"seeAlso": [
@ -766,7 +765,7 @@
"reference": "https://spdx.org/licenses/SANE-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/SANE-exception.json",
"referenceNumber": 8,
"referenceNumber": 27,
"name": "SANE Exception",
"licenseExceptionId": "SANE-exception",
"seeAlso": [
@ -779,7 +778,7 @@
"reference": "https://spdx.org/licenses/SHL-2.0.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/SHL-2.0.json",
"referenceNumber": 17,
"referenceNumber": 5,
"name": "Solderpad Hardware License v2.0",
"licenseExceptionId": "SHL-2.0",
"seeAlso": [
@ -790,7 +789,7 @@
"reference": "https://spdx.org/licenses/SHL-2.1.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/SHL-2.1.json",
"referenceNumber": 26,
"referenceNumber": 1,
"name": "Solderpad Hardware License v2.1",
"licenseExceptionId": "SHL-2.1",
"seeAlso": [
@ -801,7 +800,7 @@
"reference": "https://spdx.org/licenses/stunnel-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/stunnel-exception.json",
"referenceNumber": 43,
"referenceNumber": 49,
"name": "stunnel Exception",
"licenseExceptionId": "stunnel-exception",
"seeAlso": [
@ -812,7 +811,7 @@
"reference": "https://spdx.org/licenses/SWI-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/SWI-exception.json",
"referenceNumber": 73,
"referenceNumber": 15,
"name": "SWI exception",
"licenseExceptionId": "SWI-exception",
"seeAlso": [
@ -823,7 +822,7 @@
"reference": "https://spdx.org/licenses/Swift-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/Swift-exception.json",
"referenceNumber": 46,
"referenceNumber": 52,
"name": "Swift Exception",
"licenseExceptionId": "Swift-exception",
"seeAlso": [
@ -835,7 +834,7 @@
"reference": "https://spdx.org/licenses/Texinfo-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/Texinfo-exception.json",
"referenceNumber": 30,
"referenceNumber": 60,
"name": "Texinfo exception",
"licenseExceptionId": "Texinfo-exception",
"seeAlso": [
@ -846,7 +845,7 @@
"reference": "https://spdx.org/licenses/u-boot-exception-2.0.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/u-boot-exception-2.0.json",
"referenceNumber": 5,
"referenceNumber": 8,
"name": "U-Boot exception 2.0",
"licenseExceptionId": "u-boot-exception-2.0",
"seeAlso": [
@ -857,7 +856,7 @@
"reference": "https://spdx.org/licenses/UBDL-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/UBDL-exception.json",
"referenceNumber": 38,
"referenceNumber": 75,
"name": "Unmodified Binary Distribution exception",
"licenseExceptionId": "UBDL-exception",
"seeAlso": [
@ -868,7 +867,7 @@
"reference": "https://spdx.org/licenses/Universal-FOSS-exception-1.0.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/Universal-FOSS-exception-1.0.json",
"referenceNumber": 58,
"referenceNumber": 70,
"name": "Universal FOSS Exception, Version 1.0",
"licenseExceptionId": "Universal-FOSS-exception-1.0",
"seeAlso": [
@ -879,7 +878,7 @@
"reference": "https://spdx.org/licenses/vsftpd-openssl-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/vsftpd-openssl-exception.json",
"referenceNumber": 39,
"referenceNumber": 55,
"name": "vsftpd OpenSSL exception",
"licenseExceptionId": "vsftpd-openssl-exception",
"seeAlso": [
@ -892,7 +891,7 @@
"reference": "https://spdx.org/licenses/WxWindows-exception-3.1.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/WxWindows-exception-3.1.json",
"referenceNumber": 12,
"referenceNumber": 31,
"name": "WxWindows Library Exception 3.1",
"licenseExceptionId": "WxWindows-exception-3.1",
"seeAlso": [
@ -903,7 +902,7 @@
"reference": "https://spdx.org/licenses/x11vnc-openssl-exception.html",
"isDeprecatedLicenseId": false,
"detailsUrl": "https://spdx.org/licenses/x11vnc-openssl-exception.json",
"referenceNumber": 37,
"referenceNumber": 57,
"name": "x11vnc OpenSSL Exception",
"licenseExceptionId": "x11vnc-openssl-exception",
"seeAlso": [
@ -911,5 +910,5 @@
]
}
],
"releaseDate": "2025-07-29T00:00:00Z"
"releaseDate": "2025-07-01T00:00:00Z"
}

File diff suppressed because it is too large Load Diff

View File

@ -5,12 +5,19 @@ require "options"
# Shared functions for classes which can be depended upon.
module Dependable
extend T::Helpers
# `:run` and `:linked` are no longer used but keep them here to avoid their
# misuse in future.
RESERVED_TAGS = [:build, :optional, :recommended, :run, :test, :linked, :implicit].freeze
attr_reader :tags
abstract!
sig { abstract.returns(T::Array[String]) }
def option_names; end
def build?
tags.include? :build
end

View File

@ -80,10 +80,10 @@ class DependencyCollector
parse_spec(spec, Array(tags))
end
sig { params(related_formula_names: T::Array[String]).returns(T.nilable(Dependency)) }
sig { params(related_formula_names: T::Set[String]).returns(T.nilable(Dependency)) }
def gcc_dep_if_needed(related_formula_names); end
sig { params(related_formula_names: T::Array[String]).returns(T.nilable(Dependency)) }
sig { params(related_formula_names: T::Set[String]).returns(T.nilable(Dependency)) }
def glibc_dep_if_needed(related_formula_names); end
def git_dep_if_needed(tags)
@ -169,7 +169,7 @@ class DependencyCollector
when :maximum_macos then MacOSRequirement.new(tags, comparator: "<=")
when :xcode then XcodeRequirement.new(tags)
else
raise ArgumentError, "Unsupported special dependency #{spec.inspect}"
raise ArgumentError, "Unsupported special dependency: #{spec.inspect}"
end
end

View File

@ -27,6 +27,8 @@ module DeprecateDisable
no_longer_available: "is no longer available upstream",
no_longer_meets_criteria: "no longer meets the criteria for acceptable casks",
unmaintained: "is not maintained upstream",
fails_gatekeeper_check: "does not pass the macOS Gatekeeper check",
# odeprecate: remove the unsigned reason in a future release
unsigned: "is unsigned or does not meet signature requirements",
}.freeze, T::Hash[Symbol, String])
@ -67,6 +69,9 @@ module DeprecateDisable
formula_or_cask.disable_reason
end
# odeprecate: remove this remapping in a future release
reason = :fails_gatekeeper_check if reason == :unsigned
reason = if formula_or_cask.is_a?(Formula) && FORMULA_DEPRECATE_DISABLE_REASONS.key?(reason)
FORMULA_DEPRECATE_DISABLE_REASONS[reason]
elsif formula_or_cask.is_a?(Cask::Cask) && CASK_DEPRECATE_DISABLE_REASONS.key?(reason)

View File

@ -104,7 +104,7 @@ module Homebrew
sig { override.void }
def run
odeprecated "brew audit --token-conflicts" if args.token_conflicts?
odeprecated "`brew audit --token-conflicts`" if args.token_conflicts?
Formulary.enable_factory_cache!
@ -142,7 +142,7 @@ module Homebrew
unless eval_all
# This odisabled should probably stick around indefinitely.
odisabled "brew audit",
odisabled "`brew audit`",
"`brew audit --eval-all` or set `HOMEBREW_EVAL_ALL=1`"
end
no_named_args = true
@ -154,8 +154,8 @@ module Homebrew
if args.named.any? { |named_arg| named_arg.end_with?(".rb") }
# This odisabled should probably stick around indefinitely,
# until at least we have a way to exclude error on these in the CLI parser.
odisabled "brew audit [path ...]",
"brew audit [name ...]"
odisabled "`brew audit [path ...]`",
"`brew audit [name ...]`"
end
args.named.to_formulae_and_casks_with_taps

View File

@ -539,8 +539,9 @@ module Homebrew
ohai "Detecting if #{local_filename} is relocatable..." if bottle_path.size > 1 * 1024 * 1024
prefix_check = if prefix == HOMEBREW_DEFAULT_PREFIX
File.join(prefix, "opt")
is_usr_local_prefix = prefix == "/usr/local"
prefix_check = if is_usr_local_prefix
"#{prefix}/opt"
else
prefix
end
@ -568,11 +569,17 @@ module Homebrew
relocatable = false if keg_contain?(prefix_check, keg, ignores, formula_and_runtime_deps_names)
relocatable = false if keg_contain?(cellar, keg, ignores, formula_and_runtime_deps_names)
relocatable = false if keg_contain?(HOMEBREW_LIBRARY.to_s, keg, ignores, formula_and_runtime_deps_names)
if prefix != prefix_check
if is_usr_local_prefix
relocatable = false if keg_contain_absolute_symlink_starting_with?(prefix, keg)
relocatable = false if keg_contain?("#{prefix}/etc", keg, ignores)
relocatable = false if keg_contain?("#{prefix}/var", keg, ignores)
relocatable = false if keg_contain?("#{prefix}/share/vim", keg, ignores)
if tap.disabled_new_usr_local_relocation_formulae.exclude?(formula.name)
keg.new_usr_local_replacement_pairs.each_value do |value|
relocatable = false if keg_contain?(value.fetch(:old), keg, ignores)
end
else
relocatable = false if keg_contain?("#{prefix}/etc", keg, ignores)
relocatable = false if keg_contain?("#{prefix}/var", keg, ignores)
relocatable = false if keg_contain?("#{prefix}/share/vim", keg, ignores)
end
end
skip_relocation = relocatable && !keg.require_relocation?
end

View File

@ -273,6 +273,8 @@ module Homebrew
end
languages.each do |language|
new_cask = Cask::CaskLoader.load(tmp_contents)
next unless new_cask.url
new_cask.config = if language.blank?
tmp_cask.config
else

View File

@ -110,7 +110,7 @@ module Homebrew
raise TapUnavailableError, "#{tap_match[:user]}/#{tap_match[:repo]}"
elsif cask || core_cask_path?(path)
if !CoreCaskTap.instance.installed? && Homebrew::API::Cask.all_casks.key?(name)
if !CoreCaskTap.instance.installed? && Homebrew::API.cask_tokens.include?(name)
command = "brew tap --force #{CoreCaskTap.instance.name}"
action = "tap #{CoreCaskTap.instance.name}"
else
@ -119,7 +119,7 @@ module Homebrew
end
elsif core_formula_path?(path) &&
!CoreTap.instance.installed? &&
Homebrew::API::Formula.all_formulae.key?(name)
Homebrew::API.formula_names.include?(name)
command = "brew tap --force #{CoreTap.instance.name}"
action = "tap #{CoreTap.instance.name}"
else

View File

@ -379,6 +379,7 @@ module Homebrew
when /^13\.?/ then "macOS Ventura (13)"
when /^14\.?/ then "macOS Sonoma (14)"
when /^15\.?/ then "macOS Sequoia (15)"
when /^26\.?/ then "macOS Tahoe (26)"
when /Ubuntu(-Server)? (14|16|18|20|22)\.04/ then "Ubuntu #{Regexp.last_match(2)}.04 LTS"
when /Ubuntu(-Server)? (\d+\.\d+).\d ?(LTS)?/
"Ubuntu #{Regexp.last_match(2)} #{Regexp.last_match(3)}".strip

View File

@ -71,11 +71,24 @@ module Homebrew
File.write("_data/cask_canonical.json", "#{canonical_json}\n") unless args.dry_run?
OnSystem::VALID_OS_ARCH_TAGS.each do |bottle_tag|
variation_casks = all_casks.map do |_, cask|
Homebrew::API.merge_variations(cask, bottle_tag:)
renames = {}
variation_casks = all_casks.to_h do |token, cask|
cask = Homebrew::API.merge_variations(cask, bottle_tag:)
cask["old_tokens"]&.each do |old_token|
renames[old_token] = token
end
[token, cask]
end
File.write("api/internal/cask.#{bottle_tag}.json", JSON.generate(variation_casks)) unless args.dry_run?
json_contents = {
casks: variation_casks,
renames: renames,
tap_migrations: CoreCaskTap.instance.tap_migrations,
}
File.write("api/internal/cask.#{bottle_tag}.json", JSON.generate(json_contents)) unless args.dry_run?
end
end
end

View File

@ -57,15 +57,15 @@ module Homebrew
syntax_only = args.syntax_only?
repository = ENV.fetch("GITHUB_REPOSITORY", nil)
raise UsageError, "The GITHUB_REPOSITORY environment variable must be set." if repository.blank?
raise UsageError, "The `$GITHUB_REPOSITORY` environment variable must be set." if repository.blank?
tap = T.let(Tap.fetch(repository), Tap)
unless syntax_only
raise UsageError, "Either `--cask` or `--url` must be specified." if !args.casks? && !args.url?
raise UsageError, "Please provide a cask or url argument" if casks.blank? && pr_url.blank?
raise UsageError, "Please provide a `--cask` or `--url` argument." if casks.blank? && pr_url.blank?
end
raise UsageError, "Only one url can be specified" if pr_url&.count&.> 1
raise UsageError, "Only one `--url` can be specified." if pr_url&.count&.> 1
labels = if pr_url && (first_pr_url = pr_url.first)
pr = GitHub::API.open_rest(first_pr_url)

View File

@ -69,9 +69,19 @@ module Homebrew
File.write("_data/formula_canonical.json", "#{canonical_json}\n") unless args.dry_run?
OnSystem::VALID_OS_ARCH_TAGS.each do |bottle_tag|
aliases = {}
renames = {}
variation_formulae = all_formulae.to_h do |name, formula|
formula = Homebrew::API.merge_variations(formula, bottle_tag:)
formula["aliases"]&.each do |alias_name|
aliases[alias_name] = name
end
formula["oldnames"]&.each do |oldname|
renames[oldname] = name
end
version = Version.new(formula.dig("versions", "stable"))
pkg_version = PkgVersion.new(version, formula["revision"])
rebuild = formula.dig("bottle", "stable", "rebuild") || 0
@ -87,9 +97,14 @@ module Homebrew
[name, [pkg_version.to_s, rebuild, sha256]]
end
unless args.dry_run?
File.write("api/internal/formula.#{bottle_tag}.json", JSON.generate(variation_formulae))
end
json_contents = {
formulae: variation_formulae,
aliases: aliases,
renames: renames,
tap_migrations: CoreTap.instance.tap_migrations,
}
File.write("api/internal/formula.#{bottle_tag}.json", JSON.generate(json_contents)) unless args.dry_run?
end
end
end

View File

@ -1000,12 +1000,12 @@ module Homebrew
locale_variables = ENV.keys.grep(/^(?:LC_\S+|LANG|LANGUAGE)\Z/).sort
add_info "Cask Environment Variables:", ((locale_variables + environment_variables).sort.each do |var|
add_info "Cask Environment Variables:", (locale_variables + environment_variables).sort.each do |var|
next unless ENV.key?(var)
var = %Q(#{var}="#{ENV.fetch(var)}")
user_tilde(var)
end)
end
end
def check_cask_xattr
@ -1040,21 +1040,6 @@ module Homebrew
end
end
def check_cask_quarantine_support
case Cask::Quarantine.check_quarantine_support
when :quarantine_available
nil
when :xattr_broken
"No Cask quarantine support available: there's no working version of `xattr` on this system."
when :no_swift
"No Cask quarantine support available: there's no available version of `swift` on this system."
when :linux
"No Cask quarantine support available: not available on Linux."
else
"No Cask quarantine support available: unknown reason."
end
end
def non_core_taps
@non_core_taps ||= Tap.installed.reject(&:core_tap?).reject(&:core_cask_tap?)
end
@ -1124,6 +1109,13 @@ module Homebrew
def current_user
ENV.fetch("USER", "$(whoami)")
end
private
sig { returns(T::Array[Pathname]) }
def paths
@paths ||= T.let(ORIGINAL_PATHS.uniq.map(&:to_s), T.nilable(T::Array[Pathname]))
end
end
end
end

View File

@ -148,6 +148,11 @@ class AbstractDownloadStrategy
cached_location.basename
end
sig { override.params(title: T.any(String, Exception), sput: T.anything).void }
def ohai(title, *sput)
super unless quiet?
end
private
sig { params(args: T.anything).void }
@ -155,11 +160,6 @@ class AbstractDownloadStrategy
super unless quiet?
end
sig { params(args: T.anything).void }
def ohai(*args)
super unless quiet?
end
sig { params(args: String, options: T.untyped).returns(SystemCommand::Result) }
def silent_command(*args, **options)
system_command(*args, print_stderr: false, env:, **options)
@ -1106,20 +1106,25 @@ class GitDownloadStrategy < VCSDownloadStrategy
# Convert any shallow clone to full clone
if shallow_dir?
command! "git",
args: ["fetch", "origin", "--unshallow"],
chdir: cached_location,
timeout: Utils::Timer.remaining(timeout)
args: ["fetch", "origin", "--unshallow"],
chdir: cached_location,
timeout: Utils::Timer.remaining(timeout),
reset_uid: true
else
command! "git",
args: ["fetch", "origin"],
chdir: cached_location,
timeout: Utils::Timer.remaining(timeout)
args: ["fetch", "origin"],
chdir: cached_location,
timeout: Utils::Timer.remaining(timeout),
reset_uid: true
end
end
sig { override.params(timeout: T.nilable(Time)).void }
def clone_repo(timeout: nil)
command! "git", args: clone_args, timeout: Utils::Timer.remaining(timeout)
command! "git",
args: clone_args,
timeout: Utils::Timer.remaining(timeout),
reset_uid: true
command! "git",
args: ["config", "homebrew.cacheversion", cache_version],
@ -1595,7 +1600,7 @@ class DownloadStrategyDetector
detect_from_symbol(using)
else
raise TypeError,
"Unknown download strategy specification #{using.inspect}"
"Unknown download strategy specification: #{using.inspect}"
end
end

View File

@ -32,7 +32,7 @@ module Downloadable
@download_name = T.let(nil, T.nilable(String))
end
sig { params(other: Object).void }
sig { overridable.params(other: Downloadable).void }
def initialize_dup(other)
super
@checksum = @checksum.dup
@ -132,6 +132,26 @@ module Downloadable
EOS
end
sig { returns(Integer) }
def hash
[self.class, cached_download].hash
end
sig { params(other: Object).returns(T::Boolean) }
def eql?(other)
return false if self.class != other.class
other = T.cast(other, Downloadable)
cached_download == other.cached_download
end
sig { returns(String) }
def to_s
short_cached_download = cached_download.to_s
.delete_prefix("#{HOMEBREW_CACHE}/downloads/")
"#<#{self.class}: #{short_cached_download}>"
end
private
sig { overridable.returns(String) }

View File

@ -241,9 +241,10 @@ module Homebrew
boolean: true,
},
HOMEBREW_FORBID_PACKAGES_FROM_PATHS: {
description: "If set, Homebrew will refuse to read formulae or casks provided from file paths, " \
"e.g. `brew install ./package.rb`.",
boolean: true,
description: "If set, Homebrew will refuse to read formulae or casks provided from file paths, " \
"e.g. `brew install ./package.rb`.",
boolean: true,
default_text: "true unless `$HOMEBREW_DEVELOPER` is set.",
},
HOMEBREW_FORCE_API_AUTO_UPDATE: {
description: "If set, update the Homebrew API formula or cask data even if " \
@ -560,6 +561,7 @@ module Homebrew
:HOMEBREW_MAKE_JOBS,
:HOMEBREW_NO_FORCE_BREW_WRAPPER,
:HOMEBREW_CASK_OPTS,
:HOMEBREW_FORBID_PACKAGES_FROM_PATHS,
]).freeze, T::Set[Symbol])
FALSY_VALUES = T.let(%w[false no off nil 0].freeze, T::Array[String])
@ -660,6 +662,18 @@ module Homebrew
cask_opts.include?("--require-sha")
end
sig { returns(T::Boolean) }
def forbid_packages_from_paths?
# Undocumented opt-out for internal use.
return false if ENV["HOMEBREW_INTERNAL_ALLOW_PACKAGES_FROM_PATHS"].present?
return true if ENV["HOMEBREW_FORBID_PACKAGES_FROM_PATHS"].present?
# Provide an opt-out for tests and developers.
# Our testing framework installs formulae from file paths all over the place.
ENV["HOMEBREW_TESTS"].blank? && ENV["HOMEBREW_DEVELOPER"].blank?
end
sig { returns(T::Boolean) }
def automatically_set_no_install_from_api?
ENV["HOMEBREW_AUTOMATICALLY_SET_NO_INSTALL_FROM_API"].present?
@ -683,5 +697,10 @@ module Homebrew
[concurrency, 1].max
end
sig { returns(T::Boolean) }
def use_internal_api?
ENV["HOMEBREW_USE_INTERNAL_API"].present?
end
end
end

View File

@ -267,7 +267,7 @@ module SharedEnvExtension
ohai "Using a Fortran compiler found at #{gfortran}"
end
if gfortran
puts "This may be changed by setting the FC environment variable."
puts "This may be changed by setting the `$FC` environment variable."
self["FC"] = self["F77"] = gfortran
flags = FC_FLAG_VARS
end

View File

@ -44,11 +44,13 @@ module Kernel
Formatter.headline(title, color: :blue)
end
sig { params(title: T.any(String, Exception), sput: T.anything).void }
def ohai(title, *sput)
puts ohai_title(title.to_s)
puts sput
end
sig { params(title: T.any(String, Exception), sput: T.anything, always_display: T::Boolean).void }
def odebug(title, *sput, always_display: false)
debug = if respond_to?(:debug)
T.unsafe(self).debug?
@ -355,19 +357,6 @@ module Kernel
nil
end
def which_all(cmd, path = ENV.fetch("PATH"))
PATH.new(path).filter_map do |p|
begin
pcmd = File.expand_path(cmd, p)
rescue ArgumentError
# File.expand_path will raise an ArgumentError if the path is malformed.
# See https://github.com/Homebrew/legacy-homebrew/issues/32789
next
end
Pathname.new(pcmd) if File.file?(pcmd) && File.executable?(pcmd)
end.uniq
end
def which_editor(silent: false)
editor = Homebrew::EnvConfig.editor
return editor if editor
@ -458,11 +447,6 @@ module Kernel
Formula[formula_name].ensure_installed!(reason:, latest:).opt_bin/name
end
sig { returns(T::Array[Pathname]) }
def paths
@paths ||= T.let(ORIGINAL_PATHS.uniq.map(&:to_s), T.nilable(T::Array[Pathname]))
end
sig { params(size_in_bytes: T.any(Integer, Float)).returns(String) }
def disk_usage_readable(size_in_bytes)
if size_in_bytes.abs >= 1_073_741_824

View File

@ -10,9 +10,6 @@ module OS
requires_ancestor { ::Cask::Quarantine }
sig { returns(Symbol) }
def check_quarantine_support = :linux
sig { returns(T::Boolean) }
def available? = false
end

View File

@ -10,6 +10,7 @@ require "os/linux/kernel"
module OS
module Linux
module Diagnostic
# Linux-specific diagnostic checks for Homebrew.
module Checks
extend T::Helpers
@ -28,6 +29,7 @@ module OS
check_glibc_minimum_version
check_kernel_minimum_version
check_supported_architecture
check_for_symlinked_home
].freeze
end
@ -154,6 +156,27 @@ module OS
EOS
end
def check_for_symlinked_home
return unless File.symlink?("/home")
<<~EOS
Your /home directory is a symlink.
This is known to cause issues with formula linking, particularly when installing
multiple formulae that create symlinks in shared directories.
While this may be a standard directory structure in some distributions
(e.g. Fedora Silverblue) there are known issues as-is.
If you encounter linking issues, you may need to manually create conflicting
directories or use `brew link --overwrite` as a workaround.
We'd welcome a PR to fix this functionality.
See https://github.com/Homebrew/brew/issues/18036 for more context.
#{support_tier_message(tier: 2)}
EOS
end
def check_gcc_dependent_linkage
gcc_dependents = ::Formula.installed.select do |formula|
next false unless formula.tap&.core_tap?

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:disable Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
module OS
@ -30,6 +30,7 @@ module OS
def add_global_deps_to_spec(spec)
return unless ::DevelopmentTools.needs_build_formulae?
@global_deps ||= T.let(nil, T.nilable(T::Array[Dependency]))
@global_deps ||= begin
dependency_collector = spec.dependency_collector
related_formula_names = Set.new([

View File

@ -4,7 +4,7 @@
require "compilers"
class Keg
def relocate_dynamic_linkage(relocation)
def relocate_dynamic_linkage(relocation, skip_protodesc_cold: false)
# Patching the dynamic linker of glibc breaks it.
return if name.match? Version.formula_optionally_versioned_regex(:glibc)
@ -12,14 +12,19 @@ class Keg
elf_files.each do |file|
file.ensure_writable do
change_rpath!(file, old_prefix, new_prefix)
change_rpath!(file, old_prefix, new_prefix, skip_protodesc_cold:)
end
end
end
def change_rpath!(file, old_prefix, new_prefix)
def change_rpath!(file, old_prefix, new_prefix, skip_protodesc_cold: false)
return false if !file.elf? || !file.dynamic_elf?
# Skip relocation of files with `protodesc_cold` sections because patchelf.rb seems to break them,
# but only when bottling (as we don't want to break existing bottles that require relocation).
# https://github.com/Homebrew/homebrew-core/pull/232490#issuecomment-3161362452
return false if skip_protodesc_cold && file.section_names.include?("protodesc_cold")
updated = {}
old_rpath = file.rpath
new_rpath = if old_rpath

View File

@ -417,7 +417,7 @@ module OS
macOS won't move relative symlinks across volumes unless the target file already
exists. Formulae known to be affected by this are Git and Narwhal.
You should set the "HOMEBREW_TEMP" environment variable to a suitable
You should set the `$HOMEBREW_TEMP` environment variable to a suitable
directory on the same volume as your Cellar.
#{support_tier_message(tier: 2)}
@ -506,6 +506,88 @@ module OS
nil
end
def check_pkgconf_macos_sdk_mismatch
# We don't provide suitable bottles for these versions.
return if OS::Mac.version.prerelease? || OS::Mac.version.outdated_release?
pkgconf = begin
::Formula["pkgconf"]
rescue FormulaUnavailableError
nil
end
return unless pkgconf
return unless pkgconf.any_version_installed?
tab = Tab.for_formula(pkgconf)
return unless tab.built_on
built_on_version = tab.built_on["os_version"]
&.delete_prefix("macOS ")
&.sub(/\.\d+$/, "")
return unless built_on_version
current_version = MacOS.version.to_s
return if built_on_version == current_version
<<~EOS
You have pkgconf installed that was built on macOS #{built_on_version}
but you are running macOS #{current_version}.
This can cause issues with packages that depend on system libraries, such as libffi.
To fix this issue, reinstall pkgconf:
brew reinstall pkgconf
For more information, see: https://github.com/Homebrew/brew/issues/16137
We'd welcome a PR to automatically mitigate this instead of just warning about it.
EOS
end
def check_cask_quarantine_support
status, check_output = ::Cask::Quarantine.check_quarantine_support
case status
when :quarantine_available
nil
when :xattr_broken
"No Cask quarantine support available: there's no working version of `xattr` on this system."
when :no_swift
"No Cask quarantine support available: there's no available version of `swift` on this system."
when :swift_broken_clt
<<~EOS
No Cask quarantine support available: Swift is not working due to missing Command Line Tools.
#{MacOS::CLT.installation_then_reinstall_instructions}
EOS
when :swift_compilation_failed
<<~EOS
No Cask quarantine support available: Swift compilation failed.
This is usually due to a broken or incompatible Command Line Tools installation.
#{MacOS::CLT.installation_then_reinstall_instructions}
EOS
when :swift_runtime_error
<<~EOS
No Cask quarantine support available: Swift runtime error.
Your Command Line Tools installation may be broken or incomplete.
#{MacOS::CLT.installation_then_reinstall_instructions}
EOS
when :swift_not_executable
<<~EOS
No Cask quarantine support available: Swift is not executable.
Your Command Line Tools installation may be incomplete.
#{MacOS::CLT.installation_then_reinstall_instructions}
EOS
when :swift_unexpected_error
<<~EOS
No Cask quarantine support available: Swift returned an unexpected error:
#{check_output}
EOS
else
<<~EOS
No Cask quarantine support available: unknown reason: #{status.inspect}:
#{check_output}
EOS
end
end
end
end
end

View File

@ -20,7 +20,7 @@ module OS
end
end
def relocate_dynamic_linkage(relocation)
def relocate_dynamic_linkage(relocation, skip_protodesc_cold: false)
mach_o_files.each do |file|
file.ensure_writable do
modified = T.let(false, T::Boolean)

View File

@ -1 +1 @@
3.12
3.13

View File

@ -1,73 +1,61 @@
#
# This file is autogenerated by pip-compile with Python 3.12
# This file is autogenerated by pip-compile with Python 3.13
# by the following command:
#
# pip-compile --allow-unsafe --generate-hashes --strip-extras requirements.in
#
certifi==2025.7.14 \
--hash=sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2 \
--hash=sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995
certifi==2025.8.3 \
--hash=sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407 \
--hash=sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5
# via influxdb3-python
influxdb3-python==0.14.0 \
--hash=sha256:63318c1eb53e026d1d793ef00fef51510acd40dd80bd69b528bf05332757dc67 \
--hash=sha256:f3c676112bf8a6230278a3e0940dd4cc2b57a868906a9de8e20cc9efd708a339
influxdb3-python==0.15.0 \
--hash=sha256:47b5ea57cf9a6d7b3fbc51ab6eb0122f1196207d6472d4ec93e3da0191b54b1c \
--hash=sha256:ccbf22dfef32fe86464d258f94ed66631257b5f6f1624ae0969e23522ca27e3d
# via -r requirements.in
pyarrow==20.0.0 \
--hash=sha256:00138f79ee1b5aca81e2bdedb91e3739b987245e11fa3c826f9e57c5d102fb75 \
--hash=sha256:11529a2283cb1f6271d7c23e4a8f9f8b7fd173f7360776b668e509d712a02eec \
--hash=sha256:15aa1b3b2587e74328a730457068dc6c89e6dcbf438d4369f572af9d320a25ee \
--hash=sha256:1bcbe471ef3349be7714261dea28fe280db574f9d0f77eeccc195a2d161fd861 \
--hash=sha256:204a846dca751428991346976b914d6d2a82ae5b8316a6ed99789ebf976551e6 \
--hash=sha256:211d5e84cecc640c7a3ab900f930aaff5cd2702177e0d562d426fb7c4f737781 \
--hash=sha256:24ca380585444cb2a31324c546a9a56abbe87e26069189e14bdba19c86c049f0 \
--hash=sha256:2c3a01f313ffe27ac4126f4c2e5ea0f36a5fc6ab51f8726cf41fee4b256680bd \
--hash=sha256:30b3051b7975801c1e1d387e17c588d8ab05ced9b1e14eec57915f79869b5031 \
--hash=sha256:3346babb516f4b6fd790da99b98bed9708e3f02e734c84971faccb20736848dc \
--hash=sha256:3e1f8a47f4b4ae4c69c4d702cfbdfe4d41e18e5c7ef6f1bb1c50918c1e81c57b \
--hash=sha256:4250e28a22302ce8692d3a0e8ec9d9dde54ec00d237cff4dfa9c1fbf79e472a8 \
--hash=sha256:4680f01ecd86e0dd63e39eb5cd59ef9ff24a9d166db328679e36c108dc993d4c \
--hash=sha256:4a8b029a07956b8d7bd742ffca25374dd3f634b35e46cc7a7c3fa4c75b297191 \
--hash=sha256:4ba3cf4182828be7a896cbd232aa8dd6a31bd1f9e32776cc3796c012855e1199 \
--hash=sha256:5605919fbe67a7948c1f03b9f3727d82846c053cd2ce9303ace791855923fd20 \
--hash=sha256:5f0fb1041267e9968c6d0d2ce3ff92e3928b243e2b6d11eeb84d9ac547308232 \
--hash=sha256:6102b4864d77102dbbb72965618e204e550135a940c2534711d5ffa787df2a5a \
--hash=sha256:6415a0d0174487456ddc9beaead703d0ded5966129fa4fd3114d76b5d1c5ceae \
--hash=sha256:6bb830757103a6cb300a04610e08d9636f0cd223d32f388418ea893a3e655f1c \
--hash=sha256:6fc1499ed3b4b57ee4e090e1cea6eb3584793fe3d1b4297bbf53f09b434991a5 \
--hash=sha256:75a51a5b0eef32727a247707d4755322cb970be7e935172b6a3a9f9ae98404ba \
--hash=sha256:7a3a5dcf54286e6141d5114522cf31dd67a9e7c9133d150799f30ee302a7a1ab \
--hash=sha256:7f4c8534e2ff059765647aa69b75d6543f9fef59e2cd4c6d18015192565d2b70 \
--hash=sha256:82f1ee5133bd8f49d31be1299dc07f585136679666b502540db854968576faf9 \
--hash=sha256:851c6a8260ad387caf82d2bbf54759130534723e37083111d4ed481cb253cc0d \
--hash=sha256:89e030dc58fc760e4010148e6ff164d2f44441490280ef1e97a542375e41058e \
--hash=sha256:95b330059ddfdc591a3225f2d272123be26c8fa76e8c9ee1a77aad507361cfdb \
--hash=sha256:96d6a0a37d9c98be08f5ed6a10831d88d52cac7b13f5287f1e0f625a0de8062b \
--hash=sha256:96e37f0766ecb4514a899d9a3554fadda770fb57ddf42b63d80f14bc20aa7db3 \
--hash=sha256:97c8dc984ed09cb07d618d57d8d4b67a5100a30c3818c2fb0b04599f0da2de7b \
--hash=sha256:991f85b48a8a5e839b2128590ce07611fae48a904cae6cab1f089c5955b57eb5 \
--hash=sha256:9965a050048ab02409fb7cbbefeedba04d3d67f2cc899eff505cc084345959ca \
--hash=sha256:9b71daf534f4745818f96c214dbc1e6124d7daf059167330b610fc69b6f3d3e3 \
--hash=sha256:a15532e77b94c61efadde86d10957950392999503b3616b2ffcef7621a002893 \
--hash=sha256:a18a14baef7d7ae49247e75641fd8bcbb39f44ed49a9fc4ec2f65d5031aa3b96 \
--hash=sha256:a1f60dc14658efaa927f8214734f6a01a806d7690be4b3232ba526836d216122 \
--hash=sha256:a2791f69ad72addd33510fec7bb14ee06c2a448e06b649e264c094c5b5f7ce28 \
--hash=sha256:a5704f29a74b81673d266e5ec1fe376f060627c2e42c5c7651288ed4b0db29e9 \
--hash=sha256:a6ad3e7758ecf559900261a4df985662df54fb7fdb55e8e3b3aa99b23d526b62 \
--hash=sha256:aa0d288143a8585806e3cc7c39566407aab646fb9ece164609dac1cfff45f6ae \
--hash=sha256:b6953f0114f8d6f3d905d98e987d0924dabce59c3cda380bdfaa25a6201563b4 \
--hash=sha256:b8ff87cc837601532cc8242d2f7e09b4e02404de1b797aee747dd4ba4bd6313f \
--hash=sha256:c7dd06fd7d7b410ca5dc839cc9d485d2bc4ae5240851bcd45d85105cc90a47d7 \
--hash=sha256:ca151afa4f9b7bc45bcc791eb9a89e90a9eb2772767d0b1e5389609c7d03db63 \
--hash=sha256:cb497649e505dc36542d0e68eca1a3c94ecbe9799cb67b578b55f2441a247fbc \
--hash=sha256:d5382de8dc34c943249b01c19110783d0d64b207167c728461add1ecc2db88e4 \
--hash=sha256:db53390eaf8a4dab4dbd6d93c85c5cf002db24902dbff0ca7d988beb5c9dd15b \
--hash=sha256:dd43f58037443af715f34f1322c782ec463a3c8a94a85fdb2d987ceb5658e061 \
--hash=sha256:e22f80b97a271f0a7d9cd07394a7d348f80d3ac63ed7cc38b6d1b696ab3b2619 \
--hash=sha256:e724a3fd23ae5b9c010e7be857f4405ed5e679db5c93e66204db1a69f733936a \
--hash=sha256:e8b88758f9303fa5a83d6c90e176714b2fd3852e776fc2d7e42a22dd6c2fb368 \
--hash=sha256:f2d67ac28f57a362f1a2c1e6fa98bfe2f03230f7e15927aecd067433b1e70ce8 \
--hash=sha256:f3b117b922af5e4c6b9a9115825726cac7d8b1421c37c2b5e24fbacc8930612c \
--hash=sha256:febc4a913592573c8d5805091a6c2b5064c8bd6e002131f01061797d91c783c1
pyarrow==21.0.0 \
--hash=sha256:067c66ca29aaedae08218569a114e413b26e742171f526e828e1064fcdec13f4 \
--hash=sha256:072116f65604b822a7f22945a7a6e581cfa28e3454fdcc6939d4ff6090126623 \
--hash=sha256:0c4e75d13eb76295a49e0ea056eb18dbd87d81450bfeb8afa19a7e5a75ae2ad7 \
--hash=sha256:186aa00bca62139f75b7de8420f745f2af12941595bbbfa7ed3870ff63e25636 \
--hash=sha256:1e005378c4a2c6db3ada3ad4c217b381f6c886f0a80d6a316fe586b90f77efd7 \
--hash=sha256:203003786c9fd253ebcafa44b03c06983c9c8d06c3145e37f1b76a1f317aeae1 \
--hash=sha256:222c39e2c70113543982c6b34f3077962b44fca38c0bd9e68bb6781534425c10 \
--hash=sha256:26bfd95f6bff443ceae63c65dc7e048670b7e98bc892210acba7e4995d3d4b51 \
--hash=sha256:3a302f0e0963db37e0a24a70c56cf91a4faa0bca51c23812279ca2e23481fccd \
--hash=sha256:3a81486adc665c7eb1a2bde0224cfca6ceaba344a82a971ef059678417880eb8 \
--hash=sha256:3b4d97e297741796fead24867a8dabf86c87e4584ccc03167e4a811f50fdf74d \
--hash=sha256:40ebfcb54a4f11bcde86bc586cbd0272bac0d516cfa539c799c2453768477569 \
--hash=sha256:479ee41399fcddc46159a551705b89c05f11e8b8cb8e968f7fec64f62d91985e \
--hash=sha256:5051f2dccf0e283ff56335760cbc8622cf52264d67e359d5569541ac11b6d5bc \
--hash=sha256:555ca6935b2cbca2c0e932bedd853e9bc523098c39636de9ad4693b5b1df86d6 \
--hash=sha256:585e7224f21124dd57836b1530ac8f2df2afc43c861d7bf3d58a4870c42ae36c \
--hash=sha256:58c30a1729f82d201627c173d91bd431db88ea74dcaa3885855bc6203e433b82 \
--hash=sha256:6299449adf89df38537837487a4f8d3bd91ec94354fdd2a7d30bc11c48ef6e79 \
--hash=sha256:65f8e85f79031449ec8706b74504a316805217b35b6099155dd7e227eef0d4b6 \
--hash=sha256:689f448066781856237eca8d1975b98cace19b8dd2ab6145bf49475478bcaa10 \
--hash=sha256:69cbbdf0631396e9925e048cfa5bce4e8c3d3b41562bbd70c685a8eb53a91e61 \
--hash=sha256:731c7022587006b755d0bdb27626a1a3bb004bb56b11fb30d98b6c1b4718579d \
--hash=sha256:7be45519b830f7c24b21d630a31d48bcebfd5d4d7f9d3bdb49da9cdf6d764edb \
--hash=sha256:898afce396b80fdda05e3086b4256f8677c671f7b1d27a6976fa011d3fd0a86e \
--hash=sha256:8d58d8497814274d3d20214fbb24abcad2f7e351474357d552a8d53bce70c70e \
--hash=sha256:9b0b14b49ac10654332a805aedfc0147fb3469cbf8ea951b3d040dab12372594 \
--hash=sha256:9d9f8bcb4c3be7738add259738abdeddc363de1b80e3310e04067aa1ca596634 \
--hash=sha256:a7a102574faa3f421141a64c10216e078df467ab9576684d5cd696952546e2da \
--hash=sha256:a7f6524e3747e35f80744537c78e7302cd41deee8baa668d56d55f77d9c464b3 \
--hash=sha256:b6b27cf01e243871390474a211a7922bfbe3bda21e39bc9160daf0da3fe48876 \
--hash=sha256:b7ae0bbdc8c6674259b25bef5d2a1d6af5d39d7200c819cf99e07f7dfef1c51e \
--hash=sha256:bd04ec08f7f8bd113c55868bd3fc442a9db67c27af098c5f814a3091e71cc61a \
--hash=sha256:c077f48aab61738c237802836fc3844f85409a46015635198761b0d6a688f87b \
--hash=sha256:cdc4c17afda4dab2a9c0b79148a43a7f4e1094916b3e18d8975bfd6d6d52241f \
--hash=sha256:cf56ec8b0a5c8c9d7021d6fd754e688104f9ebebf1bf4449613c9531f5346a18 \
--hash=sha256:d2fe8e7f3ce329a71b7ddd7498b3cfac0eeb200c2789bd840234f0dc271a8efe \
--hash=sha256:dc56bc708f2d8ac71bd1dcb927e458c93cec10b98eb4120206a4091db7b67b99 \
--hash=sha256:e563271e2c5ff4d4a4cbeb2c83d5cf0d4938b891518e676025f7268c6fe5fe26 \
--hash=sha256:e72a8ec6b868e258a2cd2672d91f2860ad532d590ce94cdf7d5e7ec674ccf03d \
--hash=sha256:e99310a4ebd4479bcd1964dff9e14af33746300cb014aa4a3781738ac63baf4a \
--hash=sha256:f522e5709379d72fb3da7785aa489ff0bb87448a9dc5a75f45763a795a089ebd \
--hash=sha256:fc0d2f88b81dcf3ccf9a6ae17f89183762c8a94a5bdcfa09e05cfe413acf0503 \
--hash=sha256:fee33b0ca46f4c85443d6c450357101e47d53e6c3f008d658c27a2d020d44c79
# via influxdb3-python
python-dateutil==2.9.0.post0 \
--hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \

View File

@ -557,6 +557,12 @@ class Formula
# @see .loaded_from_api?
delegate loaded_from_api?: :"self.class"
# The API source data used to load this formula.
# Returns `nil` if the formula was not loaded from the API.
# @!method api_source
# @see .api_source
delegate api_source: :"self.class"
sig { void }
def update_head_version
return unless head?
@ -1675,7 +1681,10 @@ class Formula
# don't consider this keg current if there's a newer formula available
next if follow_installed_alias? && new_formula_available?
# this keg is the current version of the formula, so it's not outdated
# this keg is the current version of the formula, but only consider it current
# if it's actually linked - an unlinked current version means we're outdated
next if !keg.optlinked? && !keg.linked? && !pinned?
current_version = true
break
end
@ -2625,9 +2634,8 @@ class Formula
hash = to_hash
# Take from API, merging in local install status.
if loaded_from_api? && !Homebrew::EnvConfig.no_install_from_api?
json_formula = Homebrew::API::Formula.all_formulae.fetch(name).dup
return json_formula.merge(
if loaded_from_api? && (json_formula = api_source) && !Homebrew::EnvConfig.no_install_from_api?
return json_formula.dup.merge(
hash.slice("name", "installed", "linked_keg", "pinned", "outdated"),
)
end
@ -3062,12 +3070,12 @@ class Formula
@exec_count ||= T.let(0, T.nilable(Integer))
@exec_count += 1
logfn = format("#{logs}/#{active_log_prefix}%02<exec_count>d.%<cmd_base>s",
exec_count: @exec_count,
cmd_base: File.basename(cmd).split.first)
log_filename = format("#{logs}/#{active_log_prefix}%02<exec_count>d.%<cmd_base>s.log",
exec_count: @exec_count,
cmd_base: File.basename(cmd).split.first)
logs.mkpath
File.open(logfn, "w") do |log|
File.open(log_filename, "w") do |log|
log.puts Time.now, "", cmd, args, ""
log.flush
@ -3077,7 +3085,7 @@ class Formula
pid = fork do
rd.close
log.close
exec_cmd(cmd, args, wr, logfn)
exec_cmd(cmd, args, wr, log_filename)
end
wr.close
@ -3104,7 +3112,7 @@ class Formula
end
else
pid = fork do
exec_cmd(cmd, args, log, logfn)
exec_cmd(cmd, args, log, log_filename)
end
end
@ -3117,8 +3125,8 @@ class Formula
log.flush
if !verbose? || verbose_using_dots
puts "Last #{log_lines} lines from #{logfn}:"
Kernel.system "/usr/bin/tail", "-n", log_lines.to_s, logfn
puts "Last #{log_lines} lines from #{log_filename}:"
Kernel.system "/usr/bin/tail", "-n", log_lines.to_s, log_filename
end
log.puts
@ -3259,14 +3267,14 @@ class Formula
sig {
params(
cmd: T.any(String, Pathname),
args: T::Array[T.any(String, Integer, Pathname, Symbol)],
out: IO,
logfn: T.nilable(String),
cmd: T.any(String, Pathname),
args: T::Array[T.any(String, Integer, Pathname, Symbol)],
out: IO,
log_filename: T.nilable(String),
).void
}
def exec_cmd(cmd, args, out, logfn)
ENV["HOMEBREW_CC_LOG_PATH"] = logfn
def exec_cmd(cmd, args, out, log_filename)
ENV["HOMEBREW_CC_LOG_PATH"] = log_filename
ENV.remove_cc_etc if cmd.to_s.start_with? "xcodebuild"
@ -3358,6 +3366,7 @@ class Formula
@skip_clean_paths = T.let(Set.new, T.nilable(T::Set[T.any(String, Symbol)]))
@link_overwrite_paths = T.let(Set.new, T.nilable(T::Set[String]))
@loaded_from_api = T.let(false, T.nilable(T::Boolean))
@api_source = T.let(nil, T.nilable(T::Hash[String, T.untyped]))
@on_system_blocks_exist = T.let(false, T.nilable(T::Boolean))
@network_access_allowed = T.let(SUPPORTED_NETWORK_ACCESS_PHASES.to_h do |phase|
[phase, DEFAULT_NETWORK_ACCESS_ALLOWED]
@ -3382,6 +3391,10 @@ class Formula
sig { returns(T::Boolean) }
def loaded_from_api? = !!@loaded_from_api
# Whether this formula was loaded using the formulae.brew.sh API.
sig { returns(T.nilable(T::Hash[String, T.untyped])) }
attr_reader :api_source
# Whether this formula contains OS/arch-specific blocks
# (e.g. `on_macos`, `on_arm`, `on_monterey :or_older`, `on_system :linux, macos: :big_sur_or_newer`).
sig { returns(T::Boolean) }
@ -3797,12 +3810,12 @@ class Formula
# If called as a method this provides just the {url} for the {SoftwareSpec}.
# If a block is provided you can also add {.depends_on} and {Patch}es just to the {.head} {SoftwareSpec}.
# The download strategies (e.g. `:using =>`) are the same as for {url}.
# `master` is the default branch for Git and doesn't need stating with a `branch:` parameter.
# Git repositories must always specify `branch:`.
#
# ### Example
#
# ```ruby
# head "https://we.prefer.https.over.git.example.com/.git"
# head "https://we.prefer.https.over.git.example.com/.git", branch: "main"
# ```
#
# ```ruby

View File

@ -677,6 +677,19 @@ module Homebrew
problem "GitLab repository is archived" if metadata["archived"]
end
sig { void }
def audit_forgejo_repository_archived
return if formula.deprecated? || formula.disabled?
user, repo = get_repo_data(%r{https?://codeberg\.org/([^/]+)/([^/]+)/?.*}) if @online
return if user.blank?
metadata = SharedAudits.forgejo_repo_data(user, repo)
return if metadata.nil?
problem "Forgejo repository is archived since #{metadata["archived_at"]}" if metadata["archived"]
end
def audit_github_repository
user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*}) if @new_formula
@ -708,6 +721,17 @@ module Homebrew
new_formula_problem warning
end
sig { void }
def audit_forgejo_repository
user, repo = get_repo_data(%r{https?://codeberg\.org/([^/]+)/([^/]+)/?.*}) if @new_formula
return if user.blank?
warning = SharedAudits.forgejo(user, repo)
return if warning.nil?
new_formula_problem warning
end
def get_repo_data(regex)
return unless @core_tap
return unless @online
@ -791,7 +815,7 @@ module Homebrew
formula_suffix = stable.version.patch.to_i
throttled_rate = formula.livecheck.throttle
if throttled_rate && formula_suffix.modulo(throttled_rate).nonzero?
problem "should only be updated every #{throttled_rate} releases on multiples of #{throttled_rate}"
problem "Should only be updated every #{throttled_rate} releases on multiples of #{throttled_rate}"
end
case (url = stable.url)
@ -839,6 +863,16 @@ module Homebrew
error = SharedAudits.github_release(owner, repo, tag, formula:)
problem error if error
end
when %r{^https://codeberg\.org/([\w-]+)/([\w-]+)}
owner = T.must(Regexp.last_match(1))
repo = T.must(Regexp.last_match(2))
tag = SharedAudits.forgejo_tag_from_url(url)
tag ||= formula.stable.specs[:tag]
if @online && !tag.nil?
error = SharedAudits.forgejo_release(owner, repo, tag, formula:)
problem error if error
end
end
end

View File

@ -382,7 +382,7 @@ class FormulaInstaller
check_installation_already_attempted
if force_bottle? && !pour_bottle?
raise CannotInstallFormulaError, "--force-bottle passed but #{formula.full_name} has no bottle!"
raise CannotInstallFormulaError, "`--force-bottle` passed but #{formula.full_name} has no bottle!"
end
if Homebrew.default_prefix? &&
@ -477,7 +477,7 @@ class FormulaInstaller
raise CannotInstallFormulaError,
"You must `brew unpin #{pinned_unsatisfied_deps * " "}` as installing " \
"#{formula.full_name} requires the latest version of pinned dependencies"
"#{formula.full_name} requires the latest version of pinned dependencies."
end
sig { params(_formula: Formula).returns(T.nilable(T::Boolean)) }

View File

@ -0,0 +1,34 @@
# typed: strict
# frozen_string_literal: true
require "pkg_version"
module Homebrew
# A stub for a formula, with only the information needed to fetch the bottle manifest.
class FormulaStub < T::Struct
const :name, String
const :pkg_version, PkgVersion
const :rebuild, Integer, default: 0
const :sha256, T.nilable(String)
sig { returns(Version) }
def version
pkg_version.version
end
sig { returns(Integer) }
def revision
pkg_version.revision
end
sig { params(other: T.anything).returns(T::Boolean) }
def ==(other)
case other
when FormulaStub
name == other.name && pkg_version == other.pkg_version && rebuild == other.rebuild && sha256 == other.sha256
else
false
end
end
end
end

View File

@ -60,6 +60,7 @@ class FormulaVersions
# We rescue these so that we can skip bad versions and
# continue walking the history
odebug "#{e} in #{name} at revision #{revision}", Utils::Backtrace.clean(e)
nil
rescue FormulaUnavailableError
nil
ensure

View File

@ -173,9 +173,9 @@ module Formulary
platform_cache[:path][path] = klass
end
sig { params(name: String, flags: T::Array[String]).returns(T.class_of(Formula)) }
def self.load_formula_from_api!(name, flags:)
namespace = :"FormulaNamespaceAPI#{namespace_key(name)}"
sig { params(name: String, json_formula_with_variations: T::Hash[String, T.untyped], flags: T::Array[String]).returns(T.class_of(Formula)) }
def self.load_formula_from_json!(name, json_formula_with_variations, flags:)
namespace = :"FormulaNamespaceAPI#{namespace_key(json_formula_with_variations.to_json)}"
mod = Module.new
remove_const(namespace) if const_defined?(namespace)
@ -184,10 +184,7 @@ module Formulary
mod.const_set(:BUILD_FLAGS, flags)
class_name = class_s(name)
json_formula = Homebrew::API::Formula.all_formulae[name]
raise FormulaUnavailableError, name if json_formula.nil?
json_formula = Homebrew::API.merge_variations(json_formula)
json_formula = Homebrew::API.merge_variations(json_formula_with_variations)
uses_from_macos_names = json_formula.fetch("uses_from_macos", []).map do |dep|
next dep unless dep.is_a? Hash
@ -273,6 +270,7 @@ module Formulary
# rubocop:todo Sorbet/BlockMethodDefinition
klass = Class.new(::Formula) do
@loaded_from_api = true
@api_source = json_formula_with_variations
desc json_formula["desc"]
homepage json_formula["homepage"]
@ -618,8 +616,28 @@ module Formulary
return unless path.expand_path.exist?
return if Homebrew::EnvConfig.forbid_packages_from_paths? &&
!path.realpath.to_s.start_with?("#{HOMEBREW_CELLAR}/", "#{HOMEBREW_LIBRARY}/Taps/")
if Homebrew::EnvConfig.forbid_packages_from_paths?
path_realpath = path.realpath.to_s
path_string = path.to_s
if (path_realpath.end_with?(".rb") || path_string.end_with?(".rb")) &&
!path_realpath.start_with?("#{HOMEBREW_CELLAR}/", "#{HOMEBREW_LIBRARY}/Taps/", "#{HOMEBREW_CACHE}/") &&
!path_string.start_with?("#{HOMEBREW_CELLAR}/", "#{HOMEBREW_LIBRARY}/Taps/", "#{HOMEBREW_CACHE}/")
if path_string.include?("./") || path_string.end_with?(".rb") || path_string.count("/") != 2
raise <<~WARNING
Homebrew requires formulae to be in a tap, rejecting:
#{path_string} (#{path_realpath})
To create a tap, run e.g.
brew tap-new <user|org>/<repository>
To create a formula in a tap run e.g.
brew create <url> --tap=<user|org>/<repository>
WARNING
elsif path_string.count("/") == 2
# Looks like a tap, let's quietly return but not error.
return
end
end
end
if (tap = Tap.from_path(path))
# Only treat symlinks in taps as aliases.
@ -693,7 +711,7 @@ module Formulary
if ALLOWED_URL_SCHEMES.exclude?(url_scheme)
raise UnsupportedInstallationMethod,
"Non-checksummed download of #{name} formula file from an arbitrary URL is unsupported! " \
"`brew extract` or `brew create` and `brew tap-new` to create a formula file in a tap " \
"Use `brew extract` or `brew create` and `brew tap-new` to create a formula file in a tap " \
"on GitHub instead."
end
HOMEBREW_CACHE_FORMULA.mkpath
@ -877,9 +895,9 @@ module Formulary
return if Homebrew::EnvConfig.no_install_from_api?
return unless ref.is_a?(String)
return unless (name = ref[HOMEBREW_DEFAULT_TAP_FORMULA_REGEX, :name])
if !Homebrew::API::Formula.all_formulae.key?(name) &&
!Homebrew::API::Formula.all_aliases.key?(name) &&
!Homebrew::API::Formula.all_renames.key?(name)
if Homebrew::API.formula_names.exclude?(name) &&
!Homebrew::API.formula_aliases.key?(name) &&
!Homebrew::API.formula_renames.key?(name)
return
end
@ -911,7 +929,10 @@ module Formulary
private
def load_from_api(flags:)
Formulary.load_formula_from_api!(name, flags:)
json_formula = Homebrew::API::Formula.all_formulae[name]
raise FormulaUnavailableError, name if json_formula.nil?
Formulary.load_formula_from_json!(name, json_formula, flags:)
end
end

View File

@ -2,6 +2,7 @@
module Ignorable
include Kernel
# This is a workaround to enable `raise` to be aliased
# @see https://github.com/sorbet/sorbet/issues/2378#issuecomment-569474238
def self.raise(*); end

View File

@ -83,10 +83,10 @@ module Homebrew
skip_link: false,
overwrite: false
)
# head-only without --HEAD is an error
# HEAD-only without --HEAD is an error
if !head && formula.stable.nil?
odie <<~EOS
#{formula.full_name} is a head-only formula.
#{formula.full_name} is a HEAD-only formula.
To install it, run:
brew install --HEAD #{formula.full_name}
EOS

View File

@ -78,15 +78,43 @@ class Keg
end
end
def relocate_dynamic_linkage(_relocation)
def relocate_dynamic_linkage(_relocation, skip_protodesc_cold: false)
[]
end
JAVA_REGEX = %r{#{HOMEBREW_PREFIX}/opt/openjdk(@\d+(\.\d+)*)?/libexec(/openjdk\.jdk/Contents/Home)?}
sig { returns(T::Hash[Symbol, T::Hash[Symbol, String]]) }
def new_usr_local_replacement_pairs
{
prefix: {
old: "/usr/local/opt",
new: "#{PREFIX_PLACEHOLDER}/opt",
},
caskroom: {
old: "/usr/local/Caskroom",
new: "#{PREFIX_PLACEHOLDER}/Caskroom",
},
var_homebrew: {
old: "/usr/local/var/homebrew",
new: "#{PREFIX_PLACEHOLDER}/var/homebrew",
},
}
end
def prepare_relocation_to_placeholders
relocation = Relocation.new
relocation.add_replacement_pair(:prefix, HOMEBREW_PREFIX.to_s, PREFIX_PLACEHOLDER, path: true)
# Use selective HOMEBREW_PREFIX replacement when HOMEBREW_PREFIX=/usr/local
# This avoids overzealous replacement of system paths when a script refers to e.g. /usr/local/bin
if new_usr_local_relocation?
new_usr_local_replacement_pairs.each do |key, value|
relocation.add_replacement_pair(key, value.fetch(:old), value.fetch(:new), path: true)
end
else
relocation.add_replacement_pair(:prefix, HOMEBREW_PREFIX.to_s, PREFIX_PLACEHOLDER, path: true)
end
relocation.add_replacement_pair(:cellar, HOMEBREW_CELLAR.to_s, CELLAR_PLACEHOLDER, path: true)
# when HOMEBREW_PREFIX == HOMEBREW_REPOSITORY we should use HOMEBREW_PREFIX for all relocations to avoid
# being unable to differentiate between them.
@ -104,7 +132,7 @@ class Keg
def replace_locations_with_placeholders
relocation = prepare_relocation_to_placeholders.freeze
relocate_dynamic_linkage(relocation)
relocate_dynamic_linkage(relocation, skip_protodesc_cold: true)
replace_text_in_files(relocation)
end
@ -361,6 +389,25 @@ class Keg
def self.file_linked_libraries(_file, _string)
[]
end
private
sig { returns(T::Boolean) }
def new_usr_local_relocation?
return false if HOMEBREW_PREFIX.to_s != "/usr/local"
formula = begin
Formula[name]
rescue FormulaUnavailableError
nil
end
return true unless formula
tap = formula.tap
return true unless tap
tap.disabled_new_usr_local_relocation_formulae.exclude?(name)
end
end
require "extend/os/keg_relocate"

View File

@ -286,7 +286,7 @@ module Language
def slice_resources!(resources_hash, resource_names)
resource_names.map do |resource_name|
resources_hash.delete(resource_name) do
raise ArgumentError, "Resource \"#{resource_name}\" is not defined in formula or is already used"
raise ArgumentError, "Resource \"#{resource_name}\" is not defined in formula or is already used."
end
end
end

View File

@ -245,7 +245,7 @@ module Homebrew
end
# Use the `stable` version for comparison except for installed
# head-only formulae. A formula with `stable` and `head` that's
# HEAD-only formulae. A formula with `stable` and `head` that's
# installed using `--head` will still use the `stable` version for
# comparison.
current = if formula

View File

@ -120,7 +120,7 @@ module Homebrew
}
private_class_method def self.cask_deprecated(cask, livecheck_defined, full_name: false, verbose: false)
return {} if !cask.deprecated? || livecheck_defined
return {} if cask.disable_date && cask.deprecation_reason == :unsigned
return {} if cask.disable_date && cask.deprecation_reason == :fails_gatekeeper_check
Livecheck.status_hash(cask, "deprecated", full_name:, verbose:)
end

View File

@ -8,6 +8,7 @@ module Homebrew
# method implementations here.
module Strategic
extend T::Helpers
interface!
# Whether the strategy can be applied to the provided URL.

View File

@ -66,7 +66,7 @@ module Homebrew
return values if match.blank?
# The directory listing page for the project's files
values[:url] = "https://ftp.gnu.org/gnu/#{match[:project_name]}/"
values[:url] = "https://ftpmirror.gnu.org/gnu/#{match[:project_name]}/"
regex_name = Regexp.escape(T.must(match[:project_name])).gsub("\\-", "-")

View File

@ -4,7 +4,7 @@
# A {Messages} object collects messages that may need to be displayed together
# at the end of a multi-step `brew` command run.
class Messages
sig { returns(T::Array[T::Hash[Symbol, Symbol]]) }
sig { returns(T::Array[{ package: String, caveats: T.any(String, Caveats) }]) }
attr_reader :caveats
sig { returns(Integer) }
@ -15,7 +15,7 @@ class Messages
sig { void }
def initialize
@caveats = T.let([], T::Array[T::Hash[Symbol, Symbol]])
@caveats = T.let([], T::Array[{ package: String, caveats: T.any(String, Caveats) }])
@completions_and_elisp = T.let(Set.new, T::Set[String])
@package_count = T.let(0, Integer)
@install_times = T.let([], T::Array[T::Hash[String, Float]])
@ -53,7 +53,7 @@ class Messages
return if @package_count == 1 && !force
oh1 "Caveats" if @completions_and_elisp.empty?
@caveats.each { |c| ohai c[:package], c[:caveats] }
@caveats.each { |c| ohai c.fetch(:package), c.fetch(:caveats) }
end
sig { void }

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
require "os/linux/ld"
@ -44,7 +44,8 @@ module ELFShim
requires_ancestor { Pathname }
def initialize(*args)
sig { params(path: T.anything).void }
def initialize(path)
@elf = T.let(nil, T.nilable(T::Boolean))
@arch = T.let(nil, T.nilable(Symbol))
@elf_type = T.let(nil, T.nilable(Symbol))
@ -52,15 +53,16 @@ module ELFShim
@interpreter = T.let(nil, T.nilable(String))
@dynamic_elf = T.let(nil, T.nilable(T::Boolean))
@metadata = T.let(nil, T.nilable(Metadata))
@patchelf_patcher = nil
super
end
sig { params(offset: Integer).returns(Integer) }
def read_uint8(offset)
read(1, offset).unpack1("C")
end
sig { params(offset: Integer).returns(Integer) }
def read_uint16(offset)
read(2, offset).unpack1("v")
end
@ -91,6 +93,7 @@ module ELFShim
end
end
sig { params(wanted_arch: Symbol).returns(T::Boolean) }
def arch_compatible?(wanted_arch)
return true unless elf?
@ -111,10 +114,12 @@ module ELFShim
end
end
sig { returns(T::Boolean) }
def dylib?
elf_type == :dylib
end
sig { returns(T::Boolean) }
def binary_executable?
elf_type == :executable
end
@ -123,20 +128,22 @@ module ELFShim
# "/lib:/usr/lib:/usr/local/lib"
sig { returns(T.nilable(String)) }
def rpath
@rpath ||= rpath_using_patchelf_rb
metadata.rpath
end
# An array of runtime search path entries, such as:
# ["/lib", "/usr/lib", "/usr/local/lib"]
sig { returns(T::Array[String]) }
def rpaths
Array(rpath&.split(":"))
end
sig { returns(T.nilable(String)) }
def interpreter
@interpreter ||= patchelf_patcher.interpreter
metadata.interpreter
end
sig { params(interpreter: T.nilable(String), rpath: T.nilable(String)).void }
def patch!(interpreter: nil, rpath: nil)
return if interpreter.blank? && rpath.blank?
@ -145,7 +152,12 @@ module ELFShim
sig { returns(T::Boolean) }
def dynamic_elf?
@dynamic_elf ||= patchelf_patcher.elf.segment_by_type(:DYNAMIC).present?
metadata.dynamic_elf?
end
sig { returns(T::Array[String]) }
def section_names
metadata.section_names
end
# Helper class for reading metadata from an ELF file.
@ -156,35 +168,56 @@ module ELFShim
sig { returns(T.nilable(String)) }
attr_reader :dylib_id
sig { returns(T::Boolean) }
def dynamic_elf?
@dynamic_elf
end
sig { returns(T.nilable(String)) }
attr_reader :interpreter
sig { returns(T.nilable(String)) }
attr_reader :rpath
sig { returns(T::Array[String]) }
attr_reader :dylibs
attr_reader :section_names
sig { params(path: ELFShim).void }
def initialize(path)
@path = T.let(path, ELFShim)
@dylibs = T.let([], T::Array[String])
@dylib_id = T.let(nil, T.nilable(String))
@dylib_id, needed = needed_libraries path
@dylibs = needed.map { |lib| find_full_lib_path(lib).to_s } if needed.present?
require "patchelf"
patcher = path.patchelf_patcher
@metadata = T.let(nil, T.nilable(T::Hash[String, T.untyped]))
@path = T.let(path, ELFShim)
@dylibs = T.let(nil, T.nilable(T::Array[String]))
@dylib_id = T.let(nil, T.nilable(String))
@needed = T.let([], T::Array[String])
dynamic_segment = patcher.elf.segment_by_type(:dynamic)
@dynamic_elf = T.let(dynamic_segment.present?, T::Boolean)
@dylib_id, @needed = if @dynamic_elf
[patcher.soname, patcher.needed]
else
[nil, []]
end
@interpreter = T.let(patcher.interpreter, T.nilable(String))
@rpath = T.let(patcher.runpath || patcher.rpath, T.nilable(String))
@section_names = T.let(patcher.elf.sections.map(&:name).compact_blank, T::Array[String])
@dt_flags_1 = T.let(dynamic_segment&.tag_by_type(:flags_1)&.value, T.nilable(Integer))
end
sig { returns(T::Array[String]) }
def dylibs
@dylibs ||= @needed.map { |lib| find_full_lib_path(lib).to_s }
end
private
def needed_libraries(path)
return [nil, []] unless path.dynamic_elf?
needed_libraries_using_patchelf_rb path
end
def needed_libraries_using_patchelf_rb(path)
patcher = path.patchelf_patcher
[patcher.soname, patcher.needed]
end
sig { params(basename: String).returns(Pathname) }
def find_full_lib_path(basename)
local_paths = (path.patchelf_patcher.runpath || path.patchelf_patcher.rpath)&.split(":")
basename = Pathname(basename)
local_paths = rpath&.split(":")
# Search for dependencies in the runpath/rpath first
local_paths&.each do |local_path|
@ -194,11 +227,10 @@ module ELFShim
end
# Check if DF_1_NODEFLIB is set
dt_flags_1 = path.patchelf_patcher.elf.segment_by_type(:dynamic)&.tag_by_type(:flags_1)
nodeflib_flag = if dt_flags_1.nil?
nodeflib_flag = if @dt_flags_1.nil?
false
else
dt_flags_1.value & ELFTools::Constants::DF::DF_1_NODEFLIB != 0
@dt_flags_1 & ELFTools::Constants::DF::DF_1_NODEFLIB != 0
end
linker_library_paths = OS::Linux::Ld.library_paths
@ -232,6 +264,7 @@ module ELFShim
end
private_constant :Metadata
sig { params(new_interpreter: T.nilable(String), new_rpath: T.nilable(String)).void }
def save_using_patchelf_rb(new_interpreter, new_rpath)
patcher = patchelf_patcher
patcher.interpreter = new_interpreter if new_interpreter.present?
@ -239,13 +272,13 @@ module ELFShim
patcher.save(patchelf_compatible: true)
end
def rpath_using_patchelf_rb
patchelf_patcher.runpath || patchelf_patcher.rpath
end
# Don't cache the patcher; it keeps the ELF file open so long as it is alive.
# Instead, for read-only access to the ELF file's metadata, fetch it and cache
# it with {Metadata}.
sig { returns(::PatchELF::Patcher) }
def patchelf_patcher
require "patchelf"
@patchelf_patcher ||= ::PatchELF::Patcher.new to_s, on_error: :silent
::PatchELF::Patcher.new to_s, on_error: :silent
end
sig { returns(Metadata) }
@ -254,11 +287,13 @@ module ELFShim
end
private :metadata
sig { returns(T.nilable(String)) }
def dylib_id
metadata.dylib_id
end
def dynamically_linked_libraries(*)
sig { params(except: Symbol, resolve_variable_references: T::Boolean).returns(T::Array[String]) }
def dynamically_linked_libraries(except: :none, resolve_variable_references: true)
metadata.dylibs
end
end

View File

@ -49,6 +49,8 @@ module OS
def self.library_paths(conf_path = Pathname(sysconfdir)/"ld.so.conf")
conf_file = Pathname(conf_path)
return [] unless conf_file.exist?
return [] unless conf_file.file?
return [] unless conf_file.readable?
@library_paths_cache ||= T.let({}, T.nilable(T::Hash[String, T::Array[String]]))
cache_key = conf_file.to_s

View File

@ -327,6 +327,19 @@ module OS
end
end
sig { params(reason: String).returns(String) }
def self.reinstall_instructions(reason: "resolve your issues")
<<~EOS
If that doesn't #{reason}, run:
sudo rm -rf /Library/Developer/CommandLineTools
sudo xcode-select --install
Alternatively, manually download them from:
#{Formatter.url(MacOS::Xcode::APPLE_DEVELOPER_DOWNLOAD_URL)}.
You should download the Command Line Tools for Xcode #{MacOS::Xcode.latest_version}.
EOS
end
sig { returns(String) }
def self.update_instructions
return installation_instructions if OS::Mac.version.prerelease?
@ -342,13 +355,15 @@ module OS
<<~EOS
Update them from Software Update in #{software_update_location}.
If that doesn't show you any updates, run:
sudo rm -rf /Library/Developer/CommandLineTools
sudo xcode-select --install
#{reinstall_instructions(reason: "show you any updates")}
EOS
end
Alternatively, manually download them from:
#{Formatter.url(MacOS::Xcode::APPLE_DEVELOPER_DOWNLOAD_URL)}.
You should download the Command Line Tools for Xcode #{MacOS::Xcode.latest_version}.
sig { returns(String) }
def self.installation_then_reinstall_instructions
<<~EOS
#{installation_instructions}
#{reinstall_instructions}
EOS
end

View File

@ -24,7 +24,7 @@ module Patch
when nil
raise ArgumentError, "nil value for strip"
else
raise ArgumentError, "Unexpected value #{strip.inspect} for strip"
raise ArgumentError, "Unexpected value for strip: #{strip.inspect}"
end
end
end

View File

@ -14,7 +14,10 @@ require "cmd/postinstall"
require "json/add/exception"
begin
ENV.delete("HOMEBREW_FORBID_PACKAGES_FROM_PATHS")
# Undocumented opt-out for internal use.
# We need to allow formulae from paths here due to how we pass them through.
ENV["HOMEBREW_INTERNAL_ALLOW_PACKAGES_FROM_PATHS"] = "1"
args = Homebrew::Cmd::Postinstall.new.args
error_pipe = Utils::UNIXSocketExt.open(ENV.fetch("HOMEBREW_ERROR_PIPE"), &:recv_io)
error_pipe.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
@ -29,7 +32,7 @@ begin
formula.run_post_install
# Handle all possible exceptions.
rescue Exception => e # rubocop:disable Lint/RescueException
error_pipe.puts e.to_json
error_pipe.close
error_pipe&.puts e.to_json
error_pipe&.close
exit! 1
end

View File

@ -172,10 +172,6 @@ class Requirement
super(cmd, PATH.new(ORIGINAL_PATHS))
end
def which_all(cmd)
super(cmd, PATH.new(ORIGINAL_PATHS))
end
class << self
include BuildEnvironment::DSL

View File

@ -37,7 +37,7 @@ class Resource
instance_eval(&block) if block
end
sig { params(other: Object).void }
sig { override.params(other: T.any(Resource, Downloadable)).void }
def initialize_dup(other)
super
@name = @name.dup

Some files were not shown because too many files have changed in this diff Show More