2021-01-04 13:41:19 -08:00
|
|
|
# typed: true
|
2020-08-08 07:10:48 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2020-12-19 20:36:41 +01:00
|
|
|
require "livecheck/error"
|
2021-01-07 15:30:22 -05:00
|
|
|
require "livecheck/skip_conditions"
|
2020-08-27 22:46:06 +05:30
|
|
|
require "livecheck/strategy"
|
2020-09-03 00:41:16 +05:30
|
|
|
require "ruby-progressbar"
|
2020-11-07 03:18:42 +01:00
|
|
|
require "uri"
|
2020-08-27 22:46:06 +05:30
|
|
|
|
2020-08-08 07:10:48 +05:30
|
|
|
module Homebrew
|
2020-11-05 17:17:03 -05:00
|
|
|
# The {Livecheck} module consists of methods used by the `brew livecheck`
|
|
|
|
# command. These methods print the requested livecheck information
|
2020-08-27 22:46:06 +05:30
|
|
|
# for formulae.
|
|
|
|
#
|
|
|
|
# @api private
|
2020-08-08 07:10:48 +05:30
|
|
|
module Livecheck
|
2020-12-14 14:30:36 +01:00
|
|
|
extend T::Sig
|
|
|
|
|
2020-08-08 07:10:48 +05:30
|
|
|
module_function
|
|
|
|
|
2020-11-11 02:32:44 +01:00
|
|
|
GITEA_INSTANCES = %w[
|
|
|
|
codeberg.org
|
|
|
|
gitea.com
|
|
|
|
opendev.org
|
|
|
|
tildegit.org
|
|
|
|
].freeze
|
|
|
|
|
|
|
|
GOGS_INSTANCES = %w[
|
|
|
|
lolg.it
|
|
|
|
].freeze
|
|
|
|
|
2020-12-05 11:44:28 -05:00
|
|
|
STRATEGY_SYMBOLS_TO_SKIP_PREPROCESS_URL = [
|
|
|
|
:github_latest,
|
|
|
|
:page_match,
|
2021-01-06 12:34:30 +01:00
|
|
|
:header_match,
|
2020-12-19 00:51:54 -05:00
|
|
|
:sparkle,
|
2020-12-05 11:44:28 -05:00
|
|
|
].freeze
|
|
|
|
|
2020-08-08 07:10:48 +05:30
|
|
|
UNSTABLE_VERSION_KEYWORDS = %w[
|
|
|
|
alpha
|
|
|
|
beta
|
|
|
|
bpo
|
|
|
|
dev
|
|
|
|
experimental
|
|
|
|
prerelease
|
|
|
|
preview
|
|
|
|
rc
|
|
|
|
].freeze
|
|
|
|
|
2021-01-04 13:41:19 -08:00
|
|
|
sig { returns(T::Hash[Class, String]) }
|
2020-12-14 14:30:36 +01:00
|
|
|
def livecheck_strategy_names
|
|
|
|
return @livecheck_strategy_names if defined?(@livecheck_strategy_names)
|
|
|
|
|
|
|
|
# Cache demodulized strategy names, to avoid repeating this work
|
|
|
|
@livecheck_strategy_names = {}
|
|
|
|
Strategy.constants.sort.each do |strategy_symbol|
|
|
|
|
strategy = Strategy.const_get(strategy_symbol)
|
|
|
|
@livecheck_strategy_names[strategy] = strategy.name.demodulize
|
|
|
|
end
|
|
|
|
@livecheck_strategy_names.freeze
|
|
|
|
end
|
|
|
|
|
2021-01-11 08:29:34 +05:30
|
|
|
# Identify taps other than homebrew/core and homebrew/cask in use
|
|
|
|
# for current formulae and casks and load additional Strategy
|
|
|
|
# files them.
|
|
|
|
sig do
|
|
|
|
params(formulae_and_casks_to_check: T::Enumerable[T.any(Formula, Cask::Cask)]).void
|
|
|
|
end
|
|
|
|
def load_other_tap_strategies(formulae_and_casks_to_check)
|
|
|
|
other_taps = {}
|
|
|
|
formulae_and_casks_to_check.each do |formula_or_cask|
|
|
|
|
next if formula_or_cask.tap.blank?
|
|
|
|
next if formula_or_cask.tap.name == CoreTap.instance.name
|
|
|
|
next if formula_or_cask.tap.name == "homebrew/cask"
|
|
|
|
next if other_taps[formula_or_cask.tap.name]
|
|
|
|
|
|
|
|
other_taps[formula_or_cask.tap.name] = formula_or_cask.tap
|
|
|
|
end
|
|
|
|
other_taps = other_taps.sort.to_h
|
|
|
|
|
|
|
|
other_taps.each_value do |tap|
|
|
|
|
tap_strategy_path = "#{tap.path}/livecheck/strategy"
|
|
|
|
Dir["#{tap_strategy_path}/*.rb"].sort.each(&method(:require)) if Dir.exist?(tap_strategy_path)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
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.
|
2021-01-17 22:45:55 -08:00
|
|
|
sig {
|
2021-01-04 13:41:19 -08:00
|
|
|
params(
|
|
|
|
formulae_and_casks_to_check: T::Enumerable[T.any(Formula, Cask::Cask)],
|
|
|
|
full_name: T::Boolean,
|
|
|
|
json: T::Boolean,
|
|
|
|
newer_only: T::Boolean,
|
|
|
|
debug: T::Boolean,
|
|
|
|
quiet: T::Boolean,
|
|
|
|
verbose: T::Boolean,
|
|
|
|
).void
|
2021-01-17 22:45:55 -08:00
|
|
|
}
|
2020-12-14 14:30:36 +01:00
|
|
|
def run_checks(
|
|
|
|
formulae_and_casks_to_check,
|
|
|
|
full_name: false, json: false, newer_only: false, debug: false, quiet: false, verbose: false
|
|
|
|
)
|
2021-01-11 08:29:34 +05:30
|
|
|
load_other_tap_strategies(formulae_and_casks_to_check)
|
2020-08-08 07:10:48 +05:30
|
|
|
|
2021-01-04 13:41:19 -08:00
|
|
|
has_a_newer_upstream_version = T.let(false, T::Boolean)
|
2020-09-03 00:41:16 +05:30
|
|
|
|
2020-12-14 14:30:36 +01:00
|
|
|
if json && !quiet && $stderr.tty?
|
2021-01-04 13:41:19 -08:00
|
|
|
formulae_and_casks_total = formulae_and_casks_to_check.count
|
2020-09-03 00:41:16 +05:30
|
|
|
|
2020-09-18 02:39:52 +05:30
|
|
|
Tty.with($stderr) do |stderr|
|
|
|
|
stderr.puts Formatter.headline("Running checks", color: :blue)
|
|
|
|
end
|
|
|
|
|
2020-09-03 00:41:16 +05:30
|
|
|
progress = ProgressBar.create(
|
2020-12-12 10:29:25 -05:00
|
|
|
total: formulae_and_casks_total,
|
2020-09-03 00:41:16 +05:30
|
|
|
progress_mark: "#",
|
|
|
|
remainder_mark: ".",
|
|
|
|
format: " %t: [%B] %c/%C ",
|
|
|
|
output: $stderr,
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2020-09-03 20:33:24 -07:00
|
|
|
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)
|
2021-01-07 15:30:22 -05:00
|
|
|
name = formula_or_cask_name(formula_or_cask, full_name: full_name)
|
2020-09-02 12:24:21 -07:00
|
|
|
|
2020-12-14 14:30:36 +01:00
|
|
|
if debug && i.positive?
|
2020-08-08 07:10:48 +05:30
|
|
|
puts <<~EOS
|
|
|
|
|
|
|
|
----------
|
|
|
|
|
|
|
|
EOS
|
|
|
|
end
|
|
|
|
|
2021-01-07 15:30:22 -05:00
|
|
|
skip_info = SkipConditions.skip_information(formula_or_cask, full_name: full_name, verbose: verbose)
|
|
|
|
if skip_info.present?
|
|
|
|
next skip_info if json
|
|
|
|
|
|
|
|
SkipConditions.print_skip_information(skip_info) unless quiet
|
|
|
|
next
|
|
|
|
end
|
2020-08-08 07:10:48 +05:30
|
|
|
|
2020-12-11 16:27:53 +01:00
|
|
|
formula&.head&.downloader&.shutup!
|
2020-08-08 07:10:48 +05:30
|
|
|
|
2020-09-09 13:27:12 +05:30
|
|
|
# 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?
|
|
|
|
formula.any_installed_version.version.commit
|
|
|
|
else
|
|
|
|
formula.stable.version
|
|
|
|
end
|
|
|
|
else
|
|
|
|
Version.new(formula_or_cask.version)
|
2020-08-31 09:59:02 -07:00
|
|
|
end
|
2020-08-08 07:10:48 +05:30
|
|
|
|
2021-01-17 11:24:52 -08:00
|
|
|
current_str = current.to_s
|
|
|
|
current = actual_version(formula_or_cask, current)
|
|
|
|
|
2020-12-12 10:30:20 -05:00
|
|
|
latest = if formula&.head_only?
|
|
|
|
formula.head.downloader.fetch_last_commit
|
|
|
|
else
|
2020-12-14 14:30:36 +01:00
|
|
|
version_info = latest_version(
|
|
|
|
formula_or_cask,
|
|
|
|
json: json, full_name: full_name, verbose: verbose, debug: debug,
|
|
|
|
)
|
2020-08-08 07:10:48 +05:30
|
|
|
version_info[:latest] if version_info.present?
|
|
|
|
end
|
|
|
|
|
|
|
|
if latest.blank?
|
|
|
|
no_versions_msg = "Unable to get versions"
|
2020-12-19 20:36:41 +01:00
|
|
|
raise Livecheck::Error, no_versions_msg unless json
|
2020-08-08 07:10:48 +05:30
|
|
|
|
|
|
|
next version_info if version_info.is_a?(Hash) && version_info[:status] && version_info[:messages]
|
|
|
|
|
2020-12-14 14:30:36 +01:00
|
|
|
next status_hash(formula_or_cask, "error", [no_versions_msg], full_name: full_name, verbose: verbose)
|
2020-08-08 07:10:48 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
if (m = latest.to_s.match(/(.*)-release$/)) && !current.to_s.match(/.*-release$/)
|
|
|
|
latest = Version.new(m[1])
|
|
|
|
end
|
|
|
|
|
2021-01-17 11:24:52 -08:00
|
|
|
latest_str = latest.to_s
|
|
|
|
latest = actual_version(formula_or_cask, latest)
|
|
|
|
|
2020-09-02 12:24:21 -07:00
|
|
|
is_outdated = if formula&.head_only?
|
2020-08-29 00:32:34 +05:30
|
|
|
# A HEAD-only formula is considered outdated if the latest upstream
|
|
|
|
# commit hash is different than the installed version's commit hash
|
2020-08-08 07:10:48 +05:30
|
|
|
(current != latest)
|
|
|
|
else
|
|
|
|
(current < latest)
|
|
|
|
end
|
|
|
|
|
2020-09-02 12:24:21 -07:00
|
|
|
is_newer_than_upstream = (formula&.stable? || cask) && (current > latest)
|
2020-08-08 07:10:48 +05:30
|
|
|
|
2020-12-12 11:00:02 -05:00
|
|
|
info = {}
|
2021-01-07 15:30:22 -05:00
|
|
|
info[:formula] = name if formula
|
|
|
|
info[:cask] = name if cask
|
2020-12-12 11:00:02 -05:00
|
|
|
info[:version] = {
|
2021-01-17 11:24:52 -08:00
|
|
|
current: current_str,
|
|
|
|
latest: latest_str,
|
2020-12-12 11:00:02 -05:00
|
|
|
outdated: is_outdated,
|
|
|
|
newer_than_upstream: is_newer_than_upstream,
|
|
|
|
}
|
|
|
|
info[:meta] = {
|
|
|
|
livecheckable: formula_or_cask.livecheckable?,
|
|
|
|
}
|
2020-09-02 12:24:21 -07:00
|
|
|
info[:meta][:head_only] = true if formula&.head_only?
|
2020-08-08 07:10:48 +05:30
|
|
|
info[:meta].merge!(version_info[:meta]) if version_info.present? && version_info.key?(:meta)
|
|
|
|
|
2020-12-14 14:30:36 +01:00
|
|
|
next if newer_only && !info[:version][:outdated]
|
2020-08-08 07:10:48 +05:30
|
|
|
|
|
|
|
has_a_newer_upstream_version ||= true
|
|
|
|
|
2020-12-14 14:30:36 +01:00
|
|
|
if json
|
2020-09-03 00:41:16 +05:30
|
|
|
progress&.increment
|
2020-12-14 14:30:36 +01:00
|
|
|
info.except!(:meta) unless verbose
|
2020-08-08 07:10:48 +05:30
|
|
|
next info
|
|
|
|
end
|
|
|
|
|
2020-12-14 14:30:36 +01:00
|
|
|
print_latest_version(info, verbose: verbose)
|
2020-08-08 07:10:48 +05:30
|
|
|
nil
|
|
|
|
rescue => e
|
|
|
|
Homebrew.failed = true
|
|
|
|
|
2020-12-14 14:30:36 +01:00
|
|
|
if json
|
2020-09-03 00:41:16 +05:30
|
|
|
progress&.increment
|
2020-12-14 14:30:36 +01:00
|
|
|
status_hash(formula_or_cask, "error", [e.to_s], full_name: full_name, verbose: verbose)
|
|
|
|
elsif !quiet
|
2021-01-07 15:30:22 -05:00
|
|
|
onoe "#{Tty.blue}#{name}#{Tty.reset}: #{e}"
|
2020-12-19 20:36:41 +01:00
|
|
|
$stderr.puts e.backtrace if debug && !e.is_a?(Livecheck::Error)
|
2020-08-08 07:10:48 +05:30
|
|
|
nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-12-14 14:30:36 +01:00
|
|
|
puts "No newer upstream versions." if newer_only && !has_a_newer_upstream_version && !debug && !json
|
2020-08-08 07:10:48 +05:30
|
|
|
|
2020-12-14 14:30:36 +01:00
|
|
|
return unless json
|
2020-09-03 00:41:16 +05:30
|
|
|
|
|
|
|
if progress
|
|
|
|
progress.finish
|
2020-09-18 02:39:52 +05:30
|
|
|
Tty.with($stderr) do |stderr|
|
|
|
|
stderr.print "#{Tty.up}#{Tty.erase_line}" * 2
|
|
|
|
end
|
2020-09-03 00:41:16 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
puts JSON.generate(formulae_checked.compact)
|
2020-08-08 07:10:48 +05:30
|
|
|
end
|
|
|
|
|
2020-12-14 14:30:36 +01:00
|
|
|
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)
|
2020-12-12 11:36:43 -05:00
|
|
|
case formula_or_cask
|
|
|
|
when Formula
|
2020-12-14 14:30:36 +01:00
|
|
|
formula_name(formula_or_cask, full_name: full_name)
|
2020-12-12 11:36:43 -05:00
|
|
|
when Cask::Cask
|
2020-12-14 14:30:36 +01:00
|
|
|
cask_name(formula_or_cask, full_name: full_name)
|
2021-01-17 11:24:52 -08:00
|
|
|
else
|
|
|
|
T.absurd(formula_or_cask)
|
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) }
|
|
|
|
def cask_name(cask, full_name: false)
|
|
|
|
full_name ? cask.full_name : cask.token
|
2020-09-02 12:24:21 -07:00
|
|
|
end
|
|
|
|
|
2020-11-05 17:17:03 -05:00
|
|
|
# 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) }
|
|
|
|
def formula_name(formula, full_name: false)
|
|
|
|
full_name ? formula.full_name : formula.name
|
2020-08-08 07:10:48 +05:30
|
|
|
end
|
|
|
|
|
2021-01-17 22:45:55 -08:00
|
|
|
sig {
|
2021-01-04 13:41:19 -08:00
|
|
|
params(
|
|
|
|
formula_or_cask: T.any(Formula, Cask::Cask),
|
|
|
|
status_str: String,
|
|
|
|
messages: T.nilable(T::Array[String]),
|
|
|
|
full_name: T::Boolean,
|
|
|
|
verbose: T::Boolean,
|
|
|
|
).returns(Hash)
|
2021-01-17 22:45:55 -08:00
|
|
|
}
|
2020-12-14 14:30:36 +01:00
|
|
|
def status_hash(formula_or_cask, status_str, messages = nil, full_name: false, verbose: false)
|
2020-09-02 12:24:21 -07:00
|
|
|
formula = formula_or_cask if formula_or_cask.is_a?(Formula)
|
2020-12-12 11:36:43 -05:00
|
|
|
cask = formula_or_cask if formula_or_cask.is_a?(Cask::Cask)
|
2020-09-02 12:24:21 -07:00
|
|
|
|
2020-12-12 11:00:02 -05:00
|
|
|
status_hash = {}
|
2020-09-02 12:24:21 -07:00
|
|
|
if formula
|
2020-12-14 14:30:36 +01:00
|
|
|
status_hash[:formula] = formula_name(formula, full_name: full_name)
|
2020-12-12 11:36:43 -05:00
|
|
|
elsif cask
|
2020-12-14 14:30:36 +01:00
|
|
|
status_hash[:cask] = cask_name(formula_or_cask, full_name: full_name)
|
2020-09-02 12:24:21 -07:00
|
|
|
end
|
2020-12-12 11:00:02 -05:00
|
|
|
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: formula_or_cask.livecheckable?,
|
|
|
|
}
|
|
|
|
status_hash[:meta][:head_only] = true if formula&.head_only?
|
2020-08-08 07:10:48 +05:30
|
|
|
|
|
|
|
status_hash
|
|
|
|
end
|
|
|
|
|
2020-08-27 22:46:06 +05:30
|
|
|
# Formats and prints the livecheck result for a formula.
|
2021-01-04 13:41:19 -08:00
|
|
|
sig { params(info: Hash, verbose: T::Boolean).void }
|
2020-12-14 14:30:36 +01:00
|
|
|
def print_latest_version(info, verbose:)
|
2020-09-02 12:24:21 -07:00
|
|
|
formula_or_cask_s = "#{Tty.blue}#{info[:formula] || info[:cask]}#{Tty.reset}"
|
2020-12-14 14:30:36 +01:00
|
|
|
formula_or_cask_s += " (guessed)" if !info[:meta][:livecheckable] && verbose
|
2020-08-08 07:10:48 +05:30
|
|
|
|
|
|
|
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
|
|
|
|
|
2020-09-02 12:24:21 -07:00
|
|
|
puts "#{formula_or_cask_s} : #{current_s} ==> #{latest_s}"
|
2020-08-08 07:10:48 +05:30
|
|
|
end
|
|
|
|
|
2021-01-17 22:45:55 -08:00
|
|
|
sig {
|
2021-01-12 15:00:49 -05:00
|
|
|
params(
|
|
|
|
livecheck_url: T.any(String, Symbol),
|
|
|
|
formula_or_cask: T.any(Formula, Cask::Cask),
|
|
|
|
).returns(T.nilable(String))
|
2021-01-17 22:45:55 -08:00
|
|
|
}
|
2021-01-12 15:00:49 -05:00
|
|
|
def livecheck_url_to_string(livecheck_url, formula_or_cask)
|
|
|
|
case livecheck_url
|
|
|
|
when String
|
|
|
|
livecheck_url
|
|
|
|
when :url
|
|
|
|
formula_or_cask.url&.to_s if formula_or_cask.is_a?(Cask::Cask)
|
|
|
|
when :head, :stable
|
|
|
|
formula_or_cask.send(livecheck_url)&.url if formula_or_cask.is_a?(Formula)
|
|
|
|
when :homepage
|
|
|
|
formula_or_cask.homepage
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-09-03 20:43:21 -07:00
|
|
|
# Returns an Array containing the formula/cask URLs that can be used by livecheck.
|
2021-01-04 13:41:19 -08:00
|
|
|
sig { params(formula_or_cask: T.any(Formula, Cask::Cask)).returns(T::Array[String]) }
|
2020-09-03 20:43:21 -07:00
|
|
|
def checkable_urls(formula_or_cask)
|
2020-08-08 07:10:48 +05:30
|
|
|
urls = []
|
|
|
|
|
2020-09-03 20:43:21 -07:00
|
|
|
case formula_or_cask
|
|
|
|
when Formula
|
|
|
|
urls << formula_or_cask.head.url if formula_or_cask.head
|
|
|
|
if formula_or_cask.stable
|
|
|
|
urls << formula_or_cask.stable.url
|
|
|
|
urls.concat(formula_or_cask.stable.mirrors)
|
|
|
|
end
|
|
|
|
urls << formula_or_cask.homepage if formula_or_cask.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
|
2021-01-17 11:24:52 -08:00
|
|
|
else
|
|
|
|
T.absurd(formula_or_cask)
|
2020-09-03 20:43:21 -07:00
|
|
|
end
|
2020-08-08 07:10:48 +05:30
|
|
|
|
2020-09-02 12:24:21 -07:00
|
|
|
urls.compact
|
|
|
|
end
|
|
|
|
|
2020-08-27 22:46:06 +05:30
|
|
|
# Preprocesses and returns the URL used by livecheck.
|
2021-01-04 13:41:19 -08:00
|
|
|
sig { params(url: String).returns(String) }
|
2020-08-08 07:10:48 +05:30
|
|
|
def preprocess_url(url)
|
2020-11-26 10:31:38 -05:00
|
|
|
begin
|
|
|
|
uri = URI.parse url
|
|
|
|
rescue URI::InvalidURIError
|
|
|
|
return url
|
|
|
|
end
|
|
|
|
|
2021-01-04 13:41:19 -08:00
|
|
|
host = uri.host
|
|
|
|
path = uri.path
|
|
|
|
return url if host.nil? || path.nil?
|
|
|
|
|
|
|
|
host = "github.com" if host == "github.s3.amazonaws.com"
|
|
|
|
path = path.delete_prefix("/").delete_suffix(".git")
|
2020-11-11 02:32:44 +01:00
|
|
|
scheme = uri.scheme
|
2020-11-07 03:18:42 +01:00
|
|
|
|
2020-11-26 10:32:55 -05:00
|
|
|
if host.end_with?("github.com")
|
2020-11-11 02:32:44 +01:00
|
|
|
return url if path.match? %r{/releases/latest/?$}
|
2020-11-07 03:18:42 +01:00
|
|
|
|
2020-11-11 02:32:44 +01:00
|
|
|
owner, repo = path.delete_prefix("downloads/").split("/")
|
|
|
|
url = "#{scheme}://#{host}/#{owner}/#{repo}.git"
|
2020-11-26 10:32:55 -05:00
|
|
|
elsif host.end_with?(*GITEA_INSTANCES)
|
2020-11-07 03:18:42 +01:00
|
|
|
return url if path.match? %r{/releases/latest/?$}
|
|
|
|
|
|
|
|
owner, repo = path.split("/")
|
2020-11-11 02:32:44 +01:00
|
|
|
url = "#{scheme}://#{host}/#{owner}/#{repo}.git"
|
2020-11-26 10:32:55 -05:00
|
|
|
elsif host.end_with?(*GOGS_INSTANCES)
|
2020-11-11 02:32:44 +01:00
|
|
|
owner, repo = path.split("/")
|
|
|
|
url = "#{scheme}://#{host}/#{owner}/#{repo}.git"
|
|
|
|
# sourcehut
|
2020-11-26 10:32:55 -05:00
|
|
|
elsif host.end_with?("git.sr.ht")
|
2020-11-07 03:18:42 +01:00
|
|
|
owner, repo = path.split("/")
|
2020-11-11 02:32:44 +01:00
|
|
|
url = "#{scheme}://#{host}/#{owner}/#{repo}"
|
2020-11-26 10:33:20 -05:00
|
|
|
# GitLab (gitlab.com or self-hosted)
|
2020-11-11 02:32:44 +01:00
|
|
|
elsif path.include?("/-/archive/")
|
|
|
|
url = url.sub(%r{/-/archive/.*$}i, ".git")
|
2020-08-08 07:10:48 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
url
|
|
|
|
end
|
|
|
|
|
2020-08-27 22:46:06 +05:30
|
|
|
# Identifies the latest version of the formula and returns a Hash containing
|
|
|
|
# the version information. Returns nil if a latest version couldn't be found.
|
2021-01-17 22:45:55 -08:00
|
|
|
sig {
|
2021-01-04 13:41:19 -08:00
|
|
|
params(
|
|
|
|
formula_or_cask: T.any(Formula, Cask::Cask),
|
|
|
|
json: T::Boolean,
|
|
|
|
full_name: T::Boolean,
|
|
|
|
verbose: T::Boolean,
|
|
|
|
debug: T::Boolean,
|
|
|
|
).returns(T.nilable(Hash))
|
2021-01-17 22:45:55 -08:00
|
|
|
}
|
2020-12-14 14:30:36 +01:00
|
|
|
def latest_version(formula_or_cask, json: false, full_name: false, verbose: false, debug: false)
|
2020-09-02 12:24:21 -07:00
|
|
|
formula = formula_or_cask if formula_or_cask.is_a?(Formula)
|
2020-12-12 11:36:43 -05:00
|
|
|
cask = formula_or_cask if formula_or_cask.is_a?(Cask::Cask)
|
2020-09-02 12:24:21 -07:00
|
|
|
|
|
|
|
has_livecheckable = formula_or_cask.livecheckable?
|
|
|
|
livecheck = formula_or_cask.livecheck
|
2020-12-14 02:09:23 +01:00
|
|
|
livecheck_url = livecheck.url
|
2020-08-08 07:10:48 +05:30
|
|
|
livecheck_regex = livecheck.regex
|
|
|
|
livecheck_strategy = livecheck.strategy
|
|
|
|
|
2021-01-12 15:00:49 -05:00
|
|
|
livecheck_url_string = livecheck_url_to_string(livecheck_url, formula_or_cask)
|
|
|
|
|
|
|
|
urls = [livecheck_url_string] if livecheck_url_string
|
2020-09-02 12:24:21 -07:00
|
|
|
urls ||= checkable_urls(formula_or_cask)
|
2020-08-08 07:10:48 +05:30
|
|
|
|
2020-12-14 14:30:36 +01:00
|
|
|
if debug
|
2020-08-08 07:10:48 +05:30
|
|
|
puts
|
2020-09-02 12:24:21 -07:00
|
|
|
if formula
|
2020-12-14 14:30:36 +01:00
|
|
|
puts "Formula: #{formula_name(formula, full_name: full_name)}"
|
2020-09-02 12:24:21 -07:00
|
|
|
puts "Head only?: true" if formula.head_only?
|
2020-12-12 11:36:43 -05:00
|
|
|
elsif cask
|
2020-12-14 14:30:36 +01:00
|
|
|
puts "Cask: #{cask_name(formula_or_cask, full_name: full_name)}"
|
2020-09-02 12:24:21 -07:00
|
|
|
end
|
2020-08-08 07:10:48 +05:30
|
|
|
puts "Livecheckable?: #{has_livecheckable ? "Yes" : "No"}"
|
|
|
|
end
|
|
|
|
|
|
|
|
urls.each_with_index do |original_url, i|
|
2020-12-14 14:30:36 +01:00
|
|
|
if debug
|
2020-08-08 07:10:48 +05:30
|
|
|
puts
|
2021-01-12 15:00:49 -05:00
|
|
|
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
|
2020-08-08 07:10:48 +05:30
|
|
|
end
|
|
|
|
|
2020-12-05 11:44:28 -05:00
|
|
|
# Only preprocess the URL when it's appropriate
|
|
|
|
url = if STRATEGY_SYMBOLS_TO_SKIP_PREPROCESS_URL.include?(livecheck_strategy)
|
2020-08-08 07:10:48 +05:30
|
|
|
original_url
|
|
|
|
else
|
|
|
|
preprocess_url(original_url)
|
|
|
|
end
|
|
|
|
|
2020-12-05 11:49:47 -05:00
|
|
|
strategies = Strategy.from_url(
|
|
|
|
url,
|
|
|
|
livecheck_strategy: livecheck_strategy,
|
2020-12-14 02:09:23 +01:00
|
|
|
url_provided: livecheck_url.present?,
|
2020-12-05 11:49:47 -05:00
|
|
|
regex_provided: livecheck_regex.present?,
|
2020-12-14 02:16:08 +01:00
|
|
|
block_provided: livecheck.strategy_block.present?,
|
2020-12-05 11:49:47 -05:00
|
|
|
)
|
2020-08-08 07:10:48 +05:30
|
|
|
strategy = Strategy.from_symbol(livecheck_strategy)
|
|
|
|
strategy ||= strategies.first
|
2020-12-14 14:30:36 +01:00
|
|
|
strategy_name = livecheck_strategy_names[strategy]
|
2020-08-08 07:10:48 +05:30
|
|
|
|
2020-12-14 14:30:36 +01:00
|
|
|
if debug
|
2020-08-08 07:10:48 +05:30
|
|
|
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(", ")}"
|
2020-08-08 07:10:48 +05:30
|
|
|
end
|
|
|
|
puts "Strategy: #{strategy.blank? ? "None" : strategy_name}"
|
|
|
|
puts "Regex: #{livecheck_regex.inspect}" if livecheck_regex.present?
|
|
|
|
end
|
|
|
|
|
2020-12-14 02:09:23 +01:00
|
|
|
if livecheck_strategy == :page_match && (livecheck_regex.blank? && livecheck.strategy_block.blank?)
|
|
|
|
odebug "#{strategy_name} strategy requires a regex or block"
|
2020-08-08 07:10:48 +05:30
|
|
|
next
|
|
|
|
end
|
|
|
|
|
2020-12-18 22:11:36 -05:00
|
|
|
if livecheck_strategy.present? && livecheck_url.blank?
|
|
|
|
odebug "#{strategy_name} strategy requires a url"
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
2020-12-01 17:04:59 +00:00
|
|
|
if livecheck_strategy.present? && strategies.exclude?(strategy)
|
2020-08-08 07:10:48 +05:30
|
|
|
odebug "#{strategy_name} strategy does not apply to this URL"
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
|
|
|
next if strategy.blank?
|
|
|
|
|
2020-12-13 12:23:20 +01:00
|
|
|
strategy_data = strategy.find_versions(url, livecheck_regex, &livecheck.strategy_block)
|
2020-08-08 07:10:48 +05:30
|
|
|
match_version_map = strategy_data[:matches]
|
|
|
|
regex = strategy_data[:regex]
|
2021-01-04 13:41:19 -08:00
|
|
|
messages = strategy_data[:messages]
|
2020-08-08 07:10:48 +05:30
|
|
|
|
2021-01-04 13:41:19 -08:00
|
|
|
if messages.is_a?(Array) && match_version_map.blank?
|
|
|
|
puts messages unless json
|
2020-08-08 07:10:48 +05:30
|
|
|
next if i + 1 < urls.length
|
|
|
|
|
2021-01-04 13:41:19 -08:00
|
|
|
return status_hash(formula_or_cask, "error", messages, full_name: full_name, verbose: verbose)
|
2020-08-08 07:10:48 +05:30
|
|
|
end
|
|
|
|
|
2020-12-14 14:30:36 +01:00
|
|
|
if debug
|
2020-08-08 07:10:48 +05:30
|
|
|
puts "URL (strategy): #{strategy_data[:url]}" if strategy_data[:url] != url
|
2020-12-24 15:00:57 -05:00
|
|
|
puts "URL (final): #{strategy_data[:final_url]}" if strategy_data[:final_url]
|
2020-08-08 07:10:48 +05:30
|
|
|
puts "Regex (strategy): #{strategy_data[:regex].inspect}" if strategy_data[:regex] != livecheck_regex
|
2020-12-24 22:19:14 -05:00
|
|
|
puts "Cached?: Yes" if strategy_data[:cached] == true
|
2020-08-08 07:10:48 +05:30
|
|
|
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
|
|
|
|
|
2020-12-14 14:30:36 +01:00
|
|
|
if debug && match_version_map.present?
|
2020-08-08 07:10:48 +05:30
|
|
|
puts
|
|
|
|
puts "Matched Versions:"
|
|
|
|
|
2020-12-14 14:30:36 +01:00
|
|
|
if verbose
|
2020-08-08 07:10:48 +05:30
|
|
|
match_version_map.each do |match, version|
|
|
|
|
puts "#{match} => #{version.inspect}"
|
|
|
|
end
|
|
|
|
else
|
|
|
|
puts match_version_map.values.join(", ")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
next if match_version_map.blank?
|
|
|
|
|
|
|
|
version_info = {
|
2021-01-17 11:24:52 -08:00
|
|
|
latest: Version.new(match_version_map.values.max_by { |v| actual_version(formula_or_cask, v) }),
|
2020-08-08 07:10:48 +05:30
|
|
|
}
|
|
|
|
|
2020-12-14 14:30:36 +01:00
|
|
|
if json && verbose
|
2021-01-12 15:00:49 -05:00
|
|
|
version_info[:meta] = {}
|
|
|
|
|
|
|
|
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
|
2020-08-08 07:10:48 +05:30
|
|
|
version_info[:meta][:url][:processed] = url if url != original_url
|
|
|
|
version_info[:meta][:url][:strategy] = strategy_data[:url] if strategy_data[:url] != url
|
2020-12-22 22:46:52 -05:00
|
|
|
version_info[:meta][:url][:final] = strategy_data[:final_url] if strategy_data[:final_url]
|
2021-01-12 15:00:49 -05:00
|
|
|
|
|
|
|
version_info[:meta][:strategy] = strategy.present? ? strategy_name : nil
|
2020-12-14 14:30:36 +01:00
|
|
|
version_info[:meta][:strategies] = strategies.map { |s| livecheck_strategy_names[s] } if strategies.present?
|
2020-08-08 07:10:48 +05:30
|
|
|
version_info[:meta][:regex] = regex.inspect if regex.present?
|
2020-12-24 22:19:14 -05:00
|
|
|
version_info[:meta][:cached] = true if strategy_data[:cached] == true
|
2020-08-08 07:10:48 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
return version_info
|
|
|
|
end
|
|
|
|
|
|
|
|
nil
|
|
|
|
end
|
2021-01-17 11:24:52 -08:00
|
|
|
|
|
|
|
sig { params(formula_or_cask: T.any(Formula, Cask::Cask), version: Version).returns(Version) }
|
|
|
|
def actual_version(formula_or_cask, version)
|
|
|
|
case formula_or_cask
|
|
|
|
when Formula
|
|
|
|
version
|
|
|
|
when Cask::Cask
|
|
|
|
Version.new(Cask::DSL::Version.new(version.to_s).before_comma)
|
|
|
|
else
|
|
|
|
T.absurd(formula_or_cask)
|
|
|
|
end
|
|
|
|
end
|
2020-08-08 07:10:48 +05:30
|
|
|
end
|
|
|
|
end
|