diff --git a/Library/Homebrew/dev-cmd/bump.rb b/Library/Homebrew/dev-cmd/bump.rb index 79a5216b35..18f9fb2d18 100644 --- a/Library/Homebrew/dev-cmd/bump.rb +++ b/Library/Homebrew/dev-cmd/bump.rb @@ -2,6 +2,7 @@ # frozen_string_literal: true require "cli/parser" +require "livecheck/livecheck" module Homebrew extend T::Sig @@ -17,8 +18,6 @@ module Homebrew EOS flag "--limit=", description: "Limit number of package results returned." - switch :verbose - switch :debug named_args :formula end @@ -27,64 +26,107 @@ module Homebrew def bump args = bump_args.parse - requested_formulae = args.named.to_formulae.map(&:name) if args.named.to_formulae.present? - + requested_formulae = args.named.to_formulae.presence requested_limit = args.limit.to_i if args.limit.present? - repology_data = if requested_formulae - response = {} - requested_formulae.each do |formula| - raise FormulaUnavailableError, formula unless validate_formula(formula) - - package_data = Repology.single_package_query(formula) - response[package_data.keys.first] = package_data.values.first if package_data - end - - response - else - Repology.parse_api_response(requested_limit) - end - - validated_formulae = {} - - validated_formulae = Repology.validate_and_format_packages(repology_data, requested_limit) if repology_data - if requested_formulae - repology_excluded_formulae = requested_formulae.reject do |formula| - repology_data[formula] - end + Livecheck.load_other_tap_strategies(requested_formulae) - formulae = {} - repology_excluded_formulae.each do |formula| - formulae[formula] = Repology.format_package(formula, nil) - end + requested_formulae.each_with_index do |formula, i| + puts if i.positive? - formulae.each { |formula, data| validated_formulae[formula] = data } + if formula.head_only? + ohai formula.name + puts "Formula is HEAD-only." + next + end + + current_version = formula.stable.version.to_s + + package_data = Repology.single_package_query(formula.name) + repology_latest = if package_data.present? + Repology.latest_version(package_data.values.first) + else + "not found" + end + + livecheck_latest = livecheck_result(formula) + pull_requests = retrieve_pull_requests(formula) + display(formula, current_version, repology_latest, livecheck_latest, pull_requests) + end + else + outdated_packages = Repology.parse_api_response(requested_limit) + outdated_packages.each_with_index do |(_name, repositories), i| + puts if i.positive? + + homebrew_repo = repositories.find do |repo| + repo["repo"] == "homebrew" + end + + next if homebrew_repo.blank? + + formula = begin + Formula[homebrew_repo["srcname"]] + rescue + next + end + + current_version = formula.stable.version.to_s + repology_latest = Repology.latest_version(repositories) + livecheck_latest = livecheck_result(formula) + pull_requests = retrieve_pull_requests(formula) + display(formula, current_version, repology_latest, livecheck_latest, pull_requests) + + break if requested_limit && i >= requested_limit + end + end + end + + def livecheck_result(formula) + skip_result = Livecheck::SkipConditions.skip_information(formula) + if skip_result.present? + return "#{skip_result[:status]}#{" - #{skip_result[:messages].join(", ")}" if skip_result[:messages].present?}" end - display(validated_formulae) + version_info = Livecheck.latest_version( + formula, + json: true, full_name: false, verbose: false, debug: false, + ) + latest = version_info[:latest] if version_info.present? + + return "unable to get versions" if latest.blank? + + latest.to_s end - def validate_formula(formula_name) - Formula[formula_name] - rescue - nil - end - - def up_to_date?(package) - package && - package[:current_formula_version] == package[:repology_latest_version] && - package[:current_formula_version] == package[:livecheck_latest_version] - end - - def display(formulae) - formulae.each do |formula, package_details| - title = (up_to_date?(package_details) ? "#{formula} is up to date!" : formula).to_s - ohai title - puts "Current formula version: #{package_details[:current_formula_version]}" - puts "Latest Repology version: #{package_details[:repology_latest_version]}" - puts "Latest livecheck version: #{package_details[:livecheck_latest_version]}" - puts "Open pull requests: #{package_details[:open_pull_requests]}" + def retrieve_pull_requests(formula) + pull_requests = GitHub.fetch_pull_requests(formula.name, formula.tap&.full_name, state: "open") + if pull_requests.try(:any?) + pull_requests = pull_requests.map { |pr| "#{pr["title"]} (#{Formatter.url(pr["html_url"])})" }.join(", ") end + + return "none" if pull_requests.blank? + + pull_requests + end + + def up_to_date?(current_version, repology_latest, livecheck_latest) + current_version == repology_latest && + current_version == livecheck_latest + end + + def display(formula, current_version, repology_latest, livecheck_latest, pull_requests) + title = if current_version == repology_latest && + current_version == livecheck_latest + "#{formula} is up to date!" + else + formula.name + end + + ohai title + puts "Current formula version: #{current_version}" + puts "Latest Repology version: #{repology_latest}" + puts "Latest livecheck version: #{livecheck_latest}" + puts "Open pull requests: #{pull_requests}" end end diff --git a/Library/Homebrew/livecheck/livecheck.rb b/Library/Homebrew/livecheck/livecheck.rb index 1c0e6621da..d69719b7e0 100644 --- a/Library/Homebrew/livecheck/livecheck.rb +++ b/Library/Homebrew/livecheck/livecheck.rb @@ -60,6 +60,27 @@ module Homebrew @livecheck_strategy_names.freeze end + # Uses `formulae_and_casks_to_check` to identify taps in use other than + # homebrew/core and homebrew/cask and loads strategies from them. + sig { params(formulae_and_casks_to_check: T::Enumerable[T.any(Formula, Cask::Cask)]).void } + def load_other_tap_strategies(formulae_and_casks_to_check) + other_taps = {} + formulae_and_casks_to_check.each do |formula_or_cask| + next if formula_or_cask.tap.blank? + next if formula_or_cask.tap.name == CoreTap.instance.name + next if formula_or_cask.tap.name == "homebrew/cask" + next if other_taps[formula_or_cask.tap.name] + + other_taps[formula_or_cask.tap.name] = formula_or_cask.tap + end + other_taps = other_taps.sort.to_h + + other_taps.each_value do |tap| + tap_strategy_path = "#{tap.path}/livecheck/strategy" + Dir["#{tap_strategy_path}/*.rb"].sort.each(&method(:require)) if Dir.exist?(tap_strategy_path) + end + end + # Executes the livecheck logic for each formula/cask in the # `formulae_and_casks_to_check` array and prints the results. sig { @@ -77,22 +98,7 @@ module Homebrew formulae_and_casks_to_check, full_name: false, json: false, newer_only: false, debug: false, quiet: false, verbose: false ) - # Identify any non-homebrew/core taps in use for current formulae - non_core_taps = {} - formulae_and_casks_to_check.each do |formula_or_cask| - next if formula_or_cask.tap.blank? - next if formula_or_cask.tap.name == CoreTap.instance.name - next if non_core_taps[formula_or_cask.tap.name] - - non_core_taps[formula_or_cask.tap.name] = formula_or_cask.tap - end - non_core_taps = non_core_taps.sort.to_h - - # Load additional Strategy files from taps - non_core_taps.each_value do |tap| - tap_strategy_path = "#{tap.path}/livecheck/strategy" - Dir["#{tap_strategy_path}/*.rb"].sort.each(&method(:require)) if Dir.exist?(tap_strategy_path) - end + load_other_tap_strategies(formulae_and_casks_to_check) has_a_newer_upstream_version = T.let(false, T::Boolean) @@ -452,12 +458,6 @@ module Homebrew end end - # Skip Gists until/unless we create a method of identifying revisions - if original_url.include?("gist.github.com") - odebug "Skipping: GitHub Gists are not supported" - next - end - # Only preprocess the URL when it's appropriate url = if STRATEGY_SYMBOLS_TO_SKIP_PREPROCESS_URL.include?(livecheck_strategy) original_url diff --git a/Library/Homebrew/test/utils/livecheck_formula_spec.rb b/Library/Homebrew/test/utils/livecheck_formula_spec.rb deleted file mode 100644 index 7f8495b343..0000000000 --- a/Library/Homebrew/test/utils/livecheck_formula_spec.rb +++ /dev/null @@ -1,39 +0,0 @@ -# typed: false -# frozen_string_literal: true - -require "utils/livecheck_formula" -require "formula_installer" - -describe LivecheckFormula do - describe "init" do - let(:f) { formula { url "foo-1.0" } } - let(:options) { FormulaInstaller.new(f).display_options(f) } - let(:action) { "#{f.full_name} #{options}".strip } - - it "runs livecheck command for Formula" do - formatted_response = described_class.init(action) - - expect(formatted_response).not_to be_nil - expect(formatted_response).to be_a(Hash) - expect(formatted_response.size).not_to eq(0) - end - end - - describe "parse_livecheck_response" do - it "returns a hash of Formula version data" do - example_raw_command_response = "aacgain : 7834 ==> 1.8" - formatted_response = described_class.parse_livecheck_response(example_raw_command_response) - - expect(formatted_response).not_to be_nil - expect(formatted_response).to be_a(Hash) - - expect(formatted_response).to include(:name) - expect(formatted_response).to include(:formula_version) - expect(formatted_response).to include(:livecheck_version) - - expect(formatted_response[:name]).to eq("aacgain") - expect(formatted_response[:formula_version]).to eq("7834") - expect(formatted_response[:livecheck_version]).to eq("1.8") - end - end -end diff --git a/Library/Homebrew/test/utils/repology_spec.rb b/Library/Homebrew/test/utils/repology_spec.rb index acacade360..87fd550eef 100644 --- a/Library/Homebrew/test/utils/repology_spec.rb +++ b/Library/Homebrew/test/utils/repology_spec.rb @@ -4,12 +4,6 @@ require "utils/repology" describe Repology do - describe "formula_data" do - it "returns nil for invalid Homebrew Formula" do - expect(described_class.formula_data("invalidName")).to be_nil - end - end - describe "single_package_query", :needs_network do it "returns nil for non-existent package" do response = described_class.single_package_query("invalidName") diff --git a/Library/Homebrew/utils.rb b/Library/Homebrew/utils.rb index 32f7ba3ecc..1a22c41823 100644 --- a/Library/Homebrew/utils.rb +++ b/Library/Homebrew/utils.rb @@ -11,7 +11,6 @@ require "utils/git_repository" require "utils/github" require "utils/inreplace" require "utils/link" -require "utils/livecheck_formula" require "utils/popen" require "utils/repology" require "utils/svn" diff --git a/Library/Homebrew/utils/livecheck_formula.rb b/Library/Homebrew/utils/livecheck_formula.rb deleted file mode 100644 index 628d5b874b..0000000000 --- a/Library/Homebrew/utils/livecheck_formula.rb +++ /dev/null @@ -1,35 +0,0 @@ -# typed: false -# frozen_string_literal: true - -require "context" - -# Helper module for parsing output of `brew livecheck`. -# -# @api private -module LivecheckFormula - extend Context - - module_function - - def init(formula) - ohai "Checking livecheck formula: #{formula}" if verbose? - - response = Utils.popen_read(HOMEBREW_BREW_FILE, "livecheck", formula, "--quiet").chomp - - parse_livecheck_response(response) - end - - def parse_livecheck_response(response) - # e.g response => aacgain : 7834 ==> 1.8 - output = response.delete(" ").split(/:|==>/) - - # e.g. ["openclonk", "7.0", "8.1"] - package_name, brew_version, latest_version = output - - { - name: package_name, - formula_version: brew_version, - livecheck_version: latest_version, - } - end -end diff --git a/Library/Homebrew/utils/repology.rb b/Library/Homebrew/utils/repology.rb index f1a017334b..9e80e455cc 100644 --- a/Library/Homebrew/utils/repology.rb +++ b/Library/Homebrew/utils/repology.rb @@ -21,16 +21,17 @@ module Repology end def single_package_query(name) - url = "https://repology.org/api/v1/project/#{name}" + url = "https://repology.org/tools/project-by?repo=homebrew&" \ + "name_type=srcname&target_page=api_v1_project&name=#{name}" - output, _errors, _status = curl_output(url.to_s) - data = JSON.parse(output) + output, _errors, _status = curl_output("--location", url.to_s) - homebrew = data.select do |repo| - repo["repo"] == "homebrew" + begin + data = JSON.parse(output) + { name => data } + rescue + nil end - - homebrew.empty? ? nil : { name => data } end def parse_api_response(limit = nil) @@ -58,63 +59,23 @@ module Repology outdated_packages end - def validate_and_format_packages(outdated_repology_packages, limit) - if outdated_repology_packages.size > 10 && (limit.blank? || limit > 10) - ohai "Verifying outdated repology packages" + def latest_version(repositories) + # The status is "unique" when the package is present only in Homebrew, so + # Repology has no way of knowing if the package is up-to-date. + is_unique = repositories.find do |repo| + repo["status"] == "unique" + end.present? + + return "present only in Homebrew" if is_unique + + latest_version = repositories.find do |repo| + repo["status"] == "newest" end - packages = {} + # Repology cannot identify "newest" versions for packages without a version + # scheme + return "no latest version" if latest_version.blank? - outdated_repology_packages.each do |_name, repositories| - repology_homebrew_repo = repositories.find do |repo| - repo["repo"] == "homebrew" - end - - next if repology_homebrew_repo.blank? - - latest_version = repositories.find { |repo| repo["status"] == "newest" } - - next if latest_version.blank? - - latest_version = latest_version["version"] - srcname = repology_homebrew_repo["srcname"] - package_details = format_package(srcname, latest_version) - packages[srcname] = package_details unless package_details.nil? - - break if limit && packages.size >= limit - end - - packages - end - - def format_package(package_name, latest_version) - formula = formula_data(package_name) - - return if formula.blank? - - formula_name = formula.to_s - tap_full_name = formula.tap&.full_name - current_version = formula.version.to_s - livecheck_response = LivecheckFormula.init(package_name) - pull_requests = GitHub.fetch_pull_requests(formula_name, tap_full_name, state: "open") - - if pull_requests.try(:any?) - pull_requests = pull_requests.map { |pr| "#{pr["title"]} (#{Formatter.url(pr["html_url"])})" }.join(", ") - end - - pull_requests = "none" if pull_requests.blank? - - { - repology_latest_version: latest_version || "not found", - current_formula_version: current_version.to_s, - livecheck_latest_version: livecheck_response[:livecheck_version] || "not found", - open_pull_requests: pull_requests, - } - end - - def formula_data(package_name) - Formula[package_name] - rescue - nil + latest_version["version"] end end