rbus 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/CHANGELOG.txt +9 -0
  2. data/COPYING.txt +341 -0
  3. data/HACKING.txt +104 -0
  4. data/Manifest.txt +58 -0
  5. data/README.txt +17 -0
  6. data/Rakefile +97 -0
  7. data/TUTORIAL.txt +303 -0
  8. data/bin/rbus-send +165 -0
  9. data/examples/async_rb_and_notify.rb +56 -0
  10. data/examples/async_rb_loop.rb +52 -0
  11. data/examples/glib_async_rb_loop.rb +57 -0
  12. data/examples/glib_rhythmbox.rb +54 -0
  13. data/examples/hal_device_info.rb +54 -0
  14. data/examples/listnames.rb +37 -0
  15. data/examples/network_manager_get_properties.rb +50 -0
  16. data/examples/notification_bubble.rb +46 -0
  17. data/examples/rhythmbox_print_playing_uri.rb +41 -0
  18. data/examples/rhythmbox_signal_print_playing.rb +54 -0
  19. data/examples/rhythmbox_start_service.rb +39 -0
  20. data/examples/rhythmbox_toggle_playing.rb +46 -0
  21. data/lib/rbus.rb +25 -0
  22. data/lib/rbus/auth/auth.rb +53 -0
  23. data/lib/rbus/auth/dbus_cookie_sha1.rb +66 -0
  24. data/lib/rbus/auth/dummy.rb +37 -0
  25. data/lib/rbus/auth/external.rb +34 -0
  26. data/lib/rbus/auth/state_machine.rb +168 -0
  27. data/lib/rbus/bus/bus.rb +101 -0
  28. data/lib/rbus/bus/proxy.rb +137 -0
  29. data/lib/rbus/bus/transport.rb +125 -0
  30. data/lib/rbus/default.rb +29 -0
  31. data/lib/rbus/etc/exception.rb +44 -0
  32. data/lib/rbus/etc/log.rb +100 -0
  33. data/lib/rbus/etc/types.rb +56 -0
  34. data/lib/rbus/etc/version.rb +34 -0
  35. data/lib/rbus/glib.rb +29 -0
  36. data/lib/rbus/mainloop/glib.rb +77 -0
  37. data/lib/rbus/mainloop/mainloop.rb +84 -0
  38. data/lib/rbus/mainloop/observers.rb +149 -0
  39. data/lib/rbus/mainloop/thread.rb +78 -0
  40. data/lib/rbus/message/constants.rb +51 -0
  41. data/lib/rbus/message/marshal.rb +139 -0
  42. data/lib/rbus/message/message.rb +110 -0
  43. data/lib/rbus/message/reader.rb +108 -0
  44. data/lib/rbus/message/serial_generator.rb +48 -0
  45. data/lib/rbus/message/unmarshal.rb +171 -0
  46. data/lib/rbus/message/writer.rb +69 -0
  47. data/setup.rb +1608 -0
  48. data/spec/auth_spec.rb +123 -0
  49. data/spec/bus_spec.rb +178 -0
  50. data/spec/helpers/bus_mocks.rb +64 -0
  51. data/spec/helpers/spec_helper.rb +24 -0
  52. data/spec/mainloop_spec.rb +74 -0
  53. data/spec/marshal_spec.rb +91 -0
  54. data/spec/message_spec.rb +61 -0
  55. data/spec/observers_spec.rb +28 -0
  56. data/spec/proxy_spec.rb +120 -0
  57. data/spec/transport_spec.rb +187 -0
  58. data/spec/unmarshal_spec.rb +186 -0
  59. metadata +118 -0
@@ -0,0 +1,108 @@
1
+ #--
2
+ #
3
+ # R-Bus is a native Ruby implementation of the D-Bus protocol.
4
+ # Copyright (C) 2007 Kristoffer Lundén (kristoffer.lunden@gmail.com)
5
+ #
6
+ # This program is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation; either version 2 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program; if not, write to the Free Software
18
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
+ # MA 02110-1301, USA. A copy of the GNU General Public License is
20
+ # also available at http://www.gnu.org/copyleft/gpl.html.
21
+ #
22
+ #++
23
+ #
24
+ module RBus
25
+ module Message
26
+
27
+ # Reads and parses a message
28
+ class Reader
29
+ include MessageTypes
30
+ include HeaderFields
31
+
32
+ def initialize(transport)
33
+ @transport = transport
34
+ end
35
+
36
+ # Read one message from the transport, unmarshal and return
37
+ def read_message
38
+ header = @transport.read(12)
39
+ return nil if header.nil?
40
+
41
+ endian = header.dbus_unmarshal('y')
42
+ header_info = header.dbus_unmarshal('yyyyuu', endian)
43
+
44
+ unless header_info[3] == VERSION::PROTOCOL
45
+ raise InvalidProtocolException, "Message with version '#{header_info[3]}'"
46
+ end
47
+
48
+ # Read first header length, then header fields
49
+ hl_raw = @transport.read(4)
50
+ header_length = hl_raw.dbus_unmarshal('u', endian)
51
+ hf_raw = @transport.read(header_length)
52
+
53
+ # Hack:
54
+ # The padding in the middle is because we do not start on a
55
+ # 8-byte boundary and the dbus_marshal method is doing the
56
+ # right thing... it's a TODO. :)
57
+ header_fields = (hl_raw + "\0\0\0\0" + hf_raw).dbus_unmarshal('a(yv)', endian)
58
+
59
+ # Go to 8-byte boundary
60
+ @transport.read(-(header+hl_raw+hf_raw).length & 7)
61
+
62
+ body = @transport.read(header_info[4])
63
+ #Log.debug(body)
64
+
65
+ klass =
66
+ case header_info[1]
67
+ when METHOD_CALL
68
+ MethodCall
69
+ when METHOD_RETURN
70
+ MethodReturn
71
+ when ERROR
72
+ Error
73
+ when SIGNAL
74
+ Signal
75
+ else
76
+ raise MessageException, "Invalid message type #{header_info[1]}"
77
+ end
78
+
79
+ message = klass.new
80
+ message.flags = header_info[2]
81
+
82
+ header_fields.each do |hf|
83
+ case hf[0]
84
+ when PATH
85
+ message.object_path = hf[1].object
86
+ when INTERFACE
87
+ message.interface = hf[1].object
88
+ when MEMBER
89
+ message.member = hf[1].object
90
+ when ERROR_NAME
91
+ # TODO
92
+ when REPLY_SERIAL
93
+ message.serial = hf[1].object
94
+ when DESTINATION
95
+ message.destination = hf[1].object
96
+ when SENDER
97
+ message.sender = hf[1].object
98
+ when SIGNATURE
99
+ message.signature = hf[1].object
100
+ message.arguments = body.dbus_unmarshal(hf[1].object, endian)
101
+ end
102
+ end
103
+ #Log.debug(message)
104
+ message
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,48 @@
1
+ #--
2
+ #
3
+ # R-Bus is a native Ruby implementation of the D-Bus protocol.
4
+ # Copyright (C) 2007 Kristoffer Lundén (kristoffer.lunden@gmail.com)
5
+ #
6
+ # This program is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation; either version 2 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program; if not, write to the Free Software
18
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
+ # MA 02110-1301, USA. A copy of the GNU General Public License is
20
+ # also available at http://www.gnu.org/copyleft/gpl.html.
21
+ #
22
+ #++
23
+ #
24
+ require 'monitor'
25
+ require 'singleton'
26
+
27
+ module RBus
28
+ module Message
29
+
30
+ # Generate a thread-safe, unique serial number for Messages.
31
+ class SerialGenerator
32
+ include MonitorMixin
33
+ include Singleton
34
+ def initialize
35
+ @serial = 0
36
+ super
37
+ end
38
+ def self.get_unique
39
+ instance.get_next
40
+ end
41
+ def get_next
42
+ synchronize do
43
+ @serial += 1
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,171 @@
1
+ #--
2
+ #
3
+ # R-Bus is a native Ruby implementation of the D-Bus protocol.
4
+ # Copyright (C) 2007 Kristoffer Lundén (kristoffer.lunden@gmail.com)
5
+ #
6
+ # This program is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation; either version 2 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program; if not, write to the Free Software
18
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
+ # MA 02110-1301, USA. A copy of the GNU General Public License is
20
+ # also available at http://www.gnu.org/copyleft/gpl.html.
21
+ #
22
+ #++
23
+ #
24
+ module RBus
25
+ module UnMarshalMixin
26
+
27
+ # Returns an array of unmarshalled values, or
28
+ # a single, unmarshalled value
29
+ # Somewhat analouge to String#unpack
30
+ def dbus_unmarshal(signature = nil, endian = ?l)
31
+ case endian
32
+ when ?l
33
+ uint16 = 'v'
34
+ uint32 = 'V'
35
+ double = 'E'
36
+ when ?B
37
+ uint16 = 'n'
38
+ uint32 = 'N'
39
+ double = 'G'
40
+ else
41
+ raise IllegalEndianException, "Illegal endian"
42
+ end
43
+
44
+ signature ||= dbus_signature
45
+
46
+ require 'stringio'
47
+ sio = StringIO.new(to_s)
48
+ tokens = StringIO.new(signature)
49
+
50
+ # Unsigned to signed:
51
+ un2s = lambda {|un, bits| (un >= 2**(bits-1)) ? un - 2**bits : un}
52
+
53
+ # Skip padding
54
+ skip = lambda {|alignment| sio.read(-sio.pos & alignment - 1)}
55
+
56
+ um = []
57
+
58
+ parser = lambda { |sig|
59
+ # puts sig
60
+ case sig
61
+ when 'b'
62
+ case parser.call('u')
63
+ when 1: true
64
+ when 0: false
65
+ else raise TypeException, 'Booleans must be 0 or 1'
66
+ end
67
+ when 'y'
68
+ sio.read(1).unpack('C')[0]
69
+ when 'n'
70
+ un2s.call(parser.call('q'),16)
71
+ when 'q'
72
+ skip.call(2)
73
+ sio.read(2).unpack(uint16)[0]
74
+ when 'u'
75
+ skip.call(4)
76
+ sio.read(4).unpack(uint32)[0]
77
+ when 'i'
78
+ un2s.call(parser.call('u'),32)
79
+ when 't'
80
+ case endian
81
+ when ?B
82
+ parser.call('u') + (parser.call('u') << 32)
83
+ when ?l
84
+ (parser.call('u') << 32) + parser.call('u')
85
+ end
86
+ when 'x'
87
+ case endian
88
+ when ?B
89
+ parser.call('u') + (parser.call('i') << 32)
90
+ when ?l
91
+ (parser.call('i') << 32) + parser.call('u')
92
+ end
93
+ when 'd'
94
+ skip.call(8)
95
+ sio.read(8).unpack(double)[0]
96
+ when 's'
97
+ skip.call(4)
98
+ s_length = sio.read(4).unpack(uint32)[0]
99
+ str = sio.read(s_length)
100
+ sio.read(1)
101
+ str
102
+ when 'o'
103
+ ObjectPath.new(parser.call('s'))
104
+ when 'g'
105
+ s_length = parser.call('y')
106
+ str = sio.read(s_length)
107
+ sio.read(1)
108
+ str
109
+ when 'v'
110
+ v_sig_length = sio.read(1).unpack('C')[0]
111
+ v_sig = sio.read(v_sig_length)
112
+ sio.read(1) # "\0"
113
+ old_t = tokens
114
+ tokens = StringIO.new(v_sig)
115
+ object = parser.call(tokens.read(1))
116
+ tokens = old_t
117
+ Variant.new(object, v_sig)
118
+ when 'a'
119
+ skip.call(4)
120
+ a_length = sio.read(4).unpack(uint32)[0]
121
+ next_token = tokens.read(1)
122
+ t_pos = tokens.pos
123
+ s_pos = sio.pos
124
+
125
+ if next_token == '{'
126
+ ret_val = {}
127
+ else
128
+ ret_val = []
129
+ end
130
+
131
+ while sio.pos - s_pos < a_length
132
+ tokens.pos = t_pos
133
+ if next_token == '{'
134
+ skip.call(8)
135
+ ret_val[parser.call(tokens.read(1))] = parser.call(tokens.read(1))
136
+ else
137
+ ret_val << parser.call(next_token)
138
+ end
139
+ end
140
+ if next_token == '{'
141
+ tokens.read(1) # '}'
142
+ end
143
+ ret_val
144
+ when '('
145
+ skip.call(8)
146
+ struct = []
147
+ loop do
148
+ next_token = tokens.read(1)
149
+ #puts next_token
150
+ break if next_token == ')'
151
+ struct << parser.call(next_token)
152
+ end
153
+ struct
154
+ else
155
+ raise NotImplementedError, "can't unmarshal '#{sig}'"
156
+ end
157
+ }
158
+
159
+ while sig = tokens.read(1)
160
+ um << parser.call(sig)
161
+ end
162
+ # Return value only, if single
163
+ um.length == 1 ? um.first : um
164
+ end
165
+ end
166
+ end
167
+
168
+ # TODO: Do not mix into all strings, extend specific instead
169
+ class String
170
+ include RBus::UnMarshalMixin
171
+ end
@@ -0,0 +1,69 @@
1
+ #--
2
+ #
3
+ # R-Bus is a native Ruby implementation of the D-Bus protocol.
4
+ # Copyright (C) 2007 Kristoffer Lundén (kristoffer.lunden@gmail.com)
5
+ #
6
+ # This program is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation; either version 2 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program; if not, write to the Free Software
18
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
+ # MA 02110-1301, USA. A copy of the GNU General Public License is
20
+ # also available at http://www.gnu.org/copyleft/gpl.html.
21
+ #
22
+ #++
23
+ #
24
+ module RBus
25
+ module Message
26
+
27
+ # Writes messages to the Bus via a Transport
28
+ class Writer
29
+ def initialize(transport)
30
+ @transport = transport
31
+ end
32
+
33
+ # Marshal and write message
34
+ def send_message(message)
35
+
36
+ # The marshaling always uses native encoding, so
37
+ # let's find it.
38
+ endian = ([1].pack('I') == "\1\0\0\0") ? ?l : ?B
39
+
40
+ body = ''
41
+ args = message.arguments
42
+
43
+ # Got arguments for the body? Marshal.
44
+ unless args.nil? || args.empty?
45
+ args.extend(MarshalMixin)
46
+ body = args.dbus_marshal(message.signature)
47
+ end
48
+
49
+ hf = message.header_fields
50
+
51
+ # Put together the header
52
+ h_array = [endian, message.type, message.flags] +
53
+ [VERSION::PROTOCOL, body.length, message.serial, hf]
54
+
55
+ # Marshal
56
+ h_array.extend(MarshalMixin)
57
+ header = h_array.dbus_marshal('yyyyuua(yv)')
58
+
59
+ # Pad
60
+ header += "\0" * (-header.length & 7)
61
+
62
+ Log.debug(header + body)
63
+
64
+ # Send
65
+ @transport.send(header + body)
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,1608 @@
1
+ #--
2
+ #
3
+ # R-Bus is a native Ruby implementation of the D-Bus protocol.
4
+ # Copyright (C) 2007 Kristoffer Lundén (kristoffer.lunden@gmail.com)
5
+ #
6
+ # This program is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation; either version 2 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program; if not, write to the Free Software
18
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
+ # MA 02110-1301, USA. A copy of the GNU General Public License is
20
+ # also available at http://www.gnu.org/copyleft/gpl.html.
21
+ #
22
+ #++
23
+ #
24
+ #
25
+ # setup.rb
26
+ #
27
+ # Copyright (c) 2000-2005 Minero Aoki
28
+ #
29
+ # This program is free software.
30
+ # You can distribute/modify this program under the terms of
31
+ # the GNU LGPL, Lesser General Public License version 2.1.
32
+ #
33
+
34
+ unless Enumerable.method_defined?(:map) # Ruby 1.4.6
35
+ module Enumerable
36
+ alias map collect
37
+ end
38
+ end
39
+
40
+ unless File.respond_to?(:read) # Ruby 1.6
41
+ def File.read(fname)
42
+ open(fname) {|f|
43
+ return f.read
44
+ }
45
+ end
46
+ end
47
+
48
+ unless Errno.const_defined?(:ENOTEMPTY) # Windows?
49
+ module Errno
50
+ class ENOTEMPTY
51
+ # We do not raise this exception, implementation is not needed.
52
+ end
53
+ end
54
+ end
55
+
56
+ def File.binread(fname)
57
+ open(fname, 'rb') {|f|
58
+ return f.read
59
+ }
60
+ end
61
+
62
+ # for corrupted Windows' stat(2)
63
+ def File.dir?(path)
64
+ File.directory?((path[-1,1] == '/') ? path : path + '/')
65
+ end
66
+
67
+
68
+ class ConfigTable
69
+
70
+ include Enumerable
71
+
72
+ def initialize(rbconfig)
73
+ @rbconfig = rbconfig
74
+ @items = []
75
+ @table = {}
76
+ # options
77
+ @install_prefix = nil
78
+ @config_opt = nil
79
+ @verbose = true
80
+ @no_harm = false
81
+ end
82
+
83
+ attr_accessor :install_prefix
84
+ attr_accessor :config_opt
85
+
86
+ attr_writer :verbose
87
+
88
+ def verbose?
89
+ @verbose
90
+ end
91
+
92
+ attr_writer :no_harm
93
+
94
+ def no_harm?
95
+ @no_harm
96
+ end
97
+
98
+ def [](key)
99
+ lookup(key).resolve(self)
100
+ end
101
+
102
+ def []=(key, val)
103
+ lookup(key).set val
104
+ end
105
+
106
+ def names
107
+ @items.map {|i| i.name }
108
+ end
109
+
110
+ def each(&block)
111
+ @items.each(&block)
112
+ end
113
+
114
+ def key?(name)
115
+ @table.key?(name)
116
+ end
117
+
118
+ def lookup(name)
119
+ @table[name] or setup_rb_error "no such config item: #{name}"
120
+ end
121
+
122
+ def add(item)
123
+ @items.push item
124
+ @table[item.name] = item
125
+ end
126
+
127
+ def remove(name)
128
+ item = lookup(name)
129
+ @items.delete_if {|i| i.name == name }
130
+ @table.delete_if {|name, i| i.name == name }
131
+ item
132
+ end
133
+
134
+ def load_script(path, inst = nil)
135
+ if File.file?(path)
136
+ MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path
137
+ end
138
+ end
139
+
140
+ def savefile
141
+ '.config'
142
+ end
143
+
144
+ def load_savefile
145
+ begin
146
+ File.foreach(savefile()) do |line|
147
+ k, v = *line.split(/=/, 2)
148
+ self[k] = v.strip
149
+ end
150
+ rescue Errno::ENOENT
151
+ setup_rb_error $!.message + "\n#{File.basename($0)} config first"
152
+ end
153
+ end
154
+
155
+ def save
156
+ @items.each {|i| i.value }
157
+ File.open(savefile(), 'w') {|f|
158
+ @items.each do |i|
159
+ f.printf "%s=%s\n", i.name, i.value if i.value? and i.value
160
+ end
161
+ }
162
+ end
163
+
164
+ def load_standard_entries
165
+ standard_entries(@rbconfig).each do |ent|
166
+ add ent
167
+ end
168
+ end
169
+
170
+ def standard_entries(rbconfig)
171
+ c = rbconfig
172
+
173
+ rubypath = File.join(c['bindir'], c['ruby_install_name'] + c['EXEEXT'])
174
+
175
+ major = c['MAJOR'].to_i
176
+ minor = c['MINOR'].to_i
177
+ teeny = c['TEENY'].to_i
178
+ version = "#{major}.#{minor}"
179
+
180
+ # ruby ver. >= 1.4.4?
181
+ newpath_p = ((major >= 2) or
182
+ ((major == 1) and
183
+ ((minor >= 5) or
184
+ ((minor == 4) and (teeny >= 4)))))
185
+
186
+ if c['rubylibdir']
187
+ # V > 1.6.3
188
+ libruby = "#{c['prefix']}/lib/ruby"
189
+ librubyver = c['rubylibdir']
190
+ librubyverarch = c['archdir']
191
+ siteruby = c['sitedir']
192
+ siterubyver = c['sitelibdir']
193
+ siterubyverarch = c['sitearchdir']
194
+ elsif newpath_p
195
+ # 1.4.4 <= V <= 1.6.3
196
+ libruby = "#{c['prefix']}/lib/ruby"
197
+ librubyver = "#{c['prefix']}/lib/ruby/#{version}"
198
+ librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}"
199
+ siteruby = c['sitedir']
200
+ siterubyver = "$siteruby/#{version}"
201
+ siterubyverarch = "$siterubyver/#{c['arch']}"
202
+ else
203
+ # V < 1.4.4
204
+ libruby = "#{c['prefix']}/lib/ruby"
205
+ librubyver = "#{c['prefix']}/lib/ruby/#{version}"
206
+ librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}"
207
+ siteruby = "#{c['prefix']}/lib/ruby/#{version}/site_ruby"
208
+ siterubyver = siteruby
209
+ siterubyverarch = "$siterubyver/#{c['arch']}"
210
+ end
211
+ parameterize = lambda {|path|
212
+ path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix')
213
+ }
214
+
215
+ if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg }
216
+ makeprog = arg.sub(/'/, '').split(/=/, 2)[1]
217
+ else
218
+ makeprog = 'make'
219
+ end
220
+
221
+ [
222
+ ExecItem.new('installdirs', 'std/site/home',
223
+ 'std: install under libruby; site: install under site_ruby; home: install under $HOME')\
224
+ {|val, table|
225
+ case val
226
+ when 'std'
227
+ table['rbdir'] = '$librubyver'
228
+ table['sodir'] = '$librubyverarch'
229
+ when 'site'
230
+ table['rbdir'] = '$siterubyver'
231
+ table['sodir'] = '$siterubyverarch'
232
+ when 'home'
233
+ setup_rb_error '$HOME was not set' unless ENV['HOME']
234
+ table['prefix'] = ENV['HOME']
235
+ table['rbdir'] = '$libdir/ruby'
236
+ table['sodir'] = '$libdir/ruby'
237
+ end
238
+ },
239
+ PathItem.new('prefix', 'path', c['prefix'],
240
+ 'path prefix of target environment'),
241
+ PathItem.new('bindir', 'path', parameterize.call(c['bindir']),
242
+ 'the directory for commands'),
243
+ PathItem.new('libdir', 'path', parameterize.call(c['libdir']),
244
+ 'the directory for libraries'),
245
+ PathItem.new('datadir', 'path', parameterize.call(c['datadir']),
246
+ 'the directory for shared data'),
247
+ PathItem.new('mandir', 'path', parameterize.call(c['mandir']),
248
+ 'the directory for man pages'),
249
+ PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']),
250
+ 'the directory for system configuration files'),
251
+ PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']),
252
+ 'the directory for local state data'),
253
+ PathItem.new('libruby', 'path', libruby,
254
+ 'the directory for ruby libraries'),
255
+ PathItem.new('librubyver', 'path', librubyver,
256
+ 'the directory for standard ruby libraries'),
257
+ PathItem.new('librubyverarch', 'path', librubyverarch,
258
+ 'the directory for standard ruby extensions'),
259
+ PathItem.new('siteruby', 'path', siteruby,
260
+ 'the directory for version-independent aux ruby libraries'),
261
+ PathItem.new('siterubyver', 'path', siterubyver,
262
+ 'the directory for aux ruby libraries'),
263
+ PathItem.new('siterubyverarch', 'path', siterubyverarch,
264
+ 'the directory for aux ruby binaries'),
265
+ PathItem.new('rbdir', 'path', '$siterubyver',
266
+ 'the directory for ruby scripts'),
267
+ PathItem.new('sodir', 'path', '$siterubyverarch',
268
+ 'the directory for ruby extentions'),
269
+ PathItem.new('rubypath', 'path', rubypath,
270
+ 'the path to set to #! line'),
271
+ ProgramItem.new('rubyprog', 'name', rubypath,
272
+ 'the ruby program using for installation'),
273
+ ProgramItem.new('makeprog', 'name', makeprog,
274
+ 'the make program to compile ruby extentions'),
275
+ SelectItem.new('shebang', 'all/ruby/never', 'ruby',
276
+ 'shebang line (#!) editing mode'),
277
+ BoolItem.new('without-ext', 'yes/no', 'no',
278
+ 'does not compile/install ruby extentions')
279
+ ]
280
+ end
281
+ private :standard_entries
282
+
283
+ def load_multipackage_entries
284
+ multipackage_entries().each do |ent|
285
+ add ent
286
+ end
287
+ end
288
+
289
+ def multipackage_entries
290
+ [
291
+ PackageSelectionItem.new('with', 'name,name...', '', 'ALL',
292
+ 'package names that you want to install'),
293
+ PackageSelectionItem.new('without', 'name,name...', '', 'NONE',
294
+ 'package names that you do not want to install')
295
+ ]
296
+ end
297
+ private :multipackage_entries
298
+
299
+ ALIASES = {
300
+ 'std-ruby' => 'librubyver',
301
+ 'stdruby' => 'librubyver',
302
+ 'rubylibdir' => 'librubyver',
303
+ 'archdir' => 'librubyverarch',
304
+ 'site-ruby-common' => 'siteruby', # For backward compatibility
305
+ 'site-ruby' => 'siterubyver', # For backward compatibility
306
+ 'bin-dir' => 'bindir',
307
+ 'bin-dir' => 'bindir',
308
+ 'rb-dir' => 'rbdir',
309
+ 'so-dir' => 'sodir',
310
+ 'data-dir' => 'datadir',
311
+ 'ruby-path' => 'rubypath',
312
+ 'ruby-prog' => 'rubyprog',
313
+ 'ruby' => 'rubyprog',
314
+ 'make-prog' => 'makeprog',
315
+ 'make' => 'makeprog'
316
+ }
317
+
318
+ def fixup
319
+ ALIASES.each do |ali, name|
320
+ @table[ali] = @table[name]
321
+ end
322
+ @items.freeze
323
+ @table.freeze
324
+ @options_re = /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/
325
+ end
326
+
327
+ def parse_opt(opt)
328
+ m = @options_re.match(opt) or setup_rb_error "config: unknown option #{opt}"
329
+ m.to_a[1,2]
330
+ end
331
+
332
+ def dllext
333
+ @rbconfig['DLEXT']
334
+ end
335
+
336
+ def value_config?(name)
337
+ lookup(name).value?
338
+ end
339
+
340
+ class Item
341
+ def initialize(name, template, default, desc)
342
+ @name = name.freeze
343
+ @template = template
344
+ @value = default
345
+ @default = default
346
+ @description = desc
347
+ end
348
+
349
+ attr_reader :name
350
+ attr_reader :description
351
+
352
+ attr_accessor :default
353
+ alias help_default default
354
+
355
+ def help_opt
356
+ "--#{@name}=#{@template}"
357
+ end
358
+
359
+ def value?
360
+ true
361
+ end
362
+
363
+ def value
364
+ @value
365
+ end
366
+
367
+ def resolve(table)
368
+ @value.gsub(%r<\$([^/]+)>) { table[$1] }
369
+ end
370
+
371
+ def set(val)
372
+ @value = check(val)
373
+ end
374
+
375
+ private
376
+
377
+ def check(val)
378
+ setup_rb_error "config: --#{name} requires argument" unless val
379
+ val
380
+ end
381
+ end
382
+
383
+ class BoolItem < Item
384
+ def config_type
385
+ 'bool'
386
+ end
387
+
388
+ def help_opt
389
+ "--#{@name}"
390
+ end
391
+
392
+ private
393
+
394
+ def check(val)
395
+ return 'yes' unless val
396
+ case val
397
+ when /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes'
398
+ when /\An(o)?\z/i, /\Af(alse)\z/i then 'no'
399
+ else
400
+ setup_rb_error "config: --#{@name} accepts only yes/no for argument"
401
+ end
402
+ end
403
+ end
404
+
405
+ class PathItem < Item
406
+ def config_type
407
+ 'path'
408
+ end
409
+
410
+ private
411
+
412
+ def check(path)
413
+ setup_rb_error "config: --#{@name} requires argument" unless path
414
+ path[0,1] == '$' ? path : File.expand_path(path)
415
+ end
416
+ end
417
+
418
+ class ProgramItem < Item
419
+ def config_type
420
+ 'program'
421
+ end
422
+ end
423
+
424
+ class SelectItem < Item
425
+ def initialize(name, selection, default, desc)
426
+ super
427
+ @ok = selection.split('/')
428
+ end
429
+
430
+ def config_type
431
+ 'select'
432
+ end
433
+
434
+ private
435
+
436
+ def check(val)
437
+ unless @ok.include?(val.strip)
438
+ setup_rb_error "config: use --#{@name}=#{@template} (#{val})"
439
+ end
440
+ val.strip
441
+ end
442
+ end
443
+
444
+ class ExecItem < Item
445
+ def initialize(name, selection, desc, &block)
446
+ super name, selection, nil, desc
447
+ @ok = selection.split('/')
448
+ @action = block
449
+ end
450
+
451
+ def config_type
452
+ 'exec'
453
+ end
454
+
455
+ def value?
456
+ false
457
+ end
458
+
459
+ def resolve(table)
460
+ setup_rb_error "$#{name()} wrongly used as option value"
461
+ end
462
+
463
+ undef set
464
+
465
+ def evaluate(val, table)
466
+ v = val.strip.downcase
467
+ unless @ok.include?(v)
468
+ setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})"
469
+ end
470
+ @action.call v, table
471
+ end
472
+ end
473
+
474
+ class PackageSelectionItem < Item
475
+ def initialize(name, template, default, help_default, desc)
476
+ super name, template, default, desc
477
+ @help_default = help_default
478
+ end
479
+
480
+ attr_reader :help_default
481
+
482
+ def config_type
483
+ 'package'
484
+ end
485
+
486
+ private
487
+
488
+ def check(val)
489
+ unless File.dir?("packages/#{val}")
490
+ setup_rb_error "config: no such package: #{val}"
491
+ end
492
+ val
493
+ end
494
+ end
495
+
496
+ class MetaConfigEnvironment
497
+ def initialize(config, installer)
498
+ @config = config
499
+ @installer = installer
500
+ end
501
+
502
+ def config_names
503
+ @config.names
504
+ end
505
+
506
+ def config?(name)
507
+ @config.key?(name)
508
+ end
509
+
510
+ def bool_config?(name)
511
+ @config.lookup(name).config_type == 'bool'
512
+ end
513
+
514
+ def path_config?(name)
515
+ @config.lookup(name).config_type == 'path'
516
+ end
517
+
518
+ def value_config?(name)
519
+ @config.lookup(name).config_type != 'exec'
520
+ end
521
+
522
+ def add_config(item)
523
+ @config.add item
524
+ end
525
+
526
+ def add_bool_config(name, default, desc)
527
+ @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc)
528
+ end
529
+
530
+ def add_path_config(name, default, desc)
531
+ @config.add PathItem.new(name, 'path', default, desc)
532
+ end
533
+
534
+ def set_config_default(name, default)
535
+ @config.lookup(name).default = default
536
+ end
537
+
538
+ def remove_config(name)
539
+ @config.remove(name)
540
+ end
541
+
542
+ # For only multipackage
543
+ def packages
544
+ raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer
545
+ @installer.packages
546
+ end
547
+
548
+ # For only multipackage
549
+ def declare_packages(list)
550
+ raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer
551
+ @installer.packages = list
552
+ end
553
+ end
554
+
555
+ end # class ConfigTable
556
+
557
+
558
+ # This module requires: #verbose?, #no_harm?
559
+ module FileOperations
560
+
561
+ def mkdir_p(dirname, prefix = nil)
562
+ dirname = prefix + File.expand_path(dirname) if prefix
563
+ $stderr.puts "mkdir -p #{dirname}" if verbose?
564
+ return if no_harm?
565
+
566
+ # Does not check '/', it's too abnormal.
567
+ dirs = File.expand_path(dirname).split(%r<(?=/)>)
568
+ if /\A[a-z]:\z/i =~ dirs[0]
569
+ disk = dirs.shift
570
+ dirs[0] = disk + dirs[0]
571
+ end
572
+ dirs.each_index do |idx|
573
+ path = dirs[0..idx].join('')
574
+ Dir.mkdir path unless File.dir?(path)
575
+ end
576
+ end
577
+
578
+ def rm_f(path)
579
+ $stderr.puts "rm -f #{path}" if verbose?
580
+ return if no_harm?
581
+ force_remove_file path
582
+ end
583
+
584
+ def rm_rf(path)
585
+ $stderr.puts "rm -rf #{path}" if verbose?
586
+ return if no_harm?
587
+ remove_tree path
588
+ end
589
+
590
+ def remove_tree(path)
591
+ if File.symlink?(path)
592
+ remove_file path
593
+ elsif File.dir?(path)
594
+ remove_tree0 path
595
+ else
596
+ force_remove_file path
597
+ end
598
+ end
599
+
600
+ def remove_tree0(path)
601
+ Dir.foreach(path) do |ent|
602
+ next if ent == '.'
603
+ next if ent == '..'
604
+ entpath = "#{path}/#{ent}"
605
+ if File.symlink?(entpath)
606
+ remove_file entpath
607
+ elsif File.dir?(entpath)
608
+ remove_tree0 entpath
609
+ else
610
+ force_remove_file entpath
611
+ end
612
+ end
613
+ begin
614
+ Dir.rmdir path
615
+ rescue Errno::ENOTEMPTY
616
+ # directory may not be empty
617
+ end
618
+ end
619
+
620
+ def move_file(src, dest)
621
+ force_remove_file dest
622
+ begin
623
+ File.rename src, dest
624
+ rescue
625
+ File.open(dest, 'wb') {|f|
626
+ f.write File.binread(src)
627
+ }
628
+ File.chmod File.stat(src).mode, dest
629
+ File.unlink src
630
+ end
631
+ end
632
+
633
+ def force_remove_file(path)
634
+ begin
635
+ remove_file path
636
+ rescue
637
+ end
638
+ end
639
+
640
+ def remove_file(path)
641
+ File.chmod 0777, path
642
+ File.unlink path
643
+ end
644
+
645
+ def install(from, dest, mode, prefix = nil)
646
+ $stderr.puts "install #{from} #{dest}" if verbose?
647
+ return if no_harm?
648
+
649
+ realdest = prefix ? prefix + File.expand_path(dest) : dest
650
+ realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest)
651
+ str = File.binread(from)
652
+ if diff?(str, realdest)
653
+ verbose_off {
654
+ rm_f realdest if File.exist?(realdest)
655
+ }
656
+ File.open(realdest, 'wb') {|f|
657
+ f.write str
658
+ }
659
+ File.chmod mode, realdest
660
+
661
+ File.open("#{objdir_root()}/InstalledFiles", 'a') {|f|
662
+ if prefix
663
+ f.puts realdest.sub(prefix, '')
664
+ else
665
+ f.puts realdest
666
+ end
667
+ }
668
+ end
669
+ end
670
+
671
+ def diff?(new_content, path)
672
+ return true unless File.exist?(path)
673
+ new_content != File.binread(path)
674
+ end
675
+
676
+ def command(*args)
677
+ $stderr.puts args.join(' ') if verbose?
678
+ system(*args) or raise RuntimeError,
679
+ "system(#{args.map{|a| a.inspect }.join(' ')}) failed"
680
+ end
681
+
682
+ def ruby(*args)
683
+ command config('rubyprog'), *args
684
+ end
685
+
686
+ def make(task = nil)
687
+ command(*[config('makeprog'), task].compact)
688
+ end
689
+
690
+ def extdir?(dir)
691
+ File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb")
692
+ end
693
+
694
+ def files_of(dir)
695
+ Dir.open(dir) {|d|
696
+ return d.select {|ent| File.file?("#{dir}/#{ent}") }
697
+ }
698
+ end
699
+
700
+ DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn )
701
+
702
+ def directories_of(dir)
703
+ Dir.open(dir) {|d|
704
+ return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT
705
+ }
706
+ end
707
+
708
+ end
709
+
710
+
711
+ # This module requires: #srcdir_root, #objdir_root, #relpath
712
+ module HookScriptAPI
713
+
714
+ def get_config(key)
715
+ @config[key]
716
+ end
717
+
718
+ alias config get_config
719
+
720
+ # obsolete: use metaconfig to change configuration
721
+ def set_config(key, val)
722
+ @config[key] = val
723
+ end
724
+
725
+ #
726
+ # srcdir/objdir (works only in the package directory)
727
+ #
728
+
729
+ def curr_srcdir
730
+ "#{srcdir_root()}/#{relpath()}"
731
+ end
732
+
733
+ def curr_objdir
734
+ "#{objdir_root()}/#{relpath()}"
735
+ end
736
+
737
+ def srcfile(path)
738
+ "#{curr_srcdir()}/#{path}"
739
+ end
740
+
741
+ def srcexist?(path)
742
+ File.exist?(srcfile(path))
743
+ end
744
+
745
+ def srcdirectory?(path)
746
+ File.dir?(srcfile(path))
747
+ end
748
+
749
+ def srcfile?(path)
750
+ File.file?(srcfile(path))
751
+ end
752
+
753
+ def srcentries(path = '.')
754
+ Dir.open("#{curr_srcdir()}/#{path}") {|d|
755
+ return d.to_a - %w(. ..)
756
+ }
757
+ end
758
+
759
+ def srcfiles(path = '.')
760
+ srcentries(path).select {|fname|
761
+ File.file?(File.join(curr_srcdir(), path, fname))
762
+ }
763
+ end
764
+
765
+ def srcdirectories(path = '.')
766
+ srcentries(path).select {|fname|
767
+ File.dir?(File.join(curr_srcdir(), path, fname))
768
+ }
769
+ end
770
+
771
+ end
772
+
773
+
774
+ class ToplevelInstaller
775
+
776
+ Version = '3.4.1'
777
+ Copyright = 'Copyright (c) 2000-2005 Minero Aoki'
778
+
779
+ TASKS = [
780
+ [ 'all', 'do config, setup, then install' ],
781
+ [ 'config', 'saves your configurations' ],
782
+ [ 'show', 'shows current configuration' ],
783
+ [ 'setup', 'compiles ruby extentions and others' ],
784
+ [ 'install', 'installs files' ],
785
+ [ 'test', 'run all tests in test/' ],
786
+ [ 'clean', "does `make clean' for each extention" ],
787
+ [ 'distclean',"does `make distclean' for each extention" ]
788
+ ]
789
+
790
+ def ToplevelInstaller.invoke
791
+ config = ConfigTable.new(load_rbconfig())
792
+ config.load_standard_entries
793
+ config.load_multipackage_entries if multipackage?
794
+ config.fixup
795
+ klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller)
796
+ klass.new(File.dirname($0), config).invoke
797
+ end
798
+
799
+ def ToplevelInstaller.multipackage?
800
+ File.dir?(File.dirname($0) + '/packages')
801
+ end
802
+
803
+ def ToplevelInstaller.load_rbconfig
804
+ if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg }
805
+ ARGV.delete(arg)
806
+ load File.expand_path(arg.split(/=/, 2)[1])
807
+ $".push 'rbconfig.rb'
808
+ else
809
+ require 'rbconfig'
810
+ end
811
+ ::Config::CONFIG
812
+ end
813
+
814
+ def initialize(ardir_root, config)
815
+ @ardir = File.expand_path(ardir_root)
816
+ @config = config
817
+ # cache
818
+ @valid_task_re = nil
819
+ end
820
+
821
+ def config(key)
822
+ @config[key]
823
+ end
824
+
825
+ def inspect
826
+ "#<#{self.class} #{__id__()}>"
827
+ end
828
+
829
+ def invoke
830
+ run_metaconfigs
831
+ case task = parsearg_global()
832
+ when nil, 'all'
833
+ parsearg_config
834
+ init_installers
835
+ exec_config
836
+ exec_setup
837
+ exec_install
838
+ else
839
+ case task
840
+ when 'config', 'test'
841
+ ;
842
+ when 'clean', 'distclean'
843
+ @config.load_savefile if File.exist?(@config.savefile)
844
+ else
845
+ @config.load_savefile
846
+ end
847
+ __send__ "parsearg_#{task}"
848
+ init_installers
849
+ __send__ "exec_#{task}"
850
+ end
851
+ end
852
+
853
+ def run_metaconfigs
854
+ @config.load_script "#{@ardir}/metaconfig"
855
+ end
856
+
857
+ def init_installers
858
+ @installer = Installer.new(@config, @ardir, File.expand_path('.'))
859
+ end
860
+
861
+ #
862
+ # Hook Script API bases
863
+ #
864
+
865
+ def srcdir_root
866
+ @ardir
867
+ end
868
+
869
+ def objdir_root
870
+ '.'
871
+ end
872
+
873
+ def relpath
874
+ '.'
875
+ end
876
+
877
+ #
878
+ # Option Parsing
879
+ #
880
+
881
+ def parsearg_global
882
+ while arg = ARGV.shift
883
+ case arg
884
+ when /\A\w+\z/
885
+ setup_rb_error "invalid task: #{arg}" unless valid_task?(arg)
886
+ return arg
887
+ when '-q', '--quiet'
888
+ @config.verbose = false
889
+ when '--verbose'
890
+ @config.verbose = true
891
+ when '--help'
892
+ print_usage $stdout
893
+ exit 0
894
+ when '--version'
895
+ puts "#{File.basename($0)} version #{Version}"
896
+ exit 0
897
+ when '--copyright'
898
+ puts Copyright
899
+ exit 0
900
+ else
901
+ setup_rb_error "unknown global option '#{arg}'"
902
+ end
903
+ end
904
+ nil
905
+ end
906
+
907
+ def valid_task?(t)
908
+ valid_task_re() =~ t
909
+ end
910
+
911
+ def valid_task_re
912
+ @valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/
913
+ end
914
+
915
+ def parsearg_no_options
916
+ unless ARGV.empty?
917
+ task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1)
918
+ setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}"
919
+ end
920
+ end
921
+
922
+ alias parsearg_show parsearg_no_options
923
+ alias parsearg_setup parsearg_no_options
924
+ alias parsearg_test parsearg_no_options
925
+ alias parsearg_clean parsearg_no_options
926
+ alias parsearg_distclean parsearg_no_options
927
+
928
+ def parsearg_config
929
+ evalopt = []
930
+ set = []
931
+ @config.config_opt = []
932
+ while i = ARGV.shift
933
+ if /\A--?\z/ =~ i
934
+ @config.config_opt = ARGV.dup
935
+ break
936
+ end
937
+ name, value = *@config.parse_opt(i)
938
+ if @config.value_config?(name)
939
+ @config[name] = value
940
+ else
941
+ evalopt.push [name, value]
942
+ end
943
+ set.push name
944
+ end
945
+ evalopt.each do |name, value|
946
+ @config.lookup(name).evaluate value, @config
947
+ end
948
+ # Check if configuration is valid
949
+ set.each do |n|
950
+ @config[n] if @config.value_config?(n)
951
+ end
952
+ end
953
+
954
+ def parsearg_install
955
+ @config.no_harm = false
956
+ @config.install_prefix = ''
957
+ while a = ARGV.shift
958
+ case a
959
+ when '--no-harm'
960
+ @config.no_harm = true
961
+ when /\A--prefix=/
962
+ path = a.split(/=/, 2)[1]
963
+ path = File.expand_path(path) unless path[0,1] == '/'
964
+ @config.install_prefix = path
965
+ else
966
+ setup_rb_error "install: unknown option #{a}"
967
+ end
968
+ end
969
+ end
970
+
971
+ def print_usage(out)
972
+ out.puts 'Typical Installation Procedure:'
973
+ out.puts " $ ruby #{File.basename $0} config"
974
+ out.puts " $ ruby #{File.basename $0} setup"
975
+ out.puts " # ruby #{File.basename $0} install (may require root privilege)"
976
+ out.puts
977
+ out.puts 'Detailed Usage:'
978
+ out.puts " ruby #{File.basename $0} <global option>"
979
+ out.puts " ruby #{File.basename $0} [<global options>] <task> [<task options>]"
980
+
981
+ fmt = " %-24s %s\n"
982
+ out.puts
983
+ out.puts 'Global options:'
984
+ out.printf fmt, '-q,--quiet', 'suppress message outputs'
985
+ out.printf fmt, ' --verbose', 'output messages verbosely'
986
+ out.printf fmt, ' --help', 'print this message'
987
+ out.printf fmt, ' --version', 'print version and quit'
988
+ out.printf fmt, ' --copyright', 'print copyright and quit'
989
+ out.puts
990
+ out.puts 'Tasks:'
991
+ TASKS.each do |name, desc|
992
+ out.printf fmt, name, desc
993
+ end
994
+
995
+ fmt = " %-24s %s [%s]\n"
996
+ out.puts
997
+ out.puts 'Options for CONFIG or ALL:'
998
+ @config.each do |item|
999
+ out.printf fmt, item.help_opt, item.description, item.help_default
1000
+ end
1001
+ out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's"
1002
+ out.puts
1003
+ out.puts 'Options for INSTALL:'
1004
+ out.printf fmt, '--no-harm', 'only display what to do if given', 'off'
1005
+ out.printf fmt, '--prefix=path', 'install path prefix', ''
1006
+ out.puts
1007
+ end
1008
+
1009
+ #
1010
+ # Task Handlers
1011
+ #
1012
+
1013
+ def exec_config
1014
+ @installer.exec_config
1015
+ @config.save # must be final
1016
+ end
1017
+
1018
+ def exec_setup
1019
+ @installer.exec_setup
1020
+ end
1021
+
1022
+ def exec_install
1023
+ @installer.exec_install
1024
+ end
1025
+
1026
+ def exec_test
1027
+ @installer.exec_test
1028
+ end
1029
+
1030
+ def exec_show
1031
+ @config.each do |i|
1032
+ printf "%-20s %s\n", i.name, i.value if i.value?
1033
+ end
1034
+ end
1035
+
1036
+ def exec_clean
1037
+ @installer.exec_clean
1038
+ end
1039
+
1040
+ def exec_distclean
1041
+ @installer.exec_distclean
1042
+ end
1043
+
1044
+ end # class ToplevelInstaller
1045
+
1046
+
1047
+ class ToplevelInstallerMulti < ToplevelInstaller
1048
+
1049
+ include FileOperations
1050
+
1051
+ def initialize(ardir_root, config)
1052
+ super
1053
+ @packages = directories_of("#{@ardir}/packages")
1054
+ raise 'no package exists' if @packages.empty?
1055
+ @root_installer = Installer.new(@config, @ardir, File.expand_path('.'))
1056
+ end
1057
+
1058
+ def run_metaconfigs
1059
+ @config.load_script "#{@ardir}/metaconfig", self
1060
+ @packages.each do |name|
1061
+ @config.load_script "#{@ardir}/packages/#{name}/metaconfig"
1062
+ end
1063
+ end
1064
+
1065
+ attr_reader :packages
1066
+
1067
+ def packages=(list)
1068
+ raise 'package list is empty' if list.empty?
1069
+ list.each do |name|
1070
+ raise "directory packages/#{name} does not exist"\
1071
+ unless File.dir?("#{@ardir}/packages/#{name}")
1072
+ end
1073
+ @packages = list
1074
+ end
1075
+
1076
+ def init_installers
1077
+ @installers = {}
1078
+ @packages.each do |pack|
1079
+ @installers[pack] = Installer.new(@config,
1080
+ "#{@ardir}/packages/#{pack}",
1081
+ "packages/#{pack}")
1082
+ end
1083
+ with = extract_selection(config('with'))
1084
+ without = extract_selection(config('without'))
1085
+ @selected = @installers.keys.select {|name|
1086
+ (with.empty? or with.include?(name)) \
1087
+ and not without.include?(name)
1088
+ }
1089
+ end
1090
+
1091
+ def extract_selection(list)
1092
+ a = list.split(/,/)
1093
+ a.each do |name|
1094
+ setup_rb_error "no such package: #{name}" unless @installers.key?(name)
1095
+ end
1096
+ a
1097
+ end
1098
+
1099
+ def print_usage(f)
1100
+ super
1101
+ f.puts 'Inluded packages:'
1102
+ f.puts ' ' + @packages.sort.join(' ')
1103
+ f.puts
1104
+ end
1105
+
1106
+ #
1107
+ # Task Handlers
1108
+ #
1109
+
1110
+ def exec_config
1111
+ run_hook 'pre-config'
1112
+ each_selected_installers {|inst| inst.exec_config }
1113
+ run_hook 'post-config'
1114
+ @config.save # must be final
1115
+ end
1116
+
1117
+ def exec_setup
1118
+ run_hook 'pre-setup'
1119
+ each_selected_installers {|inst| inst.exec_setup }
1120
+ run_hook 'post-setup'
1121
+ end
1122
+
1123
+ def exec_install
1124
+ run_hook 'pre-install'
1125
+ each_selected_installers {|inst| inst.exec_install }
1126
+ run_hook 'post-install'
1127
+ end
1128
+
1129
+ def exec_test
1130
+ run_hook 'pre-test'
1131
+ each_selected_installers {|inst| inst.exec_test }
1132
+ run_hook 'post-test'
1133
+ end
1134
+
1135
+ def exec_clean
1136
+ rm_f @config.savefile
1137
+ run_hook 'pre-clean'
1138
+ each_selected_installers {|inst| inst.exec_clean }
1139
+ run_hook 'post-clean'
1140
+ end
1141
+
1142
+ def exec_distclean
1143
+ rm_f @config.savefile
1144
+ run_hook 'pre-distclean'
1145
+ each_selected_installers {|inst| inst.exec_distclean }
1146
+ run_hook 'post-distclean'
1147
+ end
1148
+
1149
+ #
1150
+ # lib
1151
+ #
1152
+
1153
+ def each_selected_installers
1154
+ Dir.mkdir 'packages' unless File.dir?('packages')
1155
+ @selected.each do |pack|
1156
+ $stderr.puts "Processing the package `#{pack}' ..." if verbose?
1157
+ Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}")
1158
+ Dir.chdir "packages/#{pack}"
1159
+ yield @installers[pack]
1160
+ Dir.chdir '../..'
1161
+ end
1162
+ end
1163
+
1164
+ def run_hook(id)
1165
+ @root_installer.run_hook id
1166
+ end
1167
+
1168
+ # module FileOperations requires this
1169
+ def verbose?
1170
+ @config.verbose?
1171
+ end
1172
+
1173
+ # module FileOperations requires this
1174
+ def no_harm?
1175
+ @config.no_harm?
1176
+ end
1177
+
1178
+ end # class ToplevelInstallerMulti
1179
+
1180
+
1181
+ class Installer
1182
+
1183
+ FILETYPES = %w( bin lib ext data conf man )
1184
+
1185
+ include FileOperations
1186
+ include HookScriptAPI
1187
+
1188
+ def initialize(config, srcroot, objroot)
1189
+ @config = config
1190
+ @srcdir = File.expand_path(srcroot)
1191
+ @objdir = File.expand_path(objroot)
1192
+ @currdir = '.'
1193
+ end
1194
+
1195
+ def inspect
1196
+ "#<#{self.class} #{File.basename(@srcdir)}>"
1197
+ end
1198
+
1199
+ def noop(rel)
1200
+ end
1201
+
1202
+ #
1203
+ # Hook Script API base methods
1204
+ #
1205
+
1206
+ def srcdir_root
1207
+ @srcdir
1208
+ end
1209
+
1210
+ def objdir_root
1211
+ @objdir
1212
+ end
1213
+
1214
+ def relpath
1215
+ @currdir
1216
+ end
1217
+
1218
+ #
1219
+ # Config Access
1220
+ #
1221
+
1222
+ # module FileOperations requires this
1223
+ def verbose?
1224
+ @config.verbose?
1225
+ end
1226
+
1227
+ # module FileOperations requires this
1228
+ def no_harm?
1229
+ @config.no_harm?
1230
+ end
1231
+
1232
+ def verbose_off
1233
+ begin
1234
+ save, @config.verbose = @config.verbose?, false
1235
+ yield
1236
+ ensure
1237
+ @config.verbose = save
1238
+ end
1239
+ end
1240
+
1241
+ #
1242
+ # TASK config
1243
+ #
1244
+
1245
+ def exec_config
1246
+ exec_task_traverse 'config'
1247
+ end
1248
+
1249
+ alias config_dir_bin noop
1250
+ alias config_dir_lib noop
1251
+
1252
+ def config_dir_ext(rel)
1253
+ extconf if extdir?(curr_srcdir())
1254
+ end
1255
+
1256
+ alias config_dir_data noop
1257
+ alias config_dir_conf noop
1258
+ alias config_dir_man noop
1259
+
1260
+ def extconf
1261
+ ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt
1262
+ end
1263
+
1264
+ #
1265
+ # TASK setup
1266
+ #
1267
+
1268
+ def exec_setup
1269
+ exec_task_traverse 'setup'
1270
+ end
1271
+
1272
+ def setup_dir_bin(rel)
1273
+ files_of(curr_srcdir()).each do |fname|
1274
+ update_shebang_line "#{curr_srcdir()}/#{fname}"
1275
+ end
1276
+ end
1277
+
1278
+ alias setup_dir_lib noop
1279
+
1280
+ def setup_dir_ext(rel)
1281
+ make if extdir?(curr_srcdir())
1282
+ end
1283
+
1284
+ alias setup_dir_data noop
1285
+ alias setup_dir_conf noop
1286
+ alias setup_dir_man noop
1287
+
1288
+ def update_shebang_line(path)
1289
+ return if no_harm?
1290
+ return if config('shebang') == 'never'
1291
+ old = Shebang.load(path)
1292
+ if old
1293
+ $stderr.puts "warning: #{path}: Shebang line includes too many args. It is not portable and your program may not work." if old.args.size > 1
1294
+ new = new_shebang(old)
1295
+ return if new.to_s == old.to_s
1296
+ else
1297
+ return unless config('shebang') == 'all'
1298
+ new = Shebang.new(config('rubypath'))
1299
+ end
1300
+ $stderr.puts "updating shebang: #{File.basename(path)}" if verbose?
1301
+ open_atomic_writer(path) {|output|
1302
+ File.open(path, 'rb') {|f|
1303
+ f.gets if old # discard
1304
+ output.puts new.to_s
1305
+ output.print f.read
1306
+ }
1307
+ }
1308
+ end
1309
+
1310
+ def new_shebang(old)
1311
+ if /\Aruby/ =~ File.basename(old.cmd)
1312
+ Shebang.new(config('rubypath'), old.args)
1313
+ elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby'
1314
+ Shebang.new(config('rubypath'), old.args[1..-1])
1315
+ else
1316
+ return old unless config('shebang') == 'all'
1317
+ Shebang.new(config('rubypath'))
1318
+ end
1319
+ end
1320
+
1321
+ def open_atomic_writer(path, &block)
1322
+ tmpfile = File.basename(path) + '.tmp'
1323
+ begin
1324
+ File.open(tmpfile, 'wb', &block)
1325
+ File.rename tmpfile, File.basename(path)
1326
+ ensure
1327
+ File.unlink tmpfile if File.exist?(tmpfile)
1328
+ end
1329
+ end
1330
+
1331
+ class Shebang
1332
+ def Shebang.load(path)
1333
+ line = nil
1334
+ File.open(path) {|f|
1335
+ line = f.gets
1336
+ }
1337
+ return nil unless /\A#!/ =~ line
1338
+ parse(line)
1339
+ end
1340
+
1341
+ def Shebang.parse(line)
1342
+ cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ')
1343
+ new(cmd, args)
1344
+ end
1345
+
1346
+ def initialize(cmd, args = [])
1347
+ @cmd = cmd
1348
+ @args = args
1349
+ end
1350
+
1351
+ attr_reader :cmd
1352
+ attr_reader :args
1353
+
1354
+ def to_s
1355
+ "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}")
1356
+ end
1357
+ end
1358
+
1359
+ #
1360
+ # TASK install
1361
+ #
1362
+
1363
+ def exec_install
1364
+ rm_f 'InstalledFiles'
1365
+ exec_task_traverse 'install'
1366
+ end
1367
+
1368
+ def install_dir_bin(rel)
1369
+ install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755
1370
+ end
1371
+
1372
+ def install_dir_lib(rel)
1373
+ install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644
1374
+ end
1375
+
1376
+ def install_dir_ext(rel)
1377
+ return unless extdir?(curr_srcdir())
1378
+ install_files rubyextentions('.'),
1379
+ "#{config('sodir')}/#{File.dirname(rel)}",
1380
+ 0555
1381
+ end
1382
+
1383
+ def install_dir_data(rel)
1384
+ install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644
1385
+ end
1386
+
1387
+ def install_dir_conf(rel)
1388
+ # FIXME: should not remove current config files
1389
+ # (rename previous file to .old/.org)
1390
+ install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644
1391
+ end
1392
+
1393
+ def install_dir_man(rel)
1394
+ install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644
1395
+ end
1396
+
1397
+ def install_files(list, dest, mode)
1398
+ mkdir_p dest, @config.install_prefix
1399
+ list.each do |fname|
1400
+ install fname, dest, mode, @config.install_prefix
1401
+ end
1402
+ end
1403
+
1404
+ def libfiles
1405
+ glob_reject(%w(*.y *.output), targetfiles())
1406
+ end
1407
+
1408
+ def rubyextentions(dir)
1409
+ ents = glob_select("*.#{@config.dllext}", targetfiles())
1410
+ if ents.empty?
1411
+ setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first"
1412
+ end
1413
+ ents
1414
+ end
1415
+
1416
+ def targetfiles
1417
+ mapdir(existfiles() - hookfiles())
1418
+ end
1419
+
1420
+ def mapdir(ents)
1421
+ ents.map {|ent|
1422
+ if File.exist?(ent)
1423
+ then ent # objdir
1424
+ else "#{curr_srcdir()}/#{ent}" # srcdir
1425
+ end
1426
+ }
1427
+ end
1428
+
1429
+ # picked up many entries from cvs-1.11.1/src/ignore.c
1430
+ JUNK_FILES = %w(
1431
+ core RCSLOG tags TAGS .make.state
1432
+ .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb
1433
+ *~ *.old *.bak *.BAK *.orig *.rej _$* *$
1434
+
1435
+ *.org *.in .*
1436
+ )
1437
+
1438
+ def existfiles
1439
+ glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.')))
1440
+ end
1441
+
1442
+ def hookfiles
1443
+ %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt|
1444
+ %w( config setup install clean ).map {|t| sprintf(fmt, t) }
1445
+ }.flatten
1446
+ end
1447
+
1448
+ def glob_select(pat, ents)
1449
+ re = globs2re([pat])
1450
+ ents.select {|ent| re =~ ent }
1451
+ end
1452
+
1453
+ def glob_reject(pats, ents)
1454
+ re = globs2re(pats)
1455
+ ents.reject {|ent| re =~ ent }
1456
+ end
1457
+
1458
+ GLOB2REGEX = {
1459
+ '.' => '\.',
1460
+ '$' => '\$',
1461
+ '#' => '\#',
1462
+ '*' => '.*'
1463
+ }
1464
+
1465
+ def globs2re(pats)
1466
+ /\A(?:#{
1467
+ pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|')
1468
+ })\z/
1469
+ end
1470
+
1471
+ #
1472
+ # TASK test
1473
+ #
1474
+
1475
+ TESTDIR = 'test'
1476
+
1477
+ def exec_test
1478
+ unless File.directory?('test')
1479
+ $stderr.puts 'no test in this package' if verbose?
1480
+ return
1481
+ end
1482
+ $stderr.puts 'Running tests...' if verbose?
1483
+ begin
1484
+ require 'test/unit'
1485
+ rescue LoadError
1486
+ setup_rb_error 'test/unit cannot loaded. You need Ruby 1.8 or later to invoke this task.'
1487
+ end
1488
+ runner = Test::Unit::AutoRunner.new(true)
1489
+ runner.to_run << TESTDIR
1490
+ runner.run
1491
+ end
1492
+
1493
+ #
1494
+ # TASK clean
1495
+ #
1496
+
1497
+ def exec_clean
1498
+ exec_task_traverse 'clean'
1499
+ rm_f @config.savefile
1500
+ rm_f 'InstalledFiles'
1501
+ end
1502
+
1503
+ alias clean_dir_bin noop
1504
+ alias clean_dir_lib noop
1505
+ alias clean_dir_data noop
1506
+ alias clean_dir_conf noop
1507
+ alias clean_dir_man noop
1508
+
1509
+ def clean_dir_ext(rel)
1510
+ return unless extdir?(curr_srcdir())
1511
+ make 'clean' if File.file?('Makefile')
1512
+ end
1513
+
1514
+ #
1515
+ # TASK distclean
1516
+ #
1517
+
1518
+ def exec_distclean
1519
+ exec_task_traverse 'distclean'
1520
+ rm_f @config.savefile
1521
+ rm_f 'InstalledFiles'
1522
+ end
1523
+
1524
+ alias distclean_dir_bin noop
1525
+ alias distclean_dir_lib noop
1526
+
1527
+ def distclean_dir_ext(rel)
1528
+ return unless extdir?(curr_srcdir())
1529
+ make 'distclean' if File.file?('Makefile')
1530
+ end
1531
+
1532
+ alias distclean_dir_data noop
1533
+ alias distclean_dir_conf noop
1534
+ alias distclean_dir_man noop
1535
+
1536
+ #
1537
+ # Traversing
1538
+ #
1539
+
1540
+ def exec_task_traverse(task)
1541
+ run_hook "pre-#{task}"
1542
+ FILETYPES.each do |type|
1543
+ if type == 'ext' and config('without-ext') == 'yes'
1544
+ $stderr.puts 'skipping ext/* by user option' if verbose?
1545
+ next
1546
+ end
1547
+ traverse task, type, "#{task}_dir_#{type}"
1548
+ end
1549
+ run_hook "post-#{task}"
1550
+ end
1551
+
1552
+ def traverse(task, rel, mid)
1553
+ dive_into(rel) {
1554
+ run_hook "pre-#{task}"
1555
+ __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '')
1556
+ directories_of(curr_srcdir()).each do |d|
1557
+ traverse task, "#{rel}/#{d}", mid
1558
+ end
1559
+ run_hook "post-#{task}"
1560
+ }
1561
+ end
1562
+
1563
+ def dive_into(rel)
1564
+ return unless File.dir?("#{@srcdir}/#{rel}")
1565
+
1566
+ dir = File.basename(rel)
1567
+ Dir.mkdir dir unless File.dir?(dir)
1568
+ prevdir = Dir.pwd
1569
+ Dir.chdir dir
1570
+ $stderr.puts '---> ' + rel if verbose?
1571
+ @currdir = rel
1572
+ yield
1573
+ Dir.chdir prevdir
1574
+ $stderr.puts '<--- ' + rel if verbose?
1575
+ @currdir = File.dirname(rel)
1576
+ end
1577
+
1578
+ def run_hook(id)
1579
+ path = [ "#{curr_srcdir()}/#{id}",
1580
+ "#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) }
1581
+ return unless path
1582
+ begin
1583
+ instance_eval File.read(path), path, 1
1584
+ rescue
1585
+ raise if $DEBUG
1586
+ setup_rb_error "hook #{path} failed:\n" + $!.message
1587
+ end
1588
+ end
1589
+
1590
+ end # class Installer
1591
+
1592
+
1593
+ class SetupError < StandardError; end
1594
+
1595
+ def setup_rb_error(msg)
1596
+ raise SetupError, msg
1597
+ end
1598
+
1599
+ if $0 == __FILE__
1600
+ begin
1601
+ ToplevelInstaller.invoke
1602
+ rescue SetupError
1603
+ raise if $DEBUG
1604
+ $stderr.puts $!.message
1605
+ $stderr.puts "Try 'ruby #{$0} --help' for detailed usage."
1606
+ exit 1
1607
+ end
1608
+ end