Merge branch 'mlh-outdated-packages' of github.com:MLH-Fellowship/brew into mlh-outdated-packages

This commit is contained in:
Elizabeth Tackett 2020-07-06 09:12:53 -05:00
commit 0402f0c95e
186 changed files with 7686 additions and 2651 deletions

8
.github/codecov.yml vendored Normal file
View File

@ -0,0 +1,8 @@
fixes:
- "::Library/Homebrew/"
coverage:
round: nearest
status:
project:
default:
threshold: 0.05%

View File

@ -114,6 +114,9 @@ jobs:
- name: Run brew man - name: Run brew man
run: brew man --fail-if-changed run: brew man --fail-if-changed
- name: Check for outdated license data
run: brew update-license-data --fail-if-changed
- name: Run brew tests - name: Run brew tests
run: | run: |
# brew tests doesn't like world writable directories # brew tests doesn't like world writable directories
@ -136,8 +139,7 @@ jobs:
HOMEBREW_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} HOMEBREW_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# set variables for coverage reporting # set variables for coverage reporting
HOMEBREW_CI_NAME: github-actions HOMEBREW_CODECOV_TOKEN: 3ea0364c-80ce-47a3-9fba-93a940d4b5d7
HOMEBREW_COVERALLS_REPO_TOKEN: 3F6U6ZqctoNJwKyREremsqMgpU3qYgxFk
# These cannot be queried at the macOS level on GitHub Actions. # These cannot be queried at the macOS level on GitHub Actions.
HOMEBREW_LANGUAGES: en-GB HOMEBREW_LANGUAGES: en-GB

6
.gitignore vendored
View File

@ -86,7 +86,7 @@
**/vendor/bundle/ruby/*/gems/byebug-*/ **/vendor/bundle/ruby/*/gems/byebug-*/
**/vendor/bundle/ruby/*/gems/coderay-*/ **/vendor/bundle/ruby/*/gems/coderay-*/
**/vendor/bundle/ruby/*/gems/connection_pool-*/ **/vendor/bundle/ruby/*/gems/connection_pool-*/
**/vendor/bundle/ruby/*/gems/coveralls-*/ **/vendor/bundle/ruby/*/gems/codecov-*/
**/vendor/bundle/ruby/*/gems/diff-lcs-*/ **/vendor/bundle/ruby/*/gems/diff-lcs-*/
**/vendor/bundle/ruby/*/gems/docile-*/ **/vendor/bundle/ruby/*/gems/docile-*/
**/vendor/bundle/ruby/*/gems/domain_name-*/ **/vendor/bundle/ruby/*/gems/domain_name-*/
@ -127,11 +127,9 @@
**/vendor/bundle/ruby/*/gems/ruby-prof-*/ **/vendor/bundle/ruby/*/gems/ruby-prof-*/
**/vendor/bundle/ruby/*/gems/ruby-progressbar-*/ **/vendor/bundle/ruby/*/gems/ruby-progressbar-*/
**/vendor/bundle/ruby/*/gems/simplecov-*/ **/vendor/bundle/ruby/*/gems/simplecov-*/
**/vendor/bundle/ruby/*/gems/simplecov-cobertura-*/
**/vendor/bundle/ruby/*/gems/simplecov-html-*/ **/vendor/bundle/ruby/*/gems/simplecov-html-*/
**/vendor/bundle/ruby/*/gems/term-ansicolor-*/
**/vendor/bundle/ruby/*/gems/thor-*/ **/vendor/bundle/ruby/*/gems/thor-*/
**/vendor/bundle/ruby/*/gems/tins-*/ **/vendor/bundle/ruby/*/gems/url-*/
**/vendor/bundle/ruby/*/gems/unf_ext-*/ **/vendor/bundle/ruby/*/gems/unf_ext-*/
**/vendor/bundle/ruby/*/gems/unf-*/ **/vendor/bundle/ruby/*/gems/unf-*/
**/vendor/bundle/ruby/*/gems/unicode-display_width-*/ **/vendor/bundle/ruby/*/gems/unicode-display_width-*/

View File

@ -1,4 +1,5 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
# frozen_string_literal: true
require "English" require "English"
@ -20,7 +21,8 @@ SimpleCov.start do
# Just save result, but don't write formatted output. # Just save result, but don't write formatted output.
coverage_result = Coverage.result coverage_result = Coverage.result
SimpleCov.add_not_loaded_files(coverage_result) # TODO: this method is private, find a better way.
SimpleCov.send(:add_not_loaded_files, coverage_result)
simplecov_result = SimpleCov::Result.new(coverage_result) simplecov_result = SimpleCov::Result.new(coverage_result)
SimpleCov::ResultMerger.store_result(simplecov_result) SimpleCov::ResultMerger.store_result(simplecov_result)
@ -50,8 +52,8 @@ SimpleCov.start do
require "rbconfig" require "rbconfig"
host_os = RbConfig::CONFIG["host_os"] host_os = RbConfig::CONFIG["host_os"]
add_filter %r{/os/mac} if host_os !~ /darwin/ add_filter %r{/os/mac} unless /darwin/.match?(host_os)
add_filter %r{/os/linux} if host_os !~ /linux/ add_filter %r{/os/linux} unless /linux/.match?(host_os)
# Add groups and the proper project name to the output. # Add groups and the proper project name to the output.
project_name "Homebrew" project_name "Homebrew"

View File

@ -4,7 +4,7 @@ source "https://rubygems.org"
# installed gems # installed gems
gem "byebug" gem "byebug"
gem "coveralls", "~> 0.8", require: false gem "codecov", require: false
gem "parallel_tests" gem "parallel_tests"
gem "ronn", require: false gem "ronn", require: false
gem "rspec" gem "rspec"

View File

@ -9,15 +9,13 @@ GEM
zeitwerk (~> 2.2, >= 2.2.2) zeitwerk (~> 2.2, >= 2.2.2)
ast (2.4.1) ast (2.4.1)
byebug (11.1.3) byebug (11.1.3)
codecov (0.1.17)
json
simplecov
url
concurrent-ruby (1.1.6) concurrent-ruby (1.1.6)
connection_pool (2.2.3) connection_pool (2.2.3)
coveralls (0.8.23) diff-lcs (1.4.4)
json (>= 1.8, < 3)
simplecov (~> 0.16.1)
term-ansicolor (~> 1.3)
thor (>= 0.19.4, < 2.0)
tins (~> 1.6)
diff-lcs (1.4.3)
docile (1.3.2) docile (1.3.2)
domain_name (0.5.20190701) domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0) unf (>= 0.0.5, < 1.0.0)
@ -26,7 +24,7 @@ GEM
domain_name (~> 0.5) domain_name (~> 0.5)
i18n (1.8.3) i18n (1.8.3)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
json (2.3.0) json (2.3.1)
mechanize (2.7.6) mechanize (2.7.6)
domain_name (~> 0.5, >= 0.5.1) domain_name (~> 0.5, >= 0.5.1)
http-cookie (~> 1.0) http-cookie (~> 1.0)
@ -95,28 +93,22 @@ GEM
parser (>= 2.7.0.1) parser (>= 2.7.0.1)
rubocop-performance (1.6.1) rubocop-performance (1.6.1)
rubocop (>= 0.71.0) rubocop (>= 0.71.0)
rubocop-rspec (1.40.0) rubocop-rspec (1.41.0)
rubocop (>= 0.68.1) rubocop (>= 0.68.1)
ruby-macho (2.2.0) ruby-macho (2.2.0)
ruby-progressbar (1.10.1) ruby-progressbar (1.10.1)
simplecov (0.16.1) simplecov (0.18.5)
docile (~> 1.1) docile (~> 1.1)
json (>= 1.8, < 3) simplecov-html (~> 0.11)
simplecov-html (~> 0.10.0) simplecov-html (0.12.2)
simplecov-html (0.10.2)
sync (0.5.0)
term-ansicolor (1.7.1)
tins (~> 1.0)
thor (1.0.1)
thread_safe (0.3.6) thread_safe (0.3.6)
tins (1.25.0)
sync
tzinfo (1.2.7) tzinfo (1.2.7)
thread_safe (~> 0.1) thread_safe (~> 0.1)
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.7.7) unf_ext (0.0.7.7)
unicode-display_width (1.7.0) unicode-display_width (1.7.0)
url (0.3.2)
webrobots (0.1.2) webrobots (0.1.2)
zeitwerk (2.3.1) zeitwerk (2.3.1)
@ -126,8 +118,8 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
activesupport activesupport
byebug byebug
codecov
concurrent-ruby concurrent-ruby
coveralls (~> 0.8)
mechanize mechanize
parallel_tests parallel_tests
plist plist
@ -143,4 +135,4 @@ DEPENDENCIES
simplecov simplecov
BUNDLED WITH BUNDLED WITH
1.17.2 1.17.3

View File

@ -41,7 +41,7 @@ class Bintray
def upload(local_file, repo:, package:, version:, remote_file:, sha256: nil) def upload(local_file, repo:, package:, version:, remote_file:, sha256: nil)
url = "#{API_URL}/content/#{@bintray_org}/#{repo}/#{package}/#{version}/#{remote_file}" url = "#{API_URL}/content/#{@bintray_org}/#{repo}/#{package}/#{version}/#{remote_file}"
args = ["--upload-file", local_file] args = ["--fail", "--upload-file", local_file]
args += ["--header", "X-Checksum-Sha2: #{sha256}"] unless sha256.blank? args += ["--header", "X-Checksum-Sha2: #{sha256}"] unless sha256.blank?
result = open_api url, *args result = open_api url, *args
json = JSON.parse(result.stdout) json = JSON.parse(result.stdout)
@ -50,12 +50,15 @@ class Bintray
result result
end end
def publish(repo:, package:, version:, file_count:) def publish(repo:, package:, version:, file_count:, warn_on_error: false)
url = "#{API_URL}/content/#{@bintray_org}/#{repo}/#{package}/#{version}/publish" url = "#{API_URL}/content/#{@bintray_org}/#{repo}/#{package}/#{version}/publish"
result = open_api url, "--request", "POST" result = open_api url, "--request", "POST", "--fail"
json = JSON.parse(result.stdout) json = JSON.parse(result.stdout)
if file_count.present? && json["files"] != file_count if file_count.present? && json["files"] != file_count
raise "Bottle publish failed: expected #{file_count} bottles, but published #{json["files"]} instead." message = "Bottle publish failed: expected #{file_count} bottles, but published #{json["files"]} instead."
raise message unless warn_on_error
opoo message
end end
odebug "Published #{json["files"]} bottles" odebug "Published #{json["files"]} bottles"
@ -143,7 +146,7 @@ class Bintray
end end
end end
def upload_bottle_json(json_files, publish_package: false) def upload_bottle_json(json_files, publish_package: false, warn_on_error: false)
bottles_hash = json_files.reduce({}) do |hash, json_file| bottles_hash = json_files.reduce({}) do |hash, json_file|
hash.deep_merge(JSON.parse(IO.read(json_file))) hash.deep_merge(JSON.parse(IO.read(json_file)))
end end
@ -161,14 +164,19 @@ class Bintray
odebug "Checking remote file #{@bintray_org}/#{bintray_repo}/#{filename}" odebug "Checking remote file #{@bintray_org}/#{bintray_repo}/#{filename}"
if file_published? repo: bintray_repo, remote_file: filename if file_published? repo: bintray_repo, remote_file: filename
raise Error, <<~EOS already_published = "#{filename} is already published."
#{filename} is already published. failed_message = <<~EOS
#{already_published}
Please remove it manually from: Please remove it manually from:
https://bintray.com/#{@bintray_org}/#{bintray_repo}/#{bintray_package}/view#files https://bintray.com/#{@bintray_org}/#{bintray_repo}/#{bintray_package}/view#files
Or run: Or run:
curl -X DELETE -u $HOMEBREW_BINTRAY_USER:$HOMEBREW_BINTRAY_KEY \\ curl -X DELETE -u $HOMEBREW_BINTRAY_USER:$HOMEBREW_BINTRAY_KEY \\
https://api.bintray.com/content/#{@bintray_org}/#{bintray_repo}/#{filename} https://api.bintray.com/content/#{@bintray_org}/#{bintray_repo}/#{filename}
EOS EOS
raise Error, failed_message unless warn_on_error
opoo already_published
next
end end
if !formula_packaged[formula_name] && !package_exists?(repo: bintray_repo, package: bintray_package) if !formula_packaged[formula_name] && !package_exists?(repo: bintray_repo, package: bintray_package)
@ -189,7 +197,11 @@ class Bintray
bottle_count = bottle_hash["bottle"]["tags"].length bottle_count = bottle_hash["bottle"]["tags"].length
odebug "Publishing #{@bintray_org}/#{bintray_repo}/#{bintray_package}/#{version}" odebug "Publishing #{@bintray_org}/#{bintray_repo}/#{bintray_package}/#{version}"
publish repo: bintray_repo, package: bintray_package, version: version, file_count: bottle_count publish(repo: bintray_repo,
package: bintray_package,
version: version,
file_count: bottle_count,
warn_on_error: warn_on_error)
end end
end end
end end

View File

@ -36,9 +36,11 @@ module Homebrew
# Reset cache values reliant on named_args # Reset cache values reliant on named_args
@formulae = nil @formulae = nil
@resolved_formulae = nil @resolved_formulae = nil
@resolved_formulae_casks = nil
@formulae_paths = nil @formulae_paths = nil
@casks = nil @casks = nil
@kegs = nil @kegs = nil
@kegs_casks = nil
self[:named_args] = named_args self[:named_args] = named_args
self[:named_args].freeze self[:named_args].freeze
@ -96,6 +98,25 @@ module Homebrew
end.uniq(&:name).freeze end.uniq(&:name).freeze
end end
def resolved_formulae_casks
@resolved_formulae_casks ||= begin
resolved_formulae = []
casks = []
downcased_unique_named.each do |name|
resolved_formulae << Formulary.resolve(name, spec: spec(nil))
rescue FormulaUnavailableError
begin
casks << Cask::CaskLoader.load(name)
rescue Cask::CaskUnavailableError
raise "No available formula or cask with the name \"#{name}\""
end
end
[resolved_formulae.freeze, casks.freeze].freeze
end
end
def formulae_paths def formulae_paths
@formulae_paths ||= (downcased_unique_named - casks).map do |name| @formulae_paths ||= (downcased_unique_named - casks).map do |name|
Formulary.path(name) Formulary.path(name)
@ -108,57 +129,35 @@ module Homebrew
end end
def kegs def kegs
require "keg"
require "formula"
require "missing_formula"
@kegs ||= downcased_unique_named.map do |name| @kegs ||= downcased_unique_named.map do |name|
raise UsageError if name.empty? resolve_keg name
rescue NoSuchKegError => e
rack = Formulary.to_rack(name.downcase) if (reason = Homebrew::MissingFormula.suggest_command(name, "uninstall"))
$stderr.puts reason
dirs = rack.directory? ? rack.subdirs : []
if dirs.empty?
if (reason = Homebrew::MissingFormula.suggest_command(name, "uninstall"))
$stderr.puts reason
end
raise NoSuchKegError, rack.basename
end
linked_keg_ref = HOMEBREW_LINKED_KEGS/rack.basename
opt_prefix = HOMEBREW_PREFIX/"opt/#{rack.basename}"
begin
if opt_prefix.symlink? && opt_prefix.directory?
Keg.new(opt_prefix.resolved_path)
elsif linked_keg_ref.symlink? && linked_keg_ref.directory?
Keg.new(linked_keg_ref.resolved_path)
elsif dirs.length == 1
Keg.new(dirs.first)
else
f = if name.include?("/") || File.exist?(name)
Formulary.factory(name)
else
Formulary.from_rack(rack)
end
unless (prefix = f.installed_prefix).directory?
raise MultipleVersionsInstalledError, rack.basename
end
Keg.new(prefix)
end
rescue FormulaUnavailableError
raise <<~EOS
Multiple kegs installed to #{rack}
However we don't know which one you refer to.
Please delete (with rm -rf!) all but one and then try again.
EOS
end end
raise e
end.freeze end.freeze
end end
def kegs_casks
@kegs_casks ||= begin
kegs = []
casks = []
downcased_unique_named.each do |name|
kegs << resolve_keg(name)
rescue NoSuchKegError
begin
casks << Cask::CaskLoader.load(name)
rescue Cask::CaskUnavailableError
raise "No installed keg or cask with the name \"#{name}\""
end
end
[kegs.freeze, casks.freeze].freeze
end
end
def build_stable? def build_stable?
!(HEAD? || devel?) !(HEAD? || devel?)
end end
@ -241,6 +240,50 @@ module Homebrew
default default
end end
end end
def resolve_keg(name)
require "keg"
require "formula"
require "missing_formula"
raise UsageError if name.blank?
rack = Formulary.to_rack(name.downcase)
dirs = rack.directory? ? rack.subdirs : []
raise NoSuchKegError, rack.basename if dirs.empty?
linked_keg_ref = HOMEBREW_LINKED_KEGS/rack.basename
opt_prefix = HOMEBREW_PREFIX/"opt/#{rack.basename}"
begin
if opt_prefix.symlink? && opt_prefix.directory?
Keg.new(opt_prefix.resolved_path)
elsif linked_keg_ref.symlink? && linked_keg_ref.directory?
Keg.new(linked_keg_ref.resolved_path)
elsif dirs.length == 1
Keg.new(dirs.first)
else
f = if name.include?("/") || File.exist?(name)
Formulary.factory(name)
else
Formulary.from_rack(rack)
end
unless (prefix = f.installed_prefix).directory?
raise MultipleVersionsInstalledError, "#{rack.basename} has multiple installed versions"
end
Keg.new(prefix)
end
rescue FormulaUnavailableError
raise MultipleVersionsInstalledError, <<~EOS
Multiple kegs installed to #{rack}
However we don't know which one you refer to.
Please delete (with rm -rf!) all but one and then try again.
EOS
end
end
end end
end end
end end

View File

@ -211,6 +211,8 @@ module Homebrew
puts "From: #{Formatter.url(github_info(f))}" puts "From: #{Formatter.url(github_info(f))}"
puts "License: #{f.license}" if f.license
unless f.deps.empty? unless f.deps.empty?
ohai "Dependencies" ohai "Dependencies"
%w[build required recommended optional].map do |type| %w[build required recommended optional].map do |type|

View File

@ -7,6 +7,7 @@ require "install"
require "search" require "search"
require "cleanup" require "cleanup"
require "cli/parser" require "cli/parser"
require "upgrade"
module Homebrew module Homebrew
module_function module_function
@ -261,6 +262,9 @@ module Homebrew
install_formula(f) install_formula(f)
Cleanup.install_formula_clean!(f) Cleanup.install_formula_clean!(f)
end end
check_installed_dependents
Homebrew.messages.display_messages Homebrew.messages.display_messages
rescue FormulaUnreadableError, FormulaClassUnavailableError, rescue FormulaUnreadableError, FormulaClassUnavailableError,
TapFormulaUnreadableError, TapFormulaClassUnavailableError => e TapFormulaUnreadableError, TapFormulaClassUnavailableError => e

View File

@ -11,10 +11,10 @@ module Homebrew
usage_banner <<~EOS usage_banner <<~EOS
`readall` [<options>] [<tap>] `readall` [<options>] [<tap>]
Import all formulae from the specified <tap>, or from all installed taps if none is provided. Import all items from the specified <tap>, or from all installed taps if none is provided.
This can be useful for debugging issues across all formulae when making This can be useful for debugging issues across all items when making
significant changes to `formula.rb`, testing the performance of loading significant changes to `formula.rb`, testing the performance of loading
all formulae or checking if any current formulae have Ruby issues. all items or checking if any current formulae/casks have Ruby issues.
EOS EOS
switch "--aliases", switch "--aliases",
description: "Verify any alias symlinks in each tap." description: "Verify any alias symlinks in each tap."
@ -30,7 +30,7 @@ module Homebrew
if args.syntax? if args.syntax?
scan_files = "#{HOMEBREW_LIBRARY_PATH}/**/*.rb" scan_files = "#{HOMEBREW_LIBRARY_PATH}/**/*.rb"
ruby_files = Dir.glob(scan_files).reject { |file| file =~ %r{/(vendor|cask)/} } ruby_files = Dir.glob(scan_files).reject { |file| file =~ %r{/(vendor)/} }
Homebrew.failed = true unless Readall.valid_ruby_syntax?(ruby_files) Homebrew.failed = true unless Readall.valid_ruby_syntax?(ruby_files)
end end

View File

@ -6,6 +6,10 @@ require "messages"
require "reinstall" require "reinstall"
require "cli/parser" require "cli/parser"
require "cleanup" require "cleanup"
require "cask/cmd"
require "cask/utils"
require "cask/macos"
require "upgrade"
module Homebrew module Homebrew
module_function module_function
@ -56,7 +60,8 @@ module Homebrew
Install.perform_preinstall_checks Install.perform_preinstall_checks
args.resolved_formulae.each do |f| resolved_formulae, casks = args.resolved_formulae_casks
resolved_formulae.each do |f|
if f.pinned? if f.pinned?
onoe "#{f.full_name} is pinned. You must unpin it to reinstall." onoe "#{f.full_name} is pinned. You must unpin it to reinstall."
next next
@ -65,6 +70,16 @@ module Homebrew
reinstall_formula(f) reinstall_formula(f)
Cleanup.install_formula_clean!(f) Cleanup.install_formula_clean!(f)
end end
check_installed_dependents
Homebrew.messages.display_messages Homebrew.messages.display_messages
return if casks.blank?
reinstall_cmd = Cask::Cmd::Reinstall.new(casks)
reinstall_cmd.verbose = args.verbose?
reinstall_cmd.force = args.force?
reinstall_cmd.run
end end
end end

View File

@ -5,6 +5,9 @@ require "formula"
require "diagnostic" require "diagnostic"
require "migrator" require "migrator"
require "cli/parser" require "cli/parser"
require "cask/all"
require "cask/cmd"
require "cask/cask_loader"
module Homebrew module Homebrew
module_function module_function
@ -29,15 +32,26 @@ module Homebrew
def uninstall def uninstall
uninstall_args.parse uninstall_args.parse
kegs_by_rack = if args.force? if args.force?
Hash[args.named.map do |name| casks = []
rack = Formulary.to_rack(name) kegs_by_rack = {}
next unless rack.directory?
[rack, rack.subdirs.map { |d| Keg.new(d) }] args.named.each do |name|
end] rack = Formulary.to_rack(name)
if rack.directory?
kegs_by_rack[rack] = rack.subdirs.map { |d| Keg.new(d) }
else
begin
casks << Cask::CaskLoader.load(name)
rescue Cask::CaskUnavailableError
# Since the uninstall was forced, ignore any unavailable casks
end
end
end
else else
args.kegs.group_by(&:rack) all_kegs, casks = args.kegs_casks
kegs_by_rack = all_kegs.group_by(&:rack)
end end
handle_unsatisfied_dependents(kegs_by_rack) handle_unsatisfied_dependents(kegs_by_rack)
@ -108,6 +122,13 @@ module Homebrew
end end
end end
end end
return if casks.blank?
cask_uninstall = Cask::Cmd::Uninstall.new(casks)
cask_uninstall.force = args.force?
cask_uninstall.verbose = args.verbose?
cask_uninstall.run
rescue MultipleVersionsInstalledError => e rescue MultipleVersionsInstalledError => e
ofail e ofail e
puts "Run `brew uninstall --force #{e.name}` to remove all versions." puts "Run `brew uninstall --force #{e.name}` to remove all versions."

View File

@ -1,12 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
require "install"
require "reinstall"
require "formula_installer"
require "development_tools"
require "messages"
require "cleanup"
require "cli/parser" require "cli/parser"
require "formula_installer"
require "install"
require "upgrade"
module Homebrew module Homebrew
module_function module_function
@ -114,241 +111,8 @@ module Homebrew
upgrade_formulae(formulae_to_install) upgrade_formulae(formulae_to_install)
check_dependents(formulae_to_install) check_installed_dependents
Homebrew.messages.display_messages Homebrew.messages.display_messages
end end
def upgrade_formulae(formulae_to_install)
return if formulae_to_install.empty?
return if args.dry_run?
# Sort keg-only before non-keg-only formulae to avoid any needless conflicts
# with outdated, non-keg-only versions of formulae being upgraded.
formulae_to_install.sort! do |a, b|
if !a.keg_only? && b.keg_only?
1
elsif a.keg_only? && !b.keg_only?
-1
else
0
end
end
formulae_to_install.each do |f|
Migrator.migrate_if_needed(f)
begin
upgrade_formula(f)
Cleanup.install_formula_clean!(f)
rescue UnsatisfiedRequirements => e
Homebrew.failed = true
onoe "#{f}: #{e}"
end
end
end
def upgrade_formula(f)
return if args.dry_run?
if f.opt_prefix.directory?
keg = Keg.new(f.opt_prefix.resolved_path)
keg_had_linked_opt = true
keg_was_linked = keg.linked?
end
formulae_maybe_with_kegs = [f] + f.old_installed_formulae
outdated_kegs = formulae_maybe_with_kegs
.map(&:linked_keg)
.select(&:directory?)
.map { |k| Keg.new(k.resolved_path) }
linked_kegs = outdated_kegs.select(&:linked?)
if f.opt_prefix.directory?
keg = Keg.new(f.opt_prefix.resolved_path)
tab = Tab.for_keg(keg)
end
build_options = BuildOptions.new(Options.create(args.flags_only), f.options)
options = build_options.used_options
options |= f.build.used_options
options &= f.options
fi = FormulaInstaller.new(f)
fi.options = options
fi.build_bottle = args.build_bottle?
fi.installed_on_request = args.named.present?
fi.link_keg ||= keg_was_linked if keg_had_linked_opt
if tab
fi.build_bottle ||= tab.built_bottle?
fi.installed_as_dependency = tab.installed_as_dependency
fi.installed_on_request ||= tab.installed_on_request
end
upgrade_version = if f.optlinked?
"#{Keg.new(f.opt_prefix).version} -> #{f.pkg_version}"
else
"-> #{f.pkg_version}"
end
oh1 "Upgrading #{Formatter.identifier(f.full_specified_name)} #{upgrade_version} #{fi.options.to_a.join(" ")}"
fi.prelude
fi.fetch
# first we unlink the currently active keg for this formula otherwise it is
# possible for the existing build to interfere with the build we are about to
# do! Seriously, it happens!
outdated_kegs.each(&:unlink)
fi.install
fi.finish
rescue FormulaInstallationAlreadyAttemptedError
# We already attempted to upgrade f as part of the dependency tree of
# another formula. In that case, don't generate an error, just move on.
nil
rescue CannotInstallFormulaError => e
ofail e
rescue BuildError => e
e.dump
puts
Homebrew.failed = true
rescue DownloadError => e
ofail e
ensure
# restore previous installation state if build failed
begin
linked_kegs.each(&:link) unless f.latest_version_installed?
rescue
nil
end
end
# @private
def depends_on(a, b)
if a.opt_or_installed_prefix_keg
&.runtime_dependencies
&.any? { |d| d["full_name"] == b.full_name }
1
else
a <=> b
end
end
def check_dependents(formulae_to_install)
return if formulae_to_install.empty?
oh1 "Checking for dependents of upgraded formulae..." unless args.dry_run?
outdated_dependents =
formulae_to_install.flat_map(&:runtime_installed_formula_dependents)
.select(&:outdated?)
if outdated_dependents.blank?
ohai "No dependents found!" unless args.dry_run?
return
end
outdated_dependents -= formulae_to_install if args.dry_run?
upgradeable_dependents =
outdated_dependents.reject(&:pinned?)
.sort { |a, b| depends_on(a, b) }
pinned_dependents =
outdated_dependents.select(&:pinned?)
.sort { |a, b| depends_on(a, b) }
if pinned_dependents.present?
plural = "dependent".pluralize(pinned_dependents.count)
ohai "Not upgrading #{pinned_dependents.count} pinned #{plural}:"
puts(pinned_dependents.map do |f|
"#{f.full_specified_name} #{f.pkg_version}"
end.join(", "))
end
# Print the upgradable dependents.
if upgradeable_dependents.blank?
ohai "No outdated dependents to upgrade!" unless args.dry_run?
else
plural = "dependent".pluralize(upgradeable_dependents.count)
verb = args.dry_run? ? "Would upgrade" : "Upgrading"
ohai "#{verb} #{upgradeable_dependents.count} #{plural}:"
formulae_upgrades = upgradeable_dependents.map do |f|
name = f.full_specified_name
if f.optlinked?
"#{name} #{Keg.new(f.opt_prefix).version} -> #{f.pkg_version}"
else
"#{name} #{f.pkg_version}"
end
end
puts formulae_upgrades.join(", ")
end
upgrade_formulae(upgradeable_dependents)
# Assess the dependents tree again now we've upgraded.
oh1 "Checking for dependents of upgraded formulae..." unless args.dry_run?
broken_dependents = CacheStoreDatabase.use(:linkage) do |db|
formulae_to_install.flat_map(&:runtime_installed_formula_dependents)
.select do |f|
keg = f.opt_or_installed_prefix_keg
next unless keg
LinkageChecker.new(keg, cache_db: db)
.broken_library_linkage?
end.compact
end
if broken_dependents.blank?
if args.dry_run?
ohai "No currently broken dependents found!"
opoo "If they are broken by the upgrade they will also be upgraded or reinstalled."
else
ohai "No broken dependents found!"
end
return
end
reinstallable_broken_dependents =
broken_dependents.reject(&:outdated?)
.reject(&:pinned?)
.sort { |a, b| depends_on(a, b) }
outdated_pinned_broken_dependents =
broken_dependents.select(&:outdated?)
.select(&:pinned?)
.sort { |a, b| depends_on(a, b) }
# Print the pinned dependents.
if outdated_pinned_broken_dependents.present?
count = outdated_pinned_broken_dependents.count
plural = "dependent".pluralize(outdated_pinned_broken_dependents.count)
onoe "Not reinstalling #{count} broken and outdated, but pinned #{plural}:"
$stderr.puts(outdated_pinned_broken_dependents.map do |f|
"#{f.full_specified_name} #{f.pkg_version}"
end.join(", "))
end
# Print the broken dependents.
if reinstallable_broken_dependents.blank?
ohai "No broken dependents to reinstall!"
else
count = reinstallable_broken_dependents.count
plural = "dependent".pluralize(reinstallable_broken_dependents.count)
ohai "Reinstalling #{count} broken #{plural} from source:"
puts reinstallable_broken_dependents.map(&:full_specified_name)
.join(", ")
end
return if args.dry_run?
reinstallable_broken_dependents.each do |f|
reinstall_formula(f, build_from_source: true)
rescue FormulaInstallationAlreadyAttemptedError
# We already attempted to reinstall f as part of the dependency tree of
# another formula. In that case, don't generate an error, just move on.
nil
rescue CannotInstallFormulaError => e
ofail e
rescue BuildError => e
e.dump
puts
Homebrew.failed = true
rescue DownloadError => e
ofail e
end
end
end end

View File

@ -148,8 +148,7 @@ module Commands
cmds = internal_commands + internal_developer_commands + internal_commands_aliases cmds = internal_commands + internal_developer_commands + internal_commands_aliases
file = HOMEBREW_REPOSITORY/"completions/internal_commands_list.txt" file = HOMEBREW_REPOSITORY/"completions/internal_commands_list.txt"
file.delete if file.exist? file.atomic_write(cmds.sort.join("\n") + "\n")
file.write(cmds.sort.join("\n") + "\n")
end end
def rebuild_commands_completion_list def rebuild_commands_completion_list
@ -157,7 +156,6 @@ module Commands
HOMEBREW_CACHE.mkpath HOMEBREW_CACHE.mkpath
file = HOMEBREW_CACHE/"all_commands_list.txt" file = HOMEBREW_CACHE/"all_commands_list.txt"
file.delete if file.exist? file.atomic_write(commands(aliases: true).sort.join("\n") + "\n")
file.write(commands(aliases: true).sort.join("\n") + "\n")
end end
end end

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,7 @@ require "date"
require "missing_formula" require "missing_formula"
require "digest" require "digest"
require "cli/parser" require "cli/parser"
require "json"
module Homebrew module Homebrew
module_function module_function
@ -109,7 +110,9 @@ module Homebrew
# Check style in a single batch run up front for performance # Check style in a single batch run up front for performance
style_results = Style.check_style_json(style_files, options) if style_files style_results = Style.check_style_json(style_files, options) if style_files
# load licenses
spdx = HOMEBREW_LIBRARY_PATH/"data/spdx.json"
spdx_data = JSON.parse(spdx.read)
new_formula_problem_lines = [] new_formula_problem_lines = []
audit_formulae.sort.each do |f| audit_formulae.sort.each do |f|
only = only_cops ? ["style"] : args.only only = only_cops ? ["style"] : args.only
@ -120,6 +123,7 @@ module Homebrew
git: git, git: git,
only: only, only: only,
except: args.except, except: args.except,
spdx_data: spdx_data,
} }
options[:style_offenses] = style_results.file_offenses(f.path) if style_results options[:style_offenses] = style_results.file_offenses(f.path) if style_results
options[:display_cop_names] = args.display_cop_names? options[:display_cop_names] = args.display_cop_names?
@ -215,6 +219,7 @@ module Homebrew
@new_formula_problems = [] @new_formula_problems = []
@text = FormulaText.new(formula.path) @text = FormulaText.new(formula.path)
@specs = %w[stable devel head].map { |s| formula.send(s) }.compact @specs = %w[stable devel head].map { |s| formula.send(s) }.compact
@spdx_data = options[:spdx_data]
end end
def audit_style def audit_style
@ -327,6 +332,27 @@ module Homebrew
openssl@1.1 openssl@1.1
].freeze ].freeze
def audit_license
if formula.license.present?
if @spdx_data["licenses"].any? { |lic| lic["licenseId"] == formula.license }
return unless @online
user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*}) if @new_formula
return if user.blank?
github_license = GitHub.get_repo_license(user, repo)
return if github_license && [formula.license, "NOASSERTION"].include?(github_license)
problem "License mismatch - GitHub license is: #{github_license}, "\
"but Formulae license states: #{formula.license}."
else
problem "#{formula.license} is not a standard SPDX license."
end
elsif @new_formula
problem "No license specified for package."
end
end
def audit_deps def audit_deps
@specs.each do |spec| @specs.each do |spec|
# Check for things we don't like to depend on. # Check for things we don't like to depend on.
@ -502,8 +528,9 @@ module Homebrew
end end
def audit_github_repository def audit_github_repository
user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*}) user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*}) if @new_formula
return if user.nil?
return if user.blank?
warning = SharedAudits.github(user, repo) warning = SharedAudits.github(user, repo)
return if warning.nil? return if warning.nil?
@ -512,8 +539,8 @@ module Homebrew
end end
def audit_gitlab_repository def audit_gitlab_repository
user, repo = get_repo_data(%r{https?://gitlab\.com/([^/]+)/([^/]+)/?.*}) user, repo = get_repo_data(%r{https?://gitlab\.com/([^/]+)/([^/]+)/?.*}) if @new_formula
return if user.nil? return if user.blank?
warning = SharedAudits.gitlab(user, repo) warning = SharedAudits.gitlab(user, repo)
return if warning.nil? return if warning.nil?
@ -522,8 +549,8 @@ module Homebrew
end end
def audit_bitbucket_repository def audit_bitbucket_repository
user, repo = get_repo_data(%r{https?://bitbucket\.org/([^/]+)/([^/]+)/?.*}) user, repo = get_repo_data(%r{https?://bitbucket\.org/([^/]+)/([^/]+)/?.*}) if @new_formula
return if user.nil? return if user.blank?
warning = SharedAudits.bitbucket(user, repo) warning = SharedAudits.bitbucket(user, repo)
return if warning.nil? return if warning.nil?
@ -534,7 +561,6 @@ module Homebrew
def get_repo_data(regex) def get_repo_data(regex)
return unless @core_tap return unless @core_tap
return unless @online return unless @online
return unless @new_formula
_, user, repo = *regex.match(formula.stable.url) if formula.stable _, user, repo = *regex.match(formula.stable.url) if formula.stable
_, user, repo = *regex.match(formula.homepage) unless user _, user, repo = *regex.match(formula.homepage) unless user
@ -554,6 +580,7 @@ module Homebrew
"aws-sdk-cpp" => "10", "aws-sdk-cpp" => "10",
"awscli@1" => "10", "awscli@1" => "10",
"balena-cli" => "10", "balena-cli" => "10",
"gatsby-cli" => "10",
"quicktype" => "10", "quicktype" => "10",
"vim" => "50", "vim" => "50",
}.freeze }.freeze
@ -721,11 +748,11 @@ module Homebrew
current_revision = formula.revision current_revision = formula.revision
previous_version = nil previous_version = nil
previous_checksum = nil
previous_version_scheme = nil previous_version_scheme = nil
previous_revision = nil previous_revision = nil
newest_committed_version = nil newest_committed_version = nil
newest_committed_checksum = nil
newest_committed_revision = nil newest_committed_revision = nil
fv.rev_list("origin/master") do |rev| fv.rev_list("origin/master") do |rev|
@ -739,6 +766,7 @@ module Homebrew
previous_revision = f.revision previous_revision = f.revision
newest_committed_version ||= previous_version newest_committed_version ||= previous_version
newest_committed_checksum ||= previous_checksum
newest_committed_revision ||= previous_revision newest_committed_revision ||= previous_revision
end end
@ -746,7 +774,7 @@ module Homebrew
end end
if current_version == previous_version && if current_version == previous_version &&
current_checksum != previous_checksum current_checksum != newest_committed_checksum
problem( problem(
"stable sha256 changed without the version also changing; " \ "stable sha256 changed without the version also changing; " \
"please create an issue upstream to rule out malicious " \ "please create an issue upstream to rule out malicious " \
@ -845,15 +873,6 @@ module Homebrew
# TODO: check could be in RuboCop # TODO: check could be in RuboCop
problem "`env :userpaths` in formulae is deprecated" if line.include?("env :userpaths") problem "`env :userpaths` in formulae is deprecated" if line.include?("env :userpaths")
if line =~ /system ((["'])[^"' ]*(?:\s[^"' ]*)+\2)/
bad_system = Regexp.last_match(1)
unless %w[| < > & ; *].any? { |c| bad_system.include? c }
good_system = bad_system.gsub(" ", "\", \"")
# TODO: check could be in RuboCop
problem "Use `system #{good_system}` instead of `system #{bad_system}` "
end
end
# TODO: check could be in RuboCop # TODO: check could be in RuboCop
problem "`#{Regexp.last_match(1)}` is now unnecessary" if line =~ /(require ["']formula["'])/ problem "`#{Regexp.last_match(1)}` is now unnecessary" if line =~ /(require ["']formula["'])/

View File

@ -531,25 +531,21 @@ module Homebrew
odie "--keep-old was passed but there was no existing bottle block!" if args.keep_old? odie "--keep-old was passed but there was no existing bottle block!" if args.keep_old?
puts output puts output
update_or_add = "add" update_or_add = "add"
if s.include? "stable do" pattern = /(
indent = s.slice(/^( +)stable do/, 1).length (\ {2}\#[^\n]*\n)* # comments
string = s.sub!(/^ {#{indent}}stable do(.|\n)+?^ {#{indent}}end\n/m, '\0' + output + "\n") \ {2}( # two spaces at the beginning
else (url|head)\ ['"][\S\ ]+['"] # url or head with a string
pattern = /( (
(\ {2}\#[^\n]*\n)* # comments ,[\S\ ]*$ # url may have options
\ {2}( # two spaces at the beginning (\n^\ {3}[\S\ ]+$)* # options can be in multiple lines
(url|head)\ ['"][\S\ ]+['"] # url or head with a string )?|
( (homepage|desc|sha256|version|mirror|license)\ ['"][\S\ ]+['"]| # specs with a string
,[\S\ ]*$ # url may have options (revision|version_scheme)\ \d+| # revision with a number
(\n^\ {3}[\S\ ]+$)* # options can be in multiple lines (stable|livecheck)\ do(\n+^\ {4}[\S\ ]+$)*\n+^\ {2}end # components with blocks
)?| )\n+ # multiple empty lines
(homepage|desc|sha1|sha256|version|mirror)\ ['"][\S\ ]+['"]| # specs with a string )+
(revision|version_scheme)\ \d+ # revision with a number /mx
)\n+ # multiple empty lines string = s.sub!(pattern, '\0' + output + "\n")
)+
/mx
string = s.sub!(pattern, '\0' + output + "\n")
end
odie "Bottle block addition failed!" unless string odie "Bottle block addition failed!" unless string
end end
end end

View File

@ -47,6 +47,8 @@ module Homebrew
description: "Explicitly set the <name> of the new formula." description: "Explicitly set the <name> of the new formula."
flag "--set-version=", flag "--set-version=",
description: "Explicitly set the <version> of the new formula." description: "Explicitly set the <version> of the new formula."
flag "--set-license=",
description: "Explicitly set the <license> of the new formula."
flag "--tap=", flag "--tap=",
description: "Generate the new formula within the given tap, specified as <user>`/`<repo>." description: "Generate the new formula within the given tap, specified as <user>`/`<repo>."
switch :force switch :force
@ -68,11 +70,13 @@ module Homebrew
version = args.set_version version = args.set_version
name = args.set_name name = args.set_name
license = args.set_license
tap = args.tap tap = args.tap
fc = FormulaCreator.new fc = FormulaCreator.new
fc.name = name fc.name = name
fc.version = version fc.version = version
fc.license = license
fc.tap = Tap.fetch(tap || "homebrew/core") fc.tap = Tap.fetch(tap || "homebrew/core")
raise TapUnavailableError, tap unless fc.tap.installed? raise TapUnavailableError, tap unless fc.tap.installed?

View File

@ -191,7 +191,10 @@ module Homebrew
# Remove any existing version suffixes, as a new one will be added later # Remove any existing version suffixes, as a new one will be added later
name.sub!(/\b@(.*)\z\b/i, "") name.sub!(/\b@(.*)\z\b/i, "")
versioned_name = Formulary.class_s("#{name}@#{version}") versioned_name = Formulary.class_s("#{name}@#{version}")
result.gsub!("class #{class_name} < Formula", "class #{versioned_name} < Formula") result.sub!("class #{class_name} < Formula", "class #{versioned_name} < Formula")
# Remove bottle blocks, they won't work.
result.sub!(/ bottle do.+?end\n\n/m, "") if destination_tap != source_tap
path = destination_tap.path/"Formula/#{name}@#{version}.rb" path = destination_tap.path/"Formula/#{name}@#{version}.rb"
if path.exist? if path.exist?
@ -205,7 +208,8 @@ module Homebrew
odebug "Overwriting existing formula at #{path}" odebug "Overwriting existing formula at #{path}"
path.delete path.delete
end end
ohai "Writing formula for #{name} from revision #{rev} to #{path}" ohai "Writing formula for #{name} from revision #{rev} to:"
puts path
path.write result path.write result
end end

View File

@ -33,6 +33,9 @@ module Homebrew
switch "--resolve", switch "--resolve",
description: "When a patch fails to apply, leave in progress and allow user to resolve, "\ description: "When a patch fails to apply, leave in progress and allow user to resolve, "\
"instead of aborting." "instead of aborting."
switch "--warn-on-upload-failure",
description: "Warn instead of raising an error if the bottle upload fails. "\
"Useful for repairing bottle uploads that previously failed."
flag "--workflow=", flag "--workflow=",
description: "Retrieve artifacts from the specified workflow (default: `tests.yml`)." description: "Retrieve artifacts from the specified workflow (default: `tests.yml`)."
flag "--artifact=", flag "--artifact=",
@ -73,13 +76,18 @@ module Homebrew
end end
end end
def signoff!(pr, path: ".") def signoff!(pr, tap:)
message = Utils.popen_read "git", "-C", path, "log", "-1", "--pretty=%B" message = Utils.popen_read "git", "-C", tap.path, "log", "-1", "--pretty=%B"
subject = message.lines.first.strip subject = message.lines.first.strip
# Skip the subject and separate lines that look like trailers (e.g. "Co-authored-by") # Skip the subject and separate lines that look like trailers (e.g. "Co-authored-by")
# from lines that look like regular body text. # from lines that look like regular body text.
trailers, body = message.lines.drop(1).partition { |s| s.match?(/^[a-z-]+-by:/i) } trailers, body = message.lines.drop(1).partition { |s| s.match?(/^[a-z-]+-by:/i) }
# Approving reviewers also sign-off on merge
trailers += GitHub.approved_reviews(tap.user, "homebrew-#{tap.repo}", pr).map do |r|
"Signed-off-by: #{r["name"]} <#{r["email"]}>\n"
end
trailers = trailers.uniq.join.strip trailers = trailers.uniq.join.strip
body = body.join.strip.gsub(/\n{3,}/, "\n\n") body = body.join.strip.gsub(/\n{3,}/, "\n\n")
@ -90,7 +98,7 @@ module Homebrew
if Homebrew.args.dry_run? if Homebrew.args.dry_run?
puts "git commit --amend --signoff -m $message" puts "git commit --amend --signoff -m $message"
else else
safe_system "git", "-C", path, "commit", "--amend", "--signoff", "--allow-empty", "-q", "-m", new_message safe_system "git", "-C", tap.path, "commit", "--amend", "--signoff", "--allow-empty", "-q", "-m", new_message
end end
end end
@ -232,7 +240,7 @@ module Homebrew
cd dir do cd dir do
original_commit = Utils.popen_read("git", "-C", tap.path, "rev-parse", "HEAD").chomp original_commit = Utils.popen_read("git", "-C", tap.path, "rev-parse", "HEAD").chomp
cherry_pick_pr! pr, path: tap.path cherry_pick_pr! pr, path: tap.path
signoff! pr, path: tap.path unless args.clean? signoff! pr, tap: tap unless args.clean?
unless args.no_upload? unless args.no_upload?
mirror_formulae(tap, original_commit, org: bintray_org, repo: mirror_repo, publish: !args.no_publish?) mirror_formulae(tap, original_commit, org: bintray_org, repo: mirror_repo, publish: !args.no_publish?)
@ -253,9 +261,10 @@ module Homebrew
upload_args << "--verbose" if Homebrew.args.verbose? upload_args << "--verbose" if Homebrew.args.verbose?
upload_args << "--no-publish" if args.no_publish? upload_args << "--no-publish" if args.no_publish?
upload_args << "--dry-run" if args.dry_run? upload_args << "--dry-run" if args.dry_run?
upload_args << "--warn-on-upload-failure" if args.warn_on_upload_failure?
upload_args << "--root_url=#{args.root_url}" if args.root_url upload_args << "--root_url=#{args.root_url}" if args.root_url
upload_args << "--bintray-org=#{bintray_org}" upload_args << "--bintray-org=#{bintray_org}"
system HOMEBREW_BREW_FILE, *upload_args safe_system HOMEBREW_BREW_FILE, *upload_args
end end
end end
end end

View File

@ -17,6 +17,9 @@ module Homebrew
description: "Apply the bottle commit and upload the bottles, but don't publish them." description: "Apply the bottle commit and upload the bottles, but don't publish them."
switch "-n", "--dry-run", switch "-n", "--dry-run",
description: "Print what would be done rather than doing it." description: "Print what would be done rather than doing it."
switch "--warn-on-upload-failure",
description: "Warn instead of raising an error if the bottle upload fails. "\
"Useful for repairing bottle uploads that previously failed."
flag "--bintray-org=", flag "--bintray-org=",
description: "Upload to the specified Bintray organisation (default: `homebrew`)." description: "Upload to the specified Bintray organisation (default: `homebrew`)."
flag "--root-url=", flag "--root-url=",
@ -42,13 +45,15 @@ module Homebrew
if args.dry_run? if args.dry_run?
puts "brew #{bottle_args.join " "}" puts "brew #{bottle_args.join " "}"
else else
system HOMEBREW_BREW_FILE, *bottle_args safe_system HOMEBREW_BREW_FILE, *bottle_args
end end
if args.dry_run? if args.dry_run?
puts "Upload bottles described by these JSON files to Bintray:\n #{Dir["*.json"].join("\n ")}" puts "Upload bottles described by these JSON files to Bintray:\n #{Dir["*.json"].join("\n ")}"
else else
bintray.upload_bottle_json Dir["*.json"], publish_package: !args.no_publish? bintray.upload_bottle_json(Dir["*.json"],
publish_package: !args.no_publish?,
warn_on_error: args.warn_on_upload_failure?)
end end
end end
end end

View File

@ -0,0 +1,59 @@
# frozen_string_literal: true
require "cli/parser"
require "utils/github"
module Homebrew
module_function
def sponsors_args
Homebrew::CLI::Parser.new do
usage_banner <<~EOS
`sponsors`
Print a Markdown summary of Homebrew's GitHub Sponsors, suitable for pasting into a README.
EOS
end
end
def sponsors
sponsors_args.parse
sponsors = {
"named" => [],
"users" => 0,
"orgs" => 0,
}
GitHub.sponsors_by_tier("Homebrew").each do |tier|
sponsors["named"] += tier["sponsors"] if tier["tier"] >= 100
sponsors["users"] += tier["count"]
sponsors["orgs"] += tier["sponsors"].count { |s| s["type"] == "organization" }
end
items = []
items += sponsors["named"].map { |s| "[#{s["name"]}](https://github.com/#{s["login"]})" }
anon_users = sponsors["users"] - sponsors["named"].length - sponsors["orgs"]
items << if items.length > 1
"#{anon_users} other users"
else
"#{anon_users} users"
end
if sponsors["orgs"] == 1
items << "#{sponsors["orgs"]} organization"
elsif sponsors["orgs"] > 1
items << "#{sponsors["orgs"]} organizations"
end
sponsor_text = if items.length > 2
items[0..-2].join(", ") + " and #{items.last}"
else
items.join(" and ")
end
puts "Homebrew is generously supported by #{sponsor_text} via [GitHub Sponsors](https://github.com/sponsors/Homebrew)."
end
end

View File

@ -0,0 +1,39 @@
# frozen_string_literal: true
require "commands"
require "cli/parser"
require "json"
require "net/http"
require "open-uri"
module Homebrew
module_function
SPDX_PATH = (HOMEBREW_LIBRARY_PATH/"data/spdx.json").freeze
SPDX_DATA_URL = "https://raw.githubusercontent.com/spdx/license-list-data/HEAD/json/licenses.json"
def update_license_data_args
Homebrew::CLI::Parser.new do
usage_banner <<~EOS
`update_license_data` <cmd>
Update SPDX license data in the Homebrew repository.
EOS
switch "--fail-if-changed",
description: "Return a failing status code if current license data's version is different from " \
"the upstream. This can be used to notify CI when the SPDX license data is out of date."
max_named 0
end
end
def update_license_data
update_license_data_args.parse
ohai "Updating SPDX license data..."
curl_download(SPDX_DATA_URL, to: SPDX_PATH, partial: false)
return unless args.fail_if_changed?
safe_system "git", "diff", "--stat", "--exit-code", SPDX_PATH
end
end

View File

@ -29,14 +29,7 @@ class KegUnspecifiedError < UsageError
end end
end end
class MultipleVersionsInstalledError < RuntimeError class MultipleVersionsInstalledError < RuntimeError; end
attr_reader :name
def initialize(name)
@name = name
super "#{name} has multiple installed versions"
end
end
class NotAKegError < RuntimeError; end class NotAKegError < RuntimeError; end

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
module Readall
class << self
def valid_casks?(*)
true
end
end
end

View File

@ -12,6 +12,7 @@ module Homebrew
check_xcode_minimum_version check_xcode_minimum_version
check_clt_minimum_version check_clt_minimum_version
check_if_xcode_needs_clt_installed check_if_xcode_needs_clt_installed
check_if_supported_sdk_available
].freeze ].freeze
end end
@ -357,6 +358,34 @@ module Homebrew
Untap them with `brew untap`. Untap them with `brew untap`.
EOS EOS
end end
def check_if_supported_sdk_available
return unless MacOS.sdk_root_needed?
return if MacOS.sdk
locator = MacOS.sdk_locator
source = if locator.source == :clt
"CLT"
else
"Xcode"
end
all_sdks = locator.all_sdks
sdks_found_msg = unless all_sdks.empty?
<<~EOS
Homebrew found the following SDKs in the #{source} install:
#{locator.all_sdks.map(&:version).join("\n ")}
EOS
end
<<~EOS
Could not find an SDK that supports macOS #{MacOS.version}.
You may have have an outdated or incompatible #{source}.
#{sdks_found_msg}
Please update #{source} or uninstall it if no updates are available.
EOS
end
end end
end end
end end

View File

@ -0,0 +1,3 @@
# frozen_string_literal: true
require "extend/os/linux/readall" if OS.linux?

View File

@ -351,6 +351,9 @@ class Formula
# @see .desc= # @see .desc=
delegate desc: :"self.class" delegate desc: :"self.class"
# The SPDX ID of the software license.
delegate license: :"self.class"
# The homepage for the software. # The homepage for the software.
# @method homepage # @method homepage
# @see .homepage= # @see .homepage=
@ -1109,13 +1112,14 @@ class Formula
return false if tab_tap.nil? return false if tab_tap.nil?
begin begin
Formulary.factory(keg.name) f = Formulary.factory(keg.name)
rescue FormulaUnavailableError rescue FormulaUnavailableError
# formula for this keg is deleted, so defer to allowlist # formula for this keg is deleted, so defer to allowlist
rescue TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError rescue TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError
return false # this keg belongs to another formula return false # this keg belongs to another formula
else else
return false # this keg belongs to another formula # this keg belongs to another unrelated formula
return false unless (f.aliases + f.oldname).include?(keg.name)
end end
end end
to_check = path.relative_path_from(HOMEBREW_PREFIX).to_s to_check = path.relative_path_from(HOMEBREW_PREFIX).to_s
@ -1687,6 +1691,7 @@ class Formula
"aliases" => aliases.sort, "aliases" => aliases.sort,
"versioned_formulae" => versioned_formulae.map(&:name), "versioned_formulae" => versioned_formulae.map(&:name),
"desc" => desc, "desc" => desc,
"license" => license,
"homepage" => homepage, "homepage" => homepage,
"versions" => { "versions" => {
"stable" => stable&.version&.to_s, "stable" => stable&.version&.to_s,
@ -2211,6 +2216,13 @@ class Formula
# <pre>desc "Example formula"</pre> # <pre>desc "Example formula"</pre>
attr_rw :desc attr_rw :desc
# @!attribute [w]
# The SPDX ID of the open-source license that the formula uses.
# Shows when running `brew info`.
#
# <pre>license "BSD-2-Clause"</pre>
attr_rw :license
# @!attribute [w] homepage # @!attribute [w] homepage
# The homepage for the software. Used by users to get more information # The homepage for the software. Used by users to get more information
# about the software and Homebrew maintainers as a point of contact for # about the software and Homebrew maintainers as a point of contact for
@ -2648,9 +2660,13 @@ class Formula
# #
# The block will create, run in and delete a temporary directory. # The block will create, run in and delete a temporary directory.
# #
# We are fine if the executable does not error out, so we know linking # We want tests that don't require any user input
# and building the software was OK. # and test the basic functionality of the application.
# <pre>system bin/"foobar", "--version"</pre> # For example foo build-foo input.foo is a good test
# and foo --version and foo --help are bad tests.
# However, a bad test is better than no test at all.
#
# See: https://docs.brew.sh/Formula-Cookbook#add-a-test-to-the-formula
# #
# <pre>(testpath/"test.file").write <<~EOS # <pre>(testpath/"test.file").write <<~EOS
# writing some test file, if you need to # writing some test file, if you need to

View File

@ -6,7 +6,7 @@ require "erb"
module Homebrew module Homebrew
class FormulaCreator class FormulaCreator
attr_reader :url, :sha256, :desc, :homepage attr_reader :url, :sha256, :desc, :homepage
attr_accessor :name, :version, :tap, :path, :mode attr_accessor :name, :version, :tap, :path, :mode, :license
def url=(url) def url=(url)
@url = url @url = url
@ -100,6 +100,7 @@ module Homebrew
<% end %> <% end %>
sha256 "#{sha256}" sha256 "#{sha256}"
<% end %> <% end %>
license "#{license}"
<% if mode == :cmake %> <% if mode == :cmake %>
depends_on "cmake" => :build depends_on "cmake" => :build

View File

@ -77,6 +77,14 @@ class FormulaInstaller
@attempted = Set.new @attempted = Set.new
end end
def self.installed
@installed ||= Set.new
end
def self.clear_installed
@installed = Set.new
end
# When no build tools are available and build flags are passed through ARGV, # When no build tools are available and build flags are passed through ARGV,
# it's necessary to interrupt the user before any sort of installation # it's necessary to interrupt the user before any sort of installation
# can proceed. Only invoked when the user has no developer tools. # can proceed. Only invoked when the user has no developer tools.
@ -292,7 +300,7 @@ class FormulaInstaller
self.class.attempted << formula self.class.attempted << formula
if pour_bottle?(warn: true) if pour_bottle?
begin begin
pour pour
rescue Exception => e # rubocop:disable Lint/RescueException rescue Exception => e # rubocop:disable Lint/RescueException
@ -700,6 +708,8 @@ class FormulaInstaller
ohai "Summary" if verbose? || show_summary_heading? ohai "Summary" if verbose? || show_summary_heading?
puts summary puts summary
self.class.installed << formula
ensure ensure
unlock unlock
end end
@ -983,11 +993,24 @@ class FormulaInstaller
return if only_deps? return if only_deps?
unless pour_bottle? if pour_bottle?(warn: true)
formula.fetch_patches begin
formula.resources.each(&:fetch) downloader.fetch
end rescue Exception => e # rubocop:disable Lint/RescueException
raise if Homebrew::EnvConfig.developer? ||
Homebrew::EnvConfig.no_bottle_source_fallback? ||
e.is_a?(Interrupt)
@pour_failed = true
onoe e.message
opoo "Bottle installation failed: building from source."
fetch_dependencies
end
end
return if pour_bottle?
formula.fetch_patches
formula.resources.each(&:fetch)
downloader.fetch downloader.fetch
end end

View File

@ -167,7 +167,7 @@ module Language
def needs_python?(python) def needs_python?(python)
return true if build.with?(python) return true if build.with?(python)
(requirements.to_a | deps).any? { |r| r.name == python && r.required? } (requirements.to_a | deps).any? { |r| r.name.split("/").last == python && r.required? }
end end
# Helper method for the common case of installing a Python application. # Helper method for the common case of installing a Python application.

View File

@ -85,6 +85,10 @@ module Homebrew
cargo is part of the rust formula: cargo is part of the rust formula:
brew install rust brew install rust
EOS EOS
when "cargo-completion" then <<~EOS
cargo-completion is part of the rust formula:
brew install rust
EOS
when "uconv" then <<~EOS when "uconv" then <<~EOS
uconv is part of the icu4c formula: uconv is part of the icu4c formula:
brew install icu4c brew install icu4c

View File

@ -97,14 +97,16 @@ module OS
# If no specific SDK is requested, the SDK matching the OS version is returned, # If no specific SDK is requested, the SDK matching the OS version is returned,
# if available. Otherwise, the latest SDK is returned. # if available. Otherwise, the latest SDK is returned.
def sdk(v = nil) def sdk_locator
@locator ||= if CLT.installed? && CLT.provides_sdk? if CLT.installed? && CLT.provides_sdk?
CLTSDKLocator.new CLT.sdk_locator
else else
XcodeSDKLocator.new Xcode.sdk_locator
end end
end
@locator.sdk_if_applicable(v) def sdk(v = nil)
sdk_locator.sdk_if_applicable(v)
end end
def sdk_for_formula(f, v = nil) def sdk_for_formula(f, v = nil)

View File

@ -31,6 +31,10 @@ module OS
SDK.new v, path, source SDK.new v, path, source
end end
def all_sdks
sdk_paths.map { |v, p| SDK.new v, p, source }
end
def sdk_if_applicable(v = nil) def sdk_if_applicable(v = nil)
sdk = begin sdk = begin
if v.nil? if v.nil?
@ -47,15 +51,11 @@ module OS
sdk sdk
end end
private
def source def source
nil nil
end end
def source_version private
OS::Mac::Version::NULL
end
def sdk_prefix def sdk_prefix
"" ""
@ -81,15 +81,11 @@ module OS
end end
class XcodeSDKLocator < BaseSDKLocator class XcodeSDKLocator < BaseSDKLocator
private
def source def source
:xcode :xcode
end end
def source_version private
OS::Mac::Xcode.version
end
def sdk_prefix def sdk_prefix
@sdk_prefix ||= begin @sdk_prefix ||= begin
@ -105,15 +101,11 @@ module OS
end end
class CLTSDKLocator < BaseSDKLocator class CLTSDKLocator < BaseSDKLocator
private
def source def source
:clt :clt
end end
def source_version private
OS::Mac::CLT.version
end
# While CLT SDKs existed prior to Xcode 10, those packages also # While CLT SDKs existed prior to Xcode 10, those packages also
# installed a traditional Unix-style header layout and we prefer # installed a traditional Unix-style header layout and we prefer

View File

@ -107,10 +107,12 @@ module OS
!prefix.nil? !prefix.nil?
end end
def sdk(v = nil) def sdk_locator
@locator ||= XcodeSDKLocator.new @sdk_locator ||= XcodeSDKLocator.new
end
@locator.sdk_if_applicable(v) def sdk(v = nil)
sdk_locator.sdk_if_applicable(v)
end end
def sdk_path(v = nil) def sdk_path(v = nil)
@ -219,10 +221,12 @@ module OS
version >= "8" version >= "8"
end end
def sdk(v = nil) def sdk_locator
@locator ||= CLTSDKLocator.new @sdk_locator ||= CLTSDKLocator.new
end
@locator.sdk_if_applicable(v) def sdk(v = nil)
sdk_locator.sdk_if_applicable(v)
end end
def sdk_path(v = nil) def sdk_path(v = nil)

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
require "formula" require "formula"
require "cask/cask_loader"
module Readall module Readall
class << self class << self
@ -35,28 +36,43 @@ module Readall
end end
def valid_formulae?(formulae) def valid_formulae?(formulae)
failed = false success = true
formulae.each do |file| formulae.each do |file|
Formulary.factory(file) Formulary.factory(file)
rescue Interrupt rescue Interrupt
raise raise
rescue Exception => e # rubocop:disable Lint/RescueException rescue Exception => e # rubocop:disable Lint/RescueException
onoe "Invalid formula: #{file}" onoe "Invalid formula: #{file}"
puts e $stderr.puts e
failed = true success = false
end end
!failed success
end
def valid_casks?(casks)
success = true
casks.each do |file|
Cask::CaskLoader.load(file)
rescue Interrupt
raise
rescue Exception => e # rubocop:disable Lint/RescueException
onoe "Invalid cask: #{file}"
$stderr.puts e
success = false
end
success
end end
def valid_tap?(tap, options = {}) def valid_tap?(tap, options = {})
failed = false success = true
if options[:aliases] if options[:aliases]
valid_aliases = valid_aliases?(tap.alias_dir, tap.formula_dir) valid_aliases = valid_aliases?(tap.alias_dir, tap.formula_dir)
failed = true unless valid_aliases success = false unless valid_aliases
end end
valid_formulae = valid_formulae?(tap.formula_files) valid_formulae = valid_formulae?(tap.formula_files)
failed = true unless valid_formulae valid_casks = valid_casks?(tap.cask_files)
!failed success = false if !valid_formulae || !valid_casks
success
end end
private private
@ -79,3 +95,5 @@ module Readall
end end
end end
end end
require "extend/os/readall"

View File

@ -24,7 +24,8 @@ module RuboCop
[{ name: :mirror, type: :method_call }], [{ name: :mirror, type: :method_call }],
[{ name: :version, type: :method_call }], [{ name: :version, type: :method_call }],
[{ name: :sha256, type: :method_call }], [{ name: :sha256, type: :method_call }],
[{ name: :revision, type: :method_call }], [{ name: :license, type: :method_call }],
[{ name: :revision, type: :method_call }],
[{ name: :version_scheme, type: :method_call }], [{ name: :version_scheme, type: :method_call }],
[{ name: :head, type: :method_call }], [{ name: :head, type: :method_call }],
[{ name: :stable, type: :block_call }], [{ name: :stable, type: :block_call }],

View File

@ -22,18 +22,28 @@ module RuboCop
Firefox Firefox
].freeze ].freeze
reason = string_content(parameters(keg_only_node).first) reason = parameters(keg_only_node).first
offending_node(reason)
name = Regexp.new(@formula_name, Regexp::IGNORECASE) name = Regexp.new(@formula_name, Regexp::IGNORECASE)
reason = reason.sub(name, "") reason = string_content(reason).sub(name, "")
first_word = reason.split.first first_word = reason.split.first
if reason =~ /\A[A-Z]/ && !reason.start_with?(*allowlist) if reason =~ /\A[A-Z]/ && !reason.start_with?(*allowlist)
problem "'#{first_word}' from the keg_only reason should be '#{first_word.downcase}'." problem "'#{first_word}' from the `keg_only` reason should be '#{first_word.downcase}'."
end end
return unless reason.end_with?(".") return unless reason.end_with?(".")
problem "keg_only reason should not end with a period." problem "`keg_only` reason should not end with a period."
end
def autocorrect(node)
lambda do |corrector|
reason = string_content(node)
reason[0] = reason[0].downcase
reason = reason.delete_suffix(".")
corrector.replace(node.source_range, "\"#{reason}\"")
end
end end
end end
end end

View File

@ -555,6 +555,55 @@ module RuboCop
end end
end end
end end
class ShellCommands < FormulaCop
def audit_formula(_node, _class_node, _parent_class_node, body_node)
# Match shell commands separated by spaces in the same string
shell_cmd_with_spaces_regex = /[^"' ]*(?:\s[^"' ]*)+/
popen_commands = [
:popen_read,
:safe_popen_read,
:popen_write,
:safe_popen_write,
]
shell_metacharacters = %w[> < < | ; : & * $ ? : ~ + @ !` ( ) [ ]]
find_every_method_call_by_name(body_node, :system).each do |method|
# Only separate when no shell metacharacters are present
next if shell_metacharacters.any? { |meta| string_content(parameters(method).first).include?(meta) }
next unless match = regex_match_group(parameters(method).first, shell_cmd_with_spaces_regex)
good_args = match[0].gsub(" ", "\", \"")
offending_node(parameters(method).first)
problem "Separate `system` commands into `\"#{good_args}\"`"
end
popen_commands.each do |command|
find_instance_method_call(body_node, "Utils", command) do |method|
index = parameters(method).first.hash_type? ? 1 : 0
# Only separate when no shell metacharacters are present
next if shell_metacharacters.any? { |meta| string_content(parameters(method)[index]).include?(meta) }
next unless match = regex_match_group(parameters(method)[index], shell_cmd_with_spaces_regex)
good_args = match[0].gsub(" ", "\", \"")
offending_node(parameters(method)[index])
problem "Separate `Utils.#{command}` commands into `\"#{good_args}\"`"
end
end
end
def autocorrect(node)
lambda do |corrector|
good_args = node.source.gsub(" ", "\", \"")
corrector.replace(node.source_range, good_args)
end
end
end
end end
end end
end end

View File

@ -47,12 +47,12 @@ class Sandbox
end end
def allow_cvs def allow_cvs
allow_write_path "/Users/#{ENV["USER"]}/.cvspass" allow_write_path "#{ENV["HOME"]}/.cvspass"
end end
def allow_fossil def allow_fossil
allow_write_path "/Users/#{ENV["USER"]}/.fossil" allow_write_path "#{ENV["HOME"]}/.fossil"
allow_write_path "/Users/#{ENV["USER"]}/.fossil-journal" allow_write_path "#{ENV["HOME"]}/.fossil-journal"
end end
def allow_write_cellar(formula) def allow_write_cellar(formula)
@ -63,7 +63,7 @@ class Sandbox
# Xcode projects expect access to certain cache/archive dirs. # Xcode projects expect access to certain cache/archive dirs.
def allow_write_xcode def allow_write_xcode
allow_write_path "/Users/#{ENV["USER"]}/Library/Developer" allow_write_path "#{ENV["HOME"]}/Library/Developer"
end end
def allow_write_log(formula) def allow_write_log(formula)

View File

@ -168,6 +168,7 @@ false:
- ./dev-cmd/test.rb - ./dev-cmd/test.rb
- ./dev-cmd/tests.rb - ./dev-cmd/tests.rb
- ./dev-cmd/unpack.rb - ./dev-cmd/unpack.rb
- ./dev-cmd/update-license-data.rb
- ./dev-cmd/update-test.rb - ./dev-cmd/update-test.rb
- ./dev-cmd/vendor-gems.rb - ./dev-cmd/vendor-gems.rb
- ./development_tools.rb - ./development_tools.rb
@ -185,6 +186,7 @@ false:
- ./extend/os/linux/hardware/cpu.rb - ./extend/os/linux/hardware/cpu.rb
- ./extend/os/linux/install.rb - ./extend/os/linux/install.rb
- ./extend/os/linux/keg_relocate.rb - ./extend/os/linux/keg_relocate.rb
- ./extend/os/linux/readall.rb
- ./extend/os/linux/requirements/osxfuse_requirement.rb - ./extend/os/linux/requirements/osxfuse_requirement.rb
- ./extend/os/linux/system_config.rb - ./extend/os/linux/system_config.rb
- ./extend/os/linux/tap.rb - ./extend/os/linux/tap.rb
@ -348,6 +350,7 @@ false:
- ./test/dev-cmd/audit_spec.rb - ./test/dev-cmd/audit_spec.rb
- ./test/dev-cmd/create_spec.rb - ./test/dev-cmd/create_spec.rb
- ./test/dev-cmd/extract_spec.rb - ./test/dev-cmd/extract_spec.rb
- ./test/dev-cmd/update-license-data_spec.rb
- ./test/diagnostic_checks_spec.rb - ./test/diagnostic_checks_spec.rb
- ./test/download_strategies_spec.rb - ./test/download_strategies_spec.rb
- ./test/error_during_execution_spec.rb - ./test/error_during_execution_spec.rb
@ -443,9 +446,9 @@ false:
- ./unpack_strategy/xar.rb - ./unpack_strategy/xar.rb
- ./unpack_strategy/xz.rb - ./unpack_strategy/xz.rb
- ./unpack_strategy/zip.rb - ./unpack_strategy/zip.rb
- ./upgrade.rb
- ./utils.rb - ./utils.rb
- ./utils/analytics.rb - ./utils/analytics.rb
- ./utils/bottles.rb
- ./utils/curl.rb - ./utils/curl.rb
- ./utils/fork.rb - ./utils/fork.rb
- ./utils/formatter.rb - ./utils/formatter.rb
@ -453,7 +456,6 @@ false:
- ./utils/github.rb - ./utils/github.rb
- ./utils/notability.rb - ./utils/notability.rb
- ./utils/popen.rb - ./utils/popen.rb
- ./utils/tty.rb
- ./utils/user.rb - ./utils/user.rb
false: false:
@ -848,8 +850,6 @@ false:
- ./utils/inreplace.rb - ./utils/inreplace.rb
- ./utils/link.rb - ./utils/link.rb
- ./utils/shebang.rb - ./utils/shebang.rb
- ./utils/shell.rb
- ./utils/svn.rb
- ./version.rb - ./version.rb
true: true:
@ -889,6 +889,10 @@ true:
- ./tap_constants.rb - ./tap_constants.rb
- ./test/support/helper/fixtures.rb - ./test/support/helper/fixtures.rb
- ./test/support/lib/config.rb - ./test/support/lib/config.rb
- ./utils/bottles.rb
- ./utils/shell.rb
- ./utils/svn.rb
- ./utils/tty.rb
- ./version/null.rb - ./version/null.rb
strict: strict:
@ -920,6 +924,7 @@ strict:
- ./extend/os/mac/formula_support.rb - ./extend/os/mac/formula_support.rb
- ./extend/os/missing_formula.rb - ./extend/os/missing_formula.rb
- ./extend/os/pathname.rb - ./extend/os/pathname.rb
- ./extend/os/readall.rb
- ./extend/os/requirements/java_requirement.rb - ./extend/os/requirements/java_requirement.rb
- ./extend/os/requirements/osxfuse_requirement.rb - ./extend/os/requirements/osxfuse_requirement.rb
- ./extend/os/requirements/x11_requirement.rb - ./extend/os/requirements/x11_requirement.rb

View File

@ -1,5 +1,5 @@
# This file is autogenerated. Do not edit it by hand. Regenerate it with: # This file is autogenerated. Do not edit it by hand. Regenerate it with:
# tapioca sync # tapioca sync --exclude json
# typed: true # typed: true

View File

@ -1,5 +1,5 @@
# This file is autogenerated. Do not edit it by hand. Regenerate it with: # This file is autogenerated. Do not edit it by hand. Regenerate it with:
# tapioca sync # tapioca sync --exclude json
# typed: true # typed: true

View File

@ -1,5 +1,5 @@
# This file is autogenerated. Do not edit it by hand. Regenerate it with: # This file is autogenerated. Do not edit it by hand. Regenerate it with:
# tapioca sync # tapioca sync --exclude json
# typed: true # typed: true

View File

@ -1,6 +0,0 @@
# This file is autogenerated. Do not edit it by hand. Regenerate it with:
# tapioca sync
# typed: true

View File

@ -1,6 +0,0 @@
# This file is autogenerated. Do not edit it by hand. Regenerate it with:
# tapioca sync
# typed: true

View File

@ -1,5 +1,5 @@
# This file is autogenerated. Do not edit it by hand. Regenerate it with: # This file is autogenerated. Do not edit it by hand. Regenerate it with:
# tapioca sync # tapioca sync --exclude json
# typed: true # typed: true

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,38 @@
# typed: strict
module Utils::Shell
include Kernel
sig{ params(path: String).returns(T.nilable(Symbol)) }
def from_path(path)
end
sig{ returns(T.nilable(Symbol)) }
def preferred
end
def parent
end
def export_value(key, value, shell = preferred)
end
sig{ returns(String) }
def profile
end
def set_variable_in_profile(variable, value)
end
sig{ params(path: String).returns(T.nilable(String)) }
def prepend_path_in_profile(path)
end
sig{ params(str: String).returns(T.nilable(String)) }
def csh_quote(str)
end
sig{ params(str: String).returns(T.nilable(String)) }
def sh_quote(str)
end
end

View File

@ -0,0 +1,36 @@
# typed: strict
module Tty
include Kernel
sig{ params(string: String).returns(String) }
def strip_ansi(string)
end
sig{ returns(Integer) }
def width()
end
sig{ params(string: String).returns(T.nilable(String)) }
def truncate(string)
end
def append_to_escape_sequence(code)
end
sig{ returns(String) }
def current_escape_sequence()
end
sig{ void }
def reset_escape_sequence!()
end
sig{ returns(String) }
def to_s
end
sig { returns(T::Boolean) }
def color?
end
end

View File

@ -0,0 +1,8 @@
# typed: strict
module Utils
include Kernel
class Bottles
end
end

View File

@ -8,7 +8,7 @@ describe "Homebrew.search_args" do
end end
describe "brew search", :integration_test do describe "brew search", :integration_test do
it "falls back to a GitHub tap search when no formula is found", :needs_network do it "falls back to a GitHub tap search when no formula is found", :needs_macos, :needs_network do
setup_test_formula "testball" setup_test_formula "testball"
setup_remote_tap "homebrew/cask" setup_remote_tap "homebrew/cask"
@ -16,4 +16,13 @@ describe "brew search", :integration_test do
.to output(/firefox/).to_stdout .to output(/firefox/).to_stdout
.and be_a_success .and be_a_success
end end
# doesn't actually need Linux but only want one integration test per-OS.
it "finds formula in search", :need_linux do
setup_test_formula "testball"
expect { brew "search", "testball" }
.to output(/testball/).to_stdout
.and be_a_success
end
end end

View File

@ -79,6 +79,92 @@ module Homebrew
end end
end end
describe "#audit_license" do
let(:spdx_data) {
JSON.parse Pathname(File.join(File.dirname(__FILE__), "../../data/spdx.json")).read
}
let(:custom_spdx_id) { "zzz" }
let(:standard_mismatch_spdx_id) { "0BSD" }
it "does not check if the formula is not a new formula" do
fa = formula_auditor "foo", <<~RUBY, spdx_data: spdx_data, new_formula: false
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license ""
end
RUBY
fa.audit_license
expect(fa.problems).to be_empty
end
it "detects no license info" do
fa = formula_auditor "foo", <<~RUBY, spdx_data: spdx_data, new_formula: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license ""
end
RUBY
fa.audit_license
expect(fa.problems.first).to match "No license specified for package."
end
it "detects if license is not a standard spdx-id" do
fa = formula_auditor "foo", <<~RUBY, spdx_data: spdx_data, new_formula: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license "#{custom_spdx_id}"
end
RUBY
fa.audit_license
expect(fa.problems.first).to match "#{custom_spdx_id} is not a standard SPDX license."
end
it "verifies that a license info is a standard spdx id" do
fa = formula_auditor "foo", <<~RUBY, spdx_data: spdx_data, new_formula: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license "0BSD"
end
RUBY
fa.audit_license
expect(fa.problems).to be_empty
end
it "checks online and verifies that a standard license id is the same "\
"as what is indicated on its Github repo" do
fa = formula_auditor "cask", <<~RUBY, spdx_data: spdx_data, online: true, core_tap: true, new_formula: true
class Cask < Formula
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
head "https://github.com/cask/cask.git"
license "GPL-3.0"
end
RUBY
fa.audit_license
expect(fa.problems).to be_empty
end
it "checks online and detects that a formula-specified license is not "\
"the same as what is indicated on its Github repository" do
fa = formula_auditor "cask", <<~RUBY, online: true, spdx_data: spdx_data, core_tap: true, new_formula: true
class Cask < Formula
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
head "https://github.com/cask/cask.git"
license "#{standard_mismatch_spdx_id}"
end
RUBY
fa.audit_license
expect(fa.problems.first).to match "License mismatch - GitHub license is: GPL-3.0, "\
"but Formulae license states: #{standard_mismatch_spdx_id}."
end
end
describe "#audit_file" do describe "#audit_file" do
specify "no issue" do specify "no issue" do
fa = formula_auditor "foo", <<~RUBY fa = formula_auditor "foo", <<~RUBY
@ -277,6 +363,7 @@ module Homebrew
origin_formula_path.write <<~RUBY origin_formula_path.write <<~RUBY
class Foo#{foo_version} < Formula class Foo#{foo_version} < Formula
url "https://brew.sh/foo-1.0.tar.gz" url "https://brew.sh/foo-1.0.tar.gz"
sha256 "31cccfc6630528db1c8e3a06f6decf2a370060b982841cfab2b8677400a5092e"
revision 2 revision 2
version_scheme 1 version_scheme 1
end end
@ -302,7 +389,7 @@ module Homebrew
formula_path.write text formula_path.write text
end end
def formula_gsub_commit(before, after = "") def formula_gsub_origin_commit(before, after = "")
text = origin_formula_path.read text = origin_formula_path.read
text.gsub!(before, after) text.gsub!(before, after)
origin_formula_path.unlink origin_formula_path.unlink
@ -318,19 +405,48 @@ module Homebrew
end end
end end
context "checksums" do
context "should not change with the same version" do
before do
formula_gsub(
'sha256 "31cccfc6630528db1c8e3a06f6decf2a370060b982841cfab2b8677400a5092e"',
'sha256 "3622d2a53236ed9ca62de0616a7e80fd477a9a3f862ba09d503da188f53ca523"',
)
end
it { is_expected.to match("stable sha256 changed without the version also changing") }
end
context "can change with the different version" do
before do
formula_gsub_origin_commit(
'sha256 "31cccfc6630528db1c8e3a06f6decf2a370060b982841cfab2b8677400a5092e"',
'sha256 "3622d2a53236ed9ca62de0616a7e80fd477a9a3f862ba09d503da188f53ca523"',
)
formula_gsub "foo-1.0.tar.gz", "foo-1.1.tar.gz"
formula_gsub_origin_commit(
'sha256 "3622d2a53236ed9ca62de0616a7e80fd477a9a3f862ba09d503da188f53ca523"',
'sha256 "e048c5e6144f5932d8672c2fade81d9073d5b3ca1517b84df006de3d25414fc1"',
)
end
it { is_expected.to be_nil }
end
end
context "revisions" do context "revisions" do
context "should not be removed when first committed above 0" do context "should not be removed when first committed above 0" do
it { is_expected.to be_nil } it { is_expected.to be_nil }
end end
context "should not decrease with the same version" do context "should not decrease with the same version" do
before { formula_gsub_commit "revision 2", "revision 1" } before { formula_gsub_origin_commit "revision 2", "revision 1" }
it { is_expected.to match("revision should not decrease (from 2 to 1)") } it { is_expected.to match("revision should not decrease (from 2 to 1)") }
end end
context "should not be removed with the same version" do context "should not be removed with the same version" do
before { formula_gsub_commit "revision 2" } before { formula_gsub_origin_commit "revision 2" }
it { is_expected.to match("revision should not decrease (from 2 to 0)") } it { is_expected.to match("revision should not decrease (from 2 to 0)") }
end end
@ -342,15 +458,15 @@ module Homebrew
end end
context "should be removed with a newer version" do context "should be removed with a newer version" do
before { formula_gsub_commit "foo-1.0.tar.gz", "foo-1.1.tar.gz" } before { formula_gsub_origin_commit "foo-1.0.tar.gz", "foo-1.1.tar.gz" }
it { is_expected.to match("'revision 2' should be removed") } it { is_expected.to match("'revision 2' should be removed") }
end end
context "should not warn on an newer version revision removal" do context "should not warn on an newer version revision removal" do
before do before do
formula_gsub_commit "revision 2", "" formula_gsub_origin_commit "revision 2", ""
formula_gsub_commit "foo-1.0.tar.gz", "foo-1.1.tar.gz" formula_gsub_origin_commit "foo-1.0.tar.gz", "foo-1.1.tar.gz"
end end
it { is_expected.to be_nil } it { is_expected.to be_nil }
@ -367,9 +483,9 @@ module Homebrew
context "should not warn on past increment by more than 1" do context "should not warn on past increment by more than 1" do
before do before do
formula_gsub_commit "revision 2", "# no revision" formula_gsub_origin_commit "revision 2", "# no revision"
formula_gsub_commit "foo-1.0.tar.gz", "foo-1.1.tar.gz" formula_gsub_origin_commit "foo-1.0.tar.gz", "foo-1.1.tar.gz"
formula_gsub_commit "# no revision", "revision 3" formula_gsub_origin_commit "# no revision", "revision 3"
end end
it { is_expected.to be_nil } it { is_expected.to be_nil }
@ -378,16 +494,16 @@ module Homebrew
context "version_schemes" do context "version_schemes" do
context "should not decrease with the same version" do context "should not decrease with the same version" do
before { formula_gsub_commit "version_scheme 1" } before { formula_gsub_origin_commit "version_scheme 1" }
it { is_expected.to match("version_scheme should not decrease (from 1 to 0)") } it { is_expected.to match("version_scheme should not decrease (from 1 to 0)") }
end end
context "should not decrease with a new version" do context "should not decrease with a new version" do
before do before do
formula_gsub_commit "foo-1.0.tar.gz", "foo-1.1.tar.gz" formula_gsub_origin_commit "foo-1.0.tar.gz", "foo-1.1.tar.gz"
formula_gsub_commit "version_scheme 1", "" formula_gsub_origin_commit "version_scheme 1", ""
formula_gsub_commit "revision 2", "" formula_gsub_origin_commit "revision 2", ""
end end
it { is_expected.to match("version_scheme should not decrease (from 1 to 0)") } it { is_expected.to match("version_scheme should not decrease (from 1 to 0)") }
@ -395,10 +511,10 @@ module Homebrew
context "should only increment by 1" do context "should only increment by 1" do
before do before do
formula_gsub_commit "version_scheme 1", "# no version_scheme" formula_gsub_origin_commit "version_scheme 1", "# no version_scheme"
formula_gsub_commit "foo-1.0.tar.gz", "foo-1.1.tar.gz" formula_gsub_origin_commit "foo-1.0.tar.gz", "foo-1.1.tar.gz"
formula_gsub_commit "revision 2", "" formula_gsub_origin_commit "revision 2", ""
formula_gsub_commit "# no version_scheme", "version_scheme 3" formula_gsub_origin_commit "# no version_scheme", "version_scheme 3"
end end
it { is_expected.to match("version_schemes should only increment by 1") } it { is_expected.to match("version_schemes should only increment by 1") }
@ -414,8 +530,8 @@ module Homebrew
context "committed can decrease" do context "committed can decrease" do
before do before do
formula_gsub_commit "revision 2" formula_gsub_origin_commit "revision 2"
formula_gsub_commit "foo-1.0.tar.gz", "foo-0.9.tar.gz" formula_gsub_origin_commit "foo-1.0.tar.gz", "foo-0.9.tar.gz"
end end
it { is_expected.to be_nil } it { is_expected.to be_nil }

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
require "cmd/shared_examples/args_parse"
describe "Homebrew.sponsors_args" do
it_behaves_like "parseable arguments"
end

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
require "cmd/shared_examples/args_parse"
describe "Homebrew.update_license_data_args" do
it_behaves_like "parseable arguments"
end

View File

@ -3,7 +3,7 @@
require "exceptions" require "exceptions"
describe MultipleVersionsInstalledError do describe MultipleVersionsInstalledError do
subject { described_class.new("foo") } subject { described_class.new("foo has multiple installed versions") }
its(:to_s) { is_expected.to eq("foo has multiple installed versions") } its(:to_s) { is_expected.to eq("foo has multiple installed versions") }
end end

View File

@ -19,6 +19,18 @@ describe RuboCop::Cop::FormulaAudit::ComponentsOrder do
RUBY RUBY
end end
it "When license precedes sha256" do
expect_offense(<<~RUBY)
class Foo < Formula
homepage "https://brew.sh"
url "https://brew.sh/foo-1.0.tgz"
license "0BSD"
sha256 "samplesha256"
^^^^^^^^^^^^^^^^^^^^^ `sha256` (line 5) should be put before `license` (line 4)
end
RUBY
end
it "When `bottle` precedes `livecheck`" do it "When `bottle` precedes `livecheck`" do
expect_offense(<<~RUBY) expect_offense(<<~RUBY)
class Foo < Formula class Foo < Formula

View File

@ -13,7 +13,7 @@ describe RuboCop::Cop::FormulaAudit::KegOnly do
homepage "https://brew.sh" homepage "https://brew.sh"
keg_only "Because why not" keg_only "Because why not"
^^^^^^^^^^^^^^^^^^^^^^^^^^ 'Because' from the keg_only reason should be 'because'. ^^^^^^^^^^^^^^^^^ 'Because' from the `keg_only` reason should be 'because'.
end end
RUBY RUBY
end end
@ -25,11 +25,53 @@ describe RuboCop::Cop::FormulaAudit::KegOnly do
homepage "https://brew.sh" homepage "https://brew.sh"
keg_only "ending with a period." keg_only "ending with a period."
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ keg_only reason should not end with a period. ^^^^^^^^^^^^^^^^^^^^^^^ `keg_only` reason should not end with a period.
end end
RUBY RUBY
end end
specify "keg_only_autocorrects_downcasing" do
source = <<~RUBY
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
homepage "https://brew.sh"
keg_only "Because why not"
end
RUBY
corrected_source = <<~RUBY
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
homepage "https://brew.sh"
keg_only "because why not"
end
RUBY
new_source = autocorrect_source(source)
expect(new_source).to eq(corrected_source)
end
specify "keg_only_autocorrects_redundant_period" do
source = <<~RUBY
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
homepage "https://brew.sh"
keg_only "ending with a period."
end
RUBY
corrected_source = <<~RUBY
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
homepage "https://brew.sh"
keg_only "ending with a period"
end
RUBY
new_source = autocorrect_source(source)
expect(new_source).to eq(corrected_source)
end
specify "keg_only_handles_block_correctly" do specify "keg_only_handles_block_correctly" do
expect_no_offenses(<<~RUBY) expect_no_offenses(<<~RUBY)
class Foo < Formula class Foo < Formula

View File

@ -1096,3 +1096,252 @@ describe RuboCop::Cop::FormulaAuditStrict::MakeCheck do
include_examples "formulae exist", described_class::MAKE_CHECK_ALLOWLIST include_examples "formulae exist", described_class::MAKE_CHECK_ALLOWLIST
end end
describe RuboCop::Cop::FormulaAuditStrict::ShellCommands do
subject(:cop) { described_class.new }
context "When auditing shell commands" do
it "system arguments should be separated" do
expect_offense(<<~RUBY)
class Foo < Formula
def install
system "foo bar"
^^^^^^^^^ Separate `system` commands into `\"foo\", \"bar\"`
end
end
RUBY
end
it "system arguments with string interpolation should be separated" do
expect_offense(<<~RUBY)
class Foo < Formula
def install
system "\#{bin}/foo bar"
^^^^^^^^^^^^^^^^ Separate `system` commands into `\"\#{bin}/foo\", \"bar\"`
end
end
RUBY
end
it "system arguments with metacharacters should not be separated" do
expect_no_offenses(<<~RUBY)
class Foo < Formula
def install
system "foo bar > baz"
end
end
RUBY
end
it "only the first system argument should be separated" do
expect_no_offenses(<<~RUBY)
class Foo < Formula
def install
system "foo", "bar baz"
end
end
RUBY
end
it "Utils.popen arguments should not be separated" do
expect_no_offenses(<<~RUBY)
class Foo < Formula
def install
Utils.popen("foo bar")
end
end
RUBY
end
it "Utils.popen_read arguments should be separated" do
expect_offense(<<~RUBY)
class Foo < Formula
def install
Utils.popen_read("foo bar")
^^^^^^^^^ Separate `Utils.popen_read` commands into `\"foo\", \"bar\"`
end
end
RUBY
end
it "Utils.safe_popen_read arguments should be separated" do
expect_offense(<<~RUBY)
class Foo < Formula
def install
Utils.safe_popen_read("foo bar")
^^^^^^^^^ Separate `Utils.safe_popen_read` commands into `\"foo\", \"bar\"`
end
end
RUBY
end
it "Utils.popen_write arguments should be separated" do
expect_offense(<<~RUBY)
class Foo < Formula
def install
Utils.popen_write("foo bar")
^^^^^^^^^ Separate `Utils.popen_write` commands into `\"foo\", \"bar\"`
end
end
RUBY
end
it "Utils.safe_popen_write arguments should be separated" do
expect_offense(<<~RUBY)
class Foo < Formula
def install
Utils.safe_popen_write("foo bar")
^^^^^^^^^ Separate `Utils.safe_popen_write` commands into `\"foo\", \"bar\"`
end
end
RUBY
end
it "Utils.popen_read arguments with string interpolation should be separated" do
expect_offense(<<~RUBY)
class Foo < Formula
def install
Utils.popen_read("\#{bin}/foo bar")
^^^^^^^^^^^^^^^^ Separate `Utils.popen_read` commands into `\"\#{bin}/foo\", \"bar\"`
end
end
RUBY
end
it "Utils.popen_read arguments with metacharacters should not be separated" do
expect_no_offenses(<<~RUBY)
class Foo < Formula
def install
Utils.popen_read("foo bar > baz")
end
end
RUBY
end
it "only the first Utils.popen_read argument should be separated" do
expect_no_offenses(<<~RUBY)
class Foo < Formula
def install
Utils.popen_read("foo", "bar baz")
end
end
RUBY
end
it "Utils.popen_read arguments should be separated following a shell variable" do
expect_offense(<<~RUBY)
class Foo < Formula
def install
Utils.popen_read({ "SHELL" => "bash"}, "foo bar")
^^^^^^^^^ Separate `Utils.popen_read` commands into `\"foo\", \"bar\"`
end
end
RUBY
end
it "separates shell commands in system" do
source = <<~RUBY
class Foo < Formula
def install
system "foo bar"
end
end
RUBY
corrected_source = <<~RUBY
class Foo < Formula
def install
system "foo", "bar"
end
end
RUBY
new_source = autocorrect_source(source)
expect(new_source).to eq(corrected_source)
end
it "separates shell commands with string interpolation in system" do
source = <<~RUBY
class Foo < Formula
def install
system "\#{foo}/bar baz"
end
end
RUBY
corrected_source = <<~RUBY
class Foo < Formula
def install
system "\#{foo}/bar", "baz"
end
end
RUBY
new_source = autocorrect_source(source)
expect(new_source).to eq(corrected_source)
end
it "separates shell commands in Utils.popen_read" do
source = <<~RUBY
class Foo < Formula
def install
Utils.popen_read("foo bar")
end
end
RUBY
corrected_source = <<~RUBY
class Foo < Formula
def install
Utils.popen_read("foo", "bar")
end
end
RUBY
new_source = autocorrect_source(source)
expect(new_source).to eq(corrected_source)
end
it "separates shell commands with string interpolation in Utils.popen_read" do
source = <<~RUBY
class Foo < Formula
def install
Utils.popen_read("\#{foo}/bar baz")
end
end
RUBY
corrected_source = <<~RUBY
class Foo < Formula
def install
Utils.popen_read("\#{foo}/bar", "baz")
end
end
RUBY
new_source = autocorrect_source(source)
expect(new_source).to eq(corrected_source)
end
it "separates shell commands following a shell variable in Utils.popen_read" do
source = <<~RUBY
class Foo < Formula
def install
Utils.popen_read({ "SHELL" => "bash" }, "foo bar")
end
end
RUBY
corrected_source = <<~RUBY
class Foo < Formula
def install
Utils.popen_read({ "SHELL" => "bash" }, "foo", "bar")
end
end
RUBY
new_source = autocorrect_source(source)
expect(new_source).to eq(corrected_source)
end
end
end

View File

@ -4,12 +4,10 @@ if ENV["HOMEBREW_TESTS_COVERAGE"]
require "simplecov" require "simplecov"
formatters = [SimpleCov::Formatter::HTMLFormatter] formatters = [SimpleCov::Formatter::HTMLFormatter]
if ENV["HOMEBREW_COVERALLS_REPO_TOKEN"] && RUBY_PLATFORM[/darwin/] if ENV["HOMEBREW_CODECOV_TOKEN"] && RUBY_PLATFORM[/darwin/]
require "coveralls" require "codecov"
Coveralls::Output.no_color if !ENV["HOMEBREW_COLOR"] && (ENV["HOMEBREW_NO_COLOR"] || !$stdout.tty?) formatters << SimpleCov::Formatter::Codecov
formatters << Coveralls::SimpleCov::Formatter
if ENV["TEST_ENV_NUMBER"] if ENV["TEST_ENV_NUMBER"]
SimpleCov.at_exit do SimpleCov.at_exit do
@ -18,16 +16,7 @@ if ENV["HOMEBREW_TESTS_COVERAGE"]
end end
end end
ENV["CI_NAME"] = ENV["HOMEBREW_CI_NAME"] ENV["CODECOV_TOKEN"] = ENV["HOMEBREW_CODECOV_TOKEN"]
ENV["COVERALLS_REPO_TOKEN"] = ENV["HOMEBREW_COVERALLS_REPO_TOKEN"]
ENV["CI_BUILD_NUMBER"] = ENV["HOMEBREW_CI_BUILD_NUMBER"]
ENV["CI_BRANCH"] = ENV["HOMEBREW_CI_BRANCH"]
%r{refs/pull/(?<pr>\d+)/merge} =~ ENV["HOMEBREW_CI_BUILD_NUMBER"]
ENV["CI_PULL_REQUEST"] = pr
ENV["CI_BUILD_URL"] = "https://github.com/#{ENV["HOMEBREW_GITHUB_REPOSITORY"]}/pull/#{pr}/checks"
ENV["CI_JOB_ID"] = ENV["TEST_ENV_NUMBER"] || "1"
end end
SimpleCov.formatters = SimpleCov::Formatter::MultiFormatter.new(formatters) SimpleCov.formatters = SimpleCov::Formatter::MultiFormatter.new(formatters)
@ -174,6 +163,7 @@ RSpec.configure do |config|
Keg.clear_cache Keg.clear_cache
Tab.clear_cache Tab.clear_cache
FormulaInstaller.clear_attempted FormulaInstaller.clear_attempted
FormulaInstaller.clear_installed
TEST_DIRECTORIES.each(&:mkpath) TEST_DIRECTORIES.each(&:mkpath)

View File

@ -46,7 +46,7 @@ RSpec.shared_context "integration test" do
example.run example.run
ensure ensure
FileUtils.rm_r HOMEBREW_PREFIX/"bin" FileUtils.rm_rf HOMEBREW_PREFIX/"bin"
end end
# Generate unique ID to be able to # Generate unique ID to be able to

View File

@ -42,6 +42,21 @@ describe GitHub do
end end
end end
describe "::approved_reviews", :needs_network do
it "can get reviews for a pull request" do
reviews = subject.approved_reviews("Homebrew", "homebrew-core", 1, commit: "deadbeef")
expect(reviews).to eq([])
end
end
describe "::sponsors_by_tier", :needs_network do
it "errors on an unauthenticated token" do
expect {
subject.sponsors_by_tier("Homebrew")
}.to raise_error(/INSUFFICIENT_SCOPES|FORBIDDEN/)
end
end
describe "::get_artifact_url", :needs_network do describe "::get_artifact_url", :needs_network do
it "fails to find a nonexistant workflow" do it "fails to find a nonexistant workflow" do
expect { expect {

242
Library/Homebrew/upgrade.rb Normal file
View File

@ -0,0 +1,242 @@
# frozen_string_literal: true
require "reinstall"
require "formula_installer"
require "development_tools"
require "messages"
require "cleanup"
module Homebrew
module_function
def upgrade_formulae(formulae_to_install)
return if formulae_to_install.empty?
return if args.dry_run?
# Sort keg-only before non-keg-only formulae to avoid any needless conflicts
# with outdated, non-keg-only versions of formulae being upgraded.
formulae_to_install.sort! do |a, b|
if !a.keg_only? && b.keg_only?
1
elsif a.keg_only? && !b.keg_only?
-1
else
0
end
end
formulae_to_install.each do |f|
Migrator.migrate_if_needed(f)
begin
upgrade_formula(f)
Cleanup.install_formula_clean!(f)
rescue UnsatisfiedRequirements => e
Homebrew.failed = true
onoe "#{f}: #{e}"
end
end
end
def upgrade_formula(f)
return if args.dry_run?
if f.opt_prefix.directory?
keg = Keg.new(f.opt_prefix.resolved_path)
keg_had_linked_opt = true
keg_was_linked = keg.linked?
end
formulae_maybe_with_kegs = [f] + f.old_installed_formulae
outdated_kegs = formulae_maybe_with_kegs
.map(&:linked_keg)
.select(&:directory?)
.map { |k| Keg.new(k.resolved_path) }
linked_kegs = outdated_kegs.select(&:linked?)
if f.opt_prefix.directory?
keg = Keg.new(f.opt_prefix.resolved_path)
tab = Tab.for_keg(keg)
end
build_options = BuildOptions.new(Options.create(args.flags_only), f.options)
options = build_options.used_options
options |= f.build.used_options
options &= f.options
fi = FormulaInstaller.new(f)
fi.options = options
fi.build_bottle = args.build_bottle?
fi.installed_on_request = args.named.present?
fi.link_keg ||= keg_was_linked if keg_had_linked_opt
if tab
fi.build_bottle ||= tab.built_bottle?
fi.installed_as_dependency = tab.installed_as_dependency
fi.installed_on_request ||= tab.installed_on_request
end
upgrade_version = if f.optlinked?
"#{Keg.new(f.opt_prefix).version} -> #{f.pkg_version}"
else
"-> #{f.pkg_version}"
end
oh1 "Upgrading #{Formatter.identifier(f.full_specified_name)} #{upgrade_version} #{fi.options.to_a.join(" ")}"
fi.prelude
fi.fetch
# first we unlink the currently active keg for this formula otherwise it is
# possible for the existing build to interfere with the build we are about to
# do! Seriously, it happens!
outdated_kegs.each(&:unlink)
fi.install
fi.finish
rescue FormulaInstallationAlreadyAttemptedError
# We already attempted to upgrade f as part of the dependency tree of
# another formula. In that case, don't generate an error, just move on.
nil
rescue CannotInstallFormulaError => e
ofail e
rescue BuildError => e
e.dump
puts
Homebrew.failed = true
rescue DownloadError => e
ofail e
ensure
# restore previous installation state if build failed
begin
linked_kegs.each(&:link) unless f.latest_version_installed?
rescue
nil
end
end
def check_installed_dependents
installed_formulae = FormulaInstaller.installed.to_a
return if installed_formulae.empty?
outdated_dependents =
installed_formulae.flat_map(&:runtime_installed_formula_dependents)
.select(&:outdated?)
return if outdated_dependents.blank?
outdated_dependents -= installed_formulae if args.dry_run?
upgradeable_dependents =
outdated_dependents.reject(&:pinned?)
.sort { |a, b| depends_on(a, b) }
pinned_dependents =
outdated_dependents.select(&:pinned?)
.sort { |a, b| depends_on(a, b) }
if pinned_dependents.present?
plural = "dependent".pluralize(pinned_dependents.count)
ohai "Not upgrading #{pinned_dependents.count} pinned #{plural}:"
puts(pinned_dependents.map do |f|
"#{f.full_specified_name} #{f.pkg_version}"
end.join(", "))
end
# Print the upgradable dependents.
if upgradeable_dependents.blank?
ohai "No outdated dependents to upgrade!" unless args.dry_run?
else
plural = "dependent".pluralize(upgradeable_dependents.count)
verb = args.dry_run? ? "Would upgrade" : "Upgrading"
ohai "#{verb} #{upgradeable_dependents.count} #{plural}:"
formulae_upgrades = upgradeable_dependents.map do |f|
name = f.full_specified_name
if f.optlinked?
"#{name} #{Keg.new(f.opt_prefix).version} -> #{f.pkg_version}"
else
"#{name} #{f.pkg_version}"
end
end
puts formulae_upgrades.join(", ")
end
upgrade_formulae(upgradeable_dependents)
# Assess the dependents tree again now we've upgraded.
oh1 "Checking for dependents of upgraded formulae..." unless args.dry_run?
broken_dependents = CacheStoreDatabase.use(:linkage) do |db|
installed_formulae.flat_map(&:runtime_installed_formula_dependents)
.select do |f|
keg = f.opt_or_installed_prefix_keg
next unless keg
LinkageChecker.new(keg, cache_db: db)
.broken_library_linkage?
end.compact
end
if broken_dependents.blank?
if args.dry_run?
ohai "No currently broken dependents found!"
opoo "If they are broken by the upgrade they will also be upgraded or reinstalled."
else
ohai "No broken dependents found!"
end
return
end
reinstallable_broken_dependents =
broken_dependents.reject(&:outdated?)
.reject(&:pinned?)
.sort { |a, b| depends_on(a, b) }
outdated_pinned_broken_dependents =
broken_dependents.select(&:outdated?)
.select(&:pinned?)
.sort { |a, b| depends_on(a, b) }
# Print the pinned dependents.
if outdated_pinned_broken_dependents.present?
count = outdated_pinned_broken_dependents.count
plural = "dependent".pluralize(outdated_pinned_broken_dependents.count)
onoe "Not reinstalling #{count} broken and outdated, but pinned #{plural}:"
$stderr.puts(outdated_pinned_broken_dependents.map do |f|
"#{f.full_specified_name} #{f.pkg_version}"
end.join(", "))
end
# Print the broken dependents.
if reinstallable_broken_dependents.blank?
ohai "No broken dependents to reinstall!"
else
count = reinstallable_broken_dependents.count
plural = "dependent".pluralize(reinstallable_broken_dependents.count)
ohai "Reinstalling #{count} broken #{plural} from source:"
puts reinstallable_broken_dependents.map(&:full_specified_name)
.join(", ")
end
return if args.dry_run?
reinstallable_broken_dependents.each do |f|
reinstall_formula(f, build_from_source: true)
rescue FormulaInstallationAlreadyAttemptedError
# We already attempted to reinstall f as part of the dependency tree of
# another formula. In that case, don't generate an error, just move on.
nil
rescue CannotInstallFormulaError => e
ofail e
rescue BuildError => e
e.dump
puts
Homebrew.failed = true
rescue DownloadError => e
ofail e
end
end
# @private
def depends_on(a, b)
if a.opt_or_installed_prefix_keg
&.runtime_dependencies
&.any? { |d| d["full_name"] == b.full_name }
1
else
a <=> b
end
end
end

View File

@ -54,21 +54,25 @@ def curl(*args, secrets: [], **options)
secrets: secrets secrets: secrets
end end
def curl_download(*args, to: nil, **options) def curl_download(*args, to: nil, partial: true, **options)
destination = Pathname(to) destination = Pathname(to)
destination.dirname.mkpath destination.dirname.mkpath
range_stdout = curl_output("--location", "--range", "0-1", if partial
"--dump-header", "-", range_stdout = curl_output("--location", "--range", "0-1",
"--write-out", "%\{http_code}", "--dump-header", "-",
"--output", "/dev/null", *args, **options).stdout "--write-out", "%\{http_code}",
headers, _, http_status = range_stdout.partition("\r\n\r\n") "--output", "/dev/null", *args, **options).stdout
headers, _, http_status = range_stdout.partition("\r\n\r\n")
supports_partial_download = http_status.to_i == 206 # Partial Content supports_partial_download = http_status.to_i == 206 # Partial Content
if supports_partial_download && if supports_partial_download &&
destination.exist? && destination.exist? &&
destination.size == %r{^.*Content-Range: bytes \d+-\d+/(\d+)\r\n.*$}m.match(headers)&.[](1)&.to_i destination.size == %r{^.*Content-Range: bytes \d+-\d+/(\d+)\r\n.*$}m.match(headers)&.[](1)&.to_i
return # We've already downloaded all the bytes return # We've already downloaded all the bytes
end
else
supports_partial_download = false
end end
continue_at = if destination.exist? && supports_partial_download continue_at = if destination.exist? && supports_partial_download

View File

@ -231,6 +231,15 @@ module GitHub
end end
end end
def open_graphql(query, scopes: [].freeze)
data = { query: query }
result = open_api("https://api.github.com/graphql", scopes: scopes, data: data, request_method: "POST")
raise Error, result["errors"].map { |e| "#{e["type"]}: #{e["message"]}" }.join("\n") if result["errors"].present?
result["data"]
end
def raise_api_error(output, errors, http_code, headers, scopes) def raise_api_error(output, errors, http_code, headers, scopes)
json = begin json = begin
JSON.parse(output) JSON.parse(output)
@ -429,6 +438,48 @@ module GitHub
open_api(uri) { |json| json.fetch("items", []) } open_api(uri) { |json| json.fetch("items", []) }
end end
def approved_reviews(user, repo, pr, commit: nil)
query = <<~EOS
{ repository(name: "#{repo}", owner: "#{user}") {
pullRequest(number: #{pr}) {
reviews(states: APPROVED, first: 100) {
nodes {
author {
... on User { email login name databaseId }
... on Organization { email login name databaseId }
}
authorAssociation
commit { oid }
}
}
}
}
}
EOS
result = open_graphql(query, scopes: ["user:email"])
reviews = result["repository"]["pullRequest"]["reviews"]["nodes"]
reviews.map do |r|
next if commit.present? && commit != r["commit"]["oid"]
next unless %w[MEMBER OWNER].include? r["authorAssociation"]
email = if r["author"]["email"].blank?
"#{r["author"]["databaseId"]}+#{r["author"]["login"]}@users.noreply.github.com"
else
r["author"]["email"]
end
name = r["author"]["name"].presence || r["author"]["login"]
{
"email" => email,
"name" => name,
"login" => r["author"]["login"],
}
end.compact
end
def dispatch_event(user, repo, event, **payload) def dispatch_event(user, repo, event, **payload)
url = "#{API_URL}/repos/#{user}/#{repo}/dispatches" url = "#{API_URL}/repos/#{user}/#{repo}/dispatches"
open_api(url, data: { event_type: event, client_payload: payload }, open_api(url, data: { event_type: event, client_payload: payload },
@ -484,20 +535,21 @@ module GitHub
end end
def sponsors_by_tier(user) def sponsors_by_tier(user)
url = "https://api.github.com/graphql" query = <<~EOS
data = { { organization(login: "#{user}") {
query: <<~EOS, sponsorsListing {
{ tiers(first: 10, orderBy: {field: MONTHLY_PRICE_IN_CENTS, direction: DESC}) {
organization(login: "#{user}") { nodes {
sponsorsListing { monthlyPriceInDollars
tiers(first: 100) { adminInfo {
nodes { sponsorships(first: 100, includePrivate: true) {
monthlyPriceInDollars totalCount
adminInfo { nodes {
sponsorships(first: 100) { privacyLevel
totalCount sponsorEntity {
nodes { __typename
sponsor { login } ... on Organization { login name }
... on User { login name }
} }
} }
} }
@ -506,9 +558,44 @@ module GitHub
} }
} }
} }
EOS }
} EOS
open_api(url, scopes: ["admin:org", "user"], data: data, request_method: "POST") result = open_graphql(query, scopes: ["admin:org", "user"])
tiers = result["organization"]["sponsorsListing"]["tiers"]["nodes"]
tiers.map do |t|
tier = t["monthlyPriceInDollars"]
raise Error, "Your token needs the 'admin:org' scope to access this API" if t["adminInfo"].nil?
sponsorships = t["adminInfo"]["sponsorships"]
count = sponsorships["totalCount"]
sponsors = sponsorships["nodes"].map do |sponsor|
next unless sponsor["privacyLevel"] == "PUBLIC"
se = sponsor["sponsorEntity"]
{
"name" => se["name"].presence || sponsor["login"],
"login" => se["login"],
"type" => se["__typename"].downcase,
}
end.compact
{
"tier" => tier,
"count" => count,
"sponsors" => sponsors,
}
end.compact
end
def get_repo_license(user, repo)
response = GitHub.open_api("#{GitHub::API_URL}/repos/#{user}/#{repo}/license")
return unless response.key?("license")
response["license"]["spdx_id"]
rescue GitHub::HTTPNotFoundError
nil
end end
def api_errors def api_errors

View File

@ -14,18 +14,15 @@ $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ast-2.4.1/lib"
$:.unshift "#{path}/" $:.unshift "#{path}/"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/universal-darwin-19/2.6.0/byebug-11.1.3" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/universal-darwin-19/2.6.0/byebug-11.1.3"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/byebug-11.1.3/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/byebug-11.1.3/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/connection_pool-2.2.3/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/universal-darwin-19/2.6.0/json-2.3.1"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/universal-darwin-19/2.6.0/json-2.3.0" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/json-2.3.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/json-2.3.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/docile-1.3.2/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/docile-1.3.2/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/simplecov-html-0.10.2/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/simplecov-html-0.12.2/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/simplecov-0.16.1/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/simplecov-0.18.5/lib"
$:.unshift "#{path}/../../../../../../../../Library/Ruby/Gems/2.6.0/gems/sync-0.5.0/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/url-0.3.2/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/tins-1.25.0/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/codecov-0.1.17/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/term-ansicolor-1.7.1/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/connection_pool-2.2.3/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/thor-1.0.1/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/diff-lcs-1.4.4/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/coveralls-0.8.23/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/diff-lcs-1.4.2/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/universal-darwin-19/2.6.0/unf_ext-0.0.7.7" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/universal-darwin-19/2.6.0/unf_ext-0.0.7.7"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/unf_ext-0.0.7.7/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/unf_ext-0.0.7.7/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/unf-0.1.4/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/unf-0.1.4/lib"
@ -67,5 +64,5 @@ $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ruby-progressbar-1.10
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/unicode-display_width-1.7.0/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/unicode-display_width-1.7.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-0.86.0/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-0.86.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-performance-1.6.1/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-performance-1.6.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-rspec-1.40.0/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-rspec-1.41.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ruby-macho-2.2.0/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ruby-macho-2.2.0/lib"

View File

@ -1,87 +0,0 @@
# frozen_string_literal: true
module RuboCop
module RSpec
# Wrapper for RSpec example groups
class ExampleGroup < Concept
# @!method scope_change?(node)
#
# Detect if the node is an example group or shared example
#
# Selectors which indicate that we should stop searching
#
def_node_matcher :scope_change?, (
ExampleGroups::ALL + SharedGroups::ALL + Includes::ALL
).block_pattern
def subjects
subjects_in_scope(node)
end
def examples
examples_in_scope(node).map(&Example.public_method(:new))
end
def hooks
hooks_in_scope(node).map(&Hook.public_method(:new))
end
private
def subjects_in_scope(node)
node.each_child_node.flat_map do |child|
find_subjects(child)
end
end
def find_subjects(node)
return [] if scope_change?(node)
if subject?(node)
[node]
else
subjects_in_scope(node)
end
end
def hooks_in_scope(node)
node.each_child_node.flat_map do |child|
find_hooks(child)
end
end
def find_hooks(node)
return [] if scope_change?(node) || example?(node)
if hook?(node)
[node]
else
hooks_in_scope(node)
end
end
def examples_in_scope(node, &blk)
node.each_child_node.flat_map do |child|
find_examples(child, &blk)
end
end
# Recursively search for examples within the current scope
#
# Searches node for examples and halts when a scope change is detected
#
# @param node [RuboCop::Node] node to recursively search for examples
#
# @return [Array<RuboCop::Node>] discovered example nodes
def find_examples(node)
return [] if scope_change?(node)
if example?(node)
[node]
else
examples_in_scope(node)
end
end
end
end
end

View File

@ -13,6 +13,7 @@ require_relative 'rubocop/rspec/top_level_describe'
require_relative 'rubocop/rspec/wording' require_relative 'rubocop/rspec/wording'
require_relative 'rubocop/rspec/language' require_relative 'rubocop/rspec/language'
require_relative 'rubocop/rspec/language/node_pattern' require_relative 'rubocop/rspec/language/node_pattern'
require_relative 'rubocop/rspec/top_level_group'
require_relative 'rubocop/rspec/concept' require_relative 'rubocop/rspec/concept'
require_relative 'rubocop/rspec/example_group' require_relative 'rubocop/rspec/example_group'
require_relative 'rubocop/rspec/example' require_relative 'rubocop/rspec/example'

View File

@ -43,12 +43,14 @@ module RuboCop
def_node_matcher :rails_metadata?, <<-PATTERN def_node_matcher :rails_metadata?, <<-PATTERN
(pair (pair
(sym :type) (sym :type)
(sym {:request :feature :system :routing :view}) (sym {
:channel :controller :helper :job :mailer :model :request
:routing :view :feature :system :mailbox
}
)
) )
PATTERN PATTERN
def_node_matcher :shared_group?, SharedGroups::ALL.block_pattern
def on_top_level_describe(node, (described_value, _)) def on_top_level_describe(node, (described_value, _))
return if shared_group?(root_node) return if shared_group?(root_node)
return if valid_describe?(node) return if valid_describe?(node)

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