Skip to content

Commit fb5a423

Browse files
committed
Run brew fetch before brew install
This will parallelise fetching of formulae and casks in `brew bundle`.
1 parent 2bf3eaf commit fb5a423

File tree

3 files changed

+93
-3
lines changed

3 files changed

+93
-3
lines changed

Library/Homebrew/bundle/cask_installer.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ def self.install!(name, preinstall: true, no_upgrade: false, verbose: false, for
7474
result
7575
end
7676

77+
def self.installable_or_upgradable?(name, no_upgrade: false, **options)
78+
!cask_installed?(name) || upgrading?(no_upgrade, name, options)
79+
end
80+
7781
private_class_method def self.postinstall_change_state!(name:, options:, verbose:)
7882
postinstall = options.fetch(:postinstall, nil)
7983
return true if postinstall.blank?

Library/Homebrew/bundle/installer.rb

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ def self.install!(entries, global: false, file: nil, no_lock: false, no_upgrade:
2020
success = 0
2121
failure = 0
2222

23-
entries.each do |entry|
23+
installable_entries = entries.filter_map do |entry|
24+
next if Homebrew::Bundle::Skipper.skip? entry
25+
2426
name = entry.name
2527
args = [name]
2628
options = {}
@@ -52,9 +54,26 @@ def self.install!(entries, global: false, file: nil, no_lock: false, no_upgrade:
5254
options = entry.options
5355
Homebrew::Bundle::TapInstaller
5456
end
55-
5657
next if cls.nil?
57-
next if Homebrew::Bundle::Skipper.skip? entry
58+
59+
{ name:, args:, options:, verb:, type:, cls: }
60+
end
61+
62+
if (fetchable_names = fetchable_formulae_and_casks(installable_entries, no_upgrade:).presence)
63+
fetchable_names_joined = fetchable_names.join(", ")
64+
Formatter.success("Fetching #{fetchable_names_joined}") unless quiet
65+
unless Bundle.brew("fetch", *fetchable_names, verbose:)
66+
$stderr.puts Formatter.error "`brew bundle` failed! Failed to fetch #{fetchable_names_joined}"
67+
return false
68+
end
69+
end
70+
71+
installable_entries.each do |entry|
72+
name = entry.fetch(:name)
73+
args = entry.fetch(:args)
74+
options = entry.fetch(:options)
75+
verb = entry.fetch(:verb)
76+
cls = entry.fetch(:cls)
5877

5978
preinstall = if cls.preinstall!(*args, **options, no_upgrade:, verbose:)
6079
puts Formatter.success("#{verb} #{name}")
@@ -86,6 +105,24 @@ def self.install!(entries, global: false, file: nil, no_lock: false, no_upgrade:
86105

87106
true
88107
end
108+
109+
def self.fetchable_formulae_and_casks(entries, no_upgrade:)
110+
entries.filter_map do |entry|
111+
name = entry.fetch(:name)
112+
options = entry.fetch(:options)
113+
114+
case entry.fetch(:type)
115+
when :brew
116+
next if Homebrew::Bundle::FormulaInstaller.formula_installed_and_up_to_date?(name, no_upgrade:)
117+
118+
name
119+
when :cask
120+
next unless Homebrew::Bundle::CaskInstaller.installable_or_upgradable?(name, no_upgrade:, **options)
121+
122+
options.fetch(:full_name, name)
123+
end
124+
end
125+
end
89126
end
90127
end
91128
end
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# frozen_string_literal: true
2+
3+
require "bundle"
4+
require "bundle/dsl"
5+
require "bundle/installer"
6+
7+
RSpec.describe Homebrew::Bundle::Installer do
8+
let(:formula_entry) { Homebrew::Bundle::Dsl::Entry.new(:brew, "mysql") }
9+
let(:cask_options) { { args: {}, full_name: "homebrew/cask/google-chrome" } }
10+
let(:cask_entry) { Homebrew::Bundle::Dsl::Entry.new(:cask, "google-chrome", cask_options) }
11+
12+
before do
13+
allow(Homebrew::Bundle::Skipper).to receive(:skip?).and_return(false)
14+
allow(Homebrew::Bundle::FormulaInstaller).to receive_messages(formula_upgradable?: false, install!: true)
15+
allow(Homebrew::Bundle::CaskInstaller).to receive_messages(cask_upgradable?: false, install!: true)
16+
end
17+
18+
it "prefetches installable formulae and casks before installing" do
19+
allow(Homebrew::Bundle::FormulaInstaller).to receive(:formula_installed_and_up_to_date?)
20+
.with("mysql", no_upgrade: false).and_return(false)
21+
allow(Homebrew::Bundle::CaskInstaller).to receive(:installable_or_upgradable?)
22+
.with("google-chrome", no_upgrade: false, **cask_options).and_return(true)
23+
24+
expect(Homebrew::Bundle).to receive(:brew)
25+
.with("fetch", "mysql", "homebrew/cask/google-chrome", verbose: false)
26+
.ordered
27+
.and_return(true)
28+
expect(Homebrew::Bundle::FormulaInstaller).to receive(:preinstall!)
29+
.with("mysql", no_upgrade: false, verbose: false)
30+
.ordered
31+
.and_return(true)
32+
expect(Homebrew::Bundle::CaskInstaller).to receive(:preinstall!)
33+
.with("google-chrome", **cask_options, no_upgrade: false, verbose: false)
34+
.ordered
35+
.and_return(true)
36+
37+
described_class.install!([formula_entry, cask_entry], verbose: false, force: false, quiet: true)
38+
end
39+
40+
it "skips fetching when no formulae or casks need installation or upgrade" do
41+
allow(Homebrew::Bundle::FormulaInstaller).to receive(:formula_installed_and_up_to_date?)
42+
.with("mysql", no_upgrade: true).and_return(true)
43+
allow(Homebrew::Bundle::FormulaInstaller).to receive(:preinstall!).and_return(false)
44+
45+
expect(Homebrew::Bundle).not_to receive(:brew).with("fetch", any_args)
46+
47+
described_class.install!([formula_entry], no_upgrade: true, quiet: true)
48+
end
49+
end

0 commit comments

Comments
 (0)