diff options
author | drbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2008-03-31 22:40:06 +0000 |
---|---|---|
committer | drbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2008-03-31 22:40:06 +0000 |
commit | 8cc45aae947d453acca029e13eb64f3f5f0bf942 () | |
tree | f9485a20c99defe1aae3f32555a41d23c2298ad8 /lib | |
parent | dc8359969ec71ece10357ba9396430db7f029e45 (diff) |
Import RubyGems 1.1.0
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@15873 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
46 files changed, 2883 insertions, 2076 deletions
@@ -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). # - # 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: # - # GEM_SKIP=libA:libB ruby-I../libA -I../libB ./mycode.rb # - # gem:: [String or Gem::Dependency] The gem name or dependency - # instance. # - # version_requirement:: [default=">= 0"] The version - # requirement. # - # return:: [Boolean] true if the Gem is loaded, otherwise false. # - # raises:: [Gem::LoadError] if Gem cannot be found, is listed in - # GEM_SKIP, or version requirement not met. # - 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 - def active_gem_with_options(gem_name, version_requirements, options={}) 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) 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"] ) MUTEX = Mutex.new RubyGemsPackageVersion = RubyGemsVersion - DIRECTORIES = %w[cache doc gems specifications] unless defined?(DIRECTORIES) @@source_index = nil @@win_platform = nil @@ -103,10 +101,129 @@ module Gem @ruby = nil @sources = [] # 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}" end ## - # The directory prefix this RubyGems was installed at. - def self.prefix - prefix = File.dirname File.expand_path(__FILE__) - if prefix == ConfigMap[:sitelibdir] then - nil - else - File.dirname prefix - end 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 end ## - # An Array of Regexps that match windows ruby platforms. - WIN_PATTERNS = [ - /bccwin/i, - /cygwin/i, - /djgpp/i, - /mingw/i, - /mswin/i, - /wince/i, - ] ## - # 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 - # 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' - 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 - # 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 - @sources end - # Provide an alias for the old name. - alias cache source_index - # 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 - # 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 - if defined? RUBY_FRAMEWORK_VERSION then # mac framework support - '/usr/bin' - else # generic install - ConfigMap[:bindir] 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)) end - @gem_path end - # The home directory for the user. - def user_home - @user_home ||= find_home - end - # Return the path to standard location of the users .gemrc file. - def config_file - File.join(Gem.user_home, '.gemrc') end - # The standard configuration object for gems. - def configuration - return @configuration if @configuration - require 'rubygems/config_file' - @configuration = Gem::ConfigFile.new [] - end - # Use the given configuration object (which implements the - # ConfigFile protocol) as the standard configuration object. - def configuration=(config) - @configuration = config - end - # 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 - # Return the searcher object to search for matching gems. - def searcher - MUTEX.synchronize do - @searcher ||= Gem::GemPathSearcher.new - end - end - # 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 - @ruby - end - # 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 - index - end - # 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 - unless gem.respond_to?(:name) && gem.respond_to?(:version_requirements) - 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] - # 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 - # new load - spec = matches.last - if spec.loaded? - return false unless autorequire - result = spec.autorequire ? require(spec.autorequire) : false - return result || false end - spec.loaded = true - @loaded_specs[spec.name] = spec - # Load dependent gems first - spec.dependencies.each do |dep_gem| - activate(dep_gem, autorequire) - 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 - # Now autorequire - if autorequire && spec.autorequire then # DEPRECATED - Array(spec.autorequire).each do |a_lib| - require a_lib - end - end - return true - 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. - def report_activate_error(gem) - matches = Gem.source_index.find_name(gem.name) - if matches.empty? then - error = Gem::LoadError.new( "Could not find RubyGem #{gem.name} (#{gem.version_requirements})\n") - 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 - # 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 - 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 end - def suffixes - ['', '.rb', '.rbw', '.so', '.bundle', '.dll', '.sl', '.jar'] - end - def suffix_pattern - @suffix_pattern ||= "{#{suffixes.join(',')}}" end - # manage_gems is useless and deprecated. Don't call it anymore. This - # will warn in two releases. - def manage_gems - # do nothing - end - private - # Return all the partial paths in the given +gemdir+. - def all_partials(gemdir) - Dir[File.join(gemdir, 'gems/*')] - 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] } 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) end end 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 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 - # 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 begin - File.expand_path("~") - rescue StandardError => ex - if File::ALT_SEPARATOR - "C:/" - else - "/" - end end end 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 @@ -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} end end end @@ -123,6 +123,7 @@ module Gem end private def load_and_instantiate(command_name) command_name = command_name.to_s retried = false @@ -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', '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 - 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..." - srcindex = Gem::SourceIndex.from_installed_gems - primary_gems = {} - 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 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 - } - uninstall_command = Gem::CommandManager.instance['uninstall'] - deplist = DependencyList.new - gems_to_cleanup.uniq.each do |spec| deplist.add(spec) end - 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}" - options[:args] = [spec.name] - options[:version] = "= #{spec.version}" - options[:executables] = true - uninstall_command.merge_options(options) - begin - uninstall_command.execute - rescue Gem::DependencyRemovalException => ex - say "Unable to uninstall #{spec.full_name} ... continuing with remaining gems" - end - end end - - say "Clean Up Complete" end end - end end @@ -25,19 +25,18 @@ class Gem::Commands::EnvironmentCommand < Gem::Command def execute out = '' arg = options[:args][0] - if begins?("packageversion", arg) then out << Gem::RubyGemsPackageVersion - elsif begins?("version", arg) then out << Gem::RubyGemsVersion - elsif begins?("gemdir", arg) then out << Gem.dir - elsif begins?("gempath", arg) then - out << Gem.path.join("\n") - elsif begins?("remotesources", arg) then out << Gem.sources.join("\n") - elsif arg then - fail Gem::CommandLineError, "Unknown enviroment option [#{arg}]" - else 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 end say out true @@ -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 end - say "Downloaded #{gem_file}" end end @@ -62,13 +62,15 @@ class Gem::Commands::InstallCommand < Gem::Command :install_dir => options[:install_dir], :security_policy => options[:security_policy], :wrappers => options[:wrappers], } get_all_gem_names.each do |gem_name| begin - inst = Gem::DependencyInstaller.new gem_name, options[:version], - install_options - inst.install 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}" rescue Gem::GemNotFoundException => e alert_error e.message # 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 end end @@ -6,10 +6,8 @@ module Gem class ListCommand < QueryCommand def initialize - super( - 'list', - 'Display all gems whose name starts with STRING' - ) remove_option('--name-matches') end @@ -2,7 +2,7 @@ require 'yaml' require 'zlib' require 'rubygems/command' -require 'rubygems/gem_open_uri' class Gem::Commands::MirrorCommand < Gem::Command @@ -1,15 +1,25 @@ require 'rubygems/command' require 'rubygems/local_remote_options' require 'rubygems/source_info_cache' class Gem::Commands::QueryCommand < Gem::Command include Gem::LocalRemoteOptions def initialize(name = 'query', summary = 'Query gem information in local or remote repositories') super name, summary, - :name => /.*/, :domain => :local, :details => false, :versions => true 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_local_remote_options end def defaults_str # :nodoc: - "--local --name-matches '.*' --no-details --versions" end def execute name = options[:name] if local? then say say "*** LOCAL GEMS ***" say - output_query_results Gem.cache.search(name) end if remote? then say say "*** REMOTE GEMS ***" say - output_query_results Gem::SourceInfoCache.search(name) end end private 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 @@ -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) 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 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.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 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) say "*** Removed #{desc} source cache ***" - elsif ! File.writable?(fn) say "*** Unable to remove #{desc} source cache (write protected) ***" else say "*** Unable to remove #{desc} source cache ***" @@ -3,6 +3,7 @@ require 'rubygems/command' require 'rubygems/local_remote_options' require 'rubygems/version_option' require 'rubygems/source_info_cache' 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])) 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 @@ -35,6 +35,11 @@ module Gem options[:install_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 end end end @@ -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) 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) else - return nil end end @@ -1,8 +1,10 @@ require 'rubygems/command' require 'rubygems/install_update_options' require 'rubygems/local_remote_options' require 'rubygems/source_info_cache' require 'rubygems/version_option' 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..." 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..." end - hig = highest_installed_gems = {} 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(//) - gems_to_update = if options[:args].empty? then - which_to_update(highest_installed_gems, remote_gemspecs) - else - options[:args] - end - options[:domain] = :remote # install from remote source - # HACK use the real API - install_command = Gem::CommandManager.instance['install'] 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 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 ', '}" end end end @@ -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_original_require path else raise load_error @@ -11,6 +11,9 @@ module Gem if defined? RUBY_FRAMEWORK_VERSION then File.join File.dirname(ConfigMap[:sitedir]), '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'] end # The default system-wide source info cache directory. @@ -22,8 +22,7 @@ class Gem::DependencyInstaller } ## - # Creates a new installer instance that will install +gem_name+ using - # version requirement +version+ and +options+. # # 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 = {}) 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] @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" - 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 ## @@ -208,9 +126,57 @@ class Gem::DependencyInstaller @gems_to_install = dependency_list.dependency_order.reverse end ## # Installs the gem and all its dependencies. - def install 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 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 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 spec = inst.install @@ -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::DocumentError < Gem::Exception; end @@ -65,3 +68,17 @@ class Gem::RemoteSourceException < Gem::Exception; end class Gem::VerificationError < Gem::Exception; end @@ -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? 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.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] end end format end @@ -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 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, "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})" next end - abbreviate spec - sanitize spec - @master_index.add spec - @quick_index.add spec - @marshal_index.add spec - progress.updated spec.original_name - 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)" 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.each do |file| - relative_name = file[/\A#{Regexp.escape @directory}.(.*)/, 1] - dest_name = File.join @dest_directory, relative_name - FileUtils.rm_rf dest_name, :verbose => verbose - FileUtils.mv file, @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' @@ -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 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 }) File.open "#{filename}.#{ext}", "wb" do |file| file.write zipped end @@ -0,0 +1,35 @@ @@ -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 - @files << compressed_file_name 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 if data != unzip(compressed_data) then - fail "Compressed file #{compressed_fn} does not match uncompressed file #{fn}" end end @@ -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) 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 end def add(spec) @@ -25,6 +25,12 @@ module Gem::InstallUpdateOptions options[:install_dir] = File.expand_path(value) end add_option(:"Install/Update", '-d', '--[no-]rdoc', 'Generate RDoc documentation for the gem on', 'install') do |value, options| @@ -63,7 +63,8 @@ class Gem::Installer :force => false, :install_dir => Gem.dir, :exec_format => false, - :env_shebang => false }.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] 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 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 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" 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 GOTO :EOF :WinNT -"%~dp0ruby.exe" "%~dpn0" %* TEXT end @@ -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 uned (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 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 @@ -0,0 +1,24 @@ @@ -0,0 +1,245 @@ @@ -0,0 +1,219 @@ @@ -0,0 +1,143 @@ @@ -0,0 +1,86 @@ @@ -0,0 +1,99 @@ @@ -0,0 +1,180 @@ @@ -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 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) @proxy_uri = case proxy when :no_proxy then nil @@ -38,6 +43,65 @@ class Gem::RemoteFetcher end 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}" 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}" 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) if file_uri?(uri) open(get_file_uri_path(uri), &block) else - connection_options = { - "User-Agent" => "RubyGems/#{Gem::RubyGemsVersion} #{Gem::Platform.local}" - } - 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)||''] end - uri = URI.parse uri unless URI::Generic === uri unless uri.nil? || uri.user.nil? || uri.user.empty? then - connection_options[:http_basic_authentication] = - [unescape(uri.user), unescape(uri.password)] end - open(uri, connection_options, &block) end end @@ -16,6 +16,8 @@ class Gem::Requirement include Comparable OPS = { "=" => lambda { |v, r| v == r }, "!=" => lambda { |v, r| v != r }, @@ -2,5 +2,5 @@ # This file is auto-generated by build scripts. # See: rake update_version module Gem - RubyGemsVersion = '1.0.1' end @@ -4,6 +4,7 @@ # See LICENSE.txt for permissions. #++ require 'rubygems/gem_openssl' # = Signed Gems README @@ -1,7 +1,7 @@ require 'webrick' -require 'rdoc/template' require 'yaml' require 'zlib' 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> - </div> - </div> </div> -<div id="validator-badges"> - <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p> -</div> -</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) 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 end paths = { "/gems" => "/cache/", "/doc_root" => "/doc/" } @@ -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. - # - class SourceIndex - include Enumerable 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") } 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}'" end - return nil end - end - # Instance Methods ----------------------------------------------- - # 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 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.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] - next unless prev_ver.nil? or curr_ver >= prev_ver - if prev_ver.nil? or curr_ver > prev_ver then - result[name].clear - latest[name] = curr_ver - end - result[name] << spec end - result.values.flatten - end - # Add a gem specification to the source index. - def add_spec(gem_spec) - @gems[gem_spec.full_name] = gem_spec end - # Remove a gem specification named +full_name+. - 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' - 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 - unless Gem::Requirement === version_requirement then - version_requirement = Gem::Requirement.create version_requirement end - specs = @gems.values.select do |spec| - spec.name =~ gem_pattern and - version_requirement.satisfied_by? spec.version - end - if only_platform then - specs = specs.select do |spec| - Gem::Platform.match spec.platform - end - end - specs.sort_by { |s| s.sort_obj } - 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. - # - def outdated - dep = Gem::Dependency.new '', Gem::Requirement.default - remotes = Gem::SourceInfoCache.search dep, true - 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 - outdateds end - def update(source_uri) - use_incremental = false - begin - gem_names = fetch_quick_index source_uri - remove_extra gem_names - missing_gems = find_missing gem_names - return false if missing_gems.size.zero? - say "missing #{missing_gems.size} gems" if - missing_gems.size > 0 and Gem.configuration.really_verbose - 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 - 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 - true - end - def ==(other) # :nodoc: - self.class === other and @gems == other.gems end - def dump - Marshal.dump(self) end - protected - attr_reader :gems - private - def fetcher - require 'rubygems/remote_fetcher' - Gem::RemoteFetcher.fetcher - end - def fetch_index_from(source_uri) - @fetch_error = nil - 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 end end - nil end - 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 - # 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 raise Gem::OperationNotSupportedError, - "No quick index found: " + ex.message 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 - 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 - # 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 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, "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" \ "\t#{@fetch_error.message}" - else - add_spec gemspec - progress.updated spec_name - end - @fetch_error = nil end - progress.done - progress.count end - end - # Cache is an alias for SourceIndex to allow older YAMLized source - # index objects to load properly. Cache = SourceIndex end @@ -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 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 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) end def initialize # :nodoc: @cache_data = nil @cache_file = nil @dirty = false 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 - sice = Gem::SourceInfoCacheEntry.new Gem::SourceIndex.new, 0 - sice.refresh url # HACK may be unnecessary, see ::cache and #refresh - @cache_data[url] = sice @cache_data end - def reset_cache_data - @cache_data = {} - end - # 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 # 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 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 end flush end - # Searches all source indexes for +pattern+. - def search(pattern, platform_only = false) 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) 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 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 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) 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 end - if File.writable?(dir) - File.open(fn, "wb") { |f| f << Marshal.dump({}) } - return fn - end - nil end end @@ -3,24 +3,31 @@ require 'rubygems/source_index' require 'rubygems/remote_fetcher' ## -# Entrys 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 end - def refresh(source_uri) 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 @size = remote_size updated end @@ -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) end unless defined? Time.today end # :startdoc: @@ -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) @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] 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. - # 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 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 unless answer then say "Executables and scripts will remain installed." else 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 # - # 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. - # def remove_all(list) - list.dup.each { |gem| remove(gem, 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, "Gem is not installed in directory #{@gem_home}" 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 deplist.ok_to_remove?(spec.full_name) || ask_if_ok(spec) end @@ -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 ].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) end # Return a progress reporter object |