diff options
82 files changed, 5619 insertions, 2771 deletions
@@ -1,3 +1,7 @@ +Tue Apr 1 07:31:58 2008 Eric Hodel <[email protected]> + + * lib/rubygems* test/rubygems*: Import RubyGems 1.1.0. + Tue Apr 1 03:20:40 2008 Nobuyoshi Nakada <[email protected]> * configure.in (RUBY_SETJMP, RUBY_LONGJMP, RUBY_JMP_BUF): prefers @@ -7,11 +7,12 @@ require 'rubygems' require 'rubygems/gem_runner' +require 'rubygems/exceptions' -required_version = Gem::Requirement.new ">= 1.8.3" +required_version = Gem::Requirement.new "> 1.8.3" -unless required_version.satisfied_by? Gem::Version.new(RUBY_VERSION) then - abort "Expected Ruby Version #{required_version}, was #{RUBY_VERSION}" +unless required_version.satisfied_by? Gem.ruby_version then + abort "Expected Ruby Version #{required_version}, was #{Gem.ruby_version}" end # We need to preserve the original ARGV to use for passing gem options @@ -19,5 +20,9 @@ end # it...its for the source building process. args = !ARGV.include?("--") ? ARGV.clone : ARGV[0...ARGV.index("--")] -Gem::GemRunner.new.run args +begin + Gem::GemRunner.new.run args +rescue Gem::SystemExitException => e + exit e.exit_code +end diff --git a/gem_prelude.rb b/gem_prelude.rb index 1b4ef4409e..e727bee876 100644 --- a/gem_prelude.rb +++ b/gem_prelude.rb @@ -1,207 +1,214 @@ +# depends on: array.rb dir.rb env.rb file.rb hash.rb module.rb regexp.rb + # empty gem_prelude.rb # # p Gem::Enable -if defined?(Gem::Enable) && Gem::Enable -#t = Time.now - -module Kernel +if defined?(Gem::Enable) && Gem::Enable then + + module Kernel + + def gem(gem_name, *version_requirements) + Gem.push_gem_version_on_load_path(gem_name, *version_requirements) + end - def gem(gem_name, *version_requirements) - Gem.push_gem_version_on_load_path(gem_name, *version_requirements) end -end + module Gem + + ConfigMap = { + :sitedir => RbConfig::CONFIG["sitedir"], + :ruby_version => RbConfig::CONFIG["ruby_version"], + :libdir => RbConfig::CONFIG["libdir"], + :sitelibdir => RbConfig::CONFIG["sitelibdir"], + :arch => RbConfig::CONFIG["arch"], + :bindir => RbConfig::CONFIG["bindir"], + :EXEEXT => RbConfig::CONFIG["EXEEXT"], + :RUBY_SO_NAME => RbConfig::CONFIG["RUBY_SO_NAME"], + :ruby_install_name => RbConfig::CONFIG["ruby_install_name"] + } + + class << self -module Gem - - ConfigMap = { - :sitedir => RbConfig::CONFIG["sitedir"], - :ruby_version => RbConfig::CONFIG["ruby_version"], - :libdir => RbConfig::CONFIG["libdir"], - :sitelibdir => RbConfig::CONFIG["sitelibdir"], - :arch => RbConfig::CONFIG["arch"], - :bindir => RbConfig::CONFIG["bindir"], - :EXEEXT => RbConfig::CONFIG["EXEEXT"], - :RUBY_SO_NAME => RbConfig::CONFIG["RUBY_SO_NAME"], - :ruby_install_name => RbConfig::CONFIG["ruby_install_name"] - } - - class << self - - def default_dir - if defined? RUBY_FRAMEWORK_VERSION - return File.join(File.dirname(ConfigMap[:sitedir]), "Gems") - else - File.join(ConfigMap[:libdir], 'ruby', 'gems', ConfigMap[:ruby_version]) + def default_dir + if defined? RUBY_FRAMEWORK_VERSION + return File.join(File.dirname(ConfigMap[:sitedir]), "Gems") + else + File.join(ConfigMap[:libdir], 'ruby', 'gems', ConfigMap[:ruby_version]) + end end - end - def dir - @gem_home ||= nil - set_home(ENV['GEM_HOME'] || default_dir) unless @gem_home - @gem_home - end + def dir + @gem_home ||= nil + set_home(ENV['GEM_HOME'] || default_dir) unless @gem_home + @gem_home + end - def path - @gem_path ||= nil - unless @gem_path - paths = [ENV['GEM_PATH']] - paths << APPLE_GEM_HOME if defined? APPLE_GEM_HOME - set_paths(paths.compact.join(File::PATH_SEPARATOR)) + def path + @gem_path ||= nil + unless @gem_path + paths = [ENV['GEM_PATH']] + paths << APPLE_GEM_HOME if defined? APPLE_GEM_HOME + set_paths(paths.compact.join(File::PATH_SEPARATOR)) + end + @gem_path end - @gem_path - end - # Set the Gem home directory (as reported by +dir+). - def set_home(home) - @gem_home = home - ensure_gem_subdirectories(@gem_home) - end + # Set the Gem home directory (as reported by +dir+). + def set_home(home) + @gem_home = home + ensure_gem_subdirectories(@gem_home) + end - def set_paths(gpaths) - if gpaths - @gem_path = gpaths.split(File::PATH_SEPARATOR) - @gem_path << Gem.dir - else - @gem_path = [Gem.dir] + def set_paths(gpaths) + if gpaths + @gem_path = gpaths.split(File::PATH_SEPARATOR) + @gem_path << Gem.dir + else + @gem_path = [Gem.dir] + end + @gem_path.uniq! + @gem_path.each do |gp| ensure_gem_subdirectories(gp) end end - @gem_path.uniq! - @gem_path.each do |gp| ensure_gem_subdirectories(gp) end - end - - def ensure_gem_subdirectories(path) - end - end + def ensure_gem_subdirectories(path) + end + + end - module QuickLoader + module QuickLoader - class << self - def load_full_rubygems_library - class << Gem - Gem.methods(false).each do |method_name| - undef_method method_name + class << self + def load_full_rubygems_library + class << Gem + Gem.methods(false).each do |method_name| + undef_method method_name + end end - end - Kernel.send :undef_method, :gem + Kernel.send :undef_method, :gem - $".delete File.join(Gem::ConfigMap[:libdir], 'ruby', - Gem::ConfigMap[:ruby_version], 'rubygems.rb') + $".delete File.join(Gem::ConfigMap[:libdir], 'ruby', + Gem::ConfigMap[:ruby_version], 'rubygems.rb') - require 'rubygems' + require 'rubygems' + end end - end - GemPaths = {} - GemVersions = {} - - def push_gem_version_on_load_path(gem_name, *version_requirements) - if version_requirements.empty? - unless GemPaths.has_key?(gem_name) - raise LoadError.new("Could not find RubyGem #{gem_name} (>= 0)\n") - end - # highest version gems already active - return false - else - if version_requirements.length > 1 - QuickLoader.load_full_rubygems_library - return gem(gem_name, *version_requirements) - end - requirement, version = version_requirements[0].split - requirement.strip! - if requirement == ">" || requirement == ">=" - if (GemVersions[gem_name] <=> Gem.calculate_integers_for_gem_version(version)) >= 0 - return false + GemPaths = {} + GemVersions = {} + + def push_gem_version_on_load_path(gem_name, *version_requirements) + if version_requirements.empty? + unless GemPaths.has_key?(gem_name) + raise LoadError.new("Could not find RubyGem #{gem_name} (>= 0)\n") + end + + # highest version gems already active + return false + else + if version_requirements.length > 1 + QuickLoader.load_full_rubygems_library + return gem(gem_name, *version_requirements) end - elsif requirement == "~>" - loaded_version = GemVersions[gem_name] - required_version = Gem.calculate_integers_for_gem_version(version) - if loaded_version && (loaded_version[0] == required_version[0]) - return false + + requirement, version = version_requirements[0].split + requirement.strip! + + if requirement == ">" || requirement == ">=" + if (GemVersions[gem_name] <=> Gem.calculate_integers_for_gem_version(version)) >= 0 + return false + end + elsif requirement == "~>" + loaded_version = GemVersions[gem_name] + required_version = Gem.calculate_integers_for_gem_version(version) + if loaded_version && (loaded_version[0] == required_version[0]) + return false + end end + + QuickLoader.load_full_rubygems_library + gem(gem_name, *version_requirements) end - QuickLoader.load_full_rubygems_library - gem(gem_name, *version_requirements) end - end - def calculate_integers_for_gem_version(gem_version) - numbers = gem_version.split(".").collect {|n| n.to_i} - numbers.pop while numbers.last == 0 - numbers << 0 if numbers.empty? - numbers - end - - def push_all_highest_version_gems_on_load_path - Gem.path.each do |path| - gems_directory = File.join(path, "gems") - if File.exist?(gems_directory) - Dir.entries(gems_directory).each do |gem_directory_name| - next if gem_directory_name == "." || gem_directory_name == ".." - dash = gem_directory_name.rindex("-") - next if dash.nil? - gem_name = gem_directory_name[0...dash] - current_version = GemVersions[gem_name] - new_version = calculate_integers_for_gem_version(gem_directory_name[dash+1..-1]) - if current_version - if (current_version <=> new_version) == -1 + def calculate_integers_for_gem_version(gem_version) + numbers = gem_version.split(".").collect {|n| n.to_i} + numbers.pop while numbers.last == 0 + numbers << 0 if numbers.empty? + numbers + end + + def push_all_highest_version_gems_on_load_path + Gem.path.each do |path| + gems_directory = File.join(path, "gems") + if File.exist?(gems_directory) + Dir.entries(gems_directory).each do |gem_directory_name| + next if gem_directory_name == "." || gem_directory_name == ".." + dash = gem_directory_name.rindex("-") + next if dash.nil? + gem_name = gem_directory_name[0...dash] + current_version = GemVersions[gem_name] + new_version = calculate_integers_for_gem_version(gem_directory_name[dash+1..-1]) + if current_version + if (current_version <=> new_version) == -1 + GemVersions[gem_name] = new_version + GemPaths[gem_name] = File.join(gems_directory, gem_directory_name) + end + else GemVersions[gem_name] = new_version GemPaths[gem_name] = File.join(gems_directory, gem_directory_name) end - else - GemVersions[gem_name] = new_version - GemPaths[gem_name] = File.join(gems_directory, gem_directory_name) end end end - end - require_paths = [] - GemPaths.values.each do |path| - if File.exist?(File.join(path, ".require_paths")) - require_paths.concat(File.read(File.join(path, ".require_paths")).split.map {|require_path| File.join(path, require_path)}) - else - require_paths << File.join(path, "bin") if File.exist?(File.join(path, "bin")) - require_paths << File.join(path, "lib") if File.exist?(File.join(path, "lib")) + + require_paths = [] + + GemPaths.values.each do |path| + if File.exist?(File.join(path, ".require_paths")) + require_paths.concat(File.read(File.join(path, ".require_paths")).split.map {|require_path| File.join(path, require_path)}) + else + require_paths << File.join(path, "bin") if File.exist?(File.join(path, "bin")) + require_paths << File.join(path, "lib") if File.exist?(File.join(path, "lib")) + end + end + + # "tag" the first require_path inserted into the $LOAD_PATH to enable + # indexing correctly with rubygems proper when it inserts an explicitly + # gem version + unless require_paths.empty? + require_paths.first.instance_variable_set(:@gem_prelude_index, true) end + # gem directories must come after -I and ENV['RUBYLIB'] + $:[$:.index(ConfigMap[:sitelibdir]),0] = require_paths + end + + def const_missing(constant) + QuickLoader.load_full_rubygems_library + Gem.const_get(constant) end - - # "tag" the first require_path inserted into the $LOAD_PATH to enable - # indexing correctly with rubygems proper when it inserts an explicitly - # gem version - unless require_paths.empty? - require_paths.first.instance_variable_set(:@gem_prelude_index, true) + + def method_missing(method, *args, &block) + QuickLoader.load_full_rubygems_library + super unless Gem.respond_to?(method) + Gem.send(method, *args, &block) end - # gem directories must come after -I and ENV['RUBYLIB'] - $:[$:.index(ConfigMap[:sitelibdir]),0] = require_paths end - def const_missing(constant) - QuickLoader.load_full_rubygems_library - Gem.const_get(constant) - end + extend QuickLoader - def method_missing(method, *args, &block) - QuickLoader.load_full_rubygems_library - super unless Gem.respond_to?(method) - Gem.send(method, *args, &block) - end end - - extend QuickLoader -end + begin + Gem.push_all_highest_version_gems_on_load_path + $" << File.join(Gem::ConfigMap[:libdir], "ruby", + Gem::ConfigMap[:ruby_version], "rubygems.rb") + rescue Exception => e + puts "Error loading gem paths on load path in gem_prelude" + puts e + puts e.backtrace.join("\n") + end -begin - Gem.push_all_highest_version_gems_on_load_path - $" << File.join(Gem::ConfigMap[:libdir], "ruby", - Gem::ConfigMap[:ruby_version], "rubygems.rb") -rescue Exception => e - puts "Error loading gem paths on load path in gem_prelude" - puts e - puts e.backtrace.join("\n") end -#puts "Gem load in #{Time.now - t} seconds" -end # Gem::Enable diff --git a/lib/rubygems.rb b/lib/rubygems.rb index 9549b9bdca..3f9657ac1e 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -17,82 +17,80 @@ end module Kernel - # Adds a Ruby Gem to the $LOAD_PATH. Before a Gem is loaded, its - # required Gems are loaded. If the version information is omitted, - # the highest version Gem of the supplied name is loaded. If a Gem - # is not found that meets the version requirement and/or a required - # Gem is not found, a Gem::LoadError is raised. More information on - # version requirements can be found in the Gem::Version - # documentation. - # - # The +gem+ directive should be executed *before* any require - # statements (otherwise rubygems might select a conflicting library - # version). + ## + # Use Kernel#gem to activate a specific version of +gem_name+. # - # You can define the environment variable GEM_SKIP as a way to not - # load specified gems. You might do this to test out changes that - # haven't been installed yet. Example: + # +version_requirements+ is a list of version requirements that the + # specified gem must match, most commonly "= example.version.number". See + # Gem::Requirement for how to specify a version requirement. # - # GEM_SKIP=libA:libB ruby-I../libA -I../libB ./mycode.rb + # If you will be activating the latest version of a gem, there is no need to + # call Kernel#gem, Kernel#require will do the right thing for you. # - # gem:: [String or Gem::Dependency] The gem name or dependency - # instance. + # Kernel#gem returns true if the gem was activated, otherwise false. If the + # gem could not be found, didn't match the version requirements, or a + # different version was already activated, an exception will be raised. # - # version_requirement:: [default=">= 0"] The version - # requirement. + # Kernel#gem should be called *before* any require statements (otherwise + # RubyGems may load a conflicting library version). # - # return:: [Boolean] true if the Gem is loaded, otherwise false. + # In older RubyGems versions, the environment variable GEM_SKIP could be + # used to skip activation of specified gems, for example to test out changes + # that haven't been installed yet. Now RubyGems defers to -I and the + # RUBYLIB environment variable to skip activation of a gem. # - # raises:: [Gem::LoadError] if Gem cannot be found, is listed in - # GEM_SKIP, or version requirement not met. + # Example: # - def gem(gem_name, *version_requirements) - active_gem_with_options(gem_name, version_requirements) - end - - # Return the file name (string) and line number (integer) of the caller of - # the caller of this method. - def location_of_caller - file, lineno = caller[1].split(':') - lineno = lineno.to_i - [file, lineno] - end - private :location_of_caller + # GEM_SKIP=libA:libB ruby -I../libA -I../libB ./mycode.rb - def active_gem_with_options(gem_name, version_requirements, options={}) + def gem(gem_name, *version_requirements) skip_list = (ENV['GEM_SKIP'] || "").split(/:/) raise Gem::LoadError, "skipping #{gem_name}" if skip_list.include? gem_name - Gem.activate(gem_name, options[:auto_require], *version_requirements) + Gem.activate(gem_name, *version_requirements) end - private :active_gem_with_options + end +## # Main module to hold all RubyGem classes/modules. -# + module Gem ConfigMap = {} unless defined?(ConfigMap) require 'rbconfig' RbConfig = Config unless defined? ::RbConfig + ConfigMap.merge!( - :BASERUBY => RbConfig::CONFIG["BASERUBY"], - :EXEEXT => RbConfig::CONFIG["EXEEXT"], - :RUBY_INSTALL_NAME => RbConfig::CONFIG["RUBY_INSTALL_NAME"], - :RUBY_SO_NAME => RbConfig::CONFIG["RUBY_SO_NAME"], - :arch => RbConfig::CONFIG["arch"], - :bindir => RbConfig::CONFIG["bindir"], - :libdir => RbConfig::CONFIG["libdir"], - :ruby_install_name => RbConfig::CONFIG["ruby_install_name"], - :ruby_version => RbConfig::CONFIG["ruby_version"], - :sitedir => RbConfig::CONFIG["sitedir"], - :sitelibdir => RbConfig::CONFIG["sitelibdir"] + :BASERUBY => RbConfig::CONFIG["BASERUBY"], + :EXEEXT => RbConfig::CONFIG["EXEEXT"], + :RUBY_INSTALL_NAME => RbConfig::CONFIG["RUBY_INSTALL_NAME"], + :RUBY_SO_NAME => RbConfig::CONFIG["RUBY_SO_NAME"], + :arch => RbConfig::CONFIG["arch"], + :bindir => RbConfig::CONFIG["bindir"], + :libdir => RbConfig::CONFIG["libdir"], + :ruby_install_name => RbConfig::CONFIG["ruby_install_name"], + :ruby_version => RbConfig::CONFIG["ruby_version"], + :sitedir => RbConfig::CONFIG["sitedir"], + :sitelibdir => RbConfig::CONFIG["sitelibdir"] ) + DIRECTORIES = %w[cache doc gems specifications] unless defined?(DIRECTORIES) + MUTEX = Mutex.new RubyGemsPackageVersion = RubyGemsVersion - DIRECTORIES = %w[cache doc gems specifications] unless defined?(DIRECTORIES) + ## + # An Array of Regexps that match windows ruby platforms. + + WIN_PATTERNS = [ + /bccwin/i, + /cygwin/i, + /djgpp/i, + /mingw/i, + /mswin/i, + /wince/i, + ] @@source_index = nil @@win_platform = nil @@ -103,10 +101,129 @@ module Gem @ruby = nil @sources = [] + ## + # Activates an installed gem matching +gem+. The gem must satisfy + # +version_requirements+. + # + # Returns true if the gem is activated, false if it is already + # loaded, or an exception otherwise. + # + # Gem#activate adds the library paths in +gem+ to $LOAD_PATH. Before a Gem + # is activated its required Gems are activated. If the version information + # is omitted, the highest version Gem of the supplied name is loaded. If a + # Gem is not found that meets the version requirements or a required Gem is + # not found, a Gem::LoadError is raised. + # + # More information on version requirements can be found in the + # Gem::Requirement and Gem::Version documentation. + + def self.activate(gem, *version_requirements) + if version_requirements.empty? then + version_requirements = Gem::Requirement.default + end + + unless gem.respond_to?(:name) and + gem.respond_to?(:version_requirements) then + gem = Gem::Dependency.new(gem, version_requirements) + end + + matches = Gem.source_index.find_name(gem.name, gem.version_requirements) + report_activate_error(gem) if matches.empty? + + if @loaded_specs[gem.name] then + # This gem is already loaded. If the currently loaded gem is not in the + # list of candidate gems, then we have a version conflict. + existing_spec = @loaded_specs[gem.name] + + unless matches.any? { |spec| spec.version == existing_spec.version } then + raise Gem::Exception, + "can't activate #{gem}, already activated #{existing_spec.full_name}]" + end + + return false + end + + # new load + spec = matches.last + return false if spec.loaded? + + spec.loaded = true + @loaded_specs[spec.name] = spec + + # Load dependent gems first + spec.dependencies.each do |dep_gem| + activate dep_gem + end + + # bin directory must come before library directories + spec.require_paths.unshift spec.bindir if spec.bindir + + require_paths = spec.require_paths.map do |path| + File.join spec.full_gem_path, path + end + + sitelibdir = ConfigMap[:sitelibdir] + + # gem directories must come after -I and ENV['RUBYLIB'] + insert_index = load_path_insert_index + + if insert_index then + # gem directories must come after -I and ENV['RUBYLIB'] + $LOAD_PATH.insert(insert_index, *require_paths) + else + # we are probably testing in core, -I and RUBYLIB don't apply + $LOAD_PATH.unshift(*require_paths) + end + + return true + end + + ## + # An Array of all possible load paths for all versions of all gems in the + # Gem installation. + + def self.all_load_paths + result = [] + + Gem.path.each do |gemdir| + each_load_path all_partials(gemdir) do |load_path| + result << load_path + end + end + + result + end + + ## + # Return all the partial paths in +gemdir+. + + def self.all_partials(gemdir) + Dir[File.join(gemdir, 'gems/*')] + end + + private_class_method :all_partials + + ## + # The mode needed to read a file as straight binary. + + def self.binary_mode + @binary_mode ||= RUBY_VERSION > '1.9' ? 'rb:ascii-8bit' : 'rb' + end + + ## + # The path where gem executables are to be installed. + + def self.bindir(install_dir=Gem.dir) + return File.join(install_dir, 'bin') unless + install_dir.to_s == Gem.default_dir + Gem.default_bindir + end + + ## # Reset the +dir+ and +path+ values. The next time +dir+ or +path+ # is requested, the values will be calculated from scratch. This is # mainly used by the unit tests to provide test isolation. - # + def self.clear_paths @gem_home = nil @gem_path = nil @@ -116,441 +233,430 @@ module Gem end end - # The version of the Marshal format for your Ruby. - def self.marshal_version - "#{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}" + ## + # The path to standard location of the user's .gemrc file. + + def self.config_file + File.join Gem.user_home, '.gemrc' end ## - # The directory prefix this RubyGems was installed at. + # The standard configuration object for gems. - def self.prefix - prefix = File.dirname File.expand_path(__FILE__) - if prefix == ConfigMap[:sitelibdir] then - nil - else - File.dirname prefix - end + def self.configuration + return @configuration if @configuration + require 'rubygems/config_file' + @configuration = Gem::ConfigFile.new [] end - # Returns an Cache of specifications that are in the Gem.path - # - # return:: [Gem::SourceIndex] Index of installed Gem::Specifications - # - def self.source_index - @@source_index ||= SourceIndex.from_installed_gems + ## + # Use the given configuration object (which implements the ConfigFile + # protocol) as the standard configuration object. + + def self.configuration=(config) + @configuration = config end ## - # An Array of Regexps that match windows ruby platforms. + # The path the the data directory specified by the gem name. If the + # package is not available as a gem, return nil. - WIN_PATTERNS = [ - /bccwin/i, - /cygwin/i, - /djgpp/i, - /mingw/i, - /mswin/i, - /wince/i, - ] + def self.datadir(gem_name) + spec = @loaded_specs[gem_name] + return nil if spec.nil? + File.join(spec.full_gem_path, 'data', gem_name) + end ## - # Is this a windows platform? + # The path where gems are to be installed. - def self.win_platform? - if @@win_platform.nil? then - @@win_platform = !!WIN_PATTERNS.find { |r| RUBY_PLATFORM =~ r } - end + def self.dir + @gem_home ||= nil + set_home(ENV['GEM_HOME'] || default_dir) unless @gem_home + @gem_home + end - @@win_platform + ## + # Expand each partial gem path with each of the required paths specified + # in the Gem spec. Each expanded path is yielded. + + def self.each_load_path(partials) + partials.each do |gp| + base = File.basename(gp) + specfn = File.join(dir, "specifications", base + ".gemspec") + if File.exist?(specfn) + spec = eval(File.read(specfn)) + spec.require_paths.each do |rp| + yield(File.join(gp, rp)) + end + else + filename = File.join(gp, 'lib') + yield(filename) if File.exist?(filename) + end + end end - class << self + private_class_method :each_load_path - attr_reader :loaded_specs + ## + # Quietly ensure the named Gem directory contains all the proper + # subdirectories. If we can't create a directory due to a permission + # problem, then we will silently continue. - # Quietly ensure the named Gem directory contains all the proper - # subdirectories. If we can't create a directory due to a permission - # problem, then we will silently continue. - def ensure_gem_subdirectories(gemdir) - require 'fileutils' + def self.ensure_gem_subdirectories(gemdir) + require 'fileutils' - Gem::DIRECTORIES.each do |filename| - fn = File.join gemdir, filename - FileUtils.mkdir_p fn rescue nil unless File.exist? fn - end + Gem::DIRECTORIES.each do |filename| + fn = File.join gemdir, filename + FileUtils.mkdir_p fn rescue nil unless File.exist? fn end + end - def platforms - @platforms ||= [Gem::Platform::RUBY, Gem::Platform.local] - end + ## + # Finds the user's home directory. + #-- + # Some comments from the ruby-talk list regarding finding the home + # directory: + # + # I have HOME, USERPROFILE and HOMEDRIVE + HOMEPATH. Ruby seems + # to be depending on HOME in those code samples. I propose that + # it should fallback to USERPROFILE and HOMEDRIVE + HOMEPATH (at + # least on Win32). - # Returns an Array of sources to fetch remote gems from. If the sources - # list is empty, attempts to load the "sources" gem, then uses - # default_sources if it is not installed. - def sources - if @sources.empty? then - begin - gem 'sources', '> 0.0.1' - require 'sources' - rescue LoadError - @sources = default_sources - end - end + def self.find_home + ['HOME', 'USERPROFILE'].each do |homekey| + return ENV[homekey] if ENV[homekey] + end - @sources + if ENV['HOMEDRIVE'] && ENV['HOMEPATH'] then + return "#{ENV['HOMEDRIVE']}:#{ENV['HOMEPATH']}" end + begin + File.expand_path("~") + rescue + if File::ALT_SEPARATOR then + "C:/" + else + "/" + end + end + end - # Provide an alias for the old name. - alias cache source_index + private_class_method :find_home - # The directory path where Gems are to be installed. - # - # return:: [String] The directory path - # - def dir - @gem_home ||= nil - set_home(ENV['GEM_HOME'] || default_dir) unless @gem_home - @gem_home - end + ## + # Return a list of all possible load paths for the latest version for all + # gems in the Gem installation. - # The directory path where executables are to be installed. - # - def bindir(install_dir=Gem.dir) - return File.join(install_dir, 'bin') unless - install_dir.to_s == Gem.default_dir + def self.latest_load_paths + result = [] - if defined? RUBY_FRAMEWORK_VERSION then # mac framework support - '/usr/bin' - else # generic install - ConfigMap[:bindir] + Gem.path.each do |gemdir| + each_load_path(latest_partials(gemdir)) do |load_path| + result << load_path end end - # List of directory paths to search for Gems. - # - # return:: [List<String>] List of directory paths. - # - def path - @gem_path ||= nil - unless @gem_path - paths = [ENV['GEM_PATH']] - paths << APPLE_GEM_HOME if defined? APPLE_GEM_HOME - set_paths(paths.compact.join(File::PATH_SEPARATOR)) + result + end + + ## + # Return only the latest partial paths in the given +gemdir+. + + def self.latest_partials(gemdir) + latest = {} + all_partials(gemdir).each do |gp| + base = File.basename(gp) + if base =~ /(.*)-((\d+\.)*\d+)/ then + name, version = $1, $2 + ver = Gem::Version.new(version) + if latest[name].nil? || ver > latest[name][0] + latest[name] = [ver, gp] + end end - @gem_path end + latest.collect { |k,v| v[1] } + end - # The home directory for the user. - def user_home - @user_home ||= find_home - end + private_class_method :latest_partials - # Return the path to standard location of the users .gemrc file. - def config_file - File.join(Gem.user_home, '.gemrc') + ## + # The index to insert activated gem paths into the $LOAD_PATH. + # + # Defaults to the site lib directory unless gem_prelude.rb has loaded paths, + # then it inserts the activated gem's paths before the gem_prelude.rb paths + # so you can override the gem_prelude.rb default $LOAD_PATH paths. + + def self.load_path_insert_index + index = $LOAD_PATH.index ConfigMap[:sitelibdir] + + $LOAD_PATH.each_with_index do |path, i| + if path.instance_variables.include?(:@gem_prelude_index) or + path.instance_variables.include?('@gem_prelude_index') then + index = i + break + end end - # The standard configuration object for gems. - def configuration - return @configuration if @configuration - require 'rubygems/config_file' - @configuration = Gem::ConfigFile.new [] - end + index + end - # Use the given configuration object (which implements the - # ConfigFile protocol) as the standard configuration object. - def configuration=(config) - @configuration = config - end + ## + # The file name and line number of the caller of the caller of this method. - # Return the path the the data directory specified by the gem - # name. If the package is not available as a gem, return nil. - def datadir(gem_name) - spec = @loaded_specs[gem_name] - return nil if spec.nil? - File.join(spec.full_gem_path, 'data', gem_name) - end + def self.location_of_caller + file, lineno = caller[1].split(':') + lineno = lineno.to_i + [file, lineno] + end - # Return the searcher object to search for matching gems. - def searcher - MUTEX.synchronize do - @searcher ||= Gem::GemPathSearcher.new - end - end + private_class_method :location_of_caller - # Return the Ruby command to use to execute the Ruby interpreter. - def ruby - if @ruby.nil? then - @ruby = File.join(ConfigMap[:bindir], - ConfigMap[:ruby_install_name]) - @ruby << ConfigMap[:EXEEXT] - end + ## + # manage_gems is useless and deprecated. Don't call it anymore. + #-- + # TODO warn w/ RubyGems 1.2.x release. - @ruby - end + def self.manage_gems + #file, lineno = location_of_caller - # Return the index to insert activated gem paths into the $LOAD_PATH - # Defaults to the site lib directory unless gem_prelude.rb has loaded - # paths then it inserts the path before those paths so you can override - # the gem_prelude.rb default $LOAD_PATH paths. - def load_path_insert_index - index = $LOAD_PATH.index ConfigMap[:sitelibdir] - - $LOAD_PATH.each_with_index do |path, i| - if path.instance_variables.include?(:@gem_prelude_index) or - path.instance_variables.include?('@gem_prelude_index') then - index = i - break - end - end + #warn "#{file}:#{lineno}:Warning: Gem#manage_gems is deprecated and will be removed on or after September 2008." + end - index - end + ## + # The version of the Marshal format for your Ruby. - # Activate a gem (i.e. add it to the Ruby load path). The gem - # must satisfy all the specified version constraints. If - # +autorequire+ is true, then automatically require the specified - # autorequire file in the gem spec. - # - # Returns true if the gem is loaded by this call, false if it is - # already loaded, or an exception otherwise. - # - def activate(gem, autorequire, *version_requirements) - if version_requirements.empty? then - version_requirements = Gem::Requirement.default - end + def self.marshal_version + "#{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}" + end - unless gem.respond_to?(:name) && gem.respond_to?(:version_requirements) - gem = Gem::Dependency.new(gem, version_requirements) - end + ## + # Array of paths to search for Gems. - matches = Gem.source_index.find_name(gem.name, gem.version_requirements) - report_activate_error(gem) if matches.empty? + def self.path + @gem_path ||= nil - if @loaded_specs[gem.name] - # This gem is already loaded. If the currently loaded gem is - # not in the list of candidate gems, then we have a version - # conflict. - existing_spec = @loaded_specs[gem.name] - if ! matches.any? { |spec| spec.version == existing_spec.version } - fail Gem::Exception, "can't activate #{gem}, already activated #{existing_spec.full_name}]" - end - return false - end + unless @gem_path then + paths = [ENV['GEM_PATH']] || [default_path] - # new load - spec = matches.last - if spec.loaded? - return false unless autorequire - result = spec.autorequire ? require(spec.autorequire) : false - return result || false + if defined?(APPLE_GEM_HOME) and not ENV['GEM_PATH'] then + paths << APPLE_GEM_HOME end - spec.loaded = true - @loaded_specs[spec.name] = spec + set_paths paths.compact.join(File::PATH_SEPARATOR) + end - # Load dependent gems first - spec.dependencies.each do |dep_gem| - activate(dep_gem, autorequire) - end + @gem_path + end - # bin directory must come before library directories - spec.require_paths.unshift spec.bindir if spec.bindir + ## + # Array of platforms this RubyGems supports. - require_paths = spec.require_paths.map do |path| - File.join spec.full_gem_path, path - end + def self.platforms + @platforms ||= [Gem::Platform::RUBY, Gem::Platform.local] + end - sitelibdir = ConfigMap[:sitelibdir] + ## + # The directory prefix this RubyGems was installed at. - # gem directories must come after -I and ENV['RUBYLIB'] - insert_index = load_path_insert_index + def self.prefix + prefix = File.dirname File.expand_path(__FILE__) - if insert_index then - # gem directories must come after -I and ENV['RUBYLIB'] - $LOAD_PATH.insert(insert_index, *require_paths) - else - # we are probably testing in core, -I and RUBYLIB don't apply - $LOAD_PATH.unshift(*require_paths) - end + if prefix == File.expand_path(ConfigMap[:sitelibdir]) then + nil + else + File.dirname prefix + end + end - # Now autorequire - if autorequire && spec.autorequire then # DEPRECATED - Array(spec.autorequire).each do |a_lib| - require a_lib - end - end + ## + # Safely read a file in binary mode on all platforms. - return true - end + def self.read_binary(path) + File.open path, binary_mode do |f| f.read end + end + + ## + # Report a load error during activation. The message of load error + # depends on whether it was a version mismatch or if there are not gems of + # any version by the requested name. - # Report a load error during activation. The message of load - # error depends on whether it was a version mismatch or if there - # are not gems of any version by the requested name. - def report_activate_error(gem) - matches = Gem.source_index.find_name(gem.name) + def self.report_activate_error(gem) + matches = Gem.source_index.find_name(gem.name) - if matches.empty? then - error = Gem::LoadError.new( + if matches.empty? then + error = Gem::LoadError.new( "Could not find RubyGem #{gem.name} (#{gem.version_requirements})\n") - else - error = Gem::LoadError.new( + else + error = Gem::LoadError.new( "RubyGem version error: " + "#{gem.name}(#{matches.first.version} not #{gem.version_requirements})\n") - end - - error.name = gem.name - error.version_requirement = gem.version_requirements - raise error - end - private :report_activate_error - - # Use the +home+ and (optional) +paths+ values for +dir+ and +path+. - # Used mainly by the unit tests to provide environment isolation. - # - def use_paths(home, paths=[]) - clear_paths - set_home(home) if home - set_paths(paths.join(File::PATH_SEPARATOR)) if paths end - # Return a list of all possible load paths for all versions for - # all gems in the Gem installation. - # - def all_load_paths - result = [] - Gem.path.each do |gemdir| - each_load_path(all_partials(gemdir)) do |load_path| - result << load_path - end - end - result - end + error.name = gem.name + error.version_requirement = gem.version_requirements + raise error + end - # Return a list of all possible load paths for the latest version - # for all gems in the Gem installation. - def latest_load_paths - result = [] - Gem.path.each do |gemdir| - each_load_path(latest_partials(gemdir)) do |load_path| - result << load_path - end - end - result - end + private_class_method :report_activate_error - def required_location(gemname, libfile, *version_constraints) - version_constraints = Gem::Requirement.default if version_constraints.empty? - matches = Gem.source_index.find_name(gemname, version_constraints) - return nil if matches.empty? - spec = matches.last - spec.require_paths.each do |path| - result = File.join(spec.full_gem_path, path, libfile) - return result if File.exist?(result) - end - nil + def self.required_location(gemname, libfile, *version_constraints) + version_constraints = Gem::Requirement.default if version_constraints.empty? + matches = Gem.source_index.find_name(gemname, version_constraints) + return nil if matches.empty? + spec = matches.last + spec.require_paths.each do |path| + result = File.join(spec.full_gem_path, path, libfile) + return result if File.exist?(result) end + nil + end - def suffixes - ['', '.rb', '.rbw', '.so', '.bundle', '.dll', '.sl', '.jar'] - end + ## + # The path to the running Ruby interpreter. - def suffix_pattern - @suffix_pattern ||= "{#{suffixes.join(',')}}" + def self.ruby + if @ruby.nil? then + @ruby = File.join(ConfigMap[:bindir], + ConfigMap[:ruby_install_name]) + @ruby << ConfigMap[:EXEEXT] end - # manage_gems is useless and deprecated. Don't call it anymore. This - # will warn in two releases. - def manage_gems - # do nothing - end + @ruby + end - private + ## + # A Gem::Version for the currently running ruby. - # Return all the partial paths in the given +gemdir+. - def all_partials(gemdir) - Dir[File.join(gemdir, 'gems/*')] - end + def self.ruby_version + return @ruby_version if defined? @ruby_version + version = RUBY_VERSION.dup + version << ".#{RUBY_PATCHLEVEL}" if defined? RUBY_PATCHLEVEL + @ruby_version = Gem::Version.new version + end - # Return only the latest partial paths in the given +gemdir+. - def latest_partials(gemdir) - latest = {} - all_partials(gemdir).each do |gp| - base = File.basename(gp) - if base =~ /(.*)-((\d+\.)*\d+)/ then - name, version = $1, $2 - ver = Gem::Version.new(version) - if latest[name].nil? || ver > latest[name][0] - latest[name] = [ver, gp] - end - end - end - latest.collect { |k,v| v[1] } + ## + # The GemPathSearcher object used to search for matching installed gems. + + def self.searcher + MUTEX.synchronize do + @searcher ||= Gem::GemPathSearcher.new end + end + + ## + # Set the Gem home directory (as reported by Gem.dir). + + def self.set_home(home) + home = home.gsub(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR + @gem_home = home + ensure_gem_subdirectories(@gem_home) + end - # Expand each partial gem path with each of the required paths - # specified in the Gem spec. Each expanded path is yielded. - def each_load_path(partials) - partials.each do |gp| - base = File.basename(gp) - specfn = File.join(dir, "specifications", base + ".gemspec") - if File.exist?(specfn) - spec = eval(File.read(specfn)) - spec.require_paths.each do |rp| - yield(File.join(gp, rp)) - end - else - filename = File.join(gp, 'lib') - yield(filename) if File.exist?(filename) + private_class_method :set_home + + ## + # Set the Gem search path (as reported by Gem.path). + + def self.set_paths(gpaths) + if gpaths + @gem_path = gpaths.split(File::PATH_SEPARATOR) + + if File::ALT_SEPARATOR then + @gem_path.map! do |path| + path.gsub File::ALT_SEPARATOR, File::SEPARATOR end end + + @gem_path << Gem.dir + else + @gem_path = [Gem.dir] end - # Set the Gem home directory (as reported by +dir+). - def set_home(home) - @gem_home = home - ensure_gem_subdirectories(@gem_home) - end + @gem_path.uniq! + @gem_path.each do |gp| ensure_gem_subdirectories(gp) end + end - # Set the Gem search path (as reported by +path+). - def set_paths(gpaths) - if gpaths - @gem_path = gpaths.split(File::PATH_SEPARATOR) - @gem_path << Gem.dir - else - @gem_path = [Gem.dir] - end - @gem_path.uniq! - @gem_path.each do |gp| ensure_gem_subdirectories(gp) end - end + private_class_method :set_paths - # Some comments from the ruby-talk list regarding finding the home - # directory: - # - # I have HOME, USERPROFILE and HOMEDRIVE + HOMEPATH. Ruby seems - # to be depending on HOME in those code samples. I propose that - # it should fallback to USERPROFILE and HOMEDRIVE + HOMEPATH (at - # least on Win32). - # - def find_home - ['HOME', 'USERPROFILE'].each do |homekey| - return ENV[homekey] if ENV[homekey] - end - if ENV['HOMEDRIVE'] && ENV['HOMEPATH'] - return "#{ENV['HOMEDRIVE']}:#{ENV['HOMEPATH']}" - end + ## + # Returns the Gem::SourceIndex of specifications that are in the Gem.path + + def self.source_index + @@source_index ||= SourceIndex.from_installed_gems + end + + ## + # Returns an Array of sources to fetch remote gems from. If the sources + # list is empty, attempts to load the "sources" gem, then uses + # default_sources if it is not installed. + + def self.sources + if @sources.empty? then begin - File.expand_path("~") - rescue StandardError => ex - if File::ALT_SEPARATOR - "C:/" - else - "/" - end + gem 'sources', '> 0.0.1' + require 'sources' + rescue LoadError + @sources = default_sources end end + @sources + end + + ## + # Glob pattern for require-able path suffixes. + + def self.suffix_pattern + @suffix_pattern ||= "{#{suffixes.join(',')}}" + end + + ## + # Suffixes for require-able paths. + + def self.suffixes + ['', '.rb', '.rbw', '.so', '.bundle', '.dll', '.sl', '.jar'] + end + + ## + # Use the +home+ and +paths+ values for Gem.dir and Gem.path. Used mainly + # by the unit tests to provide environment isolation. + + def self.use_paths(home, paths=[]) + clear_paths + set_home(home) if home + set_paths(paths.join(File::PATH_SEPARATOR)) if paths + end + + ## + # The home directory for the user. + + def self.user_home + @user_home ||= find_home + end + + ## + # Is this a windows platform? + + def self.win_platform? + if @@win_platform.nil? then + @@win_platform = !!WIN_PATTERNS.find { |r| RUBY_PLATFORM =~ r } + end + + @@win_platform + end + + class << self + + attr_reader :loaded_specs + + # :stopdoc: + + alias cache source_index # an alias for the old name + + # :startdoc: + end end @@ -558,6 +664,7 @@ end # Modify the non-gem version of datadir to handle gem package names. require 'rbconfig/datadir' + module Config # :nodoc: class << self alias gem_original_datadir datadir diff --git a/lib/rubygems/builder.rb b/lib/rubygems/builder.rb index f7f07e86bf..6fd8528f56 100644 --- a/lib/rubygems/builder.rb +++ b/lib/rubygems/builder.rb @@ -65,13 +65,20 @@ EOM end def write_package - Package.open(@spec.file_name, "w", @signer) do |pkg| - pkg.metadata = @spec.to_yaml - @spec.files.each do |file| - next if File.directory? file - pkg.add_file_simple(file, File.stat(@spec.file_name).mode & 0777, - File.size(file)) do |os| - os.write File.open(file, "rb"){|f|f.read} + open @spec.file_name, 'wb' do |gem_io| + Gem::Package.open gem_io, 'w', @signer do |pkg| + pkg.metadata = @spec.to_yaml + + @spec.files.each do |file| + next if File.directory? file + + stat = File.stat file + mode = stat.mode & 0777 + size = stat.size + + pkg.add_file_simple file, mode, size do |tar_io| + tar_io.write open(file, "rb") { |f| f.read } + end end end end diff --git a/lib/rubygems/command_manager.rb b/lib/rubygems/command_manager.rb index a80c821c5c..b8aa651f56 100644 --- a/lib/rubygems/command_manager.rb +++ b/lib/rubygems/command_manager.rb @@ -123,6 +123,7 @@ module Gem end private + def load_and_instantiate(command_name) command_name = command_name.to_s retried = false diff --git a/lib/rubygems/commands/cleanup_command.rb b/lib/rubygems/commands/cleanup_command.rb index f6deac9829..40dcb9db34 100644 --- a/lib/rubygems/commands/cleanup_command.rb +++ b/lib/rubygems/commands/cleanup_command.rb @@ -2,92 +2,90 @@ require 'rubygems/command' require 'rubygems/source_index' require 'rubygems/dependency_list' -module Gem - module Commands - class CleanupCommand < Command - def initialize - super( - 'cleanup', +class Gem::Commands::CleanupCommand < Gem::Command + + def initialize + super 'cleanup', 'Clean up old versions of installed gems in the local repository', - { - :force => false, - :test => false, - :install_dir => Gem.dir - }) - add_option('-d', '--dryrun', "") do |value, options| - options[:dryrun] = true - end - end + :force => false, :test => false, :install_dir => Gem.dir - def arguments # :nodoc: - "GEMNAME name of gem to cleanup" - end + add_option('-d', '--dryrun', "") do |value, options| + options[:dryrun] = true + end + end - def defaults_str # :nodoc: - "--no-dryrun" - end + def arguments # :nodoc: + "GEMNAME name of gem to cleanup" + end + + def defaults_str # :nodoc: + "--no-dryrun" + end + + def usage # :nodoc: + "#{program_name} [GEMNAME ...]" + end + + def execute + say "Cleaning up installed gems..." + primary_gems = {} - def usage # :nodoc: - "#{program_name} [GEMNAME ...]" + Gem.source_index.each do |name, spec| + if primary_gems[spec.name].nil? or + primary_gems[spec.name].version < spec.version then + primary_gems[spec.name] = spec end + end - def execute - say "Cleaning up installed gems..." - srcindex = Gem::SourceIndex.from_installed_gems - primary_gems = {} + gems_to_cleanup = [] - srcindex.each do |name, spec| - if primary_gems[spec.name].nil? or primary_gems[spec.name].version < spec.version - primary_gems[spec.name] = spec - end + unless options[:args].empty? then + options[:args].each do |gem_name| + specs = Gem.cache.search(/^#{gem_name}$/i) + specs.each do |spec| + gems_to_cleanup << spec end + end + else + Gem.source_index.each do |name, spec| + gems_to_cleanup << spec + end + end - gems_to_cleanup = [] - - unless options[:args].empty? then - options[:args].each do |gem_name| - specs = Gem.cache.search(/^#{gem_name}$/i) - specs.each do |spec| - gems_to_cleanup << spec - end - end - else - srcindex.each do |name, spec| - gems_to_cleanup << spec - end - end + gems_to_cleanup = gems_to_cleanup.select { |spec| + primary_gems[spec.name].version != spec.version + } - gems_to_cleanup = gems_to_cleanup.select { |spec| - primary_gems[spec.name].version != spec.version - } + uninstall_command = Gem::CommandManager.instance['uninstall'] + deplist = Gem::DependencyList.new + gems_to_cleanup.uniq.each do |spec| deplist.add spec end - uninstall_command = Gem::CommandManager.instance['uninstall'] - deplist = DependencyList.new - gems_to_cleanup.uniq.each do |spec| deplist.add(spec) end + deps = deplist.strongly_connected_components.flatten.reverse - deplist.dependency_order.each do |spec| - if options[:dryrun] then - say "Dry Run Mode: Would uninstall #{spec.full_name}" - else - say "Attempting uninstall on #{spec.full_name}" + deps.each do |spec| + if options[:dryrun] then + say "Dry Run Mode: Would uninstall #{spec.full_name}" + else + say "Attempting to uninstall #{spec.full_name}" - options[:args] = [spec.name] - options[:version] = "= #{spec.version}" - options[:executables] = true + options[:args] = [spec.name] + options[:version] = "= #{spec.version}" + options[:executables] = false - uninstall_command.merge_options(options) + uninstaller = Gem::Uninstaller.new spec.name, options - begin - uninstall_command.execute - rescue Gem::DependencyRemovalException => ex - say "Unable to uninstall #{spec.full_name} ... continuing with remaining gems" - end - end + begin + uninstaller.uninstall + rescue Gem::DependencyRemovalException, + Gem::GemNotInHomeException => e + say "Unable to uninstall #{spec.full_name}:" + say "\t#{e.class}: #{e.message}" end - - say "Clean Up Complete" end end - + + say "Clean Up Complete" end + end + diff --git a/lib/rubygems/commands/environment_command.rb b/lib/rubygems/commands/environment_command.rb index ab85361753..56b373cfbe 100644 --- a/lib/rubygems/commands/environment_command.rb +++ b/lib/rubygems/commands/environment_command.rb @@ -25,19 +25,18 @@ class Gem::Commands::EnvironmentCommand < Gem::Command def execute out = '' arg = options[:args][0] - if begins?("packageversion", arg) then + case arg + when /^packageversion/ then out << Gem::RubyGemsPackageVersion - elsif begins?("version", arg) then + when /^version/ then out << Gem::RubyGemsVersion - elsif begins?("gemdir", arg) then + when /^gemdir/, /^gemhome/, /^home/, /^GEM_HOME/ then out << Gem.dir - elsif begins?("gempath", arg) then - out << Gem.path.join("\n") - elsif begins?("remotesources", arg) then + when /^gempath/, /^path/, /^GEM_PATH/ then + out << Gem.path.join(File::PATH_SEPARATOR) + when /^remotesources/ then out << Gem.sources.join("\n") - elsif arg then - fail Gem::CommandLineError, "Unknown enviroment option [#{arg}]" - else + when nil then out = "RubyGems Environment:\n" out << " - RUBYGEMS VERSION: #{Gem::RubyGemsVersion} (#{Gem::RubyGemsPackageVersion})\n" @@ -75,6 +74,9 @@ class Gem::Commands::EnvironmentCommand < Gem::Command Gem.sources.each do |s| out << " - #{s}\n" end + + else + fail Gem::CommandLineError, "Unknown enviroment option [#{arg}]" end say out true diff --git a/lib/rubygems/commands/fetch_command.rb b/lib/rubygems/commands/fetch_command.rb index 7db365eba0..ccedc45401 100644 --- a/lib/rubygems/commands/fetch_command.rb +++ b/lib/rubygems/commands/fetch_command.rb @@ -44,17 +44,15 @@ class Gem::Commands::FetchCommand < Gem::Command spec, source_uri = specs_and_sources.last - gem_file = "#{spec.full_name}.gem" - - gem_path = File.join source_uri, 'gems', gem_file - - gem = Gem::RemoteFetcher.fetcher.fetch_path gem_path - - File.open gem_file, 'wb' do |fp| - fp.write gem + if spec.nil? then + alert_error "Could not find #{gem_name} in any repository" + next end - say "Downloaded #{gem_file}" + path = Gem::RemoteFetcher.fetcher.download spec, source_uri + FileUtils.mv path, "#{spec.full_name}.gem" + + say "Downloaded #{spec.full_name}" end end diff --git a/lib/rubygems/commands/install_command.rb b/lib/rubygems/commands/install_command.rb index aa9f480c2a..ce0bc6ba04 100644 --- a/lib/rubygems/commands/install_command.rb +++ b/lib/rubygems/commands/install_command.rb @@ -62,13 +62,15 @@ class Gem::Commands::InstallCommand < Gem::Command :install_dir => options[:install_dir], :security_policy => options[:security_policy], :wrappers => options[:wrappers], + :bin_dir => options[:bin_dir] } + exit_code = 0 + get_all_gem_names.each do |gem_name| begin - inst = Gem::DependencyInstaller.new gem_name, options[:version], - install_options - inst.install + inst = Gem::DependencyInstaller.new install_options + inst.install gem_name, options[:version] inst.installed_gems.each do |spec| say "Successfully installed #{spec.full_name}" @@ -77,8 +79,10 @@ class Gem::Commands::InstallCommand < Gem::Command installed_gems.push(*inst.installed_gems) rescue Gem::InstallError => e alert_error "Error installing #{gem_name}:\n\t#{e.message}" + exit_code |= 1 rescue Gem::GemNotFoundException => e alert_error e.message + exit_code |= 2 # rescue => e # # TODO: Fix this handle to allow the error to propagate to # # the top level handler. Examine the other errors as @@ -121,6 +125,8 @@ class Gem::Commands::InstallCommand < Gem::Command end end end + + raise Gem::SystemExitException, exit_code end end diff --git a/lib/rubygems/commands/list_command.rb b/lib/rubygems/commands/list_command.rb index e179ff57ee..f8b377fcde 100644 --- a/lib/rubygems/commands/list_command.rb +++ b/lib/rubygems/commands/list_command.rb @@ -6,10 +6,8 @@ module Gem class ListCommand < QueryCommand def initialize - super( - 'list', - 'Display all gems whose name starts with STRING' - ) + super 'list', 'Display gems whose name starts with STRING' + remove_option('--name-matches') end diff --git a/lib/rubygems/commands/mirror_command.rb b/lib/rubygems/commands/mirror_command.rb index fc4f086ad3..959b8eaec3 100644 --- a/lib/rubygems/commands/mirror_command.rb +++ b/lib/rubygems/commands/mirror_command.rb @@ -2,7 +2,7 @@ require 'yaml' require 'zlib' require 'rubygems/command' -require 'rubygems/gem_open_uri' +require 'open-uri' class Gem::Commands::MirrorCommand < Gem::Command diff --git a/lib/rubygems/commands/query_command.rb b/lib/rubygems/commands/query_command.rb index 4f957625ee..fdc5a6a4ea 100644 --- a/lib/rubygems/commands/query_command.rb +++ b/lib/rubygems/commands/query_command.rb @@ -1,15 +1,25 @@ require 'rubygems/command' require 'rubygems/local_remote_options' require 'rubygems/source_info_cache' +require 'rubygems/version_option' class Gem::Commands::QueryCommand < Gem::Command include Gem::LocalRemoteOptions + include Gem::VersionOption def initialize(name = 'query', summary = 'Query gem information in local or remote repositories') super name, summary, - :name => /.*/, :domain => :local, :details => false, :versions => true + :name => //, :domain => :local, :details => false, :versions => true, + :installed => false, :version => Gem::Requirement.default + + add_option('-i', '--[no-]installed', + 'Check for installed gem') do |value, options| + options[:installed] = value + end + + add_version_option add_option('-n', '--name-matches REGEXP', 'Name of gem(s) to query on matches the', @@ -28,33 +38,70 @@ class Gem::Commands::QueryCommand < Gem::Command options[:details] = false unless value end + add_option('-a', '--all', + 'Display all gem versions') do |value, options| + options[:all] = value + end + add_local_remote_options end def defaults_str # :nodoc: - "--local --name-matches '.*' --no-details --versions" + "--local --name-matches // --no-details --versions --no-installed" end def execute + exit_code = 0 + name = options[:name] + if options[:installed] then + if name.source.empty? then + alert_error "You must specify a gem name" + exit_code |= 4 + elsif installed? name.source, options[:version] then + say "true" + else + say "false" + exit_code |= 1 + end + + raise Gem::SystemExitException, exit_code + end + if local? then say say "*** LOCAL GEMS ***" say - output_query_results Gem.cache.search(name) + + output_query_results Gem.source_index.search(name) end if remote? then say say "*** REMOTE GEMS ***" say - output_query_results Gem::SourceInfoCache.search(name) + + begin + Gem::SourceInfoCache.cache.refresh options[:all] + rescue Gem::RemoteFetcher::FetchError + # no network + end + + output_query_results Gem::SourceInfoCache.search(name, false, true) end end private + ## + # Check if gem +name+ version +version+ is installed. + + def installed?(name, version = Gem::Requirement.default) + dep = Gem::Dependency.new name, version + !Gem.source_index.search(dep).empty? + end + def output_query_results(gemspecs) output = [] gem_list_with_version = {} @@ -98,7 +145,7 @@ class Gem::Commands::QueryCommand < Gem::Command ## # Used for wrapping and indenting text - # + def format_text(text, wrap, indent=0) result = [] work = text.dup diff --git a/lib/rubygems/commands/sources_command.rb b/lib/rubygems/commands/sources_command.rb index a0977f90dc..6d9d5b5b90 100644 --- a/lib/rubygems/commands/sources_command.rb +++ b/lib/rubygems/commands/sources_command.rb @@ -39,8 +39,11 @@ class Gem::Commands::SourcesCommand < Gem::Command options[:list] = !(options[:add] || options[:remove] || options[:clear_all] || options[:update]) if options[:clear_all] then - remove_cache_file("user", Gem::SourceInfoCache.user_cache_file) - remove_cache_file("system", Gem::SourceInfoCache.system_cache_file) + sic = Gem::SourceInfoCache + remove_cache_file 'user', sic.user_cache_file + remove_cache_file 'latest user', sic.latest_user_cache_file + remove_cache_file 'system', sic.system_cache_file + remove_cache_file 'latest system', sic.latest_system_cache_file end if options[:add] then @@ -48,7 +51,7 @@ class Gem::Commands::SourcesCommand < Gem::Command sice = Gem::SourceInfoCacheEntry.new nil, nil begin - sice.refresh source_uri + sice.refresh source_uri, true Gem::SourceInfoCache.cache_data[source_uri] = sice Gem::SourceInfoCache.cache.update @@ -66,7 +69,7 @@ class Gem::Commands::SourcesCommand < Gem::Command end if options[:update] then - Gem::SourceInfoCache.cache.refresh + Gem::SourceInfoCache.cache.refresh true Gem::SourceInfoCache.cache.flush say "source cache successfully updated" @@ -78,6 +81,11 @@ class Gem::Commands::SourcesCommand < Gem::Command unless Gem.sources.include? source_uri then say "source #{source_uri} not present in cache" else + begin # HACK figure out how to get the cache w/o update + Gem::SourceInfoCache.cache + rescue Gem::RemoteFetcher::FetchError + end + Gem::SourceInfoCache.cache_data.delete source_uri Gem::SourceInfoCache.cache.update Gem::SourceInfoCache.cache.flush @@ -100,11 +108,12 @@ class Gem::Commands::SourcesCommand < Gem::Command private - def remove_cache_file(desc, fn) - FileUtils.rm_rf fn rescue nil - if ! File.exist?(fn) + def remove_cache_file(desc, path) + FileUtils.rm_rf path + + if not File.exist?(path) then say "*** Removed #{desc} source cache ***" - elsif ! File.writable?(fn) + elsif not File.writable?(path) then say "*** Unable to remove #{desc} source cache (write protected) ***" else say "*** Unable to remove #{desc} source cache ***" diff --git a/lib/rubygems/commands/specification_command.rb b/lib/rubygems/commands/specification_command.rb index 1ab2ad9260..7c8598e53b 100644 --- a/lib/rubygems/commands/specification_command.rb +++ b/lib/rubygems/commands/specification_command.rb @@ -3,6 +3,7 @@ require 'rubygems/command' require 'rubygems/local_remote_options' require 'rubygems/version_option' require 'rubygems/source_info_cache' +require 'rubygems/format' class Gem::Commands::SpecificationCommand < Gem::Command @@ -41,13 +42,16 @@ class Gem::Commands::SpecificationCommand < Gem::Command gem = get_one_gem_name if local? then - source_index = Gem::SourceIndex.from_installed_gems - specs.push(*source_index.search(/\A#{gem}\z/, options[:version])) + if File.exist? gem then + specs << Gem::Format.from_file_by_path(gem).spec rescue nil + end + + if specs.empty? then + specs.push(*Gem.source_index.search(/\A#{gem}\z/, options[:version])) + end end if remote? then - alert_warning "Remote information is not complete\n\n" - Gem::SourceInfoCache.cache_data.each do |_,sice| specs.push(*sice.source_index.search(gem, options[:version])) end diff --git a/lib/rubygems/commands/uninstall_command.rb b/lib/rubygems/commands/uninstall_command.rb index 2d9c46ee52..3d6e2383bc 100644 --- a/lib/rubygems/commands/uninstall_command.rb +++ b/lib/rubygems/commands/uninstall_command.rb @@ -35,6 +35,11 @@ module Gem options[:install_dir] = File.expand_path(value) end + add_option('-n', '--bindir DIR', + 'Directory to remove binaries from') do |value, options| + options[:bin_dir] = File.expand_path(value) + end + add_version_option add_platform_option end @@ -54,7 +59,13 @@ module Gem def execute get_all_gem_names.each do |gem_name| - Gem::Uninstaller.new(gem_name, options).uninstall + begin + Gem::Uninstaller.new(gem_name, options).uninstall + rescue Gem::GemNotInHomeException => e + spec = e.spec + alert("In order to remove #{spec.name}, please execute:\n" \ + "\tgem uninstall #{spec.name} --install-dir=#{spec.installation_path}") + end end end end diff --git a/lib/rubygems/commands/unpack_command.rb b/lib/rubygems/commands/unpack_command.rb index 23ebabc21a..d187f8a9ea 100644 --- a/lib/rubygems/commands/unpack_command.rb +++ b/lib/rubygems/commands/unpack_command.rb @@ -38,6 +38,7 @@ class Gem::Commands::UnpackCommand < Gem::Command def execute gemname = get_one_gem_name path = get_path(gemname, options[:version]) + if path then basename = File.basename(path).sub(/\.gem$/, '') target_dir = File.expand_path File.join(options[:target], basename) @@ -66,16 +67,27 @@ class Gem::Commands::UnpackCommand < Gem::Command # source directories? def get_path(gemname, version_req) return gemname if gemname =~ /\.gem$/i - specs = Gem::SourceIndex.from_installed_gems.search(/\A#{gemname}\z/, version_req) + + specs = Gem::source_index.search(/\A#{gemname}\z/, version_req) + selected = specs.sort_by { |s| s.version }.last + return nil if selected.nil? + # We expect to find (basename).gem in the 'cache' directory. # Furthermore, the name match must be exact (ignoring case). if gemname =~ /^#{selected.name}$/i filename = selected.full_name + '.gem' - return File.join(Gem.dir, 'cache', filename) + path = nil + + Gem.path.find do |gem_dir| + path = File.join gem_dir, 'cache', filename + File.exist? path + end + + path else - return nil + nil end end diff --git a/lib/rubygems/commands/update_command.rb b/lib/rubygems/commands/update_command.rb index 88d48d705e..b8de911e20 100644 --- a/lib/rubygems/commands/update_command.rb +++ b/lib/rubygems/commands/update_command.rb @@ -1,8 +1,10 @@ require 'rubygems/command' +require 'rubygems/command_manager' require 'rubygems/install_update_options' require 'rubygems/local_remote_options' require 'rubygems/source_info_cache' require 'rubygems/version_option' +require 'rubygems/commands/install_command' class Gem::Commands::UpdateCommand < Gem::Command @@ -45,7 +47,7 @@ class Gem::Commands::UpdateCommand < Gem::Command def execute if options[:system] then - say "Updating RubyGems..." + say "Updating RubyGems" unless options[:args].empty? then fail "No gem names are allowed with the --system option" @@ -53,10 +55,10 @@ class Gem::Commands::UpdateCommand < Gem::Command options[:args] = ["rubygems-update"] else - say "Updating installed gems..." + say "Updating installed gems" end - hig = highest_installed_gems = {} + hig = {} Gem::SourceIndex.from_installed_gems.each do |name, spec| if hig[spec.name].nil? or hig[spec.name].version < spec.version then @@ -64,25 +66,28 @@ class Gem::Commands::UpdateCommand < Gem::Command end end - remote_gemspecs = Gem::SourceInfoCache.search(//) + pattern = if options[:args].empty? then + // + else + Regexp.union(*options[:args]) + end - gems_to_update = if options[:args].empty? then - which_to_update(highest_installed_gems, remote_gemspecs) - else - options[:args] - end + remote_gemspecs = Gem::SourceInfoCache.search pattern - options[:domain] = :remote # install from remote source + gems_to_update = which_to_update hig, remote_gemspecs - # HACK use the real API - install_command = Gem::CommandManager.instance['install'] + updated = [] + # HACK use the real API gems_to_update.uniq.sort.each do |name| - say "Attempting remote update of #{name}" - options[:args] = [name] - options[:ignore_dependencies] = true # HACK skip seen gems instead - install_command.merge_options(options) - install_command.execute + next if updated.any? { |spec| spec.name == name } + say "Updating #{name}" + installer = Gem::DependencyInstaller.new options + installer.install name + installer.installed_gems.each do |spec| + updated << spec + say "Successfully installed #{spec.full_name}" + end end if gems_to_update.include? "rubygems-update" then @@ -97,12 +102,10 @@ class Gem::Commands::UpdateCommand < Gem::Command say "RubyGems system software updated" if installed else - updated = gems_to_update.uniq.sort.collect { |g| g.to_s } - if updated.empty? then say "Nothing to update" else - say "Gems updated: #{updated.join ', '}" + say "Gems updated: #{updated.map { |spec| spec.name }.join ', '}" end end end diff --git a/lib/rubygems/custom_require.rb b/lib/rubygems/custom_require.rb index 598ec3ef98..5ff65afb14 100755 --- a/lib/rubygems/custom_require.rb +++ b/lib/rubygems/custom_require.rb @@ -28,7 +28,7 @@ module Kernel rescue LoadError => load_error if load_error.message =~ /\A[Nn]o such file to load -- #{Regexp.escape path}\z/ and spec = Gem.searcher.find(path) then - Gem.activate(spec.name, false, "= #{spec.version}") + Gem.activate(spec.name, "= #{spec.version}") gem_original_require path else raise load_error diff --git a/lib/rubygems/defaults.rb b/lib/rubygems/defaults.rb index 3a6229511b..3864e5faca 100644 --- a/lib/rubygems/defaults.rb +++ b/lib/rubygems/defaults.rb @@ -11,6 +11,9 @@ module Gem if defined? RUBY_FRAMEWORK_VERSION then File.join File.dirname(ConfigMap[:sitedir]), 'Gems', ConfigMap[:ruby_version] + elsif defined? RUBY_ENGINE then + File.join ConfigMap[:libdir], RUBY_ENGINE, 'gems', + ConfigMap[:ruby_version] else File.join ConfigMap[:libdir], 'ruby', 'gems', ConfigMap[:ruby_version] end @@ -29,7 +32,11 @@ module Gem # The default directory for binaries def self.default_bindir - Config::CONFIG['bindir'] + if defined? RUBY_FRAMEWORK_VERSION then # mac framework support + '/usr/bin' + else # generic install + ConfigMap[:bindir] + end end # The default system-wide source info cache directory. diff --git a/lib/rubygems/dependency_installer.rb b/lib/rubygems/dependency_installer.rb index ec8a50d912..26ef41b2f1 100644 --- a/lib/rubygems/dependency_installer.rb +++ b/lib/rubygems/dependency_installer.rb @@ -22,8 +22,7 @@ class Gem::DependencyInstaller } ## - # Creates a new installer instance that will install +gem_name+ using - # version requirement +version+ and +options+. + # Creates a new installer instance. # # Options are: # :env_shebang:: See Gem::Installer::new. @@ -36,7 +35,7 @@ class Gem::DependencyInstaller # :install_dir: See Gem::Installer#install. # :security_policy: See Gem::Installer::new and Gem::Security. # :wrappers: See Gem::Installer::new - def initialize(gem_name, version = nil, options = {}) + def initialize(options = {}) options = DEFAULT_OPTIONS.merge options @env_shebang = options[:env_shebang] @domain = options[:domain] @@ -46,49 +45,9 @@ class Gem::DependencyInstaller @install_dir = options[:install_dir] || Gem.dir @security_policy = options[:security_policy] @wrappers = options[:wrappers] + @bin_dir = options[:bin_dir] @installed_gems = [] - - spec_and_source = nil - - glob = if File::ALT_SEPARATOR then - gem_name.gsub File::ALT_SEPARATOR, File::SEPARATOR - else - gem_name - end - - local_gems = Dir["#{glob}*"].sort.reverse - - unless local_gems.empty? then - local_gems.each do |gem_file| - next unless gem_file =~ /gem$/ - begin - spec = Gem::Format.from_file_by_path(gem_file).spec - spec_and_source = [spec, gem_file] - break - rescue SystemCallError, Gem::Package::FormatError - end - end - end - - if spec_and_source.nil? then - version ||= Gem::Requirement.default - @dep = Gem::Dependency.new gem_name, version - spec_and_sources = find_gems_with_sources(@dep).reverse - - spec_and_source = spec_and_sources.find do |spec, source| - Gem::Platform.match spec.platform - end - end - - if spec_and_source.nil? then - raise Gem::GemNotFoundException, - "could not find #{gem_name} locally or in a repository" - end - - @specs_and_sources = [spec_and_source] - - gather_dependencies end ## @@ -107,71 +66,30 @@ class Gem::DependencyInstaller end if @domain == :both or @domain == :remote then - gems_and_sources.push(*Gem::SourceInfoCache.search_with_source(dep, true)) - end - - gems_and_sources.sort_by do |gem, source| - [gem, source !~ /^http:\/\// ? 1 : 0] # local gems win - end - end - - ## - # Moves the gem +spec+ from +source_uri+ to the cache dir unless it is - # already there. If the source_uri is local the gem cache dir copy is - # always replaced. - def download(spec, source_uri) - gem_file_name = "#{spec.full_name}.gem" - local_gem_path = File.join @install_dir, 'cache', gem_file_name - - Gem.ensure_gem_subdirectories @install_dir - - source_uri = URI.parse source_uri unless URI::Generic === source_uri - scheme = source_uri.scheme - - # URI.parse gets confused by MS Windows paths with forward slashes. - scheme = nil if scheme =~ /^[a-z]$/i - - case scheme - when 'http' then - unless File.exist? local_gem_path then - begin - say "Downloading gem #{gem_file_name}" if - Gem.configuration.really_verbose - - remote_gem_path = source_uri + "gems/#{gem_file_name}" - - gem = Gem::RemoteFetcher.fetcher.fetch_path remote_gem_path - rescue Gem::RemoteFetcher::FetchError - raise if spec.original_platform == spec.platform - - alternate_name = "#{spec.name}-#{spec.version}-#{spec.original_platform}.gem" + begin + requirements = dep.version_requirements.requirements.map do |req, ver| + req + end - say "Failed, downloading gem #{alternate_name}" if - Gem.configuration.really_verbose + all = requirements.length > 1 || + requirements.first != ">=" || requirements.first != ">" - remote_gem_path = source_uri + "gems/#{alternate_name}" + found = Gem::SourceInfoCache.search_with_source dep, true, all - gem = Gem::RemoteFetcher.fetcher.fetch_path remote_gem_path - end + gems_and_sources.push(*found) - File.open local_gem_path, 'wb' do |fp| - fp.write gem + rescue Gem::RemoteFetcher::FetchError => e + if Gem.configuration.really_verbose then + say "Error fetching remote data:\t\t#{e.message}" + say "Falling back to local-only install" end + @domain = :local end - when nil, 'file' then # TODO test for local overriding cache - begin - FileUtils.cp source_uri.to_s, local_gem_path - rescue Errno::EACCES - local_gem_path = source_uri.to_s - end - - say "Using local gem #{local_gem_path}" if - Gem.configuration.really_verbose - else - raise Gem::InstallError, "unsupported URI scheme #{source_uri.scheme}" end - local_gem_path + gems_and_sources.sort_by do |gem, source| + [gem, source =~ /^http:\/\// ? 0 : 1] # local gems win + end end ## @@ -208,9 +126,57 @@ class Gem::DependencyInstaller @gems_to_install = dependency_list.dependency_order.reverse end + def find_spec_by_name_and_version gem_name, version = Gem::Requirement.default + spec_and_source = nil + + glob = if File::ALT_SEPARATOR then + gem_name.gsub File::ALT_SEPARATOR, File::SEPARATOR + else + gem_name + end + + local_gems = Dir["#{glob}*"].sort.reverse + + unless local_gems.empty? then + local_gems.each do |gem_file| + next unless gem_file =~ /gem$/ + begin + spec = Gem::Format.from_file_by_path(gem_file).spec + spec_and_source = [spec, gem_file] + break + rescue SystemCallError, Gem::Package::FormatError + end + end + end + + if spec_and_source.nil? then + dep = Gem::Dependency.new gem_name, version + spec_and_sources = find_gems_with_sources(dep).reverse + + spec_and_source = spec_and_sources.find { |spec, source| + Gem::Platform.match spec.platform + } + end + + if spec_and_source.nil? then + raise Gem::GemNotFoundException, + "could not find #{gem_name} locally or in a repository" + end + + @specs_and_sources = [spec_and_source] + end + ## # Installs the gem and all its dependencies. - def install + def install dep_or_name, version = Gem::Requirement.default + if String === dep_or_name then + find_spec_by_name_and_version dep_or_name, version + else + @specs_and_sources = [find_gems_with_sources(dep_or_name).last] + end + + gather_dependencies + spec_dir = File.join @install_dir, 'specifications' source_index = Gem::SourceIndex.from_gems_in spec_dir @@ -219,10 +185,11 @@ class Gem::DependencyInstaller # HACK is this test for full_name acceptable? next if source_index.any? { |n,_| n == spec.full_name } and not last + # TODO: make this sorta_verbose so other users can benefit from it say "Installing gem #{spec.full_name}" if Gem.configuration.really_verbose _, source_uri = @specs_and_sources.assoc spec - local_gem_path = download spec, source_uri + local_gem_path = Gem::RemoteFetcher.fetcher.download spec, source_uri inst = Gem::Installer.new local_gem_path, :env_shebang => @env_shebang, @@ -231,7 +198,8 @@ class Gem::DependencyInstaller :ignore_dependencies => @ignore_dependencies, :install_dir => @install_dir, :security_policy => @security_policy, - :wrappers => @wrappers + :wrappers => @wrappers, + :bin_dir => @bin_dir spec = inst.install diff --git a/lib/rubygems/exceptions.rb b/lib/rubygems/exceptions.rb index b34bc718ff..c37507c62a 100644 --- a/lib/rubygems/exceptions.rb +++ b/lib/rubygems/exceptions.rb @@ -13,7 +13,10 @@ class Gem::DependencyRemovalException < Gem::Exception; end ## # Raised when attempting to uninstall a gem that isn't in GEM_HOME. -class Gem::GemNotInHomeException < Gem::Exception; end + +class Gem::GemNotInHomeException < Gem::Exception + attr_accessor :spec +end class Gem::DocumentError < Gem::Exception; end @@ -65,3 +68,17 @@ class Gem::RemoteSourceException < Gem::Exception; end class Gem::VerificationError < Gem::Exception; end +## +# Raised to indicate that a system exit should occur with the specified +# exit_code + +class Gem::SystemExitException < SystemExit + attr_accessor :exit_code + + def initialize(exit_code) + @exit_code = exit_code + + super "Exiting RubyGems with exit_code #{exit_code}" + end + +end diff --git a/lib/rubygems/format.rb b/lib/rubygems/format.rb index 378a93018c..7dc127d5f4 100644 --- a/lib/rubygems/format.rb +++ b/lib/rubygems/format.rb @@ -43,15 +43,12 @@ module Gem # check for old version gem if File.read(file_path, 20).include?("MD5SUM =") - #alert_warning "Gem #{file_path} is in old format." require 'rubygems/old_format' + format = OldFormat.from_file_by_path(file_path) else - begin - f = File.open(file_path, 'rb') - format = from_io(f, file_path, security_policy) - ensure - f.close unless f.closed? + open file_path, Gem.binary_mode do |io| + format = from_io io, file_path, security_policy end end @@ -65,15 +62,24 @@ module Gem # io:: [IO] Stream from which to read the gem # def self.from_io(io, gem_path="(io)", security_policy = nil) - format = self.new(gem_path) - Package.open_from_io(io, 'r', security_policy) do |pkg| + format = new gem_path + + Package.open io, 'r', security_policy do |pkg| format.spec = pkg.metadata format.file_entries = [] + pkg.each do |entry| - format.file_entries << [{"size" => entry.size, "mode" => entry.mode, - "path" => entry.full_name}, entry.read] + size = entry.header.size + mode = entry.header.mode + + format.file_entries << [{ + "size" => size, "mode" => mode, "path" => entry.full_name, + }, + entry.read + ] end end + format end diff --git a/lib/rubygems/indexer.rb b/lib/rubygems/indexer.rb index 272cee3fd3..5496e452cc 100644 --- a/lib/rubygems/indexer.rb +++ b/lib/rubygems/indexer.rb @@ -11,6 +11,7 @@ end ## # Top level class for building the gem repository index. + class Gem::Indexer include Gem::UserInteraction @@ -25,7 +26,9 @@ class Gem::Indexer attr_reader :directory + ## # Create an indexer that will index the gems in +directory+. + def initialize(directory) unless ''.respond_to? :to_xs then fail "Gem::Indexer requires that the XML Builder library be installed:" \ @@ -39,52 +42,60 @@ class Gem::Indexer @master_index = Gem::Indexer::MasterIndexBuilder.new "yaml", @directory @marshal_index = Gem::Indexer::MarshalIndexBuilder.new marshal_name, @directory - @quick_index = Gem::Indexer::QuickIndexBuilder.new "index", @directory + @quick_index = Gem::Indexer::QuickIndexBuilder.new 'index', @directory + + quick_dir = File.join @directory, 'quick' + @latest_index = Gem::Indexer::LatestIndexBuilder.new 'latest_index', quick_dir end + ## # Build the index. + def build_index @master_index.build do @quick_index.build do @marshal_index.build do - progress = ui.progress_reporter gem_file_list.size, + @latest_index.build do + progress = ui.progress_reporter gem_file_list.size, "Generating index for #{gem_file_list.size} gems in #{@dest_directory}", "Loaded all gems" - gem_file_list.each do |gemfile| - if File.size(gemfile.to_s) == 0 then - alert_warning "Skipping zero-length gem: #{gemfile}" - next - end - - begin - spec = Gem::Format.from_file_by_path(gemfile).spec - - unless gemfile =~ /\/#{Regexp.escape spec.original_name}.*\.gem\z/i then - alert_warning "Skipping misnamed gem: #{gemfile} => #{spec.full_name} (#{spec.original_name})" + gem_file_list.each do |gemfile| + if File.size(gemfile.to_s) == 0 then + alert_warning "Skipping zero-length gem: #{gemfile}" next end - abbreviate spec - sanitize spec + begin + spec = Gem::Format.from_file_by_path(gemfile).spec - @master_index.add spec - @quick_index.add spec - @marshal_index.add spec + unless gemfile =~ /\/#{Regexp.escape spec.original_name}.*\.gem\z/i then + alert_warning "Skipping misnamed gem: #{gemfile} => #{spec.full_name} (#{spec.original_name})" + next + end - progress.updated spec.original_name + abbreviate spec + sanitize spec - rescue SignalException => e - alert_error "Recieved signal, exiting" - raise - rescue Exception => e - alert_error "Unable to process #{gemfile}\n#{e.message} (#{e.class})\n\t#{e.backtrace.join "\n\t"}" - end - end + @master_index.add spec + @quick_index.add spec + @marshal_index.add spec + @latest_index.add spec + + progress.updated spec.original_name - progress.done + rescue SignalException => e + alert_error "Recieved signal, exiting" + raise + rescue Exception => e + alert_error "Unable to process #{gemfile}\n#{e.message} (#{e.class})\n\t#{e.backtrace.join "\n\t"}" + end + end + + progress.done - say "Generating master indexes (this may take a while)" + say "Generating master indexes (this may take a while)" + end end end end @@ -95,14 +106,15 @@ class Gem::Indexer say "Moving index into production dir #{@dest_directory}" if verbose - files = @master_index.files + @quick_index.files + @marshal_index.files + files = @master_index.files + @quick_index.files + @marshal_index.files + + @latest_index.files files.each do |file| - relative_name = file[/\A#{Regexp.escape @directory}.(.*)/, 1] - dest_name = File.join @dest_directory, relative_name + src_name = File.join @directory, file + dst_name = File.join @dest_directory, file - FileUtils.rm_rf dest_name, :verbose => verbose - FileUtils.mv file, @dest_directory, :verbose => verbose + FileUtils.rm_rf dst_name, :verbose => verbose + FileUtils.mv src_name, @dest_directory, :verbose => verbose end end @@ -160,4 +172,5 @@ require 'rubygems/indexer/abstract_index_builder' require 'rubygems/indexer/master_index_builder' require 'rubygems/indexer/quick_index_builder' require 'rubygems/indexer/marshal_index_builder' +require 'rubygems/indexer/latest_index_builder' diff --git a/lib/rubygems/indexer/abstract_index_builder.rb b/lib/rubygems/indexer/abstract_index_builder.rb index f25f21707b..5815dcda87 100644 --- a/lib/rubygems/indexer/abstract_index_builder.rb +++ b/lib/rubygems/indexer/abstract_index_builder.rb @@ -22,16 +22,18 @@ class Gem::Indexer::AbstractIndexBuilder @files = [] end + ## # Build a Gem index. Yields to block to handle the details of the # actual building. Calls +begin_index+, +end_index+ and +cleanup+ at # appropriate times to customize basic operations. + def build FileUtils.mkdir_p @directory unless File.exist? @directory raise "not a directory: #{@directory}" unless File.directory? @directory file_path = File.join @directory, @filename - @files << file_path + @files << @filename File.open file_path, "wb" do |file| @file = file @@ -39,14 +41,20 @@ class Gem::Indexer::AbstractIndexBuilder yield end_index end + cleanup ensure @file = nil end + ## # Compress the given file. + def compress(filename, ext="rz") - zipped = zip(File.open(filename, 'rb'){ |fp| fp.read }) + data = open filename, 'rb' do |fp| fp.read end + + zipped = zip data + File.open "#{filename}.#{ext}", "wb" do |file| file.write zipped end diff --git a/lib/rubygems/indexer/latest_index_builder.rb b/lib/rubygems/indexer/latest_index_builder.rb new file mode 100644 index 0000000000..a5798580a6 --- /dev/null +++ b/lib/rubygems/indexer/latest_index_builder.rb @@ -0,0 +1,35 @@ +require 'rubygems/indexer' + +## +# Construct the latest Gem index file. + +class Gem::Indexer::LatestIndexBuilder < Gem::Indexer::AbstractIndexBuilder + + def start_index + super + + @index = Gem::SourceIndex.new + end + + def end_index + super + + latest = @index.latest_specs.sort.map { |spec| spec.original_name } + + @file.write latest.join("\n") + end + + def cleanup + super + + compress @file.path + + @files.delete 'latest_index' # HACK installed via QuickIndexBuilder :/ + end + + def add(spec) + @index.add_spec(spec) + end + +end + diff --git a/lib/rubygems/indexer/master_index_builder.rb b/lib/rubygems/indexer/master_index_builder.rb index dbe02370a9..669ea5a1df 100644 --- a/lib/rubygems/indexer/master_index_builder.rb +++ b/lib/rubygems/indexer/master_index_builder.rb @@ -1,6 +1,8 @@ require 'rubygems/indexer' +## # Construct the master Gem index file. + class Gem::Indexer::MasterIndexBuilder < Gem::Indexer::AbstractIndexBuilder def start_index @@ -10,6 +12,7 @@ class Gem::Indexer::MasterIndexBuilder < Gem::Indexer::AbstractIndexBuilder def end_index super + @file.puts "--- !ruby/object:#{@index.class}" @file.puts "gems:" @@ -28,11 +31,9 @@ class Gem::Indexer::MasterIndexBuilder < Gem::Indexer::AbstractIndexBuilder index_file_name = File.join @directory, @filename compress index_file_name, "Z" - compressed_file_name = "#{index_file_name}.Z" - - paranoid index_file_name, compressed_file_name + paranoid index_file_name, "#{index_file_name}.Z" - @files << compressed_file_name + @files << "#{@filename}.Z" end def add(spec) @@ -41,12 +42,12 @@ class Gem::Indexer::MasterIndexBuilder < Gem::Indexer::AbstractIndexBuilder private - def paranoid(fn, compressed_fn) - data = File.open(fn, 'rb') do |fp| fp.read end - compressed_data = File.open(compressed_fn, 'rb') do |fp| fp.read end + def paranoid(path, compressed_path) + data = Gem.read_binary path + compressed_data = Gem.read_binary compressed_path if data != unzip(compressed_data) then - fail "Compressed file #{compressed_fn} does not match uncompressed file #{fn}" + raise "Compressed file #{compressed_path} does not match uncompressed file #{path}" end end diff --git a/lib/rubygems/indexer/quick_index_builder.rb b/lib/rubygems/indexer/quick_index_builder.rb index 23c7ca696b..dc36179dc5 100644 --- a/lib/rubygems/indexer/quick_index_builder.rb +++ b/lib/rubygems/indexer/quick_index_builder.rb @@ -1,7 +1,9 @@ require 'rubygems/indexer' +## # Construct a quick index file and all of the individual specs to support # incremental loading. + class Gem::Indexer::QuickIndexBuilder < Gem::Indexer::AbstractIndexBuilder def initialize(filename, directory) @@ -13,12 +15,12 @@ class Gem::Indexer::QuickIndexBuilder < Gem::Indexer::AbstractIndexBuilder def cleanup super - quick_index_file = File.join(@directory, @filename) + quick_index_file = File.join @directory, @filename compress quick_index_file # the complete quick index is in a directory, so move it as a whole - @files.delete quick_index_file - @files << @directory + @files.delete 'index' + @files << 'quick' end def add(spec) diff --git a/lib/rubygems/install_update_options.rb b/lib/rubygems/install_update_options.rb index af6be423f6..58807be62a 100644 --- a/lib/rubygems/install_update_options.rb +++ b/lib/rubygems/install_update_options.rb @@ -25,6 +25,12 @@ module Gem::InstallUpdateOptions options[:install_dir] = File.expand_path(value) end + add_option(:"Install/Update", '-n', '--bindir DIR', + 'Directory where binary files are', + 'located') do |value, options| + options[:bin_dir] = File.expand_path(value) + end + add_option(:"Install/Update", '-d', '--[no-]rdoc', 'Generate RDoc documentation for the gem on', 'install') do |value, options| diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb index 552a803c12..9dbbca8d08 100644 --- a/lib/rubygems/installer.rb +++ b/lib/rubygems/installer.rb @@ -63,7 +63,8 @@ class Gem::Installer :force => false, :install_dir => Gem.dir, :exec_format => false, - :env_shebang => false + :env_shebang => false, + :bin_dir => nil }.merge options @env_shebang = options[:env_shebang] @@ -74,6 +75,7 @@ class Gem::Installer @format_executable = options[:format_executable] @security_policy = options[:security_policy] @wrappers = options[:wrappers] + @bin_dir = options[:bin_dir] begin @format = Gem::Format.from_file_by_path @gem, @security_policy @@ -104,7 +106,7 @@ class Gem::Installer unless @force then if rrv = @spec.required_ruby_version then - unless rrv.satisfied_by? Gem::Version.new(RUBY_VERSION) then + unless rrv.satisfied_by? Gem.ruby_version then raise Gem::InstallError, "#{@spec.name} requires Ruby version #{rrv}" end end @@ -225,7 +227,7 @@ class Gem::Installer # If the user has asked for the gem to be installed in a directory that is # the system gem directory, then use the system bin directory, else create # (or use) a new bin dir under the gem_home. - bindir = Gem.bindir @gem_home + bindir = @bin_dir ? @bin_dir : (Gem.bindir @gem_home) Dir.mkdir bindir unless File.exist? bindir raise Gem::FilePermissionError.new(bindir) unless File.writable? bindir @@ -303,7 +305,7 @@ class Gem::Installer # necessary. def shebang(bin_file_name) if @env_shebang then - "#!/usr/bin/env ruby" + "#!/usr/bin/env " + Gem::ConfigMap[:ruby_install_name] else path = File.join @gem_dir, @spec.bindir, bin_file_name @@ -352,10 +354,10 @@ TEXT <<-TEXT @ECHO OFF IF NOT "%~f0" == "~f0" GOTO :WinNT -@"#{Gem.ruby}" "#{File.join(bindir, bin_file_name)}" %1 %2 %3 %4 %5 %6 %7 %8 %9 +@"#{File.basename(Gem.ruby)}" "#{File.join(bindir, bin_file_name)}" %1 %2 %3 %4 %5 %6 %7 %8 %9 GOTO :EOF :WinNT -"%~dp0ruby.exe" "%~dpn0" %* +@"#{File.basename(Gem.ruby)}" "%~dpn0" %* TEXT end diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb index f15e4feecb..9cb393b0c7 100644 --- a/lib/rubygems/package.rb +++ b/lib/rubygems/package.rb @@ -45,768 +45,15 @@ module Gem::Package class TooLongFileName < Error; end class FormatError < Error; end - module FSyncDir - private - def fsync_dir(dirname) - # make sure this hits the disc - begin - dir = open(dirname, "r") - dir.fsync - rescue # ignore IOError if it's an unpatched (old) Ruby - ensure - dir.close if dir rescue nil - end - end - end - - class TarHeader - FIELDS = [:name, :mode, :uid, :gid, :size, :mtime, :checksum, :typeflag, - :linkname, :magic, :version, :uname, :gname, :devmajor, - :devminor, :prefix] - FIELDS.each {|x| attr_reader x} - - def self.new_from_stream(stream) - data = stream.read(512) - fields = data.unpack("A100" + # record name - "A8A8A8" + # mode, uid, gid - "A12A12" + # size, mtime - "A8A" + # checksum, typeflag - "A100" + # linkname - "A6A2" + # magic, version - "A32" + # uname - "A32" + # gname - "A8A8" + # devmajor, devminor - "A155") # prefix - name = fields.shift - mode = fields.shift.oct - uid = fields.shift.oct - gid = fields.shift.oct - size = fields.shift.oct - mtime = fields.shift.oct - checksum = fields.shift.oct - typeflag = fields.shift - linkname = fields.shift - magic = fields.shift - version = fields.shift.oct - uname = fields.shift - gname = fields.shift - devmajor = fields.shift.oct - devminor = fields.shift.oct - prefix = fields.shift - - empty = (data == "\0" * 512) - - new(:name=>name, :mode=>mode, :uid=>uid, :gid=>gid, :size=>size, - :mtime=>mtime, :checksum=>checksum, :typeflag=>typeflag, - :magic=>magic, :version=>version, :uname=>uname, :gname=>gname, - :devmajor=>devmajor, :devminor=>devminor, :prefix=>prefix, - :empty => empty ) - end - - def initialize(vals) - unless vals[:name] && vals[:size] && vals[:prefix] && vals[:mode] - raise ArgumentError, ":name, :size, :prefix and :mode required" - end - vals[:uid] ||= 0 - vals[:gid] ||= 0 - vals[:mtime] ||= 0 - vals[:checksum] ||= "" - vals[:typeflag] ||= "0" - vals[:magic] ||= "ustar" - vals[:version] ||= "00" - vals[:uname] ||= "wheel" - vals[:gname] ||= "wheel" - vals[:devmajor] ||= 0 - vals[:devminor] ||= 0 - FIELDS.each {|x| instance_variable_set "@#{x.to_s}", vals[x]} - @empty = vals[:empty] - end - - def empty? - @empty - end - - def to_s - update_checksum - header(checksum) - end - - def update_checksum - h = header(" " * 8) - @checksum = oct(calculate_checksum(h), 6) - end - - private - def oct(num, len) - "%0#{len}o" % num - end - - def calculate_checksum(hdr) - #hdr.split('').map { |c| c[0] }.inject { |a, b| a + b } # HACK rubinius - hdr.unpack("C*").inject{|a,b| a+b} - end - - def header(chksum) - # struct tarfile_entry_posix { - # char name[100]; # ASCII + (Z unless filled) - # char mode[8]; # 0 padded, octal, null - # char uid[8]; # ditto - # char gid[8]; # ditto - # char size[12]; # 0 padded, octal, null - # char mtime[12]; # 0 padded, octal, null - # char checksum[8]; # 0 padded, octal, null, space - # char typeflag[1]; # file: "0" dir: "5" - # char linkname[100]; # ASCII + (Z unless filled) - # char magic[6]; # "ustar\0" - # char version[2]; # "00" - # char uname[32]; # ASCIIZ - # char gname[32]; # ASCIIZ - # char devmajor[8]; # 0 padded, octal, null - # char devminor[8]; # o padded, octal, null - # char prefix[155]; # ASCII + (Z unless filled) - # }; - arr = [name, oct(mode, 7), oct(uid, 7), oct(gid, 7), oct(size, 11), - oct(mtime, 11), chksum, " ", typeflag, linkname, magic, version, - uname, gname, oct(devmajor, 7), oct(devminor, 7), prefix] - str = arr.pack("a100a8a8a8a12a12" + # name, mode, uid, gid, size, mtime - "a7aaa100a6a2" + # chksum, typeflag, linkname, magic, version - "a32a32a8a8a155") # uname, gname, devmajor, devminor, prefix - str + "\0" * ((512 - str.size) % 512) - end - end - - class TarWriter - class FileOverflow < StandardError; end - class BlockNeeded < StandardError; end - - class BoundedStream - attr_reader :limit, :written - def initialize(io, limit) - @io = io - @limit = limit - @written = 0 - end - - def write(data) - if data.size + @written > @limit - raise FileOverflow, - "You tried to feed more data than fits in the file." - end - @io.write data - @written += data.size - data.size - end - end - - class RestrictedStream - def initialize(anIO) - @io = anIO - end - - def write(data) - @io.write data - end - end - - def self.new(anIO) - writer = super(anIO) - return writer unless block_given? - begin - yield writer - ensure - writer.close - end - nil - end - - def initialize(anIO) - @io = anIO - @closed = false - end - - def add_file_simple(name, mode, size) - raise BlockNeeded unless block_given? - raise ClosedIO if @closed - name, prefix = split_name(name) - header = TarHeader.new(:name => name, :mode => mode, - :size => size, :prefix => prefix).to_s - @io.write header - os = BoundedStream.new(@io, size) - yield os - #FIXME: what if an exception is raised in the block? - min_padding = size - os.written - @io.write("\0" * min_padding) - remainder = (512 - (size % 512)) % 512 - @io.write("\0" * remainder) - end - - def add_file(name, mode) - raise BlockNeeded unless block_given? - raise ClosedIO if @closed - raise NonSeekableIO unless @io.respond_to? :pos= - name, prefix = split_name(name) - init_pos = @io.pos - @io.write "\0" * 512 # placeholder for the header - yield RestrictedStream.new(@io) - #FIXME: what if an exception is raised in the block? - #FIXME: what if an exception is raised in the block? - size = @io.pos - init_pos - 512 - remainder = (512 - (size % 512)) % 512 - @io.write("\0" * remainder) - final_pos = @io.pos - @io.pos = init_pos - header = TarHeader.new(:name => name, :mode => mode, - :size => size, :prefix => prefix).to_s - @io.write header - @io.pos = final_pos - end - - def mkdir(name, mode) - raise ClosedIO if @closed - name, prefix = split_name(name) - header = TarHeader.new(:name => name, :mode => mode, :typeflag => "5", - :size => 0, :prefix => prefix).to_s - @io.write header - nil - end - - def flush - raise ClosedIO if @closed - @io.flush if @io.respond_to? :flush - end - - def close - #raise ClosedIO if @closed - return if @closed - @io.write "\0" * 1024 - @closed = true - end - - private - def split_name name - raise TooLongFileName if name.size > 256 - if name.size <= 100 - prefix = "" - else - parts = name.split(/\//) - newname = parts.pop - nxt = "" - loop do - nxt = parts.pop - break if newname.size + 1 + nxt.size > 100 - newname = nxt + "/" + newname - end - prefix = (parts + [nxt]).join "/" - name = newname - raise TooLongFileName if name.size > 100 || prefix.size > 155 - end - return name, prefix - end - end - - class TarReader - - include Gem::Package - - class UnexpectedEOF < StandardError; end - - module InvalidEntry - def read(len=nil); raise ClosedIO; end - def getc; raise ClosedIO; end - def rewind; raise ClosedIO; end - end - - class Entry - TarHeader::FIELDS.each{|x| attr_reader x} - - def initialize(header, anIO) - @io = anIO - @name = header.name - @mode = header.mode - @uid = header.uid - @gid = header.gid - @size = header.size - @mtime = header.mtime - @checksum = header.checksum - @typeflag = header.typeflag - @linkname = header.linkname - @magic = header.magic - @version = header.version - @uname = header.uname - @gname = header.gname - @devmajor = header.devmajor - @devminor = header.devminor - @prefix = header.prefix - @read = 0 - @orig_pos = @io.pos - end - - def read(len = nil) - return nil if @read >= @size - len ||= @size - @read - max_read = [len, @size - @read].min - ret = @io.read(max_read) - @read += ret.size - ret - end - - def getc - return nil if @read >= @size - ret = @io.getc - @read += 1 if ret - ret - end - - def is_directory? - @typeflag == "5" - end - - def is_file? - @typeflag == "0" - end - - def eof? - @read >= @size - end - - def pos - @read - end - - def rewind - raise NonSeekableIO unless @io.respond_to? :pos= - @io.pos = @orig_pos - @read = 0 - end - - alias_method :is_directory, :is_directory? - alias_method :is_file, :is_file? - - def bytes_read - @read - end - - def full_name - if @prefix != "" - File.join(@prefix, @name) - else - @name - end - end - - def close - invalidate - end - - private - def invalidate - extend InvalidEntry - end - end - - def self.new(anIO) - reader = super(anIO) - return reader unless block_given? - begin - yield reader - ensure - reader.close - end - nil - end - - def initialize(anIO) - @io = anIO - @init_pos = anIO.pos - end - - def each(&block) - each_entry(&block) - end - - # do not call this during a #each or #each_entry iteration - def rewind - if @init_pos == 0 - raise NonSeekableIO unless @io.respond_to? :rewind - @io.rewind - else - raise NonSeekableIO unless @io.respond_to? :pos= - @io.pos = @init_pos - end - end - - def each_entry - loop do - return if @io.eof? - header = TarHeader.new_from_stream(@io) - return if header.empty? - entry = Entry.new header, @io - size = entry.size - yield entry - skip = (512 - (size % 512)) % 512 - if @io.respond_to? :seek - # avoid reading... - @io.seek(size - entry.bytes_read, IO::SEEK_CUR) - else - pending = size - entry.bytes_read - while pending > 0 - bread = @io.read([pending, 4096].min).size - raise UnexpectedEOF if @io.eof? - pending -= bread - end - end - @io.read(skip) # discard trailing zeros - # make sure nobody can use #read, #getc or #rewind anymore - entry.close - end - end - - def close - end - - end - - class TarInput - - include FSyncDir - include Enumerable - - attr_reader :metadata - - class << self; private :new end - - def initialize(io, security_policy = nil) - @io = io - @tarreader = TarReader.new(@io) - has_meta = false - data_sig, meta_sig, data_dgst, meta_dgst = nil, nil, nil, nil - dgst_algo = security_policy ? Gem::Security::OPT[:dgst_algo] : nil - - @tarreader.each do |entry| - case entry.full_name - when "metadata" - @metadata = load_gemspec(entry.read) - has_meta = true - break - when "metadata.gz" - begin - # if we have a security_policy, then pre-read the metadata file - # and calculate it's digest - sio = nil - if security_policy - Gem.ensure_ssl_available - sio = StringIO.new(entry.read) - meta_dgst = dgst_algo.digest(sio.string) - sio.rewind - end - - gzis = Zlib::GzipReader.new(sio || entry) - # YAML wants an instance of IO - @metadata = load_gemspec(gzis) - has_meta = true - ensure - gzis.close unless gzis.nil? - end - when 'metadata.gz.sig' - meta_sig = entry.read - when 'data.tar.gz.sig' - data_sig = entry.read - when 'data.tar.gz' - if security_policy - Gem.ensure_ssl_available - data_dgst = dgst_algo.digest(entry.read) - end - end - end - - if security_policy then - Gem.ensure_ssl_available - - # map trust policy from string to actual class (or a serialized YAML - # file, if that exists) - if String === security_policy then - if Gem::Security::Policy.key? security_policy then - # load one of the pre-defined security policies - security_policy = Gem::Security::Policy[security_policy] - elsif File.exist? security_policy then - # FIXME: this doesn't work yet - security_policy = YAML.load File.read(security_policy) - else - raise Gem::Exception, "Unknown trust policy '#{security_policy}'" - end - end - - if data_sig && data_dgst && meta_sig && meta_dgst then - # the user has a trust policy, and we have a signed gem - # file, so use the trust policy to verify the gem signature - - begin - security_policy.verify_gem(data_sig, data_dgst, @metadata.cert_chain) - rescue Exception => e - raise "Couldn't verify data signature: #{e}" - end - - begin - security_policy.verify_gem(meta_sig, meta_dgst, @metadata.cert_chain) - rescue Exception => e - raise "Couldn't verify metadata signature: #{e}" - end - elsif security_policy.only_signed - raise Gem::Exception, "Unsigned gem" - else - # FIXME: should display warning here (trust policy, but - # either unsigned or badly signed gem file) - end - end - - @tarreader.rewind - @fileops = Gem::FileOperations.new - raise FormatError, "No metadata found!" unless has_meta - end - - # Attempt to YAML-load a gemspec from the given _io_ parameter. Return - # nil if it fails. - def load_gemspec(io) - Gem::Specification.from_yaml(io) - rescue Gem::Exception - nil - end - - def self.open(filename, security_policy = nil, &block) - open_from_io(File.open(filename, "rb"), security_policy, &block) - end - - def self.open_from_io(io, security_policy = nil, &block) - raise "Want a block" unless block_given? - begin - is = new(io, security_policy) - yield is - ensure - is.close if is - end - end - - def each(&block) - @tarreader.each do |entry| - next unless entry.full_name == "data.tar.gz" - is = zipped_stream(entry) - begin - TarReader.new(is) do |inner| - inner.each(&block) - end - ensure - is.close if is - end - end - @tarreader.rewind - end - - # Return an IO stream for the zipped entry. - # - # NOTE: Originally this method used two approaches, Return a GZipReader - # directly, or read the GZipReader into a string and return a StringIO on - # the string. The string IO approach was used for versions of ZLib before - # 1.2.1 to avoid buffer errors on windows machines. Then we found that - # errors happened with 1.2.1 as well, so we changed the condition. Then - # we discovered errors occurred with versions as late as 1.2.3. At this - # point (after some benchmarking to show we weren't seriously crippling - # the unpacking speed) we threw our hands in the air and declared that - # this method would use the String IO approach on all platforms at all - # times. And that's the way it is. - def zipped_stream(entry) - if defined? Rubinius then - zis = Zlib::GzipReader.new entry - dis = zis.read - is = StringIO.new(dis) - else - # This is Jamis Buck's ZLib workaround for some unknown issue - entry.read(10) # skip the gzip header - zis = Zlib::Inflate.new(-Zlib::MAX_WBITS) - is = StringIO.new(zis.inflate(entry.read)) - end - ensure - zis.finish if zis - end - - def extract_entry(destdir, entry, expected_md5sum = nil) - if entry.is_directory? - dest = File.join(destdir, entry.full_name) - if file_class.dir? dest - @fileops.chmod entry.mode, dest, :verbose=>false - else - @fileops.mkdir_p(dest, :mode => entry.mode, :verbose=>false) - end - fsync_dir dest - fsync_dir File.join(dest, "..") - return - end - # it's a file - md5 = Digest::MD5.new if expected_md5sum - destdir = File.join(destdir, File.dirname(entry.full_name)) - @fileops.mkdir_p(destdir, :mode => 0755, :verbose=>false) - destfile = File.join(destdir, File.basename(entry.full_name)) - @fileops.chmod(0600, destfile, :verbose=>false) rescue nil # Errno::ENOENT - file_class.open(destfile, "wb", entry.mode) do |os| - loop do - data = entry.read(4096) - break unless data - md5 << data if expected_md5sum - os.write(data) - end - os.fsync - end - @fileops.chmod(entry.mode, destfile, :verbose=>false) - fsync_dir File.dirname(destfile) - fsync_dir File.join(File.dirname(destfile), "..") - if expected_md5sum && expected_md5sum != md5.hexdigest - raise BadCheckSum - end - end - - def close - @io.close - @tarreader.close - end - - private - - def file_class - File - end - end - - class TarOutput - - class << self; private :new end - - def initialize(io) - @io = io - @external = TarWriter.new @io - end - - def external_handle - @external - end - - def self.open(filename, signer = nil, &block) - io = File.open(filename, "wb") - open_from_io(io, signer, &block) - nil - end - - def self.open_from_io(io, signer = nil, &block) - outputter = new(io) - metadata = nil - set_meta = lambda{|x| metadata = x} - raise "Want a block" unless block_given? - begin - data_sig, meta_sig = nil, nil - - outputter.external_handle.add_file("data.tar.gz", 0644) do |inner| - begin - sio = signer ? StringIO.new : nil - os = Zlib::GzipWriter.new(sio || inner) - - TarWriter.new(os) do |inner_tar_stream| - klass = class << inner_tar_stream; self end - klass.send(:define_method, :metadata=, &set_meta) - block.call inner_tar_stream - end - ensure - os.flush - os.finish - #os.close - - # if we have a signing key, then sign the data - # digest and return the signature - data_sig = nil - if signer - dgst_algo = Gem::Security::OPT[:dgst_algo] - dig = dgst_algo.digest(sio.string) - data_sig = signer.sign(dig) - inner.write(sio.string) - end - end - end - - # if we have a data signature, then write it to the gem too - if data_sig - sig_file = 'data.tar.gz.sig' - outputter.external_handle.add_file(sig_file, 0644) do |os| - os.write(data_sig) - end - end - - outputter.external_handle.add_file("metadata.gz", 0644) do |os| - begin - sio = signer ? StringIO.new : nil - gzos = Zlib::GzipWriter.new(sio || os) - gzos.write metadata - ensure - gzos.flush - gzos.finish - - # if we have a signing key, then sign the metadata - # digest and return the signature - if signer - dgst_algo = Gem::Security::OPT[:dgst_algo] - dig = dgst_algo.digest(sio.string) - meta_sig = signer.sign(dig) - os.write(sio.string) - end - end - end - - # if we have a metadata signature, then write to the gem as - # well - if meta_sig - sig_file = 'metadata.gz.sig' - outputter.external_handle.add_file(sig_file, 0644) do |os| - os.write(meta_sig) - end - end - - ensure - outputter.close - end - nil - end - - def close - @external.close - @io.close - end - - end - - #FIXME: refactor the following 2 methods - - def self.open(dest, mode = "r", signer = nil, &block) - raise "Block needed" unless block_given? - - case mode - when "r" - security_policy = signer - TarInput.open(dest, security_policy, &block) - when "w" - TarOutput.open(dest, signer, &block) - else - raise "Unknown Package open mode" - end - end - - def self.open_from_io(io, mode = "r", signer = nil, &block) - raise "Block needed" unless block_given? - - case mode - when "r" - security_policy = signer - TarInput.open_from_io(io, security_policy, &block) - when "w" - TarOutput.open_from_io(io, signer, &block) - else - raise "Unknown Package open mode" - end + def self.open(io, mode = "r", signer = nil, &block) + tar_type = case mode + when 'r' then TarInput + when 'w' then TarOutput + else + raise "Unknown Package open mode" + end + + tar_type.open(io, signer, &block) end def self.pack(src, destname, signer = nil) @@ -836,19 +83,13 @@ module Gem::Package end end - class << self - def file_class - File - end - - def dir_class - Dir - end - - def find_class # HACK kill me - Find - end - end - end +require 'rubygems/package/f_sync_dir' +require 'rubygems/package/tar_header' +require 'rubygems/package/tar_input' +require 'rubygems/package/tar_output' +require 'rubygems/package/tar_reader' +require 'rubygems/package/tar_reader/entry' +require 'rubygems/package/tar_writer' + diff --git a/lib/rubygems/package/f_sync_dir.rb b/lib/rubygems/package/f_sync_dir.rb new file mode 100644 index 0000000000..3e2e4a59a8 --- /dev/null +++ b/lib/rubygems/package/f_sync_dir.rb @@ -0,0 +1,24 @@ +#++ +# Copyright (C) 2004 Mauricio Julio Fern�ndez Pradier +# See LICENSE.txt for additional licensing information. +#-- + +require 'rubygems/package' + +module Gem::Package::FSyncDir + + private + + ## + # make sure this hits the disc + + def fsync_dir(dirname) + dir = open dirname, 'r' + dir.fsync + rescue # ignore IOError if it's an unpatched (old) Ruby + ensure + dir.close if dir rescue nil + end + +end + diff --git a/lib/rubygems/package/tar_header.rb b/lib/rubygems/package/tar_header.rb new file mode 100644 index 0000000000..c194cc0530 --- /dev/null +++ b/lib/rubygems/package/tar_header.rb @@ -0,0 +1,245 @@ +#++ +# Copyright (C) 2004 Mauricio Julio Fern�ndez Pradier +# See LICENSE.txt for additional licensing information. +#-- + +require 'rubygems/package' + +## +#-- +# struct tarfile_entry_posix { +# char name[100]; # ASCII + (Z unless filled) +# char mode[8]; # 0 padded, octal, null +# char uid[8]; # ditto +# char gid[8]; # ditto +# char size[12]; # 0 padded, octal, null +# char mtime[12]; # 0 padded, octal, null +# char checksum[8]; # 0 padded, octal, null, space +# char typeflag[1]; # file: "0" dir: "5" +# char linkname[100]; # ASCII + (Z unless filled) +# char magic[6]; # "ustar\0" +# char version[2]; # "00" +# char uname[32]; # ASCIIZ +# char gname[32]; # ASCIIZ +# char devmajor[8]; # 0 padded, octal, null +# char devminor[8]; # o padded, octal, null +# char prefix[155]; # ASCII + (Z unless filled) +# }; +#++ + +class Gem::Package::TarHeader + + FIELDS = [ + :checksum, + :devmajor, + :devminor, + :gid, + :gname, + :linkname, + :magic, + :mode, + :mtime, + :name, + :prefix, + :size, + :typeflag, + :uid, + :uname, + :version, + ] + + PACK_FORMAT = 'a100' + # name + 'a8' + # mode + 'a8' + # uid + 'a8' + # gid + 'a12' + # size + 'a12' + # mtime + 'a7a' + # chksum + 'a' + # typeflag + 'a100' + # linkname + 'a6' + # magic + 'a2' + # version + 'a32' + # uname + 'a32' + # gname + 'a8' + # devmajor + 'a8' + # devminor + 'a155' # prefix + + UNPACK_FORMAT = 'A100' + # name + 'A8' + # mode + 'A8' + # uid + 'A8' + # gid + 'A12' + # size + 'A12' + # mtime + 'A8' + # checksum + 'A' + # typeflag + 'A100' + # linkname + 'A6' + # magic + 'A2' + # version + 'A32' + # uname + 'A32' + # gname + 'A8' + # devmajor + 'A8' + # devminor + 'A155' # prefix + + attr_reader(*FIELDS) + + def self.from(stream) + header = stream.read 512 + empty = (header == "\0" * 512) + + fields = header.unpack UNPACK_FORMAT + + name = fields.shift + mode = fields.shift.oct + uid = fields.shift.oct + gid = fields.shift.oct + size = fields.shift.oct + mtime = fields.shift.oct + checksum = fields.shift.oct + typeflag = fields.shift + linkname = fields.shift + magic = fields.shift + version = fields.shift.oct + uname = fields.shift + gname = fields.shift + devmajor = fields.shift.oct + devminor = fields.shift.oct + prefix = fields.shift + + new :name => name, + :mode => mode, + :uid => uid, + :gid => gid, + :size => size, + :mtime => mtime, + :checksum => checksum, + :typeflag => typeflag, + :linkname => linkname, + :magic => magic, + :version => version, + :uname => uname, + :gname => gname, + :devmajor => devmajor, + :devminor => devminor, + :prefix => prefix, + + :empty => empty + + # HACK unfactor for Rubinius + #new :name => fields.shift, + # :mode => fields.shift.oct, + # :uid => fields.shift.oct, + # :gid => fields.shift.oct, + # :size => fields.shift.oct, + # :mtime => fields.shift.oct, + # :checksum => fields.shift.oct, + # :typeflag => fields.shift, + # :linkname => fields.shift, + # :magic => fields.shift, + # :version => fields.shift.oct, + # :uname => fields.shift, + # :gname => fields.shift, + # :devmajor => fields.shift.oct, + # :devminor => fields.shift.oct, + # :prefix => fields.shift, + + # :empty => empty + end + + def initialize(vals) + unless vals[:name] && vals[:size] && vals[:prefix] && vals[:mode] then + raise ArgumentError, ":name, :size, :prefix and :mode required" + end + + vals[:uid] ||= 0 + vals[:gid] ||= 0 + vals[:mtime] ||= 0 + vals[:checksum] ||= "" + vals[:typeflag] ||= "0" + vals[:magic] ||= "ustar" + vals[:version] ||= "00" + vals[:uname] ||= "wheel" + vals[:gname] ||= "wheel" + vals[:devmajor] ||= 0 + vals[:devminor] ||= 0 + + FIELDS.each do |name| + instance_variable_set "@#{name}", vals[name] + end + + @empty = vals[:empty] + end + + def empty? + @empty + end + + def ==(other) + self.class === other and + @checksum == other.checksum and + @devmajor == other.devmajor and + @devminor == other.devminor and + @gid == other.gid and + @gname == other.gname and + @linkname == other.linkname and + @magic == other.magic and + @mode == other.mode and + @mtime == other.mtime and + @name == other.name and + @prefix == other.prefix and + @size == other.size and + @typeflag == other.typeflag and + @uid == other.uid and + @uname == other.uname and + @version == other.version + end + + def to_s + update_checksum + header + end + + def update_checksum + header = header " " * 8 + @checksum = oct calculate_checksum(header), 6 + end + + private + + def calculate_checksum(header) + header.unpack("C*").inject { |a, b| a + b } + end + + def header(checksum = @checksum) + header = [ + name, + oct(mode, 7), + oct(uid, 7), + oct(gid, 7), + oct(size, 11), + oct(mtime, 11), + checksum, + " ", + typeflag, + linkname, + magic, + oct(version, 2), + uname, + gname, + oct(devmajor, 7), + oct(devminor, 7), + prefix + ] + + header = header.pack PACK_FORMAT + + header << ("\0" * ((512 - header.size) % 512)) + end + + def oct(num, len) + "%0#{len}o" % num + end + +end + diff --git a/lib/rubygems/package/tar_input.rb b/lib/rubygems/package/tar_input.rb new file mode 100644 index 0000000000..2ed3d6b772 --- /dev/null +++ b/lib/rubygems/package/tar_input.rb @@ -0,0 +1,219 @@ +#++ +# Copyright (C) 2004 Mauricio Julio Fern�ndez Pradier +# See LICENSE.txt for additional licensing information. +#-- + +require 'rubygems/package' + +class Gem::Package::TarInput + + include Gem::Package::FSyncDir + include Enumerable + + attr_reader :metadata + + private_class_method :new + + def self.open(io, security_policy = nil, &block) + is = new io, security_policy + + yield is + ensure + is.close if is + end + + def initialize(io, security_policy = nil) + @io = io + @tarreader = Gem::Package::TarReader.new @io + has_meta = false + + data_sig, meta_sig, data_dgst, meta_dgst = nil, nil, nil, nil + dgst_algo = security_policy ? Gem::Security::OPT[:dgst_algo] : nil + + @tarreader.each do |entry| + case entry.full_name + when "metadata" + @metadata = load_gemspec entry.read + has_meta = true + when "metadata.gz" + begin + # if we have a security_policy, then pre-read the metadata file + # and calculate it's digest + sio = nil + if security_policy + Gem.ensure_ssl_available + sio = StringIO.new(entry.read) + meta_dgst = dgst_algo.digest(sio.string) + sio.rewind + end + + gzis = Zlib::GzipReader.new(sio || entry) + # YAML wants an instance of IO + @metadata = load_gemspec(gzis) + has_meta = true + ensure + gzis.close unless gzis.nil? + end + when 'metadata.gz.sig' + meta_sig = entry.read + when 'data.tar.gz.sig' + data_sig = entry.read + when 'data.tar.gz' + if security_policy + Gem.ensure_ssl_available + data_dgst = dgst_algo.digest(entry.read) + end + end + end + + if security_policy then + Gem.ensure_ssl_available + + # map trust policy from string to actual class (or a serialized YAML + # file, if that exists) + if String === security_policy then + if Gem::Security::Policy.key? security_policy then + # load one of the pre-defined security policies + security_policy = Gem::Security::Policy[security_policy] + elsif File.exist? security_policy then + # FIXME: this doesn't work yet + security_policy = YAML.load File.read(security_policy) + else + raise Gem::Exception, "Unknown trust policy '#{security_policy}'" + end + end + + if data_sig && data_dgst && meta_sig && meta_dgst then + # the user has a trust policy, and we have a signed gem + # file, so use the trust policy to verify the gem signature + + begin + security_policy.verify_gem(data_sig, data_dgst, @metadata.cert_chain) + rescue Exception => e + raise "Couldn't verify data signature: #{e}" + end + + begin + security_policy.verify_gem(meta_sig, meta_dgst, @metadata.cert_chain) + rescue Exception => e + raise "Couldn't verify metadata signature: #{e}" + end + elsif security_policy.only_signed + raise Gem::Exception, "Unsigned gem" + else + # FIXME: should display warning here (trust policy, but + # either unsigned or badly signed gem file) + end + end + + @tarreader.rewind + @fileops = Gem::FileOperations.new + + raise Gem::Package::FormatError, "No metadata found!" unless has_meta + end + + def close + @io.close + @tarreader.close + end + + def each(&block) + @tarreader.each do |entry| + next unless entry.full_name == "data.tar.gz" + is = zipped_stream entry + + begin + Gem::Package::TarReader.new is do |inner| + inner.each(&block) + end + ensure + is.close if is + end + end + + @tarreader.rewind + end + + def extract_entry(destdir, entry, expected_md5sum = nil) + if entry.directory? then + dest = File.join(destdir, entry.full_name) + + if File.dir? dest then + @fileops.chmod entry.header.mode, dest, :verbose=>false + else + @fileops.mkdir_p dest, :mode => entry.header.mode, :verbose => false + end + + fsync_dir dest + fsync_dir File.join(dest, "..") + + return + end + + # it's a file + md5 = Digest::MD5.new if expected_md5sum + destdir = File.join destdir, File.dirname(entry.full_name) + @fileops.mkdir_p destdir, :mode => 0755, :verbose => false + destfile = File.join destdir, File.basename(entry.full_name) + @fileops.chmod 0600, destfile, :verbose => false rescue nil # Errno::ENOENT + + open destfile, "wb", entry.header.mode do |os| + loop do + data = entry.read 4096 + break unless data + # HACK shouldn't we check the MD5 before writing to disk? + md5 << data if expected_md5sum + os.write(data) + end + + os.fsync + end + + @fileops.chmod entry.header.mode, destfile, :verbose => false + fsync_dir File.dirname(destfile) + fsync_dir File.join(File.dirname(destfile), "..") + + if expected_md5sum && expected_md5sum != md5.hexdigest then + raise Gem::Package::BadCheckSum + end + end + + # Attempt to YAML-load a gemspec from the given _io_ parameter. Return + # nil if it fails. + def load_gemspec(io) + Gem::Specification.from_yaml io + rescue Gem::Exception + nil + end + + ## + # Return an IO stream for the zipped entry. + # + # NOTE: Originally this method used two approaches, Return a GZipReader + # directly, or read the GZipReader into a string and return a StringIO on + # the string. The string IO approach was used for versions of ZLib before + # 1.2.1 to avoid buffer errors on windows machines. Then we found that + # errors happened with 1.2.1 as well, so we changed the condition. Then + # we discovered errors occurred with versions as late as 1.2.3. At this + # point (after some benchmarking to show we weren't seriously crippling + # the unpacking speed) we threw our hands in the air and declared that + # this method would use the String IO approach on all platforms at all + # times. And that's the way it is. + + def zipped_stream(entry) + if defined? Rubinius then + zis = Zlib::GzipReader.new entry + dis = zis.read + is = StringIO.new(dis) + else + # This is Jamis Buck's Zlib workaround for some unknown issue + entry.read(10) # skip the gzip header + zis = Zlib::Inflate.new(-Zlib::MAX_WBITS) + is = StringIO.new(zis.inflate(entry.read)) + end + ensure + zis.finish if zis + end + +end + diff --git a/lib/rubygems/package/tar_output.rb b/lib/rubygems/package/tar_output.rb new file mode 100644 index 0000000000..b22f7dd86b --- /dev/null +++ b/lib/rubygems/package/tar_output.rb @@ -0,0 +1,143 @@ +#++ +# Copyright (C) 2004 Mauricio Julio Fern�ndez Pradier +# See LICENSE.txt for additional licensing information. +#-- + +require 'rubygems/package' + +## +# TarOutput is a wrapper to TarWriter that builds gem-format tar file. +# +# Gem-format tar files contain the following files: +# [data.tar.gz] A gzipped tar file containing the files that compose the gem +# which will be extracted into the gem/ dir on installation. +# [metadata.gz] A YAML format Gem::Specification. +# [data.tar.gz.sig] A signature for the gem's data.tar.gz. +# [metadata.gz.sig] A signature for the gem's metadata.gz. +# +# See TarOutput::open for usage details. + +class Gem::Package::TarOutput + + ## + # Creates a new TarOutput which will yield a TarWriter object for the + # data.tar.gz portion of a gem-format tar file. + # + # See #initialize for details on +io+ and +signer+. + # + # See #add_gem_contents for details on adding metadata to the tar file. + + def self.open(io, signer = nil, &block) # :yield: data_tar_writer + tar_outputter = new io, signer + tar_outputter.add_gem_contents(&block) + tar_outputter.add_metadata + tar_outputter.add_signatures + + ensure + tar_outputter.close + end + + ## + # Creates a new TarOutput that will write a gem-format tar file to +io+. If + # +signer+ is given, the data.tar.gz and metadata.gz will be signed and + # the signatures will be added to the tar file. + + def initialize(io, signer) + @io = io + @signer = signer + + @tar_writer = Gem::Package::TarWriter.new @io + + @metadata = nil + + @data_signature = nil + @meta_signature = nil + end + + ## + # Yields a TarWriter for the data.tar.gz inside a gem-format tar file. + # The yielded TarWriter has been extended with a #metadata= method for + # attaching a YAML format Gem::Specification which will be written by + # add_metadata. + + def add_gem_contents + @tar_writer.add_file "data.tar.gz", 0644 do |inner| + sio = @signer ? StringIO.new : nil + Zlib::GzipWriter.wrap(sio || inner) do |os| + + Gem::Package::TarWriter.new os do |data_tar_writer| + def data_tar_writer.metadata() @metadata end + def data_tar_writer.metadata=(metadata) @metadata = metadata end + + yield data_tar_writer + + @metadata = data_tar_writer.metadata + end + end + + # if we have a signing key, then sign the data + # digest and return the signature + if @signer then + digest = Gem::Security::OPT[:dgst_algo].digest sio.string + @data_signature = @signer.sign digest + inner.write sio.string + end + end + + self + end + + ## + # Adds metadata.gz to the gem-format tar file which was saved from a + # previous #add_gem_contents call. + + def add_metadata + return if @metadata.nil? + + @tar_writer.add_file "metadata.gz", 0644 do |io| + begin + sio = @signer ? StringIO.new : nil + gzos = Zlib::GzipWriter.new(sio || io) + gzos.write @metadata + ensure + gzos.flush + gzos.finish + + # if we have a signing key, then sign the metadata digest and return + # the signature + if @signer then + digest = Gem::Security::OPT[:dgst_algo].digest sio.string + @meta_signature = @signer.sign digest + io.write sio.string + end + end + end + end + + ## + # Adds data.tar.gz.sig and metadata.gz.sig to the gem-format tar files if + # a Gem::Security::Signer was sent to initialize. + + def add_signatures + if @data_signature then + @tar_writer.add_file 'data.tar.gz.sig', 0644 do |io| + io.write @data_signature + end + end + + if @meta_signature then + @tar_writer.add_file 'metadata.gz.sig', 0644 do |io| + io.write @meta_signature + end + end + end + + ## + # Closes the TarOutput. + + def close + @tar_writer.close + end + +end + diff --git a/lib/rubygems/package/tar_reader.rb b/lib/rubygems/package/tar_reader.rb new file mode 100644 index 0000000000..8359399207 --- /dev/null +++ b/lib/rubygems/package/tar_reader.rb @@ -0,0 +1,86 @@ +#++ +# Copyright (C) 2004 Mauricio Julio Fern�ndez Pradier +# See LICENSE.txt for additional licensing information. +#-- + +require 'rubygems/package' + +class Gem::Package::TarReader + + include Gem::Package + + class UnexpectedEOF < StandardError; end + + def self.new(io) + reader = super + + return reader unless block_given? + + begin + yield reader + ensure + reader.close + end + + nil + end + + def initialize(io) + @io = io + @init_pos = io.pos + end + + def close + end + + def each + loop do + return if @io.eof? + + header = Gem::Package::TarHeader.from @io + return if header.empty? + + entry = Gem::Package::TarReader::Entry.new header, @io + size = entry.header.size + + yield entry + + skip = (512 - (size % 512)) % 512 + + if @io.respond_to? :seek then + # avoid reading... + @io.seek(size - entry.bytes_read, IO::SEEK_CUR) + else + pending = size - entry.bytes_read + + while pending > 0 do + bread = @io.read([pending, 4096].min).size + raise UnexpectedEOF if @io.eof? + pending -= bread + end + end + + @io.read skip # discard trailing zeros + + # make sure nobody can use #read, #getc or #rewind anymore + entry.close + end + end + + alias each_entry each + + ## + # NOTE: Do not call #rewind during #each + + def rewind + if @init_pos == 0 then + raise Gem::Package::NonSeekableIO unless @io.respond_to? :rewind + @io.rewind + else + raise Gem::Package::NonSeekableIO unless @io.respond_to? :pos= + @io.pos = @init_pos + end + end + +end + diff --git a/lib/rubygems/package/tar_reader/entry.rb b/lib/rubygems/package/tar_reader/entry.rb new file mode 100644 index 0000000000..dcc66153d8 --- /dev/null +++ b/lib/rubygems/package/tar_reader/entry.rb @@ -0,0 +1,99 @@ +#++ +# Copyright (C) 2004 Mauricio Julio Fern�ndez Pradier +# See LICENSE.txt for additional licensing information. +#-- + +require 'rubygems/package' + +class Gem::Package::TarReader::Entry + + attr_reader :header + + def initialize(header, io) + @closed = false + @header = header + @io = io + @orig_pos = @io.pos + @read = 0 + end + + def check_closed # :nodoc: + raise IOError, "closed #{self.class}" if closed? + end + + def bytes_read + @read + end + + def close + @closed = true + end + + def closed? + @closed + end + + def eof? + check_closed + + @read >= @header.size + end + + def full_name + if @header.prefix != "" then + File.join @header.prefix, @header.name + else + @header.name + end + end + + def getc + check_closed + + return nil if @read >= @header.size + + ret = @io.getc + @read += 1 if ret + + ret + end + + def directory? + @header.typeflag == "5" + end + + def file? + @header.typeflag == "0" + end + + def pos + check_closed + + bytes_read + end + + def read(len = nil) + check_closed + + return nil if @read >= @header.size + + len ||= @header.size - @read + max_read = [len, @header.size - @read].min + + ret = @io.read max_read + @read += ret.size + + ret + end + + def rewind + check_closed + + raise Gem::Package::NonSeekableIO unless @io.respond_to? :pos= + + @io.pos = @orig_pos + @read = 0 + end + +end + diff --git a/lib/rubygems/package/tar_writer.rb b/lib/rubygems/package/tar_writer.rb new file mode 100644 index 0000000000..6e11440e22 --- /dev/null +++ b/lib/rubygems/package/tar_writer.rb @@ -0,0 +1,180 @@ +#++ +# Copyright (C) 2004 Mauricio Julio Fern�ndez Pradier +# See LICENSE.txt for additional licensing information. +#-- + +require 'rubygems/package' + +class Gem::Package::TarWriter + + class FileOverflow < StandardError; end + + class BoundedStream + + attr_reader :limit, :written + + def initialize(io, limit) + @io = io + @limit = limit + @written = 0 + end + + def write(data) + if data.size + @written > @limit + raise FileOverflow, "You tried to feed more data than fits in the file." + end + @io.write data + @written += data.size + data.size + end + + end + + class RestrictedStream + + def initialize(io) + @io = io + end + + def write(data) + @io.write data + end + + end + + def self.new(io) + writer = super + + return writer unless block_given? + + begin + yield writer + ensure + writer.close + end + + nil + end + + def initialize(io) + @io = io + @closed = false + end + + def add_file(name, mode) + check_closed + + raise Gem::Package::NonSeekableIO unless @io.respond_to? :pos= + + name, prefix = split_name name + + init_pos = @io.pos + @io.write "\0" * 512 # placeholder for the header + + yield RestrictedStream.new(@io) if block_given? + + size = @io.pos - init_pos - 512 + + remainder = (512 - (size % 512)) % 512 + @io.write "\0" * remainder + + final_pos = @io.pos + @io.pos = init_pos + + header = Gem::Package::TarHeader.new :name => name, :mode => mode, + :size => size, :prefix => prefix + + @io.write header + @io.pos = final_pos + + self + end + + def add_file_simple(name, mode, size) + check_closed + + name, prefix = split_name name + + header = Gem::Package::TarHeader.new(:name => name, :mode => mode, + :size => size, :prefix => prefix).to_s + + @io.write header + os = BoundedStream.new @io, size + + yield os if block_given? + + min_padding = size - os.written + @io.write("\0" * min_padding) + + remainder = (512 - (size % 512)) % 512 + @io.write("\0" * remainder) + + self + end + + def check_closed + raise IOError, "closed #{self.class}" if closed? + end + + def close + check_closed + + @io.write "\0" * 1024 + flush + + @closed = true + end + + def closed? + @closed + end + + def flush + check_closed + + @io.flush if @io.respond_to? :flush + end + + def mkdir(name, mode) + check_closed + + name, prefix = split_name(name) + + header = Gem::Package::TarHeader.new :name => name, :mode => mode, + :typeflag => "5", :size => 0, + :prefix => prefix + + @io.write header + + self + end + + def split_name(name) # :nodoc: + raise Gem::Package::TooLongFileName if name.size > 256 + + if name.size <= 100 then + prefix = "" + else + parts = name.split(/\//) + newname = parts.pop + nxt = "" + + loop do + nxt = parts.pop + break if newname.size + 1 + nxt.size > 100 + newname = nxt + "/" + newname + end + + prefix = (parts + [nxt]).join "/" + name = newname + + if name.size > 100 or prefix.size > 155 then + raise Gem::Package::TooLongFileName + end + end + + return name, prefix + end + +end + diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb index cb22e1f1b1..f49ee2f4a1 100644 --- a/lib/rubygems/remote_fetcher.rb +++ b/lib/rubygems/remote_fetcher.rb @@ -2,7 +2,6 @@ require 'net/http' require 'uri' require 'rubygems' -require 'rubygems/gem_open_uri' ## # RemoteFetcher handles the details of fetching gems and gem information from @@ -10,6 +9,8 @@ require 'rubygems/gem_open_uri' class Gem::RemoteFetcher + include Gem::UserInteraction + class FetchError < Gem::Exception; end @fetcher = nil @@ -29,6 +30,10 @@ class Gem::RemoteFetcher # HTTP_PROXY_PASS) # * <tt>:no_proxy</tt>: ignore environment variables and _don't_ use a proxy def initialize(proxy) + Socket.do_not_reverse_lookup = true + + @connections = {} + @requests = Hash.new 0 @proxy_uri = case proxy when :no_proxy then nil @@ -38,6 +43,65 @@ class Gem::RemoteFetcher end end + ## + # Moves the gem +spec+ from +source_uri+ to the cache dir unless it is + # already there. If the source_uri is local the gem cache dir copy is + # always replaced. + def download(spec, source_uri, install_dir = Gem.dir) + gem_file_name = "#{spec.full_name}.gem" + local_gem_path = File.join install_dir, 'cache', gem_file_name + + Gem.ensure_gem_subdirectories install_dir + + source_uri = URI.parse source_uri unless URI::Generic === source_uri + scheme = source_uri.scheme + + # URI.parse gets confused by MS Windows paths with forward slashes. + scheme = nil if scheme =~ /^[a-z]$/i + + case scheme + when 'http' then + unless File.exist? local_gem_path then + begin + say "Downloading gem #{gem_file_name}" if + Gem.configuration.really_verbose + + remote_gem_path = source_uri + "gems/#{gem_file_name}" + + gem = Gem::RemoteFetcher.fetcher.fetch_path remote_gem_path + rescue Gem::RemoteFetcher::FetchError + raise if spec.original_platform == spec.platform + + alternate_name = "#{spec.original_name}.gem" + + say "Failed, downloading gem #{alternate_name}" if + Gem.configuration.really_verbose + + remote_gem_path = source_uri + "gems/#{alternate_name}" + + gem = Gem::RemoteFetcher.fetcher.fetch_path remote_gem_path + end + + File.open local_gem_path, 'wb' do |fp| + fp.write gem + end + end + when nil, 'file' then # TODO test for local overriding cache + begin + FileUtils.cp source_uri.to_s, local_gem_path + rescue Errno::EACCES + local_gem_path = source_uri.to_s + end + + say "Using local gem #{local_gem_path}" if + Gem.configuration.really_verbose + else + raise Gem::InstallError, "unsupported URI scheme #{source_uri.scheme}" + end + + local_gem_path + end + # Downloads +uri+. def fetch_path(uri) open_uri_or_path(uri) do |input| @@ -47,9 +111,8 @@ class Gem::RemoteFetcher raise FetchError, "timed out fetching #{uri}" rescue IOError, SocketError, SystemCallError => e raise FetchError, "#{e.class}: #{e} reading #{uri}" - rescue OpenURI::HTTPError => e - body = e.io.readlines.join "\n\t" - message = "#{e.class}: #{e} reading #{uri}\n\t#{body}" + rescue => e + message = "#{e.class}: #{e} reading #{uri}" raise FetchError, message end @@ -83,7 +146,8 @@ class Gem::RemoteFetcher end rescue SocketError, SystemCallError, Timeout::Error => e - raise FetchError, "#{e.message} (#{e.class})\n\tgetting size of #{uri}" + raise Gem::RemoteFetcher::FetchError, + "#{e.message} (#{e.class})\n\tgetting size of #{uri}" end private @@ -131,26 +195,77 @@ class Gem::RemoteFetcher # Read the data from the (source based) URI, but if it is a file:// URI, # read from the filesystem instead. - def open_uri_or_path(uri, &block) + def open_uri_or_path(uri, depth = 0, &block) if file_uri?(uri) open(get_file_uri_path(uri), &block) else - connection_options = { - "User-Agent" => "RubyGems/#{Gem::RubyGemsVersion} #{Gem::Platform.local}" - } + uri = URI.parse uri unless URI::Generic === uri + net_http_args = [uri.host, uri.port] + + if @proxy_uri then + net_http_args += [ @proxy_uri.host, + @proxy_uri.port, + @proxy_uri.user, + @proxy_uri.password + ] + end + + connection_id = net_http_args.join ':' + @connections[connection_id] ||= Net::HTTP.new(*net_http_args) + connection = @connections[connection_id] - if @proxy_uri - http_proxy_url = "#{@proxy_uri.scheme}://#{@proxy_uri.host}:#{@proxy_uri.port}" - connection_options[:proxy_http_basic_authentication] = [http_proxy_url, unescape(@proxy_uri.user)||'', unescape(@proxy_uri.password)||''] + if uri.scheme == 'https' && ! connection.started? + http_obj.use_ssl = true + http_obj.verify_mode = OpenSSL::SSL::VERIFY_NONE end - uri = URI.parse uri unless URI::Generic === uri + connection.start unless connection.started? + + request = Net::HTTP::Get.new(uri.request_uri) unless uri.nil? || uri.user.nil? || uri.user.empty? then - connection_options[:http_basic_authentication] = - [unescape(uri.user), unescape(uri.password)] + request.basic_auth(uri.user, uri.password) end - open(uri, connection_options, &block) + ua = "RubyGems/#{Gem::RubyGemsVersion} #{Gem::Platform.local}" + ua << " Ruby/#{RUBY_VERSION} (#{RUBY_RELEASE_DATE}" + ua << " patchlevel #{RUBY_PATCHLEVEL}" if defined? RUBY_PATCHLEVEL + ua << ")" + + request.add_field 'User-Agent', ua + request.add_field 'Connection', 'keep-alive' + request.add_field 'Keep-Alive', '30' + + # HACK work around EOFError bug in Net::HTTP + retried = false + begin + @requests[connection_id] += 1 + response = connection.request(request) + rescue EOFError + requests = @requests[connection_id] + say "connection reset after #{requests} requests, retrying" if + Gem.configuration.really_verbose + + raise Gem::RemoteFetcher::FetchError, 'too many connection resets' if + retried + + @requests[connection_id] = 0 + + connection.finish + connection.start + retried = true + retry + end + + case response + when Net::HTTPOK then + block.call(StringIO.new(response.body)) if block + when Net::HTTPRedirection then + raise Gem::RemoteFetcher::FetchError, "too many redirects" if depth > 10 + open_uri_or_path(response['Location'], depth + 1, &block) + else + raise Gem::RemoteFetcher::FetchError, + "bad response #{response.message} #{response.code}" + end end end diff --git a/lib/rubygems/requirement.rb b/lib/rubygems/requirement.rb index 4dfba4fa61..209bd432f0 100644 --- a/lib/rubygems/requirement.rb +++ b/lib/rubygems/requirement.rb @@ -16,6 +16,8 @@ class Gem::Requirement include Comparable + attr_reader :requirements + OPS = { "=" => lambda { |v, r| v == r }, "!=" => lambda { |v, r| v != r }, diff --git a/lib/rubygems/rubygems_version.rb b/lib/rubygems/rubygems_version.rb index 146e2cfee4..cc7269d622 100644 --- a/lib/rubygems/rubygems_version.rb +++ b/lib/rubygems/rubygems_version.rb @@ -2,5 +2,5 @@ # This file is auto-generated by build scripts. # See: rake update_version module Gem - RubyGemsVersion = '1.0.1' + RubyGemsVersion = '1.1.0' end diff --git a/lib/rubygems/security.rb b/lib/rubygems/security.rb index 415e98ffc0..345d36bbd3 100644 --- a/lib/rubygems/security.rb +++ b/lib/rubygems/security.rb @@ -4,6 +4,7 @@ # See LICENSE.txt for permissions. #++ +require 'rubygems' require 'rubygems/gem_openssl' # = Signed Gems README diff --git a/lib/rubygems/server.rb b/lib/rubygems/server.rb index ed957dd38a..dab449894f 100644 --- a/lib/rubygems/server.rb +++ b/lib/rubygems/server.rb @@ -1,7 +1,7 @@ require 'webrick' -require 'rdoc/template' require 'yaml' require 'zlib' +require 'erb' require 'rubygems' @@ -27,107 +27,87 @@ class Gem::Server include Gem::UserInteraction - DOC_TEMPLATE = <<-WEBPAGE -<?xml version="1.0" encoding="iso-8859-1"?> -<!DOCTYPE html - PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> - -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> -<head> - <title>RubyGems Documentation Index</title> - <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> - <link rel="stylesheet" href="gem-server-rdoc-style.css" type="text/css" media="screen" /> -</head> -<body> - <div id="fileHeader"> - <h1>RubyGems Documentation Index</h1> - </div> - <!-- banner header --> - -<div id="bodyContent"> - <div id="contextContent"> - <div id="description"> - <h1>Summary</h1> -<p>There are %gem_count% gems installed:</p> -<p> -START:specs -IFNOT:is_last -<a href="#%name%">%name%</a>, -ENDIF:is_last -IF:is_last -<a href="#%name%">%name%</a>. -ENDIF:is_last -END:specs -<h1>Gems</h1> - -<dl> -START:specs -<dt> -IF:first_name_entry - <a name="%name%"></a> -ENDIF:first_name_entry -<b>%name% %version%</b> -IF:rdoc_installed - <a href="%doc_path%">[rdoc]</a> -ENDIF:rdoc_installed -IFNOT:rdoc_installed - <span title="rdoc not installed">[rdoc]</span> -ENDIF:rdoc_installed -IF:homepage -<a href="%homepage%" title="%homepage%">[www]</a> -ENDIF:homepage -IFNOT:homepage -<span title="no homepage available">[www]</span> -ENDIF:homepage -IF:has_deps - - depends on -START:dependencies -IFNOT:is_last -<a href="#%name%" title="%version%">%name%</a>, -ENDIF:is_last -IF:is_last -<a href="#%name%" title="%version%">%name%</a>. -ENDIF:is_last -END:dependencies -ENDIF:has_deps -</dt> -<dd> -%summary% -IF:executables - <br/> - -IF:only_one_executable - Executable is -ENDIF:only_one_executable - -IFNOT:only_one_executable - Executables are -ENDIF:only_one_executable - -START:executables -IFNOT:is_last - <span class="context-item-name">%executable%</span>, -ENDIF:is_last -IF:is_last - <span class="context-item-name">%executable%</span>. -ENDIF:is_last -END:executables -ENDIF:executables -<br/> -<br/> -</dd> -END:specs -</dl> - + DOC_TEMPLATE = <<-'WEBPAGE' + <?xml version="1.0" encoding="iso-8859-1"?> + <!DOCTYPE html + PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + + <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> + <head> + <title>RubyGems Documentation Index</title> + <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> + <link rel="stylesheet" href="gem-server-rdoc-style.css" type="text/css" media="screen" /> + </head> + <body> + <div id="fileHeader"> + <h1>RubyGems Documentation Index</h1> + </div> + <!-- banner header --> + + <div id="bodyContent"> + <div id="contextContent"> + <div id="description"> + <h1>Summary</h1> + <p>There are <%=values["gem_count"]%> gems installed:</p> + <p> + <%= values["specs"].map { |v| "<a href=\"#{v["name"]}\">#{v["name"]}</a>" }.join ', ' %>. + <h1>Gems</h1> + + <dl> + <% values["specs"].each do |spec| %> + <dt> + <% if spec["first_name_entry"] then %> + <a name="<%=spec["name"]%>"></a> + <% end %> + + <b><%=spec["name"]%> <%=spec["version"]%></b> + + <% if spec["rdoc_installed"] then %> + <a href="<%=spec["doc_path"]%>">[rdoc]</a> + <% else %> + <span title="rdoc not installed">[rdoc]</span> + <% end %> + + <% if spec["homepage"] then %> + <a href="<%=spec["homepage"]%>" title="<%=spec["homepage"]%>">[www]</a> + <% else %> + <span title="no homepage available">[www]</span> + <% end %> + + <% if spec["has_deps"] then %> + - depends on + <%= spec["dependencies"].map { |v| "<a href=\"#{v["name"]}\">#{v["name"]}</a>" }.join ', ' %>. + <% end %> + </dt> + <dd> + <%=spec["summary"]%> + <% if spec["executables"] then %> + <br/> + + <% if spec["only_one_executable"] then %> + Executable is + <% else %> + Executables are + <%end%> + + <%= spec["executables"].map { |v| "<span class=\"context-item-name\">#{v["executable"]}</span>"}.join ', ' %>. + + <%end%> + <br/> + <br/> + </dd> + <% end %> + </dl> + + </div> + </div> </div> - </div> + <div id="validator-badges"> + <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p> </div> -<div id="validator-badges"> - <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p> -</div> -</body> -</html> + </body> + </html> WEBPAGE # CSS is copy & paste from rdoc-style.css, RDoc V1.0.1 - 20041108 @@ -496,11 +476,12 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; } end # create page from template - template = TemplatePage.new(DOC_TEMPLATE) + template = ERB.new(DOC_TEMPLATE) res['content-type'] = 'text/html' - template.write_html_on res.body, - "gem_count" => specs.size.to_s, "specs" => specs, - "total_file_count" => total_file_count.to_s + values = { "gem_count" => specs.size.to_s, "specs" => specs, + "total_file_count" => total_file_count.to_s } + result = template.result binding + res.body = result end paths = { "/gems" => "/cache/", "/doc_root" => "/doc/" } diff --git a/lib/rubygems/source_index.rb b/lib/rubygems/source_index.rb index 1283f8f904..61f5324a95 100644 --- a/lib/rubygems/source_index.rb +++ b/lib/rubygems/source_index.rb @@ -8,437 +8,512 @@ require 'rubygems' require 'rubygems/user_interaction' require 'rubygems/specification' -module Gem +## +# The SourceIndex object indexes all the gems available from a +# particular source (e.g. a list of gem directories, or a remote +# source). A SourceIndex maps a gem full name to a gem +# specification. +# +# NOTE:: The class used to be named Cache, but that became +# confusing when cached source fetchers where introduced. The +# constant Gem::Cache is an alias for this class to allow old +# YAMLized source index objects to load properly. - # The SourceIndex object indexes all the gems available from a - # particular source (e.g. a list of gem directories, or a remote - # source). A SourceIndex maps a gem full name to a gem - # specification. - # - # NOTE:: The class used to be named Cache, but that became - # confusing when cached source fetchers where introduced. The - # constant Gem::Cache is an alias for this class to allow old - # YAMLized source index objects to load properly. - # - class SourceIndex +class Gem::SourceIndex - include Enumerable + include Enumerable + include Gem::UserInteraction + + class << self include Gem::UserInteraction - # Class Methods. ------------------------------------------------- - class << self - include Gem::UserInteraction - - # Factory method to construct a source index instance for a given - # path. - # - # deprecated:: - # If supplied, from_installed_gems will act just like - # +from_gems_in+. This argument is deprecated and is provided - # just for backwards compatibility, and should not generally - # be used. - # - # return:: - # SourceIndex instance - # - def from_installed_gems(*deprecated) - if deprecated.empty? - from_gems_in(*installed_spec_directories) - else - from_gems_in(*deprecated) # HACK warn - end - end - - # Return a list of directories in the current gem path that - # contain specifications. - # - # return:: - # List of directory paths (all ending in "../specifications"). - # - def installed_spec_directories - Gem.path.collect { |dir| File.join(dir, "specifications") } + ## + # Factory method to construct a source index instance for a given + # path. + # + # deprecated:: + # If supplied, from_installed_gems will act just like + # +from_gems_in+. This argument is deprecated and is provided + # just for backwards compatibility, and should not generally + # be used. + # + # return:: + # SourceIndex instance + + def from_installed_gems(*deprecated) + if deprecated.empty? + from_gems_in(*installed_spec_directories) + else + from_gems_in(*deprecated) # HACK warn end + end - # Factory method to construct a source index instance for a - # given path. - # - # spec_dirs:: - # List of directories to search for specifications. Each - # directory should have a "specifications" subdirectory - # containing the gem specifications. - # - # return:: - # SourceIndex instance - # - def from_gems_in(*spec_dirs) - self.new.load_gems_in(*spec_dirs) - end - - # Load a specification from a file (eval'd Ruby code) - # - # file_name:: [String] The .gemspec file - # return:: Specification instance or nil if an error occurs - # - def load_specification(file_name) - begin - spec_code = File.read(file_name).untaint - gemspec = eval spec_code, binding, file_name - if gemspec.is_a?(Gem::Specification) - gemspec.loaded_from = file_name - return gemspec - end - alert_warning "File '#{file_name}' does not evaluate to a gem specification" - rescue SyntaxError => e - alert_warning e - alert_warning spec_code - rescue Exception => e - alert_warning(e.inspect.to_s + "\n" + spec_code) - alert_warning "Invalid .gemspec format in '#{file_name}'" + ## + # Return a list of directories in the current gem path that + # contain specifications. + # + # return:: + # List of directory paths (all ending in "../specifications"). + + def installed_spec_directories + Gem.path.collect { |dir| File.join(dir, "specifications") } + end + + ## + # Creates a new SourceIndex from the ruby format gem specifications in + # +spec_dirs+. + + def from_gems_in(*spec_dirs) + self.new.load_gems_in(*spec_dirs) + end + + ## + # Loads a ruby-format specification from +file_name+ and returns the + # loaded spec. + + def load_specification(file_name) + begin + spec_code = File.read(file_name).untaint + gemspec = eval spec_code, binding, file_name + if gemspec.is_a?(Gem::Specification) + gemspec.loaded_from = file_name + return gemspec end - return nil + alert_warning "File '#{file_name}' does not evaluate to a gem specification" + rescue SyntaxError => e + alert_warning e + alert_warning spec_code + rescue Exception => e + alert_warning(e.inspect.to_s + "\n" + spec_code) + alert_warning "Invalid .gemspec format in '#{file_name}'" end - + return nil end - # Instance Methods ----------------------------------------------- + end - # Constructs a source index instance from the provided - # specifications - # - # specifications:: - # [Hash] hash of [Gem name, Gem::Specification] pairs - # - def initialize(specifications={}) - @gems = specifications - end - - # Reconstruct the source index from the list of source - # directories. - def load_gems_in(*spec_dirs) - @gems.clear - specs = Dir.glob File.join("{#{spec_dirs.join(',')}}", "*.gemspec") - - specs.each do |file_name| - gemspec = self.class.load_specification(file_name.untaint) - add_spec(gemspec) if gemspec + ## + # Constructs a source index instance from the provided + # specifications + # + # specifications:: + # [Hash] hash of [Gem name, Gem::Specification] pairs + + def initialize(specifications={}) + @gems = specifications + end + + ## + # Reconstruct the source index from the specifications in +spec_dirs+. + + def load_gems_in(*spec_dirs) + @gems.clear + + spec_dirs.reverse_each do |spec_dir| + spec_files = Dir.glob File.join(spec_dir, '*.gemspec') + + spec_files.each do |spec_file| + gemspec = self.class.load_specification spec_file.untaint + add_spec gemspec if gemspec end - self end - # Returns a Hash of name => Specification of the latest versions of each - # gem in this index. - def latest_specs - result, latest = Hash.new { |h,k| h[k] = [] }, {} + self + end - self.each do |_, spec| # SourceIndex is not a hash, so we're stuck with each - name = spec.name - curr_ver = spec.version - prev_ver = latest[name] + ## + # Returns a Hash of name => Specification of the latest versions of each + # gem in this index. - next unless prev_ver.nil? or curr_ver >= prev_ver + def latest_specs + result = Hash.new { |h,k| h[k] = [] } + latest = {} - if prev_ver.nil? or curr_ver > prev_ver then - result[name].clear - latest[name] = curr_ver - end + sort.each do |_, spec| + name = spec.name + curr_ver = spec.version + prev_ver = latest.key?(name) ? latest[name].version : nil + + next unless prev_ver.nil? or curr_ver >= prev_ver or + latest[name].platform != Gem::Platform::RUBY - result[name] << spec + if prev_ver.nil? or + (curr_ver > prev_ver and spec.platform == Gem::Platform::RUBY) then + result[name].clear + latest[name] = spec end - result.values.flatten - end + if spec.platform != Gem::Platform::RUBY then + result[name].delete_if do |result_spec| + result_spec.platform == spec.platform + end + end - # Add a gem specification to the source index. - def add_spec(gem_spec) - @gems[gem_spec.full_name] = gem_spec + result[name] << spec end - # Remove a gem specification named +full_name+. - def remove_spec(full_name) - @gems.delete(full_name) - end + result.values.flatten + end - # Iterate over the specifications in the source index. - def each(&block) # :yields: gem.full_name, gem - @gems.each(&block) - end + ## + # Add a gem specification to the source index. - # The gem specification given a full gem spec name. - def specification(full_name) - @gems[full_name] - end + def add_spec(gem_spec) + @gems[gem_spec.full_name] = gem_spec + end - # The signature for the source index. Changes in the signature - # indicate a change in the index. - def index_signature - require 'rubygems/digest/sha2' + ## + # Add gem specifications to the source index. - Gem::SHA256.new.hexdigest(@gems.keys.sort.join(',')).to_s + def add_specs(*gem_specs) + gem_specs.each do |spec| + add_spec spec end + end - # The signature for the given gem specification. - def gem_signature(gem_full_name) - require 'rubygems/digest/sha2' + ## + # Remove a gem specification named +full_name+. - Gem::SHA256.new.hexdigest(@gems[gem_full_name].to_yaml).to_s - end + def remove_spec(full_name) + @gems.delete(full_name) + end + + ## + # Iterate over the specifications in the source index. + + def each(&block) # :yields: gem.full_name, gem + @gems.each(&block) + end + + ## + # The gem specification given a full gem spec name. + + def specification(full_name) + @gems[full_name] + end + + ## + # The signature for the source index. Changes in the signature indicate a + # change in the index. + + def index_signature + require 'rubygems/digest/sha2' + + Gem::SHA256.new.hexdigest(@gems.keys.sort.join(',')).to_s + end + + ## + # The signature for the given gem specification. + + def gem_signature(gem_full_name) + require 'rubygems/digest/sha2' - def size - @gems.size + Gem::SHA256.new.hexdigest(@gems[gem_full_name].to_yaml).to_s + end + + def size + @gems.size + end + alias length size + + ## + # Find a gem by an exact match on the short name. + + def find_name(gem_name, version_requirement = Gem::Requirement.default) + search(/^#{gem_name}$/, version_requirement) + end + + ## + # Search for a gem by Gem::Dependency +gem_pattern+. If +only_platform+ + # is true, only gems matching Gem::Platform.local will be returned. An + # Array of matching Gem::Specification objects is returned. + # + # For backwards compatibility, a String or Regexp pattern may be passed as + # +gem_pattern+, and a Gem::Requirement for +platform_only+. This + # behavior is deprecated and will be removed. + + def search(gem_pattern, platform_only = false) + version_requirement = nil + only_platform = false + + case gem_pattern # TODO warn after 2008/03, remove three months after + when Regexp then + version_requirement = platform_only || Gem::Requirement.default + when Gem::Dependency then + only_platform = platform_only + version_requirement = gem_pattern.version_requirements + gem_pattern = if gem_pattern.name.empty? then + // + else + /^#{Regexp.escape gem_pattern.name}$/ + end + else + version_requirement = platform_only || Gem::Requirement.default + gem_pattern = /#{gem_pattern}/i end - alias length size - # Find a gem by an exact match on the short name. - def find_name(gem_name, version_requirement = Gem::Requirement.default) - search(/^#{gem_name}$/, version_requirement) + unless Gem::Requirement === version_requirement then + version_requirement = Gem::Requirement.create version_requirement end - # Search for a gem by Gem::Dependency +gem_pattern+. If +only_platform+ - # is true, only gems matching Gem::Platform.local will be returned. An - # Array of matching Gem::Specification objects is returned. - # - # For backwards compatibility, a String or Regexp pattern may be passed as - # +gem_pattern+, and a Gem::Requirement for +platform_only+. This - # behavior is deprecated and will be removed. - def search(gem_pattern, platform_only = false) - version_requirement = nil - only_platform = false - - case gem_pattern # TODO warn after 2008/03, remove three months after - when Regexp then - version_requirement = platform_only || Gem::Requirement.default - when Gem::Dependency then - only_platform = platform_only - version_requirement = gem_pattern.version_requirements - gem_pattern = if gem_pattern.name.empty? then - // - else - /^#{Regexp.escape gem_pattern.name}$/ - end - else - version_requirement = platform_only || Gem::Requirement.default - gem_pattern = /#{gem_pattern}/i - end + specs = @gems.values.select do |spec| + spec.name =~ gem_pattern and + version_requirement.satisfied_by? spec.version + end - unless Gem::Requirement === version_requirement then - version_requirement = Gem::Requirement.create version_requirement + if only_platform then + specs = specs.select do |spec| + Gem::Platform.match spec.platform end + end - specs = @gems.values.select do |spec| - spec.name =~ gem_pattern and - version_requirement.satisfied_by? spec.version - end + specs.sort_by { |s| s.sort_obj } + end - if only_platform then - specs = specs.select do |spec| - Gem::Platform.match spec.platform - end - end + ## + # Refresh the source index from the local file system. + # + # return:: Returns a pointer to itself. - specs.sort_by { |s| s.sort_obj } - end + def refresh! + load_gems_in(self.class.installed_spec_directories) + end - # Refresh the source index from the local file system. - # - # return:: Returns a pointer to itself. - # - def refresh! - load_gems_in(self.class.installed_spec_directories) - end + ## + # Returns an Array of Gem::Specifications that are not up to date. - # Returns an Array of Gem::Specifications that are not up to date. - # - def outdated - dep = Gem::Dependency.new '', Gem::Requirement.default + def outdated + dep = Gem::Dependency.new '', Gem::Requirement.default - remotes = Gem::SourceInfoCache.search dep, true + remotes = Gem::SourceInfoCache.search dep, true - outdateds = [] + outdateds = [] - latest_specs.each do |local| - name = local.name - remote = remotes.select { |spec| spec.name == name }. - sort_by { |spec| spec.version.to_ints }. - last - outdateds << name if remote and local.version < remote.version - end + latest_specs.each do |local| + name = local.name + remote = remotes.select { |spec| spec.name == name }. + sort_by { |spec| spec.version.to_ints }. + last - outdateds + outdateds << name if remote and local.version < remote.version end - def update(source_uri) - use_incremental = false + outdateds + end - begin - gem_names = fetch_quick_index source_uri - remove_extra gem_names - missing_gems = find_missing gem_names + ## + # Updates this SourceIndex from +source_uri+. If +all+ is false, only the + # latest gems are fetched. - return false if missing_gems.size.zero? + def update(source_uri, all) + source_uri = URI.parse source_uri unless URI::Generic === source_uri + source_uri.path += '/' unless source_uri.path =~ /\/$/ - say "missing #{missing_gems.size} gems" if - missing_gems.size > 0 and Gem.configuration.really_verbose + use_incremental = false - use_incremental = missing_gems.size <= Gem.configuration.bulk_threshold - rescue Gem::OperationNotSupportedError => ex - alert_error "Falling back to bulk fetch: #{ex.message}" if - Gem.configuration.really_verbose - use_incremental = false - end + begin + gem_names = fetch_quick_index source_uri, all + remove_extra gem_names + missing_gems = find_missing gem_names - if use_incremental then - update_with_missing(source_uri, missing_gems) - else - new_index = fetch_bulk_index(source_uri) - @gems.replace(new_index.gems) - end + return false if missing_gems.size.zero? - true - end + say "Missing metadata for #{missing_gems.size} gems" if + missing_gems.size > 0 and Gem.configuration.really_verbose - def ==(other) # :nodoc: - self.class === other and @gems == other.gems + use_incremental = missing_gems.size <= Gem.configuration.bulk_threshold + rescue Gem::OperationNotSupportedError => ex + alert_error "Falling back to bulk fetch: #{ex.message}" if + Gem.configuration.really_verbose + use_incremental = false end - def dump - Marshal.dump(self) + if use_incremental then + update_with_missing(source_uri, missing_gems) + else + new_index = fetch_bulk_index(source_uri) + @gems.replace(new_index.gems) end - protected + true + end - attr_reader :gems + def ==(other) # :nodoc: + self.class === other and @gems == other.gems + end - private + def dump + Marshal.dump(self) + end - def fetcher - require 'rubygems/remote_fetcher' + protected - Gem::RemoteFetcher.fetcher - end + attr_reader :gems - def fetch_index_from(source_uri) - @fetch_error = nil + private + + def fetcher + require 'rubygems/remote_fetcher' + + Gem::RemoteFetcher.fetcher + end + + def fetch_index_from(source_uri) + @fetch_error = nil - indexes = %W[ + indexes = %W[ Marshal.#{Gem.marshal_version}.Z Marshal.#{Gem.marshal_version} yaml.Z yaml ] - indexes.each do |name| - spec_data = nil - begin - spec_data = fetcher.fetch_path("#{source_uri}/#{name}") - spec_data = unzip(spec_data) if name =~ /\.Z$/ - if name =~ /Marshal/ then - return Marshal.load(spec_data) - else - return YAML.load(spec_data) - end - rescue => e - if Gem.configuration.really_verbose then - alert_error "Unable to fetch #{name}: #{e.message}" - end - @fetch_error = e + indexes.each do |name| + spec_data = nil + index = source_uri + name + begin + spec_data = fetcher.fetch_path index + spec_data = unzip(spec_data) if name =~ /\.Z$/ + + if name =~ /Marshal/ then + return Marshal.load(spec_data) + else + return YAML.load(spec_data) + end + rescue => e + if Gem.configuration.really_verbose then + alert_error "Unable to fetch #{name}: #{e.message}" end + + @fetch_error = e end - nil end - def fetch_bulk_index(source_uri) - say "Bulk updating Gem source index for: #{source_uri}" + nil + end - index = fetch_index_from(source_uri) - if index.nil? then - raise Gem::RemoteSourceException, + def fetch_bulk_index(source_uri) + say "Bulk updating Gem source index for: #{source_uri}" + + index = fetch_index_from(source_uri) + if index.nil? then + raise Gem::RemoteSourceException, "Error fetching remote gem cache: #{@fetch_error}" - end - @fetch_error = nil - index end + @fetch_error = nil + index + end + + ## + # Get the quick index needed for incremental updates. + + def fetch_quick_index(source_uri, all) + index = all ? 'index' : 'latest_index' - # Get the quick index needed for incremental updates. - def fetch_quick_index(source_uri) - zipped_index = fetcher.fetch_path source_uri + '/quick/index.rz' - unzip(zipped_index).split("\n") - rescue ::Exception => ex + zipped_index = fetcher.fetch_path source_uri + "quick/#{index}.rz" + + unzip(zipped_index).split("\n") + rescue ::Exception => e + unless all then + say "Latest index not found, using quick index" if + Gem.configuration.really_verbose + + fetch_quick_index source_uri, true + else raise Gem::OperationNotSupportedError, - "No quick index found: " + ex.message + "No quick index found: #{e.message}" end + end - # Make a list of full names for all the missing gemspecs. - def find_missing(spec_names) - spec_names.find_all { |full_name| - specification(full_name).nil? - } - end + ## + # Make a list of full names for all the missing gemspecs. - def remove_extra(spec_names) - dictionary = spec_names.inject({}) { |h, k| h[k] = true; h } - each do |name, spec| - remove_spec name unless dictionary.include? name - end - end + def find_missing(spec_names) + spec_names.find_all { |full_name| + specification(full_name).nil? + } + end - # Unzip the given string. - def unzip(string) - require 'zlib' - Zlib::Inflate.inflate(string) + def remove_extra(spec_names) + dictionary = spec_names.inject({}) { |h, k| h[k] = true; h } + each do |name, spec| + remove_spec name unless dictionary.include? name end + end - # Tries to fetch Marshal representation first, then YAML - def fetch_single_spec(source_uri, spec_name) - @fetch_error = nil - begin - marshal_uri = source_uri + "/quick/Marshal.#{Gem.marshal_version}/#{spec_name}.gemspec.rz" - zipped = fetcher.fetch_path marshal_uri - return Marshal.load(unzip(zipped)) - rescue => ex - @fetch_error = ex - if Gem.configuration.really_verbose then - say "unable to fetch marshal gemspec #{marshal_uri}: #{ex.class} - #{ex}" - end + ## + # Unzip the given string. + + def unzip(string) + require 'zlib' + Zlib::Inflate.inflate(string) + end + + ## + # Tries to fetch Marshal representation first, then YAML + + def fetch_single_spec(source_uri, spec_name) + @fetch_error = nil + + begin + marshal_uri = source_uri + "quick/Marshal.#{Gem.marshal_version}/#{spec_name}.gemspec.rz" + zipped = fetcher.fetch_path marshal_uri + return Marshal.load(unzip(zipped)) + rescue => ex + @fetch_error = ex + + if Gem.configuration.really_verbose then + say "unable to fetch marshal gemspec #{marshal_uri}: #{ex.class} - #{ex}" end + end - begin - yaml_uri = source_uri + "/quick/#{spec_name}.gemspec.rz" - zipped = fetcher.fetch_path yaml_uri - return YAML.load(unzip(zipped)) - rescue => ex - @fetch_error = ex - if Gem.configuration.really_verbose then - say "unable to fetch YAML gemspec #{yaml_uri}: #{ex.class} - #{ex}" - end + begin + yaml_uri = source_uri + "quick/#{spec_name}.gemspec.rz" + zipped = fetcher.fetch_path yaml_uri + return YAML.load(unzip(zipped)) + rescue => ex + @fetch_error = ex + if Gem.configuration.really_verbose then + say "unable to fetch YAML gemspec #{yaml_uri}: #{ex.class} - #{ex}" end - nil end - # Update the cached source index with the missing names. - def update_with_missing(source_uri, missing_names) - progress = ui.progress_reporter(missing_names.size, + nil + end + + ## + # Update the cached source index with the missing names. + + def update_with_missing(source_uri, missing_names) + progress = ui.progress_reporter(missing_names.size, "Updating metadata for #{missing_names.size} gems from #{source_uri}") - missing_names.each do |spec_name| - gemspec = fetch_single_spec(source_uri, spec_name) - if gemspec.nil? then - ui.say "Failed to download spec #{spec_name} from #{source_uri}:\n" \ + missing_names.each do |spec_name| + gemspec = fetch_single_spec(source_uri, spec_name) + if gemspec.nil? then + ui.say "Failed to download spec #{spec_name} from #{source_uri}:\n" \ "\t#{@fetch_error.message}" - else - add_spec gemspec - progress.updated spec_name - end - @fetch_error = nil + else + add_spec gemspec + progress.updated spec_name end - progress.done - progress.count + @fetch_error = nil end - + progress.done + progress.count end - # Cache is an alias for SourceIndex to allow older YAMLized source - # index objects to load properly. +end + +module Gem + + # :stopdoc: + + # Cache is an alias for SourceIndex to allow older YAMLized source index + # objects to load properly. Cache = SourceIndex + # :starddoc: + end diff --git a/lib/rubygems/source_info_cache.rb b/lib/rubygems/source_info_cache.rb index c84868a5f5..9383f6362e 100644 --- a/lib/rubygems/source_info_cache.rb +++ b/lib/rubygems/source_info_cache.rb @@ -4,6 +4,7 @@ require 'rubygems' require 'rubygems/source_info_cache_entry' require 'rubygems/user_interaction' +## # SourceInfoCache stores a copy of the gem index for each gem source. # # There are two possible cache locations, the system cache and the user cache: @@ -25,7 +26,7 @@ require 'rubygems/user_interaction' # @source_index => Gem::SourceIndex # ... # } -# + class Gem::SourceInfoCache include Gem::UserInteraction @@ -37,7 +38,7 @@ class Gem::SourceInfoCache def self.cache return @cache if @cache @cache = new - @cache.refresh if Gem.configuration.update_sources + @cache.refresh false if Gem.configuration.update_sources @cache end @@ -45,86 +46,178 @@ class Gem::SourceInfoCache cache.cache_data end - # Search all source indexes for +pattern+. - def self.search(pattern, platform_only = false) - cache.search pattern, platform_only + ## + # The name of the system cache file. + + def self.latest_system_cache_file + File.join File.dirname(system_cache_file), + "latest_#{File.basename system_cache_file}" + end + + ## + # The name of the latest user cache file. + + def self.latest_user_cache_file + File.join File.dirname(user_cache_file), + "latest_#{File.basename user_cache_file}" + end + + ## + # Search all source indexes. See Gem::SourceInfoCache#search. + + def self.search(*args) + cache.search(*args) end - # Search all source indexes for +pattern+. Only returns gems matching - # Gem.platforms when +only_platform+ is true. See #search_with_source. - def self.search_with_source(pattern, only_platform = false) - cache.search_with_source(pattern, only_platform) + ## + # Search all source indexes returning the source_uri. See + # Gem::SourceInfoCache#search_with_source. + + def self.search_with_source(*args) + cache.search_with_source(*args) + end + + ## + # The name of the system cache file. (class method) + + def self.system_cache_file + @system_cache_file ||= Gem.default_system_source_cache_dir + end + + ## + # The name of the user cache file. + + def self.user_cache_file + @user_cache_file ||= + ENV['GEMCACHE'] || Gem.default_user_source_cache_dir end def initialize # :nodoc: @cache_data = nil @cache_file = nil @dirty = false + @only_latest = true end + ## # The most recent cache data. + def cache_data return @cache_data if @cache_data cache_file # HACK writable check - begin - # Marshal loads 30-40% faster from a String, and 2MB on 20061116 is small - data = File.open cache_file, 'rb' do |fp| fp.read end - @cache_data = Marshal.load data - - @cache_data.each do |url, sice| - next unless sice.is_a?(Hash) - update - cache = sice['cache'] - size = sice['size'] - if cache.is_a?(Gem::SourceIndex) and size.is_a?(Numeric) then - new_sice = Gem::SourceInfoCacheEntry.new cache, size - @cache_data[url] = new_sice - else # irreperable, force refetch. - reset_cache_for(url) - end - end - @cache_data - rescue => e - if Gem.configuration.really_verbose then - say "Exception during cache_data handling: #{ex.class} - #{ex}" - say "Cache file was: #{cache_file}" - say "\t#{e.backtrace.join "\n\t"}" - end - reset_cache_data - end - end - - def reset_cache_for(url) - say "Reseting cache for #{url}" if Gem.configuration.really_verbose + @only_latest = true - sice = Gem::SourceInfoCacheEntry.new Gem::SourceIndex.new, 0 - sice.refresh url # HACK may be unnecessary, see ::cache and #refresh + @cache_data = read_cache_data latest_cache_file - @cache_data[url] = sice @cache_data end - def reset_cache_data - @cache_data = {} - end + ## + # The name of the cache file. - # The name of the cache file to be read def cache_file return @cache_file if @cache_file @cache_file = (try_file(system_cache_file) or try_file(user_cache_file) or raise "unable to locate a writable cache file") end + + ## + # Force cache file to be reset, useful for integration testing of rubygems + + def reset_cache_file + @cache_file = nil + end + ## # Write the cache to a local file (if it is dirty). + def flush write_cache if @dirty @dirty = false end - # Refreshes each source in the cache from its repository. - def refresh + def latest_cache_data + latest_cache_data = {} + + cache_data.each do |repo, sice| + latest = sice.source_index.latest_specs + + new_si = Gem::SourceIndex.new + new_si.add_specs(*latest) + + latest_sice = Gem::SourceInfoCacheEntry.new new_si, sice.size + latest_cache_data[repo] = latest_sice + end + + latest_cache_data + end + + ## + # The name of the latest cache file. + + def latest_cache_file + File.join File.dirname(cache_file), "latest_#{File.basename cache_file}" + end + + ## + # The name of the latest system cache file. + + def latest_system_cache_file + self.class.latest_system_cache_file + end + + ## + # The name of the latest user cache file. + + def latest_user_cache_file + self.class.latest_user_cache_file + end + + def read_all_cache_data + if @only_latest then + @only_latest = false + @cache_data = read_cache_data cache_file + end + end + + def read_cache_data(file) + # Marshal loads 30-40% faster from a String, and 2MB on 20061116 is small + data = open file, 'rb' do |fp| fp.read end + cache_data = Marshal.load data + + cache_data.each do |url, sice| + next unless sice.is_a?(Hash) + update + + cache = sice['cache'] + size = sice['size'] + + if cache.is_a?(Gem::SourceIndex) and size.is_a?(Numeric) then + new_sice = Gem::SourceInfoCacheEntry.new cache, size + cache_data[url] = new_sice + else # irreperable, force refetch. + reset_cache_for url, cache_data + end + end + + cache_data + rescue => e + if Gem.configuration.really_verbose then + say "Exception during cache_data handling: #{e.class} - #{e}" + say "Cache file was: #{file}" + say "\t#{e.backtrace.join "\n\t"}" + end + + {} + end + + ## + # Refreshes each source in the cache from its repository. If +all+ is + # false, only latest gems are updated. + + def refresh(all) Gem.sources.each do |source_uri| cache_entry = cache_data[source_uri] if cache_entry.nil? then @@ -132,14 +225,34 @@ class Gem::SourceInfoCache cache_data[source_uri] = cache_entry end - update if cache_entry.refresh source_uri + update if cache_entry.refresh source_uri, all end flush end - # Searches all source indexes for +pattern+. - def search(pattern, platform_only = false) + def reset_cache_for(url, cache_data) + say "Reseting cache for #{url}" if Gem.configuration.really_verbose + + sice = Gem::SourceInfoCacheEntry.new Gem::SourceIndex.new, 0 + sice.refresh url, false # HACK may be unnecessary, see ::cache and #refresh + + cache_data[url] = sice + cache_data + end + + def reset_cache_data + @cache_data = nil + end + + ## + # Searches all source indexes. See Gem::SourceIndex#search for details on + # +pattern+ and +platform_only+. If +all+ is set to true, the full index + # will be loaded before searching. + + def search(pattern, platform_only = false, all = false) + read_all_cache_data if all + cache_data.map do |source_uri, sic_entry| next unless Gem.sources.include? source_uri sic_entry.source_index.search pattern, platform_only @@ -150,7 +263,9 @@ class Gem::SourceInfoCache # only gems matching Gem.platforms will be selected. Returns an Array of # pairs containing the Gem::Specification found and the source_uri it was # found at. - def search_with_source(pattern, only_platform = false) + def search_with_source(pattern, only_platform = false, all = false) + read_all_cache_data if all + results = [] cache_data.map do |source_uri, sic_entry| @@ -164,68 +279,75 @@ class Gem::SourceInfoCache results end - # Mark the cache as updated (i.e. dirty). - def update - @dirty = true + ## + # Set the source info cache data directly. This is mainly used for unit + # testing when we don't want to read a file system to grab the cached source + # index information. The +hash+ should map a source URL into a + # SourceInfoCacheEntry. + + def set_cache_data(hash) + @cache_data = hash + update end + ## # The name of the system cache file. + def system_cache_file self.class.system_cache_file end - # The name of the system cache file. (class method) - def self.system_cache_file - @system_cache_file ||= Gem.default_system_source_cache_dir + ## + # Determine if +path+ is a candidate for a cache file. Returns +path+ if + # it is, nil if not. + + def try_file(path) + return path if File.writable? path + return nil if File.exist? path + + dir = File.dirname path + + unless File.exist? dir then + begin + FileUtils.mkdir_p dir + rescue RuntimeError, SystemCallError + return nil + end + end + + if File.writable? dir then + open path, "wb" do |io| io.write Marshal.dump({}) end + return path + end + + nil + end + + ## + # Mark the cache as updated (i.e. dirty). + + def update + @dirty = true end + ## # The name of the user cache file. + def user_cache_file self.class.user_cache_file end - # The name of the user cache file. (class method) - def self.user_cache_file - @user_cache_file ||= - ENV['GEMCACHE'] || Gem.default_user_source_cache_dir - end - + ## # Write data to the proper cache. + def write_cache - open cache_file, "wb" do |f| - f.write Marshal.dump(cache_data) + open cache_file, 'wb' do |io| + io.write Marshal.dump(cache_data) end - end - # Set the source info cache data directly. This is mainly used for unit - # testing when we don't want to read a file system to grab the cached source - # index information. The +hash+ should map a source URL into a - # SourceInfoCacheEntry. - def set_cache_data(hash) - @cache_data = hash - update - end - - private - - # Determine if +fn+ is a candidate for a cache file. Return fn if - # it is. Return nil if it is not. - def try_file(fn) - return fn if File.writable?(fn) - return nil if File.exist?(fn) - dir = File.dirname(fn) - unless File.exist? dir then - begin - FileUtils.mkdir_p(dir) - rescue RuntimeError, SystemCallError - return nil - end + open latest_cache_file, 'wb' do |io| + io.write Marshal.dump(latest_cache_data) end - if File.writable?(dir) - File.open(fn, "wb") { |f| f << Marshal.dump({}) } - return fn - end - nil end end diff --git a/lib/rubygems/source_info_cache_entry.rb b/lib/rubygems/source_info_cache_entry.rb index 02e03ca9db..c3f75e5b99 100644 --- a/lib/rubygems/source_info_cache_entry.rb +++ b/lib/rubygems/source_info_cache_entry.rb @@ -3,24 +3,31 @@ require 'rubygems/source_index' require 'rubygems/remote_fetcher' ## -# Entrys held by a SourceInfoCache. +# Entries held by a SourceInfoCache. class Gem::SourceInfoCacheEntry + ## # The source index for this cache entry. + attr_reader :source_index + ## # The size of the of the source entry. Used to determine if the # source index has changed. + attr_reader :size + ## # Create a cache entry. + def initialize(si, size) @source_index = si || Gem::SourceIndex.new({}) @size = size + @all = false end - def refresh(source_uri) + def refresh(source_uri, all) begin marshal_uri = URI.join source_uri.to_s, "Marshal.#{Gem.marshal_version}" remote_size = Gem::RemoteFetcher.fetcher.fetch_size marshal_uri @@ -29,9 +36,12 @@ class Gem::SourceInfoCacheEntry remote_size = Gem::RemoteFetcher.fetcher.fetch_size yaml_uri end - return false if @size == remote_size # TODO Use index_signature instead of size? - updated = @source_index.update source_uri + # TODO Use index_signature instead of size? + return false if @size == remote_size and @all + + updated = @source_index.update source_uri, all @size = remote_size + @all = all updated end diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb index be03150c96..de37a08b60 100644 --- a/lib/rubygems/specification.rb +++ b/lib/rubygems/specification.rb @@ -13,7 +13,7 @@ require 'rubygems/platform' if RUBY_VERSION < '1.9' then def Time.today t = Time.now - t - ((t.to_i + t.gmt_offset) % 86400) + t - ((t.to_f + t.gmt_offset) % 86400) end unless defined? Time.today end # :startdoc: diff --git a/lib/rubygems/uninstaller.rb b/lib/rubygems/uninstaller.rb index 1c979d8573..e2b5e5372b 100644 --- a/lib/rubygems/uninstaller.rb +++ b/lib/rubygems/uninstaller.rb @@ -12,7 +12,7 @@ require 'rubygems/user_interaction' ## # An Uninstaller. -# + class Gem::Uninstaller include Gem::UserInteraction @@ -21,8 +21,8 @@ class Gem::Uninstaller # Constructs an Uninstaller instance # # gem:: [String] The Gem name to uninstall - # - def initialize(gem, options) + + def initialize(gem, options = {}) @gem = gem @version = options[:version] || Gem::Requirement.default gem_home = options[:install_dir] || Gem.dir @@ -30,12 +30,13 @@ class Gem::Uninstaller @force_executables = options[:executables] @force_all = options[:all] @force_ignore = options[:ignore] + @bin_dir = options[:bin_dir] end ## # Performs the uninstall of the Gem. This removes the spec, the # Gem directory, and the cached .gem file, - # + def uninstall list = Gem.source_index.search(/^#{@gem}$/, @version) @@ -66,18 +67,14 @@ class Gem::Uninstaller end ## - # Remove executables and batch files (windows only) for the gem as - # it is being installed - # - # gemspec::[Specification] the gem whose executables need to be removed. - # + # Removes installed executables and batch files (windows only) for + # +gemspec+. + def remove_executables(gemspec) return if gemspec.nil? if gemspec.executables.size > 0 then - bindir = Gem.bindir @gem_home - - raise Gem::FilePermissionError, bindir unless File.writable? bindir + bindir = @bin_dir ? @bin_dir : (Gem.bindir @gem_home) list = Gem.source_index.search(gemspec.name).delete_if { |spec| spec.version == gemspec.version @@ -93,14 +90,19 @@ class Gem::Uninstaller return if executables.size == 0 - answer = @force_executables || ask_yes_no( - "Remove executables:\n" \ - "\t#{gemspec.executables.join(", ")}\n\nin addition to the gem?", - true) # " # appease ruby-mode - don't ask + answer = if @force_executables.nil? then + ask_yes_no("Remove executables:\n" \ + "\t#{gemspec.executables.join(", ")}\n\nin addition to the gem?", + true) # " # appease ruby-mode - don't ask + else + @force_executables + end unless answer then say "Executables and scripts will remain installed." else + raise Gem::FilePermissionError, bindir unless File.writable? bindir + gemspec.executables.each do |exe_name| say "Removing #{exe_name}" FileUtils.rm_f File.join(bindir, exe_name) @@ -110,23 +112,22 @@ class Gem::Uninstaller end end + ## + # Removes all gems in +list+. # - # list:: the list of all gems to remove - # - # Warning: this method modifies the +list+ parameter. Once it has - # uninstalled a gem, it is removed from that list. - # + # NOTE: removes uninstalled gems from +list+. + def remove_all(list) - list.dup.each { |gem| remove(gem, list) } + list.dup.each { |spec| remove spec, list } end - # + ## # spec:: the spec of the gem to be uninstalled # list:: the list of all such gems # # Warning: this method modifies the +list+ parameter. Once it has # uninstalled a gem, it is removed from that list. - # + def remove(spec, list) unless dependencies_ok? spec then raise Gem::DependencyRemovalException, @@ -134,10 +135,11 @@ class Gem::Uninstaller end unless path_ok? spec then - alert("In order to remove #{spec.name}, please execute:\n" \ - "\tgem uninstall #{spec.name} --install-dir=#{spec.installation_path}") - raise Gem::GemNotInHomeException, + e = Gem::GemNotInHomeException.new \ "Gem is not installed in directory #{@gem_home}" + e.spec = spec + + raise e end raise Gem::FilePermissionError, spec.installation_path unless @@ -182,8 +184,8 @@ class Gem::Uninstaller def dependencies_ok?(spec) return true if @force_ignore - srcindex = Gem::SourceIndex.from_installed_gems - deplist = Gem::DependencyList.from_source_index srcindex + source_index = Gem::SourceIndex.from_installed_gems + deplist = Gem::DependencyList.from_source_index source_index deplist.ok_to_remove?(spec.full_name) || ask_if_ok(spec) end diff --git a/lib/rubygems/user_interaction.rb b/lib/rubygems/user_interaction.rb index 4970f33c00..ffccb60314 100644 --- a/lib/rubygems/user_interaction.rb +++ b/lib/rubygems/user_interaction.rb @@ -68,7 +68,7 @@ module Gem include DefaultUserInteraction [ :choose_from_list, :ask, :ask_yes_no, :say, :alert, :alert_warning, - :alert_error, :terminate_interaction!, :terminate_interaction + :alert_error, :terminate_interaction ].each do |methname| class_eval %{ def #{methname}(*args) @@ -182,16 +182,10 @@ module Gem ask(question) if question end - # Terminate the application immediately without running any exit - # handlers. - def terminate_interaction!(status=-1) - exit!(status) - end - # Terminate the appliation normally, running any exit handlers # that might have been defined. - def terminate_interaction(status=0) - exit(status) + def terminate_interaction(status = 0) + raise Gem::SystemExitException, status end # Return a progress reporter object diff --git a/test/rubygems/gem_installer_test_case.rb b/test/rubygems/gem_installer_test_case.rb new file mode 100644 index 0000000000..0d684eb1eb --- /dev/null +++ b/test/rubygems/gem_installer_test_case.rb @@ -0,0 +1,86 @@ +require 'test/unit' +require File.join(File.expand_path(File.dirname(__FILE__)), 'gemutilities') +require 'rubygems/installer' + +class Gem::Installer + attr_accessor :gem_dir + + attr_writer :format + attr_writer :gem_home + attr_writer :env_shebang + attr_writer :ignore_dependencies + attr_writer :format_executable + attr_writer :security_policy + attr_writer :spec + attr_writer :wrappers +end + +class GemInstallerTestCase < RubyGemTestCase + + def setup + super + + @spec = quick_gem "a" + @gem = File.join @tempdir, "#{@spec.full_name}.gem" + + util_build_gem @spec + FileUtils.mv File.join(@gemhome, 'cache', "#{@spec.full_name}.gem"), + @tempdir + + @installer = Gem::Installer.new @gem + @installer.gem_dir = util_gem_dir + @installer.gem_home = @gemhome + @installer.spec = @spec + end + + def util_gem_bindir(version = '2') + File.join util_gem_dir(version), "bin" + end + + def util_gem_dir(version = '2') + File.join @gemhome, "gems", "a-#{version}" # HACK + end + + def util_inst_bindir + File.join @gemhome, "bin" + end + + def util_make_exec(version = '2', shebang = "#!/usr/bin/ruby") + @spec.executables = ["my_exec"] + + FileUtils.mkdir_p util_gem_bindir(version) + exec_file = @installer.formatted_program_filename "my_exec" + exec_path = File.join util_gem_bindir(version), exec_file + File.open exec_path, 'w' do |f| + f.puts shebang + end + end + + def util_setup_gem(ui = @ui) # HACK fix use_ui to make this automatic + @spec.files = File.join('lib', 'code.rb') + @spec.executables << 'executable' + @spec.extensions << File.join('ext', 'a', 'mkrf_conf.rb') + + Dir.chdir @tempdir do + FileUtils.mkdir_p 'bin' + FileUtils.mkdir_p 'lib' + FileUtils.mkdir_p File.join('ext', 'a') + File.open File.join('bin', 'executable'), 'w' do |f| f.puts '1' end + File.open File.join('lib', 'code.rb'), 'w' do |f| f.puts '1' end + File.open File.join('ext', 'a', 'mkrf_conf.rb'), 'w' do |f| + f << <<-EOF + File.open 'Rakefile', 'w' do |rf| rf.puts "task :default" end + EOF + end + + use_ui ui do + FileUtils.rm @gem + Gem::Builder.new(@spec).build + end + end + + @installer = Gem::Installer.new @gem + end + +end + diff --git a/test/rubygems/gem_package_tar_test_case.rb b/test/rubygems/gem_package_tar_test_case.rb new file mode 100644 index 0000000000..756b30ef27 --- /dev/null +++ b/test/rubygems/gem_package_tar_test_case.rb @@ -0,0 +1,146 @@ +require File.join(File.expand_path(File.dirname(__FILE__)), 'gemutilities') +require 'rubygems/package' + +class File + + # straight from setup.rb + def self.dir?(path) + # for corrupted windows stat() + File.directory?((path[-1,1] == '/') ? path : path + '/') + end + + def self.read_b(name) + File.open(name, "rb") { |f| f.read } + end + +end + +class TarTestCase < RubyGemTestCase + + def ASCIIZ(str, length) + str + "\0" * (length - str.length) + end + + def SP(s) + s + " " + end + + def SP_Z(s) + s + " \0" + end + + def Z(s) + s + "\0" + end + + def assert_headers_equal(expected, actual) + expected = expected.to_s unless String === expected + actual = actual.to_s unless String === actual + + fields = %w[ + name 100 + mode 8 + uid 8 + gid 8 + size 12 + mtime 12 + checksum 8 + typeflag 1 + linkname 100 + magic 6 + version 2 + uname 32 + gname 32 + devmajor 8 + devminor 8 + prefix 155 + ] + + offset = 0 + + until fields.empty? do + name = fields.shift + length = fields.shift.to_i + + if name == "checksum" then + chksum_off = offset + offset += length + next + end + + assert_equal expected[offset, length], actual[offset, length], + "Field #{name} of the tar header differs." + + offset += length + end + + assert_equal expected[chksum_off, 8], actual[chksum_off, 8] + end + + def calc_checksum(header) + sum = header.unpack("C*").inject{|s,a| s + a} + SP(Z(to_oct(sum, 6))) + end + + def header(type, fname, dname, length, mode, checksum = nil) + checksum ||= " " * 8 + + arr = [ # struct tarfile_entry_posix + ASCIIZ(fname, 100), # char name[100]; ASCII + (Z unless filled) + Z(to_oct(mode, 7)), # char mode[8]; 0 padded, octal null + Z(to_oct(0, 7)), # char uid[8]; ditto + Z(to_oct(0, 7)), # char gid[8]; ditto + Z(to_oct(length, 11)), # char size[12]; 0 padded, octal, null + Z(to_oct(0, 11)), # char mtime[12]; 0 padded, octal, null + checksum, # char checksum[8]; 0 padded, octal, null, space + type, # char typeflag[1]; file: "0" dir: "5" + "\0" * 100, # char linkname[100]; ASCII + (Z unless filled) + "ustar\0", # char magic[6]; "ustar\0" + "00", # char version[2]; "00" + ASCIIZ("wheel", 32), # char uname[32]; ASCIIZ + ASCIIZ("wheel", 32), # char gname[32]; ASCIIZ + Z(to_oct(0, 7)), # char devmajor[8]; 0 padded, octal, null + Z(to_oct(0, 7)), # char devminor[8]; 0 padded, octal, null + ASCIIZ(dname, 155) # char prefix[155]; ASCII + (Z unless filled) + ] + + format = "C100C8C8C8C12C12C8CC100C6C2C32C32C8C8C155" + h = if RUBY_VERSION >= "1.9" then + arr.join + else + arr = arr.join("").split(//).map{|x| x[0]} + arr.pack format + end + ret = h + "\0" * (512 - h.size) + assert_equal(512, ret.size) + ret + end + + def tar_dir_header(name, prefix, mode) + h = header("5", name, prefix, 0, mode) + checksum = calc_checksum(h) + header("5", name, prefix, 0, mode, checksum) + end + + def tar_file_header(fname, dname, mode, length) + h = header("0", fname, dname, length, mode) + checksum = calc_checksum(h) + header("0", fname, dname, length, mode, checksum) + end + + def to_oct(n, pad_size) + "%0#{pad_size}o" % n + end + + def util_entry(tar) + io = TempIO.new tar + header = Gem::Package::TarHeader.from io + entry = Gem::Package::TarReader::Entry.new header, io + end + + def util_dir_entry + util_entry tar_dir_header("foo", "bar", 0) + end + +end + diff --git a/test/rubygems/gemutilities.rb b/test/rubygems/gemutilities.rb index fb41210ac8..f12f4e88a5 100644 --- a/test/rubygems/gemutilities.rb +++ b/test/rubygems/gemutilities.rb @@ -10,9 +10,10 @@ at_exit { $SAFE = 1 } require 'fileutils' require 'test/unit' require 'tmpdir' +require 'tempfile' require 'uri' -require 'rubygems/gem_open_uri' require 'rubygems/source_info_cache' +require 'rubygems/package' require File.join(File.expand_path(File.dirname(__FILE__)), 'mockgemui') @@ -56,6 +57,20 @@ class FakeFetcher data.respond_to?(:call) ? data.call : data.length end + def download spec, source_uri, install_dir = Gem.dir + name = "#{spec.full_name}.gem" + path = File.join(install_dir, 'cache', name) + + if source_uri =~ /^http/ then + File.open(path, "wb") do |f| + f.write fetch_path(File.join(source_uri, "gems", name)) + end + else + FileUtils.cp source_uri, path + end + + path + end end class RubyGemTestCase < Test::Unit::TestCase @@ -76,6 +91,7 @@ class RubyGemTestCase < Test::Unit::TestCase @gemhome = File.join @tempdir, "gemhome" @gemcache = File.join(@gemhome, "source_cache") @usrcache = File.join(@gemhome, ".gem", "user_cache") + @latest_usrcache = File.join(@gemhome, ".gem", "latest_user_cache") FileUtils.mkdir_p @gemhome @@ -101,6 +117,11 @@ class RubyGemTestCase < Test::Unit::TestCase end @marshal_version = "#{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}" + + @private_key = File.expand_path File.join(File.dirname(__FILE__), + 'private_key.pem') + @public_cert = File.expand_path File.join(File.dirname(__FILE__), + 'public_cert.pem') end def teardown @@ -135,25 +156,52 @@ class RubyGemTestCase < Test::Unit::TestCase end def prep_cache_files(lc) - [ [lc.system_cache_file, 'sys'], - [lc.user_cache_file, 'usr'], - ].each do |fn, data| - FileUtils.mkdir_p File.dirname(fn).untaint - open(fn.dup.untaint, "wb") { |f| f.write(Marshal.dump({'key' => data})) } + @usr_si ||= Gem::SourceIndex.new + @usr_sice ||= Gem::SourceInfoCacheEntry.new @usr_si, 0 + + @sys_si ||= Gem::SourceIndex.new + @sys_sice ||= Gem::SourceInfoCacheEntry.new @sys_si, 0 + + latest_si = Gem::SourceIndex.new + latest_si.add_specs(*@sys_si.latest_specs) + latest_sys_sice = Gem::SourceInfoCacheEntry.new latest_si, 0 + + latest_si = Gem::SourceIndex.new + latest_si.add_specs(*@usr_si.latest_specs) + latest_usr_sice = Gem::SourceInfoCacheEntry.new latest_si, 0 + + [ [lc.system_cache_file, @sys_sice], + [lc.latest_system_cache_file, latest_sys_sice], + [lc.user_cache_file, @usr_sice], + [lc.latest_user_cache_file, latest_usr_sice], + ].each do |filename, data| + FileUtils.mkdir_p File.dirname(filename).untaint + + open filename.dup.untaint, 'wb' do |f| + f.write Marshal.dump({ @gem_repo => data }) + end + end + end + + def read_cache(path) + open path.dup.untaint, 'rb' do |io| + Marshal.load io.read end end - def read_cache(fn) - open(fn.dup.untaint) { |f| Marshal.load f.read } + def read_binary(path) + Gem.read_binary path end def write_file(path) path = File.join(@gemhome, path) dir = File.dirname path FileUtils.mkdir_p dir - File.open(path, "w") { |io| - yield(io) - } + + open path, 'wb' do |io| + yield io + end + path end @@ -204,6 +252,23 @@ class RubyGemTestCase < Test::Unit::TestCase end end + def util_gem(name, version, &block) + spec = quick_gem(name, version, &block) + + util_build_gem spec + + cache_file = File.join @tempdir, 'gems', "#{spec.original_name}.gem" + FileUtils.mv File.join(@gemhome, 'cache', "#{spec.original_name}.gem"), + cache_file + FileUtils.rm File.join(@gemhome, 'specifications', + "#{spec.full_name}.gemspec") + + spec.loaded_from = nil + spec.loaded = false + + [spec, cache_file] + end + def util_make_gems init = proc do |s| s.files = %w[lib/code.rb] @@ -212,6 +277,7 @@ class RubyGemTestCase < Test::Unit::TestCase @a1 = quick_gem('a', '1', &init) @a2 = quick_gem('a', '2', &init) + @a_evil9 = quick_gem('a_evil', '9', &init) @b2 = quick_gem('b', '2', &init) @c1_2 = quick_gem('c', '1.2', &init) @pl1 = quick_gem 'pl', '1' do |s| # l for legacy @@ -227,7 +293,7 @@ class RubyGemTestCase < Test::Unit::TestCase write_file File.join(*%W[gems #{@c1_2.original_name} lib code.rb]) do end write_file File.join(*%W[gems #{@pl1.original_name} lib code.rb]) do end - [@a1, @a2, @b2, @c1_2, @pl1].each { |spec| util_build_gem spec } + [@a1, @a2, @a_evil9, @b2, @c1_2, @pl1].each { |spec| util_build_gem spec } FileUtils.rm_r File.join(@gemhome, 'gems', @pl1.original_name) @@ -256,32 +322,19 @@ class RubyGemTestCase < Test::Unit::TestCase @fetcher = FakeFetcher.new @fetcher.uri = @uri - @gem1 = quick_gem 'gem_one' do |gem| - gem.files = %w[Rakefile lib/gem_one.rb] - end - - @gem2 = quick_gem 'gem_two' do |gem| - gem.files = %w[Rakefile lib/gem_two.rb] - end - - @gem3 = quick_gem 'gem_three' do |gem| # missing gem - gem.files = %w[Rakefile lib/gem_three.rb] - end - - # this gem has a higher version and longer name than the gem we want - @gem4 = quick_gem 'gem_one_evil', '666' do |gem| - gem.files = %w[Rakefile lib/gem_one.rb] - end + util_make_gems - @all_gems = [@gem1, @gem2, @gem3, @gem4].sort + @all_gems = [@a1, @a2, @a_evil9, @b2, @c1_2].sort @all_gem_names = @all_gems.map { |gem| gem.full_name } - gem_names = [@gem1.full_name, @gem2.full_name, @gem4.full_name] + gem_names = [@a1.full_name, @a2.full_name, @b2.full_name] @gem_names = gem_names.sort.join("\n") - @source_index = Gem::SourceIndex.new @gem1.full_name => @gem1, - @gem2.full_name => @gem2, - @gem4.full_name => @gem4 + @source_index = Gem::SourceIndex.new + @source_index.add_spec @a1 + @source_index.add_spec @a2 + @source_index.add_spec @a_evil9 + @source_index.add_spec @c1_2 Gem::RemoteFetcher.instance_variable_set :@fetcher, @fetcher end @@ -294,7 +347,12 @@ class RubyGemTestCase < Test::Unit::TestCase sice = Gem::SourceInfoCacheEntry.new si, 0 sic = Gem::SourceInfoCache.new + sic.set_cache_data( { @gem_repo => sice } ) + sic.update + sic.write_cache + sic.reset_cache_data + Gem::SourceInfoCache.instance_variable_set :@cache, sic si end @@ -313,3 +371,30 @@ class RubyGemTestCase < Test::Unit::TestCase end +class TempIO + + @@count = 0 + + def initialize(string = '') + @tempfile = Tempfile.new "TempIO-#{@@count ++ 1}" + @tempfile.binmode + @tempfile.write string + @tempfile.rewind + end + + def method_missing(meth, *args, &block) + @tempfile.send(meth, *args, &block) + end + + def respond_to?(meth) + @tempfile.respond_to? meth + end + + def string + @tempfile.flush + + Gem.read_binary @tempfile.path + end + +end + diff --git a/test/rubygems/mockgemui.rb b/test/rubygems/mockgemui.rb index d9bc2a8134..95a95fbf98 100644 --- a/test/rubygems/mockgemui.rb +++ b/test/rubygems/mockgemui.rb @@ -15,9 +15,8 @@ class MockGemUi < Gem::StreamUI def initialize(input="") super(StringIO.new(input), StringIO.new, StringIO.new) @terminated = false - @banged = false end - + def input @ins.string end @@ -30,22 +29,15 @@ class MockGemUi < Gem::StreamUI @errs.string end - def banged? - @banged - end - def terminated? @terminated end - def terminate_interaction!(status=1) - @terminated = true - @banged = true - fail TermError - end - def terminate_interaction(status=0) @terminated = true - fail TermError + + raise TermError end + end + diff --git a/test/rubygems/private_key.pem b/test/rubygems/private_key.pem new file mode 100644 index 0000000000..95b3dc76d8 --- /dev/null +++ b/test/rubygems/private_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAm24C6xixiAxO+i1f3L8XRMwrmLkt6BvT60mZ7g8HsklH3af7 +KNHA6vo/G6sujs2UsNO4HY8BTEneiVOXXWQlcsJ+Z5wEPlIu4zFueAmLefx+n9lE +ulNIUDoyUenKX4spoMRnX8k4lXL05ho/6JFq0JdDY2DmAaQ4vvTz5mh9kZiybtHQ +fzcpbA51uY+sjdQRCPDHyUUfh0SmWJlLYMwcBdVeCiGUPBLi+iP5x1btO4uiJK6Q +IMaV1H3SUCYtKGQKl7qwFd8k8ZBcHYOtmK61tupg3vqWQc0em6SxPj5lws8+1MVK +twBNIDx24jF4ntxBRNKMZ7FN5SHbobAgDYkPAQIDAQABAoIBAGQilgK8X/PUajVH +clEXU3hhSV0VQHwfIYKeYms6h6zXBVPKW0dLC0zXeDztJgueasMZQ67XaPCrTpGO +px/l2zJ6F1HM8/bqn4aDXDY9f/xRLYryQRMBgL8fHzgitNylHWaT4j2Vt7yg2SI9 +mxrMRNKqASJPVR+Nm3l6+n9gpjVb99wEucWplPPHI6KhXLYPZOqSwt+zaH5roz3k +UQmMs0Bs4hF1SzVl0n+KNoXHOwswVrmBWXgWvm2OhnwY2e26jfejc8toJc/ShAJ7 +C9exnrdimcgEKbd22Sum4G00CDYhcrG5LHHqkgwifcAEVctrvBZBZHGgpxlO8a8U +eF2Vr7kCgYEAykdrBlzp7Fn9xzUInBQ3NXTTYAq51lpuJdmHQmPuTSY0buoHkd9f +xbUCZ2qR9QAesrx4hI0qGLetc8IOKDoWx2rPepCCvO3Kx61o1SB5fAvBue03qVoq +HqACX3Uk24Em8zAz9xuP13ETH/wU7sUbUxRHMCre6ZDmlxn4g5l+Nl8CgYEAxLVl +22yBx0dfRr3UsHY9rxll2gIlnfnYfiJzq8wetzt/TfztRV5ILz7FyWqL5d7IoqkA +fT2V4HAasRJASnKohwJe7z5M/H2ExwkGNFvY+jefb2CoUl5WouK9AlhbqBk3zmHi +sY5GqQkAp/kHMntEin+sErJw6mkgAGdser3a9p8CgYEAqi31w++tunRnxw4+RRnY +7Pdx0k6T1NxV6TAe1ONAHNY0rM/mOHqml65W7GzDiU1lhlh8SIB/VzZJDqfHw15D +xdh94A7uf0bMILwrA4wDyTIW9Xa3Kpq57vQNqwPiU25QN69pOM+Ob+IpBfLOJafc ++kOINOUMj5Kh/aQS6Zzci58CgYEAk24dlFKEBjbRCvU2FrfYTYcsljPru7ZJc2gg +588J6m0WYf5CWy5pzbcviGFpzvSlzXv7GOLylQ+QgcxbETFUbDPzsT4xd0AgJwj1 +dIKuYgMUZOa94VZBer2TydEtiRS1heJJhKhM/1329u4nXceTvHYqIq1JAfeee48I +eAoZtaMCgYBz1FjWFQnMTD5nmyPEEZneoBPAR5+9jwOps+IYOoHtazoMFszzd0qo +JZW3Ihn9KRrVSxfFApKS/ZwjiZ+tJUk7DE/v/0l0sszefY7s8b0pL1lpeZSoL71e +QoG1WLXUiDV3BRlmyOAF1h3p12KRTLgwubN51ajECwcs3QwE+ZT8Gg== +-----END RSA PRIVATE KEY----- diff --git a/test/rubygems/public_cert.pem b/test/rubygems/public_cert.pem new file mode 100644 index 0000000000..9b7c3d8e98 --- /dev/null +++ b/test/rubygems/public_cert.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDNjCCAh6gAwIBAgIBADANBgkqhkiG9w0BAQUFADBBMRAwDgYDVQQDDAdkcmJy +YWluMRgwFgYKCZImiZPyLGQBGRYIc2VnbWVudDcxEzARBgoJkiaJk/IsZAEZFgNu +ZXQwHhcNMDcxMjIxMDIwNDE0WhcNMDgxMjIwMDIwNDE0WjBBMRAwDgYDVQQDDAdk +cmJyYWluMRgwFgYKCZImiZPyLGQBGRYIc2VnbWVudDcxEzARBgoJkiaJk/IsZAEZ +FgNuZXQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCbbgLrGLGIDE76 +LV/cvxdEzCuYuS3oG9PrSZnuDweySUfdp/so0cDq+j8bqy6OzZSw07gdjwFMSd6J +U5ddZCVywn5nnAQ+Ui7jMW54CYt5/H6f2US6U0hQOjJR6cpfiymgxGdfyTiVcvTm +Gj/okWrQl0NjYOYBpDi+9PPmaH2RmLJu0dB/NylsDnW5j6yN1BEI8MfJRR+HRKZY +mUtgzBwF1V4KIZQ8EuL6I/nHVu07i6IkrpAgxpXUfdJQJi0oZAqXurAV3yTxkFwd +g62YrrW26mDe+pZBzR6bpLE+PmXCzz7UxUq3AE0gPHbiMXie3EFE0oxnsU3lIduh +sCANiQ8BAgMBAAGjOTA3MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQW +BBS5k4Z75VSpdM0AclG2UvzFA/VW5DANBgkqhkiG9w0BAQUFAAOCAQEAHagT4lfX +kP/hDaiwGct7XPuVGbrOsKRVD59FF5kETBxEc9UQ1clKWngf8JoVuEoKD774dW19 +bU0GOVWO+J6FMmT/Cp7nuFJ79egMf/gy4gfUfQMuvfcr6DvZUPIs9P/TlK59iMYF +DIOQ3DxdF3rMzztNUCizN4taVscEsjCcgW6WkUJnGdqlu3OHWpQxZBJkBTjPCoc6 +UW6on70SFPmAy/5Cq0OJNGEWBfgD9q7rrs/X8GGwUWqXb85RXnUVi/P8Up75E0ag +14jEc90kN+C7oI/AGCBN0j6JnEtYIEJZibjjDJTSMWlUKKkj30kq7hlUC2CepJ4v +x52qPcexcYZR7w== +-----END CERTIFICATE----- diff --git a/test/rubygems/test_gem.rb b/test/rubygems/test_gem.rb index e0160950f8..b04d69d509 100644 --- a/test/rubygems/test_gem.rb +++ b/test/rubygems/test_gem.rb @@ -19,6 +19,7 @@ class TestGem < RubyGemTestCase expected = [ File.join(@gemhome, *%W[gems #{@a1.full_name} lib]), File.join(@gemhome, *%W[gems #{@a2.full_name} lib]), + File.join(@gemhome, *%W[gems #{@a_evil9.full_name} lib]), File.join(@gemhome, *%W[gems #{@b2.full_name} lib]), File.join(@gemhome, *%W[gems #{@c1_2.full_name} lib]), File.join(@gemhome, *%W[gems #{@pl1.full_name} lib]), @@ -213,6 +214,7 @@ class TestGem < RubyGemTestCase expected = [ File.join(@gemhome, *%W[gems #{@a2.full_name} lib]), + File.join(@gemhome, *%W[gems #{@a_evil9.full_name} lib]), File.join(@gemhome, *%W[gems #{@b2.full_name} lib]), File.join(@gemhome, *%W[gems #{@c1_2.full_name} lib]), File.join(@gemhome, *%W[gems #{@pl1.full_name} lib]), @@ -226,7 +228,7 @@ class TestGem < RubyGemTestCase install_gem foo Gem.source_index = nil - Gem.activate 'foo', false + Gem.activate 'foo' assert_equal true, Gem.loaded_specs.keys.include?('foo') end @@ -235,9 +237,29 @@ class TestGem < RubyGemTestCase assert_equal [Gem.dir], Gem.path end + def test_self_path_APPLE_GEM_HOME + Gem.clear_paths + Gem.const_set :APPLE_GEM_HOME, '/tmp/apple_gem_home' + + assert Gem.path.include?('/tmp/apple_gem_home') + ensure + Gem.send :remove_const, :APPLE_GEM_HOME + end + + def test_self_path_APPLE_GEM_HOME_GEM_PATH + Gem.clear_paths + ENV['GEM_PATH'] = @gemhome + Gem.const_set :APPLE_GEM_HOME, '/tmp/apple_gem_home' + + assert !Gem.path.include?('/tmp/apple_gem_home') + ensure + Gem.send :remove_const, :APPLE_GEM_HOME + end + def test_self_path_ENV_PATH Gem.clear_paths path_count = Gem.path.size + path_count -= 1 if defined? APPLE_GEM_HOME Gem.clear_paths util_ensure_gem_dirs @@ -257,8 +279,8 @@ class TestGem < RubyGemTestCase ENV['GEM_PATH'] = dirs.join File::PATH_SEPARATOR assert_equal @gemhome, Gem.dir + paths = [Gem.dir] - paths << APPLE_GEM_HOME if defined? APPLE_GEM_HOME assert_equal @additional + paths, Gem.path end @@ -270,8 +292,8 @@ class TestGem < RubyGemTestCase ENV['GEM_PATH'] = @additional.join(File::PATH_SEPARATOR) assert_equal @gemhome, Gem.dir + paths = [Gem.dir] - paths.insert(0, APPLE_GEM_HOME) if defined? APPLE_GEM_HOME assert_equal @additional + paths, Gem.path end @@ -284,6 +306,18 @@ class TestGem < RubyGemTestCase assert_equal File.dirname(File.dirname(file_name)), Gem.prefix end + def test_self_prefix_odd + orig_sitelibdir = Gem::ConfigMap[:sitelibdir] + + file_name = File.expand_path __FILE__ + prefix = File.join File.dirname(File.dirname(file_name)), 'lib' + Gem::ConfigMap[:sitelibdir] = prefix.sub(/[\w]\//, '\&/') + + assert_nil Gem.prefix + ensure + Gem::ConfigMap[:sitelibdir] = orig_sitelibdir + end + def test_self_required_location util_make_gems @@ -295,6 +329,13 @@ class TestGem < RubyGemTestCase Gem.required_location("a", "code.rb", "= 2") end + def test_self_ruby_version + version = RUBY_VERSION.dup + version << ".#{RUBY_PATCHLEVEL}" if defined? RUBY_PATCHLEVEL + + assert_equal Gem::Version.new(version), Gem.ruby_version + end + def test_self_searcher assert_kind_of Gem::GemPathSearcher, Gem.searcher end diff --git a/test/rubygems/test_gem_command_manager.rb b/test/rubygems/test_gem_command_manager.rb index 4198bb9a2a..b7767f421d 100644 --- a/test/rubygems/test_gem_command_manager.rb +++ b/test/rubygems/test_gem_command_manager.rb @@ -67,11 +67,12 @@ class TestGemCommandManager < RubyGemTestCase assert_equal true, check_options[:wrappers] assert_equal Gem::Requirement.default, check_options[:version] assert_equal Gem.dir, check_options[:install_dir] + assert_equal nil, check_options[:bin_dir] #check settings check_options = nil @command_manager.process_args( - "install --force --test --local --rdoc --install-dir . --version 3.0 --no-wrapper") + "install --force --test --local --rdoc --install-dir . --version 3.0 --no-wrapper --bindir . ") assert_equal true, check_options[:test] assert_equal true, check_options[:generate_rdoc] assert_equal true, check_options[:force] @@ -79,6 +80,7 @@ class TestGemCommandManager < RubyGemTestCase assert_equal false, check_options[:wrappers] assert_equal Gem::Requirement.new('3.0'), check_options[:version] assert_equal Dir.pwd, check_options[:install_dir] + assert_equal Dir.pwd, check_options[:bin_dir] #check remote domain check_options = nil @@ -164,7 +166,7 @@ class TestGemCommandManager < RubyGemTestCase #check defaults @command_manager.process_args("query") - assert_equal(/.*/, check_options[:name]) + assert_equal(//, check_options[:name]) assert_equal :local, check_options[:domain] assert_equal false, check_options[:details] diff --git a/test/rubygems/test_gem_commands_environment_command.rb b/test/rubygems/test_gem_commands_environment_command.rb index 0913888a6d..5568478652 100644 --- a/test/rubygems/test_gem_commands_environment_command.rb +++ b/test/rubygems/test_gem_commands_environment_command.rb @@ -62,6 +62,21 @@ class TestGemCommandsEnvironmentCommand < RubyGemTestCase assert_equal '', @ui.error end + def test_execute_gempath_multiple + Gem.clear_paths + path = [@gemhome, "#{@gemhome}2"].join File::PATH_SEPARATOR + ENV['GEM_PATH'] = path + + @cmd.send :handle_options, %w[gempath] + + use_ui @ui do + @cmd.execute + end + + assert_equal "#{Gem.path.join File::PATH_SEPARATOR}\n", @ui.output + assert_equal '', @ui.error + end + def test_execute_packageversion @cmd.send :handle_options, %w[packageversion] diff --git a/test/rubygems/test_gem_commands_fetch_command.rb b/test/rubygems/test_gem_commands_fetch_command.rb index d8651680b0..5a42e4e81e 100644 --- a/test/rubygems/test_gem_commands_fetch_command.rb +++ b/test/rubygems/test_gem_commands_fetch_command.rb @@ -15,13 +15,12 @@ class TestGemCommandsFetchCommand < RubyGemTestCase def test_execute util_setup_fake_fetcher - util_build_gem @gem1 @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = @source_index.dump - @fetcher.data["#{@gem_repo}/gems/#{@gem1.full_name}.gem"] = - File.read(File.join(@gemhome, 'cache', "#{@gem1.full_name}.gem")) + @fetcher.data["#{@gem_repo}/gems/#{@a2.full_name}.gem"] = + File.read(File.join(@gemhome, 'cache', "#{@a2.full_name}.gem")) - @cmd.options[:args] = [@gem1.name] + @cmd.options[:args] = [@a2.name] use_ui @ui do Dir.chdir @tempdir do @@ -29,7 +28,7 @@ class TestGemCommandsFetchCommand < RubyGemTestCase end end - assert File.exist?(File.join(@tempdir, "#{@gem1.full_name}.gem")) + assert File.exist?(File.join(@tempdir, "#{@a2.full_name}.gem")) end end diff --git a/test/rubygems/test_gem_commands_install_command.rb b/test/rubygems/test_gem_commands_install_command.rb index 78840be8c6..101195a43e 100644 --- a/test/rubygems/test_gem_commands_install_command.rb +++ b/test/rubygems/test_gem_commands_install_command.rb @@ -34,25 +34,26 @@ class TestGemCommandsInstallCommand < RubyGemTestCase util_setup_fake_fetcher @cmd.options[:domain] = :local - gem1 = quick_gem 'gem_one' - util_build_gem gem1 - FileUtils.mv File.join(@gemhome, 'cache', "#{@gem1.full_name}.gem"), + FileUtils.mv File.join(@gemhome, 'cache', "#{@a2.full_name}.gem"), File.join(@tempdir) - @cmd.options[:args] = [gem1.name] + @cmd.options[:args] = [@a2.name] use_ui @ui do orig_dir = Dir.pwd begin Dir.chdir @tempdir - @cmd.execute + e = assert_raises Gem::SystemExitException do + @cmd.execute + end + assert_equal 0, e.exit_code ensure Dir.chdir orig_dir end end out = @ui.output.split "\n" - assert_equal "Successfully installed #{@gem1.full_name}", out.shift + assert_equal "Successfully installed #{@a2.full_name}", out.shift assert_equal "1 gem installed", out.shift assert out.empty?, out.inspect end @@ -61,14 +62,17 @@ class TestGemCommandsInstallCommand < RubyGemTestCase util_setup_fake_fetcher @cmd.options[:domain] = :local - @cmd.options[:args] = %w[gem_one] + @cmd.options[:args] = %w[no_such_gem] use_ui @ui do - @cmd.execute + e = assert_raises Gem::SystemExitException do + @cmd.execute + end + assert_equal 2, e.exit_code end # HACK no repository was checked - assert_equal "ERROR: could not find gem_one locally or in a repository\n", + assert_equal "ERROR: could not find no_such_gem locally or in a repository\n", @ui.error end @@ -88,7 +92,10 @@ class TestGemCommandsInstallCommand < RubyGemTestCase @cmd.options[:args] = %w[nonexistent] use_ui @ui do - @cmd.execute + e = assert_raises Gem::SystemExitException do + @cmd.execute + end + assert_equal 2, e.exit_code end assert_equal "ERROR: could not find nonexistent locally or in a repository\n", @@ -100,25 +107,27 @@ class TestGemCommandsInstallCommand < RubyGemTestCase @cmd.options[:generate_ri] = true util_setup_fake_fetcher - util_build_gem @gem1 @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = @source_index.dump - @fetcher.data["#{@gem_repo}/gems/gem_one-0.0.2.gem"] = - File.read(File.join(@gemhome, 'cache', "#{@gem1.full_name}.gem")) + @fetcher.data["#{@gem_repo}/gems/#{@a2.full_name}.gem"] = + read_binary(File.join(@gemhome, 'cache', "#{@a2.full_name}.gem")) - @cmd.options[:args] = [@gem1.name] + @cmd.options[:args] = [@a2.name] use_ui @ui do - @cmd.execute + e = assert_raises Gem::SystemExitException do + @cmd.execute + end + assert_equal 0, e.exit_code end out = @ui.output.split "\n" assert_match %r|Bulk updating|, out.shift - assert_equal "Successfully installed #{@gem1.full_name}", out.shift + assert_equal "Successfully installed #{@a2.full_name}", out.shift assert_equal "1 gem installed", out.shift - assert_equal "Installing ri documentation for #{@gem1.full_name}...", + assert_equal "Installing ri documentation for #{@a2.full_name}...", out.shift - assert_equal "Installing RDoc documentation for #{@gem1.full_name}...", + assert_equal "Installing RDoc documentation for #{@a2.full_name}...", out.shift assert out.empty?, out.inspect end @@ -127,31 +136,30 @@ class TestGemCommandsInstallCommand < RubyGemTestCase util_setup_fake_fetcher @cmd.options[:domain] = :local - gem1 = quick_gem 'gem_one' - util_build_gem gem1 - FileUtils.mv File.join(@gemhome, 'cache', "#{@gem1.full_name}.gem"), + FileUtils.mv File.join(@gemhome, 'cache', "#{@a2.full_name}.gem"), File.join(@tempdir) - gem2 = quick_gem 'gem_two' - util_build_gem gem2 - FileUtils.mv File.join(@gemhome, 'cache', "#{@gem2.full_name}.gem"), + FileUtils.mv File.join(@gemhome, 'cache', "#{@b2.full_name}.gem"), File.join(@tempdir) - @cmd.options[:args] = [gem1.name, gem2.name] + @cmd.options[:args] = [@a2.name, @b2.name] use_ui @ui do orig_dir = Dir.pwd begin Dir.chdir @tempdir - @cmd.execute + e = assert_raises Gem::SystemExitException do + @cmd.execute + end + assert_equal 0, e.exit_code ensure Dir.chdir orig_dir end end out = @ui.output.split "\n" - assert_equal "Successfully installed #{@gem1.full_name}", out.shift - assert_equal "Successfully installed #{@gem2.full_name}", out.shift + assert_equal "Successfully installed #{@a2.full_name}", out.shift + assert_equal "Successfully installed #{@b2.full_name}", out.shift assert_equal "2 gems installed", out.shift assert out.empty?, out.inspect end diff --git a/test/rubygems/test_gem_commands_query_command.rb b/test/rubygems/test_gem_commands_query_command.rb index 4430ccfd33..7e71419089 100644 --- a/test/rubygems/test_gem_commands_query_command.rb +++ b/test/rubygems/test_gem_commands_query_command.rb @@ -7,20 +7,31 @@ class TestGemCommandsQueryCommand < RubyGemTestCase def setup super - @foo_gem = quick_gem 'foo' do |spec| - spec.summary = 'This is a lot of text. ' * 5 - end - @foo_gem_p = quick_gem 'foo' do |spec| - spec.summary = 'This is a lot of text. ' * 5 - spec.platform = Gem::Platform::CURRENT - end - @bar_gem = quick_gem 'bar' + util_make_gems + + @a2.summary = 'This is a lot of text. ' * 4 @cmd = Gem::Commands::QueryCommand.new + + @si = util_setup_source_info_cache @a1, @a2, @pl1 + util_setup_fake_fetcher + + @fetcher.data["#{@gem_repo}/Marshal.#{Gem.marshal_version}"] = proc do + raise Gem::RemoteFetcher::FetchError + end end def test_execute - util_setup_source_info_cache @foo_gem, @foo_gem_p + cache = Gem::SourceInfoCache.cache + cache.update + cache.write_cache + cache.reset_cache_data + + a2_name = @a2.full_name + @fetcher.data["#{@gem_repo}/quick/latest_index.rz"] = util_zip a2_name + @fetcher.data["#{@gem_repo}/quick/Marshal.#{Gem.marshal_version}/#{a2_name}.gemspec.rz"] = util_zip Marshal.dump(@a2) + @fetcher.data["#{@gem_repo}/Marshal.#{Gem.marshal_version}"] = + Marshal.dump @si @cmd.handle_options %w[-r] @@ -32,16 +43,50 @@ class TestGemCommandsQueryCommand < RubyGemTestCase *** REMOTE GEMS *** -foo (2) +a (2) EOF assert_equal expected, @ui.output assert_equal '', @ui.error end - def test_execute_details - util_setup_source_info_cache @foo_gem + def test_execute_all + cache = Gem::SourceInfoCache.cache + cache.update + cache.write_cache + cache.reset_cache_data + + a1_name = @a1.full_name + a2_name = @a2.full_name + @fetcher.data["#{@gem_repo}/quick/index.rz"] = + util_zip [a1_name, a2_name].join("\n") + @fetcher.data["#{@gem_repo}/quick/latest_index.rz"] = util_zip a2_name + @fetcher.data["#{@gem_repo}/quick/Marshal.#{Gem.marshal_version}/#{a1_name}.gemspec.rz"] = util_zip Marshal.dump(@a1) + @fetcher.data["#{@gem_repo}/quick/Marshal.#{Gem.marshal_version}/#{a2_name}.gemspec.rz"] = util_zip Marshal.dump(@a2) + @fetcher.data["#{@gem_repo}/Marshal.#{Gem.marshal_version}"] = + Marshal.dump @si + + @cmd.handle_options %w[-r --all] + + use_ui @ui do + @cmd.execute + end + + expected = <<-EOF +*** REMOTE GEMS *** + +Updating metadata for 1 gems from http://gems.example.com/ +. +complete +a (2, 1) + EOF + + assert_equal expected, @ui.output + assert_equal '', @ui.error + end + + def test_execute_details @cmd.handle_options %w[-r -d] use_ui @ui do @@ -52,18 +97,94 @@ foo (2) *** REMOTE GEMS *** -foo (2) - This is a lot of text. This is a lot of text. This is a lot of - text. This is a lot of text. This is a lot of text. +a (2, 1) + This is a lot of text. This is a lot of text. This is a lot of text. + This is a lot of text. + +pl (1) + this is a summary EOF assert_equal expected, @ui.output assert_equal '', @ui.error end - def test_execute_no_versions - util_setup_source_info_cache @foo_gem, @bar_gem + def test_execute_installed + @cmd.handle_options %w[-n c --installed] + e = assert_raise Gem::SystemExitException do + use_ui @ui do + @cmd.execute + end + end + + assert_equal 0, e.exit_code + + assert_equal "true\n", @ui.output + assert_equal '', @ui.error + end + + def test_execute_installed_no_name + @cmd.handle_options %w[--installed] + + e = assert_raise Gem::SystemExitException do + use_ui @ui do + @cmd.execute + end + end + + assert_equal '', @ui.output + assert_equal "ERROR: You must specify a gem name\n", @ui.error + + assert_equal 4, e.exit_code + end + + def test_execute_installed_not_installed + @cmd.handle_options %w[-n not_installed --installed] + + e = assert_raise Gem::SystemExitException do + use_ui @ui do + @cmd.execute + end + end + + assert_equal "false\n", @ui.output + assert_equal '', @ui.error + + assert_equal 1, e.exit_code + end + + def test_execute_installed_version + @cmd.handle_options %w[-n c --installed --version 1.2] + + e = assert_raise Gem::SystemExitException do + use_ui @ui do + @cmd.execute + end + end + + assert_equal "true\n", @ui.output + assert_equal '', @ui.error + + assert_equal 0, e.exit_code + end + + def test_execute_installed_version_not_installed + @cmd.handle_options %w[-n c --installed --version 2] + + e = assert_raise Gem::SystemExitException do + use_ui @ui do + @cmd.execute + end + end + + assert_equal "false\n", @ui.output + assert_equal '', @ui.error + + assert_equal 1, e.exit_code + end + + def test_execute_no_versions @cmd.handle_options %w[-r --no-versions] use_ui @ui do @@ -74,8 +195,8 @@ foo (2) *** REMOTE GEMS *** -bar -foo +a +pl EOF assert_equal expected, @ui.output diff --git a/test/rubygems/test_gem_commands_server_command.rb b/test/rubygems/test_gem_commands_server_command.rb index 3e6af2e11c..2985b036d8 100644 --- a/test/rubygems/test_gem_commands_server_command.rb +++ b/test/rubygems/test_gem_commands_server_command.rb @@ -20,7 +20,7 @@ class TestGemCommandsServerCommand < RubyGemTestCase @cmd.send :handle_options, %w[-p 9999 -d /nonexistent --daemon] assert_equal true, @cmd.options[:daemon] - assert_equal '/nonexistent', @cmd.options[:gemdir] + assert_equal File.expand_path('/nonexistent'), @cmd.options[:gemdir] assert_equal 9999, @cmd.options[:port] end end diff --git a/test/rubygems/test_gem_commands_sources_command.rb b/test/rubygems/test_gem_commands_sources_command.rb index 3d1ab801b1..7ba88fad98 100644 --- a/test/rubygems/test_gem_commands_sources_command.rb +++ b/test/rubygems/test_gem_commands_sources_command.rb @@ -31,10 +31,11 @@ class TestGemCommandsSourcesCommand < RubyGemTestCase def test_execute_add util_setup_fake_fetcher - @si = Gem::SourceIndex.new @gem1.full_name => @gem1.name + si = Gem::SourceIndex.new + si.add_spec @a1 @fetcher.data["http://beta-gems.example.com/Marshal.#{@marshal_version}"] = - @si.dump + si.dump @cmd.handle_options %w[--add http://beta-gems.example.com] @@ -45,7 +46,7 @@ class TestGemCommandsSourcesCommand < RubyGemTestCase end expected = <<-EOF -Bulk updating Gem source index for: http://beta-gems.example.com +Bulk updating Gem source index for: http://beta-gems.example.com/ http://beta-gems.example.com added to sources EOF @@ -60,14 +61,11 @@ http://beta-gems.example.com added to sources def test_execute_add_nonexistent_source util_setup_fake_fetcher - @si = Gem::SourceIndex.new @gem1.full_name => @gem1.name - @fetcher.data["http://beta-gems.example.com/Marshal.#{@marshal_version}"] = proc do raise Gem::RemoteFetcher::FetchError, 'it died' end - Gem::RemoteFetcher.instance_variable_set :@fetcher, @fetcher @cmd.handle_options %w[--add http://beta-gems.example.com] @@ -104,6 +102,41 @@ beta-gems.example.com is not a URI assert_equal '', @ui.error end + def test_execute_clear_all + @cmd.handle_options %w[--clear-all] + + util_setup_source_info_cache + + cache = Gem::SourceInfoCache.cache + cache.update + cache.write_cache + + assert File.exist?(cache.system_cache_file), + 'system cache file' + assert File.exist?(cache.latest_system_cache_file), + 'latest system cache file' + + use_ui @ui do + @cmd.execute + end + + expected = <<-EOF +*** Removed user source cache *** +*** Removed latest user source cache *** +*** Removed system source cache *** +*** Removed latest system source cache *** + EOF + + assert_equal expected, @ui.output + assert_equal '', @ui.error + + assert !File.exist?(cache.system_cache_file), + 'system cache file' + assert !File.exist?(cache.latest_system_cache_file), + 'latest system cache file' + + end + def test_execute_remove @cmd.handle_options %W[--remove #{@gem_repo}] @@ -122,20 +155,43 @@ beta-gems.example.com is not a URI assert_equal [], Gem::SourceInfoCache.cache_data.keys end + def test_execute_remove_no_network + @cmd.handle_options %W[--remove #{@gem_repo}] + + util_setup_fake_fetcher + + @fetcher.data["#{@gem_repo}/Marshal.#{Gem.marshal_version}"] = proc do + raise Gem::RemoteFetcher::FetchError + end + + use_ui @ui do + @cmd.execute + end + + expected = "#{@gem_repo} removed from sources\n" + + assert_equal expected, @ui.output + assert_equal '', @ui.error + + Gem::SourceInfoCache.cache.flush + assert_equal [], Gem::SourceInfoCache.cache_data.keys + end + def test_execute_update @cmd.handle_options %w[--update] util_setup_source_info_cache util_setup_fake_fetcher - @si = Gem::SourceIndex.new @gem1.full_name => @gem1.name - @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = @si.dump + si = Gem::SourceIndex.new + si.add_spec @a1 + @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = si.dump use_ui @ui do @cmd.execute end expected = <<-EOF -Bulk updating Gem source index for: #{@gem_repo} +Bulk updating Gem source index for: #{@gem_repo}/ source cache successfully updated EOF diff --git a/test/rubygems/test_gem_commands_specification_command.rb b/test/rubygems/test_gem_commands_specification_command.rb index c8f57fef58..f66f0c0d49 100644 --- a/test/rubygems/test_gem_commands_specification_command.rb +++ b/test/rubygems/test_gem_commands_specification_command.rb @@ -12,6 +12,7 @@ class TestGemCommandsSpecificationCommand < RubyGemTestCase def test_execute foo = quick_gem 'foo' + Gem.source_index.add_spec foo @cmd.options[:args] = %w[foo] @@ -87,7 +88,6 @@ class TestGemCommandsSpecificationCommand < RubyGemTestCase assert_match %r|\A--- !ruby/object:Gem::Specification|, @ui.output assert_match %r|name: foo|, @ui.output - assert_equal "WARNING: Remote information is not complete\n\n", @ui.error end end diff --git a/test/rubygems/test_gem_commands_unpack_command.rb b/test/rubygems/test_gem_commands_unpack_command.rb index 427f9403c3..3a62a914a4 100644 --- a/test/rubygems/test_gem_commands_unpack_command.rb +++ b/test/rubygems/test_gem_commands_unpack_command.rb @@ -17,15 +17,57 @@ class TestGemCommandsUnpackCommand < RubyGemTestCase @cmd.options[:args] = %w[a] - use_ui @ui do - Dir.chdir @tempdir do - @cmd.execute - end + use_ui @ui do + Dir.chdir @tempdir do + @cmd.execute + end + end + + assert File.exist?(File.join(@tempdir, 'a-2')) + end + + def test_execute_gem_path + util_make_gems + + Gem.clear_paths + + gemhome2 = File.join @tempdir, 'gemhome2' + + Gem.send :set_paths, [gemhome2, @gemhome].join(File::PATH_SEPARATOR) + Gem.send :set_home, gemhome2 + + @cmd.options[:args] = %w[a] + + use_ui @ui do + Dir.chdir @tempdir do + @cmd.execute end + end assert File.exist?(File.join(@tempdir, 'a-2')) end + def test_execute_gem_path_missing + util_make_gems + + Gem.clear_paths + + gemhome2 = File.join @tempdir, 'gemhome2' + + Gem.send :set_paths, [gemhome2, @gemhome].join(File::PATH_SEPARATOR) + Gem.send :set_home, gemhome2 + + @cmd.options[:args] = %w[z] + + use_ui @ui do + Dir.chdir @tempdir do + @cmd.execute + end + end + + assert_equal '', @ui.output + end + def test_execute_with_target_option util_make_gems diff --git a/test/rubygems/test_gem_commands_update_command.rb b/test/rubygems/test_gem_commands_update_command.rb new file mode 100644 index 0000000000..1fcec6a075 --- /dev/null +++ b/test/rubygems/test_gem_commands_update_command.rb @@ -0,0 +1,174 @@ +require 'test/unit' +require File.join(File.expand_path(File.dirname(__FILE__)), 'gemutilities') +require 'rubygems/commands/update_command' + +class TestGemCommandsUpdateCommand < RubyGemTestCase + + def setup + super + + @cmd = Gem::Commands::UpdateCommand.new + + util_setup_fake_fetcher + + @a1_path = File.join @gemhome, 'cache', "#{@a1.full_name}.gem" + @a2_path = File.join @gemhome, 'cache', "#{@a2.full_name}.gem" + + @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = + @source_index.dump + @fetcher.data["#{@gem_repo}/gems/#{@a1.full_name}.gem"] = read_binary @a1_path + @fetcher.data["#{@gem_repo}/gems/#{@a2.full_name}.gem"] = read_binary @a2_path + end + + def test_execute + util_remove_gems + + Gem::Installer.new(@a1_path).install + + @cmd.options[:args] = [] + + use_ui @ui do + @cmd.execute + end + + out = @ui.output.split "\n" + assert_equal "Updating installed gems", out.shift + assert_match %r|Bulk updating|, out.shift + assert_equal "Updating #{@a2.name}", out.shift + assert_equal "Successfully installed #{@a2.full_name}", out.shift + assert_equal "Gems updated: #{@a2.name}", out.shift + + assert out.empty?, out.inspect + end + + # before: + # a1 -> c1.2 + # after: + # a2 -> b2 # new dependency + # a2 -> c2 + + def test_execute_dependencies + @a1.add_dependency 'c', '1.2' + + @c2 = quick_gem 'c', '2' do |s| + s.files = %w[lib/code.rb] + s.require_paths = %w[lib] + end + + @a2.add_dependency 'c', '2' + @a2.add_dependency 'b', '2' + + @b2_path = File.join @gemhome, 'cache', "#{@b2.full_name}.gem" + @c1_2_path = File.join @gemhome, 'cache', "#{@c1_2.full_name}.gem" + @c2_path = File.join @gemhome, 'cache', "#{@c2.full_name}.gem" + + @source_index = Gem::SourceIndex.new + @source_index.add_spec @a1 + @source_index.add_spec @a2 + @source_index.add_spec @b2 + @source_index.add_spec @c1_2 + @source_index.add_spec @c2 + + util_build_gem @a1 + util_build_gem @a2 + util_build_gem @c2 + + @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = + @source_index.dump + @fetcher.data["#{@gem_repo}/gems/#{@a1.full_name}.gem"] = read_binary @a1_path + @fetcher.data["#{@gem_repo}/gems/#{@a2.full_name}.gem"] = read_binary @a2_path + @fetcher.data["#{@gem_repo}/gems/#{@b2.full_name}.gem"] = read_binary @b2_path + @fetcher.data["#{@gem_repo}/gems/#{@c1_2.full_name}.gem"] = + read_binary @c1_2_path + @fetcher.data["#{@gem_repo}/gems/#{@c2.full_name}.gem"] = read_binary @c2_path + + util_remove_gems + + Gem::Installer.new(@c1_2_path).install + Gem::Installer.new(@a1_path).install + + @cmd.options[:args] = [] + + use_ui @ui do + @cmd.execute + end + + out = @ui.output.split "\n" + assert_equal "Updating installed gems", out.shift + assert_match %r|Bulk updating|, out.shift + assert_equal "Updating #{@a2.name}", out.shift + assert_equal "Successfully installed #{@c2.full_name}", out.shift + assert_equal "Successfully installed #{@b2.full_name}", out.shift + assert_equal "Successfully installed #{@a2.full_name}", out.shift + assert_equal "Gems updated: #{@c2.name}, #{@b2.name}, #{@a2.name}", + out.shift + + assert out.empty?, out.inspect + end + + def test_execute_named + util_remove_gems + + Gem::Installer.new(@a1_path).install + + @cmd.options[:args] = [@a1.name] + + use_ui @ui do + @cmd.execute + end + + out = @ui.output.split "\n" + assert_equal "Updating installed gems", out.shift + assert_match %r|Bulk updating|, out.shift + assert_equal "Updating #{@a2.name}", out.shift + assert_equal "Successfully installed #{@a2.full_name}", out.shift + assert_equal "Gems updated: #{@a2.name}", out.shift + + assert out.empty?, out.inspect + end + + def test_execute_named_up_to_date + util_remove_gems + + Gem::Installer.new(@a2_path).install + + @cmd.options[:args] = [@a2.name] + + use_ui @ui do + @cmd.execute + end + + out = @ui.output.split "\n" + assert_equal "Updating installed gems", out.shift + assert_match %r|Bulk updating|, out.shift + assert_equal "Nothing to update", out.shift + + assert out.empty?, out.inspect + end + + def test_execute_up_to_date + util_remove_gems + + Gem::Installer.new(@a2_path).install + + @cmd.options[:args] = [] + + use_ui @ui do + @cmd.execute + end + + out = @ui.output.split "\n" + assert_equal "Updating installed gems", out.shift + assert_match %r|Bulk updating|, out.shift + assert_equal "Nothing to update", out.shift + + assert out.empty?, out.inspect + end + + def util_remove_gems + FileUtils.rm_r File.join(@gemhome, 'gems') + FileUtils.rm_r File.join(@gemhome, 'specifications') + end + +end + diff --git a/test/rubygems/test_gem_dependency_installer.rb b/test/rubygems/test_gem_dependency_installer.rb index d2d33fa313..576143904a 100644 --- a/test/rubygems/test_gem_dependency_installer.rb +++ b/test/rubygems/test_gem_dependency_installer.rb @@ -54,8 +54,8 @@ class TestGemDependencyInstaller < RubyGemTestCase inst = nil Dir.chdir @tempdir do - inst = Gem::DependencyInstaller.new 'a' - inst.install + inst = Gem::DependencyInstaller.new + inst.install 'a' end assert_equal Gem::SourceIndex.new(@a1.full_name => @a1), @@ -70,8 +70,8 @@ class TestGemDependencyInstaller < RubyGemTestCase inst = nil Dir.chdir @tempdir do - inst = Gem::DependencyInstaller.new 'b' - inst.install + inst = Gem::DependencyInstaller.new + inst.install 'b' end assert_equal %w[a-1 b-1], inst.installed_gems.map { |s| s.full_name } @@ -84,8 +84,8 @@ class TestGemDependencyInstaller < RubyGemTestCase inst = nil Dir.chdir @tempdir do - inst = Gem::DependencyInstaller.new 'b' - inst.install + inst = Gem::DependencyInstaller.new + inst.install 'b' end assert_equal %w[b-1], inst.installed_gems.map { |s| s.full_name } @@ -102,8 +102,8 @@ class TestGemDependencyInstaller < RubyGemTestCase inst = nil Dir.chdir @tempdir do - inst = Gem::DependencyInstaller.new 'f' - inst.install + inst = Gem::DependencyInstaller.new + inst.install 'f' end assert_equal %w[f-2], inst.installed_gems.map { |s| s.full_name } @@ -114,19 +114,49 @@ class TestGemDependencyInstaller < RubyGemTestCase inst = nil Dir.chdir @tempdir do - inst = Gem::DependencyInstaller.new 'a-1.gem' - inst.install + inst = Gem::DependencyInstaller.new :domain => :local + inst.install 'a-1.gem' end assert_equal %w[a-1], inst.installed_gems.map { |s| s.full_name } end + def test_install_local_dependency + FileUtils.mv @a1_gem, @tempdir + FileUtils.mv @b1_gem, @tempdir + + inst = nil + + Dir.chdir @tempdir do + inst = Gem::DependencyInstaller.new :domain => :local + inst.install 'b-1.gem' + end + + assert_equal %w[a-1 b-1], inst.installed_gems.map { |s| s.full_name } + end + + def test_install_local_dependency_installed + FileUtils.mv @a1_gem, @tempdir + FileUtils.mv @b1_gem, @tempdir + + inst = nil + + Dir.chdir @tempdir do + Gem::Installer.new('a-1.gem').install + + inst = Gem::DependencyInstaller.new :domain => :local + inst.install 'b-1.gem' + end + + assert_equal %w[b-1], inst.installed_gems.map { |s| s.full_name } + end + def test_install_local_subdir inst = nil - + Dir.chdir @tempdir do - inst = Gem::DependencyInstaller.new 'gems/a-1.gem' - inst.install + inst = Gem::DependencyInstaller.new :domain => :local + inst.install 'gems/a-1.gem' end assert_equal %w[a-1], inst.installed_gems.map { |s| s.full_name } @@ -137,12 +167,11 @@ class TestGemDependencyInstaller < RubyGemTestCase inst = nil Dir.chdir @tempdir do - inst = Gem::DependencyInstaller.new 'a', nil, :env_shebang => true, - :wrappers => true - inst.install + inst = Gem::DependencyInstaller.new :env_shebang => true, :wrappers => true + inst.install 'a' end - assert_match %r|\A#!/usr/bin/env ruby\n|, + assert_match %r|\A#!/usr/bin/env #{Gem::ConfigMap[:RUBY_INSTALL_NAME]}\n|, File.read(File.join(@gemhome, 'bin', 'a_bin')) end @@ -153,8 +182,8 @@ class TestGemDependencyInstaller < RubyGemTestCase inst = nil Dir.chdir @tempdir do - inst = Gem::DependencyInstaller.new 'b', nil, :force => true - inst.install + inst = Gem::DependencyInstaller.new :force => true + inst.install 'b' end assert_equal %w[b-1], inst.installed_gems.map { |s| s.full_name } @@ -165,8 +194,8 @@ class TestGemDependencyInstaller < RubyGemTestCase inst = nil Dir.chdir @tempdir do - inst = Gem::DependencyInstaller.new 'b', nil, :ignore_dependencies => true - inst.install + inst = Gem::DependencyInstaller.new :ignore_dependencies => true + inst.install 'b' end assert_equal %w[b-1], inst.installed_gems.map { |s| s.full_name } @@ -179,8 +208,8 @@ class TestGemDependencyInstaller < RubyGemTestCase inst = nil Dir.chdir @tempdir do - inst = Gem::DependencyInstaller.new 'a', nil, :install_dir => gemhome2 - inst.install + inst = Gem::DependencyInstaller.new :install_dir => gemhome2 + inst.install 'a' end assert_equal %w[a-1], inst.installed_gems.map { |s| s.full_name } @@ -201,8 +230,8 @@ class TestGemDependencyInstaller < RubyGemTestCase inst = nil Dir.chdir @tempdir do - inst = Gem::DependencyInstaller.new 'b', nil, :domain => :both - inst.install + inst = Gem::DependencyInstaller.new :domain => :both + inst.install 'b' end assert_equal %w[a-1 b-1], inst.installed_gems.map { |s| s.full_name } @@ -217,14 +246,34 @@ class TestGemDependencyInstaller < RubyGemTestCase assert_equal b1_expected, b1.loaded_from end + def test_install_domain_both_no_network + Gem::SourceInfoCache.instance_variable_set :@cache, nil + + @fetcher.data["http://gems.example.com/gems/Marshal.#{@marshal_version}"] = + proc do + raise Gem::RemoteFetcher::FetchError + end + + FileUtils.mv @a1_gem, @tempdir + FileUtils.mv @b1_gem, @tempdir + inst = nil + + Dir.chdir @tempdir do + inst = Gem::DependencyInstaller.new :domain => :both + inst.install 'b' + end + + assert_equal %w[a-1 b-1], inst.installed_gems.map { |s| s.full_name } + end + def test_install_domain_local FileUtils.mv @b1_gem, @tempdir inst = nil Dir.chdir @tempdir do e = assert_raise Gem::InstallError do - inst = Gem::DependencyInstaller.new 'b', nil, :domain => :local - inst.install + inst = Gem::DependencyInstaller.new :domain => :local + inst.install 'b' end assert_equal 'b requires a (>= 0)', e.message end @@ -240,8 +289,43 @@ class TestGemDependencyInstaller < RubyGemTestCase @fetcher.data['http://gems.example.com/gems/a-1.gem'] = a1_data - inst = Gem::DependencyInstaller.new 'a', nil, :domain => :remote - inst.install + inst = Gem::DependencyInstaller.new :domain => :remote + inst.install 'a' + + assert_equal %w[a-1], inst.installed_gems.map { |s| s.full_name } + end + + def test_install_remote + a1_data = nil + File.open @a1_gem, 'rb' do |fp| + a1_data = fp.read + end + + @fetcher.data['http://gems.example.com/gems/a-1.gem'] = a1_data + + inst = Gem::DependencyInstaller.new + + Dir.chdir @tempdir do + inst.install 'a' + end + + assert_equal %w[a-1], inst.installed_gems.map { |s| s.full_name } + end + + def test_install_remote_dep + a1_data = nil + File.open @a1_gem, 'rb' do |fp| + a1_data = fp.read + end + + @fetcher.data['http://gems.example.com/gems/a-1.gem'] = a1_data + + inst = Gem::DependencyInstaller.new + + Dir.chdir @tempdir do + dep = Gem::Dependency.new @a1.name, @a1.version + inst.install dep + end assert_equal %w[a-1], inst.installed_gems.map { |s| s.full_name } end @@ -266,8 +350,8 @@ class TestGemDependencyInstaller < RubyGemTestCase @fetcher.data["http://gems.example.com/gems/#{a2_o.full_name}.gem"] = a2_o_data - inst = Gem::DependencyInstaller.new 'a', nil, :domain => :remote - inst.install + inst = Gem::DependencyInstaller.new :domain => :remote + inst.install 'a' assert_equal %w[a-1], inst.installed_gems.map { |s| s.full_name } end @@ -278,8 +362,8 @@ class TestGemDependencyInstaller < RubyGemTestCase inst = nil Dir.chdir @tempdir do - inst = Gem::DependencyInstaller.new 'a' - inst.install + inst = Gem::DependencyInstaller.new + inst.install 'a' end assert_equal Gem::SourceIndex.new(@a1.full_name => @a1), @@ -290,13 +374,17 @@ class TestGemDependencyInstaller < RubyGemTestCase if defined? OpenSSL then def test_install_security_policy - FileUtils.mv @a1_gem, @cache_dir - FileUtils.mv @b1_gem, @cache_dir + data = File.open(@a1_gem, 'rb') { |f| f.read } + @fetcher.data['http://gems.example.com/gems/a-1.gem'] = data + + data = File.open(@b1_gem, 'rb') { |f| f.read } + @fetcher.data['http://gems.example.com/gems/b-1.gem'] = data + policy = Gem::Security::HighSecurity - inst = Gem::DependencyInstaller.new 'b', nil, :security_policy => policy + inst = Gem::DependencyInstaller.new :security_policy => policy e = assert_raise Gem::Exception do - inst.install + inst.install 'b' end assert_equal 'Unsigned gem', e.message @@ -305,145 +393,48 @@ class TestGemDependencyInstaller < RubyGemTestCase end end - def test_install_wrappers - FileUtils.mv @a1_gem, @cache_dir - inst = Gem::DependencyInstaller.new 'a', :wrappers => true + # Wrappers don't work on mswin + unless win_platform? then + def test_install_no_wrappers + @fetcher.data['http://gems.example.com/gems/a-1.gem'] = read_binary(@a1_gem) - inst.install + inst = Gem::DependencyInstaller.new :wrappers => false + inst.install 'a' - assert_match %r|This file was generated by RubyGems.|, - File.read(File.join(@gemhome, 'bin', 'a_bin')) - end - - def test_install_version - FileUtils.mv @d1_gem, @cache_dir - FileUtils.mv @d2_gem, @cache_dir - inst = Gem::DependencyInstaller.new 'd', '= 1' - - inst.install - - assert_equal %w[d-1], inst.installed_gems.map { |s| s.full_name } - end - - def test_install_version_default - FileUtils.mv @d1_gem, @cache_dir - FileUtils.mv @d2_gem, @cache_dir - inst = Gem::DependencyInstaller.new 'd' - - inst.install - - assert_equal %w[d-2], inst.installed_gems.map { |s| s.full_name } - end - - def test_download - a1_data = nil - File.open @a1_gem, 'rb' do |fp| - a1_data = fp.read + assert_no_match(%r|This file was generated by RubyGems.|, + File.read(File.join(@gemhome, 'bin', 'a_bin'))) end - - @fetcher.data['http://gems.example.com/gems/a-1.gem'] = a1_data - - inst = Gem::DependencyInstaller.new 'a' - - a1_cache_gem = File.join(@gemhome, 'cache', "#{@a1.full_name}.gem") - assert_equal a1_cache_gem, inst.download(@a1, 'http://gems.example.com') - - assert File.exist?(a1_cache_gem) - end - - def test_download_cached - FileUtils.mv @a1_gem, @cache_dir - - inst = Gem::DependencyInstaller.new 'a' - - assert_equal File.join(@gemhome, 'cache', "#{@a1.full_name}.gem"), - inst.download(@a1, 'http://gems.example.com') - end - - def test_download_local - FileUtils.mv @a1_gem, @tempdir - local_path = File.join @tempdir, "#{@a1.full_name}.gem" - inst = nil - - Dir.chdir @tempdir do - inst = Gem::DependencyInstaller.new 'a' - end - - assert_equal File.join(@gemhome, 'cache', "#{@a1.full_name}.gem"), - inst.download(@a1, local_path) end - def test_download_install_dir - a1_data = nil - File.open @a1_gem, 'rb' do |fp| - a1_data = fp.read - end - - @fetcher.data['http://gems.example.com/gems/a-1.gem'] = a1_data - - install_dir = File.join @tempdir, 'more_gems' - - inst = Gem::DependencyInstaller.new 'a', nil, :install_dir => install_dir - - a1_cache_gem = File.join install_dir, 'cache', "#{@a1.full_name}.gem" - assert_equal a1_cache_gem, inst.download(@a1, 'http://gems.example.com') + def test_install_version + data = File.open(@d2_gem, 'rb') { |f| f.read } + @fetcher.data['http://gems.example.com/gems/d-2.gem'] = data - assert File.exist?(a1_cache_gem) - end + data = File.open(@d1_gem, 'rb') { |f| f.read } + @fetcher.data['http://gems.example.com/gems/d-1.gem'] = data - unless win_platform? then # File.chmod doesn't work - def test_download_local_read_only - FileUtils.mv @a1_gem, @tempdir - local_path = File.join @tempdir, "#{@a1.full_name}.gem" - inst = nil - File.chmod 0555, File.join(@gemhome, 'cache') + inst = Gem::DependencyInstaller.new - Dir.chdir @tempdir do - inst = Gem::DependencyInstaller.new 'a' - end + inst.install 'd', '= 1' - assert_equal File.join(@tempdir, "#{@a1.full_name}.gem"), - inst.download(@a1, local_path) - ensure - File.chmod 0755, File.join(@gemhome, 'cache') - end + assert_equal %w[d-1], inst.installed_gems.map { |s| s.full_name } end - def test_download_platform_legacy - original_platform = 'old-platform' - - e1, e1_gem = util_gem 'e', '1' do |s| - s.platform = Gem::Platform::CURRENT - s.instance_variable_set :@original_platform, original_platform - end - - e1_data = nil - File.open e1_gem, 'rb' do |fp| - e1_data = fp.read - end - - @fetcher.data["http://gems.example.com/gems/e-1-#{original_platform}.gem"] = e1_data - - inst = Gem::DependencyInstaller.new 'a' - - e1_cache_gem = File.join(@gemhome, 'cache', "#{e1.full_name}.gem") - assert_equal e1_cache_gem, inst.download(e1, 'http://gems.example.com') - - assert File.exist?(e1_cache_gem) - end + def test_install_version_default + data = File.open(@d2_gem, 'rb') { |f| f.read } + @fetcher.data['http://gems.example.com/gems/d-2.gem'] = data - def test_download_unsupported - inst = Gem::DependencyInstaller.new 'a' + data = File.open(@d1_gem, 'rb') { |f| f.read } + @fetcher.data['http://gems.example.com/gems/d-1.gem'] = data - e = assert_raise Gem::InstallError do - inst.download @a1, 'ftp://gems.rubyforge.org' - end + inst = Gem::DependencyInstaller.new + inst.install 'd' - assert_equal 'unsupported URI scheme ftp', e.message + assert_equal %w[d-2], inst.installed_gems.map { |s| s.full_name } end def test_find_gems_gems_with_sources - inst = Gem::DependencyInstaller.new 'a' + inst = Gem::DependencyInstaller.new dep = Gem::Dependency.new 'b', '>= 0' assert_equal [[@b1, 'http://gems.example.com']], @@ -452,7 +443,7 @@ class TestGemDependencyInstaller < RubyGemTestCase def test_find_gems_with_sources_local FileUtils.mv @a1_gem, @tempdir - inst = Gem::DependencyInstaller.new 'b' + inst = Gem::DependencyInstaller.new dep = Gem::Dependency.new 'a', '>= 0' gems = nil @@ -462,7 +453,7 @@ class TestGemDependencyInstaller < RubyGemTestCase assert_equal 2, gems.length remote = gems.first - assert_equal @a1, remote.first, 'remote spec' + assert_equal 'a-1', remote.first.full_name, 'remote spec' assert_equal 'http://gems.example.com', remote.last, 'remote path' local = gems.last @@ -472,7 +463,9 @@ class TestGemDependencyInstaller < RubyGemTestCase end def test_gather_dependencies - inst = Gem::DependencyInstaller.new 'b' + inst = Gem::DependencyInstaller.new + inst.find_spec_by_name_and_version 'b' + inst.gather_dependencies assert_equal %w[a-1 b-1], inst.gems_to_install.map { |s| s.full_name } end @@ -488,7 +481,9 @@ class TestGemDependencyInstaller < RubyGemTestCase @fetcher.uri = URI.parse 'http://gems.example.com' @fetcher.data['http://gems.example.com/gems/yaml'] = si.to_yaml - inst = Gem::DependencyInstaller.new 'c' + inst = Gem::DependencyInstaller.new + inst.find_spec_by_name_and_version 'c' + inst.gather_dependencies assert_equal %w[b-2 c-1], inst.gems_to_install.map { |s| s.full_name } end @@ -496,14 +491,18 @@ class TestGemDependencyInstaller < RubyGemTestCase def test_gather_dependencies_platform_alternate util_set_arch 'cpu-my_platform1' - inst = Gem::DependencyInstaller.new 'w' + inst = Gem::DependencyInstaller.new + inst.find_spec_by_name_and_version 'w' + inst.gather_dependencies assert_equal %w[x-1-cpu-my_platform-1 w-1], inst.gems_to_install.map { |s| s.full_name } end def test_gather_dependencies_platform_bump - inst = Gem::DependencyInstaller.new 'z' + inst = Gem::DependencyInstaller.new + inst.find_spec_by_name_and_version 'z' + inst.gather_dependencies assert_equal %w[y-1 z-1], inst.gems_to_install.map { |s| s.full_name } end @@ -518,27 +517,11 @@ class TestGemDependencyInstaller < RubyGemTestCase @fetcher.uri = URI.parse 'http://gems.example.com' @fetcher.data['http://gems.example.com/gems/yaml'] = si.to_yaml - inst = Gem::DependencyInstaller.new 'e' + inst = Gem::DependencyInstaller.new + inst.find_spec_by_name_and_version 'e' + inst.gather_dependencies assert_equal %w[d-1 e-1], inst.gems_to_install.map { |s| s.full_name } end - - def util_gem(name, version, &block) - spec = quick_gem(name, version, &block) - - util_build_gem spec - - cache_file = File.join @tempdir, 'gems', "#{spec.original_name}.gem" - FileUtils.mv File.join(@gemhome, 'cache', "#{spec.original_name}.gem"), - cache_file - FileUtils.rm File.join(@gemhome, 'specifications', - "#{spec.full_name}.gemspec") - - spec.loaded_from = nil - spec.loaded = false - - [spec, cache_file] - end - end diff --git a/test/rubygems/test_gem_ext_configure_builder.rb b/test/rubygems/test_gem_ext_configure_builder.rb index 25aa7412aa..d3d0efb489 100644 --- a/test/rubygems/test_gem_ext_configure_builder.rb +++ b/test/rubygems/test_gem_ext_configure_builder.rb @@ -47,16 +47,19 @@ class TestGemExtConfigureBuilder < RubyGemTestCase end end - expected = %r|configure failed: + shell_error_msg = %r{(\./configure: No such file or directory)|(Can't open \./configure)} + sh_prefix_configure = "sh ./configure --prefix=" + + expected = %r(configure failed: -sh \./configure --prefix=#{Regexp.escape @dest_path} -.*?: \./configure: No such file or directory -| +#{Regexp.escape sh_prefix_configure}#{Regexp.escape @dest_path} +.*?: #{shell_error_msg} +) assert_match expected, error.message - assert_equal "sh ./configure --prefix=#{@dest_path}", output.shift - assert_match %r|\./configure: No such file or directory\n|, output.shift + assert_equal "#{sh_prefix_configure}#{@dest_path}", output.shift + assert_match %r(#{shell_error_msg}\n), output.shift assert_equal true, output.empty? end diff --git a/test/rubygems/test_gem_format.rb b/test/rubygems/test_gem_format.rb index 35eee6bae2..4014acfed9 100644 --- a/test/rubygems/test_gem_format.rb +++ b/test/rubygems/test_gem_format.rb @@ -22,7 +22,7 @@ class TestGemFormat < RubyGemTestCase gems = Dir[File.join(@gemhome, 'cache', '*.gem')] - names = [@a1, @a2, @b2, @c1_2, @pl1].map do |spec| + names = [@a1, @a2, @a_evil9, @b2, @c1_2, @pl1].map do |spec| spec.original_name end diff --git a/test/rubygems/test_gem_indexer.rb b/test/rubygems/test_gem_indexer.rb index 9eb78500c9..12469b5d57 100644 --- a/test/rubygems/test_gem_indexer.rb +++ b/test/rubygems/test_gem_indexer.rb @@ -53,6 +53,11 @@ class TestGemIndexer < RubyGemTestCase assert_indexed quickdir, "index" assert_indexed quickdir, "index.rz" + assert_indexed quickdir, "latest_index" + assert_indexed quickdir, "latest_index.rz" + + assert_no_match %r|a-1|, File.read(File.join(quickdir, 'latest_index')) + assert_indexed quickdir, "#{@a1.full_name}.gemspec.rz" assert_indexed quickdir, "#{@a2.full_name}.gemspec.rz" assert_indexed quickdir, "#{@b2.full_name}.gemspec.rz" @@ -74,8 +79,8 @@ class TestGemIndexer < RubyGemTestCase end expected = <<-EOF -Generating index for 5 gems in #{@tempdir} -..... +Generating index for 6 gems in #{@tempdir} +...... Loaded all gems Generating master indexes (this may take a while) EOF diff --git a/test/rubygems/test_gem_installer.rb b/test/rubygems/test_gem_installer.rb index 442ed6cfc3..f7d36c66ed 100644 --- a/test/rubygems/test_gem_installer.rb +++ b/test/rubygems/test_gem_installer.rb @@ -4,63 +4,10 @@ # See LICENSE.txt for permissions. #++ -require 'test/unit' -require File.join(File.expand_path(File.dirname(__FILE__)), 'gemutilities') -require 'rubygems/installer' - -class Gem::Installer - attr_accessor :gem_dir - - attr_writer :format - attr_writer :gem_home - attr_writer :env_shebang - attr_writer :ignore_dependencies - attr_writer :format_executable - attr_writer :security_policy - attr_writer :spec - attr_writer :wrappers -end - -class TestGemInstaller < RubyGemTestCase - - def setup - super - - @spec = quick_gem "a" - @gem = File.join @tempdir, "#{@spec.full_name}.gem" - - util_build_gem @spec - FileUtils.mv File.join(@gemhome, 'cache', "#{@spec.full_name}.gem"), - @tempdir - - @installer = Gem::Installer.new @gem - @installer.gem_dir = util_gem_dir - @installer.gem_home = @gemhome - @installer.spec = @spec - end - - def util_gem_dir(version = '2') - File.join @gemhome, "gems", "a-#{version}" # HACK - end - - def util_gem_bindir(version = '2') - File.join util_gem_dir(version), "bin" - end +require File.join(File.expand_path(File.dirname(__FILE__)), + 'gem_installer_test_case') - def util_inst_bindir - File.join @gemhome, "bin" - end - - def util_make_exec(version = '2', shebang = "#!/usr/bin/ruby") - @spec.executables = ["my_exec"] - - FileUtils.mkdir_p util_gem_bindir(version) - exec_file = @installer.formatted_program_filename "my_exec" - exec_path = File.join util_gem_bindir(version), exec_file - File.open exec_path, 'w' do |f| - f.puts shebang - end - end +class TestGemInstaller < GemInstallerTestCase def test_app_script_text util_make_exec '2', '' @@ -162,7 +109,7 @@ load 'my_exec' @installer.gem_dir = '/nonexistent' expanded_gem_dir = @installer.send(:expand_and_validate_gem_dir) if win_platform? - expected = File.join(Config::CONFIG['bindir'][0..2], 'nonexistent').downcase + expected = File.expand_path('/nonexistent').downcase expanded_gem_dir = expanded_gem_dir.downcase else expected = '/nonexistent' @@ -768,7 +715,7 @@ load 'my_exec' @installer.env_shebang = true shebang = @installer.shebang 'my_exec' - assert_equal "#!/usr/bin/env ruby", shebang + assert_equal "#!/usr/bin/env #{Gem::ConfigMap[:RUBY_INSTALL_NAME]}", shebang end def test_shebang_nested @@ -855,31 +802,5 @@ load 'my_exec' File.join @gemhome, 'cache', "#{spec.full_name}.gem" end - def util_setup_gem - @spec.files = File.join('lib', 'code.rb') - @spec.executables << 'executable' - @spec.extensions << File.join('ext', 'a', 'mkrf_conf.rb') - - Dir.chdir @tempdir do - FileUtils.mkdir_p 'bin' - FileUtils.mkdir_p 'lib' - FileUtils.mkdir_p File.join('ext', 'a') - File.open File.join('bin', 'executable'), 'w' do |f| f.puts '1' end - File.open File.join('lib', 'code.rb'), 'w' do |f| f.puts '1' end - File.open File.join('ext', 'a', 'mkrf_conf.rb'), 'w' do |f| - f << <<-EOF - File.open 'Rakefile', 'w' do |rf| rf.puts "task :default" end - EOF - end - - use_ui @ui do - FileUtils.rm @gem - Gem::Builder.new(@spec).build - end - end - - @installer = Gem::Installer.new @gem - end - end diff --git a/test/rubygems/test_gem_package_tar_header.rb b/test/rubygems/test_gem_package_tar_header.rb new file mode 100644 index 0000000000..9b7708dca8 --- /dev/null +++ b/test/rubygems/test_gem_package_tar_header.rb @@ -0,0 +1,137 @@ +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + +require File.join(File.expand_path(File.dirname(__FILE__)), + 'gem_package_tar_test_case') +require 'rubygems/package' + +class TestGemPackageTarHeader < TarTestCase + + def setup + super + + header = { + :name => 'x', + :mode => 0644, + :uid => 1000, + :gid => 10000, + :size => 100, + :mtime => 12345, + :typeflag => '0', + :linkname => 'link', + :uname => 'user', + :gname => 'group', + :devmajor => 1, + :devminor => 2, + :prefix => 'y', + } + + @tar_header = Gem::Package::TarHeader.new header + end + + def test_self_from + io = TempIO.new @tar_header.to_s + + new_header = Gem::Package::TarHeader.from io + + assert_headers_equal @tar_header, new_header + end + + def test_initialize + assert_equal '', @tar_header.checksum, 'checksum' + assert_equal 1, @tar_header.devmajor, 'devmajor' + assert_equal 2, @tar_header.devminor, 'devminor' + assert_equal 10000, @tar_header.gid, 'gid' + assert_equal 'group', @tar_header.gname, 'gname' + assert_equal 'link', @tar_header.linkname, 'linkname' + assert_equal 'ustar', @tar_header.magic, 'magic' + assert_equal 0644, @tar_header.mode, 'mode' + assert_equal 12345, @tar_header.mtime, 'mtime' + assert_equal 'x', @tar_header.name, 'name' + assert_equal 'y', @tar_header.prefix, 'prefix' + assert_equal 100, @tar_header.size, 'size' + assert_equal '0', @tar_header.typeflag, 'typeflag' + assert_equal 1000, @tar_header.uid, 'uid' + assert_equal 'user', @tar_header.uname, 'uname' + assert_equal '00', @tar_header.version, 'version' + + assert !@tar_header.empty?, 'empty' + end + + def test_initialize_bad + assert_raises ArgumentError do + Gem::Package::TarHeader.new :name => '', :size => '', :mode => '' + end + + assert_raises ArgumentError do + Gem::Package::TarHeader.new :name => '', :size => '', :prefix => '' + end + + assert_raises ArgumentError do + Gem::Package::TarHeader.new :name => '', :prefix => '', :mode => '' + end + + assert_raises ArgumentError do + Gem::Package::TarHeader.new :prefix => '', :size => '', :mode => '' + end + end + + def test_empty_eh + assert !@tar_header.empty? + + @tar_header = Gem::Package::TarHeader.new :name => 'x', :prefix => '', + :mode => 0, :size => 0, + :empty => true + + assert @tar_header.empty? + end + + def test_equals2 + assert_equal @tar_header, @tar_header + assert_equal @tar_header, @tar_header.dup + end + + def test_to_s + expected = <<-EOF.split("\n").join +x\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 +\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 +\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 +\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 +\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 +\000\000\000\0000000644\0000001750\0000023420\00000000000144\00000000030071 +\000012467\000 0link\000\000\000\000\000\000\000\000\000\000\000\000\000\000 +\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 +\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 +\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 +\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 +\000\000\000\000\000\000ustar\00000user\000\000\000\000\000\000\000\000\000 +\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 +group\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 +\000\000\000\000\000\000\000\000\0000000001\0000000002\000y\000\000\000\000 +\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 +\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 +\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 +\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 +\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 +\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 +\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 +\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 +\000\000\000\000\000\000\000\000\000\000 + EOF + + assert_headers_equal expected, @tar_header + end + + def test_update_checksum + assert_equal '', @tar_header.checksum + + @tar_header.update_checksum + + assert_equal '012467', @tar_header.checksum + end + +end + diff --git a/test/rubygems/test_gem_package_tar_input.rb b/test/rubygems/test_gem_package_tar_input.rb new file mode 100644 index 0000000000..279026b149 --- /dev/null +++ b/test/rubygems/test_gem_package_tar_input.rb @@ -0,0 +1,119 @@ +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + +require File.join(File.expand_path(File.dirname(__FILE__)), + 'gem_package_tar_test_case') +require 'rubygems/package/tar_input' + +class TestGemPackageTarInput < TarTestCase + + # Sometimes the setgid bit doesn't take. Don't know if this is a problem on + # all systems, or just some. But for now, we will ignore it in the tests. + SETGID_BIT = 02000 + + def setup + super + + inner_tar = tar_file_header("bla", "", 0612, 10) + inner_tar += "0123456789" + "\0" * 502 + inner_tar += tar_file_header("foo", "", 0636, 5) + inner_tar += "01234" + "\0" * 507 + inner_tar += tar_dir_header("__dir__", "", 0600) + inner_tar += "\0" * 1024 + str = TempIO.new + + begin + os = Zlib::GzipWriter.new str + os.write inner_tar + ensure + os.finish + end + + str.rewind + + @file = File.join @tempdir, 'bla.tar' + + File.open @file, 'wb' do |f| + f.write tar_file_header("data.tar.gz", "", 0644, str.string.size) + f.write str.string + f.write "\0" * ((512 - (str.string.size % 512)) % 512 ) + + @spec = Gem::Specification.new do |spec| + spec.author = "Mauricio :)" + end + + meta = @spec.to_yaml + + f.write tar_file_header("metadata", "", 0644, meta.size) + f.write meta + "\0" * (1024 - meta.size) + f.write "\0" * 1024 + end + + @entry_names = %w{bla foo __dir__} + @entry_sizes = [10, 5, 0] + #FIXME: are these modes system dependent? + @entry_modes = [0100612, 0100636, 040600] + @entry_files = %W[#{@tempdir}/bla #{@tempdir}/foo] + @entry_contents = %w[0123456789 01234] + end + + def test_each_works + open @file, 'rb' do |io| + Gem::Package::TarInput.open io do |tar_input| + count = 0 + + tar_input.each_with_index do |entry, i| + count = i + + assert_kind_of Gem::Package::TarReader::Entry, entry + assert_equal @entry_names[i], entry.header.name + assert_equal @entry_sizes[i], entry.header.size + end + + assert_equal 2, count + + assert_equal @spec, tar_input.metadata + end + end + end + + def test_extract_entry_works + open @file, 'rb' do |io| + Gem::Package::TarInput.open io do |tar_input| + assert_equal @spec, tar_input.metadata + + count = 0 + + tar_input.each_with_index do |entry, i| + count = i + tar_input.extract_entry @tempdir, entry + name = File.join @tempdir, entry.header.name + + if entry.directory? + assert File.dir?(name) + else + assert File.file?(name) + assert_equal @entry_sizes[i], File.stat(name).size + #FIXME: win32? !! + end + + unless Gem.win_platform? then + assert_equal @entry_modes[i], File.stat(name).mode & (~SETGID_BIT) + end + end + + assert_equal 2, count + end + end + + @entry_files.each_with_index do |x, i| + assert File.file?(x) + assert_equal @entry_contents[i], File.read_b(x) + end + end + +end + diff --git a/test/rubygems/test_gem_package_tar_output.rb b/test/rubygems/test_gem_package_tar_output.rb new file mode 100644 index 0000000000..06dbb1a4da --- /dev/null +++ b/test/rubygems/test_gem_package_tar_output.rb @@ -0,0 +1,104 @@ +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + +require File.join(File.expand_path(File.dirname(__FILE__)), + 'gem_package_tar_test_case') +require 'rubygems/package/tar_output' + +class TestGemPackageTarOutput < TarTestCase + + def setup + super + + @file = File.join @tempdir, 'bla2.tar' + end + + def test_self_open + open @file, 'wb' do |tar_io| + Gem::Package::TarOutput.open tar_io do |tar_writer| + tar_writer.add_file_simple 'README', 0, 17 do |io| + io.write "This is a README\n" + end + + tar_writer.metadata = "This is some metadata\n" + end + end + + files = util_extract + + name, data = files.shift + assert_equal 'data.tar.gz', name + + gz = Zlib::GzipReader.new StringIO.new(data) + + Gem::Package::TarReader.new gz do |tar_reader| + tar_reader.each do |entry| + assert_equal 'README', entry.full_name + assert_equal "This is a README\n", entry.read + end + end + + gz.close + + name, data = files.shift + assert_equal 'metadata.gz', name + + gz = Zlib::GzipReader.new StringIO.new(data) + assert_equal "This is some metadata\n", gz.read + + assert files.empty? + ensure + gz.close if gz + end + + if defined? OpenSSL then + def test_self_open_signed + signer = Gem::Security::Signer.new @private_key, [@public_cert] + + open @file, 'wb' do |tar_io| + Gem::Package::TarOutput.open tar_io, signer do |tar_writer| + tar_writer.add_file_simple 'README', 0, 17 do |io| + io.write "This is a README\n" + end + + tar_writer.metadata = "This is some metadata\n" + end + end + + files = util_extract + + name, data = files.shift + assert_equal 'data.tar.gz', name + + name, data = files.shift + assert_equal 'metadata.gz', name + + name, data = files.shift + assert_equal 'data.tar.gz.sig', name + + name, data = files.shift + assert_equal 'metadata.gz.sig', name + + assert files.empty? + end + end + + def util_extract + files = [] + + open @file, 'rb' do |io| + Gem::Package::TarReader.new io do |tar_reader| + tar_reader.each do |entry| + files << [entry.full_name, entry.read] + end + end + end + + files + end + +end + diff --git a/test/rubygems/test_gem_package_tar_reader.rb b/test/rubygems/test_gem_package_tar_reader.rb new file mode 100644 index 0000000000..6962088878 --- /dev/null +++ b/test/rubygems/test_gem_package_tar_reader.rb @@ -0,0 +1,53 @@ +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + +require File.join(File.expand_path(File.dirname(__FILE__)), + 'gem_package_tar_test_case') +require 'rubygems/package' + +class TestGemPackageTarReader < TarTestCase + + def test_each_entry + tar = tar_dir_header "foo", "bar", 0 + tar << tar_file_header("bar", "baz", 0, 0) + + io = TempIO.new tar + + entries = 0 + + Gem::Package::TarReader.new io do |tar_reader| + tar_reader.each_entry do |entry| + assert_kind_of Gem::Package::TarReader::Entry, entry + + entries += 1 + end + end + + assert_equal 2, entries + end + + def test_rewind + content = ('a'..'z').to_a.join(" ") + + str = tar_file_header("lib/foo", "", 010644, content.size) + content + + "\0" * (512 - content.size) + str << "\0" * 1024 + + Gem::Package::TarReader.new(TempIO.new(str)) do |tar_reader| + 3.times do + tar_reader.rewind + i = 0 + tar_reader.each_entry do |entry| + assert_equal(content, entry.read) + i += 1 + end + assert_equal(1, i) + end + end + end + +end + diff --git a/test/rubygems/test_gem_package_tar_reader_entry.rb b/test/rubygems/test_gem_package_tar_reader_entry.rb new file mode 100644 index 0000000000..7e25143a85 --- /dev/null +++ b/test/rubygems/test_gem_package_tar_reader_entry.rb @@ -0,0 +1,116 @@ +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + +require File.join(File.expand_path(File.dirname(__FILE__)), + 'gem_package_tar_test_case') +require 'rubygems/package' + +class TestGemPackageTarReaderEntry < TarTestCase + + def setup + super + + @contents = ('a'..'z').to_a.join * 100 + + @tar = '' + @tar << tar_file_header("lib/foo", "", 0, @contents.size) + @tar << @contents + @tar << "\0" * (512 - (@tar.size % 512)) + + @entry = util_entry @tar + end + + def test_bytes_read + assert_equal 0, @entry.bytes_read + + @entry.getc + + assert_equal 1, @entry.bytes_read + end + + def test_close + @entry.close + + assert @entry.bytes_read + + e = assert_raise IOError do @entry.eof? end + assert_equal 'closed Gem::Package::TarReader::Entry', e.message + + e = assert_raise IOError do @entry.getc end + assert_equal 'closed Gem::Package::TarReader::Entry', e.message + + e = assert_raise IOError do @entry.pos end + assert_equal 'closed Gem::Package::TarReader::Entry', e.message + + e = assert_raise IOError do @entry.read end + assert_equal 'closed Gem::Package::TarReader::Entry', e.message + + e = assert_raise IOError do @entry.rewind end + assert_equal 'closed Gem::Package::TarReader::Entry', e.message + end + + def test_closed_eh + @entry.close + + assert @entry.closed? + end + + def test_eof_eh + @entry.read + + assert @entry.eof? + end + + def test_full_name + assert_equal 'lib/foo', @entry.full_name + end + + def test_getc + assert_equal ?a, @entry.getc + end + + def test_directory_eh + assert_equal false, @entry.directory? + assert_equal true, util_dir_entry.directory? + end + + def test_file_eh + assert_equal true, @entry.file? + assert_equal false, util_dir_entry.file? + end + + def test_pos + assert_equal 0, @entry.pos + + @entry.getc + + assert_equal 1, @entry.pos + end + + def test_read + assert_equal @contents, @entry.read + end + + def test_read_big + assert_equal @contents, @entry.read(@contents.size * 2) + end + + def test_read_small + assert_equal @contents[0...100], @entry.read(100) + end + + def test_rewind + char = @entry.getc + + @entry.rewind + + assert_equal 0, @entry.pos + + assert_equal char, @entry.getc + end + +end + diff --git a/test/rubygems/test_gem_package_tar_writer.rb b/test/rubygems/test_gem_package_tar_writer.rb new file mode 100644 index 0000000000..e066c2967f --- /dev/null +++ b/test/rubygems/test_gem_package_tar_writer.rb @@ -0,0 +1,151 @@ +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + +require File.join(File.expand_path(File.dirname(__FILE__)), + 'gem_package_tar_test_case') +require 'rubygems/package/tar_writer' + +class TestTarWriter < TarTestCase + + def setup + super + + @data = 'abcde12345' + @io = TempIO.new + @tar_writer = Gem::Package::TarWriter.new @io + end + + def teardown + @tar_writer.close unless @tar_writer.closed? + + super + end + + def test_add_file + @tar_writer.add_file 'x', 0644 do |f| f.write 'a' * 10 end + + assert_headers_equal(tar_file_header('x', '', 0644, 10), + @io.string[0, 512]) + assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512] + assert_equal 1024, @io.pos + end + + def test_add_file_simple + @tar_writer.add_file_simple 'x', 0644, 10 do |io| io.write "a" * 10 end + + assert_headers_equal(tar_file_header('x', '', 0644, 10), + @io.string[0, 512]) + + assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512] + assert_equal 1024, @io.pos + end + + def test_add_file_simple_padding + @tar_writer.add_file_simple 'x', 0, 100 + + assert_headers_equal tar_file_header('x', '', 0, 100), + @io.string[0, 512] + + assert_equal "\0" * 512, @io.string[512, 512] + end + + def test_add_file_simple_data + @tar_writer.add_file_simple("lib/foo/bar", 0, 10) { |f| f.write @data } + @tar_writer.flush + + assert_equal @data + ("\0" * ([email protected])), + @io.string[512, 512] + end + + def test_add_file_simple_size + assert_raise Gem::Package::TarWriter::FileOverflow do + @tar_writer.add_file_simple("lib/foo/bar", 0, 10) do |io| + io.write "1" * 11 + end + end + end + + def test_add_file_unseekable + assert_raise Gem::Package::NonSeekableIO do + Gem::Package::TarWriter.new(Object.new).add_file 'x', 0 + end + end + + def test_close + @tar_writer.close + + assert_equal "\0" * 1024, @io.string + + e = assert_raise IOError do + @tar_writer.close + end + assert_equal 'closed Gem::Package::TarWriter', e.message + + e = assert_raise IOError do + @tar_writer.flush + end + assert_equal 'closed Gem::Package::TarWriter', e.message + + e = assert_raise IOError do + @tar_writer.add_file 'x', 0 + end + assert_equal 'closed Gem::Package::TarWriter', e.message + + e = assert_raise IOError do + @tar_writer.add_file_simple 'x', 0, 0 + end + assert_equal 'closed Gem::Package::TarWriter', e.message + + e = assert_raise IOError do + @tar_writer.mkdir 'x', 0 + end + assert_equal 'closed Gem::Package::TarWriter', e.message + end + + def test_mkdir + @tar_writer.mkdir 'foo', 0644 + + assert_headers_equal tar_dir_header('foo', '', 0644), + @io.string[0, 512] + assert_equal 512, @io.pos + end + + def test_split_name + assert_equal ['b' * 100, 'a' * 155], + @tar_writer.split_name("#{'a' * 155}/#{'b' * 100}") + + assert_equal ["#{'qwer/' * 19}bla", 'a' * 151], + @tar_writer.split_name("#{'a' * 151}/#{'qwer/' * 19}bla") + end + + def test_split_name_too_long_name + name = File.join 'a', 'b' * 100 + assert_equal ['b' * 100, 'a'], @tar_writer.split_name(name) + + assert_raise Gem::Package::TooLongFileName do + name = File.join 'a', 'b' * 101 + @tar_writer.split_name name + end + end + + def test_split_name_too_long_prefix + name = File.join 'a' * 155, 'b' + assert_equal ['b', 'a' * 155], @tar_writer.split_name(name) + + assert_raise Gem::Package::TooLongFileName do + name = File.join 'a' * 156, 'b' + @tar_writer.split_name name + end + end + + def test_split_name_too_long_total + assert_raise Gem::Package::TooLongFileName do + @tar_writer.split_name 'a' * 257 + end + end + +end + diff --git a/test/rubygems/test_gem_remote_fetcher.rb b/test/rubygems/test_gem_remote_fetcher.rb index 5ffaed24e3..1ac71aabc2 100644 --- a/test/rubygems/test_gem_remote_fetcher.rb +++ b/test/rubygems/test_gem_remote_fetcher.rb @@ -97,6 +97,13 @@ gems: @server_uri = base_server_uri + "/yaml" @server_z_uri = base_server_uri + "/yaml.Z" + # REFACTOR: copied from test_gem_dependency_installer.rb + @gems_dir = File.join @tempdir, 'gems' + @cache_dir = File.join @gemhome, 'cache' + FileUtils.mkdir @gems_dir + + @a1, @a1_gem = util_gem 'a', '1' do |s| s.executables << 'a_bin' end + Gem::RemoteFetcher.instance_variable_set :@fetcher, nil end @@ -156,6 +163,140 @@ gems: end end + def util_fuck_with_fetcher data, blow = false + fetcher = Gem::RemoteFetcher.fetcher + fetcher.instance_variable_set :@test_data, data + + unless blow then + def fetcher.fetch_path arg + @test_arg = arg + @test_data + end + else + def fetcher.fetch_path arg + # OMG I'm such an ass + class << self; remove_method :fetch_path; end + def self.fetch_path arg + @test_arg = arg + @test_data + end + + raise Gem::RemoteFetcher::FetchError, "haha!" + end + end + + fetcher + end + + def test_download + a1_data = nil + File.open @a1_gem, 'rb' do |fp| + a1_data = fp.read + end + + fetcher = util_fuck_with_fetcher a1_data + + a1_cache_gem = File.join(@gemhome, 'cache', "#{@a1.full_name}.gem") + assert_equal a1_cache_gem, fetcher.download(@a1, 'http://gems.example.com') + assert_equal("http://gems.example.com/gems/a-1.gem", + fetcher.instance_variable_get(:@test_arg).to_s) + assert File.exist?(a1_cache_gem) + end + + def test_download_cached + FileUtils.mv @a1_gem, @cache_dir + + inst = Gem::RemoteFetcher.fetcher + + assert_equal File.join(@gemhome, 'cache', "#{@a1.full_name}.gem"), + inst.download(@a1, 'http://gems.example.com') + end + + def test_download_local + FileUtils.mv @a1_gem, @tempdir + local_path = File.join @tempdir, "#{@a1.full_name}.gem" + inst = nil + + Dir.chdir @tempdir do + inst = Gem::RemoteFetcher.fetcher + end + + assert_equal File.join(@gemhome, 'cache', "#{@a1.full_name}.gem"), + inst.download(@a1, local_path) + end + + def test_download_install_dir + a1_data = nil + File.open @a1_gem, 'rb' do |fp| + a1_data = fp.read + end + + fetcher = util_fuck_with_fetcher a1_data + + install_dir = File.join @tempdir, 'more_gems' + + a1_cache_gem = File.join install_dir, 'cache', "#{@a1.full_name}.gem" + actual = fetcher.download(@a1, 'http://gems.example.com', install_dir) + + assert_equal a1_cache_gem, actual + assert_equal("http://gems.example.com/gems/a-1.gem", + fetcher.instance_variable_get(:@test_arg).to_s) + + assert File.exist?(a1_cache_gem) + end + + unless win_platform? then # File.chmod doesn't work + def test_download_local_read_only + FileUtils.mv @a1_gem, @tempdir + local_path = File.join @tempdir, "#{@a1.full_name}.gem" + inst = nil + File.chmod 0555, File.join(@gemhome, 'cache') + + Dir.chdir @tempdir do + inst = Gem::RemoteFetcher.fetcher + end + + assert_equal File.join(@tempdir, "#{@a1.full_name}.gem"), + inst.download(@a1, local_path) + ensure + File.chmod 0755, File.join(@gemhome, 'cache') + end + end + + def test_download_platform_legacy + original_platform = 'old-platform' + + e1, e1_gem = util_gem 'e', '1' do |s| + s.platform = Gem::Platform::CURRENT + s.instance_variable_set :@original_platform, original_platform + end + + e1_data = nil + File.open e1_gem, 'rb' do |fp| + e1_data = fp.read + end + + fetcher = util_fuck_with_fetcher e1_data, :blow_chunks + + e1_cache_gem = File.join(@gemhome, 'cache', "#{e1.full_name}.gem") + + assert_equal e1_cache_gem, fetcher.download(e1, 'http://gems.example.com') + + assert_equal("http://gems.example.com/gems/#{e1.original_name}.gem", + fetcher.instance_variable_get(:@test_arg).to_s) + assert File.exist?(e1_cache_gem) + end + + def test_download_unsupported + inst = Gem::RemoteFetcher.fetcher + + e = assert_raise Gem::InstallError do + inst.download @a1, 'ftp://gems.rubyforge.org' + end + + assert_equal 'unsupported URI scheme ftp', e.message + end + def test_explicit_proxy use_ui @ui do fetcher = Gem::RemoteFetcher.new @proxy_uri @@ -232,22 +373,6 @@ gems: assert_equal 'EOFError: EOFError reading uri', e.message end - def test_fetch_path_open_uri_http_error - fetcher = Gem::RemoteFetcher.new nil - - def fetcher.open_uri_or_path(uri) - io = StringIO.new 'went boom' - err = OpenURI::HTTPError.new 'error', io - raise err - end - - e = assert_raise Gem::RemoteFetcher::FetchError do - fetcher.fetch_path 'uri' - end - - assert_equal "OpenURI::HTTPError: error reading uri\n\twent boom", e.message - end - def test_fetch_path_socket_error fetcher = Gem::RemoteFetcher.new nil @@ -324,6 +449,53 @@ gems: end end + def test_open_uri_or_path + fetcher = Gem::RemoteFetcher.new nil + + conn = Object.new + def conn.started?() true end + def conn.request(req) + unless defined? @requested then + @requested = true + res = Net::HTTPRedirection.new nil, 301, nil + res.add_field 'Location', 'http://gems.example.com/real_path' + res + else + res = Net::HTTPOK.new nil, 200, nil + def res.body() 'real_path' end + res + end + end + + conn = { 'gems.example.com:80' => conn } + fetcher.instance_variable_set :@connections, conn + + fetcher.send :open_uri_or_path, 'http://gems.example.com/redirect' do |io| + assert_equal 'real_path', io.read + end + end + + def test_open_uri_or_path_limited_redirects + fetcher = Gem::RemoteFetcher.new nil + + conn = Object.new + def conn.started?() true end + def conn.request(req) + res = Net::HTTPRedirection.new nil, 301, nil + res.add_field 'Location', 'http://gems.example.com/redirect' + res + end + + conn = { 'gems.example.com:80' => conn } + fetcher.instance_variable_set :@connections, conn + + e = assert_raise Gem::RemoteFetcher::FetchError do + fetcher.send :open_uri_or_path, 'http://gems.example.com/redirect' + end + + assert_equal 'too many redirects', e.message + end + def test_zip use_ui @ui do self.class.enable_zip = true diff --git a/test/rubygems/test_gem_source_index.rb b/test/rubygems/test_gem_source_index.rb index 8fc8449814..74aa91d63e 100644 --- a/test/rubygems/test_gem_source_index.rb +++ b/test/rubygems/test_gem_source_index.rb @@ -36,7 +36,8 @@ class TestGemSourceIndex < RubyGemTestCase use_ui @ui do fetched_index = @source_index.fetch_bulk_index @uri - assert_equal [@gem1.full_name, @gem4.full_name, @gem2.full_name].sort, + assert_equal [@a1.full_name, @a2.full_name, @a_evil9.full_name, + @c1_2.full_name].sort, fetched_index.gems.map { |n,s| n }.sort end @@ -82,7 +83,8 @@ class TestGemSourceIndex < RubyGemTestCase use_ui @ui do fetched_index = @source_index.fetch_bulk_index @uri - assert_equal [@gem1.full_name, @gem4.full_name, @gem2.full_name].sort, + assert_equal [@a1.full_name, @a2.full_name, @a_evil9.full_name, + @c1_2.full_name].sort, fetched_index.gems.map { |n,s| n }.sort end @@ -105,7 +107,8 @@ class TestGemSourceIndex < RubyGemTestCase use_ui @ui do fetched_index = @source_index.fetch_bulk_index @uri - assert_equal [@gem1.full_name, @gem4.full_name, @gem2.full_name].sort, + assert_equal [@a1.full_name, @a2.full_name, @a_evil9.full_name, + @c1_2.full_name].sort, fetched_index.gems.map { |n,s| n }.sort end @@ -123,7 +126,8 @@ class TestGemSourceIndex < RubyGemTestCase util_setup_bulk_fetch false use_ui @ui do fetched_index = @source_index.fetch_bulk_index @uri - assert_equal [@gem1.full_name, @gem4.full_name, @gem2.full_name].sort, + assert_equal [@a1.full_name, @a2.full_name, @a_evil9.full_name, + @c1_2.full_name].sort, fetched_index.gems.map { |n,s| n }.sort end @@ -136,11 +140,32 @@ class TestGemSourceIndex < RubyGemTestCase end def test_fetch_quick_index - quick_index = util_zip @gem_names - @fetcher.data["#{@gem_repo}/quick/index.rz"] = quick_index + index = util_zip @gem_names + latest_index = util_zip [@a2.full_name, @b2.full_name].join("\n") + + @fetcher.data["#{@gem_repo}/quick/index.rz"] = index + @fetcher.data["#{@gem_repo}/quick/latest_index.rz"] = latest_index + + quick_index = @source_index.fetch_quick_index @uri, false + assert_equal [@a2.full_name, @b2.full_name].sort, + quick_index.sort + + paths = @fetcher.paths + + assert_equal "#{@gem_repo}/quick/latest_index.rz", paths.shift + + assert paths.empty?, paths.join(', ') + end + + def test_fetch_quick_index_all + index = util_zip @gem_names + latest_index = util_zip [@a2.full_name, @b2.full_name].join("\n") + + @fetcher.data["#{@gem_repo}/quick/index.rz"] = index + @fetcher.data["#{@gem_repo}/quick/latest_index.rz"] = latest_index - quick_index = @source_index.fetch_quick_index @uri - assert_equal [@gem1.full_name, @gem4.full_name, @gem2.full_name].sort, + quick_index = @source_index.fetch_quick_index @uri, true + assert_equal [@a1.full_name, @a2.full_name, @b2.full_name].sort, quick_index.sort paths = @fetcher.paths @@ -155,7 +180,7 @@ class TestGemSourceIndex < RubyGemTestCase proc { raise Exception } e = assert_raise Gem::OperationNotSupportedError do - @source_index.fetch_quick_index @uri + @source_index.fetch_quick_index @uri, true end assert_equal 'No quick index found: Exception', e.message @@ -167,41 +192,201 @@ class TestGemSourceIndex < RubyGemTestCase assert paths.empty?, paths.join(', ') end + def test_fetch_quick_index_fallback + index = util_zip @gem_names + + @fetcher.data["#{@gem_repo}/quick/index.rz"] = index + + quick_index = @source_index.fetch_quick_index @uri, false + assert_equal @gem_names.split, quick_index.sort + + paths = @fetcher.paths + + assert_equal "#{@gem_repo}/quick/latest_index.rz", paths.shift + assert_equal "#{@gem_repo}/quick/index.rz", paths.shift + + assert paths.empty?, paths.join(', ') + end + + def test_fetch_quick_index_subdir + latest_index = util_zip [@a2.full_name, @b2.full_name].join("\n") + repo = URI.parse "#{@gem_repo}/~nobody/mirror/" + + @fetcher.data["#{repo}quick/latest_index.rz"] = latest_index + + quick_index = @source_index.fetch_quick_index repo, false + assert_equal [@a2.full_name, @b2.full_name].sort, + quick_index.sort + + paths = @fetcher.paths + + assert_equal "#{repo}quick/latest_index.rz", paths.shift + + assert paths.empty?, paths.join(', ') + end + + def test_fetch_single_spec + a1_spec_url = "#{@gem_repo}/quick/Marshal.#{Gem.marshal_version}/#{@a1.full_name}.gemspec.rz" + @fetcher.data[a1_spec_url] = util_zip Marshal.dump(@a1) + + spec = @source_index.send :fetch_single_spec, URI.parse(@gem_repo), + @a1.full_name + + assert_equal @a1.full_name, spec.full_name + + paths = @fetcher.paths + + assert_equal a1_spec_url, paths.shift + + assert paths.empty?, paths.join(', ') + end + + def test_fetch_single_spec_subdir + repo = URI.parse "#{@gem_repo}/~nobody/mirror/" + + a1_spec_url = "#{repo}quick/Marshal.#{Gem.marshal_version}/#{@a1.full_name}.gemspec.rz" + @fetcher.data[a1_spec_url] = util_zip Marshal.dump(@a1) + + spec = @source_index.send :fetch_single_spec, repo, @a1.full_name + + assert_equal @a1.full_name, spec.full_name + + paths = @fetcher.paths + + assert_equal a1_spec_url, paths.shift + + assert paths.empty?, paths.join(', ') + end + + def test_fetch_single_spec_yaml + a1_spec_url = "#{@gem_repo}/quick/#{@a1.full_name}.gemspec.rz" + @fetcher.data[a1_spec_url] = util_zip @a1.to_yaml + + repo = URI.parse @gem_repo + + spec = @source_index.send :fetch_single_spec, repo, @a1.full_name + + assert_equal @a1.full_name, spec.full_name + + paths = @fetcher.paths + + assert_equal "#{@gem_repo}/quick/Marshal.#{Gem.marshal_version}/#{@a1.full_name}.gemspec.rz", paths.shift + assert_equal a1_spec_url, paths.shift + + assert paths.empty?, paths.join(', ') + end + + def test_fetch_single_spec_yaml_subdir + repo = URI.parse "#{@gem_repo}/~nobody/mirror/" + + a1_spec_url = "#{repo}quick/#{@a1.full_name}.gemspec.rz" + @fetcher.data[a1_spec_url] = util_zip @a1.to_yaml + + spec = @source_index.send :fetch_single_spec, repo, @a1.full_name + + assert_equal @a1.full_name, spec.full_name + + paths = @fetcher.paths + + assert_equal "#{repo}quick/Marshal.#{Gem.marshal_version}/#{@a1.full_name}.gemspec.rz", paths.shift + assert_equal a1_spec_url, paths.shift + + assert paths.empty?, paths.join(', ') + end + def test_find_missing - missing = @source_index.find_missing [@gem3.full_name] - assert_equal [@gem3.full_name], missing + missing = @source_index.find_missing [@b2.full_name] + assert_equal [@b2.full_name], missing end def test_find_missing_none_missing - missing = @source_index.find_missing @gem_names.split + missing = @source_index.find_missing [ + @a1.full_name, @a2.full_name, @c1_2.full_name + ] + assert_equal [], missing end def test_latest_specs - spec = quick_gem @gem1.name, '1' - @source_index.add_spec spec + p1_ruby = quick_gem 'p', '1' + p1_platform = quick_gem 'p', '1' do |spec| + spec.platform = Gem::Platform::CURRENT + end + + a1_platform = quick_gem @a1.name, (@a1.version) do |s| + s.platform = Gem::Platform.new 'x86-my_platform1' + end + + a2_platform = quick_gem @a2.name, (@a2.version) do |s| + s.platform = Gem::Platform.new 'x86-my_platform1' + end + + a2_platform_other = quick_gem @a2.name, (@a2.version) do |s| + s.platform = Gem::Platform.new 'x86-other_platform1' + end + + a3_platform_other = quick_gem @a2.name, (@a2.version.bump) do |s| + s.platform = Gem::Platform.new 'x86-other_platform1' + end + + @source_index.add_spec p1_ruby + @source_index.add_spec p1_platform + @source_index.add_spec a1_platform + @source_index.add_spec a2_platform + @source_index.add_spec a2_platform_other + @source_index.add_spec a3_platform_other expected = [ - @gem1.full_name, - @gem2.full_name, - @gem4.full_name, + @a2.full_name, + a2_platform.full_name, + a3_platform_other.full_name, + @c1_2.full_name, + @a_evil9.full_name, + p1_ruby.full_name, + p1_platform.full_name, ].sort - assert_equal expected, @source_index.latest_specs.map { |s| s.full_name }.sort + latest_specs = @source_index.latest_specs.map { |s| s.full_name }.sort + + assert_equal expected, latest_specs + end + + def test_load_gems_in + spec_dir1 = File.join @gemhome, 'specifications' + spec_dir2 = File.join @tempdir, 'gemhome2', 'specifications' + + FileUtils.rm_r spec_dir1 + + FileUtils.mkdir_p spec_dir1 + FileUtils.mkdir_p spec_dir2 + + a1 = quick_gem 'a', '1' do |spec| spec.author = 'author 1' end + a2 = quick_gem 'a', '1' do |spec| spec.author = 'author 2' end + + File.open File.join(spec_dir1, "#{a1.full_name}.gemspec"), 'w' do |fp| + fp.write a1.to_ruby + end + + File.open File.join(spec_dir2, "#{a2.full_name}.gemspec"), 'w' do |fp| + fp.write a2.to_ruby + end + + @source_index.load_gems_in spec_dir1, spec_dir2 + + assert_equal a1.author, @source_index.specification(a1.full_name).author end def test_outdated - sic = Gem::SourceInfoCache.new - Gem::SourceInfoCache.instance_variable_set :@cache, sic + util_setup_source_info_cache assert_equal [], @source_index.outdated - updated = quick_gem @gem1.name, (@gem1.version.bump) + updated = quick_gem @a2.name, (@a2.version.bump) util_setup_source_info_cache updated assert_equal [updated.name], @source_index.outdated - updated_platform = quick_gem @gem1.name, (updated.version.bump) do |s| + updated_platform = quick_gem @a2.name, (updated.version.bump) do |s| s.platform = Gem::Platform.new 'x86-other_platform1' end @@ -211,28 +396,34 @@ class TestGemSourceIndex < RubyGemTestCase end def test_remove_extra - @source_index.remove_extra [@gem1.full_name] - assert_equal [@gem1.full_name], @source_index.gems.map { |n,s| n } + @source_index.add_spec @a1 + @source_index.add_spec @a2 + + @source_index.remove_extra [@a1.full_name] + + assert_equal [@a1.full_name], @source_index.gems.map { |n,s| n } end def test_remove_extra_no_changes - gems = @gem_names.split.sort + gems = [@a1.full_name, @a2.full_name] + @source_index.add_spec @a1 + @source_index.add_spec @a2 + @source_index.remove_extra gems + assert_equal gems, @source_index.gems.map { |n,s| n }.sort end def test_search - assert_equal [@gem1, @gem4], @source_index.search("gem_one") - assert_equal [@gem1], @source_index.search("gem_one", "= 2") + assert_equal [@a1, @a2, @a_evil9], @source_index.search('a') + assert_equal [@a2], @source_index.search('a', '= 2') - assert_equal [], @source_index.search("bogusstring") - assert_equal [], @source_index.search("gem_one", "= 3.2.1") + assert_equal [], @source_index.search('bogusstring') + assert_equal [], @source_index.search('a', '= 3') - @a1 = quick_gem 'a', '1' - @a2 = quick_gem 'a', '2' - - source_index = Gem::SourceIndex.new @a1.full_name => @a1, - @a2.full_name => @a2 + source_index = Gem::SourceIndex.new + source_index.add_spec @a1 + source_index.add_spec @a2 assert_equal [@a1], source_index.search(@a1.name, '= 1') @@ -276,7 +467,7 @@ class TestGemSourceIndex < RubyGemTestCase end def test_specification - assert_equal @gem1, @source_index.specification(@gem1.full_name) + assert_equal @a1, @source_index.specification(@a1.full_name) assert_nil @source_index.specification("foo-1.2.4") end @@ -298,9 +489,11 @@ class TestGemSourceIndex < RubyGemTestCase assert_equal [], @source_index.gems.keys.sort use_ui @ui do - @source_index.update @uri + @source_index.update @uri, true - assert_equal @gem_names.split, @source_index.gems.keys.sort + assert_equal [@a1.full_name, @a2.full_name, @a_evil9.full_name, + @c1_2.full_name], + @source_index.gems.keys.sort end paths = @fetcher.paths @@ -315,15 +508,42 @@ class TestGemSourceIndex < RubyGemTestCase old_gem_conf = Gem.configuration Gem.configuration = Gem::ConfigFile.new([]) + latest_names = [@a2, @a_evil9, @b2, @c1_2].map { |s| s.full_name } + latest_index = util_zip latest_names.join("\n") + @fetcher.data["#{@gem_repo}/quick/latest_index.rz"] = latest_index + + marshal_uri = File.join @gem_repo, "quick", "Marshal.#{@marshal_version}", + "#{@b2.full_name}.gemspec.rz" + @fetcher.data[marshal_uri] = util_zip Marshal.dump(@b2) + + use_ui @ui do + @source_index.update @uri, false + + assert_equal latest_names, @source_index.gems.keys.sort + end + + paths = @fetcher.paths + assert_equal "#{@gem_repo}/quick/latest_index.rz", paths.shift + assert_equal marshal_uri, paths.shift + + assert paths.empty?, paths.join(', ') + ensure + Gem.configuration = old_gem_conf + end + + def test_update_incremental_all + old_gem_conf = Gem.configuration + Gem.configuration = Gem::ConfigFile.new([]) + quick_index = util_zip @all_gem_names.join("\n") @fetcher.data["#{@gem_repo}/quick/index.rz"] = quick_index marshal_uri = File.join @gem_repo, "quick", "Marshal.#{@marshal_version}", - "#{@gem3.full_name}.gemspec.rz" - @fetcher.data[marshal_uri] = util_zip Marshal.dump(@gem3) + "#{@b2.full_name}.gemspec.rz" + @fetcher.data[marshal_uri] = util_zip Marshal.dump(@b2) use_ui @ui do - @source_index.update @uri + @source_index.update @uri, true assert_equal @all_gem_names, @source_index.gems.keys.sort end @@ -345,13 +565,13 @@ class TestGemSourceIndex < RubyGemTestCase @fetcher.data["#{@gem_repo}/quick/index.rz"] = quick_index marshal_uri = File.join @gem_repo, "quick", "Marshal.#{@marshal_version}", - "#{@gem3.full_name}.gemspec.rz" + "#{@b2.full_name}.gemspec.rz" - yaml_uri = "#{@gem_repo}/quick/#{@gem3.full_name}.gemspec.rz" - @fetcher.data[yaml_uri] = util_zip @gem3.to_yaml + yaml_uri = "#{@gem_repo}/quick/#{@b2.full_name}.gemspec.rz" + @fetcher.data[yaml_uri] = util_zip @b2.to_yaml use_ui @ui do - @source_index.update @uri + @source_index.update @uri, true assert_equal @all_gem_names, @source_index.gems.keys.sort end @@ -374,16 +594,16 @@ class TestGemSourceIndex < RubyGemTestCase @fetcher.data["#{@gem_repo}/quick/index.rz"] = quick_index marshal_uri = File.join @gem_repo, "quick", "Marshal.#{@marshal_version}", - "#{@gem3.full_name}.gemspec.rz" - marshal_data = Marshal.dump(@gem3) + "#{@b2.full_name}.gemspec.rz" + marshal_data = Marshal.dump(@b2) marshal_data[0] = (Marshal::MAJOR_VERSION - 1).chr @fetcher.data[marshal_uri] = util_zip marshal_data - yaml_uri = "#{@gem_repo}/quick/#{@gem3.full_name}.gemspec.rz" - @fetcher.data[yaml_uri] = util_zip @gem3.to_yaml + yaml_uri = "#{@gem_repo}/quick/#{@b2.full_name}.gemspec.rz" + @fetcher.data[yaml_uri] = util_zip @b2.to_yaml use_ui @ui do - @source_index.update @uri + @source_index.update @uri, true assert_equal @all_gem_names, @source_index.gems.keys.sort end @@ -398,22 +618,48 @@ class TestGemSourceIndex < RubyGemTestCase Gem.configuration = old_gem_conf end + def test_update_subdir + @gem_repo = @gem_repo + "/subdir" + + util_setup_bulk_fetch true + + @source_index.gems.replace({}) + assert_equal [], @source_index.gems.keys.sort + + uri = @uri.to_s + "/subdir" + + use_ui @ui do + @source_index.update uri, true + + assert_equal [@a1.full_name, @a2.full_name, @a_evil9.full_name, + @c1_2.full_name], + @source_index.gems.keys.sort + end + + paths = @fetcher.paths + + assert_equal "#{@gem_repo}/quick/index.rz", paths.shift + assert_equal "#{@gem_repo}/Marshal.#{@marshal_version}.Z", paths.shift + + assert paths.empty?, paths.join(', ') + end + def test_update_with_missing marshal_uri = File.join @gem_repo, "quick", "Marshal.#{@marshal_version}", - "#{@gem3.full_name}.gemspec.rz" - dumped = Marshal.dump @gem3 + "#{@c1_2.full_name}.gemspec.rz" + dumped = Marshal.dump @c1_2 @fetcher.data[marshal_uri] = util_zip(dumped) use_ui @ui do - @source_index.update_with_missing @uri, [@gem3.full_name] + @source_index.update_with_missing @uri, [@c1_2.full_name] end - spec = @source_index.specification(@gem3.full_name) + spec = @source_index.specification(@c1_2.full_name) # We don't care about the equality of undumped attributes - @gem3.files = spec.files - @gem3.loaded_from = spec.loaded_from + @c1_2.files = spec.files + @c1_2.loaded_from = spec.loaded_from - assert_equal @gem3, spec + assert_equal @c1_2, spec end def util_setup_bulk_fetch(compressed) @@ -427,3 +673,4 @@ class TestGemSourceIndex < RubyGemTestCase end end + diff --git a/test/rubygems/test_gem_source_info_cache.rb b/test/rubygems/test_gem_source_info_cache.rb index 570b643bc5..3bdaef5aa9 100644 --- a/test/rubygems/test_gem_source_info_cache.rb +++ b/test/rubygems/test_gem_source_info_cache.rb @@ -25,7 +25,12 @@ class TestGemSourceInfoCache < RubyGemTestCase @sic = Gem::SourceInfoCache.new @sic.instance_variable_set :@fetcher, @fetcher + @si_new = Gem::SourceIndex.new + @sice_new = Gem::SourceInfoCacheEntry.new @si_new, 0 + prep_cache_files @sic + + @sic.reset_cache_data end def teardown @@ -35,8 +40,10 @@ class TestGemSourceInfoCache < RubyGemTestCase def test_self_cache_refreshes Gem.configuration.update_sources = true #true by default - source_index = Gem::SourceIndex.new 'key' => 'sys' - @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = source_index.dump + si = Gem::SourceIndex.new + si.add_spec @a1 + + @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = si.dump Gem.sources.replace %W[#{@gem_repo}] @@ -51,8 +58,10 @@ class TestGemSourceInfoCache < RubyGemTestCase def test_self_cache_skips_refresh_based_on_configuration Gem.configuration.update_sources = false - source_index = Gem::SourceIndex.new 'key' => 'sys' - @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = source_index.dump + si = Gem::SourceIndex.new + si.add_spec @a1 + + @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = si.dump Gem.sources.replace %w[#{@gem_repo}] @@ -66,20 +75,24 @@ class TestGemSourceInfoCache < RubyGemTestCase end def test_self_cache_data - source_index = Gem::SourceIndex.new 'key' => 'sys' - @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = source_index.dump + si = Gem::SourceIndex.new + si.add_spec @a1 + + @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = si.dump Gem::SourceInfoCache.instance_variable_set :@cache, nil - sice = Gem::SourceInfoCacheEntry.new source_index, 0 + sice = Gem::SourceInfoCacheEntry.new si, 0 use_ui @ui do - assert_equal source_index.gems, - Gem::SourceInfoCache.cache_data[@gem_repo].source_index.gems + gems = Gem::SourceInfoCache.cache_data[@gem_repo].source_index.gems + gem_names = gems.map { |_, spec| spec.full_name } + + assert_equal si.gems.map { |_,spec| spec.full_name }, gem_names end end def test_cache_data - assert_equal [['key','sys']], @sic.cache_data.to_a.sort + assert_equal [[@gem_repo, @usr_sice]], @sic.cache_data.to_a.sort end def test_cache_data_dirty @@ -97,7 +110,14 @@ class TestGemSourceInfoCache < RubyGemTestCase data = { @gem_repo => { 'totally' => 'borked' } } - [@sic.system_cache_file, @sic.user_cache_file].each do |fn| + cache_files = [ + @sic.system_cache_file, + @sic.latest_system_cache_file, + @sic.user_cache_file, + @sic.latest_user_cache_file + ] + + cache_files.each do |fn| FileUtils.mkdir_p File.dirname(fn) open(fn, "wb") { |f| f.write Marshal.dump(data) } end @@ -113,7 +133,9 @@ class TestGemSourceInfoCache < RubyGemTestCase def test_cache_data_none_readable FileUtils.chmod 0222, @sic.system_cache_file + FileUtils.chmod 0222, @sic.latest_system_cache_file FileUtils.chmod 0222, @sic.user_cache_file + FileUtils.chmod 0222, @sic.latest_user_cache_file return if (File.stat(@sic.system_cache_file).mode & 0222) != 0222 return if (File.stat(@sic.user_cache_file).mode & 0222) != 0222 # HACK for systems that don't support chmod @@ -129,6 +151,16 @@ class TestGemSourceInfoCache < RubyGemTestCase assert_equal 'unable to locate a writable cache file', e.message end + def test_cache_data_nonexistent + FileUtils.rm @sic.system_cache_file + FileUtils.rm @sic.latest_system_cache_file + FileUtils.rm @sic.user_cache_file + FileUtils.rm @sic.latest_user_cache_file + + # TODO test verbose output + assert_equal [], @sic.cache_data.to_a.sort + end + def test_cache_data_repair data = { @gem_repo => { @@ -152,7 +184,8 @@ class TestGemSourceInfoCache < RubyGemTestCase def test_cache_data_user_fallback FileUtils.chmod 0444, @sic.system_cache_file - assert_equal [['key','usr']], @sic.cache_data.to_a.sort + + assert_equal [[@gem_repo, @usr_sice]], @sic.cache_data.to_a.sort end def test_cache_file @@ -174,60 +207,118 @@ class TestGemSourceInfoCache < RubyGemTestCase end def test_flush - @sic.cache_data['key'] = 'new' + @sic.cache_data[@gem_repo] = @sice_new @sic.update @sic.flush - assert_equal [['key','new']], read_cache(@sic.system_cache_file).to_a.sort + assert_equal [[@gem_repo, @sice_new]], + read_cache(@sic.system_cache_file).to_a.sort + end + + def test_latest_cache_data + util_make_gems + + sice = Gem::SourceInfoCacheEntry.new @source_index, 0 + + @sic.set_cache_data @gem_repo => sice + latest = @sic.latest_cache_data + gems = latest[@gem_repo].source_index.search('a').map { |s| s.full_name } + + assert_equal %w[a-2 a_evil-9], gems + end + + def test_latest_cache_file + latest_cache_file = File.join File.dirname(@gemcache), + "latest_#{File.basename @gemcache}" + assert_equal latest_cache_file, @sic.latest_cache_file + end + + def test_latest_system_cache_file + assert_equal File.join(Gem.dir, "latest_source_cache"), + @sic.latest_system_cache_file + end + + def test_latest_user_cache_file + assert_equal @latest_usrcache, @sic.latest_user_cache_file end def test_read_system_cache - assert_equal [['key','sys']], @sic.cache_data.to_a.sort + assert_equal [[@gem_repo, @sys_sice]], @sic.cache_data.to_a.sort end def test_read_user_cache - FileUtils.chmod 0444, @sic.system_cache_file + FileUtils.chmod 0444, @sic.user_cache_file + FileUtils.chmod 0444, @sic.latest_user_cache_file + + @si = Gem::SourceIndex.new + @si.add_specs @a1, @a2 + + @sice = Gem::SourceInfoCacheEntry.new @si, 0 + + @sic.set_cache_data({ @gem_repo => @sice }) + @sic.update + @sic.write_cache + @sic.reset_cache_data - assert_equal [['key','usr']], @sic.cache_data.to_a.sort + user_cache_data = @sic.cache_data.to_a.sort + + assert_equal 1, user_cache_data.length + user_cache_data = user_cache_data.first + + assert_equal @gem_repo, user_cache_data.first + + gems = user_cache_data.last.source_index.map { |_,spec| spec.full_name } + assert_equal [@a2.full_name], gems end def test_search - si = Gem::SourceIndex.new @gem1.full_name => @gem1 - cache_data = { - @gem_repo => Gem::SourceInfoCacheEntry.new(si, nil) - } + si = Gem::SourceIndex.new + si.add_spec @a1 + cache_data = { @gem_repo => Gem::SourceInfoCacheEntry.new(si, nil) } @sic.instance_variable_set :@cache_data, cache_data - assert_equal [@gem1], @sic.search(//) + assert_equal [@a1], @sic.search(//) + end + + def test_search_all + util_make_gems + + sice = Gem::SourceInfoCacheEntry.new @source_index, 0 + + @sic.set_cache_data @gem_repo => sice + @sic.update + @sic.write_cache + @sic.reset_cache_data + + gem_names = @sic.search(//, false, true).map { |spec| spec.full_name } + + assert_equal %w[a-1 a-2 a_evil-9 c-1.2], gem_names end def test_search_dependency - si = Gem::SourceIndex.new @gem1.full_name => @gem1 - cache_data = { - @gem_repo => Gem::SourceInfoCacheEntry.new(si, nil) - } + si = Gem::SourceIndex.new + si.add_spec @a1 + cache_data = { @gem_repo => Gem::SourceInfoCacheEntry.new(si, nil) } @sic.instance_variable_set :@cache_data, cache_data - dep = Gem::Dependency.new @gem1.name, @gem1.version + dep = Gem::Dependency.new @a1.name, @a1.version - assert_equal [@gem1], @sic.search(dep) + assert_equal [@a1], @sic.search(dep) end def test_search_no_matches - si = Gem::SourceIndex.new @gem1.full_name => @gem1 - cache_data = { - @gem_repo => Gem::SourceInfoCacheEntry.new(si, nil) - } + si = Gem::SourceIndex.new + si.add_spec @a1 + cache_data = { @gem_repo => Gem::SourceInfoCacheEntry.new(si, nil) } @sic.instance_variable_set :@cache_data, cache_data assert_equal [], @sic.search(/nonexistent/) end def test_search_no_matches_in_source - si = Gem::SourceIndex.new @gem1.full_name => @gem1 - cache_data = { - @gem_repo => Gem::SourceInfoCacheEntry.new(si, nil) - } + si = Gem::SourceIndex.new + si.add_spec @a1 + cache_data = { @gem_repo => Gem::SourceInfoCacheEntry.new(si, nil) } @sic.instance_variable_set :@cache_data, cache_data Gem.sources.replace %w[more-gems.example.com] @@ -235,13 +326,12 @@ class TestGemSourceInfoCache < RubyGemTestCase end def test_search_with_source - si = Gem::SourceIndex.new @gem1.full_name => @gem1 - cache_data = { - @gem_repo => Gem::SourceInfoCacheEntry.new(si, nil) - } + si = Gem::SourceIndex.new + si.add_spec @a1 + cache_data = { @gem_repo => Gem::SourceInfoCacheEntry.new(si, nil) } @sic.instance_variable_set :@cache_data, cache_data - assert_equal [[@gem1, @gem_repo]], + assert_equal [[@a1, @gem_repo]], @sic.search_with_source(//) end @@ -254,45 +344,81 @@ class TestGemSourceInfoCache < RubyGemTestCase end def test_write_cache - @sic.cache_data['key'] = 'new' + @sic.cache_data[@gem_repo] = @sice_new @sic.write_cache - assert_equal [['key', 'new']], + assert_equal [[@gem_repo, @sice_new]], read_cache(@sic.system_cache_file).to_a.sort - assert_equal [['key', 'usr']], + assert_equal [[@gem_repo, @usr_sice]], read_cache(@sic.user_cache_file).to_a.sort end def test_write_cache_user FileUtils.chmod 0444, @sic.system_cache_file - @sic.set_cache_data({'key' => 'new'}) + @sic.set_cache_data({@gem_repo => @sice_new}) @sic.update @sic.write_cache - assert_equal [['key', 'sys']], read_cache(@sic.system_cache_file).to_a.sort - assert_equal [['key', 'new']], read_cache(@sic.user_cache_file).to_a.sort + assert File.exist?(@sic.user_cache_file), 'user_cache_file' + assert File.exist?(@sic.latest_user_cache_file), + 'latest_user_cache_file exists' + + assert_equal [[@gem_repo, @sys_sice]], + read_cache(@sic.system_cache_file).to_a.sort + assert_equal [[@gem_repo, @sice_new]], + read_cache(@sic.user_cache_file).to_a.sort end def test_write_cache_user_from_scratch FileUtils.rm_rf @sic.user_cache_file + FileUtils.rm_rf @sic.latest_user_cache_file + FileUtils.chmod 0444, @sic.system_cache_file - @sic.set_cache_data({'key' => 'new'}) + FileUtils.chmod 0444, @sic.latest_system_cache_file + + @si = Gem::SourceIndex.new + @si.add_specs @a1, @a2 + + @sice = Gem::SourceInfoCacheEntry.new @si, 0 + + @sic.set_cache_data({ @gem_repo => @sice }) @sic.update @sic.write_cache - assert_equal [['key', 'sys']], read_cache(@sic.system_cache_file).to_a.sort - assert_equal [['key', 'new']], read_cache(@sic.user_cache_file).to_a.sort + assert File.exist?(@sic.user_cache_file), 'system_cache_file' + assert File.exist?(@sic.latest_user_cache_file), + 'latest_system_cache_file' + + user_cache_data = read_cache(@sic.user_cache_file).to_a.sort + assert_equal 1, user_cache_data.length + user_cache_data = user_cache_data.first + + assert_equal @gem_repo, user_cache_data.first + + gems = user_cache_data.last.source_index.map { |_,spec| spec.full_name } + assert_equal [@a1.full_name, @a2.full_name], gems + + user_cache_data = read_cache(@sic.latest_user_cache_file).to_a.sort + assert_equal 1, user_cache_data.length + user_cache_data = user_cache_data.first + + assert_equal @gem_repo, user_cache_data.first + + gems = user_cache_data.last.source_index.map { |_,spec| spec.full_name } + assert_equal [@a2.full_name], gems end def test_write_cache_user_no_directory FileUtils.rm_rf File.dirname(@sic.user_cache_file) FileUtils.chmod 0444, @sic.system_cache_file - @sic.set_cache_data({'key' => 'new'}) + @sic.set_cache_data({ @gem_repo => @sice_new }) @sic.update @sic.write_cache - assert_equal [['key','sys']], read_cache(@sic.system_cache_file).to_a.sort - assert_equal [['key','new']], read_cache(@sic.user_cache_file).to_a.sort + assert_equal [[@gem_repo, @sys_sice]], + read_cache(@sic.system_cache_file).to_a.sort + assert_equal [[@gem_repo, @sice_new]], + read_cache(@sic.user_cache_file).to_a.sort end end diff --git a/test/rubygems/test_gem_source_info_cache_entry.rb b/test/rubygems/test_gem_source_info_cache_entry.rb index 023baf948b..c1194e34bc 100644 --- a/test/rubygems/test_gem_source_info_cache_entry.rb +++ b/test/rubygems/test_gem_source_info_cache_entry.rb @@ -9,37 +9,68 @@ class TestGemSourceInfoCacheEntry < RubyGemTestCase util_setup_fake_fetcher - @si = Gem::SourceIndex.new @gem1.full_name => @gem1.name + @si = Gem::SourceIndex.new + @si.add_spec @a1 @sic_e = Gem::SourceInfoCacheEntry.new @si, @si.dump.size end def test_refresh @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}.Z"] = - proc { raise Exception } + proc { raise } @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = @si.dump - assert_nothing_raised do - @sic_e.refresh @gem_repo + use_ui @ui do + @sic_e.refresh @gem_repo, true + end + end + + def test_refresh_all + @si.add_spec @a2 + + a1_name = @a1.full_name + a2_name = @a2.full_name + + @fetcher.data["#{@gem_repo}/quick/index.rz"] = + util_zip [a1_name, a2_name].join("\n") + @fetcher.data["#{@gem_repo}/quick/latest_index.rz"] = util_zip a2_name + @fetcher.data["#{@gem_repo}/quick/Marshal.#{Gem.marshal_version}/#{a1_name}.gemspec.rz"] = util_zip Marshal.dump(@a1) + @fetcher.data["#{@gem_repo}/quick/Marshal.#{Gem.marshal_version}/#{a2_name}.gemspec.rz"] = util_zip Marshal.dump(@a2) + @fetcher.data["#{@gem_repo}/Marshal.#{Gem.marshal_version}"] = + Marshal.dump @si + + sic_e = Gem::SourceInfoCacheEntry.new Gem::SourceIndex.new, 0 + + use_ui @ui do + sic_e.refresh @gem_repo, false end + + assert_equal [a2_name], sic_e.source_index.map { |n,| n }.sort + + use_ui @ui do + sic_e.refresh @gem_repo, true + end + + assert_equal [a1_name, a2_name], sic_e.source_index.map { |n,| n }.sort end def test_refresh_bad_uri assert_raise URI::BadURIError do - @sic_e.refresh 'gems.example.com' + @sic_e.refresh 'gems.example.com', true end end def test_refresh_update - si = Gem::SourceIndex.new @gem1.full_name => @gem1, - @gem2.full_name => @gem2 + si = Gem::SourceIndex.new + si.add_spec @a1 + si.add_spec @b2 @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = si.dump use_ui @ui do - @sic_e.refresh @gem_repo + @sic_e.refresh @gem_repo, true end - new_gem = @sic_e.source_index.specification(@gem2.full_name) - assert_equal @gem2.full_name, new_gem.full_name + new_gem = @sic_e.source_index.specification(@b2.full_name) + assert_equal @b2.full_name, new_gem.full_name end end diff --git a/test/rubygems/test_gem_uninstaller.rb b/test/rubygems/test_gem_uninstaller.rb new file mode 100644 index 0000000000..d6e41814ed --- /dev/null +++ b/test/rubygems/test_gem_uninstaller.rb @@ -0,0 +1,43 @@ +require File.join(File.expand_path(File.dirname(__FILE__)), + 'gem_installer_test_case') +require 'rubygems/uninstaller' + +class TestGemUninstaller < GemInstallerTestCase + + def setup + super + + ui = MockGemUi.new + util_setup_gem ui + + use_ui ui do + @installer.install + end + end + + def test_remove_executables_force_keep + uninstaller = Gem::Uninstaller.new nil, :executables => false + + use_ui @ui do + uninstaller.remove_executables @spec + end + + assert_equal true, File.exist?(File.join(@gemhome, 'bin', 'executable')) + + assert_equal "Executables and scripts will remain installed.\n", @ui.output + end + + def test_remove_executables_force_remove + uninstaller = Gem::Uninstaller.new nil, :executables => true + + use_ui @ui do + uninstaller.remove_executables @spec + end + + assert_equal "Removing executable\n", @ui.output + + assert_equal false, File.exist?(File.join(@gemhome, 'bin', 'executable')) + end + +end + |