# 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