net-scp 1.0.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.
- data/CHANGELOG.rdoc +8 -0
- data/Manifest +17 -0
- data/README.rdoc +98 -0
- data/Rakefile +30 -0
- data/lib/net/scp.rb +410 -0
- data/lib/net/scp/download.rb +150 -0
- data/lib/net/scp/errors.rb +5 -0
- data/lib/net/scp/upload.rb +142 -0
- data/lib/net/scp/version.rb +18 -0
- data/lib/uri/open-scp.rb +18 -0
- data/lib/uri/scp.rb +35 -0
- data/net-scp.gemspec +60 -0
- data/setup.rb +1331 -0
- data/test/common.rb +138 -0
- data/test/test_all.rb +3 -0
- data/test/test_download.rb +142 -0
- data/test/test_scp.rb +60 -0
- data/test/test_upload.rb +241 -0
- metadata +78 -0
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'net/scp/errors'
|
2
|
+
|
3
|
+
module Net; class SCP
|
4
|
+
|
5
|
+
# This module implements the state machine for downloading information from
|
6
|
+
# a remote server. It exposes no public methods. See Net::SCP#download for
|
7
|
+
# a discussion of how to use Net::SCP to download data.
|
8
|
+
module Download
|
9
|
+
private
|
10
|
+
|
11
|
+
# This is the starting state for the download state machine. The
|
12
|
+
# #start_command method puts the state machine into this state the first
|
13
|
+
# time the channel is processed. This state does some basic error checking
|
14
|
+
# and scaffolding and then sends a 0-byte to the remote server, indicating
|
15
|
+
# readiness to proceed. Then, the state machine is placed into the
|
16
|
+
# "read directive" state (see #read_directive_state).
|
17
|
+
def download_start_state(channel)
|
18
|
+
if channel[:local].respond_to?(:write) && channel[:options][:recursive]
|
19
|
+
raise Net::SCP::Error, "cannot recursively download to an in-memory location"
|
20
|
+
elsif channel[:local].respond_to?(:write) && channel[:options][:preserve]
|
21
|
+
lwarn { ":preserve option is ignored when downloading to an in-memory buffer" }
|
22
|
+
channel[:options].delete(:preserve)
|
23
|
+
elsif channel[:options][:recursive] && !File.exists?(channel[:local])
|
24
|
+
Dir.mkdir(channel[:local])
|
25
|
+
end
|
26
|
+
|
27
|
+
channel.send_data("\0")
|
28
|
+
channel[:state] = :read_directive
|
29
|
+
end
|
30
|
+
|
31
|
+
# This state parses the next full line (up to a new-line) for the next
|
32
|
+
# directive. (See the SCP protocol documentation in Net::SCP for the
|
33
|
+
# possible directives).
|
34
|
+
def read_directive_state(channel)
|
35
|
+
return unless line = channel[:buffer].read_to("\n")
|
36
|
+
channel[:buffer].consume!
|
37
|
+
|
38
|
+
directive = parse_directive(line)
|
39
|
+
case directive[:type]
|
40
|
+
when :times then
|
41
|
+
channel[:times] = directive
|
42
|
+
when :directory
|
43
|
+
read_directory(channel, directive)
|
44
|
+
when :file
|
45
|
+
read_file(channel, directive)
|
46
|
+
when :end
|
47
|
+
channel[:local] = File.dirname(channel[:local])
|
48
|
+
channel[:stack].pop
|
49
|
+
channel[:state] = :finish if channel[:stack].empty?
|
50
|
+
end
|
51
|
+
|
52
|
+
channel.send_data("\0")
|
53
|
+
end
|
54
|
+
|
55
|
+
# Reads data from the channel for as long as there is data remaining to
|
56
|
+
# be read. As soon as there is no more data to read for the current file,
|
57
|
+
# the state machine switches to #finish_read_state.
|
58
|
+
def read_data_state(channel)
|
59
|
+
return if channel[:buffer].empty?
|
60
|
+
data = channel[:buffer].read!(channel[:remaining])
|
61
|
+
channel[:io].write(data)
|
62
|
+
channel[:remaining] -= data.length
|
63
|
+
progress_callback(channel, channel[:file][:name], channel[:file][:size] - channel[:remaining], channel[:file][:size])
|
64
|
+
await_response(channel, :finish_read) if channel[:remaining] <= 0
|
65
|
+
end
|
66
|
+
|
67
|
+
# Finishes off the read, sets the times for the file (if any), and then
|
68
|
+
# jumps to either #finish_state (for single-file downloads) or
|
69
|
+
# #read_directive_state (for recursive downloads). A 0-byte is sent to the
|
70
|
+
# server to indicate that the file was recieved successfully.
|
71
|
+
def finish_read_state(channel)
|
72
|
+
channel[:io].close unless channel[:io] == channel[:local]
|
73
|
+
|
74
|
+
if channel[:options][:preserve] && channel[:file][:times]
|
75
|
+
File.utime(channel[:file][:times][:atime],
|
76
|
+
channel[:file][:times][:mtime], channel[:file][:name])
|
77
|
+
end
|
78
|
+
|
79
|
+
channel[:file] = nil
|
80
|
+
channel[:state] = channel[:stack].empty? ? :finish : :read_directive
|
81
|
+
channel.send_data("\0")
|
82
|
+
end
|
83
|
+
|
84
|
+
# Parses the given +text+ to extract which SCP directive it contains. It
|
85
|
+
# then returns a hash with at least one key, :type, which describes what
|
86
|
+
# type of directive it is. The hash may also contain other, directive-specific
|
87
|
+
# data.
|
88
|
+
def parse_directive(text)
|
89
|
+
case type = text[0]
|
90
|
+
when ?T
|
91
|
+
parts = text[1..-1].split(/ /, 4).map { |i| i.to_i }
|
92
|
+
{ :type => :times,
|
93
|
+
:mtime => Time.at(parts[0], parts[1]),
|
94
|
+
:atime => Time.at(parts[2], parts[3]) }
|
95
|
+
when ?C, ?D
|
96
|
+
parts = text[1..-1].split(/ /, 3)
|
97
|
+
{ :type => (type == ?C ? :file : :directory),
|
98
|
+
:mode => parts[0].to_i(8),
|
99
|
+
:size => parts[1].to_i,
|
100
|
+
:name => parts[2].chomp }
|
101
|
+
when ?E
|
102
|
+
{ :type => :end }
|
103
|
+
else raise ArgumentError, "unknown directive: #{text.inspect}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Sets the new directory as the current directory, creates the directory
|
108
|
+
# if it does not exist, and then falls back into #read_directive_state.
|
109
|
+
def read_directory(channel, directive)
|
110
|
+
if !channel[:options][:recursive]
|
111
|
+
raise Net::SCP::Error, ":recursive not specified for directory download"
|
112
|
+
end
|
113
|
+
|
114
|
+
channel[:local] = File.join(channel[:local], directive[:name])
|
115
|
+
|
116
|
+
if File.exists?(channel[:local]) && !File.directory?(channel[:local])
|
117
|
+
raise "#{channel[:local]} already exists and is not a directory"
|
118
|
+
elsif !File.exists?(channel[:local])
|
119
|
+
Dir.mkdir(channel[:local], directive[:mode] | 0700)
|
120
|
+
end
|
121
|
+
|
122
|
+
if channel[:options][:preserve] && channel[:times]
|
123
|
+
File.utime(channel[:times][:atime], channel[:times][:mtime], channel[:local])
|
124
|
+
end
|
125
|
+
|
126
|
+
channel[:stack] << directive
|
127
|
+
channel[:times] = nil
|
128
|
+
end
|
129
|
+
|
130
|
+
# Opens the given file locally, and switches to #read_data_state to do the
|
131
|
+
# actual read.
|
132
|
+
def read_file(channel, directive)
|
133
|
+
if !channel[:local].respond_to?(:write)
|
134
|
+
directive[:name] = (channel[:options][:recursive] || File.directory?(channel[:local])) ?
|
135
|
+
File.join(channel[:local], directive[:name]) :
|
136
|
+
channel[:local]
|
137
|
+
end
|
138
|
+
|
139
|
+
channel[:file] = directive.merge(:times => channel[:times])
|
140
|
+
channel[:io] = channel[:local].respond_to?(:write) ? channel[:local] :
|
141
|
+
File.new(directive[:name], File::CREAT|File::TRUNC|File::RDWR, directive[:mode] | 0600)
|
142
|
+
channel[:times] = nil
|
143
|
+
channel[:remaining] = channel[:file][:size]
|
144
|
+
channel[:state] = :read_data
|
145
|
+
|
146
|
+
progress_callback(channel, channel[:file][:name], 0, channel[:file][:size])
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
end; end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'net/scp/errors'
|
2
|
+
|
3
|
+
module Net; class SCP
|
4
|
+
|
5
|
+
# This module implements the state machine for uploading information to
|
6
|
+
# a remote server. It exposes no public methods. See Net::SCP#upload for
|
7
|
+
# a discussion of how to use Net::SCP to upload data.
|
8
|
+
module Upload
|
9
|
+
private
|
10
|
+
|
11
|
+
# The default read chunk size, if an explicit chunk-size is not specified
|
12
|
+
# by the client.
|
13
|
+
DEFAULT_CHUNK_SIZE = 2048
|
14
|
+
|
15
|
+
# The start state for uploads. Simply sets up the upload scaffolding,
|
16
|
+
# sets the current item to upload, and jumps to #upload_current_state.
|
17
|
+
def upload_start_state(channel)
|
18
|
+
if channel[:local].respond_to?(:read)
|
19
|
+
channel[:options].delete(:recursive)
|
20
|
+
channel[:options].delete(:preserve)
|
21
|
+
end
|
22
|
+
|
23
|
+
channel[:chunk_size] = channel[:options][:chunk_size] || DEFAULT_CHUNK_SIZE
|
24
|
+
set_current(channel, channel[:local])
|
25
|
+
await_response(channel, :upload_current)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Determines what the next thing to upload is, and branches. If the next
|
29
|
+
# item is a file, goes to #upload_file_state. If it is a directory, goes
|
30
|
+
# to #upload_directory_state.
|
31
|
+
def upload_current_state(channel)
|
32
|
+
if channel[:current].respond_to?(:read)
|
33
|
+
upload_file_state(channel)
|
34
|
+
elsif File.directory?(channel[:current])
|
35
|
+
raise Net::SCP::Error, "can't upload directories unless :recursive" unless channel[:options][:recursive]
|
36
|
+
upload_directory_state(channel)
|
37
|
+
elsif File.file?(channel[:current])
|
38
|
+
upload_file_state(channel)
|
39
|
+
else
|
40
|
+
raise Net::SCP::Error, "not a directory or a regular file: #{channel[:current].inspect}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# After transferring attributes (if requested), sends a 'D' directive and
|
45
|
+
# awaites the server's 0-byte response. Then goes to #next_item_state.
|
46
|
+
def upload_directory_state(channel)
|
47
|
+
if preserve_attributes_if_requested(channel)
|
48
|
+
mode = channel[:stat].mode & 07777
|
49
|
+
directive = "D%04o %d %s\n" % [mode, 0, File.basename(channel[:current])]
|
50
|
+
channel.send_data(directive)
|
51
|
+
channel[:cwd] = channel[:current]
|
52
|
+
channel[:stack] << Dir.entries(channel[:current]).reject { |i| i == "." || i == ".." }
|
53
|
+
await_response(channel, :next_item)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# After transferring attributes (if requested), sends a 'C' directive and
|
58
|
+
# awaits the server's 0-byte response. Then goes to #send_data_state.
|
59
|
+
def upload_file_state(channel)
|
60
|
+
if preserve_attributes_if_requested(channel)
|
61
|
+
mode = channel[:stat] ? channel[:stat].mode & 07777 : channel[:options][:mode]
|
62
|
+
channel[:name] = channel[:current].respond_to?(:read) ? channel[:remote] : channel[:current]
|
63
|
+
directive = "C%04o %d %s\n" % [mode || 0640, channel[:size], File.basename(channel[:name])]
|
64
|
+
channel.send_data(directive)
|
65
|
+
channel[:io] = channel[:current].respond_to?(:read) ? channel[:current] : File.open(channel[:current], "rb")
|
66
|
+
channel[:sent] = 0
|
67
|
+
progress_callback(channel, channel[:name], channel[:sent], channel[:size])
|
68
|
+
await_response(channel, :send_data)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# If any data remains to be transferred from the current file, sends it.
|
73
|
+
# Otherwise, sends a 0-byte and transfers to #next_item_state.
|
74
|
+
def send_data_state(channel)
|
75
|
+
data = channel[:io].read(channel[:chunk_size])
|
76
|
+
if data.nil?
|
77
|
+
channel[:io].close unless channel[:local].respond_to?(:read)
|
78
|
+
channel.send_data("\0")
|
79
|
+
await_response(channel, :next_item)
|
80
|
+
else
|
81
|
+
channel[:sent] += data.length
|
82
|
+
progress_callback(channel, channel[:name], channel[:sent], channel[:size])
|
83
|
+
channel.send_data(data)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Checks the work queue to see what needs to be done next. If there is
|
88
|
+
# nothing to do, calls Net::SCP#finish_state. If we're at the end of a
|
89
|
+
# directory, sends an 'E' directive and waits for the server to respond
|
90
|
+
# before moving to #next_item_state. Otherwise, sets the next thing to
|
91
|
+
# upload and moves to #upload_current_state.
|
92
|
+
def next_item_state(channel)
|
93
|
+
if channel[:stack].empty?
|
94
|
+
finish_state(channel)
|
95
|
+
else
|
96
|
+
next_item = channel[:stack].last.shift
|
97
|
+
if next_item.nil?
|
98
|
+
channel[:stack].pop
|
99
|
+
channel[:cwd] = File.dirname(channel[:cwd])
|
100
|
+
channel.send_data("E\n")
|
101
|
+
await_response(channel, channel[:stack].empty? ? :finish : :next_item)
|
102
|
+
else
|
103
|
+
set_current(channel, next_item)
|
104
|
+
upload_current_state(channel)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Sets the given +path+ as the new current item to upload.
|
110
|
+
def set_current(channel, path)
|
111
|
+
path = channel[:cwd] ? File.join(channel[:cwd], path) : path
|
112
|
+
channel[:current] = path
|
113
|
+
|
114
|
+
if channel[:current].respond_to?(:read)
|
115
|
+
channel[:stat] = channel[:current].stat if channel[:current].respond_to?(:stat)
|
116
|
+
else
|
117
|
+
channel[:stat] = File.stat(channel[:current])
|
118
|
+
end
|
119
|
+
|
120
|
+
channel[:size] = channel[:stat] ? channel[:stat].size : channel[:current].size
|
121
|
+
end
|
122
|
+
|
123
|
+
# If the :preserve option is set, send a 'T' directive and wait for the
|
124
|
+
# server to respond before proceeding to either #upload_file_state or
|
125
|
+
# #upload_directory_state, depending on what is being uploaded.
|
126
|
+
def preserve_attributes_if_requested(channel)
|
127
|
+
if channel[:options][:preserve] && !channel[:preserved]
|
128
|
+
channel[:preserved] = true
|
129
|
+
stat = channel[:stat]
|
130
|
+
directive = "T%d %d %d %d\n" % [stat.mtime.to_i, stat.mtime.usec, stat.atime.to_i, stat.atime.usec]
|
131
|
+
channel.send_data(directive)
|
132
|
+
type = stat.directory? ? :directory : :file
|
133
|
+
await_response(channel, "upload_#{type}")
|
134
|
+
return false
|
135
|
+
else
|
136
|
+
channel[:preserved] = false
|
137
|
+
return true
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end; end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'net/ssh/version'
|
2
|
+
|
3
|
+
module Net; class SCP
|
4
|
+
|
5
|
+
# Describes the current version of the Net::SCP library.
|
6
|
+
class Version < Net::SSH::Version
|
7
|
+
MAJOR = 1
|
8
|
+
MINOR = 0
|
9
|
+
TINY = 0
|
10
|
+
|
11
|
+
# The current version, as a Version instance
|
12
|
+
CURRENT = new(MAJOR, MINOR, TINY)
|
13
|
+
|
14
|
+
# The current version, as a String instance
|
15
|
+
STRING = CURRENT.to_s
|
16
|
+
end
|
17
|
+
|
18
|
+
end; end
|
data/lib/uri/open-scp.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'uri/scp'
|
3
|
+
require 'net/scp'
|
4
|
+
|
5
|
+
module URI
|
6
|
+
|
7
|
+
class SCP
|
8
|
+
def buffer_open(buf, proxy, open_options)
|
9
|
+
options = open_options.merge(:port => port, :password => password)
|
10
|
+
progress = options.delete(:progress_proc)
|
11
|
+
buf << Net::SCP.download!(host, user, path, nil, open_options, &progress)
|
12
|
+
buf.io.rewind
|
13
|
+
end
|
14
|
+
|
15
|
+
include OpenURI::OpenRead
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
data/lib/uri/scp.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'uri/generic'
|
2
|
+
|
3
|
+
module URI
|
4
|
+
class SCP < Generic
|
5
|
+
DEFAULT_PORT = 22
|
6
|
+
|
7
|
+
COMPONENT = [
|
8
|
+
:scheme,
|
9
|
+
:userinfo,
|
10
|
+
:host, :port, :path,
|
11
|
+
:query
|
12
|
+
].freeze
|
13
|
+
|
14
|
+
attr_reader :options
|
15
|
+
|
16
|
+
def self.new2(user, password, host, port, path, query)
|
17
|
+
new('scp', [user, password], host, port, nil, path, nil, query)
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(*args)
|
21
|
+
super(*args)
|
22
|
+
|
23
|
+
@options = Hash.new
|
24
|
+
(query || "").split(/&/).each do |pair|
|
25
|
+
name, value = pair.split(/=/, 2)
|
26
|
+
opt_name = name.to_sym
|
27
|
+
values = value.split(/,/).map { |v| v.to_i.to_s == v ? v.to_i : v }
|
28
|
+
values = values.first if values.length == 1
|
29
|
+
options[opt_name] = values
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
@@schemes['SCP'] = SCP
|
35
|
+
end
|
data/net-scp.gemspec
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
|
2
|
+
# Gem::Specification for Net-scp-1.0.0
|
3
|
+
# Originally generated by Echoe
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = %q{net-scp}
|
7
|
+
s.version = "1.0.0"
|
8
|
+
|
9
|
+
s.specification_version = 2 if s.respond_to? :specification_version=
|
10
|
+
|
11
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
|
+
s.authors = ["Jamis Buck"]
|
13
|
+
s.date = %q{2008-05-01}
|
14
|
+
s.description = %q{A pure Ruby implementation of the SCP client protocol}
|
15
|
+
s.email = %q{jamis@jamisbuck.org}
|
16
|
+
s.files = ["CHANGELOG.rdoc", "lib/net/scp/download.rb", "lib/net/scp/errors.rb", "lib/net/scp/upload.rb", "lib/net/scp/version.rb", "lib/net/scp.rb", "lib/uri/open-scp.rb", "lib/uri/scp.rb", "Rakefile", "README.rdoc", "setup.rb", "test/common.rb", "test/test_all.rb", "test/test_download.rb", "test/test_scp.rb", "test/test_upload.rb", "Manifest", "net-scp.gemspec"]
|
17
|
+
s.has_rdoc = true
|
18
|
+
s.homepage = %q{http://net-ssh.rubyforge.org/scp}
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
s.rubyforge_project = %q{net-ssh}
|
21
|
+
s.rubygems_version = %q{1.1.1}
|
22
|
+
s.summary = %q{A pure Ruby implementation of the SCP client protocol}
|
23
|
+
s.test_files = ["test/test_all.rb"]
|
24
|
+
|
25
|
+
s.add_dependency(%q<net-ssh>, [">= 1.99.1"])
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
# # Original Rakefile source (requires the Echoe gem):
|
30
|
+
#
|
31
|
+
# $LOAD_PATH.unshift "../net-ssh/lib"
|
32
|
+
# require './lib/net/scp/version'
|
33
|
+
#
|
34
|
+
# begin
|
35
|
+
# require 'echoe'
|
36
|
+
# rescue LoadError
|
37
|
+
# abort "You'll need to have `echoe' installed to use Net::SCP's Rakefile"
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# version = Net::SCP::Version::STRING.dup
|
41
|
+
# if ENV['SNAPSHOT'].to_i == 1
|
42
|
+
# version << "." << Time.now.utc.strftime("%Y%m%d%H%M%S")
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# Echoe.new('net-scp', version) do |p|
|
46
|
+
# p.project = "net-ssh"
|
47
|
+
# p.changelog = "CHANGELOG.rdoc"
|
48
|
+
#
|
49
|
+
# p.author = "Jamis Buck"
|
50
|
+
# p.email = "jamis@jamisbuck.org"
|
51
|
+
# p.summary = "A pure Ruby implementation of the SCP client protocol"
|
52
|
+
# p.url = "http://net-ssh.rubyforge.org/scp"
|
53
|
+
#
|
54
|
+
# p.dependencies = ["net-ssh >=1.99.1"]
|
55
|
+
#
|
56
|
+
# p.need_zip = true
|
57
|
+
# p.include_rakefile = true
|
58
|
+
#
|
59
|
+
# p.rdoc_pattern = /^(lib|README.rdoc|CHANGELOG.rdoc)/
|
60
|
+
# end
|
data/setup.rb
ADDED
@@ -0,0 +1,1331 @@
|
|
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 || "module.yml" == 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
|