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

View File

@ -8,6 +8,9 @@ on:
- Library/Homebrew/extend/os/diagnostic.rb
- Library/Homebrew/extend/os/mac/diagnostic.rb
- Library/Homebrew/os/mac/xcode.rb
env:
HOMEBREW_DEVELOPER: 1
HOMEBREW_NO_AUTO_UPDATE: 1
jobs:
tests:
strategy:
@ -17,21 +20,10 @@ jobs:
runs-on: ${{ matrix.version }}
env:
PATH: '/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin'
HOMEBREW_DEVELOPER: 1
HOMEBREW_NO_ANALYTICS: 1
HOMEBREW_NO_AUTO_UPDATE: 1
steps:
- name: Update Homebrew
run: brew update-reset
- 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: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
- name: 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
if: always()
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
on:
pull_request:
paths:
- .github/workflows/spdx.yml
schedule:
- cron: '0 */12 * * *'
jobs:
spdx:
if: github.repository == 'Homebrew/brew'
runs-on: ubuntu-latest
env:
HOMEBREW_NO_ANALYTICS: 1
HOMEBREW_NO_AUTO_UPDATE: 1
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
- name: Configure Git
- name: Configure Git user
uses: Homebrew/actions/git-user-config@master
with:
username: BrewTestBot
- name: Setup Homebrew
run: |
HOMEBREW_REPOSITORY="$(brew --repo)"
rm -rf "$HOMEBREW_REPOSITORY"
ln -s "$GITHUB_WORKSPACE" "$HOMEBREW_REPOSITORY"
- name: Update license data
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -4,7 +4,7 @@ on:
branches: master
pull_request: []
env:
HOMEBREW_GITHUB_ACTIONS: 1
HOMEBREW_DEVELOPER: 1
HOMEBREW_NO_AUTO_UPDATE: 1
jobs:
tests:
@ -16,72 +16,29 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
run: |
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"
uses: Homebrew/actions/setup-homebrew@master
cd "$HOMEBREW_PREFIX"
sudo mkdir -p bin etc include lib opt sbin share var/homebrew/linked Cellar
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
- name: Configure Git user
uses: Homebrew/actions/git-user-config@master
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
run: brew config
- name: Run brew doctor
run: |
# 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
run: brew doctor
- name: Cache Bundler RubyGems
id: cache
uses: actions/cache@v1
uses: actions/cache@main
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 }}
restore-keys: |
${{ runner.os }}-rubygems-
restore-keys: ${{ runner.os }}-rubygems-
- name: Install Bundler RubyGems
if: steps.cache.outputs.cache-hit != 'true'
@ -90,31 +47,19 @@ jobs:
- name: Check for uncommitted RubyGems
run: git diff --stat --exit-code Library/Homebrew/vendor/bundle/ruby
- name: Install taps
- name: Set up Homebrew official command taps
run: |
# Install taps needed for 'brew tests' and 'brew man'
cd "$(brew --repo)"
# Setup taps needed for 'brew tests' and 'brew man'
brew tap homebrew/bundle
brew update-reset Library/Taps/homebrew/homebrew-bundle
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
cd "$(brew --repo)"
if [ "$RUNNER_OS" = "macOS" ]; then
brew tap homebrew/cask
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
brew update-reset Library/Taps/homebrew/homebrew-cask Library/Taps/homebrew/homebrew-services
else
# Fix permissions for 'brew tests'
sudo chmod -R g-w,o-w /home/linuxbrew /home/runner /opt
brew update-reset Library/Taps/homebrew/homebrew-services
fi
- name: Run brew style
- name: Run brew style on Homebrew/brew
run: brew style --display-cop-names
- name: Run brew man
@ -123,12 +68,9 @@ jobs:
- name: Run brew tests
run: |
# brew tests doesn't like world writable directories
umask 022
# set variables for coverage reporting
export HOMEBREW_CI_BUILD_NUMBER="$GITHUB_REF"
export HOMEBREW_CI_BRANCH="$HEAD_GITHUB_REF"
export HOMEBREW_GITHUB_REPOSITORY="$GITHUB_REPOSITORY"
if [[ "$RUNNER_OS" = "Linux" ]]; then
sudo chmod -R g-w,o-w /home/linuxbrew/.linuxbrew/Homebrew
fi
# don't bother running all tests on both platforms (for speed)
if [ "$RUNNER_OS" = "Linux" ]; then
@ -149,8 +91,6 @@ jobs:
- name: Run brew update-tests
run: |
git config --global user.name "BrewTestBot"
git config --global user.email "homebrew-test-bot@lists.sfconservancy.org"
brew update-test
brew update-test --to-tag
brew update-test --commit=HEAD
@ -160,16 +100,27 @@ jobs:
run: brew readall --aliases
- 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
- name: Run brew style on official taps
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'
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
- name: Run vale for docs linting

View File

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

View File

@ -1,5 +1,9 @@
inherit_from: ./Homebrew/.rubocop.yml
Cask/Desc:
Description: 'Ensure that the desc stanza conforms to various content and style checks.'
Enabled: true
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'
Enabled: true
@ -13,13 +17,15 @@ Cask/NoDslVersion:
Enabled: true
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
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
# 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:
Enabled: false
Metrics/BlockLength:
Enabled: false

View File

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

View File

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

View File

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

View File

@ -4,6 +4,8 @@ require "utils/curl"
require "json"
class Bintray
include Context
API_URL = "https://api.bintray.com"
class Error < RuntimeError
@ -32,8 +34,8 @@ class Bintray
end
curl(*args, url,
show_output: Homebrew.args.verbose?,
secrets: @bintray_key)
show_output: verbose?,
secrets: key)
end
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
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"])
homebrew_path = PATH.new(ENV["HOMEBREW_PATH"])
@ -102,8 +104,8 @@ begin
# - if cmd is Cask, let Cask handle the help command instead
if (empty_argv || help_flag) && cmd != "cask"
require "help"
Homebrew::Help.help cmd, empty_argv: empty_argv
# `Homebrew.help` never returns, except for unknown commands.
Homebrew::Help.help cmd, remaining_args: args.remaining, empty_argv: empty_argv
# `Homebrew::Help.help` never returns, except for unknown commands.
end
if internal_cmd || Commands.external_ruby_v2_cmd_path(cmd)
@ -138,17 +140,17 @@ begin
end
rescue UsageError => e
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
onoe "Kernel.exit" if Homebrew.args.debug? && !e.success?
$stderr.puts e.backtrace if Homebrew.args.debug?
onoe "Kernel.exit" if args.debug? && !e.success?
$stderr.puts e.backtrace if args.debug?
raise
rescue Interrupt
$stderr.puts # seemingly a newline is typical
exit 130
rescue BuildError => e
Utils::Analytics.report_build_error(e)
e.dump
e.dump(verbose: args.verbose?)
if e.formula.head? || e.formula.deprecated? || e.formula.disabled?
$stderr.puts <<~EOS
@ -162,7 +164,7 @@ rescue RuntimeError, SystemCallError => e
raise if e.message.empty?
onoe e
$stderr.puts e.backtrace if Homebrew.args.debug?
$stderr.puts e.backtrace if args.debug?
exit 1
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 " #{Formatter.url(e.issues_url)}"
end
$stderr.puts e.backtrace if Homebrew.args.debug?
$stderr.puts e.backtrace if args.debug?
exit 1
rescue Exception => e # rubocop:disable Lint/RescueException
onoe e

View File

@ -41,6 +41,7 @@ case "$*" in
--prefix) echo "$HOMEBREW_PREFIX"; exit 0 ;;
--cellar) echo "$HOMEBREW_CELLAR"; exit 0 ;;
--repository|--repo) echo "$HOMEBREW_REPOSITORY"; exit 0 ;;
--caskroom) echo "$HOMEBREW_PREFIX/Caskroom"; exit 0 ;;
esac
# 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_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" ]]
then
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.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,
# which is not known until after the formula has been staged.
ENV["HOMEBREW_FORMULA_PREFIX"] = formula.prefix
@ -201,16 +205,15 @@ class Build
else
raise
end
Keg.new(path).optlink
Keg.new(path).optlink(verbose: args.verbose?)
rescue
raise "#{f.opt_prefix} not present or broken\nPlease reinstall #{f.full_name}. Sorry :("
end
end
begin
Homebrew.args = Homebrew::CLI::Parser.new.parse(ARGV.dup.freeze, ignore_invalid_options: true)
args = Homebrew.install_args.parse
Context.current = args.context
error_pipe = UNIXSocket.open(ENV["HOMEBREW_ERROR_PIPE"], &:recv_io)
error_pipe.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)

View File

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

View File

@ -13,7 +13,7 @@ module Cask
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,
token_conflicts: false, online: false, strict: false,
@ -34,6 +34,7 @@ module Cask
check_required_stanzas
check_version
check_sha256
check_desc
check_url
check_generic_artifacts
check_token_valid
@ -279,6 +280,14 @@ module Cask
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
return unless cask.url
@ -339,7 +348,7 @@ module Cask
end
def check_token_valid
return unless @strict
return unless strict?
add_warning "cask token is not lowercase" if cask.token.downcase!
@ -365,7 +374,7 @@ module Cask
end
def check_token_bad_words
return unless @strict
return unless strict?
token = cask.token
@ -467,8 +476,8 @@ module Cask
end
def get_repo_data(regex)
return unless @online
return unless @new_cask
return unless online?
return unless new_cask?
_, user, repo = *regex.match(cask.url.to_s)
_, user, repo = *regex.match(cask.homepage) unless user

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,13 +4,39 @@ module Cask
class Cmd
class Reinstall < Install
def run
casks.each do |cask|
Installer.new(cask, binaries: binaries?,
self.class.reinstall_casks(
*casks,
binaries: binaries?,
verbose: verbose?,
force: force?,
skip_cask_deps: skip_cask_deps?,
require_sha: require_sha?,
quarantine: quarantine?).reinstall
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|
Installer.new(cask,
binaries: binaries,
verbose: verbose,
force: force,
skip_cask_deps: skip_cask_deps,
require_sha: require_sha,
quarantine: quarantine).reinstall
end
end

View File

@ -11,17 +11,28 @@ module Cask
end
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|
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?
# use the same cask file that was used for installation, if possible
cask = CaskLoader.load(cask.installed_caskfile) if cask.installed_caskfile.exist?
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?

View File

@ -1,5 +1,6 @@
# frozen_string_literal: true
require "env_config"
require "cask/config"
module Cask
@ -17,35 +18,69 @@ module Cask
end
def run
outdated_casks = casks(alternative: lambda {
Caskroom.casks.select do |cask|
cask.outdated?(greedy?)
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
}).select do |cask|
raise CaskNotInstalledError, cask unless cask.installed? || force?
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|
cask.outdated?(greedy)
end
else
casks.select do |cask|
raise CaskNotInstalledError, cask unless cask.installed? || force
cask.outdated?(true)
end
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?
verb = dry_run? ? "Would upgrade" : "Upgrading"
return if outdated_casks.empty?
ohai "Casks with `auto_updates` or `version :latest` will not be upgraded" if casks.empty? && !greedy
verb = dry_run ? "Would upgrade" : "Upgrading"
oh1 "#{verb} #{outdated_casks.count} #{"outdated package".pluralize(outdated_casks.count)}:"
caught_exceptions = []
upgradable_casks = outdated_casks.map { |c| [CaskLoader.load(c.installed_caskfile), c] }
puts upgradable_casks
.map { |(old_cask, new_cask)| "#{new_cask.full_name} #{old_cask.version} -> #{new_cask.version}" }
.join(", ")
return if dry_run?
.join("\n")
return if dry_run
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
caught_exceptions << e.exception("#{new_cask.full_name}: #{e}")
next
@ -56,26 +91,29 @@ module Cask
raise caught_exceptions.first if caught_exceptions.count == 1
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}"
old_config = old_cask.config
old_cask_installer =
Installer.new(old_cask, binaries: binaries?,
verbose: verbose?,
force: force?,
Installer.new(old_cask, binaries: binaries,
verbose: verbose,
force: force,
upgrade: true)
new_cask.config = Config.global.merge(old_config)
new_cask_installer =
Installer.new(new_cask, binaries: binaries?,
verbose: verbose?,
force: force?,
skip_cask_deps: skip_cask_deps?,
require_sha: require_sha?,
Installer.new(new_cask, binaries: binaries,
verbose: verbose,
force: force,
skip_cask_deps: skip_cask_deps,
require_sha: require_sha,
upgrade: true,
quarantine: quarantine?)
quarantine: quarantine)
started_upgrade = false
new_artifacts_installed = false

View File

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

View File

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

View File

@ -367,11 +367,10 @@ module Cask
force: false,
).install
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_on_request = false
fi.show_header = true
fi.verbose = verbose?
fi.prelude
fi.fetch
fi.install

View File

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

View File

@ -10,6 +10,8 @@
# * sets permissions on executables
# * removes unresolved symlinks
class Cleaner
include Context
# Create a cleaner for the given formula
def initialize(f)
@f = f
@ -58,7 +60,7 @@ class Cleaner
# actual files gets removed correctly.
dirs.reverse_each do |d|
if d.children.empty?
puts "rmdir: #{d} (empty)" if Homebrew.args.verbose?
puts "rmdir: #{d} (empty)" if verbose?
d.rmdir
end
end
@ -109,7 +111,7 @@ class Cleaner
else
0444
end
if Homebrew.args.debug?
if debug?
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
end

View File

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

View File

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

View File

@ -13,7 +13,7 @@ module Homebrew
def __cache_args
Homebrew::CLI::Parser.new do
usage_banner <<~EOS
`--cache` [<options>] [<formula>]
`--cache` [<options>] [<formula|cask>]
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
require "cask/all"
require "cask"
module Homebrew
module_function

View File

@ -96,7 +96,7 @@ module Homebrew
if args.no_named?
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
end

View File

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

View File

@ -211,7 +211,13 @@ module Homebrew
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?
ohai "Dependencies"

View File

@ -325,7 +325,8 @@ module Homebrew
fi = FormulaInstaller.new(f, force_bottle: args.force_bottle?,
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.env = args.env
fi.force = args.force?

View File

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

View File

@ -11,9 +11,9 @@ module Homebrew
def list_args
Homebrew::CLI::Parser.new do
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.
EOS
@ -32,8 +32,10 @@ module Homebrew
switch "--pinned",
description: "Show the versions of pinned formulae, or only the specified (pinned) "\
"formulae if <formula> are provided. See also `pin`, `unpin`."
switch "--cask",
description: "List casks"
switch "--formula", "--formulae",
description: "List only formulae."
switch "--cask", "--casks",
description: "List only casks."
# passed through to ls
switch "-1",
description: "Force output to be one entry per line. " \
@ -46,7 +48,9 @@ module Homebrew
switch "-t",
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

View File

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

View File

@ -12,15 +12,19 @@ module Homebrew
def outdated_args
Homebrew::CLI::Parser.new do
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.
EOS
switch "-q", "--quiet",
description: "List only the names of outdated kegs (takes precedence over `--verbose`)."
switch "-v", "--verbose",
description: "Include detailed version information."
switch "--formula",
description: "Only output outdated formulae."
switch "--cask",
description: "Only output outdated casks."
flag "--json",
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. " \
@ -31,10 +35,6 @@ module Homebrew
"updates when a new stable or development version has been released."
switch "--greedy",
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 "--formula", "--cask"
@ -94,7 +94,7 @@ module Homebrew
if formula_or_cask.is_a?(Formula)
f = formula_or_cask
if verbose? args: args
if verbose?
outdated_kegs = f.outdated_kegs(fetch_head: args.fetch_HEAD?)
current_version = if f.alias_changed?
@ -122,7 +122,7 @@ module Homebrew
else
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
@ -147,13 +147,13 @@ module Homebrew
else
c = formula_or_cask
c.outdated_info(args.greedy?, verbose?(args: args), true)
c.outdated_info(args.greedy?, verbose?, true)
end
end
end
def verbose?(args:)
($stdout.tty? || args.verbose?) && !args.quiet?
def verbose?
($stdout.tty? || super) && !quiet?
end
def json_version(version)

View File

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

View File

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

View File

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

View File

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

View File

@ -69,15 +69,14 @@ module Homebrew
puts unless i.zero?
info = "#{tap}: "
if tap.installed?
info += tap.pinned? ? "pinned" : "unpinned"
info += ", private" if tap.private?
info += if (contents = tap.contents).empty?
", no commands/casks/formulae"
info += if (contents = tap.contents).blank?
"no commands/casks/formulae"
else
", #{contents.join(", ")}"
contents.join(", ")
end
info += ", private" if tap.private?
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
info += "Not installed"
end

View File

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

View File

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

View File

@ -120,7 +120,7 @@ module Homebrew
else
hub.dump(updated_formula_report: !args.preinstall?)
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|
DescriptionCacheStore.new(db)
.update_from_report!(hub)
@ -371,7 +371,7 @@ class Reporter
end
end
def migrate_formula_rename(force:)
def migrate_formula_rename(force:, verbose:)
Formula.installed.each do |formula|
next unless Migrator.needs_migration?(formula)

View File

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

View File

@ -65,15 +65,25 @@ module Homebrew
end
use_runtime_dependents = args.installed? &&
!used_formulae_missing &&
!args.include_build? &&
!args.include_test? &&
!args.include_optional? &&
!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?
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)
.reduce(&:&)
.select(&:any_version_installed?) +
@ -87,11 +97,6 @@ module Homebrew
select_used_dependents(deps, used_formulae, recursive, includes, ignores)
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
def select_used_dependents(dependents, used_formulae, recursive, includes, ignores)

View File

@ -1,5 +1,7 @@
# frozen_string_literal: true
require "compat/dependencies_helpers"
require "compat/cli/parser"
require "compat/extend/nil"
require "compat/extend/string"
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 "utils/curl"
require "utils/notability"
require "utils/spdx"
require "extend/ENV"
require "formula_cellar_checks"
require "cmd/search"
@ -37,6 +38,8 @@ module Homebrew
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 "\
"`--strict` and `--online`."
flag "--tap=",
description: "Check the formulae within the given tap, specified as <user>`/`<repo>."
switch "--fix",
description: "Fix style violations automatically using RuboCop's auto-correct feature."
switch "--display-cop-names",
@ -46,7 +49,7 @@ module Homebrew
"make output easy to grep."
switch "--skip-style",
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",
description: "Enable debugging and profiling of audit methods."
comma_array "--only",
@ -85,17 +88,23 @@ module Homebrew
strict = new_formula || args.strict?
online = new_formula || args.online?
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.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
only_cops = args.only_cops
except_cops = args.except_cops
options = { fix: args.fix? }
options = { fix: args.fix?, debug: args.debug?, verbose: args.verbose? }
if only_cops
options[:only_cops] = only_cops
@ -110,8 +119,7 @@ module Homebrew
# Check style in a single batch run up front for performance
style_results = Style.check_style_json(style_files, options) if style_files
# load licenses
spdx = HOMEBREW_LIBRARY_PATH/"data/spdx.json"
spdx_data = JSON.parse(spdx.read)
spdx_data = SPDX.spdx_data
new_formula_problem_lines = []
audit_formulae.sort.each do |f|
only = only_cops ? ["style"] : args.only
@ -335,19 +343,42 @@ module Homebrew
openssl@1.1
].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
if formula.license.present?
non_standard_licenses = []
formula.license.each do |license|
non_standard_licenses = formula.license.map do |license|
next if license == :public_domain
next if @spdx_data["licenses"].any? { |spdx| spdx["licenseId"] == license }
non_standard_licenses << license
end
license
end.compact
if non_standard_licenses.present?
problem "Formula #{formula.name} contains non-standard SPDX licenses: #{non_standard_licenses}."
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
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)
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)}, "\
"but Formulae license states: #{formula.license}."
problem "Formula license #{formula.license} does not match GitHub license #{Array(github_license)}."
elsif @new_formula
problem "No license specified for package."
elsif @new_formula && @core_tap
problem "Formulae in homebrew/core must specify a license."
end
end

View File

@ -105,7 +105,10 @@ module Homebrew
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
def bump_formula_pr
@ -128,7 +131,7 @@ module Homebrew
check_open_pull_requests(formula, tap_full_name, args: args)
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
formula_spec = formula.stable
@ -159,10 +162,10 @@ module Homebrew
old_version = old_formula_version.to_s
forced_version = new_version.present?
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
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
elsif !hash_type
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}.
EOS
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)
new_revision = Utils.popen_read("git -C \"#{resource_path}\" rev-parse -q --verify HEAD")
new_revision = new_revision.strip
@ -190,7 +193,7 @@ module Homebrew
#{new_url}
EOS
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)
tar_file_extensions = %w[.tar .tb2 .tbz .tbz2 .tgz .tlz .txz .tZ]
if tar_file_extensions.any? { |extension| new_url.include? extension }
@ -246,7 +249,8 @@ module Homebrew
]
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
replacement_pairs << [
@ -312,13 +316,13 @@ module Homebrew
end
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
You need to bump this formula manually since changing the
version from #{old_formula_version} to #{new_formula_version} would be a downgrade.
EOS
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
You need to bump this formula manually since the new version
and old version are both #{new_formula_version}.
@ -331,14 +335,14 @@ module Homebrew
alias_rename.map! { |a| formula.tap.alias_dir/a }
end
ohai "brew update-python-resources #{formula.name}"
if !args.dry_run? || (args.dry_run? && args.write?)
PyPI.update_python_resources! formula, new_formula_version, silent: true, ignore_non_pypi_packages: true
unless read_only_run
PyPI.update_python_resources! formula, new_formula_version, silent: args.quiet?, ignore_non_pypi_packages: true
end
run_audit(formula, alias_rename, old_contents, args: args)
formula.path.parent.cd do
_, base_branch = origin_branch.split("/")
branch = "bump-#{formula.name}-#{new_formula_version}"
git_dir = Utils.popen_read("git rev-parse --git-dir").chomp
shallow = !git_dir.empty? && File.exist?("#{git_dir}/shallow")
@ -354,7 +358,7 @@ module Homebrew
"#{new_formula_version}' -- #{changed_files.join(" ")}"
ohai "git push --set-upstream $HUB_REMOTE #{branch}:#{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
if args.no_fork?
@ -387,7 +391,7 @@ module Homebrew
begin
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?
puts url
else
@ -452,7 +456,8 @@ module Homebrew
end
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 }
contents = StringInreplaceExtension.new(str)
replacement_pairs.each do |old, new|
@ -494,14 +499,14 @@ module Homebrew
check_for_duplicate_pull_requests(pull_requests, args: args)
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
specs = {}
specs[:tag] = tag if tag
version = Version.detect(url, specs)
end
# if we haven't already found open requests, try for an exact match across all requests
pull_requests = GitHub.fetch_pull_requests("#{formula.name} #{version}", tap_full_name) if pull_requests.blank?
# 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, state: "closed")
check_for_duplicate_pull_requests(pull_requests, args: args)
end

View File

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

View File

@ -116,7 +116,7 @@ module Homebrew
# git cherry-pick unfortunately has no quiet option
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"
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
if args.resolve?

View File

@ -8,20 +8,19 @@ module Homebrew
def prof_args
Homebrew::CLI::Parser.new do
usage_banner <<~EOS
`prof` <command>
`prof` [<command>]
Run Homebrew with the Ruby profiler, e.g. `brew prof readall`.
EOS
min_named 1
end
end
def prof
prof_args.parse
args = prof_args.parse
Homebrew.install_gem_setup_path! "ruby-prof", version: "0.18.0"
FileUtils.mkdir_p "prof"
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

View File

@ -50,7 +50,9 @@ module Homebrew
only_cops = args.only_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
options[:only_cops] = only_cops
elsif except_cops

View File

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

View File

@ -1,14 +1,11 @@
# frozen_string_literal: true
require "cli/parser"
require "utils/github"
require "utils/spdx"
module Homebrew
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
Homebrew::CLI::Parser.new do
usage_banner <<~EOS
@ -29,16 +26,14 @@ module Homebrew
args = update_license_data_args.parse
ohai "Updating SPDX license data..."
latest_tag = GitHub.open_api(SPDX_API_URL)["tag_name"]
data_url = "https://raw.githubusercontent.com/spdx/license-list-data/#{latest_tag}/json/licenses.json"
curl_download(data_url, to: SPDX_PATH, partial: false)
SPDX.download_latest_license_data!
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?
ohai "git add"
safe_system "git", "add", SPDX_PATH
safe_system "git", "add", SPDX::JSON_PATH
ohai "git commit"
system "git", "commit", "--message", "data/spdx.json: update to #{latest_tag}"
end

View File

@ -691,7 +691,7 @@ module Homebrew
# these will result in uncommitted gems.
if path == HOMEBREW_REPOSITORY
next if ENV["HOMEBREW_SORBET"] || ENV["HOMEBREW_PATCHELF_RB"]
next if ENV["HOMEBREW_SORBET"]
end
message ||= ""
@ -907,7 +907,7 @@ module Homebrew
def check_cask_staging_location
# 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

View File

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

View File

@ -54,6 +54,9 @@ module Homebrew
"Linux: `$XDG_CACHE_HOME/Homebrew` or `$HOME/.cache/Homebrew`.",
default: HOMEBREW_DEFAULT_CACHE,
},
HOMEBREW_CASK_OPTS: {
description: "Options which should be used for all `cask` commands.",
},
HOMEBREW_CLEANUP_MAX_AGE_DAYS: {
description: "Cleanup all cached files older than this many days.",
default: 120,
@ -293,7 +296,7 @@ module Homebrew
end
elsif hash[:default].present?
# 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
ENV[env].presence || hash.fetch(:default).to_s
@ -315,5 +318,31 @@ module Homebrew
.call
.to_s
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

View File

@ -247,17 +247,6 @@ class TapAlreadyTappedError < RuntimeError
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
def initialize(name)
message = <<~EOS
@ -367,10 +356,10 @@ class BuildError < RuntimeError
[]
end
def dump
def dump(verbose: false)
puts
if Homebrew.args.verbose?
if verbose
require "system_config"
require "build_environment"

View File

@ -73,7 +73,7 @@ module Homebrew
# `brew test-bot` runs `brew doctor` in the CI for the Homebrew/brew
# repository. This only needs to support whatever CI providers
# Homebrew/brew is currently using.
return if ENV["HOMEBREW_GITHUB_ACTIONS"]
return if ENV["GITHUB_ACTIONS"]
message = <<~EOS
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
# repository. This only needs to support whatever CI providers
# Homebrew/brew is currently using.
return if ENV["HOMEBREW_GITHUB_ACTIONS"]
return if ENV["GITHUB_ACTIONS"]
<<~EOS
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
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}\"",
Language::Java.overridable_java_home_env(java_version)
(self/script_name).write <<~EOS
#!/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
def install_metafiles(from = Pathname.pwd)
@ -416,6 +419,8 @@ require "extend/os/pathname"
# @private
module ObserverPathnameExtension
class << self
include Context
attr_accessor :n, :d
def reset_counts!
@ -434,8 +439,8 @@ module ObserverPathnameExtension
MAXIMUM_VERBOSE_OUTPUT = 100
def verbose?
return Homebrew.args.verbose? unless ENV["CI"]
return false unless Homebrew.args.verbose?
return super unless ENV["CI"]
return false unless super
if total < MAXIMUM_VERBOSE_OUTPUT
true

View File

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

View File

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

View File

@ -51,7 +51,8 @@ class FormulaInstaller
force_bottle: false,
include_test_formulae: [],
build_from_source_formulae: [],
cc: nil)
cc: nil,
debug: false, quiet: false, verbose: false)
@formula = formula
@env = nil
@force = false
@ -68,9 +69,9 @@ class FormulaInstaller
@interactive = false
@git = false
@cc = cc
@verbose = Homebrew.args.verbose?
@quiet = Homebrew.args.quiet?
@debug = Homebrew.args.debug?
@verbose = verbose
@quiet = quiet
@debug = debug
@installed_as_dependency = false
@installed_on_request = true
@options = Options.new
@ -603,13 +604,11 @@ class FormulaInstaller
df = dep.to_formula
fi = FormulaInstaller.new(df, force_bottle: false,
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.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
# been done for us in `compute_dependencies` and there's no requirement to
# fetch in a particular order.
@ -644,7 +643,8 @@ class FormulaInstaller
fi = FormulaInstaller.new(df, force_bottle: false,
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.remap_deprecated_options(df.deprecated_options, dep.options)
@ -652,9 +652,6 @@ class FormulaInstaller
fi.options &= df.options
fi.force = force?
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.installed_as_dependency = true
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
ignore_interrupts do
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
raise unless e.is_a? FormulaInstallationAlreadyAttemptedError
@ -845,7 +842,7 @@ class FormulaInstaller
def link(keg)
unless link_keg
begin
keg.optlink
keg.optlink(verbose: verbose?)
Formula.clear_cache
rescue Keg::LinkError => e
onoe "Failed to create #{formula.opt_prefix}"
@ -876,7 +873,7 @@ class FormulaInstaller
backup_dir = HOMEBREW_CACHE/"Backup"
begin
keg.link
keg.link(verbose: verbose?)
rescue Keg::ConflictError => e
conflict_file = e.dst
if formula.link_overwrite?(conflict_file) && !link_overwrite_backup.key?(conflict_file)
@ -891,8 +888,7 @@ class FormulaInstaller
puts e
puts
puts "Possible conflicting files are:"
mode = OpenStruct.new(dry_run: true, overwrite: true)
keg.link(mode)
keg.link(dry_run: true, overwrite: true, verbose: verbose?)
@show_summary_heading = true
Homebrew.failed = true
rescue Keg::LinkError => e
@ -939,7 +935,7 @@ class FormulaInstaller
log.mkpath if formula.plist.include? log.to_s
rescue Exception => e # rubocop:disable Lint/RescueException
onoe "Failed to install plist file"
ohai e, e.backtrace if debug?
odebug e, e.backtrace
Homebrew.failed = true
end
@ -949,7 +945,7 @@ class FormulaInstaller
onoe "Failed to fix install linkage"
puts "The formula built, but you may encounter issues using it or linking other"
puts "formulae against it."
ohai e, e.backtrace if debug?
odebug e, e.backtrace
Homebrew.failed = true
@show_summary_heading = true
end
@ -960,7 +956,7 @@ class FormulaInstaller
rescue Exception => e # rubocop:disable Lint/RescueException
opoo "The cleaning step did not complete successfully"
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
@show_summary_heading = true
end
@ -996,7 +992,7 @@ class FormulaInstaller
rescue Exception => e # rubocop:disable Lint/RescueException
opoo "The post-install step did not complete successfully"
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
@show_summary_heading = true
end
@ -1129,14 +1125,17 @@ class FormulaInstaller
end
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?
compute_dependencies.each do |dep, _|
next if @ignore_deps
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
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
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
#{formula.name}'s licenses are all forbidden: #{formula.license}.

View File

@ -3,6 +3,8 @@
require "formula"
class FormulaVersions
include Context
IGNORED_EXCEPTIONS = [
ArgumentError, NameError, SyntaxError, TypeError,
FormulaSpecificationError, FormulaValidationError,
@ -44,7 +46,7 @@ class FormulaVersions
rescue *IGNORED_EXCEPTIONS => e
# We rescue these so that we can skip bad versions and
# 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
nil
ensure

View File

@ -109,6 +109,8 @@ module Formulary
# A FormulaLoader returns instances of formulae.
# Subclasses implement loaders for particular sources of formulae.
class FormulaLoader
include Context
# The formula's name
attr_reader :name
# The formula's ruby file's path or filename
@ -138,7 +140,7 @@ module Formulary
private
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?
Formulary.load_formula_from_path(name, path, flags: flags)
@ -314,7 +316,7 @@ module Formulary
end
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)}"
Formulary.load_formula(name, path, contents, namespace, flags: flags)
end

View File

@ -37,7 +37,6 @@ require "config"
require "os"
require "cli/args"
require "messages"
require "system_command"
HOMEBREW_PRODUCT = ENV["HOMEBREW_PRODUCT"]
HOMEBREW_VERSION = ENV["HOMEBREW_VERSION"]
@ -116,6 +115,7 @@ end.compact.freeze
require "set"
require "context"
require "extend/pathname"
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/file/atomic"
require "system_command"
require "exceptions"
require "utils"

View File

@ -40,7 +40,7 @@ module Homebrew
module Help
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?
# Handle `brew` (no arguments).
if empty_argv
@ -58,7 +58,7 @@ module Homebrew
# Display command-specific (or generic) help in response to `UsageError`.
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
onoe usage_error
exit 1
@ -68,16 +68,16 @@ module Homebrew
return if path.nil?
# 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
end
def command_help(cmd, path)
def command_help(cmd, path, remaining_args:)
# Only some types of commands can have a parser.
output = if Commands.valid_internal_cmd?(cmd) ||
Commands.valid_internal_dev_cmd?(cmd) ||
Commands.external_ruby_v2_cmd_path(cmd)
parser_help(path)
parser_help(path, remaining_args: remaining_args)
end
output ||= comment_help(path)
@ -90,13 +90,13 @@ module Homebrew
output
end
def parser_help(path)
def parser_help(path, remaining_args:)
# Let OptionParser generate help text for commands which have a parser.
cmd_parser = CLI::Parser.from_cmd_path(path)
return unless cmd_parser
# 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
end

View File

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

View File

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

View File

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

View File

@ -71,41 +71,19 @@ module ELFShim
def rpath
return @rpath if defined? @rpath
@rpath = if HOMEBREW_PATCHELF_RB
rpath_using_patchelf_rb
else
rpath_using_patchelf
end
@rpath = rpath_using_patchelf_rb
end
def interpreter
return @interpreter if defined? @interpreter
@interpreter = if HOMEBREW_PATCHELF_RB
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
@interpreter = patchelf_patcher.interpreter
end
def dynamic_elf?
return @dynamic_elf if defined? @dynamic_elf
@dynamic_elf = if HOMEBREW_PATCHELF_RB
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
@dynamic_elf = patchelf_patcher.elf.segment_by_type(:DYNAMIC).present?
end
class Metadata
@ -139,81 +117,20 @@ module ELFShim
def needed_libraries(path)
return [nil, []] unless path.dynamic_elf?
if HOMEBREW_PATCHELF_RB
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
def needed_libraries_using_patchelf_rb(path)
patcher = path.patchelf_patcher
[patcher.soname, patcher.needed]
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
def rpath_using_patchelf_rb
patchelf_patcher.runpath || patchelf_patcher.rpath
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
return unless HOMEBREW_PATCHELF_RB
Homebrew.install_bundler_gems!
require "patchelf"
@patchelf_patcher ||= PatchELF::Patcher.new to_s, on_error: :silent

View File

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

View File

@ -5,7 +5,7 @@ class Keg
return if file.dylib_id == id
@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)
rescue MachO::MachOError
onoe <<~EOS
@ -20,7 +20,7 @@ class Keg
return if old == new
@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)
rescue MachO::MachOError
onoe <<~EOS

View File

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

View File

@ -24,7 +24,8 @@ module Homebrew
options &= f.options
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.force = args.force?
fi.keep_tmp = args.keep_tmp?
@ -48,7 +49,7 @@ module Homebrew
rescue FormulaInstallationAlreadyAttemptedError
nil
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
else
begin
@ -73,7 +74,7 @@ module Homebrew
end
end
def restore_backup(keg, keg_was_linked)
def restore_backup(keg, keg_was_linked, verbose:)
path = backup_path(keg)
return unless path.directory?
@ -81,7 +82,7 @@ module Homebrew
Pathname.new(keg).rmtree if keg.exist?
path.rename keg
keg.link if keg_was_linked
keg.link(verbose: verbose) if keg_was_linked
end
def backup_path(path)

View File

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

View File

@ -29,7 +29,13 @@ module RuboCop
end
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
def hash_node

View File

@ -6,7 +6,8 @@ module RuboCop
module Constants
STANZA_GROUPS = [
[:version, :sha256],
[:url, :appcast, :name, :homepage],
[:language],
[:url, :appcast, :name, :desc, :homepage],
[
:auto_updates,
:conflicts_with,
@ -32,6 +33,7 @@ module RuboCop
:service,
:audio_unit_plugin,
:vst_plugin,
:vst3_plugin,
:artifact,
: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 Cask
# 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.
class StanzaGrouping < Cop
extend Forwardable

View File

@ -6,7 +6,7 @@ module RuboCop
module Cop
module Cask
# 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.
class StanzaOrder < Cop
extend Forwardable

View File

@ -10,11 +10,13 @@ ensure
end
require "extend/string"
require "rubocops/shared/helper_functions"
module RuboCop
module Cop
class FormulaCop < Cop
include RangeHelp
include HelperFunctions
attr_accessor :file_path
@ -32,28 +34,6 @@ module RuboCop
audit_formula(node, class_node, parent_class_node, @body)
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.
# @param urls [Array] url/mirror method call nodes
# @param regex [Regexp] pattern to match urls
@ -442,26 +422,11 @@ module RuboCop
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
def end_column(node)
node.source_range.end_pos
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
def class_name(node)
@offensive_node = node
@ -484,35 +449,6 @@ module RuboCop
block.loc.end.line - block.loc.begin.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
# Returns true if the formula is versioned
def versioned_formula?
@formula_name.include?("@")
@ -532,10 +468,6 @@ module RuboCop
match_obj[1]
end
def problem(msg)
add_offense(@offensive_node, location: @offense_source_range, message: msg)
end
private
def formula_class?(node)

View File

@ -1,105 +1,24 @@
# frozen_string_literal: true
require "rubocops/extend/formula"
require "rubocops/shared/desc_helper"
require "extend/string"
module RuboCop
module Cop
module FormulaAudit
# This cop audits `desc` in Formulae.
#
# - 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.")
# See the `DescHelper` module for details of the checks.
class Desc < FormulaCop
VALID_LOWERCASE_WORDS = %w[
macOS
].freeze
include DescHelper
def audit_formula(_node, _class_node, _parent_class_node, body_node)
desc_call = find_node_method_by_name(body_node, :desc)
# 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})"
audit_desc(:formula, @formula_name, desc_call)
end
def autocorrect(node)
lambda do |corrector|
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
autocorrect_desc(node, @formula_name)
end
end
end

View File

@ -58,6 +58,14 @@ module RuboCop
when %r{^http://([^/]*)\.(sf|sourceforge)\.net(/|$)}
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.
# Only applies to the homepage and subdomains for now, not the FTP URLs.
when %r{^http://((?:build|cloud|developer|download|extensions|git|
@ -80,6 +88,17 @@ module RuboCop
problem "Please use https:// for #{homepage}"
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

View File

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

View File

@ -11,6 +11,7 @@ require "rubocops/cask/extend/string"
require "rubocops/cask/extend/node"
require "rubocops/cask/mixin/cask_help"
require "rubocops/cask/mixin/on_homepage_stanza"
require "rubocops/cask/desc"
require "rubocops/cask/homepage_matches_url"
require "rubocops/cask/homepage_url_trailing_slash"
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