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/