rbus 0.1.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 (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