1require 'rubygems/command'
2
3##
4# Installs RubyGems itself.  This command is ordinarily only available from a
5# RubyGems checkout or tarball.
6
7class Gem::Commands::SetupCommand < Gem::Command
8  HISTORY_HEADER = /^===\s*[\d.]+\s*\/\s*\d{4}-\d{2}-\d{2}\s*$/
9  VERSION_MATCHER = /^===\s*([\d.]+)\s*\/\s*\d{4}-\d{2}-\d{2}\s*$/
10
11  def initialize
12    require 'tmpdir'
13
14    super 'setup', 'Install RubyGems',
15          :format_executable => true, :document => %w[ri],
16          :site_or_vendor => :sitelibdir,
17          :destdir => '', :prefix => '', :previous_version => ''
18
19    add_option '--previous-version=VERSION',
20               'Previous version of rubygems',
21               'Used for changelog processing' do |version, options|
22      options[:previous_version] = version
23    end
24
25    add_option '--prefix=PREFIX',
26               'Prefix path for installing RubyGems',
27               'Will not affect gem repository location' do |prefix, options|
28      options[:prefix] = File.expand_path prefix
29    end
30
31    add_option '--destdir=DESTDIR',
32               'Root directory to install RubyGems into',
33               'Mainly used for packaging RubyGems' do |destdir, options|
34      options[:destdir] = File.expand_path destdir
35    end
36
37    add_option '--[no-]vendor',
38               'Install into vendorlibdir not sitelibdir' do |vendor, options|
39      options[:site_or_vendor] = vendor ? :vendorlibdir : :sitelibdir
40    end
41
42    add_option '--[no-]format-executable',
43               'Makes `gem` match ruby',
44               'If ruby is ruby18, gem will be gem18' do |value, options|
45      options[:format_executable] = value
46    end
47
48    add_option '--[no-]document [TYPES]', Array,
49               'Generate documentation for RubyGems.',
50               'List the documentation types you wish to',
51               'generate.  For example: rdoc,ri' do |value, options|
52      options[:document] = case value
53                           when nil   then %w[rdoc ri]
54                           when false then []
55                           else            value
56                           end
57    end
58
59    add_option '--[no-]rdoc',
60               'Generate RDoc documentation for RubyGems' do |value, options|
61      if value then
62        options[:document] << 'rdoc'
63      else
64        options[:document].delete 'rdoc'
65      end
66
67      options[:document].uniq!
68    end
69
70    add_option '--[no-]ri',
71               'Generate RI documentation for RubyGems' do |value, options|
72      if value then
73        options[:document] << 'ri'
74      else
75        options[:document].delete 'ri'
76      end
77
78      options[:document].uniq!
79    end
80
81    @verbose = nil
82  end
83
84  def check_ruby_version
85    required_version = Gem::Requirement.new '>= 1.8.7'
86
87    unless required_version.satisfied_by? Gem.ruby_version then
88      alert_error "Expected Ruby version #{required_version}, is #{Gem.ruby_version}"
89      terminate_interaction 1
90    end
91  end
92
93  def defaults_str # :nodoc:
94    "--format-executable --document ri"
95  end
96
97  def description # :nodoc:
98    <<-EOF
99Installs RubyGems itself.
100
101RubyGems installs RDoc for itself in GEM_HOME.  By default this is:
102  #{Gem.dir}
103
104If you prefer a different directory, set the GEM_HOME environment variable.
105
106RubyGems will install the gem command with a name matching ruby's
107prefix and suffix.  If ruby was installed as `ruby18`, gem will be
108installed as `gem18`.
109
110By default, this RubyGems will install gem as:
111  #{Gem.default_exec_format % 'gem'}
112    EOF
113  end
114
115  def execute
116    @verbose = Gem.configuration.really_verbose
117
118    install_destdir = options[:destdir]
119
120    unless install_destdir.empty? then
121      ENV['GEM_HOME'] ||= File.join(install_destdir,
122                                    Gem.default_dir.gsub(/^[a-zA-Z]:/, ''))
123    end
124
125    check_ruby_version
126
127    require 'fileutils'
128    if Gem.configuration.really_verbose then
129      extend FileUtils::Verbose
130    else
131      extend FileUtils
132    end
133
134    lib_dir, bin_dir = make_destination_dirs install_destdir
135
136    install_lib lib_dir
137
138    install_executables bin_dir
139
140    remove_old_bin_files bin_dir
141
142    remove_old_lib_files lib_dir
143
144    say "RubyGems #{Gem::VERSION} installed"
145
146    uninstall_old_gemcutter
147
148    documentation_success = install_rdoc
149
150    say
151    if @verbose then
152      say "-" * 78
153      say
154    end
155
156    if options[:previous_version].empty?
157      options[:previous_version] = Gem::VERSION.sub(/[0-9]+$/, '0')
158    end
159
160    options[:previous_version] = Gem::Version.new(options[:previous_version])
161
162    show_release_notes
163
164    say
165    say "-" * 78
166    say
167
168    say "RubyGems installed the following executables:"
169    say @bin_file_names.map { |name| "\t#{name}\n" }
170    say
171
172    unless @bin_file_names.grep(/#{File::SEPARATOR}gem$/) then
173      say "If `gem` was installed by a previous RubyGems installation, you may need"
174      say "to remove it by hand."
175      say
176    end
177
178    if documentation_success
179      if options[:document].include? 'rdoc' then
180        say "Rdoc documentation was installed. You may now invoke:"
181        say "  gem server"
182        say "and then peruse beautifully formatted documentation for your gems"
183        say "with your web browser."
184        say "If you do not wish to install this documentation in the future, use the"
185        say "--no-document flag, or set it as the default in your ~/.gemrc file. See"
186        say "'gem help env' for details."
187        say
188      end
189
190      if options[:document].include? 'ri' then
191        say "Ruby Interactive (ri) documentation was installed. ri is kind of like man "
192        say "pages for ruby libraries. You may access it like this:"
193        say "  ri Classname"
194        say "  ri Classname.class_method"
195        say "  ri Classname#instance_method"
196        say "If you do not wish to install this documentation in the future, use the"
197        say "--no-document flag, or set it as the default in your ~/.gemrc file. See"
198        say "'gem help env' for details."
199        say
200      end
201    end
202  end
203
204  def install_executables(bin_dir)
205    say "Installing gem executable" if @verbose
206
207    @bin_file_names = []
208
209    Dir.chdir 'bin' do
210      bin_files = Dir['*']
211
212      bin_files.delete 'update_rubygems'
213
214      bin_files.each do |bin_file|
215        bin_file_formatted = if options[:format_executable] then
216                               Gem.default_exec_format % bin_file
217                             else
218                               bin_file
219                             end
220
221        dest_file = File.join bin_dir, bin_file_formatted
222        bin_tmp_file = File.join Dir.tmpdir, "#{bin_file}.#{$$}"
223
224        begin
225          bin = File.readlines bin_file
226          bin[0] = "#!#{Gem.ruby}\n"
227
228          File.open bin_tmp_file, 'w' do |fp|
229            fp.puts bin.join
230          end
231
232          install bin_tmp_file, dest_file, :mode => 0755
233          @bin_file_names << dest_file
234        ensure
235          rm bin_tmp_file
236        end
237
238        next unless Gem.win_platform?
239
240        begin
241          bin_cmd_file = File.join Dir.tmpdir, "#{bin_file}.bat"
242
243          File.open bin_cmd_file, 'w' do |file|
244            file.puts <<-TEXT
245@ECHO OFF
246IF NOT "%~f0" == "~f0" GOTO :WinNT
247@"#{File.basename(Gem.ruby).chomp('"')}" "#{dest_file}" %1 %2 %3 %4 %5 %6 %7 %8 %9
248GOTO :EOF
249:WinNT
250@"#{File.basename(Gem.ruby).chomp('"')}" "%~dpn0" %*
251TEXT
252          end
253
254          install bin_cmd_file, "#{dest_file}.bat", :mode => 0755
255        ensure
256          rm bin_cmd_file
257        end
258      end
259    end
260  end
261
262  def install_file file, dest_dir
263    dest_file = File.join dest_dir, file
264    dest_dir = File.dirname dest_file
265    mkdir_p dest_dir unless File.directory? dest_dir
266
267    install file, dest_file, :mode => 0644
268  end
269
270  def install_lib(lib_dir)
271    say "Installing RubyGems" if @verbose
272
273    lib_files = rb_files_in 'lib'
274    pem_files = pem_files_in 'lib'
275
276    Dir.chdir 'lib' do
277      lib_files.each do |lib_file|
278        install_file lib_file, lib_dir
279      end
280
281      pem_files.each do |pem_file|
282        install_file pem_file, lib_dir
283      end
284    end
285  end
286
287  def install_rdoc
288    gem_doc_dir = File.join Gem.dir, 'doc'
289    rubygems_name = "rubygems-#{Gem::VERSION}"
290    rubygems_doc_dir = File.join gem_doc_dir, rubygems_name
291
292    begin
293      Gem.ensure_gem_subdirectories Gem.dir
294    rescue SystemCallError
295      # ignore
296    end
297
298    if File.writable? gem_doc_dir and
299       (not File.exist? rubygems_doc_dir or
300        File.writable? rubygems_doc_dir) then
301      say "Removing old RubyGems RDoc and ri" if @verbose
302      Dir[File.join(Gem.dir, 'doc', 'rubygems-[0-9]*')].each do |dir|
303        rm_rf dir
304      end
305
306      require 'rubygems/rdoc'
307
308      fake_spec = Gem::Specification.new 'rubygems', Gem::VERSION
309      def fake_spec.full_gem_path
310        File.expand_path '../../../..', __FILE__
311      end
312
313      generate_ri   = options[:document].include? 'ri'
314      generate_rdoc = options[:document].include? 'rdoc'
315
316      rdoc = Gem::RDoc.new fake_spec, generate_rdoc, generate_ri
317      rdoc.generate
318
319      return true
320    elsif @verbose then
321      say "Skipping RDoc generation, #{gem_doc_dir} not writable"
322      say "Set the GEM_HOME environment variable if you want RDoc generated"
323    end
324
325    return false
326  end
327
328  def make_destination_dirs(install_destdir)
329    lib_dir, bin_dir = Gem.default_rubygems_dirs
330
331    unless lib_dir
332      lib_dir, bin_dir = generate_default_dirs(install_destdir)
333    end
334
335    mkdir_p lib_dir
336    mkdir_p bin_dir
337
338    return lib_dir, bin_dir
339  end
340
341  def generate_default_dirs(install_destdir)
342    prefix = options[:prefix]
343    site_or_vendor = options[:site_or_vendor]
344
345    if prefix.empty? then
346      lib_dir = Gem::ConfigMap[site_or_vendor]
347      bin_dir = Gem::ConfigMap[:bindir]
348    else
349      # Apple installed RubyGems into libdir, and RubyGems <= 1.1.0 gets
350      # confused about installation location, so switch back to
351      # sitelibdir/vendorlibdir.
352      if defined?(APPLE_GEM_HOME) and
353        # just in case Apple and RubyGems don't get this patched up proper.
354        (prefix == Gem::ConfigMap[:libdir] or
355         # this one is important
356         prefix == File.join(Gem::ConfigMap[:libdir], 'ruby')) then
357         lib_dir = Gem::ConfigMap[site_or_vendor]
358         bin_dir = Gem::ConfigMap[:bindir]
359      else
360        lib_dir = File.join prefix, 'lib'
361        bin_dir = File.join prefix, 'bin'
362      end
363    end
364
365    unless install_destdir.empty? then
366      lib_dir = File.join install_destdir, lib_dir.gsub(/^[a-zA-Z]:/, '')
367      bin_dir = File.join install_destdir, bin_dir.gsub(/^[a-zA-Z]:/, '')
368    end
369
370    [lib_dir, bin_dir]
371  end
372
373  def pem_files_in dir
374    Dir.chdir dir do
375      Dir[File.join('**', '*pem')]
376    end
377  end
378
379  def rb_files_in dir
380    Dir.chdir dir do
381      Dir[File.join('**', '*rb')]
382    end
383  end
384
385  def remove_old_bin_files(bin_dir)
386    old_bin_files = {
387      'gem_mirror' => 'gem mirror',
388      'gem_server' => 'gem server',
389      'gemlock' => 'gem lock',
390      'gemri' => 'ri',
391      'gemwhich' => 'gem which',
392      'index_gem_repository.rb' => 'gem generate_index',
393    }
394
395    old_bin_files.each do |old_bin_file, new_name|
396      old_bin_path = File.join bin_dir, old_bin_file
397      next unless File.exist? old_bin_path
398
399      deprecation_message = "`#{old_bin_file}` has been deprecated.  Use `#{new_name}` instead."
400
401      File.open old_bin_path, 'w' do |fp|
402        fp.write <<-EOF
403#!#{Gem.ruby}
404
405abort "#{deprecation_message}"
406    EOF
407      end
408
409      next unless Gem.win_platform?
410
411      File.open "#{old_bin_path}.bat", 'w' do |fp|
412        fp.puts %{@ECHO.#{deprecation_message}}
413      end
414    end
415  end
416
417  def remove_old_lib_files lib_dir
418    rubygems_dir = File.join lib_dir, 'rubygems'
419    lib_files = rb_files_in 'lib/rubygems'
420
421    old_lib_files = rb_files_in rubygems_dir
422
423    to_remove = old_lib_files - lib_files
424
425    to_remove.delete_if do |file|
426      file.start_with? 'defaults'
427    end
428
429    Dir.chdir rubygems_dir do
430      to_remove.each do |file|
431        FileUtils.rm_f file
432
433        warn "unable to remove old file #{file} please remove it by hand" if
434          File.exist? file
435      end
436    end
437  end
438
439  def show_release_notes
440    release_notes = File.join Dir.pwd, 'History.txt'
441
442    release_notes =
443      if File.exist? release_notes then
444        history = File.read release_notes
445
446        history.force_encoding Encoding::UTF_8 if
447          Object.const_defined? :Encoding
448
449        history = history.sub(/^# coding:.*?^=/m, '')
450
451        text = history.split(HISTORY_HEADER)
452        text.shift # correct an off-by-one generated by split
453        version_lines = history.scan(HISTORY_HEADER)
454        versions = history.scan(VERSION_MATCHER).flatten.map do |x|
455          Gem::Version.new(x)
456        end
457
458        history_string = ""
459
460        until versions.length == 0 or
461              versions.shift < options[:previous_version] do
462          history_string += version_lines.shift + text.shift
463        end
464
465        history_string
466      else
467        "Oh-no! Unable to find release notes!"
468      end
469
470    say release_notes
471  end
472
473  def uninstall_old_gemcutter
474    require 'rubygems/uninstaller'
475
476    ui = Gem::Uninstaller.new('gemcutter', :all => true, :ignore => true,
477                              :version => '< 0.4')
478    ui.uninstall
479  rescue Gem::InstallError
480  end
481
482end
483
484