Merge pull request #15763 from razvanazamfirei/bump-add-arch-support

bump: add arch-specific support
This commit is contained in:
Mike McQuaid 2023-08-04 08:10:01 +01:00 committed by GitHub
commit 0e098510c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -1,15 +1,27 @@
# typed: true # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "bump_version_parser"
require "cli/parser" require "cli/parser"
require "livecheck/livecheck" require "livecheck/livecheck"
module Homebrew module Homebrew
module_function module_function
class VersionBumpInfo < T::Struct
const :type, Symbol
const :multiple_versions, T::Boolean
const :version_name, String
const :current_version, BumpVersionParser
const :repology_latest, T.any(String, Version)
const :new_version, BumpVersionParser
const :open_pull_requests, T.nilable(T.any(T::Array[String], String))
const :closed_pull_requests, T.nilable(T.any(T::Array[String], String))
end
sig { returns(CLI::Parser) } sig { returns(CLI::Parser) }
def bump_args def bump_args
Homebrew::CLI::Parser.new do 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
@ -29,6 +41,9 @@ module Homebrew
description: "Limit number of package results returned." description: "Limit number of package results returned."
flag "--start-with=", flag "--start-with=",
description: "Letter or word that the list of package results should alphabetically follow." description: "Letter or word that the list of package results should alphabetically follow."
switch "-f", "--force",
description: "Ignore duplicate open PRs.",
hidden: true
conflicts "--cask", "--formula" conflicts "--cask", "--formula"
conflicts "--no-pull-requests", "--open-pr" conflicts "--no-pull-requests", "--open-pr"
@ -37,6 +52,7 @@ module Homebrew
end end
end end
sig { void }
def bump def bump
args = bump_args.parse args = bump_args.parse
@ -55,8 +71,6 @@ module Homebrew
formula_or_cask.respond_to?(:token) ? formula_or_cask.token : formula_or_cask.name formula_or_cask.respond_to?(:token) ? formula_or_cask.token : formula_or_cask.name
end end
limit = args.limit.to_i if args.limit.present?
unless Utils::Curl.curl_supports_tls13? unless Utils::Curl.curl_supports_tls13?
begin begin
ensure_formula_installed!("curl", reason: "Repology queries") unless HOMEBREW_BREWED_CURL_PATH.exist? ensure_formula_installed!("curl", reason: "Repology queries") unless HOMEBREW_BREWED_CURL_PATH.exist?
@ -66,6 +80,14 @@ module Homebrew
end end
if formulae_and_casks.present? if formulae_and_casks.present?
handle_formula_and_casks(formulae_and_casks, args)
else
handle_api_response(args)
end
end
sig { params(formulae_and_casks: T::Array[T.any(Formula, Cask::Cask)], args: CLI::Args).void }
def handle_formula_and_casks(formulae_and_casks, args)
Livecheck.load_other_tap_strategies(formulae_and_casks) Livecheck.load_other_tap_strategies(formulae_and_casks)
ambiguous_casks = [] ambiguous_casks = []
@ -80,8 +102,8 @@ module Homebrew
ambiguous_names = [] ambiguous_names = []
unless args.full_name? unless args.full_name?
ambiguous_names = ambiguous_names = (formulae_and_casks - ambiguous_casks)
(formulae_and_casks - ambiguous_casks).group_by { |item| Livecheck.package_or_resource_name(item) } .group_by { |item| Livecheck.package_or_resource_name(item) }
.values .values
.select { |items| items.length > 1 } .select { |items| items.length > 1 }
.flatten .flatten
@ -94,7 +116,6 @@ module Homebrew
name = Livecheck.package_or_resource_name(formula_or_cask, full_name: use_full_name) name = Livecheck.package_or_resource_name(formula_or_cask, full_name: use_full_name)
repository = if formula_or_cask.is_a?(Formula) repository = if formula_or_cask.is_a?(Formula)
if formula_or_cask.head_only? if formula_or_cask.head_only?
ohai name
puts "Formula is HEAD-only." puts "Formula is HEAD-only."
next next
end end
@ -118,7 +139,12 @@ module Homebrew
ambiguous_cask: ambiguous_casks.include?(formula_or_cask), ambiguous_cask: ambiguous_casks.include?(formula_or_cask),
) )
end end
else end
sig { params(args: CLI::Args).void }
def handle_api_response(args)
limit = args.limit.to_i if args.limit.present?
api_response = {} api_response = {}
unless args.cask? unless args.cask?
api_response[:formulae] = api_response[:formulae] =
@ -174,13 +200,18 @@ module Homebrew
end end
end end
end end
end
sig {
params(formula_or_cask: T.any(Formula, Cask::Cask)).returns(T.any(Version, String))
}
def livecheck_result(formula_or_cask) def livecheck_result(formula_or_cask)
name = Livecheck.package_or_resource_name(formula_or_cask) name = Livecheck.package_or_resource_name(formula_or_cask)
referenced_formula_or_cask, = referenced_formula_or_cask, = Livecheck.resolve_livecheck_reference(
Livecheck.resolve_livecheck_reference(formula_or_cask, full_name: false, debug: false) formula_or_cask,
full_name: false,
debug: false,
)
# Check skip conditions for a referenced formula/cask # Check skip conditions for a referenced formula/cask
if referenced_formula_or_cask if referenced_formula_or_cask
@ -192,7 +223,12 @@ module Homebrew
) )
end end
skip_info ||= Livecheck::SkipConditions.skip_information(formula_or_cask, full_name: false, verbose: false) skip_info ||= Livecheck::SkipConditions.skip_information(
formula_or_cask,
full_name: false,
verbose: false,
)
if skip_info.present? if skip_info.present?
return "#{skip_info[:status]}#{" - #{skip_info[:messages].join(", ")}" if skip_info[:messages].present?}" return "#{skip_info[:status]}#{" - #{skip_info[:messages].join(", ")}" if skip_info[:messages].present?}"
end end
@ -211,6 +247,14 @@ module Homebrew
"error: #{e}" "error: #{e}"
end end
sig {
params(
formula_or_cask: T.any(Formula, Cask::Cask),
name: String,
state: String,
version: T.nilable(String),
).returns T.nilable(T.any(T::Array[String], String))
}
def retrieve_pull_requests(formula_or_cask, name, state:, version: nil) def retrieve_pull_requests(formula_or_cask, name, state:, version: nil)
tap_remote_repo = formula_or_cask.tap&.remote_repo || formula_or_cask.tap&.full_name tap_remote_repo = formula_or_cask.tap&.remote_repo || formula_or_cask.tap&.full_name
pull_requests = GitHub.fetch_pull_requests(name, tap_remote_repo, state: state, version: version) pull_requests = GitHub.fetch_pull_requests(name, tap_remote_repo, state: state, version: version)
@ -221,55 +265,165 @@ module Homebrew
pull_requests pull_requests
end end
def retrieve_and_display_info_and_open_pr(formula_or_cask, name, repositories, args:, ambiguous_cask: false) sig {
params(
formula_or_cask: T.any(Formula, Cask::Cask),
repositories: T::Array[T.untyped],
args: T.untyped,
name: String,
).returns(VersionBumpInfo)
}
def retrieve_versions_by_arch(formula_or_cask:, repositories:, args:, name:)
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: "]
old_versions = {}
new_versions = {}
repology_latest = repositories.present? ? Repology.latest_version(repositories) : "not found"
# When blocks are absent, arch is not relevant. For consistency, we simulate the arm architecture.
arch_options = is_cask_with_blocks ? OnSystem::ARCH_OPTIONS : [:arm]
arch_options.each do |arch|
SimulateSystem.with arch: arch do
version_key = is_cask_with_blocks ? arch : :general
# We reload the formula/cask here to ensure we're getting the correct version for the current arch
if formula_or_cask.is_a?(Formula) if formula_or_cask.is_a?(Formula)
current_version = T.must(formula_or_cask.stable).version loaded_formula_or_cask = formula_or_cask
type = :formula current_version_value = T.must(loaded_formula_or_cask.stable).version
version_name = "formula version"
else else
current_version = Version.new(formula_or_cask.version) loaded_formula_or_cask = Cask::CaskLoader.load(formula_or_cask.sourcefile_path)
type = :cask current_version_value = Version.new(loaded_formula_or_cask.version)
version_name = "cask version "
end end
livecheck_latest = livecheck_result(formula_or_cask) livecheck_latest = livecheck_result(loaded_formula_or_cask)
repology_latest = if repositories.present? new_version_value = if (livecheck_latest.is_a?(Version) && livecheck_latest >= current_version_value) ||
Repology.latest_version(repositories) current_version_value == "latest"
else
"not found"
end
new_version = if livecheck_latest.is_a?(Version) && livecheck_latest > current_version
livecheck_latest livecheck_latest
elsif repology_latest.is_a?(Version) && elsif repology_latest.is_a?(Version) &&
repology_latest > current_version && repology_latest > current_version_value &&
!formula_or_cask.livecheckable? && !loaded_formula_or_cask.livecheckable? &&
current_version != "latest" current_version_value != "latest"
repology_latest repology_latest
end.presence end.presence
open_pull_requests = if !args.no_pull_requests? && (args.named.present? || new_version) # Store old and new versions
old_versions[version_key] = current_version_value
new_versions[version_key] = new_version_value
end
end
# If arm and intel versions are identical, as it happens with casks where only the checksums differ,
# we consolidate them into a single version.
if old_versions[:arm].present? && old_versions[:arm] == old_versions[:intel]
old_versions = { general: old_versions[:arm] }
end
if new_versions[:arm].present? && new_versions[:arm] == new_versions[:intel]
new_versions = { general: new_versions[:arm] }
end
multiple_versions = old_versions.values_at(:arm, :intel).all?(&:present?) ||
new_versions.values_at(:arm, :intel).all?(&:present?)
current_version = BumpVersionParser.new(general: old_versions[:general],
arm: old_versions[:arm],
intel: old_versions[:intel])
begin
new_version = BumpVersionParser.new(general: new_versions[:general],
arm: new_versions[:arm],
intel: new_versions[:intel])
rescue
# When livecheck fails, we fail gracefully. Otherwise VersionParser will
# raise a usage error
new_version = BumpVersionParser.new(general: "unable to get versions")
end
# We use the arm version for the pull request version. This is consistent
# with the behavior of bump-cask-pr.
pull_request_version = if multiple_versions && new_version.general != "unable to get versions"
new_version.arm.to_s
else
new_version.general.to_s
end
open_pull_requests = if !args.no_pull_requests? && (args.named.present? || new_version.present?)
retrieve_pull_requests(formula_or_cask, name, state: "open") retrieve_pull_requests(formula_or_cask, name, state: "open")
end.presence end.presence
closed_pull_requests = if !args.no_pull_requests? && !open_pull_requests && new_version.present? closed_pull_requests = if !args.no_pull_requests? && open_pull_requests.blank? && new_version.present?
# if we haven't already found open requests, try for an exact match across closed requests retrieve_pull_requests(formula_or_cask, name, state: "closed", version: pull_request_version)
retrieve_pull_requests(formula_or_cask, name, state: "closed", version: new_version)
end.presence end.presence
VersionBumpInfo.new(
type: type,
multiple_versions: multiple_versions,
version_name: version_name,
current_version: current_version,
repology_latest: repology_latest,
new_version: new_version,
open_pull_requests: open_pull_requests,
closed_pull_requests: closed_pull_requests,
)
end
sig {
params(
formula_or_cask: T.any(Formula, Cask::Cask),
name: String,
repositories: T::Array[T.untyped],
args: T.untyped,
ambiguous_cask: T::Boolean,
).void
}
def retrieve_and_display_info_and_open_pr(formula_or_cask, name, repositories, args:, ambiguous_cask: false)
version_info = retrieve_versions_by_arch(formula_or_cask: formula_or_cask,
repositories: repositories,
args: args,
name: name)
current_version = version_info.current_version
new_version = version_info.new_version
repology_latest = version_info.repology_latest
# Check if all versions are equal
versions_equal = [:arm, :intel, :general].all? do |key|
current_version.send(key) == new_version.send(key)
end
title_name = ambiguous_cask ? "#{name} (cask)" : name title_name = ambiguous_cask ? "#{name} (cask)" : name
title = if current_version == repology_latest && title = if (repology_latest == current_version.general || !repology_latest.is_a?(Version)) && versions_equal
current_version == livecheck_latest
"#{title_name} #{Tty.green}is up to date!#{Tty.reset}" "#{title_name} #{Tty.green}is up to date!#{Tty.reset}"
else else
title_name title_name
end end
# Conditionally format output based on type of formula_or_cask
current_versions = if version_info.multiple_versions
"arm: #{current_version.arm}
intel: #{current_version.intel}"
else
current_version.general
end
new_versions = if version_info.multiple_versions && new_version.arm && new_version.intel
"arm: #{new_version.arm}
intel: #{new_version.intel}"
else
new_version.general
end
version_label = version_info.version_name
open_pull_requests = version_info.open_pull_requests.presence
closed_pull_requests = version_info.closed_pull_requests.presence
ohai title ohai title
puts <<~EOS puts <<~EOS
Current #{version_name}: #{current_version} Current #{version_label} #{current_versions}
Latest livecheck version: #{livecheck_latest} Latest livecheck version: #{new_versions}
Latest Repology version: #{repology_latest} Latest Repology version: #{repology_latest}
Open pull requests: #{open_pull_requests || "none"} Open pull requests: #{open_pull_requests || "none"}
Closed pull requests: #{closed_pull_requests || "none"} Closed pull requests: #{closed_pull_requests || "none"}
@ -278,17 +432,33 @@ module Homebrew
return unless args.open_pr? return unless args.open_pr?
if repology_latest.is_a?(Version) && if repology_latest.is_a?(Version) &&
repology_latest > current_version && repology_latest > current_version.general &&
repology_latest > livecheck_latest && repology_latest > new_version.general &&
formula_or_cask.livecheckable? formula_or_cask.livecheckable?
puts "#{title_name} was not bumped to the Repology version because it's livecheckable." puts "#{title_name} was not bumped to the Repology version because it's livecheckable."
end end
if new_version.blank? || versions_equal ||
(!new_version.general.is_a?(Version) && !version_info.multiple_versions)
return
end
return unless new_version return if !args.force? && (open_pull_requests.present? || closed_pull_requests.present?)
return if open_pull_requests
return if closed_pull_requests
system HOMEBREW_BREW_FILE, "bump-#{type}-pr", "--no-browse", version_args = if version_info.multiple_versions
"--message=Created by `brew bump`", "--version=#{new_version}", name %W[--version-arm=#{new_version.arm} --version-intel=#{new_version.intel}]
else
"--version=#{new_version.general}"
end
bump_cask_pr_args = [
"bump-#{version_info.type}-pr",
name,
*version_args,
"--no-browse",
"--message=Created by `brew bump`",
]
bump_cask_pr_args << "--force" if args.force?
system HOMEBREW_BREW_FILE, *bump_cask_pr_args
end end
end end