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
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:
- type: markdown
attributes:

View File

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

View File

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

View File

@ -57,7 +57,7 @@ jobs:
zizmor --format sarif . > results.sarif || true
- name: Upload SARIF file
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
with:
name: results.sarif
path: results.sarif
@ -72,13 +72,13 @@ jobs:
security-events: write
steps:
- name: Download SARIF file
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9
with:
name: results.sarif
path: results.sarif
- 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:
sarif_file: results.sarif
category: zizmor

View File

@ -34,7 +34,7 @@ jobs:
test-bot: true
- name: Cache Bundler RubyGems
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
with:
path: ${{ steps.set-up-homebrew.outputs.gems-path }}
key: ${{ runner.os }}-rubygems-${{ steps.set-up-homebrew.outputs.gems-hash }}

View File

@ -28,7 +28,7 @@ jobs:
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9
uses: github/codeql-action/init@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10
with:
languages: ruby
config: |
@ -36,4 +36,4 @@ jobs:
- Library/Homebrew/vendor
- 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
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
with:
cache-binary: false
@ -112,7 +112,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker image
uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
with:
context: .
load: true
@ -141,7 +141,7 @@ jobs:
- name: Deploy the tagged Docker image
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:
context: .
push: true

View File

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

View File

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

View File

@ -43,7 +43,7 @@ jobs:
persist-credentials: false
- name: Install Ruby
uses: ruby/setup-ruby@d781c1b4ed31764801bfae177617bb0446f5ef8d # v1.218.0
uses: ruby/setup-ruby@32110d4e311bd8996b2a82bf2a43b714ccc91777 # v1.221.0
with:
bundler-cache: true
working-directory: rubydoc

View File

@ -50,7 +50,7 @@ jobs:
signing_key: ${{ secrets.BREWTESTBOT_SSH_SIGNING_KEY }}
- name: Cache Bundler RubyGems
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
with:
path: ${{ steps.set-up-homebrew.outputs.gems-path }}
key: ${{ runner.os }}-rubygems-${{ steps.set-up-homebrew.outputs.gems-hash }}

View File

@ -40,7 +40,7 @@ jobs:
test-bot: false
- name: Cache Bundler RubyGems
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
with:
path: ${{ steps.set-up-homebrew.outputs.gems-path }}
key: ${{ runner.os }}-rubygems-syntax-${{ steps.set-up-homebrew.outputs.gems-hash }}
@ -53,7 +53,7 @@ jobs:
run: brew install shellcheck shfmt
- name: Cache style cache
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
with:
path: ~/.cache/Homebrew/style
key: syntax-style-cache-${{ github.sha }}
@ -92,7 +92,7 @@ jobs:
test-bot: true
- name: Cache Bundler RubyGems
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
with:
path: ${{ steps.set-up-homebrew.outputs.gems-path }}
key: ${{ runner.os }}-rubygems-tap-syntax-${{ steps.set-up-homebrew.outputs.gems-hash }}
@ -102,7 +102,7 @@ jobs:
run: brew install-bundler-gems --groups=style
- name: Cache style cache
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
with:
path: ~/.cache/Homebrew/style
key: tap-syntax-style-cache-${{ github.sha }}
@ -242,6 +242,7 @@ jobs:
name: ${{ matrix.name }}
needs: syntax
runs-on: ${{ matrix.runs-on }}
container: ${{ matrix.container }}
strategy:
matrix:
include:
@ -259,7 +260,8 @@ jobs:
runs-on: ubuntu-22.04
- name: tests (Ubuntu 20.04)
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)
test-flags: --coverage
runs-on: macos-13
@ -279,7 +281,7 @@ jobs:
test-bot: false
- name: Cache Bundler RubyGems
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
with:
path: ${{ steps.set-up-homebrew.outputs.gems-path }}
key: ${{ matrix.runs-on }}-tests-rubygems-${{ steps.set-up-homebrew.outputs.gems-hash }}
@ -294,7 +296,7 @@ jobs:
run: mkdir tests
- name: Cache parallel tests log
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
with:
path: tests
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' ',')
echo "filenames=${filenames%,}" >> "$GITHUB_OUTPUT"
- uses: codecov/test-results-action@44ecb3a270cd942bdf0fa8f2ce14cb32493e810a # v1.0.3
- uses: codecov/test-results-action@5c441a7bcc06f8706cde90192857d337c5dab8a6 # v1.0.4
with:
working-directory: ${{ steps.set-up-homebrew.outputs.repository-path }}
files: ${{ steps.junit_xml.outputs.filenames }}
disable_search: true
token: ${{ secrets.CODECOV_TOKEN }}
- uses: codecov/codecov-action@13ce06bfc6bbe3ecf90edbbf1bc32fe5978ca1d3 # v5.3.1
- uses: codecov/codecov-action@0565863a31f2c772f9f0395002a31e3f06189574 # v5.4.0
with:
working-directory: ${{ steps.set-up-homebrew.outputs.repository-path }}
files: Library/Homebrew/test/coverage/coverage.xml
@ -364,12 +366,15 @@ jobs:
strategy:
matrix:
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)
runs-on: ubuntu-latest
container: ghcr.io/homebrew/ubuntu22.04:master
- name: test default formula (Ubuntu 20.04)
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)
runs-on: macos-13
- name: test default formula (macOS 15 arm64)
@ -415,7 +420,7 @@ jobs:
- name: Cache Homebrew Bundler RubyGems
id: cache
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
with:
path: ${{ steps.set-up-homebrew.outputs.gems-path }}
key: ${{ runner.os }}-rubygems-${{ steps.set-up-homebrew.outputs.gems-hash }}

View File

@ -92,7 +92,7 @@ jobs:
fi
- name: Generate push token
uses: actions/create-github-app-token@67e27a7eb7db372a1c61a7f9bdab8699e9ee57f7 # v1.11.3
uses: actions/create-github-app-token@21cfef2b496dd8ef5b904c159339626a10ad380e # v1.11.6
id: app-token
if: github.event_name == 'workflow_dispatch'
with:

1
.gitignore vendored
View File

@ -90,6 +90,7 @@
**/vendor/bundle/ruby/*/gems/json_schemer-*/
**/vendor/bundle/ruby/*/gems/kramdown-*/
**/vendor/bundle/ruby/*/gems/language_server-protocol-*/
**/vendor/bundle/ruby/*/gems/lint_roller-*/
**/vendor/bundle/ruby/*/gems/logger-*/
**/vendor/bundle/ruby/*/gems/method_source-*/
**/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:
- ./Homebrew/rubocops.rb
- rubocop-md
- rubocop-performance
- rubocop-rspec
- rubocop-sorbet
inherit_mode:
@ -255,6 +260,11 @@ RSpec/Focus:
RSpec/MessageSpies:
EnforcedStyle: receive
# These are legacy violations that we should try to fix.
Sorbet/AllowIncompatibleOverride:
Exclude:
- "Homebrew/livecheck/strategy/*.rb"
# Try getting rid of these.
Sorbet/ConstantsFromStrings:
Enabled: false

View File

@ -10,13 +10,13 @@ GEM
bindata (2.5.0)
coderay (1.1.3)
concurrent-ruby (1.3.5)
diff-lcs (1.5.1)
diff-lcs (1.6.0)
docile (1.4.1)
elftools (1.3.1)
bindata (~> 2)
erubi (1.13.1)
hana (1.3.7)
json (2.9.1)
json (2.10.1)
json_schemer (2.4.0)
bigdecimal
hana (~> 1.3)
@ -25,12 +25,13 @@ GEM
kramdown (2.5.1)
rexml (>= 3.3.9)
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)
minitest (5.25.4)
netrc (0.11.0)
parallel (1.26.3)
parallel_tests (4.9.0)
parallel_tests (5.0.1)
parallel
parser (3.3.7.1)
ast (~> 2.4.1)
@ -51,9 +52,9 @@ GEM
sorbet-runtime (>= 0.5.9204)
rbs (3.8.1)
logger
redcarpet (3.6.0)
redcarpet (3.6.1)
regexp_parser (2.10.0)
rexml (3.4.0)
rexml (3.4.1)
rspec (3.13.0)
rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0)
@ -75,9 +76,10 @@ GEM
rspec-support (3.13.2)
rspec_junit_formatter (0.6.0)
rspec-core (>= 2, < 4, != 2.12.0)
rubocop (1.71.2)
rubocop (1.73.2)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
parallel (~> 1.10)
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
@ -85,18 +87,21 @@ GEM
rubocop-ast (>= 1.38.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 4.0)
rubocop-ast (1.38.0)
rubocop-ast (1.38.1)
parser (>= 3.3.1.0)
rubocop-md (1.2.4)
rubocop (>= 1.45)
rubocop-performance (1.23.1)
rubocop (>= 1.48.1, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0)
rubocop-rspec (3.4.0)
rubocop (~> 1.61)
rubocop-sorbet (0.8.7)
rubocop-md (2.0.0)
lint_roller (~> 1.1)
rubocop (>= 1.72.1)
rubocop-performance (1.24.0)
lint_roller (~> 1.1)
rubocop (>= 1.72.1, < 2.0)
rubocop-ast (>= 1.38.0, < 2.0)
rubocop-rspec (3.5.0)
lint_roller (~> 1.1)
rubocop (~> 1.72, >= 1.72.1)
rubocop-sorbet (0.8.9)
rubocop (>= 1)
ruby-lsp (0.23.9)
ruby-lsp (0.23.11)
language_server-protocol (~> 3.17.0)
prism (>= 1.2, < 2.0)
rbs (>= 3, < 4)
@ -114,23 +119,23 @@ GEM
simplecov-html (0.13.1)
simplecov_json_formatter (0.1.4)
simpleidn (0.2.3)
sorbet (0.5.11812)
sorbet-static (= 0.5.11812)
sorbet-runtime (0.5.11812)
sorbet-static (0.5.11812-aarch64-linux)
sorbet-static (0.5.11812-universal-darwin)
sorbet-static (0.5.11812-x86_64-linux)
sorbet-static-and-runtime (0.5.11812)
sorbet (= 0.5.11812)
sorbet-runtime (= 0.5.11812)
spoom (1.5.3)
sorbet (0.5.11888)
sorbet-static (= 0.5.11888)
sorbet-runtime (0.5.11888)
sorbet-static (0.5.11888-aarch64-linux)
sorbet-static (0.5.11888-universal-darwin)
sorbet-static (0.5.11888-x86_64-linux)
sorbet-static-and-runtime (0.5.11888)
sorbet (= 0.5.11888)
sorbet-runtime (= 0.5.11888)
spoom (1.5.4)
erubi (>= 1.10.0)
prism (>= 0.28.0)
rbi (>= 0.2.3)
sorbet-static-and-runtime (>= 0.5.10187)
thor (>= 0.19.2)
stackprof (0.2.27)
tapioca (0.16.9)
tapioca (0.16.11)
benchmark
bundler (>= 2.2.25)
netrc (>= 0.11.0)
@ -153,7 +158,6 @@ GEM
PLATFORMS
aarch64-linux
arm-linux
arm64-darwin
x86_64-darwin
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
class BottleSpecification
extend Attrable
RELOCATABLE_CELLARS = [:any, :any_skip_relocation].freeze
attr_rw :rebuild
attr_accessor :tap
attr_reader :collector, :root_url_specs, :repository
@ -17,6 +15,11 @@ class BottleSpecification
@root_url_specs = {}
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 = {})
if var.nil?
@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"
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}" ]]
then
if [[ -z "${HOMEBREW_DEV_CMD_RUN}" ]]
@ -999,6 +992,13 @@ then
export HOMEBREW_SORBET_RUNTIME="1"
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" ]]
then
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
TZ: "UTC0",
) do
formula.patch
if args.git?
formula.selective_patch(is_data: false)
system "git", "init"
system "git", "add", "-A"
formula.selective_patch(is_data: true)
else
formula.patch
end
if args.interactive?
ohai "Entering interactive mode..."
puts <<~EOS
@ -185,6 +188,8 @@ class Build
# Find and link metafiles
formula.prefix.install_metafiles formula.buildpath
formula.prefix.install_metafiles formula.libexec if formula.libexec.exist?
normalize_pod2man_outputs!(formula)
end
end
end
@ -214,6 +219,11 @@ class Build
rescue
raise "#{formula.opt_prefix} not present or broken\nPlease reinstall #{formula.full_name}. Sorry :("
end
def normalize_pod2man_outputs!(formula)
keg = Keg.new(formula.prefix)
keg.normalize_pod2man_outputs!
end
end
begin

View File

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

View File

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

View File

@ -411,7 +411,7 @@ module Cask
next
end
if MacOS.undeletable?(resolved_path)
if undeletable?(resolved_path)
opoo "Skipping #{Formatter.identifier(action)} for undeletable path '#{path}'."
next
end
@ -538,6 +538,10 @@ module Cask
recursive_rmdir(*resolved_paths, **kwargs)
end
end
def undeletable?(target); 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
ALT_NAME_ATTRIBUTE = "com.apple.metadata:kMDItemAlternateNames"
private_constant :ALT_NAME_ATTRIBUTE
# Try to make the asset searchable under the target name. Spotlight
# 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
ohai "Linking #{self.class.english_name} '#{source.basename}' to '#{target}'"
create_filesystem_link(command:)
create_filesystem_link(command)
end
def unlink(command: nil, **)
@ -72,14 +72,10 @@ module Cask
Utils.gain_permissions_remove(target, command:)
end
def create_filesystem_link(command: nil)
Utils.gain_permissions_mkpath(target.dirname, command:)
sig { params(command: T.class_of(SystemCommand)).void }
def create_filesystem_link(command); end
end
end
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
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
# frozen_string_literal: true
require "attrable"
require "cask/denylist"
require "cask/download"
require "digest"
@ -19,7 +18,6 @@ module Cask
class Audit
include SystemCommand::Mixin
include ::Utils::Curl
extend Attrable
sig { returns(Cask) }
attr_reader :cask
@ -27,11 +25,16 @@ module Cask
sig { returns(T.nilable(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(
cask,
download: nil, quarantine: nil,
download: false, quarantine: false,
token_conflicts: nil, online: nil, strict: nil, signing: nil,
new_cask: nil, only: [], except: []
)
@ -42,7 +45,7 @@ module Cask
token_conflicts = new_cask if token_conflicts.nil?
# `online` and `signing` imply `download`
download = online || signing if download.nil?
download ||= online || signing
@cask = cask
@download = Download.new(cask, quarantine:) if download
@ -51,18 +54,34 @@ module Cask
@signing = signing
@new_cask = new_cask
@token_conflicts = token_conflicts
@only = only || []
@except = except || []
@only = only
@except = except
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!
only_audits = @only
except_audits = @except
private_methods.map(&:to_s).grep(/^audit_/).each do |audit_method_name|
name = audit_method_name.delete_prefix("audit_")
next if !only_audits.empty? && only_audits&.exclude?(name)
next if except_audits&.include?(name)
next if !only_audits.empty? && only_audits.exclude?(name)
next if except_audits.include?(name)
send(audit_method_name)
end
@ -292,6 +311,7 @@ module Cask
end
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 }
def audit_hosting_with_livecheck(livecheck_result: audit_livecheck_version)
@ -317,6 +337,7 @@ module Cask
end
SOURCEFORGE_OSDN_REFERENCE_URL = "https://docs.brew.sh/Cask-Cookbook#sourceforgeosdn-urls"
private_constant :SOURCEFORGE_OSDN_REFERENCE_URL
sig { void }
def audit_download_url_format
@ -339,6 +360,7 @@ module Cask
end
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 }
def audit_unnecessary_verified

View File

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

View File

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

View File

@ -1,7 +1,6 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# frozen_string_literal: true
require "attrable"
require "locale"
require "lazy_object"
require "livecheck"
@ -54,6 +53,9 @@ module Cask
Artifact::Suite,
Artifact::VstPlugin,
Artifact::Vst3Plugin,
Artifact::ZshCompletion,
Artifact::FishCompletion,
Artifact::BashCompletion,
Artifact::Uninstall,
Artifact::Zap,
].freeze
@ -105,19 +107,36 @@ module Cask
*ARTIFACT_BLOCK_CLASSES.flat_map { |klass| [klass.dsl_key, klass.uninstall_dsl_key] },
]).freeze
extend Attrable
include OnSystem::MacOSOnly
include OnSystem::MacOSAndLinux
attr_reader :cask, :token, :deprecation_date, :deprecation_reason, :deprecation_replacement, :disable_date,
: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)
@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
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.
#
# NOTE: Multiple names can be specified.
@ -287,6 +306,7 @@ module Cask
#
# @see DSL::Version
# @api public
sig { params(arg: T.nilable(T.any(String, Symbol))).returns(T.nilable(DSL::Version)) }
def version(arg = nil)
set_unique_stanza(:version, arg.nil?) do
if !arg.is_a?(String) && arg != :latest
@ -311,17 +331,35 @@ module Cask
#
# ```ruby
# sha256 arm: "7bdb497080ffafdfd8cc94d8c62b004af1be9599e865e5555e456e2681e150ca",
# intel: "b3c1c2442480a0219b9e05cf91d03385858c20f04b764ec08a3fa83d1b27e7b2"
# x86_64: "b3c1c2442480a0219b9e05cf91d03385858c20f04b764ec08a3fa83d1b27e7b2"
# x86_64_linux: "1a2aee7f1ddc999993d4d7d42a150c5e602bc17281678050b8ed79a0500cc90f"
# arm64_linux: "bd766af7e692afceb727a6f88e24e6e68d9882aeb3e8348412f6c03d96537c75"
# ```
#
# @api public
def sha256(arg = nil, arm: nil, intel: nil)
should_return = arg.nil? && arm.nil? && intel.nil?
sig {
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
@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
when :no_check
val
@ -352,6 +390,31 @@ module Cask
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.
#
# NOTE: Multiple dependencies can be specified.
@ -370,6 +433,13 @@ module Cask
@depends_on
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.
#
# @api public
@ -382,6 +452,7 @@ module Cask
@artifacts ||= ArtifactSet.new
end
sig { returns(Pathname) }
def caskroom_path
cask.caskroom_path
end
@ -389,6 +460,7 @@ module Cask
# The staged location for this cask, including version number.
#
# @api public
sig { returns(Pathname) }
def staged_path
return @staged_path if @staged_path
@ -520,9 +592,15 @@ module Cask
true
end
sig { returns(T.nilable(MacOSVersion)) }
def os_version
nil
end
# The directory `app`s are installed into.
#
# @api public
sig { returns(T.any(Pathname, String)) }
def appdir
return HOMEBREW_CASK_APPDIR_PLACEHOLDER if Cask.generating_hash?

View File

@ -1,8 +1,6 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# frozen_string_literal: true
require "attrable"
module Cask
class DSL
# 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
# convenience of cask authors.
class Caveats < Base
extend Attrable
attr_predicate :discontinued?
def initialize(*args)
super
@built_in_caveats = {}
@ -37,6 +31,9 @@ module Cask
private_class_method :caveat
sig { returns(T::Boolean) }
def discontinued? = @discontinued
sig { returns(String) }
def to_s
(@custom_caveats + @built_in_caveats.values).join("\n")

View File

@ -1,7 +1,6 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# frozen_string_literal: true
require "attrable"
require "formula_installer"
require "unpack_strategy"
require "utils/topological_hash"
@ -17,8 +16,15 @@ require "cgi"
module Cask
# Installer for a {Cask}.
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,
skip_cask_deps: false, binaries: true, verbose: false,
zap: false, require_sha: false, upgrade: false, reinstall: false,
@ -42,9 +48,44 @@ module Cask
@quiet = quiet
end
attr_predicate :binaries?, :force?, :adopt?, :skip_cask_deps?, :require_sha?,
:reinstall?, :upgrade?, :verbose?, :zap?, :installed_as_dependency?, :installed_on_request?,
:quarantine?, :quiet?
sig { returns(T::Boolean) }
def adopt? = @adopt
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)
odebug "Printing caveats"

View File

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

View File

@ -1,32 +1,32 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
module Cask
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(
*casks,
verbose: nil,
force: nil,
skip_cask_deps: nil,
binaries: nil,
require_sha: nil,
quarantine: nil,
zap: nil
verbose: false,
force: false,
skip_cask_deps: false,
binaries: false,
require_sha: false,
quarantine: false,
zap: false
)
require "cask/installer"
quarantine = true if quarantine.nil?
casks.each do |cask|
Installer.new(cask,
binaries:,
verbose:,
force:,
skip_cask_deps:,
require_sha:,
reinstall: true,
quarantine:,
zap:).install
Installer
.new(cask, binaries:, verbose:, force:, skip_cask_deps:, require_sha:, reinstall: true, quarantine:, zap:)
.install
end
end
end

View File

@ -8,7 +8,7 @@ module Cask
module Staged
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)]) }
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
module Cask
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"
casks.each do |cask|

View File

@ -42,9 +42,16 @@ module Cask
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?
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:)
end
else
@ -78,7 +85,7 @@ module Cask
return false if outdated_casks.empty?
if casks.empty? && !greedy
if casks.empty? && !greedy && greedy_casks.empty?
if !greedy_auto_updates && !greedy_latest
ohai "Casks with 'auto_updates true' or 'version :latest' " \
"will not be upgraded; pass `--greedy` to upgrade them."

View File

@ -267,6 +267,7 @@ module Cask
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.exclude?('#{')

View File

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

View File

@ -114,6 +114,7 @@ class Cleaner
# Arch & MacPorts amongst other packagers as well. The files are
# created as part of installing any Perl module.
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
# permissions and removing .la files, unless the files (or parent

View File

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

View File

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

View File

@ -209,7 +209,8 @@ module Homebrew
formulae.map(&:to_hash)
end
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),
@ -217,8 +218,9 @@ module Homebrew
elsif args.installed?
[Formula.installed.sort, Cask::Caskroom.casks.sort_by(&:full_name)]
else
args.named.to_formulae_to_casks
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?
{

View File

@ -123,7 +123,8 @@ module Homebrew
raise UsageError, "Cannot use #{flags.join(", ")} with formula arguments." unless args.no_named?
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?
Formula.installed.sort { |a, b| tap_and_name_comparison.call(a.full_name, b.full_name) }
else

View File

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

View File

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

View File

@ -161,7 +161,7 @@ module Homebrew
end
next unless formula.any_version_installed?
keg = formula.installed_kegs.last
keg = formula.installed_kegs.fetch(-1)
tab = keg.tab
# force a `brew upgrade` from the linuxbrew-core version to the homebrew-core version (even if lower)
tab.source["versions"]["version_scheme"] = -1
@ -277,7 +277,7 @@ module Homebrew
puts
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
puts <<~EOS
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 {
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)
recursive = args.recursive?
@ -106,8 +106,8 @@ module Homebrew
# 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])
if show_formulae_and_casks || args.formula?
deps += used_formulae.map(&:runtime_installed_formula_dependents)
.reduce(&:&)
deps += T.must(used_formulae.map(&:runtime_installed_formula_dependents)
.reduce(&:&))
.select(&:any_version_installed?)
end
if show_formulae_and_casks || args.cask?
@ -150,26 +150,30 @@ module Homebrew
sig {
params(
dependents: T::Array[Formula], used_formulae: T::Array[T.any(Formula, UnavailableFormula)],
recursive: T::Boolean, includes: T::Array[Symbol], ignores: T::Array[Symbol]
).returns(
T::Array[Formula],
)
dependents: T::Array[T.any(Formula, CaskDependent)],
used_formulae: T::Array[T.any(Formula, UnavailableFormula)],
recursive: T::Boolean,
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)
dependents.select do |d|
deps = if recursive
recursive_includes(Dependency, d, includes, ignores)
recursive_dep_includes(d, includes, ignores)
else
select_includes(d.deps, ignores, includes)
end
used_formulae.all? do |ff|
deps.any? do |dep|
match = begin
match = case dep
when Dependency
dep.to_formula.full_name == ff.full_name if dep.name.include?("/")
rescue
when Requirement
nil
else
T.absurd(dep)
end
next match unless match.nil?

View File

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

View File

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

View File

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

View File

@ -1,13 +1,32 @@
# typed: strict
class Dependencies < SimpleDelegator
include Enumerable
include Kernel
# This is a workaround to enable `alias eql? ==`
# @see https://github.com/sorbet/sorbet/issues/2378#issuecomment-569474238
sig { params(other: BasicObject).returns(T::Boolean) }
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
class Requirements < SimpleDelegator
include Enumerable
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

View File

@ -1,14 +1,10 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
require "cask_dependent"
# Helper functions for dependencies.
module DependenciesHelpers
extend T::Helpers
requires_ancestor { Kernel }
def args_includes_ignores(args)
includes = [:required?, :recommended?] # included by default
includes << :implicit? if args.include_implicit?
@ -23,9 +19,35 @@ module DependenciesHelpers
[includes, ignores]
end
def recursive_includes(klass, root_dependent, includes, ignores)
raise ArgumentError, "Invalid class argument: #{klass}" if klass != Dependency && klass != Requirement
sig {
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}"
klass.expand(root_dependent, cache_key:) do |dependent, dep|
@ -43,6 +65,13 @@ module DependenciesHelpers
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)
dependables.select do |dep|
next false if ignores.any? { |ignore| dep.public_send(ignore) }
@ -51,14 +80,18 @@ module DependenciesHelpers
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)
formulae_or_casks.map do |formula_or_cask|
if formula_or_cask.is_a?(Formula)
formula_or_cask
case formula_or_cask
when Formula then formula_or_cask
when Cask::Cask then CaskDependent.new(formula_or_cask)
else
CaskDependent.new(formula_or_cask)
raise TypeError, "Unsupported type: #{formula_or_cask.class}"
end
end
end
module_function :dependents
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,
quarantine: true,
any_named_args: !no_named_args,
only: args.only,
except: args.except,
only: args.only || [],
except: args.except || [],
).to_a
end
end.uniq

View File

@ -559,7 +559,7 @@ module Homebrew
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")
else
prefix

View File

@ -115,7 +115,7 @@ module Homebrew
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
end

View File

@ -40,6 +40,8 @@ module Homebrew
description: "Create a basic template for a Ruby build."
switch "--rust",
description: "Create a basic template for a Rust build."
switch "--zig",
description: "Create a basic template for a Zig build."
switch "--no-fetch",
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 " \
@ -58,7 +60,7 @@ module Homebrew
description: "Ignore errors for disallowed formula names and names that shadow aliases."
conflicts "--autotools", "--cmake", "--crystal", "--go", "--meson", "--node",
"--perl", "--python", "--ruby", "--rust", "--cask"
"--perl", "--python", "--ruby", "--rust", "--zig", "--cask"
conflicts "--cask", "--HEAD"
conflicts "--cask", "--set-license"
@ -173,6 +175,8 @@ module Homebrew
:ruby
elsif args.rust?
:rust
elsif args.zig?
:zig
end
fc = FormulaCreator.new(
@ -220,6 +224,7 @@ module Homebrew
path = fc.write_formula!
formula = Homebrew.with_no_api_env do
CoreTap.instance.clear_cache
Formula[fc.name]
end
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"
"#{record["prefix"]} (#{record["os"]} #{record["arch"]})"
else
(record["prefix"]).to_s
record["prefix"].to_s
end
when :os_versions
format_os_version_dimension(record["os_name_and_version"])

View File

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

View File

@ -16,10 +16,10 @@ require "system_command"
module Homebrew
# Module containing diagnostic checks.
module Diagnostic
def self.missing_deps(formulae, hide = nil)
def self.missing_deps(formulae, hide = [])
missing = {}
formulae.each do |f|
missing_dependencies = f.missing_dependencies(hide:)
missing_dependencies = f.missing_dependencies(hide: hide)
next if missing_dependencies.empty?
yield f.full_name, missing_dependencies if block_given?
@ -63,7 +63,7 @@ module Homebrew
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)
list.reduce(string.dup) { |acc, elem| acc << " #{elem}\n" }
.freeze
@ -748,15 +748,13 @@ module Homebrew
def check_for_unlinked_but_not_keg_only
unlinked = Formula.racks.reject do |rack|
if (HOMEBREW_LINKED_KEGS/rack.basename).directory?
true
else
next true if (HOMEBREW_LINKED_KEGS/rack.basename).directory?
begin
Formulary.from_rack(rack).keg_only?
rescue FormulaUnavailableError, TapFormulaAmbiguityError
false
end
end
end.map(&:basename)
return if unlinked.empty?
@ -1040,6 +1038,64 @@ module Homebrew
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
methods.map(&:to_s).grep(/^check_/).sort
end

View File

@ -490,6 +490,10 @@ module Homebrew
description: "If set, pass `--greedy` to all cask upgrade commands.",
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: {
description: "If set, always assume `--verbose` when running commands.",
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)) }
def on_arch_conditional(arm: nil, intel: nil); 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
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
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."
end
end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -29,7 +29,6 @@ require "utils/fork"
# Installer for a formula.
class FormulaInstaller
include FormulaCellarChecks
extend Attrable
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) }
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 {
params(
formula: Formula,
@ -148,6 +141,54 @@ class FormulaInstaller
@formula = T.let(T.must(previously_fetched_formula), Formula) if previously_fetched_formula
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]) }
def self.attempted
@attempted ||= T.let(Set.new, T.nilable(T::Set[Formula]))
@ -809,11 +850,8 @@ on_request: installed_on_request?, options:)
options |= inherited_options
options &= df.options
installed_on_request = if df.any_version_installed? && tab.present? && tab.installed_on_request
true
else
false
end
installed_on_request = df.any_version_installed? && tab.present? && tab.installed_on_request
installed_on_request ||= false
fi = FormulaInstaller.new(
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.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?
begin

View File

@ -136,6 +136,8 @@ class GitHubPackages
IMAGE_MANIFEST_SCHEMA_URI = "https://opencontainers.org/schema/image/manifest"
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!
schema_uri("content-descriptor",

View File

@ -5,6 +5,10 @@ require "test_runner_formula"
require "github_runner"
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) }
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
GITHUB_ACTIONS_LONG_TIMEOUT = 2160 # 36 hours
GITHUB_ACTIONS_SHORT_TIMEOUT = 60
private_constant :SELF_HOSTED_LINUX_RUNNER, :GITHUB_ACTIONS_LONG_TIMEOUT, :GITHUB_ACTIONS_SHORT_TIMEOUT
sig { returns(LinuxRunnerSpec) }
def linux_runner_spec
@ -97,6 +102,7 @@ class GitHubRunnerMatrix
VALID_PLATFORMS = T.let([:macos, :linux].freeze, T::Array[Symbol])
VALID_ARCHES = T.let([:arm64, :x86_64].freeze, T::Array[Symbol])
private_constant :VALID_PLATFORMS, :VALID_ARCHES
sig {
params(
@ -116,10 +122,6 @@ class GitHubRunnerMatrix
runner.freeze
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) }
def runner_enabled?(macos_version)
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
OLDEST_GITHUB_ACTIONS_ARM_MACOS_RUNNER = :sonoma
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 }
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/man5 share/man/man6 share/man/man7 share/man/man8
share/zsh share/zsh/site-functions
share/pwsh share/pwsh/completions
var/log
].map { |dir| HOMEBREW_PREFIX/dir } + must_exist_subdirectories + [
HOMEBREW_CACHE,
@ -354,6 +355,7 @@ class Keg
when :zsh
dir = path/"share/zsh/site-functions"
dir if dir.directory? && dir.children.any? { |f| f.basename.to_s.start_with?("_") }
when :pwsh then path/"share/pwsh/completions"
end
dir&.directory? && !dir.children.empty?
end
@ -388,6 +390,7 @@ class Keg
(path/"share/emacs/site-lisp"/name).children.any? { |f| ELISP_EXTENSIONS.include? f.extname }
end
sig { returns(PkgVersion) }
def version
require "pkg_version"
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__" }
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
[]
end

View File

@ -16,7 +16,7 @@ module Language
next false unless f.any_version_installed?
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 && !can_be_newer
end

View File

@ -340,7 +340,10 @@ module Language
version = rp.match %r{^#{HOMEBREW_CELLAR}/python@(.*?)/}o
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.make_symlink new_target
end
@ -351,7 +354,10 @@ module Language
version = prefix_path.match %r{^#{HOMEBREW_CELLAR}/python@(.*?)/}o
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
end
@ -362,7 +368,7 @@ module Language
cfg = cfg_file.read
framework = "Frameworks/Python.framework/Versions"
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
end
end

View File

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

View File

@ -2,6 +2,7 @@
# frozen_string_literal: true
require "livecheck/constants"
require "livecheck/options"
require "cask/cask"
# The {Livecheck} class implements the DSL methods used in a formula's, cask's
@ -15,6 +16,10 @@ require "cask/cask"
class Livecheck
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.
# `No longer developed or maintained`).
sig { returns(T.nilable(String)) }
@ -24,13 +29,10 @@ class Livecheck
sig { returns(T.nilable(Proc)) }
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 }
def initialize(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_formula_name = T.let(nil, T.nilable(String))
@regex = T.let(nil, T.nilable(Regexp))
@ -40,7 +42,6 @@ class Livecheck
@strategy_block = T.let(nil, T.nilable(Proc))
@throttle = T.let(nil, T.nilable(Integer))
@url = T.let(nil, T.any(NilClass, String, Symbol))
@url_options = T.let(nil, T.nilable(T::Hash[Symbol, T.untyped]))
end
# Sets the `@referenced_cask_name` instance variable to the provided `String`
@ -170,15 +171,17 @@ class Livecheck
params(
# URL to check for version information.
url: T.any(String, Symbol),
post_form: T.nilable(T::Hash[T.any(String, Symbol), String]),
post_json: T.nilable(T::Hash[T.any(String, Symbol), String]),
homebrew_curl: T.nilable(T::Boolean),
post_form: T.nilable(T::Hash[Symbol, String]),
post_json: T.nilable(T::Hash[Symbol, String]),
).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
options = { post_form:, post_json: }.compact
@url_options = options if options.present?
@options.homebrew_curl = homebrew_curl unless homebrew_curl.nil?
@options.post_form = post_form unless post_form.nil?
@options.post_json = post_json unless post_json.nil?
case url
when nil
@ -190,6 +193,7 @@ class Livecheck
end
end
delegate url_options: :@options
delegate version: :@package_or_resource
delegate arch: :@package_or_resource
private :version, :arch
@ -198,6 +202,7 @@ class Livecheck
sig { returns(T::Hash[String, T.untyped]) }
def to_hash
{
"options" => @options.to_hash,
"cask" => @referenced_cask_name,
"formula" => @referenced_formula_name,
"regex" => @regex,
@ -206,7 +211,6 @@ class Livecheck
"strategy" => @strategy,
"throttle" => @throttle,
"url" => @url,
"url_options" => @url_options,
}
end
end

View File

@ -13,6 +13,9 @@ module Homebrew
# command. These methods print the requested livecheck information
# for formulae.
module Livecheck
NO_CURRENT_VERSION_MSG = "Unable to identify current version"
NO_VERSIONS_MSG = "Unable to get versions"
UNSTABLE_VERSION_KEYWORDS = T.let(%w[
alpha
beta
@ -25,19 +28,17 @@ module Homebrew
].freeze, T::Array[String])
private_constant :UNSTABLE_VERSION_KEYWORDS
sig { returns(T::Hash[T::Class[T.anything], String]) }
private_class_method def self.livecheck_strategy_names
return T.must(@livecheck_strategy_names) if defined?(@livecheck_strategy_names)
# Cache demodulized strategy names, to avoid repeating this work
@livecheck_strategy_names = T.let({}, T.nilable(T::Hash[T::Class[T.anything], String]))
Strategy.constants.sort.each do |const_symbol|
constant = Strategy.const_get(const_symbol)
next unless constant.is_a?(Class)
T.must(@livecheck_strategy_names)[constant] = Utils.demodulize(T.must(constant.name))
sig { params(strategy_class: T::Class[Strategic]).returns(String) }
private_class_method def self.livecheck_strategy_names(strategy_class)
@livecheck_strategy_names ||= T.let({}, T.nilable(T::Hash[T::Class[Strategic], String]))
@livecheck_strategy_names[strategy_class] ||= Utils.demodulize(strategy_class.name)
end
T.must(@livecheck_strategy_names).freeze
sig { params(strategy_class: T::Class[Strategic]).returns(T::Array[Symbol]) }
private_class_method def self.livecheck_find_versions_parameters(strategy_class)
@livecheck_find_versions_parameters ||= T.let({}, T.nilable(T::Hash[T::Class[Strategic], T::Array[Symbol]]))
@livecheck_find_versions_parameters[strategy_class] ||=
T::Utils.signature_for_method(strategy_class.method(:find_versions)).parameters.map(&:second)
end
# Uses `formulae_and_casks_to_check` to identify taps in use other than
@ -249,13 +250,20 @@ module Homebrew
# comparison.
current = if formula
if formula.head_only?
Version.new(formula.any_installed_version.version.commit)
else
T.must(formula.stable).version
formula_commit = formula.any_installed_version&.version&.commit
Version.new(formula_commit) if formula_commit
elsif (stable = formula.stable)
stable.version
end
else
Version.new(formula_or_cask.version)
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 = LivecheckVersion.create(formula_or_cask, current)
@ -289,7 +297,7 @@ module Homebrew
verbose:,
)
if res_version_info.empty?
status_hash(resource, "error", ["Unable to get versions"], verbose:)
status_hash(resource, "error", [NO_VERSIONS_MSG], verbose:)
else
res_version_info
end
@ -299,13 +307,12 @@ module Homebrew
end
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 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:)
if check_for_resources
unless verbose
@ -613,8 +620,9 @@ module Homebrew
livecheck = 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_options = livecheck.url_options || referenced_livecheck&.url_options
livecheck_regex = livecheck.regex || referenced_livecheck&.regex
livecheck_strategy = livecheck.strategy || referenced_livecheck&.strategy
livecheck_strategy_block = livecheck.strategy_block || referenced_livecheck&.strategy_block
@ -659,7 +667,9 @@ module Homebrew
block_provided: livecheck_strategy_block.present?,
)
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)
url = strategy.preprocess_url(url)
@ -674,10 +684,10 @@ module Homebrew
elsif original_url.present? && original_url != "None"
puts "URL: #{original_url}"
end
puts "URL Options: #{livecheck_url_options}" if livecheck_url_options.present?
puts "URL (processed): #{url}" if url != original_url
puts "URL Options: #{livecheck_url_options}" if livecheck_url_options.present?
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
puts "Strategy: #{strategy_name}" if strategy.present?
puts "Regex: #{livecheck_regex.inspect}" if livecheck_regex.present?
@ -695,23 +705,25 @@ module Homebrew
next if strategy.blank?
homebrew_curl = case strategy_name
if (livecheck_homebrew_curl = livecheck_options.homebrew_curl).nil?
case strategy_name
when "PageMatch", "HeaderMatch"
use_homebrew_curl?(referenced_package, url)
if (homebrew_curl = use_homebrew_curl?(referenced_package, url))
livecheck_options = livecheck_options.merge({ homebrew_curl: })
livecheck_homebrew_curl = homebrew_curl
end
puts "Homebrew curl?: Yes" if debug && homebrew_curl.present?
end
end
puts "Homebrew curl?: #{livecheck_homebrew_curl ? "Yes" : "No"}" if debug && !livecheck_homebrew_curl.nil?
strategy_args = {
regex: livecheck_regex,
url_options: livecheck_url_options,
homebrew_curl:,
}
# TODO: Set `cask`/`url` args based on the presence of the keyword arg
# in the strategy's `#find_versions` method once we figure out why
# `strategy.method(:find_versions).parameters` isn't working as
# expected.
strategy_args[:cask] = cask if strategy_name == "ExtractPlist" && cask.present?
strategy_args[:url] = url
# Only use arguments that the strategy's `#find_versions` method
# supports
find_versions_parameters = livecheck_find_versions_parameters(strategy)
strategy_args = {}
strategy_args[:cask] = cask if find_versions_parameters.include?(:cask)
strategy_args[:url] = url if find_versions_parameters.include?(:url)
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)
@ -811,10 +823,9 @@ module Homebrew
end
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][:homebrew_curl] = homebrew_curl if homebrew_curl.present?
end
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][:cached] = true if strategy_data[:cached] == true
version_info[:meta][:throttle] = livecheck_throttle if livecheck_throttle
@ -858,9 +869,10 @@ module Homebrew
resource_version_info = {}
livecheck = resource.livecheck
livecheck_options = livecheck.options
livecheck_url_options = livecheck_options.url_options.compact
livecheck_reference = livecheck.formula
livecheck_url = livecheck.url
livecheck_url_options = livecheck.url_options
livecheck_regex = livecheck.regex
livecheck_strategy = livecheck.strategy
livecheck_strategy_block = livecheck.strategy_block
@ -883,7 +895,9 @@ module Homebrew
block_provided: livecheck_strategy_block.present?,
)
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)
url = strategy.preprocess_url(url)
@ -898,10 +912,10 @@ module Homebrew
elsif original_url.present? && original_url != "None"
puts "URL: #{original_url}"
end
puts "URL Options: #{livecheck_url_options}" if livecheck_url_options.present?
puts "URL (processed): #{url}" if url != original_url
puts "URL Options: #{livecheck_url_options}" if livecheck_url_options.present?
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
puts "Strategy: #{strategy_name}" if strategy.present?
puts "Regex: #{livecheck_regex.inspect}" if livecheck_regex.present?
@ -922,16 +936,22 @@ module Homebrew
puts if debug && 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
match_version_map = { formula_latest => Version.new(formula_latest) }
cached = true
else
strategy_args = {
url:,
regex: livecheck_regex,
url_options: livecheck_url_options,
homebrew_curl: false,
}.compact
# Only use arguments that the strategy's `#find_versions` method
# supports
find_versions_parameters = livecheck_find_versions_parameters(strategy)
strategy_args = {}
strategy_args[:url] = url if find_versions_parameters.include?(:url)
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)
match_version_map = strategy_data[:matches]
@ -986,7 +1006,7 @@ module Homebrew
res_current = T.must(resource.version)
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_newer_than_upstream = res_current > res_latest
@ -1023,7 +1043,7 @@ module Homebrew
end
resource_version_info[:meta][:strategy] = strategy_name if strategy.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
resource_version_info[:meta][:regex] = regex.inspect if regex.present?
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
require "utils/curl"
require "livecheck/options"
module Homebrew
module Livecheck
@ -172,8 +173,8 @@ module Homebrew
# @return [Array]
sig {
params(
post_form: T.nilable(T::Hash[T.any(String, Symbol), String]),
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::Array[String])
}
def self.post_args(post_form: nil, post_json: nil)
@ -193,23 +194,16 @@ module Homebrew
# collected into an array of hashes.
#
# @param url [String] the URL to fetch
# @param url_options [Hash] options to modify curl behavior
# @param homebrew_curl [Boolean] whether to use brewed curl with the URL
# @param options [Options] options to modify behavior
# @return [Array]
sig {
params(
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)
sig { params(url: String, options: Options).returns(T::Array[T::Hash[String, String]]) }
def self.page_headers(url, options: Options.new)
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(
post_form: url_options[:post_form],
post_json: url_options[:post_json],
post_form: options.post_form,
post_json: options.post_json,
)]
end
@ -221,7 +215,7 @@ module Homebrew
MAX_REDIRECTIONS.to_s,
url,
wanted_headers: ["location", "content-disposition"],
use_homebrew_curl: homebrew_curl,
use_homebrew_curl: options.homebrew_curl || false,
user_agent:,
**DEFAULT_CURL_OPTIONS,
)
@ -242,21 +236,14 @@ module Homebrew
# array with the error message instead.
#
# @param url [String] the URL of the content to check
# @param url_options [Hash] options to modify curl behavior
# @param homebrew_curl [Boolean] whether to use brewed curl with the URL
# @param options [Options] options to modify behavior
# @return [Hash]
sig {
params(
url: String,
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?
sig { params(url: String, options: Options).returns(T::Hash[Symbol, T.untyped]) }
def self.page_content(url, options: Options.new)
if options.post_form || options.post_json
curl_post_args = ["--request", "POST", *post_args(
post_form: url_options[:post_form],
post_json: url_options[:post_json],
post_form: options.post_form,
post_json: options.post_json,
)]
end
@ -266,7 +253,9 @@ module Homebrew
*curl_post_args,
*PAGE_CONTENT_CURL_ARGS, url,
**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:
)
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