Skip to content

Commit 50b8a98

Browse files
authored
Merge pull request #22426 from p-linnane/fetch-all-platforms
fetch: add `--all-platforms` to fetch every variant
2 parents 04366fc + 4122b54 commit 50b8a98

9 files changed

Lines changed: 113 additions & 29 deletions

File tree

Library/Homebrew/cli/args.rb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,11 @@ def only_formula_or_cask
128128
def os_arch_combinations
129129
skip_invalid_combinations = false
130130

131-
oses = case (os_sym = @table[:os]&.to_sym)
131+
# `--all-platforms` is equivalent to `--os=all --arch=all`.
132+
all_platforms = @table[:all_platforms?]
133+
134+
os_sym = all_platforms ? :all : @table[:os]&.to_sym
135+
oses = case os_sym
132136
when nil
133137
[SimulateSystem.current_os]
134138
when :all
@@ -139,7 +143,8 @@ def os_arch_combinations
139143
[os_sym]
140144
end
141145

142-
arches = case (arch_sym = @table[:arch]&.to_sym)
146+
arch_sym = all_platforms ? :all : @table[:arch]&.to_sym
147+
arches = case arch_sym
143148
when nil
144149
[SimulateSystem.current_arch]
145150
when :all

Library/Homebrew/cmd/fetch.rb

Lines changed: 74 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
require "abstract_command"
55
require "formula"
66
require "fetch"
7+
require "cask/config"
78
require "cask/download"
89
require "download_queue"
910

@@ -25,6 +26,9 @@ class FetchCmd < AbstractCommand
2526
flag "--arch=",
2627
description: "Download for the given CPU architecture. " \
2728
"(Pass `all` to download for all architectures.)"
29+
switch "--all-platforms",
30+
description: "Download for every supported operating system and architecture, plus each " \
31+
"language for <cask>s, fetching each distinct URL once."
2832
flag "--bottle-tag=",
2933
description: "Download a bottle for given tag."
3034
switch "--HEAD",
@@ -65,6 +69,9 @@ class FetchCmd < AbstractCommand
6569
conflicts "--formula", "--cask"
6670
conflicts "--os", "--bottle-tag"
6771
conflicts "--arch", "--bottle-tag"
72+
conflicts "--all-platforms", "--os"
73+
conflicts "--all-platforms", "--arch"
74+
conflicts "--all-platforms", "--bottle-tag"
6875

6976
named_args [:formula, :cask], min: 1
7077
end
@@ -154,30 +161,7 @@ def run
154161
end
155162
end
156163
when Cask::Cask
157-
cask = formula_or_cask
158-
ref = cask.loaded_from_api? ? cask.full_name : cask.sourcefile_path
159-
odie "unexpected nil cask sourcefile_path" unless ref
160-
161-
os_arch_combinations.each do |os, arch|
162-
SimulateSystem.with(os:, arch:) do
163-
cask = Cask::CaskLoader.load(ref)
164-
165-
if cask.url.nil? || cask.sha256.nil?
166-
opoo "Cask #{cask} is not supported on os #{os} and arch #{arch}"
167-
next
168-
end
169-
170-
quarantine = args.quarantine?
171-
quarantine = true if quarantine.nil?
172-
173-
download = Cask::Download.new(
174-
cask,
175-
quarantine:,
176-
require_sha: Homebrew::EnvConfig.cask_opts_require_sha?,
177-
)
178-
download_queue.enqueue(download)
179-
end
180-
end
164+
cask_downloads(formula_or_cask).each { |download| download_queue.enqueue(download) }
181165
else
182166
odie "Invalid formula or cask: #{formula_or_cask}"
183167
end
@@ -190,6 +174,72 @@ def run
190174

191175
private
192176

177+
sig { params(cask: Cask::Cask).returns(T::Array[Cask::Download]) }
178+
def cask_downloads(cask)
179+
ref = cask.loaded_from_api? ? cask.full_name : cask.sourcefile_path
180+
odie "unexpected nil cask sourcefile_path" unless ref
181+
182+
quarantine = args.quarantine?
183+
quarantine = true if quarantine.nil?
184+
185+
if args.all_platforms? && cask.loaded_from_api?
186+
opoo "Cask #{cask} was loaded from the API; cannot fetch all operating system and " \
187+
"architecture variants. Set `HOMEBREW_NO_INSTALL_FROM_API=1` to fetch them all."
188+
end
189+
190+
# With `--all-platforms`, a cask without `on_system` blocks resolves
191+
# identically everywhere, so one combination covers the whole matrix.
192+
cask_combinations = args.os_arch_combinations
193+
cask_combinations = cask_combinations.first(1) if args.all_platforms? && !cask.on_system_blocks_exist?
194+
195+
downloads = T.let([], T::Array[Cask::Download])
196+
enqueued_urls = Set.new
197+
198+
cask_combinations.each do |os, arch|
199+
SimulateSystem.with(os:, arch:) do
200+
loaded_cask = begin
201+
Cask::CaskLoader.load(ref)
202+
rescue Cask::CaskInvalidError, Cask::CaskUnreadableError
203+
raise unless cask.on_system_blocks_exist?
204+
end
205+
if loaded_cask.nil?
206+
opoo "Cask #{cask} is not supported on os #{os} and arch #{arch}"
207+
next
208+
end
209+
210+
languages = (loaded_cask.languages if args.all_platforms?)
211+
languages = [nil] if languages.blank?
212+
213+
languages.each do |language|
214+
localized_cask = loaded_cask
215+
if language
216+
# Reload per language: `Cask::Download` reads `sha256`/`url`
217+
# lazily, so each download needs its own cask instance.
218+
localized_cask = Cask::CaskLoader.load(ref)
219+
localized_cask.config = localized_cask.config.merge(
220+
Cask::Config.new(explicit: { languages: [language] }),
221+
)
222+
end
223+
224+
if localized_cask.url.nil? || localized_cask.sha256.nil?
225+
opoo "Cask #{cask} is not supported on os #{os} and arch #{arch}"
226+
next
227+
end
228+
229+
next unless enqueued_urls.add?(localized_cask.url.to_s)
230+
231+
downloads << Cask::Download.new(
232+
localized_cask,
233+
quarantine:,
234+
require_sha: Homebrew::EnvConfig.cask_opts_require_sha?,
235+
)
236+
end
237+
end
238+
end
239+
240+
downloads
241+
end
242+
193243
sig { returns(Integer) }
194244
def retries
195245
@retries ||= T.let(args.retry? ? FETCH_MAX_TRIES : 1, T.nilable(Integer))

Library/Homebrew/sorbet/rbi/dsl/homebrew/cmd/fetch_cmd.rbi

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Library/Homebrew/test/cmd/fetch_spec.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,19 @@
1919
expect(HOMEBREW_CACHE/"testball2--0.1.tbz").to exist
2020
expect((HOMEBREW_CACHE/"downloads").glob("*--caffeine.zip")).not_to be_empty
2121
end
22+
23+
describe "#cask_downloads", :cask do
24+
it "collects one download per distinct URL across all platforms" do
25+
cmd = Homebrew::Cmd::FetchCmd.new(["--cask", "--all-platforms", "sha256-os"])
26+
basenames = cmd.send(:cask_downloads, Cask::CaskLoader.load("sha256-os"))
27+
.map { |download| File.basename(download.url.to_s) }
28+
expect(basenames).to contain_exactly("caffeine-arm-darwin.zip", "caffeine-intel-darwin.zip",
29+
"caffeine-arm-linux.zip", "caffeine-intel-linux.zip")
30+
end
31+
32+
it "collapses to a single download for a cask without on_system blocks" do
33+
cmd = Homebrew::Cmd::FetchCmd.new(["--cask", "--all-platforms", "local-caffeine"])
34+
expect(cmd.send(:cask_downloads, Cask::CaskLoader.load("local-caffeine")).length).to eq(1)
35+
end
36+
end
2237
end

completions/bash/brew

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1453,6 +1453,7 @@ _brew_fetch() {
14531453
-*)
14541454
__brewcomp "
14551455
--HEAD
1456+
--all-platforms
14561457
--arch
14571458
--bottle-tag
14581459
--build-bottle

completions/fish/brew.fish

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -974,6 +974,7 @@ __fish_brew_complete_arg 'extract' -a '(__fish_brew_suggest_taps_installed)'
974974

975975
__fish_brew_complete_cmd 'fetch' 'Download a bottle (if available) or source packages for formulae and binaries for casks'
976976
__fish_brew_complete_arg 'fetch' -l HEAD -d 'Fetch HEAD version instead of stable version'
977+
__fish_brew_complete_arg 'fetch' -l all-platforms -d 'Download for every supported operating system and architecture, plus each language for casks, fetching each distinct URL once'
977978
__fish_brew_complete_arg 'fetch' -l arch -d 'Download for the given CPU architecture. (Pass `all` to download for all architectures.)'
978979
__fish_brew_complete_arg 'fetch' -l bottle-tag -d 'Download a bottle for given tag'
979980
__fish_brew_complete_arg 'fetch' -l build-bottle -d 'Download source packages (for eventual bottling) rather than a bottle'

completions/zsh/_brew

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1274,16 +1274,17 @@ _brew_extract() {
12741274
_brew_fetch() {
12751275
_arguments \
12761276
'(--cask)--HEAD[Fetch HEAD version instead of stable version]' \
1277-
'(--bottle-tag)--arch[Download for the given CPU architecture. (Pass `all` to download for all architectures.)]' \
1278-
'(--build-from-source --build-bottle --force-bottle --cask --os --arch)--bottle-tag[Download a bottle for given tag]' \
1277+
'(--os --arch --bottle-tag)--all-platforms[Download for every supported operating system and architecture, plus each language for casks, fetching each distinct URL once]' \
1278+
'(--bottle-tag --all-platforms)--arch[Download for the given CPU architecture. (Pass `all` to download for all architectures.)]' \
1279+
'(--build-from-source --build-bottle --force-bottle --cask --os --arch --all-platforms)--bottle-tag[Download a bottle for given tag]' \
12791280
'(--build-from-source --force-bottle --bottle-tag --cask)--build-bottle[Download source packages (for eventual bottling) rather than a bottle]' \
12801281
'(--build-bottle --force-bottle --bottle-tag)--build-from-source[Download source packages rather than a bottle]' \
12811282
'--debug[Display any debugging information]' \
12821283
'(--cask)--deps[Also download dependencies for any listed formula]' \
12831284
'--force[Remove a previously cached version and re-fetch]' \
12841285
'(--build-from-source --build-bottle --bottle-tag --cask)--force-bottle[Download a bottle if it exists for the current or newest version of macOS, even if it would not be used during installation]' \
12851286
'--help[Show this message]' \
1286-
'(--bottle-tag)--os[Download for the given operating system. (Pass `all` to download for all operating systems.)]' \
1287+
'(--bottle-tag --all-platforms)--os[Download for the given operating system. (Pass `all` to download for all operating systems.)]' \
12871288
'--quiet[Make some output more quiet]' \
12881289
'--retry[Retry if downloading fails or re-download if the checksum of a previously cached version no longer matches. Tries at most 5 times with exponential backoff]' \
12891290
'--verbose[Do a verbose VCS checkout, if the URL represents a VCS. This is useful for seeing if an existing VCS cache has been updated]' \

docs/Manpage.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -993,6 +993,11 @@ binaries for *`cask`*s. For files, also print SHA-256 checksums.
993993
: Download for the given CPU architecture. (Pass `all` to download for all
994994
architectures.)
995995

996+
`--all-platforms`
997+
998+
: Download for every supported operating system and architecture, plus each
999+
language for *`cask`*s, fetching each distinct URL once.
1000+
9961001
`--bottle-tag`
9971002

9981003
: Download a bottle for given tag.

manpages/brew.1

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,9 @@ Download for the given operating system\. (Pass \fBall\fP to download for all op
650650
\fB\-\-arch\fP
651651
Download for the given CPU architecture\. (Pass \fBall\fP to download for all architectures\.)
652652
.TP
653+
\fB\-\-all\-platforms\fP
654+
Download for every supported operating system and architecture, plus each language for \fIcask\fPs, fetching each distinct URL once\.
655+
.TP
653656
\fB\-\-bottle\-tag\fP
654657
Download a bottle for given tag\.
655658
.TP

0 commit comments

Comments
 (0)