Merge pull request #13613 from mohammadzainabbas/mohammad

Augment `brew livecheck` with a `--resources` option to check resources
This commit is contained in:
Nanda H Krishna 2022-10-02 13:31:17 -04:00 committed by GitHub
commit 639e8eb237
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 441 additions and 114 deletions

View File

@ -30,6 +30,9 @@ module Homebrew
sig { returns(T::Boolean) }
def newer_only?; end
sig { returns(T::Boolean) }
def resources?; end
sig { returns(T::Boolean) }
def full_name?; end

View File

@ -72,7 +72,8 @@ module Homebrew
ambiguous_casks = []
if !args.formula? && !args.cask?
ambiguous_casks = formulae_and_casks.group_by { |item| Livecheck.formula_or_cask_name(item, full_name: true) }
ambiguous_casks = formulae_and_casks \
.group_by { |item| Livecheck.package_or_resource_name(item, full_name: true) }
.values
.select { |items| items.length > 1 }
.flatten
@ -82,7 +83,7 @@ module Homebrew
ambiguous_names = []
unless args.full_name?
ambiguous_names =
(formulae_and_casks - ambiguous_casks).group_by { |item| Livecheck.formula_or_cask_name(item) }
(formulae_and_casks - ambiguous_casks).group_by { |item| Livecheck.package_or_resource_name(item) }
.values
.select { |items| items.length > 1 }
.flatten
@ -92,7 +93,7 @@ module Homebrew
puts if i.positive?
use_full_name = args.full_name? || ambiguous_names.include?(formula_or_cask)
name = Livecheck.formula_or_cask_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)
if formula_or_cask.head_only?
ohai name
@ -157,7 +158,7 @@ module Homebrew
rescue
next
end
name = Livecheck.formula_or_cask_name(formula_or_cask)
name = Livecheck.package_or_resource_name(formula_or_cask)
ambiguous_cask = begin
formula_or_cask.is_a?(Cask::Cask) && !args.cask? && Formula[name]
rescue FormulaUnavailableError
@ -178,7 +179,7 @@ module Homebrew
end
def livecheck_result(formula_or_cask)
name = Livecheck.formula_or_cask_name(formula_or_cask)
name = Livecheck.package_or_resource_name(formula_or_cask)
referenced_formula_or_cask, =
Livecheck.resolve_livecheck_reference(formula_or_cask, full_name: false, debug: false)

View File

@ -37,6 +37,8 @@ module Homebrew
description: "Show the latest version only if it's newer than the formula/cask."
switch "--json",
description: "Output information in JSON format."
switch "-r", "--resources",
description: "Also check resources for formulae."
switch "-q", "--quiet",
description: "Suppress warnings, don't print a progress bar for JSON output."
switch "--formula", "--formulae",
@ -101,6 +103,7 @@ module Homebrew
else
raise UsageError, "A watchlist file is required when no arguments are given."
end
formulae_and_casks_to_check = formulae_and_casks_to_check.sort_by do |formula_or_cask|
formula_or_cask.respond_to?(:token) ? formula_or_cask.token : formula_or_cask.name
end
@ -111,6 +114,7 @@ module Homebrew
json: args.json?,
full_name: args.full_name?,
handle_name_conflict: !args.formula? && !args.cask?,
check_resources: args.resources?,
newer_only: args.newer_only?,
quiet: args.quiet?,
debug: args.debug?,

View File

@ -126,11 +126,11 @@ module Homebrew
if debug
# Print the chain of references for debugging
puts "Reference Chain:"
puts formula_or_cask_name(first_formula_or_cask, full_name: full_name)
puts package_or_resource_name(first_formula_or_cask, full_name: full_name)
references << referenced_formula_or_cask
references.each do |ref_formula_or_cask|
puts formula_or_cask_name(ref_formula_or_cask, full_name: full_name)
puts package_or_resource_name(ref_formula_or_cask, full_name: full_name)
end
end
@ -157,11 +157,14 @@ module Homebrew
# Executes the livecheck logic for each formula/cask in the
# `formulae_and_casks_to_check` array and prints the results.
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
sig {
params(
formulae_and_casks_to_check: T::Array[T.any(Formula, Cask::Cask)],
full_name: T::Boolean,
handle_name_conflict: T::Boolean,
check_resources: T::Boolean,
json: T::Boolean,
newer_only: T::Boolean,
debug: T::Boolean,
@ -171,14 +174,15 @@ module Homebrew
}
def run_checks(
formulae_and_casks_to_check,
full_name: false, handle_name_conflict: false, json: false, newer_only: false,
full_name: false, handle_name_conflict: false, check_resources: false, json: false, newer_only: false,
debug: false, quiet: false, verbose: false
)
load_other_tap_strategies(formulae_and_casks_to_check)
ambiguous_casks = []
if handle_name_conflict
ambiguous_casks = formulae_and_casks_to_check.group_by { |item| formula_or_cask_name(item, full_name: true) }
ambiguous_casks = formulae_and_casks_to_check \
.group_by { |item| package_or_resource_name(item, full_name: true) }
.values
.select { |items| items.length > 1 }
.flatten
@ -188,7 +192,7 @@ module Homebrew
ambiguous_names = []
unless full_name
ambiguous_names =
(formulae_and_casks_to_check - ambiguous_casks).group_by { |item| formula_or_cask_name(item) }
(formulae_and_casks_to_check - ambiguous_casks).group_by { |item| package_or_resource_name(item) }
.values
.select { |items| items.length > 1 }
.flatten
@ -218,7 +222,7 @@ module Homebrew
cask = formula_or_cask if formula_or_cask.is_a?(Cask::Cask)
use_full_name = full_name || ambiguous_names.include?(formula_or_cask)
name = formula_or_cask_name(formula_or_cask, full_name: use_full_name)
name = package_or_resource_name(formula_or_cask, full_name: use_full_name)
referenced_formula_or_cask, livecheck_references =
resolve_livecheck_reference(formula_or_cask, full_name: use_full_name, debug: debug)
@ -282,6 +286,30 @@ module Homebrew
version_info[:latest] if version_info.present?
end
check_for_resources = check_resources && formula_or_cask.is_a?(Formula) && formula_or_cask.resources.present?
if check_for_resources
resource_version_info = formula_or_cask.resources.map do |resource|
res_skip_info ||= SkipConditions.skip_information(resource, verbose: verbose)
if res_skip_info.present?
res_skip_info
else
res_version_info = resource_version(
resource,
json: json,
debug: debug,
quiet: quiet,
verbose: verbose,
)
if res_version_info.empty?
status_hash(resource, "error", ["Unable to get versions"], verbose: verbose)
else
res_version_info
end
end
end.compact_blank
Homebrew.failed = true if resource_version_info.any? { |info| info[:status] == "error" }
end
if latest.blank?
no_versions_msg = "Unable to get versions"
raise Livecheck::Error, no_versions_msg unless json
@ -289,7 +317,14 @@ module Homebrew
next version_info if version_info.is_a?(Hash) && version_info[:status] && version_info[:messages]
next status_hash(formula_or_cask, "error", [no_versions_msg], full_name: use_full_name, verbose: verbose)
latest_info = status_hash(formula_or_cask, "error", [no_versions_msg], full_name: use_full_name,
verbose: verbose)
if check_for_resources
resource_version_info.map! { |r| r.except!(:meta) } unless verbose
latest_info[:resources] = resource_version_info
end
next latest_info
end
if (m = latest.to_s.match(/(.*)-release$/)) && !current.to_s.match(/.*-release$/)
@ -324,6 +359,8 @@ module Homebrew
info[:meta][:head_only] = true if formula&.head_only?
info[:meta].merge!(version_info[:meta]) if version_info.present? && version_info.key?(:meta)
info[:resources] = resource_version_info if check_for_resources
next if newer_only && !info[:version][:outdated]
has_a_newer_upstream_version ||= true
@ -331,10 +368,12 @@ module Homebrew
if json
progress&.increment
info.except!(:meta) unless verbose
resource_version_info.map! { |r| r.except!(:meta) } if check_for_resources && !verbose
next info
end
puts if debug
print_latest_version(info, verbose: verbose, ambiguous_cask: ambiguous_casks.include?(formula_or_cask))
print_resources_info(resource_version_info, verbose: verbose) if check_for_resources
nil
rescue => e
Homebrew.failed = true
@ -344,11 +383,12 @@ module Homebrew
progress&.increment
status_hash(formula_or_cask, "error", [e.to_s], full_name: use_full_name, verbose: verbose) unless quiet
elsif !quiet
name = formula_or_cask_name(formula_or_cask, full_name: use_full_name)
name = package_or_resource_name(formula_or_cask, full_name: use_full_name)
name += " (cask)" if ambiguous_casks.include?(formula_or_cask)
onoe "#{Tty.blue}#{name}#{Tty.reset}: #{e}"
$stderr.puts e.backtrace if debug && !e.is_a?(Livecheck::Error)
print_resources_info(resource_version_info, verbose: verbose) if check_for_resources
nil
end
end
@ -367,16 +407,20 @@ module Homebrew
puts JSON.pretty_generate(formulae_checked.compact)
end
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/PerceivedComplexity
sig { params(formula_or_cask: T.any(Formula, Cask::Cask), full_name: T::Boolean).returns(String) }
def formula_or_cask_name(formula_or_cask, full_name: false)
case formula_or_cask
sig { params(package_or_resource: T.any(Formula, Cask::Cask, Resource), full_name: T::Boolean).returns(String) }
def package_or_resource_name(package_or_resource, full_name: false)
case package_or_resource
when Formula
formula_name(formula_or_cask, full_name: full_name)
formula_name(package_or_resource, full_name: full_name)
when Cask::Cask
cask_name(formula_or_cask, full_name: full_name)
cask_name(package_or_resource, full_name: full_name)
when Resource
package_or_resource.name
else
T.absurd(formula_or_cask)
T.absurd(package_or_resource)
end
end
@ -396,40 +440,44 @@ module Homebrew
sig {
params(
formula_or_cask: T.any(Formula, Cask::Cask),
package_or_resource: T.any(Formula, Cask::Cask, Resource),
status_str: String,
messages: T.nilable(T::Array[String]),
full_name: T::Boolean,
verbose: T::Boolean,
).returns(Hash)
}
def status_hash(formula_or_cask, status_str, messages = nil, full_name: false, verbose: false)
formula = formula_or_cask if formula_or_cask.is_a?(Formula)
cask = formula_or_cask if formula_or_cask.is_a?(Cask::Cask)
def status_hash(package_or_resource, status_str, messages = nil, full_name: false, verbose: false)
formula = package_or_resource if package_or_resource.is_a?(Formula)
cask = package_or_resource if package_or_resource.is_a?(Cask::Cask)
resource = package_or_resource if package_or_resource.is_a?(Resource)
status_hash = {}
if formula
status_hash[:formula] = formula_name(formula, full_name: full_name)
elsif cask
status_hash[:cask] = cask_name(formula_or_cask, full_name: full_name)
status_hash[:cask] = cask_name(cask, full_name: full_name)
elsif resource
status_hash[:resource] = resource.name
end
status_hash[:status] = status_str
status_hash[:messages] = messages if messages.is_a?(Array)
status_hash[:meta] = {
livecheckable: formula_or_cask.livecheckable?,
livecheckable: package_or_resource.livecheckable?,
}
status_hash[:meta][:head_only] = true if formula&.head_only?
status_hash
end
# Formats and prints the livecheck result for a formula.
# Formats and prints the livecheck result for a formula/cask/resource.
sig { params(info: Hash, verbose: T::Boolean, ambiguous_cask: T::Boolean).void }
def print_latest_version(info, verbose:, ambiguous_cask: false)
formula_or_cask_s = "#{Tty.blue}#{info[:formula] || info[:cask]}#{Tty.reset}"
formula_or_cask_s += " (cask)" if ambiguous_cask
formula_or_cask_s += " (guessed)" if !info[:meta][:livecheckable] && verbose
def print_latest_version(info, verbose: false, ambiguous_cask: false)
package_or_resource_s = info[:resource].present? ? " " : ""
package_or_resource_s += "#{Tty.blue}#{info[:formula] || info[:cask] || info[:resource]}#{Tty.reset}"
package_or_resource_s += " (cask)" if ambiguous_cask
package_or_resource_s += " (guessed)" if verbose && !info[:meta][:livecheckable]
current_s = if info[:version][:newer_than_upstream]
"#{Tty.red}#{info[:version][:current]}#{Tty.reset}"
@ -443,47 +491,61 @@ module Homebrew
info[:version][:latest]
end
puts "#{formula_or_cask_s}: #{current_s} ==> #{latest_s}"
puts "#{package_or_resource_s}: #{current_s} ==> #{latest_s}"
end
# Prints the livecheck result for the resources of a given Formula.
sig { params(info: T::Array[Hash], verbose: T::Boolean).void }
def print_resources_info(info, verbose: false)
info.each do |r_info|
if r_info[:status] && r_info[:messages]
SkipConditions.print_skip_information(r_info)
else
print_latest_version(r_info, verbose: verbose)
end
end
end
sig {
params(
livecheck_url: T.any(String, Symbol),
formula_or_cask: T.any(Formula, Cask::Cask),
package_or_resource: T.any(Formula, Cask::Cask, Resource),
).returns(T.nilable(String))
}
def livecheck_url_to_string(livecheck_url, formula_or_cask)
def livecheck_url_to_string(livecheck_url, package_or_resource)
case livecheck_url
when String
livecheck_url
when :url
formula_or_cask.url&.to_s if formula_or_cask.is_a?(Cask::Cask)
package_or_resource.url&.to_s if package_or_resource.is_a?(Cask::Cask) || package_or_resource.is_a?(Resource)
when :head, :stable
formula_or_cask.send(livecheck_url)&.url if formula_or_cask.is_a?(Formula)
package_or_resource.send(livecheck_url)&.url if package_or_resource.is_a?(Formula)
when :homepage
formula_or_cask.homepage
package_or_resource.homepage unless package_or_resource.is_a?(Resource)
end
end
# Returns an Array containing the formula/cask URLs that can be used by livecheck.
sig { params(formula_or_cask: T.any(Formula, Cask::Cask)).returns(T::Array[String]) }
def checkable_urls(formula_or_cask)
# Returns an Array containing the formula/cask/resource URLs that can be used by livecheck.
sig { params(package_or_resource: T.any(Formula, Cask::Cask, Resource)).returns(T::Array[String]) }
def checkable_urls(package_or_resource)
urls = []
case formula_or_cask
case package_or_resource
when Formula
if formula_or_cask.stable
urls << formula_or_cask.stable.url
urls.concat(formula_or_cask.stable.mirrors)
if package_or_resource.stable
urls << package_or_resource.stable.url
urls.concat(package_or_resource.stable.mirrors)
end
urls << formula_or_cask.head.url if formula_or_cask.head
urls << formula_or_cask.homepage if formula_or_cask.homepage
urls << package_or_resource.head.url if package_or_resource.head
urls << package_or_resource.homepage if package_or_resource.homepage
when Cask::Cask
urls << formula_or_cask.appcast.to_s if formula_or_cask.appcast
urls << formula_or_cask.url.to_s if formula_or_cask.url
urls << formula_or_cask.homepage if formula_or_cask.homepage
urls << package_or_resource.appcast.to_s if package_or_resource.appcast
urls << package_or_resource.url.to_s if package_or_resource.url
urls << package_or_resource.homepage if package_or_resource.homepage
when Resource
urls << package_or_resource.url
else
T.absurd(formula_or_cask)
T.absurd(package_or_resource)
end
urls.compact.uniq
@ -561,7 +623,7 @@ module Homebrew
homebrew_curl_root_domains.include?(url_root_domain)
end
# Identifies the latest version of the formula and returns a Hash containing
# Identifies the latest version of the formula/cask and returns a Hash containing
# the version information. Returns nil if a latest version couldn't be found.
# rubocop:disable Metrics/CyclomaticComplexity
sig {
@ -713,7 +775,6 @@ module Homebrew
version.to_s.include?(rejection)
end
end
next if match_version_map.blank?
if debug
@ -770,6 +831,195 @@ module Homebrew
nil
end
# rubocop:enable Metrics/CyclomaticComplexity
# Identifies the latest version of a resource and returns a Hash containing the
# version information. Returns nil if a latest version couldn't be found.
sig {
params(
resource: Resource,
json: T::Boolean,
debug: T::Boolean,
quiet: T::Boolean,
verbose: T::Boolean,
).returns(Hash)
}
def resource_version(
resource,
json: false,
debug: false,
quiet: false,
verbose: false
)
has_livecheckable = resource.livecheckable?
if debug
puts "\n\n"
puts "Resource: #{resource.name}"
puts "Livecheckable?: #{has_livecheckable ? "Yes" : "No"}"
end
resource_version_info = {}
livecheck = resource.livecheck
livecheck_url = livecheck.url
livecheck_regex = livecheck.regex
livecheck_strategy = livecheck.strategy
livecheck_strategy_block = livecheck.strategy_block
livecheck_url_string = livecheck_url_to_string(livecheck_url, resource)
urls = [livecheck_url_string] if livecheck_url_string
urls ||= checkable_urls(resource)
checked_urls = []
# rubocop:disable Metrics/BlockLength
urls.each_with_index do |original_url, i|
# Only preprocess the URL when it's appropriate
url = if STRATEGY_SYMBOLS_TO_SKIP_PREPROCESS_URL.include?(livecheck_strategy)
original_url
else
preprocess_url(original_url)
end
next if checked_urls.include?(url)
strategies = Strategy.from_url(
url,
livecheck_strategy: livecheck_strategy,
url_provided: livecheck_url.present?,
regex_provided: livecheck_regex.present?,
block_provided: livecheck_strategy_block.present?,
)
strategy = Strategy.from_symbol(livecheck_strategy) || strategies.first
strategy_name = livecheck_strategy_names[strategy]
if debug
puts
if livecheck_url.is_a?(Symbol)
# This assumes the URL symbol will fit within the available space
puts "URL (#{livecheck_url}):".ljust(18, " ") + original_url
else
puts "URL: #{original_url}"
end
puts "URL (processed): #{url}" if url != original_url
if strategies.present? && verbose
puts "Strategies: #{strategies.map { |s| livecheck_strategy_names[s] }.join(", ")}"
end
puts "Strategy: #{strategy.blank? ? "None" : strategy_name}"
puts "Regex: #{livecheck_regex.inspect}" if livecheck_regex.present?
end
if livecheck_strategy.present?
if livecheck_url.blank? && strategy.method(:find_versions).parameters.include?([:keyreq, :url])
odebug "#{strategy_name} strategy requires a URL"
next
elsif livecheck_strategy != :page_match && strategies.exclude?(strategy)
odebug "#{strategy_name} strategy does not apply to this URL"
next
end
end
puts if debug && strategy.blank?
next if strategy.blank?
strategy_data = strategy.find_versions(
url: url,
regex: livecheck_regex,
homebrew_curl: false,
&livecheck_strategy_block
)
match_version_map = strategy_data[:matches]
regex = strategy_data[:regex]
messages = strategy_data[:messages]
checked_urls << url
if messages.is_a?(Array) && match_version_map.blank?
puts messages unless json
next if i + 1 < urls.length
return status_hash(resource, "error", messages, verbose: verbose)
end
if debug
if strategy_data[:url].present? && strategy_data[:url] != url
puts "URL (strategy): #{strategy_data[:url]}"
end
puts "URL (final): #{strategy_data[:final_url]}" if strategy_data[:final_url].present?
if strategy_data[:regex].present? && strategy_data[:regex] != livecheck_regex
puts "Regex (strategy): #{strategy_data[:regex].inspect}"
end
puts "Cached?: Yes" if strategy_data[:cached] == true
end
match_version_map.delete_if do |_match, version|
next true if version.blank?
next false if has_livecheckable
UNSTABLE_VERSION_KEYWORDS.any? do |rejection|
version.to_s.include?(rejection)
end
end
next if match_version_map.blank?
if debug
puts
puts "Matched Versions:"
if verbose
match_version_map.each do |match, version|
puts "#{match} => #{version.inspect}"
end
else
puts match_version_map.values.join(", ")
end
end
res_current = resource.version
res_latest = Version.new(match_version_map.values.max_by { |v| LivecheckVersion.create(resource, v) })
return status_hash(resource, "error", ["Unable to get versions"], verbose: verbose) if res_latest.blank?
is_outdated = res_current < res_latest
is_newer_than_upstream = res_current > res_latest
resource_version_info = {
resource: resource.name,
version: {
current: res_current.to_s,
latest: res_latest.to_s,
outdated: is_outdated,
newer_than_upstream: is_newer_than_upstream,
},
}
resource_version_info[:meta] = { livecheckable: has_livecheckable, url: {} }
if livecheck_url.is_a?(Symbol) && livecheck_url_string
resource_version_info[:meta][:url][:symbol] = livecheck_url
end
resource_version_info[:meta][:url][:original] = original_url
resource_version_info[:meta][:url][:processed] = url if url != original_url
if strategy_data[:url].present? && strategy_data[:url] != url
resource_version_info[:meta][:url][:strategy] = strategy_data[:url]
end
resource_version_info[:meta][:url][:final] = strategy_data[:final_url] if strategy_data[:final_url]
resource_version_info[:meta][:strategy] = strategy.present? ? strategy_name : nil
if strategies.present?
resource_version_info[:meta][:strategies] = strategies.map { |s| livecheck_strategy_names[s] }
end
resource_version_info[:meta][:regex] = regex.inspect if regex.present?
resource_version_info[:meta][:cached] = true if strategy_data[:cached] == true
rescue => e
Homebrew.failed = true
if json
status_hash(resource, "error", [e.to_s], verbose: verbose)
elsif !quiet
onoe "#{Tty.blue}#{resource.name}#{Tty.reset}: #{e}"
$stderr.puts e.backtrace if debug && !e.is_a?(Livecheck::Error)
nil
end
end
# rubocop:enable Metrics/BlockLength
resource_version_info
end
end
# rubocop:enable Metrics/ModuleLength
end

View File

@ -11,15 +11,17 @@ module Homebrew
include Comparable
sig { params(formula_or_cask: T.any(Formula, Cask::Cask), version: Version).returns(LivecheckVersion) }
def self.create(formula_or_cask, version)
versions = case formula_or_cask
when Formula
sig {
params(package_or_resource: T.any(Formula, Cask::Cask, Resource), version: Version).returns(LivecheckVersion)
}
def self.create(package_or_resource, version)
versions = case package_or_resource
when Formula, Resource
[version]
when Cask::Cask
version.to_s.split(/[,:]/).map { |s| Version.new(s) }
else
T.absurd(formula_or_cask)
T.absurd(package_or_resource)
end
new(versions)
end

View File

@ -6,7 +6,7 @@ require "livecheck/livecheck"
module Homebrew
module Livecheck
# The `Livecheck::SkipConditions` module primarily contains methods that
# check for various formula/cask conditions where a check should be skipped.
# check for various formula/cask/resource conditions where a check should be skipped.
#
# @api private
module SkipConditions
@ -16,14 +16,14 @@ module Homebrew
sig {
params(
formula_or_cask: T.any(Formula, Cask::Cask),
package_or_resource: T.any(Formula, Cask::Cask, Resource),
livecheckable: T::Boolean,
full_name: T::Boolean,
verbose: T::Boolean,
).returns(Hash)
}
def formula_or_cask_skip(formula_or_cask, livecheckable, full_name: false, verbose: false)
formula = formula_or_cask if formula_or_cask.is_a?(Formula)
def package_or_resource_skip(package_or_resource, livecheckable, full_name: false, verbose: false)
formula = package_or_resource if package_or_resource.is_a?(Formula)
if (stable_url = formula&.stable&.url)
stable_is_gist = stable_url.match?(%r{https?://gist\.github(?:usercontent)?\.com/}i)
@ -33,8 +33,8 @@ module Homebrew
stable_from_internet_archive = stable_url.match?(%r{https?://web\.archive\.org/}i)
end
skip_message = if formula_or_cask.livecheck.skip_msg.present?
formula_or_cask.livecheck.skip_msg
skip_message = if package_or_resource.livecheck.skip_msg.present?
package_or_resource.livecheck.skip_msg
elsif !livecheckable
if stable_from_google_code_archive
"Stable URL is from Google Code Archive"
@ -45,10 +45,10 @@ module Homebrew
end
end
return {} if !formula_or_cask.livecheck.skip? && skip_message.blank?
return {} if !package_or_resource.livecheck.skip? && skip_message.blank?
skip_messages = skip_message ? [skip_message] : nil
Livecheck.status_hash(formula_or_cask, "skipped", skip_messages, full_name: full_name, verbose: verbose)
Livecheck.status_hash(package_or_resource, "skipped", skip_messages, full_name: full_name, verbose: verbose)
end
sig {
@ -157,7 +157,7 @@ module Homebrew
# Skip conditions for formulae.
FORMULA_CHECKS = [
:formula_or_cask_skip,
:package_or_resource_skip,
:formula_head_only,
:formula_deprecated,
:formula_disabled,
@ -166,76 +166,85 @@ module Homebrew
# Skip conditions for casks.
CASK_CHECKS = [
:formula_or_cask_skip,
:package_or_resource_skip,
:cask_discontinued,
:cask_version_latest,
:cask_url_unversioned,
].freeze
# If a formula/cask should be skipped, we return a hash from
# Skip conditions for resources.
RESOURCE_CHECKS = [
:package_or_resource_skip,
].freeze
# If a formula/cask/resource should be skipped, we return a hash from
# `Livecheck#status_hash`, which contains a `status` type and sometimes
# error `messages`.
sig {
params(
formula_or_cask: T.any(Formula, Cask::Cask),
package_or_resource: T.any(Formula, Cask::Cask, Resource),
full_name: T::Boolean,
verbose: T::Boolean,
).returns(Hash)
}
def skip_information(formula_or_cask, full_name: false, verbose: false)
livecheckable = formula_or_cask.livecheckable?
def skip_information(package_or_resource, full_name: false, verbose: false)
livecheckable = package_or_resource.livecheckable?
checks = case formula_or_cask
checks = case package_or_resource
when Formula
FORMULA_CHECKS
when Cask::Cask
CASK_CHECKS
when Resource
RESOURCE_CHECKS
end
return {} unless checks
checks.each do |method_name|
skip_hash = send(method_name, formula_or_cask, livecheckable, full_name: full_name, verbose: verbose)
skip_hash = send(method_name, package_or_resource, livecheckable, full_name: full_name, verbose: verbose)
return skip_hash if skip_hash.present?
end
{}
end
# Skip conditions for formulae/casks referenced in a `livecheck` block
# Skip conditions for formulae/casks/resources referenced in a `livecheck` block
# are treated differently than normal. We only respect certain skip
# conditions (returning the related hash) and others are treated as
# errors.
sig {
params(
livecheck_formula_or_cask: T.any(Formula, Cask::Cask),
original_formula_or_cask_name: String,
livecheck_package_or_resource: T.any(Formula, Cask::Cask, Resource),
original_package_or_resource_name: String,
full_name: T::Boolean,
verbose: T::Boolean,
).returns(T.nilable(Hash))
}
def referenced_skip_information(
livecheck_formula_or_cask,
original_formula_or_cask_name,
livecheck_package_or_resource,
original_package_or_resource_name,
full_name: false,
verbose: false
)
skip_info = SkipConditions.skip_information(
livecheck_formula_or_cask,
livecheck_package_or_resource,
full_name: full_name,
verbose: verbose,
)
return if skip_info.blank?
referenced_name = Livecheck.formula_or_cask_name(livecheck_formula_or_cask, full_name: full_name)
referenced_type = case livecheck_formula_or_cask
referenced_name = Livecheck.package_or_resource_name(livecheck_package_or_resource, full_name: full_name)
referenced_type = case livecheck_package_or_resource
when Formula
:formula
when Cask::Cask
:cask
when Resource
:resource
end
if skip_info[:status] != "error" &&
!(skip_info[:status] == "skipped" && livecheck_formula_or_cask.livecheck.skip?)
!(skip_info[:status] == "skipped" && livecheck_package_or_resource.livecheck.skip?)
error_msg_end = if skip_info[:status] == "skipped"
"automatically skipped"
else
@ -245,7 +254,7 @@ module Homebrew
raise "Referenced #{referenced_type} (#{referenced_name}) is #{error_msg_end}"
end
skip_info[referenced_type] = original_formula_or_cask_name
skip_info[referenced_type] = original_package_or_resource_name
skip_info
end
@ -258,6 +267,8 @@ module Homebrew
skip_hash[:formula]
elsif skip_hash[:cask].is_a?(String)
skip_hash[:cask]
elsif skip_hash[:resource].is_a?(String)
" #{skip_hash[:resource]}"
end
return unless name

View File

@ -11,6 +11,7 @@ describe Homebrew::Livecheck do
let(:homepage_url) { "https://brew.sh" }
let(:livecheck_url) { "https://formulae.brew.sh/api/formula/ruby.json" }
let(:stable_url) { "https://brew.sh/test-0.0.1.tgz" }
let(:resource_url) { "https://brew.sh/foo-1.0.tar.gz" }
let(:f) do
formula("test") do
@ -23,8 +24,20 @@ describe Homebrew::Livecheck do
url "https://formulae.brew.sh/api/formula/ruby.json"
regex(/"stable":"(\d+(?:\.\d+)+)"/i)
end
resource "foo" do
url "https://brew.sh/foo-1.0.tar.gz"
sha256 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
livecheck do
url "https://brew.sh/test/releases"
regex(/foo[._-]v?(\d+(?:\.\d+)+)\.t/i)
end
end
end
end
let(:r) { f.resources.first }
let(:c) do
Cask::CaskLoader.load(+<<-RUBY)
@ -44,15 +57,6 @@ describe Homebrew::Livecheck do
RUBY
end
let(:f_duplicate_urls) do
formula("test_duplicate_urls") do
desc "Test formula with a duplicate URL"
homepage "https://github.com/Homebrew/brew.git"
url "https://brew.sh/test-0.0.1.tgz"
head "https://github.com/Homebrew/brew.git"
end
end
describe "::resolve_livecheck_reference" do
context "when a formula/cask has a livecheck block without formula/cask methods" do
it "returns [nil, []]" do
@ -83,7 +87,7 @@ describe Homebrew::Livecheck do
end
describe "::status_hash" do
it "returns a hash containing the livecheck status" do
it "returns a hash containing the livecheck status for a formula" do
expect(livecheck.status_hash(f, "error", ["Unable to get versions"]))
.to eq({
formula: "test",
@ -94,6 +98,18 @@ describe Homebrew::Livecheck do
},
})
end
it "returns a hash containing the livecheck status for a resource" do
expect(livecheck.status_hash(r, "error", ["Unable to get versions"]))
.to eq({
resource: "foo",
status: "error",
messages: ["Unable to get versions"],
meta: {
livecheckable: true,
},
})
end
end
describe "::livecheck_url_to_string" do
@ -101,14 +117,27 @@ describe Homebrew::Livecheck do
homepage_url_s = homepage_url
stable_url_s = stable_url
head_url_s = head_url
resource_url_s = resource_url
formula("test_livecheck_url") do
desc "Test Livecheck URL formula"
homepage homepage_url_s
url stable_url_s
head head_url_s
resource "foo" do
url resource_url_s
sha256 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
livecheck do
url "https://brew.sh/test/releases"
regex(/foo[._-]v?(\d+(?:\.\d+)+)\.t/i)
end
end
end
end
let(:r_livecheck_url) { f_livecheck_url.resources.first }
let(:c_livecheck_url) do
Cask::CaskLoader.load(+<<-RUBY)
@ -123,30 +152,48 @@ describe Homebrew::Livecheck do
RUBY
end
it "returns a URL string when given a livecheck_url string" do
it "returns a URL string when given a livecheck_url string for a formula" do
expect(livecheck.livecheck_url_to_string(livecheck_url, f_livecheck_url)).to eq(livecheck_url)
end
it "returns a URL string when given a livecheck_url string for a resource" do
expect(livecheck.livecheck_url_to_string(livecheck_url, r_livecheck_url)).to eq(livecheck_url)
end
it "returns a URL symbol when given a valid livecheck_url symbol" do
expect(livecheck.livecheck_url_to_string(:head, f_livecheck_url)).to eq(head_url)
expect(livecheck.livecheck_url_to_string(:homepage, f_livecheck_url)).to eq(homepage_url)
expect(livecheck.livecheck_url_to_string(:homepage, c_livecheck_url)).to eq(homepage_url)
expect(livecheck.livecheck_url_to_string(:stable, f_livecheck_url)).to eq(stable_url)
expect(livecheck.livecheck_url_to_string(:url, c_livecheck_url)).to eq(cask_url)
expect(livecheck.livecheck_url_to_string(:url, r_livecheck_url)).to eq(resource_url)
end
it "returns nil when not given a string or valid symbol" do
expect(livecheck.livecheck_url_to_string(nil, f_livecheck_url)).to be_nil
expect(livecheck.livecheck_url_to_string(nil, c_livecheck_url)).to be_nil
expect(livecheck.livecheck_url_to_string(nil, r_livecheck_url)).to be_nil
expect(livecheck.livecheck_url_to_string(:invalid_symbol, f_livecheck_url)).to be_nil
expect(livecheck.livecheck_url_to_string(:invalid_symbol, c_livecheck_url)).to be_nil
expect(livecheck.livecheck_url_to_string(:invalid_symbol, r_livecheck_url)).to be_nil
end
end
describe "::checkable_urls" do
let(:resource_url) { "https://brew.sh/foo-1.0.tar.gz" }
let(:f_duplicate_urls) do
formula("test_duplicate_urls") do
desc "Test formula with a duplicate URL"
homepage "https://github.com/Homebrew/brew.git"
url "https://brew.sh/test-0.0.1.tgz"
head "https://github.com/Homebrew/brew.git"
end
end
it "returns the list of URLs to check" do
expect(livecheck.checkable_urls(f)).to eq([stable_url, head_url, homepage_url])
expect(livecheck.checkable_urls(c)).to eq([cask_url, homepage_url])
expect(livecheck.checkable_urls(r)).to eq([resource_url])
expect(livecheck.checkable_urls(f_duplicate_urls)).to eq([stable_url, head_url])
end
end

View File

@ -6,6 +6,7 @@ require "livecheck/livecheck_version"
describe Homebrew::Livecheck::LivecheckVersion do
let(:formula) { instance_double(Formula) }
let(:cask) { instance_double(Cask::Cask) }
let(:resource) { instance_double(Resource) }
before do
# Case statements use #=== for case equality purposes
@ -13,6 +14,8 @@ describe Homebrew::Livecheck::LivecheckVersion do
allow(Formula).to receive(:===).with(formula).and_return(true)
allow(Cask::Cask).to receive(:===).and_call_original
allow(Cask::Cask).to receive(:===).with(cask).and_return(true)
allow(Resource).to receive(:===).and_call_original
allow(Resource).to receive(:===).with(resource).and_return(true)
end
specify "::create" do
@ -28,5 +31,11 @@ describe Homebrew::Livecheck::LivecheckVersion do
.to eq ["1.0", "100", "1426778671"]
expect(described_class.create(cask, Version.new("0.17.0,20210111183933,226")).versions)
.to eq ["0.17.0", "20210111183933", "226"]
expect(described_class.create(resource, Version.new("1.1.6")).versions).to eq ["1.1.6"]
expect(described_class.create(resource, Version.new("2.19.0,1.8.0")).versions).to eq ["2.19.0,1.8.0"]
expect(described_class.create(resource, Version.new("1.0,100:1426778671")).versions).to eq ["1.0,100:1426778671"]
expect(described_class.create(resource, Version.new("0.17.0,20210111183933,226")).versions)
.to eq ["0.17.0,20210111183933,226"]
end
end