]>
Commit | Line | Data |
---|---|---|
acbc6b09 | 1 | # |
2 | # setup.rb | |
3 | # | |
4 | # Copyright (c) 2000-2004 Minero Aoki | |
5 | # | |
6 | # This program is free software. | |
7 | # You can distribute/modify this program under the terms of | |
8 | # the GNU Lesser General Public License version 2.1. | |
9 | # | |
10 | ||
11 | # | |
12 | # For backward compatibility | |
13 | # | |
14 | ||
15 | unless Enumerable.method_defined?(:map) | |
16 | module Enumerable | |
17 | alias map collect | |
18 | end | |
19 | end | |
20 | ||
21 | unless Enumerable.method_defined?(:detect) | |
22 | module Enumerable | |
23 | alias detect find | |
24 | end | |
25 | end | |
26 | ||
27 | unless Enumerable.method_defined?(:select) | |
28 | module Enumerable | |
29 | alias select find_all | |
30 | end | |
31 | end | |
32 | ||
33 | unless Enumerable.method_defined?(:reject) | |
34 | module Enumerable | |
35 | def reject | |
36 | result = [] | |
37 | each do |i| | |
38 | result.push i unless yield(i) | |
39 | end | |
40 | result | |
41 | end | |
42 | end | |
43 | end | |
44 | ||
45 | unless Enumerable.method_defined?(:inject) | |
46 | module Enumerable | |
47 | def inject(result) | |
48 | each do |i| | |
49 | result = yield(result, i) | |
50 | end | |
51 | result | |
52 | end | |
53 | end | |
54 | end | |
55 | ||
56 | unless Enumerable.method_defined?(:any?) | |
57 | module Enumerable | |
58 | def any? | |
59 | each do |i| | |
60 | return true if yield(i) | |
61 | end | |
62 | false | |
63 | end | |
64 | end | |
65 | end | |
66 | ||
67 | unless File.respond_to?(:read) | |
68 | def File.read(fname) | |
69 | open(fname) {|f| | |
70 | return f.read | |
71 | } | |
72 | end | |
73 | end | |
74 | ||
75 | # | |
76 | # Application independent utilities | |
77 | # | |
78 | ||
79 | def File.binread(fname) | |
80 | open(fname, 'rb') {|f| | |
81 | return f.read | |
82 | } | |
83 | end | |
84 | ||
85 | # for corrupted windows stat(2) | |
86 | def File.dir?(path) | |
87 | File.directory?((path[-1,1] == '/') ? path : path + '/') | |
88 | end | |
89 | ||
90 | # | |
91 | # Config | |
92 | # | |
93 | ||
94 | if arg = ARGV.detect{|arg| /\A--rbconfig=/ =~ arg } | |
95 | ARGV.delete(arg) | |
96 | require arg.split(/=/, 2)[1] | |
97 | $".push 'rbconfig.rb' | |
98 | else | |
99 | require 'rbconfig' | |
100 | end | |
101 | ||
102 | def multipackage_install? | |
103 | FileTest.directory?(File.dirname($0) + '/packages') | |
104 | end | |
105 | ||
106 | ||
107 | class ConfigTable | |
108 | ||
109 | c = ::Config::CONFIG | |
110 | ||
111 | rubypath = c['bindir'] + '/' + c['ruby_install_name'] | |
112 | ||
113 | major = c['MAJOR'].to_i | |
114 | minor = c['MINOR'].to_i | |
115 | teeny = c['TEENY'].to_i | |
116 | version = "#{major}.#{minor}" | |
117 | ||
118 | # ruby ver. >= 1.4.4? | |
119 | newpath_p = ((major >= 2) or | |
120 | ((major == 1) and | |
121 | ((minor >= 5) or | |
122 | ((minor == 4) and (teeny >= 4))))) | |
123 | ||
124 | subprefix = lambda {|path| | |
125 | path.sub(/\A#{Regexp.quote(c['prefix'])}/o, '$prefix') | |
126 | } | |
127 | ||
128 | if c['rubylibdir'] | |
129 | # V < 1.6.3 | |
130 | stdruby = subprefix.call(c['rubylibdir']) | |
131 | siteruby = subprefix.call(c['sitedir']) | |
132 | versite = subprefix.call(c['sitelibdir']) | |
133 | sodir = subprefix.call(c['sitearchdir']) | |
134 | elsif newpath_p | |
135 | # 1.4.4 <= V <= 1.6.3 | |
136 | stdruby = "$prefix/lib/ruby/#{version}" | |
137 | siteruby = subprefix.call(c['sitedir']) | |
138 | versite = siteruby + '/' + version | |
139 | sodir = "$site-ruby/#{c['arch']}" | |
140 | else | |
141 | # V < 1.4.4 | |
142 | stdruby = "$prefix/lib/ruby/#{version}" | |
143 | siteruby = "$prefix/lib/ruby/#{version}/site_ruby" | |
144 | versite = siteruby | |
145 | sodir = "$site-ruby/#{c['arch']}" | |
146 | end | |
147 | ||
148 | if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg } | |
149 | makeprog = arg.sub(/'/, '').split(/=/, 2)[1] | |
150 | else | |
151 | makeprog = 'make' | |
152 | end | |
153 | ||
154 | common_descripters = [ | |
155 | [ 'prefix', [ c['prefix'], | |
156 | 'path', | |
157 | 'path prefix of target environment' ] ], | |
158 | [ 'std-ruby', [ stdruby, | |
159 | 'path', | |
160 | 'the directory for standard ruby libraries' ] ], | |
161 | [ 'site-ruby-common', [ siteruby, | |
162 | 'path', | |
163 | 'the directory for version-independent non-standard ruby libraries' ] ], | |
164 | [ 'site-ruby', [ versite, | |
165 | 'path', | |
166 | 'the directory for non-standard ruby libraries' ] ], | |
167 | [ 'bin-dir', [ '$prefix/bin', | |
168 | 'path', | |
169 | 'the directory for commands' ] ], | |
170 | [ 'rb-dir', [ '$site-ruby', | |
171 | 'path', | |
172 | 'the directory for ruby scripts' ] ], | |
173 | [ 'so-dir', [ sodir, | |
174 | 'path', | |
175 | 'the directory for ruby extentions' ] ], | |
176 | [ 'data-dir', [ '$prefix/share', | |
177 | 'path', | |
178 | 'the directory for shared data' ] ], | |
179 | [ 'ruby-path', [ rubypath, | |
180 | 'path', | |
181 | 'path to set to #! line' ] ], | |
182 | [ 'ruby-prog', [ rubypath, | |
183 | 'name', | |
184 | 'the ruby program using for installation' ] ], | |
185 | [ 'make-prog', [ makeprog, | |
186 | 'name', | |
187 | 'the make program to compile ruby extentions' ] ], | |
188 | [ 'without-ext', [ 'no', | |
189 | 'yes/no', | |
190 | 'does not compile/install ruby extentions' ] ] | |
191 | ] | |
192 | multipackage_descripters = [ | |
193 | [ 'with', [ '', | |
194 | 'name,name...', | |
195 | 'package names that you want to install', | |
196 | 'ALL' ] ], | |
197 | [ 'without', [ '', | |
198 | 'name,name...', | |
199 | 'package names that you do not want to install', | |
200 | 'NONE' ] ] | |
201 | ] | |
202 | if multipackage_install? | |
203 | DESCRIPTER = common_descripters + multipackage_descripters | |
204 | else | |
205 | DESCRIPTER = common_descripters | |
206 | end | |
207 | ||
208 | SAVE_FILE = 'config.save' | |
209 | ||
210 | def ConfigTable.each_name(&block) | |
211 | keys().each(&block) | |
212 | end | |
213 | ||
214 | def ConfigTable.keys | |
215 | DESCRIPTER.map {|name, *dummy| name } | |
216 | end | |
217 | ||
218 | def ConfigTable.each_definition(&block) | |
219 | DESCRIPTER.each(&block) | |
220 | end | |
221 | ||
222 | def ConfigTable.get_entry(name) | |
223 | name, ent = DESCRIPTER.assoc(name) | |
224 | ent | |
225 | end | |
226 | ||
227 | def ConfigTable.get_entry!(name) | |
228 | get_entry(name) or raise ArgumentError, "no such config: #{name}" | |
229 | end | |
230 | ||
231 | def ConfigTable.add_entry(name, vals) | |
232 | ConfigTable::DESCRIPTER.push [name,vals] | |
233 | end | |
234 | ||
235 | def ConfigTable.remove_entry(name) | |
236 | get_entry(name) or raise ArgumentError, "no such config: #{name}" | |
237 | DESCRIPTER.delete_if {|n, arr| n == name } | |
238 | end | |
239 | ||
240 | def ConfigTable.config_key?(name) | |
241 | get_entry(name) ? true : false | |
242 | end | |
243 | ||
244 | def ConfigTable.bool_config?(name) | |
245 | ent = get_entry(name) or return false | |
246 | ent[1] == 'yes/no' | |
247 | end | |
248 | ||
249 | def ConfigTable.value_config?(name) | |
250 | ent = get_entry(name) or return false | |
251 | ent[1] != 'yes/no' | |
252 | end | |
253 | ||
254 | def ConfigTable.path_config?(name) | |
255 | ent = get_entry(name) or return false | |
256 | ent[1] == 'path' | |
257 | end | |
258 | ||
259 | ||
260 | class << self | |
261 | alias newobj new | |
262 | end | |
263 | ||
264 | def ConfigTable.new | |
265 | c = newobj() | |
266 | c.initialize_from_table | |
267 | c | |
268 | end | |
269 | ||
270 | def ConfigTable.load | |
271 | c = newobj() | |
272 | c.initialize_from_file | |
273 | c | |
274 | end | |
275 | ||
276 | def initialize_from_table | |
277 | @table = {} | |
278 | DESCRIPTER.each do |k, (default, vname, desc, default2)| | |
279 | @table[k] = default | |
280 | end | |
281 | end | |
282 | ||
283 | def initialize_from_file | |
284 | raise InstallError, "#{File.basename $0} config first"\ | |
285 | unless File.file?(SAVE_FILE) | |
286 | @table = {} | |
287 | File.foreach(SAVE_FILE) do |line| | |
288 | k, v = line.split(/=/, 2) | |
289 | @table[k] = v.strip | |
290 | end | |
291 | end | |
292 | ||
293 | def save | |
294 | File.open(SAVE_FILE, 'w') {|f| | |
295 | @table.each do |k, v| | |
296 | f.printf "%s=%s\n", k, v if v | |
297 | end | |
298 | } | |
299 | end | |
300 | ||
301 | def []=(k, v) | |
302 | raise InstallError, "unknown config option #{k}"\ | |
303 | unless ConfigTable.config_key?(k) | |
304 | @table[k] = v | |
305 | end | |
306 | ||
307 | def [](key) | |
308 | return nil unless @table[key] | |
309 | @table[key].gsub(%r<\$([^/]+)>) { self[$1] } | |
310 | end | |
311 | ||
312 | def set_raw(key, val) | |
313 | @table[key] = val | |
314 | end | |
315 | ||
316 | def get_raw(key) | |
317 | @table[key] | |
318 | end | |
319 | ||
320 | end | |
321 | ||
322 | ||
323 | module MetaConfigAPI | |
324 | ||
325 | def eval_file_ifexist(fname) | |
326 | instance_eval File.read(fname), fname, 1 if File.file?(fname) | |
327 | end | |
328 | ||
329 | def config_names | |
330 | ConfigTable.keys | |
331 | end | |
332 | ||
333 | def config?(name) | |
334 | ConfigTable.config_key?(name) | |
335 | end | |
336 | ||
337 | def bool_config?(name) | |
338 | ConfigTable.bool_config?(name) | |
339 | end | |
340 | ||
341 | def value_config?(name) | |
342 | ConfigTable.value_config?(name) | |
343 | end | |
344 | ||
345 | def path_config?(name) | |
346 | ConfigTable.path_config?(name) | |
347 | end | |
348 | ||
349 | def add_config(name, argname, default, desc) | |
350 | ConfigTable.add_entry name,[default,argname,desc] | |
351 | end | |
352 | ||
353 | def add_path_config(name, default, desc) | |
354 | add_config name, 'path', default, desc | |
355 | end | |
356 | ||
357 | def add_bool_config(name, default, desc) | |
358 | add_config name, 'yes/no', default ? 'yes' : 'no', desc | |
359 | end | |
360 | ||
361 | def set_config_default(name, default) | |
362 | if bool_config?(name) | |
363 | ConfigTable.get_entry!(name)[0] = (default ? 'yes' : 'no') | |
364 | else | |
365 | ConfigTable.get_entry!(name)[0] = default | |
366 | end | |
367 | end | |
368 | ||
369 | def remove_config(name) | |
370 | ent = ConfigTable.get_entry(name) | |
371 | ConfigTable.remove_entry name | |
372 | ent | |
373 | end | |
374 | ||
375 | end | |
376 | ||
377 | # | |
378 | # File Operations | |
379 | # | |
380 | ||
381 | module FileOperations | |
382 | ||
383 | def mkdir_p(dirname, prefix = nil) | |
384 | dirname = prefix + dirname if prefix | |
385 | $stderr.puts "mkdir -p #{dirname}" if verbose? | |
386 | return if no_harm? | |
387 | ||
388 | # does not check '/'... it's too abnormal case | |
389 | dirs = dirname.split(%r<(?=/)>) | |
390 | if /\A[a-z]:\z/i =~ dirs[0] | |
391 | disk = dirs.shift | |
392 | dirs[0] = disk + dirs[0] | |
393 | end | |
394 | dirs.each_index do |idx| | |
395 | path = dirs[0..idx].join('') | |
396 | Dir.mkdir path unless File.dir?(path) | |
397 | end | |
398 | end | |
399 | ||
400 | def rm_f(fname) | |
401 | $stderr.puts "rm -f #{fname}" if verbose? | |
402 | return if no_harm? | |
403 | ||
404 | if File.exist?(fname) or File.symlink?(fname) | |
405 | File.chmod 0777, fname | |
406 | File.unlink fname | |
407 | end | |
408 | end | |
409 | ||
410 | def rm_rf(dn) | |
411 | $stderr.puts "rm -rf #{dn}" if verbose? | |
412 | return if no_harm? | |
413 | ||
414 | Dir.chdir dn | |
415 | Dir.foreach('.') do |fn| | |
416 | next if fn == '.' | |
417 | next if fn == '..' | |
418 | if File.dir?(fn) | |
419 | verbose_off { | |
420 | rm_rf fn | |
421 | } | |
422 | else | |
423 | verbose_off { | |
424 | rm_f fn | |
425 | } | |
426 | end | |
427 | end | |
428 | Dir.chdir '..' | |
429 | Dir.rmdir dn | |
430 | end | |
431 | ||
432 | def move_file(src, dest) | |
433 | File.unlink dest if File.exist?(dest) | |
434 | begin | |
435 | File.rename src, dest | |
436 | rescue | |
437 | File.open(dest, 'wb') {|f| f.write File.binread(src) } | |
438 | File.chmod File.stat(src).mode, dest | |
439 | File.unlink src | |
440 | end | |
441 | end | |
442 | ||
443 | def install(from, dest, mode, prefix = nil) | |
444 | $stderr.puts "install #{from} #{dest}" if verbose? | |
445 | return if no_harm? | |
446 | ||
447 | realdest = prefix + dest if prefix | |
448 | realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest) | |
449 | str = File.binread(from) | |
450 | if diff?(str, realdest) | |
451 | verbose_off { | |
452 | rm_f realdest if File.exist?(realdest) | |
453 | } | |
454 | File.open(realdest, 'wb') {|f| | |
455 | f.write str | |
456 | } | |
457 | File.chmod mode, realdest | |
458 | ||
459 | File.open("#{objdir_root()}/InstalledFiles", 'a') {|f| | |
460 | if prefix | |
461 | f.puts realdest.sub(prefix, '') | |
462 | else | |
463 | f.puts realdest | |
464 | end | |
465 | } | |
466 | end | |
467 | end | |
468 | ||
469 | def diff?(new_content, path) | |
470 | return true unless File.exist?(path) | |
471 | new_content != File.binread(path) | |
472 | end | |
473 | ||
474 | def command(str) | |
475 | $stderr.puts str if verbose? | |
476 | system str or raise RuntimeError, "'system #{str}' failed" | |
477 | end | |
478 | ||
479 | def ruby(str) | |
480 | command config('ruby-prog') + ' ' + str | |
481 | end | |
482 | ||
483 | def make(task = '') | |
484 | command config('make-prog') + ' ' + task | |
485 | end | |
486 | ||
487 | def extdir?(dir) | |
488 | File.exist?(dir + '/MANIFEST') | |
489 | end | |
490 | ||
491 | def all_files_in(dirname) | |
492 | Dir.open(dirname) {|d| | |
493 | return d.select {|ent| File.file?("#{dirname}/#{ent}") } | |
494 | } | |
495 | end | |
496 | ||
497 | REJECT_DIRS = %w( | |
498 | CVS SCCS RCS CVS.adm | |
499 | ) | |
500 | ||
501 | def all_dirs_in(dirname) | |
502 | Dir.open(dirname) {|d| | |
503 | return d.select {|n| File.dir?("#{dirname}/#{n}") } - %w(. ..) - REJECT_DIRS | |
504 | } | |
505 | end | |
506 | ||
507 | end | |
508 | ||
509 | # | |
510 | # Main Installer | |
511 | # | |
512 | ||
513 | class InstallError < StandardError; end | |
514 | ||
515 | ||
516 | module HookUtils | |
517 | ||
518 | def run_hook(name) | |
519 | try_run_hook "#{curr_srcdir()}/#{name}" or | |
520 | try_run_hook "#{curr_srcdir()}/#{name}.rb" | |
521 | end | |
522 | ||
523 | def try_run_hook(fname) | |
524 | return false unless File.file?(fname) | |
525 | begin | |
526 | instance_eval File.read(fname), fname, 1 | |
527 | rescue | |
528 | raise InstallError, "hook #{fname} failed:\n" + $!.message | |
529 | end | |
530 | true | |
531 | end | |
532 | ||
533 | end | |
534 | ||
535 | ||
536 | module HookScriptAPI | |
537 | ||
538 | def get_config(key) | |
539 | @config[key] | |
540 | end | |
541 | ||
542 | alias config get_config | |
543 | ||
544 | def set_config(key, val) | |
545 | @config[key] = val | |
546 | end | |
547 | ||
548 | # | |
549 | # srcdir/objdir (works only in the package directory) | |
550 | # | |
551 | ||
552 | #abstract srcdir_root | |
553 | #abstract objdir_root | |
554 | #abstract relpath | |
555 | ||
556 | def curr_srcdir | |
557 | "#{srcdir_root()}/#{relpath()}" | |
558 | end | |
559 | ||
560 | def curr_objdir | |
561 | "#{objdir_root()}/#{relpath()}" | |
562 | end | |
563 | ||
564 | def srcfile(path) | |
565 | "#{curr_srcdir()}/#{path}" | |
566 | end | |
567 | ||
568 | def srcexist?(path) | |
569 | File.exist?(srcfile(path)) | |
570 | end | |
571 | ||
572 | def srcdirectory?(path) | |
573 | File.dir?(srcfile(path)) | |
574 | end | |
575 | ||
576 | def srcfile?(path) | |
577 | File.file? srcfile(path) | |
578 | end | |
579 | ||
580 | def srcentries(path = '.') | |
581 | Dir.open("#{curr_srcdir()}/#{path}") {|d| | |
582 | return d.to_a - %w(. ..) | |
583 | } | |
584 | end | |
585 | ||
586 | def srcfiles(path = '.') | |
587 | srcentries(path).select {|fname| | |
588 | File.file?(File.join(curr_srcdir(), path, fname)) | |
589 | } | |
590 | end | |
591 | ||
592 | def srcdirectories(path = '.') | |
593 | srcentries(path).select {|fname| | |
594 | File.dir?(File.join(curr_srcdir(), path, fname)) | |
595 | } | |
596 | end | |
597 | ||
598 | end | |
599 | ||
600 | ||
601 | class ToplevelInstaller | |
602 | ||
603 | Version = '3.2.4' | |
604 | Copyright = 'Copyright (c) 2000-2004 Minero Aoki' | |
605 | ||
606 | TASKS = [ | |
607 | [ 'config', 'saves your configurations' ], | |
608 | [ 'show', 'shows current configuration' ], | |
609 | [ 'setup', 'compiles ruby extentions and others' ], | |
610 | [ 'install', 'installs files' ], | |
611 | [ 'clean', "does `make clean' for each extention" ], | |
612 | [ 'distclean',"does `make distclean' for each extention" ] | |
613 | ] | |
614 | ||
615 | def ToplevelInstaller.invoke | |
616 | instance().invoke | |
617 | end | |
618 | ||
619 | @singleton = nil | |
620 | ||
621 | def ToplevelInstaller.instance | |
622 | @singleton ||= new(File.dirname($0)) | |
623 | @singleton | |
624 | end | |
625 | ||
626 | include MetaConfigAPI | |
627 | ||
628 | def initialize(ardir_root) | |
629 | @config = nil | |
630 | @options = { 'verbose' => true } | |
631 | @ardir = File.expand_path(ardir_root) | |
632 | end | |
633 | ||
634 | def inspect | |
635 | "#<#{self.class} #{__id__()}>" | |
636 | end | |
637 | ||
638 | def invoke | |
639 | run_metaconfigs | |
640 | task = parsearg_global() | |
641 | @config = load_config(task) | |
642 | __send__ "parsearg_#{task}" | |
643 | init_installers | |
644 | __send__ "exec_#{task}" | |
645 | end | |
646 | ||
647 | def run_metaconfigs | |
648 | eval_file_ifexist "#{@ardir}/metaconfig" | |
649 | end | |
650 | ||
651 | def load_config(task) | |
652 | case task | |
653 | when 'config' | |
654 | ConfigTable.new | |
655 | when 'clean', 'distclean' | |
656 | if File.exist?('config.save') | |
657 | then ConfigTable.load | |
658 | else ConfigTable.new | |
659 | end | |
660 | else | |
661 | ConfigTable.load | |
662 | end | |
663 | end | |
664 | ||
665 | def init_installers | |
666 | @installer = Installer.new(@config, @options, @ardir, File.expand_path('.')) | |
667 | end | |
668 | ||
669 | # | |
670 | # Hook Script API bases | |
671 | # | |
672 | ||
673 | def srcdir_root | |
674 | @ardir | |
675 | end | |
676 | ||
677 | def objdir_root | |
678 | '.' | |
679 | end | |
680 | ||
681 | def relpath | |
682 | '.' | |
683 | end | |
684 | ||
685 | # | |
686 | # Option Parsing | |
687 | # | |
688 | ||
689 | def parsearg_global | |
690 | valid_task = /\A(?:#{TASKS.map {|task,desc| task }.join '|'})\z/ | |
691 | ||
692 | while arg = ARGV.shift | |
693 | case arg | |
694 | when /\A\w+\z/ | |
695 | raise InstallError, "invalid task: #{arg}" unless valid_task =~ arg | |
696 | return arg | |
697 | ||
698 | when '-q', '--quiet' | |
699 | @options['verbose'] = false | |
700 | ||
701 | when '--verbose' | |
702 | @options['verbose'] = true | |
703 | ||
704 | when '-h', '--help' | |
705 | print_usage $stdout | |
706 | exit 0 | |
707 | ||
708 | when '-v', '--version' | |
709 | puts "#{File.basename($0)} version #{Version}" | |
710 | exit 0 | |
711 | ||
712 | when '--copyright' | |
713 | puts Copyright | |
714 | exit 0 | |
715 | ||
716 | else | |
717 | raise InstallError, "unknown global option '#{arg}'" | |
718 | end | |
719 | end | |
720 | ||
721 | raise InstallError, <<EOS | |
722 | No task or global option given. | |
723 | Typical installation procedure is: | |
724 | $ ruby #{File.basename($0)} config | |
725 | $ ruby #{File.basename($0)} setup | |
726 | # ruby #{File.basename($0)} install (may require root privilege) | |
727 | EOS | |
728 | end | |
729 | ||
730 | ||
731 | def parsearg_no_options | |
732 | raise InstallError, "#{task}: unknown options: #{ARGV.join ' '}"\ | |
733 | unless ARGV.empty? | |
734 | end | |
735 | ||
736 | alias parsearg_show parsearg_no_options | |
737 | alias parsearg_setup parsearg_no_options | |
738 | alias parsearg_clean parsearg_no_options | |
739 | alias parsearg_distclean parsearg_no_options | |
740 | ||
741 | def parsearg_config | |
742 | re = /\A--(#{ConfigTable.keys.join '|'})(?:=(.*))?\z/ | |
743 | @options['config-opt'] = [] | |
744 | ||
745 | while i = ARGV.shift | |
746 | if /\A--?\z/ =~ i | |
747 | @options['config-opt'] = ARGV.dup | |
748 | break | |
749 | end | |
750 | m = re.match(i) or raise InstallError, "config: unknown option #{i}" | |
751 | name, value = m.to_a[1,2] | |
752 | if value | |
753 | if ConfigTable.bool_config?(name) | |
754 | raise InstallError, "config: --#{name} allows only yes/no for argument"\ | |
755 | unless /\A(y(es)?|n(o)?|t(rue)?|f(alse))\z/i =~ value | |
756 | value = (/\Ay(es)?|\At(rue)/i =~ value) ? 'yes' : 'no' | |
757 | end | |
758 | else | |
759 | raise InstallError, "config: --#{name} requires argument"\ | |
760 | unless ConfigTable.bool_config?(name) | |
761 | value = 'yes' | |
762 | end | |
763 | @config[name] = value | |
764 | end | |
765 | end | |
766 | ||
767 | def parsearg_install | |
768 | @options['no-harm'] = false | |
769 | @options['install-prefix'] = '' | |
770 | while a = ARGV.shift | |
771 | case a | |
772 | when /\A--no-harm\z/ | |
773 | @options['no-harm'] = true | |
774 | when /\A--prefix=(.*)\z/ | |
775 | path = $1 | |
776 | path = File.expand_path(path) unless path[0,1] == '/' | |
777 | @options['install-prefix'] = path | |
778 | else | |
779 | raise InstallError, "install: unknown option #{a}" | |
780 | end | |
781 | end | |
782 | end | |
783 | ||
784 | def print_usage(out) | |
785 | out.puts 'Typical Installation Procedure:' | |
786 | out.puts " $ ruby #{File.basename $0} config" | |
787 | out.puts " $ ruby #{File.basename $0} setup" | |
788 | out.puts " # ruby #{File.basename $0} install (may require root privilege)" | |
789 | out.puts | |
790 | out.puts 'Detailed Usage:' | |
791 | out.puts " ruby #{File.basename $0} <global option>" | |
792 | out.puts " ruby #{File.basename $0} [<global options>] <task> [<task options>]" | |
793 | ||
794 | fmt = " %-20s %s\n" | |
795 | out.puts | |
796 | out.puts 'Global options:' | |
797 | out.printf fmt, '-q,--quiet', 'suppress message outputs' | |
798 | out.printf fmt, ' --verbose', 'output messages verbosely' | |
799 | out.printf fmt, '-h,--help', 'print this message' | |
800 | out.printf fmt, '-v,--version', 'print version and quit' | |
801 | out.printf fmt, ' --copyright', 'print copyright and quit' | |
802 | ||
803 | out.puts | |
804 | out.puts 'Tasks:' | |
805 | TASKS.each do |name, desc| | |
806 | out.printf " %-10s %s\n", name, desc | |
807 | end | |
808 | ||
809 | out.puts | |
810 | out.puts 'Options for config:' | |
811 | ConfigTable.each_definition do |name, (default, arg, desc, default2)| | |
812 | out.printf " %-20s %s [%s]\n", | |
813 | '--'+ name + (ConfigTable.bool_config?(name) ? '' : '='+arg), | |
814 | desc, | |
815 | default2 || default | |
816 | end | |
817 | out.printf " %-20s %s [%s]\n", | |
818 | '--rbconfig=path', 'your rbconfig.rb to load', "running ruby's" | |
819 | ||
820 | out.puts | |
821 | out.puts 'Options for install:' | |
822 | out.printf " %-20s %s [%s]\n", | |
823 | '--no-harm', 'only display what to do if given', 'off' | |
824 | out.printf " %-20s %s [%s]\n", | |
825 | '--prefix', 'install path prefix', '$prefix' | |
826 | ||
827 | out.puts | |
828 | end | |
829 | ||
830 | # | |
831 | # Task Handlers | |
832 | # | |
833 | ||
834 | def exec_config | |
835 | @installer.exec_config | |
836 | @config.save # must be final | |
837 | end | |
838 | ||
839 | def exec_setup | |
840 | @installer.exec_setup | |
841 | end | |
842 | ||
843 | def exec_install | |
844 | @installer.exec_install | |
845 | end | |
846 | ||
847 | def exec_show | |
848 | ConfigTable.each_name do |k| | |
849 | v = @config.get_raw(k) | |
850 | if not v or v.empty? | |
851 | v = '(not specified)' | |
852 | end | |
853 | printf "%-10s %s\n", k, v | |
854 | end | |
855 | end | |
856 | ||
857 | def exec_clean | |
858 | @installer.exec_clean | |
859 | end | |
860 | ||
861 | def exec_distclean | |
862 | @installer.exec_distclean | |
863 | end | |
864 | ||
865 | end | |
866 | ||
867 | ||
868 | class ToplevelInstallerMulti < ToplevelInstaller | |
869 | ||
870 | include HookUtils | |
871 | include HookScriptAPI | |
872 | include FileOperations | |
873 | ||
874 | def initialize(ardir) | |
875 | super | |
876 | @packages = all_dirs_in("#{@ardir}/packages") | |
877 | raise 'no package exists' if @packages.empty? | |
878 | end | |
879 | ||
880 | def run_metaconfigs | |
881 | eval_file_ifexist "#{@ardir}/metaconfig" | |
882 | @packages.each do |name| | |
883 | eval_file_ifexist "#{@ardir}/packages/#{name}/metaconfig" | |
884 | end | |
885 | end | |
886 | ||
887 | def init_installers | |
888 | @installers = {} | |
889 | @packages.each do |pack| | |
890 | @installers[pack] = Installer.new(@config, @options, | |
891 | "#{@ardir}/packages/#{pack}", | |
892 | "packages/#{pack}") | |
893 | end | |
894 | ||
895 | with = extract_selection(config('with')) | |
896 | without = extract_selection(config('without')) | |
897 | @selected = @installers.keys.select {|name| | |
898 | (with.empty? or with.include?(name)) \ | |
899 | and not without.include?(name) | |
900 | } | |
901 | end | |
902 | ||
903 | def extract_selection(list) | |
904 | a = list.split(/,/) | |
905 | a.each do |name| | |
906 | raise InstallError, "no such package: #{name}" \ | |
907 | unless @installers.key?(name) | |
908 | end | |
909 | a | |
910 | end | |
911 | ||
912 | def print_usage(f) | |
913 | super | |
914 | f.puts 'Inluded packages:' | |
915 | f.puts ' ' + @packages.sort.join(' ') | |
916 | f.puts | |
917 | end | |
918 | ||
919 | # | |
920 | # multi-package metaconfig API | |
921 | # | |
922 | ||
923 | attr_reader :packages | |
924 | ||
925 | def declare_packages(list) | |
926 | raise 'package list is empty' if list.empty? | |
927 | list.each do |name| | |
928 | raise "directory packages/#{name} does not exist"\ | |
929 | unless File.dir?("#{@ardir}/packages/#{name}") | |
930 | end | |
931 | @packages = list | |
932 | end | |
933 | ||
934 | # | |
935 | # Task Handlers | |
936 | # | |
937 | ||
938 | def exec_config | |
939 | run_hook 'pre-config' | |
940 | each_selected_installers {|inst| inst.exec_config } | |
941 | run_hook 'post-config' | |
942 | @config.save # must be final | |
943 | end | |
944 | ||
945 | def exec_setup | |
946 | run_hook 'pre-setup' | |
947 | each_selected_installers {|inst| inst.exec_setup } | |
948 | run_hook 'post-setup' | |
949 | end | |
950 | ||
951 | def exec_install | |
952 | run_hook 'pre-install' | |
953 | each_selected_installers {|inst| inst.exec_install } | |
954 | run_hook 'post-install' | |
955 | end | |
956 | ||
957 | def exec_clean | |
958 | rm_f 'config.save' | |
959 | run_hook 'pre-clean' | |
960 | each_selected_installers {|inst| inst.exec_clean } | |
961 | run_hook 'post-clean' | |
962 | end | |
963 | ||
964 | def exec_distclean | |
965 | rm_f 'config.save' | |
966 | run_hook 'pre-distclean' | |
967 | each_selected_installers {|inst| inst.exec_distclean } | |
968 | run_hook 'post-distclean' | |
969 | end | |
970 | ||
971 | # | |
972 | # lib | |
973 | # | |
974 | ||
975 | def each_selected_installers | |
976 | Dir.mkdir 'packages' unless File.dir?('packages') | |
977 | @selected.each do |pack| | |
978 | $stderr.puts "Processing the package `#{pack}' ..." if @options['verbose'] | |
979 | Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}") | |
980 | Dir.chdir "packages/#{pack}" | |
981 | yield @installers[pack] | |
982 | Dir.chdir '../..' | |
983 | end | |
984 | end | |
985 | ||
986 | def verbose? | |
987 | @options['verbose'] | |
988 | end | |
989 | ||
990 | def no_harm? | |
991 | @options['no-harm'] | |
992 | end | |
993 | ||
994 | end | |
995 | ||
996 | ||
997 | class Installer | |
998 | ||
999 | FILETYPES = %w( bin lib ext data ) | |
1000 | ||
1001 | include HookScriptAPI | |
1002 | include HookUtils | |
1003 | include FileOperations | |
1004 | ||
1005 | def initialize(config, opt, srcroot, objroot) | |
1006 | @config = config | |
1007 | @options = opt | |
1008 | @srcdir = File.expand_path(srcroot) | |
1009 | @objdir = File.expand_path(objroot) | |
1010 | @currdir = '.' | |
1011 | end | |
1012 | ||
1013 | def inspect | |
1014 | "#<#{self.class} #{File.basename(@srcdir)}>" | |
1015 | end | |
1016 | ||
1017 | # | |
1018 | # Hook Script API bases | |
1019 | # | |
1020 | ||
1021 | def srcdir_root | |
1022 | @srcdir | |
1023 | end | |
1024 | ||
1025 | def objdir_root | |
1026 | @objdir | |
1027 | end | |
1028 | ||
1029 | def relpath | |
1030 | @currdir | |
1031 | end | |
1032 | ||
1033 | # | |
1034 | # configs/options | |
1035 | # | |
1036 | ||
1037 | def no_harm? | |
1038 | @options['no-harm'] | |
1039 | end | |
1040 | ||
1041 | def verbose? | |
1042 | @options['verbose'] | |
1043 | end | |
1044 | ||
1045 | def verbose_off | |
1046 | begin | |
1047 | save, @options['verbose'] = @options['verbose'], false | |
1048 | yield | |
1049 | ensure | |
1050 | @options['verbose'] = save | |
1051 | end | |
1052 | end | |
1053 | ||
1054 | # | |
1055 | # TASK config | |
1056 | # | |
1057 | ||
1058 | def exec_config | |
1059 | exec_task_traverse 'config' | |
1060 | end | |
1061 | ||
1062 | def config_dir_bin(rel) | |
1063 | end | |
1064 | ||
1065 | def config_dir_lib(rel) | |
1066 | end | |
1067 | ||
1068 | def config_dir_ext(rel) | |
1069 | extconf if extdir?(curr_srcdir()) | |
1070 | end | |
1071 | ||
1072 | def extconf | |
1073 | opt = @options['config-opt'].join(' ') | |
1074 | command "#{config('ruby-prog')} #{curr_srcdir()}/extconf.rb #{opt}" | |
1075 | end | |
1076 | ||
1077 | def config_dir_data(rel) | |
1078 | end | |
1079 | ||
1080 | # | |
1081 | # TASK setup | |
1082 | # | |
1083 | ||
1084 | def exec_setup | |
1085 | exec_task_traverse 'setup' | |
1086 | end | |
1087 | ||
1088 | def setup_dir_bin(rel) | |
1089 | all_files_in(curr_srcdir()).each do |fname| | |
1090 | adjust_shebang "#{curr_srcdir()}/#{fname}" | |
1091 | end | |
1092 | end | |
1093 | ||
1094 | # modify: #!/usr/bin/ruby | |
1095 | # modify: #! /usr/bin/ruby | |
1096 | # modify: #!ruby | |
1097 | # not modify: #!/usr/bin/env ruby | |
1098 | SHEBANG_RE = /\A\#!\s*\S*ruby\S*/ | |
1099 | ||
1100 | def adjust_shebang(path) | |
1101 | return if no_harm? | |
1102 | ||
1103 | tmpfile = File.basename(path) + '.tmp' | |
1104 | begin | |
1105 | File.open(path, 'rb') {|r| | |
1106 | File.open(tmpfile, 'wb') {|w| | |
1107 | first = r.gets | |
1108 | return unless SHEBANG_RE =~ first | |
1109 | ||
1110 | $stderr.puts "adjusting shebang: #{File.basename path}" if verbose? | |
1111 | w.print first.sub(SHEBANG_RE, '#!' + config('ruby-path')) | |
1112 | w.write r.read | |
1113 | } | |
1114 | } | |
1115 | move_file tmpfile, File.basename(path) | |
1116 | ensure | |
1117 | File.unlink tmpfile if File.exist?(tmpfile) | |
1118 | end | |
1119 | end | |
1120 | ||
1121 | def setup_dir_lib(rel) | |
1122 | end | |
1123 | ||
1124 | def setup_dir_ext(rel) | |
1125 | make if extdir?(curr_srcdir()) | |
1126 | end | |
1127 | ||
1128 | def setup_dir_data(rel) | |
1129 | end | |
1130 | ||
1131 | # | |
1132 | # TASK install | |
1133 | # | |
1134 | ||
1135 | def exec_install | |
1136 | exec_task_traverse 'install' | |
1137 | end | |
1138 | ||
1139 | def install_dir_bin(rel) | |
1140 | install_files collect_filenames_auto(), "#{config('bin-dir')}/#{rel}", 0755 | |
1141 | end | |
1142 | ||
1143 | def install_dir_lib(rel) | |
1144 | install_files ruby_scripts(), "#{config('rb-dir')}/#{rel}", 0644 | |
1145 | end | |
1146 | ||
1147 | def install_dir_ext(rel) | |
1148 | return unless extdir?(curr_srcdir()) | |
1149 | install_files ruby_extentions('.'), | |
1150 | "#{config('so-dir')}/#{File.dirname(rel)}", | |
1151 | 0555 | |
1152 | end | |
1153 | ||
1154 | def install_dir_data(rel) | |
1155 | install_files collect_filenames_auto(), "#{config('data-dir')}/#{rel}", 0644 | |
1156 | end | |
1157 | ||
1158 | def install_files(list, dest, mode) | |
1159 | mkdir_p dest, @options['install-prefix'] | |
1160 | list.each do |fname| | |
1161 | install fname, dest, mode, @options['install-prefix'] | |
1162 | end | |
1163 | end | |
1164 | ||
1165 | def ruby_scripts | |
1166 | collect_filenames_auto().select {|n| /\.rb\z/ =~ n } | |
1167 | end | |
1168 | ||
1169 | # picked up many entries from cvs-1.11.1/src/ignore.c | |
1170 | reject_patterns = %w( | |
1171 | core RCSLOG tags TAGS .make.state | |
1172 | .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb | |
1173 | *~ *.old *.bak *.BAK *.orig *.rej _$* *$ | |
1174 | ||
1175 | *.org *.in .* | |
1176 | ) | |
1177 | mapping = { | |
1178 | '.' => '\.', | |
1179 | '$' => '\$', | |
1180 | '#' => '\#', | |
1181 | '*' => '.*' | |
1182 | } | |
1183 | REJECT_PATTERNS = Regexp.new('\A(?:' + | |
1184 | reject_patterns.map {|pat| | |
1185 | pat.gsub(/[\.\$\#\*]/) {|ch| mapping[ch] } | |
1186 | }.join('|') + | |
1187 | ')\z') | |
1188 | ||
1189 | def collect_filenames_auto | |
1190 | mapdir((existfiles() - hookfiles()).reject {|fname| | |
1191 | REJECT_PATTERNS =~ fname | |
1192 | }) | |
1193 | end | |
1194 | ||
1195 | def existfiles | |
1196 | all_files_in(curr_srcdir()) | all_files_in('.') | |
1197 | end | |
1198 | ||
1199 | def hookfiles | |
1200 | %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt| | |
1201 | %w( config setup install clean ).map {|t| sprintf(fmt, t) } | |
1202 | }.flatten | |
1203 | end | |
1204 | ||
1205 | def mapdir(filelist) | |
1206 | filelist.map {|fname| | |
1207 | if File.exist?(fname) # objdir | |
1208 | fname | |
1209 | else # srcdir | |
1210 | File.join(curr_srcdir(), fname) | |
1211 | end | |
1212 | } | |
1213 | end | |
1214 | ||
1215 | def ruby_extentions(dir) | |
1216 | _ruby_extentions(dir) or | |
1217 | raise InstallError, "no ruby extention exists: 'ruby #{$0} setup' first" | |
1218 | end | |
1219 | ||
1220 | DLEXT = /\.#{ ::Config::CONFIG['DLEXT'] }\z/ | |
1221 | ||
1222 | def _ruby_extentions(dir) | |
1223 | Dir.open(dir) {|d| | |
1224 | return d.select {|fname| DLEXT =~ fname } | |
1225 | } | |
1226 | end | |
1227 | ||
1228 | # | |
1229 | # TASK clean | |
1230 | # | |
1231 | ||
1232 | def exec_clean | |
1233 | exec_task_traverse 'clean' | |
1234 | rm_f 'config.save' | |
1235 | rm_f 'InstalledFiles' | |
1236 | end | |
1237 | ||
1238 | def clean_dir_bin(rel) | |
1239 | end | |
1240 | ||
1241 | def clean_dir_lib(rel) | |
1242 | end | |
1243 | ||
1244 | def clean_dir_ext(rel) | |
1245 | return unless extdir?(curr_srcdir()) | |
1246 | make 'clean' if File.file?('Makefile') | |
1247 | end | |
1248 | ||
1249 | def clean_dir_data(rel) | |
1250 | end | |
1251 | ||
1252 | # | |
1253 | # TASK distclean | |
1254 | # | |
1255 | ||
1256 | def exec_distclean | |
1257 | exec_task_traverse 'distclean' | |
1258 | rm_f 'config.save' | |
1259 | rm_f 'InstalledFiles' | |
1260 | end | |
1261 | ||
1262 | def distclean_dir_bin(rel) | |
1263 | end | |
1264 | ||
1265 | def distclean_dir_lib(rel) | |
1266 | end | |
1267 | ||
1268 | def distclean_dir_ext(rel) | |
1269 | return unless extdir?(curr_srcdir()) | |
1270 | make 'distclean' if File.file?('Makefile') | |
1271 | end | |
1272 | ||
1273 | # | |
1274 | # lib | |
1275 | # | |
1276 | ||
1277 | def exec_task_traverse(task) | |
1278 | run_hook "pre-#{task}" | |
1279 | FILETYPES.each do |type| | |
1280 | if config('without-ext') == 'yes' and type == 'ext' | |
1281 | $stderr.puts 'skipping ext/* by user option' if verbose? | |
1282 | next | |
1283 | end | |
1284 | traverse task, type, "#{task}_dir_#{type}" | |
1285 | end | |
1286 | run_hook "post-#{task}" | |
1287 | end | |
1288 | ||
1289 | def traverse(task, rel, mid) | |
1290 | dive_into(rel) { | |
1291 | run_hook "pre-#{task}" | |
1292 | __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '') | |
1293 | all_dirs_in(curr_srcdir()).each do |d| | |
1294 | traverse task, "#{rel}/#{d}", mid | |
1295 | end | |
1296 | run_hook "post-#{task}" | |
1297 | } | |
1298 | end | |
1299 | ||
1300 | def dive_into(rel) | |
1301 | return unless File.dir?("#{@srcdir}/#{rel}") | |
1302 | ||
1303 | dir = File.basename(rel) | |
1304 | Dir.mkdir dir unless File.dir?(dir) | |
1305 | prevdir = Dir.pwd | |
1306 | Dir.chdir dir | |
1307 | $stderr.puts '---> ' + rel if verbose? | |
1308 | @currdir = rel | |
1309 | yield | |
1310 | Dir.chdir prevdir | |
1311 | $stderr.puts '<--- ' + rel if verbose? | |
1312 | @currdir = File.dirname(rel) | |
1313 | end | |
1314 | ||
1315 | end | |
1316 | ||
1317 | ||
1318 | if $0 == __FILE__ | |
1319 | begin | |
1320 | if multipackage_install? | |
1321 | ToplevelInstallerMulti.invoke | |
1322 | else | |
1323 | ToplevelInstaller.invoke | |
1324 | end | |
1325 | rescue | |
1326 | raise if $DEBUG | |
1327 | $stderr.puts $!.message | |
1328 | $stderr.puts "Try 'ruby #{$0} --help' for detailed usage." | |
1329 | exit 1 | |
1330 | end | |
1331 | end |