Merge branch 'master' into mlh-outdated-packages

This commit is contained in:
Baffour Adu Boampong 2020-08-10 21:21:09 +00:00 committed by GitHub
commit ad2809b615
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
340 changed files with 6810 additions and 4684 deletions

View File

@ -15,7 +15,7 @@ jobs:
version: ["16.04", "18.04", "20.04"] version: ["16.04", "18.04", "20.04"]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@master uses: actions/checkout@main
with: with:
fetch-depth: 0 fetch-depth: 0
persist-credentials: false persist-credentials: false
@ -23,8 +23,8 @@ jobs:
run: git fetch origin master run: git fetch origin master
- name: Build Docker image - name: Build Docker image
run: docker build -t brew --build-arg=version=${{matrix.version}} . run: docker build -t brew --build-arg=version=${{matrix.version}} .
- name: Run brew test-bot - name: Run brew test-bot --only-setup
run: docker run --rm brew brew test-bot run: docker run --rm brew brew test-bot --only-setup
- name: Deploy the tagged Docker image to GitHub - name: Deploy the tagged Docker image to GitHub
if: startsWith(github.ref, 'refs/tags/') if: startsWith(github.ref, 'refs/tags/')
run: | run: |

View File

@ -8,6 +8,9 @@ on:
- Library/Homebrew/extend/os/diagnostic.rb - Library/Homebrew/extend/os/diagnostic.rb
- Library/Homebrew/extend/os/mac/diagnostic.rb - Library/Homebrew/extend/os/mac/diagnostic.rb
- Library/Homebrew/os/mac/xcode.rb - Library/Homebrew/os/mac/xcode.rb
env:
HOMEBREW_DEVELOPER: 1
HOMEBREW_NO_AUTO_UPDATE: 1
jobs: jobs:
tests: tests:
strategy: strategy:
@ -17,21 +20,10 @@ jobs:
runs-on: ${{ matrix.version }} runs-on: ${{ matrix.version }}
env: env:
PATH: '/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin' PATH: '/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin'
HOMEBREW_DEVELOPER: 1
HOMEBREW_NO_ANALYTICS: 1
HOMEBREW_NO_AUTO_UPDATE: 1
steps: steps:
- name: Update Homebrew - name: Set up Homebrew
run: brew update-reset id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
- name: Set up Git repository
run: |
cd $(brew --repo)
git clean -ffdx
git fetch --prune --force origin master
git fetch --prune --force origin ${{github.sha}}
git checkout --force ${{github.sha}}
git log -1
- name: Run brew test-bot --only-cleanup-before - name: Run brew test-bot --only-cleanup-before
run: brew test-bot --only-cleanup-before run: brew test-bot --only-cleanup-before
@ -42,9 +34,3 @@ jobs:
- name: Run brew test-bot --only-cleanup-after - name: Run brew test-bot --only-cleanup-after
if: always() if: always()
run: brew test-bot --only-cleanup-after run: brew test-bot --only-cleanup-after
- name: Cleanup
if: always()
run: |
find .
rm -rf *

View File

@ -1,30 +1,24 @@
name: Update license data name: Update license data
on: on:
pull_request:
paths:
- .github/workflows/spdx.yml
schedule: schedule:
- cron: '0 */12 * * *' - cron: '0 */12 * * *'
jobs: jobs:
spdx: spdx:
if: github.repository == 'Homebrew/brew'
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
HOMEBREW_NO_ANALYTICS: 1
HOMEBREW_NO_AUTO_UPDATE: 1
steps: steps:
- name: Checkout - name: Set up Homebrew
uses: actions/checkout@v2 id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
- name: Configure Git - name: Configure Git user
uses: Homebrew/actions/git-user-config@master uses: Homebrew/actions/git-user-config@master
with: with:
username: BrewTestBot username: BrewTestBot
- name: Setup Homebrew
run: |
HOMEBREW_REPOSITORY="$(brew --repo)"
rm -rf "$HOMEBREW_REPOSITORY"
ln -s "$GITHUB_WORKSPACE" "$HOMEBREW_REPOSITORY"
- name: Update license data - name: Update license data
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -4,7 +4,7 @@ on:
branches: master branches: master
pull_request: [] pull_request: []
env: env:
HOMEBREW_GITHUB_ACTIONS: 1 HOMEBREW_DEVELOPER: 1
HOMEBREW_NO_AUTO_UPDATE: 1 HOMEBREW_NO_AUTO_UPDATE: 1
jobs: jobs:
tests: tests:
@ -16,72 +16,29 @@ jobs:
steps: steps:
- name: Set up Homebrew - name: Set up Homebrew
id: set-up-homebrew id: set-up-homebrew
run: | uses: Homebrew/actions/setup-homebrew@master
if which brew &>/dev/null; then
HOMEBREW_PREFIX="$(brew --prefix)"
HOMEBREW_REPOSITORY="$HOMEBREW_PREFIX/Homebrew"
else
HOMEBREW_PREFIX=/home/linuxbrew/.linuxbrew
HOMEBREW_REPOSITORY="$HOMEBREW_PREFIX/Homebrew"
sudo mkdir -p "$HOMEBREW_REPOSITORY/Library/Taps"
git -C "$HOMEBREW_REPOSITORY" init
git -C "$HOMEBREW_REPOSITORY" remote add origin "https://github.com/$GITHUB_REPOSITORY"
cd "$HOMEBREW_PREFIX" - name: Configure Git user
sudo mkdir -p bin etc include lib opt sbin share var/homebrew/linked Cellar uses: Homebrew/actions/git-user-config@master
sudo ln -sf ../Homebrew/bin/brew "$HOMEBREW_PREFIX/bin/"
cd -
fi
export PATH="$HOMEBREW_PREFIX/bin:$PATH"
echo "::add-path::$HOMEBREW_PREFIX/bin"
cd "$HOMEBREW_REPOSITORY"
rm -rf "$GITHUB_WORKSPACE"
ln -s "$HOMEBREW_REPOSITORY" "$GITHUB_WORKSPACE"
git fetch --tags origin "${{github.sha}}"
git checkout --force -B master FETCH_HEAD
brew update-reset Library/Taps/homebrew/homebrew-core
cd -
GEMS_HASH=$(shasum -a 256 "$HOMEBREW_REPOSITORY/Library/Homebrew/Gemfile.lock" | cut -f1 -d' ')
echo "::set-output name=gems-hash::$GEMS_HASH"
if [ "$RUNNER_OS" = "Linux" ]; then
sudo chown -R "$USER" "$HOMEBREW_PREFIX"
fi
- name: Set up Ruby
if: matrix.os == 'ubuntu-latest'
uses: actions/setup-ruby@main
with: with:
ruby-version: '2.6' username: BrewTestBot
- name: Run brew test-bot --only-cleanup-before
run: brew test-bot --only-cleanup-before
- name: Run brew config - name: Run brew config
run: brew config run: brew config
- name: Run brew doctor - name: Run brew doctor
run: | run: brew doctor
# minimally fix brew doctor failures (a full clean takes ~5m)
if [ "$RUNNER_OS" = "Linux" ]; then
sudo rm -rf /usr/local/include/node/
else
# link old formulae
brew link --overwrite --force gettext python@3.8
# remove deleted formula
brew uninstall --force python@2
fi
brew doctor
- name: Cache Bundler RubyGems - name: Cache Bundler RubyGems
id: cache id: cache
uses: actions/cache@v1 uses: actions/cache@main
with: with:
path: Library/Homebrew/vendor/bundle/ruby/ path: ${{ steps.set-up-homebrew.outputs.gems-path }}
key: ${{ runner.os }}-rubygems-${{ steps.set-up-homebrew.outputs.gems-hash }} key: ${{ runner.os }}-rubygems-${{ steps.set-up-homebrew.outputs.gems-hash }}
restore-keys: | restore-keys: ${{ runner.os }}-rubygems-
${{ runner.os }}-rubygems-
- name: Install Bundler RubyGems - name: Install Bundler RubyGems
if: steps.cache.outputs.cache-hit != 'true' if: steps.cache.outputs.cache-hit != 'true'
@ -90,31 +47,19 @@ jobs:
- name: Check for uncommitted RubyGems - name: Check for uncommitted RubyGems
run: git diff --stat --exit-code Library/Homebrew/vendor/bundle/ruby run: git diff --stat --exit-code Library/Homebrew/vendor/bundle/ruby
- name: Install taps - name: Set up Homebrew official command taps
run: | run: |
# Install taps needed for 'brew tests' and 'brew man' # Setup taps needed for 'brew tests' and 'brew man'
cd "$(brew --repo)"
brew tap homebrew/bundle brew tap homebrew/bundle
brew update-reset Library/Taps/homebrew/homebrew-bundle cd "$(brew --repo)"
brew tap homebrew/services
brew update-reset Library/Taps/homebrew/homebrew-services
brew tap homebrew/test-bot
brew update-reset Library/Taps/homebrew/homebrew-test-bot
if [ "$RUNNER_OS" = "macOS" ]; then if [ "$RUNNER_OS" = "macOS" ]; then
brew tap homebrew/cask brew update-reset Library/Taps/homebrew/homebrew-cask Library/Taps/homebrew/homebrew-services
brew update-reset Library/Taps/homebrew/homebrew-cask
# don't care about `brew cask style` here.
brew untap adoptopenjdk/openjdk
# don't care about `brew audit` here.
brew untap mongodb/brew
else else
# Fix permissions for 'brew tests' brew update-reset Library/Taps/homebrew/homebrew-services
sudo chmod -R g-w,o-w /home/linuxbrew /home/runner /opt
fi fi
- name: Run brew style - name: Run brew style on Homebrew/brew
run: brew style --display-cop-names run: brew style --display-cop-names
- name: Run brew man - name: Run brew man
@ -123,12 +68,9 @@ jobs:
- name: Run brew tests - name: Run brew tests
run: | run: |
# brew tests doesn't like world writable directories # brew tests doesn't like world writable directories
umask 022 if [[ "$RUNNER_OS" = "Linux" ]]; then
sudo chmod -R g-w,o-w /home/linuxbrew/.linuxbrew/Homebrew
# set variables for coverage reporting fi
export HOMEBREW_CI_BUILD_NUMBER="$GITHUB_REF"
export HOMEBREW_CI_BRANCH="$HEAD_GITHUB_REF"
export HOMEBREW_GITHUB_REPOSITORY="$GITHUB_REPOSITORY"
# don't bother running all tests on both platforms (for speed) # don't bother running all tests on both platforms (for speed)
if [ "$RUNNER_OS" = "Linux" ]; then if [ "$RUNNER_OS" = "Linux" ]; then
@ -149,8 +91,6 @@ jobs:
- name: Run brew update-tests - name: Run brew update-tests
run: | run: |
git config --global user.name "BrewTestBot"
git config --global user.email "homebrew-test-bot@lists.sfconservancy.org"
brew update-test brew update-test
brew update-test --to-tag brew update-test --to-tag
brew update-test --commit=HEAD brew update-test --commit=HEAD
@ -160,16 +100,27 @@ jobs:
run: brew readall --aliases run: brew readall --aliases
- name: Run brew style on homebrew-core - name: Run brew style on homebrew-core
if: matrix.os == 'macOS-latest'
run: brew style --display-cop-names homebrew/core
- name: Run brew style on linuxbrew-core
if: matrix.os == 'ubuntu-latest'
run: brew style --display-cop-names homebrew/core run: brew style --display-cop-names homebrew/core
- name: Run brew style on official taps - name: Run brew style on official taps
run: brew style --display-cop-names homebrew/bundle homebrew/services homebrew/test-bot run: brew style --display-cop-names homebrew/bundle homebrew/services homebrew/test-bot
- name: Run brew cask style - name: Run brew cask style on all taps
if: matrix.os == 'macOS-latest' if: matrix.os == 'macOS-latest'
run: brew cask style run: |
brew tap homebrew/cask
brew tap homebrew/cask-drivers
brew tap homebrew/cask-fonts
brew tap homebrew/cask-versions
- name: Run brew audit brew cask style
- name: Run brew audit --skip-style on all taps
run: brew audit --skip-style run: brew audit --skip-style
- name: Run vale for docs linting - name: Run vale for docs linting

View File

@ -161,7 +161,6 @@ Lint/AmbiguousRegexpLiteral:
Lint/ParenthesesAsGroupedExpression: Lint/ParenthesesAsGroupedExpression:
Enabled: false Enabled: false
# most metrics don't make sense to apply for formulae/taps # most metrics don't make sense to apply for formulae/taps
Metrics/AbcSize: Metrics/AbcSize:
Enabled: false Enabled: false

View File

@ -1,5 +1,9 @@
inherit_from: ./Homebrew/.rubocop.yml inherit_from: ./Homebrew/.rubocop.yml
Cask/Desc:
Description: 'Ensure that the desc stanza conforms to various content and style checks.'
Enabled: true
Cask/HomepageMatchesUrl: Cask/HomepageMatchesUrl:
Description: 'Ensure that the homepage and url match, otherwise add a comment. More info at https://github.com/Homebrew/homebrew-cask/blob/HEAD/doc/cask_language_reference/stanzas/url.md#when-url-and-homepage-hostnames-differ-add-a-comment' Description: 'Ensure that the homepage and url match, otherwise add a comment. More info at https://github.com/Homebrew/homebrew-cask/blob/HEAD/doc/cask_language_reference/stanzas/url.md#when-url-and-homepage-hostnames-differ-add-a-comment'
Enabled: true Enabled: true
@ -13,13 +17,15 @@ Cask/NoDslVersion:
Enabled: true Enabled: true
Cask/StanzaGrouping: Cask/StanzaGrouping:
Description: 'Ensure that cask stanzas are grouped correctly. More info at https://github.com/Homebrew/homebrew-cask/blob/HEAD/CONTRIBUTING.md#stanza-order' Description: 'Ensure that cask stanzas are grouped correctly. More info at https://github.com/Homebrew/homebrew-cask/blob/HEAD/doc/cask_language_reference/readme.md#stanza-order'
Enabled: true Enabled: true
Cask/StanzaOrder: Cask/StanzaOrder:
Description: 'Ensure that cask stanzas are sorted correctly. More info at https://github.com/Homebrew/homebrew-cask/blob/HEAD/CONTRIBUTING.md#stanza-order' Description: 'Ensure that cask stanzas are sorted correctly. More info at https://github.com/Homebrew/homebrew-cask/blob/HEAD/doc/cask_language_reference/readme.md#stanza-order'
Enabled: true Enabled: true
# don't want this for casks but re-enabled for Library/Homebrew # don't want these for casks but re-enabled for Library/Homebrew
Style/FrozenStringLiteralComment: Style/FrozenStringLiteralComment:
Enabled: false Enabled: false
Metrics/BlockLength:
Enabled: false

View File

@ -37,30 +37,33 @@ Lint/ParenthesesAsGroupedExpression:
# TODO: try to bring down all metrics maximums # TODO: try to bring down all metrics maximums
Metrics/AbcSize: Metrics/AbcSize:
Enabled: true Enabled: true
Max: 275 Max: 250
Metrics/BlockLength: Metrics/BlockLength:
Enabled: true Enabled: true
Max: 1100 Max: 100
Exclude: Exclude:
- 'test/formula_spec.rb' - 'test/**/*'
Metrics/BlockNesting: Metrics/BlockNesting:
Enabled: true Enabled: true
Max: 5 Max: 5
Metrics/ClassLength: Metrics/ClassLength:
Enabled: true Enabled: true
Max: 1400 Max: 800
Exclude:
- 'formula.rb'
- 'formula_installer.rb'
Metrics/CyclomaticComplexity: Metrics/CyclomaticComplexity:
Enabled: true Enabled: true
Max: 85 Max: 80
Metrics/MethodLength: Metrics/MethodLength:
Enabled: true Enabled: true
Max: 300 Max: 260
Metrics/ModuleLength: Metrics/ModuleLength:
Enabled: true Enabled: true
Max: 550 Max: 600
Metrics/PerceivedComplexity: Metrics/PerceivedComplexity:
Enabled: true Enabled: true
Max: 100 Max: 80
# we won't change backward compatible predicate names # we won't change backward compatible predicate names
Naming/PredicateName: Naming/PredicateName:

View File

@ -13,11 +13,9 @@ gem "rspec-retry", require: false
gem "rspec-wait", require: false gem "rspec-wait", require: false
gem "rubocop" gem "rubocop"
gem "simplecov", require: false gem "simplecov", require: false
if ENV["HOMEBREW_SORBET"] gem "sorbet"
gem "sorbet", "0.5.5823" gem "sorbet-runtime"
gem "sorbet-runtime", "0.5.5823" gem "tapioca"
gem "tapioca"
end
# vendored gems # vendored gems
gem "activesupport" gem "activesupport"

View File

@ -10,12 +10,15 @@ GEM
ast (2.4.1) ast (2.4.1)
bindata (2.4.8) bindata (2.4.8)
byebug (11.1.3) byebug (11.1.3)
codecov (0.2.3) codecov (0.2.5)
colorize colorize
json json
simplecov simplecov
coderay (1.1.3)
colorize (0.8.1) colorize (0.8.1)
concurrent-ruby (1.1.6) commander (4.5.2)
highline (~> 2.0.0)
concurrent-ruby (1.1.7)
connection_pool (2.2.3) connection_pool (2.2.3)
diff-lcs (1.4.4) diff-lcs (1.4.4)
docile (1.3.2) docile (1.3.2)
@ -23,6 +26,7 @@ GEM
unf (>= 0.0.5, < 1.0.0) unf (>= 0.0.5, < 1.0.0)
elftools (1.1.2) elftools (1.1.2)
bindata (~> 2) bindata (~> 2)
highline (2.0.3)
hpricot (0.8.6) hpricot (0.8.6)
http-cookie (1.0.3) http-cookie (1.0.3)
domain_name (~> 0.5) domain_name (~> 0.5)
@ -38,6 +42,7 @@ GEM
nokogiri (~> 1.6) nokogiri (~> 1.6)
ntlm-http (~> 0.1, >= 0.1.1) ntlm-http (~> 0.1, >= 0.1.1)
webrobots (>= 0.0.9, < 0.2) webrobots (>= 0.0.9, < 0.2)
method_source (1.0.0)
mime-types (3.3.1) mime-types (3.3.1)
mime-types-data (~> 3.2015) mime-types-data (~> 3.2015)
mime-types-data (3.2020.0512) mime-types-data (3.2020.0512)
@ -53,13 +58,21 @@ GEM
parallel (1.19.2) parallel (1.19.2)
parallel_tests (3.1.0) parallel_tests (3.1.0)
parallel parallel
parlour (4.0.1)
commander (~> 4.5)
parser
rainbow (~> 3.0)
sorbet-runtime (>= 0.5)
parser (2.7.1.4) parser (2.7.1.4)
ast (~> 2.4.1) ast (~> 2.4.1)
patchelf (1.2.0) patchelf (1.2.0)
elftools (~> 1.1) elftools (~> 1.1)
plist (3.5.0) plist (3.5.0)
pry (0.13.1)
coderay (~> 1.1)
method_source (~> 1.0)
rainbow (3.0.0) rainbow (3.0.0)
rdiscount (2.2.0.1) rdiscount (2.2.0.2)
regexp_parser (1.7.1) regexp_parser (1.7.1)
rexml (3.2.4) rexml (3.2.4)
ronn (0.7.3) ronn (0.7.3)
@ -107,6 +120,17 @@ GEM
docile (~> 1.1) docile (~> 1.1)
simplecov-html (~> 0.11) simplecov-html (~> 0.11)
simplecov-html (0.12.2) simplecov-html (0.12.2)
sorbet (0.5.5866)
sorbet-static (= 0.5.5866)
sorbet-runtime (0.5.5866)
sorbet-static (0.5.5866-universal-darwin-14)
tapioca (0.4.1)
parlour (>= 2.1.0)
pry (>= 0.12.2)
sorbet-runtime
sorbet-static (>= 0.4.4471)
thor (>= 0.19.2)
thor (1.0.1)
thread_safe (0.3.6) thread_safe (0.3.6)
tzinfo (1.2.7) tzinfo (1.2.7)
thread_safe (~> 0.1) thread_safe (~> 0.1)
@ -139,6 +163,9 @@ DEPENDENCIES
rubocop-rspec rubocop-rspec
ruby-macho ruby-macho
simplecov simplecov
sorbet
sorbet-runtime
tapioca
BUNDLED WITH BUNDLED WITH
1.17.3 1.17.2

View File

@ -4,6 +4,8 @@ require "utils/curl"
require "json" require "json"
class Bintray class Bintray
include Context
API_URL = "https://api.bintray.com" API_URL = "https://api.bintray.com"
class Error < RuntimeError class Error < RuntimeError
@ -32,8 +34,8 @@ class Bintray
end end
curl(*args, url, curl(*args, url,
show_output: Homebrew.args.verbose?, show_output: verbose?,
secrets: @bintray_key) secrets: key)
end end
def upload(local_file, repo:, package:, version:, remote_file:, sha256: nil) def upload(local_file, repo:, package:, version:, remote_file:, sha256: nil)

View File

@ -1,160 +0,0 @@
# frozen_string_literal: true
require "utils"
require "formula_info"
class BottlePublisher
def initialize(tap, changed_formulae_names, bintray_org, no_publish, warn_on_publish_failure)
@tap = tap
@changed_formulae_names = changed_formulae_names
@no_publish = no_publish
@bintray_org = bintray_org
@warn_on_publish_failure = warn_on_publish_failure
end
def publish_and_check_bottles
# Formulae with affected bottles that were published
bintray_published_formulae = []
# Publish bottles on Bintray
unless @no_publish
published = publish_changed_formula_bottles
bintray_published_formulae.concat(published)
end
# Verify bintray publishing after all patches have been applied
bintray_published_formulae.uniq!
verify_bintray_published(bintray_published_formulae)
end
def publish_changed_formula_bottles
raise "Need to load formulae to publish them!" if Homebrew::EnvConfig.disable_load_formula?
published = []
bintray_creds = { user: Homebrew::EnvConfig.bintray_user, key: Homebrew::EnvConfig.bintray_key }
if bintray_creds[:user] && bintray_creds[:key]
@changed_formulae_names.each do |name|
f = Formula[name]
next if f.bottle_unneeded? || f.bottle_disabled?
bintray_org = @bintray_org || @tap.user.downcase
next unless publish_bottle_file_on_bintray(f, bintray_org, bintray_creds)
published << f.full_name
end
else
opoo "You must set HOMEBREW_BINTRAY_USER and HOMEBREW_BINTRAY_KEY to add or update bottles on Bintray!"
end
published
end
# Publishes the current bottle files for a given formula to Bintray
def publish_bottle_file_on_bintray(f, bintray_org, creds)
repo = Utils::Bottles::Bintray.repository(f.tap)
package = Utils::Bottles::Bintray.package(f.name)
info = FormulaInfo.lookup(f.full_name)
raise "Failed publishing bottle: failed reading formula info for #{f.full_name}" if info.nil?
unless info.bottle_info_any
opoo "No bottle defined in formula #{package}"
return false
end
version = info.pkg_version
ohai "Publishing on Bintray: #{package} #{version}"
curl "--write-out", '\n', "--silent", "--fail",
"--user", "#{creds[:user]}:#{creds[:key]}", "--request", "POST",
"--header", "Content-Type: application/json",
"--data", '{"publish_wait_for_secs": 0}',
"https://api.bintray.com/content/#{bintray_org}/#{repo}/#{package}/#{version}/publish"
true
rescue => e
raise unless @warn_on_publish_failure
onoe e
false
end
# Verifies that formulae have been published on Bintray by downloading a bottle file
# for each one. Blocks until the published files are available.
# Raises an error if the verification fails.
# This does not currently work for `brew pull`, because it may have cached the old
# version of a formula.
def verify_bintray_published(formulae_names)
return if formulae_names.empty?
raise "Need to load formulae to verify their publication!" if Homebrew::EnvConfig.disable_load_formula?
ohai "Verifying bottles published on Bintray"
formulae = formulae_names.map { |n| Formula[n] }
max_retries = 300 # shared among all bottles
poll_retry_delay_seconds = 2
HOMEBREW_CACHE.cd do
formulae.each do |f|
retry_count = 0
wrote_dots = false
# Choose arbitrary bottle just to get the host/port for Bintray right
jinfo = FormulaInfo.lookup(f.full_name)
unless jinfo
opoo "Cannot publish bottle: Failed reading info for formula #{f.full_name}"
next
end
bottle_info = jinfo.bottle_info_any
unless bottle_info
opoo "No bottle defined in formula #{f.full_name}"
next
end
# Poll for publication completion using a quick partial HEAD, to avoid spurious error messages
# 401 error is normal while file is still in async publishing process
url = URI(bottle_info["url"])
puts "Verifying bottle: #{File.basename(url.path)}"
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
retry_count = 0
http.start do
loop do
req = Net::HTTP::Head.new url
req.initialize_http_header "User-Agent" => HOMEBREW_USER_AGENT_RUBY
res = http.request req
break if res.is_a?(Net::HTTPSuccess) || res.code == "302"
unless res.is_a?(Net::HTTPClientError)
raise "Failed to find published #{f} bottle at #{url} (#{res.code} #{res.message})!"
end
raise "Failed to find published #{f} bottle at #{url}!" if retry_count >= max_retries
print(wrote_dots ? "." : "Waiting on Bintray.")
wrote_dots = true
sleep poll_retry_delay_seconds
retry_count += 1
end
end
# Actual download and verification
# We do a retry on this, too, because sometimes the external curl will fail even
# when the prior HEAD has succeeded.
puts "\n" if wrote_dots
filename = File.basename(url.path)
curl_retry_delay_seconds = 4
max_curl_retries = 1
retry_count = 0
# We're in the cache; make sure to force re-download
loop do
curl_download url, to: filename
break
rescue
raise "Failed to download #{f} bottle from #{url}!" if retry_count >= max_curl_retries
puts "curl download failed; retrying in #{curl_retry_delay_seconds} sec"
sleep curl_retry_delay_seconds
curl_retry_delay_seconds *= 2
retry_count += 1
end
checksum = Checksum.new(:sha256, bottle_info["sha256"])
Pathname.new(filename).verify_checksum(checksum)
end
end
end
end

View File

@ -59,7 +59,9 @@ begin
ARGV.delete_at(help_cmd_index) if help_cmd_index ARGV.delete_at(help_cmd_index) if help_cmd_index
Homebrew.args = Homebrew::CLI::Parser.new.parse(ARGV.dup.freeze, ignore_invalid_options: true) args = Homebrew::CLI::Parser.new.parse(ARGV.dup.freeze, ignore_invalid_options: true)
Homebrew.args = args
Context.current = args.context
path = PATH.new(ENV["PATH"]) path = PATH.new(ENV["PATH"])
homebrew_path = PATH.new(ENV["HOMEBREW_PATH"]) homebrew_path = PATH.new(ENV["HOMEBREW_PATH"])
@ -102,8 +104,8 @@ begin
# - if cmd is Cask, let Cask handle the help command instead # - if cmd is Cask, let Cask handle the help command instead
if (empty_argv || help_flag) && cmd != "cask" if (empty_argv || help_flag) && cmd != "cask"
require "help" require "help"
Homebrew::Help.help cmd, empty_argv: empty_argv Homebrew::Help.help cmd, remaining_args: args.remaining, empty_argv: empty_argv
# `Homebrew.help` never returns, except for unknown commands. # `Homebrew::Help.help` never returns, except for unknown commands.
end end
if internal_cmd || Commands.external_ruby_v2_cmd_path(cmd) if internal_cmd || Commands.external_ruby_v2_cmd_path(cmd)
@ -138,17 +140,17 @@ begin
end end
rescue UsageError => e rescue UsageError => e
require "help" require "help"
Homebrew::Help.help cmd, usage_error: e.message Homebrew::Help.help cmd, remaining_args: args.remaining, usage_error: e.message
rescue SystemExit => e rescue SystemExit => e
onoe "Kernel.exit" if Homebrew.args.debug? && !e.success? onoe "Kernel.exit" if args.debug? && !e.success?
$stderr.puts e.backtrace if Homebrew.args.debug? $stderr.puts e.backtrace if args.debug?
raise raise
rescue Interrupt rescue Interrupt
$stderr.puts # seemingly a newline is typical $stderr.puts # seemingly a newline is typical
exit 130 exit 130
rescue BuildError => e rescue BuildError => e
Utils::Analytics.report_build_error(e) Utils::Analytics.report_build_error(e)
e.dump e.dump(verbose: args.verbose?)
if e.formula.head? || e.formula.deprecated? || e.formula.disabled? if e.formula.head? || e.formula.deprecated? || e.formula.disabled?
$stderr.puts <<~EOS $stderr.puts <<~EOS
@ -162,7 +164,7 @@ rescue RuntimeError, SystemCallError => e
raise if e.message.empty? raise if e.message.empty?
onoe e onoe e
$stderr.puts e.backtrace if Homebrew.args.debug? $stderr.puts e.backtrace if args.debug?
exit 1 exit 1
rescue MethodDeprecatedError => e rescue MethodDeprecatedError => e
@ -171,7 +173,7 @@ rescue MethodDeprecatedError => e
$stderr.puts "If reporting this issue please do so at (not Homebrew/brew or Homebrew/core):" $stderr.puts "If reporting this issue please do so at (not Homebrew/brew or Homebrew/core):"
$stderr.puts " #{Formatter.url(e.issues_url)}" $stderr.puts " #{Formatter.url(e.issues_url)}"
end end
$stderr.puts e.backtrace if Homebrew.args.debug? $stderr.puts e.backtrace if args.debug?
exit 1 exit 1
rescue Exception => e # rubocop:disable Lint/RescueException rescue Exception => e # rubocop:disable Lint/RescueException
onoe e onoe e

View File

@ -41,6 +41,7 @@ case "$*" in
--prefix) echo "$HOMEBREW_PREFIX"; exit 0 ;; --prefix) echo "$HOMEBREW_PREFIX"; exit 0 ;;
--cellar) echo "$HOMEBREW_CELLAR"; exit 0 ;; --cellar) echo "$HOMEBREW_CELLAR"; exit 0 ;;
--repository|--repo) echo "$HOMEBREW_REPOSITORY"; exit 0 ;; --repository|--repo) echo "$HOMEBREW_REPOSITORY"; exit 0 ;;
--caskroom) echo "$HOMEBREW_PREFIX/Caskroom"; exit 0 ;;
esac esac
# A depth of 1 means this command was directly invoked by a user. # A depth of 1 means this command was directly invoked by a user.
@ -283,11 +284,6 @@ export HOMEBREW_USER_AGENT
export HOMEBREW_USER_AGENT_CURL export HOMEBREW_USER_AGENT_CURL
export HOMEBREW_BOTTLE_DEFAULT_DOMAIN export HOMEBREW_BOTTLE_DEFAULT_DOMAIN
if [[ -n "$HOMEBREW_DEVELOPER" ]] && [[ -z "$HOMEBREW_NO_PATCHELF_RB" ]]
then
export HOMEBREW_PATCHELF_RB="1"
fi
if [[ -n "$HOMEBREW_MACOS" && -x "/usr/bin/xcode-select" ]] if [[ -n "$HOMEBREW_MACOS" && -x "/usr/bin/xcode-select" ]]
then then
XCODE_SELECT_PATH=$('/usr/bin/xcode-select' --print-path 2>/dev/null) XCODE_SELECT_PATH=$('/usr/bin/xcode-select' --print-path 2>/dev/null)

View File

@ -139,7 +139,11 @@ class Build
formula.update_head_version formula.update_head_version
formula.brew(fetch: false, keep_tmp: args.keep_tmp?, interactive: args.interactive?) do |_formula, _staging| formula.brew(
fetch: false,
keep_tmp: args.keep_tmp?,
interactive: args.interactive?,
) do
# For head builds, HOMEBREW_FORMULA_PREFIX should include the commit, # For head builds, HOMEBREW_FORMULA_PREFIX should include the commit,
# which is not known until after the formula has been staged. # which is not known until after the formula has been staged.
ENV["HOMEBREW_FORMULA_PREFIX"] = formula.prefix ENV["HOMEBREW_FORMULA_PREFIX"] = formula.prefix
@ -201,16 +205,15 @@ class Build
else else
raise raise
end end
Keg.new(path).optlink Keg.new(path).optlink(verbose: args.verbose?)
rescue rescue
raise "#{f.opt_prefix} not present or broken\nPlease reinstall #{f.full_name}. Sorry :(" raise "#{f.opt_prefix} not present or broken\nPlease reinstall #{f.full_name}. Sorry :("
end end
end end
begin begin
Homebrew.args = Homebrew::CLI::Parser.new.parse(ARGV.dup.freeze, ignore_invalid_options: true)
args = Homebrew.install_args.parse args = Homebrew.install_args.parse
Context.current = args.context
error_pipe = UNIXSocket.open(ENV["HOMEBREW_ERROR_PIPE"], &:recv_io) error_pipe = UNIXSocket.open(ENV["HOMEBREW_ERROR_PIPE"], &:recv_io)
error_pipe.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) error_pipe.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)

View File

@ -1,19 +1,26 @@
# frozen_string_literal: true # frozen_string_literal: true
require "hardware"
require "cask/artifact" require "cask/artifact"
require "cask/audit" require "cask/audit"
require "cask/auditor" require "cask/auditor"
require "cask/cache" require "cask/cache"
require "cask/cask"
require "cask/cask_loader" require "cask/cask_loader"
require "cask/cask"
require "cask/caskroom" require "cask/caskroom"
require "cask/cmd" require "cask/cmd"
require "cask/config"
require "cask/exceptions"
require "cask/denylist"
require "cask/download"
require "cask/dsl"
require "cask/exceptions" require "cask/exceptions"
require "cask/installer" require "cask/installer"
require "cask/macos" require "cask/macos"
require "cask/metadata"
require "cask/pkg" require "cask/pkg"
require "cask/quarantine"
require "cask/staged" require "cask/staged"
require "cask/topological_hash"
require "cask/url"
require "cask/utils" require "cask/utils"
require "cask/verify" require "cask/verify"

View File

@ -13,7 +13,7 @@ module Cask
attr_reader :cask, :commit_range, :download attr_reader :cask, :commit_range, :download
attr_predicate :appcast? attr_predicate :appcast?, :new_cask?, :strict?, :online?
def initialize(cask, appcast: false, download: false, quarantine: nil, def initialize(cask, appcast: false, download: false, quarantine: nil,
token_conflicts: false, online: false, strict: false, token_conflicts: false, online: false, strict: false,
@ -34,6 +34,7 @@ module Cask
check_required_stanzas check_required_stanzas
check_version check_version
check_sha256 check_sha256
check_desc
check_url check_url
check_generic_artifacts check_generic_artifacts
check_token_valid check_token_valid
@ -279,6 +280,14 @@ module Cask
end end
end end
def check_desc
return unless new_cask?
return if cask.desc.present?
add_warning "Cask should have a description. Please add a `desc` stanza."
end
def check_url def check_url
return unless cask.url return unless cask.url
@ -339,7 +348,7 @@ module Cask
end end
def check_token_valid def check_token_valid
return unless @strict return unless strict?
add_warning "cask token is not lowercase" if cask.token.downcase! add_warning "cask token is not lowercase" if cask.token.downcase!
@ -365,7 +374,7 @@ module Cask
end end
def check_token_bad_words def check_token_bad_words
return unless @strict return unless strict?
token = cask.token token = cask.token
@ -467,8 +476,8 @@ module Cask
end end
def get_repo_data(regex) def get_repo_data(regex)
return unless @online return unless online?
return unless @new_cask return unless new_cask?
_, user, repo = *regex.match(cask.url.to_s) _, user, repo = *regex.match(cask.url.to_s)
_, user, repo = *regex.match(cask.homepage) unless user _, user, repo = *regex.match(cask.homepage) unless user

View File

@ -1,5 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
require "cask/audit"
module Cask module Cask
class Auditor class Auditor
extend Predicable extend Predicable

View File

@ -155,6 +155,7 @@ module Cask
{ {
"token" => token, "token" => token,
"name" => name, "name" => name,
"desc" => desc,
"homepage" => homepage, "homepage" => homepage,
"url" => url, "url" => url,
"appcast" => appcast, "appcast" => appcast,

View File

@ -35,6 +35,8 @@ require "cask/cmd/internal_stanza"
module Cask module Cask
class Cmd class Cmd
include Context
ALIASES = { ALIASES = {
"ls" => "list", "ls" => "list",
"homepage" => "home", "homepage" => "home",
@ -154,7 +156,7 @@ module Cask
end end
rescue CaskError, MethodDeprecatedError, ArgumentError, OptionParser::InvalidOption => e rescue CaskError, MethodDeprecatedError, ArgumentError, OptionParser::InvalidOption => e
onoe e.message onoe e.message
$stderr.puts e.backtrace if Homebrew.args.debug? $stderr.puts e.backtrace if debug?
exit 1 exit 1
rescue StandardError, ScriptError, NoMemoryError => e rescue StandardError, ScriptError, NoMemoryError => e
onoe e.message onoe e.message

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
require "cli/parser" require "cli/parser"
require "cask/auditor"
module Cask module Cask
class Cmd class Cmd

View File

@ -24,15 +24,16 @@ module Cask
def self.template(cask_token) def self.template(cask_token)
<<~RUBY <<~RUBY
cask '#{cask_token}' do cask "#{cask_token}" do
version '' version ""
sha256 '' sha256 ""
url "https://" url "https://"
name '' name ""
homepage '' desc ""
homepage ""
app '' app ""
end end
RUBY RUBY
end end

View File

@ -4,13 +4,39 @@ module Cask
class Cmd class Cmd
class Reinstall < Install class Reinstall < Install
def run def run
self.class.reinstall_casks(
*casks,
binaries: binaries?,
verbose: verbose?,
force: force?,
skip_cask_deps: skip_cask_deps?,
require_sha: require_sha?,
quarantine: quarantine?,
)
end
def self.reinstall_casks(
*casks,
verbose: false,
force: false,
skip_cask_deps: false,
binaries: nil,
require_sha: nil,
quarantine: nil
)
# TODO: Handle this in `CLI::Parser`.
binaries = Homebrew::EnvConfig.cask_opts_binaries? if binaries.nil?
quarantine = Homebrew::EnvConfig.cask_opts_quarantine? if quarantine.nil?
require_sha = Homebrew::EnvConfig.cask_opts_require_sha? if require_sha.nil?
casks.each do |cask| casks.each do |cask|
Installer.new(cask, binaries: binaries?, Installer.new(cask,
verbose: verbose?, binaries: binaries,
force: force?, verbose: verbose,
skip_cask_deps: skip_cask_deps?, force: force,
require_sha: require_sha?, skip_cask_deps: skip_cask_deps,
quarantine: quarantine?).reinstall require_sha: require_sha,
quarantine: quarantine).reinstall
end end
end end

View File

@ -11,17 +11,28 @@ module Cask
end end
def run def run
self.class.uninstall_casks(
*casks,
binaries: binaries?,
verbose: verbose?,
force: force?,
)
end
def self.uninstall_casks(*casks, verbose: false, force: false, binaries: nil)
binaries = Homebrew::EnvConfig.cask_opts_binaries? if binaries.nil?
casks.each do |cask| casks.each do |cask|
odebug "Uninstalling Cask #{cask}" odebug "Uninstalling Cask #{cask}"
raise CaskNotInstalledError, cask unless cask.installed? || force? raise CaskNotInstalledError, cask unless cask.installed? || force
if cask.installed? && !cask.installed_caskfile.nil? if cask.installed? && !cask.installed_caskfile.nil?
# use the same cask file that was used for installation, if possible # use the same cask file that was used for installation, if possible
cask = CaskLoader.load(cask.installed_caskfile) if cask.installed_caskfile.exist? cask = CaskLoader.load(cask.installed_caskfile) if cask.installed_caskfile.exist?
end end
Installer.new(cask, binaries: binaries?, verbose: verbose?, force: force?).uninstall Installer.new(cask, binaries: binaries, verbose: verbose, force: force).uninstall
next if (versions = cask.versions).empty? next if (versions = cask.versions).empty?

View File

@ -1,5 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require "env_config"
require "cask/config" require "cask/config"
module Cask module Cask
@ -17,35 +18,69 @@ module Cask
end end
def run def run
outdated_casks = casks(alternative: lambda { self.class.upgrade_casks(
*casks,
force: force?,
greedy: greedy?,
dry_run: dry_run?,
binaries: binaries?,
quarantine: quarantine?,
require_sha: require_sha?,
skip_cask_deps: skip_cask_deps?,
verbose: verbose?,
)
end
def self.upgrade_casks(
*casks,
force: false,
greedy: false,
dry_run: false,
skip_cask_deps: false,
verbose: false,
binaries: nil,
quarantine: nil,
require_sha: nil
)
# TODO: Handle this in `CLI::Parser`.
binaries = Homebrew::EnvConfig.cask_opts_binaries? if binaries.nil?
quarantine = Homebrew::EnvConfig.cask_opts_quarantine? if quarantine.nil?
require_sha = Homebrew::EnvConfig.cask_opts_require_sha? if require_sha.nil?
outdated_casks = if casks.empty?
Caskroom.casks.select do |cask| Caskroom.casks.select do |cask|
cask.outdated?(greedy?) cask.outdated?(greedy)
end end
}).select do |cask| else
raise CaskNotInstalledError, cask unless cask.installed? || force? casks.select do |cask|
raise CaskNotInstalledError, cask unless cask.installed? || force
cask.outdated?(true) cask.outdated?(true)
end
end end
if outdated_casks.empty? return if outdated_casks.empty?
oh1 "No Casks to upgrade"
return
end
ohai "Casks with `auto_updates` or `version :latest` will not be upgraded" if args.empty? && !greedy? ohai "Casks with `auto_updates` or `version :latest` will not be upgraded" if casks.empty? && !greedy
verb = dry_run? ? "Would upgrade" : "Upgrading"
verb = dry_run ? "Would upgrade" : "Upgrading"
oh1 "#{verb} #{outdated_casks.count} #{"outdated package".pluralize(outdated_casks.count)}:" oh1 "#{verb} #{outdated_casks.count} #{"outdated package".pluralize(outdated_casks.count)}:"
caught_exceptions = [] caught_exceptions = []
upgradable_casks = outdated_casks.map { |c| [CaskLoader.load(c.installed_caskfile), c] } upgradable_casks = outdated_casks.map { |c| [CaskLoader.load(c.installed_caskfile), c] }
puts upgradable_casks puts upgradable_casks
.map { |(old_cask, new_cask)| "#{new_cask.full_name} #{old_cask.version} -> #{new_cask.version}" } .map { |(old_cask, new_cask)| "#{new_cask.full_name} #{old_cask.version} -> #{new_cask.version}" }
.join(", ") .join("\n")
return if dry_run? return if dry_run
upgradable_casks.each do |(old_cask, new_cask)| upgradable_casks.each do |(old_cask, new_cask)|
upgrade_cask(old_cask, new_cask) upgrade_cask(
old_cask, new_cask,
binaries: binaries, force: force, skip_cask_deps: skip_cask_deps, verbose: verbose,
quarantine: quarantine, require_sha: require_sha
)
rescue => e rescue => e
caught_exceptions << e.exception("#{new_cask.full_name}: #{e}") caught_exceptions << e.exception("#{new_cask.full_name}: #{e}")
next next
@ -56,26 +91,29 @@ module Cask
raise caught_exceptions.first if caught_exceptions.count == 1 raise caught_exceptions.first if caught_exceptions.count == 1
end end
def upgrade_cask(old_cask, new_cask) def self.upgrade_cask(
old_cask, new_cask,
binaries:, force:, quarantine:, require_sha:, skip_cask_deps:, verbose:
)
odebug "Started upgrade process for Cask #{old_cask}" odebug "Started upgrade process for Cask #{old_cask}"
old_config = old_cask.config old_config = old_cask.config
old_cask_installer = old_cask_installer =
Installer.new(old_cask, binaries: binaries?, Installer.new(old_cask, binaries: binaries,
verbose: verbose?, verbose: verbose,
force: force?, force: force,
upgrade: true) upgrade: true)
new_cask.config = Config.global.merge(old_config) new_cask.config = Config.global.merge(old_config)
new_cask_installer = new_cask_installer =
Installer.new(new_cask, binaries: binaries?, Installer.new(new_cask, binaries: binaries,
verbose: verbose?, verbose: verbose,
force: force?, force: force,
skip_cask_deps: skip_cask_deps?, skip_cask_deps: skip_cask_deps,
require_sha: require_sha?, require_sha: require_sha,
upgrade: true, upgrade: true,
quarantine: quarantine?) quarantine: quarantine)
started_upgrade = false started_upgrade = false
new_artifacts_installed = false new_artifacts_installed = false

View File

@ -64,6 +64,7 @@ module Cask
:caveats, :caveats,
:conflicts_with, :conflicts_with,
:container, :container,
:desc,
:depends_on, :depends_on,
:homepage, :homepage,
:language, :language,
@ -93,6 +94,10 @@ module Cask
@name.concat(args.flatten) @name.concat(args.flatten)
end end
def desc(description = nil)
set_unique_stanza(:desc, description.nil?) { description }
end
def set_unique_stanza(stanza, should_return) def set_unique_stanza(stanza, should_return)
return instance_variable_get("@#{stanza}") if should_return return instance_variable_get("@#{stanza}") if should_return

View File

@ -1,5 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
require "cask/utils"
module Cask module Cask
class DSL class DSL
class Base class Base

View File

@ -367,11 +367,10 @@ module Cask
force: false, force: false,
).install ).install
else else
FormulaInstaller.new(cask_or_formula).yield_self do |fi| FormulaInstaller.new(cask_or_formula, verbose: verbose?).yield_self do |fi|
fi.installed_as_dependency = true fi.installed_as_dependency = true
fi.installed_on_request = false fi.installed_on_request = false
fi.show_header = true fi.show_header = true
fi.verbose = verbose?
fi.prelude fi.prelude
fi.fetch fi.fetch
fi.install fi.install

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
require "development_tools" require "development_tools"
require "cask/exceptions"
module Cask module Cask
module Quarantine module Quarantine
module_function module_function

View File

@ -10,6 +10,8 @@
# * sets permissions on executables # * sets permissions on executables
# * removes unresolved symlinks # * removes unresolved symlinks
class Cleaner class Cleaner
include Context
# Create a cleaner for the given formula # Create a cleaner for the given formula
def initialize(f) def initialize(f)
@f = f @f = f
@ -58,7 +60,7 @@ class Cleaner
# actual files gets removed correctly. # actual files gets removed correctly.
dirs.reverse_each do |d| dirs.reverse_each do |d|
if d.children.empty? if d.children.empty?
puts "rmdir: #{d} (empty)" if Homebrew.args.verbose? puts "rmdir: #{d} (empty)" if verbose?
d.rmdir d.rmdir
end end
end end
@ -109,7 +111,7 @@ class Cleaner
else else
0444 0444
end end
if Homebrew.args.debug? if debug?
old_perms = path.stat.mode & 0777 old_perms = path.stat.mode & 0777
odebug "Fixing #{path} permissions from #{old_perms.to_s(8)} to #{perms.to_s(8)}" if perms != old_perms odebug "Fixing #{path} permissions from #{old_perms.to_s(8)} to #{perms.to_s(8)}" if perms != old_perms
end end

View File

@ -35,6 +35,7 @@ module Homebrew
@resolved_formulae_casks = nil @resolved_formulae_casks = nil
@formulae_paths = nil @formulae_paths = nil
@casks = nil @casks = nil
@loaded_casks = nil
@kegs = nil @kegs = nil
@kegs_casks = nil @kegs_casks = nil
@ -125,6 +126,10 @@ module Homebrew
.freeze .freeze
end end
def loaded_casks
@loaded_casks ||= downcased_unique_named.map(&Cask::CaskLoader.method(:load)).freeze
end
def kegs def kegs
@kegs ||= downcased_unique_named.map do |name| @kegs ||= downcased_unique_named.map do |name|
resolve_keg name resolve_keg name
@ -183,6 +188,10 @@ module Homebrew
flag_with_value.delete_prefix(arg_prefix) flag_with_value.delete_prefix(arg_prefix)
end end
def context
Context::ContextStruct.new(debug: debug?, quiet: quiet?, verbose: verbose?)
end
private private
def option_to_name(option) def option_to_name(option)

View File

@ -88,9 +88,7 @@ module Homebrew
def env?(env) def env?(env)
return false if env.blank? return false if env.blank?
Homebrew::EnvConfig.send("#{env}?") Homebrew::EnvConfig.try(:"#{env}?")
rescue NoMethodError
false
end end
def usage_banner(text) def usage_banner(text)

View File

@ -13,7 +13,7 @@ module Homebrew
def __cache_args def __cache_args
Homebrew::CLI::Parser.new do Homebrew::CLI::Parser.new do
usage_banner <<~EOS usage_banner <<~EOS
`--cache` [<options>] [<formula>] `--cache` [<options>] [<formula|cask>]
Display Homebrew's download cache. See also `HOMEBREW_CACHE`. Display Homebrew's download cache. See also `HOMEBREW_CACHE`.

View File

@ -0,0 +1,30 @@
# frozen_string_literal: true
module Homebrew
module_function
def __caskroom_args
Homebrew::CLI::Parser.new do
usage_banner <<~EOS
`--caskroom` [<cask>]
Display Homebrew's Caskroom path.
If <cask> is provided, display the location in the Caskroom where <cask>
would be installed, without any sort of versioned directory as the last path.
EOS
end
end
def __caskroom
args = __caskroom_args.parse
if args.loaded_casks.blank?
puts Cask::Caskroom.path
else
args.loaded_casks.each do |cask|
puts "#{Cask::Caskroom.path}/#{cask.token}"
end
end
end
end

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require "cask/all" require "cask"
module Homebrew module Homebrew
module_function module_function

View File

@ -96,7 +96,7 @@ module Homebrew
if args.no_named? if args.no_named?
raise FormulaUnspecifiedError unless args.installed? raise FormulaUnspecifiedError unless args.installed?
puts_deps sorted_dependents(Formula.installed + Cask::Caskroom.casks), recursive puts_deps sorted_dependents(Formula.installed + Cask::Caskroom.casks), recursive, args: args
return return
end end

View File

@ -3,7 +3,7 @@
require "help" require "help"
module Homebrew module Homebrew
def help(cmd = nil, flags = {}) def help
Help.help(cmd, flags) Help.help
end end
end end

View File

@ -211,7 +211,13 @@ module Homebrew
puts "From: #{Formatter.url(github_info(f))}" puts "From: #{Formatter.url(github_info(f))}"
puts "License: #{f.license.join(", ")}" if f.license if f.license.present?
licenses = f.license
.map(&:to_s)
.join(", ")
.sub("public_domain", "Public Domain")
puts "License: #{licenses}"
end
unless f.deps.empty? unless f.deps.empty?
ohai "Dependencies" ohai "Dependencies"

View File

@ -325,7 +325,8 @@ module Homebrew
fi = FormulaInstaller.new(f, force_bottle: args.force_bottle?, fi = FormulaInstaller.new(f, force_bottle: args.force_bottle?,
include_test_formulae: args.include_test_formulae, include_test_formulae: args.include_test_formulae,
build_from_source_formulae: args.build_from_source_formulae) build_from_source_formulae: args.build_from_source_formulae,
debug: args.debug?, quiet: args.quiet?, verbose: args.verbose?)
fi.options = build_options.used_options fi.options = build_options.used_options
fi.env = args.env fi.env = args.env
fi.force = args.force? fi.force = args.force?

View File

@ -31,10 +31,11 @@ module Homebrew
def link def link
args = link_args.parse args = link_args.parse
mode = OpenStruct.new options = {
overwrite: args.overwrite?,
mode.overwrite = true if args.overwrite? dry_run: args.dry_run?,
mode.dry_run = true if args.dry_run? verbose: args.verbose?,
}
args.kegs.each do |keg| args.kegs.each do |keg|
keg_only = Formulary.keg_only?(keg.rack) keg_only = Formulary.keg_only?(keg.rack)
@ -53,13 +54,13 @@ module Homebrew
next next
end end
if mode.dry_run if args.dry_run?
if mode.overwrite if args.overwrite?
puts "Would remove:" puts "Would remove:"
else else
puts "Would link:" puts "Would link:"
end end
keg.link(mode) keg.link(**options)
puts_keg_only_path_message(keg) if keg_only puts_keg_only_path_message(keg) if keg_only
next next
end end
@ -89,7 +90,7 @@ module Homebrew
puts if args.verbose? puts if args.verbose?
begin begin
n = keg.link(mode) n = keg.link(**options)
rescue Keg::LinkError rescue Keg::LinkError
puts puts
raise raise

View File

@ -11,9 +11,9 @@ module Homebrew
def list_args def list_args
Homebrew::CLI::Parser.new do Homebrew::CLI::Parser.new do
usage_banner <<~EOS usage_banner <<~EOS
`list`, `ls` [<options>] [<formula>] `list`, `ls` [<options>] [<formula|cask>]
List all installed formulae. List all installed formulae or casks
If <formula> is provided, summarise the paths within its current keg. If <formula> is provided, summarise the paths within its current keg.
EOS EOS
@ -32,8 +32,10 @@ module Homebrew
switch "--pinned", switch "--pinned",
description: "Show the versions of pinned formulae, or only the specified (pinned) "\ description: "Show the versions of pinned formulae, or only the specified (pinned) "\
"formulae if <formula> are provided. See also `pin`, `unpin`." "formulae if <formula> are provided. See also `pin`, `unpin`."
switch "--cask", switch "--formula", "--formulae",
description: "List casks" description: "List only formulae."
switch "--cask", "--casks",
description: "List only casks."
# passed through to ls # passed through to ls
switch "-1", switch "-1",
description: "Force output to be one entry per line. " \ description: "Force output to be one entry per line. " \
@ -46,7 +48,9 @@ module Homebrew
switch "-t", switch "-t",
description: "Sort by time modified, listing most recently modified first." description: "Sort by time modified, listing most recently modified first."
["--unbrewed", "--multiple", "--pinned", "-l", "-r", "-t"].each { |flag| conflicts "--cask", flag } ["--formula", "--unbrewed", "--multiple", "--pinned", "-l", "-r", "-t"].each do |flag|
conflicts "--cask", flag
end
end end
end end

View File

@ -37,7 +37,7 @@ module Homebrew
ENV["PATH"] = ENV["HOMEBREW_PATH"] ENV["PATH"] = ENV["HOMEBREW_PATH"]
if args.no_named? if args.no_named?
git_log HOMEBREW_REPOSITORY git_log HOMEBREW_REPOSITORY, args: args
else else
path = Formulary.path(args.named.first) path = Formulary.path(args.named.first)
tap = Tap.from_path(path) tap = Tap.from_path(path)

View File

@ -12,15 +12,19 @@ module Homebrew
def outdated_args def outdated_args
Homebrew::CLI::Parser.new do Homebrew::CLI::Parser.new do
usage_banner <<~EOS usage_banner <<~EOS
`outdated` [<options>] [<formula>] `outdated` [<options>] [<formula>|<cask>]
List installed formulae that have an updated version available. By default, version List installed casks and formulae that have an updated version available. By default, version
information is displayed in interactive shells, and suppressed otherwise. information is displayed in interactive shells, and suppressed otherwise.
EOS EOS
switch "-q", "--quiet", switch "-q", "--quiet",
description: "List only the names of outdated kegs (takes precedence over `--verbose`)." description: "List only the names of outdated kegs (takes precedence over `--verbose`)."
switch "-v", "--verbose", switch "-v", "--verbose",
description: "Include detailed version information." description: "Include detailed version information."
switch "--formula",
description: "Only output outdated formulae."
switch "--cask",
description: "Only output outdated casks."
flag "--json", flag "--json",
description: "Print output in JSON format. There are two versions: v1 and v2. " \ description: "Print output in JSON format. There are two versions: v1 and v2. " \
"v1 is deprecated and is currently the default if no version is specified. " \ "v1 is deprecated and is currently the default if no version is specified. " \
@ -31,10 +35,6 @@ module Homebrew
"updates when a new stable or development version has been released." "updates when a new stable or development version has been released."
switch "--greedy", switch "--greedy",
description: "Print outdated casks with `auto_updates` or `version :latest`." description: "Print outdated casks with `auto_updates` or `version :latest`."
switch "--formula",
description: "Treat all arguments as formulae."
switch "--cask",
description: "Treat all arguments as casks."
conflicts "--quiet", "--verbose", "--json" conflicts "--quiet", "--verbose", "--json"
conflicts "--formula", "--cask" conflicts "--formula", "--cask"
@ -94,7 +94,7 @@ module Homebrew
if formula_or_cask.is_a?(Formula) if formula_or_cask.is_a?(Formula)
f = formula_or_cask f = formula_or_cask
if verbose? args: args if verbose?
outdated_kegs = f.outdated_kegs(fetch_head: args.fetch_HEAD?) outdated_kegs = f.outdated_kegs(fetch_head: args.fetch_HEAD?)
current_version = if f.alias_changed? current_version = if f.alias_changed?
@ -122,7 +122,7 @@ module Homebrew
else else
c = formula_or_cask c = formula_or_cask
puts c.outdated_info(args.greedy?, verbose?(args: args), false) puts c.outdated_info(args.greedy?, verbose?, false)
end end
end end
end end
@ -147,13 +147,13 @@ module Homebrew
else else
c = formula_or_cask c = formula_or_cask
c.outdated_info(args.greedy?, verbose?(args: args), true) c.outdated_info(args.greedy?, verbose?, true)
end end
end end
end end
def verbose?(args:) def verbose?
($stdout.tty? || args.verbose?) && !args.quiet? ($stdout.tty? || super) && !quiet?
end end
def json_version(version) def json_version(version)

View File

@ -24,7 +24,7 @@ module Homebrew
args.resolved_formulae.each do |f| args.resolved_formulae.each do |f|
ohai "Postinstalling #{f}" ohai "Postinstalling #{f}"
fi = FormulaInstaller.new(f) fi = FormulaInstaller.new(f, debug: args.debug?, quiet: args.quiet?, verbose: args.verbose?)
fi.post_install fi.post_install
end end
end end

View File

@ -78,9 +78,14 @@ module Homebrew
return if casks.blank? return if casks.blank?
reinstall_cmd = Cask::Cmd::Reinstall.new(casks) Cask::Cmd::Reinstall.reinstall_casks(
reinstall_cmd.verbose = args.verbose? *casks,
reinstall_cmd.force = args.force? binaries: args.binaries?,
reinstall_cmd.run verbose: args.verbose?,
force: args.force?,
require_sha: args.require_sha?,
skip_cask_deps: args.skip_cask_deps?,
quarantine: args.quarantine?,
)
end end
end end

View File

@ -36,10 +36,10 @@ module Homebrew
If no <text> is provided, list all locally available formulae (including tapped ones). If no <text> is provided, list all locally available formulae (including tapped ones).
No online search is performed. No online search is performed.
EOS EOS
switch "--formulae", switch "--formula", "--formulae",
description: "Without <text>, list all locally available formulae (no online search is performed). " \ description: "Without <text>, list all locally available formulae (no online search is performed). " \
"With <text>, search online and locally for formulae." "With <text>, search online and locally for formulae."
switch "--casks", switch "--cask", "--casks",
description: "Without <text>, list all locally available casks (including tapped ones, no online " \ description: "Without <text>, list all locally available casks (including tapped ones, no online " \
"search is performed). With <text>, search online and locally for casks." "search is performed). With <text>, search online and locally for casks."
switch "--desc", switch "--desc",
@ -66,8 +66,8 @@ module Homebrew
end end
if args.no_named? if args.no_named?
if args.casks? if args.cask?
raise UsageError, "specifying both --formulae and --casks requires <text>" if args.formulae? raise UsageError, "specifying both --formula and --cask requires <text>" if args.formula?
puts Formatter.columns(Cask::Cask.to_a.map(&:full_name).sort) puts Formatter.columns(Cask::Cask.to_a.map(&:full_name).sort)
else else
@ -92,8 +92,8 @@ module Homebrew
local_casks = search_casks(string_or_regex) local_casks = search_casks(string_or_regex)
remote_casks = remote_results[:casks] remote_casks = remote_results[:casks]
all_casks = local_casks + remote_casks all_casks = local_casks + remote_casks
print_formulae = args.formulae? print_formulae = args.formula?
print_casks = args.casks? print_casks = args.cask?
print_formulae = print_casks = true if !print_formulae && !print_casks print_formulae = print_casks = true if !print_formulae && !print_casks
if print_formulae && all_formulae.any? if print_formulae && all_formulae.any?
@ -102,7 +102,7 @@ module Homebrew
end end
if print_casks && all_casks.any? if print_casks && all_casks.any?
puts if args.formulae? && all_formulae.any? puts if args.formula? && all_formulae.any?
ohai "Casks" ohai "Casks"
puts Formatter.columns(all_casks) puts Formatter.columns(all_casks)
end end

View File

@ -49,7 +49,7 @@ module Homebrew
# Link new version, if not keg-only # Link new version, if not keg-only
if Formulary.keg_only?(rack) if Formulary.keg_only?(rack)
keg.optlink keg.optlink(verbose: args.verbose?)
puts "Opt link created for #{keg}" puts "Opt link created for #{keg}"
else else
puts "#{keg.link} links created for #{keg}" puts "#{keg.link} links created for #{keg}"

View File

@ -69,15 +69,14 @@ module Homebrew
puts unless i.zero? puts unless i.zero?
info = "#{tap}: " info = "#{tap}: "
if tap.installed? if tap.installed?
info += tap.pinned? ? "pinned" : "unpinned" info += if (contents = tap.contents).blank?
info += ", private" if tap.private? "no commands/casks/formulae"
info += if (contents = tap.contents).empty?
", no commands/casks/formulae"
else else
", #{contents.join(", ")}" contents.join(", ")
end end
info += ", private" if tap.private?
info += "\n#{tap.path} (#{tap.path.abv})" info += "\n#{tap.path} (#{tap.path.abv})"
info += "\nFrom: #{tap.remote.nil? ? "N/A" : tap.remote}" info += "\nFrom: #{tap.remote.blank? ? "N/A" : tap.remote}"
else else
info += "Not installed" info += "Not installed"
end end

View File

@ -5,7 +5,6 @@ require "formula"
require "diagnostic" require "diagnostic"
require "migrator" require "migrator"
require "cli/parser" require "cli/parser"
require "cask/all"
require "cask/cmd" require "cask/cmd"
require "cask/cask_loader" require "cask/cask_loader"
@ -127,10 +126,12 @@ module Homebrew
return if casks.blank? return if casks.blank?
cask_uninstall = Cask::Cmd::Uninstall.new(casks) Cask::Cmd::Uninstall.uninstall_casks(
cask_uninstall.force = args.force? *casks,
cask_uninstall.verbose = args.verbose? binaries: args.binaries?,
cask_uninstall.run verbose: args.verbose?,
force: args.force?,
)
rescue MultipleVersionsInstalledError => e rescue MultipleVersionsInstalledError => e
ofail e ofail e
puts "Run `brew uninstall --force #{e.name}` to remove all versions." puts "Run `brew uninstall --force #{e.name}` to remove all versions."

View File

@ -26,20 +26,19 @@ module Homebrew
def unlink def unlink
args = unlink_args.parse args = unlink_args.parse
mode = OpenStruct.new options = { dry_run: args.dry_run?, verbose: args.verbose? }
mode.dry_run = true if args.dry_run?
args.kegs.each do |keg| args.kegs.each do |keg|
if mode.dry_run if args.dry_run?
puts "Would remove:" puts "Would remove:"
keg.unlink(mode) keg.unlink(**options)
next next
end end
keg.lock do keg.lock do
print "Unlinking #{keg}... " print "Unlinking #{keg}... "
puts if args.verbose? puts if args.verbose?
puts "#{keg.unlink(mode)} symlinks removed" puts "#{keg.unlink(**options)} symlinks removed"
end end
end end
end end

View File

@ -120,7 +120,7 @@ module Homebrew
else else
hub.dump(updated_formula_report: !args.preinstall?) hub.dump(updated_formula_report: !args.preinstall?)
hub.reporters.each(&:migrate_tap_migration) hub.reporters.each(&:migrate_tap_migration)
hub.reporters.each { |r| r.migrate_formula_rename(force: args.force?) } hub.reporters.each { |r| r.migrate_formula_rename(force: args.force?, verbose: args.verbose?) }
CacheStoreDatabase.use(:descriptions) do |db| CacheStoreDatabase.use(:descriptions) do |db|
DescriptionCacheStore.new(db) DescriptionCacheStore.new(db)
.update_from_report!(hub) .update_from_report!(hub)
@ -371,7 +371,7 @@ class Reporter
end end
end end
def migrate_formula_rename(force:) def migrate_formula_rename(force:, verbose:)
Formula.installed.each do |formula| Formula.installed.each do |formula|
next unless Migrator.needs_migration?(formula) next unless Migrator.needs_migration?(formula)

View File

@ -14,11 +14,11 @@ module Homebrew
def upgrade_args def upgrade_args
Homebrew::CLI::Parser.new do Homebrew::CLI::Parser.new do
usage_banner <<~EOS usage_banner <<~EOS
`upgrade` [<options>] [<formula>] `upgrade` [<options>] [<formula>|<cask>]
Upgrade outdated, unpinned formulae using the same options they were originally Upgrade outdated casks and outdated, unpinned formulae using the same options they were originally
installed with, plus any appended brew formula options. If <formula> are specified, installed with, plus any appended brew formula options. If <cask> or <formula> are specified,
upgrade only the given <formula> kegs (unless they are pinned; see `pin`, `unpin`). upgrade only the given <cask> or <formula> kegs (unless they are pinned; see `pin`, `unpin`).
Unless `HOMEBREW_NO_INSTALL_CLEANUP` is set, `brew cleanup` will then be run for the Unless `HOMEBREW_NO_INSTALL_CLEANUP` is set, `brew cleanup` will then be run for the
upgraded formulae or, every 30 days, for all formulae. upgraded formulae or, every 30 days, for all formulae.
@ -26,6 +26,10 @@ module Homebrew
switch "-d", "--debug", switch "-d", "--debug",
description: "If brewing fails, open an interactive debugging session with access to IRB "\ description: "If brewing fails, open an interactive debugging session with access to IRB "\
"or a shell inside the temporary build directory." "or a shell inside the temporary build directory."
switch "--formula",
description: "Only upgrade outdated formulae."
switch "--cask",
description: "Only upgrade outdated casks."
switch "-s", "--build-from-source", switch "-s", "--build-from-source",
description: "Compile <formula> from source even if a bottle is available." description: "Compile <formula> from source even if a bottle is available."
switch "-i", "--interactive", switch "-i", "--interactive",
@ -56,6 +60,12 @@ module Homebrew
switch "--greedy", switch "--greedy",
description: "Upgrade casks with `auto_updates` or `version :latest`" description: "Upgrade casks with `auto_updates` or `version :latest`"
conflicts "--build-from-source", "--force-bottle" conflicts "--build-from-source", "--force-bottle"
conflicts "--formula", "--greedy"
["--formula", "-s", "--build-from-source", "-i", "--interactive",
"--force-bottle", "--fetch-HEAD", "--ignore-pinned", "--keep-tmp",
"--display-times"].each do |flag|
conflicts "--cask", flag
end
formula_options formula_options
end end
end end
@ -67,14 +77,16 @@ module Homebrew
# If one or more formulae are specified, but no casks were # If one or more formulae are specified, but no casks were
# specified, we want to make note of that so we don't # specified, we want to make note of that so we don't
# try to upgrade all outdated casks. # try to upgrade all outdated casks.
named_formulae_specified = !formulae.empty? && casks.empty? upgrade_formulae = formulae.present? && casks.blank?
named_casks_specified = !casks.empty? && formulae.empty? upgrade_casks = casks.present? && formulae.blank?
upgrade_outdated_formulae(formulae, args: args) unless named_casks_specified upgrade_outdated_formulae(formulae, args: args) unless upgrade_casks
upgrade_outdated_casks(casks, args: args) unless named_formulae_specified upgrade_outdated_casks(casks, args: args) unless upgrade_formulae
end end
def upgrade_outdated_formulae(formulae, args:) def upgrade_outdated_formulae(formulae, args:)
return if args.cask?
FormulaInstaller.prevent_build_flags(args) FormulaInstaller.prevent_build_flags(args)
Install.perform_preinstall_checks Install.perform_preinstall_checks
@ -133,10 +145,18 @@ module Homebrew
end end
def upgrade_outdated_casks(casks, args:) def upgrade_outdated_casks(casks, args:)
cask_upgrade = Cask::Cmd::Upgrade.new(casks) return if args.formula?
cask_upgrade.force = args.force?
cask_upgrade.dry_run = args.dry_run? Cask::Cmd::Upgrade.upgrade_casks(
cask_upgrade.greedy = args.greedy? *casks,
cask_upgrade.run force: args.force?,
greedy: args.greedy?,
dry_run: args.dry_run?,
binaries: args.binaries?,
quarantine: args.quarantine?,
require_sha: args.require_sha?,
skip_cask_deps: args.skip_cask_deps?,
verbose: args.verbose?,
)
end end
end end

View File

@ -65,15 +65,25 @@ module Homebrew
end end
use_runtime_dependents = args.installed? && use_runtime_dependents = args.installed? &&
!used_formulae_missing &&
!args.include_build? && !args.include_build? &&
!args.include_test? && !args.include_test? &&
!args.include_optional? && !args.include_optional? &&
!args.skip_recommended? !args.skip_recommended?
uses = intersection_of_dependents(use_runtime_dependents, used_formulae, args: args)
return if uses.empty?
puts Formatter.columns(uses.map(&:full_name).sort)
odie "Missing formulae should not have dependents!" if used_formulae_missing
end
def intersection_of_dependents(use_runtime_dependents, used_formulae, args:)
recursive = args.recursive? recursive = args.recursive?
includes, ignores = args_includes_ignores(args) includes, ignores = args_includes_ignores(args)
uses = if use_runtime_dependents && !used_formulae_missing if use_runtime_dependents
used_formulae.map(&:runtime_installed_formula_dependents) used_formulae.map(&:runtime_installed_formula_dependents)
.reduce(&:&) .reduce(&:&)
.select(&:any_version_installed?) + .select(&:any_version_installed?) +
@ -87,11 +97,6 @@ module Homebrew
select_used_dependents(deps, used_formulae, recursive, includes, ignores) select_used_dependents(deps, used_formulae, recursive, includes, ignores)
end end
return if uses.empty?
puts Formatter.columns(uses.map(&:full_name).sort)
odie "Missing formulae should not have dependents!" if used_formulae_missing
end end
def select_used_dependents(dependents, used_formulae, recursive, includes, ignores) def select_used_dependents(dependents, used_formulae, recursive, includes, ignores)

View File

@ -1,5 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
require "compat/dependencies_helpers"
require "compat/cli/parser"
require "compat/extend/nil" require "compat/extend/nil"
require "compat/extend/string" require "compat/extend/string"
require "compat/formula" require "compat/formula"

View File

@ -0,0 +1,32 @@
# frozen_string_literal: true
module Homebrew
module CLI
class Parser
module Compat
module DeprecatedArgs
def respond_to_missing?(*)
super
end
def method_missing(method, *)
if ![:debug?, :quiet?, :verbose?, :value].include?(method) && !@printed_args_warning
odeprecated "Homebrew.args", "`args = <command>_args.parse` and pass `args` along the call chain"
@printed_args_warning = true
end
super
end
end
def parse(*)
args = super
Homebrew.args = args.dup.extend(DeprecatedArgs)
args
end
end
prepend Compat
end
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
require "cli/args"
module DependenciesHelpers
module Compat
def argv_includes_ignores(argv = nil)
unless @printed_includes_ignores_warning
odeprecated "Homebrew.argv_includes_ignores", "Homebrew.args_includes_ignores"
@printed_includes_ignores_warning = true
end
args_includes_ignores(argv ? Homebrew::CLI::Args.new : Homebrew.args)
end
end
prepend Compat
end

View File

@ -0,0 +1,71 @@
# frozen_string_literal: true
require "monitor"
module Context
extend MonitorMixin
def self.current=(context)
synchronize do
@current = context
end
end
def self.current
if current_context = Thread.current[:context]
return current_context
end
synchronize do
@current ||= ContextStruct.new
end
end
class ContextStruct
def initialize(debug: nil, quiet: nil, verbose: nil)
@debug = debug
@quiet = quiet
@verbose = verbose
end
def debug?
@debug
end
def quiet?
@quiet
end
def verbose?
@verbose
end
end
def debug?
Context.current.debug?
end
def quiet?
Context.current.quiet?
end
def verbose?
Context.current.verbose?
end
def with_context(**options)
old_context = Thread.current[:context]
new_context = ContextStruct.new(
debug: options.fetch(:debug, old_context&.debug?),
quiet: options.fetch(:quiet, old_context&.quiet?),
verbose: options.fetch(:verbose, old_context&.verbose?),
)
Thread.current[:context] = new_context
yield
ensure
Thread.current[:context] = old_context
end
end

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,7 @@ require "formula"
require "formula_versions" require "formula_versions"
require "utils/curl" require "utils/curl"
require "utils/notability" require "utils/notability"
require "utils/spdx"
require "extend/ENV" require "extend/ENV"
require "formula_cellar_checks" require "formula_cellar_checks"
require "cmd/search" require "cmd/search"
@ -37,6 +38,8 @@ module Homebrew
description: "Run various additional style checks to determine if a new formula is eligible "\ description: "Run various additional style checks to determine if a new formula is eligible "\
"for Homebrew. This should be used when creating new formula and implies "\ "for Homebrew. This should be used when creating new formula and implies "\
"`--strict` and `--online`." "`--strict` and `--online`."
flag "--tap=",
description: "Check the formulae within the given tap, specified as <user>`/`<repo>."
switch "--fix", switch "--fix",
description: "Fix style violations automatically using RuboCop's auto-correct feature." description: "Fix style violations automatically using RuboCop's auto-correct feature."
switch "--display-cop-names", switch "--display-cop-names",
@ -46,7 +49,7 @@ module Homebrew
"make output easy to grep." "make output easy to grep."
switch "--skip-style", switch "--skip-style",
description: "Skip running non-RuboCop style checks. Useful if you plan on running "\ description: "Skip running non-RuboCop style checks. Useful if you plan on running "\
"`brew style` separately." "`brew style` separately. Default unless a formula is specified by name"
switch "-D", "--audit-debug", switch "-D", "--audit-debug",
description: "Enable debugging and profiling of audit methods." description: "Enable debugging and profiling of audit methods."
comma_array "--only", comma_array "--only",
@ -85,17 +88,23 @@ module Homebrew
strict = new_formula || args.strict? strict = new_formula || args.strict?
online = new_formula || args.online? online = new_formula || args.online?
git = args.git? git = args.git?
skip_style = args.skip_style? || args.no_named? skip_style = args.skip_style? || args.no_named? || args.tap
ENV.activate_extensions! ENV.activate_extensions!
ENV.setup_build_environment ENV.setup_build_environment
audit_formulae = args.no_named? ? Formula : args.resolved_formulae audit_formulae = if args.tap
Tap.fetch(args.tap).formula_names.map { |name| Formula[name] }
elsif args.no_named?
Formula
else
args.resolved_formulae
end
style_files = args.formulae_paths unless skip_style style_files = args.formulae_paths unless skip_style
only_cops = args.only_cops only_cops = args.only_cops
except_cops = args.except_cops except_cops = args.except_cops
options = { fix: args.fix? } options = { fix: args.fix?, debug: args.debug?, verbose: args.verbose? }
if only_cops if only_cops
options[:only_cops] = only_cops options[:only_cops] = only_cops
@ -110,8 +119,7 @@ module Homebrew
# Check style in a single batch run up front for performance # Check style in a single batch run up front for performance
style_results = Style.check_style_json(style_files, options) if style_files style_results = Style.check_style_json(style_files, options) if style_files
# load licenses # load licenses
spdx = HOMEBREW_LIBRARY_PATH/"data/spdx.json" spdx_data = SPDX.spdx_data
spdx_data = JSON.parse(spdx.read)
new_formula_problem_lines = [] new_formula_problem_lines = []
audit_formulae.sort.each do |f| audit_formulae.sort.each do |f|
only = only_cops ? ["style"] : args.only only = only_cops ? ["style"] : args.only
@ -335,19 +343,42 @@ module Homebrew
openssl@1.1 openssl@1.1
].freeze ].freeze
PERMITTED_LICENSE_MISMATCHES = {
"AGPL-3.0" => ["AGPL-3.0-only", "AGPL-3.0-or-later"],
"GPL-2.0" => ["GPL-2.0-only", "GPL-2.0-or-later"],
"GPL-3.0" => ["GPL-3.0-only", "GPL-3.0-or-later"],
"LGPL-2.1" => ["LGPL-2.1-only", "LGPL-2.1-or-later"],
"LGPL-3.0" => ["LGPL-3.0-only", "LGPL-3.0-or-later"],
}.freeze
def audit_license def audit_license
if formula.license.present? if formula.license.present?
non_standard_licenses = [] non_standard_licenses = formula.license.map do |license|
formula.license.each do |license| next if license == :public_domain
next if @spdx_data["licenses"].any? { |spdx| spdx["licenseId"] == license } next if @spdx_data["licenses"].any? { |spdx| spdx["licenseId"] == license }
non_standard_licenses << license license
end end.compact
if non_standard_licenses.present? if non_standard_licenses.present?
problem "Formula #{formula.name} contains non-standard SPDX licenses: #{non_standard_licenses}." problem "Formula #{formula.name} contains non-standard SPDX licenses: #{non_standard_licenses}."
end end
if @strict
deprecated_licenses = formula.license.map do |license|
next if license == :public_domain
next if @spdx_data["licenses"].any? do |spdx|
spdx["licenseId"] == license && !spdx["isDeprecatedLicenseId"]
end
license
end.compact
if deprecated_licenses.present?
problem "Formula #{formula.name} contains deprecated SPDX licenses: #{deprecated_licenses}."
end
end
return unless @online return unless @online
user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*}) if @new_formula user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*}) if @new_formula
@ -355,12 +386,12 @@ module Homebrew
github_license = GitHub.get_repo_license(user, repo) github_license = GitHub.get_repo_license(user, repo)
return if github_license && (formula.license + ["NOASSERTION"]).include?(github_license) return if github_license && (formula.license + ["NOASSERTION"]).include?(github_license)
return if PERMITTED_LICENSE_MISMATCHES[github_license]&.any? { |license| formula.license.include? license }
problem "License mismatch - GitHub license is: #{Array(github_license)}, "\ problem "Formula license #{formula.license} does not match GitHub license #{Array(github_license)}."
"but Formulae license states: #{formula.license}."
elsif @new_formula elsif @new_formula && @core_tap
problem "No license specified for package." problem "Formulae in homebrew/core must specify a license."
end end
end end

View File

@ -105,7 +105,10 @@ module Homebrew
end end
end end
end end
[formula.tap&.full_name, "origin/master", "-"] origin_branch = Utils.popen_read("git", "-C", formula.tap.path.to_s, "symbolic-ref", "-q", "--short",
"refs/remotes/origin/HEAD").chomp.presence
origin_branch ||= "origin/master"
[formula.tap&.full_name, origin_branch, "-"]
end end
def bump_formula_pr def bump_formula_pr
@ -128,7 +131,7 @@ module Homebrew
check_open_pull_requests(formula, tap_full_name, args: args) check_open_pull_requests(formula, tap_full_name, args: args)
new_version = args.version new_version = args.version
check_all_pull_requests(formula, tap_full_name, version: new_version, args: args) if new_version check_closed_pull_requests(formula, tap_full_name, version: new_version, args: args) if new_version
requested_spec = :stable requested_spec = :stable
formula_spec = formula.stable formula_spec = formula.stable
@ -159,10 +162,10 @@ module Homebrew
old_version = old_formula_version.to_s old_version = old_formula_version.to_s
forced_version = new_version.present? forced_version = new_version.present?
new_url_hash = if new_url && new_hash new_url_hash = if new_url && new_hash
check_all_pull_requests(formula, tap_full_name, url: new_url, args: args) unless new_version check_closed_pull_requests(formula, tap_full_name, url: new_url, args: args) unless new_version
true true
elsif new_tag && new_revision elsif new_tag && new_revision
check_all_pull_requests(formula, tap_full_name, url: old_url, tag: new_tag, args: args) unless new_version check_closed_pull_requests(formula, tap_full_name, url: old_url, tag: new_tag, args: args) unless new_version
false false
elsif !hash_type elsif !hash_type
odie "#{formula}: no --tag= or --version= argument specified!" if !new_tag && !new_version odie "#{formula}: no --tag= or --version= argument specified!" if !new_tag && !new_version
@ -173,7 +176,7 @@ module Homebrew
and old tag are both #{new_tag}. and old tag are both #{new_tag}.
EOS EOS
end end
check_all_pull_requests(formula, tap_full_name, url: old_url, tag: new_tag, args: args) unless new_version check_closed_pull_requests(formula, tap_full_name, url: old_url, tag: new_tag, args: args) unless new_version
resource_path, forced_version = fetch_resource(formula, new_version, old_url, tag: new_tag) resource_path, forced_version = fetch_resource(formula, new_version, old_url, tag: new_tag)
new_revision = Utils.popen_read("git -C \"#{resource_path}\" rev-parse -q --verify HEAD") new_revision = Utils.popen_read("git -C \"#{resource_path}\" rev-parse -q --verify HEAD")
new_revision = new_revision.strip new_revision = new_revision.strip
@ -190,7 +193,7 @@ module Homebrew
#{new_url} #{new_url}
EOS EOS
end end
check_all_pull_requests(formula, tap_full_name, url: new_url, args: args) unless new_version check_closed_pull_requests(formula, tap_full_name, url: new_url, args: args) unless new_version
resource_path, forced_version = fetch_resource(formula, new_version, new_url) resource_path, forced_version = fetch_resource(formula, new_version, new_url)
tar_file_extensions = %w[.tar .tb2 .tbz .tbz2 .tgz .tlz .txz .tZ] tar_file_extensions = %w[.tar .tb2 .tbz .tbz2 .tgz .tlz .txz .tZ]
if tar_file_extensions.any? { |extension| new_url.include? extension } if tar_file_extensions.any? { |extension| new_url.include? extension }
@ -246,7 +249,8 @@ module Homebrew
] ]
end end
old_contents = File.read(formula.path) unless args.dry_run? read_only_run = args.dry_run? && !args.write?
old_contents = File.read(formula.path) unless read_only_run
if new_mirrors if new_mirrors
replacement_pairs << [ replacement_pairs << [
@ -312,13 +316,13 @@ module Homebrew
end end
if new_formula_version < old_formula_version if new_formula_version < old_formula_version
formula.path.atomic_write(old_contents) unless args.dry_run? formula.path.atomic_write(old_contents) unless read_only_run
odie <<~EOS odie <<~EOS
You need to bump this formula manually since changing the You need to bump this formula manually since changing the
version from #{old_formula_version} to #{new_formula_version} would be a downgrade. version from #{old_formula_version} to #{new_formula_version} would be a downgrade.
EOS EOS
elsif new_formula_version == old_formula_version elsif new_formula_version == old_formula_version
formula.path.atomic_write(old_contents) unless args.dry_run? formula.path.atomic_write(old_contents) unless read_only_run
odie <<~EOS odie <<~EOS
You need to bump this formula manually since the new version You need to bump this formula manually since the new version
and old version are both #{new_formula_version}. and old version are both #{new_formula_version}.
@ -331,14 +335,14 @@ module Homebrew
alias_rename.map! { |a| formula.tap.alias_dir/a } alias_rename.map! { |a| formula.tap.alias_dir/a }
end end
ohai "brew update-python-resources #{formula.name}" unless read_only_run
if !args.dry_run? || (args.dry_run? && args.write?) PyPI.update_python_resources! formula, new_formula_version, silent: args.quiet?, ignore_non_pypi_packages: true
PyPI.update_python_resources! formula, new_formula_version, silent: true, ignore_non_pypi_packages: true
end end
run_audit(formula, alias_rename, old_contents, args: args) run_audit(formula, alias_rename, old_contents, args: args)
formula.path.parent.cd do formula.path.parent.cd do
_, base_branch = origin_branch.split("/")
branch = "bump-#{formula.name}-#{new_formula_version}" branch = "bump-#{formula.name}-#{new_formula_version}"
git_dir = Utils.popen_read("git rev-parse --git-dir").chomp git_dir = Utils.popen_read("git rev-parse --git-dir").chomp
shallow = !git_dir.empty? && File.exist?("#{git_dir}/shallow") shallow = !git_dir.empty? && File.exist?("#{git_dir}/shallow")
@ -354,7 +358,7 @@ module Homebrew
"#{new_formula_version}' -- #{changed_files.join(" ")}" "#{new_formula_version}' -- #{changed_files.join(" ")}"
ohai "git push --set-upstream $HUB_REMOTE #{branch}:#{branch}" ohai "git push --set-upstream $HUB_REMOTE #{branch}:#{branch}"
ohai "git checkout --quiet #{previous_branch}" ohai "git checkout --quiet #{previous_branch}"
ohai "create pull request with GitHub API" ohai "create pull request with GitHub API (base branch: #{base_branch})"
else else
if args.no_fork? if args.no_fork?
@ -387,7 +391,7 @@ module Homebrew
begin begin
url = GitHub.create_pull_request(tap_full_name, pr_title, url = GitHub.create_pull_request(tap_full_name, pr_title,
"#{username}:#{branch}", "master", pr_message)["html_url"] "#{username}:#{branch}", base_branch, pr_message)["html_url"]
if args.no_browse? if args.no_browse?
puts url puts url
else else
@ -452,7 +456,8 @@ module Homebrew
end end
def inreplace_pairs(path, replacement_pairs, args:) def inreplace_pairs(path, replacement_pairs, args:)
if args.dry_run? read_only_run = args.dry_run? && !args.write?
if read_only_run
str = path.open("r") { |f| Formulary.ensure_utf8_encoding(f).read } str = path.open("r") { |f| Formulary.ensure_utf8_encoding(f).read }
contents = StringInreplaceExtension.new(str) contents = StringInreplaceExtension.new(str)
replacement_pairs.each do |old, new| replacement_pairs.each do |old, new|
@ -494,14 +499,14 @@ module Homebrew
check_for_duplicate_pull_requests(pull_requests, args: args) check_for_duplicate_pull_requests(pull_requests, args: args)
end end
def check_all_pull_requests(formula, tap_full_name, version: nil, url: nil, tag: nil, args:) def check_closed_pull_requests(formula, tap_full_name, version: nil, url: nil, tag: nil, args:)
unless version unless version
specs = {} specs = {}
specs[:tag] = tag if tag specs[:tag] = tag if tag
version = Version.detect(url, specs) version = Version.detect(url, specs)
end end
# if we haven't already found open requests, try for an exact match across all requests # if we haven't already found open requests, try for an exact match across closed requests
pull_requests = GitHub.fetch_pull_requests("#{formula.name} #{version}", tap_full_name) if pull_requests.blank? pull_requests = GitHub.fetch_pull_requests("#{formula.name} #{version}", tap_full_name, state: "closed")
check_for_duplicate_pull_requests(pull_requests, args: args) check_for_duplicate_pull_requests(pull_requests, args: args)
end end

View File

@ -54,7 +54,7 @@ module Homebrew
require "formula" require "formula"
require "keg" require "keg"
require "cask/all" require "cask"
ohai "Interactive Homebrew Shell" ohai "Interactive Homebrew Shell"
puts "Example commands available with: brew irb --examples" puts "Example commands available with: brew irb --examples"

View File

@ -116,7 +116,7 @@ module Homebrew
# git cherry-pick unfortunately has no quiet option # git cherry-pick unfortunately has no quiet option
ohai "Cherry-picking #{commit_count} commit#{"s" unless commit_count == 1} from ##{pr}" ohai "Cherry-picking #{commit_count} commit#{"s" unless commit_count == 1} from ##{pr}"
cherry_pick_args = "git", "-C", path, "cherry-pick", "--ff", "--allow-empty", "#{merge_base}..FETCH_HEAD" cherry_pick_args = "git", "-C", path, "cherry-pick", "--ff", "--allow-empty", "#{merge_base}..FETCH_HEAD"
result = Homebrew.args.verbose? ? system(*cherry_pick_args) : quiet_system(*cherry_pick_args) result = args.verbose? ? system(*cherry_pick_args) : quiet_system(*cherry_pick_args)
unless result unless result
if args.resolve? if args.resolve?

View File

@ -8,20 +8,19 @@ module Homebrew
def prof_args def prof_args
Homebrew::CLI::Parser.new do Homebrew::CLI::Parser.new do
usage_banner <<~EOS usage_banner <<~EOS
`prof` <command> `prof` [<command>]
Run Homebrew with the Ruby profiler, e.g. `brew prof readall`. Run Homebrew with the Ruby profiler, e.g. `brew prof readall`.
EOS EOS
min_named 1
end end
end end
def prof def prof
prof_args.parse args = prof_args.parse
Homebrew.install_gem_setup_path! "ruby-prof", version: "0.18.0" Homebrew.install_gem_setup_path! "ruby-prof", version: "0.18.0"
FileUtils.mkdir_p "prof" FileUtils.mkdir_p "prof"
brew_rb = (HOMEBREW_LIBRARY_PATH/"brew.rb").resolved_path brew_rb = (HOMEBREW_LIBRARY_PATH/"brew.rb").resolved_path
safe_system "ruby-prof", "--printer=multi", "--file=prof", brew_rb, "--", *ARGV safe_system "ruby-prof", "--printer=multi", "--file=prof", brew_rb, "--", *args.named
end end
end end

View File

@ -50,7 +50,9 @@ module Homebrew
only_cops = args.only_cops only_cops = args.only_cops
except_cops = args.except_cops except_cops = args.except_cops
options = { fix: args.fix?, display_cop_names: args.display_cop_names? } options = {
fix: args.fix?, display_cop_names: args.display_cop_names?, debug: args.debug?, verbose: args.verbose?
}
if only_cops if only_cops
options[:only_cops] = only_cops options[:only_cops] = only_cops
elsif except_cops elsif except_cops

View File

@ -53,23 +53,43 @@ module Homebrew
pull_request: [] pull_request: []
jobs: jobs:
test-bot: test-bot:
runs-on: macos-latest runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macOS-latest]
steps: steps:
- name: Set up Git repository - name: Set up Homebrew
uses: actions/checkout@v2 id: set-up-homebrew
- name: Run brew test-bot uses: Homebrew/actions/setup-homebrew@master
run: |
set -e - name: Cache Homebrew Bundler RubyGems
brew update id: cache
HOMEBREW_TAP_DIR="/usr/local/Homebrew/Library/Taps/#{tap.full_name}" uses: actions/cache@main
mkdir -p "$HOMEBREW_TAP_DIR" with:
rm -rf "$HOMEBREW_TAP_DIR" path: ${{ steps.set-up-homebrew.outputs.gems-path }}
ln -s "$PWD" "$HOMEBREW_TAP_DIR" key: ${{ runner.os }}-rubygems-${{ steps.set-up-homebrew.outputs.gems-hash }}
brew test-bot restore-keys: ${{ runner.os }}-rubygems-
- name: Install Homebrew Bundler RubyGems
if: steps.cache.outputs.cache-hit != 'true'
run: brew install-bundler-gems
- name: Run brew test-bot --only-cleanup-before
run: brew test-bot --only-cleanup-before
- name: Run brew test-bot --only-setup
run: brew test-bot --only-setup
- name: Run brew test-bot --only-tap-syntax
run: brew test-bot --only-tap-syntax
- name: Run brew test-bot --only-formulae
if: github.event_name == 'pull_request'
run: brew test-bot --only-formulae
YAML YAML
(tap.path/".github/workflows").mkpath (tap.path/".github/workflows").mkpath
write_path(tap, ".github/workflows/main.yml", actions) write_path(tap, ".github/workflows/tests.yml", actions)
ohai "Created #{tap}" ohai "Created #{tap}"
puts tap.path.to_s puts tap.path.to_s
end end

View File

@ -1,14 +1,11 @@
# frozen_string_literal: true # frozen_string_literal: true
require "cli/parser" require "cli/parser"
require "utils/github" require "utils/spdx"
module Homebrew module Homebrew
module_function module_function
SPDX_PATH = (HOMEBREW_LIBRARY_PATH/"data/spdx.json").freeze
SPDX_API_URL = "https://api.github.com/repos/spdx/license-list-data/releases/latest"
def update_license_data_args def update_license_data_args
Homebrew::CLI::Parser.new do Homebrew::CLI::Parser.new do
usage_banner <<~EOS usage_banner <<~EOS
@ -29,16 +26,14 @@ module Homebrew
args = update_license_data_args.parse args = update_license_data_args.parse
ohai "Updating SPDX license data..." ohai "Updating SPDX license data..."
latest_tag = GitHub.open_api(SPDX_API_URL)["tag_name"] SPDX.download_latest_license_data!
data_url = "https://raw.githubusercontent.com/spdx/license-list-data/#{latest_tag}/json/licenses.json"
curl_download(data_url, to: SPDX_PATH, partial: false)
Homebrew.failed = system("git", "diff", "--stat", "--exit-code", SPDX_PATH) if args.fail_if_not_changed? Homebrew.failed = system("git", "diff", "--stat", "--exit-code", SPDX::JSON_PATH) if args.fail_if_not_changed?
return unless args.commit? return unless args.commit?
ohai "git add" ohai "git add"
safe_system "git", "add", SPDX_PATH safe_system "git", "add", SPDX::JSON_PATH
ohai "git commit" ohai "git commit"
system "git", "commit", "--message", "data/spdx.json: update to #{latest_tag}" system "git", "commit", "--message", "data/spdx.json: update to #{latest_tag}"
end end

View File

@ -691,7 +691,7 @@ module Homebrew
# these will result in uncommitted gems. # these will result in uncommitted gems.
if path == HOMEBREW_REPOSITORY if path == HOMEBREW_REPOSITORY
next if ENV["HOMEBREW_SORBET"] || ENV["HOMEBREW_PATCHELF_RB"] next if ENV["HOMEBREW_SORBET"]
end end
message ||= "" message ||= ""
@ -907,7 +907,7 @@ module Homebrew
def check_cask_staging_location def check_cask_staging_location
# Skip this check when running CI since the staging path is not writable for security reasons # Skip this check when running CI since the staging path is not writable for security reasons
return if ENV["HOMEBREW_GITHUB_ACTIONS"] return if ENV["GITHUB_ACTIONS"]
path = Cask::Caskroom.path path = Cask::Caskroom.path

View File

@ -13,6 +13,7 @@ require "mechanize/http/content_disposition_parser"
class AbstractDownloadStrategy class AbstractDownloadStrategy
extend Forwardable extend Forwardable
include FileUtils include FileUtils
include Context
module Pourable module Pourable
def stage def stage
@ -21,9 +22,9 @@ class AbstractDownloadStrategy
end end
end end
attr_reader :cache, :cached_location, :url, :meta, :name, :version, :shutup attr_reader :cache, :cached_location, :url, :meta, :name, :version
private :meta, :name, :version, :shutup private :meta, :name, :version
def initialize(url, name, version, **meta) def initialize(url, name, version, **meta)
@url = url @url = url
@ -31,24 +32,18 @@ class AbstractDownloadStrategy
@version = version @version = version
@cache = meta.fetch(:cache, HOMEBREW_CACHE) @cache = meta.fetch(:cache, HOMEBREW_CACHE)
@meta = meta @meta = meta
@shutup = false
extend Pourable if meta[:bottle] extend Pourable if meta[:bottle]
end end
# Download and cache the resource as {#cached_location}. # Download and cache the resource as {#cached_location}.
def fetch; end def fetch; end
# Suppress output
def shutup!
@shutup = true
end
def puts(*args) def puts(*args)
super(*args) unless shutup super(*args) unless quiet?
end end
def ohai(*args) def ohai(*args)
super(*args) unless shutup super(*args) unless quiet?
end end
# Unpack {#cached_location} into the current working directory, and possibly # Unpack {#cached_location} into the current working directory, and possibly
@ -60,7 +55,7 @@ class AbstractDownloadStrategy
ref_type: @ref_type, ref: @ref) ref_type: @ref_type, ref: @ref)
.extract_nestedly(basename: basename, .extract_nestedly(basename: basename,
prioritise_extension: true, prioritise_extension: true,
verbose: Homebrew.args.verbose? && !shutup) verbose: verbose? && !quiet?)
chdir chdir
end end
@ -102,9 +97,9 @@ class AbstractDownloadStrategy
def system_command!(*args, **options) def system_command!(*args, **options)
super( super(
*args, *args,
print_stdout: !shutup, print_stdout: !quiet?,
print_stderr: !shutup, print_stderr: !quiet?,
verbose: Homebrew.args.verbose? && !shutup, verbose: verbose? && !quiet?,
env: env, env: env,
**options, **options,
) )
@ -498,7 +493,7 @@ class NoUnzipCurlDownloadStrategy < CurlDownloadStrategy
def stage def stage
UnpackStrategy::Uncompressed.new(cached_location) UnpackStrategy::Uncompressed.new(cached_location)
.extract(basename: basename, .extract(basename: basename,
verbose: Homebrew.args.verbose? && !shutup) verbose: verbose? && !quiet?)
end end
end end
@ -553,7 +548,7 @@ class SubversionDownloadStrategy < VCSDownloadStrategy
# This saves on bandwidth and will have a similar effect to verifying the # This saves on bandwidth and will have a similar effect to verifying the
# cache as it will make any changes to get the right revision. # cache as it will make any changes to get the right revision.
args = [] args = []
args << "--quiet" unless Homebrew.args.verbose? args << "--quiet" unless verbose?
if revision if revision
ohai "Checking out #{@ref}" ohai "Checking out #{@ref}"
@ -897,7 +892,7 @@ class CVSDownloadStrategy < VCSDownloadStrategy
end end
def quiet_flag def quiet_flag
"-Q" unless Homebrew.args.verbose? "-Q" unless verbose?
end end
def clone_repo def clone_repo

View File

@ -54,6 +54,9 @@ module Homebrew
"Linux: `$XDG_CACHE_HOME/Homebrew` or `$HOME/.cache/Homebrew`.", "Linux: `$XDG_CACHE_HOME/Homebrew` or `$HOME/.cache/Homebrew`.",
default: HOMEBREW_DEFAULT_CACHE, default: HOMEBREW_DEFAULT_CACHE,
}, },
HOMEBREW_CASK_OPTS: {
description: "Options which should be used for all `cask` commands.",
},
HOMEBREW_CLEANUP_MAX_AGE_DAYS: { HOMEBREW_CLEANUP_MAX_AGE_DAYS: {
description: "Cleanup all cached files older than this many days.", description: "Cleanup all cached files older than this many days.",
default: 120, default: 120,
@ -293,7 +296,7 @@ module Homebrew
end end
elsif hash[:default].present? elsif hash[:default].present?
# Needs a custom implementation. # Needs a custom implementation.
next if env == "HOMEBREW_MAKE_JOBS" next if ["HOMEBREW_MAKE_JOBS", "HOMEBREW_CASK_OPTS"].include?(env)
define_method(method_name) do define_method(method_name) do
ENV[env].presence || hash.fetch(:default).to_s ENV[env].presence || hash.fetch(:default).to_s
@ -315,5 +318,31 @@ module Homebrew
.call .call
.to_s .to_s
end end
def cask_opts
Shellwords.shellsplit(ENV.fetch("HOMEBREW_CASK_OPTS", ""))
end
def cask_opts_binaries?
cask_opts.reverse_each do |opt|
return true if opt == "--binaries"
return false if opt == "--no-binaries"
end
true
end
def cask_opts_quarantine?
cask_opts.reverse_each do |opt|
return true if opt == "--quarantine"
return false if opt == "--no-quarantine"
end
true
end
def cask_opts_require_sha?
cask_opts.include?("--require-sha")
end
end end
end end

View File

@ -247,17 +247,6 @@ class TapAlreadyTappedError < RuntimeError
end end
end end
class TapPinStatusError < RuntimeError
attr_reader :name, :pinned
def initialize(name, pinned)
@name = name
@pinned = pinned
super pinned ? "#{name} is already pinned." : "#{name} is already unpinned."
end
end
class OperationInProgressError < RuntimeError class OperationInProgressError < RuntimeError
def initialize(name) def initialize(name)
message = <<~EOS message = <<~EOS
@ -367,10 +356,10 @@ class BuildError < RuntimeError
[] []
end end
def dump def dump(verbose: false)
puts puts
if Homebrew.args.verbose? if verbose
require "system_config" require "system_config"
require "build_environment" require "build_environment"

View File

@ -73,7 +73,7 @@ module Homebrew
# `brew test-bot` runs `brew doctor` in the CI for the Homebrew/brew # `brew test-bot` runs `brew doctor` in the CI for the Homebrew/brew
# repository. This only needs to support whatever CI providers # repository. This only needs to support whatever CI providers
# Homebrew/brew is currently using. # Homebrew/brew is currently using.
return if ENV["HOMEBREW_GITHUB_ACTIONS"] return if ENV["GITHUB_ACTIONS"]
message = <<~EOS message = <<~EOS
Your Xcode (#{MacOS::Xcode.version}) is outdated. Your Xcode (#{MacOS::Xcode.version}) is outdated.
@ -100,7 +100,7 @@ module Homebrew
# `brew test-bot` runs `brew doctor` in the CI for the Homebrew/brew # `brew test-bot` runs `brew doctor` in the CI for the Homebrew/brew
# repository. This only needs to support whatever CI providers # repository. This only needs to support whatever CI providers
# Homebrew/brew is currently using. # Homebrew/brew is currently using.
return if ENV["HOMEBREW_GITHUB_ACTIONS"] return if ENV["GITHUB_ACTIONS"]
<<~EOS <<~EOS
A newer Command Line Tools release is available. A newer Command Line Tools release is available.

View File

@ -374,8 +374,11 @@ class Pathname
# Writes an exec script that invokes a Java jar # Writes an exec script that invokes a Java jar
def write_jar_script(target_jar, script_name, java_opts = "", java_version: nil) def write_jar_script(target_jar, script_name, java_opts = "", java_version: nil)
(self/script_name).write_env_script "java", "#{java_opts} -jar \"#{target_jar}\"", (self/script_name).write <<~EOS
Language::Java.overridable_java_home_env(java_version) #!/bin/bash
export JAVA_HOME="#{Language::Java.overridable_java_home_env(java_version)[:JAVA_HOME]}"
exec "${JAVA_HOME}/bin/java" #{java_opts} -jar "#{target_jar}" "$@"
EOS
end end
def install_metafiles(from = Pathname.pwd) def install_metafiles(from = Pathname.pwd)
@ -416,6 +419,8 @@ require "extend/os/pathname"
# @private # @private
module ObserverPathnameExtension module ObserverPathnameExtension
class << self class << self
include Context
attr_accessor :n, :d attr_accessor :n, :d
def reset_counts! def reset_counts!
@ -434,8 +439,8 @@ module ObserverPathnameExtension
MAXIMUM_VERBOSE_OUTPUT = 100 MAXIMUM_VERBOSE_OUTPUT = 100
def verbose? def verbose?
return Homebrew.args.verbose? unless ENV["CI"] return super unless ENV["CI"]
return false unless Homebrew.args.verbose? return false unless super
if total < MAXIMUM_VERBOSE_OUTPUT if total < MAXIMUM_VERBOSE_OUTPUT
true true

View File

@ -54,9 +54,11 @@ class Formula
include Utils::Inreplace include Utils::Inreplace
include Utils::Shebang include Utils::Shebang
include Utils::Shell include Utils::Shell
include Context
extend Enumerable extend Enumerable
extend Forwardable extend Forwardable
extend Cachable extend Cachable
extend Predicable
# @!method inreplace(paths, before = nil, after = nil) # @!method inreplace(paths, before = nil, after = nil)
# Actually implemented in {Utils::Inreplace.inreplace}. # Actually implemented in {Utils::Inreplace.inreplace}.
@ -536,8 +538,10 @@ class Formula
return false unless head&.downloader.is_a?(VCSDownloadStrategy) return false unless head&.downloader.is_a?(VCSDownloadStrategy)
downloader = head.downloader downloader = head.downloader
downloader.shutup! unless Homebrew.args.verbose?
downloader.commit_outdated?(version.version.commit) with_context quiet: true do
downloader.commit_outdated?(version.version.commit)
end
end end
# The latest prefix for this formula. Checks for {#head}, then {#devel} # The latest prefix for this formula. Checks for {#head}, then {#devel}
@ -1182,7 +1186,7 @@ class Formula
begin begin
yield self, staging yield self, staging
rescue rescue
staging.retain! if interactive || Homebrew.args.debug? staging.retain! if interactive || debug?
raise raise
ensure ensure
cp Dir["config.log", "CMakeCache.txt"], logs cp Dir["config.log", "CMakeCache.txt"], logs
@ -1831,13 +1835,13 @@ class Formula
end end
end end
rescue Exception # rubocop:disable Lint/RescueException rescue Exception # rubocop:disable Lint/RescueException
staging.retain! if Homebrew.args.debug? staging.retain! if debug?
raise raise
end end
end end
ensure ensure
@testpath = nil
@prefix_returns_versioned_prefix = false @prefix_returns_versioned_prefix = false
@testpath = nil
end end
# @private # @private
@ -1927,13 +1931,12 @@ class Formula
# # If there is a "make", "install" available, please use it! # # If there is a "make", "install" available, please use it!
# system "make", "install"</pre> # system "make", "install"</pre>
def system(cmd, *args) def system(cmd, *args)
verbose = Homebrew.args.verbose?
verbose_using_dots = Homebrew::EnvConfig.verbose_using_dots? verbose_using_dots = Homebrew::EnvConfig.verbose_using_dots?
# remove "boring" arguments so that the important ones are more likely to # remove "boring" arguments so that the important ones are more likely to
# be shown considering that we trim long ohai lines to the terminal width # be shown considering that we trim long ohai lines to the terminal width
pretty_args = args.dup pretty_args = args.dup
unless verbose unless verbose?
case cmd case cmd
when "./configure" when "./configure"
pretty_args -= %w[--disable-dependency-tracking --disable-debug --disable-silent-rules] pretty_args -= %w[--disable-dependency-tracking --disable-debug --disable-silent-rules]
@ -1961,7 +1964,7 @@ class Formula
log.puts Time.now, "", cmd, args, "" log.puts Time.now, "", cmd, args, ""
log.flush log.flush
if verbose if verbose?
rd, wr = IO.pipe rd, wr = IO.pipe
begin begin
pid = fork do pid = fork do
@ -2004,7 +2007,7 @@ class Formula
log_lines = Homebrew::EnvConfig.fail_log_lines log_lines = Homebrew::EnvConfig.fail_log_lines
log.flush log.flush
if !verbose || verbose_using_dots if !verbose? || verbose_using_dots
puts "Last #{log_lines} lines from #{logfn}:" puts "Last #{log_lines} lines from #{logfn}:"
Kernel.system "/usr/bin/tail", "-n", log_lines, logfn Kernel.system "/usr/bin/tail", "-n", log_lines, logfn
end end
@ -2221,6 +2224,8 @@ class Formula
# Multiple licenses means that the software is licensed under multiple licenses. # Multiple licenses means that the software is licensed under multiple licenses.
# Do not use multiple licenses if e.g. different parts are under different licenses. # Do not use multiple licenses if e.g. different parts are under different licenses.
# <pre>license "BSD-2-Clause"</pre> # <pre>license "BSD-2-Clause"</pre>
# <pre>license ["MIT", "GPL-2.0"]</pre>
# <pre>license :public_domain</pre>
def license(args = nil) def license(args = nil)
if args.nil? if args.nil?
@licenses @licenses

View File

@ -2,6 +2,8 @@
module Homebrew module Homebrew
module Assertions module Assertions
include Context
require "test/unit/assertions" require "test/unit/assertions"
include ::Test::Unit::Assertions include ::Test::Unit::Assertions
@ -12,7 +14,7 @@ module Homebrew
assert_equal result, $CHILD_STATUS.exitstatus assert_equal result, $CHILD_STATUS.exitstatus
output output
rescue Test::Unit::AssertionFailedError rescue Test::Unit::AssertionFailedError
puts output if Homebrew.args.verbose? puts output if verbose?
raise raise
end end
@ -28,7 +30,7 @@ module Homebrew
assert_equal result, $CHILD_STATUS.exitstatus unless result.nil? assert_equal result, $CHILD_STATUS.exitstatus unless result.nil?
output output
rescue Test::Unit::AssertionFailedError rescue Test::Unit::AssertionFailedError
puts output if Homebrew.args.verbose? puts output if verbose?
raise raise
end end
end end

View File

@ -51,7 +51,8 @@ class FormulaInstaller
force_bottle: false, force_bottle: false,
include_test_formulae: [], include_test_formulae: [],
build_from_source_formulae: [], build_from_source_formulae: [],
cc: nil) cc: nil,
debug: false, quiet: false, verbose: false)
@formula = formula @formula = formula
@env = nil @env = nil
@force = false @force = false
@ -68,9 +69,9 @@ class FormulaInstaller
@interactive = false @interactive = false
@git = false @git = false
@cc = cc @cc = cc
@verbose = Homebrew.args.verbose? @verbose = verbose
@quiet = Homebrew.args.quiet? @quiet = quiet
@debug = Homebrew.args.debug? @debug = debug
@installed_as_dependency = false @installed_as_dependency = false
@installed_on_request = true @installed_on_request = true
@options = Options.new @options = Options.new
@ -601,15 +602,13 @@ class FormulaInstaller
def fetch_dependency(dep) def fetch_dependency(dep)
df = dep.to_formula df = dep.to_formula
fi = FormulaInstaller.new(df, force_bottle: false, fi = FormulaInstaller.new(df, force_bottle: false,
include_test_formulae: include_test_formulae, include_test_formulae: include_test_formulae,
build_from_source_formulae: build_from_source_formulae) build_from_source_formulae: build_from_source_formulae,
debug: debug?, quiet: quiet?, verbose: verbose?)
fi.force = force? fi.force = force?
fi.keep_tmp = keep_tmp? fi.keep_tmp = keep_tmp?
fi.verbose = verbose?
fi.quiet = quiet?
fi.debug = debug?
# When fetching we don't need to recurse the dependency tree as it's already # When fetching we don't need to recurse the dependency tree as it's already
# been done for us in `compute_dependencies` and there's no requirement to # been done for us in `compute_dependencies` and there's no requirement to
# fetch in a particular order. # fetch in a particular order.
@ -642,9 +641,10 @@ class FormulaInstaller
EOS EOS
end end
fi = FormulaInstaller.new(df, force_bottle: false, fi = FormulaInstaller.new(df, force_bottle: false,
include_test_formulae: include_test_formulae, include_test_formulae: include_test_formulae,
build_from_source_formulae: build_from_source_formulae) build_from_source_formulae: build_from_source_formulae,
debug: debug?, quiet: quiet?, verbose: verbose?)
fi.options |= tab.used_options fi.options |= tab.used_options
fi.options |= Tab.remap_deprecated_options(df.deprecated_options, dep.options) fi.options |= Tab.remap_deprecated_options(df.deprecated_options, dep.options)
@ -652,9 +652,6 @@ class FormulaInstaller
fi.options &= df.options fi.options &= df.options
fi.force = force? fi.force = force?
fi.keep_tmp = keep_tmp? fi.keep_tmp = keep_tmp?
fi.verbose = verbose?
fi.quiet = quiet?
fi.debug = debug?
fi.link_keg ||= keg_was_linked if keg_had_linked_keg fi.link_keg ||= keg_was_linked if keg_had_linked_keg
fi.installed_as_dependency = true fi.installed_as_dependency = true
fi.installed_on_request = df.any_version_installed? && tab.installed_on_request fi.installed_on_request = df.any_version_installed? && tab.installed_on_request
@ -665,7 +662,7 @@ class FormulaInstaller
rescue Exception => e # rubocop:disable Lint/RescueException rescue Exception => e # rubocop:disable Lint/RescueException
ignore_interrupts do ignore_interrupts do
tmp_keg.rename(installed_keg) if tmp_keg && !installed_keg.directory? tmp_keg.rename(installed_keg) if tmp_keg && !installed_keg.directory?
linked_keg.link if keg_was_linked linked_keg.link(verbose: verbose?) if keg_was_linked
end end
raise unless e.is_a? FormulaInstallationAlreadyAttemptedError raise unless e.is_a? FormulaInstallationAlreadyAttemptedError
@ -845,7 +842,7 @@ class FormulaInstaller
def link(keg) def link(keg)
unless link_keg unless link_keg
begin begin
keg.optlink keg.optlink(verbose: verbose?)
Formula.clear_cache Formula.clear_cache
rescue Keg::LinkError => e rescue Keg::LinkError => e
onoe "Failed to create #{formula.opt_prefix}" onoe "Failed to create #{formula.opt_prefix}"
@ -876,7 +873,7 @@ class FormulaInstaller
backup_dir = HOMEBREW_CACHE/"Backup" backup_dir = HOMEBREW_CACHE/"Backup"
begin begin
keg.link keg.link(verbose: verbose?)
rescue Keg::ConflictError => e rescue Keg::ConflictError => e
conflict_file = e.dst conflict_file = e.dst
if formula.link_overwrite?(conflict_file) && !link_overwrite_backup.key?(conflict_file) if formula.link_overwrite?(conflict_file) && !link_overwrite_backup.key?(conflict_file)
@ -891,8 +888,7 @@ class FormulaInstaller
puts e puts e
puts puts
puts "Possible conflicting files are:" puts "Possible conflicting files are:"
mode = OpenStruct.new(dry_run: true, overwrite: true) keg.link(dry_run: true, overwrite: true, verbose: verbose?)
keg.link(mode)
@show_summary_heading = true @show_summary_heading = true
Homebrew.failed = true Homebrew.failed = true
rescue Keg::LinkError => e rescue Keg::LinkError => e
@ -939,7 +935,7 @@ class FormulaInstaller
log.mkpath if formula.plist.include? log.to_s log.mkpath if formula.plist.include? log.to_s
rescue Exception => e # rubocop:disable Lint/RescueException rescue Exception => e # rubocop:disable Lint/RescueException
onoe "Failed to install plist file" onoe "Failed to install plist file"
ohai e, e.backtrace if debug? odebug e, e.backtrace
Homebrew.failed = true Homebrew.failed = true
end end
@ -949,7 +945,7 @@ class FormulaInstaller
onoe "Failed to fix install linkage" onoe "Failed to fix install linkage"
puts "The formula built, but you may encounter issues using it or linking other" puts "The formula built, but you may encounter issues using it or linking other"
puts "formulae against it." puts "formulae against it."
ohai e, e.backtrace if debug? odebug e, e.backtrace
Homebrew.failed = true Homebrew.failed = true
@show_summary_heading = true @show_summary_heading = true
end end
@ -960,7 +956,7 @@ class FormulaInstaller
rescue Exception => e # rubocop:disable Lint/RescueException rescue Exception => e # rubocop:disable Lint/RescueException
opoo "The cleaning step did not complete successfully" opoo "The cleaning step did not complete successfully"
puts "Still, the installation was successful, so we will link it into your prefix" puts "Still, the installation was successful, so we will link it into your prefix"
ohai e, e.backtrace if debug? odebug e, e.backtrace
Homebrew.failed = true Homebrew.failed = true
@show_summary_heading = true @show_summary_heading = true
end end
@ -996,7 +992,7 @@ class FormulaInstaller
rescue Exception => e # rubocop:disable Lint/RescueException rescue Exception => e # rubocop:disable Lint/RescueException
opoo "The post-install step did not complete successfully" opoo "The post-install step did not complete successfully"
puts "You can try again using `brew postinstall #{formula.full_name}`" puts "You can try again using `brew postinstall #{formula.full_name}`"
ohai e, e.backtrace if debug? || Homebrew::EnvConfig.developer? odebug e, e.backtrace, always_display: Homebrew::EnvConfig.developer?
Homebrew.failed = true Homebrew.failed = true
@show_summary_heading = true @show_summary_heading = true
end end
@ -1129,14 +1125,17 @@ class FormulaInstaller
end end
def forbidden_license_check def forbidden_license_check
forbidden_licenses = Homebrew::EnvConfig.forbidden_licenses.to_s.split(" ") forbidden_licenses = Homebrew::EnvConfig.forbidden_licenses
.to_s
.sub("Public Domain", "public_domain")
.split(" ")
return if forbidden_licenses.blank? return if forbidden_licenses.blank?
compute_dependencies.each do |dep, _| compute_dependencies.each do |dep, _|
next if @ignore_deps next if @ignore_deps
dep_f = dep.to_formula dep_f = dep.to_formula
next unless dep_f.license.all? { |license| forbidden_licenses.include? license } next unless dep_f.license.all? { |license| forbidden_licenses.include?(license.to_s) }
raise CannotInstallFormulaError, <<~EOS raise CannotInstallFormulaError, <<~EOS
The installation of #{formula.name} has a dependency on #{dep.name} where all its licenses are forbidden: #{dep_f.license}. The installation of #{formula.name} has a dependency on #{dep.name} where all its licenses are forbidden: #{dep_f.license}.
@ -1144,7 +1143,7 @@ class FormulaInstaller
end end
return if @only_deps return if @only_deps
return unless formula.license.all? { |license| forbidden_licenses.include? license } return unless formula.license.all? { |license| forbidden_licenses.include?(license.to_s) }
raise CannotInstallFormulaError, <<~EOS raise CannotInstallFormulaError, <<~EOS
#{formula.name}'s licenses are all forbidden: #{formula.license}. #{formula.name}'s licenses are all forbidden: #{formula.license}.

View File

@ -3,6 +3,8 @@
require "formula" require "formula"
class FormulaVersions class FormulaVersions
include Context
IGNORED_EXCEPTIONS = [ IGNORED_EXCEPTIONS = [
ArgumentError, NameError, SyntaxError, TypeError, ArgumentError, NameError, SyntaxError, TypeError,
FormulaSpecificationError, FormulaValidationError, FormulaSpecificationError, FormulaValidationError,
@ -44,7 +46,7 @@ class FormulaVersions
rescue *IGNORED_EXCEPTIONS => e rescue *IGNORED_EXCEPTIONS => e
# We rescue these so that we can skip bad versions and # We rescue these so that we can skip bad versions and
# continue walking the history # continue walking the history
odebug "#{e} in #{name} at revision #{rev}", e.backtrace if Homebrew.args.debug? odebug "#{e} in #{name} at revision #{rev}", e.backtrace if debug?
rescue FormulaUnavailableError rescue FormulaUnavailableError
nil nil
ensure ensure

View File

@ -109,6 +109,8 @@ module Formulary
# A FormulaLoader returns instances of formulae. # A FormulaLoader returns instances of formulae.
# Subclasses implement loaders for particular sources of formulae. # Subclasses implement loaders for particular sources of formulae.
class FormulaLoader class FormulaLoader
include Context
# The formula's name # The formula's name
attr_reader :name attr_reader :name
# The formula's ruby file's path or filename # The formula's ruby file's path or filename
@ -138,7 +140,7 @@ module Formulary
private private
def load_file(flags:) def load_file(flags:)
$stderr.puts "#{$PROGRAM_NAME} (#{self.class.name}): loading #{path}" if Homebrew.args.debug? $stderr.puts "#{$PROGRAM_NAME} (#{self.class.name}): loading #{path}" if debug?
raise FormulaUnavailableError, name unless path.file? raise FormulaUnavailableError, name unless path.file?
Formulary.load_formula_from_path(name, path, flags: flags) Formulary.load_formula_from_path(name, path, flags: flags)
@ -314,7 +316,7 @@ module Formulary
end end
def klass(flags:) def klass(flags:)
$stderr.puts "#{$PROGRAM_NAME} (#{self.class.name}): loading #{path}" if Homebrew.args.debug? $stderr.puts "#{$PROGRAM_NAME} (#{self.class.name}): loading #{path}" if debug?
namespace = "FormulaNamespace#{Digest::MD5.hexdigest(contents.to_s)}" namespace = "FormulaNamespace#{Digest::MD5.hexdigest(contents.to_s)}"
Formulary.load_formula(name, path, contents, namespace, flags: flags) Formulary.load_formula(name, path, contents, namespace, flags: flags)
end end

View File

@ -37,7 +37,6 @@ require "config"
require "os" require "os"
require "cli/args" require "cli/args"
require "messages" require "messages"
require "system_command"
HOMEBREW_PRODUCT = ENV["HOMEBREW_PRODUCT"] HOMEBREW_PRODUCT = ENV["HOMEBREW_PRODUCT"]
HOMEBREW_VERSION = ENV["HOMEBREW_VERSION"] HOMEBREW_VERSION = ENV["HOMEBREW_VERSION"]
@ -116,6 +115,7 @@ end.compact.freeze
require "set" require "set"
require "context"
require "extend/pathname" require "extend/pathname"
require "extend/module" require "extend/module"
@ -125,6 +125,7 @@ require "active_support/core_ext/object/blank"
require "active_support/core_ext/hash/deep_merge" require "active_support/core_ext/hash/deep_merge"
require "active_support/core_ext/file/atomic" require "active_support/core_ext/file/atomic"
require "system_command"
require "exceptions" require "exceptions"
require "utils" require "utils"

View File

@ -40,7 +40,7 @@ module Homebrew
module Help module Help
module_function module_function
def help(cmd = nil, empty_argv: false, usage_error: nil) def help(cmd = nil, empty_argv: false, usage_error: nil, remaining_args: [])
if cmd.nil? if cmd.nil?
# Handle `brew` (no arguments). # Handle `brew` (no arguments).
if empty_argv if empty_argv
@ -58,7 +58,7 @@ module Homebrew
# Display command-specific (or generic) help in response to `UsageError`. # Display command-specific (or generic) help in response to `UsageError`.
if usage_error if usage_error
$stderr.puts path ? command_help(cmd, path) : HOMEBREW_HELP $stderr.puts path ? command_help(cmd, path, remaining_args: remaining_args) : HOMEBREW_HELP
$stderr.puts $stderr.puts
onoe usage_error onoe usage_error
exit 1 exit 1
@ -68,16 +68,16 @@ module Homebrew
return if path.nil? return if path.nil?
# Display help for internal command (or generic help if undocumented). # Display help for internal command (or generic help if undocumented).
puts command_help(cmd, path) puts command_help(cmd, path, remaining_args: remaining_args)
exit 0 exit 0
end end
def command_help(cmd, path) def command_help(cmd, path, remaining_args:)
# Only some types of commands can have a parser. # Only some types of commands can have a parser.
output = if Commands.valid_internal_cmd?(cmd) || output = if Commands.valid_internal_cmd?(cmd) ||
Commands.valid_internal_dev_cmd?(cmd) || Commands.valid_internal_dev_cmd?(cmd) ||
Commands.external_ruby_v2_cmd_path(cmd) Commands.external_ruby_v2_cmd_path(cmd)
parser_help(path) parser_help(path, remaining_args: remaining_args)
end end
output ||= comment_help(path) output ||= comment_help(path)
@ -90,13 +90,13 @@ module Homebrew
output output
end end
def parser_help(path) def parser_help(path, remaining_args:)
# Let OptionParser generate help text for commands which have a parser. # Let OptionParser generate help text for commands which have a parser.
cmd_parser = CLI::Parser.from_cmd_path(path) cmd_parser = CLI::Parser.from_cmd_path(path)
return unless cmd_parser return unless cmd_parser
# Try parsing arguments here in order to show formula options in help output. # Try parsing arguments here in order to show formula options in help output.
cmd_parser.parse(Homebrew.args.remaining, ignore_invalid_options: true) cmd_parser.parse(remaining_args, ignore_invalid_options: true)
cmd_parser.generate_help_text cmd_parser.generate_help_text
end end

View File

@ -323,7 +323,7 @@ class Keg
EOS EOS
end end
def unlink(mode = OpenStruct.new) def unlink(**options)
ObserverPathnameExtension.reset_counts! ObserverPathnameExtension.reset_counts!
dirs = [] dirs = []
@ -341,7 +341,7 @@ class Keg
next unless dst.symlink? next unless dst.symlink?
next if src != dst.resolved_path next if src != dst.resolved_path
if mode.dry_run if options[:dry_run]
puts dst puts dst
Find.prune if src.directory? Find.prune if src.directory?
next next
@ -354,7 +354,7 @@ class Keg
end end
end end
unless mode.dry_run unless options[:dry_run]
remove_linked_keg_record if linked? remove_linked_keg_record if linked?
dirs.reverse_each(&:rmdir_if_possible) dirs.reverse_each(&:rmdir_if_possible)
end end
@ -436,21 +436,21 @@ class Keg
end end
end end
def link(mode = OpenStruct.new) def link(**options)
raise AlreadyLinkedError, self if linked_keg_record.directory? raise AlreadyLinkedError, self if linked_keg_record.directory?
ObserverPathnameExtension.reset_counts! ObserverPathnameExtension.reset_counts!
optlink(mode) unless mode.dry_run optlink(**options) unless options[:dry_run]
# yeah indeed, you have to force anything you need in the main tree into # yeah indeed, you have to force anything you need in the main tree into
# these dirs REMEMBER that *NOT* everything needs to be in the main tree # these dirs REMEMBER that *NOT* everything needs to be in the main tree
link_dir("etc", mode) { :mkpath } link_dir("etc", **options) { :mkpath }
link_dir("bin", mode) { :skip_dir } link_dir("bin", **options) { :skip_dir }
link_dir("sbin", mode) { :skip_dir } link_dir("sbin", **options) { :skip_dir }
link_dir("include", mode) { :link } link_dir("include", **options) { :link }
link_dir("share", mode) do |relative_path| link_dir("share", **options) do |relative_path|
case relative_path.to_s case relative_path.to_s
when "locale/locale.alias" then :skip_file when "locale/locale.alias" then :skip_file
when INFOFILE_RX then :info when INFOFILE_RX then :info
@ -468,7 +468,7 @@ class Keg
end end
end end
link_dir("lib", mode) do |relative_path| link_dir("lib", **options) do |relative_path|
case relative_path.to_s case relative_path.to_s
when "charset.alias" then :skip_file when "charset.alias" then :skip_file
# pkg-config database gets explicitly created # pkg-config database gets explicitly created
@ -494,7 +494,7 @@ class Keg
end end
end end
link_dir("Frameworks", mode) do |relative_path| link_dir("Frameworks", **options) do |relative_path|
# Frameworks contain symlinks pointing into a subdir, so we have to use # Frameworks contain symlinks pointing into a subdir, so we have to use
# the :link strategy. However, for Foo.framework and # the :link strategy. However, for Foo.framework and
# Foo.framework/Versions we have to use :mkpath so that multiple formulae # Foo.framework/Versions we have to use :mkpath so that multiple formulae
@ -506,9 +506,9 @@ class Keg
end end
end end
make_relative_symlink(linked_keg_record, path, mode) unless mode.dry_run make_relative_symlink(linked_keg_record, path, **options) unless options[:dry_run]
rescue LinkError rescue LinkError
unlink unlink(verbose: options[:verbose])
raise raise
else else
ObserverPathnameExtension.n ObserverPathnameExtension.n
@ -536,19 +536,19 @@ class Keg
tab.aliases || [] tab.aliases || []
end end
def optlink(mode = OpenStruct.new) def optlink(**options)
opt_record.delete if opt_record.symlink? || opt_record.exist? opt_record.delete if opt_record.symlink? || opt_record.exist?
make_relative_symlink(opt_record, path, mode) make_relative_symlink(opt_record, path, **options)
aliases.each do |a| aliases.each do |a|
alias_opt_record = opt_record.parent/a alias_opt_record = opt_record.parent/a
alias_opt_record.delete if alias_opt_record.symlink? || alias_opt_record.exist? alias_opt_record.delete if alias_opt_record.symlink? || alias_opt_record.exist?
make_relative_symlink(alias_opt_record, path, mode) make_relative_symlink(alias_opt_record, path, **options)
end end
return unless oldname_opt_record return unless oldname_opt_record
oldname_opt_record.delete oldname_opt_record.delete
make_relative_symlink(oldname_opt_record, path, mode) make_relative_symlink(oldname_opt_record, path, **options)
end end
def delete_pyc_files! def delete_pyc_files!
@ -558,7 +558,7 @@ class Keg
private private
def resolve_any_conflicts(dst, mode) def resolve_any_conflicts(dst, **options)
return unless dst.symlink? return unless dst.symlink?
src = dst.resolved_path src = dst.resolved_path
@ -571,7 +571,7 @@ class Keg
stat = src.lstat stat = src.lstat
rescue Errno::ENOENT rescue Errno::ENOENT
# dst is a broken symlink, so remove it. # dst is a broken symlink, so remove it.
dst.unlink unless mode.dry_run dst.unlink unless options[:dry_run]
return return
end end
@ -580,25 +580,23 @@ class Keg
begin begin
keg = Keg.for(src) keg = Keg.for(src)
rescue NotAKegError rescue NotAKegError
if Homebrew.args.verbose? puts "Won't resolve conflicts for symlink #{dst} as it doesn't resolve into the Cellar" if options[:verbose]
puts "Won't resolve conflicts for symlink #{dst} as it doesn't resolve into the Cellar"
end
return return
end end
dst.unlink unless mode.dry_run dst.unlink unless options[:dry_run]
keg.link_dir(src, mode) { :mkpath } keg.link_dir(src, **options) { :mkpath }
true true
end end
def make_relative_symlink(dst, src, mode) def make_relative_symlink(dst, src, **options)
if dst.symlink? && src == dst.resolved_path if dst.symlink? && src == dst.resolved_path
puts "Skipping; link already exists: #{dst}" if Homebrew.args.verbose? puts "Skipping; link already exists: #{dst}" if options[:verbose]
return return
end end
# cf. git-clean -n: list files to delete, don't really link or delete # cf. git-clean -n: list files to delete, don't really link or delete
if mode.dry_run && mode.overwrite if options[:dry_run] && options[:overwrite]
if dst.symlink? if dst.symlink?
puts "#{dst} -> #{dst.resolved_path}" puts "#{dst} -> #{dst.resolved_path}"
elsif dst.exist? elsif dst.exist?
@ -608,12 +606,12 @@ class Keg
end end
# list all link targets # list all link targets
if mode.dry_run if options[:dry_run]
puts dst puts dst
return return
end end
dst.delete if mode.overwrite && (dst.exist? || dst.symlink?) dst.delete if options[:overwrite] && (dst.exist? || dst.symlink?)
dst.make_relative_symlink(src) dst.make_relative_symlink(src)
rescue Errno::EEXIST => e rescue Errno::EEXIST => e
raise ConflictError.new(self, src.relative_path_from(path), dst, e) if dst.exist? raise ConflictError.new(self, src.relative_path_from(path), dst, e) if dst.exist?
@ -631,7 +629,7 @@ class Keg
protected protected
# symlinks the contents of path+relative_dir recursively into #{HOMEBREW_PREFIX}/relative_dir # symlinks the contents of path+relative_dir recursively into #{HOMEBREW_PREFIX}/relative_dir
def link_dir(relative_dir, mode) def link_dir(relative_dir, **options)
root = path/relative_dir root = path/relative_dir
return unless root.exist? return unless root.exist?
@ -655,10 +653,10 @@ class Keg
when :info when :info
next if File.basename(src) == "dir" # skip historical local 'dir' files next if File.basename(src) == "dir" # skip historical local 'dir' files
make_relative_symlink dst, src, mode make_relative_symlink dst, src, **options
dst.install_info dst.install_info
else else
make_relative_symlink dst, src, mode make_relative_symlink dst, src, **options
end end
elsif src.directory? elsif src.directory?
# if the dst dir already exists, then great! walk the rest of the tree tho # if the dst dir already exists, then great! walk the rest of the tree tho
@ -672,10 +670,10 @@ class Keg
when :skip_dir when :skip_dir
Find.prune Find.prune
when :mkpath when :mkpath
dst.mkpath unless resolve_any_conflicts(dst, mode) dst.mkpath unless resolve_any_conflicts(dst, **options)
else else
unless resolve_any_conflicts(dst, mode) unless resolve_any_conflicts(dst, **options)
make_relative_symlink dst, src, mode make_relative_symlink dst, src, **options
Find.prune Find.prune
end end
end end

View File

@ -1,13 +1,15 @@
# frozen_string_literal: true # frozen_string_literal: true
# Livecheck can be used to check for newer versions of the software. # The `Livecheck` class implements the DSL methods used in a formula's
# The livecheck DSL specified in the formula is evaluated the methods # `livecheck` block and stores related instance variables. Most of these methods
# of this class, which set the instance variables accordingly. The # also return the related instance variable when no argument is provided.
# information is used by brew livecheck when checking for newer versions #
# of the software. # This information is used by the `brew livecheck` command to control its
# behavior.
class Livecheck class Livecheck
# The reason for skipping livecheck for the formula. # A very brief description of why the formula is skipped (e.g., `No longer
# e.g. `Not maintained` # developed or maintained`).
# @return [String, nil]
attr_reader :skip_msg attr_reader :skip_msg
def initialize(formula) def initialize(formula)
@ -15,52 +17,92 @@ class Livecheck
@regex = nil @regex = nil
@skip = false @skip = false
@skip_msg = nil @skip_msg = nil
@strategy = nil
@url = nil @url = nil
end end
# Sets the regex instance variable to the argument given, returns the # Sets the `@regex` instance variable to the provided `Regexp` or returns the
# regex instance variable when no argument is given. # `@regex` instance variable when no argument is provided.
# @param pattern [Regexp] regex to use for matching versions in content
# @return [Regexp, nil]
def regex(pattern = nil) def regex(pattern = nil)
return @regex if pattern.nil? case pattern
when nil
@regex = pattern @regex
when Regexp
@regex = pattern
else
raise TypeError, "Livecheck#regex expects a Regexp"
end
end end
# Sets the skip instance variable to true, indicating that livecheck # Sets the `@skip` instance variable to `true` and sets the `@skip_msg`
# must be skipped for the formula. If an argument is given and present, # instance variable if a `String` is provided. `@skip` is used to indicate
# its value is assigned to the skip_msg instance variable, else nil is # that the formula should be skipped and the `skip_msg` very briefly describes
# assigned. # why the formula is skipped (e.g., `No longer developed or maintained`).
# @param skip_msg [String] string describing why the formula is skipped
# @return [Boolean]
def skip(skip_msg = nil) def skip(skip_msg = nil)
if skip_msg.is_a?(String)
@skip_msg = skip_msg
elsif skip_msg.present?
raise TypeError, "Livecheck#skip expects a String"
end
@skip = true @skip = true
@skip_msg = skip_msg.presence
end end
# Should livecheck be skipped for the formula? # Should `livecheck` skip this formula?
def skip? def skip?
@skip @skip
end end
# Sets the url instance variable to the argument given, returns the url # Sets the `@strategy` instance variable to the provided `Symbol` or returns
# instance variable when no argument is given. # the `@strategy` instance variable when no argument is provided. The strategy
def url(val = nil) # symbols use snake case (e.g., `:page_match`) and correspond to the strategy
return @url if val.nil? # file name.
# @param symbol [Symbol] symbol for the desired strategy
# @return [Symbol, nil]
def strategy(symbol = nil)
case symbol
when nil
@strategy
when Symbol
@strategy = symbol
else
raise TypeError, "Livecheck#strategy expects a Symbol"
end
end
# Sets the `@url` instance variable to the provided argument or returns the
# `@url` instance variable when no argument is provided. The argument can be
# a `String` (a URL) or a supported `Symbol` corresponding to a URL in the
# formula (e.g., `:stable`, `:homepage`, or `:head`).
# @param val [String, Symbol] URL to check for version information
# @return [String, nil]
def url(val = nil)
@url = case val @url = case val
when nil
return @url
when :head, :stable, :devel when :head, :stable, :devel
@formula.send(val).url @formula.send(val).url
when :homepage when :homepage
@formula.homepage @formula.homepage
else when String
val val
else
raise TypeError, "Livecheck#url expects a String or valid Symbol"
end end
end end
# Returns a Hash of all instance variable values. # Returns a `Hash` of all instance variable values.
# @return [Hash]
def to_hash def to_hash
{ {
"regex" => @regex, "regex" => @regex,
"skip" => @skip, "skip" => @skip,
"skip_msg" => @skip_msg, "skip_msg" => @skip_msg,
"strategy" => @strategy,
"url" => @url, "url" => @url,
} }
end end

View File

@ -5,6 +5,8 @@ require "keg"
require "tab" require "tab"
class Migrator class Migrator
include Context
class MigrationNeededError < RuntimeError class MigrationNeededError < RuntimeError
def initialize(formula) def initialize(formula)
super <<~EOS super <<~EOS
@ -209,7 +211,7 @@ class Migrator
rescue Exception => e # rubocop:disable Lint/RescueException rescue Exception => e # rubocop:disable Lint/RescueException
onoe "Error occurred while migrating." onoe "Error occurred while migrating."
puts e puts e
puts e.backtrace if Homebrew.args.debug? puts e.backtrace if debug?
puts "Backing up..." puts "Backing up..."
ignore_interrupts { backup_oldname } ignore_interrupts { backup_oldname }
ensure ensure
@ -267,7 +269,7 @@ class Migrator
oh1 "Unlinking #{Formatter.identifier(oldname)}" oh1 "Unlinking #{Formatter.identifier(oldname)}"
old_cellar.subdirs.each do |d| old_cellar.subdirs.each do |d|
keg = Keg.new(d) keg = Keg.new(d)
keg.unlink keg.unlink(verbose: verbose?)
end end
end end
@ -275,7 +277,7 @@ class Migrator
oh1 "Temporarily unlinking #{Formatter.identifier(newname)}" oh1 "Temporarily unlinking #{Formatter.identifier(newname)}"
new_cellar.subdirs.each do |d| new_cellar.subdirs.each do |d|
keg = Keg.new(d) keg = Keg.new(d)
keg.unlink keg.unlink(verbose: verbose?)
end end
end end
@ -288,7 +290,7 @@ class Migrator
# If formula is keg-only we also optlink it. # If formula is keg-only we also optlink it.
if formula.keg_only? || !old_linked_keg_record if formula.keg_only? || !old_linked_keg_record
begin begin
new_keg.optlink new_keg.optlink(verbose: verbose?)
rescue Keg::LinkError => e rescue Keg::LinkError => e
onoe "Failed to create #{formula.opt_prefix}" onoe "Failed to create #{formula.opt_prefix}"
raise raise
@ -299,15 +301,13 @@ class Migrator
new_keg.remove_linked_keg_record if new_keg.linked? new_keg.remove_linked_keg_record if new_keg.linked?
begin begin
mode = OpenStruct.new(overwrite: true) new_keg.link(overwrite: true, verbose: verbose?)
new_keg.link(mode)
rescue Keg::ConflictError => e rescue Keg::ConflictError => e
onoe "Error while executing `brew link` step on #{newname}" onoe "Error while executing `brew link` step on #{newname}"
puts e puts e
puts puts
puts "Possible conflicting files are:" puts "Possible conflicting files are:"
mode = OpenStruct.new(dry_run: true, overwrite: true) new_keg.link(dry_run: true, overwrite: true, verbose: verbose?)
new_keg.link(mode)
raise raise
rescue Keg::LinkError => e rescue Keg::LinkError => e
onoe "Error while linking" onoe "Error while linking"
@ -318,8 +318,8 @@ class Migrator
rescue Exception => e # rubocop:disable Lint/RescueException rescue Exception => e # rubocop:disable Lint/RescueException
onoe "An unexpected error occurred during linking" onoe "An unexpected error occurred during linking"
puts e puts e
puts e.backtrace if Homebrew.args.debug? puts e.backtrace if debug?
ignore_interrupts { new_keg.unlink } ignore_interrupts { new_keg.unlink(verbose: verbose?) }
raise raise
end end
end end
@ -384,7 +384,7 @@ class Migrator
if new_cellar.exist? if new_cellar.exist?
new_cellar.subdirs.each do |d| new_cellar.subdirs.each do |d|
newname_keg = Keg.new(d) newname_keg = Keg.new(d)
newname_keg.unlink newname_keg.unlink(verbose: verbose?)
newname_keg.uninstall if new_cellar_existed newname_keg.uninstall if new_cellar_existed
end end
end end
@ -396,16 +396,16 @@ class Migrator
# create a keg using its old path # create a keg using its old path
if old_linked_keg_record if old_linked_keg_record
begin begin
old_linked_keg.link old_linked_keg.link(verbose: verbose?)
rescue Keg::LinkError rescue Keg::LinkError
old_linked_keg.unlink old_linked_keg.unlink(verbose: verbose?)
raise raise
rescue Keg::AlreadyLinkedError rescue Keg::AlreadyLinkedError
old_linked_keg.unlink old_linked_keg.unlink(verbose: verbose?)
retry retry
end end
else else
old_linked_keg.optlink old_linked_keg.optlink(verbose: verbose?)
end end
end end

View File

@ -71,41 +71,19 @@ module ELFShim
def rpath def rpath
return @rpath if defined? @rpath return @rpath if defined? @rpath
@rpath = if HOMEBREW_PATCHELF_RB @rpath = rpath_using_patchelf_rb
rpath_using_patchelf_rb
else
rpath_using_patchelf
end
end end
def interpreter def interpreter
return @interpreter if defined? @interpreter return @interpreter if defined? @interpreter
@interpreter = if HOMEBREW_PATCHELF_RB @interpreter = patchelf_patcher.interpreter
patchelf_patcher.interpreter
elsif (patchelf = DevelopmentTools.locate "patchelf")
interp = Utils.popen_read(patchelf, "--print-interpreter", to_s, err: :out).strip
$CHILD_STATUS.success? ? interp : nil
elsif (file = DevelopmentTools.locate("file"))
output = Utils.popen_read(file, "-L", "-b", to_s, err: :out).strip
output[/^ELF.*, interpreter (.+?), /, 1]
else
raise "Please install either patchelf or file."
end
end end
def dynamic_elf? def dynamic_elf?
return @dynamic_elf if defined? @dynamic_elf return @dynamic_elf if defined? @dynamic_elf
@dynamic_elf = if HOMEBREW_PATCHELF_RB @dynamic_elf = patchelf_patcher.elf.segment_by_type(:DYNAMIC).present?
patchelf_patcher.elf.segment_by_type(:DYNAMIC).present?
elsif which "readelf"
Utils.popen_read("readelf", "-l", to_path).include?(" DYNAMIC ")
elsif which "file"
!Utils.popen_read("file", "-L", "-b", to_path)[/dynamic|shared/].nil?
else
raise "Please install either readelf (from binutils) or file."
end
end end
class Metadata class Metadata
@ -139,81 +117,20 @@ module ELFShim
def needed_libraries(path) def needed_libraries(path)
return [nil, []] unless path.dynamic_elf? return [nil, []] unless path.dynamic_elf?
if HOMEBREW_PATCHELF_RB needed_libraries_using_patchelf_rb path
needed_libraries_using_patchelf_rb path
elsif DevelopmentTools.locate "readelf"
needed_libraries_using_readelf path
elsif DevelopmentTools.locate "patchelf"
needed_libraries_using_patchelf path
else
return [nil, []] if path.basename.to_s == "patchelf"
raise "patchelf must be installed: brew install patchelf"
end
end end
def needed_libraries_using_patchelf_rb(path) def needed_libraries_using_patchelf_rb(path)
patcher = path.patchelf_patcher patcher = path.patchelf_patcher
[patcher.soname, patcher.needed] [patcher.soname, patcher.needed]
end end
def needed_libraries_using_patchelf(path)
patchelf = DevelopmentTools.locate "patchelf"
if path.dylib?
command = [patchelf, "--print-soname", path.expand_path.to_s]
soname = Utils.safe_popen_read(*command).chomp
end
command = [patchelf, "--print-needed", path.expand_path.to_s]
needed = Utils.safe_popen_read(*command).split("\n")
[soname, needed]
end
def needed_libraries_using_readelf(path)
soname = nil
needed = []
command = ["readelf", "-d", path.expand_path.to_s]
lines = Utils.popen_read(*command, err: :out).split("\n")
lines.each do |s|
next if s.start_with?("readelf: Warning: possibly corrupt ELF header")
filename = s[/\[(.*)\]/, 1]
next if filename.nil?
if s.include? "(SONAME)"
soname = filename
elsif s.include? "(NEEDED)"
needed << filename
end
end
[soname, needed]
end
end end
def rpath_using_patchelf_rb def rpath_using_patchelf_rb
patchelf_patcher.runpath || patchelf_patcher.rpath patchelf_patcher.runpath || patchelf_patcher.rpath
end end
def rpath_using_patchelf
patchelf = DevelopmentTools.locate "patchelf"
odie "Could not locate patchelf, please: brew install patchelf." if patchelf.nil?
cmd_rpath = [patchelf, "--print-rpath", to_s]
rpath = Utils.popen_read(*cmd_rpath, err: :out).strip
# patchelf requires that the ELF file have a .dynstr section.
# Skip ELF files that do not have a .dynstr section.
return if ["cannot find section .dynstr", "strange: no string table"].include?(rpath)
unless $CHILD_STATUS.success?
raise ErrorDuringExecution.new(cmd_rpath, status: $CHILD_STATUS, output: [[:stderr, rpath]])
end
rpath unless rpath.blank?
end
def patchelf_patcher def patchelf_patcher
return unless HOMEBREW_PATCHELF_RB
Homebrew.install_bundler_gems! Homebrew.install_bundler_gems!
require "patchelf" require "patchelf"
@patchelf_patcher ||= PatchELF::Patcher.new to_s, on_error: :silent @patchelf_patcher ||= PatchELF::Patcher.new to_s, on_error: :silent

View File

@ -1,8 +1,5 @@
# frozen_string_literal: true # frozen_string_literal: true
# enables experimental readelf.rb, patchelf support.
HOMEBREW_PATCHELF_RB = ENV["HOMEBREW_PATCHELF_RB"].present?.freeze
module Homebrew module Homebrew
DEFAULT_PREFIX ||= if Homebrew::EnvConfig.force_homebrew_on_linux? DEFAULT_PREFIX ||= if Homebrew::EnvConfig.force_homebrew_on_linux?
HOMEBREW_DEFAULT_PREFIX HOMEBREW_DEFAULT_PREFIX

View File

@ -5,7 +5,7 @@ class Keg
return if file.dylib_id == id return if file.dylib_id == id
@require_relocation = true @require_relocation = true
odebug "Changing dylib ID of #{file}\n from #{file.dylib_id}\n to #{id}" if Homebrew.args.debug? odebug "Changing dylib ID of #{file}\n from #{file.dylib_id}\n to #{id}"
MachO::Tools.change_dylib_id(file, id, strict: false) MachO::Tools.change_dylib_id(file, id, strict: false)
rescue MachO::MachOError rescue MachO::MachOError
onoe <<~EOS onoe <<~EOS
@ -20,7 +20,7 @@ class Keg
return if old == new return if old == new
@require_relocation = true @require_relocation = true
odebug "Changing install name in #{file}\n from #{old}\n to #{new}" if Homebrew.args.debug? odebug "Changing install name in #{file}\n from #{old}\n to #{new}"
MachO::Tools.change_install_name(file, old, new, strict: false) MachO::Tools.change_install_name(file, old, new, strict: false)
rescue MachO::MachOError rescue MachO::MachOError
onoe <<~EOS onoe <<~EOS

View File

@ -17,7 +17,7 @@ begin
trap("INT", old_trap) trap("INT", old_trap)
formula = args.resolved_formulae.first formula = args.resolved_formulae.first
formula.extend(Debrew::Formula) if Homebrew.args.debug? formula.extend(Debrew::Formula) if args.debug?
formula.run_post_install formula.run_post_install
rescue Exception => e # rubocop:disable Lint/RescueException rescue Exception => e # rubocop:disable Lint/RescueException
error_pipe.puts e.to_json error_pipe.puts e.to_json

View File

@ -24,7 +24,8 @@ module Homebrew
options &= f.options options &= f.options
fi = FormulaInstaller.new(f, force_bottle: args.force_bottle?, fi = FormulaInstaller.new(f, force_bottle: args.force_bottle?,
build_from_source_formulae: args.build_from_source_formulae) build_from_source_formulae: args.build_from_source_formulae,
debug: args.debug?, quiet: args.quiet?, verbose: args.verbose?)
fi.options = options fi.options = options
fi.force = args.force? fi.force = args.force?
fi.keep_tmp = args.keep_tmp? fi.keep_tmp = args.keep_tmp?
@ -48,7 +49,7 @@ module Homebrew
rescue FormulaInstallationAlreadyAttemptedError rescue FormulaInstallationAlreadyAttemptedError
nil nil
rescue Exception # rubocop:disable Lint/RescueException rescue Exception # rubocop:disable Lint/RescueException
ignore_interrupts { restore_backup(keg, keg_was_linked) } ignore_interrupts { restore_backup(keg, keg_was_linked, verbose: args.verbose?) }
raise raise
else else
begin begin
@ -73,7 +74,7 @@ module Homebrew
end end
end end
def restore_backup(keg, keg_was_linked) def restore_backup(keg, keg_was_linked, verbose:)
path = backup_path(keg) path = backup_path(keg)
return unless path.directory? return unless path.directory?
@ -81,7 +82,7 @@ module Homebrew
Pathname.new(keg).rmtree if keg.exist? Pathname.new(keg).rmtree if keg.exist?
path.rename keg path.rename keg
keg.link if keg_was_linked keg.link(verbose: verbose) if keg_was_linked
end end
def backup_path(path) def backup_path(path)

View File

@ -9,6 +9,7 @@ require "mktemp"
# primary formula download, along with other declared resources, are instances # primary formula download, along with other declared resources, are instances
# of this class. # of this class.
class Resource class Resource
include Context
include FileUtils include FileUtils
attr_reader :mirrors, :specs, :using, :source_modified_time, :patches, :owner attr_reader :mirrors, :specs, :using, :source_modified_time, :patches, :owner
@ -140,7 +141,7 @@ class Resource
def verify_download_integrity(fn) def verify_download_integrity(fn)
if fn.file? if fn.file?
ohai "Verifying #{fn.basename} checksum" if Homebrew.args.verbose? ohai "Verifying #{fn.basename} checksum" if verbose?
fn.verify_checksum(checksum) fn.verify_checksum(checksum)
end end
rescue ChecksumMissingError rescue ChecksumMissingError

View File

@ -29,7 +29,13 @@ module RuboCop
end end
def cask_token def cask_token
@cask_token ||= pair_node.val_node.children.first @cask_token ||= begin
if dsl_version?
pair_node.val_node.children.first
else
method_node.first_argument.str_content
end
end
end end
def hash_node def hash_node

View File

@ -6,7 +6,8 @@ module RuboCop
module Constants module Constants
STANZA_GROUPS = [ STANZA_GROUPS = [
[:version, :sha256], [:version, :sha256],
[:url, :appcast, :name, :homepage], [:language],
[:url, :appcast, :name, :desc, :homepage],
[ [
:auto_updates, :auto_updates,
:conflicts_with, :conflicts_with,
@ -32,6 +33,7 @@ module RuboCop
:service, :service,
:audio_unit_plugin, :audio_unit_plugin,
:vst_plugin, :vst_plugin,
:vst3_plugin,
:artifact, :artifact,
:stage_only, :stage_only,
], ],

View File

@ -0,0 +1,30 @@
# frozen_string_literal: true
require "forwardable"
require "uri"
require "rubocops/cask/mixin/on_desc_stanza"
require "rubocops/shared/desc_helper"
module RuboCop
module Cop
module Cask
# This cop audits `desc` in Casks.
# See the `DescHelper` module for details of the checks.
class Desc < Cop
include OnDescStanza
include DescHelper
def on_desc_stanza(stanza)
name = cask_block.header.cask_token
desc_call = stanza.stanza_node
audit_desc(:cask, name, desc_call)
end
def autocorrect(node)
name = cask_block.header.cask_token
autocorrect_desc(node, name)
end
end
end
end
end

View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
module RuboCop
module Cop
module Cask
# Common functionality for checking desc stanzas.
module OnDescStanza
extend Forwardable
include CaskHelp
def on_cask(cask_block)
@cask_block = cask_block
toplevel_stanzas.select(&:desc?).each do |stanza|
on_desc_stanza(stanza)
end
end
private
attr_reader :cask_block
def_delegators :cask_block,
:toplevel_stanzas
end
end
end
end

View File

@ -6,7 +6,7 @@ module RuboCop
module Cop module Cop
module Cask module Cask
# This cop checks that a cask's stanzas are grouped correctly. # This cop checks that a cask's stanzas are grouped correctly.
# See https://github.com/Homebrew/homebrew-cask/blob/HEAD/CONTRIBUTING.md#stanza-order # See https://github.com/Homebrew/homebrew-cask/blob/HEAD/doc/cask_language_reference/readme.md#stanza-order
# for more info. # for more info.
class StanzaGrouping < Cop class StanzaGrouping < Cop
extend Forwardable extend Forwardable

View File

@ -6,7 +6,7 @@ module RuboCop
module Cop module Cop
module Cask module Cask
# This cop checks that a cask's stanzas are ordered correctly. # This cop checks that a cask's stanzas are ordered correctly.
# See https://github.com/Homebrew/homebrew-cask/blob/HEAD/CONTRIBUTING.md#stanza-order # See https://github.com/Homebrew/homebrew-cask/blob/HEAD/doc/cask_language_reference/readme.md#stanza-order
# for more info. # for more info.
class StanzaOrder < Cop class StanzaOrder < Cop
extend Forwardable extend Forwardable

View File

@ -10,11 +10,13 @@ ensure
end end
require "extend/string" require "extend/string"
require "rubocops/shared/helper_functions"
module RuboCop module RuboCop
module Cop module Cop
class FormulaCop < Cop class FormulaCop < Cop
include RangeHelp include RangeHelp
include HelperFunctions
attr_accessor :file_path attr_accessor :file_path
@ -32,28 +34,6 @@ module RuboCop
audit_formula(node, class_node, parent_class_node, @body) audit_formula(node, class_node, parent_class_node, @body)
end end
# Checks for regex match of pattern in the node and
# sets the appropriate instance variables to report the match
def regex_match_group(node, pattern)
string_repr = string_content(node).encode("UTF-8", invalid: :replace)
match_object = string_repr.match(pattern)
return unless match_object
node_begin_pos = start_column(node)
line_begin_pos = line_start_column(node)
@column = if node_begin_pos == line_begin_pos
node_begin_pos + match_object.begin(0) - line_begin_pos
else
node_begin_pos + match_object.begin(0) - line_begin_pos + 1
end
@length = match_object.to_s.length
@line_no = line_number(node)
@source_buf = source_buffer(node)
@offense_source_range = source_range(@source_buf, @line_no, @column, @length)
@offensive_node = node
match_object
end
# Yields to block when there is a match. # Yields to block when there is a match.
# @param urls [Array] url/mirror method call nodes # @param urls [Array] url/mirror method call nodes
# @param regex [Regexp] pattern to match urls # @param regex [Regexp] pattern to match urls
@ -442,26 +422,11 @@ module RuboCop
end end
end end
# Returns the begin position of the node's line in source code
def line_start_column(node)
node.source_range.source_buffer.line_range(node.loc.line).begin_pos
end
# Returns the begin position of the node in source code
def start_column(node)
node.source_range.begin_pos
end
# Returns the ending position of the node in source code # Returns the ending position of the node in source code
def end_column(node) def end_column(node)
node.source_range.end_pos node.source_range.end_pos
end end
# Returns the line number of the node
def line_number(node)
node.loc.line
end
# Returns the class node's name, nil if not a class node # Returns the class node's name, nil if not a class node
def class_name(node) def class_name(node)
@offensive_node = node @offensive_node = node
@ -484,35 +449,6 @@ module RuboCop
block.loc.end.line - block.loc.begin.line block.loc.end.line - block.loc.begin.line
end end
# Source buffer is required as an argument to report style violations
def source_buffer(node)
node.source_range.source_buffer
end
# Returns the string representation if node is of type str(plain) or dstr(interpolated) or const
def string_content(node)
case node.type
when :str
node.str_content
when :dstr
content = ""
node.each_child_node(:str, :begin) do |child|
content += if child.begin_type?
child.source
else
child.str_content
end
end
content
when :const
node.const_name
when :sym
node.children.first.to_s
else
""
end
end
# Returns true if the formula is versioned # Returns true if the formula is versioned
def versioned_formula? def versioned_formula?
@formula_name.include?("@") @formula_name.include?("@")
@ -532,10 +468,6 @@ module RuboCop
match_obj[1] match_obj[1]
end end
def problem(msg)
add_offense(@offensive_node, location: @offense_source_range, message: msg)
end
private private
def formula_class?(node) def formula_class?(node)

View File

@ -1,105 +1,24 @@
# frozen_string_literal: true # frozen_string_literal: true
require "rubocops/extend/formula" require "rubocops/extend/formula"
require "rubocops/shared/desc_helper"
require "extend/string" require "extend/string"
module RuboCop module RuboCop
module Cop module Cop
module FormulaAudit module FormulaAudit
# This cop audits `desc` in Formulae. # This cop audits `desc` in Formulae.
# # See the `DescHelper` module for details of the checks.
# - Checks for existence of `desc`
# - Checks if size of `desc` > 80
# - Checks for leading/trailing whitespace in `desc`
# - Checks if `desc` begins with an article
# - Checks for correct usage of `command-line` in `desc`
# - Checks description starts with a capital letter
# - Checks if `desc` contains the formula name
# - Checks if `desc` ends with a full stop (apart from in the case of "etc.")
class Desc < FormulaCop class Desc < FormulaCop
VALID_LOWERCASE_WORDS = %w[ include DescHelper
macOS
].freeze
def audit_formula(_node, _class_node, _parent_class_node, body_node) def audit_formula(_node, _class_node, _parent_class_node, body_node)
desc_call = find_node_method_by_name(body_node, :desc) desc_call = find_node_method_by_name(body_node, :desc)
audit_desc(:formula, @formula_name, desc_call)
# Check if a formula's desc is present
if desc_call.nil?
problem "Formula should have a desc (Description)."
return
end
desc = parameters(desc_call).first
# Check the formula's desc length. Should be >0 and <80 characters.
pure_desc_length = string_content(desc).length
if pure_desc_length.zero?
problem "The desc (description) should not be an empty string."
return
end
# Check for leading whitespace.
problem "Description shouldn't have a leading space" if regex_match_group(desc, /^\s+/)
# Check for trailing whitespace.
problem "Description shouldn't have a trailing space" if regex_match_group(desc, /\s+$/)
# Check if command-line is wrongly used in formula's desc
if match = regex_match_group(desc, /(command ?line)/i)
c = match.to_s[0]
problem "Description should use \"#{c}ommand-line\" instead of \"#{match}\""
end
# Check if a/an are used in a formula's desc
if match = regex_match_group(desc, /^(an?)\s/i)
problem "Description shouldn't start with an indefinite article, i.e. \"#{match.to_s.strip}\""
end
# Check if invalid uppercase words are at the start of a
# formula's desc
if !VALID_LOWERCASE_WORDS.include?(string_content(desc).split.first) &&
regex_match_group(desc, /^[a-z]/)
problem "Description should start with a capital letter"
end
# Check if formula's desc starts with formula's name
if regex_match_group(desc, /^#{@formula_name} /i)
problem "Description shouldn't start with the formula name"
end
# Check if a full stop is used at the end of a formula's desc (apart from in the case of "etc.")
if regex_match_group(desc, /\.$/) && !string_content(desc).end_with?("etc.")
problem "Description shouldn't end with a full stop"
end
desc_length = "#{@formula_name}: #{string_content(desc)}".length
max_desc_length = 80
return if desc_length <= max_desc_length
problem "Description is too long. \"name: desc\" should be less than #{max_desc_length} characters. " \
"Length is calculated as #{@formula_name} + desc. (currently #{desc_length})"
end end
def autocorrect(node) def autocorrect(node)
lambda do |corrector| autocorrect_desc(node, @formula_name)
correction = node.source
first_word = string_content(node).split.first
unless VALID_LOWERCASE_WORDS.include?(first_word)
first_char = first_word.to_s[0]
correction.sub!(/^(['"]?)([a-z])/, "\\1#{first_char.upcase}") if first_char
end
correction.sub!(/^(['"]?)an?\s/i, "\\1")
correction.gsub!(/(ommand ?line)/i, "ommand-line")
correction.gsub!(/(^|[^a-z])#{@formula_name}([^a-z]|$)/i, "\\1\\2")
correction.gsub!(/^(['"]?)\s+/, "\\1")
correction.gsub!(/\s+(['"]?)$/, "\\1")
correction.gsub!(/\.(['"]?)$/, "\\1")
correction.gsub!(/^\s+/, "")
correction.gsub!(/\s+$/, "")
corrector.insert_before(node.source_range, correction)
corrector.remove(node.source_range)
end
end end
end end
end end

View File

@ -58,6 +58,14 @@ module RuboCop
when %r{^http://([^/]*)\.(sf|sourceforge)\.net(/|$)} when %r{^http://([^/]*)\.(sf|sourceforge)\.net(/|$)}
problem "#{homepage} should be `https://#{Regexp.last_match(1)}.sourceforge.io/`" problem "#{homepage} should be `https://#{Regexp.last_match(1)}.sourceforge.io/`"
when /readthedocs\.org/
offending_node(parameters(homepage_node).first)
problem "#{homepage} should be `#{homepage.sub("readthedocs.org", "readthedocs.io")}`"
when %r{^https://github.com.*\.git}
offending_node(parameters(homepage_node).first)
problem "GitHub homepages (`#{homepage}`) should not end with .git"
# There's an auto-redirect here, but this mistake is incredibly common too. # There's an auto-redirect here, but this mistake is incredibly common too.
# Only applies to the homepage and subdomains for now, not the FTP URLs. # Only applies to the homepage and subdomains for now, not the FTP URLs.
when %r{^http://((?:build|cloud|developer|download|extensions|git| when %r{^http://((?:build|cloud|developer|download|extensions|git|
@ -80,6 +88,17 @@ module RuboCop
problem "Please use https:// for #{homepage}" problem "Please use https:// for #{homepage}"
end end
end end
def autocorrect(node)
lambda do |corrector|
return if node.nil?
homepage = string_content(node)
homepage.sub!("readthedocs.org", "readthedocs.io")
homepage.delete_suffix!(".git") if homepage.start_with?("https://github.com")
corrector.replace(node.source_range, "\"#{homepage}\"")
end
end
end end
end end
end end

View File

@ -284,9 +284,7 @@ module RuboCop
end end
end end
find_instance_call(body_node, "ARGV") do |method_node| find_instance_call(body_node, "ARGV") do |_method_node|
next if [:debug?, :verbose?, :value].index(method_node.method_name)
problem "Use build instead of ARGV to check options" problem "Use build instead of ARGV to check options"
end end

View File

@ -11,6 +11,7 @@ require "rubocops/cask/extend/string"
require "rubocops/cask/extend/node" require "rubocops/cask/extend/node"
require "rubocops/cask/mixin/cask_help" require "rubocops/cask/mixin/cask_help"
require "rubocops/cask/mixin/on_homepage_stanza" require "rubocops/cask/mixin/on_homepage_stanza"
require "rubocops/cask/desc"
require "rubocops/cask/homepage_matches_url" require "rubocops/cask/homepage_matches_url"
require "rubocops/cask/homepage_url_trailing_slash" require "rubocops/cask/homepage_url_trailing_slash"
require "rubocops/cask/no_dsl_version" require "rubocops/cask/no_dsl_version"

View File

@ -0,0 +1,99 @@
# frozen_string_literal: true
require "rubocops/shared/helper_functions"
module RuboCop
module Cop
# This module performs common checks the `desc` field in both Formulae and Casks.
module DescHelper
include HelperFunctions
VALID_LOWERCASE_WORDS = %w[
macOS
].freeze
def audit_desc(type, name, desc_call)
# Check if a desc is present.
if desc_call.nil?
problem "#{type.to_s.capitalize} should have a desc (Description)."
return
end
desc = desc_call.first_argument
# Check if the desc is empty.
pure_desc_length = string_content(desc).length
if pure_desc_length.zero?
problem "The desc (description) should not be an empty string."
return
end
# Check the desc for leading whitespace.
problem "Description shouldn't have leading spaces." if regex_match_group(desc, /^\s+/)
# Check the desc for trailing whitespace.
problem "Description shouldn't have trailing spaces." if regex_match_group(desc, /\s+$/)
# Check if "command-line" is spelled incorrectly in the desc.
if match = regex_match_group(desc, /(command ?line)/i)
c = match.to_s[0]
problem "Description should use \"#{c}ommand-line\" instead of \"#{match}\"."
end
# Check if the desc starts with "A" or "An".
if match = regex_match_group(desc, /^(an?)(?=\s)/i)
problem "Description shouldn't start with an indefinite article, i.e. \"#{match}\"."
end
# Check if invalid lowercase words are at the start of a desc.
if !VALID_LOWERCASE_WORDS.include?(string_content(desc).split.first) &&
regex_match_group(desc, /^[a-z]/)
problem "Description should start with a capital letter."
end
# Check if the desc starts with the formula's or cask's name.
problem "Description shouldn't start with the #{type} name." if regex_match_group(desc, /^#{name} /i)
# Check if a full stop is used at the end of a desc (apart from in the case of "etc.").
if regex_match_group(desc, /\.$/) && !string_content(desc).end_with?("etc.")
problem "Description shouldn't end with a full stop."
end
# Check if the desc length exceeds 80 characters.
desc_length = "#{name}: #{string_content(desc)}".length
max_desc_length = 80
return if desc_length <= max_desc_length
problem "Description is too long. \"name: desc\" should be less than #{max_desc_length} characters. " \
"The current combined length is #{desc_length}."
end
def autocorrect_desc(node, name)
lambda do |corrector|
/\A(?<quote>["'])(?<correction>.*)(?:\k<quote>)\Z/ =~ node.source
next if correction.nil?
correction.gsub!(/^\s+/, "")
correction.gsub!(/\s+$/, "")
correction.sub!(/^an?\s+/i, "")
first_word = correction.split.first
unless VALID_LOWERCASE_WORDS.include?(first_word)
first_char = first_word.to_s[0]
correction[0] = first_char.upcase if first_char
end
correction.gsub!(/(ommand ?line)/i, "ommand-line")
correction.gsub!(/(^|[^a-z])#{name}([^a-z]|$)/i, "\\1\\2")
correction.gsub!(/^\s+/, "")
correction.gsub!(/\s+$/, "")
correction.gsub!(/\.$/, "")
corrector.replace(node.source_range, "#{quote}#{correction}#{quote}")
end
end
end
end
end

View File

@ -0,0 +1,79 @@
# frozen_string_literal: true
module RuboCop
module Cop
module HelperFunctions
include RangeHelp
# Checks for regex match of pattern in the node and
# sets the appropriate instance variables to report the match
def regex_match_group(node, pattern)
string_repr = string_content(node).encode("UTF-8", invalid: :replace)
match_object = string_repr.match(pattern)
return unless match_object
node_begin_pos = start_column(node)
line_begin_pos = line_start_column(node)
@column = if node_begin_pos == line_begin_pos
node_begin_pos + match_object.begin(0) - line_begin_pos
else
node_begin_pos + match_object.begin(0) - line_begin_pos + 1
end
@length = match_object.to_s.length
@line_no = line_number(node)
@source_buf = source_buffer(node)
@offense_source_range = source_range(@source_buf, @line_no, @column, @length)
@offensive_node = node
match_object
end
# Returns the begin position of the node's line in source code
def line_start_column(node)
node.source_range.source_buffer.line_range(node.loc.line).begin_pos
end
# Returns the begin position of the node in source code
def start_column(node)
node.source_range.begin_pos
end
# Returns the line number of the node
def line_number(node)
node.loc.line
end
# Source buffer is required as an argument to report style violations
def source_buffer(node)
node.source_range.source_buffer
end
# Returns the string representation if node is of type str(plain) or dstr(interpolated) or const
def string_content(node)
case node.type
when :str
node.str_content
when :dstr
content = ""
node.each_child_node(:str, :begin) do |child|
content += if child.begin_type?
child.source
else
child.str_content
end
end
content
when :const
node.const_name
when :sym
node.children.first.to_s
else
""
end
end
def problem(msg)
add_offense(@offensive_node, location: @offense_source_range, message: msg)
end
end
end
end

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