1# frozen_string_literal: true
2require 'rubygems/command'
3
4##
5# Installs RubyGems itself.  This command is ordinarily only available from a
6# RubyGems checkout or tarball.
7
8class Gem::Commands::SetupCommand < Gem::Command
9  HISTORY_HEADER = /^===\s*[\d.a-zA-Z]+\s*\/\s*\d{4}-\d{2}-\d{2}\s*$/.freeze
10  VERSION_MATCHER = /^===\s*([\d.a-zA-Z]+)\s*\/\s*\d{4}-\d{2}-\d{2}\s*$/.freeze
11
12  ENV_PATHS = %w[/usr/bin/env /bin/env].freeze
13
14  def initialize
15    require 'tmpdir'
16
17    super 'setup', 'Install RubyGems',
18          :format_executable => true, :document => %w[ri],
19          :site_or_vendor => 'sitelibdir',
20          :destdir => '', :prefix => '', :previous_version => '',
21          :regenerate_binstubs => true
22
23    add_option '--previous-version=VERSION',
24               'Previous version of RubyGems',
25               'Used for changelog processing' do |version, options|
26      options[:previous_version] = version
27    end
28
29    add_option '--prefix=PREFIX',
30               'Prefix path for installing RubyGems',
31               'Will not affect gem repository location' do |prefix, options|
32      options[:prefix] = File.expand_path prefix
33    end
34
35    add_option '--destdir=DESTDIR',
36               'Root directory to install RubyGems into',
37               'Mainly used for packaging RubyGems' do |destdir, options|
38      options[:destdir] = File.expand_path destdir
39    end
40
41    add_option '--[no-]vendor',
42               'Install into vendorlibdir not sitelibdir' do |vendor, options|
43      options[:site_or_vendor] = vendor ? 'vendorlibdir' : 'sitelibdir'
44    end
45
46    add_option '--[no-]format-executable',
47               'Makes `gem` match ruby',
48               'If Ruby is ruby18, gem will be gem18' do |value, options|
49      options[:format_executable] = value
50    end
51
52    add_option '--[no-]document [TYPES]', Array,
53               'Generate documentation for RubyGems',
54               'List the documentation types you wish to',
55               'generate.  For example: rdoc,ri' do |value, options|
56      options[:document] = case value
57                           when nil   then %w[rdoc ri]
58                           when false then []
59                           else            value
60                           end
61    end
62
63    add_option '--[no-]rdoc',
64               'Generate RDoc documentation for RubyGems' do |value, options|
65      if value
66        options[:document] << 'rdoc'
67      else
68        options[:document].delete 'rdoc'
69      end
70
71      options[:document].uniq!
72    end
73
74    add_option '--[no-]ri',
75               'Generate RI documentation for RubyGems' do |value, options|
76      if value
77        options[:document] << 'ri'
78      else
79        options[:document].delete 'ri'
80      end
81
82      options[:document].uniq!
83    end
84
85    add_option '--[no-]regenerate-binstubs',
86               'Regenerate gem binstubs' do |value, options|
87      options[:regenerate_binstubs] = value
88    end
89
90    add_option('-E', '--[no-]env-shebang',
91               'Rewrite executables with a shebang',
92               'of /usr/bin/env') do |value, options|
93      options[:env_shebang] = value
94    end
95
96    @verbose = nil
97  end
98
99  def check_ruby_version
100    required_version = Gem::Requirement.new '>= 1.8.7'
101
102    unless required_version.satisfied_by? Gem.ruby_version
103      alert_error "Expected Ruby version #{required_version}, is #{Gem.ruby_version}"
104      terminate_interaction 1
105    end
106  end
107
108  def defaults_str # :nodoc:
109    "--format-executable --document ri --regenerate-binstubs"
110  end
111
112  def description # :nodoc:
113    <<-EOF
114Installs RubyGems itself.
115
116RubyGems installs RDoc for itself in GEM_HOME.  By default this is:
117  #{Gem.dir}
118
119If you prefer a different directory, set the GEM_HOME environment variable.
120
121RubyGems will install the gem command with a name matching ruby's
122prefix and suffix.  If ruby was installed as `ruby18`, gem will be
123installed as `gem18`.
124
125By default, this RubyGems will install gem as:
126  #{Gem.default_exec_format % 'gem'}
127    EOF
128  end
129
130  module MakeDirs
131    def mkdir_p(path, *opts)
132      super
133      (@mkdirs ||= []) << path
134    end
135  end
136
137  def execute
138    @verbose = Gem.configuration.really_verbose
139
140    install_destdir = options[:destdir]
141
142    unless install_destdir.empty?
143      ENV['GEM_HOME'] ||= File.join(install_destdir,
144                                    Gem.default_dir.gsub(/^[a-zA-Z]:/, ''))
145    end
146
147    check_ruby_version
148
149    require 'fileutils'
150    if Gem.configuration.really_verbose
151      extend FileUtils::Verbose
152    else
153      extend FileUtils
154    end
155    extend MakeDirs
156
157    lib_dir, bin_dir = make_destination_dirs install_destdir
158
159    install_lib lib_dir
160
161    install_executables bin_dir
162
163    remove_old_bin_files bin_dir
164
165    remove_old_lib_files lib_dir
166
167    install_default_bundler_gem
168
169    if mode = options[:dir_mode]
170      @mkdirs.uniq!
171      File.chmod(mode, @mkdirs)
172    end
173
174    say "RubyGems #{Gem::VERSION} installed"
175
176    regenerate_binstubs if options[:regenerate_binstubs]
177
178    uninstall_old_gemcutter
179
180    documentation_success = install_rdoc
181
182    say
183    if @verbose
184      say "-" * 78
185      say
186    end
187
188    if options[:previous_version].empty?
189      options[:previous_version] = Gem::VERSION.sub(/[0-9]+$/, '0')
190    end
191
192    options[:previous_version] = Gem::Version.new(options[:previous_version])
193
194    show_release_notes
195
196    say
197    say "-" * 78
198    say
199
200    say "RubyGems installed the following executables:"
201    say @bin_file_names.map { |name| "\t#{name}\n" }
202    say
203
204    unless @bin_file_names.grep(/#{File::SEPARATOR}gem$/)
205      say "If `gem` was installed by a previous RubyGems installation, you may need"
206      say "to remove it by hand."
207      say
208    end
209
210    if documentation_success
211      if options[:document].include? 'rdoc'
212        say "Rdoc documentation was installed. You may now invoke:"
213        say "  gem server"
214        say "and then peruse beautifully formatted documentation for your gems"
215        say "with your web browser."
216        say "If you do not wish to install this documentation in the future, use the"
217        say "--no-document flag, or set it as the default in your ~/.gemrc file. See"
218        say "'gem help env' for details."
219        say
220      end
221
222      if options[:document].include? 'ri'
223        say "Ruby Interactive (ri) documentation was installed. ri is kind of like man "
224        say "pages for Ruby libraries. You may access it like this:"
225        say "  ri Classname"
226        say "  ri Classname.class_method"
227        say "  ri Classname#instance_method"
228        say "If you do not wish to install this documentation in the future, use the"
229        say "--no-document flag, or set it as the default in your ~/.gemrc file. See"
230        say "'gem help env' for details."
231        say
232      end
233    end
234  end
235
236
237  def install_executables(bin_dir)
238    @bin_file_names = []
239
240    prog_mode = options[:prog_mode] || 0755
241
242    executables = { 'gem' => 'bin' }
243    executables['bundler'] = 'bundler/exe' if Gem::USE_BUNDLER_FOR_GEMDEPS
244    executables.each do |tool, path|
245      say "Installing #{tool} executable" if @verbose
246
247      Dir.chdir path do
248        bin_files = Dir['*']
249
250        bin_files -= %w[update_rubygems bundler bundle_ruby]
251
252        bin_files.each do |bin_file|
253          bin_file_formatted = if options[:format_executable]
254                                 Gem.default_exec_format % bin_file
255                               else
256                                 bin_file
257                               end
258
259          dest_file = File.join bin_dir, bin_file_formatted
260          bin_tmp_file = File.join Dir.tmpdir, "#{bin_file}.#{$$}"
261
262          begin
263            bin = File.readlines bin_file
264            bin[0] = shebang
265
266            File.open bin_tmp_file, 'w' do |fp|
267              fp.puts bin.join
268            end
269
270            install bin_tmp_file, dest_file, :mode => prog_mode
271            @bin_file_names << dest_file
272          ensure
273            rm bin_tmp_file
274          end
275
276          next unless Gem.win_platform?
277
278          begin
279            bin_cmd_file = File.join Dir.tmpdir, "#{bin_file}.bat"
280
281            File.open bin_cmd_file, 'w' do |file|
282              file.puts <<-TEXT
283  @ECHO OFF
284  IF NOT "%~f0" == "~f0" GOTO :WinNT
285  @"#{File.basename(Gem.ruby).chomp('"')}" "#{dest_file}" %1 %2 %3 %4 %5 %6 %7 %8 %9
286  GOTO :EOF
287  :WinNT
288  @"#{File.basename(Gem.ruby).chomp('"')}" "%~dpn0" %*
289  TEXT
290            end
291
292            install bin_cmd_file, "#{dest_file}.bat", :mode => prog_mode
293          ensure
294            rm bin_cmd_file
295          end
296        end
297      end
298    end
299  end
300
301  def shebang
302    if options[:env_shebang]
303      ruby_name = RbConfig::CONFIG['ruby_install_name']
304      @env_path ||= ENV_PATHS.find {|env_path| File.executable? env_path }
305      "#!#{@env_path} #{ruby_name}\n"
306    else
307      "#!#{Gem.ruby}\n"
308    end
309  end
310
311  def install_file(file, dest_dir)
312    dest_file = File.join dest_dir, file
313    dest_dir = File.dirname dest_file
314    unless File.directory? dest_dir
315      mkdir_p dest_dir, :mode => 0755
316    end
317
318    install file, dest_file, :mode => options[:data_mode] || 0644
319  end
320
321  def install_lib(lib_dir)
322    libs = { 'RubyGems' => 'lib' }
323    libs['Bundler'] = 'bundler/lib' if Gem::USE_BUNDLER_FOR_GEMDEPS
324    libs.each do |tool, path|
325      say "Installing #{tool}" if @verbose
326
327      lib_files = rb_files_in path
328      lib_files.concat(template_files) if tool == 'Bundler'
329
330      pem_files = pem_files_in path
331
332      Dir.chdir path do
333        lib_files.each do |lib_file|
334          install_file lib_file, lib_dir
335        end
336
337        pem_files.each do |pem_file|
338          install_file pem_file, lib_dir
339        end
340      end
341    end
342  end
343
344  def install_rdoc
345    gem_doc_dir = File.join Gem.dir, 'doc'
346    rubygems_name = "rubygems-#{Gem::VERSION}"
347    rubygems_doc_dir = File.join gem_doc_dir, rubygems_name
348
349    begin
350      Gem.ensure_gem_subdirectories Gem.dir
351    rescue SystemCallError
352      # ignore
353    end
354
355    if File.writable? gem_doc_dir and
356       (not File.exist? rubygems_doc_dir or
357        File.writable? rubygems_doc_dir)
358      say "Removing old RubyGems RDoc and ri" if @verbose
359      Dir[File.join(Gem.dir, 'doc', 'rubygems-[0-9]*')].each do |dir|
360        rm_rf dir
361      end
362
363      require 'rubygems/rdoc'
364
365      fake_spec = Gem::Specification.new 'rubygems', Gem::VERSION
366      def fake_spec.full_gem_path
367        File.expand_path '../../../..', __FILE__
368      end
369
370      generate_ri   = options[:document].include? 'ri'
371      generate_rdoc = options[:document].include? 'rdoc'
372
373      rdoc = Gem::RDoc.new fake_spec, generate_rdoc, generate_ri
374      rdoc.generate
375
376      return true
377    elsif @verbose
378      say "Skipping RDoc generation, #{gem_doc_dir} not writable"
379      say "Set the GEM_HOME environment variable if you want RDoc generated"
380    end
381
382    return false
383  end
384
385  def install_default_bundler_gem
386    return unless Gem::USE_BUNDLER_FOR_GEMDEPS
387
388    specs_dir = Gem::Specification.default_specifications_dir
389    specs_dir = File.join(options[:destdir], specs_dir) unless Gem.win_platform?
390    mkdir_p specs_dir, :mode => 0755
391
392    # Workaround for non-git environment.
393    gemspec = File.open('bundler/bundler.gemspec', 'rb'){|f| f.read.gsub(/`git ls-files -z`/, "''") }
394    File.open('bundler/bundler.gemspec', 'w'){|f| f.write gemspec }
395
396    bundler_spec = Gem::Specification.load("bundler/bundler.gemspec")
397    bundler_spec.files = Dir.chdir("bundler") { Dir["{*.md,{lib,exe,man}/**/*}"] }
398    bundler_spec.executables -= %w[bundler bundle_ruby]
399
400    # Remove bundler-*.gemspec in default specification directory.
401    Dir.entries(specs_dir).
402      select {|gs| gs.start_with?("bundler-") }.
403      each {|gs| File.delete(File.join(specs_dir, gs)) }
404
405    default_spec_path = File.join(specs_dir, "#{bundler_spec.full_name}.gemspec")
406    Gem.write_binary(default_spec_path, bundler_spec.to_ruby)
407
408    bundler_spec = Gem::Specification.load(default_spec_path)
409
410    # Remove gemspec that was same version of vendored bundler.
411    normal_gemspec = File.join(Gem.default_dir, "specifications", "bundler-#{bundler_spec.version}.gemspec")
412    if File.file? normal_gemspec
413      File.delete normal_gemspec
414    end
415
416    # Remove gem files that were same version of vendored bundler.
417    if File.directory? bundler_spec.gems_dir
418      Dir.entries(bundler_spec.gems_dir).
419        select {|default_gem| File.basename(default_gem) == "bundler-#{bundler_spec.version}" }.
420        each {|default_gem| rm_r File.join(bundler_spec.gems_dir, default_gem) }
421    end
422
423    bundler_bin_dir = bundler_spec.bin_dir
424    bundler_bin_dir = File.join(options[:destdir], bundler_bin_dir) unless Gem.win_platform?
425    mkdir_p bundler_bin_dir, :mode => 0755
426    bundler_spec.executables.each do |e|
427      cp File.join("bundler", bundler_spec.bindir, e), File.join(bundler_bin_dir, e)
428    end
429
430    if Gem.win_platform?
431      require 'rubygems/installer'
432
433      installer = Gem::Installer.for_spec bundler_spec
434      bundler_spec.executables.each do |e|
435        installer.generate_windows_script e, bundler_spec.bin_dir
436      end
437    end
438
439    say "Bundler #{bundler_spec.version} installed"
440  end
441
442  def make_destination_dirs(install_destdir)
443    lib_dir, bin_dir = Gem.default_rubygems_dirs
444
445    unless lib_dir
446      lib_dir, bin_dir = generate_default_dirs(install_destdir)
447    end
448
449    mkdir_p lib_dir, :mode => 0755
450    mkdir_p bin_dir, :mode => 0755
451
452    return lib_dir, bin_dir
453  end
454
455  def generate_default_dirs(install_destdir)
456    prefix = options[:prefix]
457    site_or_vendor = options[:site_or_vendor]
458
459    if prefix.empty?
460      lib_dir = RbConfig::CONFIG[site_or_vendor]
461      bin_dir = RbConfig::CONFIG['bindir']
462    else
463      # Apple installed RubyGems into libdir, and RubyGems <= 1.1.0 gets
464      # confused about installation location, so switch back to
465      # sitelibdir/vendorlibdir.
466      if defined?(APPLE_GEM_HOME) and
467        # just in case Apple and RubyGems don't get this patched up proper.
468        (prefix == RbConfig::CONFIG['libdir'] or
469         # this one is important
470         prefix == File.join(RbConfig::CONFIG['libdir'], 'ruby'))
471        lib_dir = RbConfig::CONFIG[site_or_vendor]
472        bin_dir = RbConfig::CONFIG['bindir']
473      else
474        lib_dir = File.join prefix, 'lib'
475        bin_dir = File.join prefix, 'bin'
476      end
477    end
478
479    unless install_destdir.empty?
480      lib_dir = File.join install_destdir, lib_dir.gsub(/^[a-zA-Z]:/, '')
481      bin_dir = File.join install_destdir, bin_dir.gsub(/^[a-zA-Z]:/, '')
482    end
483
484    [lib_dir, bin_dir]
485  end
486
487  def pem_files_in(dir)
488    Dir.chdir dir do
489      Dir[File.join('**', '*pem')]
490    end
491  end
492
493  def rb_files_in(dir)
494    Dir.chdir dir do
495      Dir[File.join('**', '*rb')]
496    end
497  end
498
499  # for installation of bundler as default gems
500  def template_files
501    Dir.chdir "bundler/lib" do
502      (Dir[File.join('bundler', 'templates', '**', '{*,.*}')]).
503        select{|f| !File.directory?(f)}
504    end
505  end
506
507  # for cleanup old bundler files
508  def template_files_in(dir)
509    Dir.chdir dir do
510      (Dir[File.join('templates', '**', '{*,.*}')]).
511        select{|f| !File.directory?(f)}
512    end
513  end
514
515  def remove_old_bin_files(bin_dir)
516    old_bin_files = {
517      'gem_mirror' => 'gem mirror',
518      'gem_server' => 'gem server',
519      'gemlock' => 'gem lock',
520      'gemri' => 'ri',
521      'gemwhich' => 'gem which',
522      'index_gem_repository.rb' => 'gem generate_index',
523    }
524
525    old_bin_files.each do |old_bin_file, new_name|
526      old_bin_path = File.join bin_dir, old_bin_file
527      next unless File.exist? old_bin_path
528
529      deprecation_message = "`#{old_bin_file}` has been deprecated. Use `#{new_name}` instead."
530
531      File.open old_bin_path, 'w' do |fp|
532        fp.write <<-EOF
533#!#{Gem.ruby}
534
535abort "#{deprecation_message}"
536    EOF
537      end
538
539      next unless Gem.win_platform?
540
541      File.open "#{old_bin_path}.bat", 'w' do |fp|
542        fp.puts %{@ECHO.#{deprecation_message}}
543      end
544    end
545  end
546
547  def remove_old_lib_files(lib_dir)
548    lib_dirs = { File.join(lib_dir, 'rubygems') => 'lib/rubygems' }
549    lib_dirs[File.join(lib_dir, 'bundler')] = 'bundler/lib/bundler' if Gem::USE_BUNDLER_FOR_GEMDEPS
550    lib_dirs.each do |old_lib_dir, new_lib_dir|
551      lib_files = rb_files_in(new_lib_dir)
552      lib_files.concat(template_files_in(new_lib_dir)) if new_lib_dir =~ /bundler/
553
554      old_lib_files = rb_files_in(old_lib_dir)
555      old_lib_files.concat(template_files_in(old_lib_dir)) if old_lib_dir =~ /bundler/
556
557      to_remove = old_lib_files - lib_files
558
559      to_remove.delete_if do |file|
560        file.start_with? 'defaults'
561      end
562
563      Dir.chdir old_lib_dir do
564        to_remove.each do |file|
565          FileUtils.rm_f file
566
567          warn "unable to remove old file #{file} please remove it by hand" if
568            File.exist? file
569        end
570      end
571    end
572  end
573
574  def show_release_notes
575    release_notes = File.join Dir.pwd, 'History.txt'
576
577    release_notes =
578      if File.exist? release_notes
579        history = File.read release_notes
580
581        history.force_encoding Encoding::UTF_8
582
583        history = history.sub(/^# coding:.*?(?=^=)/m, '')
584
585        text = history.split(HISTORY_HEADER)
586        text.shift # correct an off-by-one generated by split
587        version_lines = history.scan(HISTORY_HEADER)
588        versions = history.scan(VERSION_MATCHER).flatten.map do |x|
589          Gem::Version.new(x)
590        end
591
592        history_string = ""
593
594        until versions.length == 0 or
595              versions.shift < options[:previous_version] do
596          history_string += version_lines.shift + text.shift
597        end
598
599        history_string
600      else
601        "Oh-no! Unable to find release notes!"
602      end
603
604    say release_notes
605  end
606
607  def uninstall_old_gemcutter
608    require 'rubygems/uninstaller'
609
610    ui = Gem::Uninstaller.new('gemcutter', :all => true, :ignore => true,
611                              :version => '< 0.4')
612    ui.uninstall
613  rescue Gem::InstallError
614  end
615
616  def regenerate_binstubs
617    require "rubygems/commands/pristine_command"
618    say "Regenerating binstubs"
619
620    args = %w[--all --only-executables --silent]
621    if options[:env_shebang]
622      args << "--env-shebang"
623    end
624
625    command = Gem::Commands::PristineCommand.new
626    command.invoke(*args)
627  end
628
629end
630