mongrel 0.2.0

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.
Files changed (74) hide show
  1. data/COPYING +504 -0
  2. data/LICENSE +504 -0
  3. data/README +117 -0
  4. data/Rakefile +30 -0
  5. data/doc/rdoc/classes/Mongrel.html +144 -0
  6. data/doc/rdoc/classes/Mongrel/Error404Handler.html +171 -0
  7. data/doc/rdoc/classes/Mongrel/Error404Handler.src/M000023.html +18 -0
  8. data/doc/rdoc/classes/Mongrel/Error404Handler.src/M000024.html +18 -0
  9. data/doc/rdoc/classes/Mongrel/HeaderOut.html +167 -0
  10. data/doc/rdoc/classes/Mongrel/HeaderOut.src/M000013.html +18 -0
  11. data/doc/rdoc/classes/Mongrel/HeaderOut.src/M000014.html +21 -0
  12. data/doc/rdoc/classes/Mongrel/HttpHandler.html +159 -0
  13. data/doc/rdoc/classes/Mongrel/HttpHandler.src/M000019.html +17 -0
  14. data/doc/rdoc/classes/Mongrel/HttpParser.html +271 -0
  15. data/doc/rdoc/classes/Mongrel/HttpParser.src/M000001.html +28 -0
  16. data/doc/rdoc/classes/Mongrel/HttpParser.src/M000002.html +29 -0
  17. data/doc/rdoc/classes/Mongrel/HttpParser.src/M000003.html +29 -0
  18. data/doc/rdoc/classes/Mongrel/HttpParser.src/M000004.html +41 -0
  19. data/doc/rdoc/classes/Mongrel/HttpParser.src/M000005.html +27 -0
  20. data/doc/rdoc/classes/Mongrel/HttpParser.src/M000006.html +27 -0
  21. data/doc/rdoc/classes/Mongrel/HttpParser.src/M000007.html +28 -0
  22. data/doc/rdoc/classes/Mongrel/HttpRequest.html +177 -0
  23. data/doc/rdoc/classes/Mongrel/HttpRequest.src/M000025.html +30 -0
  24. data/doc/rdoc/classes/Mongrel/HttpResponse.html +202 -0
  25. data/doc/rdoc/classes/Mongrel/HttpResponse.src/M000020.html +21 -0
  26. data/doc/rdoc/classes/Mongrel/HttpResponse.src/M000021.html +20 -0
  27. data/doc/rdoc/classes/Mongrel/HttpResponse.src/M000022.html +25 -0
  28. data/doc/rdoc/classes/Mongrel/HttpServer.html +336 -0
  29. data/doc/rdoc/classes/Mongrel/HttpServer.src/M000008.html +26 -0
  30. data/doc/rdoc/classes/Mongrel/HttpServer.src/M000009.html +58 -0
  31. data/doc/rdoc/classes/Mongrel/HttpServer.src/M000010.html +22 -0
  32. data/doc/rdoc/classes/Mongrel/HttpServer.src/M000011.html +18 -0
  33. data/doc/rdoc/classes/Mongrel/HttpServer.src/M000012.html +18 -0
  34. data/doc/rdoc/classes/Mongrel/URIClassifier.html +257 -0
  35. data/doc/rdoc/classes/Mongrel/URIClassifier.src/M000015.html +54 -0
  36. data/doc/rdoc/classes/Mongrel/URIClassifier.src/M000016.html +50 -0
  37. data/doc/rdoc/classes/Mongrel/URIClassifier.src/M000017.html +36 -0
  38. data/doc/rdoc/classes/Mongrel/URIClassifier.src/M000018.html +73 -0
  39. data/doc/rdoc/created.rid +1 -0
  40. data/doc/rdoc/files/COPYING.html +756 -0
  41. data/doc/rdoc/files/LICENSE.html +756 -0
  42. data/doc/rdoc/files/README.html +273 -0
  43. data/doc/rdoc/files/ext/http11/http11_c.html +101 -0
  44. data/doc/rdoc/files/lib/mongrel_rb.html +111 -0
  45. data/doc/rdoc/fr_class_index.html +35 -0
  46. data/doc/rdoc/fr_file_index.html +31 -0
  47. data/doc/rdoc/fr_method_index.html +51 -0
  48. data/doc/rdoc/index.html +24 -0
  49. data/doc/rdoc/rdoc-style.css +208 -0
  50. data/examples/camping/blog.rb +300 -0
  51. data/examples/camping/tepee.rb +168 -0
  52. data/examples/simpletest.rb +16 -0
  53. data/examples/webrick_compare.rb +20 -0
  54. data/ext/http11/MANIFEST +0 -0
  55. data/ext/http11/ext_help.h +14 -0
  56. data/ext/http11/extconf.rb +6 -0
  57. data/ext/http11/http11.c +436 -0
  58. data/ext/http11/http11_parser.c +918 -0
  59. data/ext/http11/http11_parser.h +37 -0
  60. data/ext/http11/tst.h +40 -0
  61. data/ext/http11/tst_cleanup.c +24 -0
  62. data/ext/http11/tst_delete.c +146 -0
  63. data/ext/http11/tst_grow_node_free_list.c +38 -0
  64. data/ext/http11/tst_init.c +41 -0
  65. data/ext/http11/tst_insert.c +192 -0
  66. data/ext/http11/tst_search.c +54 -0
  67. data/lib/mongrel.rb +298 -0
  68. data/setup.rb +1360 -0
  69. data/test/test_http11.rb +38 -0
  70. data/test/test_response.rb +44 -0
  71. data/test/test_uriclassifier.rb +104 -0
  72. data/test/test_ws.rb +33 -0
  73. data/tools/rakehelp.rb +99 -0
  74. metadata +132 -0
@@ -0,0 +1,54 @@
1
+
2
+ #include "tst.h"
3
+ #include <stdio.h>
4
+ #include <stdlib.h>
5
+ #include <assert.h>
6
+
7
+ void *tst_search(unsigned char *key, struct tst *tst, int *prefix_len)
8
+ {
9
+ struct node *current_node;
10
+ int key_index;
11
+
12
+ assert(key != NULL && "key can't be NULL");
13
+ assert(tst != NULL && "tst can't be NULL");
14
+
15
+
16
+ if(key[0] == 0)
17
+ return NULL;
18
+
19
+ if(tst->head[(int)key[0]] == NULL)
20
+ return NULL;
21
+
22
+ current_node = tst->head[(int)key[0]];
23
+ key_index = 1;
24
+
25
+ while (current_node != NULL)
26
+ {
27
+ if(key[key_index] == current_node->value)
28
+ {
29
+ if(current_node->value == 0) {
30
+ if(prefix_len) *prefix_len = key_index;
31
+ return current_node->middle;
32
+ } else {
33
+ current_node = current_node->middle;
34
+ key_index++;
35
+ continue;
36
+ }
37
+ }
38
+ else if( ((current_node->value == 0) && (key[key_index] < 64)) ||
39
+ ((current_node->value != 0) && (key[key_index] <
40
+ current_node->value)) )
41
+ {
42
+ current_node = current_node->left;
43
+ continue;
44
+ }
45
+ else
46
+ {
47
+ current_node = current_node->right;
48
+ continue;
49
+ }
50
+ }
51
+
52
+ if(prefix_len) *prefix_len = key_index;
53
+ return NULL;
54
+ }
@@ -0,0 +1,298 @@
1
+ require 'socket'
2
+ require 'http11'
3
+ require 'thread'
4
+ require 'stringio'
5
+
6
+ # Mongrel module containing all of the classes (include C extensions) for running
7
+ # a Mongrel web server. It contains a minimalist HTTP server with just enough
8
+ # functionality to service web application requests fast as possible.
9
+ module Mongrel
10
+
11
+ HTTP_STATUS_CODES = {
12
+ 100 => 'Continue',
13
+ 101 => 'Switching Protocols',
14
+ 200 => 'OK',
15
+ 201 => 'Created',
16
+ 202 => 'Accepted',
17
+ 203 => 'Non-Authoritative Information',
18
+ 204 => 'No Content',
19
+ 205 => 'Reset Content',
20
+ 206 => 'Partial Content',
21
+ 300 => 'Multiple Choices',
22
+ 301 => 'Moved Permanently',
23
+ 302 => 'Moved Temporarily',
24
+ 303 => 'See Other',
25
+ 304 => 'Not Modified',
26
+ 305 => 'Use Proxy',
27
+ 400 => 'Bad Request',
28
+ 401 => 'Unauthorized',
29
+ 402 => 'Payment Required',
30
+ 403 => 'Forbidden',
31
+ 404 => 'Not Found',
32
+ 405 => 'Method Not Allowed',
33
+ 406 => 'Not Acceptable',
34
+ 407 => 'Proxy Authentication Required',
35
+ 408 => 'Request Time-out',
36
+ 409 => 'Conflict',
37
+ 410 => 'Gone',
38
+ 411 => 'Length Required',
39
+ 412 => 'Precondition Failed',
40
+ 413 => 'Request Entity Too Large',
41
+ 414 => 'Request-URI Too Large',
42
+ 415 => 'Unsupported Media Type',
43
+ 500 => 'Internal Server Error',
44
+ 501 => 'Not Implemented',
45
+ 502 => 'Bad Gateway',
46
+ 503 => 'Service Unavailable',
47
+ 504 => 'Gateway Time-out',
48
+ 505 => 'HTTP Version not supported'
49
+ }
50
+
51
+ # When a handler is found for a registered URI then this class is constructed
52
+ # and passed to your HttpHandler::process method. You should assume that
53
+ # *one* handler processes all requests. Included in the HttpReqeust is a
54
+ # HttpRequest.params Hash that matches common CGI params, and a HttpRequest.body
55
+ # which is a string containing the request body (raw for now).
56
+ #
57
+ # Mongrel really only support small-ish request bodies right now since really
58
+ # huge ones have to be completely read off the wire and put into a string.
59
+ # Later there will be several options for efficiently handling large file
60
+ # uploads.
61
+ class HttpRequest
62
+ attr_reader :body, :params
63
+
64
+ # You don't really call this. It's made for you.
65
+ # Main thing it does is hook up the params, and store any remaining
66
+ # body data into the HttpRequest.body attribute.
67
+ def initialize(params, initial_body, socket)
68
+ @body = initial_body || ""
69
+ @params = params
70
+ @socket = socket
71
+
72
+ # fix up the CGI requirements
73
+ params['CONTENT_LENGTH'] = params['HTTP_CONTENT_LENGTH'] || 0
74
+
75
+ # now, if the initial_body isn't long enough for the content length we have to fill it
76
+ # TODO: adapt for big ass stuff by writing to a temp file
77
+ clen = params['HTTP_CONTENT_LENGTH'].to_i
78
+ if @body.length < clen
79
+ @body << @socket.read(clen - @body.length)
80
+ end
81
+ end
82
+ end
83
+
84
+
85
+ class HeaderOut
86
+ attr_reader :out
87
+
88
+ def initialize(out)
89
+ @out = out
90
+ end
91
+
92
+ def[]=(key,value)
93
+ @out.write(key)
94
+ @out.write(": ")
95
+ @out.write(value)
96
+ @out.write("\r\n")
97
+ end
98
+ end
99
+
100
+
101
+ class HttpResponse
102
+ attr_reader :socket
103
+ attr_reader :body
104
+ attr_reader :header
105
+ attr_reader :status
106
+ attr_writer :status
107
+
108
+ def initialize(socket)
109
+ @socket = socket
110
+ @body = StringIO.new
111
+ @status = 404
112
+ @header = HeaderOut.new(StringIO.new)
113
+ end
114
+
115
+ def start(status=200)
116
+ @status = status
117
+ yield @header, @body
118
+ finished
119
+ end
120
+
121
+ def finished
122
+ @header.out.rewind
123
+ @body.rewind
124
+
125
+ # connection: close is also added to ensure that the client does not pipeline.
126
+ @socket.write("HTTP/1.1 #{@status} #{HTTP_STATUS_CODES[@status]}\r\nContent-Length: #{@body.length}\r\nConnection: close\r\n")
127
+ @socket.write(@header.out.read)
128
+ @socket.write("\r\n")
129
+ @socket.write(@body.read)
130
+ end
131
+ end
132
+
133
+
134
+ # You implement your application handler with this. It's very light giving
135
+ # just the minimum necessary for you to handle a request and shoot back
136
+ # a response. Look at the HttpRequest and HttpResponse objects for how
137
+ # to use them.
138
+ class HttpHandler
139
+ attr_accessor :script_name
140
+
141
+ def process(request, response)
142
+ end
143
+ end
144
+
145
+
146
+ # The server normally returns a 404 response if a URI is requested, but it
147
+ # also returns a lame empty message. This lets you do a 404 response
148
+ # with a custom message for special URIs.
149
+ class Error404Handler < HttpHandler
150
+
151
+ # Sets the message to return. This is constructed once for the handler
152
+ # so it's pretty efficient.
153
+ def initialize(msg)
154
+ @response = HttpServer::ERROR_404_RESPONSE + msg
155
+ end
156
+
157
+ # Just kicks back the standard 404 response with your special message.
158
+ def process(request, response)
159
+ response.socket.write(@response)
160
+ end
161
+
162
+ end
163
+
164
+
165
+ # This is the main driver of Mongrel, while the Mognrel::HttpParser and Mongrel::URIClassifier
166
+ # make up the majority of how the server functions. It's a very simple class that just
167
+ # has a thread accepting connections and a simple HttpServer.process_client function
168
+ # to do the heavy lifting with the IO and Ruby.
169
+ #
170
+ # You use it by doing the following:
171
+ #
172
+ # server = HttpServer.new("0.0.0.0", 3000)
173
+ # server.register("/stuff", MyNifterHandler.new)
174
+ # server.run.join
175
+ #
176
+ # The last line can be just server.run if you don't want to join the thread used.
177
+ # If you don't though Ruby will mysteriously just exit on you.
178
+ #
179
+ # Ruby's thread implementation is "interesting" to say the least. Experiments with
180
+ # *many* different types of IO processing simply cannot make a dent in it. Future
181
+ # releases of Mongrel will find other creative ways to make threads faster, but don't
182
+ # hold your breath until Ruby 1.9 is actually finally useful.
183
+ class HttpServer
184
+ attr_reader :acceptor
185
+
186
+ # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
187
+ ERROR_404_RESPONSE="HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Mongrel/0.2\r\n\r\nNOT FOUND"
188
+ ERROR_503_RESPONSE="HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY"
189
+
190
+ # The basic max request size we'll try to read.
191
+ CHUNK_SIZE=(16 * 1024)
192
+
193
+ PATH_INFO="PATH_INFO"
194
+ SCRIPT_NAME="SCRIPT_NAME"
195
+
196
+ # Creates a working server on host:port (strange things happen if port isn't a Number).
197
+ # Use HttpServer::run to start the server.
198
+ #
199
+ # The num_processors variable has varying affects on how requests are processed. You'd
200
+ # think adding more processing threads (processors) would make the server faster, but
201
+ # that's just not true. There's actually an effect of how Ruby does threads such that
202
+ # the more processors waiting on the request queue, the slower the system is to handle
203
+ # each request. But, the lower the number of processors the fewer concurrent responses
204
+ # the server can make.
205
+ #
206
+ # 20 is the default number of processors and is based on experimentation on a few
207
+ # systems. If you find that you overload Mongrel too much
208
+ # try changing it higher. If you find that responses are way too slow
209
+ # try lowering it (after you've tuned your stuff of course).
210
+ # Future versions of Mongrel will make this more dynamic (hopefully).
211
+ def initialize(host, port, num_processors=20)
212
+ @socket = TCPServer.new(host, port)
213
+ @classifier = URIClassifier.new
214
+ @req_queue = Queue.new
215
+ num_processors.times {|i| Thread.new do
216
+ while client = @req_queue.deq
217
+ process_client(client)
218
+ end
219
+ end
220
+ }
221
+ end
222
+
223
+
224
+ # Does the majority of the IO processing. It has been written in Ruby using
225
+ # about 7 different IO processing strategies and no matter how it's done
226
+ # the performance just does not improve. Ruby's use of select to implement
227
+ # threads means that it will most likely never improve, so the only remaining
228
+ # approach is to write all or some of this function in C. That will be the
229
+ # focus of future releases.
230
+ def process_client(client)
231
+ begin
232
+ parser = HttpParser.new
233
+ params = {}
234
+ data = client.readpartial(CHUNK_SIZE)
235
+
236
+ while true
237
+ nread = parser.execute(params, data)
238
+ if parser.finished?
239
+ script_name, path_info, handler = @classifier.resolve(params[PATH_INFO])
240
+
241
+ if handler
242
+ params[PATH_INFO] = path_info
243
+ params[SCRIPT_NAME] = script_name
244
+
245
+ request = HttpRequest.new(params, data[nread ... data.length], client)
246
+ response = HttpResponse.new(client)
247
+ handler.process(request, response)
248
+ else
249
+ client.write(ERROR_404_RESPONSE)
250
+ end
251
+
252
+ break
253
+ else
254
+ # gotta stream and read again until we can get the parser to be character safe
255
+ # TODO: make this more efficient since this means we're parsing a lot repeatedly
256
+ parser.reset
257
+ data << client.readpartial(CHUNK_SIZE)
258
+ end
259
+ end
260
+ rescue EOFError
261
+ # ignored
262
+ rescue Errno::ECONNRESET
263
+ # ignored
264
+ rescue Errno::EPIPE
265
+ # ignored
266
+ rescue => details
267
+ STDERR.puts "ERROR(#{details.class}): #{details}"
268
+ STDERR.puts details.backtrace.join("\n")
269
+ ensure
270
+ client.close
271
+ end
272
+ end
273
+
274
+ # Runs the thing. It returns the thread used so you can "join" it. You can also
275
+ # access the HttpServer::acceptor attribute to get the thread later.
276
+ def run
277
+ @acceptor = Thread.new do
278
+ while true
279
+ @req_queue << @socket.accept
280
+ end
281
+ end
282
+ end
283
+
284
+
285
+ # Simply registers a handler with the internal URIClassifier. When the URI is
286
+ # found in the prefix of a request then your handler's HttpHandler::process method
287
+ # is called. See Mongrel::URIClassifier#register for more information.
288
+ def register(uri, handler)
289
+ @classifier.register(uri, handler)
290
+ end
291
+
292
+ # Removes any handler registered at the given URI. See Mongrel::URIClassifier#unregister
293
+ # for more information.
294
+ def unregister(uri)
295
+ @classifier.unregister(uri)
296
+ end
297
+ end
298
+ end
@@ -0,0 +1,1360 @@
1
+ #
2
+ # setup.rb
3
+ #
4
+ # Copyright (c) 2000-2004 Minero Aoki
5
+ #
6
+ # This program is free software.
7
+ # You can distribute/modify this program under the terms of
8
+ # the GNU LGPL, Lesser General Public License version 2.1.
9
+ #
10
+
11
+ unless Enumerable.method_defined?(:map) # Ruby 1.4.6
12
+ module Enumerable
13
+ alias map collect
14
+ end
15
+ end
16
+
17
+ unless File.respond_to?(:read) # Ruby 1.6
18
+ def File.read(fname)
19
+ open(fname) {|f|
20
+ return f.read
21
+ }
22
+ end
23
+ end
24
+
25
+ def File.binread(fname)
26
+ open(fname, 'rb') {|f|
27
+ return f.read
28
+ }
29
+ end
30
+
31
+ # for corrupted windows stat(2)
32
+ def File.dir?(path)
33
+ File.directory?((path[-1,1] == '/') ? path : path + '/')
34
+ end
35
+
36
+
37
+ class SetupError < StandardError; end
38
+
39
+ def setup_rb_error(msg)
40
+ raise SetupError, msg
41
+ end
42
+
43
+ #
44
+ # Config
45
+ #
46
+
47
+ if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg }
48
+ ARGV.delete(arg)
49
+ require arg.split(/=/, 2)[1]
50
+ $".push 'rbconfig.rb'
51
+ else
52
+ require 'rbconfig'
53
+ end
54
+
55
+ def multipackage_install?
56
+ FileTest.directory?(File.dirname($0) + '/packages')
57
+ end
58
+
59
+
60
+ class ConfigItem
61
+ def initialize(name, template, default, desc)
62
+ @name = name.freeze
63
+ @template = template
64
+ @value = default
65
+ @default = default.dup.freeze
66
+ @description = desc
67
+ end
68
+
69
+ attr_reader :name
70
+ attr_reader :description
71
+
72
+ attr_accessor :default
73
+ alias help_default default
74
+
75
+ def help_opt
76
+ "--#{@name}=#{@template}"
77
+ end
78
+
79
+ def value
80
+ @value
81
+ end
82
+
83
+ def eval(table)
84
+ @value.gsub(%r<\$([^/]+)>) { table[$1] }
85
+ end
86
+
87
+ def set(val)
88
+ @value = check(val)
89
+ end
90
+
91
+ private
92
+
93
+ def check(val)
94
+ setup_rb_error "config: --#{name} requires argument" unless val
95
+ val
96
+ end
97
+ end
98
+
99
+ class BoolItem < ConfigItem
100
+ def config_type
101
+ 'bool'
102
+ end
103
+
104
+ def help_opt
105
+ "--#{@name}"
106
+ end
107
+
108
+ private
109
+
110
+ def check(val)
111
+ return 'yes' unless val
112
+ unless /\A(y(es)?|n(o)?|t(rue)?|f(alse))\z/i =~ val
113
+ setup_rb_error "config: --#{@name} accepts only yes/no for argument"
114
+ end
115
+ (/\Ay(es)?|\At(rue)/i =~ value) ? 'yes' : 'no'
116
+ end
117
+ end
118
+
119
+ class PathItem < ConfigItem
120
+ def config_type
121
+ 'path'
122
+ end
123
+
124
+ private
125
+
126
+ def check(path)
127
+ setup_rb_error "config: --#{@name} requires argument" unless path
128
+ path[0,1] == '$' ? path : File.expand_path(path)
129
+ end
130
+ end
131
+
132
+ class ProgramItem < ConfigItem
133
+ def config_type
134
+ 'program'
135
+ end
136
+ end
137
+
138
+ class SelectItem < ConfigItem
139
+ def initialize(name, template, default, desc)
140
+ super
141
+ @ok = template.split('/')
142
+ end
143
+
144
+ def config_type
145
+ 'select'
146
+ end
147
+
148
+ private
149
+
150
+ def check(val)
151
+ unless @ok.include?(val.strip)
152
+ setup_rb_error "config: use --#{@name}=#{@template} (#{val})"
153
+ end
154
+ val.strip
155
+ end
156
+ end
157
+
158
+ class PackageSelectionItem < ConfigItem
159
+ def initialize(name, template, default, help_default, desc)
160
+ super name, template, default, desc
161
+ @help_default = help_default
162
+ end
163
+
164
+ attr_reader :help_default
165
+
166
+ def config_type
167
+ 'package'
168
+ end
169
+
170
+ private
171
+
172
+ def check(val)
173
+ unless File.dir?("packages/#{val}")
174
+ setup_rb_error "config: no such package: #{val}"
175
+ end
176
+ val
177
+ end
178
+ end
179
+
180
+ class ConfigTable_class
181
+
182
+ def initialize(items)
183
+ @items = items
184
+ @table = {}
185
+ items.each do |i|
186
+ @table[i.name] = i
187
+ end
188
+ ALIASES.each do |ali, name|
189
+ @table[ali] = @table[name]
190
+ end
191
+ end
192
+
193
+ include Enumerable
194
+
195
+ def each(&block)
196
+ @items.each(&block)
197
+ end
198
+
199
+ def key?(name)
200
+ @table.key?(name)
201
+ end
202
+
203
+ def lookup(name)
204
+ @table[name] or raise ArgumentError, "no such config item: #{name}"
205
+ end
206
+
207
+ def add(item)
208
+ @items.push item
209
+ @table[item.name] = item
210
+ end
211
+
212
+ def remove(name)
213
+ item = lookup(name)
214
+ @items.delete_if {|i| i.name == name }
215
+ @table.delete_if {|name, i| i.name == name }
216
+ item
217
+ end
218
+
219
+ def new
220
+ dup()
221
+ end
222
+
223
+ def savefile
224
+ '.config'
225
+ end
226
+
227
+ def load
228
+ begin
229
+ t = dup()
230
+ File.foreach(savefile()) do |line|
231
+ k, v = *line.split(/=/, 2)
232
+ t[k] = v.strip
233
+ end
234
+ t
235
+ rescue Errno::ENOENT
236
+ setup_rb_error $!.message + "#{File.basename($0)} config first"
237
+ end
238
+ end
239
+
240
+ def save
241
+ @items.each {|i| i.value }
242
+ File.open(savefile(), 'w') {|f|
243
+ @items.each do |i|
244
+ f.printf "%s=%s\n", i.name, i.value if i.value
245
+ end
246
+ }
247
+ end
248
+
249
+ def [](key)
250
+ lookup(key).eval(self)
251
+ end
252
+
253
+ def []=(key, val)
254
+ lookup(key).set val
255
+ end
256
+
257
+ end
258
+
259
+ c = ::Config::CONFIG
260
+
261
+ rubypath = c['bindir'] + '/' + c['ruby_install_name']
262
+
263
+ major = c['MAJOR'].to_i
264
+ minor = c['MINOR'].to_i
265
+ teeny = c['TEENY'].to_i
266
+ version = "#{major}.#{minor}"
267
+
268
+ # ruby ver. >= 1.4.4?
269
+ newpath_p = ((major >= 2) or
270
+ ((major == 1) and
271
+ ((minor >= 5) or
272
+ ((minor == 4) and (teeny >= 4)))))
273
+
274
+ if c['rubylibdir']
275
+ # V < 1.6.3
276
+ _stdruby = c['rubylibdir']
277
+ _siteruby = c['sitedir']
278
+ _siterubyver = c['sitelibdir']
279
+ _siterubyverarch = c['sitearchdir']
280
+ elsif newpath_p
281
+ # 1.4.4 <= V <= 1.6.3
282
+ _stdruby = "$prefix/lib/ruby/#{version}"
283
+ _siteruby = c['sitedir']
284
+ _siterubyver = "$siteruby/#{version}"
285
+ _siterubyverarch = "$siterubyver/#{c['arch']}"
286
+ else
287
+ # V < 1.4.4
288
+ _stdruby = "$prefix/lib/ruby/#{version}"
289
+ _siteruby = "$prefix/lib/ruby/#{version}/site_ruby"
290
+ _siterubyver = _siteruby
291
+ _siterubyverarch = "$siterubyver/#{c['arch']}"
292
+ end
293
+ libdir = '-* dummy libdir *-'
294
+ stdruby = '-* dummy rubylibdir *-'
295
+ siteruby = '-* dummy site_ruby *-'
296
+ siterubyver = '-* dummy site_ruby version *-'
297
+ parameterize = lambda {|path|
298
+ path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix')\
299
+ .sub(/\A#{Regexp.quote(libdir)}/, '$libdir')\
300
+ .sub(/\A#{Regexp.quote(stdruby)}/, '$stdruby')\
301
+ .sub(/\A#{Regexp.quote(siteruby)}/, '$siteruby')\
302
+ .sub(/\A#{Regexp.quote(siterubyver)}/, '$siterubyver')
303
+ }
304
+ libdir = parameterize.call(c['libdir'])
305
+ stdruby = parameterize.call(_stdruby)
306
+ siteruby = parameterize.call(_siteruby)
307
+ siterubyver = parameterize.call(_siterubyver)
308
+ siterubyverarch = parameterize.call(_siterubyverarch)
309
+
310
+ if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg }
311
+ makeprog = arg.sub(/'/, '').split(/=/, 2)[1]
312
+ else
313
+ makeprog = 'make'
314
+ end
315
+
316
+ common_conf = [
317
+ PathItem.new('prefix', 'path', c['prefix'],
318
+ 'path prefix of target environment'),
319
+ PathItem.new('bindir', 'path', parameterize.call(c['bindir']),
320
+ 'the directory for commands'),
321
+ PathItem.new('libdir', 'path', libdir,
322
+ 'the directory for libraries'),
323
+ PathItem.new('datadir', 'path', parameterize.call(c['datadir']),
324
+ 'the directory for shared data'),
325
+ PathItem.new('mandir', 'path', parameterize.call(c['mandir']),
326
+ 'the directory for man pages'),
327
+ PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']),
328
+ 'the directory for man pages'),
329
+ PathItem.new('stdruby', 'path', stdruby,
330
+ 'the directory for standard ruby libraries'),
331
+ PathItem.new('siteruby', 'path', siteruby,
332
+ 'the directory for version-independent aux ruby libraries'),
333
+ PathItem.new('siterubyver', 'path', siterubyver,
334
+ 'the directory for aux ruby libraries'),
335
+ PathItem.new('siterubyverarch', 'path', siterubyverarch,
336
+ 'the directory for aux ruby binaries'),
337
+ PathItem.new('rbdir', 'path', '$siterubyver',
338
+ 'the directory for ruby scripts'),
339
+ PathItem.new('sodir', 'path', '$siterubyverarch',
340
+ 'the directory for ruby extentions'),
341
+ PathItem.new('rubypath', 'path', rubypath,
342
+ 'the path to set to #! line'),
343
+ ProgramItem.new('rubyprog', 'name', rubypath,
344
+ 'the ruby program using for installation'),
345
+ ProgramItem.new('makeprog', 'name', makeprog,
346
+ 'the make program to compile ruby extentions'),
347
+ SelectItem.new('shebang', 'all/ruby/never', 'ruby',
348
+ 'shebang line (#!) editing mode'),
349
+ BoolItem.new('without-ext', 'yes/no', 'no',
350
+ 'does not compile/install ruby extentions')
351
+ ]
352
+ class ConfigTable_class # open again
353
+ ALIASES = {
354
+ 'std-ruby' => 'stdruby',
355
+ 'site-ruby-common' => 'siteruby', # For backward compatibility
356
+ 'site-ruby' => 'siterubyver', # For backward compatibility
357
+ 'bin-dir' => 'bindir',
358
+ 'bin-dir' => 'bindir',
359
+ 'rb-dir' => 'rbdir',
360
+ 'so-dir' => 'sodir',
361
+ 'data-dir' => 'datadir',
362
+ 'ruby-path' => 'rubypath',
363
+ 'ruby-prog' => 'rubyprog',
364
+ 'ruby' => 'rubyprog',
365
+ 'make-prog' => 'makeprog',
366
+ 'make' => 'makeprog'
367
+ }
368
+ end
369
+ multipackage_conf = [
370
+ PackageSelectionItem.new('with', 'name,name...', '', 'ALL',
371
+ 'package names that you want to install'),
372
+ PackageSelectionItem.new('without', 'name,name...', '', 'NONE',
373
+ 'package names that you do not want to install')
374
+ ]
375
+ if multipackage_install?
376
+ ConfigTable = ConfigTable_class.new(common_conf + multipackage_conf)
377
+ else
378
+ ConfigTable = ConfigTable_class.new(common_conf)
379
+ end
380
+
381
+
382
+ module MetaConfigAPI
383
+
384
+ def eval_file_ifexist(fname)
385
+ instance_eval File.read(fname), fname, 1 if File.file?(fname)
386
+ end
387
+
388
+ def config_names
389
+ ConfigTable.map {|i| i.name }
390
+ end
391
+
392
+ def config?(name)
393
+ ConfigTable.key?(name)
394
+ end
395
+
396
+ def bool_config?(name)
397
+ ConfigTable.lookup(name).config_type == 'bool'
398
+ end
399
+
400
+ def path_config?(name)
401
+ ConfigTable.lookup(name).config_type == 'path'
402
+ end
403
+
404
+ def value_config?(name)
405
+ case ConfigTable.lookup(name).config_type
406
+ when 'bool', 'path'
407
+ true
408
+ else
409
+ false
410
+ end
411
+ end
412
+
413
+ def add_config(item)
414
+ ConfigTable.add item
415
+ end
416
+
417
+ def add_bool_config(name, default, desc)
418
+ ConfigTable.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc)
419
+ end
420
+
421
+ def add_path_config(name, default, desc)
422
+ ConfigTable.add PathItem.new(name, 'path', default, desc)
423
+ end
424
+
425
+ def set_config_default(name, default)
426
+ ConfigTable.lookup(name).default = default
427
+ end
428
+
429
+ def remove_config(name)
430
+ ConfigTable.remove(name)
431
+ end
432
+
433
+ end
434
+
435
+
436
+ #
437
+ # File Operations
438
+ #
439
+
440
+ module FileOperations
441
+
442
+ def mkdir_p(dirname, prefix = nil)
443
+ dirname = prefix + File.expand_path(dirname) if prefix
444
+ $stderr.puts "mkdir -p #{dirname}" if verbose?
445
+ return if no_harm?
446
+
447
+ # does not check '/'... it's too abnormal case
448
+ dirs = File.expand_path(dirname).split(%r<(?=/)>)
449
+ if /\A[a-z]:\z/i =~ dirs[0]
450
+ disk = dirs.shift
451
+ dirs[0] = disk + dirs[0]
452
+ end
453
+ dirs.each_index do |idx|
454
+ path = dirs[0..idx].join('')
455
+ Dir.mkdir path unless File.dir?(path)
456
+ end
457
+ end
458
+
459
+ def rm_f(fname)
460
+ $stderr.puts "rm -f #{fname}" if verbose?
461
+ return if no_harm?
462
+
463
+ if File.exist?(fname) or File.symlink?(fname)
464
+ File.chmod 0777, fname
465
+ File.unlink fname
466
+ end
467
+ end
468
+
469
+ def rm_rf(dn)
470
+ $stderr.puts "rm -rf #{dn}" if verbose?
471
+ return if no_harm?
472
+
473
+ Dir.chdir dn
474
+ Dir.foreach('.') do |fn|
475
+ next if fn == '.'
476
+ next if fn == '..'
477
+ if File.dir?(fn)
478
+ verbose_off {
479
+ rm_rf fn
480
+ }
481
+ else
482
+ verbose_off {
483
+ rm_f fn
484
+ }
485
+ end
486
+ end
487
+ Dir.chdir '..'
488
+ Dir.rmdir dn
489
+ end
490
+
491
+ def move_file(src, dest)
492
+ File.unlink dest if File.exist?(dest)
493
+ begin
494
+ File.rename src, dest
495
+ rescue
496
+ File.open(dest, 'wb') {|f| f.write File.binread(src) }
497
+ File.chmod File.stat(src).mode, dest
498
+ File.unlink src
499
+ end
500
+ end
501
+
502
+ def install(from, dest, mode, prefix = nil)
503
+ $stderr.puts "install #{from} #{dest}" if verbose?
504
+ return if no_harm?
505
+
506
+ realdest = prefix ? prefix + File.expand_path(dest) : dest
507
+ realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest)
508
+ str = File.binread(from)
509
+ if diff?(str, realdest)
510
+ verbose_off {
511
+ rm_f realdest if File.exist?(realdest)
512
+ }
513
+ File.open(realdest, 'wb') {|f|
514
+ f.write str
515
+ }
516
+ File.chmod mode, realdest
517
+
518
+ File.open("#{objdir_root()}/InstalledFiles", 'a') {|f|
519
+ if prefix
520
+ f.puts realdest.sub(prefix, '')
521
+ else
522
+ f.puts realdest
523
+ end
524
+ }
525
+ end
526
+ end
527
+
528
+ def diff?(new_content, path)
529
+ return true unless File.exist?(path)
530
+ new_content != File.binread(path)
531
+ end
532
+
533
+ def command(str)
534
+ $stderr.puts str if verbose?
535
+ system str or raise RuntimeError, "'system #{str}' failed"
536
+ end
537
+
538
+ def ruby(str)
539
+ command config('rubyprog') + ' ' + str
540
+ end
541
+
542
+ def make(task = '')
543
+ command config('makeprog') + ' ' + task
544
+ end
545
+
546
+ def extdir?(dir)
547
+ File.exist?(dir + '/MANIFEST')
548
+ end
549
+
550
+ def all_files_in(dirname)
551
+ Dir.open(dirname) {|d|
552
+ return d.select {|ent| File.file?("#{dirname}/#{ent}") }
553
+ }
554
+ end
555
+
556
+ REJECT_DIRS = %w(
557
+ CVS SCCS RCS CVS.adm .svn
558
+ )
559
+
560
+ def all_dirs_in(dirname)
561
+ Dir.open(dirname) {|d|
562
+ return d.select {|n| File.dir?("#{dirname}/#{n}") } - %w(. ..) - REJECT_DIRS
563
+ }
564
+ end
565
+
566
+ end
567
+
568
+
569
+ #
570
+ # Main Installer
571
+ #
572
+
573
+ module HookUtils
574
+
575
+ def run_hook(name)
576
+ try_run_hook "#{curr_srcdir()}/#{name}" or
577
+ try_run_hook "#{curr_srcdir()}/#{name}.rb"
578
+ end
579
+
580
+ def try_run_hook(fname)
581
+ return false unless File.file?(fname)
582
+ begin
583
+ instance_eval File.read(fname), fname, 1
584
+ rescue
585
+ setup_rb_error "hook #{fname} failed:\n" + $!.message
586
+ end
587
+ true
588
+ end
589
+
590
+ end
591
+
592
+
593
+ module HookScriptAPI
594
+
595
+ def get_config(key)
596
+ @config[key]
597
+ end
598
+
599
+ alias config get_config
600
+
601
+ def set_config(key, val)
602
+ @config[key] = val
603
+ end
604
+
605
+ #
606
+ # srcdir/objdir (works only in the package directory)
607
+ #
608
+
609
+ #abstract srcdir_root
610
+ #abstract objdir_root
611
+ #abstract relpath
612
+
613
+ def curr_srcdir
614
+ "#{srcdir_root()}/#{relpath()}"
615
+ end
616
+
617
+ def curr_objdir
618
+ "#{objdir_root()}/#{relpath()}"
619
+ end
620
+
621
+ def srcfile(path)
622
+ "#{curr_srcdir()}/#{path}"
623
+ end
624
+
625
+ def srcexist?(path)
626
+ File.exist?(srcfile(path))
627
+ end
628
+
629
+ def srcdirectory?(path)
630
+ File.dir?(srcfile(path))
631
+ end
632
+
633
+ def srcfile?(path)
634
+ File.file? srcfile(path)
635
+ end
636
+
637
+ def srcentries(path = '.')
638
+ Dir.open("#{curr_srcdir()}/#{path}") {|d|
639
+ return d.to_a - %w(. ..)
640
+ }
641
+ end
642
+
643
+ def srcfiles(path = '.')
644
+ srcentries(path).select {|fname|
645
+ File.file?(File.join(curr_srcdir(), path, fname))
646
+ }
647
+ end
648
+
649
+ def srcdirectories(path = '.')
650
+ srcentries(path).select {|fname|
651
+ File.dir?(File.join(curr_srcdir(), path, fname))
652
+ }
653
+ end
654
+
655
+ end
656
+
657
+
658
+ class ToplevelInstaller
659
+
660
+ Version = '3.3.1'
661
+ Copyright = 'Copyright (c) 2000-2004 Minero Aoki'
662
+
663
+ TASKS = [
664
+ [ 'all', 'do config, setup, then install' ],
665
+ [ 'config', 'saves your configurations' ],
666
+ [ 'show', 'shows current configuration' ],
667
+ [ 'setup', 'compiles ruby extentions and others' ],
668
+ [ 'install', 'installs files' ],
669
+ [ 'clean', "does `make clean' for each extention" ],
670
+ [ 'distclean',"does `make distclean' for each extention" ]
671
+ ]
672
+
673
+ def ToplevelInstaller.invoke
674
+ instance().invoke
675
+ end
676
+
677
+ @singleton = nil
678
+
679
+ def ToplevelInstaller.instance
680
+ @singleton ||= new(File.dirname($0))
681
+ @singleton
682
+ end
683
+
684
+ include MetaConfigAPI
685
+
686
+ def initialize(ardir_root)
687
+ @config = nil
688
+ @options = { 'verbose' => true }
689
+ @ardir = File.expand_path(ardir_root)
690
+ end
691
+
692
+ def inspect
693
+ "#<#{self.class} #{__id__()}>"
694
+ end
695
+
696
+ def invoke
697
+ run_metaconfigs
698
+ case task = parsearg_global()
699
+ when nil, 'all'
700
+ @config = load_config('config')
701
+ parsearg_config
702
+ init_installers
703
+ exec_config
704
+ exec_setup
705
+ exec_install
706
+ else
707
+ @config = load_config(task)
708
+ __send__ "parsearg_#{task}"
709
+ init_installers
710
+ __send__ "exec_#{task}"
711
+ end
712
+ end
713
+
714
+ def run_metaconfigs
715
+ eval_file_ifexist "#{@ardir}/metaconfig"
716
+ end
717
+
718
+ def load_config(task)
719
+ case task
720
+ when 'config'
721
+ ConfigTable.new
722
+ when 'clean', 'distclean'
723
+ if File.exist?(ConfigTable.savefile)
724
+ then ConfigTable.load
725
+ else ConfigTable.new
726
+ end
727
+ else
728
+ ConfigTable.load
729
+ end
730
+ end
731
+
732
+ def init_installers
733
+ @installer = Installer.new(@config, @options, @ardir, File.expand_path('.'))
734
+ end
735
+
736
+ #
737
+ # Hook Script API bases
738
+ #
739
+
740
+ def srcdir_root
741
+ @ardir
742
+ end
743
+
744
+ def objdir_root
745
+ '.'
746
+ end
747
+
748
+ def relpath
749
+ '.'
750
+ end
751
+
752
+ #
753
+ # Option Parsing
754
+ #
755
+
756
+ def parsearg_global
757
+ valid_task = /\A(?:#{TASKS.map {|task,desc| task }.join '|'})\z/
758
+
759
+ while arg = ARGV.shift
760
+ case arg
761
+ when /\A\w+\z/
762
+ setup_rb_error "invalid task: #{arg}" unless valid_task =~ arg
763
+ return arg
764
+
765
+ when '-q', '--quiet'
766
+ @options['verbose'] = false
767
+
768
+ when '--verbose'
769
+ @options['verbose'] = true
770
+
771
+ when '-h', '--help'
772
+ print_usage $stdout
773
+ exit 0
774
+
775
+ when '-v', '--version'
776
+ puts "#{File.basename($0)} version #{Version}"
777
+ exit 0
778
+
779
+ when '--copyright'
780
+ puts Copyright
781
+ exit 0
782
+
783
+ else
784
+ setup_rb_error "unknown global option '#{arg}'"
785
+ end
786
+ end
787
+
788
+ nil
789
+ end
790
+
791
+
792
+ def parsearg_no_options
793
+ unless ARGV.empty?
794
+ setup_rb_error "#{task}: unknown options: #{ARGV.join ' '}"
795
+ end
796
+ end
797
+
798
+ alias parsearg_show parsearg_no_options
799
+ alias parsearg_setup parsearg_no_options
800
+ alias parsearg_clean parsearg_no_options
801
+ alias parsearg_distclean parsearg_no_options
802
+
803
+ def parsearg_config
804
+ re = /\A--(#{ConfigTable.map {|i| i.name }.join('|')})(?:=(.*))?\z/
805
+ @options['config-opt'] = []
806
+
807
+ while i = ARGV.shift
808
+ if /\A--?\z/ =~ i
809
+ @options['config-opt'] = ARGV.dup
810
+ break
811
+ end
812
+ m = re.match(i) or setup_rb_error "config: unknown option #{i}"
813
+ name, value = *m.to_a[1,2]
814
+ @config[name] = value
815
+ end
816
+ end
817
+
818
+ def parsearg_install
819
+ @options['no-harm'] = false
820
+ @options['install-prefix'] = ''
821
+ while a = ARGV.shift
822
+ case a
823
+ when /\A--no-harm\z/
824
+ @options['no-harm'] = true
825
+ when /\A--prefix=(.*)\z/
826
+ path = $1
827
+ path = File.expand_path(path) unless path[0,1] == '/'
828
+ @options['install-prefix'] = path
829
+ else
830
+ setup_rb_error "install: unknown option #{a}"
831
+ end
832
+ end
833
+ end
834
+
835
+ def print_usage(out)
836
+ out.puts 'Typical Installation Procedure:'
837
+ out.puts " $ ruby #{File.basename $0} config"
838
+ out.puts " $ ruby #{File.basename $0} setup"
839
+ out.puts " # ruby #{File.basename $0} install (may require root privilege)"
840
+ out.puts
841
+ out.puts 'Detailed Usage:'
842
+ out.puts " ruby #{File.basename $0} <global option>"
843
+ out.puts " ruby #{File.basename $0} [<global options>] <task> [<task options>]"
844
+
845
+ fmt = " %-24s %s\n"
846
+ out.puts
847
+ out.puts 'Global options:'
848
+ out.printf fmt, '-q,--quiet', 'suppress message outputs'
849
+ out.printf fmt, ' --verbose', 'output messages verbosely'
850
+ out.printf fmt, '-h,--help', 'print this message'
851
+ out.printf fmt, '-v,--version', 'print version and quit'
852
+ out.printf fmt, ' --copyright', 'print copyright and quit'
853
+ out.puts
854
+ out.puts 'Tasks:'
855
+ TASKS.each do |name, desc|
856
+ out.printf fmt, name, desc
857
+ end
858
+
859
+ fmt = " %-24s %s [%s]\n"
860
+ out.puts
861
+ out.puts 'Options for CONFIG or ALL:'
862
+ ConfigTable.each do |item|
863
+ out.printf fmt, item.help_opt, item.description, item.help_default
864
+ end
865
+ out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's"
866
+ out.puts
867
+ out.puts 'Options for INSTALL:'
868
+ out.printf fmt, '--no-harm', 'only display what to do if given', 'off'
869
+ out.printf fmt, '--prefix=path', 'install path prefix', '$prefix'
870
+ out.puts
871
+ end
872
+
873
+ #
874
+ # Task Handlers
875
+ #
876
+
877
+ def exec_config
878
+ @installer.exec_config
879
+ @config.save # must be final
880
+ end
881
+
882
+ def exec_setup
883
+ @installer.exec_setup
884
+ end
885
+
886
+ def exec_install
887
+ @installer.exec_install
888
+ end
889
+
890
+ def exec_show
891
+ ConfigTable.each do |i|
892
+ printf "%-20s %s\n", i.name, i.value
893
+ end
894
+ end
895
+
896
+ def exec_clean
897
+ @installer.exec_clean
898
+ end
899
+
900
+ def exec_distclean
901
+ @installer.exec_distclean
902
+ end
903
+
904
+ end
905
+
906
+
907
+ class ToplevelInstallerMulti < ToplevelInstaller
908
+
909
+ include HookUtils
910
+ include HookScriptAPI
911
+ include FileOperations
912
+
913
+ def initialize(ardir)
914
+ super
915
+ @packages = all_dirs_in("#{@ardir}/packages")
916
+ raise 'no package exists' if @packages.empty?
917
+ end
918
+
919
+ def run_metaconfigs
920
+ eval_file_ifexist "#{@ardir}/metaconfig"
921
+ @packages.each do |name|
922
+ eval_file_ifexist "#{@ardir}/packages/#{name}/metaconfig"
923
+ end
924
+ end
925
+
926
+ def init_installers
927
+ @installers = {}
928
+ @packages.each do |pack|
929
+ @installers[pack] = Installer.new(@config, @options,
930
+ "#{@ardir}/packages/#{pack}",
931
+ "packages/#{pack}")
932
+ end
933
+
934
+ with = extract_selection(config('with'))
935
+ without = extract_selection(config('without'))
936
+ @selected = @installers.keys.select {|name|
937
+ (with.empty? or with.include?(name)) \
938
+ and not without.include?(name)
939
+ }
940
+ end
941
+
942
+ def extract_selection(list)
943
+ a = list.split(/,/)
944
+ a.each do |name|
945
+ setup_rb_error "no such package: #{name}" unless @installers.key?(name)
946
+ end
947
+ a
948
+ end
949
+
950
+ def print_usage(f)
951
+ super
952
+ f.puts 'Inluded packages:'
953
+ f.puts ' ' + @packages.sort.join(' ')
954
+ f.puts
955
+ end
956
+
957
+ #
958
+ # multi-package metaconfig API
959
+ #
960
+
961
+ attr_reader :packages
962
+
963
+ def declare_packages(list)
964
+ raise 'package list is empty' if list.empty?
965
+ list.each do |name|
966
+ raise "directory packages/#{name} does not exist"\
967
+ unless File.dir?("#{@ardir}/packages/#{name}")
968
+ end
969
+ @packages = list
970
+ end
971
+
972
+ #
973
+ # Task Handlers
974
+ #
975
+
976
+ def exec_config
977
+ run_hook 'pre-config'
978
+ each_selected_installers {|inst| inst.exec_config }
979
+ run_hook 'post-config'
980
+ @config.save # must be final
981
+ end
982
+
983
+ def exec_setup
984
+ run_hook 'pre-setup'
985
+ each_selected_installers {|inst| inst.exec_setup }
986
+ run_hook 'post-setup'
987
+ end
988
+
989
+ def exec_install
990
+ run_hook 'pre-install'
991
+ each_selected_installers {|inst| inst.exec_install }
992
+ run_hook 'post-install'
993
+ end
994
+
995
+ def exec_clean
996
+ rm_f ConfigTable.savefile
997
+ run_hook 'pre-clean'
998
+ each_selected_installers {|inst| inst.exec_clean }
999
+ run_hook 'post-clean'
1000
+ end
1001
+
1002
+ def exec_distclean
1003
+ rm_f ConfigTable.savefile
1004
+ run_hook 'pre-distclean'
1005
+ each_selected_installers {|inst| inst.exec_distclean }
1006
+ run_hook 'post-distclean'
1007
+ end
1008
+
1009
+ #
1010
+ # lib
1011
+ #
1012
+
1013
+ def each_selected_installers
1014
+ Dir.mkdir 'packages' unless File.dir?('packages')
1015
+ @selected.each do |pack|
1016
+ $stderr.puts "Processing the package `#{pack}' ..." if @options['verbose']
1017
+ Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}")
1018
+ Dir.chdir "packages/#{pack}"
1019
+ yield @installers[pack]
1020
+ Dir.chdir '../..'
1021
+ end
1022
+ end
1023
+
1024
+ def verbose?
1025
+ @options['verbose']
1026
+ end
1027
+
1028
+ def no_harm?
1029
+ @options['no-harm']
1030
+ end
1031
+
1032
+ end
1033
+
1034
+
1035
+ class Installer
1036
+
1037
+ FILETYPES = %w( bin lib ext data )
1038
+
1039
+ include HookScriptAPI
1040
+ include HookUtils
1041
+ include FileOperations
1042
+
1043
+ def initialize(config, opt, srcroot, objroot)
1044
+ @config = config
1045
+ @options = opt
1046
+ @srcdir = File.expand_path(srcroot)
1047
+ @objdir = File.expand_path(objroot)
1048
+ @currdir = '.'
1049
+ end
1050
+
1051
+ def inspect
1052
+ "#<#{self.class} #{File.basename(@srcdir)}>"
1053
+ end
1054
+
1055
+ #
1056
+ # Hook Script API base methods
1057
+ #
1058
+
1059
+ def srcdir_root
1060
+ @srcdir
1061
+ end
1062
+
1063
+ def objdir_root
1064
+ @objdir
1065
+ end
1066
+
1067
+ def relpath
1068
+ @currdir
1069
+ end
1070
+
1071
+ #
1072
+ # configs/options
1073
+ #
1074
+
1075
+ def no_harm?
1076
+ @options['no-harm']
1077
+ end
1078
+
1079
+ def verbose?
1080
+ @options['verbose']
1081
+ end
1082
+
1083
+ def verbose_off
1084
+ begin
1085
+ save, @options['verbose'] = @options['verbose'], false
1086
+ yield
1087
+ ensure
1088
+ @options['verbose'] = save
1089
+ end
1090
+ end
1091
+
1092
+ #
1093
+ # TASK config
1094
+ #
1095
+
1096
+ def exec_config
1097
+ exec_task_traverse 'config'
1098
+ end
1099
+
1100
+ def config_dir_bin(rel)
1101
+ end
1102
+
1103
+ def config_dir_lib(rel)
1104
+ end
1105
+
1106
+ def config_dir_ext(rel)
1107
+ extconf if extdir?(curr_srcdir())
1108
+ end
1109
+
1110
+ def extconf
1111
+ opt = @options['config-opt'].join(' ')
1112
+ command "#{config('rubyprog')} #{curr_srcdir()}/extconf.rb #{opt}"
1113
+ end
1114
+
1115
+ def config_dir_data(rel)
1116
+ end
1117
+
1118
+ #
1119
+ # TASK setup
1120
+ #
1121
+
1122
+ def exec_setup
1123
+ exec_task_traverse 'setup'
1124
+ end
1125
+
1126
+ def setup_dir_bin(rel)
1127
+ all_files_in(curr_srcdir()).each do |fname|
1128
+ adjust_shebang "#{curr_srcdir()}/#{fname}"
1129
+ end
1130
+ end
1131
+
1132
+ def adjust_shebang(path)
1133
+ return if no_harm?
1134
+ tmpfile = File.basename(path) + '.tmp'
1135
+ begin
1136
+ File.open(path, 'rb') {|r|
1137
+ first = r.gets
1138
+ return unless File.basename(config('rubypath')) == 'ruby'
1139
+ return unless File.basename(first.sub(/\A\#!/, '').split[0]) == 'ruby'
1140
+ $stderr.puts "adjusting shebang: #{File.basename(path)}" if verbose?
1141
+ File.open(tmpfile, 'wb') {|w|
1142
+ w.print first.sub(/\A\#!\s*\S+/, '#! ' + config('rubypath'))
1143
+ w.write r.read
1144
+ }
1145
+ move_file tmpfile, File.basename(path)
1146
+ }
1147
+ ensure
1148
+ File.unlink tmpfile if File.exist?(tmpfile)
1149
+ end
1150
+ end
1151
+
1152
+ def setup_dir_lib(rel)
1153
+ end
1154
+
1155
+ def setup_dir_ext(rel)
1156
+ make if extdir?(curr_srcdir())
1157
+ end
1158
+
1159
+ def setup_dir_data(rel)
1160
+ end
1161
+
1162
+ #
1163
+ # TASK install
1164
+ #
1165
+
1166
+ def exec_install
1167
+ rm_f 'InstalledFiles'
1168
+ exec_task_traverse 'install'
1169
+ end
1170
+
1171
+ def install_dir_bin(rel)
1172
+ install_files collect_filenames_auto(), "#{config('bindir')}/#{rel}", 0755
1173
+ end
1174
+
1175
+ def install_dir_lib(rel)
1176
+ install_files ruby_scripts(), "#{config('rbdir')}/#{rel}", 0644
1177
+ end
1178
+
1179
+ def install_dir_ext(rel)
1180
+ return unless extdir?(curr_srcdir())
1181
+ install_files ruby_extentions('.'),
1182
+ "#{config('sodir')}/#{File.dirname(rel)}",
1183
+ 0555
1184
+ end
1185
+
1186
+ def install_dir_data(rel)
1187
+ install_files collect_filenames_auto(), "#{config('datadir')}/#{rel}", 0644
1188
+ end
1189
+
1190
+ def install_files(list, dest, mode)
1191
+ mkdir_p dest, @options['install-prefix']
1192
+ list.each do |fname|
1193
+ install fname, dest, mode, @options['install-prefix']
1194
+ end
1195
+ end
1196
+
1197
+ def ruby_scripts
1198
+ collect_filenames_auto().select {|n| /\.rb\z/ =~ n }
1199
+ end
1200
+
1201
+ # picked up many entries from cvs-1.11.1/src/ignore.c
1202
+ reject_patterns = %w(
1203
+ core RCSLOG tags TAGS .make.state
1204
+ .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb
1205
+ *~ *.old *.bak *.BAK *.orig *.rej _$* *$
1206
+
1207
+ *.org *.in .*
1208
+ )
1209
+ mapping = {
1210
+ '.' => '\.',
1211
+ '$' => '\$',
1212
+ '#' => '\#',
1213
+ '*' => '.*'
1214
+ }
1215
+ REJECT_PATTERNS = Regexp.new('\A(?:' +
1216
+ reject_patterns.map {|pat|
1217
+ pat.gsub(/[\.\$\#\*]/) {|ch| mapping[ch] }
1218
+ }.join('|') +
1219
+ ')\z')
1220
+
1221
+ def collect_filenames_auto
1222
+ mapdir((existfiles() - hookfiles()).reject {|fname|
1223
+ REJECT_PATTERNS =~ fname
1224
+ })
1225
+ end
1226
+
1227
+ def existfiles
1228
+ all_files_in(curr_srcdir()) | all_files_in('.')
1229
+ end
1230
+
1231
+ def hookfiles
1232
+ %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt|
1233
+ %w( config setup install clean ).map {|t| sprintf(fmt, t) }
1234
+ }.flatten
1235
+ end
1236
+
1237
+ def mapdir(filelist)
1238
+ filelist.map {|fname|
1239
+ if File.exist?(fname) # objdir
1240
+ fname
1241
+ else # srcdir
1242
+ File.join(curr_srcdir(), fname)
1243
+ end
1244
+ }
1245
+ end
1246
+
1247
+ def ruby_extentions(dir)
1248
+ Dir.open(dir) {|d|
1249
+ ents = d.select {|fname| /\.#{::Config::CONFIG['DLEXT']}\z/ =~ fname }
1250
+ if ents.empty?
1251
+ setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first"
1252
+ end
1253
+ return ents
1254
+ }
1255
+ end
1256
+
1257
+ #
1258
+ # TASK clean
1259
+ #
1260
+
1261
+ def exec_clean
1262
+ exec_task_traverse 'clean'
1263
+ rm_f ConfigTable.savefile
1264
+ rm_f 'InstalledFiles'
1265
+ end
1266
+
1267
+ def clean_dir_bin(rel)
1268
+ end
1269
+
1270
+ def clean_dir_lib(rel)
1271
+ end
1272
+
1273
+ def clean_dir_ext(rel)
1274
+ return unless extdir?(curr_srcdir())
1275
+ make 'clean' if File.file?('Makefile')
1276
+ end
1277
+
1278
+ def clean_dir_data(rel)
1279
+ end
1280
+
1281
+ #
1282
+ # TASK distclean
1283
+ #
1284
+
1285
+ def exec_distclean
1286
+ exec_task_traverse 'distclean'
1287
+ rm_f ConfigTable.savefile
1288
+ rm_f 'InstalledFiles'
1289
+ end
1290
+
1291
+ def distclean_dir_bin(rel)
1292
+ end
1293
+
1294
+ def distclean_dir_lib(rel)
1295
+ end
1296
+
1297
+ def distclean_dir_ext(rel)
1298
+ return unless extdir?(curr_srcdir())
1299
+ make 'distclean' if File.file?('Makefile')
1300
+ end
1301
+
1302
+ #
1303
+ # lib
1304
+ #
1305
+
1306
+ def exec_task_traverse(task)
1307
+ run_hook "pre-#{task}"
1308
+ FILETYPES.each do |type|
1309
+ if config('without-ext') == 'yes' and type == 'ext'
1310
+ $stderr.puts 'skipping ext/* by user option' if verbose?
1311
+ next
1312
+ end
1313
+ traverse task, type, "#{task}_dir_#{type}"
1314
+ end
1315
+ run_hook "post-#{task}"
1316
+ end
1317
+
1318
+ def traverse(task, rel, mid)
1319
+ dive_into(rel) {
1320
+ run_hook "pre-#{task}"
1321
+ __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '')
1322
+ all_dirs_in(curr_srcdir()).each do |d|
1323
+ traverse task, "#{rel}/#{d}", mid
1324
+ end
1325
+ run_hook "post-#{task}"
1326
+ }
1327
+ end
1328
+
1329
+ def dive_into(rel)
1330
+ return unless File.dir?("#{@srcdir}/#{rel}")
1331
+
1332
+ dir = File.basename(rel)
1333
+ Dir.mkdir dir unless File.dir?(dir)
1334
+ prevdir = Dir.pwd
1335
+ Dir.chdir dir
1336
+ $stderr.puts '---> ' + rel if verbose?
1337
+ @currdir = rel
1338
+ yield
1339
+ Dir.chdir prevdir
1340
+ $stderr.puts '<--- ' + rel if verbose?
1341
+ @currdir = File.dirname(rel)
1342
+ end
1343
+
1344
+ end
1345
+
1346
+
1347
+ if $0 == __FILE__
1348
+ begin
1349
+ if multipackage_install?
1350
+ ToplevelInstallerMulti.invoke
1351
+ else
1352
+ ToplevelInstaller.invoke
1353
+ end
1354
+ rescue SetupError
1355
+ raise if $DEBUG
1356
+ $stderr.puts $!.message
1357
+ $stderr.puts "Try 'ruby #{$0} --help' for detailed usage."
1358
+ exit 1
1359
+ end
1360
+ end