Merge pull request #16921 from Homebrew/ported-cmds

Convert some dev commands to use AbstractCommand
This commit is contained in:
Douglas Eichelberger 2024-03-20 10:47:44 -07:00 committed by GitHub
commit 0ac23c0690
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 2927 additions and 2890 deletions

View File

@ -16,21 +16,21 @@ module Homebrew
abstract! abstract!
class << self class << self
sig { returns(T.nilable(CLI::Parser)) }
attr_reader :parser
sig { returns(String) } sig { returns(String) }
def command_name = T.must(name).split("::").fetch(-1).downcase def command_name = Utils.underscore(T.must(name).split("::").fetch(-1)).tr("_", "-")
# @return the AbstractCommand subclass associated with the brew CLI command name. # @return the AbstractCommand subclass associated with the brew CLI command name.
sig { params(name: String).returns(T.nilable(T.class_of(AbstractCommand))) } sig { params(name: String).returns(T.nilable(T.class_of(AbstractCommand))) }
def command(name) = subclasses.find { _1.command_name == name } def command(name) = subclasses.find { _1.command_name == name }
sig { returns(CLI::Parser) }
def parser = CLI::Parser.new(self, &@parser_block)
private private
sig { params(block: T.proc.bind(CLI::Parser).void).void } sig { params(block: T.proc.bind(CLI::Parser).void).void }
def cmd_args(&block) def cmd_args(&block)
@parser = T.let(CLI::Parser.new(&block), T.nilable(CLI::Parser)) @parser_block = T.let(block, T.nilable(T.proc.void))
end end
end end
@ -39,10 +39,7 @@ module Homebrew
sig { params(argv: T::Array[String]).void } sig { params(argv: T::Array[String]).void }
def initialize(argv = ARGV.freeze) def initialize(argv = ARGV.freeze)
parser = self.class.parser @args = T.let(self.class.parser.parse(argv), CLI::Args)
raise "Commands must include a `cmd_args` block" if parser.nil?
@args = T.let(parser.parse(argv), CLI::Args)
end end
sig { abstract.void } sig { abstract.void }

View File

@ -1,6 +1,7 @@
# typed: true # typed: true
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command"
require "env_config" require "env_config"
require "cask/config" require "cask/config"
require "cli/args" require "cli/args"
@ -19,9 +20,18 @@ module Homebrew
def self.from_cmd_path(cmd_path) def self.from_cmd_path(cmd_path)
cmd_args_method_name = Commands.args_method_name(cmd_path) cmd_args_method_name = Commands.args_method_name(cmd_path)
cmd_name = cmd_args_method_name.to_s.delete_suffix("_args").tr("_", "-")
begin begin
Homebrew.send(cmd_args_method_name) if require?(cmd_path) if require?(cmd_path)
cmd = Homebrew::AbstractCommand.command(cmd_name)
if cmd
cmd.parser
else
# FIXME: remove once commands are all subclasses of `AbstractCommand`:
Homebrew.send(cmd_args_method_name)
end
end
rescue NoMethodError => e rescue NoMethodError => e
raise if e.name.to_sym != cmd_args_method_name raise if e.name.to_sym != cmd_args_method_name
@ -109,8 +119,10 @@ module Homebrew
] ]
end end
sig { params(block: T.nilable(T.proc.bind(Parser).void)).void } sig {
def initialize(&block) params(cmd: T.nilable(T.class_of(Homebrew::AbstractCommand)), block: T.nilable(T.proc.bind(Parser).void)).void
}
def initialize(cmd = nil, &block)
@parser = OptionParser.new @parser = OptionParser.new
@parser.summary_indent = " " * 2 @parser.summary_indent = " " * 2
@ -123,12 +135,18 @@ module Homebrew
@args = Homebrew::CLI::Args.new @args = Homebrew::CLI::Args.new
if cmd
@command_name = cmd.command_name
@is_dev_cmd = cmd.name&.start_with?("Homebrew::DevCmd")
else
# FIXME: remove once commands are all subclasses of `AbstractCommand`:
# Filter out Sorbet runtime type checking method calls. # Filter out Sorbet runtime type checking method calls.
cmd_location = T.must(caller_locations).select do |location| cmd_location = T.must(caller_locations).select do |location|
T.must(location.path).exclude?("/gems/sorbet-runtime-") T.must(location.path).exclude?("/gems/sorbet-runtime-")
end.fetch(1) end.fetch(1)
@command_name = T.must(cmd_location.label).chomp("_args").tr("_", "-") @command_name = T.must(cmd_location.label).chomp("_args").tr("_", "-")
@is_dev_cmd = T.must(cmd_location.absolute_path).start_with?(Commands::HOMEBREW_DEV_CMD_PATH) @is_dev_cmd = T.must(cmd_location.absolute_path).start_with?(Commands::HOMEBREW_DEV_CMD_PATH)
end
@constraints = [] @constraints = []
@conflicts = [] @conflicts = []

View File

@ -1,6 +1,7 @@
# typed: true # typed: true
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command"
require "formula" require "formula"
require "formula_versions" require "formula_versions"
require "utils/curl" require "utils/curl"
@ -20,9 +21,9 @@ require "formula_auditor"
require "tap_auditor" require "tap_auditor"
module Homebrew module Homebrew
sig { returns(CLI::Parser) } module DevCmd
def self.audit_args class Audit < AbstractCommand
Homebrew::CLI::Parser.new do cmd_args do
description <<~EOS description <<~EOS
Check <formula> for Homebrew coding style violations. This should be run before Check <formula> for Homebrew coding style violations. This should be run before
submitting a new formula or cask. If no <formula>|<cask> are provided, check all submitting a new formula or cask. If no <formula>|<cask> are provided, check all
@ -102,12 +103,9 @@ module Homebrew
named_args [:formula, :cask], without_api: true named_args [:formula, :cask], without_api: true
end end
end
sig { void }
def self.audit
args = audit_args.parse
sig { override.void }
def run
new_cask = args.new? || args.new_cask? new_cask = args.new? || args.new_cask?
new_formula = args.new? || args.new_formula? new_formula = args.new? || args.new_formula?
@ -116,7 +114,7 @@ module Homebrew
os_arch_combinations = args.os_arch_combinations os_arch_combinations = args.os_arch_combinations
Homebrew.auditing = true Homebrew.auditing = true
inject_dump_stats!(FormulaAuditor, /^audit_/) if args.audit_debug? Homebrew.inject_dump_stats!(FormulaAuditor, /^audit_/) if args.audit_debug?
strict = new_formula || args.strict? strict = new_formula || args.strict?
online = new_formula || args.online? online = new_formula || args.online?
@ -129,7 +127,7 @@ module Homebrew
audit_formulae, audit_casks = Homebrew.with_no_api_env do # audit requires full Ruby source audit_formulae, audit_casks = Homebrew.with_no_api_env do # audit requires full Ruby source
if args.tap if args.tap
Tap.fetch(args.tap).then do |tap| Tap.fetch(T.must(args.tap)).then do |tap|
[ [
tap.formula_files.map { |path| Formulary.factory(path) }, tap.formula_files.map { |path| Formulary.factory(path) },
tap.cask_files.map { |path| Cask::CaskLoader.load(path) }, tap.cask_files.map { |path| Cask::CaskLoader.load(path) },
@ -228,8 +226,8 @@ module Homebrew
audit_proc = proc { FormulaAuditor.new(Formulary.factory(path), **options).tap(&:audit) } audit_proc = proc { FormulaAuditor.new(Formulary.factory(path), **options).tap(&:audit) }
# Audit requires full Ruby source so disable API. # Audit requires full Ruby source so disable API. We shouldn't do this for taps however so that we
# We shouldn't do this for taps however so that we don't unnecessarily require a full Homebrew/core clone. # don't unnecessarily require a full Homebrew/core clone.
fa = if f.core_formula? fa = if f.core_formula?
Homebrew.with_no_api_env(&audit_proc) Homebrew.with_no_api_env(&audit_proc)
else else
@ -278,9 +276,9 @@ module Homebrew
problems[[cask.full_name, path]] = errors if errors.any? problems[[cask.full_name, path]] = errors if errors.any?
end end
print_problems(tap_problems, display_filename: args.display_filename?) print_problems(tap_problems)
print_problems(formula_problems, display_filename: args.display_filename?) print_problems(formula_problems)
print_problems(cask_problems, display_filename: args.display_filename?) print_problems(cask_problems)
tap_count = tap_problems.keys.count tap_count = tap_problems.keys.count
formula_count = formula_problems.keys.count formula_count = formula_problems.keys.count
@ -309,7 +307,8 @@ module Homebrew
errors_summary += " detected" errors_summary += " detected"
if corrected_problem_count.positive? if corrected_problem_count.positive?
errors_summary += ", #{Utils.pluralize("problem", corrected_problem_count, include_count: true)} corrected" errors_summary +=
", #{Utils.pluralize("problem", corrected_problem_count, include_count: true)} corrected"
end end
ofail "#{errors_summary}." ofail "#{errors_summary}."
@ -334,11 +333,13 @@ module Homebrew
end end
end end
def self.print_problems(results, display_filename:) private
def print_problems(results)
results.each do |(name, path), problems| results.each do |(name, path), problems|
problem_lines = format_problem_lines(problems) problem_lines = format_problem_lines(problems)
if display_filename if args.display_filename?
problem_lines.each do |l| problem_lines.each do |l|
puts "#{path}: #{l}" puts "#{path}: #{l}"
end end
@ -348,13 +349,17 @@ module Homebrew
end end
end end
def self.format_problem_lines(problems) def format_problem_lines(problems)
problems.map do |problem| problems.map do |problem|
status = " #{Formatter.success("[corrected]")}" if problem.fetch(:corrected) status = " #{Formatter.success("[corrected]")}" if problem.fetch(:corrected)
location = problem.fetch(:location) location = problem.fetch(:location)
location = "#{location.line&.to_s&.prepend("line ")}#{location.column&.to_s&.prepend(", col ")}: " if location if location
location = "#{location.line&.to_s&.prepend("line ")}#{location.column&.to_s&.prepend(", col ")}: "
end
message = problem.fetch(:message) message = problem.fetch(:message)
"* #{location}#{message.chomp.gsub("\n", "\n ")}#{status}" "* #{location}#{message.chomp.gsub("\n", "\n ")}#{status}"
end end
end end
end end
end
end

View File

@ -1,6 +1,7 @@
# typed: true # typed: true
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command"
require "formula" require "formula"
require "utils/bottles" require "utils/bottles"
require "tab" require "tab"
@ -13,6 +14,11 @@ require "utils/gzip"
require "api" require "api"
require "extend/hash/deep_merge" require "extend/hash/deep_merge"
module Homebrew
module DevCmd
class Bottle < AbstractCommand
include FileUtils
BOTTLE_ERB = <<-EOS.freeze BOTTLE_ERB = <<-EOS.freeze
bottle do bottle do
<% if [HOMEBREW_BOTTLE_DEFAULT_DOMAIN.to_s, <% if [HOMEBREW_BOTTLE_DEFAULT_DOMAIN.to_s,
@ -36,10 +42,7 @@ ALLOWABLE_HOMEBREW_REPOSITORY_LINKS = [
%r{#{Regexp.escape(HOMEBREW_LIBRARY)}/Homebrew/os/(mac|linux)/pkgconfig}, %r{#{Regexp.escape(HOMEBREW_LIBRARY)}/Homebrew/os/(mac|linux)/pkgconfig},
].freeze ].freeze
module Homebrew cmd_args do
sig { returns(CLI::Parser) }
def self.bottle_args
Homebrew::CLI::Parser.new do
description <<~EOS description <<~EOS
Generate a bottle (binary package) from a formula that was installed with Generate a bottle (binary package) from a formula that was installed with
`--build-bottle`. `--build-bottle`.
@ -89,24 +92,22 @@ module Homebrew
named_args [:installed_formula, :file], min: 1, without_api: true named_args [:installed_formula, :file], min: 1, without_api: true
end end
end
def self.bottle
args = bottle_args.parse
sig { override.void }
def run
if args.merge? if args.merge?
Homebrew.install_bundler_gems!(groups: ["ast"]) Homebrew.install_bundler_gems!(groups: ["ast"])
return merge(args:) return merge
end end
gnu_tar_formula_ensure_installed_if_needed!(only_json_tab: args.only_json_tab?) gnu_tar_formula_ensure_installed_if_needed!
args.named.to_resolved_formulae(uniq: false).each do |formula| args.named.to_resolved_formulae(uniq: false).each do |formula|
bottle_formula formula, args: bottle_formula formula
end end
end end
def self.keg_contain?(string, keg, ignores, formula_and_runtime_deps_names = nil, args:) def keg_contain?(string, keg, ignores, formula_and_runtime_deps_names = nil)
@put_string_exists_header, @put_filenames = nil @put_string_exists_header, @put_filenames = nil
print_filename = lambda do |str, filename| print_filename = lambda do |str, filename|
@ -138,7 +139,8 @@ module Homebrew
end end
end end
text_matches = Keg.text_matches_in_file(file, string, ignores, linked_libraries, formula_and_runtime_deps_names) text_matches = Keg.text_matches_in_file(file, string, ignores, linked_libraries,
formula_and_runtime_deps_names)
result = true if text_matches.any? result = true if text_matches.any?
next if !args.verbose? || text_matches.empty? next if !args.verbose? || text_matches.empty?
@ -153,10 +155,10 @@ module Homebrew
end end
end end
keg_contain_absolute_symlink_starting_with?(string, keg, args:) || result keg_contain_absolute_symlink_starting_with?(string, keg) || result
end end
def self.keg_contain_absolute_symlink_starting_with?(string, keg, args:) def keg_contain_absolute_symlink_starting_with?(string, keg)
absolute_symlinks_start_with_string = [] absolute_symlinks_start_with_string = []
keg.find do |pn| keg.find do |pn|
next if !pn.symlink? || !(link = pn.readlink).absolute? next if !pn.symlink? || !(link = pn.readlink).absolute?
@ -174,7 +176,7 @@ module Homebrew
!absolute_symlinks_start_with_string.empty? !absolute_symlinks_start_with_string.empty?
end end
def self.cellar_parameter_needed?(cellar) def cellar_parameter_needed?(cellar)
default_cellars = [ default_cellars = [
Homebrew::DEFAULT_MACOS_CELLAR, Homebrew::DEFAULT_MACOS_CELLAR,
Homebrew::DEFAULT_MACOS_ARM_CELLAR, Homebrew::DEFAULT_MACOS_ARM_CELLAR,
@ -183,7 +185,7 @@ module Homebrew
cellar.present? && default_cellars.exclude?(cellar) cellar.present? && default_cellars.exclude?(cellar)
end end
def self.generate_sha256_line(tag, digest, cellar, tag_column, digest_column) def generate_sha256_line(tag, digest, cellar, tag_column, digest_column)
line = "sha256 " line = "sha256 "
tag_column += line.length tag_column += line.length
digest_column += line.length digest_column += line.length
@ -198,7 +200,7 @@ module Homebrew
%Q(#{line}"#{digest}") %Q(#{line}"#{digest}")
end end
def self.bottle_output(bottle, root_url_using) def bottle_output(bottle, root_url_using)
cellars = bottle.checksums.filter_map do |checksum| cellars = bottle.checksums.filter_map do |checksum|
cellar = checksum["cellar"] cellar = checksum["cellar"]
next unless cellar_parameter_needed? cellar next unless cellar_parameter_needed? cellar
@ -226,24 +228,24 @@ module Homebrew
erb.result(erb_binding).gsub(/^\s*$\n/, "") erb.result(erb_binding).gsub(/^\s*$\n/, "")
end end
def self.sudo_purge def sudo_purge
return unless ENV["HOMEBREW_BOTTLE_SUDO_PURGE"] return unless ENV["HOMEBREW_BOTTLE_SUDO_PURGE"]
system "/usr/bin/sudo", "--non-interactive", "/usr/sbin/purge" system "/usr/bin/sudo", "--non-interactive", "/usr/sbin/purge"
end end
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
def self.tar_args def tar_args
[].freeze [].freeze
end end
sig { params(gnu_tar_formula: Formula).returns(String) } sig { params(gnu_tar_formula: Formula).returns(String) }
def self.gnu_tar(gnu_tar_formula) def gnu_tar(gnu_tar_formula)
"#{gnu_tar_formula.opt_bin}/tar" "#{gnu_tar_formula.opt_bin}/tar"
end end
sig { params(mtime: String).returns(T::Array[String]) } sig { params(mtime: String).returns(T::Array[String]) }
def self.reproducible_gnutar_args(mtime) def reproducible_gnutar_args(mtime)
# Ensure gnu tar is set up for reproducibility. # Ensure gnu tar is set up for reproducibility.
# https://reproducible-builds.org/docs/archives/ # https://reproducible-builds.org/docs/archives/
[ [
@ -260,8 +262,8 @@ module Homebrew
].freeze ].freeze
end end
sig { params(only_json_tab: T::Boolean).returns(T.nilable(Formula)) } sig { returns(T.nilable(Formula)) }
def self.gnu_tar_formula_ensure_installed_if_needed!(only_json_tab:) def gnu_tar_formula_ensure_installed_if_needed!
gnu_tar_formula = begin gnu_tar_formula = begin
Formula["gnu-tar"] Formula["gnu-tar"]
rescue FormulaUnavailableError rescue FormulaUnavailableError
@ -274,21 +276,21 @@ module Homebrew
gnu_tar_formula gnu_tar_formula
end end
sig { params(args: T.untyped, mtime: String).returns([String, T::Array[String]]) } sig { params(mtime: String).returns([String, T::Array[String]]) }
def self.setup_tar_and_args!(args, mtime) def setup_tar_and_args!(mtime)
# Without --only-json-tab bottles are never reproducible # Without --only-json-tab bottles are never reproducible
default_tar_args = ["tar", tar_args].freeze default_tar_args = ["tar", tar_args].freeze
return default_tar_args unless args.only_json_tab? return default_tar_args unless args.only_json_tab?
# Use gnu-tar as it can be set up for reproducibility better than libarchive # Use gnu-tar as it can be set up for reproducibility better than libarchive
# and to be consistent between macOS and Linux. # and to be consistent between macOS and Linux.
gnu_tar_formula = gnu_tar_formula_ensure_installed_if_needed!(only_json_tab: args.only_json_tab?) gnu_tar_formula = gnu_tar_formula_ensure_installed_if_needed!
return default_tar_args if gnu_tar_formula.blank? return default_tar_args if gnu_tar_formula.blank?
[gnu_tar(gnu_tar_formula), reproducible_gnutar_args(mtime)].freeze [gnu_tar(gnu_tar_formula), reproducible_gnutar_args(mtime)].freeze
end end
def self.formula_ignores(formula) def formula_ignores(formula)
ignores = [] ignores = []
cellar_regex = Regexp.escape(HOMEBREW_CELLAR) cellar_regex = Regexp.escape(HOMEBREW_CELLAR)
prefix_regex = Regexp.escape(HOMEBREW_PREFIX) prefix_regex = Regexp.escape(HOMEBREW_PREFIX)
@ -318,7 +320,7 @@ module Homebrew
ignores.compact ignores.compact
end end
def self.bottle_formula(formula, args:) def bottle_formula(formula)
local_bottle_json = args.json? && formula.local_bottle_path.present? local_bottle_json = args.json? && formula.local_bottle_path.present?
unless local_bottle_json unless local_bottle_json
@ -366,7 +368,7 @@ module Homebrew
end || 0 end || 0
end end
filename = Bottle::Filename.create(formula, bottle_tag, rebuild) filename = ::Bottle::Filename.create(formula, bottle_tag, rebuild)
local_filename = filename.to_s local_filename = filename.to_s
bottle_path = Pathname.pwd/local_filename bottle_path = Pathname.pwd/local_filename
@ -395,7 +397,8 @@ module Homebrew
tab_json = Utils::Bottles.file_from_bottle(bottle_path, tab_path) tab_json = Utils::Bottles.file_from_bottle(bottle_path, tab_path)
tab = Tab.from_file_content(tab_json, tab_path) tab = Tab.from_file_content(tab_json, tab_path)
tag_spec = Formula[formula.name].bottle_specification.tag_specification_for(bottle_tag, no_older_versions: true) tag_spec = Formula[formula.name].bottle_specification
.tag_specification_for(bottle_tag, no_older_versions: true)
relocatable = [:any, :any_skip_relocation].include?(tag_spec.cellar) relocatable = [:any, :any_skip_relocation].include?(tag_spec.cellar)
skip_relocation = tag_spec.cellar == :any_skip_relocation skip_relocation = tag_spec.cellar == :any_skip_relocation
@ -445,7 +448,7 @@ module Homebrew
sudo_purge sudo_purge
# Tar then gzip for reproducible bottles. # Tar then gzip for reproducible bottles.
tar_mtime = tab.source_modified_time.strftime("%Y-%m-%d %H:%M:%S") tar_mtime = tab.source_modified_time.strftime("%Y-%m-%d %H:%M:%S")
tar, tar_args = setup_tar_and_args!(args, tar_mtime) tar, tar_args = setup_tar_and_args!(tar_mtime)
safe_system tar, "--create", "--numeric-owner", safe_system tar, "--create", "--numeric-owner",
*tar_args, *tar_args,
"--file", tar_path, "#{formula.name}/#{formula.pkg_version}" "--file", tar_path, "#{formula.name}/#{formula.pkg_version}"
@ -482,7 +485,7 @@ module Homebrew
else else
HOMEBREW_REPOSITORY HOMEBREW_REPOSITORY
end.to_s end.to_s
if keg_contain?(repository_reference, keg, ignores + ALLOWABLE_HOMEBREW_REPOSITORY_LINKS, args:) if keg_contain?(repository_reference, keg, ignores + ALLOWABLE_HOMEBREW_REPOSITORY_LINKS)
odie "Bottle contains non-relocatable reference to #{repository_reference}!" odie "Bottle contains non-relocatable reference to #{repository_reference}!"
end end
@ -490,16 +493,14 @@ module Homebrew
if args.skip_relocation? if args.skip_relocation?
skip_relocation = true skip_relocation = true
else else
relocatable = false if keg_contain?(prefix_check, keg, ignores, formula_and_runtime_deps_names, args:) relocatable = false if keg_contain?(prefix_check, keg, ignores, formula_and_runtime_deps_names)
relocatable = false if keg_contain?(cellar, keg, ignores, formula_and_runtime_deps_names, args:) relocatable = false if keg_contain?(cellar, keg, ignores, formula_and_runtime_deps_names)
if keg_contain?(HOMEBREW_LIBRARY.to_s, keg, ignores, formula_and_runtime_deps_names, args:) relocatable = false if keg_contain?(HOMEBREW_LIBRARY.to_s, keg, ignores, formula_and_runtime_deps_names)
relocatable = false
end
if prefix != prefix_check if prefix != prefix_check
relocatable = false if keg_contain_absolute_symlink_starting_with?(prefix, keg, args:) relocatable = false if keg_contain_absolute_symlink_starting_with?(prefix, keg)
relocatable = false if keg_contain?("#{prefix}/etc", keg, ignores, args:) relocatable = false if keg_contain?("#{prefix}/etc", keg, ignores)
relocatable = false if keg_contain?("#{prefix}/var", keg, ignores, args:) relocatable = false if keg_contain?("#{prefix}/var", keg, ignores)
relocatable = false if keg_contain?("#{prefix}/share/vim", keg, ignores, args:) relocatable = false if keg_contain?("#{prefix}/share/vim", keg, ignores)
end end
skip_relocation = relocatable && !keg.require_relocation? skip_relocation = relocatable && !keg.require_relocation?
end end
@ -599,13 +600,13 @@ module Homebrew
json_path.write(JSON.pretty_generate(json)) json_path.write(JSON.pretty_generate(json))
end end
def self.parse_json_files(filenames) def parse_json_files(filenames)
filenames.map do |filename| filenames.map do |filename|
JSON.parse(File.read(filename)) JSON.parse(File.read(filename))
end end
end end
def self.merge_json_files(json_files) def merge_json_files(json_files)
json_files.reduce({}) do |hash, json_file| json_files.reduce({}) do |hash, json_file|
json_file.each_value do |json_hash| json_file.each_value do |json_hash|
json_bottle = json_hash["bottle"] json_bottle = json_hash["bottle"]
@ -618,7 +619,7 @@ module Homebrew
end end
end end
def self.merge(args:) def merge
bottles_hash = merge_json_files(parse_json_files(args.named)) bottles_hash = merge_json_files(parse_json_files(args.named))
any_cellars = ["any", "any_skip_relocation"] any_cellars = ["any", "any_skip_relocation"]
@ -689,7 +690,7 @@ module Homebrew
all_bottle_hash = T.let(nil, T.nilable(Hash)) all_bottle_hash = T.let(nil, T.nilable(Hash))
bottle_hash["bottle"]["tags"].each do |tag, tag_hash| bottle_hash["bottle"]["tags"].each do |tag, tag_hash|
filename = Bottle::Filename.new( filename = ::Bottle::Filename.new(
formula_name, formula_name,
PkgVersion.parse(bottle_hash["formula"]["pkg_version"]), PkgVersion.parse(bottle_hash["formula"]["pkg_version"]),
Utils::Bottles::Tag.from_symbol(tag.to_sym), Utils::Bottles::Tag.from_symbol(tag.to_sym),
@ -699,7 +700,7 @@ module Homebrew
if all_bottle && all_bottle_hash.nil? if all_bottle && all_bottle_hash.nil?
all_bottle_tag_hash = tag_hash.dup all_bottle_tag_hash = tag_hash.dup
all_filename = Bottle::Filename.new( all_filename = ::Bottle::Filename.new(
formula_name, formula_name,
PkgVersion.parse(bottle_hash["formula"]["pkg_version"]), PkgVersion.parse(bottle_hash["formula"]["pkg_version"]),
Utils::Bottles::Tag.from_symbol(:all), Utils::Bottles::Tag.from_symbol(:all),
@ -735,7 +736,7 @@ module Homebrew
require "utils/ast" require "utils/ast"
formula_ast = Utils::AST::FormulaAST.new(path.read) formula_ast = Utils::AST::FormulaAST.new(path.read)
checksums = old_checksums(formula, formula_ast, bottle_hash, args:) checksums = old_checksums(formula, formula_ast, bottle_hash)
update_or_add = checksums.nil? ? "add" : "update" update_or_add = checksums.nil? ? "add" : "update"
checksums&.each(&bottle.method(:sha256)) checksums&.each(&bottle.method(:sha256))
@ -772,7 +773,7 @@ module Homebrew
end end
end end
def self.merge_bottle_spec(old_keys, old_bottle_spec, new_bottle_hash) def merge_bottle_spec(old_keys, old_bottle_spec, new_bottle_hash)
mismatches = [] mismatches = []
checksums = [] checksums = []
@ -812,12 +813,13 @@ module Homebrew
[mismatches, checksums] [mismatches, checksums]
end end
def self.old_checksums(formula, formula_ast, bottle_hash, args:) def old_checksums(formula, formula_ast, bottle_hash)
bottle_node = formula_ast.bottle_block bottle_node = formula_ast.bottle_block
return if bottle_node.nil? return if bottle_node.nil?
return [] unless args.keep_old? return [] unless args.keep_old?
old_keys = T.cast(Utils::AST.body_children(bottle_node.body), T::Array[RuboCop::AST::SendNode]).map(&:method_name) old_keys = T.cast(Utils::AST.body_children(bottle_node.body), T::Array[RuboCop::AST::SendNode])
.map(&:method_name)
old_bottle_spec = formula.bottle_specification old_bottle_spec = formula.bottle_specification
mismatches, checksums = merge_bottle_spec(old_keys, old_bottle_spec, bottle_hash["bottle"]) mismatches, checksums = merge_bottle_spec(old_keys, old_bottle_spec, bottle_hash["bottle"])
if mismatches.present? if mismatches.present?
@ -829,5 +831,7 @@ module Homebrew
checksums checksums
end end
end end
end
end
require "extend/os/dev-cmd/bottle" require "extend/os/dev-cmd/bottle"

View File

@ -1,6 +1,7 @@
# typed: strict # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command"
require "bump_version_parser" require "bump_version_parser"
require "cask" require "cask"
require "cask/download" require "cask/download"
@ -8,11 +9,9 @@ require "cli/parser"
require "utils/tar" require "utils/tar"
module Homebrew module Homebrew
module_function module DevCmd
class BumpCaskPr < AbstractCommand
sig { returns(CLI::Parser) } cmd_args do
def bump_cask_pr_args
Homebrew::CLI::Parser.new do
description <<~EOS description <<~EOS
Create a pull request to update <cask> with a new version. Create a pull request to update <cask> with a new version.
@ -61,12 +60,9 @@ module Homebrew
named_args :cask, number: 1, without_api: true named_args :cask, number: 1, without_api: true
end end
end
sig { void }
def bump_cask_pr
args = bump_cask_pr_args.parse
sig { override.void }
def run
odeprecated "brew bump-cask-pr --online" if args.online? odeprecated "brew bump-cask-pr --online" if args.online?
odisabled "brew bump-cask-pr --force" if args.force? odisabled "brew bump-cask-pr --force" if args.force?
@ -123,7 +119,7 @@ module Homebrew
raise UsageError, "No `--version`, `--url` or `--sha256` argument specified!" raise UsageError, "No `--version`, `--url` or `--sha256` argument specified!"
end end
check_pull_requests(cask, args:, new_version:) check_pull_requests(cask, new_version:)
replacement_pairs ||= [] replacement_pairs ||= []
branch_name = "bump-#{cask.token}" branch_name = "bump-#{cask.token}"
@ -166,8 +162,8 @@ module Homebrew
read_only_run: args.dry_run?, read_only_run: args.dry_run?,
silent: args.quiet?) silent: args.quiet?)
run_cask_audit(cask, old_contents, args:) run_cask_audit(cask, old_contents)
run_cask_style(cask, old_contents, args:) run_cask_style(cask, old_contents)
pr_info = { pr_info = {
branch_name:, branch_name:,
@ -180,6 +176,8 @@ module Homebrew
GitHub.create_bump_pr(pr_info, args:) GitHub.create_bump_pr(pr_info, args:)
end end
private
sig { params(version: Cask::DSL::Version, cask: Cask::Cask).returns(Cask::DSL::Version) } sig { params(version: Cask::DSL::Version, cask: Cask::Cask).returns(Cask::DSL::Version) }
def shortened_version(version, cask:) def shortened_version(version, cask:)
if version.before_comma == cask.version.before_comma if version.before_comma == cask.version.before_comma
@ -253,8 +251,8 @@ module Homebrew
replacement_pairs replacement_pairs
end end
sig { params(cask: Cask::Cask, args: CLI::Args, new_version: BumpVersionParser).void } sig { params(cask: Cask::Cask, new_version: BumpVersionParser).void }
def check_pull_requests(cask, args:, new_version:) def check_pull_requests(cask, new_version:)
tap_remote_repo = cask.tap.full_name || cask.tap.remote_repo tap_remote_repo = cask.tap.full_name || cask.tap.remote_repo
GitHub.check_for_duplicate_pull_requests(cask.token, tap_remote_repo, GitHub.check_for_duplicate_pull_requests(cask.token, tap_remote_repo,
@ -279,8 +277,8 @@ module Homebrew
end end
end end
sig { params(cask: Cask::Cask, old_contents: String, args: T.untyped).void } sig { params(cask: Cask::Cask, old_contents: String).void }
def run_cask_audit(cask, old_contents, args:) def run_cask_audit(cask, old_contents)
if args.dry_run? if args.dry_run?
if args.no_audit? if args.no_audit?
ohai "Skipping `brew audit`" ohai "Skipping `brew audit`"
@ -302,8 +300,8 @@ module Homebrew
odie "`brew audit` failed!" odie "`brew audit` failed!"
end end
sig { params(cask: Cask::Cask, old_contents: String, args: T.untyped).void } sig { params(cask: Cask::Cask, old_contents: String).void }
def run_cask_style(cask, old_contents, args:) def run_cask_style(cask, old_contents)
if args.dry_run? if args.dry_run?
if args.no_style? if args.no_style?
ohai "Skipping `brew style --fix`" ohai "Skipping `brew style --fix`"
@ -325,3 +323,5 @@ module Homebrew
odie "`brew style --fix` failed!" odie "`brew style --fix` failed!"
end end
end end
end
end

View File

@ -1,17 +1,16 @@
# typed: true # typed: true
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command"
require "formula" require "formula"
require "cli/parser" require "cli/parser"
require "utils/pypi" require "utils/pypi"
require "utils/tar" require "utils/tar"
module Homebrew module Homebrew
module_function module DevCmd
class BumpFormulaPr < AbstractCommand
sig { returns(CLI::Parser) } cmd_args do
def bump_formula_pr_args
Homebrew::CLI::Parser.new do
description <<~EOS description <<~EOS
Create a pull request to update <formula> with a new URL or a new tag. Create a pull request to update <formula> with a new URL or a new tag.
@ -89,11 +88,9 @@ module Homebrew
named_args :formula, max: 1, without_api: true named_args :formula, max: 1, without_api: true
end end
end
def bump_formula_pr
args = bump_formula_pr_args.parse
sig { override.void }
def run
if args.revision.present? && args.tag.nil? && args.version.nil? if args.revision.present? && args.tag.nil? && args.version.nil?
raise UsageError, "`--revision` must be passed with either `--tag` or `--version`!" raise UsageError, "`--revision` must be passed with either `--tag` or `--version`!"
end end
@ -134,10 +131,10 @@ module Homebrew
remote_branch = formula.tap.git_repo.origin_branch_name remote_branch = formula.tap.git_repo.origin_branch_name
previous_branch = "-" previous_branch = "-"
check_open_pull_requests(formula, tap_remote_repo, args:) check_open_pull_requests(formula, tap_remote_repo)
new_version = args.version new_version = args.version
check_new_version(formula, tap_remote_repo, version: new_version, args:) if new_version.present? check_new_version(formula, tap_remote_repo, version: new_version) if new_version.present?
opoo "This formula has patches that may be resolved upstream." if formula.patchlist.present? opoo "This formula has patches that may be resolved upstream." if formula.patchlist.present?
if formula.resources.any? { |resource| !resource.name.start_with?("homebrew-") } if formula.resources.any? { |resource| !resource.name.start_with?("homebrew-") }
@ -149,7 +146,7 @@ module Homebrew
new_mirror ||= determine_mirror(new_url) new_mirror ||= determine_mirror(new_url)
new_mirrors ||= [new_mirror] if new_mirror.present? new_mirrors ||= [new_mirror] if new_mirror.present?
check_for_mirrors(formula, old_mirrors, new_mirrors, args:) if new_url.present? check_for_mirrors(formula, old_mirrors, new_mirrors) if new_url.present?
old_hash = formula_spec.checksum&.hexdigest old_hash = formula_spec.checksum&.hexdigest
new_hash = args.sha256 new_hash = args.sha256
@ -161,10 +158,10 @@ module Homebrew
old_version = old_formula_version.to_s old_version = old_formula_version.to_s
forced_version = new_version.present? forced_version = new_version.present?
new_url_hash = if new_url.present? && new_hash.present? new_url_hash = if new_url.present? && new_hash.present?
check_new_version(formula, tap_remote_repo, url: new_url, args:) if new_version.blank? check_new_version(formula, tap_remote_repo, url: new_url) if new_version.blank?
true true
elsif new_tag.present? && new_revision.present? elsif new_tag.present? && new_revision.present?
check_new_version(formula, tap_remote_repo, url: old_url, tag: new_tag, args:) if new_version.blank? check_new_version(formula, tap_remote_repo, url: old_url, tag: new_tag) if new_version.blank?
false false
elsif old_hash.blank? elsif old_hash.blank?
if new_tag.blank? && new_version.blank? && new_revision.blank? if new_tag.blank? && new_version.blank? && new_revision.blank?
@ -179,8 +176,9 @@ module Homebrew
and old tag are both #{new_tag}. and old tag are both #{new_tag}.
EOS EOS
end end
check_new_version(formula, tap_remote_repo, url: old_url, tag: new_tag, args:) if new_version.blank? check_new_version(formula, tap_remote_repo, url: old_url, tag: new_tag) if new_version.blank?
resource_path, forced_version = fetch_resource_and_forced_version(formula, new_version, old_url, tag: new_tag) resource_path, forced_version = fetch_resource_and_forced_version(formula, new_version, old_url,
tag: new_tag)
new_revision = Utils.popen_read("git", "-C", resource_path.to_s, "rev-parse", "-q", "--verify", "HEAD") new_revision = Utils.popen_read("git", "-C", resource_path.to_s, "rev-parse", "-q", "--verify", "HEAD")
new_revision = new_revision.strip new_revision = new_revision.strip
elsif new_revision.blank? elsif new_revision.blank?
@ -190,12 +188,12 @@ module Homebrew
elsif new_url.blank? && new_version.blank? elsif new_url.blank? && new_version.blank?
raise UsageError, "#{formula}: no `--url` or `--version` argument specified!" raise UsageError, "#{formula}: no `--url` or `--version` argument specified!"
else else
new_url ||= PyPI.update_pypi_url(old_url, new_version) new_url ||= PyPI.update_pypi_url(old_url, T.must(new_version))
if new_url.blank? if new_url.blank?
new_url = update_url(old_url, old_version, new_version) new_url = update_url(old_url, old_version, T.must(new_version))
if new_mirrors.blank? && old_mirrors.present? if new_mirrors.blank? && old_mirrors.present?
new_mirrors = old_mirrors.map do |old_mirror| new_mirrors = old_mirrors.map do |old_mirror|
update_url(old_mirror, old_version, new_version) update_url(old_mirror, old_version, T.must(new_version))
end end
end end
end end
@ -206,7 +204,7 @@ module Homebrew
#{new_url} #{new_url}
EOS EOS
end end
check_new_version(formula, tap_remote_repo, url: new_url, args:) if new_version.blank? check_new_version(formula, tap_remote_repo, url: new_url) if new_version.blank?
resource_path, forced_version = fetch_resource_and_forced_version(formula, new_version, new_url) resource_path, forced_version = fetch_resource_and_forced_version(formula, new_version, new_url)
Utils::Tar.validate_file(resource_path) Utils::Tar.validate_file(resource_path)
new_hash = resource_path.sha256 new_hash = resource_path.sha256
@ -273,7 +271,7 @@ module Homebrew
if new_mirrors.present? if new_mirrors.present?
replacement_pairs << [ replacement_pairs << [
/^( +)(url "#{Regexp.escape(new_url)}"[^\n]*?\n)/m, /^( +)(url "#{Regexp.escape(T.must(new_url))}"[^\n]*?\n)/m,
"\\1\\2\\1mirror \"#{new_mirrors.join("\"\n\\1mirror \"")}\"\n", "\\1\\2\\1mirror \"#{new_mirrors.join("\"\n\\1mirror \"")}\"\n",
] ]
end end
@ -344,7 +342,7 @@ module Homebrew
ignore_non_pypi_packages: true ignore_non_pypi_packages: true
end end
run_audit(formula, alias_rename, old_contents, args:) run_audit(formula, alias_rename, old_contents)
pr_message = "Created with `brew bump-formula-pr`." pr_message = "Created with `brew bump-formula-pr`."
if resources_checked.nil? && formula.resources.any? { |resource| !resource.name.start_with?("homebrew-") } if resources_checked.nil? && formula.resources.any? { |resource| !resource.name.start_with?("homebrew-") }
@ -393,6 +391,8 @@ module Homebrew
GitHub.create_bump_pr(pr_info, args:) GitHub.create_bump_pr(pr_info, args:)
end end
private
def determine_mirror(url) def determine_mirror(url)
case url case url
when %r{.*ftp\.gnu\.org/gnu.*} when %r{.*ftp\.gnu\.org/gnu.*}
@ -406,7 +406,7 @@ module Homebrew
end end
end end
def check_for_mirrors(formula, old_mirrors, new_mirrors, args:) def check_for_mirrors(formula, old_mirrors, new_mirrors)
return if new_mirrors.present? || old_mirrors.empty? return if new_mirrors.present? || old_mirrors.empty?
if args.force? if args.force?
@ -451,14 +451,14 @@ module Homebrew
end end
end end
def check_open_pull_requests(formula, tap_remote_repo, args:) def check_open_pull_requests(formula, tap_remote_repo)
GitHub.check_for_duplicate_pull_requests(formula.name, tap_remote_repo, GitHub.check_for_duplicate_pull_requests(formula.name, tap_remote_repo,
state: "open", state: "open",
file: formula.path.relative_path_from(formula.tap.path).to_s, file: formula.path.relative_path_from(formula.tap.path).to_s,
quiet: args.quiet?) quiet: args.quiet?)
end end
def check_new_version(formula, tap_remote_repo, args:, version: nil, url: nil, tag: nil) def check_new_version(formula, tap_remote_repo, version: nil, url: nil, tag: nil)
if version.nil? if version.nil?
specs = {} specs = {}
specs[:tag] = tag if tag.present? specs[:tag] = tag if tag.present?
@ -467,7 +467,7 @@ module Homebrew
end end
check_throttle(formula, version) check_throttle(formula, version)
check_closed_pull_requests(formula, tap_remote_repo, args:, version:) check_closed_pull_requests(formula, tap_remote_repo, version:)
end end
def check_throttle(formula, new_version) def check_throttle(formula, new_version)
@ -480,7 +480,7 @@ module Homebrew
odie "#{formula} should only be updated every #{throttled_rate} releases on multiples of #{throttled_rate}" odie "#{formula} should only be updated every #{throttled_rate} releases on multiples of #{throttled_rate}"
end end
def check_closed_pull_requests(formula, tap_remote_repo, args:, version:) def check_closed_pull_requests(formula, tap_remote_repo, version:)
# if we haven't already found open requests, try for an exact match across closed requests # if we haven't already found open requests, try for an exact match across closed requests
GitHub.check_for_duplicate_pull_requests(formula.name, tap_remote_repo, GitHub.check_for_duplicate_pull_requests(formula.name, tap_remote_repo,
version:, version:,
@ -501,7 +501,7 @@ module Homebrew
[versioned_alias, "#{name}@#{new_alias_version}"] [versioned_alias, "#{name}@#{new_alias_version}"]
end end
def run_audit(formula, alias_rename, old_contents, args:) def run_audit(formula, alias_rename, old_contents)
audit_args = ["--formula"] audit_args = ["--formula"]
audit_args << "--strict" if args.strict? audit_args << "--strict" if args.strict?
audit_args << "--online" if args.online? audit_args << "--online" if args.online?
@ -533,3 +533,5 @@ module Homebrew
odie "`brew audit` failed!" odie "`brew audit` failed!"
end end
end end
end
end

View File

@ -1,15 +1,14 @@
# typed: true # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command"
require "formula" require "formula"
require "cli/parser" require "cli/parser"
module Homebrew module Homebrew
module_function module DevCmd
class BumpRevision < AbstractCommand
sig { returns(CLI::Parser) } cmd_args do
def bump_revision_args
Homebrew::CLI::Parser.new do
description <<~EOS description <<~EOS
Create a commit to increment the revision of <formula>. If no revision is Create a commit to increment the revision of <formula>. If no revision is
present, "revision 1" will be added. present, "revision 1" will be added.
@ -27,11 +26,9 @@ module Homebrew
named_args :formula, min: 1, without_api: true named_args :formula, min: 1, without_api: true
end end
end
def bump_revision
args = bump_revision_args.parse
sig { override.void }
def run
# As this command is simplifying user-run commands then let's just use a # As this command is simplifying user-run commands then let's just use a
# user path, too. # user path, too.
ENV["PATH"] = PATH.new(ORIGINAL_PATHS).to_s ENV["PATH"] = PATH.new(ORIGINAL_PATHS).to_s
@ -76,3 +73,5 @@ module Homebrew
end end
end end
end end
end
end

View File

@ -1,4 +1,4 @@
# typed: true # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "timeout" require "timeout"
@ -11,11 +11,11 @@ require "tap"
require "unversioned_cask_checker" require "unversioned_cask_checker"
module Homebrew module Homebrew
extend SystemCommand::Mixin module DevCmd
class BumpUnversionedCask < AbstractCommand
include SystemCommand::Mixin
sig { returns(CLI::Parser) } cmd_args do
def self.bump_unversioned_casks_args
Homebrew::CLI::Parser.new do
description <<~EOS description <<~EOS
Check all casks with unversioned URLs in a given <tap> for updates. Check all casks with unversioned URLs in a given <tap> for updates.
EOS EOS
@ -28,16 +28,13 @@ module Homebrew
named_args [:cask, :tap], min: 1, without_api: true named_args [:cask, :tap], min: 1, without_api: true
end end
end
sig { void }
def self.bump_unversioned_casks
args = bump_unversioned_casks_args.parse
sig { override.void }
def run
Homebrew.install_bundler_gems!(groups: ["bump_unversioned_casks"]) Homebrew.install_bundler_gems!(groups: ["bump_unversioned_casks"])
state_file = if args.state_file.present? state_file = if args.state_file.present?
Pathname(args.state_file).expand_path Pathname(T.must(args.state_file)).expand_path
else else
HOMEBREW_CACHE/"bump_unversioned_casks.json" HOMEBREW_CACHE/"bump_unversioned_casks.json"
end end
@ -75,7 +72,7 @@ module Homebrew
key = cask.full_name key = cask.full_name
new_state = bump_unversioned_cask(cask, state: state.fetch(key, {}), dry_run: args.dry_run?) new_state = bump_unversioned_cask(cask, state: state.fetch(key, {}))
next unless new_state next unless new_state
@ -85,11 +82,13 @@ module Homebrew
end end
end end
private
sig { sig {
params(cask: Cask::Cask, state: T::Hash[String, T.untyped], dry_run: T.nilable(T::Boolean)) params(cask: Cask::Cask, state: T::Hash[String, T.untyped])
.returns(T.nilable(T::Hash[String, T.untyped])) .returns(T.nilable(T::Hash[String, T.untyped]))
} }
def self.bump_unversioned_cask(cask, state:, dry_run:) def bump_unversioned_cask(cask, state:)
ohai "Checking #{cask.full_name}" ohai "Checking #{cask.full_name}"
unversioned_cask_checker = UnversionedCaskChecker.new(cask) unversioned_cask_checker = UnversionedCaskChecker.new(cask)
@ -150,7 +149,7 @@ module Homebrew
cask.sourcefile_path cask.sourcefile_path
] ]
if dry_run if args.dry_run?
bump_cask_pr_args << "--dry-run" bump_cask_pr_args << "--dry-run"
oh1 "Would bump #{cask} from #{cask.version} to #{version}" oh1 "Would bump #{cask} from #{cask.version} to #{version}"
else else
@ -175,3 +174,5 @@ module Homebrew
} }
end end
end end
end
end

View File

@ -1,13 +1,14 @@
# typed: strict # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command"
require "bump_version_parser" require "bump_version_parser"
require "cli/parser" require "cli/parser"
require "livecheck/livecheck" require "livecheck/livecheck"
module Homebrew module Homebrew
module_function module DevCmd
class Bump < AbstractCommand
class VersionBumpInfo < T::Struct class VersionBumpInfo < T::Struct
const :type, Symbol const :type, Symbol
const :multiple_versions, T::Boolean const :multiple_versions, T::Boolean
@ -19,9 +20,7 @@ module Homebrew
const :closed_pull_requests, T.nilable(T.any(T::Array[String], String)) const :closed_pull_requests, T.nilable(T.any(T::Array[String], String))
end end
sig { returns(CLI::Parser) } cmd_args do
def bump_args
CLI::Parser.new do
description <<~EOS description <<~EOS
Display out-of-date brew formulae and the latest version available. If the Display out-of-date brew formulae and the latest version available. If the
returned current and livecheck versions differ or when querying specific returned current and livecheck versions differ or when querying specific
@ -56,12 +55,9 @@ module Homebrew
named_args [:formula, :cask], without_api: true named_args [:formula, :cask], without_api: true
end end
end
sig { void }
def bump
args = bump_args.parse
sig { override.void }
def run
Homebrew.install_bundler_gems!(groups: ["livecheck"]) Homebrew.install_bundler_gems!(groups: ["livecheck"])
if args.limit.present? && !args.formula? && !args.cask? if args.limit.present? && !args.formula? && !args.cask?
@ -72,7 +68,7 @@ module Homebrew
Homebrew.with_no_api_env do Homebrew.with_no_api_env do
formulae_and_casks = if args.tap formulae_and_casks = if args.tap
tap = Tap.fetch(args.tap) tap = Tap.fetch(T.must(args.tap))
raise UsageError, "`--tap` cannot be used with official taps." if tap.official? raise UsageError, "`--tap` cannot be used with official taps." if tap.official?
formulae = args.cask? ? [] : tap.formula_files.map { |path| Formulary.factory(path) } formulae = args.cask? ? [] : tap.formula_files.map { |path| Formulary.factory(path) }
@ -105,21 +101,23 @@ module Homebrew
end end
if formulae_and_casks.present? if formulae_and_casks.present?
handle_formula_and_casks(formulae_and_casks, args) handle_formula_and_casks(formulae_and_casks)
else else
handle_api_response(args) handle_api_response
end end
end end
end end
sig { params(formula_or_cask: T.any(Formula, Cask::Cask), args: CLI::Args).returns(T::Boolean) } private
def skip_repology?(formula_or_cask, args:)
sig { params(formula_or_cask: T.any(Formula, Cask::Cask)).returns(T::Boolean) }
def skip_repology?(formula_or_cask)
(ENV["CI"].present? && args.open_pr? && formula_or_cask.livecheckable?) || (ENV["CI"].present? && args.open_pr? && formula_or_cask.livecheckable?) ||
(formula_or_cask.is_a?(Formula) && formula_or_cask.versioned_formula?) (formula_or_cask.is_a?(Formula) && formula_or_cask.versioned_formula?)
end end
sig { params(formulae_and_casks: T::Array[T.any(Formula, Cask::Cask)], args: CLI::Args).void } sig { params(formulae_and_casks: T::Array[T.any(Formula, Cask::Cask)]).void }
def handle_formula_and_casks(formulae_and_casks, args) def handle_formula_and_casks(formulae_and_casks)
Livecheck.load_other_tap_strategies(formulae_and_casks) Livecheck.load_other_tap_strategies(formulae_and_casks)
ambiguous_casks = [] ambiguous_casks = []
@ -153,20 +151,19 @@ module Homebrew
Repology::HOMEBREW_CASK Repology::HOMEBREW_CASK
end end
package_data = Repology.single_package_query(name, repository:) unless skip_repology?(formula_or_cask, args:) package_data = Repology.single_package_query(name, repository:) unless skip_repology?(formula_or_cask)
retrieve_and_display_info_and_open_pr( retrieve_and_display_info_and_open_pr(
formula_or_cask, formula_or_cask,
name, name,
package_data&.values&.first, package_data&.values&.first,
args:,
ambiguous_cask: ambiguous_casks.include?(formula_or_cask), ambiguous_cask: ambiguous_casks.include?(formula_or_cask),
) )
end end
end end
sig { params(args: CLI::Args).void } sig { void }
def handle_api_response(args) def handle_api_response
limit = args.limit.to_i if args.limit.present? limit = args.limit.to_i if args.limit.present?
api_response = {} api_response = {}
@ -220,7 +217,6 @@ module Homebrew
formula_or_cask, formula_or_cask,
name, name,
repositories, repositories,
args:,
ambiguous_cask:, ambiguous_cask:,
) )
end end
@ -321,13 +317,16 @@ module Homebrew
params( params(
formula_or_cask: T.any(Formula, Cask::Cask), formula_or_cask: T.any(Formula, Cask::Cask),
repositories: T::Array[T.untyped], repositories: T::Array[T.untyped],
args: CLI::Args,
name: String, name: String,
).returns(VersionBumpInfo) ).returns(VersionBumpInfo)
} }
def retrieve_versions_by_arch(formula_or_cask:, repositories:, args:, name:) def retrieve_versions_by_arch(formula_or_cask:, repositories:, name:)
is_cask_with_blocks = formula_or_cask.is_a?(Cask::Cask) && formula_or_cask.on_system_blocks_exist? is_cask_with_blocks = formula_or_cask.is_a?(Cask::Cask) && formula_or_cask.on_system_blocks_exist?
type, version_name = formula_or_cask.is_a?(Formula) ? [:formula, "formula version:"] : [:cask, "cask version: "] type, version_name = if formula_or_cask.is_a?(Formula)
[:formula, "formula version:"]
else
[:cask, "cask version: "]
end
old_versions = {} old_versions = {}
new_versions = {} new_versions = {}
@ -429,14 +428,12 @@ module Homebrew
formula_or_cask: T.any(Formula, Cask::Cask), formula_or_cask: T.any(Formula, Cask::Cask),
name: String, name: String,
repositories: T::Array[T.untyped], repositories: T::Array[T.untyped],
args: CLI::Args,
ambiguous_cask: T::Boolean, ambiguous_cask: T::Boolean,
).void ).void
} }
def retrieve_and_display_info_and_open_pr(formula_or_cask, name, repositories, args:, ambiguous_cask: false) def retrieve_and_display_info_and_open_pr(formula_or_cask, name, repositories, ambiguous_cask: false)
version_info = retrieve_versions_by_arch(formula_or_cask:, version_info = retrieve_versions_by_arch(formula_or_cask:,
repositories:, repositories:,
args:,
name:) name:)
current_version = version_info.current_version current_version = version_info.current_version
@ -480,7 +477,7 @@ module Homebrew
Current #{version_label} #{current_versions} Current #{version_label} #{current_versions}
Latest livecheck version: #{new_versions} Latest livecheck version: #{new_versions}
EOS EOS
puts <<~EOS unless skip_repology?(formula_or_cask, args:) puts <<~EOS unless skip_repology?(formula_or_cask)
Latest Repology version: #{repology_latest} Latest Repology version: #{repology_latest}
EOS EOS
if formula_or_cask.is_a?(Formula) && formula_or_cask.synced_with_other_formulae? if formula_or_cask.is_a?(Formula) && formula_or_cask.synced_with_other_formulae?
@ -552,3 +549,5 @@ module Homebrew
synced_with synced_with
end end
end end
end
end

View File

@ -1,12 +1,15 @@
# typed: true # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command"
require "cli/parser" require "cli/parser"
module Homebrew module Homebrew
sig { returns(CLI::Parser) } module DevCmd
def self.cat_args class Cat < AbstractCommand
Homebrew::CLI::Parser.new do include FileUtils
cmd_args do
description <<~EOS description <<~EOS
Display the source of a <formula> or <cask>. Display the source of a <formula> or <cask>.
EOS EOS
@ -20,11 +23,9 @@ module Homebrew
named_args [:formula, :cask], min: 1, without_api: true named_args [:formula, :cask], min: 1, without_api: true
end end
end
def self.cat
args = cat_args.parse
sig { override.void }
def run
cd HOMEBREW_REPOSITORY do cd HOMEBREW_REPOSITORY do
pager = if Homebrew::EnvConfig.bat? pager = if Homebrew::EnvConfig.bat?
ENV["BAT_CONFIG_PATH"] = Homebrew::EnvConfig.bat_config_path ENV["BAT_CONFIG_PATH"] = Homebrew::EnvConfig.bat_config_path
@ -60,3 +61,5 @@ module Homebrew
end end
end end
end end
end
end

View File

@ -1,26 +1,23 @@
# typed: true # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command"
require "commands" require "commands"
require "cli/parser" require "cli/parser"
module Homebrew module Homebrew
module_function module DevCmd
class Command < AbstractCommand
sig { returns(CLI::Parser) } cmd_args do
def command_args
Homebrew::CLI::Parser.new do
description <<~EOS description <<~EOS
Display the path to the file being used when invoking `brew` <cmd>. Display the path to the file being used when invoking `brew` <cmd>.
EOS EOS
named_args :command, min: 1 named_args :command, min: 1
end end
end
def command
args = command_args.parse
sig { override.void }
def run
args.named.each do |cmd| args.named.each do |cmd|
path = Commands.path(cmd) path = Commands.path(cmd)
odie "Unknown command: #{cmd}" unless path odie "Unknown command: #{cmd}" unless path
@ -28,3 +25,5 @@ module Homebrew
end end
end end
end end
end
end

View File

@ -5,8 +5,8 @@ require "cli/parser"
require "csv" require "csv"
module Homebrew module Homebrew
module_function module DevCmd
class Contributions < AbstractCommand
PRIMARY_REPOS = %w[brew core cask].freeze PRIMARY_REPOS = %w[brew core cask].freeze
SUPPORTED_REPOS = [ SUPPORTED_REPOS = [
PRIMARY_REPOS, PRIMARY_REPOS,
@ -15,9 +15,7 @@ module Homebrew
].flatten.freeze ].flatten.freeze
MAX_REPO_COMMITS = 1000 MAX_REPO_COMMITS = 1000
sig { returns(CLI::Parser) } cmd_args do
def contributions_args
Homebrew::CLI::Parser.new do
usage_banner "`contributions` [--user=<email|username>] [<--repositories>`=`] [<--csv>]" usage_banner "`contributions` [--user=<email|username>] [<--repositories>`=`] [<--csv>]"
description <<~EOS description <<~EOS
Summarise contributions to Homebrew repositories. Summarise contributions to Homebrew repositories.
@ -43,18 +41,15 @@ module Homebrew
switch "--csv", switch "--csv",
description: "Print a CSV of contributions across repositories over the time period." description: "Print a CSV of contributions across repositories over the time period."
end end
end
sig { void }
def contributions
args = contributions_args.parse
sig { override.void }
def run
results = {} results = {}
grand_totals = {} grand_totals = {}
repos = if args.repositories.blank? || args.repositories.include?("primary") repos = if args.repositories.blank? || T.must(args.repositories).include?("primary")
PRIMARY_REPOS PRIMARY_REPOS
elsif args.repositories.include?("all") elsif T.must(args.repositories).include?("all")
SUPPORTED_REPOS SUPPORTED_REPOS
else else
args.repositories args.repositories
@ -71,7 +66,7 @@ module Homebrew
# committer details to match the ones on GitHub. # committer details to match the ones on GitHub.
# TODO: Switch to using the GitHub APIs instead of `git log` if # TODO: Switch to using the GitHub APIs instead of `git log` if
# they ever support trailers. # they ever support trailers.
results[username] = scan_repositories(repos, username, args, from:) results[username] = scan_repositories(repos, username, from:)
grand_totals[username] = total(results[username]) grand_totals[username] = total(results[username])
contributions = contribution_types.filter_map do |type| contributions = contribution_types.filter_map do |type|
@ -80,7 +75,8 @@ module Homebrew
"#{Utils.pluralize("time", type_count, include_count: true)} (#{type})" "#{Utils.pluralize("time", type_count, include_count: true)} (#{type})"
end end
contributions << "#{Utils.pluralize("time", grand_totals[username].values.sum, include_count: true)} (total)" contributions <<
"#{Utils.pluralize("time", grand_totals[username].values.sum, include_count: true)} (total)"
puts [ puts [
"#{username} contributed", "#{username} contributed",
@ -95,6 +91,8 @@ module Homebrew
puts generate_csv(grand_totals) puts generate_csv(grand_totals)
end end
private
sig { params(repo: String).returns(Pathname) } sig { params(repo: String).returns(Pathname) }
def find_repo_path_for_repo(repo) def find_repo_path_for_repo(repo)
return HOMEBREW_REPOSITORY if repo == "brew" return HOMEBREW_REPOSITORY if repo == "brew"
@ -139,7 +137,7 @@ module Homebrew
] ]
end end
def scan_repositories(repos, person, args, from:) def scan_repositories(repos, person, from:)
data = {} data = {}
repos.each do |repo| repos.each do |repo|
@ -168,7 +166,7 @@ module Homebrew
author: author_commits, author: author_commits,
committer: committer_commits, committer: committer_commits,
coauthorship: git_log_trailers_cmd(T.must(repo_path), person, "Co-authored-by", from:, to: args.to), coauthorship: git_log_trailers_cmd(T.must(repo_path), person, "Co-authored-by", from:, to: args.to),
review: count_reviews(repo_full_name, person, args), review: count_reviews(repo_full_name, person),
} }
end end
@ -201,8 +199,8 @@ module Homebrew
Utils.safe_popen_read(*cmd).lines.count { |l| l.include?(person) } Utils.safe_popen_read(*cmd).lines.count { |l| l.include?(person) }
end end
sig { params(repo_full_name: String, person: String, args: Homebrew::CLI::Args).returns(Integer) } sig { params(repo_full_name: String, person: String).returns(Integer) }
def count_reviews(repo_full_name, person, args) def count_reviews(repo_full_name, person)
GitHub.count_issues("", is: "pr", repo: repo_full_name, reviewed_by: person, review: "approved", args:) GitHub.count_issues("", is: "pr", repo: repo_full_name, reviewed_by: person, review: "approved", args:)
rescue GitHub::API::ValidationFailedError rescue GitHub::API::ValidationFailedError
if args.verbose? if args.verbose?
@ -211,3 +209,5 @@ module Homebrew
0 # Users who have made their contributions private are not searchable to determine counts. 0 # Users who have made their contributions private are not searchable to determine counts.
end end
end end
end
end

View File

@ -2,7 +2,8 @@
# frozen_string_literal: true # frozen_string_literal: true
module Homebrew module Homebrew
class << self module DevCmd
class Bottle < AbstractCommand
undef tar_args undef tar_args
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
@ -22,3 +23,4 @@ module Homebrew
end end
end end
end end
end

View File

@ -49,7 +49,8 @@ module Tapioca
end end
else else
root.create_path(Homebrew::CLI::Args) do |klass| root.create_path(Homebrew::CLI::Args) do |klass|
create_args_methods(klass, T.must(T.cast(constant, T.class_of(Homebrew::AbstractCommand)).parser)) parser = T.cast(constant, T.class_of(Homebrew::AbstractCommand)).parser
create_args_methods(klass, parser)
end end
end end
end end

View File

@ -583,7 +583,7 @@ RSpec.describe Homebrew::CLI::Parser do
# commands for formulae and casks on Linux. # commands for formulae and casks on Linux.
it "succeeds for developer commands" do it "succeeds for developer commands" do
require "dev-cmd/cat" require "dev-cmd/cat"
args = Homebrew.cat_args.parse(["--cask", "cask_name"]) args = Homebrew::DevCmd::Cat.new(["--cask", "cask_name"]).args
expect(args.cask?).to be(true) expect(args.cask?).to be(true)
end end
end end

View File

@ -5,7 +5,7 @@ require "formulary"
require "cmd/shared_examples/args_parse" require "cmd/shared_examples/args_parse"
require "utils/spdx" require "utils/spdx"
RSpec.describe "brew audit" do RSpec.describe Homebrew::DevCmd::Audit do
it_behaves_like "parseable arguments" it_behaves_like "parseable arguments"
end end

View File

@ -3,7 +3,7 @@
require "cmd/shared_examples/args_parse" require "cmd/shared_examples/args_parse"
require "dev-cmd/bottle" require "dev-cmd/bottle"
RSpec.describe "brew bottle" do RSpec.describe Homebrew::DevCmd::Bottle do
def stub_hash(parameters) def stub_hash(parameters)
<<~EOS <<~EOS
{ {
@ -30,7 +30,7 @@ RSpec.describe "brew bottle" do
EOS EOS
end end
it_behaves_like "parseable arguments" it_behaves_like "parseable arguments", argv: ["foo"]
it "builds a bottle for the given Formula", :integration_test do it "builds a bottle for the given Formula", :integration_test do
install_test_formula "testball", build_bottle: true install_test_formula "testball", build_bottle: true
@ -308,8 +308,8 @@ RSpec.describe "brew bottle" do
end end
end end
describe Homebrew do describe "bottle_cmd" do
subject(:homebrew) { described_class } subject(:homebrew) { described_class.new(["foo"]) }
let(:hello_hash_big_sur) do let(:hello_hash_big_sur) do
JSON.parse stub_hash( JSON.parse stub_hash(

View File

@ -3,6 +3,6 @@
require "cmd/shared_examples/args_parse" require "cmd/shared_examples/args_parse"
require "dev-cmd/bump-cask-pr" require "dev-cmd/bump-cask-pr"
RSpec.describe "brew bump-cask-pr" do RSpec.describe Homebrew::DevCmd::BumpCaskPr do
it_behaves_like "parseable arguments" it_behaves_like "parseable arguments", argv: ["foo"]
end end

View File

@ -1,7 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
require "cmd/shared_examples/args_parse" require "cmd/shared_examples/args_parse"
require "dev-cmd/bump-formula-pr"
RSpec.describe "brew bump-formula-pr" do RSpec.describe Homebrew::DevCmd::BumpFormulaPr do
it_behaves_like "parseable arguments" it_behaves_like "parseable arguments"
end end

View File

@ -1,7 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
require "cmd/shared_examples/args_parse" require "cmd/shared_examples/args_parse"
require "dev-cmd/bump-revision"
RSpec.describe "brew bump-revision" do RSpec.describe Homebrew::DevCmd::BumpRevision do
it_behaves_like "parseable arguments" it_behaves_like "parseable arguments", argv: ["foo"]
end end

View File

@ -1,7 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
require "cmd/shared_examples/args_parse" require "cmd/shared_examples/args_parse"
require "dev-cmd/bump-unversioned-casks"
RSpec.describe "brew bump-unversioned-casks" do RSpec.describe Homebrew::DevCmd::BumpUnversionedCask do
it_behaves_like "parseable arguments" it_behaves_like "parseable arguments", argv: ["foo"]
end end

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
require "cmd/shared_examples/args_parse" require "cmd/shared_examples/args_parse"
require "dev-cmd/bump"
RSpec.describe "brew bump" do RSpec.describe Homebrew::DevCmd::Bump do
it_behaves_like "parseable arguments" it_behaves_like "parseable arguments"
describe "formula", :integration_test, :needs_homebrew_curl, :needs_network do describe "formula", :integration_test, :needs_homebrew_curl, :needs_network do

View File

@ -1,9 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
require "cmd/shared_examples/args_parse" require "cmd/shared_examples/args_parse"
require "dev-cmd/cat"
RSpec.describe "brew cat" do RSpec.describe Homebrew::DevCmd::Cat do
it_behaves_like "parseable arguments" it_behaves_like "parseable arguments", argv: ["foo"]
it "prints the content of a given Formula", :integration_test do it "prints the content of a given Formula", :integration_test do
formula_file = setup_test_formula "testball" formula_file = setup_test_formula "testball"

View File

@ -1,9 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
require "cmd/shared_examples/args_parse" require "cmd/shared_examples/args_parse"
require "dev-cmd/command"
RSpec.describe "brew command" do RSpec.describe Homebrew::DevCmd::Command do
it_behaves_like "parseable arguments" it_behaves_like "parseable arguments", argv: ["foo"]
it "returns the file for a given command", :integration_test do it "returns the file for a given command", :integration_test do
expect { brew "command", "info" } expect { brew "command", "info" }

View File

@ -1,7 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
require "cmd/shared_examples/args_parse" require "cmd/shared_examples/args_parse"
require "dev-cmd/contributions"
RSpec.describe "brew contributions" do RSpec.describe Homebrew::DevCmd::Contributions do
it_behaves_like "parseable arguments" it_behaves_like "parseable arguments"
end end