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
203
204
205
206
207
208
209
210
211
212
213
214
215
|
require "download_strategy"
require "checksum"
require "version"
# Resource is the fundamental representation of an external resource. The
# primary formula download, along with other declared resources, are instances
# of this class.
class Resource
include FileUtils
attr_reader :mirrors, :specs, :using, :source_modified_time
attr_writer :version
attr_accessor :download_strategy, :checksum
# Formula name must be set after the DSL, as we have no access to the
# formula name before initialization of the formula
attr_accessor :name, :owner
class Download
def initialize(resource)
@resource = resource
end
def url
@resource.url
end
def specs
@resource.specs
end
def version
@resource.version
end
def mirrors
@resource.mirrors
end
end
def initialize(name = nil, &block)
@name = name
@url = nil
@version = nil
@mirrors = []
@specs = {}
@checksum = nil
@using = nil
instance_eval(&block) if block_given?
end
def downloader
download_strategy.new(download_name, Download.new(self))
end
# Removes /s from resource names; this allows go package names
# to be used as resource names without confusing software that
# interacts with download_name, e.g. github.com/foo/bar
def escaped_name
name.tr("/", "-")
end
def download_name
name.nil? ? owner.name : "#{owner.name}--#{escaped_name}"
end
def cached_download
downloader.cached_location
end
def clear_cache
downloader.clear_cache
end
# Verifies download and unpacks it
# The block may call `|resource,staging| staging.retain!` to retain the staging
# directory. Subclasses that override stage should implement the tmp
# dir using FileUtils.mktemp so that works with all subtypes.
def stage(target = nil, &block)
unless target || block
raise ArgumentError, "target directory or block is required"
end
verify_download_integrity(fetch)
unpack(target, &block)
end
# If a target is given, unpack there; else unpack to a temp folder.
# If block is given, yield to that block with |stage|, where stage
# is a ResourceStagingContext.
# A target or a block must be given, but not both.
def unpack(target = nil)
mktemp(download_name) do |staging|
downloader.stage
@source_modified_time = downloader.source_modified_time
if block_given?
yield ResourceStageContext.new(self, staging)
elsif target
target = Pathname.new(target) unless target.is_a? Pathname
target.install Pathname.pwd.children
end
end
end
Partial = Struct.new(:resource, :files)
def files(*files)
Partial.new(self, files)
end
def fetch
HOMEBREW_CACHE.mkpath
begin
downloader.fetch
rescue ErrorDuringExecution, CurlDownloadStrategyError => e
raise DownloadError.new(self, e)
end
cached_download
end
def verify_download_integrity(fn)
if fn.file?
ohai "Verifying #{fn.basename} checksum" if ARGV.verbose?
fn.verify_checksum(checksum)
end
rescue ChecksumMissingError
opoo "Cannot verify integrity of #{fn.basename}"
puts "A checksum was not provided for this resource"
puts "For your reference the SHA256 is: #{fn.sha256}"
end
Checksum::TYPES.each do |type|
define_method(type) { |val| @checksum = Checksum.new(type, val) }
end
def url(val = nil, specs = {})
return @url if val.nil?
@url = val
@specs.merge!(specs)
@using = @specs.delete(:using)
@download_strategy = DownloadStrategyDetector.detect(url, using)
end
def version(val = nil)
@version ||= begin
version = detect_version(val)
version.null? ? nil : version
end
end
def mirror(val)
mirrors << val
end
private
def detect_version(val)
return Version::NULL if val.nil? && url.nil?
case val
when nil then Version.detect(url, specs)
when String then Version.create(val)
when Version then val
else
raise TypeError, "version '#{val.inspect}' should be a string"
end
end
class Go < Resource
def stage(target)
super(target/name)
end
end
class Patch < Resource
attr_reader :patch_files
def initialize(&block)
@patch_files = []
super "patch", &block
end
def apply(*paths)
paths.flatten!
@patch_files.concat(paths)
@patch_files.uniq!
end
end
end
# The context in which a Resource.stage() occurs. Supports access to both
# the Resource and associated Mktemp in a single block argument. The interface
# is back-compatible with Resource itself as used in that context.
class ResourceStageContext
extend Forwardable
# The Resource that is being staged
attr_reader :resource
# The Mktemp in which @resource is staged
attr_reader :staging
def_delegators :@resource, :version, :url, :mirrors, :specs, :using, :source_modified_time
def_delegators :@staging, :retain!
def initialize(resource, staging)
@resource = resource
@staging = staging
end
def to_s
"<#{self.class}: resource=#{resource} staging=#{staging}>"
end
end
|