mirror of
https://github.com/Homebrew/brew.git
synced 2025-07-14 16:09:03 +08:00
Refactor formula, cask and Ruby source downloads to use shared code
This commit is contained in:
parent
7386d4e407
commit
44f058edb5
@ -14,6 +14,7 @@ module Homebrew
|
|||||||
extend Cachable
|
extend Cachable
|
||||||
|
|
||||||
HOMEBREW_CACHE_API = (HOMEBREW_CACHE/"api").freeze
|
HOMEBREW_CACHE_API = (HOMEBREW_CACHE/"api").freeze
|
||||||
|
HOMEBREW_CACHE_API_SOURCE = (HOMEBREW_CACHE/"api-source").freeze
|
||||||
|
|
||||||
sig { params(endpoint: String).returns(Hash) }
|
sig { params(endpoint: String).returns(Hash) }
|
||||||
def self.fetch(endpoint)
|
def self.fetch(endpoint)
|
||||||
@ -114,50 +115,6 @@ module Homebrew
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
sig {
|
|
||||||
params(name: String, path: T.any(Pathname, String), git_head: String,
|
|
||||||
sha256: T.nilable(String)).returns(String)
|
|
||||||
}
|
|
||||||
def self.fetch_homebrew_cask_source(name, path:, git_head:, sha256: nil)
|
|
||||||
# TODO: unify with formula logic (https://github.com/Homebrew/brew/issues/14746)
|
|
||||||
raw_endpoint = "#{git_head}/#{path}"
|
|
||||||
return cache[raw_endpoint] if cache.present? && cache.key?(raw_endpoint)
|
|
||||||
|
|
||||||
# This API sometimes returns random 404s so needs a fallback at formulae.brew.sh.
|
|
||||||
raw_source_url = "https://raw.githubusercontent.com/Homebrew/homebrew-cask/#{raw_endpoint}"
|
|
||||||
api_source_url = "#{HOMEBREW_API_DEFAULT_DOMAIN}/cask-source/#{name}.rb"
|
|
||||||
|
|
||||||
url = raw_source_url
|
|
||||||
output = Utils::Curl.curl_output("--fail", url)
|
|
||||||
|
|
||||||
if !output.success? || output.blank?
|
|
||||||
url = api_source_url
|
|
||||||
output = Utils::Curl.curl_output("--fail", url)
|
|
||||||
if !output.success? || output.blank?
|
|
||||||
raise ArgumentError, <<~EOS
|
|
||||||
No valid file found at either of:
|
|
||||||
#{Tty.underline}#{raw_source_url}#{Tty.reset}
|
|
||||||
#{Tty.underline}#{api_source_url}#{Tty.reset}
|
|
||||||
EOS
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
cask_source = output.stdout
|
|
||||||
actual_sha256 = Digest::SHA256.hexdigest(cask_source)
|
|
||||||
if sha256 && actual_sha256 != sha256
|
|
||||||
raise ArgumentError, <<~EOS
|
|
||||||
SHA256 mismatch
|
|
||||||
Expected: #{Formatter.success(sha256.to_s)}
|
|
||||||
Actual: #{Formatter.error(actual_sha256.to_s)}
|
|
||||||
URL: #{url}
|
|
||||||
Check if you can access the URL in your browser.
|
|
||||||
Regardless, try again in a few minutes.
|
|
||||||
EOS
|
|
||||||
end
|
|
||||||
|
|
||||||
cache[raw_endpoint] = cask_source
|
|
||||||
end
|
|
||||||
|
|
||||||
sig { params(json: Hash).returns(Hash) }
|
sig { params(json: Hash).returns(Hash) }
|
||||||
def self.merge_variations(json)
|
def self.merge_variations(json)
|
||||||
bottle_tag = ::Utils::Bottles::Tag.new(system: Homebrew::SimulateSystem.current_os,
|
bottle_tag = ::Utils::Bottles::Tag.new(system: Homebrew::SimulateSystem.current_os,
|
||||||
@ -207,5 +164,16 @@ module Homebrew
|
|||||||
|
|
||||||
[true, JSON.parse(json_data["payload"])]
|
[true, JSON.parse(json_data["payload"])]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { params(path: Pathname).returns(T.nilable(Tap)) }
|
||||||
|
def self.tap_from_source_download(path)
|
||||||
|
source_relative_path = path.relative_path_from(Homebrew::API::HOMEBREW_CACHE_API_SOURCE)
|
||||||
|
return if source_relative_path.to_s.start_with?("../")
|
||||||
|
|
||||||
|
org, repo = source_relative_path.each_filename.first(2)
|
||||||
|
return if org.blank? || repo.blank?
|
||||||
|
|
||||||
|
Tap.fetch(org, repo)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "extend/cachable"
|
require "extend/cachable"
|
||||||
|
require "api/download"
|
||||||
|
|
||||||
module Homebrew
|
module Homebrew
|
||||||
module API
|
module API
|
||||||
@ -19,12 +20,25 @@ module Homebrew
|
|||||||
Homebrew::API.fetch "cask/#{token}.json"
|
Homebrew::API.fetch "cask/#{token}.json"
|
||||||
end
|
end
|
||||||
|
|
||||||
sig {
|
sig { params(cask: ::Cask::Cask).returns(::Cask::Cask) }
|
||||||
params(token: String, path: T.any(String, Pathname), git_head: String,
|
def source_download(cask)
|
||||||
sha256: T.nilable(String)).returns(String)
|
path = cask.ruby_source_path.to_s || "Casks/#{cask.token}.rb"
|
||||||
}
|
sha256 = cask.ruby_source_checksum[:sha256]
|
||||||
def fetch_source(token, path:, git_head:, sha256: nil)
|
checksum = Checksum.new(sha256) if sha256
|
||||||
Homebrew::API.fetch_homebrew_cask_source token, path: path, git_head: git_head, sha256: sha256
|
git_head = cask.tap_git_head || "HEAD"
|
||||||
|
tap = cask.tap&.full_name || "Homebrew/homebrew-cask"
|
||||||
|
|
||||||
|
download = Homebrew::API::Download.new(
|
||||||
|
"https://raw.githubusercontent.com/#{tap}/#{git_head}/#{path}",
|
||||||
|
checksum,
|
||||||
|
mirrors: [
|
||||||
|
"#{HOMEBREW_API_DEFAULT_DOMAIN}/cask-source/#{File.basename(path)}",
|
||||||
|
],
|
||||||
|
cache: HOMEBREW_CACHE_API_SOURCE/"#{tap}/#{git_head}/Cask",
|
||||||
|
)
|
||||||
|
download.fetch
|
||||||
|
::Cask::CaskLoader::FromPathLoader.new(download.symlink_location)
|
||||||
|
.load(config: cask.config)
|
||||||
end
|
end
|
||||||
|
|
||||||
sig { returns(T::Boolean) }
|
sig { returns(T::Boolean) }
|
||||||
|
45
Library/Homebrew/api/download.rb
Normal file
45
Library/Homebrew/api/download.rb
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# typed: true
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "downloadable"
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module API
|
||||||
|
# @api private
|
||||||
|
class DownloadStrategy < CurlDownloadStrategy
|
||||||
|
sig { override.returns(Pathname) }
|
||||||
|
def symlink_location
|
||||||
|
cache/name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @api private
|
||||||
|
class Download < Downloadable
|
||||||
|
sig {
|
||||||
|
params(
|
||||||
|
url: String,
|
||||||
|
checksum: T.nilable(Checksum),
|
||||||
|
mirrors: T::Array[String],
|
||||||
|
cache: T.nilable(Pathname),
|
||||||
|
).void
|
||||||
|
}
|
||||||
|
def initialize(url, checksum, mirrors: [], cache: nil)
|
||||||
|
super()
|
||||||
|
@url = URL.new(url, using: API::DownloadStrategy)
|
||||||
|
@checksum = checksum
|
||||||
|
@mirrors = mirrors
|
||||||
|
@cache = cache
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { override.returns(Pathname) }
|
||||||
|
def cache
|
||||||
|
@cache || super
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { returns(Pathname) }
|
||||||
|
def symlink_location
|
||||||
|
T.cast(downloader, API::DownloadStrategy).symlink_location
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -2,6 +2,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "extend/cachable"
|
require "extend/cachable"
|
||||||
|
require "api/download"
|
||||||
|
|
||||||
module Homebrew
|
module Homebrew
|
||||||
module API
|
module API
|
||||||
@ -19,6 +20,24 @@ module Homebrew
|
|||||||
Homebrew::API.fetch "formula/#{name}.json"
|
Homebrew::API.fetch "formula/#{name}.json"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { params(formula: ::Formula).returns(::Formula) }
|
||||||
|
def source_download(formula)
|
||||||
|
path = formula.ruby_source_path || "Formula/#{formula.name}.rb"
|
||||||
|
git_head = formula.tap_git_head || "HEAD"
|
||||||
|
tap = formula.tap&.full_name || "Homebrew/homebrew-core"
|
||||||
|
|
||||||
|
download = Homebrew::API::Download.new(
|
||||||
|
"https://raw.githubusercontent.com/#{tap}/#{git_head}/#{path}",
|
||||||
|
formula.ruby_source_checksum,
|
||||||
|
cache: HOMEBREW_CACHE_API_SOURCE/"#{tap}/#{git_head}/Formula",
|
||||||
|
)
|
||||||
|
download.fetch
|
||||||
|
Formulary.factory(download.symlink_location,
|
||||||
|
formula.active_spec_sym,
|
||||||
|
alias_path: formula.alias_path,
|
||||||
|
flags: formula.class.build_flags)
|
||||||
|
end
|
||||||
|
|
||||||
sig { returns(T::Boolean) }
|
sig { returns(T::Boolean) }
|
||||||
def download_and_cache_data!
|
def download_and_cache_data!
|
||||||
json_formulae, updated = Homebrew::API.fetch_json_api_file "formula.jws.json",
|
json_formulae, updated = Homebrew::API.fetch_json_api_file "formula.jws.json",
|
||||||
|
@ -21,10 +21,33 @@ module Cask
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Loads a cask from a string.
|
# Loads a cask from a string.
|
||||||
class FromContentLoader
|
class AbstractContentLoader
|
||||||
include ILoader
|
include ILoader
|
||||||
attr_reader :content, :tap
|
extend T::Helpers
|
||||||
|
abstract!
|
||||||
|
|
||||||
|
sig { returns(String) }
|
||||||
|
attr_reader :content
|
||||||
|
|
||||||
|
sig { returns(T.nilable(Tap)) }
|
||||||
|
attr_reader :tap
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
sig {
|
||||||
|
overridable.params(
|
||||||
|
header_token: String,
|
||||||
|
options: T.untyped,
|
||||||
|
block: T.nilable(T.proc.bind(DSL).void),
|
||||||
|
).returns(Cask)
|
||||||
|
}
|
||||||
|
def cask(header_token, **options, &block)
|
||||||
|
Cask.new(header_token, source: content, tap: tap, **options, config: @config, &block)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Loads a cask from a string.
|
||||||
|
class FromContentLoader < AbstractContentLoader
|
||||||
def self.can_load?(ref)
|
def self.can_load?(ref)
|
||||||
return false unless ref.respond_to?(:to_str)
|
return false unless ref.respond_to?(:to_str)
|
||||||
|
|
||||||
@ -42,6 +65,8 @@ module Cask
|
|||||||
end
|
end
|
||||||
|
|
||||||
def initialize(content, tap: nil)
|
def initialize(content, tap: nil)
|
||||||
|
super()
|
||||||
|
|
||||||
@content = content.force_encoding("UTF-8")
|
@content = content.force_encoding("UTF-8")
|
||||||
@tap = tap
|
@tap = tap
|
||||||
end
|
end
|
||||||
@ -51,16 +76,10 @@ module Cask
|
|||||||
|
|
||||||
instance_eval(content, __FILE__, __LINE__)
|
instance_eval(content, __FILE__, __LINE__)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def cask(header_token, **options, &block)
|
|
||||||
Cask.new(header_token, source: content, tap: tap, **options, config: @config, &block)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Loads a cask from a path.
|
# Loads a cask from a path.
|
||||||
class FromPathLoader < FromContentLoader
|
class FromPathLoader < AbstractContentLoader
|
||||||
def self.can_load?(ref)
|
def self.can_load?(ref)
|
||||||
path = Pathname(ref)
|
path = Pathname(ref)
|
||||||
%w[.rb .json].include?(path.extname) && path.expand_path.exist?
|
%w[.rb .json].include?(path.extname) && path.expand_path.exist?
|
||||||
@ -68,11 +87,15 @@ module Cask
|
|||||||
|
|
||||||
attr_reader :token, :path
|
attr_reader :token, :path
|
||||||
|
|
||||||
def initialize(path) # rubocop:disable Lint/MissingSuper
|
def initialize(path, token: nil)
|
||||||
|
super()
|
||||||
|
|
||||||
path = Pathname(path).expand_path
|
path = Pathname(path).expand_path
|
||||||
|
|
||||||
@token = path.basename(path.extname).to_s
|
@token = path.basename(path.extname).to_s
|
||||||
|
|
||||||
@path = path
|
@path = path
|
||||||
|
@tap = Homebrew::API.tap_from_source_download(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
def load(config:)
|
def load(config:)
|
||||||
@ -153,8 +176,8 @@ module Cask
|
|||||||
end
|
end
|
||||||
|
|
||||||
def initialize(path)
|
def initialize(path)
|
||||||
@tap = Tap.from_path(path)
|
|
||||||
super(path)
|
super(path)
|
||||||
|
@tap = Tap.from_path(path)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -172,7 +195,7 @@ module Cask
|
|||||||
end
|
end
|
||||||
|
|
||||||
def load(config:)
|
def load(config:)
|
||||||
raise TapCaskUnavailableError.new(tap, token) unless tap.installed?
|
raise TapCaskUnavailableError.new(tap, token) unless T.must(tap).installed?
|
||||||
|
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
@ -215,12 +238,12 @@ module Cask
|
|||||||
return false unless ref.is_a?(String)
|
return false unless ref.is_a?(String)
|
||||||
return false unless ref.match?(HOMEBREW_MAIN_TAP_CASK_REGEX)
|
return false unless ref.match?(HOMEBREW_MAIN_TAP_CASK_REGEX)
|
||||||
|
|
||||||
token = ref.delete_prefix("homebrew/cask/")
|
token = ref.sub(%r{^homebrew/(?:homebrew-)?cask/}i, "")
|
||||||
Homebrew::API::Cask.all_casks.key?(token)
|
Homebrew::API::Cask.all_casks.key?(token)
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(token, from_json: nil)
|
def initialize(token, from_json: nil)
|
||||||
@token = token.delete_prefix("homebrew/cask/")
|
@token = token.sub(%r{^homebrew/(?:homebrew-)?cask/}i, "")
|
||||||
@path = CaskLoader.default_path(token)
|
@path = CaskLoader.default_path(token)
|
||||||
@from_json = from_json
|
@from_json = from_json
|
||||||
end
|
end
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
# typed: true
|
# typed: true
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "downloadable"
|
||||||
require "fileutils"
|
require "fileutils"
|
||||||
require "cask/cache"
|
require "cask/cache"
|
||||||
require "cask/quarantine"
|
require "cask/quarantine"
|
||||||
@ -9,71 +10,75 @@ module Cask
|
|||||||
# A download corresponding to a {Cask}.
|
# A download corresponding to a {Cask}.
|
||||||
#
|
#
|
||||||
# @api private
|
# @api private
|
||||||
class Download
|
class Download < ::Downloadable
|
||||||
include Context
|
include Context
|
||||||
|
|
||||||
attr_reader :cask
|
attr_reader :cask
|
||||||
|
|
||||||
def initialize(cask, quarantine: nil)
|
def initialize(cask, quarantine: nil)
|
||||||
|
super()
|
||||||
|
|
||||||
@cask = cask
|
@cask = cask
|
||||||
@quarantine = quarantine
|
@quarantine = quarantine
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { override.returns(T.nilable(::URL)) }
|
||||||
|
def url
|
||||||
|
@url ||= ::URL.new(cask.url.to_s, cask.url.specs)
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { override.returns(T.nilable(::Checksum)) }
|
||||||
|
def checksum
|
||||||
|
@checksum ||= cask.sha256 if cask.sha256 != :no_check
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { override.returns(T.nilable(::Version)) }
|
||||||
|
def version
|
||||||
|
@version ||= ::Version.create(cask.version)
|
||||||
|
end
|
||||||
|
|
||||||
|
sig {
|
||||||
|
override
|
||||||
|
.params(quiet: T.nilable(T::Boolean),
|
||||||
|
verify_download_integrity: T::Boolean,
|
||||||
|
timeout: T.nilable(T.any(Integer, Float)))
|
||||||
|
.returns(Pathname)
|
||||||
|
}
|
||||||
def fetch(quiet: nil, verify_download_integrity: true, timeout: nil)
|
def fetch(quiet: nil, verify_download_integrity: true, timeout: nil)
|
||||||
downloaded_path = begin
|
|
||||||
downloader.shutup! if quiet
|
downloader.shutup! if quiet
|
||||||
downloader.fetch(timeout: timeout)
|
|
||||||
downloader.cached_location
|
begin
|
||||||
rescue => e
|
super(verify_download_integrity: false, timeout: timeout)
|
||||||
error = CaskError.new("Download failed on Cask '#{cask}' with message: #{e}")
|
rescue DownloadError => e
|
||||||
|
error = CaskError.new("Download failed on Cask '#{cask}' with message: #{e.cause}")
|
||||||
error.set_backtrace e.backtrace
|
error.set_backtrace e.backtrace
|
||||||
raise error
|
raise error
|
||||||
end
|
end
|
||||||
|
|
||||||
|
downloaded_path = cached_download
|
||||||
quarantine(downloaded_path)
|
quarantine(downloaded_path)
|
||||||
self.verify_download_integrity(downloaded_path) if verify_download_integrity
|
self.verify_download_integrity(downloaded_path) if verify_download_integrity
|
||||||
downloaded_path
|
downloaded_path
|
||||||
end
|
end
|
||||||
|
|
||||||
def downloader
|
|
||||||
@downloader ||= begin
|
|
||||||
strategy = DownloadStrategyDetector.detect(cask.url.to_s, cask.url.using)
|
|
||||||
strategy.new(cask.url.to_s, cask.token, cask.version, cache: Cache.path, **cask.url.specs)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def time_file_size(timeout: nil)
|
def time_file_size(timeout: nil)
|
||||||
downloader.resolved_time_file_size(timeout: timeout)
|
raise ArgumentError, "not supported for this download strategy" unless downloader.is_a?(CurlDownloadStrategy)
|
||||||
end
|
|
||||||
|
|
||||||
def clear_cache
|
T.cast(downloader, CurlDownloadStrategy).resolved_time_file_size(timeout: timeout)
|
||||||
downloader.clear_cache
|
|
||||||
end
|
|
||||||
|
|
||||||
def cached_download
|
|
||||||
downloader.cached_location
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def basename
|
def basename
|
||||||
downloader.basename
|
downloader.basename
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { override.params(filename: Pathname).void }
|
||||||
def verify_download_integrity(filename)
|
def verify_download_integrity(filename)
|
||||||
if @cask.sha256 == :no_check
|
if @cask.sha256 == :no_check
|
||||||
opoo "No checksum defined for cask '#{@cask}', skipping verification."
|
opoo "No checksum defined for cask '#{@cask}', skipping verification."
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
begin
|
super
|
||||||
ohai "Verifying checksum for cask '#{@cask}'" if verbose?
|
|
||||||
filename.verify_checksum(@cask.sha256)
|
|
||||||
rescue ChecksumMissingError
|
|
||||||
opoo <<~EOS
|
|
||||||
Cannot verify integrity of '#{filename.basename}'.
|
|
||||||
No checksum was provided for this cask.
|
|
||||||
For your reference, the checksum is:
|
|
||||||
sha256 "#{filename.sha256}"
|
|
||||||
EOS
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@ -88,5 +93,20 @@ module Cask
|
|||||||
Quarantine.release!(download_path: path)
|
Quarantine.release!(download_path: path)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { override.returns(String) }
|
||||||
|
def download_name
|
||||||
|
cask.token
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { override.returns(T.nilable(::URL)) }
|
||||||
|
def determine_url
|
||||||
|
url
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { override.returns(Pathname) }
|
||||||
|
def cache
|
||||||
|
Cache.path
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -562,13 +562,7 @@ on_request: true)
|
|||||||
end
|
end
|
||||||
|
|
||||||
def load_cask_from_source_api!
|
def load_cask_from_source_api!
|
||||||
cask_source = Homebrew::API::Cask.fetch_source(
|
@cask = Homebrew::API::Cask.source_download(@cask)
|
||||||
@cask.token,
|
|
||||||
path: @cask.ruby_source_path || "Casks/#{@cask.token}.rb",
|
|
||||||
git_head: @cask.tap_git_head,
|
|
||||||
sha256: @cask.ruby_source_checksum["sha256"],
|
|
||||||
)
|
|
||||||
@cask = CaskLoader::FromContentLoader.new(cask_source, tap: @cask.tap).load(config: @cask.config)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
# typed: true
|
# typed: true
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# Class corresponding to the `url` stanza.
|
module Cask
|
||||||
#
|
# Class corresponding to the `url` stanza.
|
||||||
# @api private
|
#
|
||||||
class URL < Delegator
|
# @api private
|
||||||
|
class URL < Delegator
|
||||||
# @api private
|
# @api private
|
||||||
class DSL
|
class DSL
|
||||||
attr_reader :uri, :specs,
|
attr_reader :uri, :specs,
|
||||||
@ -85,7 +86,7 @@ class URL < Delegator
|
|||||||
sig {
|
sig {
|
||||||
params(
|
params(
|
||||||
uri: T.nilable(T.any(URI::Generic, String)),
|
uri: T.nilable(T.any(URI::Generic, String)),
|
||||||
dsl: T.nilable(Cask::DSL),
|
dsl: T.nilable(::Cask::DSL),
|
||||||
block: T.proc.params(arg0: T.all(String, PageWithURL)).returns(T.untyped),
|
block: T.proc.params(arg0: T.all(String, PageWithURL)).returns(T.untyped),
|
||||||
).void
|
).void
|
||||||
}
|
}
|
||||||
@ -154,7 +155,7 @@ class URL < Delegator
|
|||||||
data: T.nilable(T::Hash[String, String]),
|
data: T.nilable(T::Hash[String, String]),
|
||||||
only_path: T.nilable(String),
|
only_path: T.nilable(String),
|
||||||
caller_location: Thread::Backtrace::Location,
|
caller_location: Thread::Backtrace::Location,
|
||||||
dsl: T.nilable(Cask::DSL),
|
dsl: T.nilable(::Cask::DSL),
|
||||||
block: T.nilable(T.proc.params(arg0: T.all(String, BlockDSL::PageWithURL)).returns(T.untyped)),
|
block: T.nilable(T.proc.params(arg0: T.all(String, BlockDSL::PageWithURL)).returns(T.untyped)),
|
||||||
).void
|
).void
|
||||||
}
|
}
|
||||||
@ -243,4 +244,5 @@ class URL < Delegator
|
|||||||
def from_block?
|
def from_block?
|
||||||
@from_block
|
@from_block
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# typed: strict
|
# typed: strict
|
||||||
|
|
||||||
class URL
|
module Cask
|
||||||
|
class URL
|
||||||
include Kernel
|
include Kernel
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -51,11 +51,15 @@ module Homebrew
|
|||||||
pathname.mtime < days_ago && pathname.ctime < days_ago
|
pathname.mtime < days_ago && pathname.ctime < days_ago
|
||||||
end
|
end
|
||||||
|
|
||||||
sig { params(pathname: Pathname, scrub: T::Boolean).returns(T::Boolean) }
|
sig { params(entry: { path: Pathname, type: T.nilable(Symbol) }, scrub: T::Boolean).returns(T::Boolean) }
|
||||||
def stale?(pathname, scrub: false)
|
def stale?(entry, scrub: false)
|
||||||
|
pathname = entry[:path]
|
||||||
return false unless pathname.resolved_path.file?
|
return false unless pathname.resolved_path.file?
|
||||||
|
|
||||||
if pathname.dirname.basename.to_s == "Cask"
|
case entry[:type]
|
||||||
|
when :api_source
|
||||||
|
stale_api_source?(pathname, scrub)
|
||||||
|
when :cask
|
||||||
stale_cask?(pathname, scrub)
|
stale_cask?(pathname, scrub)
|
||||||
else
|
else
|
||||||
stale_formula?(pathname, scrub)
|
stale_formula?(pathname, scrub)
|
||||||
@ -64,6 +68,31 @@ module Homebrew
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
sig { params(pathname: Pathname, scrub: T::Boolean).returns(T::Boolean) }
|
||||||
|
def stale_api_source?(pathname, scrub)
|
||||||
|
return true if scrub
|
||||||
|
|
||||||
|
org, repo, git_head, type, basename = pathname.each_filename.to_a.last(5)
|
||||||
|
|
||||||
|
name = "#{org}/#{repo}/#{File.basename(T.must(basename), ".rb")}"
|
||||||
|
package = if type == "Cask"
|
||||||
|
begin
|
||||||
|
Cask::CaskLoader.load(name)
|
||||||
|
rescue Cask::CaskError
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
Formulary.factory(name)
|
||||||
|
rescue FormulaUnavailableError
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true if package.nil?
|
||||||
|
|
||||||
|
package.tap_git_head != git_head
|
||||||
|
end
|
||||||
|
|
||||||
sig { params(pathname: Pathname, scrub: T::Boolean).returns(T::Boolean) }
|
sig { params(pathname: Pathname, scrub: T::Boolean).returns(T::Boolean) }
|
||||||
def stale_formula?(pathname, scrub)
|
def stale_formula?(pathname, scrub)
|
||||||
return false unless HOMEBREW_CELLAR.directory?
|
return false unless HOMEBREW_CELLAR.directory?
|
||||||
@ -235,6 +264,7 @@ module Homebrew
|
|||||||
Cleanup.autoremove(dry_run: dry_run?) if Homebrew::EnvConfig.autoremove?
|
Cleanup.autoremove(dry_run: dry_run?) if Homebrew::EnvConfig.autoremove?
|
||||||
|
|
||||||
cleanup_cache
|
cleanup_cache
|
||||||
|
cleanup_empty_api_source_directories
|
||||||
cleanup_logs
|
cleanup_logs
|
||||||
cleanup_lockfiles
|
cleanup_lockfiles
|
||||||
cleanup_python_site_packages
|
cleanup_python_site_packages
|
||||||
@ -287,14 +317,14 @@ module Homebrew
|
|||||||
def cleanup_formula(formula, quiet: false, ds_store: true, cache_db: true)
|
def cleanup_formula(formula, quiet: false, ds_store: true, cache_db: true)
|
||||||
formula.eligible_kegs_for_cleanup(quiet: quiet)
|
formula.eligible_kegs_for_cleanup(quiet: quiet)
|
||||||
.each(&method(:cleanup_keg))
|
.each(&method(:cleanup_keg))
|
||||||
cleanup_cache(Pathname.glob(cache/"#{formula.name}--*"))
|
cleanup_cache(Pathname.glob(cache/"#{formula.name}--*").map { |path| { path: path, type: nil } })
|
||||||
rm_ds_store([formula.rack]) if ds_store
|
rm_ds_store([formula.rack]) if ds_store
|
||||||
cleanup_cache_db(formula.rack) if cache_db
|
cleanup_cache_db(formula.rack) if cache_db
|
||||||
cleanup_lockfiles(FormulaLock.new(formula.name).path)
|
cleanup_lockfiles(FormulaLock.new(formula.name).path)
|
||||||
end
|
end
|
||||||
|
|
||||||
def cleanup_cask(cask, ds_store: true)
|
def cleanup_cask(cask, ds_store: true)
|
||||||
cleanup_cache(Pathname.glob(cache/"Cask/#{cask.token}--*"))
|
cleanup_cache(Pathname.glob(cache/"Cask/#{cask.token}--*").map { |path| { path: path, type: :cask } })
|
||||||
rm_ds_store([cask.caskroom_path]) if ds_store
|
rm_ds_store([cask.caskroom_path]) if ds_store
|
||||||
cleanup_lockfiles(CaskLock.new(cask.token).path)
|
cleanup_lockfiles(CaskLock.new(cask.token).path)
|
||||||
end
|
end
|
||||||
@ -316,16 +346,35 @@ module Homebrew
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def cache_files
|
||||||
|
files = cache.directory? ? cache.children : []
|
||||||
|
cask_files = (cache/"Cask").directory? ? (cache/"Cask").children : []
|
||||||
|
api_source_files = (cache/"api-source").glob("*/*/*/*/*") # org/repo/git_head/type/file.rb
|
||||||
|
|
||||||
|
files.map { |path| { path: path, type: nil } } +
|
||||||
|
cask_files.map { |path| { path: path, type: :cask } } +
|
||||||
|
api_source_files.map { |path| { path: path, type: :api_source } }
|
||||||
|
end
|
||||||
|
|
||||||
|
def cleanup_empty_api_source_directories(directory = cache/"api-source")
|
||||||
|
return if dry_run?
|
||||||
|
return unless directory.directory?
|
||||||
|
|
||||||
|
directory.each_child do |child|
|
||||||
|
next unless child.directory?
|
||||||
|
|
||||||
|
cleanup_empty_api_source_directories(child)
|
||||||
|
child.rmdir if child.empty?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def cleanup_unreferenced_downloads
|
def cleanup_unreferenced_downloads
|
||||||
return if dry_run?
|
return if dry_run?
|
||||||
return unless (cache/"downloads").directory?
|
return unless (cache/"downloads").directory?
|
||||||
|
|
||||||
downloads = (cache/"downloads").children
|
downloads = (cache/"downloads").children
|
||||||
|
|
||||||
referenced_downloads = [cache, cache/"Cask"].select(&:directory?)
|
referenced_downloads = cache_files.map { |file| file[:path] }.select(&:symlink?).map(&:resolved_path)
|
||||||
.flat_map(&:children)
|
|
||||||
.select(&:symlink?)
|
|
||||||
.map(&:resolved_path)
|
|
||||||
|
|
||||||
(downloads - referenced_downloads).each do |download|
|
(downloads - referenced_downloads).each do |download|
|
||||||
if self.class.incomplete?(download)
|
if self.class.incomplete?(download)
|
||||||
@ -346,9 +395,10 @@ module Homebrew
|
|||||||
end
|
end
|
||||||
|
|
||||||
def cleanup_cache(entries = nil)
|
def cleanup_cache(entries = nil)
|
||||||
entries ||= [cache, cache/"Cask"].select(&:directory?).flat_map(&:children)
|
entries ||= cache_files
|
||||||
|
|
||||||
entries.each do |path|
|
entries.each do |entry|
|
||||||
|
path = entry[:path]
|
||||||
next if path == PERIODIC_CLEAN_FILE
|
next if path == PERIODIC_CLEAN_FILE
|
||||||
|
|
||||||
FileUtils.chmod_R 0755, path if self.class.go_cache_directory?(path) && !dry_run?
|
FileUtils.chmod_R 0755, path if self.class.go_cache_directory?(path) && !dry_run?
|
||||||
@ -365,7 +415,7 @@ module Homebrew
|
|||||||
end
|
end
|
||||||
|
|
||||||
# If we've specified --prune don't do the (expensive) .stale? check.
|
# If we've specified --prune don't do the (expensive) .stale? check.
|
||||||
cleanup_path(path) { path.unlink } if !prune? && self.class.stale?(path, scrub: scrub?)
|
cleanup_path(path) { path.unlink } if !prune? && self.class.stale?(entry, scrub: scrub?)
|
||||||
end
|
end
|
||||||
|
|
||||||
cleanup_unreferenced_downloads
|
cleanup_unreferenced_downloads
|
||||||
|
@ -413,7 +413,7 @@ module Homebrew
|
|||||||
resource.url(url, specs)
|
resource.url(url, specs)
|
||||||
resource.owner = Resource.new(formula.name)
|
resource.owner = Resource.new(formula.name)
|
||||||
forced_version = new_version && new_version != resource.version.to_s
|
forced_version = new_version && new_version != resource.version.to_s
|
||||||
resource.version = new_version if forced_version
|
resource.version(new_version) if forced_version
|
||||||
odie "Couldn't identify version, specify it using `--version=`." if resource.version.blank?
|
odie "Couldn't identify version, specify it using `--version=`." if resource.version.blank?
|
||||||
[resource.fetch, forced_version]
|
[resource.fetch, forced_version]
|
||||||
end
|
end
|
||||||
|
133
Library/Homebrew/downloadable.rb
Normal file
133
Library/Homebrew/downloadable.rb
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
# typed: true
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "url"
|
||||||
|
require "checksum"
|
||||||
|
|
||||||
|
# @api private
|
||||||
|
class Downloadable
|
||||||
|
include Context
|
||||||
|
extend T::Helpers
|
||||||
|
|
||||||
|
abstract!
|
||||||
|
|
||||||
|
sig { returns(T.nilable(URL)) }
|
||||||
|
attr_reader :url
|
||||||
|
|
||||||
|
sig { returns(T.nilable(Checksum)) }
|
||||||
|
attr_reader :checksum
|
||||||
|
|
||||||
|
sig { returns(T::Array[String]) }
|
||||||
|
attr_reader :mirrors
|
||||||
|
|
||||||
|
sig { void }
|
||||||
|
def initialize
|
||||||
|
@mirrors = T.let([], T::Array[String])
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize_dup(other)
|
||||||
|
super
|
||||||
|
@checksum = @checksum.dup
|
||||||
|
@mirrors = @mirrors.dup
|
||||||
|
@version = @version.dup
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { override.returns(T.self_type) }
|
||||||
|
def freeze
|
||||||
|
@checksum.freeze
|
||||||
|
@mirrors.freeze
|
||||||
|
@version.freeze
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { returns(T::Boolean) }
|
||||||
|
def downloaded?
|
||||||
|
cached_download.exist?
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { returns(Pathname) }
|
||||||
|
def cached_download
|
||||||
|
downloader.cached_location
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
|
def clear_cache
|
||||||
|
downloader.clear_cache
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { returns(T.nilable(Version)) }
|
||||||
|
def version
|
||||||
|
return @version if @version && !@version.null?
|
||||||
|
|
||||||
|
version = determine_url&.version
|
||||||
|
version unless version&.null?
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { returns(T.class_of(AbstractDownloadStrategy)) }
|
||||||
|
def download_strategy
|
||||||
|
@download_strategy ||= determine_url&.download_strategy
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { returns(AbstractDownloadStrategy) }
|
||||||
|
def downloader
|
||||||
|
@downloader ||= begin
|
||||||
|
primary_url, *mirrors = determine_url_mirrors
|
||||||
|
raise ArgumentError, "attempted to use a Downloadable without a URL!" if primary_url.blank?
|
||||||
|
|
||||||
|
download_strategy.new(primary_url, download_name, version,
|
||||||
|
mirrors: mirrors, cache: cache, **T.must(@url).specs)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { params(verify_download_integrity: T::Boolean, timeout: T.nilable(T.any(Integer, Float))).returns(Pathname) }
|
||||||
|
def fetch(verify_download_integrity: true, timeout: nil)
|
||||||
|
cache.mkpath
|
||||||
|
|
||||||
|
begin
|
||||||
|
downloader.fetch(timeout: timeout)
|
||||||
|
rescue ErrorDuringExecution, CurlDownloadStrategyError => e
|
||||||
|
raise DownloadError.new(self, e)
|
||||||
|
end
|
||||||
|
|
||||||
|
download = cached_download
|
||||||
|
verify_download_integrity(download) if verify_download_integrity
|
||||||
|
download
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { params(filename: Pathname).void }
|
||||||
|
def verify_download_integrity(filename)
|
||||||
|
if filename.file?
|
||||||
|
ohai "Verifying checksum for '#{filename.basename}'" if verbose?
|
||||||
|
filename.verify_checksum(checksum)
|
||||||
|
end
|
||||||
|
rescue ChecksumMissingError
|
||||||
|
opoo <<~EOS
|
||||||
|
Cannot verify integrity of '#{filename.basename}'.
|
||||||
|
No checksum was provided.
|
||||||
|
For your reference, the checksum is:
|
||||||
|
sha256 "#{filename.sha256}"
|
||||||
|
EOS
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
sig { overridable.returns(String) }
|
||||||
|
def download_name
|
||||||
|
File.basename(determine_url.to_s)
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { overridable.returns(T.nilable(URL)) }
|
||||||
|
def determine_url
|
||||||
|
@url
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { overridable.returns(T::Array[String]) }
|
||||||
|
def determine_url_mirrors
|
||||||
|
[determine_url.to_s, *mirrors].uniq
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { overridable.returns(Pathname) }
|
||||||
|
def cache
|
||||||
|
HOMEBREW_CACHE
|
||||||
|
end
|
||||||
|
end
|
@ -603,13 +603,16 @@ class CompilerSelectionError < RuntimeError
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Raised in {Resource#fetch}.
|
# Raised in {Downloadable#fetch}.
|
||||||
class DownloadError < RuntimeError
|
class DownloadError < RuntimeError
|
||||||
def initialize(resource, cause)
|
attr_reader :cause
|
||||||
|
|
||||||
|
def initialize(downloadable, cause)
|
||||||
super <<~EOS
|
super <<~EOS
|
||||||
Failed to download resource #{resource.download_name.inspect}
|
Failed to download resource #{downloadable.download_name.inspect}
|
||||||
#{cause.message}
|
#{cause.message}
|
||||||
EOS
|
EOS
|
||||||
|
@cause = cause
|
||||||
set_backtrace(cause.backtrace)
|
set_backtrace(cause.backtrace)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -181,7 +181,7 @@ class Formula
|
|||||||
attr_accessor :force_bottle
|
attr_accessor :force_bottle
|
||||||
|
|
||||||
# @private
|
# @private
|
||||||
def initialize(name, path, spec, alias_path: nil, force_bottle: false)
|
def initialize(name, path, spec, alias_path: nil, tap: nil, force_bottle: false)
|
||||||
# Only allow instances of subclasses. The base class does not hold any spec information (URLs etc).
|
# Only allow instances of subclasses. The base class does not hold any spec information (URLs etc).
|
||||||
raise "Do not call `Formula.new' directly without a subclass." unless self.class < Formula
|
raise "Do not call `Formula.new' directly without a subclass." unless self.class < Formula
|
||||||
|
|
||||||
@ -191,7 +191,8 @@ class Formula
|
|||||||
self.class.freeze
|
self.class.freeze
|
||||||
|
|
||||||
@name = name
|
@name = name
|
||||||
@path = path
|
@unresolved_path = path
|
||||||
|
@path = path.resolved_path
|
||||||
@alias_path = alias_path
|
@alias_path = alias_path
|
||||||
@alias_name = (File.basename(alias_path) if alias_path)
|
@alias_name = (File.basename(alias_path) if alias_path)
|
||||||
@revision = self.class.revision || 0
|
@revision = self.class.revision || 0
|
||||||
@ -199,7 +200,8 @@ class Formula
|
|||||||
|
|
||||||
@force_bottle = force_bottle
|
@force_bottle = force_bottle
|
||||||
|
|
||||||
@tap = if path == Formulary.core_path(name)
|
@tap = tap
|
||||||
|
@tap ||= if path == Formulary.core_path(name)
|
||||||
CoreTap.instance
|
CoreTap.instance
|
||||||
else
|
else
|
||||||
Tap.from_path(path)
|
Tap.from_path(path)
|
||||||
@ -320,7 +322,7 @@ class Formula
|
|||||||
# The path that was specified to find this formula.
|
# The path that was specified to find this formula.
|
||||||
def specified_path
|
def specified_path
|
||||||
default_specified_path = Pathname(alias_path) if alias_path.present?
|
default_specified_path = Pathname(alias_path) if alias_path.present?
|
||||||
default_specified_path ||= path
|
default_specified_path ||= @unresolved_path
|
||||||
|
|
||||||
return default_specified_path if default_specified_path.presence&.exist?
|
return default_specified_path if default_specified_path.presence&.exist?
|
||||||
return local_bottle_path if local_bottle_path.presence&.exist?
|
return local_bottle_path if local_bottle_path.presence&.exist?
|
||||||
@ -2094,6 +2096,18 @@ class Formula
|
|||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @private
|
||||||
|
sig { returns(T.nilable(String)) }
|
||||||
|
def ruby_source_path
|
||||||
|
path.relative_path_from(tap.path).to_s if tap && path.exist?
|
||||||
|
end
|
||||||
|
|
||||||
|
# @private
|
||||||
|
sig { returns(T.nilable(Checksum)) }
|
||||||
|
def ruby_source_checksum
|
||||||
|
Checksum.new(Digest::SHA256.file(path).hexdigest) if path.exist?
|
||||||
|
end
|
||||||
|
|
||||||
# @private
|
# @private
|
||||||
def to_hash
|
def to_hash
|
||||||
dependencies = deps
|
dependencies = deps
|
||||||
@ -2157,6 +2171,7 @@ class Formula
|
|||||||
"disable_reason" => disable_reason,
|
"disable_reason" => disable_reason,
|
||||||
"service" => service&.serialize,
|
"service" => service&.serialize,
|
||||||
"tap_git_head" => tap_git_head,
|
"tap_git_head" => tap_git_head,
|
||||||
|
"ruby_source_path" => ruby_source_path,
|
||||||
"ruby_source_checksum" => {},
|
"ruby_source_checksum" => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2208,14 +2223,9 @@ class Formula
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
if self.class.loaded_from_api && active_spec.resource_defined?("ruby-source")
|
if (source_checksum = ruby_source_checksum)
|
||||||
hsh["ruby_source_checksum"] = {
|
hsh["ruby_source_checksum"] = {
|
||||||
"sha256" => resource("ruby-source").checksum.hexdigest,
|
"sha256" => source_checksum.hexdigest,
|
||||||
}
|
|
||||||
elsif !self.class.loaded_from_api && path.exist?
|
|
||||||
hsh["ruby_source_path"] = (path.relative_path_from(tap.path).to_s if tap)
|
|
||||||
hsh["ruby_source_checksum"] = {
|
|
||||||
"sha256" => Digest::SHA256.file(path).hexdigest,
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1191,16 +1191,7 @@ on_request: installed_on_request?, options: options)
|
|||||||
if pour_bottle?(output_warning: true)
|
if pour_bottle?(output_warning: true)
|
||||||
formula.fetch_bottle_tab
|
formula.fetch_bottle_tab
|
||||||
else
|
else
|
||||||
if formula.class.loaded_from_api
|
@formula = Homebrew::API::Formula.source_download(formula) if formula.class.loaded_from_api
|
||||||
# TODO: unify with cask logic (https://github.com/Homebrew/brew/issues/14746)
|
|
||||||
resource = formula.resource("ruby-source")
|
|
||||||
resource.fetch
|
|
||||||
@formula = Formulary.factory(resource.cached_download,
|
|
||||||
formula.active_spec_sym,
|
|
||||||
alias_path: formula.alias_path,
|
|
||||||
flags: formula.class.build_flags,
|
|
||||||
from: :formula_installer)
|
|
||||||
end
|
|
||||||
|
|
||||||
formula.fetch_patches
|
formula.fetch_patches
|
||||||
formula.resources.each(&:fetch)
|
formula.resources.each(&:fetch)
|
||||||
|
@ -252,15 +252,6 @@ module Formulary
|
|||||||
link_overwrite overwrite_path
|
link_overwrite overwrite_path
|
||||||
end
|
end
|
||||||
|
|
||||||
resource "ruby-source" do
|
|
||||||
tap_git_head = json_formula.fetch("tap_git_head", "HEAD")
|
|
||||||
ruby_source_path = json_formula.fetch("ruby_source_path", "Formula/#{name}.rb")
|
|
||||||
ruby_source_sha256 = json_formula.dig("ruby_source_checksum", "sha256")
|
|
||||||
|
|
||||||
url "https://raw.githubusercontent.com/Homebrew/homebrew-core/#{tap_git_head}/#{ruby_source_path}"
|
|
||||||
sha256 ruby_source_sha256 if ruby_source_sha256
|
|
||||||
end
|
|
||||||
|
|
||||||
def install
|
def install
|
||||||
raise "Cannot build from source from abstract formula."
|
raise "Cannot build from source from abstract formula."
|
||||||
end
|
end
|
||||||
@ -307,6 +298,17 @@ module Formulary
|
|||||||
def versioned_formulae_names
|
def versioned_formulae_names
|
||||||
self.class.instance_variable_get(:@versioned_formulae_array)
|
self.class.instance_variable_get(:@versioned_formulae_array)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ruby_source_path_string = json_formula["ruby_source_path"]
|
||||||
|
def ruby_source_path
|
||||||
|
self.class.instance_variable_get(:@ruby_source_path_string)
|
||||||
|
end
|
||||||
|
|
||||||
|
@ruby_source_checksum_hash = json_formula["ruby_source_checksum"]
|
||||||
|
def ruby_source_checksum
|
||||||
|
checksum_hash = self.class.instance_variable_get(:@ruby_source_checksum_hash)
|
||||||
|
Checksum.new(checksum_hash["sha256"]) if checksum_hash&.key?("sha256")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
T.cast(klass, T.class_of(Formula)).loaded_from_api = true
|
T.cast(klass, T.class_of(Formula)).loaded_from_api = true
|
||||||
@ -384,10 +386,13 @@ module Formulary
|
|||||||
attr_reader :path
|
attr_reader :path
|
||||||
# The name used to install the formula
|
# The name used to install the formula
|
||||||
attr_reader :alias_path
|
attr_reader :alias_path
|
||||||
|
# The formula's tap (nil if it should be implicitly determined)
|
||||||
|
attr_reader :tap
|
||||||
|
|
||||||
def initialize(name, path)
|
def initialize(name, path, tap: nil)
|
||||||
@name = name
|
@name = name
|
||||||
@path = path.resolved_path
|
@path = path
|
||||||
|
@tap = tap
|
||||||
end
|
end
|
||||||
|
|
||||||
# Gets the formula instance.
|
# Gets the formula instance.
|
||||||
@ -396,7 +401,7 @@ module Formulary
|
|||||||
def get_formula(spec, alias_path: nil, force_bottle: false, flags: [], ignore_errors: false)
|
def get_formula(spec, alias_path: nil, force_bottle: false, flags: [], ignore_errors: false)
|
||||||
alias_path ||= self.alias_path
|
alias_path ||= self.alias_path
|
||||||
klass(flags: flags, ignore_errors: ignore_errors)
|
klass(flags: flags, ignore_errors: ignore_errors)
|
||||||
.new(name, path, spec, alias_path: alias_path, force_bottle: force_bottle)
|
.new(name, path, spec, alias_path: alias_path, tap: tap, force_bottle: force_bottle)
|
||||||
end
|
end
|
||||||
|
|
||||||
def klass(flags:, ignore_errors:)
|
def klass(flags:, ignore_errors:)
|
||||||
@ -473,12 +478,7 @@ module Formulary
|
|||||||
def initialize(path)
|
def initialize(path)
|
||||||
path = Pathname.new(path).expand_path
|
path = Pathname.new(path).expand_path
|
||||||
name = path.basename(".rb").to_s
|
name = path.basename(".rb").to_s
|
||||||
|
super name, path, tap: Homebrew::API.tap_from_source_download(path)
|
||||||
# For files we've downloaded, they will be prefixed with `{URL MD5}--`.
|
|
||||||
# Remove that prefix to get the original filename.
|
|
||||||
name = name.split("--", 2).last if path.dirname == HOMEBREW_CACHE/"downloads"
|
|
||||||
|
|
||||||
super name, path
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -498,7 +498,6 @@ module Formulary
|
|||||||
end
|
end
|
||||||
|
|
||||||
def load_file(flags:, ignore_errors:)
|
def load_file(flags:, ignore_errors:)
|
||||||
if @from != :formula_installer
|
|
||||||
match = url.match(%r{githubusercontent.com/[\w-]+/[\w-]+/[a-f0-9]{40}(?:/Formula)?/(?<name>[\w+-.@]+).rb})
|
match = url.match(%r{githubusercontent.com/[\w-]+/[\w-]+/[a-f0-9]{40}(?:/Formula)?/(?<name>[\w+-.@]+).rb})
|
||||||
if match
|
if match
|
||||||
raise UnsupportedInstallationMethod,
|
raise UnsupportedInstallationMethod,
|
||||||
@ -510,7 +509,6 @@ module Formulary
|
|||||||
"`brew extract` or `brew create` and `brew tap-new` to create a formula file in a tap " \
|
"`brew extract` or `brew create` and `brew tap-new` to create a formula file in a tap " \
|
||||||
"on GitHub instead."
|
"on GitHub instead."
|
||||||
end
|
end
|
||||||
end
|
|
||||||
HOMEBREW_CACHE_FORMULA.mkpath
|
HOMEBREW_CACHE_FORMULA.mkpath
|
||||||
FileUtils.rm_f(path)
|
FileUtils.rm_f(path)
|
||||||
curl_download url, to: path
|
curl_download url, to: path
|
||||||
@ -525,30 +523,28 @@ module Formulary
|
|||||||
|
|
||||||
# Loads tapped formulae.
|
# Loads tapped formulae.
|
||||||
class TapLoader < FormulaLoader
|
class TapLoader < FormulaLoader
|
||||||
attr_reader :tap
|
|
||||||
|
|
||||||
def initialize(tapped_name, from: nil)
|
def initialize(tapped_name, from: nil)
|
||||||
warn = [:keg, :rack].exclude?(from)
|
warn = [:keg, :rack].exclude?(from)
|
||||||
name, path = formula_name_path(tapped_name, warn: warn)
|
name, path, tap = formula_name_path(tapped_name, warn: warn)
|
||||||
super name, path
|
super name, path, tap: tap
|
||||||
end
|
end
|
||||||
|
|
||||||
def formula_name_path(tapped_name, warn: true)
|
def formula_name_path(tapped_name, warn: true)
|
||||||
user, repo, name = tapped_name.split("/", 3).map(&:downcase)
|
user, repo, name = tapped_name.split("/", 3).map(&:downcase)
|
||||||
@tap = Tap.fetch user, repo
|
tap = Tap.fetch user, repo
|
||||||
path = find_formula_from_name(name)
|
path = find_formula_from_name(name, tap)
|
||||||
|
|
||||||
unless path.file?
|
unless path.file?
|
||||||
if (possible_alias = @tap.alias_dir/name).file?
|
if (possible_alias = tap.alias_dir/name).file?
|
||||||
path = possible_alias.resolved_path
|
path = possible_alias.resolved_path
|
||||||
name = path.basename(".rb").to_s
|
name = path.basename(".rb").to_s
|
||||||
elsif (new_name = @tap.formula_renames[name]) &&
|
elsif (new_name = tap.formula_renames[name]) &&
|
||||||
(new_path = find_formula_from_name(new_name)).file?
|
(new_path = find_formula_from_name(new_name, tap)).file?
|
||||||
old_name = name
|
old_name = name
|
||||||
path = new_path
|
path = new_path
|
||||||
name = new_name
|
name = new_name
|
||||||
new_name = @tap.core_tap? ? name : "#{@tap}/#{name}"
|
new_name = tap.core_tap? ? name : "#{tap}/#{name}"
|
||||||
elsif (new_tap_name = @tap.tap_migrations[name])
|
elsif (new_tap_name = tap.tap_migrations[name])
|
||||||
new_tap_user, new_tap_repo, = new_tap_name.split("/")
|
new_tap_user, new_tap_repo, = new_tap_name.split("/")
|
||||||
new_tap_name = "#{new_tap_user}/#{new_tap_repo}"
|
new_tap_name = "#{new_tap_user}/#{new_tap_repo}"
|
||||||
new_tap = Tap.fetch new_tap_name
|
new_tap = Tap.fetch new_tap_name
|
||||||
@ -562,7 +558,7 @@ module Formulary
|
|||||||
opoo "Use #{new_name} instead of deprecated #{old_name}" if warn && old_name && new_name
|
opoo "Use #{new_name} instead of deprecated #{old_name}" if warn && old_name && new_name
|
||||||
end
|
end
|
||||||
|
|
||||||
[name, path]
|
[name, path, tap]
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_formula(spec, alias_path: nil, force_bottle: false, flags: [], ignore_errors: false)
|
def get_formula(spec, alias_path: nil, force_bottle: false, flags: [], ignore_errors: false)
|
||||||
@ -584,8 +580,8 @@ module Formulary
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def find_formula_from_name(name)
|
def find_formula_from_name(name, tap)
|
||||||
Formulary.find_formula_in_tap(name, @tap)
|
Formulary.find_formula_in_tap(name, tap)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -768,7 +764,7 @@ module Formulary
|
|||||||
when URL_START_REGEX
|
when URL_START_REGEX
|
||||||
return FromUrlLoader.new(ref, from: from)
|
return FromUrlLoader.new(ref, from: from)
|
||||||
when HOMEBREW_TAP_FORMULA_REGEX
|
when HOMEBREW_TAP_FORMULA_REGEX
|
||||||
if ref.start_with?("homebrew/core/") && !Homebrew::EnvConfig.no_install_from_api?
|
if ref.match?(%r{^homebrew/(?:homebrew-)?core/}i) && !Homebrew::EnvConfig.no_install_from_api?
|
||||||
name = ref.split("/", 3).last
|
name = ref.split("/", 3).last
|
||||||
return FormulaAPILoader.new(name) if Homebrew::API::Formula.all_formulae.key?(name)
|
return FormulaAPILoader.new(name) if Homebrew::API::Formula.all_formulae.key?(name)
|
||||||
return AliasAPILoader.new(name) if Homebrew::API::Formula.all_aliases.key?(name)
|
return AliasAPILoader.new(name) if Homebrew::API::Formula.all_aliases.key?(name)
|
||||||
|
@ -126,7 +126,7 @@ class ExternalPatch
|
|||||||
|
|
||||||
def owner=(owner)
|
def owner=(owner)
|
||||||
resource.owner = owner
|
resource.owner = owner
|
||||||
resource.version = resource.checksum || ERB::Util.url_encode(resource.url)
|
resource.version(resource.checksum&.hexdigest || ERB::Util.url_encode(resource.url))
|
||||||
end
|
end
|
||||||
|
|
||||||
def apply
|
def apply
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
# typed: true
|
# typed: true
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "download_strategy"
|
require "downloadable"
|
||||||
require "checksum"
|
|
||||||
require "version"
|
|
||||||
require "mktemp"
|
require "mktemp"
|
||||||
require "livecheck"
|
require "livecheck"
|
||||||
require "extend/on_system"
|
require "extend/on_system"
|
||||||
@ -13,14 +11,13 @@ require "extend/on_system"
|
|||||||
# of this class.
|
# of this class.
|
||||||
#
|
#
|
||||||
# @api private
|
# @api private
|
||||||
class Resource
|
class Resource < Downloadable
|
||||||
include Context
|
|
||||||
include FileUtils
|
include FileUtils
|
||||||
include OnSystem::MacOSAndLinux
|
include OnSystem::MacOSAndLinux
|
||||||
|
|
||||||
attr_reader :mirrors, :specs, :using, :source_modified_time, :patches, :owner
|
attr_reader :source_modified_time, :patches, :owner
|
||||||
attr_writer :version
|
attr_writer :checksum
|
||||||
attr_accessor :download_strategy, :checksum
|
attr_accessor :download_strategy
|
||||||
|
|
||||||
# Formula name must be set after the DSL, as we have no access to the
|
# Formula name must be set after the DSL, as we have no access to the
|
||||||
# formula name before initialization of the formula.
|
# formula name before initialization of the formula.
|
||||||
@ -28,39 +25,25 @@ class Resource
|
|||||||
|
|
||||||
sig { params(name: T.nilable(String), block: T.nilable(T.proc.bind(Resource).void)).void }
|
sig { params(name: T.nilable(String), block: T.nilable(T.proc.bind(Resource).void)).void }
|
||||||
def initialize(name = nil, &block)
|
def initialize(name = nil, &block)
|
||||||
|
super()
|
||||||
# Ensure this is synced with `initialize_dup` and `freeze` (excluding simple objects like integers and booleans)
|
# Ensure this is synced with `initialize_dup` and `freeze` (excluding simple objects like integers and booleans)
|
||||||
@name = name
|
@name = name
|
||||||
@url = nil
|
|
||||||
@version = nil
|
|
||||||
@mirrors = []
|
|
||||||
@specs = {}
|
|
||||||
@checksum = nil
|
|
||||||
@using = nil
|
|
||||||
@patches = []
|
@patches = []
|
||||||
@livecheck = Livecheck.new(self)
|
@livecheck = Livecheck.new(self)
|
||||||
@livecheckable = false
|
@livecheckable = false
|
||||||
|
@insecure = false
|
||||||
instance_eval(&block) if block
|
instance_eval(&block) if block
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize_dup(other)
|
def initialize_dup(other)
|
||||||
super
|
super
|
||||||
@name = @name.dup
|
@name = @name.dup
|
||||||
@version = @version.dup
|
|
||||||
@mirrors = @mirrors.dup
|
|
||||||
@specs = @specs.dup
|
|
||||||
@checksum = @checksum.dup
|
|
||||||
@using = @using.dup
|
|
||||||
@patches = @patches.dup
|
@patches = @patches.dup
|
||||||
@livecheck = @livecheck.dup
|
@livecheck = @livecheck.dup
|
||||||
end
|
end
|
||||||
|
|
||||||
def freeze
|
def freeze
|
||||||
@name.freeze
|
@name.freeze
|
||||||
@version.freeze
|
|
||||||
@mirrors.freeze
|
|
||||||
@specs.freeze
|
|
||||||
@checksum.freeze
|
|
||||||
@using.freeze
|
|
||||||
@patches.freeze
|
@patches.freeze
|
||||||
@livecheck.freeze
|
@livecheck.freeze
|
||||||
super
|
super
|
||||||
@ -73,15 +56,15 @@ class Resource
|
|||||||
return if !owner.respond_to?(:full_name) || owner.full_name != "ca-certificates"
|
return if !owner.respond_to?(:full_name) || owner.full_name != "ca-certificates"
|
||||||
return if Homebrew::EnvConfig.no_insecure_redirect?
|
return if Homebrew::EnvConfig.no_insecure_redirect?
|
||||||
|
|
||||||
@specs[:insecure] = !specs[:bottle] && !DevelopmentTools.ca_file_handles_most_https_certificates?
|
@insecure = !specs[:bottle] && !DevelopmentTools.ca_file_handles_most_https_certificates?
|
||||||
|
return if @url.nil?
|
||||||
|
|
||||||
|
specs = if @insecure
|
||||||
|
@url.specs.merge({ insecure: true })
|
||||||
|
else
|
||||||
|
@url.specs.except(:insecure)
|
||||||
end
|
end
|
||||||
|
@url = URL.new(@url.to_s, specs)
|
||||||
def downloader
|
|
||||||
return @downloader if @downloader.present?
|
|
||||||
|
|
||||||
url, *mirrors = determine_url_mirrors
|
|
||||||
@downloader = download_strategy.new(url, download_name, version,
|
|
||||||
mirrors: mirrors, **specs)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Removes /s from resource names; this allows Go package names
|
# Removes /s from resource names; this allows Go package names
|
||||||
@ -98,18 +81,6 @@ class Resource
|
|||||||
"#{owner.name}--#{escaped_name}"
|
"#{owner.name}--#{escaped_name}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def downloaded?
|
|
||||||
cached_download.exist?
|
|
||||||
end
|
|
||||||
|
|
||||||
def cached_download
|
|
||||||
downloader.cached_location
|
|
||||||
end
|
|
||||||
|
|
||||||
def clear_cache
|
|
||||||
downloader.clear_cache
|
|
||||||
end
|
|
||||||
|
|
||||||
# Verifies download and unpacks it.
|
# Verifies download and unpacks it.
|
||||||
# The block may call `|resource, staging| staging.retain!` to retain the staging
|
# The block may call `|resource, staging| staging.retain!` to retain the staging
|
||||||
# directory. Subclasses that override stage should implement the tmp
|
# directory. Subclasses that override stage should implement the tmp
|
||||||
@ -171,33 +142,9 @@ class Resource
|
|||||||
end
|
end
|
||||||
|
|
||||||
def fetch(verify_download_integrity: true)
|
def fetch(verify_download_integrity: true)
|
||||||
HOMEBREW_CACHE.mkpath
|
|
||||||
|
|
||||||
fetch_patches
|
fetch_patches
|
||||||
|
|
||||||
begin
|
super(verify_download_integrity: verify_download_integrity)
|
||||||
downloader.fetch
|
|
||||||
rescue ErrorDuringExecution, CurlDownloadStrategyError => e
|
|
||||||
raise DownloadError.new(self, e)
|
|
||||||
end
|
|
||||||
|
|
||||||
download = cached_download
|
|
||||||
verify_download_integrity(download) if verify_download_integrity
|
|
||||||
download
|
|
||||||
end
|
|
||||||
|
|
||||||
def verify_download_integrity(filename)
|
|
||||||
if filename.file?
|
|
||||||
ohai "Verifying checksum for '#{filename.basename}'" if verbose?
|
|
||||||
filename.verify_checksum(checksum)
|
|
||||||
end
|
|
||||||
rescue ChecksumMissingError
|
|
||||||
opoo <<~EOS
|
|
||||||
Cannot verify integrity of '#{filename.basename}'.
|
|
||||||
No checksum was provided for this resource.
|
|
||||||
For your reference, the checksum is:
|
|
||||||
sha256 "#{filename.sha256}"
|
|
||||||
EOS
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# @!attribute [w] livecheck
|
# @!attribute [w] livecheck
|
||||||
@ -230,24 +177,29 @@ class Resource
|
|||||||
end
|
end
|
||||||
|
|
||||||
def url(val = nil, **specs)
|
def url(val = nil, **specs)
|
||||||
return @url if val.nil?
|
return @url&.to_s if val.nil?
|
||||||
|
|
||||||
specs = specs.dup
|
specs = specs.dup
|
||||||
# Don't allow this to be set.
|
# Don't allow this to be set.
|
||||||
specs.delete(:insecure)
|
specs.delete(:insecure)
|
||||||
|
|
||||||
@url = val
|
specs[:insecure] = true if @insecure
|
||||||
@using = specs.delete(:using)
|
|
||||||
@download_strategy = DownloadStrategyDetector.detect(url, using)
|
@url = URL.new(val, specs)
|
||||||
@specs.merge!(specs)
|
|
||||||
@downloader = nil
|
@downloader = nil
|
||||||
@version = detect_version(@version)
|
@download_strategy = @url.download_strategy
|
||||||
end
|
end
|
||||||
|
|
||||||
def version(val = nil)
|
def version(val = nil)
|
||||||
return @version if val.nil?
|
return super() if val.nil?
|
||||||
|
|
||||||
@version = detect_version(val)
|
@version = case val
|
||||||
|
when String then Version.create(val)
|
||||||
|
when Version then val
|
||||||
|
else
|
||||||
|
# TODO: This can probably go if/when typechecking is enforced in taps.
|
||||||
|
raise TypeError, "version '#{val.inspect}' should be a string"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def mirror(val)
|
def mirror(val)
|
||||||
@ -259,6 +211,14 @@ class Resource
|
|||||||
patches << p
|
patches << p
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def using
|
||||||
|
@url&.using
|
||||||
|
end
|
||||||
|
|
||||||
|
def specs
|
||||||
|
@url&.specs || {}.freeze
|
||||||
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def stage_resource(prefix, debug_symbols: false, &block)
|
def stage_resource(prefix, debug_symbols: false, &block)
|
||||||
@ -267,18 +227,6 @@ class Resource
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def detect_version(val)
|
|
||||||
version = case val
|
|
||||||
when nil then url.nil? ? Version::NULL : Version.detect(url, **specs)
|
|
||||||
when String then Version.create(val)
|
|
||||||
when Version then val
|
|
||||||
else
|
|
||||||
raise TypeError, "version '#{val.inspect}' should be a string"
|
|
||||||
end
|
|
||||||
|
|
||||||
version unless version.null?
|
|
||||||
end
|
|
||||||
|
|
||||||
def determine_url_mirrors
|
def determine_url_mirrors
|
||||||
extra_urls = []
|
extra_urls = []
|
||||||
|
|
||||||
@ -301,7 +249,7 @@ class Resource
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
[*extra_urls, url, *mirrors].uniq
|
[*extra_urls, *super].uniq
|
||||||
end
|
end
|
||||||
|
|
||||||
# A resource containing a Go package.
|
# A resource containing a Go package.
|
||||||
|
@ -89,15 +89,11 @@ class SoftwareSpec
|
|||||||
@resource.owner = self
|
@resource.owner = self
|
||||||
resources.each_value do |r|
|
resources.each_value do |r|
|
||||||
r.owner = self
|
r.owner = self
|
||||||
r.version ||= begin
|
next if r.version
|
||||||
|
|
||||||
raise "#{full_name}: version missing for \"#{r.name}\" resource!" if version.nil?
|
raise "#{full_name}: version missing for \"#{r.name}\" resource!" if version.nil?
|
||||||
|
|
||||||
if version.head?
|
r.version(version.head? ? Version.create("HEAD") : version.dup)
|
||||||
Version.create("HEAD")
|
|
||||||
else
|
|
||||||
version.dup
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
patches.each { |p| p.owner = self }
|
patches.each { |p| p.owner = self }
|
||||||
end
|
end
|
||||||
@ -281,7 +277,7 @@ end
|
|||||||
class HeadSoftwareSpec < SoftwareSpec
|
class HeadSoftwareSpec < SoftwareSpec
|
||||||
def initialize(flags: [])
|
def initialize(flags: [])
|
||||||
super
|
super
|
||||||
@resource.version = Version.create("HEAD")
|
@resource.version(Version.create("HEAD"))
|
||||||
end
|
end
|
||||||
|
|
||||||
def verify_download_integrity(_filename)
|
def verify_download_integrity(_filename)
|
||||||
@ -340,7 +336,6 @@ class Bottle
|
|||||||
def initialize(formula, spec, tag = nil)
|
def initialize(formula, spec, tag = nil)
|
||||||
@name = formula.name
|
@name = formula.name
|
||||||
@resource = Resource.new
|
@resource = Resource.new
|
||||||
@resource.specs[:bottle] = true
|
|
||||||
@resource.owner = formula
|
@resource.owner = formula
|
||||||
@spec = spec
|
@spec = spec
|
||||||
|
|
||||||
@ -350,7 +345,7 @@ class Bottle
|
|||||||
@cellar = tag_spec.cellar
|
@cellar = tag_spec.cellar
|
||||||
@rebuild = spec.rebuild
|
@rebuild = spec.rebuild
|
||||||
|
|
||||||
@resource.version = formula.pkg_version.to_s
|
@resource.version(formula.pkg_version.to_s)
|
||||||
@resource.checksum = tag_spec.checksum
|
@resource.checksum = tag_spec.checksum
|
||||||
|
|
||||||
@fetch_tab_retried = false
|
@fetch_tab_retried = false
|
||||||
@ -468,13 +463,15 @@ class Bottle
|
|||||||
using: CurlGitHubPackagesDownloadStrategy,
|
using: CurlGitHubPackagesDownloadStrategy,
|
||||||
headers: ["Accept: application/vnd.oci.image.index.v1+json"],
|
headers: ["Accept: application/vnd.oci.image.index.v1+json"],
|
||||||
)
|
)
|
||||||
resource.downloader.resolved_basename = "#{name}-#{version_rebuild}.bottle_manifest.json"
|
T.cast(resource.downloader, CurlGitHubPackagesDownloadStrategy).resolved_basename =
|
||||||
|
"#{name}-#{version_rebuild}.bottle_manifest.json"
|
||||||
resource
|
resource
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def select_download_strategy(specs)
|
def select_download_strategy(specs)
|
||||||
specs[:using] ||= DownloadStrategyDetector.detect(@root_url)
|
specs[:using] ||= DownloadStrategyDetector.detect(@root_url)
|
||||||
|
specs[:bottle] = true
|
||||||
specs
|
specs
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -6,11 +6,12 @@ HOMEBREW_TAP_FORMULA_REGEX = %r{^([\w-]+)/([\w-]+)/([\w+-.@]+)$}.freeze
|
|||||||
# Match taps' casks, e.g. `someuser/sometap/somecask`
|
# Match taps' casks, e.g. `someuser/sometap/somecask`
|
||||||
HOMEBREW_TAP_CASK_REGEX = %r{^([\w-]+)/([\w-]+)/([a-z0-9\-_]+)$}.freeze
|
HOMEBREW_TAP_CASK_REGEX = %r{^([\w-]+)/([\w-]+)/([a-z0-9\-_]+)$}.freeze
|
||||||
# Match main cask taps' casks, e.g. `homebrew/cask/somecask` or `somecask`
|
# Match main cask taps' casks, e.g. `homebrew/cask/somecask` or `somecask`
|
||||||
HOMEBREW_MAIN_TAP_CASK_REGEX = %r{^(homebrew/cask/)?[a-z0-9\-_]+$}.freeze
|
HOMEBREW_MAIN_TAP_CASK_REGEX = %r{^([Hh]omebrew/(?:homebrew-)?cask/)?[a-z0-9\-_]+$}.freeze
|
||||||
# Match taps' directory paths, e.g. `HOMEBREW_LIBRARY/Taps/someuser/sometap`
|
# Match taps' directory paths, e.g. `HOMEBREW_LIBRARY/Taps/someuser/sometap`
|
||||||
HOMEBREW_TAP_DIR_REGEX = %r{#{Regexp.escape(HOMEBREW_LIBRARY.to_s)}/Taps/(?<user>[\w-]+)/(?<repo>[\w-]+)}.freeze
|
HOMEBREW_TAP_DIR_REGEX = %r{#{Regexp.escape(HOMEBREW_LIBRARY.to_s)}/Taps/(?<user>[\w-]+)/(?<repo>[\w-]+)}.freeze
|
||||||
# Match taps' formula paths, e.g. `HOMEBREW_LIBRARY/Taps/someuser/sometap/someformula`
|
# Match taps' formula paths, e.g. `HOMEBREW_LIBRARY/Taps/someuser/sometap/someformula`
|
||||||
HOMEBREW_TAP_PATH_REGEX = Regexp.new(HOMEBREW_TAP_DIR_REGEX.source + %r{(?:/.*)?$}.source).freeze
|
HOMEBREW_TAP_PATH_REGEX = Regexp.new(HOMEBREW_TAP_DIR_REGEX.source + %r{(?:/.*)?$}.source).freeze
|
||||||
# Match official taps' casks, e.g. `homebrew/cask/somecask or homebrew/cask-versions/somecask`
|
# Match official taps' casks, e.g. `homebrew/cask/somecask or homebrew/cask-versions/somecask`
|
||||||
HOMEBREW_CASK_TAP_CASK_REGEX = %r{^(?:([Cc]askroom)/(cask|versions)|(homebrew)/(cask|cask-[\w-]+))/([\w+-.]+)$}.freeze
|
HOMEBREW_CASK_TAP_CASK_REGEX =
|
||||||
|
%r{^(?:([Cc]askroom)/(cask|versions)|([Hh]omebrew)/(?:homebrew-)?(cask|cask-[\w-]+))/([\w+-.]+)$}.freeze
|
||||||
HOMEBREW_OFFICIAL_REPO_PREFIXES_REGEX = /^(home|linux)brew-/.freeze
|
HOMEBREW_OFFICIAL_REPO_PREFIXES_REGEX = /^(home|linux)brew-/.freeze
|
||||||
|
@ -45,14 +45,4 @@ describe Homebrew::API::Cask do
|
|||||||
expect(casks_output).to eq casks_hash
|
expect(casks_output).to eq casks_hash
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "::fetch_source" do
|
|
||||||
it "fetches the source of a cask (defaulting to master when no `git_head` is passed)" do
|
|
||||||
curl_output = instance_double(SystemCommand::Result, stdout: "foo", success?: true)
|
|
||||||
expect(Utils::Curl).to receive(:curl_output)
|
|
||||||
.with("--fail", "https://raw.githubusercontent.com/Homebrew/homebrew-cask/HEAD/Casks/foo.rb")
|
|
||||||
.and_return(curl_output)
|
|
||||||
described_class.fetch_source("foo", path: "Casks/foo.rb", git_head: "HEAD")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
@ -67,19 +67,4 @@ describe Homebrew::API do
|
|||||||
end.to raise_error(SystemExit)
|
end.to raise_error(SystemExit)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "::fetch_file_source" do
|
|
||||||
it "fetches a file" do
|
|
||||||
mock_curl_output stdout: json
|
|
||||||
fetched_json = described_class.fetch_homebrew_cask_source("foo", path: "Casks/foo.rb", git_head: "HEAD")
|
|
||||||
expect(fetched_json).to eq json
|
|
||||||
end
|
|
||||||
|
|
||||||
it "raises an error if the file does not exist" do
|
|
||||||
mock_curl_output success: false
|
|
||||||
expect do
|
|
||||||
described_class.fetch_homebrew_cask_source("bar", path: "Casks/bar.rb", git_head: "HEAD")
|
|
||||||
end.to raise_error(ArgumentError, /No valid file found/)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
@ -231,7 +231,7 @@ describe Cask::Cask, :cask do
|
|||||||
|
|
||||||
context "when loaded from json file" do
|
context "when loaded from json file" do
|
||||||
it "returns expected hash" do
|
it "returns expected hash" do
|
||||||
expect(Homebrew::API::Cask).not_to receive(:fetch_source)
|
expect(Homebrew::API::Cask).not_to receive(:source_download)
|
||||||
hash = Cask::CaskLoader::FromAPILoader.new(
|
hash = Cask::CaskLoader::FromAPILoader.new(
|
||||||
"everything", from_json: JSON.parse(expected_json)
|
"everything", from_json: JSON.parse(expected_json)
|
||||||
).load(config: nil).to_h
|
).load(config: nil).to_h
|
||||||
|
@ -12,6 +12,7 @@ module Cask
|
|||||||
let(:downloaded_path) { Pathname.new("cask.zip") }
|
let(:downloaded_path) { Pathname.new("cask.zip") }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
allow(downloaded_path).to receive(:file?).and_return(true)
|
||||||
allow(downloaded_path).to receive(:sha256).and_return(computed_sha256)
|
allow(downloaded_path).to receive(:sha256).and_return(computed_sha256)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -240,7 +240,8 @@ describe Cask::Installer, :cask do
|
|||||||
let(:content) { File.read(path) }
|
let(:content) { File.read(path) }
|
||||||
|
|
||||||
it "installs cask" do
|
it "installs cask" do
|
||||||
expect(Homebrew::API::Cask).to receive(:fetch_source).once.and_return(content)
|
source_caffeine = Cask::CaskLoader.load(path)
|
||||||
|
expect(Homebrew::API::Cask).to receive(:source_download).once.and_return(source_caffeine)
|
||||||
|
|
||||||
caffeine = Cask::CaskLoader.load(path)
|
caffeine = Cask::CaskLoader.load(path)
|
||||||
expect(caffeine).to receive(:loaded_from_api?).once.and_return(true)
|
expect(caffeine).to receive(:loaded_from_api?).once.and_return(true)
|
||||||
@ -307,7 +308,8 @@ describe Cask::Installer, :cask do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "uninstalls cask" do
|
it "uninstalls cask" do
|
||||||
expect(Homebrew::API::Cask).to receive(:fetch_source).twice.and_return(content)
|
source_caffeine = Cask::CaskLoader.load(path)
|
||||||
|
expect(Homebrew::API::Cask).to receive(:source_download).twice.and_return(source_caffeine)
|
||||||
|
|
||||||
caffeine = Cask::CaskLoader.load(path)
|
caffeine = Cask::CaskLoader.load(path)
|
||||||
expect(caffeine).to receive(:loaded_from_api?).twice.and_return(true)
|
expect(caffeine).to receive(:loaded_from_api?).twice.and_return(true)
|
||||||
|
@ -206,14 +206,14 @@ describe Formula do
|
|||||||
example "installed alias with tap" do
|
example "installed alias with tap" do
|
||||||
tap = Tap.new("user", "repo")
|
tap = Tap.new("user", "repo")
|
||||||
name = "foo"
|
name = "foo"
|
||||||
path = "#{tap.path}/Formula/#{name}.rb"
|
path = tap.path/"Formula/#{name}.rb"
|
||||||
f = formula name, path: path do
|
f = formula name, path: path do
|
||||||
url "foo-1.0"
|
url "foo-1.0"
|
||||||
end
|
end
|
||||||
|
|
||||||
build_values_with_no_installed_alias = [
|
build_values_with_no_installed_alias = [
|
||||||
BuildOptions.new(Options.new, f.options),
|
BuildOptions.new(Options.new, f.options),
|
||||||
Tab.new(source: { "path" => f.path }),
|
Tab.new(source: { "path" => f.path.to_s }),
|
||||||
]
|
]
|
||||||
build_values_with_no_installed_alias.each do |build|
|
build_values_with_no_installed_alias.each do |build|
|
||||||
f.build = build
|
f.build = build
|
||||||
|
@ -15,7 +15,7 @@ describe Formulary do
|
|||||||
|
|
||||||
bottle do
|
bottle do
|
||||||
root_url "file://#{bottle_dir}"
|
root_url "file://#{bottle_dir}"
|
||||||
sha256 cellar: :any_skip_relocation, #{Utils::Bottles.tag}: "8f9aecd233463da6a4ea55f5f88fc5841718c013f3e2a7941350d6130f1dc149"
|
sha256 cellar: :any_skip_relocation, #{Utils::Bottles.tag}: "d7b9f4e8bf83608b71fe958a99f19f2e5e68bb2582965d32e41759c24f1aef97"
|
||||||
end
|
end
|
||||||
|
|
||||||
def install
|
def install
|
||||||
@ -243,7 +243,7 @@ describe Formulary do
|
|||||||
Utils::Bottles.tag.to_s => {
|
Utils::Bottles.tag.to_s => {
|
||||||
"cellar" => ":any",
|
"cellar" => ":any",
|
||||||
"url" => "file://#{bottle_dir}/#{formula_name}",
|
"url" => "file://#{bottle_dir}/#{formula_name}",
|
||||||
"sha256" => "8f9aecd233463da6a4ea55f5f88fc5841718c013f3e2a7941350d6130f1dc149",
|
"sha256" => "d7b9f4e8bf83608b71fe958a99f19f2e5e68bb2582965d32e41759c24f1aef97",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -143,7 +143,7 @@ describe Resource do
|
|||||||
|
|
||||||
describe "#download_strategy" do
|
describe "#download_strategy" do
|
||||||
it "returns the download strategy" do
|
it "returns the download strategy" do
|
||||||
strategy = Object.new
|
strategy = Class.new(AbstractDownloadStrategy)
|
||||||
expect(DownloadStrategyDetector)
|
expect(DownloadStrategyDetector)
|
||||||
.to receive(:detect).with("foo", nil).and_return(strategy)
|
.to receive(:detect).with("foo", nil).and_return(strategy)
|
||||||
resource.url("foo")
|
resource.url("foo")
|
||||||
|
Binary file not shown.
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
class Failball < Formula
|
class Failball < Formula
|
||||||
def initialize(name = "failball", path = Pathname.new(__FILE__).expand_path, spec = :stable,
|
def initialize(name = "failball", path = Pathname.new(__FILE__).expand_path, spec = :stable,
|
||||||
alias_path: nil, force_bottle: false)
|
alias_path: nil, tap: nil, force_bottle: false)
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
class Testball < Formula
|
class Testball < Formula
|
||||||
def initialize(name = "testball", path = Pathname.new(__FILE__).expand_path, spec = :stable,
|
def initialize(name = "testball", path = Pathname.new(__FILE__).expand_path, spec = :stable,
|
||||||
alias_path: nil, force_bottle: false)
|
alias_path: nil, tap: nil, force_bottle: false)
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
class TestballBottle < Formula
|
class TestballBottle < Formula
|
||||||
def initialize(name = "testball_bottle", path = Pathname.new(__FILE__).expand_path, spec = :stable,
|
def initialize(name = "testball_bottle", path = Pathname.new(__FILE__).expand_path, spec = :stable,
|
||||||
alias_path: nil, force_bottle: false)
|
alias_path: nil, tap: nil, force_bottle: false)
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -13,7 +13,7 @@ class TestballBottle < Formula
|
|||||||
|
|
||||||
bottle do
|
bottle do
|
||||||
root_url "file://#{TEST_FIXTURE_DIR}/bottles"
|
root_url "file://#{TEST_FIXTURE_DIR}/bottles"
|
||||||
sha256 cellar: :any_skip_relocation, Utils::Bottles.tag.to_sym => "8f9aecd233463da6a4ea55f5f88fc5841718c013f3e2a7941350d6130f1dc149"
|
sha256 cellar: :any_skip_relocation, Utils::Bottles.tag.to_sym => "d7b9f4e8bf83608b71fe958a99f19f2e5e68bb2582965d32e41759c24f1aef97"
|
||||||
end
|
end
|
||||||
|
|
||||||
cxxstdlib_check :skip
|
cxxstdlib_check :skip
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
class TestballBottleCellar < Formula
|
class TestballBottleCellar < Formula
|
||||||
def initialize(name = "testball_bottle", path = Pathname.new(__FILE__).expand_path, spec = :stable,
|
def initialize(name = "testball_bottle", path = Pathname.new(__FILE__).expand_path, spec = :stable,
|
||||||
alias_path: nil, force_bottle: false)
|
alias_path: nil, tap: nil, force_bottle: false)
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -13,7 +13,7 @@ class TestballBottleCellar < Formula
|
|||||||
|
|
||||||
bottle do
|
bottle do
|
||||||
root_url "file://#{TEST_FIXTURE_DIR}/bottles"
|
root_url "file://#{TEST_FIXTURE_DIR}/bottles"
|
||||||
sha256 cellar: :any_skip_relocation, Utils::Bottles.tag.to_sym => "8f9aecd233463da6a4ea55f5f88fc5841718c013f3e2a7941350d6130f1dc149"
|
sha256 cellar: :any_skip_relocation, Utils::Bottles.tag.to_sym => "d7b9f4e8bf83608b71fe958a99f19f2e5e68bb2582965d32e41759c24f1aef97"
|
||||||
end
|
end
|
||||||
|
|
||||||
cxxstdlib_check :skip
|
cxxstdlib_check :skip
|
||||||
|
33
Library/Homebrew/url.rb
Normal file
33
Library/Homebrew/url.rb
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# typed: true
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "download_strategy"
|
||||||
|
require "version"
|
||||||
|
|
||||||
|
# @api private
|
||||||
|
class URL
|
||||||
|
attr_reader :specs, :using
|
||||||
|
|
||||||
|
sig { params(url: String, specs: T::Hash[Symbol, T.untyped]).void }
|
||||||
|
def initialize(url, specs = {})
|
||||||
|
@url = url.freeze
|
||||||
|
@specs = specs.dup
|
||||||
|
@using = @specs.delete(:using)
|
||||||
|
@specs.freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { returns(String) }
|
||||||
|
def to_s
|
||||||
|
@url
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { returns(T.class_of(AbstractDownloadStrategy)) }
|
||||||
|
def download_strategy
|
||||||
|
@download_strategy ||= DownloadStrategyDetector.detect(@url, @using)
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { returns(Version) }
|
||||||
|
def version
|
||||||
|
@version ||= Version.detect(@url, **@specs)
|
||||||
|
end
|
||||||
|
end
|
Loading…
x
Reference in New Issue
Block a user