Merge pull request #10924 from reitermarkus/timeouts-compat
Re-revert allowing timeouts for commands and downloads.
This commit is contained in:
commit
b1ad030853
@ -19,9 +19,10 @@ module Cask
|
||||
@quarantine = quarantine
|
||||
end
|
||||
|
||||
def fetch(verify_download_integrity: true)
|
||||
def fetch(quiet: nil, verify_download_integrity: true, timeout: nil)
|
||||
downloaded_path = begin
|
||||
downloader.fetch
|
||||
downloader.shutup! if quiet
|
||||
downloader.fetch(timeout: timeout)
|
||||
downloader.cached_location
|
||||
rescue => e
|
||||
error = CaskError.new("Download failed on Cask '#{cask}' with message: #{e}")
|
||||
@ -40,8 +41,8 @@ module Cask
|
||||
end
|
||||
end
|
||||
|
||||
def time_file_size
|
||||
downloader.resolved_time_file_size
|
||||
def time_file_size(timeout: nil)
|
||||
downloader.resolved_time_file_size(timeout: timeout)
|
||||
end
|
||||
|
||||
def clear_cache
|
||||
|
||||
@ -62,13 +62,15 @@ module Cask
|
||||
EOS
|
||||
end
|
||||
|
||||
def fetch
|
||||
sig { params(quiet: T.nilable(T::Boolean), timeout: T.nilable(T.any(Integer, Float))).void }
|
||||
def fetch(quiet: nil, timeout: nil)
|
||||
odebug "Cask::Installer#fetch"
|
||||
|
||||
verify_has_sha if require_sha? && !force?
|
||||
satisfy_dependencies
|
||||
|
||||
download
|
||||
download(quiet: quiet, timeout: timeout)
|
||||
|
||||
satisfy_dependencies
|
||||
end
|
||||
|
||||
def stage
|
||||
@ -162,9 +164,10 @@ module Cask
|
||||
@downloader ||= Download.new(@cask, quarantine: quarantine?)
|
||||
end
|
||||
|
||||
sig { returns(Pathname) }
|
||||
def download
|
||||
@download ||= downloader.fetch(verify_download_integrity: @verify_download_integrity)
|
||||
sig { params(quiet: T.nilable(T::Boolean), timeout: T.nilable(T.any(Integer, Float))).returns(Pathname) }
|
||||
def download(quiet: nil, timeout: nil)
|
||||
@download ||= downloader.fetch(quiet: quiet, verify_download_integrity: @verify_download_integrity,
|
||||
timeout: timeout)
|
||||
end
|
||||
|
||||
def verify_has_sha
|
||||
@ -179,7 +182,7 @@ module Cask
|
||||
|
||||
def primary_container
|
||||
@primary_container ||= begin
|
||||
downloaded_path = download
|
||||
downloaded_path = download(quiet: true)
|
||||
UnpackStrategy.detect(downloaded_path, type: @cask.container&.type, merge_xattrs: true)
|
||||
end
|
||||
end
|
||||
|
||||
4
Library/Homebrew/compat/early.rb
Normal file
4
Library/Homebrew/compat/early.rb
Normal file
@ -0,0 +1,4 @@
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "early/download_strategy"
|
||||
46
Library/Homebrew/compat/early/download_strategy.rb
Normal file
46
Library/Homebrew/compat/early/download_strategy.rb
Normal file
@ -0,0 +1,46 @@
|
||||
# typed: false
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AbstractDownloadStrategy
|
||||
module CompatFetch
|
||||
def fetch(timeout: nil)
|
||||
super()
|
||||
end
|
||||
end
|
||||
|
||||
module Compat_Fetch # rubocop:disable Naming/ClassAndModuleCamelCase
|
||||
def _fetch(*args, **options)
|
||||
options[:timeout] = nil unless options.key?(:timeout)
|
||||
|
||||
begin
|
||||
super
|
||||
rescue ArgumentError => e
|
||||
raise unless e.message.include?("timeout")
|
||||
|
||||
odeprecated "`def _fetch` in a subclass of `CurlDownloadStrategy`"
|
||||
options.delete(:timeout)
|
||||
super(*args, **options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class << self
|
||||
def method_added(method)
|
||||
if method == :fetch && instance_method(method).arity.zero?
|
||||
odeprecated "`def fetch` in a subclass of `#{self}`",
|
||||
"`def fetch(timeout: nil, **options)` and output a warning " \
|
||||
"when `options` contains new unhandled options"
|
||||
|
||||
class_eval do
|
||||
prepend CompatFetch
|
||||
end
|
||||
elsif method == :_fetch
|
||||
class_eval do
|
||||
prepend Compat_Fetch
|
||||
end
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -455,7 +455,7 @@ end
|
||||
class GitHubArtifactDownloadStrategy < AbstractFileDownloadStrategy
|
||||
extend T::Sig
|
||||
|
||||
def fetch
|
||||
def fetch(timeout: nil)
|
||||
ohai "Downloading #{url}"
|
||||
if cached_location.exist?
|
||||
puts "Already downloaded: #{cached_location}"
|
||||
@ -463,7 +463,8 @@ class GitHubArtifactDownloadStrategy < AbstractFileDownloadStrategy
|
||||
begin
|
||||
curl "--location", "--create-dirs", "--output", temporary_path, url,
|
||||
*meta.fetch(:curl_args, []),
|
||||
secrets: meta.fetch(:secrets, [])
|
||||
secrets: meta.fetch(:secrets, []),
|
||||
timeout: timeout
|
||||
rescue ErrorDuringExecution
|
||||
raise CurlDownloadStrategyError, url
|
||||
end
|
||||
|
||||
@ -15,6 +15,9 @@ require "utils/curl"
|
||||
|
||||
require "github_packages"
|
||||
|
||||
require "extend/time"
|
||||
using TimeRemaining
|
||||
|
||||
# @abstract Abstract superclass for all download strategies.
|
||||
#
|
||||
# @api private
|
||||
@ -35,7 +38,19 @@ class AbstractDownloadStrategy
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :cache, :cached_location, :url, :meta, :name, :version
|
||||
# The download URL.
|
||||
#
|
||||
# @api public
|
||||
sig { returns(String) }
|
||||
attr_reader :url
|
||||
|
||||
# Location of the cached download.
|
||||
#
|
||||
# @api public
|
||||
sig { returns(Pathname) }
|
||||
attr_reader :cached_location
|
||||
|
||||
attr_reader :cache, :meta, :name, :version
|
||||
|
||||
private :meta, :name, :version
|
||||
|
||||
@ -52,7 +67,7 @@ class AbstractDownloadStrategy
|
||||
# Download and cache the resource at {#cached_location}.
|
||||
#
|
||||
# @api public
|
||||
def fetch; end
|
||||
def fetch(timeout: nil); end
|
||||
|
||||
# Disable any output during downloading.
|
||||
#
|
||||
@ -108,6 +123,7 @@ class AbstractDownloadStrategy
|
||||
# Returns the most recent modified time for all files in the current working directory after stage.
|
||||
#
|
||||
# @api public
|
||||
sig { returns(Time) }
|
||||
def source_modified_time
|
||||
Pathname.pwd.to_enum(:find).select(&:file?).map(&:mtime).max
|
||||
end
|
||||
@ -176,18 +192,20 @@ class VCSDownloadStrategy < AbstractDownloadStrategy
|
||||
# Download and cache the repository at {#cached_location}.
|
||||
#
|
||||
# @api public
|
||||
def fetch
|
||||
def fetch(timeout: nil)
|
||||
end_time = Time.now + timeout if timeout
|
||||
|
||||
ohai "Cloning #{url}"
|
||||
|
||||
if cached_location.exist? && repo_valid?
|
||||
puts "Updating #{cached_location}"
|
||||
update
|
||||
update(timeout: timeout)
|
||||
elsif cached_location.exist?
|
||||
puts "Removing invalid repository from cache"
|
||||
clear_cache
|
||||
clone_repo
|
||||
clone_repo(timeout: end_time)
|
||||
else
|
||||
clone_repo
|
||||
clone_repo(timeout: end_time)
|
||||
end
|
||||
|
||||
version.update_commit(last_commit) if head?
|
||||
@ -214,10 +232,12 @@ class VCSDownloadStrategy < AbstractDownloadStrategy
|
||||
version.respond_to?(:head?) && version.head?
|
||||
end
|
||||
|
||||
# @!attribute [r] last_commit
|
||||
# Return last commit's unique identifier for the repository.
|
||||
# Return most recent modified timestamp unless overridden.
|
||||
#
|
||||
# @api public
|
||||
sig { returns(String) }
|
||||
def last_commit
|
||||
source_modified_time.to_i.to_s
|
||||
end
|
||||
@ -232,9 +252,11 @@ class VCSDownloadStrategy < AbstractDownloadStrategy
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def clone_repo; end
|
||||
sig { params(timeout: T.nilable(Time)).void }
|
||||
def clone_repo(timeout: nil); end
|
||||
|
||||
def update; end
|
||||
sig { params(timeout: T.nilable(Time)).void }
|
||||
def update(timeout: nil); end
|
||||
|
||||
def current_revision; end
|
||||
|
||||
@ -353,7 +375,9 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
|
||||
# Download and cache the file at {#cached_location}.
|
||||
#
|
||||
# @api public
|
||||
def fetch
|
||||
def fetch(timeout: nil)
|
||||
end_time = Time.now + timeout if timeout
|
||||
|
||||
download_lock = LockFile.new(temporary_path.basename)
|
||||
download_lock.lock
|
||||
|
||||
@ -364,7 +388,7 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
|
||||
|
||||
ohai "Downloading #{url}"
|
||||
|
||||
resolved_url, _, url_time, = resolve_url_basename_time_file_size(url)
|
||||
resolved_url, _, url_time, = resolve_url_basename_time_file_size(url, timeout: end_time&.remaining!)
|
||||
|
||||
fresh = if cached_location.exist? && url_time
|
||||
url_time <= cached_location.mtime
|
||||
@ -378,7 +402,7 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
|
||||
puts "Already downloaded: #{cached_location}"
|
||||
else
|
||||
begin
|
||||
_fetch(url: url, resolved_url: resolved_url)
|
||||
_fetch(url: url, resolved_url: resolved_url, timeout: end_time&.remaining!)
|
||||
rescue ErrorDuringExecution
|
||||
raise CurlDownloadStrategyError, url
|
||||
end
|
||||
@ -395,6 +419,8 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
|
||||
|
||||
puts "Trying a mirror..."
|
||||
retry
|
||||
rescue Timeout::Error => e
|
||||
raise Timeout::Error, "Timed out downloading #{self.url}: #{e}"
|
||||
end
|
||||
ensure
|
||||
download_lock&.unlock
|
||||
@ -406,19 +432,19 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
|
||||
rm_rf(temporary_path)
|
||||
end
|
||||
|
||||
def resolved_time_file_size
|
||||
_, _, time, file_size = resolve_url_basename_time_file_size(url)
|
||||
def resolved_time_file_size(timeout: nil)
|
||||
_, _, time, file_size = resolve_url_basename_time_file_size(url, timeout: timeout)
|
||||
[time, file_size]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def resolved_url_and_basename
|
||||
resolved_url, basename, = resolve_url_basename_time_file_size(url)
|
||||
def resolved_url_and_basename(timeout: nil)
|
||||
resolved_url, basename, = resolve_url_basename_time_file_size(url, timeout: nil)
|
||||
[resolved_url, basename]
|
||||
end
|
||||
|
||||
def resolve_url_basename_time_file_size(url)
|
||||
def resolve_url_basename_time_file_size(url, timeout: nil)
|
||||
@resolved_info_cache ||= {}
|
||||
return @resolved_info_cache[url] if @resolved_info_cache.include?(url)
|
||||
|
||||
@ -426,7 +452,7 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
|
||||
url = url.sub(%r{^((ht|f)tps?://)?}, "#{domain.chomp("/")}/")
|
||||
end
|
||||
|
||||
out, _, status = curl_output("--location", "--silent", "--head", "--request", "GET", url.to_s)
|
||||
out, _, status= curl_output("--location", "--silent", "--head", "--request", "GET", url.to_s, timeout: timeout)
|
||||
|
||||
lines = status.success? ? out.lines.map(&:chomp) : []
|
||||
|
||||
@ -485,7 +511,7 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
|
||||
@resolved_info_cache[url] = [redirect_url, basename, time, file_size]
|
||||
end
|
||||
|
||||
def _fetch(url:, resolved_url:)
|
||||
def _fetch(url:, resolved_url:, timeout:)
|
||||
ohai "Downloading from #{resolved_url}" if url != resolved_url
|
||||
|
||||
if Homebrew::EnvConfig.no_insecure_redirect? &&
|
||||
@ -494,7 +520,7 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
|
||||
raise CurlDownloadStrategyError, url
|
||||
end
|
||||
|
||||
curl_download resolved_url, to: temporary_path
|
||||
curl_download resolved_url, to: temporary_path, timeout: timeout
|
||||
end
|
||||
|
||||
# Curl options to be always passed to curl,
|
||||
@ -567,9 +593,9 @@ class CurlApacheMirrorDownloadStrategy < CurlDownloadStrategy
|
||||
@combined_mirrors = [*@mirrors, *backup_mirrors]
|
||||
end
|
||||
|
||||
def resolve_url_basename_time_file_size(url)
|
||||
def resolve_url_basename_time_file_size(url, timeout: nil)
|
||||
if url == self.url
|
||||
super("#{apache_mirrors["preferred"]}#{apache_mirrors["path_info"]}")
|
||||
super("#{apache_mirrors["preferred"]}#{apache_mirrors["path_info"]}", timeout: timeout)
|
||||
else
|
||||
super
|
||||
end
|
||||
@ -592,7 +618,7 @@ end
|
||||
class CurlPostDownloadStrategy < CurlDownloadStrategy
|
||||
private
|
||||
|
||||
def _fetch(url:, resolved_url:)
|
||||
def _fetch(url:, resolved_url:, timeout:)
|
||||
args = if meta.key?(:data)
|
||||
escape_data = ->(d) { ["-d", URI.encode_www_form([d])] }
|
||||
[url, *meta[:data].flat_map(&escape_data)]
|
||||
@ -601,7 +627,7 @@ class CurlPostDownloadStrategy < CurlDownloadStrategy
|
||||
query.nil? ? [url, "-X", "POST"] : [url, "-d", query]
|
||||
end
|
||||
|
||||
curl_download(*args, to: temporary_path)
|
||||
curl_download(*args, to: temporary_path, timeout: timeout)
|
||||
end
|
||||
end
|
||||
|
||||
@ -641,14 +667,15 @@ class SubversionDownloadStrategy < VCSDownloadStrategy
|
||||
# Download and cache the repository at {#cached_location}.
|
||||
#
|
||||
# @api public
|
||||
def fetch
|
||||
def fetch(timeout: nil)
|
||||
if @url.chomp("/") != repo_url || !silent_command("svn", args: ["switch", @url, cached_location]).success?
|
||||
clear_cache
|
||||
end
|
||||
super
|
||||
end
|
||||
|
||||
# (see AbstractDownloadStrategy#source_modified_time)
|
||||
# @see AbstractDownloadStrategy#source_modified_time
|
||||
# @api public
|
||||
sig { returns(Time) }
|
||||
def source_modified_time
|
||||
time = if Version.create(Utils::Svn.version) >= Version.create("1.9")
|
||||
@ -661,7 +688,9 @@ class SubversionDownloadStrategy < VCSDownloadStrategy
|
||||
Time.parse time
|
||||
end
|
||||
|
||||
# (see VCSDownloadStrategy#source_modified_time)
|
||||
# @see VCSDownloadStrategy#last_commit
|
||||
# @api public
|
||||
sig { returns(String) }
|
||||
def last_commit
|
||||
out, = silent_command("svn", args: ["info", "--show-item", "revision"], chdir: cached_location)
|
||||
out.strip
|
||||
@ -682,7 +711,11 @@ class SubversionDownloadStrategy < VCSDownloadStrategy
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_repo(target, url, revision = nil, ignore_externals: false)
|
||||
sig {
|
||||
params(target: Pathname, url: String, revision: T.nilable(String), ignore_externals: T::Boolean,
|
||||
timeout: T.nilable(Time)).void
|
||||
}
|
||||
def fetch_repo(target, url, revision = nil, ignore_externals: false, timeout: nil)
|
||||
# Use "svn update" when the repository already exists locally.
|
||||
# This saves on bandwidth and will have a similar effect to verifying the
|
||||
# cache as it will make any changes to get the right revision.
|
||||
@ -702,9 +735,9 @@ class SubversionDownloadStrategy < VCSDownloadStrategy
|
||||
end
|
||||
|
||||
if target.directory?
|
||||
command!("svn", args: ["update", *args], chdir: target.to_s)
|
||||
command! "svn", args: ["update", *args], chdir: target.to_s, timeout: timeout&.remaining
|
||||
else
|
||||
command!("svn", args: ["checkout", url, target, *args])
|
||||
command! "svn", args: ["checkout", url, target, *args], timeout: timeout&.remaining
|
||||
end
|
||||
end
|
||||
|
||||
@ -717,20 +750,22 @@ class SubversionDownloadStrategy < VCSDownloadStrategy
|
||||
(cached_location/".svn").directory?
|
||||
end
|
||||
|
||||
def clone_repo
|
||||
sig { params(timeout: T.nilable(Time)).void }
|
||||
def clone_repo(timeout: nil)
|
||||
case @ref_type
|
||||
when :revision
|
||||
fetch_repo cached_location, @url, @ref
|
||||
fetch_repo cached_location, @url, @ref, timeout: timeout
|
||||
when :revisions
|
||||
# nil is OK for main_revision, as fetch_repo will then get latest
|
||||
main_revision = @ref[:trunk]
|
||||
fetch_repo cached_location, @url, main_revision, ignore_externals: true
|
||||
fetch_repo cached_location, @url, main_revision, ignore_externals: true, timeout: timeout
|
||||
|
||||
externals do |external_name, external_url|
|
||||
fetch_repo cached_location/external_name, external_url, @ref[external_name], ignore_externals: true
|
||||
fetch_repo cached_location/external_name, external_url, @ref[external_name], ignore_externals: true,
|
||||
timeout: timeout
|
||||
end
|
||||
else
|
||||
fetch_repo cached_location, @url
|
||||
fetch_repo cached_location, @url, timeout: timeout
|
||||
end
|
||||
end
|
||||
alias update clone_repo
|
||||
@ -754,14 +789,17 @@ class GitDownloadStrategy < VCSDownloadStrategy
|
||||
@shallow = meta.fetch(:shallow, true)
|
||||
end
|
||||
|
||||
# (see AbstractDownloadStrategy#source_modified_time)
|
||||
# @see AbstractDownloadStrategy#source_modified_time
|
||||
# @api public
|
||||
sig { returns(Time) }
|
||||
def source_modified_time
|
||||
out, = silent_command("git", args: ["--git-dir", git_dir, "show", "-s", "--format=%cD"])
|
||||
Time.parse(out)
|
||||
end
|
||||
|
||||
# (see VCSDownloadStrategy#source_modified_time)
|
||||
# @see VCSDownloadStrategy#last_commit
|
||||
sig { returns(String) }
|
||||
# @api public
|
||||
def last_commit
|
||||
out, = silent_command("git", args: ["--git-dir", git_dir, "rev-parse", "--short=7", "HEAD"])
|
||||
out.chomp
|
||||
@ -779,12 +817,13 @@ class GitDownloadStrategy < VCSDownloadStrategy
|
||||
0
|
||||
end
|
||||
|
||||
def update
|
||||
sig { params(timeout: T.nilable(Time)).void }
|
||||
def update(timeout: nil)
|
||||
config_repo
|
||||
update_repo
|
||||
checkout
|
||||
update_repo(timeout: timeout)
|
||||
checkout(timeout: timeout)
|
||||
reset
|
||||
update_submodules if submodules?
|
||||
update_submodules(timeout: timeout) if submodules?
|
||||
end
|
||||
|
||||
def shallow_clone?
|
||||
@ -845,6 +884,7 @@ class GitDownloadStrategy < VCSDownloadStrategy
|
||||
end
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def config_repo
|
||||
command! "git",
|
||||
args: ["config", "remote.origin.url", @url],
|
||||
@ -857,35 +897,42 @@ class GitDownloadStrategy < VCSDownloadStrategy
|
||||
chdir: cached_location
|
||||
end
|
||||
|
||||
def update_repo
|
||||
sig { params(timeout: T.nilable(Time)).void }
|
||||
def update_repo(timeout: nil)
|
||||
return if @ref_type != :branch && ref?
|
||||
|
||||
if !shallow_clone? && shallow_dir?
|
||||
command! "git",
|
||||
args: ["fetch", "origin", "--unshallow"],
|
||||
chdir: cached_location
|
||||
args: ["fetch", "origin", "--unshallow"],
|
||||
chdir: cached_location,
|
||||
timeout: timeout&.remaining
|
||||
else
|
||||
command! "git",
|
||||
args: ["fetch", "origin"],
|
||||
chdir: cached_location
|
||||
args: ["fetch", "origin"],
|
||||
chdir: cached_location,
|
||||
timeout: timeout&.remaining
|
||||
end
|
||||
end
|
||||
|
||||
def clone_repo
|
||||
command! "git", args: clone_args
|
||||
sig { params(timeout: T.nilable(Time)).void }
|
||||
def clone_repo(timeout: nil)
|
||||
command! "git", args: clone_args, timeout: timeout&.remaining
|
||||
|
||||
command! "git",
|
||||
args: ["config", "homebrew.cacheversion", cache_version],
|
||||
chdir: cached_location
|
||||
checkout
|
||||
update_submodules if submodules?
|
||||
args: ["config", "homebrew.cacheversion", cache_version],
|
||||
chdir: cached_location,
|
||||
timeout: timeout&.remaining
|
||||
checkout(timeout: timeout)
|
||||
update_submodules(timeout: timeout) if submodules?
|
||||
end
|
||||
|
||||
def checkout
|
||||
sig { params(timeout: T.nilable(Time)).void }
|
||||
def checkout(timeout: nil)
|
||||
ohai "Checking out #{@ref_type} #{@ref}" if @ref_type && @ref
|
||||
command! "git", args: ["checkout", "-f", @ref, "--"], chdir: cached_location
|
||||
command! "git", args: ["checkout", "-f", @ref, "--"], chdir: cached_location, timeout: timeout&.remaining
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def reset
|
||||
ref = case @ref_type
|
||||
when :branch
|
||||
@ -899,13 +946,16 @@ class GitDownloadStrategy < VCSDownloadStrategy
|
||||
chdir: cached_location
|
||||
end
|
||||
|
||||
def update_submodules
|
||||
sig { params(timeout: T.nilable(Time)).void }
|
||||
def update_submodules(timeout: nil)
|
||||
command! "git",
|
||||
args: ["submodule", "foreach", "--recursive", "git submodule sync"],
|
||||
chdir: cached_location
|
||||
args: ["submodule", "foreach", "--recursive", "git submodule sync"],
|
||||
chdir: cached_location,
|
||||
timeout: timeout&.remaining
|
||||
command! "git",
|
||||
args: ["submodule", "update", "--init", "--recursive"],
|
||||
chdir: cached_location
|
||||
args: ["submodule", "update", "--init", "--recursive"],
|
||||
chdir: cached_location,
|
||||
timeout: timeout&.remaining
|
||||
fix_absolute_submodule_gitdir_references!
|
||||
end
|
||||
|
||||
@ -1022,7 +1072,8 @@ class CVSDownloadStrategy < VCSDownloadStrategy
|
||||
end
|
||||
end
|
||||
|
||||
# (see AbstractDownloadStrategy#source_modified_time)
|
||||
# @see AbstractDownloadStrategy#source_modified_time
|
||||
# @api public
|
||||
sig { returns(Time) }
|
||||
def source_modified_time
|
||||
# Filter CVS's files because the timestamp for each of them is the moment
|
||||
@ -1057,19 +1108,23 @@ class CVSDownloadStrategy < VCSDownloadStrategy
|
||||
"-Q" unless verbose?
|
||||
end
|
||||
|
||||
def clone_repo
|
||||
sig { params(timeout: T.nilable(Time)).void }
|
||||
def clone_repo(timeout: nil)
|
||||
# Login is only needed (and allowed) with pserver; skip for anoncvs.
|
||||
command! "cvs", args: [*quiet_flag, "-d", @url, "login"] if @url.include? "pserver"
|
||||
command! "cvs", args: [*quiet_flag, "-d", @url, "login"], timeout: timeout&.remaining if @url.include? "pserver"
|
||||
|
||||
command! "cvs",
|
||||
args: [*quiet_flag, "-d", @url, "checkout", "-d", cached_location.basename, @module],
|
||||
chdir: cached_location.dirname
|
||||
args: [*quiet_flag, "-d", @url, "checkout", "-d", cached_location.basename, @module],
|
||||
chdir: cached_location.dirname,
|
||||
timeout: timeout&.remaining
|
||||
end
|
||||
|
||||
def update
|
||||
sig { params(timeout: T.nilable(Time)).void }
|
||||
def update(timeout: nil)
|
||||
command! "cvs",
|
||||
args: [*quiet_flag, "update"],
|
||||
chdir: cached_location
|
||||
args: [*quiet_flag, "update"],
|
||||
chdir: cached_location,
|
||||
timeout: timeout&.remaining
|
||||
end
|
||||
|
||||
def split_url(in_url)
|
||||
@ -1091,7 +1146,8 @@ class MercurialDownloadStrategy < VCSDownloadStrategy
|
||||
@url = @url.sub(%r{^hg://}, "")
|
||||
end
|
||||
|
||||
# (see AbstractDownloadStrategy#source_modified_time)
|
||||
# @see AbstractDownloadStrategy#source_modified_time
|
||||
# @api public
|
||||
sig { returns(Time) }
|
||||
def source_modified_time
|
||||
out, = silent_command("hg",
|
||||
@ -1100,7 +1156,9 @@ class MercurialDownloadStrategy < VCSDownloadStrategy
|
||||
Time.parse(out)
|
||||
end
|
||||
|
||||
# (see VCSDownloadStrategy#source_modified_time)
|
||||
# @see VCSDownloadStrategy#last_commit
|
||||
# @api public
|
||||
sig { returns(String) }
|
||||
def last_commit
|
||||
out, = silent_command("hg", args: ["parent", "--template", "{node|short}", "-R", cached_location])
|
||||
out.chomp
|
||||
@ -1121,12 +1179,14 @@ class MercurialDownloadStrategy < VCSDownloadStrategy
|
||||
(cached_location/".hg").directory?
|
||||
end
|
||||
|
||||
def clone_repo
|
||||
command! "hg", args: ["clone", @url, cached_location]
|
||||
sig { params(timeout: T.nilable(Time)).void }
|
||||
def clone_repo(timeout: nil)
|
||||
command! "hg", args: ["clone", @url, cached_location], timeout: timeout&.remaining
|
||||
end
|
||||
|
||||
def update
|
||||
command! "hg", args: ["--cwd", cached_location, "pull", "--update"]
|
||||
sig { params(timeout: T.nilable(Time)).void }
|
||||
def update(timeout: nil)
|
||||
command! "hg", args: ["--cwd", cached_location, "pull", "--update"], timeout: timeout&.remaining
|
||||
|
||||
update_args = if @ref_type && @ref
|
||||
ohai "Checking out #{@ref_type} #{@ref}"
|
||||
@ -1135,7 +1195,7 @@ class MercurialDownloadStrategy < VCSDownloadStrategy
|
||||
["--clean"]
|
||||
end
|
||||
|
||||
command! "hg", args: ["--cwd", cached_location, "update", *update_args]
|
||||
command! "hg", args: ["--cwd", cached_location, "update", *update_args], timeout: timeout&.remaining
|
||||
end
|
||||
end
|
||||
|
||||
@ -1150,7 +1210,8 @@ class BazaarDownloadStrategy < VCSDownloadStrategy
|
||||
@url.sub!(%r{^bzr://}, "")
|
||||
end
|
||||
|
||||
# (see AbstractDownloadStrategy#source_modified_time)
|
||||
# @see AbstractDownloadStrategy#source_modified_time
|
||||
# @api public
|
||||
sig { returns(Time) }
|
||||
def source_modified_time
|
||||
out, = silent_command("bzr", args: ["log", "-l", "1", "--timezone=utc", cached_location])
|
||||
@ -1160,7 +1221,9 @@ class BazaarDownloadStrategy < VCSDownloadStrategy
|
||||
Time.parse(timestamp)
|
||||
end
|
||||
|
||||
# (see VCSDownloadStrategy#source_modified_time)
|
||||
# @see VCSDownloadStrategy#last_commit
|
||||
# @api public
|
||||
sig { returns(String) }
|
||||
def last_commit
|
||||
out, = silent_command("bzr", args: ["revno", cached_location])
|
||||
out.chomp
|
||||
@ -1184,16 +1247,20 @@ class BazaarDownloadStrategy < VCSDownloadStrategy
|
||||
(cached_location/".bzr").directory?
|
||||
end
|
||||
|
||||
def clone_repo
|
||||
sig { params(timeout: T.nilable(Time)).void }
|
||||
def clone_repo(timeout: nil)
|
||||
# "lightweight" means history-less
|
||||
command! "bzr",
|
||||
args: ["checkout", "--lightweight", @url, cached_location]
|
||||
args: ["checkout", "--lightweight", @url, cached_location],
|
||||
timeout: timeout&.remaining
|
||||
end
|
||||
|
||||
def update
|
||||
sig { params(timeout: T.nilable(Time)).void }
|
||||
def update(timeout: nil)
|
||||
command! "bzr",
|
||||
args: ["update"],
|
||||
chdir: cached_location
|
||||
args: ["update"],
|
||||
chdir: cached_location,
|
||||
timeout: timeout&.remaining
|
||||
end
|
||||
end
|
||||
|
||||
@ -1208,14 +1275,17 @@ class FossilDownloadStrategy < VCSDownloadStrategy
|
||||
@url = @url.sub(%r{^fossil://}, "")
|
||||
end
|
||||
|
||||
# (see AbstractDownloadStrategy#source_modified_time)
|
||||
# @see AbstractDownloadStrategy#source_modified_time
|
||||
# @api public
|
||||
sig { returns(Time) }
|
||||
def source_modified_time
|
||||
out, = silent_command("fossil", args: ["info", "tip", "-R", cached_location])
|
||||
Time.parse(out[/^uuid: +\h+ (.+)$/, 1])
|
||||
end
|
||||
|
||||
# (see VCSDownloadStrategy#source_modified_time)
|
||||
# @see VCSDownloadStrategy#last_commit
|
||||
# @api public
|
||||
sig { returns(String) }
|
||||
def last_commit
|
||||
out, = silent_command("fossil", args: ["info", "tip", "-R", cached_location])
|
||||
out[/^uuid: +(\h+) .+$/, 1]
|
||||
@ -1236,12 +1306,14 @@ class FossilDownloadStrategy < VCSDownloadStrategy
|
||||
"fossil"
|
||||
end
|
||||
|
||||
def clone_repo
|
||||
silent_command!("fossil", args: ["clone", @url, cached_location])
|
||||
sig { params(timeout: T.nilable(Time)).void }
|
||||
def clone_repo(timeout: nil)
|
||||
silent_command! "fossil", args: ["clone", @url, cached_location], timeout: timeout&.remaining
|
||||
end
|
||||
|
||||
def update
|
||||
silent_command!("fossil", args: ["pull", "-R", cached_location])
|
||||
sig { params(timeout: T.nilable(Time)).void }
|
||||
def update(timeout: nil)
|
||||
silent_command! "fossil", args: ["pull", "-R", cached_location], timeout: timeout&.remaining
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "context"
|
||||
require "resource"
|
||||
require "metafiles"
|
||||
|
||||
|
||||
18
Library/Homebrew/extend/time.rb
Normal file
18
Library/Homebrew/extend/time.rb
Normal file
@ -0,0 +1,18 @@
|
||||
# typed: false
|
||||
# frozen_string_literal: true
|
||||
|
||||
module TimeRemaining
|
||||
refine Time do
|
||||
def remaining
|
||||
[0, self - Time.now].max
|
||||
end
|
||||
|
||||
def remaining!
|
||||
r = remaining
|
||||
|
||||
raise Timeout::Error if r <= 0
|
||||
|
||||
r
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -4,6 +4,7 @@
|
||||
require_relative "load_path"
|
||||
|
||||
require "English"
|
||||
require "fileutils"
|
||||
require "json"
|
||||
require "json/add/exception"
|
||||
require "pathname"
|
||||
@ -39,8 +40,6 @@ ActiveSupport::Inflector.inflections(:en) do |inflect|
|
||||
inflect.irregular "it", "they"
|
||||
end
|
||||
|
||||
require "utils/sorbet"
|
||||
|
||||
HOMEBREW_BOTTLE_DEFAULT_DOMAIN = ENV["HOMEBREW_BOTTLE_DEFAULT_DOMAIN"]
|
||||
HOMEBREW_BREW_DEFAULT_GIT_REMOTE = ENV["HOMEBREW_BREW_DEFAULT_GIT_REMOTE"]
|
||||
HOMEBREW_CORE_DEFAULT_GIT_REMOTE = ENV["HOMEBREW_CORE_DEFAULT_GIT_REMOTE"]
|
||||
@ -73,10 +72,11 @@ HOMEBREW_PULL_OR_COMMIT_URL_REGEX =
|
||||
%r[https://github\.com/([\w-]+)/([\w-]+)?/(?:pull/(\d+)|commit/[0-9a-fA-F]{4,40})].freeze
|
||||
HOMEBREW_BOTTLES_EXTNAME_REGEX = /\.([a-z0-9_]+)\.bottle\.(?:(\d+)\.)?tar\.gz$/.freeze
|
||||
|
||||
require "fileutils"
|
||||
require "utils/sorbet"
|
||||
|
||||
require "os"
|
||||
require "env_config"
|
||||
require "compat/early" unless Homebrew::EnvConfig.no_compat?
|
||||
require "os"
|
||||
require "messages"
|
||||
|
||||
module Homebrew
|
||||
@ -152,7 +152,7 @@ require "official_taps"
|
||||
require "tap"
|
||||
require "tap_constants"
|
||||
|
||||
require "compat" unless Homebrew::EnvConfig.no_compat?
|
||||
|
||||
# Enables `patchelf.rb` write support.
|
||||
HOMEBREW_PATCHELF_RB_WRITE = ENV["HOMEBREW_NO_PATCHELF_RB_WRITE"].blank?.freeze
|
||||
|
||||
require "compat/late" unless Homebrew::EnvConfig.no_compat?
|
||||
|
||||
@ -28,4 +28,16 @@ when "HashValidator"
|
||||
def assert_valid_keys!(*valid_keys); end
|
||||
end
|
||||
RUBY
|
||||
when "TimeRemaining"
|
||||
puts <<-RUBY
|
||||
# typed: strict
|
||||
|
||||
class ::Time
|
||||
sig { returns(T.any(Integer, Float)) }
|
||||
def remaining; end
|
||||
|
||||
sig { returns(T.any(Integer, Float)) }
|
||||
def remaining!; end
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
|
||||
@ -10,13 +10,15 @@ require "extend/io"
|
||||
require "extend/predicable"
|
||||
require "extend/hash_validator"
|
||||
|
||||
require "extend/time"
|
||||
|
||||
# Class for running sub-processes and capturing their output and exit status.
|
||||
#
|
||||
# @api private
|
||||
class SystemCommand
|
||||
extend T::Sig
|
||||
|
||||
using HashValidator
|
||||
using TimeRemaining
|
||||
|
||||
# Helper functions for calling {SystemCommand.run}.
|
||||
module Mixin
|
||||
@ -78,10 +80,24 @@ class SystemCommand
|
||||
verbose: T.nilable(T::Boolean),
|
||||
secrets: T.any(String, T::Array[String]),
|
||||
chdir: T.any(String, Pathname),
|
||||
timeout: T.nilable(T.any(Integer, Float)),
|
||||
).void
|
||||
}
|
||||
def initialize(executable, args: [], sudo: false, env: {}, input: [], must_succeed: false,
|
||||
print_stdout: false, print_stderr: true, debug: nil, verbose: nil, secrets: [], chdir: T.unsafe(nil))
|
||||
def initialize(
|
||||
executable,
|
||||
args: [],
|
||||
sudo: false,
|
||||
env: {},
|
||||
input: [],
|
||||
must_succeed: false,
|
||||
print_stdout: false,
|
||||
print_stderr: true,
|
||||
debug: nil,
|
||||
verbose: false,
|
||||
secrets: [],
|
||||
chdir: T.unsafe(nil),
|
||||
timeout: nil
|
||||
)
|
||||
require "extend/ENV"
|
||||
@executable = executable
|
||||
@args = args
|
||||
@ -100,6 +116,7 @@ class SystemCommand
|
||||
@verbose = verbose
|
||||
@secrets = (Array(secrets) + ENV.sensitive_environment.values).uniq
|
||||
@chdir = chdir
|
||||
@timeout = timeout
|
||||
end
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
@ -197,26 +214,31 @@ class SystemCommand
|
||||
|
||||
sig { params(sources: T::Array[IO], _block: T.proc.params(type: Symbol, line: String).void).void }
|
||||
def each_line_from(sources, &_block)
|
||||
sources_remaining = sources.dup
|
||||
while sources_remaining.present?
|
||||
readable_sources, = IO.select(sources_remaining)
|
||||
readable_sources = T.must(readable_sources)
|
||||
end_time = Time.now + @timeout if @timeout
|
||||
|
||||
break if readable_sources.empty?
|
||||
sources = {
|
||||
sources[0] => :stdout,
|
||||
sources[1] => :stderr,
|
||||
}
|
||||
|
||||
readable_sources.each do |source|
|
||||
loop do
|
||||
readable_sources, = IO.select(sources.keys, [], [], end_time&.remaining!)
|
||||
raise Timeout::Error if readable_sources.nil?
|
||||
|
||||
break if readable_sources.none? do |source|
|
||||
line = source.readline_nonblock || ""
|
||||
type = (source == sources[0]) ? :stdout : :stderr
|
||||
yield(type, line)
|
||||
yield(sources.fetch(source), line)
|
||||
true
|
||||
rescue EOFError
|
||||
source.close_read
|
||||
sources_remaining.delete(source)
|
||||
sources.delete(source)
|
||||
sources.any?
|
||||
rescue IO::WaitReadable
|
||||
next
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
sources_remaining.each(&:close_read)
|
||||
sources.each_key(&:close_read)
|
||||
end
|
||||
|
||||
# Result containing the output and exit status of a finished sub-process.
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
# typed: false
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "time"
|
||||
|
||||
require "utils/analytics"
|
||||
require "utils/curl"
|
||||
require "utils/fork"
|
||||
@ -16,7 +18,6 @@ require "utils/repology"
|
||||
require "utils/svn"
|
||||
require "utils/tty"
|
||||
require "tap_constants"
|
||||
require "time"
|
||||
|
||||
module Homebrew
|
||||
extend Context
|
||||
@ -206,7 +207,7 @@ module Kernel
|
||||
|
||||
# Don't throw deprecations at all for cached, .brew or .metadata files.
|
||||
return if backtrace.any? do |line|
|
||||
next true if line.include?(HOMEBREW_CACHE)
|
||||
next true if line.include?(HOMEBREW_CACHE.to_s)
|
||||
next true if line.include?("/.brew/")
|
||||
next true if line.include?("/.metadata/")
|
||||
|
||||
|
||||
@ -3,11 +3,15 @@
|
||||
|
||||
require "open3"
|
||||
|
||||
require "extend/time"
|
||||
|
||||
module Utils
|
||||
# Helper function for interacting with `curl`.
|
||||
#
|
||||
# @api private
|
||||
module Curl
|
||||
using TimeRemaining
|
||||
|
||||
module_function
|
||||
|
||||
def curl_executable
|
||||
@ -49,14 +53,20 @@ module Utils
|
||||
args << "--silent" unless $stdout.tty?
|
||||
end
|
||||
|
||||
args << "--connect-timeout" << connect_timeout.round(3) if options[:connect_timeout]
|
||||
args << "--max-time" << max_time.round(3) if options[:max_time]
|
||||
args << "--retry" << Homebrew::EnvConfig.curl_retries unless options[:retry] == false
|
||||
args << "--retry-max-time" << retry_max_time.round if options[:retry_max_time]
|
||||
|
||||
args + extra_args
|
||||
end
|
||||
|
||||
def curl_with_workarounds(
|
||||
*args, secrets: nil, print_stdout: nil, print_stderr: nil, debug: nil, verbose: nil, env: {}, **options
|
||||
*args,
|
||||
secrets: nil, print_stdout: nil, print_stderr: nil, debug: nil, verbose: nil, env: {}, timeout: nil, **options
|
||||
)
|
||||
end_time = Time.now + timeout if timeout
|
||||
|
||||
command_options = {
|
||||
secrets: secrets,
|
||||
print_stdout: print_stdout,
|
||||
@ -68,14 +78,22 @@ module Utils
|
||||
# SSL_CERT_FILE can be incorrectly set by users or portable-ruby and screw
|
||||
# with SSL downloads so unset it here.
|
||||
result = system_command curl_executable,
|
||||
args: curl_args(*args, **options),
|
||||
env: { "SSL_CERT_FILE" => nil }.merge(env),
|
||||
args: curl_args(*args, **options),
|
||||
env: { "SSL_CERT_FILE" => nil }.merge(env),
|
||||
timeout: end_time&.remaining,
|
||||
**command_options
|
||||
|
||||
return result if result.success? || !args.exclude?("--http1.1")
|
||||
|
||||
raise Timeout::Error, result.stderr.lines.last.chomp if timeout && result.status.exitstatus == 28
|
||||
|
||||
# Error in the HTTP2 framing layer
|
||||
return curl_with_workarounds(*args, "--http1.1", **command_options, **options) if result.status.exitstatus == 16
|
||||
if result.status.exitstatus == 16
|
||||
return curl_with_workarounds(
|
||||
*args, "--http1.1",
|
||||
timeout: end_time&.remaining, **command_options, **options
|
||||
)
|
||||
end
|
||||
|
||||
# This is a workaround for https://github.com/curl/curl/issues/1618.
|
||||
if result.status.exitstatus == 56 # Unexpected EOF
|
||||
@ -158,9 +176,12 @@ module Utils
|
||||
hash_needed = false
|
||||
if url != secure_url
|
||||
user_agents.each do |user_agent|
|
||||
secure_details =
|
||||
secure_details = begin
|
||||
curl_http_content_headers_and_checksum(secure_url, specs: specs, hash_needed: true,
|
||||
user_agent: user_agent)
|
||||
rescue Timeout::Error
|
||||
next
|
||||
end
|
||||
|
||||
next unless http_status_ok?(secure_details[:status])
|
||||
|
||||
|
||||
@ -630,7 +630,9 @@ If you need more control over the way files are downloaded and staged, you can c
|
||||
|
||||
```ruby
|
||||
class MyDownloadStrategy < SomeHomebrewDownloadStrategy
|
||||
def fetch
|
||||
def fetch(timeout: nil, **options)
|
||||
opoo "Unhandled options in #{self.class}#fetch: #{options.keys.join(", ")}" unless options.empty?
|
||||
|
||||
# downloads output to `temporary_path`
|
||||
end
|
||||
end
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user