forked from Homebrew/brew
-
Notifications
You must be signed in to change notification settings - Fork 0
/
fetch.rb
202 lines (168 loc) 路 6.09 KB
/
fetch.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# typed: false
# frozen_string_literal: true
require "formula"
require "fetch"
require "cli/parser"
require "cask/download"
module Homebrew
extend T::Sig
extend Fetch
module_function
FETCH_MAX_TRIES = 5
sig { returns(CLI::Parser) }
def fetch_args
Homebrew::CLI::Parser.new do
description <<~EOS
Download a bottle (if available) or source packages for <formula>e
and binaries for <cask>s. For files, also print SHA-256 checksums.
EOS
flag "--bottle-tag=",
description: "Download a bottle for given tag."
switch "--HEAD",
description: "Fetch HEAD version instead of stable version."
switch "-f", "--force",
description: "Remove a previously cached version and re-fetch."
switch "-v", "--verbose",
description: "Do a verbose VCS checkout, if the URL represents a VCS. This is useful for " \
"seeing if an existing VCS cache has been updated."
switch "--retry",
description: "Retry if downloading fails or re-download if the checksum of a previously cached " \
"version no longer matches. Tries at most #{FETCH_MAX_TRIES} times with " \
"exponential backoff."
switch "--deps",
description: "Also download dependencies for any listed <formula>."
switch "-s", "--build-from-source",
description: "Download source packages rather than a bottle."
switch "--build-bottle",
description: "Download source packages (for eventual bottling) rather than a bottle."
switch "--force-bottle",
description: "Download a bottle if it exists for the current or newest version of macOS, " \
"even if it would not be used during installation."
switch "--[no-]quarantine",
description: "Disable/enable quarantining of downloads (default: enabled).",
env: :cask_opts_quarantine
switch "--formula", "--formulae",
description: "Treat all named arguments as formulae."
switch "--cask", "--casks",
description: "Treat all named arguments as casks."
conflicts "--build-from-source", "--build-bottle", "--force-bottle", "--bottle-tag"
conflicts "--cask", "--HEAD"
conflicts "--cask", "--deps"
conflicts "--cask", "-s"
conflicts "--cask", "--build-bottle"
conflicts "--cask", "--force-bottle"
conflicts "--cask", "--bottle-tag"
conflicts "--formula", "--cask"
named_args [:formula, :cask], min: 1
end
end
def fetch
args = fetch_args.parse
bucket = if args.deps?
args.named.to_formulae_and_casks.flat_map do |formula_or_cask|
case formula_or_cask
when Formula
f = formula_or_cask
[f, *f.recursive_dependencies.map(&:to_formula)]
else
formula_or_cask
end
end
else
args.named.to_formulae_and_casks
end.uniq
puts "Fetching: #{bucket * ", "}" if bucket.size > 1
bucket.each do |formula_or_cask|
case formula_or_cask
when Formula
f = formula_or_cask
f.print_tap_action verb: "Fetching"
fetched_bottle = false
if fetch_bottle?(f, args: args)
begin
f.clear_cache if args.force?
f.fetch_bottle_tab
fetch_formula(f.bottle_for_tag(args.bottle_tag&.to_sym), args: args)
rescue Interrupt
raise
rescue => e
raise if Homebrew::EnvConfig.developer?
fetched_bottle = false
onoe e.message
opoo "Bottle fetch failed, fetching the source instead."
else
fetched_bottle = true
end
end
next if fetched_bottle
fetch_formula(f, args: args)
f.resources.each do |r|
fetch_resource(r, args: args)
r.patches.each { |p| fetch_patch(p, args: args) if p.external? }
end
f.patchlist.each { |p| fetch_patch(p, args: args) if p.external? }
else
cask = formula_or_cask
quarantine = args.quarantine?
quarantine = true if quarantine.nil?
download = Cask::Download.new(cask, quarantine: quarantine)
fetch_cask(download, args: args)
end
end
end
def fetch_resource(r, args:)
puts "Resource: #{r.name}"
fetch_fetchable r, args: args
rescue ChecksumMismatchError => e
retry if retry_fetch?(r, args: args)
opoo "Resource #{r.name} reports different sha256: #{e.expected}"
end
def fetch_formula(f, args:)
fetch_fetchable f, args: args
rescue ChecksumMismatchError => e
retry if retry_fetch?(f, args: args)
opoo "Formula reports different sha256: #{e.expected}"
end
def fetch_cask(cask_download, args:)
fetch_fetchable cask_download, args: args
rescue ChecksumMismatchError => e
retry if retry_fetch?(cask_download, args: args)
opoo "Cask reports different sha256: #{e.expected}"
end
def fetch_patch(p, args:)
fetch_fetchable p, args: args
rescue ChecksumMismatchError => e
opoo "Patch reports different sha256: #{e.expected}"
Homebrew.failed = true
end
def retry_fetch?(f, args:)
@fetch_tries ||= Hash.new { |h, k| h[k] = 1 }
if args.retry? && (@fetch_tries[f] < FETCH_MAX_TRIES)
wait = 2 ** @fetch_tries[f]
remaining = FETCH_MAX_TRIES - @fetch_tries[f]
what = Utils.pluralize("tr", remaining, plural: "ies", singular: "y")
ohai "Retrying download in #{wait}s... (#{remaining} #{what} left)"
sleep wait
f.clear_cache
@fetch_tries[f] += 1
true
else
Homebrew.failed = true
false
end
end
def fetch_fetchable(f, args:)
f.clear_cache if args.force?
already_fetched = f.cached_download.exist?
begin
download = f.fetch(verify_download_integrity: false)
rescue DownloadError
retry if retry_fetch?(f, args: args)
raise
end
return unless download.file?
puts "Downloaded to: #{download}" unless already_fetched
puts "SHA256: #{download.sha256}"
f.verify_download_integrity(download)
end
end