1005 lines
37 KiB
Ruby
Raw Normal View History

2023-12-12 10:10:18 -08:00
# typed: strict
# frozen_string_literal: true
require "livecheck/constants"
require "livecheck/error"
require "livecheck/livecheck_version"
require "livecheck/skip_conditions"
require "livecheck/strategy"
Livecheck: Use Homebrew curl based on root domain At the moment, `#use_homebrew_curl?` can only be true for a `homepage` or `stable`/cask `url` with `using: :homebrew_curl`. If the checked URL differs from these URLs, livecheck won't use brewed curl. This limitation prevents livecheck from using brewed curl for a `livecheck` block URL that's a string literal (not a symbol for a `#checkable_url` like `:stable`, `:head`, `:url`). `libzip` was the original formula referenced in the related brew issue and it meets this criterion, so it doesn't appear to be handled by the existing `#use_homebrew_curl?` implementation. Additionally, the existing behavior can cause livecheck to unnecessarily use brewed curl for a completely different website (e.g., `cubelib`, `otf2`). For example, if the `stable` URL has `using: :homebrew_curl` and the `livecheck` block has `url :homepage`, livecheck will use brewed curl when checking the `homepage`. If these are completely different domains/servers, it's unlikely that we would need to use brewed curl when checking the `homepage`, so this particular behavior may not be beneficial. This commit reimplements `use_homebrew_curl?` to apply brewed curl when the checked URL's root domain is the same as the root domain of an aforementioned formula/cask URL with `using: :homebrew_curl`. For example, this looser approach would allow a `livecheck` block checking `https://www.example.com/downloads/` to use brewed curl if the `stable` URL was `https://downloads.example.com/example.zip` with `using: :homebrew_curl`. These could be different servers but, based on related formulae, this looseness is necessary for the moment. This approach aims to resolve both issues, allowing brewed curl to be applied to a slightly broader range of URLs (i.e., not limited to just the `#checkable_urls`) while also helping to avoid unnecessarily applying brewed curl when it's less likely to be useful (completely different domains). Neither approach is perfect but this one may be more useful in the interim time. Depending on how this looser approach works in practice, we may want to consider returning to a stricter approach once we have something like `using: :homebrew_curl` in `livecheck` blocks (this is forthcoming). Being explicit in a `livecheck` block is the most reliable approach (i.e., only use brewed curl when needed), so we could favor that and pare down the automated approach to only what's needed to support implicit checks (i.e., with no `livecheck` block). Of course, it's also possible to drop the automated approach entirely and simply require a `livecheck` block in this scenario but we can decide on how to handle this when the time comes.
2022-05-18 16:40:30 -04:00
require "addressable"
module Homebrew
# The {Livecheck} module consists of methods used by the `brew livecheck`
# command. These methods print the requested livecheck information
# for formulae.
module Livecheck
2023-12-12 10:10:18 -08:00
UNSTABLE_VERSION_KEYWORDS = T.let(%w[
alpha
beta
bpo
dev
experimental
prerelease
preview
rc
2023-12-12 10:10:18 -08:00
].freeze, T::Array[String])
2024-08-23 03:15:57 +01:00
private_constant :UNSTABLE_VERSION_KEYWORDS
2023-12-14 15:34:15 +00:00
sig { returns(T::Hash[T::Class[T.anything], String]) }
2024-08-23 03:15:57 +01:00
private_class_method def self.livecheck_strategy_names
2023-12-12 10:10:18 -08:00
return T.must(@livecheck_strategy_names) if defined?(@livecheck_strategy_names)
2020-12-14 14:30:36 +01:00
# Cache demodulized strategy names, to avoid repeating this work
2023-12-14 15:34:15 +00:00
@livecheck_strategy_names = T.let({}, T.nilable(T::Hash[T::Class[T.anything], String]))
Strategy.constants.sort.each do |const_symbol|
constant = Strategy.const_get(const_symbol)
next unless constant.is_a?(Class)
2023-12-12 10:10:18 -08:00
T.must(@livecheck_strategy_names)[constant] = Utils.demodulize(T.must(constant.name))
2020-12-14 14:30:36 +01:00
end
2023-12-12 10:10:18 -08:00
T.must(@livecheck_strategy_names).freeze
2020-12-14 14:30:36 +01:00
end
# Uses `formulae_and_casks_to_check` to identify taps in use other than
# homebrew/core and homebrew/cask and loads strategies from them.
2021-03-04 01:01:56 +05:30
sig { params(formulae_and_casks_to_check: T::Array[T.any(Formula, Cask::Cask)]).void }
2024-08-23 03:15:57 +01:00
def self.load_other_tap_strategies(formulae_and_casks_to_check)
2021-01-11 08:29:34 +05:30
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.core_tap?
next if formula_or_cask.tap.core_cask_tap?
2021-01-11 08:29:34 +05:30
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"].each { require(_1) } if Dir.exist?(tap_strategy_path)
2021-01-11 08:29:34 +05:30
end
end
# Resolve formula/cask references in `livecheck` blocks to a final formula
# or cask.
sig {
params(
formula_or_cask: T.any(Formula, Cask::Cask),
first_formula_or_cask: T.any(Formula, Cask::Cask),
references: T::Array[T.any(Formula, Cask::Cask)],
full_name: T::Boolean,
debug: T::Boolean,
).returns(T.nilable(T::Array[T.untyped]))
}
2024-08-23 03:15:57 +01:00
def self.resolve_livecheck_reference(
formula_or_cask,
first_formula_or_cask = formula_or_cask,
references = [],
full_name: false,
debug: false
)
# Check the livecheck block for a formula or cask reference
livecheck = formula_or_cask.livecheck
livecheck_formula = livecheck.formula
livecheck_cask = livecheck.cask
return [nil, references] if livecheck_formula.blank? && livecheck_cask.blank?
# Load the referenced formula or cask
referenced_formula_or_cask = Homebrew.with_no_api_env do
if livecheck_formula
Formulary.factory(livecheck_formula)
elsif livecheck_cask
Cask::CaskLoader.load(livecheck_cask)
end
end
# Error if a `livecheck` block references a formula/cask that was already
# referenced (or itself)
if referenced_formula_or_cask == first_formula_or_cask ||
referenced_formula_or_cask == formula_or_cask ||
references.include?(referenced_formula_or_cask)
if debug
# Print the chain of references for debugging
puts "Reference Chain:"
2024-03-07 16:20:20 +00:00
puts package_or_resource_name(first_formula_or_cask, full_name:)
references << referenced_formula_or_cask
references.each do |ref_formula_or_cask|
2024-03-07 16:20:20 +00:00
puts package_or_resource_name(ref_formula_or_cask, full_name:)
end
end
raise "Circular formula/cask reference encountered"
end
references << referenced_formula_or_cask
# Check the referenced formula/cask for a reference
next_referenced_formula_or_cask, next_references = resolve_livecheck_reference(
referenced_formula_or_cask,
first_formula_or_cask,
references,
2024-03-07 16:20:20 +00:00
full_name:,
debug:,
)
# Returning references along with the final referenced formula/cask
# allows us to print the chain of references in the debug output
[
next_referenced_formula_or_cask || referenced_formula_or_cask,
next_references,
]
end
2020-09-02 12:24:21 -07:00
# Executes the livecheck logic for each formula/cask in the
# `formulae_and_casks_to_check` array and prints the results.
sig {
2021-01-04 13:41:19 -08:00
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,
extract_plist: T::Boolean,
debug: T::Boolean,
quiet: T::Boolean,
verbose: T::Boolean,
2021-01-04 13:41:19 -08:00
).void
}
2024-08-23 03:15:57 +01:00
def self.run_checks(
2020-12-14 14:30:36 +01:00
formulae_and_casks_to_check,
full_name: false, handle_name_conflict: false, check_resources: false, json: false, newer_only: false,
extract_plist: false, debug: false, quiet: false, verbose: false
2020-12-14 14:30:36 +01:00
)
2021-01-11 08:29:34 +05:30
load_other_tap_strategies(formulae_and_casks_to_check)
2021-03-04 01:01:56 +05:30
ambiguous_casks = []
if handle_name_conflict
2023-04-04 15:37:24 +01:00
ambiguous_casks = formulae_and_casks_to_check
2022-09-14 04:54:52 +02:00
.group_by { |item| package_or_resource_name(item, full_name: true) }
.values
.select { |items| items.length > 1 }
.flatten
.select { |item| item.is_a?(Cask::Cask) }
2021-03-04 01:01:56 +05:30
end
ambiguous_names = []
unless full_name
ambiguous_names =
2022-09-14 04:54:52 +02:00
(formulae_and_casks_to_check - ambiguous_casks).group_by { |item| package_or_resource_name(item) }
.values
.select { |items| items.length > 1 }
.flatten
end
2021-01-04 13:41:19 -08:00
has_a_newer_upstream_version = T.let(false, T::Boolean)
formulae_and_casks_total = formulae_and_casks_to_check.count
if json && !quiet && $stderr.tty?
Tty.with($stderr) do |stderr|
stderr.puts Formatter.headline("Running checks", color: :blue)
end
require "ruby-progressbar"
progress = ProgressBar.create(
total: formulae_and_casks_total,
progress_mark: "#",
remainder_mark: ".",
format: " %t: [%B] %c/%C ",
output: $stderr,
)
end
# Allow ExtractPlist strategy if only one formula/cask is being checked.
extract_plist = true if formulae_and_casks_total == 1
formulae_checked = formulae_and_casks_to_check.map.with_index do |formula_or_cask, i|
2020-09-02 12:24:21 -07:00
formula = formula_or_cask if formula_or_cask.is_a?(Formula)
cask = formula_or_cask if formula_or_cask.is_a?(Cask::Cask)
use_full_name = full_name || ambiguous_names.include?(formula_or_cask)
2022-09-14 04:54:52 +02:00
name = package_or_resource_name(formula_or_cask, full_name: use_full_name)
2020-09-02 12:24:21 -07:00
referenced_formula_or_cask, livecheck_references =
2024-03-07 16:20:20 +00:00
resolve_livecheck_reference(formula_or_cask, full_name: use_full_name, debug:)
2020-12-14 14:30:36 +01:00
if debug && i.positive?
puts <<~EOS
----------
EOS
elsif debug
puts
end
# Check skip conditions for a referenced formula/cask
if referenced_formula_or_cask
skip_info = SkipConditions.referenced_skip_information(
referenced_formula_or_cask,
name,
full_name: use_full_name,
2024-03-07 16:20:20 +00:00
verbose:,
extract_plist:,
)
end
skip_info ||= SkipConditions.skip_information(
formula_or_cask,
full_name: use_full_name,
verbose:,
extract_plist:,
)
if skip_info.present?
next skip_info if json && !newer_only
SkipConditions.print_skip_information(skip_info) if !newer_only && !quiet
next
end
formula&.head&.downloader&.quiet!
# Use the `stable` version for comparison except for installed
# head-only formulae. A formula with `stable` and `head` that's
# installed using `--head` will still use the `stable` version for
# comparison.
2020-12-11 18:58:01 +01:00
current = if formula
2020-12-11 16:24:49 +01:00
if formula.head_only?
Version.new(formula.any_installed_version.version.commit)
2020-12-11 16:24:49 +01:00
else
2023-07-24 14:01:53 -07:00
T.must(formula.stable).version
2020-12-11 16:24:49 +01:00
end
else
Version.new(formula_or_cask.version)
end
current_str = current.to_s
2022-08-17 17:41:34 +02:00
current = LivecheckVersion.create(formula_or_cask, current)
latest = if formula&.head_only?
Version.new(T.must(formula.head).downloader.fetch_last_commit)
else
version_info = latest_version(
formula_or_cask,
2024-03-07 16:20:20 +00:00
referenced_formula_or_cask:,
livecheck_references:,
json:, full_name: use_full_name, verbose:, debug:
)
version_info[:latest] if version_info.present?
end
2022-09-25 20:48:44 +02:00
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|
2024-03-07 16:20:20 +00:00
res_skip_info ||= SkipConditions.skip_information(resource, verbose:)
2022-09-25 20:48:44 +02:00
if res_skip_info.present?
res_skip_info
else
res_version_info = resource_version(
resource,
latest.to_s,
2024-03-07 16:20:20 +00:00
json:,
debug:,
quiet:,
verbose:,
2022-09-25 20:48:44 +02:00
)
if res_version_info.empty?
2024-03-07 16:20:20 +00:00
status_hash(resource, "error", ["Unable to get versions"], verbose:)
2022-09-25 20:48:44 +02:00
else
res_version_info
end
end
end.compact_blank
Homebrew.failed = true if resource_version_info.any? { |info| info[:status] == "error" }
2022-09-25 20:48:44 +02:00
end
if latest.blank?
no_versions_msg = "Unable to get versions"
raise Livecheck::Error, no_versions_msg unless json
next if quiet
next version_info if version_info.is_a?(Hash) && version_info[:status] && version_info[:messages]
2022-09-14 06:05:43 +02:00
latest_info = status_hash(formula_or_cask, "error", [no_versions_msg], full_name: use_full_name,
2024-03-07 16:20:20 +00:00
verbose:)
if check_for_resources
2023-12-12 10:09:49 -08:00
unless verbose
2023-12-12 10:19:58 -08:00
resource_version_info.map! do |info|
info.delete(:meta)
info
2023-12-12 10:09:49 -08:00
end
end
latest_info[:resources] = resource_version_info
end
2022-09-14 06:05:43 +02:00
next latest_info
end
if (m = latest.to_s.match(/(.*)-release$/)) && !current.to_s.match(/.*-release$/)
latest = Version.new(m[1])
end
latest_str = latest.to_s
2022-08-17 17:41:34 +02:00
latest = LivecheckVersion.create(formula_or_cask, latest)
2020-09-02 12:24:21 -07:00
is_outdated = if formula&.head_only?
# A HEAD-only formula is considered outdated if the latest upstream
# commit hash is different than the installed version's commit hash
(current != latest)
else
(current < latest)
end
2020-09-02 12:24:21 -07:00
is_newer_than_upstream = (formula&.stable? || cask) && (current > latest)
info = {}
info[:formula] = name if formula
info[:cask] = name if cask
info[:version] = {
current: current_str,
latest: latest_str,
latest_throttled: version_info&.dig(:latest_throttled),
outdated: is_outdated,
newer_than_upstream: is_newer_than_upstream,
}.compact
info[:meta] = {
livecheckable: formula_or_cask.livecheckable?,
}
2020-09-02 12:24:21 -07:00
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
if json
progress&.increment
2023-12-12 10:09:49 -08:00
info.delete(:meta) unless verbose
if check_for_resources && !verbose
2023-12-12 10:19:58 -08:00
resource_version_info.map! do |info|
info.delete(:meta)
info
2023-12-12 10:09:49 -08:00
end
end
next info
end
puts if debug
2024-03-07 16:20:20 +00:00
print_latest_version(info, verbose:, ambiguous_cask: ambiguous_casks.include?(formula_or_cask))
print_resources_info(resource_version_info, verbose:) if check_for_resources
nil
rescue => e
Homebrew.failed = true
use_full_name = full_name || ambiguous_names.include?(formula_or_cask)
2020-12-14 14:30:36 +01:00
if json
progress&.increment
unless quiet
status_hash(formula_or_cask, "error", [e.to_s], full_name: use_full_name,
2024-03-07 16:20:20 +00:00
verbose:)
end
2020-12-14 14:30:36 +01:00
elsif !quiet
2022-09-14 04:54:52 +02:00
name = package_or_resource_name(formula_or_cask, full_name: use_full_name)
2021-03-04 01:01:56 +05:30
name += " (cask)" if ambiguous_casks.include?(formula_or_cask)
onoe "#{Tty.blue}#{name}#{Tty.reset}: #{e}"
if debug && !e.is_a?(Livecheck::Error)
require "utils/backtrace"
$stderr.puts Utils::Backtrace.clean(e)
end
2024-03-07 16:20:20 +00:00
print_resources_info(resource_version_info, verbose:) if check_for_resources
nil
end
end
puts "No newer upstream versions." if newer_only && !has_a_newer_upstream_version && !debug && !json && !quiet
2020-12-14 14:30:36 +01:00
return unless json
if progress
progress.finish
Tty.with($stderr) do |stderr|
stderr.print "#{Tty.up}#{Tty.erase_line}" * 2
end
end
puts JSON.pretty_generate(formulae_checked.compact)
end
2022-09-14 04:54:52 +02:00
sig { params(package_or_resource: T.any(Formula, Cask::Cask, Resource), full_name: T::Boolean).returns(String) }
2024-08-23 03:15:57 +01:00
def self.package_or_resource_name(package_or_resource, full_name: false)
2022-09-14 04:54:52 +02:00
case package_or_resource
when Formula
2024-03-07 16:20:20 +00:00
formula_name(package_or_resource, full_name:)
when Cask::Cask
2024-03-07 16:20:20 +00:00
cask_name(package_or_resource, full_name:)
2022-09-14 04:54:52 +02:00
when Resource
package_or_resource.name
else
2022-09-14 04:54:52 +02:00
T.absurd(package_or_resource)
2020-09-02 12:24:21 -07:00
end
end
2020-12-14 14:30:36 +01:00
# Returns the fully-qualified name of a cask if the `full_name` argument is
# provided; returns the name otherwise.
sig { params(cask: Cask::Cask, full_name: T::Boolean).returns(String) }
2024-08-23 03:15:57 +01:00
private_class_method def self.cask_name(cask, full_name: false)
2020-12-14 14:30:36 +01:00
full_name ? cask.full_name : cask.token
2020-09-02 12:24:21 -07:00
end
# Returns the fully-qualified name of a formula if the `full_name` argument is
# provided; returns the name otherwise.
2020-12-14 14:30:36 +01:00
sig { params(formula: Formula, full_name: T::Boolean).returns(String) }
2024-08-23 03:15:57 +01:00
private_class_method def self.formula_name(formula, full_name: false)
2020-12-14 14:30:36 +01:00
full_name ? formula.full_name : formula.name
end
sig {
2021-01-04 13:41:19 -08:00
params(
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,
2023-12-12 10:10:18 -08:00
).returns(T::Hash[Symbol, T.untyped])
}
2024-08-23 03:15:57 +01:00
def self.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)
2020-09-02 12:24:21 -07:00
status_hash = {}
2020-09-02 12:24:21 -07:00
if formula
2024-03-07 16:20:20 +00:00
status_hash[:formula] = formula_name(formula, full_name:)
elsif cask
2024-03-07 16:20:20 +00:00
status_hash[:cask] = cask_name(cask, full_name:)
elsif resource
2022-08-11 21:50:54 +02:00
status_hash[:resource] = resource.name
2020-09-02 12:24:21 -07:00
end
status_hash[:status] = status_str
status_hash[:messages] = messages if messages.is_a?(Array)
2020-09-02 12:24:21 -07:00
2020-12-14 14:30:36 +01:00
status_hash[:meta] = {
livecheckable: package_or_resource.livecheckable?,
2020-12-14 14:30:36 +01:00
}
status_hash[:meta][:head_only] = true if formula&.head_only?
status_hash
end
# Formats and prints the livecheck result for a formula/cask/resource.
2023-12-12 10:10:18 -08:00
sig { params(info: T::Hash[Symbol, T.untyped], verbose: T::Boolean, ambiguous_cask: T::Boolean).void }
2024-08-23 03:15:57 +01:00
private_class_method def self.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}"
else
info[:version][:current]
end
latest_s = if info[:version][:outdated]
"#{Tty.green}#{info[:version][:latest]}#{Tty.reset}"
else
info[:version][:latest]
end
puts "#{package_or_resource_s}: #{current_s} ==> #{latest_s}"
end
2022-09-27 00:59:37 -07:00
# Prints the livecheck result for the resources of a given Formula.
2023-12-12 10:10:18 -08:00
sig { params(info: T::Array[T::Hash[Symbol, T.untyped]], verbose: T::Boolean).void }
2024-08-23 03:15:57 +01:00
private_class_method def self.print_resources_info(info, verbose: false)
2022-09-14 04:54:52 +02:00
info.each do |r_info|
2022-09-18 21:27:10 +02:00
if r_info[:status] && r_info[:messages]
2022-09-14 04:54:52 +02:00
SkipConditions.print_skip_information(r_info)
else
2024-03-07 16:20:20 +00:00
print_latest_version(r_info, verbose:)
2022-09-14 04:54:52 +02:00
end
end
end
sig {
params(
livecheck_url: T.any(String, Symbol),
package_or_resource: T.any(Formula, Cask::Cask, Resource),
).returns(T.nilable(String))
}
2024-08-23 03:15:57 +01:00
def self.livecheck_url_to_string(livecheck_url, package_or_resource)
case livecheck_url
when String
livecheck_url
when :url
package_or_resource.url&.to_s if package_or_resource.is_a?(Cask::Cask) || package_or_resource.is_a?(Resource)
when :head, :stable
package_or_resource.send(livecheck_url)&.url if package_or_resource.is_a?(Formula)
when :homepage
2022-08-07 19:36:06 +02:00
package_or_resource.homepage unless package_or_resource.is_a?(Resource)
end
end
# 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]) }
2024-08-23 03:15:57 +01:00
def self.checkable_urls(package_or_resource)
urls = []
case package_or_resource
when Formula
if package_or_resource.stable
2023-07-24 14:01:53 -07:00
urls << T.must(package_or_resource.stable).url
urls.concat(T.must(package_or_resource.stable).mirrors)
end
2023-07-24 14:01:53 -07:00
urls << T.must(package_or_resource.head).url if package_or_resource.head
urls << package_or_resource.homepage if package_or_resource.homepage
when Cask::Cask
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(package_or_resource)
end
urls.compact.uniq
2020-09-02 12:24:21 -07:00
end
2022-09-18 21:27:10 +02:00
# livecheck should fetch a URL using brewed curl if the formula/cask
Livecheck: Use Homebrew curl based on root domain At the moment, `#use_homebrew_curl?` can only be true for a `homepage` or `stable`/cask `url` with `using: :homebrew_curl`. If the checked URL differs from these URLs, livecheck won't use brewed curl. This limitation prevents livecheck from using brewed curl for a `livecheck` block URL that's a string literal (not a symbol for a `#checkable_url` like `:stable`, `:head`, `:url`). `libzip` was the original formula referenced in the related brew issue and it meets this criterion, so it doesn't appear to be handled by the existing `#use_homebrew_curl?` implementation. Additionally, the existing behavior can cause livecheck to unnecessarily use brewed curl for a completely different website (e.g., `cubelib`, `otf2`). For example, if the `stable` URL has `using: :homebrew_curl` and the `livecheck` block has `url :homepage`, livecheck will use brewed curl when checking the `homepage`. If these are completely different domains/servers, it's unlikely that we would need to use brewed curl when checking the `homepage`, so this particular behavior may not be beneficial. This commit reimplements `use_homebrew_curl?` to apply brewed curl when the checked URL's root domain is the same as the root domain of an aforementioned formula/cask URL with `using: :homebrew_curl`. For example, this looser approach would allow a `livecheck` block checking `https://www.example.com/downloads/` to use brewed curl if the `stable` URL was `https://downloads.example.com/example.zip` with `using: :homebrew_curl`. These could be different servers but, based on related formulae, this looseness is necessary for the moment. This approach aims to resolve both issues, allowing brewed curl to be applied to a slightly broader range of URLs (i.e., not limited to just the `#checkable_urls`) while also helping to avoid unnecessarily applying brewed curl when it's less likely to be useful (completely different domains). Neither approach is perfect but this one may be more useful in the interim time. Depending on how this looser approach works in practice, we may want to consider returning to a stricter approach once we have something like `using: :homebrew_curl` in `livecheck` blocks (this is forthcoming). Being explicit in a `livecheck` block is the most reliable approach (i.e., only use brewed curl when needed), so we could favor that and pare down the automated approach to only what's needed to support implicit checks (i.e., with no `livecheck` block). Of course, it's also possible to drop the automated approach entirely and simply require a `livecheck` block in this scenario but we can decide on how to handle this when the time comes.
2022-05-18 16:40:30 -04:00
# contains a `stable`/`url` or `head` URL `using: :homebrew_curl` that
# shares the same root domain.
2022-09-18 21:27:10 +02:00
sig { params(formula_or_cask: T.any(Formula, Cask::Cask), url: String).returns(T::Boolean) }
2024-08-23 03:15:57 +01:00
def self.use_homebrew_curl?(formula_or_cask, url)
Livecheck: Use Homebrew curl based on root domain At the moment, `#use_homebrew_curl?` can only be true for a `homepage` or `stable`/cask `url` with `using: :homebrew_curl`. If the checked URL differs from these URLs, livecheck won't use brewed curl. This limitation prevents livecheck from using brewed curl for a `livecheck` block URL that's a string literal (not a symbol for a `#checkable_url` like `:stable`, `:head`, `:url`). `libzip` was the original formula referenced in the related brew issue and it meets this criterion, so it doesn't appear to be handled by the existing `#use_homebrew_curl?` implementation. Additionally, the existing behavior can cause livecheck to unnecessarily use brewed curl for a completely different website (e.g., `cubelib`, `otf2`). For example, if the `stable` URL has `using: :homebrew_curl` and the `livecheck` block has `url :homepage`, livecheck will use brewed curl when checking the `homepage`. If these are completely different domains/servers, it's unlikely that we would need to use brewed curl when checking the `homepage`, so this particular behavior may not be beneficial. This commit reimplements `use_homebrew_curl?` to apply brewed curl when the checked URL's root domain is the same as the root domain of an aforementioned formula/cask URL with `using: :homebrew_curl`. For example, this looser approach would allow a `livecheck` block checking `https://www.example.com/downloads/` to use brewed curl if the `stable` URL was `https://downloads.example.com/example.zip` with `using: :homebrew_curl`. These could be different servers but, based on related formulae, this looseness is necessary for the moment. This approach aims to resolve both issues, allowing brewed curl to be applied to a slightly broader range of URLs (i.e., not limited to just the `#checkable_urls`) while also helping to avoid unnecessarily applying brewed curl when it's less likely to be useful (completely different domains). Neither approach is perfect but this one may be more useful in the interim time. Depending on how this looser approach works in practice, we may want to consider returning to a stricter approach once we have something like `using: :homebrew_curl` in `livecheck` blocks (this is forthcoming). Being explicit in a `livecheck` block is the most reliable approach (i.e., only use brewed curl when needed), so we could favor that and pare down the automated approach to only what's needed to support implicit checks (i.e., with no `livecheck` block). Of course, it's also possible to drop the automated approach entirely and simply require a `livecheck` block in this scenario but we can decide on how to handle this when the time comes.
2022-05-18 16:40:30 -04:00
url_root_domain = Addressable::URI.parse(url)&.domain
return false if url_root_domain.blank?
Livecheck: Use Homebrew curl based on root domain At the moment, `#use_homebrew_curl?` can only be true for a `homepage` or `stable`/cask `url` with `using: :homebrew_curl`. If the checked URL differs from these URLs, livecheck won't use brewed curl. This limitation prevents livecheck from using brewed curl for a `livecheck` block URL that's a string literal (not a symbol for a `#checkable_url` like `:stable`, `:head`, `:url`). `libzip` was the original formula referenced in the related brew issue and it meets this criterion, so it doesn't appear to be handled by the existing `#use_homebrew_curl?` implementation. Additionally, the existing behavior can cause livecheck to unnecessarily use brewed curl for a completely different website (e.g., `cubelib`, `otf2`). For example, if the `stable` URL has `using: :homebrew_curl` and the `livecheck` block has `url :homepage`, livecheck will use brewed curl when checking the `homepage`. If these are completely different domains/servers, it's unlikely that we would need to use brewed curl when checking the `homepage`, so this particular behavior may not be beneficial. This commit reimplements `use_homebrew_curl?` to apply brewed curl when the checked URL's root domain is the same as the root domain of an aforementioned formula/cask URL with `using: :homebrew_curl`. For example, this looser approach would allow a `livecheck` block checking `https://www.example.com/downloads/` to use brewed curl if the `stable` URL was `https://downloads.example.com/example.zip` with `using: :homebrew_curl`. These could be different servers but, based on related formulae, this looseness is necessary for the moment. This approach aims to resolve both issues, allowing brewed curl to be applied to a slightly broader range of URLs (i.e., not limited to just the `#checkable_urls`) while also helping to avoid unnecessarily applying brewed curl when it's less likely to be useful (completely different domains). Neither approach is perfect but this one may be more useful in the interim time. Depending on how this looser approach works in practice, we may want to consider returning to a stricter approach once we have something like `using: :homebrew_curl` in `livecheck` blocks (this is forthcoming). Being explicit in a `livecheck` block is the most reliable approach (i.e., only use brewed curl when needed), so we could favor that and pare down the automated approach to only what's needed to support implicit checks (i.e., with no `livecheck` block). Of course, it's also possible to drop the automated approach entirely and simply require a `livecheck` block in this scenario but we can decide on how to handle this when the time comes.
2022-05-18 16:40:30 -04:00
# Collect root domains of URLs with `using: :homebrew_curl`
homebrew_curl_root_domains = []
2022-09-18 21:27:10 +02:00
case formula_or_cask
Livecheck: Use Homebrew curl based on root domain At the moment, `#use_homebrew_curl?` can only be true for a `homepage` or `stable`/cask `url` with `using: :homebrew_curl`. If the checked URL differs from these URLs, livecheck won't use brewed curl. This limitation prevents livecheck from using brewed curl for a `livecheck` block URL that's a string literal (not a symbol for a `#checkable_url` like `:stable`, `:head`, `:url`). `libzip` was the original formula referenced in the related brew issue and it meets this criterion, so it doesn't appear to be handled by the existing `#use_homebrew_curl?` implementation. Additionally, the existing behavior can cause livecheck to unnecessarily use brewed curl for a completely different website (e.g., `cubelib`, `otf2`). For example, if the `stable` URL has `using: :homebrew_curl` and the `livecheck` block has `url :homepage`, livecheck will use brewed curl when checking the `homepage`. If these are completely different domains/servers, it's unlikely that we would need to use brewed curl when checking the `homepage`, so this particular behavior may not be beneficial. This commit reimplements `use_homebrew_curl?` to apply brewed curl when the checked URL's root domain is the same as the root domain of an aforementioned formula/cask URL with `using: :homebrew_curl`. For example, this looser approach would allow a `livecheck` block checking `https://www.example.com/downloads/` to use brewed curl if the `stable` URL was `https://downloads.example.com/example.zip` with `using: :homebrew_curl`. These could be different servers but, based on related formulae, this looseness is necessary for the moment. This approach aims to resolve both issues, allowing brewed curl to be applied to a slightly broader range of URLs (i.e., not limited to just the `#checkable_urls`) while also helping to avoid unnecessarily applying brewed curl when it's less likely to be useful (completely different domains). Neither approach is perfect but this one may be more useful in the interim time. Depending on how this looser approach works in practice, we may want to consider returning to a stricter approach once we have something like `using: :homebrew_curl` in `livecheck` blocks (this is forthcoming). Being explicit in a `livecheck` block is the most reliable approach (i.e., only use brewed curl when needed), so we could favor that and pare down the automated approach to only what's needed to support implicit checks (i.e., with no `livecheck` block). Of course, it's also possible to drop the automated approach entirely and simply require a `livecheck` block in this scenario but we can decide on how to handle this when the time comes.
2022-05-18 16:40:30 -04:00
when Formula
[:stable, :head].each do |spec_name|
2022-09-18 21:27:10 +02:00
next unless (spec = formula_or_cask.send(spec_name))
2023-04-18 15:06:50 -07:00
next if spec.using != :homebrew_curl
Livecheck: Use Homebrew curl based on root domain At the moment, `#use_homebrew_curl?` can only be true for a `homepage` or `stable`/cask `url` with `using: :homebrew_curl`. If the checked URL differs from these URLs, livecheck won't use brewed curl. This limitation prevents livecheck from using brewed curl for a `livecheck` block URL that's a string literal (not a symbol for a `#checkable_url` like `:stable`, `:head`, `:url`). `libzip` was the original formula referenced in the related brew issue and it meets this criterion, so it doesn't appear to be handled by the existing `#use_homebrew_curl?` implementation. Additionally, the existing behavior can cause livecheck to unnecessarily use brewed curl for a completely different website (e.g., `cubelib`, `otf2`). For example, if the `stable` URL has `using: :homebrew_curl` and the `livecheck` block has `url :homepage`, livecheck will use brewed curl when checking the `homepage`. If these are completely different domains/servers, it's unlikely that we would need to use brewed curl when checking the `homepage`, so this particular behavior may not be beneficial. This commit reimplements `use_homebrew_curl?` to apply brewed curl when the checked URL's root domain is the same as the root domain of an aforementioned formula/cask URL with `using: :homebrew_curl`. For example, this looser approach would allow a `livecheck` block checking `https://www.example.com/downloads/` to use brewed curl if the `stable` URL was `https://downloads.example.com/example.zip` with `using: :homebrew_curl`. These could be different servers but, based on related formulae, this looseness is necessary for the moment. This approach aims to resolve both issues, allowing brewed curl to be applied to a slightly broader range of URLs (i.e., not limited to just the `#checkable_urls`) while also helping to avoid unnecessarily applying brewed curl when it's less likely to be useful (completely different domains). Neither approach is perfect but this one may be more useful in the interim time. Depending on how this looser approach works in practice, we may want to consider returning to a stricter approach once we have something like `using: :homebrew_curl` in `livecheck` blocks (this is forthcoming). Being explicit in a `livecheck` block is the most reliable approach (i.e., only use brewed curl when needed), so we could favor that and pare down the automated approach to only what's needed to support implicit checks (i.e., with no `livecheck` block). Of course, it's also possible to drop the automated approach entirely and simply require a `livecheck` block in this scenario but we can decide on how to handle this when the time comes.
2022-05-18 16:40:30 -04:00
domain = Addressable::URI.parse(spec.url)&.domain
homebrew_curl_root_domains << domain if domain.present?
end
Livecheck: Use Homebrew curl based on root domain At the moment, `#use_homebrew_curl?` can only be true for a `homepage` or `stable`/cask `url` with `using: :homebrew_curl`. If the checked URL differs from these URLs, livecheck won't use brewed curl. This limitation prevents livecheck from using brewed curl for a `livecheck` block URL that's a string literal (not a symbol for a `#checkable_url` like `:stable`, `:head`, `:url`). `libzip` was the original formula referenced in the related brew issue and it meets this criterion, so it doesn't appear to be handled by the existing `#use_homebrew_curl?` implementation. Additionally, the existing behavior can cause livecheck to unnecessarily use brewed curl for a completely different website (e.g., `cubelib`, `otf2`). For example, if the `stable` URL has `using: :homebrew_curl` and the `livecheck` block has `url :homepage`, livecheck will use brewed curl when checking the `homepage`. If these are completely different domains/servers, it's unlikely that we would need to use brewed curl when checking the `homepage`, so this particular behavior may not be beneficial. This commit reimplements `use_homebrew_curl?` to apply brewed curl when the checked URL's root domain is the same as the root domain of an aforementioned formula/cask URL with `using: :homebrew_curl`. For example, this looser approach would allow a `livecheck` block checking `https://www.example.com/downloads/` to use brewed curl if the `stable` URL was `https://downloads.example.com/example.zip` with `using: :homebrew_curl`. These could be different servers but, based on related formulae, this looseness is necessary for the moment. This approach aims to resolve both issues, allowing brewed curl to be applied to a slightly broader range of URLs (i.e., not limited to just the `#checkable_urls`) while also helping to avoid unnecessarily applying brewed curl when it's less likely to be useful (completely different domains). Neither approach is perfect but this one may be more useful in the interim time. Depending on how this looser approach works in practice, we may want to consider returning to a stricter approach once we have something like `using: :homebrew_curl` in `livecheck` blocks (this is forthcoming). Being explicit in a `livecheck` block is the most reliable approach (i.e., only use brewed curl when needed), so we could favor that and pare down the automated approach to only what's needed to support implicit checks (i.e., with no `livecheck` block). Of course, it's also possible to drop the automated approach entirely and simply require a `livecheck` block in this scenario but we can decide on how to handle this when the time comes.
2022-05-18 16:40:30 -04:00
when Cask::Cask
return false if formula_or_cask.url&.using != :homebrew_curl
2022-08-07 19:36:06 +02:00
2022-09-18 21:27:10 +02:00
domain = Addressable::URI.parse(formula_or_cask.url.to_s)&.domain
Livecheck: Use Homebrew curl based on root domain At the moment, `#use_homebrew_curl?` can only be true for a `homepage` or `stable`/cask `url` with `using: :homebrew_curl`. If the checked URL differs from these URLs, livecheck won't use brewed curl. This limitation prevents livecheck from using brewed curl for a `livecheck` block URL that's a string literal (not a symbol for a `#checkable_url` like `:stable`, `:head`, `:url`). `libzip` was the original formula referenced in the related brew issue and it meets this criterion, so it doesn't appear to be handled by the existing `#use_homebrew_curl?` implementation. Additionally, the existing behavior can cause livecheck to unnecessarily use brewed curl for a completely different website (e.g., `cubelib`, `otf2`). For example, if the `stable` URL has `using: :homebrew_curl` and the `livecheck` block has `url :homepage`, livecheck will use brewed curl when checking the `homepage`. If these are completely different domains/servers, it's unlikely that we would need to use brewed curl when checking the `homepage`, so this particular behavior may not be beneficial. This commit reimplements `use_homebrew_curl?` to apply brewed curl when the checked URL's root domain is the same as the root domain of an aforementioned formula/cask URL with `using: :homebrew_curl`. For example, this looser approach would allow a `livecheck` block checking `https://www.example.com/downloads/` to use brewed curl if the `stable` URL was `https://downloads.example.com/example.zip` with `using: :homebrew_curl`. These could be different servers but, based on related formulae, this looseness is necessary for the moment. This approach aims to resolve both issues, allowing brewed curl to be applied to a slightly broader range of URLs (i.e., not limited to just the `#checkable_urls`) while also helping to avoid unnecessarily applying brewed curl when it's less likely to be useful (completely different domains). Neither approach is perfect but this one may be more useful in the interim time. Depending on how this looser approach works in practice, we may want to consider returning to a stricter approach once we have something like `using: :homebrew_curl` in `livecheck` blocks (this is forthcoming). Being explicit in a `livecheck` block is the most reliable approach (i.e., only use brewed curl when needed), so we could favor that and pare down the automated approach to only what's needed to support implicit checks (i.e., with no `livecheck` block). Of course, it's also possible to drop the automated approach entirely and simply require a `livecheck` block in this scenario but we can decide on how to handle this when the time comes.
2022-05-18 16:40:30 -04:00
homebrew_curl_root_domains << domain if domain.present?
end
Livecheck: Use Homebrew curl based on root domain At the moment, `#use_homebrew_curl?` can only be true for a `homepage` or `stable`/cask `url` with `using: :homebrew_curl`. If the checked URL differs from these URLs, livecheck won't use brewed curl. This limitation prevents livecheck from using brewed curl for a `livecheck` block URL that's a string literal (not a symbol for a `#checkable_url` like `:stable`, `:head`, `:url`). `libzip` was the original formula referenced in the related brew issue and it meets this criterion, so it doesn't appear to be handled by the existing `#use_homebrew_curl?` implementation. Additionally, the existing behavior can cause livecheck to unnecessarily use brewed curl for a completely different website (e.g., `cubelib`, `otf2`). For example, if the `stable` URL has `using: :homebrew_curl` and the `livecheck` block has `url :homepage`, livecheck will use brewed curl when checking the `homepage`. If these are completely different domains/servers, it's unlikely that we would need to use brewed curl when checking the `homepage`, so this particular behavior may not be beneficial. This commit reimplements `use_homebrew_curl?` to apply brewed curl when the checked URL's root domain is the same as the root domain of an aforementioned formula/cask URL with `using: :homebrew_curl`. For example, this looser approach would allow a `livecheck` block checking `https://www.example.com/downloads/` to use brewed curl if the `stable` URL was `https://downloads.example.com/example.zip` with `using: :homebrew_curl`. These could be different servers but, based on related formulae, this looseness is necessary for the moment. This approach aims to resolve both issues, allowing brewed curl to be applied to a slightly broader range of URLs (i.e., not limited to just the `#checkable_urls`) while also helping to avoid unnecessarily applying brewed curl when it's less likely to be useful (completely different domains). Neither approach is perfect but this one may be more useful in the interim time. Depending on how this looser approach works in practice, we may want to consider returning to a stricter approach once we have something like `using: :homebrew_curl` in `livecheck` blocks (this is forthcoming). Being explicit in a `livecheck` block is the most reliable approach (i.e., only use brewed curl when needed), so we could favor that and pare down the automated approach to only what's needed to support implicit checks (i.e., with no `livecheck` block). Of course, it's also possible to drop the automated approach entirely and simply require a `livecheck` block in this scenario but we can decide on how to handle this when the time comes.
2022-05-18 16:40:30 -04:00
homebrew_curl_root_domains.include?(url_root_domain)
end
# 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.
sig {
params(
formula_or_cask: T.any(Formula, Cask::Cask),
referenced_formula_or_cask: T.nilable(T.any(Formula, Cask::Cask)),
livecheck_references: T::Array[T.any(Formula, Cask::Cask)],
json: T::Boolean,
full_name: T::Boolean,
verbose: T::Boolean,
debug: T::Boolean,
2023-12-12 10:10:18 -08:00
).returns(T.nilable(T::Hash[Symbol, T.untyped]))
}
2024-08-23 03:15:57 +01:00
def self.latest_version(
formula_or_cask,
referenced_formula_or_cask: nil,
livecheck_references: [],
json: false, full_name: false, verbose: false, debug: false
)
formula = formula_or_cask if formula_or_cask.is_a?(Formula)
cask = formula_or_cask if formula_or_cask.is_a?(Cask::Cask)
has_livecheckable = formula_or_cask.livecheckable?
livecheck = formula_or_cask.livecheck
referenced_livecheck = referenced_formula_or_cask&.livecheck
livecheck_url = livecheck.url || referenced_livecheck&.url
livecheck_regex = livecheck.regex || referenced_livecheck&.regex
livecheck_strategy = livecheck.strategy || referenced_livecheck&.strategy
livecheck_strategy_block = livecheck.strategy_block || referenced_livecheck&.strategy_block
livecheck_throttle = livecheck.throttle || referenced_livecheck&.throttle
2024-08-23 03:15:57 +01:00
referenced_package = referenced_formula_or_cask || formula_or_cask
livecheck_url_string = livecheck_url_to_string(livecheck_url, referenced_package) if livecheck_url
urls = [livecheck_url_string] if livecheck_url_string
2024-08-23 03:15:57 +01:00
urls ||= checkable_urls(referenced_package)
if debug
if formula
2024-03-07 16:20:20 +00:00
puts "Formula: #{formula_name(formula, full_name:)}"
puts "Head only?: true" if formula.head_only?
elsif cask
2024-03-07 16:20:20 +00:00
puts "Cask: #{cask_name(formula_or_cask, full_name:)}"
end
puts "Livecheckable?: #{has_livecheckable ? "Yes" : "No"}"
puts "Throttle: #{livecheck_throttle}" if livecheck_throttle
livecheck_references.each do |ref_formula_or_cask|
case ref_formula_or_cask
when Formula
2024-03-07 16:20:20 +00:00
puts "Formula Ref: #{formula_name(ref_formula_or_cask, full_name:)}"
when Cask::Cask
2024-03-07 16:20:20 +00:00
puts "Cask Ref: #{cask_name(ref_formula_or_cask, full_name:)}"
end
end
end
2022-07-27 21:28:28 +02:00
checked_urls = []
urls.each_with_index do |original_url, i|
url = original_url
next if checked_urls.include?(url)
strategies = Strategy.from_url(
url,
2024-03-07 16:20:20 +00:00
livecheck_strategy:,
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 strategy.respond_to?(:preprocess_url)
url = strategy.preprocess_url(url)
next if checked_urls.include?(url)
end
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(", ")}"
2022-07-27 21:28:28 +02:00
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
2022-07-27 21:28:28 +02:00
end
end
next if strategy.blank?
homebrew_curl = case strategy_name
when "PageMatch", "HeaderMatch"
2024-08-23 03:15:57 +01:00
use_homebrew_curl?(referenced_package, url)
end
puts "Homebrew curl?: Yes" if debug && homebrew_curl.present?
strategy_args = {
regex: livecheck_regex,
2024-03-07 16:20:20 +00:00
homebrew_curl:,
}
# TODO: Set `cask`/`url` args based on the presence of the keyword arg
# in the strategy's `#find_versions` method once we figure out why
# `strategy.method(:find_versions).parameters` isn't working as
# expected.
strategy_args[:cask] = cask if strategy_name == "ExtractPlist" && cask.present?
strategy_args[:url] = url
strategy_args.compact!
strategy_data = strategy.find_versions(**strategy_args, &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
2024-03-07 16:20:20 +00:00
return status_hash(formula_or_cask, "error", messages, full_name:, 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}"
2022-07-27 21:28:28 +02:00
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)
2022-07-27 21:28:28 +02:00
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(", ")
2022-07-27 21:28:28 +02:00
end
end
version_info = {
latest: Version.new(match_version_map.values.max_by { |v| LivecheckVersion.create(formula_or_cask, v) }),
}
if livecheck_throttle
match_version_map.keep_if { |_match, version| version.patch.to_i.modulo(livecheck_throttle).zero? }
version_info[:latest_throttled] = if match_version_map.blank?
nil
else
Version.new(match_version_map.values.max_by { |v| LivecheckVersion.create(formula_or_cask, v) })
end
if debug
puts
puts "Matched Throttled Versions:"
if verbose
match_version_map.each do |match, version|
puts "#{match} => #{version.inspect}"
end
else
puts match_version_map.values.join(", ")
end
end
end
if json && verbose
version_info[:meta] = {}
2022-08-07 19:14:13 +02:00
if livecheck_references.present?
version_info[:meta][:references] = livecheck_references.map do |ref_formula_or_cask|
case ref_formula_or_cask
when Formula
2024-03-07 16:20:20 +00:00
{ formula: formula_name(ref_formula_or_cask, full_name:) }
when Cask::Cask
2024-03-07 16:20:20 +00:00
{ cask: cask_name(ref_formula_or_cask, full_name:) }
end
end
end
version_info[:meta][:url] = {}
version_info[:meta][:url][:symbol] = livecheck_url if livecheck_url.is_a?(Symbol) && livecheck_url_string
version_info[:meta][:url][:original] = original_url
version_info[:meta][:url][:processed] = url if url != original_url
if strategy_data[:url].present? && strategy_data[:url] != url
version_info[:meta][:url][:strategy] = strategy_data[:url]
end
version_info[:meta][:url][:final] = strategy_data[:final_url] if strategy_data[:final_url]
version_info[:meta][:url][:homebrew_curl] = homebrew_curl if homebrew_curl.present?
version_info[:meta][:strategy] = strategy.present? ? strategy_name : nil
version_info[:meta][:strategies] = strategies.map { |s| livecheck_strategy_names[s] } if strategies.present?
version_info[:meta][:regex] = regex.inspect if regex.present?
version_info[:meta][:cached] = true if strategy_data[:cached] == true
version_info[:meta][:throttle] = livecheck_throttle if livecheck_throttle
2022-07-27 21:28:28 +02:00
end
return version_info
end
nil
end
2023-12-12 10:10:18 -08:00
2022-09-27 00:59:37 -07:00
# 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 {
2021-01-04 13:41:19 -08:00
params(
resource: Resource,
formula_latest: String,
json: T::Boolean,
debug: T::Boolean,
quiet: T::Boolean,
verbose: T::Boolean,
2023-12-12 10:10:18 -08:00
).returns(T::Hash[Symbol, T.untyped])
}
2024-08-23 03:15:57 +01:00
def self.resource_version(
resource,
formula_latest,
json: false,
debug: false,
quiet: false,
verbose: false
)
has_livecheckable = resource.livecheckable?
2020-09-02 12:24:21 -07:00
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 = []
urls.each_with_index do |original_url, i|
url = original_url.gsub(Constants::LATEST_VERSION, formula_latest)
next if checked_urls.include?(url)
strategies = Strategy.from_url(
url,
2024-03-07 16:20:20 +00:00
livecheck_strategy:,
regex_provided: livecheck_regex.present?,
block_provided: livecheck_strategy_block.present?,
)
2021-04-04 03:00:34 +02:00
strategy = Strategy.from_symbol(livecheck_strategy) || strategies.first
2020-12-14 14:30:36 +01:00
strategy_name = livecheck_strategy_names[strategy]
if strategy.respond_to?(:preprocess_url)
url = strategy.preprocess_url(url)
next if checked_urls.include?(url)
end
2020-12-14 14:30:36 +01:00
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
2020-12-14 14:30:36 +01:00
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
2021-04-04 03:00:34 +02:00
if livecheck_strategy.present?
if livecheck_url.blank? && strategy.method(:find_versions).parameters.include?([:keyreq, :url])
2021-04-04 03:00:34 +02:00
odebug "#{strategy_name} strategy requires a URL"
next
elsif livecheck_strategy != :page_match && strategies.exclude?(strategy)
2021-04-04 03:00:34 +02:00
odebug "#{strategy_name} strategy does not apply to this URL"
next
end
end
puts if debug && strategy.blank?
next if strategy.blank?
strategy_args = {
2024-03-07 16:20:20 +00:00
url:,
regex: livecheck_regex,
homebrew_curl: false,
}.compact
strategy_data = strategy.find_versions(**strategy_args, &livecheck_strategy_block)
match_version_map = strategy_data[:matches]
regex = strategy_data[:regex]
2021-01-04 13:41:19 -08:00
messages = strategy_data[:messages]
checked_urls << url
2021-01-04 13:41:19 -08:00
if messages.is_a?(Array) && match_version_map.blank?
puts messages unless json
next if i + 1 < urls.length
2024-03-07 16:20:20 +00:00
return status_hash(resource, "error", messages, verbose:)
end
2020-12-14 14:30:36 +01:00
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
2021-04-04 03:00:34 +02:00
next if match_version_map.blank?
if debug
puts
puts "Matched Versions:"
2020-12-14 14:30:36 +01:00
if verbose
match_version_map.each do |match, version|
puts "#{match} => #{version.inspect}"
end
else
puts match_version_map.values.join(", ")
end
end
2023-05-01 08:08:19 +02:00
res_current = T.must(resource.version)
res_latest = Version.new(match_version_map.values.max_by { |v| LivecheckVersion.create(resource, v) })
2024-03-07 16:20:20 +00:00
return status_hash(resource, "error", ["Unable to get versions"], 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
2024-03-07 16:20:20 +00:00
status_hash(resource, "error", [e.to_s], verbose:)
elsif !quiet
onoe "#{Tty.blue}#{resource.name}#{Tty.reset}: #{e}"
if debug && !e.is_a?(Livecheck::Error)
require "utils/backtrace"
$stderr.puts Utils::Backtrace.clean(e)
end
nil
end
end
resource_version_info
end
end
end