[前][次][番号順一覧][スレッド一覧]

ruby-changes:26513

From: drbrain <ko1@a...>
Date: Sun, 23 Dec 2012 09:39:51 +0900 (JST)
Subject: [ruby-changes:26513] drbrain:r38564 (trunk): * lib/rubygems/commands/check_command.rb: Added --doctor and --dry-run

drbrain	2012-12-23 09:35:09 +0900 (Sun, 23 Dec 2012)

  New Revision: 38564

  http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=rev&revision=38564

  Log:
    * lib/rubygems/commands/check_command.rb:  Added --doctor and --dry-run
      options to clean up after failed uninstallation.
    * test/rubygems/test_gem_commands_check_command.rb:  Test for above.
    
    * lib/rubygems/commands/push_command.rb:  Allow pushes from RubyGems
      2.0.0.preview3
    
    * lib/rubygems/commands/update_command.rb:  Use Gem.ruby_version
    
    * lib/rubygems/dependency.rb:  Update style.
    
    * lib/rubygems/installer.rb:  Ensure installed gem specifications will
      be useable.  Refactor.
    * test/rubygems/test_gem_installer.rb:  ditto.
    
    * lib/rubygems/validator.rb:  Fixed bug with unreadable files.
    
    * lib/rubygems.rb:  Fixed broken methods.
    * test/rubygems/test_gem.rb:  Test for above.
    
    * test/rubygems/test_gem_commands_push_command.rb:  Fixed overridden
      Gem.latest_rubygems_version

  Added files:
    trunk/lib/rubygems/doctor.rb
    trunk/test/rubygems/test_gem_doctor.rb
  Modified files:
    trunk/ChangeLog
    trunk/lib/rubygems/commands/check_command.rb
    trunk/lib/rubygems/commands/push_command.rb
    trunk/lib/rubygems/commands/update_command.rb
    trunk/lib/rubygems/dependency.rb
    trunk/lib/rubygems/installer.rb
    trunk/lib/rubygems/validator.rb
    trunk/lib/rubygems.rb
    trunk/test/rubygems/test_gem.rb
    trunk/test/rubygems/test_gem_commands_check_command.rb
    trunk/test/rubygems/test_gem_commands_push_command.rb
    trunk/test/rubygems/test_gem_installer.rb

Index: ChangeLog
===================================================================
--- ChangeLog	(revision 38563)
+++ ChangeLog	(revision 38564)
@@ -1,3 +1,28 @@ https://github.com/ruby/ruby/blob/trunk/ChangeLog#L1
+Sun Dec 23 09:34:07 2012  Eric Hodel  <drbrain@s...>
+
+	* lib/rubygems/commands/check_command.rb:  Added --doctor and --dry-run
+	  options to clean up after failed uninstallation.
+	* test/rubygems/test_gem_commands_check_command.rb:  Test for above.
+
+	* lib/rubygems/commands/push_command.rb:  Allow pushes from RubyGems
+	  2.0.0.preview3
+
+	* lib/rubygems/commands/update_command.rb:  Use Gem.ruby_version
+
+	* lib/rubygems/dependency.rb:  Update style.
+
+	* lib/rubygems/installer.rb:  Ensure installed gem specifications will
+	  be useable.  Refactor.
+	* test/rubygems/test_gem_installer.rb:  ditto.
+
+	* lib/rubygems/validator.rb:  Fixed bug with unreadable files.
+
+	* lib/rubygems.rb:  Fixed broken methods.
+	* test/rubygems/test_gem.rb:  Test for above.
+
+	* test/rubygems/test_gem_commands_push_command.rb:  Fixed overridden
+	  Gem.latest_rubygems_version
+
 Sun Dec 23 01:52:01 2012  Akinori MUSHA  <knu@i...>
 
 	* io.c (rb_io_lines, rb_io_bytes, rb_io_chars, rb_io_codepoints):
Index: lib/rubygems/dependency.rb
===================================================================
--- lib/rubygems/dependency.rb	(revision 38563)
+++ lib/rubygems/dependency.rb	(revision 38564)
@@ -10,14 +10,14 @@ class Gem::Dependency https://github.com/ruby/ruby/blob/trunk/lib/rubygems/dependency.rb#L10
   #--
   # When this list is updated, be sure to change
   # Gem::Specification::CURRENT_SPECIFICATION_VERSION as well.
-
+  #
   # REFACTOR: This type of constant, TYPES, indicates we might want
-  # two classes, used via inheretance or duck typing.
+  # two classes, used via inheritance or duck typing.
 
   TYPES = [
-           :development,
-           :runtime,
-          ]
+    :development,
+    :runtime,
+  ]
 
   ##
   # Dependency name or regular expression.
Index: lib/rubygems/validator.rb
===================================================================
--- lib/rubygems/validator.rb	(revision 38563)
+++ lib/rubygems/validator.rb	(revision 38564)
@@ -58,13 +58,11 @@ class Gem::Validator https://github.com/ruby/ruby/blob/trunk/lib/rubygems/validator.rb#L58
   public
 
   ErrorData = Struct.new :path, :problem do
-
     def <=> other
       return nil unless self.class === other
 
       [path, problem] <=> [other.path, other.problem]
     end
-
   end
 
   ##
@@ -121,7 +119,6 @@ class Gem::Validator https://github.com/ruby/ruby/blob/trunk/lib/rubygems/validator.rb#L119
             File.readable? File.join(gem_directory, file_name)
           }
 
-          unreadable.map! { |entry, _| entry['path'] }
           unreadable.sort.each do |path|
             errors[gem_name][path] = "Unreadable file"
           end
@@ -153,7 +150,9 @@ class Gem::Validator https://github.com/ruby/ruby/blob/trunk/lib/rubygems/validator.rb#L150
     end
 
     errors.each do |name, subhash|
-      errors[name] = subhash.map { |path, msg| ErrorData.new(path, msg) }.sort
+      errors[name] = subhash.map do |path, msg|
+        ErrorData.new path, msg
+      end.sort
     end
 
     errors
Index: lib/rubygems/doctor.rb
===================================================================
--- lib/rubygems/doctor.rb	(revision 0)
+++ lib/rubygems/doctor.rb	(revision 38564)
@@ -0,0 +1,124 @@ https://github.com/ruby/ruby/blob/trunk/lib/rubygems/doctor.rb#L1
+require 'rubygems'
+require 'rubygems/user_interaction'
+require 'pathname'
+
+##
+# Cleans up after a partially-failed uninstall or for an invalid
+# Gem::Specification.
+#
+# If a specification was removed by hand this will remove any remaining files.
+#
+# If a corrupt specification was installed this will clean up warnings by
+# removing the bogus specification.
+
+class Gem::Doctor
+
+  include Gem::UserInteraction
+
+  ##
+  # Maps a gem subdirectory to the files that are expected to exist in the
+  # subdirectory.
+
+  REPOSITORY_EXTENSION_MAP = { # :nodoc:
+    'build_info' =>     '.info',
+    'cache'      =>     '.gem',
+    'doc'        =>     '',
+    'gems'       =>     '',
+    'specifications' => '.gemspec'
+  }
+
+  raise 'Update REPOSITORY_EXTENSION_MAP' unless
+    Gem::REPOSITORY_SUBDIRECTORIES == REPOSITORY_EXTENSION_MAP.keys.sort
+
+  ##
+  # Creates a new Gem::Doctor that will clean up +gem_repository+.  Only one
+  # gem repository may be cleaned at a time.
+  #
+  # If +dry_run+ is true no files or directories will be removed.
+
+  def initialize gem_repository, dry_run = false
+    @gem_repository = Pathname(gem_repository)
+    @dry_run        = dry_run
+
+    @installed_specs = nil
+  end
+
+  ##
+  # Specs installed in this gem repository
+
+  def installed_specs # :nodoc:
+    @installed_specs ||= Gem::Specification.map { |s| s.full_name }
+  end
+
+  ##
+  # Are we doctoring a gem repository?
+
+  def gem_repository?
+    not installed_specs.empty?
+  end
+
+  ##
+  # Cleans up uninstalled files and invalid gem specifications
+
+  def doctor
+    @orig_home = Gem.dir
+    @orig_path = Gem.path
+
+    say "Checking #{@gem_repository}"
+
+    Gem.use_paths @gem_repository.to_s
+
+    unless gem_repository? then
+      say 'This directory does not appear to be a RubyGems repository, ' +
+          'skipping'
+      say
+      return
+    end
+
+    doctor_children
+
+    say
+  ensure
+    Gem.use_paths @orig_home, *@orig_path
+  end
+
+  ##
+  # Cleans up children of this gem repository
+
+  def doctor_children # :nodoc:
+    REPOSITORY_EXTENSION_MAP.each do |sub_directory, extension|
+      doctor_child sub_directory, extension
+    end
+  end
+
+  ##
+  # Removes files in +sub_directory+ with +extension+
+
+  def doctor_child sub_directory, extension # :nodoc:
+    directory = @gem_repository + sub_directory
+
+    directory.each_child do |child|
+      next unless child.exist?
+
+      basename = child.basename(extension).to_s
+      next if installed_specs.include? basename
+      next if /^rubygems-\d/ =~ basename
+      next if 'specifications' == sub_directory and 'default' == basename
+
+      type = child.directory? ? 'directory' : 'file'
+
+      action = if @dry_run then
+                 'Extra'
+               else
+                 child.rmtree
+                 'Removed'
+               end
+
+      say "#{action} #{type} #{sub_directory}/#{child.basename}"
+    end
+  rescue Errno::ENOENT
+    # ignore
+  end
+
+end
+

Property changes on: lib/rubygems/doctor.rb
___________________________________________________________________
Added: svn:eol-style
   + LF

Index: lib/rubygems/installer.rb
===================================================================
--- lib/rubygems/installer.rb	(revision 38563)
+++ lib/rubygems/installer.rb	(revision 38564)
@@ -202,47 +202,24 @@ class Gem::Installer https://github.com/ruby/ruby/blob/trunk/lib/rubygems/installer.rb#L202
   #     specifications/<gem-version>.gemspec #=> the Gem::Specification
 
   def install
-    verify_gem_home(options[:unpack])
-
-    # If we're forcing the install then disable security unless the security
-    # policy says that we only install signed gems.
-    @security_policy = nil if @force and @security_policy and
-                              not @security_policy.only_signed
-
-    unless @force
-      ensure_required_ruby_version_met
-      ensure_required_rubygems_version_met
-      ensure_dependencies_met unless @ignore_dependencies
-    end
+    pre_install_checks
 
     run_pre_install_hooks
 
-    Gem.ensure_gem_subdirectories gem_home
-
     # Completely remove any previous gem files
-    FileUtils.rm_rf(gem_dir)
+    FileUtils.rm_rf gem_dir
 
     FileUtils.mkdir_p gem_dir
 
     extract_files
-    build_extensions
 
+    build_extensions
+    write_build_info_file
     run_post_build_hooks
 
     generate_bin
     write_spec
-
-    unless @build_args.empty?
-      File.open spec.build_info_file, "w" do |f|
-        @build_args.each { |a| f.puts a }
-      end
-    end
-
-    # TODO should be always cache the file? Other classes have options
-    # to controls if caching is done.
-    cache_file = File.join(gem_home, "cache", "#{spec.full_name}.gem")
-
-    FileUtils.cp gem, cache_file unless File.exist? cache_file
+    write_cache_file
 
     say spec.post_install_message unless spec.post_install_message.nil?
 
@@ -255,7 +232,7 @@ class Gem::Installer https://github.com/ruby/ruby/blob/trunk/lib/rubygems/installer.rb#L232
     spec
 
   # TODO This rescue is in the wrong place. What is raising this exception?
-  # move this rescue to arround the code that actually might raise it.
+  # move this rescue to around the code that actually might raise it.
   rescue Zlib::GzipFile::Error
     raise Gem::InstallError, "gzip error installing #{gem}"
   end
@@ -506,6 +483,21 @@ class Gem::Installer https://github.com/ruby/ruby/blob/trunk/lib/rubygems/installer.rb#L483
     end
   end
 
+  ##
+  # Ensures the Gem::Specification written out for this gem is loadable upon
+  # installation.
+
+  def ensure_loadable_spec
+    ruby = spec.to_ruby_for_cache
+
+    begin
+      eval ruby
+    rescue StandardError, SyntaxError => e
+      raise Gem::InstallError,
+            "The specification for #{spec.full_name} is corrupt (#{e.class})"
+    end
+  end
+
   # DOC: Missing docs or :nodoc:.
   def ensure_required_ruby_version_met
     if rrv = spec.required_ruby_version then
@@ -736,5 +728,59 @@ EOF https://github.com/ruby/ruby/blob/trunk/lib/rubygems/installer.rb#L728
   def dir
     gem_dir.to_s
   end
+
+  ##
+  # Performs various checks before installing the gem such as the install
+  # repository is writable and its directories exist, required ruby and
+  # rubygems versions are met and that dependencies are installed.
+  #
+  # Version and dependency checks are skipped if this install is forced.
+  #
+  # The dependent check will be skipped this install is ignoring dependencies.
+
+  def pre_install_checks
+    verify_gem_home options[:unpack]
+
+    # If we're forcing the install then disable security unless the security
+    # policy says that we only install signed gems.
+    @security_policy = nil if
+      @force and @security_policy and not @security_policy.only_signed
+
+    ensure_loadable_spec
+
+    Gem.ensure_gem_subdirectories gem_home
+
+    return true if @force
+
+    ensure_required_ruby_version_met
+    ensure_required_rubygems_version_met
+    ensure_dependencies_met unless @ignore_dependencies
+
+    true
+  end
+
+  ##
+  # Writes the file containing the arguments for building this gem's
+  # extensions.
+
+  def write_build_info_file
+    return if @build_args.empty?
+
+    open spec.build_info_file, 'w' do |io|
+      @build_args.each do |arg|
+        io.puts arg
+      end
+    end
+  end
+
+  ##
+  # Writes the .gem file to the cache directory
+
+  def write_cache_file
+    cache_file = File.join gem_home, 'cache', spec.file_name
+
+    FileUtils.cp @gem, cache_file unless File.exist? cache_file
+  end
+
 end
 
Index: lib/rubygems/commands/push_command.rb
===================================================================
--- lib/rubygems/commands/push_command.rb	(revision 38563)
+++ lib/rubygems/commands/push_command.rb	(revision 38564)
@@ -40,9 +40,17 @@ class Gem::Commands::PushCommand < Gem:: https://github.com/ruby/ruby/blob/trunk/lib/rubygems/commands/push_command.rb#L40
   def send_gem name
     args = [:post, "api/v1/gems"]
 
+    latest_rubygems_version = Gem.latest_rubygems_version
 
-    if Gem.latest_rubygems_version < Gem::Version.new(Gem::VERSION) then
-      alert_error "Using beta/unreleased version of rubygems. Not pushing."
+    if latest_rubygems_version < Gem.rubygems_version and
+         Gem.rubygems_version.prerelease? and
+         Gem::Version.new('2.0.0.preview3') != Gem.rubygems_version then
+      alert_error <<-ERROR
+You are using a beta release of RubyGems (#{Gem::VERSION}) which is not
+allowed to push gems.  Please downgrade or upgrade to a release version.
+
+The latest released RubyGems version is #{latest_rubygems_version}
+      ERROR
       terminate_interaction 1
     end
 
Index: lib/rubygems/commands/update_command.rb
===================================================================
--- lib/rubygems/commands/update_command.rb	(revision 38563)
+++ lib/rubygems/commands/update_command.rb	(revision 38564)
@@ -142,7 +142,7 @@ class Gem::Commands::UpdateCommand < Gem https://github.com/ruby/ruby/blob/trunk/lib/rubygems/commands/update_command.rb#L142
 
     gems_to_update = which_to_update hig, options[:args], :system
     name, up_ver   = gems_to_update.first
-    current_ver    = Gem::Version.new Gem::VERSION
+    current_ver    = Gem.rubygems_version
 
     target = if options[:system] == true then
                up_ver
Index: lib/rubygems/commands/check_command.rb
===================================================================
--- lib/rubygems/commands/check_command.rb	(revision 38563)
+++ lib/rubygems/commands/check_command.rb	(revision 38564)
@@ -1,25 +1,44 @@ https://github.com/ruby/ruby/blob/trunk/lib/rubygems/commands/check_command.rb#L1
 require 'rubygems/command'
 require 'rubygems/version_option'
 require 'rubygems/validator'
+require 'rubygems/doctor'
 
 class Gem::Commands::CheckCommand < Gem::Command
 
   include Gem::VersionOption
 
   def initialize
-    super 'check', 'Check installed gems',
-          :alien => true
+    super 'check', 'Check a gem repository for added or missing files',
+          :alien => true, :doctor => false, :dry_run => false, :gems => true
 
-    add_option('-a', '--alien', "Report 'unmanaged' or rogue files in the",
-               "gem repository") do |value, options|
-      options[:alien] = true
+    add_option('-a', '--[no-]alien',
+               'Report "unmanaged" or rogue files in the',
+               'gem repository') do |value, options|
+      options[:alien] = value
+    end
+
+    add_option('--[no-]doctor',
+               'Clean up uninstalled gems and broken',
+               'specifications') do |value, options|
+      options[:doctor] = value
+    end
+
+    add_option('--[no-]dry-run',
+               'Do not remove files, only report what',
+               'would be removed') do |value, options|
+      options[:dry_run] = value
+    end
+
+    add_option('--[no-]gems',
+               'Check installed gems for problems') do |value, options|
+      options[:gems] = value
     end
 
     add_version_option 'check'
   end
 
-  def execute
-    say "Checking gems..."
+  def check_gems
+    say 'Checking gems...'
     say
     gems = get_all_gem_names rescue []
 
@@ -37,4 +56,31 @@ class Gem::Commands::CheckCommand < Gem: https://github.com/ruby/ruby/blob/trunk/lib/rubygems/commands/check_command.rb#L56
     end
   end
 
+  def doctor
+    say 'Checking for files from uninstalled gems...'
+    say
+
+    Gem.path.each do |gem_repo|
+      doctor = Gem::Doctor.new gem_repo, options[:dry_run]
+      doctor.doctor
+    end
+  end
+
+  def execute
+    check_gems if options[:gems]
+    doctor if options[:doctor]
+  end
+
+  def arguments # :nodoc:
+    'GEMNAME       name of gem to check'
+  end
+
+  def defaults_str # :nodoc:
+    '--gems --alien'
+  end
+
+  def usage # :nodoc:
+    "#{program_name} [OPTIONS] [GEMNAME ...]"
+  end
+
 end
Index: lib/rubygems.rb
===================================================================
--- lib/rubygems.rb	(revision 38563)
+++ lib/rubygems.rb	(revision 38564)
@@ -98,7 +98,7 @@ https://github.com/ruby/ruby/blob/trunk/lib/rubygems.rb#L98
 require 'rbconfig'
 
 module Gem
-  VERSION = '2.0.0.preview2.2'
+  VERSION = '2.0.0.preview3'
 end
 
 # Must be first since it unloads the prelude from 1.9.2
@@ -123,7 +123,22 @@ module Gem https://github.com/ruby/ruby/blob/trunk/lib/rubygems.rb#L123
     /wince/i,
   ]
 
-  GEM_DEP_FILES = %w!gem.deps.rb Gemfile Isolate!
+  GEM_DEP_FILES = %w[
+    gem.deps.rb
+    Gemfile
+    Isolate
+  ]
+
+  ##
+  # Subdirectories in a gem repository
+
+  REPOSITORY_SUBDIRECTORIES = %w[
+    build_info
+    cache
+    doc
+    gems
+    specifications
+  ]
 
   @@win_platform = nil
 
@@ -388,7 +403,7 @@ module Gem https://github.com/ruby/ruby/blob/trunk/lib/rubygems.rb#L403
 
     require 'fileutils'
 
-    %w[cache build_info doc gems specifications].each do |name|
+    REPOSITORY_SUBDIRECTORIES.each do |name|
       subdir = File.join dir, name
       next if File.exist? subdir
       FileUtils.mkdir_p subdir rescue nil # in case of perms issues -- lame
@@ -750,34 +765,36 @@ module Gem https://github.com/ruby/ruby/blob/trunk/lib/rubygems.rb#L765
     @ruby
   end
 
-  # DOC: needs doc'd or :nodoc'd
+  ##
+  # Returns the latest release-version specification for the gem +name+.
+
   def self.latest_spec_for name
-    dependency  = Gem::Dependency.new name
-    fetcher     = Gem::SpecFetcher.fetcher
-    spec_tuples = fetcher.find_matching dependency
-
-    match = spec_tuples.select { |(n, _, p), _|
-      n == name and Gem::Platform.match p
-    }.sort_by { |(_, version, _), _|
-      version
-    }.last
+    dependency   = Gem::Dependency.new name
+    fetcher      = Gem::SpecFetcher.fetcher
+    spec_tuples, = fetcher.spec_for_dependency dependency
 
-    match and fetcher.fetch_spec(*match)
-  end
+    spec, = spec_tuples.first
 
-  # DOC: needs doc'd or :nodoc'd
-  def self.latest_version_for name
-    spec = latest_spec_for name
-    spec and spec.version
+    spec
   end
 
-  # DOC: needs doc'd or :nodoc'd
+  ##
+  # Returns the latest release version of RubyGems.
+
   def self.latest_rubygems_version
-    latest_version_for("rubygems-update") or
+    latest_version_for('rubygems-update') or
       raise "Can't find 'rubygems-update' in any repo. Check `gem source list`."
   end
 
   ##
+  # Returns the version of the latest release-version of gem +name+
+
+  def self.latest_version_for name
+    spec = latest_spec_for name
+    spec and spec.version
+  end
+
+  ##
   # A Gem::Version for the currently running ruby.
 
   def self.ruby_version
Index: test/rubygems/test_gem_doctor.rb
===================================================================
--- test/rubygems/test_gem_doctor.rb	(revision 0)
+++ test/rubygems/test_gem_doctor.rb	(revision 38564)
@@ -0,0 +1,168 @@ https://github.com/ruby/ruby/blob/trunk/test/rubygems/test_gem_doctor.rb#L1
+require 'rubygems/test_case'
+require 'rubygems/doctor'
+
+class TestGemDoctor < Gem::TestCase
+
+  def gem name
+    spec = quick_gem name do |gem|
+      gem.files = %W[lib/#{name}.rb Rakefile]
+    end
+
+    write_file File.join(*%W[gems #{spec.full_name} lib #{name}.rb])
+    write_file File.join(*%W[gems #{spec.full_name} Rakefile])
+
+    spec
+  end
+
+  def test_doctor
+    a = gem 'a'
+    b = gem 'b'
+    c = gem 'c'
+
+    Gem.use_paths @userhome, @gemhome
+
+    FileUtils.rm b.spec_file
+
+    open c.spec_file, 'w' do |io|
+      io.write 'this will raise an exception when evaluated.'
+    end
+
+    assert_path_exists File.join(a.gem_dir, 'Rakefile')
+    assert_path_exists File.join(a.gem_dir, 'lib', 'a.rb')
+
+    assert_path_exists b.gem_dir
+    refute_path_exists b.spec_file
+
+    assert_path_exists c.gem_dir
+    assert_path_exists c.spec_file
+
+    doctor = Gem::Doctor.new @gemhome
+
+    capture_io do
+      use_ui @ui do
+        doctor.doctor
+      end
+    end
+
+    assert_path_exists File.join(a.gem_dir, 'Rakefile')
+    assert_path_exists File.join(a.gem_dir, 'lib', 'a.rb')
+
+    refute_path_exists b.gem_dir
+    refute_path_exists b.spec_file
+
+    refute_path_exists c.gem_dir
+    refute_path_exists c.spec_file
+
+    expected = <<-OUTPUT
+Checking #{@gemhome}
+Removed directory gems/b-2
+Removed directory gems/c-2
+Removed file specifications/c-2.gemspec
+
+    OUTPUT
+
+    assert_equal expected, @ui.output
+
+    assert_equal Gem.dir,  @userhome
+    assert_equal Gem.path, [@gemho (... truncated)

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]