Merge branch 'master' into ask-test

# Conflicts:
#	Library/Homebrew/vendor/bundle/bundler/setup.rb
This commit is contained in:
thibhero 2025-03-06 00:12:32 -05:00
commit f0f2e59fb7
319 changed files with 5890 additions and 3040 deletions

View File

@ -1,6 +1,6 @@
name: New issue for Reproducible Bug name: New issue for Reproducible Bug
description: "If you're sure it's reproducible and not just your machine: submit an issue so we can investigate." description: "If you're sure it's reproducible and not just your machine: submit an issue so we can investigate."
labels: [bug] type: "Bug"
body: body:
- type: markdown - type: markdown
attributes: attributes:

View File

@ -1,6 +1,6 @@
name: New issue for Feature Suggestion name: New issue for Feature Suggestion
description: Request our thoughts on your suggestion for a new feature for Homebrew. description: Request our thoughts on your suggestion for a new feature for Homebrew.
labels: features type: "Feature"
body: body:
- type: markdown - type: markdown
attributes: attributes:

View File

@ -14,6 +14,7 @@ updates:
artifacts: artifacts:
patterns: patterns:
- actions/*-artifact - actions/*-artifact
open-pull-requests-limit: 10
- package-ecosystem: bundler - package-ecosystem: bundler
directory: /Library/Homebrew directory: /Library/Homebrew
@ -25,6 +26,7 @@ updates:
sorbet: sorbet:
patterns: patterns:
- "sorbet*" - "sorbet*"
open-pull-requests-limit: 10
- package-ecosystem: npm - package-ecosystem: npm
directory: / directory: /
@ -32,6 +34,7 @@ updates:
interval: daily interval: daily
allow: allow:
- dependency-type: all - dependency-type: all
open-pull-requests-limit: 10
- package-ecosystem: docker - package-ecosystem: docker
directory: / directory: /
@ -39,6 +42,7 @@ updates:
interval: daily interval: daily
allow: allow:
- dependency-type: all - dependency-type: all
open-pull-requests-limit: 10
- package-ecosystem: devcontainers - package-ecosystem: devcontainers
directory: / directory: /
@ -46,6 +50,7 @@ updates:
interval: daily interval: daily
allow: allow:
- dependency-type: all - dependency-type: all
open-pull-requests-limit: 10
- package-ecosystem: pip - package-ecosystem: pip
directory: / directory: /
@ -53,6 +58,7 @@ updates:
interval: daily interval: daily
allow: allow:
- dependency-type: all - dependency-type: all
open-pull-requests-limit: 10
- package-ecosystem: pip - package-ecosystem: pip
directory: /Library/Homebrew/formula-analytics/ directory: /Library/Homebrew/formula-analytics/
@ -60,3 +66,4 @@ updates:
interval: daily interval: daily
allow: allow:
- dependency-type: all - dependency-type: all
open-pull-requests-limit: 10

View File

@ -57,7 +57,7 @@ jobs:
zizmor --format sarif . > results.sarif || true zizmor --format sarif . > results.sarif || true
- name: Upload SARIF file - name: Upload SARIF file
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
with: with:
name: results.sarif name: results.sarif
path: results.sarif path: results.sarif
@ -72,13 +72,13 @@ jobs:
security-events: write security-events: write
steps: steps:
- name: Download SARIF file - name: Download SARIF file
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9
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@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9 uses: github/codeql-action/upload-sarif@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10
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@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
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

@ -28,7 +28,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9 uses: github/codeql-action/init@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10
with: with:
languages: ruby languages: ruby
config: | config: |
@ -36,4 +36,4 @@ jobs:
- Library/Homebrew/vendor - Library/Homebrew/vendor
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9 uses: github/codeql-action/analyze@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10

View File

@ -37,7 +37,7 @@ jobs:
run: git fetch origin master run: git fetch origin master
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0 uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
with: with:
cache-binary: false cache-binary: false
@ -112,7 +112,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker image - name: Build Docker image
uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0 uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
with: with:
context: . context: .
load: true load: true
@ -141,7 +141,7 @@ jobs:
- name: Deploy the tagged Docker image - name: Deploy the tagged Docker image
if: steps.attributes.outputs.push == 'true' if: steps.attributes.outputs.push == 'true'
uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0 uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
with: with:
context: . context: .
push: true push: true

View File

@ -53,7 +53,7 @@ jobs:
run: vale docs/ run: vale docs/
- name: Install Ruby - name: Install Ruby
uses: ruby/setup-ruby@d781c1b4ed31764801bfae177617bb0446f5ef8d # v1.218.0 uses: ruby/setup-ruby@32110d4e311bd8996b2a82bf2a43b714ccc91777 # v1.221.0
with: with:
bundler-cache: true bundler-cache: true
working-directory: docs working-directory: docs

View File

@ -133,12 +133,12 @@ jobs:
fi fi
- name: Generate build provenance - name: Generate build provenance
uses: actions/attest-build-provenance@520d128f165991a6c774bcb264f323e3d70747f4 # v2.2.0 uses: actions/attest-build-provenance@bd77c077858b8d561b7a36cbe48ef4cc642ca39d # v2.2.2
with: with:
subject-path: Homebrew-${{ steps.homebrew-version.outputs.version }}.pkg subject-path: Homebrew-${{ steps.homebrew-version.outputs.version }}.pkg
- name: Upload installer to GitHub Actions - name: Upload installer to GitHub Actions
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
with: with:
name: Homebrew-${{ steps.homebrew-version.outputs.version }}.pkg name: Homebrew-${{ steps.homebrew-version.outputs.version }}.pkg
path: Homebrew-${{ steps.homebrew-version.outputs.version }}.pkg path: Homebrew-${{ steps.homebrew-version.outputs.version }}.pkg
@ -160,7 +160,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@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9
with: with:
name: "${{ needs.build.outputs.installer_path }}" name: "${{ needs.build.outputs.installer_path }}"
@ -213,7 +213,7 @@ jobs:
contents: write contents: write
steps: steps:
- name: Download installer from GitHub Actions - name: Download installer from GitHub Actions
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9
with: with:
name: "${{ needs.build.outputs.installer_path }}" name: "${{ needs.build.outputs.installer_path }}"

View File

@ -43,7 +43,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: Install Ruby - name: Install Ruby
uses: ruby/setup-ruby@d781c1b4ed31764801bfae177617bb0446f5ef8d # v1.218.0 uses: ruby/setup-ruby@32110d4e311bd8996b2a82bf2a43b714ccc91777 # v1.221.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@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
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

@ -40,7 +40,7 @@ jobs:
test-bot: false test-bot: false
- name: Cache Bundler RubyGems - name: Cache Bundler RubyGems
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
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@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
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@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
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@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
with: with:
path: ~/.cache/Homebrew/style path: ~/.cache/Homebrew/style
key: tap-syntax-style-cache-${{ github.sha }} key: tap-syntax-style-cache-${{ github.sha }}
@ -242,6 +242,7 @@ jobs:
name: ${{ matrix.name }} name: ${{ matrix.name }}
needs: syntax needs: syntax
runs-on: ${{ matrix.runs-on }} runs-on: ${{ matrix.runs-on }}
container: ${{ matrix.container }}
strategy: strategy:
matrix: matrix:
include: include:
@ -259,7 +260,8 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
- name: tests (Ubuntu 20.04) - name: tests (Ubuntu 20.04)
test-flags: --coverage test-flags: --coverage
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
container: ghcr.io/homebrew/ubuntu20.04:latest
- name: tests (macOS 13 x86_64) - name: tests (macOS 13 x86_64)
test-flags: --coverage test-flags: --coverage
runs-on: macos-13 runs-on: macos-13
@ -279,7 +281,7 @@ jobs:
test-bot: false test-bot: false
- name: Cache Bundler RubyGems - name: Cache Bundler RubyGems
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
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 }}
@ -294,7 +296,7 @@ jobs:
run: mkdir tests run: mkdir tests
- name: Cache parallel tests log - name: Cache parallel tests log
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
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 }}
@ -341,14 +343,14 @@ jobs:
filenames=$(find Library/Homebrew/test/junit -name 'rspec*.xml' -print | tr '\n' ',') filenames=$(find Library/Homebrew/test/junit -name 'rspec*.xml' -print | tr '\n' ',')
echo "filenames=${filenames%,}" >> "$GITHUB_OUTPUT" echo "filenames=${filenames%,}" >> "$GITHUB_OUTPUT"
- uses: codecov/test-results-action@44ecb3a270cd942bdf0fa8f2ce14cb32493e810a # v1.0.3 - uses: codecov/test-results-action@5c441a7bcc06f8706cde90192857d337c5dab8a6 # v1.0.4
with: with:
working-directory: ${{ steps.set-up-homebrew.outputs.repository-path }} working-directory: ${{ steps.set-up-homebrew.outputs.repository-path }}
files: ${{ steps.junit_xml.outputs.filenames }} files: ${{ steps.junit_xml.outputs.filenames }}
disable_search: true disable_search: true
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
- uses: codecov/codecov-action@13ce06bfc6bbe3ecf90edbbf1bc32fe5978ca1d3 # v5.3.1 - uses: codecov/codecov-action@0565863a31f2c772f9f0395002a31e3f06189574 # v5.4.0
with: with:
working-directory: ${{ steps.set-up-homebrew.outputs.repository-path }} working-directory: ${{ steps.set-up-homebrew.outputs.repository-path }}
files: Library/Homebrew/test/coverage/coverage.xml files: Library/Homebrew/test/coverage/coverage.xml
@ -364,12 +366,15 @@ jobs:
strategy: strategy:
matrix: matrix:
include: include:
- name: test default formula (Ubuntu 24.04)
runs-on: ubuntu-latest
container: ghcr.io/homebrew/ubuntu24.04:latest
- name: test default formula (Ubuntu 22.04) - name: test default formula (Ubuntu 22.04)
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: ghcr.io/homebrew/ubuntu22.04:master container: ghcr.io/homebrew/ubuntu22.04:master
- name: test default formula (Ubuntu 20.04) - name: test default formula (Ubuntu 20.04)
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: ghcr.io/homebrew/ubuntu20.04 container: ghcr.io/homebrew/ubuntu20.04:latest
- name: test default formula (macOS 13 x86_64) - name: test default formula (macOS 13 x86_64)
runs-on: macos-13 runs-on: macos-13
- name: test default formula (macOS 15 arm64) - name: test default formula (macOS 15 arm64)
@ -415,7 +420,7 @@ jobs:
- name: Cache Homebrew Bundler RubyGems - name: Cache Homebrew Bundler RubyGems
id: cache id: cache
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
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@67e27a7eb7db372a1c61a7f9bdab8699e9ee57f7 # v1.11.3 uses: actions/create-github-app-token@21cfef2b496dd8ef5b904c159339626a10ad380e # v1.11.6
id: app-token id: app-token
if: github.event_name == 'workflow_dispatch' if: github.event_name == 'workflow_dispatch'
with: with:

1
.gitignore vendored
View File

@ -90,6 +90,7 @@
**/vendor/bundle/ruby/*/gems/json_schemer-*/ **/vendor/bundle/ruby/*/gems/json_schemer-*/
**/vendor/bundle/ruby/*/gems/kramdown-*/ **/vendor/bundle/ruby/*/gems/kramdown-*/
**/vendor/bundle/ruby/*/gems/language_server-protocol-*/ **/vendor/bundle/ruby/*/gems/language_server-protocol-*/
**/vendor/bundle/ruby/*/gems/lint_roller-*/
**/vendor/bundle/ruby/*/gems/logger-*/ **/vendor/bundle/ruby/*/gems/logger-*/
**/vendor/bundle/ruby/*/gems/method_source-*/ **/vendor/bundle/ruby/*/gems/method_source-*/
**/vendor/bundle/ruby/*/gems/mini_portile2-*/ **/vendor/bundle/ruby/*/gems/mini_portile2-*/

View File

@ -1,9 +1,14 @@
--- ---
plugins:
- rubocop-md:
plugin_class_name: RuboCop::Markdown::Plugin
- rubocop-performance:
plugin_class_name: RuboCop::Performance::Plugin
- rubocop-rspec:
plugin_class_name: RuboCop::RSpec::Plugin
require: require:
- ./Homebrew/rubocops.rb - ./Homebrew/rubocops.rb
- rubocop-md
- rubocop-performance
- rubocop-rspec
- rubocop-sorbet - rubocop-sorbet
inherit_mode: inherit_mode:
@ -255,6 +260,11 @@ RSpec/Focus:
RSpec/MessageSpies: RSpec/MessageSpies:
EnforcedStyle: receive EnforcedStyle: receive
# These are legacy violations that we should try to fix.
Sorbet/AllowIncompatibleOverride:
Exclude:
- "Homebrew/livecheck/strategy/*.rb"
# Try getting rid of these. # Try getting rid of these.
Sorbet/ConstantsFromStrings: Sorbet/ConstantsFromStrings:
Enabled: false Enabled: false

View File

@ -10,13 +10,13 @@ GEM
bindata (2.5.0) bindata (2.5.0)
coderay (1.1.3) coderay (1.1.3)
concurrent-ruby (1.3.5) concurrent-ruby (1.3.5)
diff-lcs (1.5.1) diff-lcs (1.6.0)
docile (1.4.1) docile (1.4.1)
elftools (1.3.1) elftools (1.3.1)
bindata (~> 2) bindata (~> 2)
erubi (1.13.1) erubi (1.13.1)
hana (1.3.7) hana (1.3.7)
json (2.9.1) json (2.10.1)
json_schemer (2.4.0) json_schemer (2.4.0)
bigdecimal bigdecimal
hana (~> 1.3) hana (~> 1.3)
@ -25,12 +25,13 @@ GEM
kramdown (2.5.1) kramdown (2.5.1)
rexml (>= 3.3.9) rexml (>= 3.3.9)
language_server-protocol (3.17.0.4) language_server-protocol (3.17.0.4)
logger (1.6.5) lint_roller (1.1.0)
logger (1.6.6)
method_source (1.1.0) method_source (1.1.0)
minitest (5.25.4) minitest (5.25.4)
netrc (0.11.0) netrc (0.11.0)
parallel (1.26.3) parallel (1.26.3)
parallel_tests (4.9.0) parallel_tests (5.0.1)
parallel parallel
parser (3.3.7.1) parser (3.3.7.1)
ast (~> 2.4.1) ast (~> 2.4.1)
@ -51,9 +52,9 @@ GEM
sorbet-runtime (>= 0.5.9204) sorbet-runtime (>= 0.5.9204)
rbs (3.8.1) rbs (3.8.1)
logger logger
redcarpet (3.6.0) redcarpet (3.6.1)
regexp_parser (2.10.0) regexp_parser (2.10.0)
rexml (3.4.0) rexml (3.4.1)
rspec (3.13.0) rspec (3.13.0)
rspec-core (~> 3.13.0) rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0) rspec-expectations (~> 3.13.0)
@ -75,9 +76,10 @@ GEM
rspec-support (3.13.2) rspec-support (3.13.2)
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.71.2) rubocop (1.73.2)
json (~> 2.3) json (~> 2.3)
language_server-protocol (>= 3.17.0) language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 3.3.0.2) parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
@ -85,18 +87,21 @@ GEM
rubocop-ast (>= 1.38.0, < 2.0) rubocop-ast (>= 1.38.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.38.0) rubocop-ast (1.38.1)
parser (>= 3.3.1.0) parser (>= 3.3.1.0)
rubocop-md (1.2.4) rubocop-md (2.0.0)
rubocop (>= 1.45) lint_roller (~> 1.1)
rubocop-performance (1.23.1) rubocop (>= 1.72.1)
rubocop (>= 1.48.1, < 2.0) rubocop-performance (1.24.0)
rubocop-ast (>= 1.31.1, < 2.0) lint_roller (~> 1.1)
rubocop-rspec (3.4.0) rubocop (>= 1.72.1, < 2.0)
rubocop (~> 1.61) rubocop-ast (>= 1.38.0, < 2.0)
rubocop-sorbet (0.8.7) rubocop-rspec (3.5.0)
lint_roller (~> 1.1)
rubocop (~> 1.72, >= 1.72.1)
rubocop-sorbet (0.8.9)
rubocop (>= 1) rubocop (>= 1)
ruby-lsp (0.23.9) ruby-lsp (0.23.11)
language_server-protocol (~> 3.17.0) language_server-protocol (~> 3.17.0)
prism (>= 1.2, < 2.0) prism (>= 1.2, < 2.0)
rbs (>= 3, < 4) rbs (>= 3, < 4)
@ -114,23 +119,23 @@ GEM
simplecov-html (0.13.1) simplecov-html (0.13.1)
simplecov_json_formatter (0.1.4) simplecov_json_formatter (0.1.4)
simpleidn (0.2.3) simpleidn (0.2.3)
sorbet (0.5.11812) sorbet (0.5.11888)
sorbet-static (= 0.5.11812) sorbet-static (= 0.5.11888)
sorbet-runtime (0.5.11812) sorbet-runtime (0.5.11888)
sorbet-static (0.5.11812-aarch64-linux) sorbet-static (0.5.11888-aarch64-linux)
sorbet-static (0.5.11812-universal-darwin) sorbet-static (0.5.11888-universal-darwin)
sorbet-static (0.5.11812-x86_64-linux) sorbet-static (0.5.11888-x86_64-linux)
sorbet-static-and-runtime (0.5.11812) sorbet-static-and-runtime (0.5.11888)
sorbet (= 0.5.11812) sorbet (= 0.5.11888)
sorbet-runtime (= 0.5.11812) sorbet-runtime (= 0.5.11888)
spoom (1.5.3) spoom (1.5.4)
erubi (>= 1.10.0) erubi (>= 1.10.0)
prism (>= 0.28.0) prism (>= 0.28.0)
rbi (>= 0.2.3) rbi (>= 0.2.3)
sorbet-static-and-runtime (>= 0.5.10187) sorbet-static-and-runtime (>= 0.5.10187)
thor (>= 0.19.2) thor (>= 0.19.2)
stackprof (0.2.27) stackprof (0.2.27)
tapioca (0.16.9) tapioca (0.16.11)
benchmark benchmark
bundler (>= 2.2.25) bundler (>= 2.2.25)
netrc (>= 0.11.0) netrc (>= 0.11.0)
@ -153,7 +158,6 @@ GEM
PLATFORMS PLATFORMS
aarch64-linux aarch64-linux
arm-linux
arm64-darwin arm64-darwin
x86_64-darwin x86_64-darwin
x86_64-linux x86_64-linux

View File

@ -1,30 +0,0 @@
# typed: strict
# frozen_string_literal: true
# This module provides methods to define specialized attributes.
# Method stubs are generated by the {Tapioca::Compilers::Attrables} compiler.
# @note The compiler is fragile, and must be updated if the filename changes, if methods are added or removed,
# or if a method's arity changes.
module Attrable
extend T::Helpers
requires_ancestor { Module }
sig { params(attrs: Symbol).void }
def attr_predicate(*attrs)
attrs.each do |attr|
define_method attr do
instance_variable_get("@#{attr.to_s.sub(/\?$/, "")}") == true
end
end
end
sig { params(attrs: Symbol).void }
def attr_rw(*attrs)
attrs.each do |attr|
define_method attr do |val = nil|
val.nil? ? instance_variable_get(:"@#{attr}") : instance_variable_set(:"@#{attr}", val)
end
end
end
end

View File

@ -2,10 +2,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class BottleSpecification class BottleSpecification
extend Attrable
RELOCATABLE_CELLARS = [:any, :any_skip_relocation].freeze RELOCATABLE_CELLARS = [:any, :any_skip_relocation].freeze
attr_rw :rebuild
attr_accessor :tap attr_accessor :tap
attr_reader :collector, :root_url_specs, :repository attr_reader :collector, :root_url_specs, :repository
@ -17,6 +15,11 @@ class BottleSpecification
@root_url_specs = {} @root_url_specs = {}
end end
sig { params(val: Integer).returns(T.nilable(Integer)) }
def rebuild(val = T.unsafe(nil))
val.nil? ? @rebuild : @rebuild = val
end
def root_url(var = nil, specs = {}) def root_url(var = nil, specs = {})
if var.nil? if var.nil?
@root_url ||= if (github_packages_url = GitHubPackages.root_url_if_match(Homebrew::EnvConfig.bottle_domain)) @root_url ||= if (github_packages_url = GitHubPackages.root_url_if_match(Homebrew::EnvConfig.bottle_domain))

View File

@ -969,13 +969,6 @@ then
export HOMEBREW_DEVELOPER_COMMAND="1" export HOMEBREW_DEVELOPER_COMMAND="1"
fi fi
# Provide a (temporary, undocumented) way to disable Sorbet globally if needed
# to avoid reverting the above.
if [[ -n "${HOMEBREW_NO_SORBET_RUNTIME}" ]]
then
unset HOMEBREW_SORBET_RUNTIME
fi
if [[ -n "${HOMEBREW_DEVELOPER_COMMAND}" && -z "${HOMEBREW_DEVELOPER}" ]] if [[ -n "${HOMEBREW_DEVELOPER_COMMAND}" && -z "${HOMEBREW_DEVELOPER}" ]]
then then
if [[ -z "${HOMEBREW_DEV_CMD_RUN}" ]] if [[ -z "${HOMEBREW_DEV_CMD_RUN}" ]]
@ -999,6 +992,13 @@ then
export HOMEBREW_SORBET_RUNTIME="1" export HOMEBREW_SORBET_RUNTIME="1"
fi fi
# Provide a (temporary, undocumented) way to disable Sorbet globally if needed
# to avoid reverting the above.
if [[ -n "${HOMEBREW_NO_SORBET_RUNTIME}" ]]
then
unset HOMEBREW_SORBET_RUNTIME
fi
if [[ -f "${HOMEBREW_LIBRARY}/Homebrew/cmd/${HOMEBREW_COMMAND}.sh" ]] if [[ -f "${HOMEBREW_LIBRARY}/Homebrew/cmd/${HOMEBREW_COMMAND}.sh" ]]
then then
HOMEBREW_BASH_COMMAND="${HOMEBREW_LIBRARY}/Homebrew/cmd/${HOMEBREW_COMMAND}.sh" HOMEBREW_BASH_COMMAND="${HOMEBREW_LIBRARY}/Homebrew/cmd/${HOMEBREW_COMMAND}.sh"

View File

@ -148,12 +148,15 @@ class Build
# https://github.com/Homebrew/homebrew-core/pull/87470 # https://github.com/Homebrew/homebrew-core/pull/87470
TZ: "UTC0", TZ: "UTC0",
) do ) do
formula.patch
if args.git? if args.git?
formula.selective_patch(is_data: false)
system "git", "init" system "git", "init"
system "git", "add", "-A" system "git", "add", "-A"
formula.selective_patch(is_data: true)
else
formula.patch
end end
if args.interactive? if args.interactive?
ohai "Entering interactive mode..." ohai "Entering interactive mode..."
puts <<~EOS puts <<~EOS
@ -185,6 +188,8 @@ class Build
# Find and link metafiles # Find and link metafiles
formula.prefix.install_metafiles formula.buildpath formula.prefix.install_metafiles formula.buildpath
formula.prefix.install_metafiles formula.libexec if formula.libexec.exist? formula.prefix.install_metafiles formula.libexec if formula.libexec.exist?
normalize_pod2man_outputs!(formula)
end end
end end
end end
@ -214,6 +219,11 @@ class Build
rescue rescue
raise "#{formula.opt_prefix} not present or broken\nPlease reinstall #{formula.full_name}. Sorry :(" raise "#{formula.opt_prefix} not present or broken\nPlease reinstall #{formula.full_name}. Sorry :("
end end
def normalize_pod2man_outputs!(formula)
keg = Keg.new(formula.prefix)
keg.normalize_pod2man_outputs!
end
end end
begin begin

View File

@ -22,6 +22,9 @@ require "cask/artifact/prefpane"
require "cask/artifact/qlplugin" require "cask/artifact/qlplugin"
require "cask/artifact/mdimporter" require "cask/artifact/mdimporter"
require "cask/artifact/screen_saver" require "cask/artifact/screen_saver"
require "cask/artifact/bashcompletion"
require "cask/artifact/fishcompletion"
require "cask/artifact/zshcompletion"
require "cask/artifact/service" require "cask/artifact/service"
require "cask/artifact/stage_only" require "cask/artifact/stage_only"
require "cask/artifact/suite" require "cask/artifact/suite"

View File

@ -1,7 +1,6 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: true # rubocop:todo Sorbet/StrictSigil
# frozen_string_literal: true # frozen_string_literal: true
require "attrable"
require "extend/object/deep_dup" require "extend/object/deep_dup"
module Cask module Cask

View File

@ -411,7 +411,7 @@ module Cask
next next
end end
if MacOS.undeletable?(resolved_path) if undeletable?(resolved_path)
opoo "Skipping #{Formatter.identifier(action)} for undeletable path '#{path}'." opoo "Skipping #{Formatter.identifier(action)} for undeletable path '#{path}'."
next next
end end
@ -538,6 +538,10 @@ module Cask
recursive_rmdir(*resolved_paths, **kwargs) recursive_rmdir(*resolved_paths, **kwargs)
end end
end end
def undeletable?(target); end
end end
end end
end end
require "extend/os/cask/artifact/abstract_uninstall"

View File

@ -0,0 +1,25 @@
# typed: strict
# frozen_string_literal: true
require "cask/artifact/shellcompletion"
module Cask
module Artifact
# Artifact corresponding to the `bash_completion` stanza.
class BashCompletion < ShellCompletion
sig { params(target: T.any(String, Pathname)).returns(Pathname) }
def resolve_target(target)
name = if File.extname(target).nil?
target
else
new_name = File.basename(target, File.extname(target))
odebug "Renaming completion #{target} to #{new_name}"
new_name
end
config.bash_completion/name
end
end
end
end

View File

@ -0,0 +1,25 @@
# typed: strict
# frozen_string_literal: true
require "cask/artifact/shellcompletion"
module Cask
module Artifact
# Artifact corresponding to the `fish_completion` stanza.
class FishCompletion < ShellCompletion
sig { params(target: T.any(String, Pathname)).returns(Pathname) }
def resolve_target(target)
name = if target.to_s.end_with? ".fish"
target
else
new_name = "#{File.basename(target, File.extname(target))}.fish"
odebug "Renaming completion #{target} to #{new_name}"
new_name
end
config.fish_completion/name
end
end
end
end

View File

@ -72,6 +72,7 @@ module Cask
private private
ALT_NAME_ATTRIBUTE = "com.apple.metadata:kMDItemAlternateNames" ALT_NAME_ATTRIBUTE = "com.apple.metadata:kMDItemAlternateNames"
private_constant :ALT_NAME_ATTRIBUTE
# Try to make the asset searchable under the target name. Spotlight # Try to make the asset searchable under the target name. Spotlight
# respects this attribute for many filetypes, but ignores it for App # respects this attribute for many filetypes, but ignores it for App

View File

@ -0,0 +1,20 @@
# typed: strict
# frozen_string_literal: true
require "cask/artifact/symlinked"
module Cask
module Artifact
class ShellCompletion < Symlinked
sig { params(cask: Cask, source: T.any(String, Pathname)).returns(ShellCompletion) }
def self.from_args(cask, source)
new(cask, source)
end
sig { params(_: T.any(String, Pathname)).returns(Pathname) }
def resolve_target(_)
raise CaskInvalidError, "Shell completion without shell info"
end
end
end
end

View File

@ -62,7 +62,7 @@ module Cask
end end
ohai "Linking #{self.class.english_name} '#{source.basename}' to '#{target}'" ohai "Linking #{self.class.english_name} '#{source.basename}' to '#{target}'"
create_filesystem_link(command:) create_filesystem_link(command)
end end
def unlink(command: nil, **) def unlink(command: nil, **)
@ -72,14 +72,10 @@ module Cask
Utils.gain_permissions_remove(target, command:) Utils.gain_permissions_remove(target, command:)
end end
def create_filesystem_link(command: nil) sig { params(command: T.class_of(SystemCommand)).void }
Utils.gain_permissions_mkpath(target.dirname, command:) def create_filesystem_link(command); end
command.run! "/bin/ln", args: ["-h", "-f", "-s", "--", source, target],
sudo: !target.dirname.writable?
add_altname_metadata(source, target.basename, command:)
end
end end
end end
end end
require "extend/os/cask/artifact/symlinked"

View File

@ -0,0 +1,25 @@
# typed: strict
# frozen_string_literal: true
require "cask/artifact/shellcompletion"
module Cask
module Artifact
# Artifact corresponding to the `zsh_completion` stanza.
class ZshCompletion < ShellCompletion
sig { params(target: T.any(String, Pathname)).returns(Pathname) }
def resolve_target(target)
name = if target.to_s.start_with? "_"
target
else
new_name = "_#{File.basename(target, File.extname(target))}"
odebug "Renaming completion #{target} to #{new_name}"
new_name
end
config.zsh_completion/name
end
end
end
end

View File

@ -1,7 +1,6 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: true # rubocop:todo Sorbet/StrictSigil
# frozen_string_literal: true # frozen_string_literal: true
require "attrable"
require "cask/denylist" require "cask/denylist"
require "cask/download" require "cask/download"
require "digest" require "digest"
@ -19,7 +18,6 @@ module Cask
class Audit class Audit
include SystemCommand::Mixin include SystemCommand::Mixin
include ::Utils::Curl include ::Utils::Curl
extend Attrable
sig { returns(Cask) } sig { returns(Cask) }
attr_reader :cask attr_reader :cask
@ -27,11 +25,16 @@ module Cask
sig { returns(T.nilable(Download)) } sig { returns(T.nilable(Download)) }
attr_reader :download attr_reader :download
attr_predicate :new_cask?, :strict?, :signing?, :online?, :token_conflicts? sig {
params(
cask: ::Cask::Cask, download: T::Boolean, quarantine: T::Boolean, token_conflicts: T.nilable(T::Boolean),
online: T.nilable(T::Boolean), strict: T.nilable(T::Boolean), signing: T.nilable(T::Boolean),
new_cask: T.nilable(T::Boolean), only: T::Array[String], except: T::Array[String]
).void
}
def initialize( def initialize(
cask, cask,
download: nil, quarantine: nil, download: false, quarantine: false,
token_conflicts: nil, online: nil, strict: nil, signing: nil, token_conflicts: nil, online: nil, strict: nil, signing: nil,
new_cask: nil, only: [], except: [] new_cask: nil, only: [], except: []
) )
@ -42,7 +45,7 @@ module Cask
token_conflicts = new_cask if token_conflicts.nil? token_conflicts = new_cask if token_conflicts.nil?
# `online` and `signing` imply `download` # `online` and `signing` imply `download`
download = online || signing if download.nil? download ||= online || signing
@cask = cask @cask = cask
@download = Download.new(cask, quarantine:) if download @download = Download.new(cask, quarantine:) if download
@ -51,18 +54,34 @@ module Cask
@signing = signing @signing = signing
@new_cask = new_cask @new_cask = new_cask
@token_conflicts = token_conflicts @token_conflicts = token_conflicts
@only = only || [] @only = only
@except = except || [] @except = except
end end
sig { returns(T::Boolean) }
def new_cask? = !!@new_cask
sig { returns(T::Boolean) }
def online? =!!@online
sig { returns(T::Boolean) }
def signing? = !!@signing
sig { returns(T::Boolean) }
def strict? = !!@strict
sig { returns(T::Boolean) }
def token_conflicts? = !!@token_conflicts
sig { returns(::Cask::Audit) }
def run! def run!
only_audits = @only only_audits = @only
except_audits = @except except_audits = @except
private_methods.map(&:to_s).grep(/^audit_/).each do |audit_method_name| private_methods.map(&:to_s).grep(/^audit_/).each do |audit_method_name|
name = audit_method_name.delete_prefix("audit_") name = audit_method_name.delete_prefix("audit_")
next if !only_audits.empty? && only_audits&.exclude?(name) next if !only_audits.empty? && only_audits.exclude?(name)
next if except_audits&.include?(name) next if except_audits.include?(name)
send(audit_method_name) send(audit_method_name)
end end
@ -292,6 +311,7 @@ module Cask
end end
LIVECHECK_REFERENCE_URL = "https://docs.brew.sh/Cask-Cookbook#stanza-livecheck" LIVECHECK_REFERENCE_URL = "https://docs.brew.sh/Cask-Cookbook#stanza-livecheck"
private_constant :LIVECHECK_REFERENCE_URL
sig { params(livecheck_result: T.any(NilClass, T::Boolean, Symbol)).void } sig { params(livecheck_result: T.any(NilClass, T::Boolean, Symbol)).void }
def audit_hosting_with_livecheck(livecheck_result: audit_livecheck_version) def audit_hosting_with_livecheck(livecheck_result: audit_livecheck_version)
@ -317,6 +337,7 @@ module Cask
end end
SOURCEFORGE_OSDN_REFERENCE_URL = "https://docs.brew.sh/Cask-Cookbook#sourceforgeosdn-urls" SOURCEFORGE_OSDN_REFERENCE_URL = "https://docs.brew.sh/Cask-Cookbook#sourceforgeosdn-urls"
private_constant :SOURCEFORGE_OSDN_REFERENCE_URL
sig { void } sig { void }
def audit_download_url_format def audit_download_url_format
@ -339,6 +360,7 @@ module Cask
end end
VERIFIED_URL_REFERENCE_URL = "https://docs.brew.sh/Cask-Cookbook#when-url-and-homepage-domains-differ-add-verified" VERIFIED_URL_REFERENCE_URL = "https://docs.brew.sh/Cask-Cookbook#when-url-and-homepage-domains-differ-add-verified"
private_constant :VERIFIED_URL_REFERENCE_URL
sig { void } sig { void }
def audit_unnecessary_verified def audit_unnecessary_verified

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "cask/audit" require "cask/audit"
@ -6,22 +6,50 @@ require "cask/audit"
module Cask module Cask
# Helper class for auditing all available languages of a cask. # Helper class for auditing all available languages of a cask.
class Auditor class Auditor
def self.audit(cask, **options) # TODO: use argument forwarding (...) when Sorbet supports it in strict mode
new(cask, **options).audit sig {
params(
cask: ::Cask::Cask, audit_download: T::Boolean, audit_online: T.nilable(T::Boolean),
audit_strict: T.nilable(T::Boolean), audit_signing: T.nilable(T::Boolean),
audit_token_conflicts: T.nilable(T::Boolean), audit_new_cask: T.nilable(T::Boolean), quarantine: T::Boolean,
any_named_args: T::Boolean, language: T.nilable(String), only: T::Array[String], except: T::Array[String]
).returns(T::Set[String])
}
def self.audit(
cask, audit_download: false, audit_online: nil, audit_strict: nil, audit_signing: nil,
audit_token_conflicts: nil, audit_new_cask: nil, quarantine: false, any_named_args: false, language: nil,
only: [], except: []
)
new(
cask, audit_download:, audit_online:, audit_strict:, audit_signing:, audit_token_conflicts:,
audit_new_cask:, quarantine:, any_named_args:, language:, only:, except:
).audit
end end
attr_reader :cask, :language sig { returns(::Cask::Cask) }
attr_reader :cask
sig { returns(T.nilable(String)) }
attr_reader :language
sig {
params(
cask: ::Cask::Cask, audit_download: T::Boolean, audit_online: T.nilable(T::Boolean),
audit_strict: T.nilable(T::Boolean), audit_signing: T.nilable(T::Boolean),
audit_token_conflicts: T.nilable(T::Boolean), audit_new_cask: T.nilable(T::Boolean), quarantine: T::Boolean,
any_named_args: T::Boolean, language: T.nilable(String), only: T::Array[String], except: T::Array[String]
).void
}
def initialize( def initialize(
cask, cask,
audit_download: nil, audit_download: false,
audit_online: nil, audit_online: nil,
audit_strict: nil, audit_strict: nil,
audit_signing: nil, audit_signing: nil,
audit_token_conflicts: nil, audit_token_conflicts: nil,
audit_new_cask: nil, audit_new_cask: nil,
quarantine: nil, quarantine: false,
any_named_args: nil, any_named_args: false,
language: nil, language: nil,
only: [], only: [],
except: [] except: []
@ -42,17 +70,18 @@ module Cask
LANGUAGE_BLOCK_LIMIT = 10 LANGUAGE_BLOCK_LIMIT = 10
sig { returns(T::Set[String]) }
def audit def audit
errors = Set.new errors = Set.new
if !language && language_blocks if !language && (blocks = language_blocks)
sample_languages = if language_blocks.length > LANGUAGE_BLOCK_LIMIT && !@audit_new_cask sample_languages = if blocks.length > LANGUAGE_BLOCK_LIMIT && !@audit_new_cask
sample_keys = language_blocks.keys.sample(LANGUAGE_BLOCK_LIMIT) sample_keys = T.must(blocks.keys.sample(LANGUAGE_BLOCK_LIMIT))
ohai "Auditing a sample of available languages for #{cask}: " \ ohai "Auditing a sample of available languages for #{cask}: " \
"#{sample_keys.map { |lang| lang[0].to_s }.to_sentence}" "#{sample_keys.map { |lang| lang[0].to_s }.to_sentence}"
language_blocks.select { |k| sample_keys.include?(k) } blocks.select { |k| sample_keys.include?(k) }
else else
language_blocks blocks
end end
sample_languages.each_key do |l| sample_languages.each_key do |l|
@ -74,14 +103,16 @@ module Cask
private private
sig { params(audit: T.nilable(Audit)).returns(T::Boolean) }
def output_summary?(audit = nil) def output_summary?(audit = nil)
return true if @any_named_args.present? return true if @any_named_args
return true if @audit_strict.present? return true if @audit_strict
return false if audit.blank? return false if audit.nil?
audit.errors? audit.errors?
end end
sig { params(languages: T::Array[String]).returns(::Cask::Audit) }
def audit_languages(languages) def audit_languages(languages)
original_config = cask.config original_config = cask.config
localized_config = original_config.merge(Config.new(explicit: { languages: })) localized_config = original_config.merge(Config.new(explicit: { languages: }))
@ -92,6 +123,7 @@ module Cask
cask.config = original_config cask.config = original_config
end end
sig { params(cask: ::Cask::Cask).returns(::Cask::Audit) }
def audit_cask_instance(cask) def audit_cask_instance(cask)
audit = Audit.new( audit = Audit.new(
cask, cask,
@ -108,6 +140,7 @@ module Cask
audit.run! audit.run!
end end
sig { returns(T.nilable(T::Hash[T::Array[String], T.proc.returns(T.untyped)])) }
def language_blocks def language_blocks
cask.instance_variable_get(:@dsl).instance_variable_get(:@language_blocks) cask.instance_variable_get(:@dsl).instance_variable_get(:@language_blocks)
end end

View File

@ -1,7 +1,6 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: true # rubocop:todo Sorbet/StrictSigil
# frozen_string_literal: true # frozen_string_literal: true
require "attrable"
require "bundle_version" require "bundle_version"
require "cask/cask_loader" require "cask/cask_loader"
require "cask/config" require "cask/config"
@ -15,7 +14,6 @@ module Cask
# An instance of a cask. # An instance of a cask.
class Cask class Cask
extend Forwardable extend Forwardable
extend Attrable
extend APIHashable extend APIHashable
include Metadata include Metadata
@ -32,8 +30,6 @@ module Cask
attr_reader :sourcefile_path, :source, :default_config, :loader attr_reader :sourcefile_path, :source, :default_config, :loader
attr_accessor :download, :allow_reassignment attr_accessor :download, :allow_reassignment
attr_predicate :loaded_from_api?
def self.all(eval_all: false) def self.all(eval_all: false)
if !eval_all && !Homebrew::EnvConfig.eval_all? if !eval_all && !Homebrew::EnvConfig.eval_all?
raise ArgumentError, "Cask::Cask#all cannot be used without `--eval-all` or HOMEBREW_EVAL_ALL" raise ArgumentError, "Cask::Cask#all cannot be used without `--eval-all` or HOMEBREW_EVAL_ALL"
@ -92,6 +88,9 @@ module Cask
end end
end end
sig { returns(T::Boolean) }
def loaded_from_api? = @loaded_from_api
# 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
@ -114,12 +113,11 @@ module Cask
return unless @block return unless @block
@dsl.instance_eval(&@block) @dsl.instance_eval(&@block)
@dsl.add_implicit_macos_dependency
@dsl.language_eval @dsl.language_eval
end end
::Cask::DSL::DSL_METHODS.each do |method_name| def_delegators :@dsl, *::Cask::DSL::DSL_METHODS
define_method(method_name) { |*args, &block| @dsl.send(method_name, *args, &block) }
end
sig { params(caskroom_path: Pathname).returns(T::Array[[String, String]]) } sig { params(caskroom_path: Pathname).returns(T::Array[[String, String]]) }
def timestamped_versions(caskroom_path: self.caskroom_path) def timestamped_versions(caskroom_path: self.caskroom_path)

View File

@ -1,67 +0,0 @@
# typed: strict
module Cask
class Cask
def appdir; end
def artifacts; end
def auto_updates; end
def caveats; end
def conflicts_with; end
def container; end
def depends_on; end
def desc; end
def discontinued?; end
def deprecated?; end
def deprecation_date; end
def deprecation_reason; end
def deprecation_replacement; end
def disabled?; end
def disable_date; end
def disable_reason; end
def disable_replacement; end
def homepage; end
def language; end
def languages; end
def livecheck; end
def livecheck_defined?; end
def livecheckable?; end
def name; end
def on_system_blocks_exist?; end
sig { returns(T.nilable(MacOSVersion)) }
def on_system_block_min_os; end
def sha256; end
def staged_path; end
sig { returns(T.nilable(::Cask::URL)) }
def url; end
def version; end
end
end

View File

@ -176,6 +176,21 @@ module Cask
@manpagedir ||= T.let(HOMEBREW_PREFIX/"share/man", T.nilable(Pathname)) @manpagedir ||= T.let(HOMEBREW_PREFIX/"share/man", T.nilable(Pathname))
end end
sig { returns(Pathname) }
def bash_completion
@bash_completion ||= T.let(HOMEBREW_PREFIX/"etc/bash_completion.d", T.nilable(Pathname))
end
sig { returns(Pathname) }
def zsh_completion
@zsh_completion ||= T.let(HOMEBREW_PREFIX/"share/zsh/site-functions", T.nilable(Pathname))
end
sig { returns(Pathname) }
def fish_completion
@fish_completion ||= T.let(HOMEBREW_PREFIX/"share/fish/vendor_completions.d", T.nilable(Pathname))
end
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
def languages def languages
[ [

View File

@ -1,7 +1,6 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: true # rubocop:todo Sorbet/StrictSigil
# frozen_string_literal: true # frozen_string_literal: true
require "attrable"
require "locale" require "locale"
require "lazy_object" require "lazy_object"
require "livecheck" require "livecheck"
@ -54,6 +53,9 @@ module Cask
Artifact::Suite, Artifact::Suite,
Artifact::VstPlugin, Artifact::VstPlugin,
Artifact::Vst3Plugin, Artifact::Vst3Plugin,
Artifact::ZshCompletion,
Artifact::FishCompletion,
Artifact::BashCompletion,
Artifact::Uninstall, Artifact::Uninstall,
Artifact::Zap, Artifact::Zap,
].freeze ].freeze
@ -105,19 +107,36 @@ module Cask
*ARTIFACT_BLOCK_CLASSES.flat_map { |klass| [klass.dsl_key, klass.uninstall_dsl_key] }, *ARTIFACT_BLOCK_CLASSES.flat_map { |klass| [klass.dsl_key, klass.uninstall_dsl_key] },
]).freeze ]).freeze
extend Attrable include OnSystem::MacOSAndLinux
include OnSystem::MacOSOnly
attr_reader :cask, :token, :deprecation_date, :deprecation_reason, :deprecation_replacement, :disable_date, attr_reader :cask, :token, :deprecation_date, :deprecation_reason, :deprecation_replacement, :disable_date,
:disable_reason, :disable_replacement, :on_system_block_min_os :disable_reason, :disable_replacement, :on_system_block_min_os
attr_predicate :deprecated?, :disabled?, :livecheck_defined?, :on_system_blocks_exist?, :depends_on_set_in_block?
def initialize(cask) def initialize(cask)
@cask = cask @cask = cask
@depends_on_set_in_block = T.let(false, T::Boolean)
@deprecated = T.let(false, T::Boolean)
@disabled = T.let(false, T::Boolean)
@livecheck_defined = T.let(false, T::Boolean)
@on_system_blocks_exist = T.let(false, T::Boolean)
@token = cask.token @token = cask.token
end end
sig { returns(T::Boolean) }
def depends_on_set_in_block? = @depends_on_set_in_block
sig { returns(T::Boolean) }
def deprecated? = @deprecated
sig { returns(T::Boolean) }
def disabled? = @disabled
sig { returns(T::Boolean) }
def livecheck_defined? = @livecheck_defined
sig { returns(T::Boolean) }
def on_system_blocks_exist? = @on_system_blocks_exist
# Specifies the cask's name. # Specifies the cask's name.
# #
# NOTE: Multiple names can be specified. # NOTE: Multiple names can be specified.
@ -287,6 +306,7 @@ module Cask
# #
# @see DSL::Version # @see DSL::Version
# @api public # @api public
sig { params(arg: T.nilable(T.any(String, Symbol))).returns(T.nilable(DSL::Version)) }
def version(arg = nil) def version(arg = nil)
set_unique_stanza(:version, arg.nil?) do set_unique_stanza(:version, arg.nil?) do
if !arg.is_a?(String) && arg != :latest if !arg.is_a?(String) && arg != :latest
@ -310,18 +330,36 @@ module Cask
# For architecture-dependent downloads: # For architecture-dependent downloads:
# #
# ```ruby # ```ruby
# sha256 arm: "7bdb497080ffafdfd8cc94d8c62b004af1be9599e865e5555e456e2681e150ca", # sha256 arm: "7bdb497080ffafdfd8cc94d8c62b004af1be9599e865e5555e456e2681e150ca",
# intel: "b3c1c2442480a0219b9e05cf91d03385858c20f04b764ec08a3fa83d1b27e7b2" # x86_64: "b3c1c2442480a0219b9e05cf91d03385858c20f04b764ec08a3fa83d1b27e7b2"
# x86_64_linux: "1a2aee7f1ddc999993d4d7d42a150c5e602bc17281678050b8ed79a0500cc90f"
# arm64_linux: "bd766af7e692afceb727a6f88e24e6e68d9882aeb3e8348412f6c03d96537c75"
# ``` # ```
# #
# @api public # @api public
def sha256(arg = nil, arm: nil, intel: nil) sig {
should_return = arg.nil? && arm.nil? && intel.nil? params(
arg: T.nilable(T.any(String, Symbol)),
arm: T.nilable(String),
intel: T.nilable(String),
x86_64: T.nilable(String),
x86_64_linux: T.nilable(String),
arm64_linux: T.nilable(String),
).returns(T.nilable(T.any(Symbol, Checksum)))
}
def sha256(arg = nil, arm: nil, intel: nil, x86_64: nil, x86_64_linux: nil, arm64_linux: nil)
should_return = arg.nil? && arm.nil? && (intel.nil? || x86_64.nil?) && x86_64_linux.nil? && arm64_linux.nil?
x86_64 ||= intel if intel.present? && x86_64.nil?
set_unique_stanza(:sha256, should_return) do set_unique_stanza(:sha256, should_return) do
@on_system_blocks_exist = true if arm.present? || intel.present? if arm.present? || x86_64.present? || x86_64_linux.present? || arm64_linux.present?
@on_system_blocks_exist = true
end
val = arg || on_arch_conditional(arm:, intel:) val = arg || on_system_conditional(
macos: on_arch_conditional(arm:, intel: x86_64),
linux: on_arch_conditional(arm: arm64_linux, intel: x86_64_linux),
)
case val case val
when :no_check when :no_check
val val
@ -352,6 +390,31 @@ module Cask
end end
end end
# Sets the cask's os strings.
#
# ### Example
#
# ```ruby
# os macos: "darwin", linux: "tux"
# ```
#
# @api public
sig {
params(
macos: T.nilable(String),
linux: T.nilable(String),
).returns(T.nilable(String))
}
def os(macos: nil, linux: nil)
should_return = macos.nil? && linux.nil?
set_unique_stanza(:os, should_return) do
@on_system_blocks_exist = true
on_system_conditional(macos:, linux:)
end
end
# Declare dependencies and requirements for a cask. # Declare dependencies and requirements for a cask.
# #
# NOTE: Multiple dependencies can be specified. # NOTE: Multiple dependencies can be specified.
@ -370,6 +433,13 @@ module Cask
@depends_on @depends_on
end end
# @api private
def add_implicit_macos_dependency
return if @depends_on.present? && @depends_on.macos.present?
depends_on macos: ">= :#{MacOSVersion::SYMBOLS.key MacOSVersion::SYMBOLS.values.min}"
end
# Declare conflicts that keep a cask from installing or working correctly. # Declare conflicts that keep a cask from installing or working correctly.
# #
# @api public # @api public
@ -382,6 +452,7 @@ module Cask
@artifacts ||= ArtifactSet.new @artifacts ||= ArtifactSet.new
end end
sig { returns(Pathname) }
def caskroom_path def caskroom_path
cask.caskroom_path cask.caskroom_path
end end
@ -389,6 +460,7 @@ module Cask
# The staged location for this cask, including version number. # The staged location for this cask, including version number.
# #
# @api public # @api public
sig { returns(Pathname) }
def staged_path def staged_path
return @staged_path if @staged_path return @staged_path if @staged_path
@ -520,9 +592,15 @@ module Cask
true true
end end
sig { returns(T.nilable(MacOSVersion)) }
def os_version
nil
end
# The directory `app`s are installed into. # The directory `app`s are installed into.
# #
# @api public # @api public
sig { returns(T.any(Pathname, String)) }
def appdir def appdir
return HOMEBREW_CASK_APPDIR_PLACEHOLDER if Cask.generating_hash? return HOMEBREW_CASK_APPDIR_PLACEHOLDER if Cask.generating_hash?

View File

@ -1,8 +1,6 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: true # rubocop:todo Sorbet/StrictSigil
# frozen_string_literal: true # frozen_string_literal: true
require "attrable"
module Cask module Cask
class DSL class DSL
# Class corresponding to the `caveats` stanza. # Class corresponding to the `caveats` stanza.
@ -15,10 +13,6 @@ module Cask
# to the output by the caller, but that feature is only for the # to the output by the caller, but that feature is only for the
# convenience of cask authors. # convenience of cask authors.
class Caveats < Base class Caveats < Base
extend Attrable
attr_predicate :discontinued?
def initialize(*args) def initialize(*args)
super super
@built_in_caveats = {} @built_in_caveats = {}
@ -37,6 +31,9 @@ module Cask
private_class_method :caveat private_class_method :caveat
sig { returns(T::Boolean) }
def discontinued? = @discontinued
sig { returns(String) } sig { returns(String) }
def to_s def to_s
(@custom_caveats + @built_in_caveats.values).join("\n") (@custom_caveats + @built_in_caveats.values).join("\n")

View File

@ -1,7 +1,6 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: true # rubocop:todo Sorbet/StrictSigil
# frozen_string_literal: true # frozen_string_literal: true
require "attrable"
require "formula_installer" require "formula_installer"
require "unpack_strategy" require "unpack_strategy"
require "utils/topological_hash" require "utils/topological_hash"
@ -17,8 +16,15 @@ require "cgi"
module Cask module Cask
# Installer for a {Cask}. # Installer for a {Cask}.
class Installer class Installer
extend Attrable sig {
params(
cask: ::Cask::Cask, command: T::Class[SystemCommand], force: T::Boolean, adopt: T::Boolean,
skip_cask_deps: T::Boolean, binaries: T::Boolean, verbose: T::Boolean, zap: T::Boolean,
require_sha: T::Boolean, upgrade: T::Boolean, reinstall: T::Boolean, installed_as_dependency: T::Boolean,
installed_on_request: T::Boolean, quarantine: T::Boolean, verify_download_integrity: T::Boolean,
quiet: T::Boolean
).void
}
def initialize(cask, command: SystemCommand, force: false, adopt: false, def initialize(cask, command: SystemCommand, force: false, adopt: false,
skip_cask_deps: false, binaries: true, verbose: false, skip_cask_deps: false, binaries: true, verbose: false,
zap: false, require_sha: false, upgrade: false, reinstall: false, zap: false, require_sha: false, upgrade: false, reinstall: false,
@ -42,9 +48,44 @@ module Cask
@quiet = quiet @quiet = quiet
end end
attr_predicate :binaries?, :force?, :adopt?, :skip_cask_deps?, :require_sha?, sig { returns(T::Boolean) }
:reinstall?, :upgrade?, :verbose?, :zap?, :installed_as_dependency?, :installed_on_request?, def adopt? = @adopt
:quarantine?, :quiet?
sig { returns(T::Boolean) }
def binaries? = @binaries
sig { returns(T::Boolean) }
def force? = @force
sig { returns(T::Boolean) }
def installed_as_dependency? = @installed_as_dependency
sig { returns(T::Boolean) }
def installed_on_request? = @installed_on_request
sig { returns(T::Boolean) }
def quarantine? = @quarantine
sig { returns(T::Boolean) }
def quiet? = @quiet
sig { returns(T::Boolean) }
def reinstall? = @reinstall
sig { returns(T::Boolean) }
def require_sha? = @require_sha
sig { returns(T::Boolean) }
def skip_cask_deps? = @skip_cask_deps
sig { returns(T::Boolean) }
def upgrade? = @upgrade
sig { returns(T::Boolean) }
def verbose? = @verbose
sig { returns(T::Boolean) }
def zap? = @zap
def self.caveats(cask) def self.caveats(cask)
odebug "Printing caveats" odebug "Printing caveats"

View File

@ -1,11 +1,9 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
module OS module OS
module Mac module Mac
module_function SYSTEM_DIRS = T.let([
SYSTEM_DIRS = [
"/", "/",
"/Applications", "/Applications",
"/Applications/Utilities", "/Applications/Utilities",
@ -234,14 +232,13 @@ module OS
"/var/spool/mail", "/var/spool/mail",
"/var/tmp", "/var/tmp",
] ]
.map(&method(:Pathname)) .to_set { Pathname(_1) }
.to_set .freeze, T::Set[Pathname])
.freeze
private_constant :SYSTEM_DIRS private_constant :SYSTEM_DIRS
# TODO: There should be a way to specify a containing # TODO: There should be a way to specify a containing
# directory under which nothing can be deleted. # directory under which nothing can be deleted.
UNDELETABLE_PATHS = [ UNDELETABLE_PATHS = T.let([
"~/", "~/",
"~/Applications", "~/Applications",
"~/Applications/.localized", "~/Applications/.localized",
@ -378,14 +375,16 @@ module OS
] ]
.to_set { |path| Pathname(path.sub(%r{^~(?=(/|$))}, Dir.home)).expand_path } .to_set { |path| Pathname(path.sub(%r{^~(?=(/|$))}, Dir.home)).expand_path }
.union(SYSTEM_DIRS) .union(SYSTEM_DIRS)
.freeze .freeze, T::Set[Pathname])
private_constant :UNDELETABLE_PATHS private_constant :UNDELETABLE_PATHS
def system_dir?(dir) sig { params(dir: T.any(Pathname, String)).returns(T::Boolean) }
def self.system_dir?(dir)
SYSTEM_DIRS.include?(Pathname.new(dir).expand_path) SYSTEM_DIRS.include?(Pathname.new(dir).expand_path)
end end
def undeletable?(path) sig { params(path: T.any(Pathname, String)).returns(T::Boolean) }
def self.undeletable?(path)
UNDELETABLE_PATHS.include?(Pathname.new(path).expand_path) UNDELETABLE_PATHS.include?(Pathname.new(path).expand_path)
end end
end end

View File

@ -1,32 +1,32 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
module Cask module Cask
class Reinstall class Reinstall
sig {
params(
casks: ::Cask::Cask, verbose: T::Boolean, force: T::Boolean, skip_cask_deps: T::Boolean, binaries: T::Boolean,
require_sha: T::Boolean, quarantine: T::Boolean, zap: T::Boolean
).void
}
def self.reinstall_casks( def self.reinstall_casks(
*casks, *casks,
verbose: nil, verbose: false,
force: nil, force: false,
skip_cask_deps: nil, skip_cask_deps: false,
binaries: nil, binaries: false,
require_sha: nil, require_sha: false,
quarantine: nil, quarantine: false,
zap: nil zap: false
) )
require "cask/installer" require "cask/installer"
quarantine = true if quarantine.nil? quarantine = true if quarantine.nil?
casks.each do |cask| casks.each do |cask|
Installer.new(cask, Installer
binaries:, .new(cask, binaries:, verbose:, force:, skip_cask_deps:, require_sha:, reinstall: true, quarantine:, zap:)
verbose:, .install
force:,
skip_cask_deps:,
require_sha:,
reinstall: true,
quarantine:,
zap:).install
end end
end end
end end

View File

@ -8,7 +8,7 @@ module Cask
module Staged module Staged
extend T::Helpers extend T::Helpers
requires_ancestor { Kernel } requires_ancestor { ::Cask::DSL::Base }
Paths = T.type_alias { T.any(String, Pathname, T::Array[T.any(String, Pathname)]) } Paths = T.type_alias { T.any(String, Pathname, T::Array[T.any(String, Pathname)]) }
sig { params(paths: Paths, permissions_str: String).void } sig { params(paths: Paths, permissions_str: String).void }

View File

@ -1,9 +1,10 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
module Cask module Cask
class Uninstall class Uninstall
def self.uninstall_casks(*casks, binaries: nil, force: false, verbose: false) sig { params(casks: ::Cask::Cask, binaries: T::Boolean, force: T::Boolean, verbose: T::Boolean).void }
def self.uninstall_casks(*casks, binaries: false, force: false, verbose: false)
require "cask/installer" require "cask/installer"
casks.each do |cask| casks.each do |cask|

View File

@ -42,9 +42,16 @@ module Cask
greedy = true if Homebrew::EnvConfig.upgrade_greedy? greedy = true if Homebrew::EnvConfig.upgrade_greedy?
greedy_casks = if (upgrade_greedy_casks = Homebrew::EnvConfig.upgrade_greedy_casks.presence)
upgrade_greedy_casks.split
else
[]
end
outdated_casks = if casks.empty? outdated_casks = if casks.empty?
Caskroom.casks(config: Config.from_args(args)).select do |cask| Caskroom.casks(config: Config.from_args(args)).select do |cask|
cask.outdated?(greedy:, greedy_latest:, cask_greedy = greedy || greedy_casks.include?(cask.token)
cask.outdated?(greedy: cask_greedy, greedy_latest:,
greedy_auto_updates:) greedy_auto_updates:)
end end
else else
@ -78,7 +85,7 @@ module Cask
return false if outdated_casks.empty? return false if outdated_casks.empty?
if casks.empty? && !greedy if casks.empty? && !greedy && greedy_casks.empty?
if !greedy_auto_updates && !greedy_latest if !greedy_auto_updates && !greedy_latest
ohai "Casks with 'auto_updates true' or 'version :latest' " \ ohai "Casks with 'auto_updates true' or 'version :latest' " \
"will not be upgraded; pass `--greedy` to upgrade them." "will not be upgraded; pass `--greedy` to upgrade them."

View File

@ -267,6 +267,7 @@ module Cask
return false unless interpolated_url return false unless interpolated_url
interpolated_url = interpolated_url.gsub(/\#{\s*arch\s*}/, "")
interpolated_url = interpolated_url.gsub(/\#{\s*version\s*\.major\s*}/, "") if ignore_major_version interpolated_url = interpolated_url.gsub(/\#{\s*version\s*\.major\s*}/, "") if ignore_major_version
interpolated_url.exclude?('#{') interpolated_url.exclude?('#{')

View File

@ -19,8 +19,8 @@ class Caveats
sig { returns(String) } sig { returns(String) }
def caveats def caveats
caveats = [] caveats = []
build = formula.build
begin begin
build = formula.build
formula.build = Tab.for_formula(formula) formula.build = Tab.for_formula(formula)
string = formula.caveats.to_s string = formula.caveats.to_s
caveats << "#{string.chomp}\n" unless string.empty? caveats << "#{string.chomp}\n" unless string.empty?
@ -29,7 +29,7 @@ class Caveats
end end
caveats << keg_only_text caveats << keg_only_text
valid_shells = [:bash, :zsh, :fish].freeze valid_shells = [:bash, :zsh, :fish, :pwsh].freeze
current_shell = Utils::Shell.preferred || Utils::Shell.parent current_shell = Utils::Shell.preferred || Utils::Shell.parent
shells = if current_shell.present? && shells = if current_shell.present? &&
(shell_sym = current_shell.to_sym) && (shell_sym = current_shell.to_sym) &&
@ -143,6 +143,11 @@ class Caveats
zsh #{installed.join(" and ")} have been installed to: zsh #{installed.join(" and ")} have been installed to:
#{root_dir}/share/zsh/site-functions #{root_dir}/share/zsh/site-functions
EOS EOS
when :pwsh
<<~EOS
PowerShell completion has been installed to:
#{root_dir}/share/pwsh/completions
EOS
end end
end end

View File

@ -114,6 +114,7 @@ class Cleaner
# Arch & MacPorts amongst other packagers as well. The files are # Arch & MacPorts amongst other packagers as well. The files are
# created as part of installing any Perl module. # created as part of installing any Perl module.
PERL_BASENAMES = T.let(Set.new(%w[perllocal.pod .packlist]).freeze, T::Set[String]) PERL_BASENAMES = T.let(Set.new(%w[perllocal.pod .packlist]).freeze, T::Set[String])
private_constant :PERL_BASENAMES
# Clean a top-level (`bin`, `sbin`, `lib`) directory, recursively, by fixing file # Clean a top-level (`bin`, `sbin`, `lib`) directory, recursively, by fixing file
# permissions and removing .la files, unless the files (or parent # permissions and removing .la files, unless the files (or parent

View File

@ -3,7 +3,6 @@
require "utils/bottles" require "utils/bottles"
require "attrable"
require "formula" require "formula"
require "cask/cask_loader" require "cask/cask_loader"
@ -209,11 +208,8 @@ module Homebrew
end end
end end
extend Attrable
PERIODIC_CLEAN_FILE = (HOMEBREW_CACHE/".cleaned").freeze PERIODIC_CLEAN_FILE = (HOMEBREW_CACHE/".cleaned").freeze
attr_predicate :dry_run?, :scrub?, :prune?
attr_reader :args, :days, :cache, :disk_cleanup_size attr_reader :args, :days, :cache, :disk_cleanup_size
def initialize(*args, dry_run: false, scrub: false, days: nil, cache: HOMEBREW_CACHE) def initialize(*args, dry_run: false, scrub: false, days: nil, cache: HOMEBREW_CACHE)
@ -227,6 +223,15 @@ module Homebrew
@cleaned_up_paths = Set.new @cleaned_up_paths = Set.new
end end
sig { returns(T::Boolean) }
def dry_run? = @dry_run
sig { returns(T::Boolean) }
def prune? = @prune
sig { returns(T::Boolean) }
def scrub? = @scrub
def self.install_formula_clean!(formula, dry_run: false) def self.install_formula_clean!(formula, dry_run: false)
return if Homebrew::EnvConfig.no_install_cleanup? return if Homebrew::EnvConfig.no_install_cleanup?
return unless formula.latest_version_installed? return unless formula.latest_version_installed?

View File

@ -221,8 +221,8 @@ module Homebrew
deps = dependency.runtime_dependencies if @use_runtime_dependencies deps = dependency.runtime_dependencies if @use_runtime_dependencies
if recursive if recursive
deps ||= recursive_includes(Dependency, dependency, includes, ignores) deps ||= recursive_dep_includes(dependency, includes, ignores)
reqs = recursive_includes(Requirement, dependency, includes, ignores) reqs = recursive_req_includes(dependency, includes, ignores)
else else
deps ||= select_includes(dependency.deps, ignores, includes) deps ||= select_includes(dependency.deps, ignores, includes)
reqs = select_includes(dependency.requirements, ignores, includes) reqs = select_includes(dependency.requirements, ignores, includes)

View File

@ -209,16 +209,18 @@ module Homebrew
formulae.map(&:to_hash) formulae.map(&:to_hash)
end end
when :v2 when :v2
formulae, casks = if all formulae, casks = T.let(
[ if all
Formula.all(eval_all: args.eval_all?).sort, [
Cask::Cask.all(eval_all: args.eval_all?).sort_by(&:full_name), Formula.all(eval_all: args.eval_all?).sort,
] Cask::Cask.all(eval_all: args.eval_all?).sort_by(&:full_name),
elsif args.installed? ]
[Formula.installed.sort, Cask::Caskroom.casks.sort_by(&:full_name)] elsif args.installed?
else [Formula.installed.sort, Cask::Caskroom.casks.sort_by(&:full_name)]
args.named.to_formulae_to_casks else
end T.cast(args.named.to_formulae_to_casks, [T::Array[Formula], T::Array[Cask::Cask]])
end, [T::Array[Formula], T::Array[Cask::Cask]]
)
if args.variations? if args.variations?
{ {

View File

@ -123,7 +123,8 @@ module Homebrew
raise UsageError, "Cannot use #{flags.join(", ")} with formula arguments." unless args.no_named? raise UsageError, "Cannot use #{flags.join(", ")} with formula arguments." unless args.no_named?
formulae = if args.t? formulae = if args.t?
Formula.installed.sort_by { |formula| test("M", formula.rack) }.reverse! # See https://ruby-doc.org/3.2/Kernel.html#method-i-test
Formula.installed.sort_by { |formula| T.cast(test("M", formula.rack.to_s), Time) }.reverse!
elsif args.full_name? elsif args.full_name?
Formula.installed.sort { |a, b| tap_and_name_comparison.call(a.full_name, b.full_name) } Formula.installed.sort { |a, b| tap_and_name_comparison.call(a.full_name, b.full_name) }
else else

View File

@ -33,7 +33,7 @@ module Homebrew
end end
ff.each do |f| ff.each do |f|
missing = f.missing_dependencies(hide: args.hide) missing = f.missing_dependencies(hide: args.hide || [])
next if missing.empty? next if missing.empty?
Homebrew.failed = true Homebrew.failed = true

View File

@ -59,14 +59,14 @@ module Homebrew
elsif args.no_named? elsif args.no_named?
puts Tap.installed.sort_by(&:name) puts Tap.installed.sort_by(&:name)
else else
tap = Tap.fetch(args.named.fetch(0))
begin begin
tap = Tap.fetch(args.named.fetch(0))
tap.install clone_target: args.named.second, tap.install clone_target: args.named.second,
custom_remote: args.custom_remote?, custom_remote: args.custom_remote?,
quiet: args.quiet?, quiet: args.quiet?,
verify: args.eval_all? || Homebrew::EnvConfig.eval_all?, verify: args.eval_all? || Homebrew::EnvConfig.eval_all?,
force: args.force? force: args.force?
rescue TapRemoteMismatchError, TapNoCustomRemoteError => e rescue Tap::InvalidNameError, TapRemoteMismatchError, TapNoCustomRemoteError => e
odie e odie e
rescue TapAlreadyTappedError rescue TapAlreadyTappedError
nil nil

View File

@ -161,7 +161,7 @@ module Homebrew
end end
next unless formula.any_version_installed? next unless formula.any_version_installed?
keg = formula.installed_kegs.last keg = formula.installed_kegs.fetch(-1)
tab = keg.tab tab = keg.tab
# force a `brew upgrade` from the linuxbrew-core version to the homebrew-core version (even if lower) # force a `brew upgrade` from the linuxbrew-core version to the homebrew-core version (even if lower)
tab.source["versions"]["version_scheme"] = -1 tab.source["versions"]["version_scheme"] = -1
@ -277,7 +277,7 @@ module Homebrew
puts puts
new_major_version, new_minor_version, new_patch_version = new_tag.split(".").map(&:to_i) new_major_version, new_minor_version, new_patch_version = new_tag.split(".").map(&:to_i)
old_major_version, old_minor_version = (old_tag.split(".")[0, 2]).map(&:to_i) if old_tag.present? old_major_version, old_minor_version = old_tag.split(".")[0, 2].map(&:to_i) if old_tag.present?
if old_tag.blank? || new_major_version > old_major_version || new_minor_version > old_minor_version if old_tag.blank? || new_major_version > old_major_version || new_minor_version > old_minor_version
puts <<~EOS puts <<~EOS
The #{new_major_version}.#{new_minor_version}.0 release notes are available on the Homebrew Blog: The #{new_major_version}.#{new_minor_version}.0 release notes are available on the Homebrew Blog:

View File

@ -94,7 +94,7 @@ module Homebrew
sig { sig {
params(use_runtime_dependents: T::Boolean, used_formulae: T::Array[T.any(Formula, UnavailableFormula)]) params(use_runtime_dependents: T::Boolean, used_formulae: T::Array[T.any(Formula, UnavailableFormula)])
.returns(T::Array[Formula]) .returns(T::Array[T.any(Formula, CaskDependent)])
} }
def intersection_of_dependents(use_runtime_dependents, used_formulae) def intersection_of_dependents(use_runtime_dependents, used_formulae)
recursive = args.recursive? recursive = args.recursive?
@ -106,9 +106,9 @@ module Homebrew
# We can only get here if `used_formulae_missing` is false, thus there are no UnavailableFormula. # We can only get here if `used_formulae_missing` is false, thus there are no UnavailableFormula.
used_formulae = T.cast(used_formulae, T::Array[Formula]) used_formulae = T.cast(used_formulae, T::Array[Formula])
if show_formulae_and_casks || args.formula? if show_formulae_and_casks || args.formula?
deps += used_formulae.map(&:runtime_installed_formula_dependents) deps += T.must(used_formulae.map(&:runtime_installed_formula_dependents)
.reduce(&:&) .reduce(&:&))
.select(&:any_version_installed?) .select(&:any_version_installed?)
end end
if show_formulae_and_casks || args.cask? if show_formulae_and_casks || args.cask?
deps += select_used_dependents( deps += select_used_dependents(
@ -150,26 +150,30 @@ module Homebrew
sig { sig {
params( params(
dependents: T::Array[Formula], used_formulae: T::Array[T.any(Formula, UnavailableFormula)], dependents: T::Array[T.any(Formula, CaskDependent)],
recursive: T::Boolean, includes: T::Array[Symbol], ignores: T::Array[Symbol] used_formulae: T::Array[T.any(Formula, UnavailableFormula)],
).returns( recursive: T::Boolean,
T::Array[Formula], includes: T::Array[Symbol],
) ignores: T::Array[Symbol],
).returns(T::Array[T.any(Formula, CaskDependent)])
} }
def select_used_dependents(dependents, used_formulae, recursive, includes, ignores) def select_used_dependents(dependents, used_formulae, recursive, includes, ignores)
dependents.select do |d| dependents.select do |d|
deps = if recursive deps = if recursive
recursive_includes(Dependency, d, includes, ignores) recursive_dep_includes(d, includes, ignores)
else else
select_includes(d.deps, ignores, includes) select_includes(d.deps, ignores, includes)
end end
used_formulae.all? do |ff| used_formulae.all? do |ff|
deps.any? do |dep| deps.any? do |dep|
match = begin match = case dep
when Dependency
dep.to_formula.full_name == ff.full_name if dep.name.include?("/") dep.to_formula.full_name == ff.full_name if dep.name.include?("/")
rescue when Requirement
nil nil
else
T.absurd(dep)
end end
next match unless match.nil? next match unless match.nil?

View File

@ -92,6 +92,7 @@ class CompilerFailure
create(:clang), create(:clang),
], ],
}.freeze }.freeze
private_constant :COLLECTIONS
end end
# Class for selecting a compiler for a formula. # Class for selecting a compiler for a formula.

View File

@ -1,7 +1,6 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: true # rubocop:todo Sorbet/StrictSigil
# frozen_string_literal: true # frozen_string_literal: true
require "attrable"
require "mutex_m" require "mutex_m"
require "ignorable" require "ignorable"
@ -73,9 +72,10 @@ module Debrew
@debugged_exceptions = Set.new @debugged_exceptions = Set.new
class << self class << self
extend Attrable
attr_predicate :active?
attr_reader :debugged_exceptions attr_reader :debugged_exceptions
sig { returns(T::Boolean) }
def active? = @active
end end
def self.debrew def self.debrew

View File

@ -39,11 +39,6 @@ class Dependencies < SimpleDelegator
def inspect def inspect
"#<#{self.class.name}: #{__getobj__}>" "#<#{self.class.name}: #{__getobj__}>"
end end
sig { returns(T::Array[Dependency]) }
def to_a
__getobj__.to_a
end
end end
# A collection of requirements. # A collection of requirements.

View File

@ -1,13 +1,32 @@
# typed: strict # typed: strict
class Dependencies < SimpleDelegator class Dependencies < SimpleDelegator
include Enumerable
include Kernel include Kernel
# This is a workaround to enable `alias eql? ==` # This is a workaround to enable `alias eql? ==`
# @see https://github.com/sorbet/sorbet/issues/2378#issuecomment-569474238 # @see https://github.com/sorbet/sorbet/issues/2378#issuecomment-569474238
sig { params(other: BasicObject).returns(T::Boolean) } sig { params(other: BasicObject).returns(T::Boolean) }
def ==(other); end def ==(other); end
sig { params(blk: T.proc.params(arg0: Dependency).void).returns(T.self_type) }
sig { returns(T::Enumerator[Dependency]) }
def each(&blk); end
sig { params(blk: T.proc.params(arg0: Dependency).returns(T::Boolean)).returns(T::Array[Dependency]) }
sig { returns(T::Enumerator[Dependency]) }
def select(&blk); end
end end
class Requirements < SimpleDelegator class Requirements < SimpleDelegator
include Enumerable
include Kernel include Kernel
sig { params(blk: T.proc.params(arg0: Requirement).void).returns(T.self_type) }
sig { returns(T::Enumerator[Requirement]) }
def each(&blk); end
sig { params(blk: T.proc.params(arg0: Requirement).returns(T::Boolean)).returns(T::Array[Requirement]) }
sig { returns(T::Enumerator[Requirement]) }
def select(&blk); end
end end

View File

@ -1,14 +1,10 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "cask_dependent" require "cask_dependent"
# Helper functions for dependencies. # Helper functions for dependencies.
module DependenciesHelpers module DependenciesHelpers
extend T::Helpers
requires_ancestor { Kernel }
def args_includes_ignores(args) def args_includes_ignores(args)
includes = [:required?, :recommended?] # included by default includes = [:required?, :recommended?] # included by default
includes << :implicit? if args.include_implicit? includes << :implicit? if args.include_implicit?
@ -23,9 +19,35 @@ module DependenciesHelpers
[includes, ignores] [includes, ignores]
end end
def recursive_includes(klass, root_dependent, includes, ignores) sig {
raise ArgumentError, "Invalid class argument: #{klass}" if klass != Dependency && klass != Requirement params(root_dependent: T.any(Formula, CaskDependent), includes: T::Array[Symbol], ignores: T::Array[Symbol])
.returns(T::Array[Dependency])
}
def recursive_dep_includes(root_dependent, includes, ignores)
# The use of T.unsafe is recommended by the Sorbet docs:
# https://sorbet.org/docs/overloads#multiple-methods-but-sharing-a-common-implementation
T.unsafe(recursive_includes(Dependency, root_dependent, includes, ignores))
end
sig {
params(root_dependent: T.any(Formula, CaskDependent), includes: T::Array[Symbol], ignores: T::Array[Symbol])
.returns(Requirements)
}
def recursive_req_includes(root_dependent, includes, ignores)
# The use of T.unsafe is recommended by the Sorbet docs:
# https://sorbet.org/docs/overloads#multiple-methods-but-sharing-a-common-implementation
T.unsafe(recursive_includes(Requirement, root_dependent, includes, ignores))
end
sig {
params(
klass: T.any(T.class_of(Dependency), T.class_of(Requirement)),
root_dependent: T.any(Formula, CaskDependent),
includes: T::Array[Symbol],
ignores: T::Array[Symbol],
).returns(T.any(T::Array[Dependency], Requirements))
}
def recursive_includes(klass, root_dependent, includes, ignores)
cache_key = "recursive_includes_#{includes}_#{ignores}" cache_key = "recursive_includes_#{includes}_#{ignores}"
klass.expand(root_dependent, cache_key:) do |dependent, dep| klass.expand(root_dependent, cache_key:) do |dependent, dep|
@ -43,6 +65,13 @@ module DependenciesHelpers
end end
end end
sig {
params(
dependables: T.any(Dependencies, Requirements, T::Array[Dependency], T::Array[Requirement]),
ignores: T::Array[Symbol],
includes: T::Array[Symbol],
).returns(T::Array[T.any(Dependency, Requirement)])
}
def select_includes(dependables, ignores, includes) def select_includes(dependables, ignores, includes)
dependables.select do |dep| dependables.select do |dep|
next false if ignores.any? { |ignore| dep.public_send(ignore) } next false if ignores.any? { |ignore| dep.public_send(ignore) }
@ -51,14 +80,18 @@ module DependenciesHelpers
end end
end end
sig {
params(formulae_or_casks: T::Array[T.any(Formula, Keg, Cask::Cask)])
.returns(T::Array[T.any(Formula, CaskDependent)])
}
def dependents(formulae_or_casks) def dependents(formulae_or_casks)
formulae_or_casks.map do |formula_or_cask| formulae_or_casks.map do |formula_or_cask|
if formula_or_cask.is_a?(Formula) case formula_or_cask
formula_or_cask when Formula then formula_or_cask
when Cask::Cask then CaskDependent.new(formula_or_cask)
else else
CaskDependent.new(formula_or_cask) raise TypeError, "Unsupported type: #{formula_or_cask.class}"
end end
end end
end end
module_function :dependents
end end

View File

@ -0,0 +1,12 @@
# typed: strict
module DependenciesHelpers
include Kernel
# This sig is in an RBI to avoid both circular dependencies and unnecessary requires
sig {
params(args: T.any(Homebrew::Cmd::Deps::Args, Homebrew::Cmd::Uses::Args))
.returns([T::Array[Symbol], T::Array[Symbol]])
}
def args_includes_ignores(args); end
end

View File

@ -261,8 +261,8 @@ module Homebrew
audit_token_conflicts: args.token_conflicts? || nil, audit_token_conflicts: args.token_conflicts? || nil,
quarantine: true, quarantine: true,
any_named_args: !no_named_args, any_named_args: !no_named_args,
only: args.only, only: args.only || [],
except: args.except, except: args.except || [],
).to_a ).to_a
end end
end.uniq end.uniq

View File

@ -559,7 +559,7 @@ 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 Homebrew.default_prefix?(prefix) prefix_check = if prefix == HOMEBREW_DEFAULT_PREFIX
File.join(prefix, "opt") File.join(prefix, "opt")
else else
prefix prefix

View File

@ -115,7 +115,7 @@ module Homebrew
end end
end end
formulae_and_casks = formulae_and_casks&.sort_by do |formula_or_cask| formulae_and_casks = formulae_and_casks.sort_by do |formula_or_cask|
formula_or_cask.respond_to?(:token) ? formula_or_cask.token : formula_or_cask.name formula_or_cask.respond_to?(:token) ? formula_or_cask.token : formula_or_cask.name
end end

View File

@ -40,6 +40,8 @@ module Homebrew
description: "Create a basic template for a Ruby build." description: "Create a basic template for a Ruby build."
switch "--rust", switch "--rust",
description: "Create a basic template for a Rust build." description: "Create a basic template for a Rust build."
switch "--zig",
description: "Create a basic template for a Zig build."
switch "--no-fetch", switch "--no-fetch",
description: "Homebrew will not download <URL> to the cache and will thus not add its SHA-256 " \ description: "Homebrew will not download <URL> to the cache and will thus not add its SHA-256 " \
"to the formula for you, nor will it check the GitHub API for GitHub projects " \ "to the formula for you, nor will it check the GitHub API for GitHub projects " \
@ -58,7 +60,7 @@ module Homebrew
description: "Ignore errors for disallowed formula names and names that shadow aliases." description: "Ignore errors for disallowed formula names and names that shadow aliases."
conflicts "--autotools", "--cmake", "--crystal", "--go", "--meson", "--node", conflicts "--autotools", "--cmake", "--crystal", "--go", "--meson", "--node",
"--perl", "--python", "--ruby", "--rust", "--cask" "--perl", "--python", "--ruby", "--rust", "--zig", "--cask"
conflicts "--cask", "--HEAD" conflicts "--cask", "--HEAD"
conflicts "--cask", "--set-license" conflicts "--cask", "--set-license"
@ -173,6 +175,8 @@ module Homebrew
:ruby :ruby
elsif args.rust? elsif args.rust?
:rust :rust
elsif args.zig?
:zig
end end
fc = FormulaCreator.new( fc = FormulaCreator.new(
@ -220,6 +224,7 @@ module Homebrew
path = fc.write_formula! path = fc.write_formula!
formula = Homebrew.with_no_api_env do formula = Homebrew.with_no_api_env do
CoreTap.instance.clear_cache
Formula[fc.name] Formula[fc.name]
end end
PyPI.update_python_resources! formula, ignore_non_pypi_packages: true if args.python? PyPI.update_python_resources! formula, ignore_non_pypi_packages: true if args.python?

View File

@ -229,7 +229,7 @@ module Homebrew
if record["prefix"] == "custom-prefix" if record["prefix"] == "custom-prefix"
"#{record["prefix"]} (#{record["os"]} #{record["arch"]})" "#{record["prefix"]} (#{record["os"]} #{record["arch"]})"
else else
(record["prefix"]).to_s record["prefix"].to_s
end end
when :os_versions when :os_versions
format_os_version_dimension(record["os_name_and_version"]) format_os_version_dimension(record["os_name_and_version"])

View File

@ -39,7 +39,7 @@ module Homebrew
sig { override.void } sig { override.void }
def run def run
args.named.to_formulae.each do |formula| args.named.to_formulae.each do |formula|
ignore_errors = if T.must(formula.tap).name == "homebrew/core" ignore_errors = if formula.tap&.official?
false false
else else
args.ignore_errors? args.ignore_errors?

View File

@ -16,10 +16,10 @@ require "system_command"
module Homebrew module Homebrew
# Module containing diagnostic checks. # Module containing diagnostic checks.
module Diagnostic module Diagnostic
def self.missing_deps(formulae, hide = nil) def self.missing_deps(formulae, hide = [])
missing = {} missing = {}
formulae.each do |f| formulae.each do |f|
missing_dependencies = f.missing_dependencies(hide:) missing_dependencies = f.missing_dependencies(hide: hide)
next if missing_dependencies.empty? next if missing_dependencies.empty?
yield f.full_name, missing_dependencies if block_given? yield f.full_name, missing_dependencies if block_given?
@ -63,7 +63,7 @@ module Homebrew
end end
end end
sig { params(list: T::Array[String], string: String).returns(String) } sig { params(list: T::Array[T.any(Formula, Pathname, String)], string: String).returns(String) }
def inject_file_list(list, string) def inject_file_list(list, string)
list.reduce(string.dup) { |acc, elem| acc << " #{elem}\n" } list.reduce(string.dup) { |acc, elem| acc << " #{elem}\n" }
.freeze .freeze
@ -748,14 +748,12 @@ module Homebrew
def check_for_unlinked_but_not_keg_only def check_for_unlinked_but_not_keg_only
unlinked = Formula.racks.reject do |rack| unlinked = Formula.racks.reject do |rack|
if (HOMEBREW_LINKED_KEGS/rack.basename).directory? next true if (HOMEBREW_LINKED_KEGS/rack.basename).directory?
true
else begin
begin Formulary.from_rack(rack).keg_only?
Formulary.from_rack(rack).keg_only? rescue FormulaUnavailableError, TapFormulaAmbiguityError
rescue FormulaUnavailableError, TapFormulaAmbiguityError false
false
end
end end
end.map(&:basename) end.map(&:basename)
return if unlinked.empty? return if unlinked.empty?
@ -1040,6 +1038,64 @@ module Homebrew
end end
end end
def non_core_taps
@non_core_taps ||= Tap.installed.reject(&:core_tap?).reject(&:core_cask_tap?)
end
def check_for_duplicate_formulae
return if ENV["HOMEBREW_TEST_BOT"].present?
core_formula_names = CoreTap.instance.formula_names
shadowed_formula_full_names = non_core_taps.flat_map do |tap|
tap_formula_names = tap.formula_names.map { |s| s.delete_prefix("#{tap.name}/") }
(core_formula_names & tap_formula_names).map { |f| "#{tap.name}/#{f}" }
end.compact.sort
return if shadowed_formula_full_names.empty?
installed_formula_tap_names = Formula.installed.filter_map(&:tap).uniq.reject(&:official?).map(&:name)
shadowed_formula_tap_names = shadowed_formula_full_names.map { |s| s.rpartition("/").first }.uniq
unused_shadowed_formula_tap_names = (shadowed_formula_tap_names - installed_formula_tap_names).sort
resolution = if unused_shadowed_formula_tap_names.empty?
"Their taps are in use, so you must use these full names throughout Homebrew."
else
"Some of these can be resolved with:\n brew untap #{unused_shadowed_formula_tap_names.join(" ")}"
end
<<~EOS
The following formulae have the same name as core formulae:
#{shadowed_formula_full_names.join("\n ")}
#{resolution}
EOS
end
def check_for_duplicate_casks
return if ENV["HOMEBREW_TEST_BOT"].present?
core_cask_names = CoreCaskTap.instance.cask_tokens
shadowed_cask_full_names = non_core_taps.flat_map do |tap|
tap_cask_names = tap.cask_tokens.map { |s| s.delete_prefix("#{tap.name}/") }
(core_cask_names & tap_cask_names).map { |f| "#{tap.name}/#{f}" }
end.compact.sort
return if shadowed_cask_full_names.empty?
installed_cask_tap_names = Cask::Caskroom.casks.filter_map(&:tap).uniq.reject(&:official?).map(&:name)
shadowed_cask_tap_names = shadowed_cask_full_names.map { |s| s.rpartition("/").first }.uniq
unused_shadowed_cask_tap_names = (shadowed_cask_tap_names - installed_cask_tap_names).sort
resolution = if unused_shadowed_cask_tap_names.empty?
"Their taps are in use, so you must use these full names throughout Homebrew."
else
"Some of these can be resolved with:\n brew untap #{unused_shadowed_cask_tap_names.join(" ")}"
end
<<~EOS
The following casks have the same name as core casks:
#{shadowed_cask_full_names.join("\n ")}
#{resolution}
EOS
end
def all def all
methods.map(&:to_s).grep(/^check_/).sort methods.map(&:to_s).grep(/^check_/).sort
end end

View File

@ -490,6 +490,10 @@ module Homebrew
description: "If set, pass `--greedy` to all cask upgrade commands.", description: "If set, pass `--greedy` to all cask upgrade commands.",
boolean: true, boolean: true,
}, },
HOMEBREW_UPGRADE_GREEDY_CASKS: {
description: "A space-separated list of casks. Homebrew will act as " \
"if `--greedy` was passed when upgrading any cask on this list.",
},
HOMEBREW_VERBOSE: { HOMEBREW_VERBOSE: {
description: "If set, always assume `--verbose` when running commands.", description: "If set, always assume `--verbose` when running commands.",
boolean: true, boolean: true,

View File

@ -4,3 +4,23 @@ module OnSystem::MacOSOnly
sig { params(arm: T.nilable(String), intel: T.nilable(String)).returns(T.nilable(String)) } sig { params(arm: T.nilable(String), intel: T.nilable(String)).returns(T.nilable(String)) }
def on_arch_conditional(arm: nil, intel: nil); end def on_arch_conditional(arm: nil, intel: nil); end
end end
module OnSystem::MacOSAndLinux
sig {
params(
macos: T.nilable(T.any(T::Array[T.any(String, Pathname)], String, Pathname)),
linux: T.nilable(T.any(T::Array[T.any(String, Pathname)], String, Pathname)),
).returns(T.nilable(T.any(T::Array[T.any(String, Pathname)], String, Pathname)))
}
def on_system_conditional(macos: nil, linux: nil); end
sig {
type_parameters(:U)
.params(block: T.proc.returns(T.type_parameter(:U)))
.returns(T.type_parameter(:U))
}
def on_macos(&block); end
sig { params(arm: T.nilable(String), intel: T.nilable(String)).returns(T.nilable(String)) }
def on_arch_conditional(arm: nil, intel: nil); end
end

View File

@ -0,0 +1,5 @@
# typed: strict
# frozen_string_literal: true
require "extend/os/mac/cask/artifact/abstract_uninstall" if OS.mac?
require "extend/os/linux/cask/artifact/abstract_uninstall" if OS.linux?

View File

@ -0,0 +1,5 @@
# typed: strict
# frozen_string_literal: true
require "extend/os/mac/cask/artifact/symlinked" if OS.mac?
require "extend/os/linux/cask/artifact/symlinked" if OS.linux?

View File

@ -0,0 +1,4 @@
# typed: strict
# frozen_string_literal: true
require "extend/os/mac/cask/dsl" if OS.mac?

View File

@ -0,0 +1,23 @@
# typed: strict
# frozen_string_literal: true
module OS
module Linux
module Cask
module Artifact
module AbstractUninstall
extend T::Helpers
requires_ancestor { ::Cask::Artifact::AbstractUninstall }
sig { params(target: Pathname).returns(T::Boolean) }
def undeletable?(target)
!target.parent.writable?
end
end
end
end
end
end
Cask::Artifact::AbstractUninstall.prepend(OS::Linux::Cask::Artifact::AbstractUninstall)

View File

@ -20,4 +20,4 @@ module OS
end end
end end
Cask::Artifact::Moved.prepend(OS::Linux::Cask::Config) Cask::Artifact::Moved.prepend(OS::Linux::Cask::Artifact::Moved)

View File

@ -0,0 +1,26 @@
# typed: strict
# frozen_string_literal: true
module OS
module Linux
module Cask
module Artifact
module Symlinked
extend T::Helpers
requires_ancestor { ::Cask::Artifact::Symlinked }
sig { params(command: T.class_of(SystemCommand)).void }
def create_filesystem_link(command)
::Cask::Utils.gain_permissions_mkpath(target.dirname, command:)
command.run! "/bin/ln", args: ["--no-dereference", "--force", "--symbolic", source, target],
sudo: !target.dirname.writable?
end
end
end
end
end
end
Cask::Artifact::Symlinked.prepend(OS::Linux::Cask::Artifact::Symlinked)

View File

@ -15,6 +15,14 @@ module OS
def check_stanza_os_requirements def check_stanza_os_requirements
return if artifacts.all?(::Cask::Artifact::Font) return if artifacts.all?(::Cask::Artifact::Font)
install_artifacts = artifacts.reject { |artifact| artifact.instance_of?(::Cask::Artifact::Zap) }
return if install_artifacts.all? do |artifact|
artifact.is_a?(::Cask::Artifact::Binary) ||
artifact.is_a?(::Cask::Artifact::ShellCompletion) ||
artifact.is_a?(::Cask::Artifact::Artifact) ||
artifact.is_a?(::Cask::Artifact::Manpage)
end
raise ::Cask::CaskError, "macOS is required for this software." raise ::Cask::CaskError, "macOS is required for this software."
end end
end end

View File

@ -24,7 +24,7 @@ module Homebrew
end end
next unless recursive_runtime_dependencies.map(&:name).include? "gcc" next unless recursive_runtime_dependencies.map(&:name).include? "gcc"
keg = formula.installed_kegs.last keg = formula.installed_kegs.fetch(-1)
tab = keg.tab tab = keg.tab
# Force reinstallation upon `brew upgrade` to fix the bottle RPATH. # Force reinstallation upon `brew upgrade` to fix the bottle RPATH.
tab.source["versions"]["version_scheme"] = -1 tab.source["versions"]["version_scheme"] = -1

View File

@ -33,6 +33,7 @@ module OS
GLIBC = "glibc" GLIBC = "glibc"
GCC = OS::LINUX_PREFERRED_GCC_RUNTIME_FORMULA GCC = OS::LINUX_PREFERRED_GCC_RUNTIME_FORMULA
private_constant :GLIBC, :GCC
sig { void } sig { void }
def init_global_dep_tree_if_needed! def init_global_dep_tree_if_needed!
@ -47,9 +48,9 @@ module OS
built_global_dep_tree! built_global_dep_tree!
end end
sig { params(name: String).returns(T.nilable(Formula)) } sig { params(name: String).returns(T.nilable(::Formula)) }
def formula_for(name) def formula_for(name)
@formula_for ||= T.let({}, T.nilable(T::Hash[String, Formula])) @formula_for ||= T.let({}, T.nilable(T::Hash[String, ::Formula]))
@formula_for[name] ||= ::Formula[name] @formula_for[name] ||= ::Formula[name]
rescue FormulaUnavailableError rescue FormulaUnavailableError
nil nil

View File

@ -0,0 +1,25 @@
# typed: strict
# frozen_string_literal: true
require "cask/macos"
module OS
module Mac
module Cask
module Artifact
module AbstractUninstall
extend T::Helpers
requires_ancestor { ::Cask::Artifact::AbstractUninstall }
sig { params(target: Pathname).returns(T::Boolean) }
def undeletable?(target)
MacOS.undeletable?(target)
end
end
end
end
end
end
Cask::Artifact::AbstractUninstall.prepend(OS::Mac::Cask::Artifact::AbstractUninstall)

View File

@ -0,0 +1,30 @@
# typed: strict
# frozen_string_literal: true
require "cask/macos"
module OS
module Mac
module Cask
module Artifact
module Symlinked
extend T::Helpers
requires_ancestor { ::Cask::Artifact::Symlinked }
sig { params(command: T.class_of(SystemCommand)).void }
def create_filesystem_link(command)
::Cask::Utils.gain_permissions_mkpath(target.dirname, command:)
command.run! "/bin/ln", args: ["-h", "-f", "-s", "--", source, target],
sudo: !target.dirname.writable?
add_altname_metadata(source, target.basename, command:)
end
end
end
end
end
end
Cask::Artifact::Symlinked.prepend(OS::Mac::Cask::Artifact::Symlinked)

View File

@ -0,0 +1,23 @@
# typed: strict
# frozen_string_literal: true
require "cask/macos"
module OS
module Mac
module Cask
module DSL
extend T::Helpers
requires_ancestor { ::Cask::DSL }
sig { returns(T.nilable(MacOSVersion)) }
def os_version
MacOS.full_version
end
end
end
end
end
Cask::DSL.prepend(OS::Mac::Cask::DSL)

View File

@ -32,6 +32,18 @@ module OS
args args
end end
sig {
params(
prefix: T.any(String, Pathname),
release_mode: Symbol,
).returns(T::Array[String])
}
def std_zig_args(prefix: self.prefix, release_mode: :fast)
args = super
args << "-fno-rosetta" if ::Hardware::CPU.arm?
args
end
end end
end end
end end

View File

@ -240,6 +240,7 @@ module OS
private private
CELLAR_RX = %r{\A#{HOMEBREW_CELLAR}/(?<formula_name>[^/]+)/[^/]+} CELLAR_RX = %r{\A#{HOMEBREW_CELLAR}/(?<formula_name>[^/]+)/[^/]+}
private_constant :CELLAR_RX
# Replace HOMEBREW_CELLAR references with HOMEBREW_PREFIX/opt references # Replace HOMEBREW_CELLAR references with HOMEBREW_PREFIX/opt references
# if the Cellar reference is to a different keg. # if the Cellar reference is to a different keg.

View File

@ -8,53 +8,53 @@ certifi==2025.1.31 \
--hash=sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651 \ --hash=sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651 \
--hash=sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe --hash=sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe
# via influxdb3-python # via influxdb3-python
influxdb3-python==0.10.0 \ influxdb3-python==0.11.0 \
--hash=sha256:d279e5f8a597d49b44035263b1cf1472a3861ceba930fd08e1e3b1721a07d3cf \ --hash=sha256:07bea8e1150be9707f818cda9634600a42487ee14802208f3f0357af2847f6e3 \
--hash=sha256:f3d44dff4c4bbfdcb1fa1c4013ccfa317fbbd7df5812eb46395421166ffb385a --hash=sha256:dd5a1197f776f9836935d797be2dbc9e09f75465188492409e0b5931e6a033c4
# via -r requirements.in # via -r requirements.in
pyarrow==19.0.0 \ pyarrow==19.0.1 \
--hash=sha256:239ca66d9a05844bdf5af128861af525e14df3c9591bcc05bac25918e650d3a2 \ --hash=sha256:008a4009efdb4ea3d2e18f05cd31f9d43c388aad29c636112c2966605ba33466 \
--hash=sha256:2795064647add0f16563e57e3d294dbfc067b723f0fd82ecd80af56dad15f503 \ --hash=sha256:0148bb4fc158bfbc3d6dfe5001d93ebeed253793fff4435167f6ce1dc4bddeae \
--hash=sha256:29cd86c8001a94f768f79440bf83fee23963af5e7bc68ce3a7e5f120e17edf89 \ --hash=sha256:1b93ef2c93e77c442c979b0d596af45e4665d8b96da598db145b0fec014b9136 \
--hash=sha256:2a0144a712d990d60f7f42b7a31f0acaccf4c1e43e957f7b1ad58150d6f639c1 \ --hash=sha256:1c7556165bd38cf0cd992df2636f8bcdd2d4b26916c6b7e646101aff3c16f76f \
--hash=sha256:2a1a109dfda558eb011e5f6385837daffd920d54ca00669f7a11132d0b1e6042 \ --hash=sha256:335d170e050bcc7da867a1ed8ffb8b44c57aaa6e0843b156a501298657b1e972 \
--hash=sha256:2b6d3ce4288793350dc2d08d1e184fd70631ea22a4ff9ea5c4ff182130249d9b \ --hash=sha256:3bf266b485df66a400f282ac0b6d1b500b9d2ae73314a153dbe97d6d5cc8a99e \
--hash=sha256:2f672f5364b2d7829ef7c94be199bb88bf5661dd485e21d2d37de12ccb78a136 \ --hash=sha256:41f9706fbe505e0abc10e84bf3a906a1338905cbbcf1177b71486b03e6ea6608 \
--hash=sha256:3c1c162c4660e0978411a4761f91113dde8da3433683efa473501254563dcbe8 \ --hash=sha256:4982f8e2b7afd6dae8608d70ba5bd91699077323f812a0448d8b7abdff6cb5d3 \
--hash=sha256:450a7d27e840e4d9a384b5c77199d489b401529e75a3b7a3799d4cd7957f2f9c \ --hash=sha256:49a3aecb62c1be1d822f8bf629226d4a96418228a42f5b40835c1f10d42e4db6 \
--hash=sha256:4624c89d6f777c580e8732c27bb8e77fd1433b89707f17c04af7635dd9638351 \ --hash=sha256:4d5d1ec7ec5324b98887bdc006f4d2ce534e10e60f7ad995e7875ffa0ff9cb14 \
--hash=sha256:4d8b0c0de0a73df1f1bf439af1b60f273d719d70648e898bc077547649bb8352 \ --hash=sha256:58d9397b2e273ef76264b45531e9d552d8ec8a6688b7390b5be44c02a37aade8 \
--hash=sha256:5418d4d0fab3a0ed497bad21d17a7973aad336d66ad4932a3f5f7480d4ca0c04 \ --hash=sha256:5a9137cf7e1640dce4c190551ee69d478f7121b5c6f323553b319cac936395f6 \
--hash=sha256:597360ffc71fc8cceea1aec1fb60cb510571a744fffc87db33d551d5de919bec \ --hash=sha256:5bd1618ae5e5476b7654c7b55a6364ae87686d4724538c24185bbb2952679960 \
--hash=sha256:5e8a28b918e2e878c918f6d89137386c06fe577cd08d73a6be8dafb317dc2d73 \ --hash=sha256:65cf9feebab489b19cdfcfe4aa82f62147218558d8d3f0fc1e9dea0ab8e7905a \
--hash=sha256:62ef8360ff256e960f57ce0299090fb86423afed5e46f18f1225f960e05aae3d \ --hash=sha256:699799f9c80bebcf1da0983ba86d7f289c5a2a5c04b945e2f2bcf7e874a91911 \
--hash=sha256:66732e39eaa2247996a6b04c8aa33e3503d351831424cdf8d2e9a0582ac54b34 \ --hash=sha256:6c5941c1aac89a6c2f2b16cd64fe76bcdb94b2b1e99ca6459de4e6f07638d755 \
--hash=sha256:718947fb6d82409013a74b176bf93e0f49ef952d8a2ecd068fecd192a97885b7 \ --hash=sha256:6ebfb5171bb5f4a52319344ebbbecc731af3f021e49318c74f33d520d31ae0c4 \
--hash=sha256:8d47c691765cf497aaeed4954d226568563f1b3b74ff61139f2d77876717084b \ --hash=sha256:7a544ec12de66769612b2d6988c36adc96fb9767ecc8ee0a4d270b10b1c51e00 \
--hash=sha256:8e3a839bf36ec03b4315dc924d36dcde5444a50066f1c10f8290293c0427b46a \ --hash=sha256:7c1bca1897c28013db5e4c83944a2ab53231f541b9e0c3f4791206d0c0de389a \
--hash=sha256:9348a0137568c45601b031a8d118275069435f151cbb77e6a08a27e8125f59d4 \ --hash=sha256:80b2ad2b193e7d19e81008a96e313fbd53157945c7be9ac65f44f8937a55427b \
--hash=sha256:a08e2a8a039a3f72afb67a6668180f09fddaa38fe0d21f13212b4aba4b5d2451 \ --hash=sha256:8464c9fbe6d94a7fe1599e7e8965f350fd233532868232ab2596a71586c5a429 \
--hash=sha256:a218670b26fb1bc74796458d97bcab072765f9b524f95b2fccad70158feb8b17 \ --hash=sha256:8f04d49a6b64cf24719c080b3c2029a3a5b16417fd5fd7c4041f94233af732f3 \
--hash=sha256:a22a4bc0937856263df8b94f2f2781b33dd7f876f787ed746608e06902d691a5 \ --hash=sha256:96606c3ba57944d128e8a8399da4812f56c7f61de8c647e3470b417f795d0ef9 \
--hash=sha256:a7bbe7109ab6198688b7079cbad5a8c22de4d47c4880d8e4847520a83b0d1b68 \ --hash=sha256:99bc1bec6d234359743b01e70d4310d0ab240c3d6b0da7e2a93663b0158616f6 \
--hash=sha256:a92aff08e23d281c69835e4a47b80569242a504095ef6a6223c1f6bb8883431d \ --hash=sha256:ad76aef7f5f7e4a757fddcdcf010a8290958f09e3470ea458c80d26f4316ae89 \
--hash=sha256:b34d3bde38eba66190b215bae441646330f8e9da05c29e4b5dd3e41bde701098 \ --hash=sha256:b4c4156a625f1e35d6c0b2132635a237708944eb41df5fbe7d50f20d20c17832 \
--hash=sha256:b903afaa5df66d50fc38672ad095806443b05f202c792694f3a604ead7c6ea6e \ --hash=sha256:b9766a47a9cb56fefe95cb27f535038b5a195707a08bf61b180e642324963b46 \
--hash=sha256:be686bf625aa7b9bada18defb3a3ea3981c1099697239788ff111d87f04cd263 \ --hash=sha256:c0fe3dbbf054a00d1f162fda94ce236a899ca01123a798c561ba307ca38af5f0 \
--hash=sha256:c0423393e4a07ff6fea08feb44153302dd261d0551cc3b538ea7a5dc853af43a \ --hash=sha256:c6cb2335a411b713fdf1e82a752162f72d4a7b5dbc588e32aa18383318b05866 \
--hash=sha256:c318eda14f6627966997a7d8c374a87d084a94e4e38e9abbe97395c215830e0c \ --hash=sha256:cc55d71898ea30dc95900297d191377caba257612f384207fe9f8293b5850f90 \
--hash=sha256:c3b78eff5968a1889a0f3bc81ca57e1e19b75f664d9c61a42a604bf9d8402aae \ --hash=sha256:d03c9d6f2a3dffbd62671ca070f13fc527bb1867b4ec2b98c7eeed381d4f389a \
--hash=sha256:c73268cf557e688efb60f1ccbc7376f7e18cd8e2acae9e663e98b194c40c1a2d \ --hash=sha256:d383591f3dcbe545f6cc62daaef9c7cdfe0dff0fb9e1c8121101cabe9098cfa6 \
--hash=sha256:c751c1c93955b7a84c06794df46f1cec93e18610dcd5ab7d08e89a81df70a849 \ --hash=sha256:d9d46e06846a41ba906ab25302cf0fd522f81aa2a85a71021826f34639ad31ef \
--hash=sha256:ce42275097512d9e4e4a39aade58ef2b3798a93aa3026566b7892177c266f735 \ --hash=sha256:d9dedeaf19097a143ed6da37f04f4051aba353c95ef507764d344229b2b740ae \
--hash=sha256:cf3bf0ce511b833f7bc5f5bb3127ba731e97222023a444b7359f3a22e2a3b463 \ --hash=sha256:e45274b20e524ae5c39d7fc1ca2aa923aab494776d2d4b316b49ec7572ca324c \
--hash=sha256:da410b70a7ab8eb524112f037a7a35da7128b33d484f7671a264a4c224ac131d \ --hash=sha256:ee8dec072569f43835932a3b10c55973593abc00936c202707a4ad06af7cb294 \
--hash=sha256:e675a3ad4732b92d72e4d24009707e923cab76b0d088e5054914f11a797ebe44 \ --hash=sha256:f24faab6ed18f216a37870d8c5623f9c044566d75ec586ef884e13a02a9d62c5 \
--hash=sha256:e82c3d5e44e969c217827b780ed8faf7ac4c53f934ae9238872e749fa531f7c9 \ --hash=sha256:f2a21d39fbdb948857f67eacb5bbaaf36802de044ec36fbef7a1c8f0dd3a4ab2 \
--hash=sha256:edfe6d3916e915ada9acc4e48f6dafca7efdbad2e6283db6fd9385a1b23055f1 \ --hash=sha256:f3ad4c0eb4e2a9aeb990af6c09e6fa0b195c8c0e7b272ecc8d4d2b6574809d34 \
--hash=sha256:f094742275586cdd6b1a03655ccff3b24b2610c3af76f810356c4c71d24a2a6c \ --hash=sha256:fc28912a2dc924dddc2087679cc8b7263accc71b9ff025a1362b004711661a69 \
--hash=sha256:f208c3b58a6df3b239e0bb130e13bc7487ed14f39a9ff357b6415e3f6339b560 \ --hash=sha256:fca15aabbe9b8355800d923cc2e82c8ef514af321e18b437c3d782aa884eaeec \
--hash=sha256:f43f5aef2a13d4d56adadae5720d1fed4c1356c993eda8b59dace4b5983843c1 --hash=sha256:fd44d66093a239358d07c42a91eebf5015aa54fccba959db899f932218ac9cc8
# via influxdb3-python # via influxdb3-python
python-dateutil==2.9.0.post0 \ python-dateutil==2.9.0.post0 \
--hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \
@ -78,7 +78,7 @@ urllib3==2.3.0 \
# via influxdb3-python # via influxdb3-python
# The following packages are considered to be unsafe in a requirements file: # The following packages are considered to be unsafe in a requirements file:
setuptools==75.8.0 \ setuptools==75.8.2 \
--hash=sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6 \ --hash=sha256:4880473a969e5f23f2a2be3646b2dfd84af9028716d398e46192f84bc36900d2 \
--hash=sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3 --hash=sha256:558e47c15f1811c1fa7adbd0096669bf76c1d3f433f58324df69f3f5ecac4e8f
# via influxdb3-python # via influxdb3-python

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +0,0 @@
# typed: strict
class Formula
# This method is included by `OnSystem`
def self.on_macos(&block); end
end

View File

@ -307,7 +307,7 @@ module FormulaCellarChecks
return unless formula.service? return unless formula.service?
return unless formula.service.command? return unless formula.service.command?
"Service command does not exist" unless File.exist?(formula.service.command.first) "Service command does not exist" unless File.exist?(T.must(formula.service.command).first)
end end
sig { params(formula: Formula).returns(T.nilable(String)) } sig { params(formula: Formula).returns(T.nilable(String)) }

View File

@ -100,9 +100,9 @@ module Homebrew
sig { params(name: String).returns(String) } sig { params(name: String).returns(String) }
def latest_versioned_formula(name) def latest_versioned_formula(name)
name_prefix = "#{name}@" name_prefix = "#{name}@"
Tap.fetch("homebrew/core").formula_names CoreTap.instance.formula_names
.select { |f| f.start_with?(name_prefix) } .select { |f| f.start_with?(name_prefix) }
.max_by { |v| Gem::Version.new(v.sub(name_prefix, "")) } || "python" .max_by { |v| Gem::Version.new(v.sub(name_prefix, "")) } || "python"
end end
sig { returns(String) } sig { returns(String) }
@ -151,6 +151,8 @@ module Homebrew
uses_from_macos "ruby" uses_from_macos "ruby"
<% elsif @mode == :rust %> <% elsif @mode == :rust %>
depends_on "rust" => :build depends_on "rust" => :build
<% elsif @mode == :zig %>
depends_on "zig" => :build
<% elsif @mode.nil? %> <% elsif @mode.nil? %>
# depends_on "cmake" => :build # depends_on "cmake" => :build
<% end %> <% end %>
@ -217,6 +219,8 @@ module Homebrew
bin.env_script_all_files(libexec/"bin", GEM_HOME: ENV["GEM_HOME"]) bin.env_script_all_files(libexec/"bin", GEM_HOME: ENV["GEM_HOME"])
<% elsif @mode == :rust %> <% elsif @mode == :rust %>
system "cargo", "install", *std_cargo_args system "cargo", "install", *std_cargo_args
<% elsif @mode == :zig %>
system "zig", "build", *std_zig_args
<% else %> <% else %>
# Remove unrecognized options if they cause configure to fail # Remove unrecognized options if they cause configure to fail
# https://rubydoc.brew.sh/Formula.html#std_configure_args-instance_method # https://rubydoc.brew.sh/Formula.html#std_configure_args-instance_method

View File

@ -29,7 +29,6 @@ require "utils/fork"
# Installer for a formula. # Installer for a formula.
class FormulaInstaller class FormulaInstaller
include FormulaCellarChecks include FormulaCellarChecks
extend Attrable
ETC_VAR_DIRS = T.let([HOMEBREW_PREFIX/"etc", HOMEBREW_PREFIX/"var"].freeze, T::Array[Pathname]) ETC_VAR_DIRS = T.let([HOMEBREW_PREFIX/"etc", HOMEBREW_PREFIX/"var"].freeze, T::Array[Pathname])
@ -45,12 +44,6 @@ class FormulaInstaller
sig { returns(T::Boolean) } sig { returns(T::Boolean) }
attr_accessor :link_keg attr_accessor :link_keg
attr_predicate :installed_as_dependency?, :installed_on_request?
attr_predicate :show_summary_heading?, :show_header?
attr_predicate :force_bottle?, :ignore_deps?, :only_deps?, :interactive?, :git?, :force?, :overwrite?, :keep_tmp?
attr_predicate :debug_symbols?
attr_predicate :verbose?, :debug?, :quiet?
sig { sig {
params( params(
formula: Formula, formula: Formula,
@ -148,6 +141,54 @@ class FormulaInstaller
@formula = T.let(T.must(previously_fetched_formula), Formula) if previously_fetched_formula @formula = T.let(T.must(previously_fetched_formula), Formula) if previously_fetched_formula
end end
sig { returns(T::Boolean) }
def debug? = @debug
sig { returns(T::Boolean) }
def debug_symbols? = @debug_symbols
sig { returns(T::Boolean) }
def force? = @force
sig { returns(T::Boolean) }
def force_bottle? = @force_bottle
sig { returns(T::Boolean) }
def git? = @git
sig { returns(T::Boolean) }
def ignore_deps? = @ignore_deps
sig { returns(T::Boolean) }
def installed_as_dependency? = @installed_as_dependency
sig { returns(T::Boolean) }
def installed_on_request? = @installed_on_request
sig { returns(T::Boolean) }
def interactive? = @interactive
sig { returns(T::Boolean) }
def keep_tmp? = @keep_tmp
sig { returns(T::Boolean) }
def only_deps? = @only_deps
sig { returns(T::Boolean) }
def overwrite? = @overwrite
sig { returns(T::Boolean) }
def quiet? = @quiet
sig { returns(T::Boolean) }
def show_header? = @show_header
sig { returns(T::Boolean) }
def show_summary_heading? = @show_summary_heading
sig { returns(T::Boolean) }
def verbose? = @verbose
sig { returns(T::Set[Formula]) } sig { returns(T::Set[Formula]) }
def self.attempted def self.attempted
@attempted ||= T.let(Set.new, T.nilable(T::Set[Formula])) @attempted ||= T.let(Set.new, T.nilable(T::Set[Formula]))
@ -809,11 +850,8 @@ on_request: installed_on_request?, options:)
options |= inherited_options options |= inherited_options
options &= df.options options &= df.options
installed_on_request = if df.any_version_installed? && tab.present? && tab.installed_on_request installed_on_request = df.any_version_installed? && tab.present? && tab.installed_on_request
true installed_on_request ||= false
else
false
end
fi = FormulaInstaller.new( fi = FormulaInstaller.new(
df, df,
@ -1230,7 +1268,7 @@ on_request: installed_on_request?, options:)
return keg_formula_path if formula.loaded_from_api? return keg_formula_path if formula.loaded_from_api?
return keg_formula_path if formula.local_bottle_path.present? return keg_formula_path if formula.local_bottle_path.present?
tap_formula_path = formula.specified_path tap_formula_path = T.must(formula.specified_path)
return keg_formula_path unless tap_formula_path.exist? return keg_formula_path unless tap_formula_path.exist?
begin begin

View File

@ -136,6 +136,8 @@ class GitHubPackages
IMAGE_MANIFEST_SCHEMA_URI = "https://opencontainers.org/schema/image/manifest" IMAGE_MANIFEST_SCHEMA_URI = "https://opencontainers.org/schema/image/manifest"
GITHUB_PACKAGE_TYPE = "homebrew_bottle" GITHUB_PACKAGE_TYPE = "homebrew_bottle"
private_constant :IMAGE_CONFIG_SCHEMA_URI, :IMAGE_INDEX_SCHEMA_URI, :IMAGE_LAYOUT_SCHEMA_URI,
:IMAGE_MANIFEST_SCHEMA_URI, :GITHUB_PACKAGE_TYPE
def load_schemas! def load_schemas!
schema_uri("content-descriptor", schema_uri("content-descriptor",

View File

@ -5,6 +5,10 @@ require "test_runner_formula"
require "github_runner" require "github_runner"
class GitHubRunnerMatrix class GitHubRunnerMatrix
NEWEST_HOMEBREW_CORE_MACOS_RUNNER = :sequoia
OLDEST_HOMEBREW_CORE_MACOS_RUNNER = :ventura
NEWEST_HOMEBREW_CORE_INTEL_MACOS_RUNNER = :sonoma
RunnerSpec = T.type_alias { T.any(LinuxRunnerSpec, MacOSRunnerSpec) } RunnerSpec = T.type_alias { T.any(LinuxRunnerSpec, MacOSRunnerSpec) }
private_constant :RunnerSpec private_constant :RunnerSpec
@ -77,6 +81,7 @@ class GitHubRunnerMatrix
# https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#usage-limits # https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#usage-limits
GITHUB_ACTIONS_LONG_TIMEOUT = 2160 # 36 hours GITHUB_ACTIONS_LONG_TIMEOUT = 2160 # 36 hours
GITHUB_ACTIONS_SHORT_TIMEOUT = 60 GITHUB_ACTIONS_SHORT_TIMEOUT = 60
private_constant :SELF_HOSTED_LINUX_RUNNER, :GITHUB_ACTIONS_LONG_TIMEOUT, :GITHUB_ACTIONS_SHORT_TIMEOUT
sig { returns(LinuxRunnerSpec) } sig { returns(LinuxRunnerSpec) }
def linux_runner_spec def linux_runner_spec
@ -97,6 +102,7 @@ class GitHubRunnerMatrix
VALID_PLATFORMS = T.let([:macos, :linux].freeze, T::Array[Symbol]) VALID_PLATFORMS = T.let([:macos, :linux].freeze, T::Array[Symbol])
VALID_ARCHES = T.let([:arm64, :x86_64].freeze, T::Array[Symbol]) VALID_ARCHES = T.let([:arm64, :x86_64].freeze, T::Array[Symbol])
private_constant :VALID_PLATFORMS, :VALID_ARCHES
sig { sig {
params( params(
@ -116,10 +122,6 @@ class GitHubRunnerMatrix
runner.freeze runner.freeze
end end
NEWEST_HOMEBREW_CORE_MACOS_RUNNER = :sequoia
OLDEST_HOMEBREW_CORE_MACOS_RUNNER = :ventura
NEWEST_HOMEBREW_CORE_INTEL_MACOS_RUNNER = :sonoma
sig { params(macos_version: MacOSVersion).returns(T::Boolean) } sig { params(macos_version: MacOSVersion).returns(T::Boolean) }
def runner_enabled?(macos_version) def runner_enabled?(macos_version)
macos_version <= NEWEST_HOMEBREW_CORE_MACOS_RUNNER && macos_version >= OLDEST_HOMEBREW_CORE_MACOS_RUNNER macos_version <= NEWEST_HOMEBREW_CORE_MACOS_RUNNER && macos_version >= OLDEST_HOMEBREW_CORE_MACOS_RUNNER
@ -130,6 +132,9 @@ class GitHubRunnerMatrix
NEWEST_GITHUB_ACTIONS_ARM_MACOS_RUNNER = :sequoia NEWEST_GITHUB_ACTIONS_ARM_MACOS_RUNNER = :sequoia
OLDEST_GITHUB_ACTIONS_ARM_MACOS_RUNNER = :sonoma OLDEST_GITHUB_ACTIONS_ARM_MACOS_RUNNER = :sonoma
GITHUB_ACTIONS_RUNNER_TIMEOUT = 360 GITHUB_ACTIONS_RUNNER_TIMEOUT = 360
private_constant :NEWEST_GITHUB_ACTIONS_INTEL_MACOS_RUNNER, :OLDEST_GITHUB_ACTIONS_INTEL_MACOS_RUNNER,
:NEWEST_GITHUB_ACTIONS_ARM_MACOS_RUNNER, :OLDEST_GITHUB_ACTIONS_ARM_MACOS_RUNNER,
:GITHUB_ACTIONS_RUNNER_TIMEOUT
sig { void } sig { void }
def generate_runners! def generate_runners!

View File

@ -147,6 +147,7 @@ class Keg
share/man/man1 share/man/man2 share/man/man3 share/man/man4 share/man/man1 share/man/man2 share/man/man3 share/man/man4
share/man/man5 share/man/man6 share/man/man7 share/man/man8 share/man/man5 share/man/man6 share/man/man7 share/man/man8
share/zsh share/zsh/site-functions share/zsh share/zsh/site-functions
share/pwsh share/pwsh/completions
var/log var/log
].map { |dir| HOMEBREW_PREFIX/dir } + must_exist_subdirectories + [ ].map { |dir| HOMEBREW_PREFIX/dir } + must_exist_subdirectories + [
HOMEBREW_CACHE, HOMEBREW_CACHE,
@ -354,6 +355,7 @@ class Keg
when :zsh when :zsh
dir = path/"share/zsh/site-functions" dir = path/"share/zsh/site-functions"
dir if dir.directory? && dir.children.any? { |f| f.basename.to_s.start_with?("_") } dir if dir.directory? && dir.children.any? { |f| f.basename.to_s.start_with?("_") }
when :pwsh then path/"share/pwsh/completions"
end end
dir&.directory? && !dir.children.empty? dir&.directory? && !dir.children.empty?
end end
@ -388,6 +390,7 @@ class Keg
(path/"share/emacs/site-lisp"/name).children.any? { |f| ELISP_EXTENSIONS.include? f.extname } (path/"share/emacs/site-lisp"/name).children.any? { |f| ELISP_EXTENSIONS.include? f.extname }
end end
sig { returns(PkgVersion) }
def version def version
require "pkg_version" require "pkg_version"
PkgVersion.parse(path.basename.to_s) PkgVersion.parse(path.basename.to_s)
@ -554,6 +557,32 @@ class Keg
path.find { |pn| FileUtils.rm_rf pn if pn.basename.to_s == "__pycache__" } path.find { |pn| FileUtils.rm_rf pn if pn.basename.to_s == "__pycache__" }
end end
def normalize_pod2man_outputs!
# Only process uncompressed manpages, which end in a digit
manpages = Dir[path/"share/man/*/*.[1-9]"]
generated_regex = /^\.\\"\s*Automatically generated by .*\n/
manpages.each do |f|
manpage = Pathname.new(f)
next unless manpage.file?
content = manpage.read
content = content.gsub(generated_regex, "")
content = content.lines.map do |line|
next line unless line.start_with?(".TH")
# Split the line by spaces, but preserve quoted strings
parts = line.split(/\s(?=(?:[^"]|"[^"]*")*$)/)
next line if parts.length != 6
# pod2man embeds the perl version used into the 5th field of the footer
T.must(parts[4]).gsub!(/^"perl v.*"$/, "\"\"")
"#{parts.join(" ")}\n"
end.join
manpage.atomic_write(content)
end
end
def binary_executable_or_library_files def binary_executable_or_library_files
[] []
end end

View File

@ -16,7 +16,7 @@ module Language
next false unless f.any_version_installed? next false unless f.any_version_installed?
unless version.zero? unless version.zero?
major = f.any_installed_version.major major = T.must(f.any_installed_version).major
next false if major < version next false if major < version
next false if major > version && !can_be_newer next false if major > version && !can_be_newer
end end

View File

@ -340,7 +340,10 @@ module Language
version = rp.match %r{^#{HOMEBREW_CELLAR}/python@(.*?)/}o version = rp.match %r{^#{HOMEBREW_CELLAR}/python@(.*?)/}o
version = "@#{version.captures.first}" unless version.nil? version = "@#{version.captures.first}" unless version.nil?
new_target = rp.sub %r{#{HOMEBREW_CELLAR}/python#{version}/[^/]+}, Formula["python#{version}"].opt_prefix new_target = rp.sub(
%r{#{HOMEBREW_CELLAR}/python#{version}/[^/]+},
Formula["python#{version}"].opt_prefix.to_s,
)
f.unlink f.unlink
f.make_symlink new_target f.make_symlink new_target
end end
@ -351,7 +354,10 @@ module Language
version = prefix_path.match %r{^#{HOMEBREW_CELLAR}/python@(.*?)/}o version = prefix_path.match %r{^#{HOMEBREW_CELLAR}/python@(.*?)/}o
version = "@#{version.captures.first}" unless version.nil? version = "@#{version.captures.first}" unless version.nil?
prefix_path.sub! %r{^#{HOMEBREW_CELLAR}/python#{version}/[^/]+}, Formula["python#{version}"].opt_prefix prefix_path.sub!(
%r{^#{HOMEBREW_CELLAR}/python#{version}/[^/]+},
Formula["python#{version}"].opt_prefix.to_s,
)
prefix_file.atomic_write prefix_path prefix_file.atomic_write prefix_path
end end
@ -362,7 +368,7 @@ module Language
cfg = cfg_file.read cfg = cfg_file.read
framework = "Frameworks/Python.framework/Versions" framework = "Frameworks/Python.framework/Versions"
cfg.match(%r{= *(#{HOMEBREW_CELLAR}/(python@[\d.]+)/[^/]+(?:/#{framework}/[\d.]+)?/bin)}) do |match| cfg.match(%r{= *(#{HOMEBREW_CELLAR}/(python@[\d.]+)/[^/]+(?:/#{framework}/[\d.]+)?/bin)}) do |match|
cfg.sub! match[1].to_s, Formula[match[2]].opt_bin cfg.sub! match[1].to_s, Formula[T.must(match[2])].opt_bin.to_s
cfg_file.atomic_write cfg cfg_file.atomic_write cfg
end end
end end

View File

@ -65,6 +65,7 @@ class LinkageCacheStore < CacheStore
private private
HASH_LINKAGE_TYPES = [:keg_files_dylibs].freeze HASH_LINKAGE_TYPES = [:keg_files_dylibs].freeze
private_constant :HASH_LINKAGE_TYPES
# @param type [Symbol] # @param type [Symbol]
# @return [Hash] # @return [Hash]

View File

@ -2,6 +2,7 @@
# frozen_string_literal: true # frozen_string_literal: true
require "livecheck/constants" require "livecheck/constants"
require "livecheck/options"
require "cask/cask" require "cask/cask"
# The {Livecheck} class implements the DSL methods used in a formula's, cask's # The {Livecheck} class implements the DSL methods used in a formula's, cask's
@ -15,6 +16,10 @@ require "cask/cask"
class Livecheck class Livecheck
extend Forwardable extend Forwardable
# Options to modify livecheck's behavior.
sig { returns(Homebrew::Livecheck::Options) }
attr_reader :options
# A very brief description of why the formula/cask/resource is skipped (e.g. # A very brief description of why the formula/cask/resource is skipped (e.g.
# `No longer developed or maintained`). # `No longer developed or maintained`).
sig { returns(T.nilable(String)) } sig { returns(T.nilable(String)) }
@ -24,13 +29,10 @@ class Livecheck
sig { returns(T.nilable(Proc)) } sig { returns(T.nilable(Proc)) }
attr_reader :strategy_block attr_reader :strategy_block
# Options used by `Strategy` methods to modify `curl` behavior.
sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
attr_reader :url_options
sig { params(package_or_resource: T.any(Cask::Cask, T.class_of(Formula), Resource)).void } sig { params(package_or_resource: T.any(Cask::Cask, T.class_of(Formula), Resource)).void }
def initialize(package_or_resource) def initialize(package_or_resource)
@package_or_resource = package_or_resource @package_or_resource = package_or_resource
@options = T.let(Homebrew::Livecheck::Options.new, Homebrew::Livecheck::Options)
@referenced_cask_name = T.let(nil, T.nilable(String)) @referenced_cask_name = T.let(nil, T.nilable(String))
@referenced_formula_name = T.let(nil, T.nilable(String)) @referenced_formula_name = T.let(nil, T.nilable(String))
@regex = T.let(nil, T.nilable(Regexp)) @regex = T.let(nil, T.nilable(Regexp))
@ -40,7 +42,6 @@ class Livecheck
@strategy_block = T.let(nil, T.nilable(Proc)) @strategy_block = T.let(nil, T.nilable(Proc))
@throttle = T.let(nil, T.nilable(Integer)) @throttle = T.let(nil, T.nilable(Integer))
@url = T.let(nil, T.any(NilClass, String, Symbol)) @url = T.let(nil, T.any(NilClass, String, Symbol))
@url_options = T.let(nil, T.nilable(T::Hash[Symbol, T.untyped]))
end end
# Sets the `@referenced_cask_name` instance variable to the provided `String` # Sets the `@referenced_cask_name` instance variable to the provided `String`
@ -169,16 +170,18 @@ class Livecheck
sig { sig {
params( params(
# URL to check for version information. # URL to check for version information.
url: T.any(String, Symbol), url: T.any(String, Symbol),
post_form: T.nilable(T::Hash[T.any(String, Symbol), String]), homebrew_curl: T.nilable(T::Boolean),
post_json: T.nilable(T::Hash[T.any(String, Symbol), String]), post_form: T.nilable(T::Hash[Symbol, String]),
post_json: T.nilable(T::Hash[Symbol, String]),
).returns(T.nilable(T.any(String, Symbol))) ).returns(T.nilable(T.any(String, Symbol)))
} }
def url(url = T.unsafe(nil), post_form: nil, post_json: nil) def url(url = T.unsafe(nil), homebrew_curl: nil, post_form: nil, post_json: nil)
raise ArgumentError, "Only use `post_form` or `post_json`, not both" if post_form && post_json raise ArgumentError, "Only use `post_form` or `post_json`, not both" if post_form && post_json
options = { post_form:, post_json: }.compact @options.homebrew_curl = homebrew_curl unless homebrew_curl.nil?
@url_options = options if options.present? @options.post_form = post_form unless post_form.nil?
@options.post_json = post_json unless post_json.nil?
case url case url
when nil when nil
@ -190,6 +193,7 @@ class Livecheck
end end
end end
delegate url_options: :@options
delegate version: :@package_or_resource delegate version: :@package_or_resource
delegate arch: :@package_or_resource delegate arch: :@package_or_resource
private :version, :arch private :version, :arch
@ -198,15 +202,15 @@ class Livecheck
sig { returns(T::Hash[String, T.untyped]) } sig { returns(T::Hash[String, T.untyped]) }
def to_hash def to_hash
{ {
"cask" => @referenced_cask_name, "options" => @options.to_hash,
"formula" => @referenced_formula_name, "cask" => @referenced_cask_name,
"regex" => @regex, "formula" => @referenced_formula_name,
"skip" => @skip, "regex" => @regex,
"skip_msg" => @skip_msg, "skip" => @skip,
"strategy" => @strategy, "skip_msg" => @skip_msg,
"throttle" => @throttle, "strategy" => @strategy,
"url" => @url, "throttle" => @throttle,
"url_options" => @url_options, "url" => @url,
} }
end end
end end

View File

@ -13,6 +13,9 @@ module Homebrew
# command. These methods print the requested livecheck information # command. These methods print the requested livecheck information
# for formulae. # for formulae.
module Livecheck module Livecheck
NO_CURRENT_VERSION_MSG = "Unable to identify current version"
NO_VERSIONS_MSG = "Unable to get versions"
UNSTABLE_VERSION_KEYWORDS = T.let(%w[ UNSTABLE_VERSION_KEYWORDS = T.let(%w[
alpha alpha
beta beta
@ -25,19 +28,17 @@ module Homebrew
].freeze, T::Array[String]) ].freeze, T::Array[String])
private_constant :UNSTABLE_VERSION_KEYWORDS private_constant :UNSTABLE_VERSION_KEYWORDS
sig { returns(T::Hash[T::Class[T.anything], String]) } sig { params(strategy_class: T::Class[Strategic]).returns(String) }
private_class_method def self.livecheck_strategy_names private_class_method def self.livecheck_strategy_names(strategy_class)
return T.must(@livecheck_strategy_names) if defined?(@livecheck_strategy_names) @livecheck_strategy_names ||= T.let({}, T.nilable(T::Hash[T::Class[Strategic], String]))
@livecheck_strategy_names[strategy_class] ||= Utils.demodulize(strategy_class.name)
end
# Cache demodulized strategy names, to avoid repeating this work sig { params(strategy_class: T::Class[Strategic]).returns(T::Array[Symbol]) }
@livecheck_strategy_names = T.let({}, T.nilable(T::Hash[T::Class[T.anything], String])) private_class_method def self.livecheck_find_versions_parameters(strategy_class)
Strategy.constants.sort.each do |const_symbol| @livecheck_find_versions_parameters ||= T.let({}, T.nilable(T::Hash[T::Class[Strategic], T::Array[Symbol]]))
constant = Strategy.const_get(const_symbol) @livecheck_find_versions_parameters[strategy_class] ||=
next unless constant.is_a?(Class) T::Utils.signature_for_method(strategy_class.method(:find_versions)).parameters.map(&:second)
T.must(@livecheck_strategy_names)[constant] = Utils.demodulize(T.must(constant.name))
end
T.must(@livecheck_strategy_names).freeze
end end
# Uses `formulae_and_casks_to_check` to identify taps in use other than # Uses `formulae_and_casks_to_check` to identify taps in use other than
@ -249,13 +250,20 @@ module Homebrew
# comparison. # comparison.
current = if formula current = if formula
if formula.head_only? if formula.head_only?
Version.new(formula.any_installed_version.version.commit) formula_commit = formula.any_installed_version&.version&.commit
else Version.new(formula_commit) if formula_commit
T.must(formula.stable).version elsif (stable = formula.stable)
stable.version
end end
else else
Version.new(formula_or_cask.version) Version.new(formula_or_cask.version)
end end
unless current
raise Livecheck::Error, NO_CURRENT_VERSION_MSG unless json
next if quiet
next status_hash(formula_or_cask, "error", [NO_CURRENT_VERSION_MSG], full_name: use_full_name, verbose:)
end
current_str = current.to_s current_str = current.to_s
current = LivecheckVersion.create(formula_or_cask, current) current = LivecheckVersion.create(formula_or_cask, current)
@ -289,7 +297,7 @@ module Homebrew
verbose:, verbose:,
) )
if res_version_info.empty? if res_version_info.empty?
status_hash(resource, "error", ["Unable to get versions"], verbose:) status_hash(resource, "error", [NO_VERSIONS_MSG], verbose:)
else else
res_version_info res_version_info
end end
@ -299,13 +307,12 @@ module Homebrew
end end
if latest.blank? if latest.blank?
no_versions_msg = "Unable to get versions" raise Livecheck::Error, NO_VERSIONS_MSG unless json
raise Livecheck::Error, no_versions_msg unless json
next if quiet next if quiet
next version_info if version_info.is_a?(Hash) && version_info[:status] && version_info[:messages] next version_info if version_info.is_a?(Hash) && version_info[:status] && version_info[:messages]
latest_info = status_hash(formula_or_cask, "error", [no_versions_msg], full_name: use_full_name, latest_info = status_hash(formula_or_cask, "error", [NO_VERSIONS_MSG], full_name: use_full_name,
verbose:) verbose:)
if check_for_resources if check_for_resources
unless verbose unless verbose
@ -613,8 +620,9 @@ module Homebrew
livecheck = formula_or_cask.livecheck livecheck = formula_or_cask.livecheck
referenced_livecheck = referenced_formula_or_cask&.livecheck referenced_livecheck = referenced_formula_or_cask&.livecheck
livecheck_options = livecheck.options || referenced_livecheck&.options
livecheck_url_options = livecheck_options.url_options.compact
livecheck_url = livecheck.url || referenced_livecheck&.url livecheck_url = livecheck.url || referenced_livecheck&.url
livecheck_url_options = livecheck.url_options || referenced_livecheck&.url_options
livecheck_regex = livecheck.regex || referenced_livecheck&.regex livecheck_regex = livecheck.regex || referenced_livecheck&.regex
livecheck_strategy = livecheck.strategy || referenced_livecheck&.strategy livecheck_strategy = livecheck.strategy || referenced_livecheck&.strategy
livecheck_strategy_block = livecheck.strategy_block || referenced_livecheck&.strategy_block livecheck_strategy_block = livecheck.strategy_block || referenced_livecheck&.strategy_block
@ -659,7 +667,9 @@ module Homebrew
block_provided: livecheck_strategy_block.present?, block_provided: livecheck_strategy_block.present?,
) )
strategy = Strategy.from_symbol(livecheck_strategy) || strategies.first strategy = Strategy.from_symbol(livecheck_strategy) || strategies.first
strategy_name = livecheck_strategy_names[strategy] next unless strategy
strategy_name = livecheck_strategy_names(strategy)
if strategy.respond_to?(:preprocess_url) if strategy.respond_to?(:preprocess_url)
url = strategy.preprocess_url(url) url = strategy.preprocess_url(url)
@ -674,10 +684,10 @@ module Homebrew
elsif original_url.present? && original_url != "None" elsif original_url.present? && original_url != "None"
puts "URL: #{original_url}" puts "URL: #{original_url}"
end end
puts "URL Options: #{livecheck_url_options}" if livecheck_url_options.present?
puts "URL (processed): #{url}" if url != original_url puts "URL (processed): #{url}" if url != original_url
puts "URL Options: #{livecheck_url_options}" if livecheck_url_options.present?
if strategies.present? && verbose if strategies.present? && verbose
puts "Strategies: #{strategies.map { |s| livecheck_strategy_names[s] }.join(", ")}" puts "Strategies: #{strategies.map { |s| livecheck_strategy_names(s) }.join(", ")}"
end end
puts "Strategy: #{strategy_name}" if strategy.present? puts "Strategy: #{strategy_name}" if strategy.present?
puts "Regex: #{livecheck_regex.inspect}" if livecheck_regex.present? puts "Regex: #{livecheck_regex.inspect}" if livecheck_regex.present?
@ -695,23 +705,25 @@ module Homebrew
next if strategy.blank? next if strategy.blank?
homebrew_curl = case strategy_name if (livecheck_homebrew_curl = livecheck_options.homebrew_curl).nil?
when "PageMatch", "HeaderMatch" case strategy_name
use_homebrew_curl?(referenced_package, url) when "PageMatch", "HeaderMatch"
if (homebrew_curl = use_homebrew_curl?(referenced_package, url))
livecheck_options = livecheck_options.merge({ homebrew_curl: })
livecheck_homebrew_curl = homebrew_curl
end
end
end end
puts "Homebrew curl?: Yes" if debug && homebrew_curl.present? puts "Homebrew curl?: #{livecheck_homebrew_curl ? "Yes" : "No"}" if debug && !livecheck_homebrew_curl.nil?
strategy_args = { # Only use arguments that the strategy's `#find_versions` method
regex: livecheck_regex, # supports
url_options: livecheck_url_options, find_versions_parameters = livecheck_find_versions_parameters(strategy)
homebrew_curl:, strategy_args = {}
} strategy_args[:cask] = cask if find_versions_parameters.include?(:cask)
# TODO: Set `cask`/`url` args based on the presence of the keyword arg strategy_args[:url] = url if find_versions_parameters.include?(:url)
# in the strategy's `#find_versions` method once we figure out why strategy_args[:regex] = livecheck_regex if find_versions_parameters.include?(:regex)
# `strategy.method(:find_versions).parameters` isn't working as strategy_args[:options] = livecheck_options if find_versions_parameters.include?(:options)
# expected.
strategy_args[:cask] = cask if strategy_name == "ExtractPlist" && cask.present?
strategy_args[:url] = url
strategy_args.compact! strategy_args.compact!
strategy_data = strategy.find_versions(**strategy_args, &livecheck_strategy_block) strategy_data = strategy.find_versions(**strategy_args, &livecheck_strategy_block)
@ -811,10 +823,9 @@ module Homebrew
end end
version_info[:meta][:url][:final] = strategy_data[:final_url] if strategy_data[:final_url] version_info[:meta][:url][:final] = strategy_data[:final_url] if strategy_data[:final_url]
version_info[:meta][:url][:options] = livecheck_url_options if livecheck_url_options.present? version_info[:meta][:url][:options] = livecheck_url_options if livecheck_url_options.present?
version_info[:meta][:url][:homebrew_curl] = homebrew_curl if homebrew_curl.present?
end end
version_info[:meta][:strategy] = strategy_name if strategy.present? version_info[:meta][:strategy] = strategy_name if strategy.present?
version_info[:meta][:strategies] = strategies.map { |s| livecheck_strategy_names[s] } if strategies.present? version_info[:meta][:strategies] = strategies.map { |s| livecheck_strategy_names(s) } if strategies.present?
version_info[:meta][:regex] = regex.inspect if regex.present? version_info[:meta][:regex] = regex.inspect if regex.present?
version_info[:meta][:cached] = true if strategy_data[:cached] == true version_info[:meta][:cached] = true if strategy_data[:cached] == true
version_info[:meta][:throttle] = livecheck_throttle if livecheck_throttle version_info[:meta][:throttle] = livecheck_throttle if livecheck_throttle
@ -858,9 +869,10 @@ module Homebrew
resource_version_info = {} resource_version_info = {}
livecheck = resource.livecheck livecheck = resource.livecheck
livecheck_options = livecheck.options
livecheck_url_options = livecheck_options.url_options.compact
livecheck_reference = livecheck.formula livecheck_reference = livecheck.formula
livecheck_url = livecheck.url livecheck_url = livecheck.url
livecheck_url_options = livecheck.url_options
livecheck_regex = livecheck.regex livecheck_regex = livecheck.regex
livecheck_strategy = livecheck.strategy livecheck_strategy = livecheck.strategy
livecheck_strategy_block = livecheck.strategy_block livecheck_strategy_block = livecheck.strategy_block
@ -883,7 +895,9 @@ module Homebrew
block_provided: livecheck_strategy_block.present?, block_provided: livecheck_strategy_block.present?,
) )
strategy = Strategy.from_symbol(livecheck_strategy) || strategies.first strategy = Strategy.from_symbol(livecheck_strategy) || strategies.first
strategy_name = livecheck_strategy_names[strategy] next unless strategy
strategy_name = livecheck_strategy_names(strategy)
if strategy.respond_to?(:preprocess_url) if strategy.respond_to?(:preprocess_url)
url = strategy.preprocess_url(url) url = strategy.preprocess_url(url)
@ -898,10 +912,10 @@ module Homebrew
elsif original_url.present? && original_url != "None" elsif original_url.present? && original_url != "None"
puts "URL: #{original_url}" puts "URL: #{original_url}"
end end
puts "URL Options: #{livecheck_url_options}" if livecheck_url_options.present?
puts "URL (processed): #{url}" if url != original_url puts "URL (processed): #{url}" if url != original_url
puts "URL Options: #{livecheck_url_options}" if livecheck_url_options.present?
if strategies.present? && verbose if strategies.present? && verbose
puts "Strategies: #{strategies.map { |s| livecheck_strategy_names[s] }.join(", ")}" puts "Strategies: #{strategies.map { |s| livecheck_strategy_names(s) }.join(", ")}"
end end
puts "Strategy: #{strategy_name}" if strategy.present? puts "Strategy: #{strategy_name}" if strategy.present?
puts "Regex: #{livecheck_regex.inspect}" if livecheck_regex.present? puts "Regex: #{livecheck_regex.inspect}" if livecheck_regex.present?
@ -922,16 +936,22 @@ module Homebrew
puts if debug && strategy.blank? && livecheck_reference != :parent puts if debug && strategy.blank? && livecheck_reference != :parent
next if strategy.blank? && livecheck_reference != :parent next if strategy.blank? && livecheck_reference != :parent
if debug && !(livecheck_homebrew_curl = livecheck_options.homebrew_curl).nil?
puts "Homebrew curl?: #{livecheck_homebrew_curl ? "Yes" : "No"}"
end
if livecheck_reference == :parent if livecheck_reference == :parent
match_version_map = { formula_latest => Version.new(formula_latest) } match_version_map = { formula_latest => Version.new(formula_latest) }
cached = true cached = true
else else
strategy_args = { # Only use arguments that the strategy's `#find_versions` method
url:, # supports
regex: livecheck_regex, find_versions_parameters = livecheck_find_versions_parameters(strategy)
url_options: livecheck_url_options, strategy_args = {}
homebrew_curl: false, strategy_args[:url] = url if find_versions_parameters.include?(:url)
}.compact strategy_args[:regex] = livecheck_regex if find_versions_parameters.include?(:regex)
strategy_args[:options] = livecheck_options if find_versions_parameters.include?(:options)
strategy_args.compact!
strategy_data = strategy.find_versions(**strategy_args, &livecheck_strategy_block) strategy_data = strategy.find_versions(**strategy_args, &livecheck_strategy_block)
match_version_map = strategy_data[:matches] match_version_map = strategy_data[:matches]
@ -986,7 +1006,7 @@ module Homebrew
res_current = T.must(resource.version) res_current = T.must(resource.version)
res_latest = Version.new(match_version_map.values.max_by { |v| LivecheckVersion.create(resource, v) }) res_latest = Version.new(match_version_map.values.max_by { |v| LivecheckVersion.create(resource, v) })
return status_hash(resource, "error", ["Unable to get versions"], verbose:) if res_latest.blank? return status_hash(resource, "error", [NO_VERSIONS_MSG], verbose:) if res_latest.blank?
is_outdated = res_current < res_latest is_outdated = res_current < res_latest
is_newer_than_upstream = res_current > res_latest is_newer_than_upstream = res_current > res_latest
@ -1023,7 +1043,7 @@ module Homebrew
end end
resource_version_info[:meta][:strategy] = strategy_name if strategy.present? resource_version_info[:meta][:strategy] = strategy_name if strategy.present?
if strategies.present? if strategies.present?
resource_version_info[:meta][:strategies] = strategies.map { |s| livecheck_strategy_names[s] } resource_version_info[:meta][:strategies] = strategies.map { |s| livecheck_strategy_names(s) }
end end
resource_version_info[:meta][:regex] = regex.inspect if regex.present? resource_version_info[:meta][:regex] = regex.inspect if regex.present?
resource_version_info[:meta][:cached] = true if cached == true resource_version_info[:meta][:cached] = true if cached == true

View File

@ -0,0 +1,105 @@
# typed: strong
# frozen_string_literal: true
module Homebrew
module Livecheck
# Options to modify livecheck's behavior. These primarily come from
# `livecheck` blocks but they can also be set by livecheck at runtime.
#
# Option values use a `nil` default to indicate that the value has not been
# set.
class Options < T::Struct
# Whether to use brewed curl.
prop :homebrew_curl, T.nilable(T::Boolean)
# Form data to use when making a `POST` request.
prop :post_form, T.nilable(T::Hash[Symbol, String])
# JSON data to use when making a `POST` request.
prop :post_json, T.nilable(T::Hash[Symbol, String])
# Returns a `Hash` of options that are provided as arguments to `url`.
sig { returns(T::Hash[Symbol, T.untyped]) }
def url_options
{
homebrew_curl:,
post_form:,
post_json:,
}
end
# Returns a `Hash` of all instance variables, using `String` keys.
sig { returns(T::Hash[String, T.untyped]) }
def to_hash
T.let(serialize, T::Hash[String, T.untyped])
end
# Returns a `Hash` of all instance variables, using `Symbol` keys.
sig { returns(T::Hash[Symbol, T.untyped]) }
def to_h = to_hash.transform_keys(&:to_sym)
# Returns a new object formed by merging `other` values with a copy of
# `self`.
#
# `nil` values are removed from `other` before merging if it is an
# `Options` object, as these are unitiailized values. This ensures that
# existing values in `self` aren't unexpectedly overwritten with defaults.
sig { params(other: T.any(Options, T::Hash[Symbol, T.untyped])).returns(Options) }
def merge(other)
return dup if other.empty?
this_hash = to_h
other_hash = other.is_a?(Options) ? other.to_h : other
return dup if this_hash == other_hash
new_options = this_hash.merge(other_hash)
Options.new(**new_options)
end
# Merges values from `other` into `self` and returns `self`.
#
# `nil` values are removed from `other` before merging if it is an
# `Options` object, as these are unitiailized values. This ensures that
# existing values in `self` aren't unexpectedly overwritten with defaults.
sig { params(other: T.any(Options, T::Hash[Symbol, T.untyped])).returns(Options) }
def merge!(other)
return self if other.empty?
if other.is_a?(Options)
return self if self == other
other.instance_variables.each do |ivar|
next if (v = T.let(other.instance_variable_get(ivar), Object)).nil?
instance_variable_set(ivar, v)
end
else
other.each do |k, v|
cmd = :"#{k}="
send(cmd, v) if respond_to?(cmd)
end
end
self
end
sig { params(other: Object).returns(T::Boolean) }
def ==(other)
return false unless other.is_a?(Options)
@homebrew_curl == other.homebrew_curl &&
@post_form == other.post_form &&
@post_json == other.post_json
end
alias eql? ==
# Whether the object has only default values.
sig { returns(T::Boolean) }
def empty? = to_hash.empty?
# Whether the object has any non-default values.
sig { returns(T::Boolean) }
def present? = !empty?
end
end
end

View File

@ -0,0 +1,40 @@
# typed: strong
# frozen_string_literal: true
module Homebrew
module Livecheck
# The interface for livecheck strategies. Because third-party strategies
# are not required to extend this module, we do not provide any default
# method implementations here.
module Strategic
extend T::Helpers
interface!
# Whether the strategy can be applied to the provided URL.
#
# @param url [String] the URL to match against
sig { abstract.params(url: String).returns(T::Boolean) }
def match?(url); end
# Checks the content at the URL for new versions. Implementations may not
# support all options.
#
# @param url the URL of the content to check
# @param regex a regex for matching versions in content
# @param provided_content content to check instead of
# fetching
# @param options options to modify behavior
# @param block a block to match the content
sig {
abstract.params(
url: String,
regex: T.nilable(Regexp),
provided_content: T.nilable(String),
options: Options,
block: T.nilable(Proc),
).returns(T::Hash[Symbol, T.anything])
}
def find_versions(url:, regex: nil, provided_content: nil, options: Options.new, &block); end
end
end
end

View File

@ -2,6 +2,7 @@
# frozen_string_literal: true # frozen_string_literal: true
require "utils/curl" require "utils/curl"
require "livecheck/options"
module Homebrew module Homebrew
module Livecheck module Livecheck
@ -172,8 +173,8 @@ module Homebrew
# @return [Array] # @return [Array]
sig { sig {
params( params(
post_form: T.nilable(T::Hash[T.any(String, Symbol), String]), post_form: T.nilable(T::Hash[Symbol, String]),
post_json: T.nilable(T::Hash[T.any(String, Symbol), String]), post_json: T.nilable(T::Hash[Symbol, String]),
).returns(T::Array[String]) ).returns(T::Array[String])
} }
def self.post_args(post_form: nil, post_json: nil) def self.post_args(post_form: nil, post_json: nil)
@ -193,23 +194,16 @@ module Homebrew
# collected into an array of hashes. # collected into an array of hashes.
# #
# @param url [String] the URL to fetch # @param url [String] the URL to fetch
# @param url_options [Hash] options to modify curl behavior # @param options [Options] options to modify behavior
# @param homebrew_curl [Boolean] whether to use brewed curl with the URL
# @return [Array] # @return [Array]
sig { sig { params(url: String, options: Options).returns(T::Array[T::Hash[String, String]]) }
params( def self.page_headers(url, options: Options.new)
url: String,
url_options: T::Hash[Symbol, T.untyped],
homebrew_curl: T::Boolean,
).returns(T::Array[T::Hash[String, String]])
}
def self.page_headers(url, url_options: {}, homebrew_curl: false)
headers = [] headers = []
if url_options[:post_form].present? || url_options[:post_json].present? if options.post_form || options.post_json
curl_post_args = ["--request", "POST", *post_args( curl_post_args = ["--request", "POST", *post_args(
post_form: url_options[:post_form], post_form: options.post_form,
post_json: url_options[:post_json], post_json: options.post_json,
)] )]
end end
@ -221,7 +215,7 @@ module Homebrew
MAX_REDIRECTIONS.to_s, MAX_REDIRECTIONS.to_s,
url, url,
wanted_headers: ["location", "content-disposition"], wanted_headers: ["location", "content-disposition"],
use_homebrew_curl: homebrew_curl, use_homebrew_curl: options.homebrew_curl || false,
user_agent:, user_agent:,
**DEFAULT_CURL_OPTIONS, **DEFAULT_CURL_OPTIONS,
) )
@ -242,21 +236,14 @@ module Homebrew
# array with the error message instead. # array with the error message instead.
# #
# @param url [String] the URL of the content to check # @param url [String] the URL of the content to check
# @param url_options [Hash] options to modify curl behavior # @param options [Options] options to modify behavior
# @param homebrew_curl [Boolean] whether to use brewed curl with the URL
# @return [Hash] # @return [Hash]
sig { sig { params(url: String, options: Options).returns(T::Hash[Symbol, T.untyped]) }
params( def self.page_content(url, options: Options.new)
url: String, if options.post_form || options.post_json
url_options: T::Hash[Symbol, T.untyped],
homebrew_curl: T::Boolean,
).returns(T::Hash[Symbol, T.untyped])
}
def self.page_content(url, url_options: {}, homebrew_curl: false)
if url_options[:post_form].present? || url_options[:post_json].present?
curl_post_args = ["--request", "POST", *post_args( curl_post_args = ["--request", "POST", *post_args(
post_form: url_options[:post_form], post_form: options.post_form,
post_json: url_options[:post_json], post_json: options.post_json,
)] )]
end end
@ -266,7 +253,9 @@ module Homebrew
*curl_post_args, *curl_post_args,
*PAGE_CONTENT_CURL_ARGS, url, *PAGE_CONTENT_CURL_ARGS, url,
**DEFAULT_CURL_OPTIONS, **DEFAULT_CURL_OPTIONS,
use_homebrew_curl: homebrew_curl || !curl_supports_fail_with_body?, use_homebrew_curl: options.homebrew_curl ||
!curl_supports_fail_with_body? ||
false,
user_agent: user_agent:
) )
next unless status.success? next unless status.success?

View File

@ -1,9 +0,0 @@
# typed: strict
module Homebrew
module Livecheck
module Strategy
include Kernel
end
end
end

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