From e1fdd2eda4c06e80fde0c2e000115a67c61fce30 Mon Sep 17 00:00:00 2001 From: Douglas Eichelberger Date: Tue, 3 Dec 2024 17:43:22 -0800 Subject: [PATCH 1/2] Enable strict typing in NamedArgs --- Library/Homebrew/cli/named_args.rb | 141 ++++++++++++------ Library/Homebrew/cmd/info.rb | 6 - Library/Homebrew/cmd/reinstall.rb | 5 +- Library/Homebrew/cmd/tab.rb | 2 +- Library/Homebrew/cmd/tap.rb | 2 +- Library/Homebrew/dev-cmd/bump-cask-pr.rb | 2 +- Library/Homebrew/dev-cmd/bump-formula-pr.rb | 19 +-- Library/Homebrew/dev-cmd/create.rb | 4 +- .../dev-cmd/determine-test-runners.rb | 7 +- Library/Homebrew/dev-cmd/extract.rb | 4 +- Library/Homebrew/dev-cmd/pr-pull.rb | 2 +- 11 files changed, 122 insertions(+), 72 deletions(-) diff --git a/Library/Homebrew/cli/named_args.rb b/Library/Homebrew/cli/named_args.rb index 5990e875f3d54..6a05f78ae8ac5 100644 --- a/Library/Homebrew/cli/named_args.rb +++ b/Library/Homebrew/cli/named_args.rb @@ -1,4 +1,4 @@ -# typed: true # rubocop:todo Sorbet/StrictSigil +# typed: strict # frozen_string_literal: true require "delegate" @@ -8,6 +8,10 @@ module Homebrew module CLI # Helper class for loading formulae/casks from named arguments. class NamedArgs < Array + extend T::Generic + + Elem = type_member(:out) { { fixed: String } } + sig { params( args: String, @@ -39,14 +43,23 @@ def initialize( super(@args) end + sig { returns(Args) } attr_reader :parent + sig { returns(T::Array[Cask::Cask]) } def to_casks - @to_casks ||= to_formulae_and_casks(only: :cask).freeze + @to_casks ||= T.let( + to_formulae_and_casks(only: :cask).freeze, T.nilable(T::Array[T.any(Formula, Keg, Cask::Cask)]) + ) + T.cast(@to_casks, T::Array[Cask::Cask]) end + sig { returns(T::Array[Formula]) } def to_formulae - @to_formulae ||= to_formulae_and_casks(only: :formula).freeze + @to_formulae ||= T.let( + to_formulae_and_casks(only: :formula).freeze, T.nilable(T::Array[T.any(Formula, Keg, Cask::Cask)]) + ) + T.cast(@to_formulae, T::Array[Formula]) end # Convert named arguments to {Formula} or {Cask} objects. @@ -62,13 +75,15 @@ def to_formulae ).returns(T::Array[T.any(Formula, Keg, Cask::Cask)]) } def to_formulae_and_casks( - only: parent&.only_formula_or_cask, + only: parent.only_formula_or_cask, ignore_unavailable: false, method: T.unsafe(nil), uniq: true, warn: T.unsafe(nil) ) - @to_formulae_and_casks ||= {} + @to_formulae_and_casks ||= T.let( + {}, T.nilable(T::Hash[T.nilable(Symbol), T::Array[T.any(Formula, Keg, Cask::Cask)]]) + ) @to_formulae_and_casks[only] ||= downcased_unique_named.flat_map do |name| options = { warn: }.compact load_formula_or_cask(name, only:, method:, **options) @@ -83,20 +98,31 @@ def to_formulae_and_casks( end.freeze if uniq - @to_formulae_and_casks[only].uniq.freeze + @to_formulae_and_casks.fetch(only).uniq.freeze else - @to_formulae_and_casks[only] + @to_formulae_and_casks.fetch(only) end end - def to_formulae_to_casks(only: parent&.only_formula_or_cask, method: nil) - @to_formulae_to_casks ||= {} - @to_formulae_to_casks[[method, only]] = to_formulae_and_casks(only:, method:) - .partition { |o| o.is_a?(Formula) || o.is_a?(Keg) } - .map(&:freeze).freeze + sig { + params(only: T.nilable(Symbol), method: T.nilable(Symbol)) + .returns([T::Array[T.any(Formula, Keg)], T::Array[Cask::Cask]]) + } + def to_formulae_to_casks(only: parent.only_formula_or_cask, method: nil) + @to_formulae_to_casks ||= T.let( + {}, T.nilable(T::Hash[[T.nilable(Symbol), T.nilable(Symbol)], + [T::Array[T.any(Formula, Keg)], T::Array[Cask::Cask]]]) + ) + @to_formulae_to_casks[[method, only]] = + T.cast( + to_formulae_and_casks(only:, method:).partition { |o| o.is_a?(Formula) || o.is_a?(Keg) } + .map(&:freeze).freeze, + [T::Array[T.any(Formula, Keg)], T::Array[Cask::Cask]], + ) end # Returns formulae and casks after validating that a tap is present for each of them. + sig { returns(T::Array[T.any(Formula, Keg, Cask::Cask)]) } def to_formulae_and_casks_with_taps formulae_and_casks_with_taps, formulae_and_casks_without_taps = to_formulae_and_casks.partition do |formula_or_cask| @@ -118,8 +144,18 @@ def to_formulae_and_casks_with_taps ERROR end - def to_formulae_and_casks_and_unavailable(only: parent&.only_formula_or_cask, method: nil) - @to_formulae_casks_unknowns ||= {} + sig { + params(only: T.nilable(Symbol), method: T.nilable(Symbol)) + .returns(T::Array[T.any(Formula, Keg, Cask::Cask, T::Array[Keg], FormulaOrCaskUnavailableError)]) + } + def to_formulae_and_casks_and_unavailable(only: parent.only_formula_or_cask, method: nil) + @to_formulae_casks_unknowns ||= T.let( + {}, + T.nilable( + T::Hash[T.nilable(Symbol), + T::Array[T.any(Formula, Keg, Cask::Cask, T::Array[Keg], FormulaOrCaskUnavailableError)]], + ), + ) @to_formulae_casks_unknowns[method] = downcased_unique_named.map do |name| load_formula_or_cask(name, only:, method:) rescue FormulaOrCaskUnavailableError => e @@ -127,6 +163,10 @@ def to_formulae_and_casks_and_unavailable(only: parent&.only_formula_or_cask, me end.uniq.freeze end + sig { + params(name: String, only: T.nilable(Symbol), method: T.nilable(Symbol), warn: T.nilable(T::Boolean)) + .returns(T.any(Formula, Keg, Cask::Cask, T::Array[Keg])) + } def load_formula_or_cask(name, only: nil, method: nil, warn: nil) Homebrew.with_no_api_env_if_needed(@without_api) do unreadable_error = nil @@ -246,17 +286,16 @@ def load_formula_or_cask(name, only: nil, method: nil, warn: nil) user, repo, short_name = name.downcase.split("/", 3) if repo.present? && short_name.present? - tap = Tap.fetch(user, repo) + tap = Tap.fetch(T.must(user), repo) raise TapFormulaOrCaskUnavailableError.new(tap, short_name) end raise NoSuchKegError, name if resolve_formula(name) - - raise FormulaOrCaskUnavailableError, name end end private :load_formula_or_cask + sig { params(name: String).returns(Formula) } def resolve_formula(name) Formulary.resolve(name, **{ spec: @override_spec, force_bottle: @force_bottle, flags: @flags }.compact) end @@ -264,12 +303,16 @@ def resolve_formula(name) sig { params(uniq: T::Boolean).returns(T::Array[Formula]) } def to_resolved_formulae(uniq: true) - @to_resolved_formulae ||= to_formulae_and_casks(only: :formula, method: :resolve, uniq:) - .freeze + @to_resolved_formulae ||= T.let( + to_formulae_and_casks(only: :formula, method: :resolve, uniq:).freeze, + T.nilable(T::Array[T.any(Formula, Keg, Cask::Cask)]), + ) + T.cast(@to_resolved_formulae, T::Array[Formula]) end - def to_resolved_formulae_to_casks(only: parent&.only_formula_or_cask) - to_formulae_to_casks(only:, method: :resolve) + sig { params(only: T.nilable(Symbol)).returns([T::Array[Formula], T::Array[Cask::Cask]]) } + def to_resolved_formulae_to_casks(only: parent.only_formula_or_cask) + T.cast(to_formulae_to_casks(only:, method: :resolve), [T::Array[Formula], T::Array[Cask::Cask]]) end LOCAL_PATH_REGEX = %r{^/|[.]|/$} @@ -280,8 +323,8 @@ def to_resolved_formulae_to_casks(only: parent&.only_formula_or_cask) # If a cask and formula with the same name exist, includes both their paths # unless `only` is specified. sig { params(only: T.nilable(Symbol), recurse_tap: T::Boolean).returns(T::Array[Pathname]) } - def to_paths(only: parent&.only_formula_or_cask, recurse_tap: false) - @to_paths ||= {} + def to_paths(only: parent.only_formula_or_cask, recurse_tap: false) + @to_paths ||= T.let({}, T.nilable(T::Hash[T.nilable(Symbol), T::Array[Pathname]])) @to_paths[only] ||= Homebrew.with_no_api_env_if_needed(@without_api) do downcased_unique_named.flat_map do |name| path = Pathname(name).expand_path @@ -328,67 +371,70 @@ def to_paths(only: parent&.only_formula_or_cask, recurse_tap: false) def to_default_kegs require "missing_formula" - @to_default_kegs ||= begin + @to_default_kegs ||= T.let(begin to_formulae_and_casks(only: :formula, method: :default_kegs).freeze rescue NoSuchKegError => e if (reason = MissingFormula.suggest_command(e.name, "uninstall")) $stderr.puts reason end raise e - end + end, T.nilable(T::Array[T.any(Formula, Keg, Cask::Cask)])) + T.cast(@to_default_kegs, T::Array[Keg]) end sig { returns(T::Array[Keg]) } def to_latest_kegs require "missing_formula" - @to_latest_kegs ||= begin + @to_latest_kegs ||= T.let(begin to_formulae_and_casks(only: :formula, method: :latest_kegs).freeze rescue NoSuchKegError => e if (reason = MissingFormula.suggest_command(e.name, "uninstall")) $stderr.puts reason end raise e - end + end, T.nilable(T::Array[T.any(Formula, Keg, Cask::Cask)])) + T.cast(@to_latest_kegs, T::Array[Keg]) end sig { returns(T::Array[Keg]) } def to_kegs require "missing_formula" - @to_kegs ||= begin + @to_kegs ||= T.let(begin to_formulae_and_casks(only: :formula, method: :kegs).freeze rescue NoSuchKegError => e if (reason = MissingFormula.suggest_command(e.name, "uninstall")) $stderr.puts reason end raise e - end + end, T.nilable(T::Array[T.any(Formula, Keg, Cask::Cask)])) + T.cast(@to_kegs, T::Array[Keg]) end sig { params(only: T.nilable(Symbol), ignore_unavailable: T::Boolean, all_kegs: T.nilable(T::Boolean)) .returns([T::Array[Keg], T::Array[Cask::Cask]]) } - def to_kegs_to_casks(only: parent&.only_formula_or_cask, ignore_unavailable: false, all_kegs: nil) + def to_kegs_to_casks(only: parent.only_formula_or_cask, ignore_unavailable: false, all_kegs: nil) method = all_kegs ? :kegs : :default_kegs - @to_kegs_to_casks ||= {} + @to_kegs_to_casks ||= T.let({}, T.nilable(T::Hash[T.nilable(Symbol), [T::Array[Keg], T::Array[Cask::Cask]]])) @to_kegs_to_casks[method] ||= - to_formulae_and_casks(only:, ignore_unavailable:, method:) + T.cast(to_formulae_and_casks(only:, ignore_unavailable:, method:) .partition { |o| o.is_a?(Keg) } - .map(&:freeze).freeze + .map(&:freeze).freeze, [T::Array[Keg], T::Array[Cask::Cask]]) end sig { returns(T::Array[Tap]) } def to_taps - @to_taps ||= downcased_unique_named.map { |name| Tap.fetch name }.uniq.freeze + @to_taps ||= T.let(downcased_unique_named.map { |name| Tap.fetch name }.uniq.freeze, T.nilable(T::Array[Tap])) end sig { returns(T::Array[Tap]) } def to_installed_taps - @to_installed_taps ||= to_taps.each do |tap| + @to_installed_taps ||= T.let(to_taps.each do |tap| raise TapUnavailableError, tap.name unless tap.installed? - end.uniq.freeze + end.uniq.freeze, T.nilable(T::Array[Tap])) end sig { returns(T::Array[String]) } @@ -410,6 +456,7 @@ def downcased_unique_named end.uniq end + sig { params(name: String).returns([Pathname, T::Array[Keg]]) } def resolve_kegs(name) raise UsageError if name.blank? @@ -433,23 +480,26 @@ def resolve_kegs(name) [rack, kegs] end + sig { params(name: String).returns(Keg) } def resolve_latest_keg(name) _, kegs = resolve_kegs(name) # Return keg if it is the only installed keg - return kegs if kegs.length == 1 + return kegs.fetch(0) if kegs.length == 1 stable_kegs = kegs.reject { |keg| keg.version.head? } - if stable_kegs.blank? - return kegs.max_by do |keg| + latest_keg = if stable_kegs.empty? + kegs.max_by do |keg| [keg.tab.source_modified_time, keg.version.revision] end + else + stable_kegs.max_by(&:scheme_and_version) end - - stable_kegs.max_by(&:scheme_and_version) + T.must(latest_keg) end + sig { params(name: String).returns(Keg) } def resolve_default_keg(name) rack, kegs = resolve_kegs(name) @@ -459,7 +509,7 @@ def resolve_default_keg(name) begin return Keg.new(opt_prefix.resolved_path) if opt_prefix.symlink? && opt_prefix.directory? return Keg.new(linked_keg_ref.resolved_path) if linked_keg_ref.symlink? && linked_keg_ref.directory? - return kegs.first if kegs.length == 1 + return kegs.fetch(0) if kegs.length == 1 f = if name.include?("/") || File.exist?(name) Formulary.factory(name) @@ -484,6 +534,12 @@ def resolve_default_keg(name) end end + sig { + params( + ref: String, loaded_type: String, + package: T.any(T::Array[T.any(Formula, Keg)], Cask::Cask, Formula, Keg, NilClass) + ).returns(String) + } def package_conflicts_message(ref, loaded_type, package) message = "Treating #{ref} as a #{loaded_type}." case package @@ -503,6 +559,7 @@ def package_conflicts_message(ref, loaded_type, package) message.freeze end + sig { params(ref: String, loaded_type: String).void } def warn_if_cask_conflicts(ref, loaded_type) available = true cask = begin diff --git a/Library/Homebrew/cmd/info.rb b/Library/Homebrew/cmd/info.rb index 9b0424e65e40f..32fae43a1d3e0 100644 --- a/Library/Homebrew/cmd/info.rb +++ b/Library/Homebrew/cmd/info.rb @@ -162,12 +162,6 @@ def print_info info_formula(obj) when Cask::Cask info_cask(obj) - when FormulaUnreadableError, FormulaClassUnavailableError, - TapFormulaUnreadableError, TapFormulaClassUnavailableError, - Cask::CaskUnreadableError - # We found the formula/cask, but failed to read it - $stderr.puts obj.backtrace if Homebrew::EnvConfig.developer? - ofail obj.message when FormulaOrCaskUnavailableError # The formula/cask could not be found ofail obj.message diff --git a/Library/Homebrew/cmd/reinstall.rb b/Library/Homebrew/cmd/reinstall.rb index 6c660560076d3..b31fd2204c2ae 100644 --- a/Library/Homebrew/cmd/reinstall.rb +++ b/Library/Homebrew/cmd/reinstall.rb @@ -108,10 +108,7 @@ class Reinstall < AbstractCommand sig { override.void } def run - formulae, casks = T.cast( - args.named.to_resolved_formulae_to_casks, - [T::Array[Formula], T::Array[Cask::Cask]], - ) + formulae, casks = args.named.to_resolved_formulae_to_casks if args.build_from_source? unless DevelopmentTools.installed? diff --git a/Library/Homebrew/cmd/tab.rb b/Library/Homebrew/cmd/tab.rb index 48c891c74ac5e..5b911422cd88f 100644 --- a/Library/Homebrew/cmd/tab.rb +++ b/Library/Homebrew/cmd/tab.rb @@ -42,7 +42,7 @@ def run end raise UsageError, "No marking option specified." if installed_on_request.nil? - formulae, casks = args.named.to_formulae_to_casks + formulae, casks = T.cast(args.named.to_formulae_to_casks, [T::Array[Formula], T::Array[Cask::Cask]]) formulae_not_installed = formulae.reject(&:any_version_installed?) casks_not_installed = casks.reject(&:installed?) if formulae_not_installed.any? || casks_not_installed.any? diff --git a/Library/Homebrew/cmd/tap.rb b/Library/Homebrew/cmd/tap.rb index b902510452f0b..1c2d90021c4ba 100644 --- a/Library/Homebrew/cmd/tap.rb +++ b/Library/Homebrew/cmd/tap.rb @@ -58,7 +58,7 @@ def run elsif args.no_named? puts Tap.installed.sort_by(&:name) else - tap = Tap.fetch(args.named.first) + tap = Tap.fetch(args.named.fetch(0)) begin tap.install clone_target: args.named.second, custom_remote: args.custom_remote?, diff --git a/Library/Homebrew/dev-cmd/bump-cask-pr.rb b/Library/Homebrew/dev-cmd/bump-cask-pr.rb index 953501321c300..579e9db915b18 100644 --- a/Library/Homebrew/dev-cmd/bump-cask-pr.rb +++ b/Library/Homebrew/dev-cmd/bump-cask-pr.rb @@ -72,7 +72,7 @@ def run # Use the user's browser, too. ENV["BROWSER"] = EnvConfig.browser - cask = args.named.to_casks.first + cask = args.named.to_casks.fetch(0) odie "This cask is not in a tap!" if cask.tap.blank? odie "This cask's tap is not a Git repository!" unless cask.tap.git? diff --git a/Library/Homebrew/dev-cmd/bump-formula-pr.rb b/Library/Homebrew/dev-cmd/bump-formula-pr.rb index a924c07d01a2a..df97ced7fb3ba 100644 --- a/Library/Homebrew/dev-cmd/bump-formula-pr.rb +++ b/Library/Homebrew/dev-cmd/bump-formula-pr.rb @@ -109,18 +109,19 @@ def run odie "This formula is disabled!" if formula.disabled? odie "This formula is deprecated and does not build!" if formula.deprecation_reason == :does_not_build - odie "This formula is not in a tap!" if formula.tap.blank? - odie "This formula's tap is not a Git repository!" unless formula.tap.git? + tap = formula.tap + odie "This formula is not in a tap!" if tap.blank? + odie "This formula's tap is not a Git repository!" unless tap.git? - odie <<~EOS unless formula.tap.allow_bump?(formula.name) + odie <<~EOS unless tap.allow_bump?(formula.name) Whoops, the #{formula.name} formula has its version update pull requests automatically opened by BrewTestBot every ~3 hours! We'd still love your contributions, though, so try another one that's not in the autobump list: - #{Formatter.url("#{formula.tap.remote}/blob/master/.github/autobump.txt")} + #{Formatter.url("#{tap.remote}/blob/master/.github/autobump.txt")} EOS - odie "You have too many PRs open: close or merge some first!" if GitHub.too_many_open_prs?(formula.tap) + odie "You have too many PRs open: close or merge some first!" if GitHub.too_many_open_prs?(tap) formula_spec = formula.stable odie "#{formula}: no stable specification found!" if formula_spec.blank? @@ -129,9 +130,9 @@ def run # spamming during normal output. Homebrew.install_bundler_gems!(groups: ["audit", "style"]) unless args.no_audit? - tap_remote_repo = formula.tap.remote_repository + tap_remote_repo = T.must(tap.remote_repository) remote = "origin" - remote_branch = formula.tap.git_repository.origin_branch_name + remote_branch = tap.git_repository.origin_branch_name previous_branch = "-" check_pull_requests(formula, tap_remote_repo, state: "open") @@ -333,7 +334,7 @@ def run alias_rename = alias_update_pair(formula, new_formula_version) if alias_rename.present? ohai "Renaming alias #{alias_rename.first} to #{alias_rename.last}" - alias_rename.map! { |a| formula.tap.alias_dir/a } + alias_rename.map! { |a| tap.alias_dir/a } end unless args.dry_run? @@ -389,7 +390,7 @@ def run branch_name: "bump-#{formula.name}-#{new_formula_version}", commit_message: "#{formula.name} #{new_formula_version}", previous_branch:, - tap: formula.tap, + tap: tap, tap_remote_repo:, pr_message:, } diff --git a/Library/Homebrew/dev-cmd/create.rb b/Library/Homebrew/dev-cmd/create.rb index 13917b8439b28..7b1f87ca6b5ec 100644 --- a/Library/Homebrew/dev-cmd/create.rb +++ b/Library/Homebrew/dev-cmd/create.rb @@ -81,7 +81,7 @@ def run sig { returns(Pathname) } def create_cask - url = args.named.first + url = args.named.fetch(0) name = if args.set_name.blank? stem = Pathname.new(url).stem.rpartition("=").last print "Cask name [#{stem}]: " @@ -179,7 +179,7 @@ def create_formula args.set_name, args.set_version, tap: args.tap, - url: args.named.first, + url: args.named.fetch(0), mode:, license: args.set_license, fetch: !args.no_fetch?, diff --git a/Library/Homebrew/dev-cmd/determine-test-runners.rb b/Library/Homebrew/dev-cmd/determine-test-runners.rb index cba08ded8ec7a..b5b64666914a1 100644 --- a/Library/Homebrew/dev-cmd/determine-test-runners.rb +++ b/Library/Homebrew/dev-cmd/determine-test-runners.rb @@ -39,9 +39,10 @@ def run raise UsageError, "`--all-supported` is mutually exclusive to other arguments." end - testing_formulae = args.named.first&.split(",").to_a - testing_formulae.map! { |name| TestRunnerFormula.new(Formulary.factory(name), eval_all: args.eval_all?) } - .freeze + testing_formulae = args.named.first&.split(",").to_a.map do |name| + TestRunnerFormula.new(Formulary.factory(name), eval_all: args.eval_all?) + end + .freeze deleted_formulae = args.named.second&.split(",").to_a.freeze runner_matrix = GitHubRunnerMatrix.new(testing_formulae, deleted_formulae, all_supported: args.all_supported?, diff --git a/Library/Homebrew/dev-cmd/extract.rb b/Library/Homebrew/dev-cmd/extract.rb index d186d4f0d1ced..6063f9314f421 100644 --- a/Library/Homebrew/dev-cmd/extract.rb +++ b/Library/Homebrew/dev-cmd/extract.rb @@ -37,12 +37,12 @@ def run if (tap_with_name = args.named.first&.then { Tap.with_formula_name(_1) }) source_tap, name = tap_with_name else - name = args.named.first.downcase + name = args.named.fetch(0).downcase source_tap = CoreTap.instance end raise TapFormulaUnavailableError.new(source_tap, name) unless source_tap.installed? - destination_tap = Tap.fetch(args.named.second) + destination_tap = Tap.fetch(args.named.fetch(1)) unless Homebrew::EnvConfig.developer? odie "Cannot extract formula to homebrew/core!" if destination_tap.core_tap? odie "Cannot extract formula to homebrew/cask!" if destination_tap.core_cask_tap? diff --git a/Library/Homebrew/dev-cmd/pr-pull.rb b/Library/Homebrew/dev-cmd/pr-pull.rb index d3f8225109210..116fc736c4436 100644 --- a/Library/Homebrew/dev-cmd/pr-pull.rb +++ b/Library/Homebrew/dev-cmd/pr-pull.rb @@ -93,7 +93,7 @@ def run arg = "#{tap.default_remote}/pull/#{arg}" if arg.to_i.positive? url_match = arg.match HOMEBREW_PULL_OR_COMMIT_URL_REGEX _, user, repo, pr = *url_match - odie "Not a GitHub pull request: #{arg}" unless pr + odie "Not a GitHub pull request: #{arg}" if !user || !repo || !pr git_repo = tap.git_repository if !git_repo.default_origin_branch? && !args.branch_okay? && !args.no_commit? && !args.no_cherry_pick? From 90d066c7b8178269f88fd2225dda6f04ee62143a Mon Sep 17 00:00:00 2001 From: Douglas Eichelberger Date: Tue, 3 Dec 2024 17:53:00 -0800 Subject: [PATCH 2/2] Tidy up layout --- Library/Homebrew/cli/named_args.rb | 294 +++++++++--------- .../dev-cmd/determine-test-runners.rb | 3 +- 2 files changed, 146 insertions(+), 151 deletions(-) diff --git a/Library/Homebrew/cli/named_args.rb b/Library/Homebrew/cli/named_args.rb index 6a05f78ae8ac5..378f5133f82cd 100644 --- a/Library/Homebrew/cli/named_args.rb +++ b/Library/Homebrew/cli/named_args.rb @@ -1,7 +1,6 @@ # typed: strict # frozen_string_literal: true -require "delegate" require "cli/args" module Homebrew @@ -12,6 +11,9 @@ class NamedArgs < Array Elem = type_member(:out) { { fixed: String } } + sig { returns(Args) } + attr_reader :parent + sig { params( args: String, @@ -32,20 +34,16 @@ def initialize( cask_options: false, without_api: false ) - @args = args + super(args) + @override_spec = override_spec @force_bottle = force_bottle @flags = flags @cask_options = cask_options @without_api = without_api @parent = parent - - super(@args) end - sig { returns(Args) } - attr_reader :parent - sig { returns(T::Array[Cask::Cask]) } def to_casks @to_casks ||= T.let( @@ -151,10 +149,10 @@ def to_formulae_and_casks_with_taps def to_formulae_and_casks_and_unavailable(only: parent.only_formula_or_cask, method: nil) @to_formulae_casks_unknowns ||= T.let( {}, - T.nilable( - T::Hash[T.nilable(Symbol), - T::Array[T.any(Formula, Keg, Cask::Cask, T::Array[Keg], FormulaOrCaskUnavailableError)]], - ), + T.nilable(T::Hash[ + T.nilable(Symbol), + T::Array[T.any(Formula, Keg, Cask::Cask, T::Array[Keg], FormulaOrCaskUnavailableError)] + ]), ) @to_formulae_casks_unknowns[method] = downcased_unique_named.map do |name| load_formula_or_cask(name, only:, method:) @@ -163,144 +161,6 @@ def to_formulae_and_casks_and_unavailable(only: parent.only_formula_or_cask, met end.uniq.freeze end - sig { - params(name: String, only: T.nilable(Symbol), method: T.nilable(Symbol), warn: T.nilable(T::Boolean)) - .returns(T.any(Formula, Keg, Cask::Cask, T::Array[Keg])) - } - def load_formula_or_cask(name, only: nil, method: nil, warn: nil) - Homebrew.with_no_api_env_if_needed(@without_api) do - unreadable_error = nil - - formula_or_kegs = if only != :cask - begin - case method - when nil, :factory - options = { warn:, force_bottle: @force_bottle, flags: @flags }.compact - Formulary.factory(name, *@override_spec, **options) - when :resolve - resolve_formula(name) - when :latest_kegs - resolve_latest_keg(name) - when :default_kegs - resolve_default_keg(name) - when :kegs - _, kegs = resolve_kegs(name) - kegs - else - raise - end - rescue FormulaUnreadableError, FormulaClassUnavailableError, - TapFormulaUnreadableError, TapFormulaClassUnavailableError, - FormulaSpecificationError => e - # Need to rescue before `FormulaUnavailableError` (superclass of this) - # The formula was found, but there's a problem with its implementation - unreadable_error ||= e - nil - rescue NoSuchKegError, FormulaUnavailableError => e - raise e if only == :formula - - nil - end - end - - if only == :formula - return formula_or_kegs if formula_or_kegs - elsif formula_or_kegs && (!formula_or_kegs.is_a?(Formula) || formula_or_kegs.tap&.core_tap?) - warn_if_cask_conflicts(name, "formula") - return formula_or_kegs - else - want_keg_like_cask = [:latest_kegs, :default_kegs, :kegs].include?(method) - - cask = begin - config = Cask::Config.from_args(@parent) if @cask_options - options = { warn: }.compact - candidate_cask = Cask::CaskLoader.load(name, config:, **options) - - if unreadable_error.present? - onoe <<~EOS - Failed to load formula: #{name} - #{unreadable_error} - EOS - opoo "Treating #{name} as a cask." - end - - # If we're trying to get a keg-like Cask, do our best to use the same cask - # file that was used for installation, if possible. - if want_keg_like_cask && - (installed_caskfile = candidate_cask.installed_caskfile) && - installed_caskfile.exist? - cask = Cask::CaskLoader.load_from_installed_caskfile(installed_caskfile) - - requested_tap, requested_token = Tap.with_cask_token(name) - if requested_tap && requested_token - installed_cask_tap = cask.tab.tap - - if installed_cask_tap && installed_cask_tap != requested_tap - raise Cask::TapCaskUnavailableError.new(requested_tap, requested_token) - end - end - - cask - else - candidate_cask - end - rescue Cask::CaskUnreadableError, Cask::CaskInvalidError => e - # If we're trying to get a keg-like Cask, do our best to handle it - # not being readable and return something that can be used. - if want_keg_like_cask - cask_version = Cask::Cask.new(name, config:).installed_version - Cask::Cask.new(name, config:) do - version cask_version if cask_version - end - else - # Need to rescue before `CaskUnavailableError` (superclass of this) - # The cask was found, but there's a problem with its implementation - unreadable_error ||= e - nil - end - rescue Cask::CaskUnavailableError => e - raise e if only == :cask - - nil - end - - # Prioritise formulae unless it's a core tap cask (we already prioritised core tap formulae above) - if formula_or_kegs && !cask&.tap&.core_cask_tap? - if cask || unreadable_error - onoe <<~EOS if unreadable_error - Failed to load cask: #{name} - #{unreadable_error} - EOS - opoo package_conflicts_message(name, "formula", cask) unless Context.current.quiet? - end - return formula_or_kegs - elsif cask - if formula_or_kegs && !Context.current.quiet? - opoo package_conflicts_message(name, "cask", formula_or_kegs) - end - return cask - end - end - - raise unreadable_error if unreadable_error.present? - - user, repo, short_name = name.downcase.split("/", 3) - if repo.present? && short_name.present? - tap = Tap.fetch(T.must(user), repo) - raise TapFormulaOrCaskUnavailableError.new(tap, short_name) - end - - raise NoSuchKegError, name if resolve_formula(name) - end - end - private :load_formula_or_cask - - sig { params(name: String).returns(Formula) } - def resolve_formula(name) - Formulary.resolve(name, **{ spec: @override_spec, force_bottle: @force_bottle, flags: @flags }.compact) - end - private :resolve_formula - sig { params(uniq: T::Boolean).returns(T::Array[Formula]) } def to_resolved_formulae(uniq: true) @to_resolved_formulae ||= T.let( @@ -456,6 +316,142 @@ def downcased_unique_named end.uniq end + sig { + params(name: String, only: T.nilable(Symbol), method: T.nilable(Symbol), warn: T.nilable(T::Boolean)) + .returns(T.any(Formula, Keg, Cask::Cask, T::Array[Keg])) + } + def load_formula_or_cask(name, only: nil, method: nil, warn: nil) + Homebrew.with_no_api_env_if_needed(@without_api) do + unreadable_error = nil + + formula_or_kegs = if only != :cask + begin + case method + when nil, :factory + options = { warn:, force_bottle: @force_bottle, flags: @flags }.compact + Formulary.factory(name, *@override_spec, **options) + when :resolve + resolve_formula(name) + when :latest_kegs + resolve_latest_keg(name) + when :default_kegs + resolve_default_keg(name) + when :kegs + _, kegs = resolve_kegs(name) + kegs + else + raise + end + rescue FormulaUnreadableError, FormulaClassUnavailableError, + TapFormulaUnreadableError, TapFormulaClassUnavailableError, + FormulaSpecificationError => e + # Need to rescue before `FormulaUnavailableError` (superclass of this) + # The formula was found, but there's a problem with its implementation + unreadable_error ||= e + nil + rescue NoSuchKegError, FormulaUnavailableError => e + raise e if only == :formula + + nil + end + end + + if only == :formula + return formula_or_kegs if formula_or_kegs + elsif formula_or_kegs && (!formula_or_kegs.is_a?(Formula) || formula_or_kegs.tap&.core_tap?) + warn_if_cask_conflicts(name, "formula") + return formula_or_kegs + else + want_keg_like_cask = [:latest_kegs, :default_kegs, :kegs].include?(method) + + cask = begin + config = Cask::Config.from_args(@parent) if @cask_options + options = { warn: }.compact + candidate_cask = Cask::CaskLoader.load(name, config:, **options) + + if unreadable_error.present? + onoe <<~EOS + Failed to load formula: #{name} + #{unreadable_error} + EOS + opoo "Treating #{name} as a cask." + end + + # If we're trying to get a keg-like Cask, do our best to use the same cask + # file that was used for installation, if possible. + if want_keg_like_cask && + (installed_caskfile = candidate_cask.installed_caskfile) && + installed_caskfile.exist? + cask = Cask::CaskLoader.load_from_installed_caskfile(installed_caskfile) + + requested_tap, requested_token = Tap.with_cask_token(name) + if requested_tap && requested_token + installed_cask_tap = cask.tab.tap + + if installed_cask_tap && installed_cask_tap != requested_tap + raise Cask::TapCaskUnavailableError.new(requested_tap, requested_token) + end + end + + cask + else + candidate_cask + end + rescue Cask::CaskUnreadableError, Cask::CaskInvalidError => e + # If we're trying to get a keg-like Cask, do our best to handle it + # not being readable and return something that can be used. + if want_keg_like_cask + cask_version = Cask::Cask.new(name, config:).installed_version + Cask::Cask.new(name, config:) do + version cask_version if cask_version + end + else + # Need to rescue before `CaskUnavailableError` (superclass of this) + # The cask was found, but there's a problem with its implementation + unreadable_error ||= e + nil + end + rescue Cask::CaskUnavailableError => e + raise e if only == :cask + + nil + end + + # Prioritise formulae unless it's a core tap cask (we already prioritised core tap formulae above) + if formula_or_kegs && !cask&.tap&.core_cask_tap? + if cask || unreadable_error + onoe <<~EOS if unreadable_error + Failed to load cask: #{name} + #{unreadable_error} + EOS + opoo package_conflicts_message(name, "formula", cask) unless Context.current.quiet? + end + return formula_or_kegs + elsif cask + if formula_or_kegs && !Context.current.quiet? + opoo package_conflicts_message(name, "cask", formula_or_kegs) + end + return cask + end + end + + raise unreadable_error if unreadable_error.present? + + user, repo, short_name = name.downcase.split("/", 3) + if repo.present? && short_name.present? + tap = Tap.fetch(T.must(user), repo) + raise TapFormulaOrCaskUnavailableError.new(tap, short_name) + end + + raise NoSuchKegError, name if resolve_formula(name) + end + end + + sig { params(name: String).returns(Formula) } + def resolve_formula(name) + Formulary.resolve(name, **{ spec: @override_spec, force_bottle: @force_bottle, flags: @flags }.compact) + end + sig { params(name: String).returns([Pathname, T::Array[Keg]]) } def resolve_kegs(name) raise UsageError if name.blank? diff --git a/Library/Homebrew/dev-cmd/determine-test-runners.rb b/Library/Homebrew/dev-cmd/determine-test-runners.rb index b5b64666914a1..39449ea83ff8c 100644 --- a/Library/Homebrew/dev-cmd/determine-test-runners.rb +++ b/Library/Homebrew/dev-cmd/determine-test-runners.rb @@ -41,8 +41,7 @@ def run testing_formulae = args.named.first&.split(",").to_a.map do |name| TestRunnerFormula.new(Formulary.factory(name), eval_all: args.eval_all?) - end - .freeze + end.freeze deleted_formulae = args.named.second&.split(",").to_a.freeze runner_matrix = GitHubRunnerMatrix.new(testing_formulae, deleted_formulae, all_supported: args.all_supported?,