cask: delay loading from source API
For casks with certain stanzas, *flight and language stanzas in this case, we need to use the caskfile to install them correctly. The JSON API is not an option. This delays loading from the source API until just before we try to install one of these casks. This reduces the number of requests we make to the source API.
This commit is contained in:
parent
0172ad1028
commit
fd62cdf636
@ -83,15 +83,14 @@ module Cask
|
||||
@tap
|
||||
end
|
||||
|
||||
def initialize(token, sourcefile_path: nil, source: nil, source_checksum: nil, tap: nil,
|
||||
config: nil, allow_reassignment: false, loaded_from_api: false, loader: nil, &block)
|
||||
def initialize(token, sourcefile_path: nil, source: nil, tap: nil,
|
||||
config: nil, allow_reassignment: false, loader: nil, &block)
|
||||
@token = token
|
||||
@sourcefile_path = sourcefile_path
|
||||
@source = source
|
||||
@ruby_source_checksum = source_checksum
|
||||
@tap = tap
|
||||
@allow_reassignment = allow_reassignment
|
||||
@loaded_from_api = loaded_from_api
|
||||
@loaded_from_api = false
|
||||
@loader = loader
|
||||
@block = block
|
||||
|
||||
@ -166,6 +165,12 @@ module Cask
|
||||
!versions.empty?
|
||||
end
|
||||
|
||||
# The caskfile is needed during installation when there are
|
||||
# `*flight` blocks or the cask has multiple languages
|
||||
def caskfile_only?
|
||||
languages.any? || artifacts.any?(Artifact::AbstractFlightBlock)
|
||||
end
|
||||
|
||||
sig { returns(T.nilable(Time)) }
|
||||
def install_time
|
||||
_, time = timestamped_versions.last
|
||||
@ -258,6 +263,27 @@ module Cask
|
||||
end
|
||||
end
|
||||
|
||||
def ruby_source_checksum
|
||||
@ruby_source_checksum ||= {
|
||||
"sha256" => Digest::SHA256.file(sourcefile_path).hexdigest,
|
||||
}.freeze
|
||||
end
|
||||
|
||||
def languages
|
||||
@languages ||= @dsl.languages
|
||||
end
|
||||
|
||||
def tap_git_head
|
||||
@tap_git_head ||= tap&.git_head
|
||||
end
|
||||
|
||||
def populate_from_api!(json_cask)
|
||||
@loaded_from_api = true
|
||||
@languages = json_cask[:languages]
|
||||
@tap_git_head = json_cask[:tap_git_head]
|
||||
@ruby_source_checksum = json_cask[:ruby_source_checksum].freeze
|
||||
end
|
||||
|
||||
def to_s
|
||||
@token
|
||||
end
|
||||
@ -306,7 +332,7 @@ module Cask
|
||||
"conflicts_with" => conflicts_with,
|
||||
"container" => container&.pairs,
|
||||
"auto_updates" => auto_updates,
|
||||
"tap_git_head" => tap&.git_head,
|
||||
"tap_git_head" => tap_git_head,
|
||||
"languages" => languages,
|
||||
"ruby_source_checksum" => ruby_source_checksum,
|
||||
}
|
||||
@ -360,12 +386,6 @@ module Cask
|
||||
hash
|
||||
end
|
||||
|
||||
def ruby_source_checksum
|
||||
@ruby_source_checksum ||= {
|
||||
"sha256" => Digest::SHA256.file(sourcefile_path).hexdigest,
|
||||
}
|
||||
end
|
||||
|
||||
def artifacts_list
|
||||
artifacts.map do |artifact|
|
||||
case artifact
|
||||
|
||||
@ -45,10 +45,7 @@ module Cask
|
||||
private
|
||||
|
||||
def cask(header_token, **options, &block)
|
||||
checksum = {
|
||||
"sha256" => Digest::SHA256.hexdigest(content),
|
||||
}
|
||||
Cask.new(header_token, source: content, source_checksum: checksum, **options, config: @config, &block)
|
||||
Cask.new(header_token, source: content, **options, config: @config, &block)
|
||||
end
|
||||
end
|
||||
|
||||
@ -212,8 +209,6 @@ module Cask
|
||||
class FromAPILoader
|
||||
attr_reader :token, :path
|
||||
|
||||
FLIGHT_STANZAS = [:preflight, :postflight, :uninstall_preflight, :uninstall_postflight].freeze
|
||||
|
||||
def self.can_load?(ref)
|
||||
return false if Homebrew::EnvConfig.no_install_from_api?
|
||||
return false unless ref.is_a?(String)
|
||||
@ -233,16 +228,7 @@ module Cask
|
||||
json_cask = @from_json || Homebrew::API::Cask.all_casks[token]
|
||||
cask_source = JSON.pretty_generate(json_cask)
|
||||
|
||||
json_cask = Homebrew::API.merge_variations(json_cask).deep_symbolize_keys
|
||||
|
||||
# Use the cask-source API if there are any `*flight` blocks or the cask has multiple languages
|
||||
if json_cask[:artifacts].any? { |artifact| FLIGHT_STANZAS.include?(artifact.keys.first) } ||
|
||||
json_cask[:languages].any?
|
||||
cask_source = Homebrew::API::Cask.fetch_source(token,
|
||||
git_head: json_cask[:tap_git_head],
|
||||
sha256: json_cask.dig(:ruby_source_checksum, :sha256))
|
||||
return FromContentLoader.new(cask_source).load(config: config)
|
||||
end
|
||||
json_cask = Homebrew::API.merge_variations(json_cask).deep_symbolize_keys.freeze
|
||||
|
||||
tap = Tap.fetch(json_cask[:tap]) if json_cask[:tap].to_s.include?("/")
|
||||
|
||||
@ -252,13 +238,7 @@ module Cask
|
||||
json_cask[:url_specs][:using] = using.to_sym
|
||||
end
|
||||
|
||||
Cask.new(token,
|
||||
tap: tap,
|
||||
source: cask_source,
|
||||
source_checksum: json_cask[:ruby_source_checksum],
|
||||
config: config,
|
||||
loaded_from_api: true,
|
||||
loader: self) do
|
||||
api_cask = Cask.new(token, tap: tap, source: cask_source, config: config, loader: self) do
|
||||
version json_cask[:version]
|
||||
|
||||
if json_cask[:sha256] == "no_check"
|
||||
@ -318,15 +298,21 @@ module Cask
|
||||
# convert generic string replacements into actual ones
|
||||
artifact = cask.loader.from_h_hash_gsubs(artifact, appdir)
|
||||
key = artifact.keys.first
|
||||
send(key, *artifact[key])
|
||||
if artifact[key].nil?
|
||||
# for artifacts with blocks that can't be loaded from the API
|
||||
send(key) {} # empty on purpose
|
||||
else
|
||||
send(key, *artifact[key])
|
||||
end
|
||||
end
|
||||
|
||||
if json_cask[:caveats].present?
|
||||
# convert generic string replacements into actual ones
|
||||
json_cask[:caveats] = cask.loader.from_h_string_gsubs(json_cask[:caveats], appdir)
|
||||
caveats json_cask[:caveats]
|
||||
caveats cask.loader.from_h_string_gsubs(json_cask[:caveats], appdir)
|
||||
end
|
||||
end
|
||||
api_cask.populate_from_api!(json_cask)
|
||||
api_cask
|
||||
end
|
||||
|
||||
def from_h_string_gsubs(string, appdir)
|
||||
|
||||
@ -77,7 +77,6 @@ module Cask
|
||||
:depends_on,
|
||||
:homepage,
|
||||
:language,
|
||||
:languages,
|
||||
:name,
|
||||
:sha256,
|
||||
:staged_path,
|
||||
|
||||
@ -64,6 +64,8 @@ module Cask
|
||||
def fetch(quiet: nil, timeout: nil)
|
||||
odebug "Cask::Installer#fetch"
|
||||
|
||||
load_cask_from_source_api! if @cask.loaded_from_api && @cask.caskfile_only?
|
||||
|
||||
verify_has_sha if require_sha? && !force?
|
||||
|
||||
download(quiet: quiet, timeout: timeout)
|
||||
@ -149,16 +151,8 @@ module Cask
|
||||
def uninstall_existing_cask
|
||||
return unless @cask.installed?
|
||||
|
||||
# use the same cask file that was used for installation, if possible
|
||||
installed_caskfile = @cask.installed_caskfile
|
||||
installed_cask = begin
|
||||
installed_caskfile.exist? ? CaskLoader.load(installed_caskfile) : @cask
|
||||
rescue CaskInvalidError # could be thrown by call to CaskLoader#load with outdated caskfile
|
||||
@cask # default
|
||||
end
|
||||
|
||||
# Always force uninstallation, ignore method parameter
|
||||
cask_installer = Installer.new(installed_cask, verbose: verbose?, force: true, upgrade: upgrade?)
|
||||
cask_installer = Installer.new(@cask, verbose: verbose?, force: true, upgrade: upgrade?)
|
||||
zap? ? cask_installer.zap : cask_installer.uninstall
|
||||
end
|
||||
|
||||
@ -403,6 +397,7 @@ module Cask
|
||||
end
|
||||
|
||||
def uninstall
|
||||
load_installed_caskfile!
|
||||
oh1 "Uninstalling Cask #{Formatter.identifier(@cask)}"
|
||||
uninstall_artifacts(clear: true)
|
||||
if !reinstall? && !upgrade?
|
||||
@ -480,6 +475,7 @@ module Cask
|
||||
end
|
||||
|
||||
def zap
|
||||
load_installed_caskfile!
|
||||
ohai "Implied `brew uninstall --cask #{@cask}`"
|
||||
uninstall_artifacts
|
||||
if (zap_stanzas = @cask.artifacts.select { |a| a.is_a?(Artifact::Zap) }).empty?
|
||||
@ -547,5 +543,30 @@ module Cask
|
||||
odebug "Purging all staged versions of Cask #{@cask}"
|
||||
gain_permissions_remove(@cask.caskroom_path)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# load the same cask file that was used for installation, if possible
|
||||
def load_installed_caskfile!
|
||||
installed_caskfile = @cask.installed_caskfile
|
||||
|
||||
if installed_caskfile.exist?
|
||||
begin
|
||||
@cask = CaskLoader.load(installed_caskfile)
|
||||
return
|
||||
rescue CaskInvalidError
|
||||
# could be caused by trying to load outdated caskfile
|
||||
end
|
||||
end
|
||||
|
||||
load_cask_from_source_api! if @cask.loaded_from_api && @cask.caskfile_only?
|
||||
# otherwise we default to the current cask
|
||||
end
|
||||
|
||||
def load_cask_from_source_api!
|
||||
options = { git_head: @cask.tap_git_head, sha256: @cask.ruby_source_checksum["sha256"] }
|
||||
cask_source = Homebrew::API::Cask.fetch_source(@cask.token, **options)
|
||||
@cask = CaskLoader::FromContentLoader.new(cask_source).load(config: @cask.config)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -59,80 +59,64 @@ describe Cask::CaskLoader::FromAPILoader, :cask do
|
||||
end
|
||||
|
||||
describe "#load" do
|
||||
shared_examples "loads from fetched source" do |cask_token|
|
||||
include_context "with API setup", cask_token
|
||||
let(:content_loader) { instance_double(Cask::CaskLoader::FromContentLoader) }
|
||||
|
||||
it "fetches cask source from API" do
|
||||
expect(Homebrew::API::Cask).to receive(:fetch_source).once
|
||||
expect(Cask::CaskLoader::FromContentLoader)
|
||||
.to receive(:new).once
|
||||
.and_return(content_loader)
|
||||
expect(content_loader).to receive(:load).once
|
||||
|
||||
api_loader.load(config: nil)
|
||||
end
|
||||
end
|
||||
|
||||
context "with a preflight stanza" do
|
||||
include_examples "loads from fetched source", "with-preflight"
|
||||
end
|
||||
|
||||
context "with an uninstall-preflight stanza" do
|
||||
include_examples "loads from fetched source", "with-uninstall-preflight"
|
||||
end
|
||||
|
||||
context "with a postflight stanza" do
|
||||
include_examples "loads from fetched source", "with-postflight"
|
||||
end
|
||||
|
||||
context "with an uninstall-postflight stanza" do
|
||||
include_examples "loads from fetched source", "with-uninstall-postflight"
|
||||
end
|
||||
|
||||
context "with a language stanza" do
|
||||
include_examples "loads from fetched source", "with-languages"
|
||||
end
|
||||
|
||||
shared_examples "loads from API" do |cask_token|
|
||||
shared_examples "loads from API" do |cask_token, caskfile_only|
|
||||
include_context "with API setup", cask_token
|
||||
let(:cask_from_api) { api_loader.load(config: nil) }
|
||||
|
||||
it "loads from JSON API" do
|
||||
expect(Homebrew::API::Cask).not_to receive(:fetch_source)
|
||||
expect(Cask::CaskLoader::FromContentLoader).not_to receive(:new)
|
||||
|
||||
expect(cask_from_api).to be_a(Cask::Cask)
|
||||
expect(cask_from_api.token).to eq(cask_token)
|
||||
expect(cask_from_api.loaded_from_api).to be(true)
|
||||
expect(cask_from_api.caskfile_only?).to be(caskfile_only)
|
||||
end
|
||||
end
|
||||
|
||||
context "with a binary stanza" do
|
||||
include_examples "loads from API", "with-binary"
|
||||
include_examples "loads from API", "with-binary", false
|
||||
end
|
||||
|
||||
context "with cask dependencies" do
|
||||
include_examples "loads from API", "with-depends-on-cask-multiple"
|
||||
include_examples "loads from API", "with-depends-on-cask-multiple", false
|
||||
end
|
||||
|
||||
context "with formula dependencies" do
|
||||
include_examples "loads from API", "with-depends-on-formula-multiple"
|
||||
include_examples "loads from API", "with-depends-on-formula-multiple", false
|
||||
end
|
||||
|
||||
context "with macos dependencies" do
|
||||
include_examples "loads from API", "with-depends-on-macos-array"
|
||||
include_examples "loads from API", "with-depends-on-macos-array", false
|
||||
end
|
||||
|
||||
context "with an installer stanza" do
|
||||
include_examples "loads from API", "with-installer-script"
|
||||
include_examples "loads from API", "with-installer-script", false
|
||||
end
|
||||
|
||||
context "with uninstall stanzas" do
|
||||
include_examples "loads from API", "with-uninstall-multi"
|
||||
include_examples "loads from API", "with-uninstall-multi", false
|
||||
end
|
||||
|
||||
context "with a zap stanza" do
|
||||
include_examples "loads from API", "with-zap"
|
||||
include_examples "loads from API", "with-zap", false
|
||||
end
|
||||
|
||||
context "with a preflight stanza" do
|
||||
include_examples "loads from API", "with-preflight", true
|
||||
end
|
||||
|
||||
context "with an uninstall-preflight stanza" do
|
||||
include_examples "loads from API", "with-uninstall-preflight", true
|
||||
end
|
||||
|
||||
context "with a postflight stanza" do
|
||||
include_examples "loads from API", "with-postflight", true
|
||||
end
|
||||
|
||||
context "with an uninstall-postflight stanza" do
|
||||
include_examples "loads from API", "with-uninstall-postflight", true
|
||||
end
|
||||
|
||||
context "with a language stanza" do
|
||||
include_examples "loads from API", "with-languages", true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user