Skip to content

Commit ca3a377

Browse files
committed
Pre-stage cask downloads
- Extract cask containers during queued fetches so installs spend less serial time in the staging phase. - Keep receipt writing and artifact installation in the normal install path so installed state does not change early. - Skip queued extraction when unpack dependencies are still missing so dependency installation ordering stays intact.
1 parent e2e4517 commit ca3a377

3 files changed

Lines changed: 109 additions & 2 deletions

File tree

Library/Homebrew/cask/download.rb

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
require "fileutils"
66
require "unpack_strategy"
77
require "cask/cache"
8+
require "cask/caskroom"
89
require "cask/quarantine"
10+
require "cask/utils"
911

1012
module Cask
1113
# A download corresponding to a {Cask}.
@@ -140,6 +142,58 @@ def process_rename_operations(target_dir:)
140142
end
141143
end
142144

145+
sig { returns(Pathname) }
146+
def staged_path_from_download_queue
147+
HOMEBREW_TEMP/"caskroom"/cask.staged_path.relative_path_from(Caskroom.path)
148+
end
149+
150+
sig { returns(Pathname) }
151+
def staged_path_from_download_queue_marker
152+
Pathname("#{staged_path_from_download_queue}.staged")
153+
end
154+
155+
sig { params(command: T.class_of(SystemCommand)).void }
156+
def purge_staged_from_download_queue(command: SystemCommand)
157+
staged_marker = staged_path_from_download_queue_marker
158+
FileUtils.rm(staged_marker) if staged_marker.symlink? || staged_marker.exist?
159+
160+
staged_path = staged_path_from_download_queue
161+
return unless staged_path.exist?
162+
163+
Utils.gain_permissions_remove(staged_path, command:)
164+
staged_path.parent.rmdir_if_possible
165+
end
166+
167+
sig { override.params(download: Pathname, pour: T::Boolean).returns(T::Boolean) }
168+
def stage_from_download_queue?(download, pour:)
169+
return false unless pour
170+
return false if cask.staged_path.exist? || staged_path_from_download_queue_marker.exist?
171+
172+
cask.download ||= download
173+
primary_container.dependencies.all? do |dependency|
174+
case dependency
175+
when Formula
176+
dependency.any_version_installed? && dependency.optlinked?
177+
when Cask
178+
dependency.installed?
179+
end
180+
end
181+
end
182+
183+
sig { override.params(download: Pathname, pour: T::Boolean).void }
184+
def stage_from_download_queue(download, pour:)
185+
return unless stage_from_download_queue?(download, pour:)
186+
187+
purge_staged_from_download_queue
188+
cask.download ||= download
189+
extract_primary_container(to: staged_path_from_download_queue, verbose: false)
190+
process_rename_operations(target_dir: staged_path_from_download_queue)
191+
FileUtils.ln_s(staged_path_from_download_queue, staged_path_from_download_queue_marker)
192+
rescue
193+
purge_staged_from_download_queue
194+
raise
195+
end
196+
143197
sig { override.returns(T::Boolean) }
144198
def downloaded_and_valid?
145199
return false unless super

Library/Homebrew/cask/installer.rb

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,21 @@ def stage
132132

133133
Caskroom.ensure_caskroom_exists
134134

135-
extract_primary_container
136-
process_rename_operations
135+
queued_staged_path = downloader.staged_path_from_download_queue
136+
queued_staged_marker = downloader.staged_path_from_download_queue_marker
137+
if @defer_fetch && queued_staged_marker.exist? && !@cask.staged_path.exist?
138+
@cask.staged_path.dirname.mkpath
139+
FileUtils.mv(queued_staged_path, @cask.staged_path)
140+
FileUtils.rm(queued_staged_marker)
141+
queued_staged_path.parent.rmdir_if_possible
142+
else
143+
downloader.purge_staged_from_download_queue(command: @command) if @defer_fetch
144+
extract_primary_container
145+
process_rename_operations
146+
end
137147
save_caskfile
138148
rescue => e
149+
downloader.purge_staged_from_download_queue(command: @command) if @defer_fetch
139150
purge_versioned_files
140151
raise e
141152
end

Library/Homebrew/test/cask/installer_spec.rb

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,48 @@ class #{Formulary.class_s(dep_name)} < Formula
737737

738738
installer.enqueue_downloads
739739
end
740+
741+
it "stages the main cask download outside Caskroom before install" do
742+
cask = Cask::CaskLoader.load(cask_path("local-caffeine"))
743+
download_queue = Homebrew::DownloadQueue.new(pour: true)
744+
installer = described_class.new(cask, download_queue:, defer_fetch: true)
745+
746+
begin
747+
installer.enqueue_downloads
748+
download_queue.fetch
749+
ensure
750+
download_queue.shutdown
751+
end
752+
753+
expect(cask.staged_path).not_to exist
754+
expect(cask).not_to be_installed
755+
756+
expect(installer).not_to receive(:extract_primary_container)
757+
758+
installer.stage
759+
760+
expect(cask.staged_path/"Caffeine.app").to be_a_directory
761+
expect(cask).to be_installed
762+
end
763+
764+
it "does not stage queued downloads with missing unpack dependencies" do
765+
cask = Cask::CaskLoader.load(cask_path("container-bzip2"))
766+
download_queue = Homebrew::DownloadQueue.new(pour: true)
767+
installer = described_class.new(cask, download_queue:, defer_fetch: true)
768+
769+
allow_any_instance_of(Formula).to receive(:any_version_installed?).and_return(false)
770+
expect(installer).not_to receive(:extract_primary_container)
771+
772+
begin
773+
installer.enqueue_downloads
774+
download_queue.fetch
775+
ensure
776+
download_queue.shutdown
777+
end
778+
779+
expect(cask.staged_path).not_to exist
780+
expect(cask).not_to be_installed
781+
end
740782
end
741783

742784
describe "rename operations" do

0 commit comments

Comments
 (0)