1require 'rubygems/command'
2require 'rubygems/command_manager'
3require 'rubygems/dependency_installer'
4require 'rubygems/install_update_options'
5require 'rubygems/local_remote_options'
6require 'rubygems/spec_fetcher'
7require 'rubygems/version_option'
8require 'rubygems/install_message' # must come before rdoc for messaging
9require 'rubygems/rdoc'
10
11class Gem::Commands::UpdateCommand < Gem::Command
12
13  include Gem::InstallUpdateOptions
14  include Gem::LocalRemoteOptions
15  include Gem::VersionOption
16
17  attr_reader :installer # :nodoc:
18
19  def initialize
20    super 'update', 'Update installed gems to the latest version',
21      :document => %w[rdoc ri],
22      :force    => false
23
24    add_install_update_options
25
26    OptionParser.accept Gem::Version do |value|
27      Gem::Version.new value
28
29      value
30    end
31
32    add_option('--system [VERSION]', Gem::Version,
33               'Update the RubyGems system software') do |value, options|
34      value = true unless value
35
36      options[:system] = value
37    end
38
39    add_local_remote_options
40    add_platform_option
41    add_prerelease_option "as update targets"
42
43    @updated   = []
44    @installer = nil
45  end
46
47  def arguments # :nodoc:
48    "GEMNAME       name of gem to update"
49  end
50
51  def defaults_str # :nodoc:
52    "--document --no-force --install-dir #{Gem.dir}"
53  end
54
55  def usage # :nodoc:
56    "#{program_name} GEMNAME [GEMNAME ...]"
57  end
58
59  def execute
60    hig = {}
61
62    if options[:system] then
63      update_rubygems
64      return
65    else
66      say "Updating installed gems"
67
68      hig = {} # highest installed gems
69
70      Gem::Specification.each do |spec|
71        if hig[spec.name].nil? or hig[spec.name].version < spec.version then
72          hig[spec.name] = spec
73        end
74      end
75    end
76
77    gems_to_update = which_to_update hig, options[:args].uniq
78
79    updated = update_gems gems_to_update
80
81    if updated.empty? then
82      say "Nothing to update"
83    else
84      say "Gems updated: #{updated.map { |spec| spec.name }.join ' '}"
85    end
86  end
87
88  def update_gem name, version = Gem::Requirement.default
89    return if @updated.any? { |spec| spec.name == name }
90
91    @installer ||= Gem::DependencyInstaller.new options
92
93    success = false
94
95    say "Updating #{name}"
96    begin
97      @installer.install name, Gem::Requirement.new(version)
98      success = true
99    rescue Gem::InstallError => e
100      alert_error "Error installing #{name}:\n\t#{e.message}"
101      success = false
102    end
103
104    @installer.installed_gems.each do |spec|
105      @updated << spec
106    end
107  end
108
109  def update_gems gems_to_update
110    gems_to_update.uniq.sort.each do |(name, version)|
111      update_gem name, version
112    end
113
114    @updated
115  end
116
117  ##
118  # Update RubyGems software to the latest version.
119
120  def update_rubygems
121    unless options[:args].empty? then
122      alert_error "Gem names are not allowed with the --system option"
123      terminate_interaction 1
124    end
125
126    options[:user_install] = false
127
128    # TODO: rename version and other variable name conflicts
129    # TODO: get rid of all this indirection on name and other BS
130
131    version = options[:system]
132    if version == true then
133      version     = Gem::Version.new     Gem::VERSION
134      requirement = Gem::Requirement.new ">= #{Gem::VERSION}"
135    else
136      version     = Gem::Version.new     version
137      requirement = Gem::Requirement.new version
138    end
139
140    rubygems_update         = Gem::Specification.new
141    rubygems_update.name    = 'rubygems-update'
142    rubygems_update.version = version
143
144    hig = {
145      'rubygems-update' => rubygems_update
146    }
147
148    gems_to_update = which_to_update hig, options[:args], :system
149    name, up_ver   = gems_to_update.first
150    current_ver    = Gem.rubygems_version
151
152    target = if options[:system] == true then
153               up_ver
154             else
155               version
156             end
157
158    if current_ver == target then
159      # if options[:system] != true and version == current_ver then
160      say "Latest version currently installed. Aborting."
161      terminate_interaction
162    end
163
164    update_gem name, target
165
166    installed_gems = Gem::Specification.find_all_by_name 'rubygems-update', requirement
167    version        = installed_gems.last.version
168
169    args = []
170    args << '--prefix' << Gem.prefix if Gem.prefix
171    # TODO use --document for >= 1.9 , --no-rdoc --no-ri < 1.9
172    args << '--no-rdoc' unless options[:document].include? 'rdoc'
173    args << '--no-ri'   unless options[:document].include? 'ri'
174    args << '--no-format-executable' if options[:no_format_executable]
175
176    update_dir = File.join Gem.dir, 'gems', "rubygems-update-#{version}"
177
178    Dir.chdir update_dir do
179      say "Installing RubyGems #{version}"
180      setup_cmd = "#{Gem.ruby} setup.rb #{args.join ' '}"
181
182      # Make sure old rubygems isn't loaded
183      old = ENV["RUBYOPT"]
184      ENV.delete("RUBYOPT") if old
185      installed = system setup_cmd
186      say "RubyGems system software updated" if installed
187      ENV["RUBYOPT"] = old if old
188    end
189  end
190
191  def which_to_update highest_installed_gems, gem_names, system = false
192    result = []
193
194    highest_installed_gems.each do |l_name, l_spec|
195      next if not gem_names.empty? and
196              gem_names.all? { |name| /#{name}/ !~ l_spec.name }
197
198      dependency = Gem::Dependency.new l_spec.name, "> #{l_spec.version}"
199      dependency.prerelease = options[:prerelease]
200
201      fetcher = Gem::SpecFetcher.fetcher
202
203      spec_tuples, _ = fetcher.search_for_dependency dependency
204
205      matching_gems = spec_tuples.select do |g,_|
206        g.name == l_name and g.match_platform?
207      end
208
209      highest_remote_gem = matching_gems.sort_by { |g,_| g.version }.last
210
211      highest_remote_gem ||= [Gem::NameTuple.null]
212      highest_remote_ver = highest_remote_gem.first.version
213
214      if system or (l_spec.version < highest_remote_ver) then
215        result << [l_spec.name, [l_spec.version, highest_remote_ver].max]
216      end
217    end
218
219    result
220  end
221
222end
223
224