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
|
@tap
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(token, sourcefile_path: nil, source: nil, source_checksum: nil, tap: nil,
|
def initialize(token, sourcefile_path: nil, source: nil, tap: nil,
|
||||||
config: nil, allow_reassignment: false, loaded_from_api: false, loader: nil, &block)
|
config: nil, allow_reassignment: false, loader: nil, &block)
|
||||||
@token = token
|
@token = token
|
||||||
@sourcefile_path = sourcefile_path
|
@sourcefile_path = sourcefile_path
|
||||||
@source = source
|
@source = source
|
||||||
@ruby_source_checksum = source_checksum
|
|
||||||
@tap = tap
|
@tap = tap
|
||||||
@allow_reassignment = allow_reassignment
|
@allow_reassignment = allow_reassignment
|
||||||
@loaded_from_api = loaded_from_api
|
@loaded_from_api = false
|
||||||
@loader = loader
|
@loader = loader
|
||||||
@block = block
|
@block = block
|
||||||
|
|
||||||
@ -166,6 +165,12 @@ module Cask
|
|||||||
!versions.empty?
|
!versions.empty?
|
||||||
end
|
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)) }
|
sig { returns(T.nilable(Time)) }
|
||||||
def install_time
|
def install_time
|
||||||
_, time = timestamped_versions.last
|
_, time = timestamped_versions.last
|
||||||
@ -258,6 +263,27 @@ module Cask
|
|||||||
end
|
end
|
||||||
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
|
def to_s
|
||||||
@token
|
@token
|
||||||
end
|
end
|
||||||
@ -306,7 +332,7 @@ module Cask
|
|||||||
"conflicts_with" => conflicts_with,
|
"conflicts_with" => conflicts_with,
|
||||||
"container" => container&.pairs,
|
"container" => container&.pairs,
|
||||||
"auto_updates" => auto_updates,
|
"auto_updates" => auto_updates,
|
||||||
"tap_git_head" => tap&.git_head,
|
"tap_git_head" => tap_git_head,
|
||||||
"languages" => languages,
|
"languages" => languages,
|
||||||
"ruby_source_checksum" => ruby_source_checksum,
|
"ruby_source_checksum" => ruby_source_checksum,
|
||||||
}
|
}
|
||||||
@ -360,12 +386,6 @@ module Cask
|
|||||||
hash
|
hash
|
||||||
end
|
end
|
||||||
|
|
||||||
def ruby_source_checksum
|
|
||||||
@ruby_source_checksum ||= {
|
|
||||||
"sha256" => Digest::SHA256.file(sourcefile_path).hexdigest,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def artifacts_list
|
def artifacts_list
|
||||||
artifacts.map do |artifact|
|
artifacts.map do |artifact|
|
||||||
case artifact
|
case artifact
|
||||||
|
|||||||
@ -45,10 +45,7 @@ module Cask
|
|||||||
private
|
private
|
||||||
|
|
||||||
def cask(header_token, **options, &block)
|
def cask(header_token, **options, &block)
|
||||||
checksum = {
|
Cask.new(header_token, source: content, **options, config: @config, &block)
|
||||||
"sha256" => Digest::SHA256.hexdigest(content),
|
|
||||||
}
|
|
||||||
Cask.new(header_token, source: content, source_checksum: checksum, **options, config: @config, &block)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -212,8 +209,6 @@ module Cask
|
|||||||
class FromAPILoader
|
class FromAPILoader
|
||||||
attr_reader :token, :path
|
attr_reader :token, :path
|
||||||
|
|
||||||
FLIGHT_STANZAS = [:preflight, :postflight, :uninstall_preflight, :uninstall_postflight].freeze
|
|
||||||
|
|
||||||
def self.can_load?(ref)
|
def self.can_load?(ref)
|
||||||
return false if Homebrew::EnvConfig.no_install_from_api?
|
return false if Homebrew::EnvConfig.no_install_from_api?
|
||||||
return false unless ref.is_a?(String)
|
return false unless ref.is_a?(String)
|
||||||
@ -233,16 +228,7 @@ module Cask
|
|||||||
json_cask = @from_json || Homebrew::API::Cask.all_casks[token]
|
json_cask = @from_json || Homebrew::API::Cask.all_casks[token]
|
||||||
cask_source = JSON.pretty_generate(json_cask)
|
cask_source = JSON.pretty_generate(json_cask)
|
||||||
|
|
||||||
json_cask = Homebrew::API.merge_variations(json_cask).deep_symbolize_keys
|
json_cask = Homebrew::API.merge_variations(json_cask).deep_symbolize_keys.freeze
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
tap = Tap.fetch(json_cask[:tap]) if json_cask[:tap].to_s.include?("/")
|
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
|
json_cask[:url_specs][:using] = using.to_sym
|
||||||
end
|
end
|
||||||
|
|
||||||
Cask.new(token,
|
api_cask = Cask.new(token, tap: tap, source: cask_source, config: config, loader: self) do
|
||||||
tap: tap,
|
|
||||||
source: cask_source,
|
|
||||||
source_checksum: json_cask[:ruby_source_checksum],
|
|
||||||
config: config,
|
|
||||||
loaded_from_api: true,
|
|
||||||
loader: self) do
|
|
||||||
version json_cask[:version]
|
version json_cask[:version]
|
||||||
|
|
||||||
if json_cask[:sha256] == "no_check"
|
if json_cask[:sha256] == "no_check"
|
||||||
@ -318,15 +298,21 @@ module Cask
|
|||||||
# convert generic string replacements into actual ones
|
# convert generic string replacements into actual ones
|
||||||
artifact = cask.loader.from_h_hash_gsubs(artifact, appdir)
|
artifact = cask.loader.from_h_hash_gsubs(artifact, appdir)
|
||||||
key = artifact.keys.first
|
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
|
end
|
||||||
|
|
||||||
if json_cask[:caveats].present?
|
if json_cask[:caveats].present?
|
||||||
# convert generic string replacements into actual ones
|
# convert generic string replacements into actual ones
|
||||||
json_cask[:caveats] = cask.loader.from_h_string_gsubs(json_cask[:caveats], appdir)
|
caveats cask.loader.from_h_string_gsubs(json_cask[:caveats], appdir)
|
||||||
caveats json_cask[:caveats]
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
api_cask.populate_from_api!(json_cask)
|
||||||
|
api_cask
|
||||||
end
|
end
|
||||||
|
|
||||||
def from_h_string_gsubs(string, appdir)
|
def from_h_string_gsubs(string, appdir)
|
||||||
|
|||||||
@ -77,7 +77,6 @@ module Cask
|
|||||||
:depends_on,
|
:depends_on,
|
||||||
:homepage,
|
:homepage,
|
||||||
:language,
|
:language,
|
||||||
:languages,
|
|
||||||
:name,
|
:name,
|
||||||
:sha256,
|
:sha256,
|
||||||
:staged_path,
|
:staged_path,
|
||||||
|
|||||||
@ -64,6 +64,8 @@ module Cask
|
|||||||
def fetch(quiet: nil, timeout: nil)
|
def fetch(quiet: nil, timeout: nil)
|
||||||
odebug "Cask::Installer#fetch"
|
odebug "Cask::Installer#fetch"
|
||||||
|
|
||||||
|
load_cask_from_source_api! if @cask.loaded_from_api && @cask.caskfile_only?
|
||||||
|
|
||||||
verify_has_sha if require_sha? && !force?
|
verify_has_sha if require_sha? && !force?
|
||||||
|
|
||||||
download(quiet: quiet, timeout: timeout)
|
download(quiet: quiet, timeout: timeout)
|
||||||
@ -149,16 +151,8 @@ module Cask
|
|||||||
def uninstall_existing_cask
|
def uninstall_existing_cask
|
||||||
return unless @cask.installed?
|
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
|
# 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
|
zap? ? cask_installer.zap : cask_installer.uninstall
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -403,6 +397,7 @@ module Cask
|
|||||||
end
|
end
|
||||||
|
|
||||||
def uninstall
|
def uninstall
|
||||||
|
load_installed_caskfile!
|
||||||
oh1 "Uninstalling Cask #{Formatter.identifier(@cask)}"
|
oh1 "Uninstalling Cask #{Formatter.identifier(@cask)}"
|
||||||
uninstall_artifacts(clear: true)
|
uninstall_artifacts(clear: true)
|
||||||
if !reinstall? && !upgrade?
|
if !reinstall? && !upgrade?
|
||||||
@ -480,6 +475,7 @@ module Cask
|
|||||||
end
|
end
|
||||||
|
|
||||||
def zap
|
def zap
|
||||||
|
load_installed_caskfile!
|
||||||
ohai "Implied `brew uninstall --cask #{@cask}`"
|
ohai "Implied `brew uninstall --cask #{@cask}`"
|
||||||
uninstall_artifacts
|
uninstall_artifacts
|
||||||
if (zap_stanzas = @cask.artifacts.select { |a| a.is_a?(Artifact::Zap) }).empty?
|
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}"
|
odebug "Purging all staged versions of Cask #{@cask}"
|
||||||
gain_permissions_remove(@cask.caskroom_path)
|
gain_permissions_remove(@cask.caskroom_path)
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
|||||||
@ -59,80 +59,64 @@ describe Cask::CaskLoader::FromAPILoader, :cask do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe "#load" do
|
describe "#load" do
|
||||||
shared_examples "loads from fetched source" do |cask_token|
|
shared_examples "loads from API" do |cask_token, caskfile_only|
|
||||||
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|
|
|
||||||
include_context "with API setup", cask_token
|
include_context "with API setup", cask_token
|
||||||
let(:cask_from_api) { api_loader.load(config: nil) }
|
let(:cask_from_api) { api_loader.load(config: nil) }
|
||||||
|
|
||||||
it "loads from JSON API" do
|
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).to be_a(Cask::Cask)
|
||||||
expect(cask_from_api.token).to eq(cask_token)
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with a binary stanza" do
|
context "with a binary stanza" do
|
||||||
include_examples "loads from API", "with-binary"
|
include_examples "loads from API", "with-binary", false
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with cask dependencies" do
|
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
|
end
|
||||||
|
|
||||||
context "with formula dependencies" do
|
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
|
end
|
||||||
|
|
||||||
context "with macos dependencies" do
|
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
|
end
|
||||||
|
|
||||||
context "with an installer stanza" do
|
context "with an installer stanza" do
|
||||||
include_examples "loads from API", "with-installer-script"
|
include_examples "loads from API", "with-installer-script", false
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with uninstall stanzas" do
|
context "with uninstall stanzas" do
|
||||||
include_examples "loads from API", "with-uninstall-multi"
|
include_examples "loads from API", "with-uninstall-multi", false
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with a zap stanza" do
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user