omgf 0.0.0.GIT
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +4 -0
- data/.gitignore +14 -0
- data/.wrongdoc.yml +6 -0
- data/COPYING +661 -0
- data/GIT-VERSION-GEN +40 -0
- data/GNUmakefile +5 -0
- data/README +69 -0
- data/examples/hyst.README +90 -0
- data/examples/hyst.bash +365 -0
- data/lib/omgf.rb +6 -0
- data/lib/omgf/hysterical_raisins.rb +336 -0
- data/lib/omgf/pool.rb +48 -0
- data/omgf.gemspec +26 -0
- data/pkg.mk +175 -0
- data/test/integration.rb +201 -0
- data/test/test_hyst.rb +78 -0
- data/test/test_hysterical_raisins.rb +238 -0
- metadata +134 -0
data/lib/omgf.rb
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
# :stopdoc:
|
|
3
|
+
# Copyright (C) 2008-2012, Eric Wong <normalperson@yhbt.net>
|
|
4
|
+
# License: AGPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt)
|
|
5
|
+
# :startdoc:
|
|
6
|
+
require "omgf/pool"
|
|
7
|
+
require "time"
|
|
8
|
+
require "json"
|
|
9
|
+
require "mogilefs"
|
|
10
|
+
require "rack"
|
|
11
|
+
require "rack/utils"
|
|
12
|
+
require "rack/mime"
|
|
13
|
+
|
|
14
|
+
# Basic REST interface. This is bug-for-bug compatible with an old app
|
|
15
|
+
# that's been deployed for years in a private LAN.
|
|
16
|
+
#
|
|
17
|
+
# This started out as a WORM (write-once, read-many) system, but
|
|
18
|
+
# eventually gained the ability to handle deletes.
|
|
19
|
+
#
|
|
20
|
+
# This was meant to behave like the MogileFS protocol somewhat (but is
|
|
21
|
+
# pure-HTTP), so it redirects to the storage nodes and bypasses Ruby
|
|
22
|
+
# for bulk I/O when retrieving files. PUTs (writes) still go through
|
|
23
|
+
# Ruby, however.
|
|
24
|
+
class OMGF::HystericalRaisins
|
|
25
|
+
include OMGF::Pool
|
|
26
|
+
|
|
27
|
+
def initialize(opts)
|
|
28
|
+
@default_class_cb = opts[:default_class_cb] || {}
|
|
29
|
+
mg_opts = {
|
|
30
|
+
domain: "any",
|
|
31
|
+
hosts: opts[:hosts],
|
|
32
|
+
fail_timeout: opts[:fail_timeout] || 0.5,
|
|
33
|
+
|
|
34
|
+
# high defaults because of slow seeks on storage nodes (for size verify)
|
|
35
|
+
timeout: opts[:timeout] || 30,
|
|
36
|
+
get_file_data_timeout: opts[:get_file_data_timeout] || 30,
|
|
37
|
+
}
|
|
38
|
+
@reproxy_header = opts[:reproxy_header] || "HTTP_X_OMGF_REPROXY"
|
|
39
|
+
@reproxy_path = opts[:reproxy_path]
|
|
40
|
+
@get_paths_opts = {
|
|
41
|
+
noverify: opts[:noverify],
|
|
42
|
+
pathcount: opts[:pathcount] || 0x7fffffff,
|
|
43
|
+
}
|
|
44
|
+
@put_overwrite_header = opts[:put_overwrite_header] || "HTTP_X_OMGF_FORCE"
|
|
45
|
+
pool_init(mg_opts)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# The entry point for Rack
|
|
49
|
+
def call(env)
|
|
50
|
+
case env["REQUEST_METHOD"]
|
|
51
|
+
when "GET"
|
|
52
|
+
get(env)
|
|
53
|
+
when "HEAD"
|
|
54
|
+
head(env)
|
|
55
|
+
when "PUT"
|
|
56
|
+
put(env)
|
|
57
|
+
when "DELETE"
|
|
58
|
+
delete(env)
|
|
59
|
+
else
|
|
60
|
+
r(405)
|
|
61
|
+
end
|
|
62
|
+
rescue MogileFS::Backend::UnknownKeyError,
|
|
63
|
+
MogileFS::Backend::DomainNotFoundError
|
|
64
|
+
r(404, "")
|
|
65
|
+
rescue => e
|
|
66
|
+
logger = env["rack.logger"]
|
|
67
|
+
logger.error "#{e.message} (#{e.class})"
|
|
68
|
+
e.backtrace.each { |line| logger.error(line) }
|
|
69
|
+
r(500, "")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# GET /$DOMAIN?prefix=foo - list keys
|
|
73
|
+
# GET /$DOMAIN/$KEY - redirects to FIDs on storage nodes
|
|
74
|
+
def get(env)
|
|
75
|
+
case env["PATH_INFO"].squeeze("/")
|
|
76
|
+
when %r{\A/([^/]+)/(.+)\z} # /$DOMAIN/$KEY
|
|
77
|
+
redirect_key(env, $1, $2)
|
|
78
|
+
when %r{\A/([^/]+)/?\z} # /$DOMAIN
|
|
79
|
+
get_keys(env, $1)
|
|
80
|
+
when "/"
|
|
81
|
+
r(200, "")
|
|
82
|
+
else
|
|
83
|
+
r(404, "")
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# returns metadata for a given domain/key
|
|
88
|
+
def head(env)
|
|
89
|
+
case env["PATH_INFO"].squeeze("/")
|
|
90
|
+
when %r{\A/([^/]+)/(.+)\z} # HEAD /$DOMAIN/$KEY
|
|
91
|
+
stat_key(env, $1, $2)
|
|
92
|
+
else
|
|
93
|
+
# pass on headers from listing results
|
|
94
|
+
status, headers, _ = get(env)
|
|
95
|
+
[ status, headers, [] ]
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# HEAD /$DOMAIN/$KEY
|
|
100
|
+
def stat_key(env, domain, key)
|
|
101
|
+
size, paths = mg_size_and_paths(domain, key, @get_paths_opts)
|
|
102
|
+
return r(503, "") unless paths && location = paths[0]
|
|
103
|
+
h = { "Content-Length" => size.to_s }
|
|
104
|
+
fn = filename(h, query(env)) || key
|
|
105
|
+
h["Content-Type"] = key_mime_type(fn)
|
|
106
|
+
unless reproxy?(env, key, h, location)
|
|
107
|
+
paths.each_with_index { |path,i| h["X-URL-#{i}"] = path }
|
|
108
|
+
end
|
|
109
|
+
[ 200, h, [] ]
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# GET /$DOMAIN/$KEY
|
|
113
|
+
def redirect_key(env, domain, key)
|
|
114
|
+
uris = mg_get_uris(domain, key, @get_paths_opts)
|
|
115
|
+
|
|
116
|
+
return r(503, "") unless uris && dest = uris.shift
|
|
117
|
+
|
|
118
|
+
location = dest.to_s
|
|
119
|
+
h = {
|
|
120
|
+
'Content-Length' => '0',
|
|
121
|
+
'Location' => location,
|
|
122
|
+
'Content-Type' => 'text/html'
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
unless reproxy?(env, key, h, location)
|
|
126
|
+
uris -= [ dest ]
|
|
127
|
+
uris.each_with_index { |uri,i| h["X-Alt-Location-#{i}"] = uri.to_s }
|
|
128
|
+
end
|
|
129
|
+
[ 302, h, [] ]
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# GET /$DOMAIN/
|
|
133
|
+
def get_keys(env, domain) # :nodoc:
|
|
134
|
+
params = query(env)
|
|
135
|
+
prefix = params["prefix"] || ""
|
|
136
|
+
after = params["after"]
|
|
137
|
+
limit = (params["limit"] || 1000).to_i
|
|
138
|
+
|
|
139
|
+
case env["HTTP_ACCEPT"]
|
|
140
|
+
when "application/json"
|
|
141
|
+
tmp = []
|
|
142
|
+
h = { "Content-Type" => "application/json" }
|
|
143
|
+
mg_list_keys(domain, prefix, after, limit) { |*x| tmp << x }
|
|
144
|
+
tmp = tmp.to_json
|
|
145
|
+
else
|
|
146
|
+
tmp = ""
|
|
147
|
+
h = { "Content-Type" => "text/plain" }
|
|
148
|
+
mg_list_keys(domain, prefix, after, limit) do |dkey,length,devcount|
|
|
149
|
+
tmp << "#{dkey}|#{length}|#{devcount}\n"
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
h["Content-Length"] = tmp.size.to_s
|
|
153
|
+
|
|
154
|
+
[ 200, h, [ tmp ] ]
|
|
155
|
+
rescue MogileFS::Backend::UnregDomainError,
|
|
156
|
+
MogileFS::Backend::DomainNotFoundError
|
|
157
|
+
r(404, "Domain not found")
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# returns a plain-text HTTP response
|
|
161
|
+
def r(code, msg = nil, env = nil) # :nodoc:
|
|
162
|
+
if env && logger = env["rack.logger"]
|
|
163
|
+
logger.warn("#{env['REQUEST_METHOD']} #{env['PATH_INFO']} " \
|
|
164
|
+
"#{code} #{msg.inspect}")
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(code)
|
|
168
|
+
[ code, {}, [] ]
|
|
169
|
+
else
|
|
170
|
+
msg ||= Rack::Utils::HTTP_STATUS_CODES[code] || ""
|
|
171
|
+
|
|
172
|
+
if msg.size > 0
|
|
173
|
+
# using += to not modify original string (owned by Rack)
|
|
174
|
+
msg += "\n"
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
[ code,
|
|
178
|
+
{ 'Content-Type' => 'text/plain', 'Content-Length' => msg.size.to_s },
|
|
179
|
+
[ msg ] ]
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# returns the mime type based on key name
|
|
184
|
+
def key_mime_type(key) # :nodoc:
|
|
185
|
+
/\.([^.]+)\z/ =~ key
|
|
186
|
+
Rack::Mime.mime_type($1) # nil => 'application/octet-stream'
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# sets Content-Disposition response header if requested by the user
|
|
190
|
+
# in a query string (via "inline" or "attachment" param)
|
|
191
|
+
# Returns requested filename, or nil if invalid
|
|
192
|
+
def filename(h, params) # :nodoc:
|
|
193
|
+
if fn = params['inline'] and Rack::Utils.escape(fn) == fn
|
|
194
|
+
h['Content-Disposition'] = "inline; filename=#{fn}"
|
|
195
|
+
return fn
|
|
196
|
+
elsif fn = (params['attachment'] || params['filename']) and
|
|
197
|
+
Rack::Utils.escape(fn) == fn
|
|
198
|
+
h['Content-Disposition'] = "attachment; filename=#{fn}"
|
|
199
|
+
return fn
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# This relies on nginx to reproxy paths for us
|
|
204
|
+
def reproxy?(env, key, h, location) # :nodoc:
|
|
205
|
+
return false unless @reproxy_path && env[@reproxy_header].to_i != 0
|
|
206
|
+
%r{/0*(\d+)\.fid\z} =~ location
|
|
207
|
+
fid = $1
|
|
208
|
+
h['ETag'] = %("#{fid}")
|
|
209
|
+
fn = filename(h, query(env)) || key
|
|
210
|
+
|
|
211
|
+
case env["REQUEST_METHOD"]
|
|
212
|
+
when "GET"
|
|
213
|
+
# Fake a Last-Modified time (enough to bust caches)
|
|
214
|
+
h['X-Redirect-Last-Modified'] = Time.at(fid.to_i).httpdate
|
|
215
|
+
|
|
216
|
+
# yes we violate Rack::Lint here, no Content-Type or Content-Length
|
|
217
|
+
h.delete("Content-Length")
|
|
218
|
+
h.delete("Content-Type")
|
|
219
|
+
h['X-Redirect-Content-Type'] = key_mime_type(fn)
|
|
220
|
+
h['X-Accel-Redirect'] = @reproxy_path
|
|
221
|
+
when "HEAD"
|
|
222
|
+
# Fake a Last-Modified time (enough to bust caches)
|
|
223
|
+
h['Last-Modified'] = Time.at(fid.to_i).httpdate
|
|
224
|
+
else
|
|
225
|
+
raise "BUG: bad request method #{env["REQUEST_METHOD"]}"
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
true
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# PUT /domain/key
|
|
232
|
+
def put(env)
|
|
233
|
+
case env["PATH_INFO"]
|
|
234
|
+
when %r{\A/([^/]+)/(.+)\z} # /$DOMAIN/$KEY
|
|
235
|
+
put_key(env, $1, $2)
|
|
236
|
+
else
|
|
237
|
+
r(404, "")
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# we're very picky about keys, we only allow keys which won't cause
|
|
242
|
+
# ambiguity when passed through URI escapers/normalizers.
|
|
243
|
+
def bad_key?(key) # :nodoc:
|
|
244
|
+
%r{\A[\w./-]+\z} !~ key ||
|
|
245
|
+
%r{//} =~ key ||
|
|
246
|
+
%r{[./]} =~ "#{key[0]}#{key[-1]}"
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# PUT /$DOMAIN/$KEY
|
|
250
|
+
def put_key(env, domain, key)
|
|
251
|
+
return r(403, "") if env[@reproxy_header].to_i != 0
|
|
252
|
+
return r(406, "key `#{key}' is not URI-friendly") if bad_key?(key)
|
|
253
|
+
return r(406, "key is too long") if key.size > 128
|
|
254
|
+
|
|
255
|
+
# this was written before MogileFS supported empty files,
|
|
256
|
+
# but empty files waste DB space so we don't support them
|
|
257
|
+
# Not bothering with Transfer-Encoding: chunked, though...
|
|
258
|
+
return r(403, "empty files forbidden") if env["CONTENT_LENGTH"] == "0"
|
|
259
|
+
|
|
260
|
+
params = query(env)
|
|
261
|
+
input = env["rack.input"]
|
|
262
|
+
paths = nil
|
|
263
|
+
retried = false
|
|
264
|
+
|
|
265
|
+
begin
|
|
266
|
+
pool_use(domain) do |mg|
|
|
267
|
+
begin
|
|
268
|
+
# TOCTOU issue, but probably not worth worrying about
|
|
269
|
+
# Nothing we can do about it without explicit MogileFS support
|
|
270
|
+
# or a 3rd-party locking daemon
|
|
271
|
+
paths = mg.get_paths(key)
|
|
272
|
+
if paths && paths[0]
|
|
273
|
+
|
|
274
|
+
# overwriting existing files is not permitted by default
|
|
275
|
+
if env[@put_overwrite_header] != "true"
|
|
276
|
+
# show the existing paths in response
|
|
277
|
+
return r(403, paths.join("\n"))
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
rescue MogileFS::Backend::UnknownKeyError
|
|
281
|
+
# good, not clobbering anything
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# the original deployment of this created a class for every
|
|
285
|
+
# domain with the same class having the same name as the domain
|
|
286
|
+
klass = params['class'] || @default_class_cb[domain] || "default"
|
|
287
|
+
mg.store_file(key, klass, input)
|
|
288
|
+
end # pool_use
|
|
289
|
+
|
|
290
|
+
# should always return 201 if ! found, but we keep 200 for legacy
|
|
291
|
+
# compat if they're not logged in (via REMOTE_USER)
|
|
292
|
+
status = paths ? 204 : (env["REMOTE_USER"] ? 201 : 200)
|
|
293
|
+
r(status, "")
|
|
294
|
+
rescue MogileFS::Backend::UnregDomainError,
|
|
295
|
+
MogileFS::Backend::DomainNotFoundError
|
|
296
|
+
r(406, "Invalid domain: #{domain}")
|
|
297
|
+
rescue => e
|
|
298
|
+
if retried == false && input.respond_to?(:rewind)
|
|
299
|
+
begin
|
|
300
|
+
retried = true
|
|
301
|
+
input.rewind # may raise on future perfectly compliant Rack servers
|
|
302
|
+
env["rack.logger"].warn("#{e.message} (#{e.class})")
|
|
303
|
+
retry
|
|
304
|
+
rescue
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
raise
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# DELETE /domain/key
|
|
313
|
+
def delete(env)
|
|
314
|
+
case env["PATH_INFO"]
|
|
315
|
+
when %r{\A/([^/]+)/(.+)\z} # /$DOMAIN/$KEY
|
|
316
|
+
delete_key(env, $1, $2)
|
|
317
|
+
else
|
|
318
|
+
r(404, "")
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# DELETE /domain/key
|
|
323
|
+
def delete_key(env, domain, key)
|
|
324
|
+
pool_use(domain) { |mg| mg.delete(key) }
|
|
325
|
+
r(204, "")
|
|
326
|
+
rescue MogileFS::Backend::UnregDomainError,
|
|
327
|
+
MogileFS::Backend::DomainNotFoundError
|
|
328
|
+
r(406, "Invalid domain: #{domain}")
|
|
329
|
+
rescue MogileFS::Backend::UnknownKeyError
|
|
330
|
+
r(404, "")
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def query(env)
|
|
334
|
+
Rack::Utils.parse_query(env["QUERY_STRING"])
|
|
335
|
+
end
|
|
336
|
+
end
|
data/lib/omgf/pool.rb
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
# Copyright (C) 2008-2012, Eric Wong <normalperson@yhbt.net>
|
|
3
|
+
# License: AGPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt)
|
|
4
|
+
require "thread"
|
|
5
|
+
require "omgf"
|
|
6
|
+
|
|
7
|
+
module OMGF::Pool
|
|
8
|
+
# TODO: backport these improvements to MogileFS::Pool (but break compat?)
|
|
9
|
+
def pool_init(mg_opts)
|
|
10
|
+
@mg_opts = mg_opts
|
|
11
|
+
@pool = []
|
|
12
|
+
@lock = Mutex.new
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def pool_use(domain)
|
|
16
|
+
# Array#pop is faster than Queue#pop since Queue#pop shifts off
|
|
17
|
+
# the array internally, and taking the last element off the end is faster.
|
|
18
|
+
mg = @lock.synchronize { @pool.pop || MogileFS::MogileFS.new(@mg_opts) }
|
|
19
|
+
mg.domain = domain
|
|
20
|
+
rv = yield mg
|
|
21
|
+
@lock.synchronize do
|
|
22
|
+
if @pool.size < 3
|
|
23
|
+
@pool << mg
|
|
24
|
+
mg = nil # prevent shutdown
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
rv
|
|
28
|
+
ensure
|
|
29
|
+
# shutdown if we didn't return to the pool
|
|
30
|
+
if mg
|
|
31
|
+
mg.backend.shutdown rescue nil
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def mg_list_keys(domain, prefix, after, limit, &block) # :nodoc:
|
|
36
|
+
pool_use(domain) { |mg| mg.list_keys(prefix, after, limit, &block) }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def mg_get_uris(domain, key, opts) # :nodoc:
|
|
40
|
+
pool_use(domain) { |mg| mg.get_uris(key, opts) }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def mg_size_and_paths(domain, key, opts) # :nodoc:
|
|
44
|
+
pool_use(domain) do |mg|
|
|
45
|
+
[ mg.size(key), mg.get_paths(key, opts) ]
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
data/omgf.gemspec
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
ENV["VERSION"] or abort "VERSION= must be specified"
|
|
3
|
+
manifest = File.readlines('.manifest').map! { |x| x.chomp! }
|
|
4
|
+
require 'wrongdoc'
|
|
5
|
+
extend Wrongdoc::Gemspec
|
|
6
|
+
name, summary, title = readme_metadata
|
|
7
|
+
|
|
8
|
+
Gem::Specification.new do |s|
|
|
9
|
+
s.name = %q{omgf}
|
|
10
|
+
s.version = ENV["VERSION"].dup
|
|
11
|
+
|
|
12
|
+
s.authors = ["OMGF hackers"]
|
|
13
|
+
s.date = Time.now.utc.strftime('%Y-%m-%d')
|
|
14
|
+
s.description = readme_description
|
|
15
|
+
s.email = %q{omgf@librelist.org}
|
|
16
|
+
s.extra_rdoc_files = extra_rdoc_files(manifest)
|
|
17
|
+
s.files = manifest
|
|
18
|
+
s.homepage = Wrongdoc.config[:rdoc_url]
|
|
19
|
+
s.summary = summary
|
|
20
|
+
s.rdoc_options = rdoc_options
|
|
21
|
+
s.test_files = Dir['test/test_*.rb']
|
|
22
|
+
s.add_dependency('rack', ['~> 1.3'])
|
|
23
|
+
s.add_dependency('mogilefs-client', ['~> 3.1'])
|
|
24
|
+
|
|
25
|
+
s.licenses = %w(AGPLv3+)
|
|
26
|
+
end
|
data/pkg.mk
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
RUBY = ruby
|
|
2
|
+
RAKE = rake
|
|
3
|
+
RSYNC = rsync
|
|
4
|
+
WRONGDOC = wrongdoc
|
|
5
|
+
|
|
6
|
+
GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
|
|
7
|
+
@./GIT-VERSION-GEN
|
|
8
|
+
-include GIT-VERSION-FILE
|
|
9
|
+
-include local.mk
|
|
10
|
+
DLEXT := $(shell $(RUBY) -rrbconfig -e 'puts RbConfig::CONFIG["DLEXT"]')
|
|
11
|
+
RUBY_VERSION := $(shell $(RUBY) -e 'puts RUBY_VERSION')
|
|
12
|
+
RUBY_ENGINE := $(shell $(RUBY) -e 'puts((RUBY_ENGINE rescue "ruby"))')
|
|
13
|
+
lib := lib
|
|
14
|
+
|
|
15
|
+
ifeq ($(shell test -f script/isolate_for_tests && echo t),t)
|
|
16
|
+
isolate_libs := tmp/isolate/$(RUBY_ENGINE)-$(RUBY_VERSION)/isolate.mk
|
|
17
|
+
$(isolate_libs): script/isolate_for_tests
|
|
18
|
+
@$(RUBY) script/isolate_for_tests
|
|
19
|
+
-include $(isolate_libs)
|
|
20
|
+
lib := $(lib):$(ISOLATE_LIBS)
|
|
21
|
+
endif
|
|
22
|
+
|
|
23
|
+
ext := $(firstword $(wildcard ext/*))
|
|
24
|
+
ifneq ($(ext),)
|
|
25
|
+
ext_pfx := tmp/ext/$(RUBY_ENGINE)-$(RUBY_VERSION)
|
|
26
|
+
ext_h := $(wildcard $(ext)/*/*.h $(ext)/*.h)
|
|
27
|
+
ext_src := $(wildcard $(ext)/*.c $(ext_h))
|
|
28
|
+
ext_pfx_src := $(addprefix $(ext_pfx)/,$(ext_src))
|
|
29
|
+
ext_d := $(ext_pfx)/$(ext)/.d
|
|
30
|
+
$(ext)/extconf.rb: $(wildcard $(ext)/*.h)
|
|
31
|
+
@>> $@
|
|
32
|
+
$(ext_d):
|
|
33
|
+
@mkdir -p $(@D)
|
|
34
|
+
@> $@
|
|
35
|
+
$(ext_pfx)/$(ext)/%: $(ext)/% $(ext_d)
|
|
36
|
+
install -m 644 $< $@
|
|
37
|
+
$(ext_pfx)/$(ext)/Makefile: $(ext)/extconf.rb $(ext_d) $(ext_h)
|
|
38
|
+
$(RM) -f $(@D)/*.o
|
|
39
|
+
cd $(@D) && $(RUBY) $(CURDIR)/$(ext)/extconf.rb
|
|
40
|
+
ext_sfx := _ext.$(DLEXT)
|
|
41
|
+
ext_dl := $(ext_pfx)/$(ext)/$(notdir $(ext)_ext.$(DLEXT))
|
|
42
|
+
$(ext_dl): $(ext_src) $(ext_pfx_src) $(ext_pfx)/$(ext)/Makefile
|
|
43
|
+
@echo $^ == $@
|
|
44
|
+
$(MAKE) -C $(@D)
|
|
45
|
+
lib := $(lib):$(ext_pfx)/$(ext)
|
|
46
|
+
build: $(ext_dl)
|
|
47
|
+
else
|
|
48
|
+
build:
|
|
49
|
+
endif
|
|
50
|
+
|
|
51
|
+
pkg_extra += GIT-VERSION-FILE NEWS ChangeLog LATEST
|
|
52
|
+
ChangeLog: GIT-VERSION-FILE .wrongdoc.yml
|
|
53
|
+
$(WRONGDOC) prepare
|
|
54
|
+
NEWS LATEST: ChangeLog
|
|
55
|
+
|
|
56
|
+
manifest:
|
|
57
|
+
$(RM) .manifest
|
|
58
|
+
$(MAKE) .manifest
|
|
59
|
+
|
|
60
|
+
.manifest: $(pkg_extra)
|
|
61
|
+
(git ls-files && for i in $@ $(pkg_extra); do echo $$i; done) | \
|
|
62
|
+
LC_ALL=C sort > $@+
|
|
63
|
+
cmp $@+ $@ || mv $@+ $@
|
|
64
|
+
$(RM) $@+
|
|
65
|
+
|
|
66
|
+
doc:: .document .wrongdoc.yml $(pkg_extra)
|
|
67
|
+
-find lib -type f -name '*.rbc' -exec rm -f '{}' ';'
|
|
68
|
+
-find ext -type f -name '*.rbc' -exec rm -f '{}' ';'
|
|
69
|
+
$(RM) -r doc
|
|
70
|
+
$(WRONGDOC) all
|
|
71
|
+
install -m644 COPYING doc/COPYING
|
|
72
|
+
install -m644 $(shell LC_ALL=C grep '^[A-Z]' .document) doc/
|
|
73
|
+
|
|
74
|
+
ifneq ($(VERSION),)
|
|
75
|
+
pkggem := pkg/$(rfpackage)-$(VERSION).gem
|
|
76
|
+
pkgtgz := pkg/$(rfpackage)-$(VERSION).tgz
|
|
77
|
+
release_notes := release_notes-$(VERSION)
|
|
78
|
+
release_changes := release_changes-$(VERSION)
|
|
79
|
+
|
|
80
|
+
release-notes: $(release_notes)
|
|
81
|
+
release-changes: $(release_changes)
|
|
82
|
+
$(release_changes):
|
|
83
|
+
$(WRONGDOC) release_changes > $@+
|
|
84
|
+
$(VISUAL) $@+ && test -s $@+ && mv $@+ $@
|
|
85
|
+
$(release_notes):
|
|
86
|
+
$(WRONGDOC) release_notes > $@+
|
|
87
|
+
$(VISUAL) $@+ && test -s $@+ && mv $@+ $@
|
|
88
|
+
|
|
89
|
+
# ensures we're actually on the tagged $(VERSION), only used for release
|
|
90
|
+
verify:
|
|
91
|
+
test x"$(shell umask)" = x0022
|
|
92
|
+
git rev-parse --verify refs/tags/v$(VERSION)^{}
|
|
93
|
+
git diff-index --quiet HEAD^0
|
|
94
|
+
test $$(git rev-parse --verify HEAD^0) = \
|
|
95
|
+
$$(git rev-parse --verify refs/tags/v$(VERSION)^{})
|
|
96
|
+
|
|
97
|
+
fix-perms:
|
|
98
|
+
-git ls-tree -r HEAD | awk '/^100644 / {print $$NF}' | xargs chmod 644
|
|
99
|
+
-git ls-tree -r HEAD | awk '/^100755 / {print $$NF}' | xargs chmod 755
|
|
100
|
+
|
|
101
|
+
gem: $(pkggem)
|
|
102
|
+
|
|
103
|
+
install-gem: $(pkggem)
|
|
104
|
+
gem install $(CURDIR)/$<
|
|
105
|
+
|
|
106
|
+
$(pkggem): manifest fix-perms
|
|
107
|
+
gem build $(rfpackage).gemspec
|
|
108
|
+
mkdir -p pkg
|
|
109
|
+
mv $(@F) $@
|
|
110
|
+
|
|
111
|
+
$(pkgtgz): distdir = $(basename $@)
|
|
112
|
+
$(pkgtgz): HEAD = v$(VERSION)
|
|
113
|
+
$(pkgtgz): manifest fix-perms
|
|
114
|
+
@test -n "$(distdir)"
|
|
115
|
+
$(RM) -r $(distdir)
|
|
116
|
+
mkdir -p $(distdir)
|
|
117
|
+
tar cf - $$(cat .manifest) | (cd $(distdir) && tar xf -)
|
|
118
|
+
cd pkg && tar cf - $(basename $(@F)) | gzip -9 > $(@F)+
|
|
119
|
+
mv $@+ $@
|
|
120
|
+
|
|
121
|
+
package: $(pkgtgz) $(pkggem)
|
|
122
|
+
|
|
123
|
+
test-release:: verify package $(release_notes) $(release_changes)
|
|
124
|
+
# make tgz release on RubyForge
|
|
125
|
+
@echo rubyforge add_release -f \
|
|
126
|
+
-n $(release_notes) -a $(release_changes) \
|
|
127
|
+
$(rfproject) $(rfpackage) $(VERSION) $(pkgtgz)
|
|
128
|
+
@echo gem push $(pkggem)
|
|
129
|
+
@echo rubyforge add_file \
|
|
130
|
+
$(rfproject) $(rfpackage) $(VERSION) $(pkggem)
|
|
131
|
+
release:: verify package $(release_notes) $(release_changes)
|
|
132
|
+
# make tgz release on RubyForge
|
|
133
|
+
rubyforge add_release -f -n $(release_notes) -a $(release_changes) \
|
|
134
|
+
$(rfproject) $(rfpackage) $(VERSION) $(pkgtgz)
|
|
135
|
+
# push gem to RubyGems.org
|
|
136
|
+
gem push $(pkggem)
|
|
137
|
+
# in case of gem downloads from RubyForge releases page
|
|
138
|
+
rubyforge add_file \
|
|
139
|
+
$(rfproject) $(rfpackage) $(VERSION) $(pkggem)
|
|
140
|
+
else
|
|
141
|
+
gem install-gem: GIT-VERSION-FILE
|
|
142
|
+
$(MAKE) $@ VERSION=$(GIT_VERSION)
|
|
143
|
+
endif
|
|
144
|
+
|
|
145
|
+
all:: test
|
|
146
|
+
test_units := $(wildcard test/test_*.rb)
|
|
147
|
+
test: test-unit
|
|
148
|
+
test-unit: $(test_units)
|
|
149
|
+
$(test_units): build
|
|
150
|
+
$(RUBY) -I $(lib) $@ $(RUBY_TEST_OPTS)
|
|
151
|
+
|
|
152
|
+
# this requires GNU coreutils variants
|
|
153
|
+
ifneq ($(RSYNC_DEST),)
|
|
154
|
+
publish_doc:
|
|
155
|
+
-git set-file-times
|
|
156
|
+
$(MAKE) doc
|
|
157
|
+
find doc/images -type f | \
|
|
158
|
+
TZ=UTC xargs touch -d '1970-01-01 00:00:06' doc/rdoc.css
|
|
159
|
+
$(MAKE) doc_gz
|
|
160
|
+
$(RSYNC) -av doc/ $(RSYNC_DEST)/
|
|
161
|
+
git ls-files | xargs touch
|
|
162
|
+
endif
|
|
163
|
+
|
|
164
|
+
# Create gzip variants of the same timestamp as the original so nginx
|
|
165
|
+
# "gzip_static on" can serve the gzipped versions directly.
|
|
166
|
+
doc_gz: docs = $(shell find doc -type f ! -regex '^.*\.\(gif\|jpg\|png\|gz\)$$')
|
|
167
|
+
doc_gz:
|
|
168
|
+
for i in $(docs); do \
|
|
169
|
+
gzip --rsyncable -9 < $$i > $$i.gz; touch -r $$i $$i.gz; done
|
|
170
|
+
check-warnings:
|
|
171
|
+
@(for i in $$(git ls-files '*.rb'| grep -v '^setup\.rb$$'); \
|
|
172
|
+
do $(RUBY) -d -W2 -c $$i; done) | grep -v '^Syntax OK$$' || :
|
|
173
|
+
|
|
174
|
+
.PHONY: all .FORCE-GIT-VERSION-FILE doc test $(test_units) manifest
|
|
175
|
+
.PHONY: check-warnings
|