
Originally we were going to try and load the *flight blocks from the API but we ended up going with downloading the caskfile for the subset of casks that need this functionality for consisty's sake. This reverts the following commits from most recent to oldest: - ffc74a51fb32b66a4cd8bc41dbd076dd23d9100e - e5616e94fe42505434c330be35eeafef2739944f - d1490c3d5c087d00f2bca1787cce331202b195c5 - 7ca5a5d9a71a73f21bbb8555a38048f027bee89b - 2d5d132713d0701d02d5ff33e9918812d13d2a83 It also changes how *flight blocks are handled in `.to_h`. Essentially, when *flight blocks exist they are just included as a hash of the artifact to nil to indicate that they exist. More information isn't necessary since we don't evaluate the current source code in the *flight artifacts that we get from the API.
367 lines
10 KiB
Ruby
367 lines
10 KiB
Ruby
# typed: false
|
|
# frozen_string_literal: true
|
|
|
|
require "cask/cask_loader"
|
|
require "cask/config"
|
|
require "cask/dsl"
|
|
require "cask/metadata"
|
|
require "searchable"
|
|
require "utils/bottles"
|
|
|
|
module Cask
|
|
# An instance of a cask.
|
|
#
|
|
# @api private
|
|
class Cask
|
|
extend T::Sig
|
|
|
|
extend Forwardable
|
|
extend Searchable
|
|
include Metadata
|
|
|
|
attr_reader :token, :sourcefile_path, :source, :config, :default_config, :loaded_from_api
|
|
|
|
attr_accessor :download, :allow_reassignment
|
|
|
|
def self.all
|
|
# TODO: ideally avoid using ARGV by moving to e.g. CLI::Parser
|
|
if ARGV.exclude?("--eval-all") && !Homebrew::EnvConfig.eval_all?
|
|
odeprecated "Cask::Cask#all without --all or HOMEBREW_EVAL_ALL"
|
|
end
|
|
|
|
Tap.flat_map(&:cask_files).map do |f|
|
|
CaskLoader::FromTapPathLoader.new(f).load(config: nil)
|
|
rescue CaskUnreadableError => e
|
|
opoo e.message
|
|
|
|
nil
|
|
end.compact
|
|
end
|
|
|
|
def tap
|
|
return super if block_given? # Object#tap
|
|
|
|
@tap
|
|
end
|
|
|
|
def initialize(token, sourcefile_path: nil, source: nil, tap: nil, config: nil,
|
|
allow_reassignment: false, loaded_from_api: false, &block)
|
|
@token = token
|
|
@sourcefile_path = sourcefile_path
|
|
@source = source
|
|
@tap = tap
|
|
@allow_reassignment = allow_reassignment
|
|
@loaded_from_api = loaded_from_api
|
|
@block = block
|
|
|
|
@default_config = config || Config.new
|
|
|
|
self.config = if config_path.exist?
|
|
Config.from_json(File.read(config_path), ignore_invalid_keys: true)
|
|
else
|
|
@default_config
|
|
end
|
|
end
|
|
|
|
def config=(config)
|
|
@config = config
|
|
|
|
refresh
|
|
end
|
|
|
|
def refresh
|
|
@dsl = DSL.new(self)
|
|
return unless @block
|
|
|
|
@dsl.instance_eval(&@block)
|
|
@dsl.language_eval
|
|
end
|
|
|
|
DSL::DSL_METHODS.each do |method_name|
|
|
define_method(method_name) { |&block| @dsl.send(method_name, &block) }
|
|
end
|
|
|
|
sig { returns(T::Array[[String, String]]) }
|
|
def timestamped_versions
|
|
Pathname.glob(metadata_timestamped_path(version: "*", timestamp: "*"))
|
|
.map { |p| p.relative_path_from(p.parent.parent) }
|
|
.sort_by(&:basename) # sort by timestamp
|
|
.map { |p| p.split.map(&:to_s) }
|
|
end
|
|
|
|
def versions
|
|
timestamped_versions.map(&:first)
|
|
.reverse
|
|
.uniq
|
|
.reverse
|
|
end
|
|
|
|
def os_versions
|
|
# TODO: use #to_hash_with_variations instead once all casks use on_system blocks
|
|
@os_versions ||= begin
|
|
version_os_hash = {}
|
|
actual_version = MacOS.full_version.to_s
|
|
|
|
MacOSVersions::SYMBOLS.each do |os_name, os_version|
|
|
MacOS.full_version = os_version
|
|
cask = CaskLoader.load(full_name)
|
|
version_os_hash[os_name] = cask.version if cask.version != version
|
|
end
|
|
|
|
version_os_hash
|
|
ensure
|
|
MacOS.full_version = actual_version
|
|
end
|
|
end
|
|
|
|
def full_name
|
|
return token if tap.nil?
|
|
return token if tap.user == "Homebrew"
|
|
|
|
"#{tap.name}/#{token}"
|
|
end
|
|
|
|
def installed?
|
|
!versions.empty?
|
|
end
|
|
|
|
sig { returns(T.nilable(Time)) }
|
|
def install_time
|
|
_, time = timestamped_versions.last
|
|
return unless time
|
|
|
|
Time.strptime(time, Metadata::TIMESTAMP_FORMAT)
|
|
end
|
|
|
|
def installed_caskfile
|
|
installed_version = timestamped_versions.last
|
|
caskfile_dir = metadata_main_container_path.join(*installed_version, "Casks")
|
|
return caskfile_dir.join("#{token}.json") if caskfile_dir.join("#{token}.json").exist?
|
|
|
|
caskfile_dir.join("#{token}.rb")
|
|
end
|
|
|
|
def config_path
|
|
metadata_main_container_path/"config.json"
|
|
end
|
|
|
|
def checksumable?
|
|
DownloadStrategyDetector.detect(url.to_s, url.using) <= AbstractFileDownloadStrategy
|
|
end
|
|
|
|
def download_sha_path
|
|
metadata_main_container_path/"LATEST_DOWNLOAD_SHA256"
|
|
end
|
|
|
|
def new_download_sha
|
|
require "cask/installer"
|
|
|
|
# Call checksumable? before hashing
|
|
@new_download_sha ||= Installer.new(self, verify_download_integrity: false)
|
|
.download(quiet: true)
|
|
.instance_eval { |x| Digest::SHA256.file(x).hexdigest }
|
|
end
|
|
|
|
def outdated_download_sha?
|
|
return true unless checksumable?
|
|
|
|
current_download_sha = download_sha_path.read if download_sha_path.exist?
|
|
current_download_sha.blank? || current_download_sha != new_download_sha
|
|
end
|
|
|
|
def caskroom_path
|
|
@caskroom_path ||= Caskroom.path.join(token)
|
|
end
|
|
|
|
def outdated?(greedy: false, greedy_latest: false, greedy_auto_updates: false)
|
|
!outdated_versions(greedy: greedy, greedy_latest: greedy_latest,
|
|
greedy_auto_updates: greedy_auto_updates).empty?
|
|
end
|
|
|
|
def outdated_versions(greedy: false, greedy_latest: false, greedy_auto_updates: false)
|
|
# special case: tap version is not available
|
|
return [] if version.nil?
|
|
|
|
if version.latest?
|
|
return versions if (greedy || greedy_latest) && outdated_download_sha?
|
|
|
|
return []
|
|
elsif auto_updates && !greedy && !greedy_auto_updates
|
|
return []
|
|
end
|
|
|
|
installed = versions
|
|
current = installed.last
|
|
|
|
# not outdated unless there is a different version on tap
|
|
return [] if current == version
|
|
|
|
# collect all installed versions that are different than tap version and return them
|
|
installed.reject { |v| v == version }
|
|
end
|
|
|
|
def outdated_info(greedy, verbose, json, greedy_latest, greedy_auto_updates)
|
|
return token if !verbose && !json
|
|
|
|
installed_versions = outdated_versions(greedy: greedy, greedy_latest: greedy_latest,
|
|
greedy_auto_updates: greedy_auto_updates).join(", ")
|
|
|
|
if json
|
|
{
|
|
name: token,
|
|
installed_versions: installed_versions,
|
|
current_version: version,
|
|
}
|
|
else
|
|
"#{token} (#{installed_versions}) != #{version}"
|
|
end
|
|
end
|
|
|
|
def to_s
|
|
@token
|
|
end
|
|
|
|
def hash
|
|
token.hash
|
|
end
|
|
|
|
def eql?(other)
|
|
instance_of?(other.class) && token == other.token
|
|
end
|
|
alias == eql?
|
|
|
|
def to_h
|
|
if loaded_from_api && Homebrew::EnvConfig.install_from_api?
|
|
json_cask = Homebrew::API::Cask.all_casks[token]
|
|
return api_to_local_hash(Homebrew::API.merge_variations(json_cask))
|
|
end
|
|
|
|
{
|
|
"token" => token,
|
|
"full_token" => full_name,
|
|
"tap" => tap&.name,
|
|
"name" => name,
|
|
"desc" => desc,
|
|
"homepage" => homepage,
|
|
"url" => url,
|
|
"appcast" => appcast,
|
|
"version" => version,
|
|
"versions" => os_versions,
|
|
"installed" => versions.last,
|
|
"outdated" => outdated?,
|
|
"sha256" => sha256,
|
|
"artifacts" => artifacts_list,
|
|
"caveats" => (to_h_string_gsubs(caveats) unless caveats.empty?),
|
|
"depends_on" => depends_on,
|
|
"conflicts_with" => conflicts_with,
|
|
"container" => container&.pairs,
|
|
"auto_updates" => auto_updates,
|
|
"tap_git_head" => tap&.git_head,
|
|
"languages" => languages,
|
|
}
|
|
end
|
|
|
|
def to_hash_with_variations
|
|
if loaded_from_api && Homebrew::EnvConfig.install_from_api?
|
|
return api_to_local_hash(Homebrew::API::Cask.all_casks[token])
|
|
end
|
|
|
|
hash = to_h
|
|
variations = {}
|
|
|
|
hash_keys_to_skip = %w[outdated installed versions]
|
|
|
|
if @dsl.on_system_blocks_exist?
|
|
[:arm, :intel].each do |arch|
|
|
MacOSVersions::SYMBOLS.each_key do |os_name|
|
|
bottle_tag = ::Utils::Bottles::Tag.new(system: os_name, arch: arch)
|
|
next unless bottle_tag.valid_combination?
|
|
|
|
Homebrew::SimulateSystem.os = os_name
|
|
Homebrew::SimulateSystem.arch = arch
|
|
|
|
refresh
|
|
|
|
to_h.each do |key, value|
|
|
next if hash_keys_to_skip.include? key
|
|
next if value.to_s == hash[key].to_s
|
|
|
|
variations[bottle_tag.to_sym] ||= {}
|
|
variations[bottle_tag.to_sym][key] = value
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
Homebrew::SimulateSystem.clear
|
|
refresh
|
|
|
|
hash["variations"] = variations
|
|
hash
|
|
end
|
|
|
|
private
|
|
|
|
def api_to_local_hash(hash)
|
|
hash["token"] = token
|
|
hash["installed"] = versions.last
|
|
hash["outdated"] = outdated?
|
|
hash
|
|
end
|
|
|
|
def artifacts_list
|
|
artifacts.map do |artifact|
|
|
case artifact
|
|
when Artifact::AbstractFlightBlock
|
|
# Only indicate whether this block is used as we don't load it from the API
|
|
{ artifact.summarize => nil }
|
|
when Artifact::Relocated
|
|
# Don't replace the Homebrew prefix in the source path since the source could include /usr/local
|
|
source, *args = artifact.to_args
|
|
{ artifact.class.dsl_key => [to_h_string_gsubs(source, replace_prefix: false), *to_h_gsubs(args)] }
|
|
else
|
|
{ artifact.class.dsl_key => to_h_gsubs(artifact.to_args) }
|
|
end
|
|
end
|
|
end
|
|
|
|
def to_h_string_gsubs(string, replace_prefix: true)
|
|
string = string.to_s.gsub(Dir.home, "$HOME")
|
|
|
|
if replace_prefix
|
|
string.gsub(HOMEBREW_PREFIX, "$(brew --prefix)")
|
|
else
|
|
string.gsub(Caskroom.path, "$(brew --prefix)/Caskroom")
|
|
end
|
|
end
|
|
|
|
def to_h_array_gsubs(array)
|
|
array.to_a.map do |value|
|
|
to_h_gsubs(value)
|
|
end
|
|
end
|
|
|
|
def to_h_hash_gsubs(hash)
|
|
hash.to_h.transform_values do |value|
|
|
to_h_gsubs(value)
|
|
end
|
|
rescue TypeError
|
|
to_h_array_gsubs(hash)
|
|
end
|
|
|
|
def to_h_gsubs(value)
|
|
return value if value.blank?
|
|
|
|
if value.respond_to? :to_h
|
|
to_h_hash_gsubs(value)
|
|
elsif value.respond_to? :to_a
|
|
to_h_array_gsubs(value)
|
|
elsif [true, false].include? value
|
|
value
|
|
else
|
|
to_h_string_gsubs(value)
|
|
end
|
|
end
|
|
end
|
|
end
|