omgf 0.0.0.GIT
Sign up to get free protection for your applications and to get access to all the features.
- 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
|