Load casks from the JSON API with HOMEBREW_INSTALL_FROM_API

This commit is contained in:
Rylan Polster 2022-12-29 02:48:31 -05:00
parent ea0b786388
commit 374b61584b
No known key found for this signature in database
GPG Key ID: 46A744940CFF4D64
3 changed files with 147 additions and 23 deletions

View File

@ -10,10 +10,45 @@ module Homebrew
class << self
extend T::Sig
MAX_RETRIES = 3
sig { returns(String) }
def cached_cask_json_file
HOMEBREW_CACHE_API/"cask.json"
end
sig { params(name: String).returns(Hash) }
def fetch(name)
Homebrew::API.fetch "cask/#{name}.json"
end
sig { returns(Hash) }
def all_casks
@all_casks ||= begin
retry_count = 0
url = "https://formulae.brew.sh/api/cask.json"
json_casks = begin
curl_args = %W[--compressed --silent #{url}]
if cached_cask_json_file.exist? && !cached_cask_json_file.empty?
curl_args.prepend("--time-cond", cached_cask_json_file)
end
curl_download(*curl_args, to: cached_cask_json_file, max_time: 5)
JSON.parse(cached_cask_json_file.read)
rescue JSON::ParserError
cached_cask_json_file.unlink
retry_count += 1
odie "Cannot download non-corrupt #{url}!" if retry_count > MAX_RETRIES
retry
end
json_casks.to_h do |json_cask|
[json_cask["token"], json_cask.except("token")]
end
end
end
end
end
end

View File

@ -186,6 +186,91 @@ module Cask
end
end
# Loads a cask from the JSON API.
class FromAPILoader
attr_reader :token, :path
FLIGHT_STANZAS = [:preflight, :postflight, :uninstall_preflight, :uninstall_postflight].freeze
def self.can_load?(ref)
Homebrew::API::Cask.all_casks.key? ref
end
def initialize(token)
@token = token
@path = CaskLoader.default_path(token)
end
def load(config:)
json_cask = Homebrew::API::Cask.all_casks[token]
cask_source = Homebrew::API::CaskSource.fetch(token)
if (bottle_tag = ::Utils::Bottles.tag.to_s.presence) &&
(variations = json_cask["variations"].presence) &&
(variation = variations[bottle_tag].presence)
json_cask = json_cask.merge(variation)
end
json_cask.deep_symbolize_keys!
Cask.new(token, source: cask_source, config: config) do
version json_cask[:version]
if json_cask[:sha256] == "no_check"
sha256 :no_check
else
sha256 json_cask[:sha256]
end
url json_cask[:url]
appcast json_cask[:appcast] if json_cask[:appcast].present?
json_cask[:name].each do |cask_name|
name cask_name
end
desc json_cask[:desc]
homepage json_cask[:homepage]
auto_updates json_cask[:auto_updates] if json_cask[:auto_updates].present?
conflicts_with(**json_cask[:conflicts_with]) if json_cask[:conflicts_with].present?
if json_cask[:depends_on].present?
dep_hash = json_cask[:depends_on].to_h do |dep_key, dep_value|
next [dep_key, dep_value] unless dep_key == :macos
dep_type = dep_value.keys.first
if dep_type == :==
version_symbols = dep_value[dep_type].map do |version|
MacOSVersions::SYMBOLS.key(version) || version
end
next [dep_key, version_symbols]
end
version_symbol = dep_value[dep_type].first
version_symbol = MacOSVersions::SYMBOLS.key(version_symbol) || version_symbol
[dep_key, "#{dep_type} :#{version_symbol}"]
end.compact
depends_on(**dep_hash)
end
if json_cask[:container].present?
container_hash = json_cask[:container].to_h do |container_key, container_value|
next [container_key, container_value] unless container_key == :type
[container_key, container_value.to_sym]
end
container(**container_hash)
end
json_cask[:artifacts].each do |artifact|
key = artifact.keys.first
send(key, *artifact[key])
end
caveats json_cask[:caveats] if json_cask[:caveats].present?
end
end
end
# Pseudo-loader which raises an error when trying to load the corresponding cask.
class NullLoader < FromPathLoader
extend T::Sig
@ -225,15 +310,15 @@ module Cask
next unless loader_class.can_load?(ref)
if loader_class == FromTapLoader && Homebrew::EnvConfig.install_from_api? &&
ref.start_with?("homebrew/cask/") && Homebrew::API::CaskSource.available?(ref)
return FromContentLoader.new(Homebrew::API::CaskSource.fetch(ref))
ref.start_with?("homebrew/cask/") && FromAPILoader.can_load?(ref)
return FromAPILoader.new(ref)
end
return loader_class.new(ref)
end
if Homebrew::EnvConfig.install_from_api? && !need_path && Homebrew::API::CaskSource.available?(ref)
return FromContentLoader.new(Homebrew::API::CaskSource.fetch(ref))
return FromAPILoader.new(ref)
end
return FromTapPathLoader.new(default_path(ref)) if FromTapPathLoader.can_load?(default_path(ref))

View File

@ -754,28 +754,32 @@ EOS
if [[ -n "${HOMEBREW_INSTALL_FROM_API}" ]]
then
mkdir -p "${HOMEBREW_CACHE}/api"
if [[ -f "${HOMEBREW_CACHE}/api/formula.json" ]]
then
INITIAL_JSON_BYTESIZE="$(wc -c "${HOMEBREW_CACHE}"/api/formula.json)"
fi
curl \
"${CURL_DISABLE_CURLRC_ARGS[@]}" \
--fail --compressed --silent --max-time 5 \
--location --remote-time --output "${HOMEBREW_CACHE}/api/formula.json" \
--time-cond "${HOMEBREW_CACHE}/api/formula.json" \
--user-agent "${HOMEBREW_USER_AGENT_CURL}" \
"https://formulae.brew.sh/api/formula.json"
curl_exit_code=$?
if [[ ${curl_exit_code} -eq 0 ]]
then
CURRENT_JSON_BYTESIZE="$(wc -c "${HOMEBREW_CACHE}"/api/formula.json)"
if [[ "${INITIAL_JSON_BYTESIZE}" != "${CURRENT_JSON_BYTESIZE}" ]]
for formula_or_cask in formula cask
do
if [[ -f "${HOMEBREW_CACHE}/api/${formula_or_cask}.json" ]]
then
HOMEBREW_UPDATED="1"
INITIAL_JSON_BYTESIZE="$(wc -c "${HOMEBREW_CACHE}"/api/"${formula_or_cask}".json)"
fi
else
echo "Failed to download formula.json!" >>"${update_failed_file}"
fi
curl \
"${CURL_DISABLE_CURLRC_ARGS[@]}" \
--fail --compressed --silent --max-time 5 \
--location --remote-time --output "${HOMEBREW_CACHE}/api/${formula_or_cask}.json" \
--time-cond "${HOMEBREW_CACHE}/api/${formula_or_cask}.json" \
--user-agent "${HOMEBREW_USER_AGENT_CURL}" \
"https://formulae.brew.sh/api/${formula_or_cask}.json"
curl_exit_code=$?
if [[ ${curl_exit_code} -eq 0 ]]
then
CURRENT_JSON_BYTESIZE="$(wc -c "${HOMEBREW_CACHE}"/api/"${formula_or_cask}".json)"
if [[ "${INITIAL_JSON_BYTESIZE}" != "${CURRENT_JSON_BYTESIZE}" ]]
then
HOMEBREW_UPDATED="1"
fi
else
echo "Failed to download ${formula_or_cask}.json!" >>"${update_failed_file}"
fi
done
fi
safe_cd "${HOMEBREW_REPOSITORY}"