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

6
.gitignore vendored
View File

@ -86,7 +86,7 @@
**/vendor/bundle/ruby/*/gems/byebug-*/
**/vendor/bundle/ruby/*/gems/coderay-*/
**/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/docile-*/
**/vendor/bundle/ruby/*/gems/domain_name-*/
@ -127,11 +127,9 @@
**/vendor/bundle/ruby/*/gems/ruby-prof-*/
**/vendor/bundle/ruby/*/gems/ruby-progressbar-*/
**/vendor/bundle/ruby/*/gems/simplecov-*/
**/vendor/bundle/ruby/*/gems/simplecov-cobertura-*/
**/vendor/bundle/ruby/*/gems/simplecov-html-*/
**/vendor/bundle/ruby/*/gems/term-ansicolor-*/
**/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-*/
**/vendor/bundle/ruby/*/gems/unicode-display_width-*/

View File

@ -1,4 +1,5 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require "English"
@ -20,7 +21,8 @@ SimpleCov.start do
# Just save result, but don't write formatted output.
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::ResultMerger.store_result(simplecov_result)
@ -50,8 +52,8 @@ SimpleCov.start do
require "rbconfig"
host_os = RbConfig::CONFIG["host_os"]
add_filter %r{/os/mac} if host_os !~ /darwin/
add_filter %r{/os/linux} if host_os !~ /linux/
add_filter %r{/os/mac} unless /darwin/.match?(host_os)
add_filter %r{/os/linux} unless /linux/.match?(host_os)
# Add groups and the proper project name to the output.
project_name "Homebrew"

View File

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

View File

@ -9,15 +9,13 @@ GEM
zeitwerk (~> 2.2, >= 2.2.2)
ast (2.4.1)
byebug (11.1.3)
codecov (0.1.17)
json
simplecov
url
concurrent-ruby (1.1.6)
connection_pool (2.2.3)
coveralls (0.8.23)
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)
diff-lcs (1.4.4)
docile (1.3.2)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
@ -26,7 +24,7 @@ GEM
domain_name (~> 0.5)
i18n (1.8.3)
concurrent-ruby (~> 1.0)
json (2.3.0)
json (2.3.1)
mechanize (2.7.6)
domain_name (~> 0.5, >= 0.5.1)
http-cookie (~> 1.0)
@ -95,28 +93,22 @@ GEM
parser (>= 2.7.0.1)
rubocop-performance (1.6.1)
rubocop (>= 0.71.0)
rubocop-rspec (1.40.0)
rubocop-rspec (1.41.0)
rubocop (>= 0.68.1)
ruby-macho (2.2.0)
ruby-progressbar (1.10.1)
simplecov (0.16.1)
simplecov (0.18.5)
docile (~> 1.1)
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.2)
sync (0.5.0)
term-ansicolor (1.7.1)
tins (~> 1.0)
thor (1.0.1)
simplecov-html (~> 0.11)
simplecov-html (0.12.2)
thread_safe (0.3.6)
tins (1.25.0)
sync
tzinfo (1.2.7)
thread_safe (~> 0.1)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.7)
unicode-display_width (1.7.0)
url (0.3.2)
webrobots (0.1.2)
zeitwerk (2.3.1)
@ -126,8 +118,8 @@ PLATFORMS
DEPENDENCIES
activesupport
byebug
codecov
concurrent-ruby
coveralls (~> 0.8)
mechanize
parallel_tests
plist
@ -143,4 +135,4 @@ DEPENDENCIES
simplecov
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)
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?
result = open_api url, *args
json = JSON.parse(result.stdout)
@ -50,12 +50,15 @@ class Bintray
result
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"
result = open_api url, "--request", "POST"
result = open_api url, "--request", "POST", "--fail"
json = JSON.parse(result.stdout)
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
odebug "Published #{json["files"]} bottles"
@ -143,7 +146,7 @@ class Bintray
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|
hash.deep_merge(JSON.parse(IO.read(json_file)))
end
@ -161,14 +164,19 @@ class Bintray
odebug "Checking remote file #{@bintray_org}/#{bintray_repo}/#{filename}"
if file_published? repo: bintray_repo, remote_file: filename
raise Error, <<~EOS
#{filename} is already published.
already_published = "#{filename} is already published."
failed_message = <<~EOS
#{already_published}
Please remove it manually from:
https://bintray.com/#{@bintray_org}/#{bintray_repo}/#{bintray_package}/view#files
Or run:
curl -X DELETE -u $HOMEBREW_BINTRAY_USER:$HOMEBREW_BINTRAY_KEY \\
https://api.bintray.com/content/#{@bintray_org}/#{bintray_repo}/#{filename}
EOS
raise Error, failed_message unless warn_on_error
opoo already_published
next
end
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
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

View File

@ -36,9 +36,11 @@ module Homebrew
# Reset cache values reliant on named_args
@formulae = nil
@resolved_formulae = nil
@resolved_formulae_casks = nil
@formulae_paths = nil
@casks = nil
@kegs = nil
@kegs_casks = nil
self[:named_args] = named_args
self[:named_args].freeze
@ -96,6 +98,25 @@ module Homebrew
end.uniq(&:name).freeze
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
@formulae_paths ||= (downcased_unique_named - casks).map do |name|
Formulary.path(name)
@ -108,57 +129,35 @@ module Homebrew
end
def kegs
require "keg"
require "formula"
require "missing_formula"
@kegs ||= downcased_unique_named.map do |name|
raise UsageError if name.empty?
rack = Formulary.to_rack(name.downcase)
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
resolve_keg name
rescue NoSuchKegError => e
if (reason = Homebrew::MissingFormula.suggest_command(name, "uninstall"))
$stderr.puts reason
end
raise e
end.freeze
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?
!(HEAD? || devel?)
end
@ -241,6 +240,50 @@ module Homebrew
default
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

View File

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

View File

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

View File

@ -11,10 +11,10 @@ module Homebrew
usage_banner <<~EOS
`readall` [<options>] [<tap>]
Import all formulae 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
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 items when making
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
switch "--aliases",
description: "Verify any alias symlinks in each tap."
@ -30,7 +30,7 @@ module Homebrew
if args.syntax?
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)
end

View File

@ -6,6 +6,10 @@ require "messages"
require "reinstall"
require "cli/parser"
require "cleanup"
require "cask/cmd"
require "cask/utils"
require "cask/macos"
require "upgrade"
module Homebrew
module_function
@ -56,7 +60,8 @@ module Homebrew
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?
onoe "#{f.full_name} is pinned. You must unpin it to reinstall."
next
@ -65,6 +70,16 @@ module Homebrew
reinstall_formula(f)
Cleanup.install_formula_clean!(f)
end
check_installed_dependents
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

View File

@ -5,6 +5,9 @@ require "formula"
require "diagnostic"
require "migrator"
require "cli/parser"
require "cask/all"
require "cask/cmd"
require "cask/cask_loader"
module Homebrew
module_function
@ -29,15 +32,26 @@ module Homebrew
def uninstall
uninstall_args.parse
kegs_by_rack = if args.force?
Hash[args.named.map do |name|
rack = Formulary.to_rack(name)
next unless rack.directory?
if args.force?
casks = []
kegs_by_rack = {}
[rack, rack.subdirs.map { |d| Keg.new(d) }]
end]
args.named.each do |name|
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
args.kegs.group_by(&:rack)
all_kegs, casks = args.kegs_casks
kegs_by_rack = all_kegs.group_by(&:rack)
end
handle_unsatisfied_dependents(kegs_by_rack)
@ -108,6 +122,13 @@ module Homebrew
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
ofail e
puts "Run `brew uninstall --force #{e.name}` to remove all versions."

View File

@ -1,12 +1,9 @@
# frozen_string_literal: true
require "install"
require "reinstall"
require "formula_installer"
require "development_tools"
require "messages"
require "cleanup"
require "cli/parser"
require "formula_installer"
require "install"
require "upgrade"
module Homebrew
module_function
@ -114,241 +111,8 @@ module Homebrew
upgrade_formulae(formulae_to_install)
check_dependents(formulae_to_install)
check_installed_dependents
Homebrew.messages.display_messages
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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,7 @@ require "date"
require "missing_formula"
require "digest"
require "cli/parser"
require "json"
module Homebrew
module_function
@ -109,7 +110,9 @@ module Homebrew
# Check style in a single batch run up front for performance
style_results = Style.check_style_json(style_files, options) if style_files
# load licenses
spdx = HOMEBREW_LIBRARY_PATH/"data/spdx.json"
spdx_data = JSON.parse(spdx.read)
new_formula_problem_lines = []
audit_formulae.sort.each do |f|
only = only_cops ? ["style"] : args.only
@ -120,6 +123,7 @@ module Homebrew
git: git,
only: only,
except: args.except,
spdx_data: spdx_data,
}
options[:style_offenses] = style_results.file_offenses(f.path) if style_results
options[:display_cop_names] = args.display_cop_names?
@ -215,6 +219,7 @@ module Homebrew
@new_formula_problems = []
@text = FormulaText.new(formula.path)
@specs = %w[stable devel head].map { |s| formula.send(s) }.compact
@spdx_data = options[:spdx_data]
end
def audit_style
@ -327,6 +332,27 @@ module Homebrew
openssl@1.1
].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
@specs.each do |spec|
# Check for things we don't like to depend on.
@ -502,8 +528,9 @@ module Homebrew
end
def audit_github_repository
user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*})
return if user.nil?
user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*}) if @new_formula
return if user.blank?
warning = SharedAudits.github(user, repo)
return if warning.nil?
@ -512,8 +539,8 @@ module Homebrew
end
def audit_gitlab_repository
user, repo = get_repo_data(%r{https?://gitlab\.com/([^/]+)/([^/]+)/?.*})
return if user.nil?
user, repo = get_repo_data(%r{https?://gitlab\.com/([^/]+)/([^/]+)/?.*}) if @new_formula
return if user.blank?
warning = SharedAudits.gitlab(user, repo)
return if warning.nil?
@ -522,8 +549,8 @@ module Homebrew
end
def audit_bitbucket_repository
user, repo = get_repo_data(%r{https?://bitbucket\.org/([^/]+)/([^/]+)/?.*})
return if user.nil?
user, repo = get_repo_data(%r{https?://bitbucket\.org/([^/]+)/([^/]+)/?.*}) if @new_formula
return if user.blank?
warning = SharedAudits.bitbucket(user, repo)
return if warning.nil?
@ -534,7 +561,6 @@ module Homebrew
def get_repo_data(regex)
return unless @core_tap
return unless @online
return unless @new_formula
_, user, repo = *regex.match(formula.stable.url) if formula.stable
_, user, repo = *regex.match(formula.homepage) unless user
@ -554,6 +580,7 @@ module Homebrew
"aws-sdk-cpp" => "10",
"awscli@1" => "10",
"balena-cli" => "10",
"gatsby-cli" => "10",
"quicktype" => "10",
"vim" => "50",
}.freeze
@ -721,11 +748,11 @@ module Homebrew
current_revision = formula.revision
previous_version = nil
previous_checksum = nil
previous_version_scheme = nil
previous_revision = nil
newest_committed_version = nil
newest_committed_checksum = nil
newest_committed_revision = nil
fv.rev_list("origin/master") do |rev|
@ -739,6 +766,7 @@ module Homebrew
previous_revision = f.revision
newest_committed_version ||= previous_version
newest_committed_checksum ||= previous_checksum
newest_committed_revision ||= previous_revision
end
@ -746,7 +774,7 @@ module Homebrew
end
if current_version == previous_version &&
current_checksum != previous_checksum
current_checksum != newest_committed_checksum
problem(
"stable sha256 changed without the version also changing; " \
"please create an issue upstream to rule out malicious " \
@ -845,15 +873,6 @@ module Homebrew
# TODO: check could be in RuboCop
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
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?
puts output
update_or_add = "add"
if s.include? "stable do"
indent = s.slice(/^( +)stable do/, 1).length
string = s.sub!(/^ {#{indent}}stable do(.|\n)+?^ {#{indent}}end\n/m, '\0' + output + "\n")
else
pattern = /(
(\ {2}\#[^\n]*\n)* # comments
\ {2}( # two spaces at the beginning
(url|head)\ ['"][\S\ ]+['"] # url or head with a string
(
,[\S\ ]*$ # url may have options
(\n^\ {3}[\S\ ]+$)* # options can be in multiple lines
)?|
(homepage|desc|sha1|sha256|version|mirror)\ ['"][\S\ ]+['"]| # specs with a string
(revision|version_scheme)\ \d+ # revision with a number
)\n+ # multiple empty lines
)+
/mx
string = s.sub!(pattern, '\0' + output + "\n")
end
pattern = /(
(\ {2}\#[^\n]*\n)* # comments
\ {2}( # two spaces at the beginning
(url|head)\ ['"][\S\ ]+['"] # url or head with a string
(
,[\S\ ]*$ # url may have options
(\n^\ {3}[\S\ ]+$)* # options can be in multiple lines
)?|
(homepage|desc|sha256|version|mirror|license)\ ['"][\S\ ]+['"]| # specs with a string
(revision|version_scheme)\ \d+| # revision with a number
(stable|livecheck)\ do(\n+^\ {4}[\S\ ]+$)*\n+^\ {2}end # components with blocks
)\n+ # multiple empty lines
)+
/mx
string = s.sub!(pattern, '\0' + output + "\n")
odie "Bottle block addition failed!" unless string
end
end

View File

@ -47,6 +47,8 @@ module Homebrew
description: "Explicitly set the <name> of the new formula."
flag "--set-version=",
description: "Explicitly set the <version> of the new formula."
flag "--set-license=",
description: "Explicitly set the <license> of the new formula."
flag "--tap=",
description: "Generate the new formula within the given tap, specified as <user>`/`<repo>."
switch :force
@ -68,11 +70,13 @@ module Homebrew
version = args.set_version
name = args.set_name
license = args.set_license
tap = args.tap
fc = FormulaCreator.new
fc.name = name
fc.version = version
fc.license = license
fc.tap = Tap.fetch(tap || "homebrew/core")
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
name.sub!(/\b@(.*)\z\b/i, "")
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"
if path.exist?
@ -205,7 +208,8 @@ module Homebrew
odebug "Overwriting existing formula at #{path}"
path.delete
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
end

View File

@ -33,6 +33,9 @@ module Homebrew
switch "--resolve",
description: "When a patch fails to apply, leave in progress and allow user to resolve, "\
"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=",
description: "Retrieve artifacts from the specified workflow (default: `tests.yml`)."
flag "--artifact=",
@ -73,13 +76,18 @@ module Homebrew
end
end
def signoff!(pr, path: ".")
message = Utils.popen_read "git", "-C", path, "log", "-1", "--pretty=%B"
def signoff!(pr, tap:)
message = Utils.popen_read "git", "-C", tap.path, "log", "-1", "--pretty=%B"
subject = message.lines.first.strip
# Skip the subject and separate lines that look like trailers (e.g. "Co-authored-by")
# from lines that look like regular body text.
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
body = body.join.strip.gsub(/\n{3,}/, "\n\n")
@ -90,7 +98,7 @@ module Homebrew
if Homebrew.args.dry_run?
puts "git commit --amend --signoff -m $message"
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
@ -232,7 +240,7 @@ module Homebrew
cd dir do
original_commit = Utils.popen_read("git", "-C", tap.path, "rev-parse", "HEAD").chomp
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?
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 << "--no-publish" if args.no_publish?
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 << "--bintray-org=#{bintray_org}"
system HOMEBREW_BREW_FILE, *upload_args
safe_system HOMEBREW_BREW_FILE, *upload_args
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."
switch "-n", "--dry-run",
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=",
description: "Upload to the specified Bintray organisation (default: `homebrew`)."
flag "--root-url=",
@ -42,13 +45,15 @@ module Homebrew
if args.dry_run?
puts "brew #{bottle_args.join " "}"
else
system HOMEBREW_BREW_FILE, *bottle_args
safe_system HOMEBREW_BREW_FILE, *bottle_args
end
if args.dry_run?
puts "Upload bottles described by these JSON files to Bintray:\n #{Dir["*.json"].join("\n ")}"
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

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
class MultipleVersionsInstalledError < RuntimeError
attr_reader :name
def initialize(name)
@name = name
super "#{name} has multiple installed versions"
end
end
class MultipleVersionsInstalledError < 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_clt_minimum_version
check_if_xcode_needs_clt_installed
check_if_supported_sdk_available
].freeze
end
@ -357,6 +358,34 @@ module Homebrew
Untap them with `brew untap`.
EOS
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

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=
delegate desc: :"self.class"
# The SPDX ID of the software license.
delegate license: :"self.class"
# The homepage for the software.
# @method homepage
# @see .homepage=
@ -1109,13 +1112,14 @@ class Formula
return false if tab_tap.nil?
begin
Formulary.factory(keg.name)
f = Formulary.factory(keg.name)
rescue FormulaUnavailableError
# formula for this keg is deleted, so defer to allowlist
rescue TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError
return false # this keg belongs to another formula
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
to_check = path.relative_path_from(HOMEBREW_PREFIX).to_s
@ -1687,6 +1691,7 @@ class Formula
"aliases" => aliases.sort,
"versioned_formulae" => versioned_formulae.map(&:name),
"desc" => desc,
"license" => license,
"homepage" => homepage,
"versions" => {
"stable" => stable&.version&.to_s,
@ -2211,6 +2216,13 @@ class Formula
# <pre>desc "Example formula"</pre>
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
# The homepage for the software. Used by users to get more information
# 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.
#
# We are fine if the executable does not error out, so we know linking
# and building the software was OK.
# <pre>system bin/"foobar", "--version"</pre>
# We want tests that don't require any user input
# and test the basic functionality of the application.
# 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
# writing some test file, if you need to

View File

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

View File

@ -77,6 +77,14 @@ class FormulaInstaller
@attempted = Set.new
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,
# it's necessary to interrupt the user before any sort of installation
# can proceed. Only invoked when the user has no developer tools.
@ -292,7 +300,7 @@ class FormulaInstaller
self.class.attempted << formula
if pour_bottle?(warn: true)
if pour_bottle?
begin
pour
rescue Exception => e # rubocop:disable Lint/RescueException
@ -700,6 +708,8 @@ class FormulaInstaller
ohai "Summary" if verbose? || show_summary_heading?
puts summary
self.class.installed << formula
ensure
unlock
end
@ -983,11 +993,24 @@ class FormulaInstaller
return if only_deps?
unless pour_bottle?
formula.fetch_patches
formula.resources.each(&:fetch)
end
if pour_bottle?(warn: true)
begin
downloader.fetch
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
end

View File

@ -167,7 +167,7 @@ module Language
def needs_python?(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
# 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:
brew install rust
EOS
when "cargo-completion" then <<~EOS
cargo-completion is part of the rust formula:
brew install rust
EOS
when "uconv" then <<~EOS
uconv is part of the icu4c formula:
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 available. Otherwise, the latest SDK is returned.
def sdk(v = nil)
@locator ||= if CLT.installed? && CLT.provides_sdk?
CLTSDKLocator.new
def sdk_locator
if CLT.installed? && CLT.provides_sdk?
CLT.sdk_locator
else
XcodeSDKLocator.new
Xcode.sdk_locator
end
end
@locator.sdk_if_applicable(v)
def sdk(v = nil)
sdk_locator.sdk_if_applicable(v)
end
def sdk_for_formula(f, v = nil)

View File

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

View File

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

View File

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

View File

@ -24,7 +24,8 @@ module RuboCop
[{ name: :mirror, type: :method_call }],
[{ name: :version, 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: :head, type: :method_call }],
[{ name: :stable, type: :block_call }],

View File

@ -22,18 +22,28 @@ module RuboCop
Firefox
].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)
reason = reason.sub(name, "")
reason = string_content(reason).sub(name, "")
first_word = reason.split.first
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
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

View File

@ -555,6 +555,55 @@ module RuboCop
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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
# This file is autogenerated. Do not edit it by hand. Regenerate it with:
# tapioca sync
# tapioca sync --exclude json
# 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:
# tapioca sync
# tapioca sync --exclude json
# 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
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_remote_tap "homebrew/cask"
@ -16,4 +16,13 @@ describe "brew search", :integration_test do
.to output(/firefox/).to_stdout
.and be_a_success
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

View File

@ -79,6 +79,92 @@ module Homebrew
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
specify "no issue" do
fa = formula_auditor "foo", <<~RUBY
@ -277,6 +363,7 @@ module Homebrew
origin_formula_path.write <<~RUBY
class Foo#{foo_version} < Formula
url "https://brew.sh/foo-1.0.tar.gz"
sha256 "31cccfc6630528db1c8e3a06f6decf2a370060b982841cfab2b8677400a5092e"
revision 2
version_scheme 1
end
@ -302,7 +389,7 @@ module Homebrew
formula_path.write text
end
def formula_gsub_commit(before, after = "")
def formula_gsub_origin_commit(before, after = "")
text = origin_formula_path.read
text.gsub!(before, after)
origin_formula_path.unlink
@ -318,19 +405,48 @@ module Homebrew
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 "should not be removed when first committed above 0" do
it { is_expected.to be_nil }
end
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)") }
end
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)") }
end
@ -342,15 +458,15 @@ module Homebrew
end
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") }
end
context "should not warn on an newer version revision removal" do
before do
formula_gsub_commit "revision 2", ""
formula_gsub_commit "foo-1.0.tar.gz", "foo-1.1.tar.gz"
formula_gsub_origin_commit "revision 2", ""
formula_gsub_origin_commit "foo-1.0.tar.gz", "foo-1.1.tar.gz"
end
it { is_expected.to be_nil }
@ -367,9 +483,9 @@ module Homebrew
context "should not warn on past increment by more than 1" do
before do
formula_gsub_commit "revision 2", "# no revision"
formula_gsub_commit "foo-1.0.tar.gz", "foo-1.1.tar.gz"
formula_gsub_commit "# no revision", "revision 3"
formula_gsub_origin_commit "revision 2", "# no revision"
formula_gsub_origin_commit "foo-1.0.tar.gz", "foo-1.1.tar.gz"
formula_gsub_origin_commit "# no revision", "revision 3"
end
it { is_expected.to be_nil }
@ -378,16 +494,16 @@ module Homebrew
context "version_schemes" 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)") }
end
context "should not decrease with a new version" do
before do
formula_gsub_commit "foo-1.0.tar.gz", "foo-1.1.tar.gz"
formula_gsub_commit "version_scheme 1", ""
formula_gsub_commit "revision 2", ""
formula_gsub_origin_commit "foo-1.0.tar.gz", "foo-1.1.tar.gz"
formula_gsub_origin_commit "version_scheme 1", ""
formula_gsub_origin_commit "revision 2", ""
end
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
before do
formula_gsub_commit "version_scheme 1", "# no version_scheme"
formula_gsub_commit "foo-1.0.tar.gz", "foo-1.1.tar.gz"
formula_gsub_commit "revision 2", ""
formula_gsub_commit "# no version_scheme", "version_scheme 3"
formula_gsub_origin_commit "version_scheme 1", "# no version_scheme"
formula_gsub_origin_commit "foo-1.0.tar.gz", "foo-1.1.tar.gz"
formula_gsub_origin_commit "revision 2", ""
formula_gsub_origin_commit "# no version_scheme", "version_scheme 3"
end
it { is_expected.to match("version_schemes should only increment by 1") }
@ -414,8 +530,8 @@ module Homebrew
context "committed can decrease" do
before do
formula_gsub_commit "revision 2"
formula_gsub_commit "foo-1.0.tar.gz", "foo-0.9.tar.gz"
formula_gsub_origin_commit "revision 2"
formula_gsub_origin_commit "foo-1.0.tar.gz", "foo-0.9.tar.gz"
end
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"
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") }
end

View File

@ -19,6 +19,18 @@ describe RuboCop::Cop::FormulaAudit::ComponentsOrder do
RUBY
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
expect_offense(<<~RUBY)
class Foo < Formula

View File

@ -13,7 +13,7 @@ describe RuboCop::Cop::FormulaAudit::KegOnly do
homepage "https://brew.sh"
keg_only "Because why not"
^^^^^^^^^^^^^^^^^^^^^^^^^^ 'Because' from the keg_only reason should be 'because'.
^^^^^^^^^^^^^^^^^ 'Because' from the `keg_only` reason should be 'because'.
end
RUBY
end
@ -25,11 +25,53 @@ describe RuboCop::Cop::FormulaAudit::KegOnly do
homepage "https://brew.sh"
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
RUBY
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
expect_no_offenses(<<~RUBY)
class Foo < Formula

View File

@ -1096,3 +1096,252 @@ describe RuboCop::Cop::FormulaAuditStrict::MakeCheck do
include_examples "formulae exist", described_class::MAKE_CHECK_ALLOWLIST
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"
formatters = [SimpleCov::Formatter::HTMLFormatter]
if ENV["HOMEBREW_COVERALLS_REPO_TOKEN"] && RUBY_PLATFORM[/darwin/]
require "coveralls"
if ENV["HOMEBREW_CODECOV_TOKEN"] && RUBY_PLATFORM[/darwin/]
require "codecov"
Coveralls::Output.no_color if !ENV["HOMEBREW_COLOR"] && (ENV["HOMEBREW_NO_COLOR"] || !$stdout.tty?)
formatters << Coveralls::SimpleCov::Formatter
formatters << SimpleCov::Formatter::Codecov
if ENV["TEST_ENV_NUMBER"]
SimpleCov.at_exit do
@ -18,16 +16,7 @@ if ENV["HOMEBREW_TESTS_COVERAGE"]
end
end
ENV["CI_NAME"] = ENV["HOMEBREW_CI_NAME"]
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"
ENV["CODECOV_TOKEN"] = ENV["HOMEBREW_CODECOV_TOKEN"]
end
SimpleCov.formatters = SimpleCov::Formatter::MultiFormatter.new(formatters)
@ -174,6 +163,7 @@ RSpec.configure do |config|
Keg.clear_cache
Tab.clear_cache
FormulaInstaller.clear_attempted
FormulaInstaller.clear_installed
TEST_DIRECTORIES.each(&:mkpath)

View File

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

View File

@ -42,6 +42,21 @@ describe GitHub do
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
it "fails to find a nonexistant workflow" do
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
end
def curl_download(*args, to: nil, **options)
def curl_download(*args, to: nil, partial: true, **options)
destination = Pathname(to)
destination.dirname.mkpath
range_stdout = curl_output("--location", "--range", "0-1",
"--dump-header", "-",
"--write-out", "%\{http_code}",
"--output", "/dev/null", *args, **options).stdout
headers, _, http_status = range_stdout.partition("\r\n\r\n")
if partial
range_stdout = curl_output("--location", "--range", "0-1",
"--dump-header", "-",
"--write-out", "%\{http_code}",
"--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
if supports_partial_download &&
destination.exist? &&
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
supports_partial_download = http_status.to_i == 206 # Partial Content
if supports_partial_download &&
destination.exist? &&
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
end
else
supports_partial_download = false
end
continue_at = if destination.exist? && supports_partial_download

View File

@ -231,6 +231,15 @@ module GitHub
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)
json = begin
JSON.parse(output)
@ -429,6 +438,48 @@ module GitHub
open_api(uri) { |json| json.fetch("items", []) }
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)
url = "#{API_URL}/repos/#{user}/#{repo}/dispatches"
open_api(url, data: { event_type: event, client_payload: payload },
@ -484,20 +535,21 @@ module GitHub
end
def sponsors_by_tier(user)
url = "https://api.github.com/graphql"
data = {
query: <<~EOS,
{
organization(login: "#{user}") {
sponsorsListing {
tiers(first: 100) {
nodes {
monthlyPriceInDollars
adminInfo {
sponsorships(first: 100) {
totalCount
nodes {
sponsor { login }
query = <<~EOS
{ organization(login: "#{user}") {
sponsorsListing {
tiers(first: 10, orderBy: {field: MONTHLY_PRICE_IN_CENTS, direction: DESC}) {
nodes {
monthlyPriceInDollars
adminInfo {
sponsorships(first: 100, includePrivate: true) {
totalCount
nodes {
privacyLevel
sponsorEntity {
__typename
... on Organization { login name }
... on User { login name }
}
}
}
@ -506,9 +558,44 @@ module GitHub
}
}
}
EOS
}
open_api(url, scopes: ["admin:org", "user"], data: data, request_method: "POST")
}
EOS
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
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}/../#{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/connection_pool-2.2.3/lib"
$:.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.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/universal-darwin-19/2.6.0/json-2.3.1"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/json-2.3.1/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-0.16.1/lib"
$:.unshift "#{path}/../../../../../../../../Library/Ruby/Gems/2.6.0/gems/sync-0.5.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/tins-1.25.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/term-ansicolor-1.7.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/thor-1.0.1/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}/gems/simplecov-html-0.12.2/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/simplecov-0.18.5/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/url-0.3.2/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/codecov-0.1.17/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/connection_pool-2.2.3/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/diff-lcs-1.4.4/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}/gems/unf_ext-0.0.7.7/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/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-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"

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/language'
require_relative 'rubocop/rspec/language/node_pattern'
require_relative 'rubocop/rspec/top_level_group'
require_relative 'rubocop/rspec/concept'
require_relative 'rubocop/rspec/example_group'
require_relative 'rubocop/rspec/example'

View File

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

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