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 ### Required Before Each Commit
- Run `brew typecheck` to verify types are declared correctly using Sorbet. - 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. - 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). - 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`. 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 ### Development Flow
- Write new code (using Sorbet `sig` type signatures and `typed: strict` files whenever possible) - 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) - 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 ## Repository Structure
@ -34,7 +38,10 @@ Please follow these guidelines when contributing:
## Key Guidelines ## Key Guidelines
1. Follow Ruby best practices and idiomatic patterns 1. Follow Ruby and Bash best practices and idiomatic patterns.
2. Maintain existing code structure and organisation 2. Maintain existing code structure and organisation.
3. Write unit tests for new functionality. Use one assertion per test where possible. 3. Write unit tests for new functionality.
4. Document public APIs and complex logic. Suggest changes to the `docs/` folder when appropriate 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: allow:
- dependency-type: all - dependency-type: all
cooldown:
default-days: 1
semver-major-days: 14
semver-minor-days: 7
semver-patch-days: 1
include:
- "*"
- package-ecosystem: devcontainers - package-ecosystem: devcontainers
directory: "/" directory: "/"
multi-ecosystem-group: all multi-ecosystem-group: all
@ -31,10 +24,6 @@ updates:
- "*" - "*"
allow: allow:
- dependency-type: all - dependency-type: all
cooldown:
default-days: 1
include:
- "*"
- package-ecosystem: docker - package-ecosystem: docker
directory: "/" directory: "/"
multi-ecosystem-group: all multi-ecosystem-group: all
@ -49,10 +38,6 @@ updates:
- "*" - "*"
allow: allow:
- dependency-type: all - dependency-type: all
cooldown:
default-days: 1
include:
- "*"
- package-ecosystem: pip - package-ecosystem: pip
directories: directories:
- "/Library/Homebrew/formula-analytics/" - "/Library/Homebrew/formula-analytics/"
@ -61,11 +46,4 @@ updates:
- "*" - "*"
allow: allow:
- dependency-type: all - 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 - name: Install tools
run: brew install actionlint shellcheck zizmor run: brew install actionlint shellcheck zizmor
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
@ -87,13 +87,13 @@ jobs:
security-events: write security-events: write
steps: steps:
- name: Download SARIF file - name: Download SARIF file
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with: with:
name: results.sarif name: results.sarif
path: results.sarif path: results.sarif
- name: Upload SARIF file - 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: with:
sarif_file: results.sarif sarif_file: results.sarif
category: zizmor category: zizmor

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -92,7 +92,7 @@ jobs:
fi fi
- name: Generate push token - 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 id: app-token
if: github.event_name == 'workflow_dispatch' if: github.event_name == 'workflow_dispatch'
with: with:

View File

@ -96,6 +96,11 @@ Layout/ArgumentAlignment:
Layout/CaseIndentation: Layout/CaseIndentation:
EnforcedStyle: end 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 # significantly less indentation involved; more consistent
Layout/FirstArrayElementIndentation: Layout/FirstArrayElementIndentation:
EnforcedStyle: consistent EnforcedStyle: consistent

View File

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

View File

@ -4,6 +4,7 @@
require "api/analytics" require "api/analytics"
require "api/cask" require "api/cask"
require "api/formula" require "api/formula"
require "api/internal"
require "base64" require "base64"
module Homebrew module Homebrew
@ -26,7 +27,7 @@ module Homebrew
api_url = "#{HOMEBREW_API_DEFAULT_DOMAIN}/#{endpoint}" api_url = "#{HOMEBREW_API_DEFAULT_DOMAIN}/#{endpoint}"
output = Utils::Curl.curl_output("--fail", api_url) output = Utils::Curl.curl_output("--fail", api_url)
end 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) cache[endpoint] = JSON.parse(output.stdout, freeze: true)
rescue JSON::ParserError rescue JSON::ParserError
@ -151,6 +152,30 @@ module Homebrew
json.except("variations") json.except("variations")
end 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) } sig { params(names: T::Array[String], type: String, regenerate: T::Boolean).returns(T::Boolean) }
def self.write_names_file!(names, type, regenerate:) def self.write_names_file!(names, type, regenerate:)
names_path = HOMEBREW_CACHE_API/"#{type}_names.txt" names_path = HOMEBREW_CACHE_API/"#{type}_names.txt"
@ -162,6 +187,20 @@ module Homebrew
false false
end 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 { sig {
params(json_data: T::Hash[String, T.untyped]) params(json_data: T::Hash[String, T.untyped])
.returns([T::Boolean, T.any(String, T::Array[T.untyped], 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) Tap.fetch(org, repo)
end 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 end
sig { params(block: T.proc.returns(T.untyped)).returns(T.untyped) } 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" 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 private_class_method :cache
sig { params(token: String).returns(T::Hash[String, T.untyped]) } sig { params(name: String).returns(T::Hash[String, T.untyped]) }
def self.fetch(token) def self.cask_json(name)
Homebrew::API.fetch "cask/#{token}.json" 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 end
sig { params(cask: ::Cask::Cask, download_queue: T.nilable(Homebrew::DownloadQueue)).returns(Homebrew::API::SourceDownload) } sig { params(cask: ::Cask::Cask, download_queue: T.nilable(Homebrew::DownloadQueue)).returns(Homebrew::API::SourceDownload) }
@ -64,7 +71,7 @@ module Homebrew
sig { returns(Pathname) } sig { returns(Pathname) }
def self.cached_json_file_path def self.cached_json_file_path
HOMEBREW_CACHE_API/api_filename HOMEBREW_CACHE_API/DEFAULT_API_FILENAME
end end
sig { sig {
@ -72,7 +79,7 @@ module Homebrew
.returns([T.any(T::Array[T.untyped], T::Hash[String, T.untyped]), T::Boolean]) .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) 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 end
sig { sig {

View File

@ -14,18 +14,25 @@ module Homebrew
DEFAULT_API_FILENAME = "formula.jws.json" 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 private_class_method :cache
sig { params(name: String).returns(T::Hash[String, T.untyped]) } sig { params(name: String).returns(T::Hash[String, T.untyped]) }
def self.fetch(name) def self.formula_json(name)
Homebrew::API.fetch "formula/#{name}.json" 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 end
sig { params(formula: ::Formula, download_queue: T.nilable(Homebrew::DownloadQueue)).returns(Homebrew::API::SourceDownload) } sig { params(formula: ::Formula, download_queue: T.nilable(Homebrew::DownloadQueue)).returns(Homebrew::API::SourceDownload) }
@ -63,7 +70,7 @@ module Homebrew
sig { returns(Pathname) } sig { returns(Pathname) }
def self.cached_json_file_path def self.cached_json_file_path
HOMEBREW_CACHE_API/api_filename HOMEBREW_CACHE_API/DEFAULT_API_FILENAME
end end
sig { sig {
@ -71,7 +78,7 @@ module Homebrew
.returns([T.any(T::Array[T.untyped], T::Hash[String, T.untyped]), T::Boolean]) .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) 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 end
sig { sig {
@ -147,13 +154,8 @@ module Homebrew
def self.write_names_and_aliases(regenerate: false) def self.write_names_and_aliases(regenerate: false)
download_and_cache_data! unless cache.key?("formulae") download_and_cache_data! unless cache.key?("formulae")
return unless Homebrew::API.write_names_file!(all_formulae.keys, "formula", regenerate:) Homebrew::API.write_names_file!(all_formulae.keys, "formula", regenerate:)
Homebrew::API.write_aliases_file!(all_aliases, "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
end end
end 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 class BottleSpecification
RELOCATABLE_CELLARS = [:any, :any_skip_relocation].freeze RELOCATABLE_CELLARS = [:any, :any_skip_relocation].freeze
sig { returns(T.nilable(Tap)) }
attr_accessor :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 } sig { void }
def initialize def initialize
@rebuild = 0 @rebuild = T.let(0, Integer)
@repository = Homebrew::DEFAULT_REPOSITORY @repository = T.let(Homebrew::DEFAULT_REPOSITORY, String)
@collector = Utils::Bottles::Collector.new @collector = T.let(Utils::Bottles::Collector.new, Utils::Bottles::Collector)
@root_url_specs = {} @root_url_specs = T.let({}, T::Hash[String, T.untyped])
@root_url = T.let(nil, T.nilable(String))
end end
sig { params(val: Integer).returns(T.nilable(Integer)) } sig { params(val: Integer).returns(Integer) }
def rebuild(val = T.unsafe(nil)) def rebuild(val = T.unsafe(nil))
val.nil? ? @rebuild : @rebuild = val val.nil? ? @rebuild : @rebuild = val
end end

View File

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

View File

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

View File

@ -227,7 +227,10 @@ class Build
end end
begin 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 args = Homebrew::Cmd::InstallCmd.new.args
Context.current = args.context Context.current = args.context
@ -268,7 +271,7 @@ rescue Exception => e # rubocop:disable Lint/RescueException
error_hash["output"] = e.output error_hash["output"] = e.output
end end
error_pipe.puts error_hash.to_json error_pipe&.puts error_hash.to_json
error_pipe.close error_pipe&.close
exit! 1 exit! 1
end end

View File

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

View File

@ -389,7 +389,7 @@ module Cask
end end
def uninstall_pkgutil(*pkgs, command: nil, **_) 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| pkgs.each do |regex|
::Cask::Pkg.all_matching(regex, command).each do |pkg| ::Cask::Pkg.all_matching(regex, command).each do |pkg|
puts pkg.package_id puts pkg.package_id

View File

@ -35,7 +35,7 @@ module Cask
private private
def run_installer(command: nil, verbose: false, **_options) 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? unless path.exist?
pkg = path.relative_path_from(cask.staged_path) pkg = path.relative_path_from(cask.staged_path)
pkgs = Pathname.glob(cask.staged_path/"**"/"*.pkg").map { |path| 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}/")) (target.realpath == source.realpath || target.realpath.to_s.start_with?("#{cask.caskroom_path}/"))
opoo "#{message}; overwriting." opoo "#{message}; overwriting."
Utils.gain_permissions_remove(target, command:) Utils.gain_permissions_remove(target, command:)
elsif (formula = conflicting_formula)
opoo "#{message} from formula #{formula}; skipping link."
return
else else
raise CaskError, "#{message}." raise CaskError, "#{message}."
end end
@ -69,11 +72,32 @@ module Cask
return unless target.symlink? return unless target.symlink?
ohai "Unlinking #{self.class.english_name} '#{target}'" 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:) Utils.gain_permissions_remove(target, command:)
end end
sig { params(command: T.class_of(SystemCommand)).void } sig { params(command: T.class_of(SystemCommand)).void }
def create_filesystem_link(command); end 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 end
end end

View File

@ -3,6 +3,8 @@
require "cask/denylist" require "cask/denylist"
require "cask/download" require "cask/download"
require "cask/installer"
require "cask/quarantine"
require "digest" require "digest"
require "livecheck/livecheck" require "livecheck/livecheck"
require "source_location" require "source_location"
@ -498,20 +500,32 @@ module Cask
return if url.nil? return if url.nil?
return if !cask.tap.official? && !signing? 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" odebug "Auditing signing"
is_in_skiplist = cask.tap&.audit_exception(:signing_audit_skiplist, cask.token)
extract_artifacts do |artifacts, tmpdir| extract_artifacts do |artifacts, tmpdir|
is_container = artifacts.any? { |a| a.is_a?(Artifact::App) || a.is_a?(Artifact::Pkg) } is_container = artifacts.any? { |a| a.is_a?(Artifact::App) || a.is_a?(Artifact::Pkg) }
artifacts.each do |artifact| any_signing_failure = artifacts.any? do |artifact|
next if artifact.is_a?(Artifact::Binary) && is_container == true next false if artifact.is_a?(Artifact::Binary) && is_container == true
artifact_path = artifact.is_a?(Artifact::Pkg) ? artifact.path : artifact.source artifact_path = artifact.is_a?(Artifact::Pkg) ? artifact.path : artifact.source
path = tmpdir/artifact_path.relative_path_from(cask.staged_path) 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 result = case artifact
when Artifact::Pkg when Artifact::Pkg
system_command("spctl", args: ["--assess", "--type", "install", path], print_stderr: false) 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) system_command("gktool", args: ["scan", path], print_stderr: false)
when Artifact::Binary when Artifact::Binary
# Shell scripts cannot be signed, so we skip them # 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], system_command("codesign", args: ["--verify", "-R=notarized", "--check-notarization", path],
print_stderr: false) print_stderr: false)
else else
add_error "Unknown artifact type: #{artifact.class}", location: url.location add_error "Unknown artifact type: #{artifact.class}", location: url.location
end end
if result.success? && cask.deprecated? && cask.deprecation_reason == :unsigned next false if result.success?
add_error "Cask is deprecated as unsigned but artifacts are signed!" next true if cask.deprecated? && cask.deprecation_reason == :fails_gatekeeper_check
end next true if is_in_skiplist
next if cask.deprecated? && cask.deprecation_reason == :unsigned
next if result.success?
add_error <<~EOS, location: url.location add_error <<~EOS, location: url.location
Signature verification failed: Signature verification failed:
@ -543,7 +553,21 @@ module Cask
macOS on ARM requires software to be signed. macOS on ARM requires software to be signed.
Please contact the upstream developer to let them know they should sign and notarize their software. Please contact the upstream developer to let them know they should sign and notarize their software.
EOS EOS
true
end 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
end end
@ -619,6 +643,11 @@ module Cask
.extract_nestedly(to: @tmpdir, verbose: false) .extract_nestedly(to: @tmpdir, verbose: false)
end 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. # Set the flag to indicate that extraction has occurred.
@artifacts_extracted = T.let(true, T.nilable(TrueClass)) @artifacts_extracted = T.let(true, T.nilable(TrueClass))
@ -640,10 +669,20 @@ module Cask
extract_artifacts do |artifacts, tmpdir| extract_artifacts do |artifacts, tmpdir|
is_container = artifacts.any? { |a| a.is_a?(Artifact::App) || a.is_a?(Artifact::Pkg) } is_container = artifacts.any? { |a| a.is_a?(Artifact::App) || a.is_a?(Artifact::Pkg) }
artifacts.each do |artifact| mentions_rosetta = cask.caveats.include?("requires Rosetta 2")
next if !artifact.is_a?(Artifact::App) && !artifact.is_a?(Artifact::Binary) requires_intel = cask.depends_on.arch&.any? { |arch| arch[:type] == :intel }
next if artifact.is_a?(Artifact::Binary) && is_container
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) path = tmpdir/artifact.source.relative_path_from(cask.staged_path)
result = case artifact result = case artifact
@ -665,7 +704,7 @@ module Cask
end end
# binary stanza can contain shell scripts, so we just continue if lipo fails. # 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}" odebug "Architectures: #{result.merged_output}"
@ -675,17 +714,17 @@ module Cask
next next
end end
supports_arm = result.merged_output.include?("arm64") result.merged_output.exclude?("arm64") && result.merged_output.include?("x86_64")
mentions_rosetta = cask.caveats.include?("requires Rosetta 2") end
requires_intel = cask.depends_on.arch&.any? { |arch| arch[:type] == :intel }
if supports_arm && mentions_rosetta if any_requires_rosetta
add_error "Artifacts do not require Rosetta 2 but the caveats say otherwise!", if !mentions_rosetta && !requires_intel
location: url.location add_error "At least one artifact requires Rosetta 2 but this is not indicated by the caveats!",
elsif !supports_arm && !mentions_rosetta && !requires_intel
add_error "Artifacts require Rosetta 2 but this is not indicated by the caveats!",
location: url.location location: url.location
end end
elsif mentions_rosetta
add_error "No artifacts require Rosetta 2 but the caveats say otherwise!",
location: url.location
end end
end end
end end
@ -896,6 +935,20 @@ module Cask
add_error error, location: url.location if error add_error error, location: url.location if error
end 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 } sig { void }
def audit_github_repository_archived def audit_github_repository_archived
# Deprecated/disabled casks may have an archived repository. # 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"] add_error "GitLab repo is archived", location: url.location if metadata["archived"]
end 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 } sig { void }
def audit_github_repository def audit_github_repository
return unless new_cask? return unless new_cask?
@ -970,6 +1040,20 @@ module Cask
add_error error, location: url.location if error add_error error, location: url.location if error
end 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 } sig { void }
def audit_denylist def audit_denylist
return unless cask.tap return unless cask.tap

View File

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

View File

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

View File

@ -19,6 +19,7 @@ require "cask/dsl/container"
require "cask/dsl/depends_on" require "cask/dsl/depends_on"
require "cask/dsl/postflight" require "cask/dsl/postflight"
require "cask/dsl/preflight" require "cask/dsl/preflight"
require "cask/dsl/rename"
require "cask/dsl/uninstall_postflight" require "cask/dsl/uninstall_postflight"
require "cask/dsl/uninstall_preflight" require "cask/dsl/uninstall_preflight"
require "cask/dsl/version" require "cask/dsl/version"
@ -81,6 +82,7 @@ module Cask
:language, :language,
:name, :name,
:os, :os,
:rename,
:sha256, :sha256,
:staged_path, :staged_path,
:url, :url,
@ -162,6 +164,7 @@ module Cask
@on_system_block_min_os = T.let(nil, T.nilable(MacOSVersion)) @on_system_block_min_os = T.let(nil, T.nilable(MacOSVersion))
@os = T.let(nil, T.nilable(String)) @os = T.let(nil, T.nilable(String))
@os_set_in_block = T.let(false, T::Boolean) @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 = T.let(nil, T.nilable(T.any(Checksum, Symbol)))
@sha256_set_in_block = T.let(false, T::Boolean) @sha256_set_in_block = T.let(false, T::Boolean)
@staged_path = T.let(nil, T.nilable(Pathname)) @staged_path = T.let(nil, T.nilable(Pathname))
@ -343,6 +346,28 @@ module Cask
end end
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. # Sets the cask's version.
# #
# ### Example # ### Example
@ -600,6 +625,9 @@ module Cask
raise ArgumentError, "more than one of replacement, replacement_formula and/or replacement_cask specified!" raise ArgumentError, "more than one of replacement, replacement_formula and/or replacement_cask specified!"
end end
# odeprecate: remove this remapping when the :unsigned reason is removed
because = :fails_gatekeeper_check if because == :unsigned
if replacement if replacement
odeprecated( odeprecated(
"deprecate!(:replacement)", "deprecate!(:replacement)",
@ -626,6 +654,9 @@ module Cask
raise ArgumentError, "more than one of replacement, replacement_formula and/or replacement_cask specified!" raise ArgumentError, "more than one of replacement, replacement_formula and/or replacement_cask specified!"
end end
# odeprecate: remove this remapping when the :unsigned reason is removed
because = :fails_gatekeeper_check if because == :unsigned
if replacement if replacement
odeprecated( odeprecated(
"disable!(:replacement)", "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:) def self.info(cask, args:)
puts get_info(cask) puts get_info(cask)
return unless cask.tap.core_cask_tap?
require "utils/analytics" require "utils/analytics"
::Utils::Analytics.cask_output(cask, args:) ::Utils::Analytics.cask_output(cask, args:)
end end

View File

@ -125,6 +125,7 @@ module Cask
Caskroom.ensure_caskroom_exists Caskroom.ensure_caskroom_exists
extract_primary_container extract_primary_container
process_rename_operations
save_caskfile save_caskfile
rescue => e rescue => e
purge_versioned_files purge_versioned_files
@ -292,6 +293,19 @@ on_request: true)
Quarantine.propagate(from: primary_container.path, to:) Quarantine.propagate(from: primary_container.path, to:)
end 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 } sig { params(predecessor: T.nilable(Cask)).void }
def install_artifacts(predecessor: nil) def install_artifacts(predecessor: nil)
already_installed_artifacts = [] already_installed_artifacts = []

View File

@ -40,11 +40,12 @@ module Cask
end end
private_class_method :swift_target_args private_class_method :swift_target_args
sig { returns(Symbol) } sig { returns([Symbol, T.nilable(String)]) }
def self.check_quarantine_support def self.check_quarantine_support
odebug "Checking 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." odebug "There's no working version of `xattr` on this system."
:xattr_broken :xattr_broken
elsif swift.nil? elsif swift.nil?
@ -55,21 +56,41 @@ module Cask
args: [*swift_target_args, QUARANTINE_SCRIPT], args: [*swift_target_args, QUARANTINE_SCRIPT],
print_stderr: false) 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 when 2
odebug "Quarantine is available." odebug "Quarantine is available."
:quarantine_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 else
odebug "Unknown support status" odebug "Swift returned unexpected exit status: #{exit_status}"
:unknown :swift_unexpected_error
end end
end end
[status, check_output]
end end
sig { returns(T::Boolean) }
def self.available? def self.available?
@status ||= check_quarantine_support @quarantine_support ||= check_quarantine_support
@status == :quarantine_available @quarantine_support[0] == :quarantine_available
end end
def self.detect(file) def self.detect(file)

View File

@ -25,7 +25,7 @@ module Cask
full_paths = remove_nonexistent(paths) full_paths = remove_nonexistent(paths)
return if full_paths.empty? 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], @command.run!("/usr/sbin/chown", args: ["-R", "--", "#{user}:#{group}", *full_paths],
sudo: true) sudo: true)
end end

View File

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

View File

@ -171,7 +171,7 @@ module Homebrew
stable = formula.stable stable = formula.stable
if resource_name == "patch" 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)) return true unless patch_hashes&.include?(Checksum.new(version.to_s))
elsif resource_name && stable && (resource_version = stable.resources[resource_name]&.version) elsif resource_name && stable && (resource_version = stable.resources[resource_name]&.version)
return true if resource_version != version return true if resource_version != version

View File

@ -207,13 +207,13 @@ module Homebrew
if formula_path.exist? || if formula_path.exist? ||
(!Homebrew::EnvConfig.no_install_from_api? && (!Homebrew::EnvConfig.no_install_from_api? &&
!CoreTap.instance.installed? && !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 paths << formula_path
end end
if cask_path.exist? || if cask_path.exist? ||
(!Homebrew::EnvConfig.no_install_from_api? && (!Homebrew::EnvConfig.no_install_from_api? &&
!CoreCaskTap.instance.installed? && !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 paths << cask_path
end end

View File

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

View File

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

View File

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

View File

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

View File

@ -181,7 +181,7 @@ module Homebrew
# `build.rb`. Instead, `hide_from_man_page` and don't do anything with # `build.rb`. Instead, `hide_from_man_page` and don't do anything with
# this argument here. # this argument here.
# This odisabled should stick around indefinitely. # 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 end
args.named.each do |name| 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). 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). 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). 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`. 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). 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). 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). 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), Stop the service <formula> immediately and unregister it from launching at login (or boot),
unless `--keep` is specified. unless `--keep` is specified.
[`sudo`] `brew services kill` (<formula>|`--all`): [`sudo`] `brew services kill` (<formula>|`--all`):
Stop the service <formula> immediately but keep it registered to launch at login (or boot). 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). Stop (if necessary) and start the service <formula> immediately and register it to launch at login (or boot).
[`sudo`] `brew services cleanup`: [`sudo`] `brew services cleanup`:
@ -58,15 +58,16 @@ module Homebrew
flag "--max-wait=", flag "--max-wait=",
description: "Wait at most this many seconds for `stop` to finish stopping a service. " \ 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." "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", switch "--no-wait",
description: "Don't wait for `stop` to finish stopping the service." description: "Don't wait for `stop` to finish stopping the service."
switch "--keep", switch "--keep",
description: "When stopped, don't unregister the service from launching at login (or boot)." 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" conflicts "--max-wait=", "--no-wait"
named_args %w[list info run start stop kill restart cleanup] named_args %w[list info run start stop kill restart cleanup]
@ -112,7 +113,7 @@ module Homebrew
] ]
if no_named_formula_commands.include?(subcommand) 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 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 end
if args.file if args.file
@ -122,22 +123,22 @@ module Homebrew
*Homebrew::Services::Commands::Restart::TRIGGERS, *Homebrew::Services::Commands::Restart::TRIGGERS,
] ]
if file_commands.exclude?(subcommand) if file_commands.exclude?(subcommand)
raise UsageError, "The `#{subcommand}` subcommand does not accept the --file= argument!" 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!"
end end
end end
unless Homebrew::Services::Commands::Stop::TRIGGERS.include?(subcommand) 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 `--keep` argument!" if args.keep?
raise UsageError, "The `#{subcommand}` subcommand does not accept the --no-wait argument!" if args.no_wait?
if args.no_wait?
raise UsageError, "The `#{subcommand}` subcommand does not accept the `--no-wait` argument!"
end
if args.max_wait 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
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? targets = if args.all?
if subcommand == "start" if subcommand == "start"

View File

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

View File

@ -126,7 +126,7 @@ module Homebrew
sig { override.void } sig { override.void }
def run def run
if args.build_from_source? && args.named.empty? 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 end
formulae, casks = args.named.to_resolved_formulae_to_casks 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 # Non-Apple compilers are in the format fails_with compiler => version
if spec.is_a?(Hash) if spec.is_a?(Hash)
compiler, major_version = spec.first 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 type = compiler
# so fails_with :gcc => '7' simply marks all 7 releases incompatible # 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): # - For changes to a command under `COMMANDS` or `DEVELOPER COMMANDS` sections):
# - Find the source file in `Library/Homebrew/[dev-]cmd/<command>.{rb,sh}`. # - 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 `.sh` files, edit the top comment, being sure to use the line prefix
# `#:` for the comments to be recognized as documentation. If in doubt, # `#:` for the comments to be recognized as documentation. If in doubt,
# compare with already documented commands. # compare with already documented commands.

View File

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

View File

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

View File

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

View File

@ -80,10 +80,10 @@ class DependencyCollector
parse_spec(spec, Array(tags)) parse_spec(spec, Array(tags))
end 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 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 glibc_dep_if_needed(related_formula_names); end
def git_dep_if_needed(tags) def git_dep_if_needed(tags)
@ -169,7 +169,7 @@ class DependencyCollector
when :maximum_macos then MacOSRequirement.new(tags, comparator: "<=") when :maximum_macos then MacOSRequirement.new(tags, comparator: "<=")
when :xcode then XcodeRequirement.new(tags) when :xcode then XcodeRequirement.new(tags)
else else
raise ArgumentError, "Unsupported special dependency #{spec.inspect}" raise ArgumentError, "Unsupported special dependency: #{spec.inspect}"
end end
end end

View File

@ -27,6 +27,8 @@ module DeprecateDisable
no_longer_available: "is no longer available upstream", no_longer_available: "is no longer available upstream",
no_longer_meets_criteria: "no longer meets the criteria for acceptable casks", no_longer_meets_criteria: "no longer meets the criteria for acceptable casks",
unmaintained: "is not maintained upstream", 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", unsigned: "is unsigned or does not meet signature requirements",
}.freeze, T::Hash[Symbol, String]) }.freeze, T::Hash[Symbol, String])
@ -67,6 +69,9 @@ module DeprecateDisable
formula_or_cask.disable_reason formula_or_cask.disable_reason
end 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) reason = if formula_or_cask.is_a?(Formula) && FORMULA_DEPRECATE_DISABLE_REASONS.key?(reason)
FORMULA_DEPRECATE_DISABLE_REASONS[reason] FORMULA_DEPRECATE_DISABLE_REASONS[reason]
elsif formula_or_cask.is_a?(Cask::Cask) && CASK_DEPRECATE_DISABLE_REASONS.key?(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 } sig { override.void }
def run def run
odeprecated "brew audit --token-conflicts" if args.token_conflicts? odeprecated "`brew audit --token-conflicts`" if args.token_conflicts?
Formulary.enable_factory_cache! Formulary.enable_factory_cache!
@ -142,7 +142,7 @@ module Homebrew
unless eval_all unless eval_all
# This odisabled should probably stick around indefinitely. # This odisabled should probably stick around indefinitely.
odisabled "brew audit", odisabled "`brew audit`",
"`brew audit --eval-all` or set `HOMEBREW_EVAL_ALL=1`" "`brew audit --eval-all` or set `HOMEBREW_EVAL_ALL=1`"
end end
no_named_args = true no_named_args = true
@ -154,8 +154,8 @@ module Homebrew
if args.named.any? { |named_arg| named_arg.end_with?(".rb") } if args.named.any? { |named_arg| named_arg.end_with?(".rb") }
# This odisabled should probably stick around indefinitely, # This odisabled should probably stick around indefinitely,
# until at least we have a way to exclude error on these in the CLI parser. # until at least we have a way to exclude error on these in the CLI parser.
odisabled "brew audit [path ...]", odisabled "`brew audit [path ...]`",
"brew audit [name ...]" "`brew audit [name ...]`"
end end
args.named.to_formulae_and_casks_with_taps 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 ohai "Detecting if #{local_filename} is relocatable..." if bottle_path.size > 1 * 1024 * 1024
prefix_check = if prefix == HOMEBREW_DEFAULT_PREFIX is_usr_local_prefix = prefix == "/usr/local"
File.join(prefix, "opt") prefix_check = if is_usr_local_prefix
"#{prefix}/opt"
else else
prefix prefix
end 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?(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?(cellar, keg, ignores, formula_and_runtime_deps_names)
relocatable = false if keg_contain?(HOMEBREW_LIBRARY.to_s, 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_absolute_symlink_starting_with?(prefix, keg)
relocatable = false if keg_contain?("#{prefix}/etc", keg, ignores) if tap.disabled_new_usr_local_relocation_formulae.exclude?(formula.name)
relocatable = false if keg_contain?("#{prefix}/var", keg, ignores) keg.new_usr_local_replacement_pairs.each_value do |value|
relocatable = false if keg_contain?("#{prefix}/share/vim", keg, ignores) 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 end
skip_relocation = relocatable && !keg.require_relocation? skip_relocation = relocatable && !keg.require_relocation?
end end

View File

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

View File

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

View File

@ -379,6 +379,7 @@ module Homebrew
when /^13\.?/ then "macOS Ventura (13)" when /^13\.?/ then "macOS Ventura (13)"
when /^14\.?/ then "macOS Sonoma (14)" when /^14\.?/ then "macOS Sonoma (14)"
when /^15\.?/ then "macOS Sequoia (15)" 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)? (14|16|18|20|22)\.04/ then "Ubuntu #{Regexp.last_match(2)}.04 LTS"
when /Ubuntu(-Server)? (\d+\.\d+).\d ?(LTS)?/ when /Ubuntu(-Server)? (\d+\.\d+).\d ?(LTS)?/
"Ubuntu #{Regexp.last_match(2)} #{Regexp.last_match(3)}".strip "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? File.write("_data/cask_canonical.json", "#{canonical_json}\n") unless args.dry_run?
OnSystem::VALID_OS_ARCH_TAGS.each do |bottle_tag| OnSystem::VALID_OS_ARCH_TAGS.each do |bottle_tag|
variation_casks = all_casks.map do |_, cask| renames = {}
Homebrew::API.merge_variations(cask, bottle_tag:) 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 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 end
end end

View File

@ -57,15 +57,15 @@ module Homebrew
syntax_only = args.syntax_only? syntax_only = args.syntax_only?
repository = ENV.fetch("GITHUB_REPOSITORY", nil) 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) tap = T.let(Tap.fetch(repository), Tap)
unless syntax_only unless syntax_only
raise UsageError, "Either `--cask` or `--url` must be specified." if !args.casks? && !args.url? 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 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) labels = if pr_url && (first_pr_url = pr_url.first)
pr = GitHub::API.open_rest(first_pr_url) 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? File.write("_data/formula_canonical.json", "#{canonical_json}\n") unless args.dry_run?
OnSystem::VALID_OS_ARCH_TAGS.each do |bottle_tag| OnSystem::VALID_OS_ARCH_TAGS.each do |bottle_tag|
aliases = {}
renames = {}
variation_formulae = all_formulae.to_h do |name, formula| variation_formulae = all_formulae.to_h do |name, formula|
formula = Homebrew::API.merge_variations(formula, bottle_tag:) 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")) version = Version.new(formula.dig("versions", "stable"))
pkg_version = PkgVersion.new(version, formula["revision"]) pkg_version = PkgVersion.new(version, formula["revision"])
rebuild = formula.dig("bottle", "stable", "rebuild") || 0 rebuild = formula.dig("bottle", "stable", "rebuild") || 0
@ -87,9 +97,14 @@ module Homebrew
[name, [pkg_version.to_s, rebuild, sha256]] [name, [pkg_version.to_s, rebuild, sha256]]
end end
unless args.dry_run? json_contents = {
File.write("api/internal/formula.#{bottle_tag}.json", JSON.generate(variation_formulae)) formulae: variation_formulae,
end 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 end
end end

View File

@ -1000,12 +1000,12 @@ module Homebrew
locale_variables = ENV.keys.grep(/^(?:LC_\S+|LANG|LANGUAGE)\Z/).sort 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) next unless ENV.key?(var)
var = %Q(#{var}="#{ENV.fetch(var)}") var = %Q(#{var}="#{ENV.fetch(var)}")
user_tilde(var) user_tilde(var)
end) end
end end
def check_cask_xattr def check_cask_xattr
@ -1040,21 +1040,6 @@ module Homebrew
end end
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 def non_core_taps
@non_core_taps ||= Tap.installed.reject(&:core_tap?).reject(&:core_cask_tap?) @non_core_taps ||= Tap.installed.reject(&:core_tap?).reject(&:core_cask_tap?)
end end
@ -1124,6 +1109,13 @@ module Homebrew
def current_user def current_user
ENV.fetch("USER", "$(whoami)") ENV.fetch("USER", "$(whoami)")
end 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 end
end end

View File

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

View File

@ -32,7 +32,7 @@ module Downloadable
@download_name = T.let(nil, T.nilable(String)) @download_name = T.let(nil, T.nilable(String))
end end
sig { params(other: Object).void } sig { overridable.params(other: Downloadable).void }
def initialize_dup(other) def initialize_dup(other)
super super
@checksum = @checksum.dup @checksum = @checksum.dup
@ -132,6 +132,26 @@ module Downloadable
EOS EOS
end 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 private
sig { overridable.returns(String) } sig { overridable.returns(String) }

View File

@ -241,9 +241,10 @@ module Homebrew
boolean: true, boolean: true,
}, },
HOMEBREW_FORBID_PACKAGES_FROM_PATHS: { HOMEBREW_FORBID_PACKAGES_FROM_PATHS: {
description: "If set, Homebrew will refuse to read formulae or casks provided from file paths, " \ description: "If set, Homebrew will refuse to read formulae or casks provided from file paths, " \
"e.g. `brew install ./package.rb`.", "e.g. `brew install ./package.rb`.",
boolean: true, boolean: true,
default_text: "true unless `$HOMEBREW_DEVELOPER` is set.",
}, },
HOMEBREW_FORCE_API_AUTO_UPDATE: { HOMEBREW_FORCE_API_AUTO_UPDATE: {
description: "If set, update the Homebrew API formula or cask data even if " \ description: "If set, update the Homebrew API formula or cask data even if " \
@ -560,6 +561,7 @@ module Homebrew
:HOMEBREW_MAKE_JOBS, :HOMEBREW_MAKE_JOBS,
:HOMEBREW_NO_FORCE_BREW_WRAPPER, :HOMEBREW_NO_FORCE_BREW_WRAPPER,
:HOMEBREW_CASK_OPTS, :HOMEBREW_CASK_OPTS,
:HOMEBREW_FORBID_PACKAGES_FROM_PATHS,
]).freeze, T::Set[Symbol]) ]).freeze, T::Set[Symbol])
FALSY_VALUES = T.let(%w[false no off nil 0].freeze, T::Array[String]) 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") cask_opts.include?("--require-sha")
end 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) } sig { returns(T::Boolean) }
def automatically_set_no_install_from_api? def automatically_set_no_install_from_api?
ENV["HOMEBREW_AUTOMATICALLY_SET_NO_INSTALL_FROM_API"].present? ENV["HOMEBREW_AUTOMATICALLY_SET_NO_INSTALL_FROM_API"].present?
@ -683,5 +697,10 @@ module Homebrew
[concurrency, 1].max [concurrency, 1].max
end end
sig { returns(T::Boolean) }
def use_internal_api?
ENV["HOMEBREW_USE_INTERNAL_API"].present?
end
end end
end end

View File

@ -267,7 +267,7 @@ module SharedEnvExtension
ohai "Using a Fortran compiler found at #{gfortran}" ohai "Using a Fortran compiler found at #{gfortran}"
end end
if gfortran 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 self["FC"] = self["F77"] = gfortran
flags = FC_FLAG_VARS flags = FC_FLAG_VARS
end end

View File

@ -44,11 +44,13 @@ module Kernel
Formatter.headline(title, color: :blue) Formatter.headline(title, color: :blue)
end end
sig { params(title: T.any(String, Exception), sput: T.anything).void }
def ohai(title, *sput) def ohai(title, *sput)
puts ohai_title(title.to_s) puts ohai_title(title.to_s)
puts sput puts sput
end end
sig { params(title: T.any(String, Exception), sput: T.anything, always_display: T::Boolean).void }
def odebug(title, *sput, always_display: false) def odebug(title, *sput, always_display: false)
debug = if respond_to?(:debug) debug = if respond_to?(:debug)
T.unsafe(self).debug? T.unsafe(self).debug?
@ -355,19 +357,6 @@ module Kernel
nil nil
end 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) def which_editor(silent: false)
editor = Homebrew::EnvConfig.editor editor = Homebrew::EnvConfig.editor
return editor if editor return editor if editor
@ -458,11 +447,6 @@ module Kernel
Formula[formula_name].ensure_installed!(reason:, latest:).opt_bin/name Formula[formula_name].ensure_installed!(reason:, latest:).opt_bin/name
end 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) } sig { params(size_in_bytes: T.any(Integer, Float)).returns(String) }
def disk_usage_readable(size_in_bytes) def disk_usage_readable(size_in_bytes)
if size_in_bytes.abs >= 1_073_741_824 if size_in_bytes.abs >= 1_073_741_824

View File

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

View File

@ -10,6 +10,7 @@ require "os/linux/kernel"
module OS module OS
module Linux module Linux
module Diagnostic module Diagnostic
# Linux-specific diagnostic checks for Homebrew.
module Checks module Checks
extend T::Helpers extend T::Helpers
@ -28,6 +29,7 @@ module OS
check_glibc_minimum_version check_glibc_minimum_version
check_kernel_minimum_version check_kernel_minimum_version
check_supported_architecture check_supported_architecture
check_for_symlinked_home
].freeze ].freeze
end end
@ -154,6 +156,27 @@ module OS
EOS EOS
end 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 def check_gcc_dependent_linkage
gcc_dependents = ::Formula.installed.select do |formula| gcc_dependents = ::Formula.installed.select do |formula|
next false unless formula.tap&.core_tap? 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 # frozen_string_literal: true
module OS module OS
@ -30,6 +30,7 @@ module OS
def add_global_deps_to_spec(spec) def add_global_deps_to_spec(spec)
return unless ::DevelopmentTools.needs_build_formulae? return unless ::DevelopmentTools.needs_build_formulae?
@global_deps ||= T.let(nil, T.nilable(T::Array[Dependency]))
@global_deps ||= begin @global_deps ||= begin
dependency_collector = spec.dependency_collector dependency_collector = spec.dependency_collector
related_formula_names = Set.new([ related_formula_names = Set.new([

View File

@ -4,7 +4,7 @@
require "compilers" require "compilers"
class Keg class Keg
def relocate_dynamic_linkage(relocation) def relocate_dynamic_linkage(relocation, skip_protodesc_cold: false)
# Patching the dynamic linker of glibc breaks it. # Patching the dynamic linker of glibc breaks it.
return if name.match? Version.formula_optionally_versioned_regex(:glibc) return if name.match? Version.formula_optionally_versioned_regex(:glibc)
@ -12,14 +12,19 @@ class Keg
elf_files.each do |file| elf_files.each do |file|
file.ensure_writable do file.ensure_writable do
change_rpath!(file, old_prefix, new_prefix) change_rpath!(file, old_prefix, new_prefix, skip_protodesc_cold:)
end end
end 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? 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 = {} updated = {}
old_rpath = file.rpath old_rpath = file.rpath
new_rpath = if old_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 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. 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. directory on the same volume as your Cellar.
#{support_tier_message(tier: 2)} #{support_tier_message(tier: 2)}
@ -506,6 +506,88 @@ module OS
nil nil
end 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 end
end end

View File

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

View File

@ -557,6 +557,12 @@ class Formula
# @see .loaded_from_api? # @see .loaded_from_api?
delegate loaded_from_api?: :"self.class" 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 } sig { void }
def update_head_version def update_head_version
return unless head? return unless head?
@ -1675,7 +1681,10 @@ class Formula
# don't consider this keg current if there's a newer formula available # don't consider this keg current if there's a newer formula available
next if follow_installed_alias? && new_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 current_version = true
break break
end end
@ -2625,9 +2634,8 @@ class Formula
hash = to_hash hash = to_hash
# Take from API, merging in local install status. # Take from API, merging in local install status.
if loaded_from_api? && !Homebrew::EnvConfig.no_install_from_api? if loaded_from_api? && (json_formula = api_source) && !Homebrew::EnvConfig.no_install_from_api?
json_formula = Homebrew::API::Formula.all_formulae.fetch(name).dup return json_formula.dup.merge(
return json_formula.merge(
hash.slice("name", "installed", "linked_keg", "pinned", "outdated"), hash.slice("name", "installed", "linked_keg", "pinned", "outdated"),
) )
end end
@ -3062,12 +3070,12 @@ class Formula
@exec_count ||= T.let(0, T.nilable(Integer)) @exec_count ||= T.let(0, T.nilable(Integer))
@exec_count += 1 @exec_count += 1
logfn = format("#{logs}/#{active_log_prefix}%02<exec_count>d.%<cmd_base>s", log_filename = format("#{logs}/#{active_log_prefix}%02<exec_count>d.%<cmd_base>s.log",
exec_count: @exec_count, exec_count: @exec_count,
cmd_base: File.basename(cmd).split.first) cmd_base: File.basename(cmd).split.first)
logs.mkpath logs.mkpath
File.open(logfn, "w") do |log| File.open(log_filename, "w") do |log|
log.puts Time.now, "", cmd, args, "" log.puts Time.now, "", cmd, args, ""
log.flush log.flush
@ -3077,7 +3085,7 @@ class Formula
pid = fork do pid = fork do
rd.close rd.close
log.close log.close
exec_cmd(cmd, args, wr, logfn) exec_cmd(cmd, args, wr, log_filename)
end end
wr.close wr.close
@ -3104,7 +3112,7 @@ class Formula
end end
else else
pid = fork do pid = fork do
exec_cmd(cmd, args, log, logfn) exec_cmd(cmd, args, log, log_filename)
end end
end end
@ -3117,8 +3125,8 @@ class Formula
log.flush log.flush
if !verbose? || verbose_using_dots if !verbose? || verbose_using_dots
puts "Last #{log_lines} lines from #{logfn}:" puts "Last #{log_lines} lines from #{log_filename}:"
Kernel.system "/usr/bin/tail", "-n", log_lines.to_s, logfn Kernel.system "/usr/bin/tail", "-n", log_lines.to_s, log_filename
end end
log.puts log.puts
@ -3259,14 +3267,14 @@ class Formula
sig { sig {
params( params(
cmd: T.any(String, Pathname), cmd: T.any(String, Pathname),
args: T::Array[T.any(String, Integer, Pathname, Symbol)], args: T::Array[T.any(String, Integer, Pathname, Symbol)],
out: IO, out: IO,
logfn: T.nilable(String), log_filename: T.nilable(String),
).void ).void
} }
def exec_cmd(cmd, args, out, logfn) def exec_cmd(cmd, args, out, log_filename)
ENV["HOMEBREW_CC_LOG_PATH"] = logfn ENV["HOMEBREW_CC_LOG_PATH"] = log_filename
ENV.remove_cc_etc if cmd.to_s.start_with? "xcodebuild" 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)])) @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])) @link_overwrite_paths = T.let(Set.new, T.nilable(T::Set[String]))
@loaded_from_api = T.let(false, T.nilable(T::Boolean)) @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)) @on_system_blocks_exist = T.let(false, T.nilable(T::Boolean))
@network_access_allowed = T.let(SUPPORTED_NETWORK_ACCESS_PHASES.to_h do |phase| @network_access_allowed = T.let(SUPPORTED_NETWORK_ACCESS_PHASES.to_h do |phase|
[phase, DEFAULT_NETWORK_ACCESS_ALLOWED] [phase, DEFAULT_NETWORK_ACCESS_ALLOWED]
@ -3382,6 +3391,10 @@ class Formula
sig { returns(T::Boolean) } sig { returns(T::Boolean) }
def loaded_from_api? = !!@loaded_from_api 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 # 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`). # (e.g. `on_macos`, `on_arm`, `on_monterey :or_older`, `on_system :linux, macos: :big_sur_or_newer`).
sig { returns(T::Boolean) } sig { returns(T::Boolean) }
@ -3797,12 +3810,12 @@ class Formula
# If called as a method this provides just the {url} for the {SoftwareSpec}. # 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}. # 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}. # 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 # ### Example
# #
# ```ruby # ```ruby
# head "https://we.prefer.https.over.git.example.com/.git" # head "https://we.prefer.https.over.git.example.com/.git", branch: "main"
# ``` # ```
# #
# ```ruby # ```ruby

View File

@ -677,6 +677,19 @@ module Homebrew
problem "GitLab repository is archived" if metadata["archived"] problem "GitLab repository is archived" if metadata["archived"]
end 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 def audit_github_repository
user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*}) if @new_formula user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*}) if @new_formula
@ -708,6 +721,17 @@ module Homebrew
new_formula_problem warning new_formula_problem warning
end 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) def get_repo_data(regex)
return unless @core_tap return unless @core_tap
return unless @online return unless @online
@ -791,7 +815,7 @@ module Homebrew
formula_suffix = stable.version.patch.to_i formula_suffix = stable.version.patch.to_i
throttled_rate = formula.livecheck.throttle throttled_rate = formula.livecheck.throttle
if throttled_rate && formula_suffix.modulo(throttled_rate).nonzero? 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 end
case (url = stable.url) case (url = stable.url)
@ -839,6 +863,16 @@ module Homebrew
error = SharedAudits.github_release(owner, repo, tag, formula:) error = SharedAudits.github_release(owner, repo, tag, formula:)
problem error if error problem error if error
end 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
end end

View File

@ -382,7 +382,7 @@ class FormulaInstaller
check_installation_already_attempted check_installation_already_attempted
if force_bottle? && !pour_bottle? 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 end
if Homebrew.default_prefix? && if Homebrew.default_prefix? &&
@ -477,7 +477,7 @@ class FormulaInstaller
raise CannotInstallFormulaError, raise CannotInstallFormulaError,
"You must `brew unpin #{pinned_unsatisfied_deps * " "}` as installing " \ "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 end
sig { params(_formula: Formula).returns(T.nilable(T::Boolean)) } 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 # We rescue these so that we can skip bad versions and
# continue walking the history # continue walking the history
odebug "#{e} in #{name} at revision #{revision}", Utils::Backtrace.clean(e) odebug "#{e} in #{name} at revision #{revision}", Utils::Backtrace.clean(e)
nil
rescue FormulaUnavailableError rescue FormulaUnavailableError
nil nil
ensure ensure

View File

@ -173,9 +173,9 @@ module Formulary
platform_cache[:path][path] = klass platform_cache[:path][path] = klass
end end
sig { params(name: String, flags: T::Array[String]).returns(T.class_of(Formula)) } 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_api!(name, flags:) def self.load_formula_from_json!(name, json_formula_with_variations, flags:)
namespace = :"FormulaNamespaceAPI#{namespace_key(name)}" namespace = :"FormulaNamespaceAPI#{namespace_key(json_formula_with_variations.to_json)}"
mod = Module.new mod = Module.new
remove_const(namespace) if const_defined?(namespace) remove_const(namespace) if const_defined?(namespace)
@ -184,10 +184,7 @@ module Formulary
mod.const_set(:BUILD_FLAGS, flags) mod.const_set(:BUILD_FLAGS, flags)
class_name = class_s(name) class_name = class_s(name)
json_formula = Homebrew::API::Formula.all_formulae[name] json_formula = Homebrew::API.merge_variations(json_formula_with_variations)
raise FormulaUnavailableError, name if json_formula.nil?
json_formula = Homebrew::API.merge_variations(json_formula)
uses_from_macos_names = json_formula.fetch("uses_from_macos", []).map do |dep| uses_from_macos_names = json_formula.fetch("uses_from_macos", []).map do |dep|
next dep unless dep.is_a? Hash next dep unless dep.is_a? Hash
@ -273,6 +270,7 @@ module Formulary
# rubocop:todo Sorbet/BlockMethodDefinition # rubocop:todo Sorbet/BlockMethodDefinition
klass = Class.new(::Formula) do klass = Class.new(::Formula) do
@loaded_from_api = true @loaded_from_api = true
@api_source = json_formula_with_variations
desc json_formula["desc"] desc json_formula["desc"]
homepage json_formula["homepage"] homepage json_formula["homepage"]
@ -618,8 +616,28 @@ module Formulary
return unless path.expand_path.exist? return unless path.expand_path.exist?
return if Homebrew::EnvConfig.forbid_packages_from_paths? && if Homebrew::EnvConfig.forbid_packages_from_paths?
!path.realpath.to_s.start_with?("#{HOMEBREW_CELLAR}/", "#{HOMEBREW_LIBRARY}/Taps/") 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)) if (tap = Tap.from_path(path))
# Only treat symlinks in taps as aliases. # Only treat symlinks in taps as aliases.
@ -693,7 +711,7 @@ module Formulary
if ALLOWED_URL_SCHEMES.exclude?(url_scheme) if ALLOWED_URL_SCHEMES.exclude?(url_scheme)
raise UnsupportedInstallationMethod, raise UnsupportedInstallationMethod,
"Non-checksummed download of #{name} formula file from an arbitrary URL is unsupported! " \ "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." "on GitHub instead."
end end
HOMEBREW_CACHE_FORMULA.mkpath HOMEBREW_CACHE_FORMULA.mkpath
@ -877,9 +895,9 @@ module Formulary
return if Homebrew::EnvConfig.no_install_from_api? return if Homebrew::EnvConfig.no_install_from_api?
return unless ref.is_a?(String) return unless ref.is_a?(String)
return unless (name = ref[HOMEBREW_DEFAULT_TAP_FORMULA_REGEX, :name]) return unless (name = ref[HOMEBREW_DEFAULT_TAP_FORMULA_REGEX, :name])
if !Homebrew::API::Formula.all_formulae.key?(name) && if Homebrew::API.formula_names.exclude?(name) &&
!Homebrew::API::Formula.all_aliases.key?(name) && !Homebrew::API.formula_aliases.key?(name) &&
!Homebrew::API::Formula.all_renames.key?(name) !Homebrew::API.formula_renames.key?(name)
return return
end end
@ -911,7 +929,10 @@ module Formulary
private private
def load_from_api(flags:) 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
end end

View File

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

View File

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

View File

@ -78,15 +78,43 @@ class Keg
end end
end end
def relocate_dynamic_linkage(_relocation) def relocate_dynamic_linkage(_relocation, skip_protodesc_cold: false)
[] []
end end
JAVA_REGEX = %r{#{HOMEBREW_PREFIX}/opt/openjdk(@\d+(\.\d+)*)?/libexec(/openjdk\.jdk/Contents/Home)?} 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 def prepare_relocation_to_placeholders
relocation = Relocation.new 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) 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 # when HOMEBREW_PREFIX == HOMEBREW_REPOSITORY we should use HOMEBREW_PREFIX for all relocations to avoid
# being unable to differentiate between them. # being unable to differentiate between them.
@ -104,7 +132,7 @@ class Keg
def replace_locations_with_placeholders def replace_locations_with_placeholders
relocation = prepare_relocation_to_placeholders.freeze relocation = prepare_relocation_to_placeholders.freeze
relocate_dynamic_linkage(relocation) relocate_dynamic_linkage(relocation, skip_protodesc_cold: true)
replace_text_in_files(relocation) replace_text_in_files(relocation)
end end
@ -361,6 +389,25 @@ class Keg
def self.file_linked_libraries(_file, _string) def self.file_linked_libraries(_file, _string)
[] []
end 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 end
require "extend/os/keg_relocate" require "extend/os/keg_relocate"

View File

@ -286,7 +286,7 @@ module Language
def slice_resources!(resources_hash, resource_names) def slice_resources!(resources_hash, resource_names)
resource_names.map do |resource_name| resource_names.map do |resource_name|
resources_hash.delete(resource_name) do 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 end
end end

View File

@ -245,7 +245,7 @@ module Homebrew
end end
# Use the `stable` version for comparison except for installed # 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 # installed using `--head` will still use the `stable` version for
# comparison. # comparison.
current = if formula 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) private_class_method def self.cask_deprecated(cask, livecheck_defined, full_name: false, verbose: false)
return {} if !cask.deprecated? || livecheck_defined 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:) Livecheck.status_hash(cask, "deprecated", full_name:, verbose:)
end end

View File

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

View File

@ -66,7 +66,7 @@ module Homebrew
return values if match.blank? return values if match.blank?
# The directory listing page for the project's files # 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("\\-", "-") 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 # A {Messages} object collects messages that may need to be displayed together
# at the end of a multi-step `brew` command run. # at the end of a multi-step `brew` command run.
class Messages class Messages
sig { returns(T::Array[T::Hash[Symbol, Symbol]]) } sig { returns(T::Array[{ package: String, caveats: T.any(String, Caveats) }]) }
attr_reader :caveats attr_reader :caveats
sig { returns(Integer) } sig { returns(Integer) }
@ -15,7 +15,7 @@ class Messages
sig { void } sig { void }
def initialize 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]) @completions_and_elisp = T.let(Set.new, T::Set[String])
@package_count = T.let(0, Integer) @package_count = T.let(0, Integer)
@install_times = T.let([], T::Array[T::Hash[String, Float]]) @install_times = T.let([], T::Array[T::Hash[String, Float]])
@ -53,7 +53,7 @@ class Messages
return if @package_count == 1 && !force return if @package_count == 1 && !force
oh1 "Caveats" if @completions_and_elisp.empty? 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 end
sig { void } sig { void }

View File

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

View File

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

View File

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

View File

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

View File

@ -14,7 +14,10 @@ require "cmd/postinstall"
require "json/add/exception" require "json/add/exception"
begin 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 args = Homebrew::Cmd::Postinstall.new.args
error_pipe = Utils::UNIXSocketExt.open(ENV.fetch("HOMEBREW_ERROR_PIPE"), &:recv_io) error_pipe = Utils::UNIXSocketExt.open(ENV.fetch("HOMEBREW_ERROR_PIPE"), &:recv_io)
error_pipe.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) error_pipe.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
@ -29,7 +32,7 @@ begin
formula.run_post_install formula.run_post_install
# Handle all possible exceptions. # Handle all possible exceptions.
rescue Exception => e # rubocop:disable Lint/RescueException rescue Exception => e # rubocop:disable Lint/RescueException
error_pipe.puts e.to_json error_pipe&.puts e.to_json
error_pipe.close error_pipe&.close
exit! 1 exit! 1
end end

View File

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

View File

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

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