From 8bb8b5ac655bb000bf1e11350de35d38c894e6e1 Mon Sep 17 00:00:00 2001 From: Markus Reiter Date: Tue, 30 Jul 2024 21:35:25 +0200 Subject: [PATCH] Implement `Downloadable` for more types. --- Library/Homebrew/cmd/fetch.rb | 4 + Library/Homebrew/cmd/install.rb | 1 - Library/Homebrew/download_strategy.rb | 8 -- Library/Homebrew/downloadable.rb | 2 +- Library/Homebrew/formula.rb | 2 + Library/Homebrew/formula_installer.rb | 4 +- Library/Homebrew/resource.rb | 27 +++++++ Library/Homebrew/sbom.rb | 104 ++++++++++++++++++++------ 8 files changed, 119 insertions(+), 33 deletions(-) diff --git a/Library/Homebrew/cmd/fetch.rb b/Library/Homebrew/cmd/fetch.rb index b25e2d008c9c1b..3bed8cfe75eb0c 100644 --- a/Library/Homebrew/cmd/fetch.rb +++ b/Library/Homebrew/cmd/fetch.rb @@ -296,6 +296,10 @@ def run sleep 0.05 rescue Interrupt + remaining_downloads.each do |_, future| + # FIXME: Implement cancellation of running downloads. + end + print "\n" * previous_pending_line_count $stdout.flush raise diff --git a/Library/Homebrew/cmd/install.rb b/Library/Homebrew/cmd/install.rb index 27a153d5755f86..97bf4d3a8ee945 100644 --- a/Library/Homebrew/cmd/install.rb +++ b/Library/Homebrew/cmd/install.rb @@ -207,7 +207,6 @@ def run end if casks.any? - if args.dry_run? if (casks_to_install = casks.reject(&:installed?).presence) ohai "Would install #{::Utils.pluralize("cask", casks_to_install.count, include_count: true)}:" diff --git a/Library/Homebrew/download_strategy.rb b/Library/Homebrew/download_strategy.rb index 171c1d33cb24fe..314738342be7a4 100644 --- a/Library/Homebrew/download_strategy.rb +++ b/Library/Homebrew/download_strategy.rb @@ -710,14 +710,6 @@ def stage end end -# Strategy for extracting local binary packages. -class LocalBottleDownloadStrategy < AbstractFileDownloadStrategy - def initialize(path) # rubocop:disable Lint/MissingSuper - @cached_location = path - extend Pourable - end -end - # Strategy for downloading a Subversion repository. # # @api public diff --git a/Library/Homebrew/downloadable.rb b/Library/Homebrew/downloadable.rb index 2387753476fbe1..5c0f73b6553980 100644 --- a/Library/Homebrew/downloadable.rb +++ b/Library/Homebrew/downloadable.rb @@ -126,7 +126,7 @@ def verify_download_integrity(filename) sig { overridable.returns(String) } def download_name - File.basename(determine_url.to_s) + @download_name ||= File.basename(determine_url.to_s) end private diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index 3cefae9f95ce9b..7b948126420da5 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -2773,10 +2773,12 @@ def on_system_blocks_exist? ).returns(Pathname) } def fetch(verify_download_integrity: true, timeout: nil, quiet: false) + odeprecated "Formula#fetch", "Resource#fetch on Formula#resource" active_spec.fetch(verify_download_integrity:, timeout:, quiet:) end def verify_download_integrity(filename) + odeprecated "Formula#verify_download_integrity", "Resource#verify_download_integrity on Formula#resource" active_spec.verify_download_integrity(filename) end diff --git a/Library/Homebrew/formula_installer.rb b/Library/Homebrew/formula_installer.rb index e73c05b52e9900..8a6ac4d686e92e 100644 --- a/Library/Homebrew/formula_installer.rb +++ b/Library/Homebrew/formula_installer.rb @@ -1263,11 +1263,11 @@ def fetch def downloader if (bottle_path = formula.local_bottle_path) - LocalBottleDownloadStrategy.new(bottle_path) + Resource::Local.new(bottle_path) elsif pour_bottle? formula.bottle else - formula + formula.resource end end diff --git a/Library/Homebrew/resource.rb b/Library/Homebrew/resource.rb index b715a87d8aefaa..b65fc74d182ffb 100644 --- a/Library/Homebrew/resource.rb +++ b/Library/Homebrew/resource.rb @@ -269,6 +269,33 @@ def determine_url_mirrors [*extra_urls, *super].uniq end + # A local resource that doesn't need to be downloaded. + class Local < Resource + def initialize(path) + super(File.basename(path)) + @path = path + end + + sig { override.returns(Pathname) } + def cached_download + @path + end + + sig { override.void } + def clear_cache; end + + sig { + override.params( + verify_download_integrity: T::Boolean, + timeout: T.nilable(T.any(Integer, Float)), + quiet: T::Boolean, + ).returns(Pathname) + } + def fetch(verify_download_integrity: true, timeout: nil, quiet: false) + cached_download + end + end + # A resource for a formula. class Formula < Resource sig { override.returns(String) } diff --git a/Library/Homebrew/sbom.rb b/Library/Homebrew/sbom.rb index facc7f37bfe2b6..9468a156442aa9 100644 --- a/Library/Homebrew/sbom.rb +++ b/Library/Homebrew/sbom.rb @@ -2,6 +2,7 @@ # frozen_string_literal: true require "cxxstdlib" +require "downloadable" require "json" require "development_tools" require "extend/cachable" @@ -89,43 +90,104 @@ def self.exist?(formula) spdxfile(formula).exist? end - sig { returns(T::Hash[String, String]) } - def self.fetch_schema! - return @schema if @schema.present? + class Schema + include Downloadable - url = SCHEMA_URL - target = SCHEMA_CACHE_TARGET - quieter = target.exist? && !target.empty? + sig { override.void } + def initialize + super - curl_args = Utils::Curl.curl_args(retries: 0) - curl_args += ["--silent", "--time-cond", target.to_s] if quieter + @url = URL.new(SCHEMA_URL) + @download_name = SCHEMA_FILENAME + end - begin - unless quieter + sig { override.returns(String) } + def name + download_name + end + + sig { override.returns(String) } + def download_type + "SBOM schema" + end + + sig { override.returns(Pathname) } + def cached_download + cache/download_name + end + + sig { override.void } + def clear_cache + cached_download.unlink + end + + sig { + override.params( + verify_download_integrity: T::Boolean, + timeout: T.nilable(T.any(Integer, Float)), + quiet: T::Boolean, + ).returns(Pathname) + } + def fetch(verify_download_integrity: true, timeout: nil, quiet: false) + quiet ||= cached_download.exist? && !cached_download.empty? + + curl_args = Utils::Curl.curl_args(retries: 0) + curl_args += ["--silent", "--time-cond", cached_download.to_s] if quiet + + unless quiet oh1 "Fetching SBOM schema" ohai "Downloading #{url}" end - Utils::Curl.curl_download(*curl_args, url, to: target, retries: 0) - FileUtils.touch(target, mtime: Time.now) + + Utils::Curl.curl_download(*curl_args, url.to_s, to: cached_download, retries: 0) + FileUtils.touch(cached_download, mtime: Time.now) + + download = cached_download + verify_download_integrity(download) if verify_download_integrity + download + end + + sig { override.params(filename: Pathname).void } + def verify_download_integrity(filename) + JSON.parse(filename.read) + rescue JSON::ParserError => e + raise DownloadError.new(self, e) + end + + sig { override.returns(Pathname) } + def cache + super.join("sbom") + end + end + + sig { returns(T::Hash[String, String]) } + def self.fetch_schema! + return @schema if @schema.present? + + schema = Schema.new + + begin + schema.fetch rescue ErrorDuringExecution - target.unlink if target.exist? && target.empty? + schema.clear_cache if schema.downloaded? && schema.cached_download.empty? - if target.exist? + if schema.downloaded? opoo "SBOM schema update failed, falling back to cached version." else opoo "Failed to fetch SBOM schema, cannot perform SBOM validation!" return {} end - end + rescue DownloadError => e + if e.cause.is_a?(JSON::ParserError) + schema.clear_cache + opoo "Failed to fetch SBOM schema, cached version corrupted, cannot perform SBOM validation!" + end - @schema = begin - JSON.parse(target.read, freeze: true) - rescue JSON::ParserError - target.unlink - opoo "Failed to fetch SBOM schema, cached version corrupted, cannot perform SBOM validation!" - {} + return {} end + + @schema = JSON.parse(schema.cached_download.read, freeze: true) end sig { params(bottling: T::Boolean).returns(T::Boolean) }