Project

General

Profile

« Previous | Next » 

Revision 2435

Replaces the repositories management SOAP API with a simple REST API.
reposman usage is unchanged but the script now requires activeresource.
actionwebservice is now longer used and thus removed from plugins.

View differences:

trunk/app/apis/sys_api.rb
1
# redMine - project management software
2
# Copyright (C) 2006-2007  Jean-Philippe Lang
3
#
4
# This program is free software; you can redistribute it and/or
5
# modify it under the terms of the GNU General Public License
6
# as published by the Free Software Foundation; either version 2
7
# of the License, or (at your option) any later version.
8
# 
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
# 
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17

  
18
class AWSProjectWithRepository < ActionWebService::Struct
19
  member :id,              :int
20
  member :identifier,      :string
21
  member :name,            :string
22
  member :is_public,       :bool
23
  member :repository,      Repository
24
end
25

  
26
class SysApi < ActionWebService::API::Base
27
  api_method :projects_with_repository_enabled,
28
             :expects => [],
29
             :returns => [[AWSProjectWithRepository]]
30
  api_method :repository_created,
31
             :expects => [:string, :string, :string],
32
             :returns => [:int]
33
end
34 0

  
trunk/app/controllers/sys_controller.rb
1
# redMine - project management software
2
# Copyright (C) 2006-2007  Jean-Philippe Lang
1
# Redmine - project management software
2
# Copyright (C) 2006-2009  Jean-Philippe Lang
3 3
#
4 4
# This program is free software; you can redistribute it and/or
5 5
# modify it under the terms of the GNU General Public License
......
16 16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 17

  
18 18
class SysController < ActionController::Base
19
  wsdl_service_name 'Sys'
20
  web_service_api SysApi
21
  web_service_scaffold :invoke
19
  before_filter :check_enabled
22 20
  
23
  before_invocation :check_enabled
21
  def projects
22
    p = Project.active.has_module(:repository).find(:all, :include => :repository, :order => 'identifier')
23
    render :xml => p.to_xml(:include => :repository)
24
  end
24 25
  
25
  # Returns the projects list, with their repositories
26
  def projects_with_repository_enabled
27
    Project.has_module(:repository).find(:all, :include => :repository, :order => 'identifier')
26
  def create_project_repository
27
    project = Project.find(params[:id])
28
    if project.repository
29
      render :nothing => true, :status => 409
30
    else
31
      logger.info "Repository for #{project.name} was reported to be created by #{request.remote_ip}."
32
      project.repository = Repository.factory(params[:vendor], params[:repository])
33
      if project.repository && project.repository.save
34
        render :xml => project.repository, :status => 201
35
      else
36
        render :nothing => true, :status => 422
37
      end
38
    end
28 39
  end
29 40

  
30
  # Registers a repository for the given project identifier
31
  def repository_created(identifier, vendor, url)
32
    project = Project.find_by_identifier(identifier)
33
    # Do not create the repository if the project has already one
34
    return 0 unless project && project.repository.nil?
35
    logger.debug "Repository for #{project.name} was created"
36
    repository = Repository.factory(vendor, :project => project, :url => url)
37
    repository.save
38
    repository.id || 0
39
  end
41
  protected
40 42

  
41
protected
42

  
43
  def check_enabled(name, args)
44
    Setting.sys_api_enabled?
43
  def check_enabled
44
    User.current = nil
45
    unless Setting.sys_api_enabled?
46
      render :nothing => 'Access denied. Repository management WS is disabled.', :status => 403
47
      return false
48
    end
45 49
  end
46 50
end
trunk/config/routes.rb
246 246
    omap.repositories_entry 'repositories/annotate/:id/*path', :action => 'annotate'
247 247
    omap.connect 'repositories/revision/:id/:rev', :action => 'revision'
248 248
  end
249
   
250
  # Allow downloading Web Service WSDL as a file with an extension
251
  # instead of a file named 'wsdl'
252
  map.connect ':controller/service.wsdl', :action => 'wsdl'
249
  
250
  map.with_options :controller => 'sys' do |sys|
251
    sys.connect 'sys/projects.:format', :action => 'projects', :conditions => {:method => :get}
252
    sys.connect 'sys/projects/:id/repository.:format', :action => 'create_project_repository', :conditions => {:method => :post}
253
  end
253 254
 
254 255
  # Install the default route as the lowest priority.
255 256
  map.connect ':controller/:action/:id'
trunk/extra/svn/reposman.rb
57 57

  
58 58
require 'getoptlong'
59 59
require 'rdoc/usage'
60
require 'soap/wsdlDriver'
61 60
require 'find'
62 61
require 'etc'
63 62

  
64
Version = "1.1"
63
Version = "1.2"
65 64
SUPPORTED_SCM = %w( Subversion Darcs Mercurial Bazaar Git Filesystem )
66 65

  
67 66
opts = GetoptLong.new(
......
164 163
  log("directory '#{$repos_base}' doesn't exists", :exit => true)
165 164
end
166 165

  
166
begin
167
  require 'activeresource'
168
rescue LoadError
169
  log("This script requires activeresource.\nRun 'gem install activeresource' to install it.", :exit => true)
170
end
171

  
172
class Project < ActiveResource::Base; end
173

  
167 174
log("querying Redmine for projects...", :level => 1);
168 175

  
169 176
$redmine_host.gsub!(/^/, "http://") unless $redmine_host.match("^https?://")
170 177
$redmine_host.gsub!(/\/$/, '')
171 178

  
172
wsdl_url = "#{$redmine_host}/sys/service.wsdl";
179
Project.site = "#{$redmine_host}/sys";
173 180

  
174 181
begin
175
  soap = SOAP::WSDLDriverFactory.new(wsdl_url).create_rpc_driver
182
  # Get all active projects that have the Repository module enabled
183
  projects = Project.find(:all)
176 184
rescue => e
177
  log("Unable to connect to #{wsdl_url} : #{e}", :exit => true)
185
  log("Unable to connect to #{Project.site}: #{e}", :exit => true)
178 186
end
179 187

  
180
projects = soap.ProjectsWithRepositoryEnabled
181

  
182 188
if projects.nil?
183 189
  log('no project found, perhaps you forgot to "Enable WS for repository management"', :exit => true)
184 190
end
......
247 253
  else
248 254
    # if repository is already declared in redmine, we don't create
249 255
    # unless user use -f with reposman
250
    if $force == false and not project.repository.nil?
256
    if $force == false and project.respond_to?(:repository)
251 257
      log("\trepository for project #{project.identifier} already exists in Redmine", :level => 1)
252 258
      next
253 259
    end
......
274 280
    end
275 281

  
276 282
    if $svn_url
277
      ret = soap.RepositoryCreated project.identifier, $scm, "#{$svn_url}#{project.identifier}"
278
      if ret > 0
283
      begin
284
        project.post(:repository, :vendor => $scm, :repository => {:url => "#{$svn_url}#{project.identifier}"})
279 285
        log("\trepository #{repos_path} registered in Redmine with url #{$svn_url}#{project.identifier}");
280
      else
281
        log("\trepository #{repos_path} not registered in Redmine. Look in your log to find why.");
286
      rescue => e
287
        log("\trepository #{repos_path} not registered in Redmine: #{e.message}");
282 288
      end
283 289
    end
284 290

  
trunk/test/functional/sys_api_test.rb
1
require File.dirname(__FILE__) + '/../test_helper'
2
require 'sys_controller'
3

  
4
# Re-raise errors caught by the controller.
5
class SysController; def rescue_action(e) raise e end; end
6

  
7
class SysControllerTest < Test::Unit::TestCase
8
  fixtures :projects, :enabled_modules, :repositories
9
  
10
  def setup
11
    @controller = SysController.new
12
    @request = ActionController::TestRequest.new
13
    @response = ActionController::TestResponse.new
14
    # Enable WS
15
    Setting.sys_api_enabled = 1
16
  end
17
  
18
  def test_projects_with_repository_enabled
19
    result = invoke :projects_with_repository_enabled
20
    assert_equal EnabledModule.count(:all, :conditions => {:name => 'repository'}), result.size
21
    
22
    project = result.first
23
    assert project.is_a?(AWSProjectWithRepository)
24
    
25
    assert project.respond_to?(:id)
26
    assert_equal 1, project.id
27
    
28
    assert project.respond_to?(:identifier)
29
    assert_equal 'ecookbook', project.identifier
30
    
31
    assert project.respond_to?(:name)
32
    assert_equal 'eCookbook', project.name
33
    
34
    assert project.respond_to?(:is_public)
35
    assert project.is_public
36
    
37
    assert project.respond_to?(:repository)
38
    assert project.repository.is_a?(Repository)
39
  end
40

  
41
  def test_repository_created
42
    project = Project.find(3)
43
    assert_nil project.repository
44
    assert invoke(:repository_created, project.identifier, 'Subversion', 'http://localhost/svn')
45
    project.reload
46
    assert_not_nil project.repository
47
    assert project.repository.is_a?(Repository::Subversion)
48
    assert_equal 'http://localhost/svn', project.repository.url
49
  end
50
end
51 0

  
trunk/test/functional/sys_controller_test.rb
1
# Redmine - project management software
2
# Copyright (C) 2006-2009  Jean-Philippe Lang
3
#
4
# This program is free software; you can redistribute it and/or
5
# modify it under the terms of the GNU General Public License
6
# as published by the Free Software Foundation; either version 2
7
# of the License, or (at your option) any later version.
8
# 
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
# 
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17

  
18
require File.dirname(__FILE__) + '/../test_helper'
19
require 'sys_controller'
20

  
21
# Re-raise errors caught by the controller.
22
class SysController; def rescue_action(e) raise e end; end
23

  
24
class SysControllerTest < Test::Unit::TestCase
25
  fixtures :projects, :repositories
26
  
27
  def setup
28
    @controller = SysController.new
29
    @request    = ActionController::TestRequest.new
30
    @response   = ActionController::TestResponse.new
31
    Setting.sys_api_enabled = '1'
32
  end
33
  
34
  def test_projects_with_repository_enabled
35
    get :projects
36
    assert_response :success
37
    assert_equal 'application/xml', @response.content_type
38
    with_options :tag => 'projects' do |test|
39
      test.assert_tag :children => { :count  => Project.active.has_module(:repository).count }
40
    end
41
  end
42

  
43
  def test_create_project_repository
44
    assert_nil Project.find(4).repository
45
    
46
    post :create_project_repository, :id => 4, 
47
                                     :vendor => 'Subversion',
48
                                     :repository => { :url => 'file:///create/project/repository/subproject2'}
49
    assert_response :created
50
    
51
    r = Project.find(4).repository
52
    assert r.is_a?(Repository::Subversion)
53
    assert_equal 'file:///create/project/repository/subproject2', r.url
54
  end
55
end
0 56

  
trunk/vendor/plugins/actionwebservice/Rakefile
1
require 'rubygems'
2
require 'rake'
3
require 'rake/testtask'
4
require 'rake/rdoctask'
5
require 'rake/packagetask'
6
require 'rake/gempackagetask'
7
require 'rake/contrib/rubyforgepublisher'
8
require 'fileutils'
9
require File.join(File.dirname(__FILE__), 'lib', 'action_web_service', 'version')
10

  
11
PKG_BUILD     = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
12
PKG_NAME      = 'actionwebservice'
13
PKG_VERSION   = ActionWebService::VERSION::STRING + PKG_BUILD
14
PKG_FILE_NAME   = "#{PKG_NAME}-#{PKG_VERSION}"
15
PKG_DESTINATION = ENV["RAILS_PKG_DESTINATION"] || "../#{PKG_NAME}"
16

  
17
RELEASE_NAME  = "REL #{PKG_VERSION}"
18

  
19
RUBY_FORGE_PROJECT = "aws"
20
RUBY_FORGE_USER    = "webster132"
21

  
22
desc "Default Task"
23
task :default => [ :test ]
24

  
25

  
26
# Run the unit tests
27
Rake::TestTask.new { |t|
28
  t.libs << "test"
29
  t.test_files = Dir['test/*_test.rb']
30
  t.verbose = true
31
}
32

  
33
SCHEMA_PATH = File.join(File.dirname(__FILE__), *%w(test fixtures db_definitions))
34

  
35
desc 'Build the MySQL test database'
36
task :build_database do 
37
  %x( mysqladmin  create actionwebservice_unittest )
38
  %x( mysql  actionwebservice_unittest < #{File.join(SCHEMA_PATH, 'mysql.sql')} )
39
end
40

  
41

  
42
# Generate the RDoc documentation
43
Rake::RDocTask.new { |rdoc|
44
  rdoc.rdoc_dir = 'doc'
45
  rdoc.title    = "Action Web Service -- Web services for Action Pack"
46
  rdoc.options << '--line-numbers' << '--inline-source'
47
  rdoc.options << '--charset' << 'utf-8'
48
  rdoc.template = "#{ENV['template']}.rb" if ENV['template']
49
  rdoc.rdoc_files.include('README')
50
  rdoc.rdoc_files.include('CHANGELOG')
51
  rdoc.rdoc_files.include('lib/action_web_service.rb')
52
  rdoc.rdoc_files.include('lib/action_web_service/*.rb')
53
  rdoc.rdoc_files.include('lib/action_web_service/api/*.rb')
54
  rdoc.rdoc_files.include('lib/action_web_service/client/*.rb')
55
  rdoc.rdoc_files.include('lib/action_web_service/container/*.rb')
56
  rdoc.rdoc_files.include('lib/action_web_service/dispatcher/*.rb')
57
  rdoc.rdoc_files.include('lib/action_web_service/protocol/*.rb')
58
  rdoc.rdoc_files.include('lib/action_web_service/support/*.rb')
59
}
60

  
61

  
62
# Create compressed packages
63
spec = Gem::Specification.new do |s|
64
  s.platform = Gem::Platform::RUBY
65
  s.name = PKG_NAME
66
  s.summary = "Web service support for Action Pack."
67
  s.description = %q{Adds WSDL/SOAP and XML-RPC web service support to Action Pack}
68
  s.version = PKG_VERSION
69

  
70
  s.author = "Leon Breedt"
71
  s.email = "[email protected]"
72
  s.rubyforge_project = "aws"
73
  s.homepage = "http://www.rubyonrails.org"
74

  
75
  s.add_dependency('actionpack', '= 1.13.5' + PKG_BUILD)
76
  s.add_dependency('activerecord', '= 1.15.5' + PKG_BUILD)
77

  
78
  s.has_rdoc = true
79
  s.requirements << 'none'
80
  s.require_path = 'lib'
81
  s.autorequire = 'action_web_service'
82

  
83
  s.files = [ "Rakefile", "setup.rb", "README", "TODO", "CHANGELOG", "MIT-LICENSE" ]
84
  s.files = s.files + Dir.glob( "examples/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
85
  s.files = s.files + Dir.glob( "lib/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
86
  s.files = s.files + Dir.glob( "test/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
87
end
88
Rake::GemPackageTask.new(spec) do |p|
89
  p.gem_spec = spec
90
  p.need_tar = true
91
  p.need_zip = true
92
end
93

  
94

  
95
# Publish beta gem
96
desc "Publish the API documentation"
97
task :pgem => [:package] do 
98
  Rake::SshFilePublisher.new("[email protected]", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
99
  `ssh [email protected] './gemupdate.sh'`
100
end
101

  
102
# Publish documentation
103
desc "Publish the API documentation"
104
task :pdoc => [:rdoc] do 
105
  Rake::SshDirPublisher.new("[email protected]", "public_html/aws", "doc").upload
106
end
107

  
108

  
109
def each_source_file(*args)
110
	prefix, includes, excludes, open_file = args
111
	prefix ||= File.dirname(__FILE__)
112
	open_file = true if open_file.nil?
113
	includes ||= %w[lib\/action_web_service\.rb$ lib\/action_web_service\/.*\.rb$]
114
	excludes ||= %w[lib\/action_web_service\/vendor]
115
	Find.find(prefix) do |file_name|
116
		next if file_name =~ /\.svn/
117
		file_name.gsub!(/^\.\//, '')
118
		continue = false
119
		includes.each do |inc|
120
			if file_name.match(/#{inc}/)
121
				continue = true
122
				break
123
			end
124
		end
125
		next unless continue
126
		excludes.each do |exc|
127
			if file_name.match(/#{exc}/)
128
				continue = false
129
				break
130
			end
131
		end
132
		next unless continue
133
		if open_file
134
			File.open(file_name) do |f|
135
				yield file_name, f
136
			end
137
		else
138
			yield file_name
139
		end
140
	end
141
end
142

  
143
desc "Count lines of the AWS source code"
144
task :lines do
145
  total_lines = total_loc = 0
146
  puts "Per File:"
147
	each_source_file do |file_name, f|
148
    file_lines = file_loc = 0
149
    while line = f.gets
150
      file_lines += 1
151
      next if line =~ /^\s*$/
152
      next if line =~ /^\s*#/
153
      file_loc += 1
154
    end
155
    puts "  #{file_name}: Lines #{file_lines}, LOC #{file_loc}"
156
    total_lines += file_lines
157
    total_loc += file_loc
158
  end
159
  puts "Total:"
160
  puts "  Lines #{total_lines}, LOC #{total_loc}"
161
end
162

  
163
desc "Publish the release files to RubyForge."
164
task :release => [ :package ] do
165
  require 'rubyforge'
166

  
167
  packages = %w( gem tgz zip ).collect{ |ext| "pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}" }
168

  
169
  rubyforge = RubyForge.new
170
  rubyforge.login
171
  rubyforge.add_release(PKG_NAME, PKG_NAME, "REL #{PKG_VERSION}", *packages)
172
end
173 0

  
trunk/vendor/plugins/actionwebservice/setup.rb
1
#
2
# setup.rb
3
#
4
# Copyright (c) 2000-2004 Minero Aoki
5
#
6
# Permission is hereby granted, free of charge, to any person obtaining
7
# a copy of this software and associated documentation files (the
8
# "Software"), to deal in the Software without restriction, including
9
# without limitation the rights to use, copy, modify, merge, publish,
10
# distribute, sublicense, and/or sell copies of the Software, and to
11
# permit persons to whom the Software is furnished to do so, subject to
12
# the following conditions:
13
#
14
# The above copyright notice and this permission notice shall be
15
# included in all copies or substantial portions of the Software.
16
#
17
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
#
25
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
26
# with permission of Minero Aoki.
27

  
28
#
29

  
30
unless Enumerable.method_defined?(:map)   # Ruby 1.4.6
31
  module Enumerable
32
    alias map collect
33
  end
34
end
35

  
36
unless File.respond_to?(:read)   # Ruby 1.6
37
  def File.read(fname)
38
    open(fname) {|f|
39
      return f.read
40
    }
41
  end
42
end
43

  
44
def File.binread(fname)
45
  open(fname, 'rb') {|f|
46
    return f.read
47
  }
48
end
49

  
50
# for corrupted windows stat(2)
51
def File.dir?(path)
52
  File.directory?((path[-1,1] == '/') ? path : path + '/')
53
end
54

  
55

  
56
class SetupError < StandardError; end
57

  
58
def setup_rb_error(msg)
59
  raise SetupError, msg
60
end
61

  
62
#
63
# Config
64
#
65

  
66
if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg }
67
  ARGV.delete(arg)
68
  require arg.split(/=/, 2)[1]
69
  $".push 'rbconfig.rb'
70
else
71
  require 'rbconfig'
72
end
73

  
74
def multipackage_install?
75
  FileTest.directory?(File.dirname($0) + '/packages')
76
end
77

  
78

  
79
class ConfigItem
80
  def initialize(name, template, default, desc)
81
    @name = name.freeze
82
    @template = template
83
    @value = default
84
    @default = default.dup.freeze
85
    @description = desc
86
  end
87

  
88
  attr_reader :name
89
  attr_reader :description
90

  
91
  attr_accessor :default
92
  alias help_default default
93

  
94
  def help_opt
95
    "--#{@name}=#{@template}"
96
  end
97

  
98
  def value
99
    @value
100
  end
101

  
102
  def eval(table)
103
    @value.gsub(%r<\$([^/]+)>) { table[$1] }
104
  end
105

  
106
  def set(val)
107
    @value = check(val)
108
  end
109

  
110
  private
111

  
112
  def check(val)
113
    setup_rb_error "config: --#{name} requires argument" unless val
114
    val
115
  end
116
end
117

  
118
class BoolItem < ConfigItem
119
  def config_type
120
    'bool'
121
  end
122

  
123
  def help_opt
124
    "--#{@name}"
125
  end
126

  
127
  private
128

  
129
  def check(val)
130
    return 'yes' unless val
131
    unless /\A(y(es)?|n(o)?|t(rue)?|f(alse))\z/i =~ val
132
      setup_rb_error "config: --#{@name} accepts only yes/no for argument"
133
    end
134
    (/\Ay(es)?|\At(rue)/i =~ value) ? 'yes' : 'no'
135
  end
136
end
137

  
138
class PathItem < ConfigItem
139
  def config_type
140
    'path'
141
  end
142

  
143
  private
144

  
145
  def check(path)
146
    setup_rb_error "config: --#{@name} requires argument"  unless path
147
    path[0,1] == '$' ? path : File.expand_path(path)
148
  end
149
end
150

  
151
class ProgramItem < ConfigItem
152
  def config_type
153
    'program'
154
  end
155
end
156

  
157
class SelectItem < ConfigItem
158
  def initialize(name, template, default, desc)
159
    super
160
    @ok = template.split('/')
161
  end
162

  
163
  def config_type
164
    'select'
165
  end
166

  
167
  private
168

  
169
  def check(val)
170
    unless @ok.include?(val.strip)
171
      setup_rb_error "config: use --#{@name}=#{@template} (#{val})"
172
    end
173
    val.strip
174
  end
175
end
176

  
177
class PackageSelectionItem < ConfigItem
178
  def initialize(name, template, default, help_default, desc)
179
    super name, template, default, desc
180
    @help_default = help_default
181
  end
182

  
183
  attr_reader :help_default
184

  
185
  def config_type
186
    'package'
187
  end
188

  
189
  private
190

  
191
  def check(val)
192
    unless File.dir?("packages/#{val}")
193
      setup_rb_error "config: no such package: #{val}"
194
    end
195
    val
196
  end
197
end
198

  
199
class ConfigTable_class
200

  
201
  def initialize(items)
202
    @items = items
203
    @table = {}
204
    items.each do |i|
205
      @table[i.name] = i
206
    end
207
    ALIASES.each do |ali, name|
208
      @table[ali] = @table[name]
209
    end
210
  end
211

  
212
  include Enumerable
213

  
214
  def each(&block)
215
    @items.each(&block)
216
  end
217

  
218
  def key?(name)
219
    @table.key?(name)
220
  end
221

  
222
  def lookup(name)
223
    @table[name] or raise ArgumentError, "no such config item: #{name}"
224
  end
225

  
226
  def add(item)
227
    @items.push item
228
    @table[item.name] = item
229
  end
230

  
231
  def remove(name)
232
    item = lookup(name)
233
    @items.delete_if {|i| i.name == name }
234
    @table.delete_if {|name, i| i.name == name }
235
    item
236
  end
237

  
238
  def new
239
    dup()
240
  end
241

  
242
  def savefile
243
    '.config'
244
  end
245

  
246
  def load
247
    begin
248
      t = dup()
249
      File.foreach(savefile()) do |line|
250
        k, v = *line.split(/=/, 2)
251
        t[k] = v.strip
252
      end
253
      t
254
    rescue Errno::ENOENT
255
      setup_rb_error $!.message + "#{File.basename($0)} config first"
256
    end
257
  end
258

  
259
  def save
260
    @items.each {|i| i.value }
261
    File.open(savefile(), 'w') {|f|
262
      @items.each do |i|
263
        f.printf "%s=%s\n", i.name, i.value if i.value
264
      end
265
    }
266
  end
267

  
268
  def [](key)
269
    lookup(key).eval(self)
270
  end
271

  
272
  def []=(key, val)
273
    lookup(key).set val
274
  end
275

  
276
end
277

  
278
c = ::Config::CONFIG
279

  
280
rubypath = c['bindir'] + '/' + c['ruby_install_name']
281

  
282
major = c['MAJOR'].to_i
283
minor = c['MINOR'].to_i
284
teeny = c['TEENY'].to_i
285
version = "#{major}.#{minor}"
286

  
287
# ruby ver. >= 1.4.4?
288
newpath_p = ((major >= 2) or
289
             ((major == 1) and
290
              ((minor >= 5) or
291
               ((minor == 4) and (teeny >= 4)))))
292

  
293
if c['rubylibdir']
294
  # V < 1.6.3
295
  _stdruby         = c['rubylibdir']
296
  _siteruby        = c['sitedir']
297
  _siterubyver     = c['sitelibdir']
298
  _siterubyverarch = c['sitearchdir']
299
elsif newpath_p
300
  # 1.4.4 <= V <= 1.6.3
301
  _stdruby         = "$prefix/lib/ruby/#{version}"
302
  _siteruby        = c['sitedir']
303
  _siterubyver     = "$siteruby/#{version}"
304
  _siterubyverarch = "$siterubyver/#{c['arch']}"
305
else
306
  # V < 1.4.4
307
  _stdruby         = "$prefix/lib/ruby/#{version}"
308
  _siteruby        = "$prefix/lib/ruby/#{version}/site_ruby"
309
  _siterubyver     = _siteruby
310
  _siterubyverarch = "$siterubyver/#{c['arch']}"
311
end
312
libdir = '-* dummy libdir *-'
313
stdruby = '-* dummy rubylibdir *-'
314
siteruby = '-* dummy site_ruby *-'
315
siterubyver = '-* dummy site_ruby version *-'
316
parameterize = lambda {|path|
317
  path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix')\
318
      .sub(/\A#{Regexp.quote(libdir)}/,      '$libdir')\
319
      .sub(/\A#{Regexp.quote(stdruby)}/,     '$stdruby')\
320
      .sub(/\A#{Regexp.quote(siteruby)}/,    '$siteruby')\
321
      .sub(/\A#{Regexp.quote(siterubyver)}/, '$siterubyver')
322
}
323
libdir          = parameterize.call(c['libdir'])
324
stdruby         = parameterize.call(_stdruby)
325
siteruby        = parameterize.call(_siteruby)
326
siterubyver     = parameterize.call(_siterubyver)
327
siterubyverarch = parameterize.call(_siterubyverarch)
328

  
329
if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg }
330
  makeprog = arg.sub(/'/, '').split(/=/, 2)[1]
331
else
332
  makeprog = 'make'
333
end
334

  
335
common_conf = [
336
  PathItem.new('prefix', 'path', c['prefix'],
337
               'path prefix of target environment'),
338
  PathItem.new('bindir', 'path', parameterize.call(c['bindir']),
339
               'the directory for commands'),
340
  PathItem.new('libdir', 'path', libdir,
341
               'the directory for libraries'),
342
  PathItem.new('datadir', 'path', parameterize.call(c['datadir']),
343
               'the directory for shared data'),
344
  PathItem.new('mandir', 'path', parameterize.call(c['mandir']),
345
               'the directory for man pages'),
346
  PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']),
347
               'the directory for man pages'),
348
  PathItem.new('stdruby', 'path', stdruby,
349
               'the directory for standard ruby libraries'),
350
  PathItem.new('siteruby', 'path', siteruby,
351
      'the directory for version-independent aux ruby libraries'),
352
  PathItem.new('siterubyver', 'path', siterubyver,
353
               'the directory for aux ruby libraries'),
354
  PathItem.new('siterubyverarch', 'path', siterubyverarch,
355
               'the directory for aux ruby binaries'),
356
  PathItem.new('rbdir', 'path', '$siterubyver',
357
               'the directory for ruby scripts'),
358
  PathItem.new('sodir', 'path', '$siterubyverarch',
359
               'the directory for ruby extentions'),
360
  PathItem.new('rubypath', 'path', rubypath,
361
               'the path to set to #! line'),
362
  ProgramItem.new('rubyprog', 'name', rubypath,
363
                  'the ruby program using for installation'),
364
  ProgramItem.new('makeprog', 'name', makeprog,
365
                  'the make program to compile ruby extentions'),
366
  SelectItem.new('shebang', 'all/ruby/never', 'ruby',
367
                 'shebang line (#!) editing mode'),
368
  BoolItem.new('without-ext', 'yes/no', 'no',
369
               'does not compile/install ruby extentions')
370
]
371
class ConfigTable_class   # open again
372
  ALIASES = {
373
    'std-ruby'         => 'stdruby',
374
    'site-ruby-common' => 'siteruby',     # For backward compatibility
375
    'site-ruby'        => 'siterubyver',  # For backward compatibility
376
    'bin-dir'          => 'bindir',
377
    'bin-dir'          => 'bindir',
378
    'rb-dir'           => 'rbdir',
379
    'so-dir'           => 'sodir',
380
    'data-dir'         => 'datadir',
381
    'ruby-path'        => 'rubypath',
382
    'ruby-prog'        => 'rubyprog',
383
    'ruby'             => 'rubyprog',
384
    'make-prog'        => 'makeprog',
385
    'make'             => 'makeprog'
386
  }
387
end
388
multipackage_conf = [
389
  PackageSelectionItem.new('with', 'name,name...', '', 'ALL',
390
                           'package names that you want to install'),
391
  PackageSelectionItem.new('without', 'name,name...', '', 'NONE',
392
                           'package names that you do not want to install')
393
]
394
if multipackage_install?
395
  ConfigTable = ConfigTable_class.new(common_conf + multipackage_conf)
396
else
397
  ConfigTable = ConfigTable_class.new(common_conf)
398
end
399

  
400

  
401
module MetaConfigAPI
402

  
403
  def eval_file_ifexist(fname)
404
    instance_eval File.read(fname), fname, 1 if File.file?(fname)
405
  end
406

  
407
  def config_names
408
    ConfigTable.map {|i| i.name }
409
  end
410

  
411
  def config?(name)
412
    ConfigTable.key?(name)
413
  end
414

  
415
  def bool_config?(name)
416
    ConfigTable.lookup(name).config_type == 'bool'
417
  end
418

  
419
  def path_config?(name)
420
    ConfigTable.lookup(name).config_type == 'path'
421
  end
422

  
423
  def value_config?(name)
424
    case ConfigTable.lookup(name).config_type
425
    when 'bool', 'path'
426
      true
427
    else
428
      false
429
    end
430
  end
431

  
432
  def add_config(item)
433
    ConfigTable.add item
434
  end
435

  
436
  def add_bool_config(name, default, desc)
437
    ConfigTable.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc)
438
  end
439

  
440
  def add_path_config(name, default, desc)
441
    ConfigTable.add PathItem.new(name, 'path', default, desc)
442
  end
443

  
444
  def set_config_default(name, default)
445
    ConfigTable.lookup(name).default = default
446
  end
447

  
448
  def remove_config(name)
449
    ConfigTable.remove(name)
450
  end
451

  
452
end
453

  
454

  
455
#
456
# File Operations
457
#
458

  
459
module FileOperations
460

  
461
  def mkdir_p(dirname, prefix = nil)
462
    dirname = prefix + File.expand_path(dirname) if prefix
463
    $stderr.puts "mkdir -p #{dirname}" if verbose?
464
    return if no_harm?
465

  
466
    # does not check '/'... it's too abnormal case
467
    dirs = File.expand_path(dirname).split(%r<(?=/)>)
468
    if /\A[a-z]:\z/i =~ dirs[0]
469
      disk = dirs.shift
470
      dirs[0] = disk + dirs[0]
471
    end
472
    dirs.each_index do |idx|
473
      path = dirs[0..idx].join('')
474
      Dir.mkdir path unless File.dir?(path)
475
    end
476
  end
477

  
478
  def rm_f(fname)
479
    $stderr.puts "rm -f #{fname}" if verbose?
480
    return if no_harm?
481

  
482
    if File.exist?(fname) or File.symlink?(fname)
483
      File.chmod 0777, fname
484
      File.unlink fname
485
    end
486
  end
487

  
488
  def rm_rf(dn)
489
    $stderr.puts "rm -rf #{dn}" if verbose?
490
    return if no_harm?
491

  
492
    Dir.chdir dn
493
    Dir.foreach('.') do |fn|
494
      next if fn == '.'
495
      next if fn == '..'
496
      if File.dir?(fn)
497
        verbose_off {
498
          rm_rf fn
499
        }
500
      else
501
        verbose_off {
502
          rm_f fn
503
        }
504
      end
505
    end
506
    Dir.chdir '..'
507
    Dir.rmdir dn
508
  end
509

  
510
  def move_file(src, dest)
511
    File.unlink dest if File.exist?(dest)
512
    begin
513
      File.rename src, dest
514
    rescue
515
      File.open(dest, 'wb') {|f| f.write File.binread(src) }
516
      File.chmod File.stat(src).mode, dest
517
      File.unlink src
518
    end
519
  end
520

  
521
  def install(from, dest, mode, prefix = nil)
522
    $stderr.puts "install #{from} #{dest}" if verbose?
523
    return if no_harm?
524

  
525
    realdest = prefix ? prefix + File.expand_path(dest) : dest
526
    realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest)
527
    str = File.binread(from)
528
    if diff?(str, realdest)
529
      verbose_off {
530
        rm_f realdest if File.exist?(realdest)
531
      }
532
      File.open(realdest, 'wb') {|f|
533
        f.write str
534
      }
535
      File.chmod mode, realdest
536

  
537
      File.open("#{objdir_root()}/InstalledFiles", 'a') {|f|
538
        if prefix
539
          f.puts realdest.sub(prefix, '')
540
        else
541
          f.puts realdest
542
        end
543
      }
544
    end
545
  end
546

  
547
  def diff?(new_content, path)
548
    return true unless File.exist?(path)
549
    new_content != File.binread(path)
550
  end
551

  
552
  def command(str)
553
    $stderr.puts str if verbose?
554
    system str or raise RuntimeError, "'system #{str}' failed"
555
  end
556

  
557
  def ruby(str)
558
    command config('rubyprog') + ' ' + str
559
  end
560
  
561
  def make(task = '')
562
    command config('makeprog') + ' ' + task
563
  end
564

  
565
  def extdir?(dir)
566
    File.exist?(dir + '/MANIFEST')
567
  end
568

  
569
  def all_files_in(dirname)
570
    Dir.open(dirname) {|d|
571
      return d.select {|ent| File.file?("#{dirname}/#{ent}") }
572
    }
573
  end
574

  
575
  REJECT_DIRS = %w(
576
    CVS SCCS RCS CVS.adm .svn
577
  )
578

  
579
  def all_dirs_in(dirname)
580
    Dir.open(dirname) {|d|
581
      return d.select {|n| File.dir?("#{dirname}/#{n}") } - %w(. ..) - REJECT_DIRS
582
    }
583
  end
584

  
585
end
586

  
587

  
588
#
589
# Main Installer
590
#
591

  
592
module HookUtils
593

  
594
  def run_hook(name)
595
    try_run_hook "#{curr_srcdir()}/#{name}" or
596
    try_run_hook "#{curr_srcdir()}/#{name}.rb"
597
  end
598

  
599
  def try_run_hook(fname)
600
    return false unless File.file?(fname)
601
    begin
602
      instance_eval File.read(fname), fname, 1
603
    rescue
604
      setup_rb_error "hook #{fname} failed:\n" + $!.message
605
    end
606
    true
607
  end
608

  
609
end
610

  
611

  
612
module HookScriptAPI
613

  
614
  def get_config(key)
615
    @config[key]
616
  end
617

  
618
  alias config get_config
619

  
620
  def set_config(key, val)
621
    @config[key] = val
622
  end
623

  
624
  #
625
  # srcdir/objdir (works only in the package directory)
626
  #
627

  
628
  #abstract srcdir_root
629
  #abstract objdir_root
630
  #abstract relpath
631

  
632
  def curr_srcdir
633
    "#{srcdir_root()}/#{relpath()}"
634
  end
635

  
636
  def curr_objdir
637
    "#{objdir_root()}/#{relpath()}"
638
  end
639

  
640
  def srcfile(path)
641
    "#{curr_srcdir()}/#{path}"
642
  end
643

  
644
  def srcexist?(path)
645
    File.exist?(srcfile(path))
646
  end
647

  
648
  def srcdirectory?(path)
649
    File.dir?(srcfile(path))
650
  end
651
  
652
  def srcfile?(path)
653
    File.file? srcfile(path)
654
  end
655

  
656
  def srcentries(path = '.')
657
    Dir.open("#{curr_srcdir()}/#{path}") {|d|
658
      return d.to_a - %w(. ..)
659
    }
660
  end
661

  
662
  def srcfiles(path = '.')
663
    srcentries(path).select {|fname|
664
      File.file?(File.join(curr_srcdir(), path, fname))
665
    }
666
  end
667

  
668
  def srcdirectories(path = '.')
669
    srcentries(path).select {|fname|
670
      File.dir?(File.join(curr_srcdir(), path, fname))
671
    }
672
  end
673

  
674
end
675

  
676

  
677
class ToplevelInstaller
678

  
679
  Version   = '3.3.1'
680
  Copyright = 'Copyright (c) 2000-2004 Minero Aoki'
681

  
682
  TASKS = [
683
    [ 'all',      'do config, setup, then install' ],
684
    [ 'config',   'saves your configurations' ],
685
    [ 'show',     'shows current configuration' ],
686
    [ 'setup',    'compiles ruby extentions and others' ],
687
    [ 'install',  'installs files' ],
688
    [ 'clean',    "does `make clean' for each extention" ],
689
    [ 'distclean',"does `make distclean' for each extention" ]
690
  ]
691

  
692
  def ToplevelInstaller.invoke
693
    instance().invoke
694
  end
695

  
696
  @singleton = nil
697

  
698
  def ToplevelInstaller.instance
699
    @singleton ||= new(File.dirname($0))
700
    @singleton
701
  end
702

  
703
  include MetaConfigAPI
704

  
705
  def initialize(ardir_root)
706
    @config = nil
707
    @options = { 'verbose' => true }
708
    @ardir = File.expand_path(ardir_root)
709
  end
710

  
711
  def inspect
712
    "#<#{self.class} #{__id__()}>"
713
  end
714

  
715
  def invoke
716
    run_metaconfigs
717
    case task = parsearg_global()
718
    when nil, 'all'
719
      @config = load_config('config')
720
      parsearg_config
721
      init_installers
722
      exec_config
723
      exec_setup
724
      exec_install
725
    else
726
      @config = load_config(task)
727
      __send__ "parsearg_#{task}"
728
      init_installers
729
      __send__ "exec_#{task}"
730
    end
731
  end
732
  
733
  def run_metaconfigs
734
    eval_file_ifexist "#{@ardir}/metaconfig"
735
  end
736

  
737
  def load_config(task)
738
    case task
739
    when 'config'
740
      ConfigTable.new
741
    when 'clean', 'distclean'
742
      if File.exist?(ConfigTable.savefile)
743
      then ConfigTable.load
744
      else ConfigTable.new
745
      end
746
    else
747
      ConfigTable.load
748
    end
749
  end
750

  
751
  def init_installers
752
    @installer = Installer.new(@config, @options, @ardir, File.expand_path('.'))
753
  end
754

  
755
  #
756
  # Hook Script API bases
757
  #
758

  
759
  def srcdir_root
760
    @ardir
761
  end
762

  
763
  def objdir_root
764
    '.'
765
  end
766

  
767
  def relpath
768
    '.'
769
  end
770

  
771
  #
772
  # Option Parsing
773
  #
774

  
775
  def parsearg_global
776
    valid_task = /\A(?:#{TASKS.map {|task,desc| task }.join '|'})\z/
777

  
778
    while arg = ARGV.shift
779
      case arg
780
      when /\A\w+\z/
781
        setup_rb_error "invalid task: #{arg}" unless valid_task =~ arg
782
        return arg
783

  
784
      when '-q', '--quiet'
785
        @options['verbose'] = false
786

  
787
      when       '--verbose'
788
        @options['verbose'] = true
789

  
790
      when '-h', '--help'
791
        print_usage $stdout
792
        exit 0
793

  
794
      when '-v', '--version'
795
        puts "#{File.basename($0)} version #{Version}"
796
        exit 0
797
      
798
      when '--copyright'
799
        puts Copyright
800
        exit 0
801

  
802
      else
803
        setup_rb_error "unknown global option '#{arg}'"
804
      end
805
    end
806

  
807
    nil
808
  end
809

  
810

  
811
  def parsearg_no_options
812
    unless ARGV.empty?
813
      setup_rb_error "#{task}:  unknown options: #{ARGV.join ' '}"
814
    end
815
  end
816

  
817
  alias parsearg_show       parsearg_no_options
818
  alias parsearg_setup      parsearg_no_options
819
  alias parsearg_clean      parsearg_no_options
820
  alias parsearg_distclean  parsearg_no_options
821

  
822
  def parsearg_config
823
    re = /\A--(#{ConfigTable.map {|i| i.name }.join('|')})(?:=(.*))?\z/
824
    @options['config-opt'] = []
825

  
826
    while i = ARGV.shift
827
      if /\A--?\z/ =~ i
828
        @options['config-opt'] = ARGV.dup
829
        break
830
      end
831
      m = re.match(i)  or setup_rb_error "config: unknown option #{i}"
832
      name, value = *m.to_a[1,2]
833
      @config[name] = value
834
    end
835
  end
836

  
837
  def parsearg_install
838
    @options['no-harm'] = false
839
    @options['install-prefix'] = ''
840
    while a = ARGV.shift
841
      case a
842
      when /\A--no-harm\z/
843
        @options['no-harm'] = true
844
      when /\A--prefix=(.*)\z/
845
        path = $1
846
        path = File.expand_path(path) unless path[0,1] == '/'
847
        @options['install-prefix'] = path
848
      else
849
        setup_rb_error "install: unknown option #{a}"
850
      end
851
    end
852
  end
853

  
854
  def print_usage(out)
855
    out.puts 'Typical Installation Procedure:'
856
    out.puts "  $ ruby #{File.basename $0} config"
857
    out.puts "  $ ruby #{File.basename $0} setup"
858
    out.puts "  # ruby #{File.basename $0} install (may require root privilege)"
859
    out.puts
860
    out.puts 'Detailed Usage:'
861
    out.puts "  ruby #{File.basename $0} <global option>"
862
    out.puts "  ruby #{File.basename $0} [<global options>] <task> [<task options>]"
863

  
864
    fmt = "  %-24s %s\n"
865
    out.puts
866
    out.puts 'Global options:'
867
    out.printf fmt, '-q,--quiet',   'suppress message outputs'
868
    out.printf fmt, '   --verbose', 'output messages verbosely'
869
    out.printf fmt, '-h,--help',    'print this message'
870
    out.printf fmt, '-v,--version', 'print version and quit'
871
    out.printf fmt, '   --copyright',  'print copyright and quit'
872
    out.puts
873
    out.puts 'Tasks:'
874
    TASKS.each do |name, desc|
875
      out.printf fmt, name, desc
876
    end
877

  
878
    fmt = "  %-24s %s [%s]\n"
879
    out.puts
880
    out.puts 'Options for CONFIG or ALL:'
881
    ConfigTable.each do |item|
882
      out.printf fmt, item.help_opt, item.description, item.help_default
883
    end
884
    out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's"
885
    out.puts
886
    out.puts 'Options for INSTALL:'
887
    out.printf fmt, '--no-harm', 'only display what to do if given', 'off'
888
    out.printf fmt, '--prefix=path',  'install path prefix', '$prefix'
889
    out.puts
890
  end
891

  
892
  #
893
  # Task Handlers
894
  #
895

  
896
  def exec_config
897
    @installer.exec_config
898
    @config.save   # must be final
899
  end
900

  
901
  def exec_setup
902
    @installer.exec_setup
903
  end
904

  
905
  def exec_install
906
    @installer.exec_install
907
  end
908

  
909
  def exec_show
910
    ConfigTable.each do |i|
911
      printf "%-20s %s\n", i.name, i.value
912
    end
913
  end
914

  
915
  def exec_clean
916
    @installer.exec_clean
917
  end
918

  
919
  def exec_distclean
920
    @installer.exec_distclean
921
  end
922

  
923
end
924

  
925

  
926
class ToplevelInstallerMulti < ToplevelInstaller
927

  
928
  include HookUtils
929
  include HookScriptAPI
930
  include FileOperations
931

  
932
  def initialize(ardir)
933
    super
934
    @packages = all_dirs_in("#{@ardir}/packages")
935
    raise 'no package exists' if @packages.empty?
936
  end
937

  
938
  def run_metaconfigs
939
    eval_file_ifexist "#{@ardir}/metaconfig"
940
    @packages.each do |name|
941
      eval_file_ifexist "#{@ardir}/packages/#{name}/metaconfig"
942
    end
943
  end
944

  
945
  def init_installers
946
    @installers = {}
947
    @packages.each do |pack|
948
      @installers[pack] = Installer.new(@config, @options,
949
                                       "#{@ardir}/packages/#{pack}",
950
                                       "packages/#{pack}")
951
    end
952

  
953
    with    = extract_selection(config('with'))
954
    without = extract_selection(config('without'))
955
    @selected = @installers.keys.select {|name|
956
                  (with.empty? or with.include?(name)) \
957
                      and not without.include?(name)
958
                }
959
  end
960

  
961
  def extract_selection(list)
962
    a = list.split(/,/)
963
    a.each do |name|
964
      setup_rb_error "no such package: #{name}"  unless @installers.key?(name)
965
    end
966
    a
967
  end
968

  
969
  def print_usage(f)
970
    super
971
    f.puts 'Included packages:'
972
    f.puts '  ' + @packages.sort.join(' ')
973
    f.puts
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff