Port Homebrew::DevCmd::BumpUnversionedCask
This commit is contained in:
parent
1097496a06
commit
c70dc040c6
@ -1,4 +1,4 @@
|
||||
# typed: true
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "timeout"
|
||||
@ -11,167 +11,168 @@ require "tap"
|
||||
require "unversioned_cask_checker"
|
||||
|
||||
module Homebrew
|
||||
extend SystemCommand::Mixin
|
||||
module DevCmd
|
||||
class BumpUnversionedCask < AbstractCommand
|
||||
include SystemCommand::Mixin
|
||||
|
||||
sig { returns(CLI::Parser) }
|
||||
def self.bump_unversioned_casks_args
|
||||
Homebrew::CLI::Parser.new do
|
||||
description <<~EOS
|
||||
Check all casks with unversioned URLs in a given <tap> for updates.
|
||||
EOS
|
||||
switch "-n", "--dry-run",
|
||||
description: "Do everything except caching state and opening pull requests."
|
||||
flag "--limit=",
|
||||
description: "Maximum runtime in minutes."
|
||||
flag "--state-file=",
|
||||
description: "File for caching state."
|
||||
cmd_args do
|
||||
description <<~EOS
|
||||
Check all casks with unversioned URLs in a given <tap> for updates.
|
||||
EOS
|
||||
switch "-n", "--dry-run",
|
||||
description: "Do everything except caching state and opening pull requests."
|
||||
flag "--limit=",
|
||||
description: "Maximum runtime in minutes."
|
||||
flag "--state-file=",
|
||||
description: "File for caching state."
|
||||
|
||||
named_args [:cask, :tap], min: 1, without_api: true
|
||||
end
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def self.bump_unversioned_casks
|
||||
args = bump_unversioned_casks_args.parse
|
||||
|
||||
Homebrew.install_bundler_gems!(groups: ["bump_unversioned_casks"])
|
||||
|
||||
state_file = if args.state_file.present?
|
||||
Pathname(args.state_file).expand_path
|
||||
else
|
||||
HOMEBREW_CACHE/"bump_unversioned_casks.json"
|
||||
end
|
||||
state_file.dirname.mkpath
|
||||
|
||||
state = state_file.exist? ? JSON.parse(state_file.read) : {}
|
||||
|
||||
casks = args.named.to_paths(only: :cask, recurse_tap: true).map { |path| Cask::CaskLoader.load(path) }
|
||||
|
||||
unversioned_casks = casks.select do |cask|
|
||||
cask.url&.unversioned? && !cask.livecheckable? && !cask.discontinued?
|
||||
end
|
||||
|
||||
ohai "Unversioned Casks: #{unversioned_casks.count} (#{state.size} cached)"
|
||||
|
||||
checked, unchecked = unversioned_casks.partition { |c| state.key?(c.full_name) }
|
||||
|
||||
queue = Queue.new
|
||||
|
||||
# Start with random casks which have not been checked.
|
||||
unchecked.shuffle.each do |c|
|
||||
queue.enq c
|
||||
end
|
||||
|
||||
# Continue with previously checked casks, ordered by when they were last checked.
|
||||
checked.sort_by { |c| state.dig(c.full_name, "check_time") }.each do |c|
|
||||
queue.enq c
|
||||
end
|
||||
|
||||
limit = args.limit.presence&.to_i
|
||||
end_time = Time.now + (limit * 60) if limit
|
||||
|
||||
until queue.empty? || (end_time && end_time < Time.now)
|
||||
cask = queue.deq
|
||||
|
||||
key = cask.full_name
|
||||
|
||||
new_state = bump_unversioned_cask(cask, state: state.fetch(key, {}), dry_run: args.dry_run?)
|
||||
|
||||
next unless new_state
|
||||
|
||||
state[key] = new_state
|
||||
|
||||
state_file.atomic_write JSON.pretty_generate(state) unless args.dry_run?
|
||||
end
|
||||
end
|
||||
|
||||
sig {
|
||||
params(cask: Cask::Cask, state: T::Hash[String, T.untyped], dry_run: T.nilable(T::Boolean))
|
||||
.returns(T.nilable(T::Hash[String, T.untyped]))
|
||||
}
|
||||
def self.bump_unversioned_cask(cask, state:, dry_run:)
|
||||
ohai "Checking #{cask.full_name}"
|
||||
|
||||
unversioned_cask_checker = UnversionedCaskChecker.new(cask)
|
||||
|
||||
if !unversioned_cask_checker.single_app_cask? &&
|
||||
!unversioned_cask_checker.single_pkg_cask? &&
|
||||
!unversioned_cask_checker.single_qlplugin_cask?
|
||||
opoo "Skipping, not a single-app or PKG cask."
|
||||
return
|
||||
end
|
||||
|
||||
last_check_time = state["check_time"]&.then { |t| Time.parse(t) }
|
||||
|
||||
check_time = Time.now
|
||||
if last_check_time && (check_time - last_check_time) / 3600 < 24
|
||||
opoo "Skipping, already checked within the last 24 hours."
|
||||
return
|
||||
end
|
||||
|
||||
last_sha256 = state["sha256"]
|
||||
last_time = state["time"]&.then { |t| Time.parse(t) }
|
||||
last_file_size = state["file_size"]
|
||||
|
||||
download = Cask::Download.new(cask)
|
||||
time, file_size = begin
|
||||
download.time_file_size
|
||||
rescue
|
||||
[nil, nil]
|
||||
end
|
||||
|
||||
if last_time != time || last_file_size != file_size
|
||||
sha256 = begin
|
||||
Timeout.timeout(5 * 60) do
|
||||
unversioned_cask_checker.installer.download.sha256
|
||||
end
|
||||
rescue => e
|
||||
onoe e
|
||||
named_args [:cask, :tap], min: 1, without_api: true
|
||||
end
|
||||
|
||||
if sha256.present? && last_sha256 != sha256
|
||||
version = begin
|
||||
Timeout.timeout(60) do
|
||||
unversioned_cask_checker.guess_cask_version
|
||||
end
|
||||
rescue Timeout::Error
|
||||
onoe "Timed out guessing version for cask '#{cask}'."
|
||||
sig { override.void }
|
||||
def run
|
||||
Homebrew.install_bundler_gems!(groups: ["bump_unversioned_casks"])
|
||||
|
||||
state_file = if args.state_file.present?
|
||||
Pathname(T.must(args.state_file)).expand_path
|
||||
else
|
||||
HOMEBREW_CACHE/"bump_unversioned_casks.json"
|
||||
end
|
||||
state_file.dirname.mkpath
|
||||
|
||||
state = state_file.exist? ? JSON.parse(state_file.read) : {}
|
||||
|
||||
casks = args.named.to_paths(only: :cask, recurse_tap: true).map { |path| Cask::CaskLoader.load(path) }
|
||||
|
||||
unversioned_casks = casks.select do |cask|
|
||||
cask.url&.unversioned? && !cask.livecheckable? && !cask.discontinued?
|
||||
end
|
||||
|
||||
if version
|
||||
if cask.version == version
|
||||
oh1 "Cask #{cask} is up-to-date at #{version}"
|
||||
else
|
||||
bump_cask_pr_args = [
|
||||
"bump-cask-pr",
|
||||
"--version", version.to_s,
|
||||
"--sha256", ":no_check",
|
||||
"--message", "Automatic update via `brew bump-unversioned-casks`.",
|
||||
cask.sourcefile_path
|
||||
]
|
||||
ohai "Unversioned Casks: #{unversioned_casks.count} (#{state.size} cached)"
|
||||
|
||||
if dry_run
|
||||
bump_cask_pr_args << "--dry-run"
|
||||
oh1 "Would bump #{cask} from #{cask.version} to #{version}"
|
||||
else
|
||||
oh1 "Bumping #{cask} from #{cask.version} to #{version}"
|
||||
checked, unchecked = unversioned_casks.partition { |c| state.key?(c.full_name) }
|
||||
|
||||
queue = Queue.new
|
||||
|
||||
# Start with random casks which have not been checked.
|
||||
unchecked.shuffle.each do |c|
|
||||
queue.enq c
|
||||
end
|
||||
|
||||
# Continue with previously checked casks, ordered by when they were last checked.
|
||||
checked.sort_by { |c| state.dig(c.full_name, "check_time") }.each do |c|
|
||||
queue.enq c
|
||||
end
|
||||
|
||||
limit = args.limit.presence&.to_i
|
||||
end_time = Time.now + (limit * 60) if limit
|
||||
|
||||
until queue.empty? || (end_time && end_time < Time.now)
|
||||
cask = queue.deq
|
||||
|
||||
key = cask.full_name
|
||||
|
||||
new_state = bump_unversioned_cask(cask, state: state.fetch(key, {}))
|
||||
|
||||
next unless new_state
|
||||
|
||||
state[key] = new_state
|
||||
|
||||
state_file.atomic_write JSON.pretty_generate(state) unless args.dry_run?
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
sig {
|
||||
params(cask: Cask::Cask, state: T::Hash[String, T.untyped])
|
||||
.returns(T.nilable(T::Hash[String, T.untyped]))
|
||||
}
|
||||
def bump_unversioned_cask(cask, state:)
|
||||
ohai "Checking #{cask.full_name}"
|
||||
|
||||
unversioned_cask_checker = UnversionedCaskChecker.new(cask)
|
||||
|
||||
if !unversioned_cask_checker.single_app_cask? &&
|
||||
!unversioned_cask_checker.single_pkg_cask? &&
|
||||
!unversioned_cask_checker.single_qlplugin_cask?
|
||||
opoo "Skipping, not a single-app or PKG cask."
|
||||
return
|
||||
end
|
||||
|
||||
last_check_time = state["check_time"]&.then { |t| Time.parse(t) }
|
||||
|
||||
check_time = Time.now
|
||||
if last_check_time && (check_time - last_check_time) / 3600 < 24
|
||||
opoo "Skipping, already checked within the last 24 hours."
|
||||
return
|
||||
end
|
||||
|
||||
last_sha256 = state["sha256"]
|
||||
last_time = state["time"]&.then { |t| Time.parse(t) }
|
||||
last_file_size = state["file_size"]
|
||||
|
||||
download = Cask::Download.new(cask)
|
||||
time, file_size = begin
|
||||
download.time_file_size
|
||||
rescue
|
||||
[nil, nil]
|
||||
end
|
||||
|
||||
if last_time != time || last_file_size != file_size
|
||||
sha256 = begin
|
||||
Timeout.timeout(5 * 60) do
|
||||
unversioned_cask_checker.installer.download.sha256
|
||||
end
|
||||
rescue => e
|
||||
onoe e
|
||||
end
|
||||
|
||||
if sha256.present? && last_sha256 != sha256
|
||||
version = begin
|
||||
Timeout.timeout(60) do
|
||||
unversioned_cask_checker.guess_cask_version
|
||||
end
|
||||
rescue Timeout::Error
|
||||
onoe "Timed out guessing version for cask '#{cask}'."
|
||||
end
|
||||
|
||||
begin
|
||||
system_command! HOMEBREW_BREW_FILE, args: bump_cask_pr_args
|
||||
rescue ErrorDuringExecution => e
|
||||
onoe e
|
||||
if version
|
||||
if cask.version == version
|
||||
oh1 "Cask #{cask} is up-to-date at #{version}"
|
||||
else
|
||||
bump_cask_pr_args = [
|
||||
"bump-cask-pr",
|
||||
"--version", version.to_s,
|
||||
"--sha256", ":no_check",
|
||||
"--message", "Automatic update via `brew bump-unversioned-casks`.",
|
||||
cask.sourcefile_path
|
||||
]
|
||||
|
||||
if args.dry_run?
|
||||
bump_cask_pr_args << "--dry-run"
|
||||
oh1 "Would bump #{cask} from #{cask.version} to #{version}"
|
||||
else
|
||||
oh1 "Bumping #{cask} from #{cask.version} to #{version}"
|
||||
end
|
||||
|
||||
begin
|
||||
system_command! HOMEBREW_BREW_FILE, args: bump_cask_pr_args
|
||||
rescue ErrorDuringExecution => e
|
||||
onoe e
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
{
|
||||
"sha256" => sha256,
|
||||
"check_time" => check_time.iso8601,
|
||||
"time" => time&.iso8601,
|
||||
"file_size" => file_size,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
{
|
||||
"sha256" => sha256,
|
||||
"check_time" => check_time.iso8601,
|
||||
"time" => time&.iso8601,
|
||||
"file_size" => file_size,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
@ -1,7 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "cmd/shared_examples/args_parse"
|
||||
require "dev-cmd/bump-unversioned-casks"
|
||||
|
||||
RSpec.describe "brew bump-unversioned-casks" do
|
||||
it_behaves_like "parseable arguments"
|
||||
RSpec.describe Homebrew::DevCmd::BumpUnversionedCask do
|
||||
it_behaves_like "parseable arguments", argv: ["foo"]
|
||||
end
|
||||
|
Loading…
x
Reference in New Issue
Block a user