mirror of
https://github.com/Homebrew/brew.git
synced 2025-07-14 16:09:03 +08:00
Split up SoftwareSpec
This came up in the AGM and has bothered me for years: let's actually split out `software_spec.rb` into one file per class, as is more typical in Ruby. This will make these classes easier to find.
This commit is contained in:
parent
153c6dd300
commit
2b737f0423
219
Library/Homebrew/bottle.rb
Normal file
219
Library/Homebrew/bottle.rb
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Bottle
|
||||||
|
include Downloadable
|
||||||
|
|
||||||
|
class Filename
|
||||||
|
attr_reader :name, :version, :tag, :rebuild
|
||||||
|
|
||||||
|
sig { params(formula: Formula, tag: Utils::Bottles::Tag, rebuild: Integer).returns(T.attached_class) }
|
||||||
|
def self.create(formula, tag, rebuild)
|
||||||
|
new(formula.name, formula.pkg_version, tag, rebuild)
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { params(name: String, version: PkgVersion, tag: Utils::Bottles::Tag, rebuild: Integer).void }
|
||||||
|
def initialize(name, version, tag, rebuild)
|
||||||
|
@name = File.basename name
|
||||||
|
|
||||||
|
raise ArgumentError, "Invalid bottle name" unless Utils.safe_filename?(@name)
|
||||||
|
raise ArgumentError, "Invalid bottle version" unless Utils.safe_filename?(version.to_s)
|
||||||
|
|
||||||
|
@version = version
|
||||||
|
@tag = tag.to_unstandardized_sym.to_s
|
||||||
|
@rebuild = rebuild
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { returns(String) }
|
||||||
|
def to_str
|
||||||
|
"#{name}--#{version}#{extname}"
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { returns(String) }
|
||||||
|
def to_s = to_str
|
||||||
|
|
||||||
|
sig { returns(String) }
|
||||||
|
def json
|
||||||
|
"#{name}--#{version}.#{tag}.bottle.json"
|
||||||
|
end
|
||||||
|
|
||||||
|
def url_encode
|
||||||
|
ERB::Util.url_encode("#{name}-#{version}#{extname}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def github_packages
|
||||||
|
"#{name}--#{version}#{extname}"
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { returns(String) }
|
||||||
|
def extname
|
||||||
|
s = rebuild.positive? ? ".#{rebuild}" : ""
|
||||||
|
".#{tag}.bottle#{s}.tar.gz"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
extend Forwardable
|
||||||
|
|
||||||
|
attr_reader :name, :resource, :tag, :cellar, :rebuild
|
||||||
|
|
||||||
|
def_delegators :resource, :url, :verify_download_integrity
|
||||||
|
def_delegators :resource, :cached_download, :downloader
|
||||||
|
|
||||||
|
def initialize(formula, spec, tag = nil)
|
||||||
|
super()
|
||||||
|
|
||||||
|
@name = formula.name
|
||||||
|
@resource = Resource.new
|
||||||
|
@resource.owner = formula
|
||||||
|
@spec = spec
|
||||||
|
|
||||||
|
tag_spec = spec.tag_specification_for(Utils::Bottles.tag(tag))
|
||||||
|
|
||||||
|
@tag = tag_spec.tag
|
||||||
|
@cellar = tag_spec.cellar
|
||||||
|
@rebuild = spec.rebuild
|
||||||
|
|
||||||
|
@resource.version(formula.pkg_version.to_s)
|
||||||
|
@resource.checksum = tag_spec.checksum
|
||||||
|
|
||||||
|
@fetch_tab_retried = false
|
||||||
|
|
||||||
|
root_url(spec.root_url, spec.root_url_specs)
|
||||||
|
end
|
||||||
|
|
||||||
|
sig {
|
||||||
|
override.params(
|
||||||
|
verify_download_integrity: T::Boolean,
|
||||||
|
timeout: T.nilable(T.any(Integer, Float)),
|
||||||
|
quiet: T.nilable(T::Boolean),
|
||||||
|
).returns(Pathname)
|
||||||
|
}
|
||||||
|
def fetch(verify_download_integrity: true, timeout: nil, quiet: false)
|
||||||
|
resource.fetch(verify_download_integrity:, timeout:, quiet:)
|
||||||
|
rescue DownloadError
|
||||||
|
raise unless fallback_on_error
|
||||||
|
|
||||||
|
fetch_tab
|
||||||
|
retry
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { override.void }
|
||||||
|
def clear_cache
|
||||||
|
@resource.clear_cache
|
||||||
|
github_packages_manifest_resource&.clear_cache
|
||||||
|
@fetch_tab_retried = false
|
||||||
|
end
|
||||||
|
|
||||||
|
def compatible_locations?
|
||||||
|
@spec.compatible_locations?(tag: @tag)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Does the bottle need to be relocated?
|
||||||
|
def skip_relocation?
|
||||||
|
@spec.skip_relocation?(tag: @tag)
|
||||||
|
end
|
||||||
|
|
||||||
|
def stage = downloader.stage
|
||||||
|
|
||||||
|
def fetch_tab(timeout: nil, quiet: false)
|
||||||
|
return unless (resource = github_packages_manifest_resource)
|
||||||
|
|
||||||
|
begin
|
||||||
|
resource.fetch(timeout:, quiet:)
|
||||||
|
rescue DownloadError
|
||||||
|
raise unless fallback_on_error
|
||||||
|
|
||||||
|
retry
|
||||||
|
rescue Resource::BottleManifest::Error
|
||||||
|
raise if @fetch_tab_retried
|
||||||
|
|
||||||
|
@fetch_tab_retried = true
|
||||||
|
resource.clear_cache
|
||||||
|
retry
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def tab_attributes
|
||||||
|
if (resource = github_packages_manifest_resource) && resource.downloaded?
|
||||||
|
return resource.tab
|
||||||
|
end
|
||||||
|
|
||||||
|
{}
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { returns(T.nilable(Integer)) }
|
||||||
|
def bottle_size
|
||||||
|
resource = github_packages_manifest_resource
|
||||||
|
return unless resource&.downloaded?
|
||||||
|
|
||||||
|
resource.bottle_size
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { returns(T.nilable(Integer)) }
|
||||||
|
def installed_size
|
||||||
|
resource = github_packages_manifest_resource
|
||||||
|
return unless resource&.downloaded?
|
||||||
|
|
||||||
|
resource.installed_size
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { returns(Filename) }
|
||||||
|
def filename
|
||||||
|
Filename.create(resource.owner, @tag, @spec.rebuild)
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { returns(T.nilable(Resource::BottleManifest)) }
|
||||||
|
def github_packages_manifest_resource
|
||||||
|
return if @resource.download_strategy != CurlGitHubPackagesDownloadStrategy
|
||||||
|
|
||||||
|
@github_packages_manifest_resource ||= begin
|
||||||
|
resource = Resource::BottleManifest.new(self)
|
||||||
|
|
||||||
|
version_rebuild = GitHubPackages.version_rebuild(@resource.version, rebuild)
|
||||||
|
resource.version(version_rebuild)
|
||||||
|
|
||||||
|
image_name = GitHubPackages.image_formula_name(@name)
|
||||||
|
image_tag = GitHubPackages.image_version_rebuild(version_rebuild)
|
||||||
|
resource.url(
|
||||||
|
"#{root_url}/#{image_name}/manifests/#{image_tag}",
|
||||||
|
using: CurlGitHubPackagesDownloadStrategy,
|
||||||
|
headers: ["Accept: application/vnd.oci.image.index.v1+json"],
|
||||||
|
)
|
||||||
|
T.cast(resource.downloader, CurlGitHubPackagesDownloadStrategy).resolved_basename =
|
||||||
|
"#{name}-#{version_rebuild}.bottle_manifest.json"
|
||||||
|
resource
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def select_download_strategy(specs)
|
||||||
|
specs[:using] ||= DownloadStrategyDetector.detect(@root_url)
|
||||||
|
specs[:bottle] = true
|
||||||
|
specs
|
||||||
|
end
|
||||||
|
|
||||||
|
def fallback_on_error
|
||||||
|
# Use the default bottle domain as a fallback mirror
|
||||||
|
if @resource.url.start_with?(Homebrew::EnvConfig.bottle_domain) &&
|
||||||
|
Homebrew::EnvConfig.bottle_domain != HOMEBREW_BOTTLE_DEFAULT_DOMAIN
|
||||||
|
opoo "Bottle missing, falling back to the default domain..."
|
||||||
|
root_url(HOMEBREW_BOTTLE_DEFAULT_DOMAIN)
|
||||||
|
@github_packages_manifest_resource = nil
|
||||||
|
true
|
||||||
|
else
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def root_url(val = nil, specs = {})
|
||||||
|
return @root_url if val.nil?
|
||||||
|
|
||||||
|
@root_url = val
|
||||||
|
|
||||||
|
filename = Filename.create(resource.owner, @tag, @spec.rebuild)
|
||||||
|
path, resolved_basename = Utils::Bottles.path_resolved_basename(val, name, resource.checksum, filename)
|
||||||
|
@resource.url("#{val}/#{path}", **select_download_strategy(specs))
|
||||||
|
@resource.downloader.resolved_basename = resolved_basename if resolved_basename.present?
|
||||||
|
end
|
||||||
|
end
|
135
Library/Homebrew/bottle_specification.rb
Normal file
135
Library/Homebrew/bottle_specification.rb
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class BottleSpecification
|
||||||
|
extend Attrable
|
||||||
|
RELOCATABLE_CELLARS = [:any, :any_skip_relocation].freeze
|
||||||
|
|
||||||
|
attr_rw :rebuild
|
||||||
|
attr_accessor :tap
|
||||||
|
attr_reader :collector, :root_url_specs, :repository
|
||||||
|
|
||||||
|
sig { void }
|
||||||
|
def initialize
|
||||||
|
@rebuild = 0
|
||||||
|
@repository = Homebrew::DEFAULT_REPOSITORY
|
||||||
|
@collector = Utils::Bottles::Collector.new
|
||||||
|
@root_url_specs = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def root_url(var = nil, specs = {})
|
||||||
|
if var.nil?
|
||||||
|
@root_url ||= if (github_packages_url = GitHubPackages.root_url_if_match(Homebrew::EnvConfig.bottle_domain))
|
||||||
|
github_packages_url
|
||||||
|
else
|
||||||
|
Homebrew::EnvConfig.bottle_domain
|
||||||
|
end
|
||||||
|
else
|
||||||
|
@root_url = if (github_packages_url = GitHubPackages.root_url_if_match(var))
|
||||||
|
github_packages_url
|
||||||
|
else
|
||||||
|
var
|
||||||
|
end
|
||||||
|
@root_url_specs.merge!(specs)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def ==(other)
|
||||||
|
self.class == other.class && rebuild == other.rebuild && collector == other.collector &&
|
||||||
|
root_url == other.root_url && root_url_specs == other.root_url_specs && tap == other.tap
|
||||||
|
end
|
||||||
|
alias eql? ==
|
||||||
|
|
||||||
|
sig { params(tag: Utils::Bottles::Tag).returns(T.any(Symbol, String)) }
|
||||||
|
def tag_to_cellar(tag = Utils::Bottles.tag)
|
||||||
|
spec = collector.specification_for(tag)
|
||||||
|
if spec.present?
|
||||||
|
spec.cellar
|
||||||
|
else
|
||||||
|
tag.default_cellar
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { params(tag: Utils::Bottles::Tag).returns(T::Boolean) }
|
||||||
|
def compatible_locations?(tag: Utils::Bottles.tag)
|
||||||
|
cellar = tag_to_cellar(tag)
|
||||||
|
|
||||||
|
return true if RELOCATABLE_CELLARS.include?(cellar)
|
||||||
|
|
||||||
|
prefix = Pathname(cellar.to_s).parent.to_s
|
||||||
|
|
||||||
|
cellar_relocatable = cellar.size >= HOMEBREW_CELLAR.to_s.size && ENV["HOMEBREW_RELOCATE_BUILD_PREFIX"].present?
|
||||||
|
prefix_relocatable = prefix.size >= HOMEBREW_PREFIX.to_s.size && ENV["HOMEBREW_RELOCATE_BUILD_PREFIX"].present?
|
||||||
|
|
||||||
|
compatible_cellar = cellar == HOMEBREW_CELLAR.to_s || cellar_relocatable
|
||||||
|
compatible_prefix = prefix == HOMEBREW_PREFIX.to_s || prefix_relocatable
|
||||||
|
|
||||||
|
compatible_cellar && compatible_prefix
|
||||||
|
end
|
||||||
|
|
||||||
|
# Does the {Bottle} this {BottleSpecification} belongs to need to be relocated?
|
||||||
|
sig { params(tag: Utils::Bottles::Tag).returns(T::Boolean) }
|
||||||
|
def skip_relocation?(tag: Utils::Bottles.tag)
|
||||||
|
spec = collector.specification_for(tag)
|
||||||
|
spec&.cellar == :any_skip_relocation
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { params(tag: T.any(Symbol, Utils::Bottles::Tag), no_older_versions: T::Boolean).returns(T::Boolean) }
|
||||||
|
def tag?(tag, no_older_versions: false)
|
||||||
|
collector.tag?(tag, no_older_versions:)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Checksum methods in the DSL's bottle block take
|
||||||
|
# a Hash, which indicates the platform the checksum applies on.
|
||||||
|
# Example bottle block syntax:
|
||||||
|
# bottle do
|
||||||
|
# sha256 cellar: :any_skip_relocation, big_sur: "69489ae397e4645..."
|
||||||
|
# sha256 cellar: :any, catalina: "449de5ea35d0e94..."
|
||||||
|
# end
|
||||||
|
def sha256(hash)
|
||||||
|
sha256_regex = /^[a-f0-9]{64}$/i
|
||||||
|
|
||||||
|
# find new `sha256 big_sur: "69489ae397e4645..."` format
|
||||||
|
tag, digest = hash.find do |key, value|
|
||||||
|
key.is_a?(Symbol) && value.is_a?(String) && value.match?(sha256_regex)
|
||||||
|
end
|
||||||
|
|
||||||
|
cellar = hash[:cellar] if digest && tag
|
||||||
|
|
||||||
|
tag = Utils::Bottles::Tag.from_symbol(tag)
|
||||||
|
|
||||||
|
cellar ||= tag.default_cellar
|
||||||
|
|
||||||
|
collector.add(tag, checksum: Checksum.new(digest), cellar:)
|
||||||
|
end
|
||||||
|
|
||||||
|
sig {
|
||||||
|
params(tag: Utils::Bottles::Tag, no_older_versions: T::Boolean)
|
||||||
|
.returns(T.nilable(Utils::Bottles::TagSpecification))
|
||||||
|
}
|
||||||
|
def tag_specification_for(tag, no_older_versions: false)
|
||||||
|
collector.specification_for(tag, no_older_versions:)
|
||||||
|
end
|
||||||
|
|
||||||
|
def checksums
|
||||||
|
tags = collector.tags.sort_by do |tag|
|
||||||
|
version = tag.to_macos_version
|
||||||
|
# Give `arm64` bottles a higher priority so they are first.
|
||||||
|
priority = (tag.arch == :arm64) ? 2 : 1
|
||||||
|
"#{priority}.#{version}_#{tag}"
|
||||||
|
rescue MacOSVersion::Error
|
||||||
|
# Sort non-macOS tags below macOS tags.
|
||||||
|
"0.#{tag}"
|
||||||
|
end
|
||||||
|
tags.reverse.map do |tag|
|
||||||
|
spec = collector.specification_for(tag)
|
||||||
|
{
|
||||||
|
"tag" => spec.tag.to_sym,
|
||||||
|
"digest" => spec.checksum,
|
||||||
|
"cellar" => spec.cellar,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
require "extend/os/bottle_specification"
|
4
Library/Homebrew/extend/os/bottle_specification.rb
Normal file
4
Library/Homebrew/extend/os/bottle_specification.rb
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# typed: strict
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "extend/os/linux/bottle_specification" if OS.linux?
|
@ -1,4 +0,0 @@
|
|||||||
# typed: strict
|
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "extend/os/linux/software_spec" if OS.linux?
|
|
@ -19,6 +19,10 @@ require "build_environment"
|
|||||||
require "build_options"
|
require "build_options"
|
||||||
require "formulary"
|
require "formulary"
|
||||||
require "software_spec"
|
require "software_spec"
|
||||||
|
require "bottle"
|
||||||
|
require "pour_bottle_check"
|
||||||
|
require "head_software_spec"
|
||||||
|
require "bottle_specification"
|
||||||
require "livecheck"
|
require "livecheck"
|
||||||
require "service"
|
require "service"
|
||||||
require "install_renamed"
|
require "install_renamed"
|
||||||
|
15
Library/Homebrew/head_software_spec.rb
Normal file
15
Library/Homebrew/head_software_spec.rb
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "software_spec"
|
||||||
|
|
||||||
|
class HeadSoftwareSpec < SoftwareSpec
|
||||||
|
def initialize(flags: [])
|
||||||
|
super
|
||||||
|
@resource.version(Version.new("HEAD"))
|
||||||
|
end
|
||||||
|
|
||||||
|
def verify_download_integrity(_filename)
|
||||||
|
# no-op
|
||||||
|
end
|
||||||
|
end
|
18
Library/Homebrew/pour_bottle_check.rb
Normal file
18
Library/Homebrew/pour_bottle_check.rb
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class PourBottleCheck
|
||||||
|
include OnSystem::MacOSAndLinux
|
||||||
|
|
||||||
|
def initialize(formula)
|
||||||
|
@formula = formula
|
||||||
|
end
|
||||||
|
|
||||||
|
def reason(reason)
|
||||||
|
@formula.pour_bottle_check_unsatisfied_reason = reason
|
||||||
|
end
|
||||||
|
|
||||||
|
def satisfy(&block)
|
||||||
|
@formula.send(:define_method, :pour_bottle?, &block)
|
||||||
|
end
|
||||||
|
end
|
@ -284,380 +284,3 @@ class SoftwareSpec
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class HeadSoftwareSpec < SoftwareSpec
|
|
||||||
def initialize(flags: [])
|
|
||||||
super
|
|
||||||
@resource.version(Version.new("HEAD"))
|
|
||||||
end
|
|
||||||
|
|
||||||
def verify_download_integrity(_filename)
|
|
||||||
# no-op
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Bottle
|
|
||||||
include Downloadable
|
|
||||||
|
|
||||||
class Filename
|
|
||||||
attr_reader :name, :version, :tag, :rebuild
|
|
||||||
|
|
||||||
sig { params(formula: Formula, tag: Utils::Bottles::Tag, rebuild: Integer).returns(T.attached_class) }
|
|
||||||
def self.create(formula, tag, rebuild)
|
|
||||||
new(formula.name, formula.pkg_version, tag, rebuild)
|
|
||||||
end
|
|
||||||
|
|
||||||
sig { params(name: String, version: PkgVersion, tag: Utils::Bottles::Tag, rebuild: Integer).void }
|
|
||||||
def initialize(name, version, tag, rebuild)
|
|
||||||
@name = File.basename name
|
|
||||||
|
|
||||||
raise ArgumentError, "Invalid bottle name" unless Utils.safe_filename?(@name)
|
|
||||||
raise ArgumentError, "Invalid bottle version" unless Utils.safe_filename?(version.to_s)
|
|
||||||
|
|
||||||
@version = version
|
|
||||||
@tag = tag.to_unstandardized_sym.to_s
|
|
||||||
@rebuild = rebuild
|
|
||||||
end
|
|
||||||
|
|
||||||
sig { returns(String) }
|
|
||||||
def to_str
|
|
||||||
"#{name}--#{version}#{extname}"
|
|
||||||
end
|
|
||||||
|
|
||||||
sig { returns(String) }
|
|
||||||
def to_s = to_str
|
|
||||||
|
|
||||||
sig { returns(String) }
|
|
||||||
def json
|
|
||||||
"#{name}--#{version}.#{tag}.bottle.json"
|
|
||||||
end
|
|
||||||
|
|
||||||
def url_encode
|
|
||||||
ERB::Util.url_encode("#{name}-#{version}#{extname}")
|
|
||||||
end
|
|
||||||
|
|
||||||
def github_packages
|
|
||||||
"#{name}--#{version}#{extname}"
|
|
||||||
end
|
|
||||||
|
|
||||||
sig { returns(String) }
|
|
||||||
def extname
|
|
||||||
s = rebuild.positive? ? ".#{rebuild}" : ""
|
|
||||||
".#{tag}.bottle#{s}.tar.gz"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
extend Forwardable
|
|
||||||
|
|
||||||
attr_reader :name, :resource, :tag, :cellar, :rebuild
|
|
||||||
|
|
||||||
def_delegators :resource, :url, :verify_download_integrity
|
|
||||||
def_delegators :resource, :cached_download, :downloader
|
|
||||||
|
|
||||||
def initialize(formula, spec, tag = nil)
|
|
||||||
super()
|
|
||||||
|
|
||||||
@name = formula.name
|
|
||||||
@resource = Resource.new
|
|
||||||
@resource.owner = formula
|
|
||||||
@spec = spec
|
|
||||||
|
|
||||||
tag_spec = spec.tag_specification_for(Utils::Bottles.tag(tag))
|
|
||||||
|
|
||||||
@tag = tag_spec.tag
|
|
||||||
@cellar = tag_spec.cellar
|
|
||||||
@rebuild = spec.rebuild
|
|
||||||
|
|
||||||
@resource.version(formula.pkg_version.to_s)
|
|
||||||
@resource.checksum = tag_spec.checksum
|
|
||||||
|
|
||||||
@fetch_tab_retried = false
|
|
||||||
|
|
||||||
root_url(spec.root_url, spec.root_url_specs)
|
|
||||||
end
|
|
||||||
|
|
||||||
sig {
|
|
||||||
override.params(
|
|
||||||
verify_download_integrity: T::Boolean,
|
|
||||||
timeout: T.nilable(T.any(Integer, Float)),
|
|
||||||
quiet: T.nilable(T::Boolean),
|
|
||||||
).returns(Pathname)
|
|
||||||
}
|
|
||||||
def fetch(verify_download_integrity: true, timeout: nil, quiet: false)
|
|
||||||
resource.fetch(verify_download_integrity:, timeout:, quiet:)
|
|
||||||
rescue DownloadError
|
|
||||||
raise unless fallback_on_error
|
|
||||||
|
|
||||||
fetch_tab
|
|
||||||
retry
|
|
||||||
end
|
|
||||||
|
|
||||||
sig { override.void }
|
|
||||||
def clear_cache
|
|
||||||
@resource.clear_cache
|
|
||||||
github_packages_manifest_resource&.clear_cache
|
|
||||||
@fetch_tab_retried = false
|
|
||||||
end
|
|
||||||
|
|
||||||
def compatible_locations?
|
|
||||||
@spec.compatible_locations?(tag: @tag)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Does the bottle need to be relocated?
|
|
||||||
def skip_relocation?
|
|
||||||
@spec.skip_relocation?(tag: @tag)
|
|
||||||
end
|
|
||||||
|
|
||||||
def stage = downloader.stage
|
|
||||||
|
|
||||||
def fetch_tab(timeout: nil, quiet: false)
|
|
||||||
return unless (resource = github_packages_manifest_resource)
|
|
||||||
|
|
||||||
begin
|
|
||||||
resource.fetch(timeout:, quiet:)
|
|
||||||
rescue DownloadError
|
|
||||||
raise unless fallback_on_error
|
|
||||||
|
|
||||||
retry
|
|
||||||
rescue Resource::BottleManifest::Error
|
|
||||||
raise if @fetch_tab_retried
|
|
||||||
|
|
||||||
@fetch_tab_retried = true
|
|
||||||
resource.clear_cache
|
|
||||||
retry
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def tab_attributes
|
|
||||||
if (resource = github_packages_manifest_resource) && resource.downloaded?
|
|
||||||
return resource.tab
|
|
||||||
end
|
|
||||||
|
|
||||||
{}
|
|
||||||
end
|
|
||||||
|
|
||||||
sig { returns(T.nilable(Integer)) }
|
|
||||||
def bottle_size
|
|
||||||
resource = github_packages_manifest_resource
|
|
||||||
return unless resource&.downloaded?
|
|
||||||
|
|
||||||
resource.bottle_size
|
|
||||||
end
|
|
||||||
|
|
||||||
sig { returns(T.nilable(Integer)) }
|
|
||||||
def installed_size
|
|
||||||
resource = github_packages_manifest_resource
|
|
||||||
return unless resource&.downloaded?
|
|
||||||
|
|
||||||
resource.installed_size
|
|
||||||
end
|
|
||||||
|
|
||||||
sig { returns(Filename) }
|
|
||||||
def filename
|
|
||||||
Filename.create(resource.owner, @tag, @spec.rebuild)
|
|
||||||
end
|
|
||||||
|
|
||||||
sig { returns(T.nilable(Resource::BottleManifest)) }
|
|
||||||
def github_packages_manifest_resource
|
|
||||||
return if @resource.download_strategy != CurlGitHubPackagesDownloadStrategy
|
|
||||||
|
|
||||||
@github_packages_manifest_resource ||= begin
|
|
||||||
resource = Resource::BottleManifest.new(self)
|
|
||||||
|
|
||||||
version_rebuild = GitHubPackages.version_rebuild(@resource.version, rebuild)
|
|
||||||
resource.version(version_rebuild)
|
|
||||||
|
|
||||||
image_name = GitHubPackages.image_formula_name(@name)
|
|
||||||
image_tag = GitHubPackages.image_version_rebuild(version_rebuild)
|
|
||||||
resource.url(
|
|
||||||
"#{root_url}/#{image_name}/manifests/#{image_tag}",
|
|
||||||
using: CurlGitHubPackagesDownloadStrategy,
|
|
||||||
headers: ["Accept: application/vnd.oci.image.index.v1+json"],
|
|
||||||
)
|
|
||||||
T.cast(resource.downloader, CurlGitHubPackagesDownloadStrategy).resolved_basename =
|
|
||||||
"#{name}-#{version_rebuild}.bottle_manifest.json"
|
|
||||||
resource
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def select_download_strategy(specs)
|
|
||||||
specs[:using] ||= DownloadStrategyDetector.detect(@root_url)
|
|
||||||
specs[:bottle] = true
|
|
||||||
specs
|
|
||||||
end
|
|
||||||
|
|
||||||
def fallback_on_error
|
|
||||||
# Use the default bottle domain as a fallback mirror
|
|
||||||
if @resource.url.start_with?(Homebrew::EnvConfig.bottle_domain) &&
|
|
||||||
Homebrew::EnvConfig.bottle_domain != HOMEBREW_BOTTLE_DEFAULT_DOMAIN
|
|
||||||
opoo "Bottle missing, falling back to the default domain..."
|
|
||||||
root_url(HOMEBREW_BOTTLE_DEFAULT_DOMAIN)
|
|
||||||
@github_packages_manifest_resource = nil
|
|
||||||
true
|
|
||||||
else
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def root_url(val = nil, specs = {})
|
|
||||||
return @root_url if val.nil?
|
|
||||||
|
|
||||||
@root_url = val
|
|
||||||
|
|
||||||
filename = Filename.create(resource.owner, @tag, @spec.rebuild)
|
|
||||||
path, resolved_basename = Utils::Bottles.path_resolved_basename(val, name, resource.checksum, filename)
|
|
||||||
@resource.url("#{val}/#{path}", **select_download_strategy(specs))
|
|
||||||
@resource.downloader.resolved_basename = resolved_basename if resolved_basename.present?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class BottleSpecification
|
|
||||||
extend Attrable
|
|
||||||
RELOCATABLE_CELLARS = [:any, :any_skip_relocation].freeze
|
|
||||||
|
|
||||||
attr_rw :rebuild
|
|
||||||
attr_accessor :tap
|
|
||||||
attr_reader :collector, :root_url_specs, :repository
|
|
||||||
|
|
||||||
sig { void }
|
|
||||||
def initialize
|
|
||||||
@rebuild = 0
|
|
||||||
@repository = Homebrew::DEFAULT_REPOSITORY
|
|
||||||
@collector = Utils::Bottles::Collector.new
|
|
||||||
@root_url_specs = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
def root_url(var = nil, specs = {})
|
|
||||||
if var.nil?
|
|
||||||
@root_url ||= if (github_packages_url = GitHubPackages.root_url_if_match(Homebrew::EnvConfig.bottle_domain))
|
|
||||||
github_packages_url
|
|
||||||
else
|
|
||||||
Homebrew::EnvConfig.bottle_domain
|
|
||||||
end
|
|
||||||
else
|
|
||||||
@root_url = if (github_packages_url = GitHubPackages.root_url_if_match(var))
|
|
||||||
github_packages_url
|
|
||||||
else
|
|
||||||
var
|
|
||||||
end
|
|
||||||
@root_url_specs.merge!(specs)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def ==(other)
|
|
||||||
self.class == other.class && rebuild == other.rebuild && collector == other.collector &&
|
|
||||||
root_url == other.root_url && root_url_specs == other.root_url_specs && tap == other.tap
|
|
||||||
end
|
|
||||||
alias eql? ==
|
|
||||||
|
|
||||||
sig { params(tag: Utils::Bottles::Tag).returns(T.any(Symbol, String)) }
|
|
||||||
def tag_to_cellar(tag = Utils::Bottles.tag)
|
|
||||||
spec = collector.specification_for(tag)
|
|
||||||
if spec.present?
|
|
||||||
spec.cellar
|
|
||||||
else
|
|
||||||
tag.default_cellar
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
sig { params(tag: Utils::Bottles::Tag).returns(T::Boolean) }
|
|
||||||
def compatible_locations?(tag: Utils::Bottles.tag)
|
|
||||||
cellar = tag_to_cellar(tag)
|
|
||||||
|
|
||||||
return true if RELOCATABLE_CELLARS.include?(cellar)
|
|
||||||
|
|
||||||
prefix = Pathname(cellar.to_s).parent.to_s
|
|
||||||
|
|
||||||
cellar_relocatable = cellar.size >= HOMEBREW_CELLAR.to_s.size && ENV["HOMEBREW_RELOCATE_BUILD_PREFIX"].present?
|
|
||||||
prefix_relocatable = prefix.size >= HOMEBREW_PREFIX.to_s.size && ENV["HOMEBREW_RELOCATE_BUILD_PREFIX"].present?
|
|
||||||
|
|
||||||
compatible_cellar = cellar == HOMEBREW_CELLAR.to_s || cellar_relocatable
|
|
||||||
compatible_prefix = prefix == HOMEBREW_PREFIX.to_s || prefix_relocatable
|
|
||||||
|
|
||||||
compatible_cellar && compatible_prefix
|
|
||||||
end
|
|
||||||
|
|
||||||
# Does the {Bottle} this {BottleSpecification} belongs to need to be relocated?
|
|
||||||
sig { params(tag: Utils::Bottles::Tag).returns(T::Boolean) }
|
|
||||||
def skip_relocation?(tag: Utils::Bottles.tag)
|
|
||||||
spec = collector.specification_for(tag)
|
|
||||||
spec&.cellar == :any_skip_relocation
|
|
||||||
end
|
|
||||||
|
|
||||||
sig { params(tag: T.any(Symbol, Utils::Bottles::Tag), no_older_versions: T::Boolean).returns(T::Boolean) }
|
|
||||||
def tag?(tag, no_older_versions: false)
|
|
||||||
collector.tag?(tag, no_older_versions:)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Checksum methods in the DSL's bottle block take
|
|
||||||
# a Hash, which indicates the platform the checksum applies on.
|
|
||||||
# Example bottle block syntax:
|
|
||||||
# bottle do
|
|
||||||
# sha256 cellar: :any_skip_relocation, big_sur: "69489ae397e4645..."
|
|
||||||
# sha256 cellar: :any, catalina: "449de5ea35d0e94..."
|
|
||||||
# end
|
|
||||||
def sha256(hash)
|
|
||||||
sha256_regex = /^[a-f0-9]{64}$/i
|
|
||||||
|
|
||||||
# find new `sha256 big_sur: "69489ae397e4645..."` format
|
|
||||||
tag, digest = hash.find do |key, value|
|
|
||||||
key.is_a?(Symbol) && value.is_a?(String) && value.match?(sha256_regex)
|
|
||||||
end
|
|
||||||
|
|
||||||
cellar = hash[:cellar] if digest && tag
|
|
||||||
|
|
||||||
tag = Utils::Bottles::Tag.from_symbol(tag)
|
|
||||||
|
|
||||||
cellar ||= tag.default_cellar
|
|
||||||
|
|
||||||
collector.add(tag, checksum: Checksum.new(digest), cellar:)
|
|
||||||
end
|
|
||||||
|
|
||||||
sig {
|
|
||||||
params(tag: Utils::Bottles::Tag, no_older_versions: T::Boolean)
|
|
||||||
.returns(T.nilable(Utils::Bottles::TagSpecification))
|
|
||||||
}
|
|
||||||
def tag_specification_for(tag, no_older_versions: false)
|
|
||||||
collector.specification_for(tag, no_older_versions:)
|
|
||||||
end
|
|
||||||
|
|
||||||
def checksums
|
|
||||||
tags = collector.tags.sort_by do |tag|
|
|
||||||
version = tag.to_macos_version
|
|
||||||
# Give `arm64` bottles a higher priority so they are first.
|
|
||||||
priority = (tag.arch == :arm64) ? 2 : 1
|
|
||||||
"#{priority}.#{version}_#{tag}"
|
|
||||||
rescue MacOSVersion::Error
|
|
||||||
# Sort non-macOS tags below macOS tags.
|
|
||||||
"0.#{tag}"
|
|
||||||
end
|
|
||||||
tags.reverse.map do |tag|
|
|
||||||
spec = collector.specification_for(tag)
|
|
||||||
{
|
|
||||||
"tag" => spec.tag.to_sym,
|
|
||||||
"digest" => spec.checksum,
|
|
||||||
"cellar" => spec.cellar,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class PourBottleCheck
|
|
||||||
include OnSystem::MacOSAndLinux
|
|
||||||
|
|
||||||
def initialize(formula)
|
|
||||||
@formula = formula
|
|
||||||
end
|
|
||||||
|
|
||||||
def reason(reason)
|
|
||||||
@formula.pour_bottle_check_unsatisfied_reason = reason
|
|
||||||
end
|
|
||||||
|
|
||||||
def satisfy(&block)
|
|
||||||
@formula.send(:define_method, :pour_bottle?, &block)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
require "extend/os/software_spec"
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "software_spec"
|
require "bottle_specification"
|
||||||
require "test/support/fixtures/testball_bottle"
|
require "test/support/fixtures/testball_bottle"
|
||||||
|
|
||||||
RSpec.describe Bottle do
|
RSpec.describe Bottle do
|
@ -1,6 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "software_spec"
|
require "bottle_specification"
|
||||||
|
|
||||||
RSpec.describe BottleSpecification do
|
RSpec.describe BottleSpecification do
|
||||||
subject(:bottle_spec) { described_class.new }
|
subject(:bottle_spec) { described_class.new }
|
@ -1,6 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "software_spec"
|
require "head_software_spec"
|
||||||
|
|
||||||
RSpec.describe HeadSoftwareSpec do
|
RSpec.describe HeadSoftwareSpec do
|
||||||
subject(:head_spec) { described_class.new }
|
subject(:head_spec) { described_class.new }
|
Loading…
x
Reference in New Issue
Block a user