diff --git a/Library/Homebrew/.rubocop_todo.yml b/Library/Homebrew/.rubocop_todo.yml index a74f62002a71a8ddb995d26af0759c81d61ba1cf..0cdd362aa28690c1863298520d497f256162720e 100644 --- a/Library/Homebrew/.rubocop_todo.yml +++ b/Library/Homebrew/.rubocop_todo.yml @@ -8,7 +8,6 @@ Style/Documentation: - 'cask/macos.rb' - 'cli/args.rb' - 'cli/parser.rb' - - 'download_strategy.rb' - 'global.rb' - 'keg_relocate.rb' - 'os/linux/global.rb' diff --git a/Library/Homebrew/download_strategy.rb b/Library/Homebrew/download_strategy.rb index 739b9f5c787fe38404a677099c1cf998538422f0..536c22481d5a93f02d9929392fbad1f8aa366bf7 100644 --- a/Library/Homebrew/download_strategy.rb +++ b/Library/Homebrew/download_strategy.rb @@ -10,11 +10,17 @@ require "lock_file" require "mechanize/version" require "mechanize/http/content_disposition_parser" +# @abstract Abstract superclass for all download strategies. +# +# @api private class AbstractDownloadStrategy extend Forwardable include FileUtils include Context + # Extension for bottle downloads. + # + # @api private module Pourable def stage ohai "Pouring #{basename}" @@ -35,25 +41,25 @@ class AbstractDownloadStrategy extend Pourable if meta[:bottle] end - # Download and cache the resource as {#cached_location}. + # Download and cache the resource at {#cached_location}. + # + # @api public def fetch; end + # Disable any output during downloading. + # # TODO: Deprecate once we have an explicitly documented alternative. + # + # @api public def shutup! @quiet = true end - def puts(*args) - super(*args) unless quiet? - end - - def ohai(*args) - super(*args) unless quiet? - end - # Unpack {#cached_location} into the current working directory, and possibly # chdir into the newly-unpacked directory. # Unlike {Resource#stage}, this does not take a block. + # + # @api public def stage UnpackStrategy.detect(cached_location, prioritise_extension: true, @@ -79,12 +85,16 @@ class AbstractDownloadStrategy # @!attribute [r] source_modified_time # Returns the most recent modified time for all files in the current working directory after stage. + # + # @api public def source_modified_time Pathname.pwd.to_enum(:find).select(&:file?).map(&:mtime).max end # Remove {#cached_location} and any other files associated with the resource # from the cache. + # + # @api public def clear_cache rm_rf(cached_location) end @@ -95,6 +105,14 @@ class AbstractDownloadStrategy private + def puts(*args) + super(*args) unless quiet? + end + + def ohai(*args) + super(*args) unless quiet? + end + def system_command(*args, **options) super(*args, print_stderr: false, env: env, **options) end @@ -115,6 +133,9 @@ class AbstractDownloadStrategy end end +# @abstract Abstract superclass for all download strategies downloading from a version control system. +# +# @api private class VCSDownloadStrategy < AbstractDownloadStrategy REF_TYPES = [:tag, :branch, :revisions, :revision].freeze @@ -125,6 +146,9 @@ class VCSDownloadStrategy < AbstractDownloadStrategy @cached_location = @cache/"#{name}--#{cache_tag}" end + # Download and cache the repository at {#cached_location}. + # + # @api public def fetch ohai "Cloning #{url}" @@ -167,6 +191,8 @@ class VCSDownloadStrategy < AbstractDownloadStrategy # Return last commit's unique identifier for the repository. # Return most recent modified timestamp unless overridden. + # + # @api public def last_commit source_modified_time.to_i.to_s end @@ -193,11 +219,21 @@ class VCSDownloadStrategy < AbstractDownloadStrategy end end +# @abstract Abstract superclass for all download strategies downloading a single file. +# +# @api private class AbstractFileDownloadStrategy < AbstractDownloadStrategy + # Path for storing an incomplete download while the download is still in progress. + # + # @api public def temporary_path @temporary_path ||= Pathname.new("#{cached_location}.incomplete") end + # Path of the symlink (whose name includes the resource name, version and extension) + # pointing to {#cached_location}. + # + # @api public def symlink_location return @symlink_location if defined?(@symlink_location) @@ -205,6 +241,9 @@ class AbstractFileDownloadStrategy < AbstractDownloadStrategy @symlink_location = @cache/"#{name}--#{version}#{ext}" end + # Path for storing the completed download . + # + # @api public def cached_location return @cached_location if defined?(@cached_location) @@ -273,6 +312,9 @@ class AbstractFileDownloadStrategy < AbstractDownloadStrategy end end +# Strategy for downloading files using `curl`. +# +# @api public class CurlDownloadStrategy < AbstractFileDownloadStrategy attr_reader :mirrors @@ -281,6 +323,9 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy @mirrors = meta.fetch(:mirrors, []) end + # Download and cache the file at {#cached_location}. + # + # @api public def fetch download_lock = LockFile.new(temporary_path.basename) download_lock.lock @@ -443,7 +488,9 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy end end -# Detect and download from Apache Mirror. +# Strategy for downloading a file from an Apache Mirror URL. +# +# @api public class CurlApacheMirrorDownloadStrategy < CurlDownloadStrategy def mirrors return @combined_mirrors if defined?(@combined_mirrors) @@ -474,8 +521,10 @@ class CurlApacheMirrorDownloadStrategy < CurlDownloadStrategy end end -# Download via an HTTP POST. +# Strategy for downloading via an HTTP POST request using `curl`. # Query parameters on the URL are converted into POST parameters. +# +# @api public class CurlPostDownloadStrategy < CurlDownloadStrategy private @@ -492,8 +541,10 @@ class CurlPostDownloadStrategy < CurlDownloadStrategy end end -# Use this strategy to download but not unzip a file. -# Useful for installing jars. +# Strategy for downloading archives without automatically extracting them. +# (Useful for downloading `.jar` files.) +# +# @api public class NoUnzipCurlDownloadStrategy < CurlDownloadStrategy def stage UnpackStrategy::Uncompressed.new(cached_location) @@ -502,19 +553,27 @@ class NoUnzipCurlDownloadStrategy < CurlDownloadStrategy end end -# This strategy extracts local binary packages. +# Strategy for extracting local binary packages. +# +# @api private class LocalBottleDownloadStrategy < AbstractFileDownloadStrategy def initialize(path) # rubocop:disable Lint/MissingSuper @cached_location = path end end +# Strategy for downloading a Subversion repository. +# +# @api public class SubversionDownloadStrategy < VCSDownloadStrategy def initialize(url, name, version, **meta) super @url = @url.sub("svn+http://", "") end + # Download and cache the repository at {#cached_location}. + # + # @api public def fetch if @url.chomp("/") != repo_url || !system_command("svn", args: ["switch", @url, cached_location]).success? clear_cache @@ -522,6 +581,7 @@ class SubversionDownloadStrategy < VCSDownloadStrategy super end + # (see AbstractDownloadStrategy#source_modified_time) def source_modified_time time = if Version.create(Utils::Svn.version) >= Version.create("1.9") out, = system_command("svn", args: ["info", "--show-item", "last-changed-date"], chdir: cached_location) @@ -533,6 +593,7 @@ class SubversionDownloadStrategy < VCSDownloadStrategy Time.parse time end + # (see VCSDownloadStrategy#source_modified_time) def last_commit out, = system_command("svn", args: ["info", "--show-item", "revision"], chdir: cached_location) out.strip @@ -606,6 +667,9 @@ class SubversionDownloadStrategy < VCSDownloadStrategy alias update clone_repo end +# Strategy for downloading a Git repository. +# +# @api public class GitDownloadStrategy < VCSDownloadStrategy SHALLOW_CLONE_ALLOWLIST = [ %r{git://}, @@ -621,11 +685,13 @@ class GitDownloadStrategy < VCSDownloadStrategy @shallow = meta.fetch(:shallow, true) end + # (see AbstractDownloadStrategy#source_modified_time) def source_modified_time out, = system_command("git", args: ["--git-dir", git_dir, "show", "-s", "--format=%cD"]) Time.parse(out) end + # (see VCSDownloadStrategy#source_modified_time) def last_commit out, = system_command("git", args: ["--git-dir", git_dir, "rev-parse", "--short=7", "HEAD"]) out.chomp @@ -803,6 +869,9 @@ class GitDownloadStrategy < VCSDownloadStrategy end end +# Strategy for downloading a Git repository from GitHub. +# +# @api public class GitHubGitDownloadStrategy < GitDownloadStrategy def initialize(url, name, version, **meta) super @@ -859,6 +928,9 @@ class GitHubGitDownloadStrategy < GitDownloadStrategy end end +# Strategy for downloading a CVS repository. +# +# @api public class CVSDownloadStrategy < VCSDownloadStrategy def initialize(url, name, version, **meta) super @@ -873,6 +945,7 @@ class CVSDownloadStrategy < VCSDownloadStrategy end end + # (see AbstractDownloadStrategy#source_modified_time) def source_modified_time # Filter CVS's files because the timestamp for each of them is the moment # of clone. @@ -928,12 +1001,16 @@ class CVSDownloadStrategy < VCSDownloadStrategy end end +# Strategy for downloading a Mercurial repository. +# +# @api public class MercurialDownloadStrategy < VCSDownloadStrategy def initialize(url, name, version, **meta) super @url = @url.sub(%r{^hg://}, "") end + # (see AbstractDownloadStrategy#source_modified_time) def source_modified_time out, = system_command("hg", args: ["tip", "--template", "{date|isodate}", "-R", cached_location]) @@ -941,6 +1018,7 @@ class MercurialDownloadStrategy < VCSDownloadStrategy Time.parse(out) end + # (see VCSDownloadStrategy#source_modified_time) def last_commit out, = system_command("hg", args: ["parent", "--template", "{node|short}", "-R", cached_location]) out.chomp @@ -978,12 +1056,16 @@ class MercurialDownloadStrategy < VCSDownloadStrategy end end +# Strategy for downloading a Bazaar repository. +# +# @api public class BazaarDownloadStrategy < VCSDownloadStrategy def initialize(url, name, version, **meta) super @url.sub!(%r{^bzr://}, "") end + # (see AbstractDownloadStrategy#source_modified_time) def source_modified_time out, = system_command("bzr", args: ["log", "-l", "1", "--timezone=utc", cached_location]) timestamp = out.chomp @@ -992,6 +1074,7 @@ class BazaarDownloadStrategy < VCSDownloadStrategy Time.parse(timestamp) end + # (see VCSDownloadStrategy#source_modified_time) def last_commit out, = system_command("bzr", args: ["revno", cached_location]) out.chomp @@ -1027,17 +1110,22 @@ class BazaarDownloadStrategy < VCSDownloadStrategy end end +# Strategy for downloading a Fossil repository. +# +# @api public class FossilDownloadStrategy < VCSDownloadStrategy def initialize(url, name, version, **meta) super @url = @url.sub(%r{^fossil://}, "") end + # (see AbstractDownloadStrategy#source_modified_time) def source_modified_time out, = system_command("fossil", args: ["info", "tip", "-R", cached_location]) Time.parse(out[/^uuid: +\h+ (.+)$/, 1]) end + # (see VCSDownloadStrategy#source_modified_time) def last_commit out, = system_command("fossil", args: ["info", "tip", "-R", cached_location]) out[/^uuid: +(\h+) .+$/, 1] @@ -1066,6 +1154,9 @@ class FossilDownloadStrategy < VCSDownloadStrategy end end +# Helper class for detecting a download strategy from a URL. +# +# @api private class DownloadStrategyDetector def self.detect(url, using = nil) if using.nil?