Skip to content

Commit bafc57c

Browse files
committed
Add Cask install/upgrade/reinstall support for download queue
This will allow installing/upgrading/reinstalling casks and all their dependencies in parallel.
1 parent cb1fe9a commit bafc57c

File tree

6 files changed

+104
-22
lines changed

6 files changed

+104
-22
lines changed

Library/Homebrew/api/cask.rb

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ def self.fetch(token)
2828
Homebrew::API.fetch "cask/#{token}.json"
2929
end
3030

31-
sig { params(cask: ::Cask::Cask).returns(::Cask::Cask) }
32-
def self.source_download(cask)
31+
sig { params(cask: ::Cask::Cask, download_queue: T.nilable(Homebrew::DownloadQueue)).returns(Homebrew::API::SourceDownload) }
32+
def self.source_download(cask, download_queue: nil)
3333
path = cask.ruby_source_path.to_s
3434
sha256 = cask.ruby_source_checksum[:sha256]
3535
checksum = Checksum.new(sha256) if sha256
@@ -44,7 +44,20 @@ def self.source_download(cask)
4444
],
4545
cache: HOMEBREW_CACHE_API_SOURCE/"#{tap}/#{git_head}/Cask",
4646
)
47-
download.fetch
47+
48+
if download_queue
49+
download_queue.enqueue(download)
50+
elsif !download.cache.exist?
51+
download.fetch
52+
end
53+
54+
download
55+
end
56+
57+
sig { params(cask: ::Cask::Cask).returns(::Cask::Cask) }
58+
def self.source_download_cask(cask)
59+
download = source_download(cask)
60+
4861
::Cask::CaskLoader::FromPathLoader.new(download.symlink_location)
4962
.load(config: cask.config)
5063
end

Library/Homebrew/cask/installer.rb

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ class Installer
2121
skip_cask_deps: T::Boolean, binaries: T::Boolean, verbose: T::Boolean, zap: T::Boolean,
2222
require_sha: T::Boolean, upgrade: T::Boolean, reinstall: T::Boolean, installed_as_dependency: T::Boolean,
2323
installed_on_request: T::Boolean, quarantine: T::Boolean, verify_download_integrity: T::Boolean,
24-
quiet: T::Boolean
24+
quiet: T::Boolean, download_queue: T.nilable(Homebrew::DownloadQueue)
2525
).void
2626
}
2727
def initialize(cask, command: SystemCommand, force: false, adopt: false,
2828
skip_cask_deps: false, binaries: true, verbose: false,
2929
zap: false, require_sha: false, upgrade: false, reinstall: false,
3030
installed_as_dependency: false, installed_on_request: true,
31-
quarantine: true, verify_download_integrity: true, quiet: false)
31+
quarantine: true, verify_download_integrity: true, quiet: false, download_queue: nil)
3232
@cask = cask
3333
@command = command
3434
@force = force
@@ -45,6 +45,7 @@ def initialize(cask, command: SystemCommand, force: false, adopt: false,
4545
@quarantine = quarantine
4646
@verify_download_integrity = verify_download_integrity
4747
@quiet = quiet
48+
@download_queue = download_queue
4849
end
4950

5051
sig { returns(T::Boolean) }
@@ -104,14 +105,14 @@ def self.caveats(cask)
104105
def fetch(quiet: nil, timeout: nil)
105106
odebug "Cask::Installer#fetch"
106107

107-
load_cask_from_source_api! if @cask.loaded_from_api? && @cask.caskfile_only?
108+
load_cask_from_source_api! if cask_from_source_api?
108109
verify_has_sha if require_sha? && !force?
109110
check_requirements
110111

111112
forbidden_tap_check
112113
forbidden_cask_and_formula_check
113114

114-
download(quiet:, timeout:)
115+
download(quiet:, timeout:) if @download_queue.nil?
115116

116117
satisfy_cask_and_formula_dependencies
117118
end
@@ -790,9 +791,20 @@ def forbidden_cask_and_formula_check
790791
)
791792
end
792793

794+
sig { void }
795+
def enqueue_downloads
796+
download_queue = @download_queue
797+
return if download_queue.nil?
798+
799+
Homebrew::API::Cask.source_download(@cask, download_queue:) if cask_from_source_api?
800+
801+
download_queue.enqueue(downloader)
802+
end
803+
793804
private
794805

795806
# load the same cask file that was used for installation, if possible
807+
sig { void }
796808
def load_installed_caskfile!
797809
Migrator.migrate_if_needed(@cask)
798810

@@ -807,12 +819,18 @@ def load_installed_caskfile!
807819
end
808820
end
809821

810-
load_cask_from_source_api! if @cask.loaded_from_api? && @cask.caskfile_only?
822+
load_cask_from_source_api! if cask_from_source_api?
811823
# otherwise we default to the current cask
812824
end
813825

826+
sig { void }
814827
def load_cask_from_source_api!
815-
@cask = Homebrew::API::Cask.source_download(@cask)
828+
@cask = Homebrew::API::Cask.source_download_cask(@cask)
829+
end
830+
831+
sig { returns(T::Boolean) }
832+
def cask_from_source_api?
833+
@cask.loaded_from_api? && @cask.caskfile_only?
816834
end
817835
end
818836
end

Library/Homebrew/cask/reinstall.rb

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,20 @@ def self.reinstall_casks(
2323

2424
quarantine = true if quarantine.nil?
2525

26-
casks.each do |cask|
27-
Installer
28-
.new(cask, binaries:, verbose:, force:, skip_cask_deps:, require_sha:, reinstall: true, quarantine:, zap:)
29-
.install
26+
download_queue = Homebrew::DownloadQueue.new(pour: true) if Homebrew::EnvConfig.download_concurrency > 1
27+
cask_installers = casks.map do |cask|
28+
Installer.new(cask, binaries:, verbose:, force:, skip_cask_deps:, require_sha:, reinstall: true,
29+
quarantine:, zap:, download_queue:)
3030
end
31+
32+
if download_queue
33+
oh1 "Fetching downloads for: #{casks.map { |cask| Formatter.identifier(cask.full_name) }.to_sentence}",
34+
truncate: false
35+
cask_installers.each(&:enqueue_downloads)
36+
download_queue.fetch
37+
end
38+
39+
cask_installers.each(&:install)
3140
end
3241
end
3342
end

Library/Homebrew/cask/upgrade.rb

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,6 @@ def self.upgrade_casks!(
9898
end
9999
end
100100

101-
verb = dry_run ? "Would upgrade" : "Upgrading"
102-
oh1 "#{verb} #{outdated_casks.count} outdated #{::Utils.pluralize("package", outdated_casks.count)}:"
103-
104-
caught_exceptions = []
105-
106101
upgradable_casks = outdated_casks.map do |c|
107102
unless c.installed?
108103
odie <<~EOS
@@ -114,6 +109,33 @@ def self.upgrade_casks!(
114109
[CaskLoader.load(c.installed_caskfile), c]
115110
end
116111

112+
return false if upgradable_casks.empty?
113+
114+
if !dry_run && Homebrew::EnvConfig.download_concurrency > 1
115+
download_queue = Homebrew::DownloadQueue.new(pour: true)
116+
117+
fetchable_casks = upgradable_casks.map(&:last)
118+
fetchable_casks_sentence = fetchable_casks.map { |cask| Formatter.identifier(cask.full_name) }.to_sentence
119+
oh1 "Fetching downloads for: #{fetchable_casks_sentence}", truncate: false
120+
121+
fetchable_casks.each do |cask|
122+
# This is significantly easier given the weird difference in Sorbet signatures here.
123+
# rubocop:disable Style/DoubleNegation
124+
Installer.new(cask, binaries: !!binaries, verbose: !!verbose, force: !!force,
125+
skip_cask_deps: !!skip_cask_deps, require_sha: !!require_sha,
126+
upgrade: true, quarantine:, download_queue:)
127+
.enqueue_downloads
128+
# rubocop:enable Style/DoubleNegation
129+
end
130+
131+
download_queue.fetch
132+
end
133+
134+
verb = dry_run ? "Would upgrade" : "Upgrading"
135+
oh1 "#{verb} #{upgradable_casks.count} outdated #{::Utils.pluralize("package", upgradable_casks.count)}:"
136+
137+
caught_exceptions = []
138+
117139
puts upgradable_casks
118140
.map { |(old_cask, new_cask)| "#{new_cask.full_name} #{old_cask.version} -> #{new_cask.version}" }
119141
.join("\n")
@@ -123,7 +145,7 @@ def self.upgrade_casks!(
123145
upgrade_cask(
124146
old_cask, new_cask,
125147
binaries:, force:, skip_cask_deps:, verbose:,
126-
quarantine:, require_sha:
148+
quarantine:, require_sha:, download_queue:
127149
)
128150
rescue => e
129151
new_exception = e.exception("#{new_cask.full_name}: #{e}")
@@ -149,11 +171,12 @@ def self.upgrade_casks!(
149171
require_sha: T.nilable(T::Boolean),
150172
skip_cask_deps: T.nilable(T::Boolean),
151173
verbose: T.nilable(T::Boolean),
174+
download_queue: T.nilable(Homebrew::DownloadQueue),
152175
).void
153176
}
154177
def self.upgrade_cask(
155178
old_cask, new_cask,
156-
binaries:, force:, quarantine:, require_sha:, skip_cask_deps:, verbose:
179+
binaries:, force:, quarantine:, require_sha:, skip_cask_deps:, verbose:, download_queue:
157180
)
158181
require "cask/installer"
159182

@@ -181,6 +204,7 @@ def self.upgrade_cask(
181204
require_sha:,
182205
upgrade: true,
183206
quarantine:,
207+
download_queue:,
184208
}.compact
185209

186210
new_cask_installer =

Library/Homebrew/cmd/install.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,24 @@ def run
242242

243243
installed_casks, new_casks = casks.partition(&:installed?)
244244

245+
download_queue = Homebrew::DownloadQueue.new(pour: true) if Homebrew::EnvConfig.download_concurrency > 1
246+
fetch_casks = Homebrew::EnvConfig.no_install_upgrade? ? new_casks : casks
247+
248+
if download_queue
249+
fetch_casks_sentence = fetch_casks.map { |cask| Formatter.identifier(cask.full_name) }.to_sentence
250+
oh1 "Fetching downloads for: #{fetch_casks_sentence}", truncate: false
251+
252+
fetch_casks.each do |cask|
253+
Cask::Installer.new(cask, binaries: args.binaries?, verbose: args.verbose?,
254+
force: args.force?, skip_cask_deps: args.skip_cask_deps?,
255+
require_sha: args.require_sha?, reinstall: true,
256+
quarantine: args.quarantine?, zap: args.zap?, download_queue:)
257+
.enqueue_downloads
258+
end
259+
260+
download_queue.fetch
261+
end
262+
245263
new_casks.each do |cask|
246264
Cask::Installer.new(
247265
cask,

Library/Homebrew/test/cask/installer_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@
225225

226226
it "installs cask" do
227227
source_caffeine = Cask::CaskLoader.load(path)
228-
expect(Homebrew::API::Cask).to receive(:source_download).once.and_return(source_caffeine)
228+
expect(Homebrew::API::Cask).to receive(:source_download_cask).once.and_return(source_caffeine)
229229

230230
caffeine = Cask::CaskLoader.load(path)
231231
expect(caffeine).to receive(:loaded_from_api?).once.and_return(true)
@@ -293,7 +293,7 @@
293293

294294
it "uninstalls cask" do
295295
source_caffeine = Cask::CaskLoader.load(path)
296-
expect(Homebrew::API::Cask).to receive(:source_download).twice.and_return(source_caffeine)
296+
expect(Homebrew::API::Cask).to receive(:source_download_cask).twice.and_return(source_caffeine)
297297

298298
caffeine = Cask::CaskLoader.load(path)
299299
expect(caffeine).to receive(:loaded_from_api?).twice.and_return(true)

0 commit comments

Comments
 (0)