From 25a9b62d45ddd60a231272567c7dda9337da9b62 Mon Sep 17 00:00:00 2001 From: ryan Date: Tue, 1 Mar 2011 09:41:32 +0000 Subject: Import rubygems 1.6.0 (released version @ 58d8a0b9) git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@30996 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- lib/rubygems.rb | 197 ++++++++++++++++++++--------- lib/rubygems/commands/owner_command.rb | 5 +- lib/rubygems/commands/pristine_command.rb | 8 +- lib/rubygems/commands/push_command.rb | 12 +- lib/rubygems/commands/setup_command.rb | 11 +- lib/rubygems/commands/uninstall_command.rb | 5 + lib/rubygems/commands/unpack_command.rb | 26 ++-- lib/rubygems/commands/update_command.rb | 137 ++++++++++++-------- lib/rubygems/config_file.rb | 17 ++- lib/rubygems/custom_require.rb | 19 ++- lib/rubygems/dependency.rb | 41 ++++-- lib/rubygems/dependency_installer.rb | 27 +++- lib/rubygems/dependency_list.rb | 38 +++++- lib/rubygems/doc_manager.rb | 9 +- lib/rubygems/gem_path_searcher.rb | 46 ++++++- lib/rubygems/gemcutter_utilities.rb | 31 +++++ lib/rubygems/installer.rb | 18 ++- lib/rubygems/installer_test_case.rb | 38 +++--- lib/rubygems/package/tar_reader/entry.rb | 3 +- lib/rubygems/remote_fetcher.rb | 39 ++++-- lib/rubygems/requirement.rb | 5 +- lib/rubygems/security.rb | 1 + lib/rubygems/source_index.rb | 5 +- lib/rubygems/spec_fetcher.rb | 6 +- lib/rubygems/specification.rb | 83 +++++++++--- lib/rubygems/test_case.rb | 155 ++++++++++++++++------- lib/rubygems/test_utilities.rb | 12 +- lib/rubygems/uninstaller.rb | 33 +++-- lib/rubygems/user_interaction.rb | 53 ++++---- lib/rubygems/validator.rb | 2 +- 30 files changed, 764 insertions(+), 318 deletions(-) (limited to 'lib') diff --git a/lib/rubygems.rb b/lib/rubygems.rb index ddb4c2aaa4..b1aa6c547d 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -32,8 +32,8 @@ if Gem::GEM_PRELUDE_SUCKAGE and defined?(Gem::QuickLoader) then end require 'rubygems/defaults' +require "rubygems/dependency_list" require 'rbconfig' -require 'thread' # HACK: remove me for 1.5 - this is here just for rails ## # RubyGems is the Ruby standard for publishing and managing third party @@ -124,7 +124,7 @@ require 'thread' # HACK: remove me for 1.5 - this is here just for rails # -The RubyGems Team module Gem - RubyGemsVersion = VERSION = '1.5.0' + VERSION = '1.6.0' ## # Raised when RubyGems is unable to load or activate a gem. Contains the @@ -137,25 +137,11 @@ module Gem # Version requirement of gem attr_accessor :requirement + end - def version_requirement - warn "#{Gem.location_of_caller.join ':'}:Warning: " \ - "Gem::LoadError#version_requirement is deprecated " \ - "and will be removed on or after January 2011. " \ - "Use #requirement." - - requirement - end - - def version_requirement= requirement - warn "#{Gem.location_of_caller.join ':'}:Warning: " \ - "Gem::LoadError#version_requirement= is deprecated " \ - "and will be removed on or after January 2011. " \ - "Use #requirement." + # :stopdoc: - self.requirement = requirement - end - end + RubyGemsVersion = VERSION RbConfigPriorities = %w[ EXEEXT RUBY_SO_NAME arch bindir datadir libdir ruby_install_name @@ -180,6 +166,9 @@ module Gem DIRECTORIES = %w[cache doc gems specifications] unless defined?(DIRECTORIES) RubyGemsPackageVersion = VERSION + + RUBYGEMS_DIR = File.dirname File.expand_path(__FILE__) + # :startdoc: ## @@ -216,21 +205,30 @@ module Gem # activated. Returns false if it can't find the path in a gem. def self.try_activate path + # finds the _latest_ version... regardless of loaded specs and their deps + + # TODO: use find_all and bork if ambiguous + spec = Gem.searcher.find path return false unless spec - Gem.activate spec.name, "= #{spec.version}" + begin + Gem.activate spec.name, "= #{spec.version}" + rescue Gem::LoadError # this could fail due to gem dep collisions, go lax + Gem.activate spec.name + end + return true end ## - # Activates an installed gem matching +gem+. The gem must satisfy + # Activates an installed gem matching +dep+. The gem must satisfy # +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 + # Gem#activate adds the library paths in +dep+ 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 @@ -239,63 +237,78 @@ module Gem # More information on version requirements can be found in the # Gem::Requirement and Gem::Version documentation. - def self.activate(gem, *requirements) + def self.activate(dep, *requirements) + # TODO: remove options entirely if requirements.last.is_a?(Hash) options = requirements.pop else options = {} end - sources = options[:sources] || [] - - if requirements.empty? then - requirements = Gem::Requirement.default - end - - unless gem.respond_to?(:name) and - gem.respond_to?(:requirement) then - gem = Gem::Dependency.new(gem, requirements) - end + requirements = Gem::Requirement.default if requirements.empty? + dep = Gem::Dependency.new(dep, requirements) unless Gem::Dependency === dep - matches = Gem.source_index.find_name(gem.name, gem.requirement) - report_activate_error(gem) if matches.empty? + # TODO: remove sources entirely + sources = options[:sources] || [] + matches = Gem.source_index.search dep, true + report_activate_error(dep) if matches.empty? - if @loaded_specs[gem.name] then + if @loaded_specs[dep.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] + existing_spec = @loaded_specs[dep.name] unless matches.any? { |spec| spec.version == existing_spec.version } then - sources_message = sources.map { |spec| spec.full_name } - stack_message = @loaded_stacks[gem.name].map { |spec| spec.full_name } + sources_message = sources.map { |spec| spec.full_name } + stack_message = @loaded_stacks[dep.name].map { |spec| spec.full_name } - msg = "can't activate #{gem} for #{sources_message.inspect}, " - msg << "already activated #{existing_spec.full_name} for " - msg << "#{stack_message.inspect}" + msg = "can't activate #{dep} for #{sources_message.inspect}, " + msg << "already activated #{existing_spec.full_name} for " + msg << "#{stack_message.inspect}" - e = Gem::LoadError.new msg - e.name = gem.name - e.requirement = gem.requirement + e = Gem::LoadError.new msg + e.name = dep.name + e.requirement = dep.requirement - raise e + raise e end return false end - # new load spec = matches.last + + conf = spec.conflicts + unless conf.empty? then + why = conf.map { |act,con| + "#{act.full_name} conflicts with #{con.join(", ")}" + }.join ", " + + # TODO: improve message by saying who activated `con` + + raise LoadError, "Unable to activate #{spec.full_name}, because #{why}" + end + return false if spec.loaded? spec.loaded = true - @loaded_specs[spec.name] = spec + @loaded_specs[spec.name] = spec @loaded_stacks[spec.name] = sources.dup - # Load dependent gems first - spec.runtime_dependencies.each do |dep_gem| - activate dep_gem, :sources => [spec, *sources] + spec.runtime_dependencies.each do |spec_dep| + next if Gem.loaded_specs.include? spec_dep.name + specs = Gem.source_index.search spec_dep, true + + if specs.size == 1 then + self.activate spec_dep + else + name = spec_dep.name + unresolved_deps[name] = unresolved_deps[name].merge spec_dep + end end + unresolved_deps.delete spec.name + require_paths = spec.require_paths.map do |path| File.join spec.full_gem_path, path end @@ -314,6 +327,10 @@ module Gem return true end + def self.unresolved_deps + @unresolved_deps ||= Hash.new { |h, n| h[n] = Gem::Dependency.new n } + end + ## # An Array of all possible load paths for all versions of all gems in the # Gem installation. @@ -546,6 +563,7 @@ module Gem # 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 self.find_home unless RUBY_VERSION > '1.9' then @@ -722,6 +740,27 @@ module Gem @gem_path end + ## + # Get the appropriate cache path. + # + # Pass a string to use a different base path, or nil/false (default) for + # Gem.dir. + # + + def self.cache_dir(custom_dir=false) + File.join(custom_dir ? custom_dir : Gem.dir, 'cache') + end + + ## + # Given a gem path, find the gem in cache. + # + # Pass a string as the second argument to use a different base path, or + # nil/false (default) for Gem.dir. + + def self.cache_gem(filename, user_dir=false) + File.join(cache_dir(user_dir), filename) + end + ## # Set array of platforms this RubyGems supports (primarily for testing). @@ -787,17 +826,16 @@ module Gem end ## - # The directory prefix this RubyGems was installed at. + # The directory prefix this RubyGems was installed at. If your + # prefix is in a standard location (ie, rubygems is installed where + # you'd expect it to be), then prefix returns nil. def self.prefix - dir = File.dirname File.expand_path(__FILE__) - prefix = File.dirname dir + prefix = File.dirname RUBYGEMS_DIR - if prefix == File.expand_path(ConfigMap[:sitelibdir]) or - prefix == File.expand_path(ConfigMap[:libdir]) or - 'lib' != File.basename(dir) then - nil - else + if prefix != File.expand_path(ConfigMap[:sitelibdir]) and + prefix != File.expand_path(ConfigMap[:libdir]) and + 'lib' == File.basename(RUBYGEMS_DIR) then prefix end end @@ -905,6 +943,29 @@ module Gem @ruby end + def self.latest_spec_for name + dependency = Gem::Dependency.new name + fetcher = Gem::SpecFetcher.fetcher + spec_tuples = fetcher.find_matching dependency + + match = spec_tuples.select { |(n, _, p), _| + n == name and Gem::Platform.match p + }.sort_by { |(_, version, _), _| + version + }.last + + match and fetcher.fetch_spec(*match) + end + + def self.latest_version_for name + spec = latest_spec_for name + spec and spec.version + end + + def self.latest_rubygems_version + latest_version_for "rubygems-update" + end + ## # A Gem::Version for the currently running ruby. @@ -1002,6 +1063,13 @@ module Gem @suffix_pattern ||= "{#{suffixes.join(',')}}" end + def self.loaded_path? path + # TODO: ruby needs a feature to let us query what's loaded in 1.8 and 1.9 + $LOADED_FEATURES.find { |s| + s =~ /(^|\/)#{Regexp.escape path}#{Regexp.union(*Gem.suffixes)}$/ + } + end + ## # Suffixes for require-able paths. @@ -1148,12 +1216,15 @@ module Gem attr_reader :pre_uninstall_hooks - # :stopdoc: - - alias cache source_index # an alias for the old name + end - # :startdoc: + def self.cache # :nodoc: + warn "#{Gem.location_of_caller.join ':'}:Warning: " \ + "Gem::cache is deprecated and will be removed on or after " \ + "August 2012. " \ + "Use Gem::source_index." + source_index end ## diff --git a/lib/rubygems/commands/owner_command.rb b/lib/rubygems/commands/owner_command.rb index 8b770e6f54..34f4efaa59 100644 --- a/lib/rubygems/commands/owner_command.rb +++ b/lib/rubygems/commands/owner_command.rb @@ -23,6 +23,7 @@ class Gem::Commands::OwnerCommand < Gem::Command def initialize super 'owner', description add_proxy_option + add_key_option defaults.merge! :add => [], :remove => [] add_option '-a', '--add EMAIL', 'Add an owner' do |value, options| @@ -45,7 +46,7 @@ class Gem::Commands::OwnerCommand < Gem::Command def show_owners name response = rubygems_api_request :get, "api/v1/gems/#{name}/owners.yaml" do |request| - request.add_field "Authorization", Gem.configuration.rubygems_api_key + request.add_field "Authorization", api_key end with_response response do |resp| @@ -70,7 +71,7 @@ class Gem::Commands::OwnerCommand < Gem::Command owners.each do |owner| response = rubygems_api_request method, "api/v1/gems/#{name}/owners" do |request| request.set_form_data 'email' => owner - request.add_field "Authorization", Gem.configuration.rubygems_api_key + request.add_field "Authorization", api_key end with_response response diff --git a/lib/rubygems/commands/pristine_command.rb b/lib/rubygems/commands/pristine_command.rb index 64ea8ce91c..9a0b2e9f11 100644 --- a/lib/rubygems/commands/pristine_command.rb +++ b/lib/rubygems/commands/pristine_command.rb @@ -79,11 +79,13 @@ revert the gem. say "Restoring gem(s) to pristine condition..." specs.each do |spec| - gem = Dir[File.join(Gem.dir, 'cache', spec.file_name)].first + gem = spec.cache_gem if gem.nil? then - alert_error "Cached gem for #{spec.full_name} not found, use `gem install` to restore" - next + say "Cached gem for #{spec.full_name} not found, attempting to fetch..." + dep = Gem::Dependency.new spec.name, spec.version + Gem::RemoteFetcher.fetcher.download_to_cache dep + gem = spec.cache_gem end # TODO use installer options diff --git a/lib/rubygems/commands/push_command.rb b/lib/rubygems/commands/push_command.rb index e72b74ebbf..c85a259564 100644 --- a/lib/rubygems/commands/push_command.rb +++ b/lib/rubygems/commands/push_command.rb @@ -27,7 +27,8 @@ class Gem::Commands::PushCommand < Gem::Command def initialize super 'push', description add_proxy_option - + add_key_option + add_option( '--host HOST', 'Push to another gemcutter-compatible host' @@ -42,17 +43,20 @@ class Gem::Commands::PushCommand < Gem::Command end def send_gem name - say "Pushing gem to #{options[:host] || Gem.host}..." - args = [:post, "api/v1/gems"] args << options[:host] if options[:host] + if Gem.latest_rubygems_version < Gem::Version.new(Gem::VERSION) then + alert_error "Using beta/unreleased version of rubygems. Not pushing." + terminate_interaction 1 + end + response = rubygems_api_request(*args) do |request| request.body = Gem.read_binary name request.add_field "Content-Length", request.body.size request.add_field "Content-Type", "application/octet-stream" - request.add_field "Authorization", Gem.configuration.rubygems_api_key + request.add_field "Authorization", api_key end with_response response diff --git a/lib/rubygems/commands/setup_command.rb b/lib/rubygems/commands/setup_command.rb index f40733a3ed..9090353e3b 100644 --- a/lib/rubygems/commands/setup_command.rb +++ b/lib/rubygems/commands/setup_command.rb @@ -33,13 +33,7 @@ class Gem::Commands::SetupCommand < Gem::Command end add_option '--[no-]vendor', - 'Install into vendorlibdir not sitelibdir', - '(Requires Ruby 1.8.7)' do |vendor, options| - if vendor and Gem.ruby_version < Gem::Version.new('1.8.7') then - raise OptionParser::InvalidOption, - "requires ruby 1.8.7+ (you have #{Gem.ruby_version})" - end - + 'Install into vendorlibdir not sitelibdir' do |vendor, options| options[:site_or_vendor] = vendor ? :vendorlibdir : :sitelibdir end @@ -61,7 +55,7 @@ class Gem::Commands::SetupCommand < Gem::Command end def check_ruby_version - required_version = Gem::Requirement.new '>= 1.8.6' + required_version = Gem::Requirement.new '>= 1.8.7' unless required_version.satisfied_by? Gem.ruby_version then alert_error "Expected Ruby version #{required_version}, is #{Gem.ruby_version}" @@ -341,7 +335,6 @@ abort "#{deprecation_message}" require 'rdoc/rdoc' - args << '--quiet' args << '--main' << 'README.rdoc' args << '.' args << 'README.rdoc' << 'UPGRADING.rdoc' diff --git a/lib/rubygems/commands/uninstall_command.rb b/lib/rubygems/commands/uninstall_command.rb index 52d5b03edb..d190777825 100644 --- a/lib/rubygems/commands/uninstall_command.rb +++ b/lib/rubygems/commands/uninstall_command.rb @@ -55,6 +55,11 @@ class Gem::Commands::UninstallCommand < Gem::Command options[:user_install] = value end + add_option('--[no-]format-executable', + 'Assume executable names match Ruby\'s prefix and suffix.') do |value, options| + options[:format_executable] = value + end + add_version_option add_platform_option end diff --git a/lib/rubygems/commands/unpack_command.rb b/lib/rubygems/commands/unpack_command.rb index ac1d376106..ef0d65e073 100644 --- a/lib/rubygems/commands/unpack_command.rb +++ b/lib/rubygems/commands/unpack_command.rb @@ -7,6 +7,7 @@ require 'rubygems/command' require 'rubygems/installer' require 'rubygems/version_option' +require 'rubygems/remote_fetcher' class Gem::Commands::UnpackCommand < Gem::Command @@ -39,16 +40,6 @@ class Gem::Commands::UnpackCommand < Gem::Command "#{program_name} GEMNAME" end - def download dependency - found = Gem::SpecFetcher.fetcher.fetch dependency - - return if found.empty? - - spec, source_uri = found.first - - Gem::RemoteFetcher.fetcher.download spec, source_uri - end - #-- # TODO: allow, e.g., 'gem unpack rake-0.3.1'. Find a general solution for # this, so that it works for uninstall as well. (And check other commands @@ -79,8 +70,9 @@ class Gem::Commands::UnpackCommand < Gem::Command # TODO: see comments in get_path() about general service. def find_in_cache(filename) - Gem.path.each do |gem_dir| - this_path = File.join gem_dir, 'cache', filename + + Gem.path.each do |path| + this_path = Gem.cache_gem(filename, path) return this_path if File.exist? this_path end @@ -111,15 +103,17 @@ class Gem::Commands::UnpackCommand < Gem::Command selected = specs.sort_by { |s| s.version }.last - return download(dependency) if selected.nil? + return Gem::RemoteFetcher.fetcher.download_to_cache(dependency) unless + selected return unless dependency.name =~ /^#{selected.name}$/i # We expect to find (basename).gem in the 'cache' directory. Furthermore, # the name match must be exact (ignoring case). - - path = find_in_cache(selected.file_name) - return download(dependency) unless path + + path = find_in_cache selected.file_name + + return Gem::RemoteFetcher.fetcher.download_to_cache(dependency) unless path path end diff --git a/lib/rubygems/commands/update_command.rb b/lib/rubygems/commands/update_command.rb index b2f69a5b52..b7c65eb2a4 100644 --- a/lib/rubygems/commands/update_command.rb +++ b/lib/rubygems/commands/update_command.rb @@ -27,8 +27,16 @@ class Gem::Commands::UpdateCommand < Gem::Command add_install_update_options - add_option('--system', + OptionParser.accept Gem::Version do |value| + Gem::Version.new value + + value + end + + add_option('--system [VERSION]', Gem::Version, 'Update the RubyGems system software') do |value, options| + value = true unless value + options[:system] = value end @@ -50,33 +58,13 @@ class Gem::Commands::UpdateCommand < Gem::Command end def execute + @installer = Gem::DependencyInstaller.new options + @updated = [] + hig = {} if options[:system] then - say "Updating RubyGems" - - unless options[:args].empty? then - raise "No gem names are allowed with the --system option" - end - - rubygems_update = Gem::Specification.new - rubygems_update.name = 'rubygems-update' - rubygems_update.version = Gem::Version.new Gem::VERSION - hig['rubygems-update'] = rubygems_update - - options[:user_install] = false - - Gem.source_index.refresh! - - update_gems = Gem.source_index.find_name 'rubygems-update' - - latest_update_gem = update_gems.sort_by { |s| s.version }.last - - say "Updating RubyGems to #{latest_update_gem.version}" - installed = do_rubygems_update latest_update_gem.version - - say "RubyGems system software updated" if installed - + update_rubygems return else say "Updating installed gems" @@ -92,28 +80,7 @@ class Gem::Commands::UpdateCommand < Gem::Command gems_to_update = which_to_update hig, options[:args] - updated = [] - - installer = Gem::DependencyInstaller.new options - - gems_to_update.uniq.sort.each do |name| - next if updated.any? { |spec| spec.name == name } - success = false - - say "Updating #{name}" - begin - installer.install name - success = true - rescue Gem::InstallError => e - alert_error "Error installing #{name}:\n\t#{e.message}" - success = false - end - - installer.installed_gems.each do |spec| - updated << spec - say "Successfully installed #{spec.full_name}" if success - end - end + updated = update_gems gems_to_update if updated.empty? then say "Nothing to update" @@ -136,12 +103,77 @@ class Gem::Commands::UpdateCommand < Gem::Command end end + def update_gem name, version = Gem::Requirement.default + return if @updated.any? { |spec| spec.name == name } + success = false + + say "Updating #{name}" + begin + @installer.install name, version + success = true + rescue Gem::InstallError => e + alert_error "Error installing #{name}:\n\t#{e.message}" + success = false + end + + @installer.installed_gems.each do |spec| + @updated << spec + say "Successfully installed #{spec.full_name}" if success + end + end + + def update_gems gems_to_update + gems_to_update.uniq.sort.each do |name| + update_gem name + end + + @updated + end + ## - # Update the RubyGems software to +version+. + # Update RubyGems software to the latest version. + + def update_rubygems + unless options[:args].empty? then + alert_error "Gem names are not allowed with the --system option" + terminate_interaction 1 + end + + options[:user_install] = false + + version = options[:system] + if version == true then + version = Gem::Version.new Gem::VERSION + requirement = Gem::Requirement.new ">= #{Gem::VERSION}" + else + version = Gem::Version.new version + requirement = Gem::Requirement.new version + end + + rubygems_update = Gem::Specification.new + rubygems_update.name = 'rubygems-update' + rubygems_update.version = version + + hig = { + 'rubygems-update' => rubygems_update + } + + gems_to_update = which_to_update hig, options[:args] + + if gems_to_update.empty? then + say "Latest version currently installed. Aborting." + terminate_interaction + end + + update_gem gems_to_update.first, requirement + + Gem.source_index.refresh! + + installed_gems = Gem.source_index.find_name 'rubygems-update', requirement + version = installed_gems.last.version - def do_rubygems_update(version) args = [] - args.push '--prefix', Gem.prefix unless Gem.prefix.nil? + args << '--prefix' << Gem.prefix if Gem.prefix args << '--no-rdoc' unless options[:generate_rdoc] args << '--no-ri' unless options[:generate_ri] args << '--no-format-executable' if options[:no_format_executable] @@ -154,8 +186,9 @@ class Gem::Commands::UpdateCommand < Gem::Command # Make sure old rubygems isn't loaded old = ENV["RUBYOPT"] - ENV.delete("RUBYOPT") - system setup_cmd + ENV.delete("RUBYOPT") if old + installed = system setup_cmd + say "RubyGems system software updated" if installed ENV["RUBYOPT"] = old if old end end diff --git a/lib/rubygems/config_file.rb b/lib/rubygems/config_file.rb index f3593999d3..1d16cd90ce 100644 --- a/lib/rubygems/config_file.rb +++ b/lib/rubygems/config_file.rb @@ -129,6 +129,11 @@ class Gem::ConfigFile attr_reader :rubygems_api_key + ## + # Hash of RubyGems.org and alternate API keys + + attr_reader :api_keys + ## # Create the config file object. +args+ is the list of arguments # from the command line. @@ -192,7 +197,7 @@ class Gem::ConfigFile @update_sources = @hash[:update_sources] if @hash.key? :update_sources @verbose = @hash[:verbose] if @hash.key? :verbose - load_rubygems_api_key + load_api_keys Gem.sources = @hash[:sources] if @hash.key? :sources handle_arguments arg_list @@ -205,10 +210,12 @@ class Gem::ConfigFile File.join(Gem.user_home, '.gem', 'credentials') end - def load_rubygems_api_key - api_key_hash = File.exists?(credentials_path) ? load_file(credentials_path) : @hash - - @rubygems_api_key = api_key_hash[:rubygems_api_key] if api_key_hash.key? :rubygems_api_key + def load_api_keys + @api_keys = File.exists?(credentials_path) ? load_file(credentials_path) : @hash + if @api_keys.key? :rubygems_api_key then + @rubygems_api_key = @api_keys[:rubygems_api_key] + @api_keys[:rubygems] = @api_keys.delete :rubygems_api_key unless @api_keys.key? :rubygems + end end def rubygems_api_key=(api_key) diff --git a/lib/rubygems/custom_require.rb b/lib/rubygems/custom_require.rb index 88e7aa4c4c..ebe7b05558 100644 --- a/lib/rubygems/custom_require.rb +++ b/lib/rubygems/custom_require.rb @@ -31,8 +31,23 @@ module Kernel # The normal require functionality of returning false if # that file has already been loaded is preserved. - def require(path) # :doc: - gem_original_require path + def require path + if Gem.unresolved_deps.empty? or Gem.loaded_path? path then + gem_original_require path + else + specs = Gem.searcher.find_in_unresolved path + unless specs.empty? then + specs = [specs.last] + else + specs = Gem.searcher.find_in_unresolved_tree path + end + + specs.each do |spec| + Gem.activate spec.name, spec.version # FIX: this is dumb + end + + return gem_original_require path + end rescue LoadError => load_error if load_error.message.end_with?(path) and Gem.try_activate(path) then return gem_original_require(path) diff --git a/lib/rubygems/dependency.rb b/lib/rubygems/dependency.rb index 4c61b6563e..e4d1bfffe8 100644 --- a/lib/rubygems/dependency.rb +++ b/lib/rubygems/dependency.rb @@ -32,11 +32,6 @@ class Gem::Dependency attr_writer :prerelease - ## - # Dependency type. - - attr_reader :type - ## # Constructs a dependency with +name+ and +requirements+. The last # argument can optionally be the dependency type, which defaults to @@ -72,7 +67,7 @@ class Gem::Dependency def inspect # :nodoc: "<%s type=%p name=%p requirements=%p>" % - [self.class, @type, @name, requirement.to_s] + [self.class, self.type, self.name, requirement.to_s] end ## @@ -132,7 +127,18 @@ class Gem::Dependency end def to_s # :nodoc: - "#{name} (#{requirement}, #{type})" + if type != :runtime then + "#{name} (#{requirement}, #{type})" + else + "#{name} (#{requirement})" + end + end + + ## + # Dependency type. + + def type + @type ||= :runtime end def == other # :nodoc: @@ -146,7 +152,7 @@ class Gem::Dependency # Dependencies are ordered by name. def <=> other - @name <=> other.name + self.name <=> other.name end ## @@ -187,5 +193,24 @@ class Gem::Dependency requirement.satisfied_by?(spec.version) end + ## + # Merges the requirements of +other+ into this dependency + + def merge other + unless name == other.name then + raise ArgumentError, + "#{self} and #{other} have different names" + end + + default = Gem::Requirement.default + self_req = self.requirement + other_req = other.requirement + + return self.class.new name, self_req if other_req == default + return self.class.new name, other_req if self_req == default + + self.class.new name, self_req.as_list.concat(other_req.as_list) + end + end diff --git a/lib/rubygems/dependency_installer.rb b/lib/rubygems/dependency_installer.rb index 23462fbe33..46ec63f14f 100644 --- a/lib/rubygems/dependency_installer.rb +++ b/lib/rubygems/dependency_installer.rb @@ -74,7 +74,7 @@ class Gem::DependencyInstaller @installed_gems = [] @install_dir = options[:install_dir] || Gem.dir - @cache_dir = options[:cache_dir] || @install_dir + @cache_dir = options[:cache_dir] || Gem.cache_dir(@install_dir) # Set with any errors that SpecFetcher finds while search through # gemspecs for a dep @@ -136,17 +136,33 @@ class Gem::DependencyInstaller def gather_dependencies specs = @specs_and_sources.map { |spec,_| spec } + # these gems were listed by the user, always install them + keep_names = specs.map { |spec| spec.full_name } + dependency_list = Gem::DependencyList.new @development dependency_list.add(*specs) to_do = specs.dup add_found_dependencies to_do, dependency_list unless @ignore_dependencies + dependency_list.specs.reject! { |spec| + ! keep_names.include? spec.full_name and + @source_index.any? { |n,_| n == spec.full_name } + } + + unless dependency_list.ok? or @ignore_dependencies or @force then + reason = dependency_list.why_not_ok?.map { |k,v| + "#{k} requires #{v.join(", ")}" + }.join("; ") + raise Gem::DependencyError, "Unable to resolve dependencies: #{reason}" + end + @gems_to_install = dependency_list.dependency_order.reverse end def add_found_dependencies to_do, dependency_list seen = {} + dependencies = Hash.new { |h, name| h[name] = Gem::Dependency.new name } until to_do.empty? do spec = to_do.shift @@ -157,10 +173,10 @@ class Gem::DependencyInstaller deps |= spec.development_dependencies if @development deps.each do |dep| + dependencies[dep.name] = dependencies[dep.name].merge dep + results = find_gems_with_sources(dep).reverse - # FIX: throw in everything that satisfies, and let - # FIX: dependencylist reduce to the chosen few results.reject! do |dep_spec,| to_do.push dep_spec @@ -172,14 +188,14 @@ class Gem::DependencyInstaller end results.each do |dep_spec, source_uri| - next if seen[dep_spec.name] @specs_and_sources << [dep_spec, source_uri] - # FIX: this is the bug dependency_list.add dep_spec end end end + + dependency_list.remove_specs_unsatisfied_by dependencies end ## @@ -259,7 +275,6 @@ class Gem::DependencyInstaller @gems_to_install.each do |spec| last = spec == @gems_to_install.last - # 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 diff --git a/lib/rubygems/dependency_list.rb b/lib/rubygems/dependency_list.rb index 647bb91ae2..91c7c5ade4 100644 --- a/lib/rubygems/dependency_list.rb +++ b/lib/rubygems/dependency_list.rb @@ -17,6 +17,7 @@ require 'tsort' # correct order to avoid conflicts. class Gem::DependencyList + attr_reader :specs include Enumerable include TSort @@ -56,6 +57,10 @@ class Gem::DependencyList @specs.push(*gemspecs) end + def clear + @specs.clear + end + ## # Return a list of the gem specifications in the dependency list, sorted in # order so that no gemspec in the list depends on a gemspec earlier in the @@ -110,11 +115,26 @@ class Gem::DependencyList # Are all the dependencies in the list satisfied? def ok? - @specs.all? do |spec| - spec.runtime_dependencies.all? do |dep| - @specs.find { |s| s.satisfies_requirement? dep } + why_not_ok?(:quick).empty? + end + + def why_not_ok? quick = false + unsatisfied = Hash.new { |h,k| h[k] = [] } + source_index = Gem.source_index + @specs.each do |spec| + spec.runtime_dependencies.each do |dep| + inst = source_index.any? { |_, installed_spec| + dep.name == installed_spec.name and + dep.requirement.satisfied_by? installed_spec.version + } + + unless inst or @specs.find { |s| s.satisfies_requirement? dep } then + unsatisfied[spec.name] << dep + return unsatisfied if quick + end end end + unsatisfied end ## @@ -146,6 +166,18 @@ class Gem::DependencyList } end + ## + # Remove everything in the DependencyList that matches but doesn't + # satisfy items in +dependencies+ (a hash of gem names to arrays of + # dependencies). + + def remove_specs_unsatisfied_by dependencies + specs.reject! { |spec| + dep = dependencies[spec.name] + dep and not dep.requirement.satisfied_by? spec.version + } + end + ## # Removes the gemspec matching +full_name+ from the dependency list diff --git a/lib/rubygems/doc_manager.rb b/lib/rubygems/doc_manager.rb index e900396aa4..71c7d850ad 100644 --- a/lib/rubygems/doc_manager.rb +++ b/lib/rubygems/doc_manager.rb @@ -168,7 +168,6 @@ class Gem::DocManager def run_rdoc(*args) args << @spec.rdoc_options args << self.class.configured_args - args << '--quiet' args << @spec.require_paths.clone args << @spec.extra_rdoc_files args << '--title' << "#{@spec.full_name} Documentation" @@ -182,6 +181,8 @@ class Gem::DocManager # HACK more end + debug_args = args.dup + r = RDoc::RDoc.new old_pwd = Dir.pwd @@ -199,10 +200,10 @@ class Gem::DocManager rescue Exception => ex alert_error "While generating documentation for #{@spec.full_name}" ui.errs.puts "... MESSAGE: #{ex}" - ui.errs.puts "... RDOC args: #{args.join(' ')}" + ui.errs.puts "... RDOC args: #{debug_args.join(' ')}" ui.errs.puts "\t#{ex.backtrace.join "\n\t"}" if - Gem.configuration.backtrace - ui.errs.puts "(continuing with the rest of the installation)" + Gem.configuration.backtrace + terminate_interaction 1 ensure Dir.chdir old_pwd end diff --git a/lib/rubygems/gem_path_searcher.rb b/lib/rubygems/gem_path_searcher.rb index 9dae006222..5b85cbc9fb 100644 --- a/lib/rubygems/gem_path_searcher.rb +++ b/lib/rubygems/gem_path_searcher.rb @@ -16,6 +16,7 @@ class Gem::GemPathSearcher def initialize # We want a record of all the installed gemspecs, in the order we wish to # examine them. + # TODO: remove this stupid method @gemspecs = init_gemspecs # Map gem spec to glob of full require_path directories. Preparing this @@ -48,7 +49,9 @@ class Gem::GemPathSearcher # only that there is a match. def find(glob) + # HACK violation of encapsulation @gemspecs.find do |spec| + # TODO: inverted responsibility matching_file? spec, glob end end @@ -57,9 +60,39 @@ class Gem::GemPathSearcher # Works like #find, but finds all gemspecs matching +glob+. def find_all(glob) + # HACK violation of encapsulation @gemspecs.select do |spec| + # TODO: inverted responsibility matching_file? spec, glob + end || [] + end + + def find_in_unresolved(glob) + # HACK violation + specs = Gem.unresolved_deps.values.map { |dep| + Gem.source_index.search dep, true + }.flatten + + specs.select do |spec| + # TODO: inverted responsibility + matching_file? spec, glob + end || [] + end + + def find_in_unresolved_tree glob + # HACK violation + # TODO: inverted responsibility + specs = Gem.unresolved_deps.values.map { |dep| + Gem.source_index.search dep, true + }.flatten + + specs.reverse_each do |spec| + trails = matching_paths(spec, glob) + next if trails.empty? + return trails.map(&:reverse).sort.first.reverse end + + [] end ## @@ -67,7 +100,18 @@ class Gem::GemPathSearcher # +spec+. def matching_file?(spec, path) - !matching_files(spec, path).empty? + not matching_files(spec, path).empty? + end + + def matching_paths(spec, path) + trails = [] + + spec.traverse do |from_spec, dep, to_spec, trail| + next unless to_spec.conflicts.empty? + trails << trail unless matching_files(to_spec, path).empty? + end + + trails end ## diff --git a/lib/rubygems/gemcutter_utilities.rb b/lib/rubygems/gemcutter_utilities.rb index 1681356805..a77a4911f2 100644 --- a/lib/rubygems/gemcutter_utilities.rb +++ b/lib/rubygems/gemcutter_utilities.rb @@ -7,6 +7,26 @@ require 'rubygems/remote_fetcher' module Gem::GemcutterUtilities + OptionParser.accept Symbol do |value| + value.to_sym + end + + ## + # Add the --key option + + def add_key_option + add_option '-k', '--key KEYNAME', Symbol, 'Use the given API key' do |value,options| + options[:key] = value + end + end + + def api_key + if options[:key] then + verify_api_key options[:key] + else + Gem.configuration.rubygems_api_key + end + end def sign_in return if Gem.configuration.rubygems_api_key @@ -33,6 +53,8 @@ module Gem::GemcutterUtilities host = ENV['RUBYGEMS_HOST'] if ENV['RUBYGEMS_HOST'] uri = URI.parse "#{host}/#{path}" + say "Pushing gem to #{host}..." + request_method = Net::HTTP.const_get method.to_s.capitalize Gem::RemoteFetcher.fetcher.request(uri, request_method, &block) @@ -52,4 +74,13 @@ module Gem::GemcutterUtilities end end + def verify_api_key(key) + if Gem.configuration.api_keys.key? key then + Gem.configuration.api_keys[key] + else + alert_error "No such API key. You can add it with gem keys --add #{key}" + terminate_interaction 1 + end + end + end diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb index 5c7c57685b..8c6aeb44c7 100644 --- a/lib/rubygems/installer.rb +++ b/lib/rubygems/installer.rb @@ -156,6 +156,9 @@ class Gem::Installer Gem.ensure_gem_subdirectories @gem_home + # Completely remove any previous gem files + FileUtils.rm_rf(@gem_dir) if File.exist?(@gem_dir) + FileUtils.mkdir_p @gem_dir extract_files @@ -179,10 +182,9 @@ class Gem::Installer write_require_paths_file_if_needed if Gem::QUICKLOADER_SUCKAGE - # HACK remove? Isn't this done in multiple places? - cached_gem = File.join @gem_home, "cache", @gem.split(/\//).pop + cached_gem = Gem.cache_gem(File.basename(@gem), @gem_home) unless File.exist? cached_gem then - FileUtils.cp @gem, File.join(@gem_home, "cache") + FileUtils.cp @gem, Gem.cache_dir(@gem_home) end say @spec.post_install_message unless @spec.post_install_message.nil? @@ -235,7 +237,7 @@ class Gem::Installer # specifications directory. def write_spec - rubycode = @spec.to_ruby + rubycode = @spec.to_ruby_for_cache file_name = File.join @gem_home, 'specifications', @spec.spec_name @@ -275,8 +277,10 @@ class Gem::Installer @spec.executables.each do |filename| filename.untaint bin_path = File.expand_path "#{@spec.bindir}/#{filename}", @gem_dir - mode = File.stat(bin_path).mode | 0111 - File.chmod mode, bin_path + if File.exist?(bin_path) + mode = File.stat(bin_path).mode | 0111 + File.chmod mode, bin_path + end if @wrappers then generate_bin_script filename, bindir @@ -298,7 +302,7 @@ class Gem::Installer FileUtils.rm_f bin_script_path # prior install may have been --no-wrappers - File.open bin_script_path, 'w', 0755 do |file| + File.open bin_script_path, 'wb', 0755 do |file| file.print app_script_text(filename) end diff --git a/lib/rubygems/installer_test_case.rb b/lib/rubygems/installer_test_case.rb index c1f86d547b..26b590786b 100644 --- a/lib/rubygems/installer_test_case.rb +++ b/lib/rubygems/installer_test_case.rb @@ -64,12 +64,15 @@ class Gem::InstallerTestCase < Gem::TestCase super @spec = quick_gem 'a' + util_make_exec @spec @gem = File.join @tempdir, @spec.file_name @installer = util_installer @spec, @gem, @gemhome @user_spec = quick_gem 'b' + util_make_exec @user_spec + @user_gem = File.join @tempdir, @user_spec.file_name @user_installer = util_installer @user_spec, @user_gem, Gem.user_dir @@ -77,31 +80,38 @@ class Gem::InstallerTestCase < Gem::TestCase @user_spec.full_name) end - def util_gem_bindir(version = '2') - File.join util_gem_dir(version), "bin" + def util_gem_bindir spec = @spec + File.join util_gem_dir(spec), "bin" end - def util_gem_dir(version = '2') - File.join @gemhome, "gems", "a-#{version}" # HACK + def util_gem_dir spec = @spec + File.join @gemhome, "gems", spec.full_name end def util_inst_bindir File.join @gemhome, "bin" end - def util_make_exec(version = '2', shebang = "#!/usr/bin/ruby") - @spec.executables = ["my_exec"] + def util_make_exec(spec = @spec, shebang = "#!/usr/bin/ruby") + spec.executables = %w[executable] + spec.files << 'bin/executable' + + bindir = util_gem_bindir spec + FileUtils.mkdir_p bindir + exec_path = File.join bindir, 'executable' + open exec_path, 'w' do |io| + io.puts shebang + end - FileUtils.mkdir_p util_gem_bindir(version) - exec_path = File.join util_gem_bindir(version), "my_exec" - File.open exec_path, 'w' do |f| - f.puts shebang + temp_bin = File.join(@tempdir, 'bin') + FileUtils.mkdir_p temp_bin + open File.join(temp_bin, 'executable'), 'w' do |io| + io.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.files << File.join('lib', 'code.rb') @spec.extensions << File.join('ext', 'a', 'mkrf_conf.rb') Dir.chdir @tempdir do @@ -127,9 +137,7 @@ class Gem::InstallerTestCase < Gem::TestCase def util_installer(spec, gem_path, gem_home) util_build_gem spec - FileUtils.mv File.join(@gemhome, 'cache', spec.file_name), - @tempdir - + FileUtils.mv Gem.cache_gem(spec.file_name), @tempdir installer = Gem::Installer.new gem_path installer.gem_dir = util_gem_dir installer.gem_home = gem_home diff --git a/lib/rubygems/package/tar_reader/entry.rb b/lib/rubygems/package/tar_reader/entry.rb index c70248f159..d7b5280213 100644 --- a/lib/rubygems/package/tar_reader/entry.rb +++ b/lib/rubygems/package/tar_reader/entry.rb @@ -4,8 +4,9 @@ # File a patch instead and assign it to Ryan Davis or Eric Hodel. ###################################################################### +# -*- coding: utf-8 -*- #++ -# Copyright (C) 2004 Mauricio Julio Fernández Pradier +# Copyright (C) 2004 Mauricio Julio Fernández Pradier # See LICENSE.txt for additional licensing information. #-- diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb index aa63392848..520adb5827 100644 --- a/lib/rubygems/remote_fetcher.rb +++ b/lib/rubygems/remote_fetcher.rb @@ -77,6 +77,23 @@ class Gem::RemoteFetcher end end + ## + # Given a name and requirement, downloads this gem into cache and returns the + # filename. Returns nil if the gem cannot be located. + #-- + # Should probably be integrated with #download below, but that will be a + # larger, more emcompassing effort. -erikh + + def download_to_cache dependency + found = Gem::SpecFetcher.fetcher.fetch dependency + + return if found.empty? + + spec, source_uri = found.first + + download spec, source_uri + 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 @@ -86,9 +103,9 @@ class Gem::RemoteFetcher Gem.ensure_gem_subdirectories(install_dir) rescue nil if File.writable?(install_dir) - cache_dir = File.join install_dir, 'cache' + cache_dir = Gem.cache_dir(install_dir) else - cache_dir = File.join(Gem.user_dir, 'cache') + cache_dir = Gem.cache_dir(Gem.user_dir) end gem_file_name = spec.file_name @@ -140,7 +157,7 @@ class Gem::RemoteFetcher path = source_uri.path path = File.dirname(path) if File.extname(path) == '.gem' - remote_gem_path = File.join(path, 'gems', gem_file_name) + remote_gem_path = correct_for_windows_path(File.join(path, 'gems', gem_file_name)) FileUtils.cp(remote_gem_path, local_gem_path) rescue Errno::EACCES @@ -276,6 +293,14 @@ class Gem::RemoteFetcher raise FetchError.new(e.message, uri) end + def correct_for_windows_path(path) + if path[0].chr == '/' && path[1].chr =~ /[a-z]/i && path[2].chr == ':' + path = path[1..-1] + else + path + end + end + ## # Read the data from the (source based) URI, but if it is a file:// URI, # read from the filesystem instead. @@ -293,13 +318,7 @@ class Gem::RemoteFetcher end if uri.scheme == 'file' - path = uri.path - - # Deal with leading slash on Windows paths - if path[0].chr == '/' && path[1].chr =~ /[a-zA-Z]/ && path[2].chr == ':' - path = path[1..-1] - end - + path = correct_for_windows_path(uri.path) return Gem.read_binary(path) end diff --git a/lib/rubygems/requirement.rb b/lib/rubygems/requirement.rb index 8af5fbd6b4..35f7d0809a 100644 --- a/lib/rubygems/requirement.rb +++ b/lib/rubygems/requirement.rb @@ -108,7 +108,7 @@ class Gem::Requirement end def as_list # :nodoc: - requirements.map { |op, version| "#{op} #{version}" } + requirements.map { |op, version| "#{op} #{version}" }.sort end def hash # :nodoc: @@ -137,7 +137,8 @@ class Gem::Requirement # True if +version+ satisfies this Requirement. def satisfied_by? version - requirements.all? { |op, rv| OPS[op].call version, rv } + # #28965: syck has a bug with unquoted '=' YAML.loading as YAML::DefaultKey + requirements.all? { |op, rv| (OPS[op] || OPS["="]).call version, rv } end def to_s # :nodoc: diff --git a/lib/rubygems/security.rb b/lib/rubygems/security.rb index 9f3daa9453..1ff375dc4a 100644 --- a/lib/rubygems/security.rb +++ b/lib/rubygems/security.rb @@ -12,6 +12,7 @@ require 'rubygems/exceptions' require 'rubygems/gem_openssl' +require 'fileutils' # # = Signed Gems README diff --git a/lib/rubygems/source_index.rb b/lib/rubygems/source_index.rb index 7c571fff55..a6733fac31 100644 --- a/lib/rubygems/source_index.rb +++ b/lib/rubygems/source_index.rb @@ -131,7 +131,7 @@ class Gem::SourceIndex # Returns an Array specifications for the latest released versions # of each gem in this index. - def latest_specs + def latest_specs(include_prerelease=false) result = Hash.new { |h,k| h[k] = [] } latest = {} @@ -140,7 +140,7 @@ class Gem::SourceIndex curr_ver = spec.version prev_ver = latest.key?(name) ? latest[name].version : nil - next if curr_ver.prerelease? + next if !include_prerelease && curr_ver.prerelease? next unless prev_ver.nil? or curr_ver >= prev_ver or latest[name].platform != Gem::Platform::RUBY @@ -273,6 +273,7 @@ class Gem::SourceIndex when Gem::Dependency then only_platform = platform_only requirement = gem_pattern.requirement + gem_pattern = if Regexp === gem_pattern.name then gem_pattern.name elsif gem_pattern.name.empty? then diff --git a/lib/rubygems/spec_fetcher.rb b/lib/rubygems/spec_fetcher.rb index c999a65cd9..ab05c2f9dd 100644 --- a/lib/rubygems/spec_fetcher.rb +++ b/lib/rubygems/spec_fetcher.rb @@ -76,7 +76,8 @@ class Gem::SpecFetcher # Returns the local directory to write +uri+ to. def cache_dir(uri) - File.join @dir, "#{uri.host}%#{uri.port}", File.dirname(uri.path) + escaped_path = uri.path.sub(%r[^/([a-z]):/]i, '/\\1-/') # Correct for windows paths + File.join @dir, "#{uri.host}%#{uri.port}", File.dirname(escaped_path) end ## @@ -100,6 +101,7 @@ class Gem::SpecFetcher end def fetch_spec(spec, source_uri) + source_uri = URI.parse source_uri if String === source_uri spec = spec - [nil, 'ruby', ''] spec_file_name = "#{spec.join '-'}.gemspec" @@ -179,7 +181,7 @@ class Gem::SpecFetcher def suggest_gems_from_name gem_name gem_name = gem_name.downcase max = gem_name.size / 2 - specs = list.values.flatten(1) # flatten(1) is 1.8.7 and up + specs = list.values.flatten 1 matches = specs.map { |name, version, platform| next unless Gem::Platform.match platform diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb index f66288498b..a5e250a5df 100644 --- a/lib/rubygems/specification.rb +++ b/lib/rubygems/specification.rb @@ -339,7 +339,8 @@ class Gem::Specification # List of dependencies that will automatically be activated at runtime. def runtime_dependencies - dependencies.select { |d| d.type == :runtime || d.type == nil } + # TODO: fix #type to return :runtime if nil + dependencies.select { |d| d.type == :runtime } end ## @@ -542,8 +543,8 @@ class Gem::Specification def self.normalize_yaml_input(input) result = input.respond_to?(:read) ? input.read : input - result = "--- " + result unless result =~ /^--- / - result + result = "--- " + result unless result =~ /\A--- / + result.gsub(/ !!null \n/, " \n") end ## @@ -684,6 +685,14 @@ class Gem::Specification alias eql? == # :nodoc: + ## + # A macro to yield cached gem path + # + def cache_gem + cache_name = File.join(Gem.dir, 'cache', file_name) + return File.exist?(cache_name) ? cache_name : nil + end + ## # True if this gem has the same attributes as +other+. @@ -726,11 +735,13 @@ class Gem::Specification end def to_yaml(opts = {}) # :nodoc: - return super if YAML.const_defined?(:ENGINE) && !YAML::ENGINE.syck? - - YAML.quick_emit object_id, opts do |out| - out.map taguri, to_yaml_style do |map| - encode_with map + if YAML.const_defined?(:ENGINE) && !YAML::ENGINE.syck? then + super.gsub(/ !!null \n/, " \n") + else + YAML.quick_emit object_id, opts do |out| + out.map taguri, to_yaml_style do |map| + encode_with map + end end end end @@ -795,21 +806,17 @@ class Gem::Specification result << " if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then" - unless dependencies.empty? then - dependencies.each do |dep| - version_reqs_param = dep.requirements_list.inspect - dep.instance_variable_set :@type, :runtime if dep.type.nil? # HACK - result << " s.add_#{dep.type}_dependency(%q<#{dep.name}>, #{version_reqs_param})" - end + dependencies.each do |dep| + req = dep.requirements_list.inspect + dep.instance_variable_set :@type, :runtime if dep.type.nil? # HACK + result << " s.add_#{dep.type}_dependency(%q<#{dep.name}>, #{req})" end result << " else" - unless dependencies.empty? then - dependencies.each do |dep| - version_reqs_param = dep.requirements_list.inspect - result << " s.add_dependency(%q<#{dep.name}>, #{version_reqs_param})" - end + dependencies.each do |dep| + version_reqs_param = dep.requirements_list.inspect + result << " s.add_dependency(%q<#{dep.name}>, #{version_reqs_param})" end result << ' end' @@ -827,6 +834,15 @@ class Gem::Specification result.join "\n" end + def to_ruby_for_cache + s = dup + # remove large blobs that aren't used at runtime: + s.files = nil + s.extra_rdoc_files = nil + s.rdoc_options = nil + s.to_ruby + end + ## # Checks that the specification contains all required fields, and does a # very basic sanity check. @@ -835,6 +851,7 @@ class Gem::Specification # checks.. def validate + require 'rubygems/user_interaction' extend Gem::UserInteraction normalize @@ -1523,4 +1540,32 @@ class Gem::Specification @extensions, ].flatten.uniq.compact end + + def conflicts + conflicts = {} + Gem.loaded_specs.values.each do |spec| + bad = self.runtime_dependencies.find_all { |dep| + spec.name == dep.name and not spec.satisfies_requirement? dep + } + + conflicts[spec] = bad unless bad.empty? + end + conflicts + end + + def traverse trail = [], &b + trail = trail + [self] + runtime_dependencies.each do |dep| + dep_specs = Gem.source_index.search dep, true + dep_specs.each do |dep_spec| + b[self, dep, dep_spec, trail + [dep_spec]] + dep_spec.traverse(trail, &b) unless + trail.map(&:name).include? dep_spec.name + end + end + end + + def dependent_specs + runtime_dependencies.map { |dep| Gem.source_index.search dep, true }.flatten + end end diff --git a/lib/rubygems/test_case.rb b/lib/rubygems/test_case.rb index 447a30f780..5c32bd2ddb 100644 --- a/lib/rubygems/test_case.rb +++ b/lib/rubygems/test_case.rb @@ -24,20 +24,8 @@ require 'uri' require 'rubygems/package' require 'rubygems/test_utilities' require 'pp' -require 'yaml' require 'zlib' - -begin - YAML::ENGINE.yamler = 'psych' -rescue LoadError -end if YAML.const_defined? :ENGINE - -begin - gem 'rdoc' -rescue Gem::LoadError -end - -require 'rdoc/rdoc' +Gem.load_yaml require 'rubygems/mock_gem_ui' @@ -99,6 +87,8 @@ class Gem::TestCase < MiniTest::Unit::TestCase undef_method :default_test if instance_methods.include? 'default_test' or instance_methods.include? :default_test + @@project_dir = Dir.pwd + ## # #setup prepares a sandboxed location to install gems. All installs are # directed to a temporary directory. All install plugins are removed. @@ -128,8 +118,6 @@ class Gem::TestCase < MiniTest::Unit::TestCase @gemhome = File.join @tempdir, 'gemhome' @userhome = File.join @tempdir, 'userhome' - Gem.ensure_gem_subdirectories @gemhome - @orig_ruby = if ruby = ENV['RUBY'] then Gem.class_eval { ruby, @ruby = @ruby, ruby } ruby @@ -137,6 +125,8 @@ class Gem::TestCase < MiniTest::Unit::TestCase Gem.ensure_gem_subdirectories @gemhome + Dir.chdir @tempdir + @orig_ENV_HOME = ENV['HOME'] ENV['HOME'] = @userhome Gem.instance_variable_set :@user_home, nil @@ -146,6 +136,7 @@ class Gem::TestCase < MiniTest::Unit::TestCase Gem.use_paths(@gemhome) Gem.loaded_specs.clear + Gem.unresolved_deps.clear Gem.configuration.verbose = true Gem.configuration.update_sources = true @@ -154,6 +145,7 @@ class Gem::TestCase < MiniTest::Unit::TestCase @uri = URI.parse @gem_repo Gem.sources.replace [@gem_repo] + Gem.searcher = nil Gem::SpecFetcher.fetcher = nil @orig_BASERUBY = Gem::ConfigMap[:BASERUBY] @@ -169,17 +161,14 @@ class Gem::TestCase < MiniTest::Unit::TestCase @marshal_version = "#{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}" - @private_key = File.expand_path('../../../test/rubygems/private_key.pem', - __FILE__) - @public_cert = File.expand_path('../../../test/rubygems/public_cert.pem', - __FILE__) - + # TODO: move to installer test cases Gem.post_build_hooks.clear Gem.post_install_hooks.clear Gem.post_uninstall_hooks.clear Gem.pre_install_hooks.clear Gem.pre_uninstall_hooks.clear + # TODO: move to installer test cases Gem.post_build do |installer| @post_build_hook_arg = installer true @@ -219,6 +208,8 @@ class Gem::TestCase < MiniTest::Unit::TestCase Gem::RemoteFetcher.fetcher = nil end + Dir.chdir @@project_dir + FileUtils.rm_rf @tempdir unless ENV['KEEP_FILES'] ENV['GEM_HOME'] = @orig_gem_home @@ -319,14 +310,14 @@ class Gem::TestCase < MiniTest::Unit::TestCase require 'rubygems/specification' spec = Gem::Specification.new do |s| - s.platform = Gem::Platform::RUBY - s.name = name - s.version = version - s.author = 'A User' - s.email = 'example@example.com' - s.homepage = 'http://example.com' - s.has_rdoc = true - s.summary = "this is a summary" + s.platform = Gem::Platform::RUBY + s.name = name + s.version = version + s.author = 'A User' + s.email = 'example@example.com' + s.homepage = 'http://example.com' + s.has_rdoc = true + s.summary = "this is a summary" s.description = "This is a test description" yield(s) if block_given? @@ -344,6 +335,30 @@ class Gem::TestCase < MiniTest::Unit::TestCase return spec end + def quick_spec name, version = '2' + require 'rubygems/specification' + + spec = Gem::Specification.new do |s| + s.platform = Gem::Platform::RUBY + s.name = name + s.version = version + s.author = 'A User' + s.email = 'example@example.com' + s.homepage = 'http://example.com' + s.has_rdoc = true + s.summary = "this is a summary" + s.description = "This is a test description" + + yield(s) if block_given? + end + + spec.loaded_from = @gemhome + + Gem.source_index.add_spec spec + + return spec + end + ## # Builds a gem from +spec+ and places it in File.join @gemhome, # 'cache'. Automatically creates files based on +spec.files+ @@ -364,7 +379,7 @@ class Gem::TestCase < MiniTest::Unit::TestCase end FileUtils.mv spec.file_name, - File.join(@gemhome, 'cache', "#{spec.original_name}.gem") + Gem.cache_gem("#{spec.original_name}.gem") end end @@ -372,11 +387,60 @@ class Gem::TestCase < MiniTest::Unit::TestCase # Removes all installed gems from +@gemhome+. def util_clear_gems - FileUtils.rm_r File.join(@gemhome, 'gems') - FileUtils.rm_r File.join(@gemhome, 'specifications') + FileUtils.rm_rf File.join(@gemhome, 'gems') + FileUtils.rm_rf File.join(@gemhome, 'specifications') Gem.source_index.refresh! end + ## + # Install the provided specs + + def install_specs(*specs) + specs.each do |spec| + # TODO: inverted responsibility + Gem.source_index.add_spec spec + end + Gem.searcher = nil + end + + ## + # Create a new spec (or gem if passed an array of files) and set it + # up properly. Use this instead of util_spec and util_gem. + + def new_spec name, version, deps = nil, *files + # TODO: unfactor and deprecate util_gem and util_spec + spec, = unless files.empty? then + util_gem name, version do |s| + Array(deps).each do |n,v| + s.add_dependency n, v + end + s.files.push(*files) + end + else + util_spec name, version, deps + end + spec.loaded_from = File.join @gemhome, 'specifications', spec.spec_name + spec.loaded = false + spec + end + + ## + # Creates a spec with +name+, +version+ and +deps+. + + def util_spec(name, version, deps = nil, &block) + raise "deps or block, not both" if deps and block + + if deps then + block = proc do |s| + deps.each do |n, req| + s.add_dependency n, (req || '>= 0') + end + end + end + + quick_spec(name, version, &block) + end + ## # Creates a gem with +name+, +version+ and +deps+. The specification will # be yielded before gem creation for customization. The gem will be placed @@ -384,6 +448,8 @@ class Gem::TestCase < MiniTest::Unit::TestCase # location are returned. def util_gem(name, version, deps = nil, &block) + raise "deps or block, not both" if deps and block + if deps then block = proc do |s| deps.each do |n, req| @@ -397,8 +463,9 @@ class Gem::TestCase < MiniTest::Unit::TestCase 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 + gems_dir = File.dirname cache_file + FileUtils.mkdir_p File.dirname cache_file + FileUtils.mv Gem.cache_gem("#{spec.original_name}.gem"), cache_file FileUtils.rm File.join(@gemhome, 'specifications', spec.spec_name) spec.loaded_from = nil @@ -436,6 +503,7 @@ class Gem::TestCase < MiniTest::Unit::TestCase # Additional +prerelease+ gems may also be created: # # +@a2_pre+:: gem a version 2.a + # TODO: nuke this and fix tests. this should speed up a lot def util_make_gems(prerelease = false) @a1 = quick_gem 'a', '1' do |s| @@ -459,11 +527,12 @@ Also, a list: s.require_paths = %w[lib] end - @a2 = quick_gem('a', '2', &init) - @a3a = quick_gem('a', '3.a', &init) + @a2 = quick_gem('a', '2', &init) + @a3a = quick_gem('a', '3.a', &init) @a_evil9 = quick_gem('a_evil', '9', &init) - @b2 = quick_gem('b', '2', &init) - @c1_2 = quick_gem('c', '1.2', &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 s.files = %w[lib/code.rb] s.require_paths = %w[lib] @@ -477,12 +546,12 @@ Also, a list: util_build_gem @a2_pre end - write_file File.join(*%W[gems #{@a1.original_name} lib code.rb]) - write_file File.join(*%W[gems #{@a2.original_name} lib code.rb]) - write_file File.join(*%W[gems #{@a3a.original_name} lib code.rb]) - write_file File.join(*%W[gems #{@b2.original_name} lib code.rb]) + write_file File.join(*%W[gems #{@a1.original_name} lib code.rb]) + write_file File.join(*%W[gems #{@a2.original_name} lib code.rb]) + write_file File.join(*%W[gems #{@a3a.original_name} lib code.rb]) + write_file File.join(*%W[gems #{@b2.original_name} lib code.rb]) write_file File.join(*%W[gems #{@c1_2.original_name} lib code.rb]) - write_file File.join(*%W[gems #{@pl1.original_name} lib code.rb]) + write_file File.join(*%W[gems #{@pl1.original_name} lib code.rb]) [@a1, @a2, @a3a, @a_evil9, @b2, @c1_2, @pl1].each do |spec| util_build_gem spec @@ -703,7 +772,7 @@ Also, a list: @@ruby = rubybin env_rake = ENV['rake'] - ruby19_rake = File.expand_path("../../../bin/rake", __FILE__) + ruby19_rake = File.expand_path("bin/rake", @@project_dir) @@rake = if env_rake then ENV["rake"] elsif File.exist? ruby19_rake then diff --git a/lib/rubygems/test_utilities.rb b/lib/rubygems/test_utilities.rb index 892e4dd9e5..43f905f017 100644 --- a/lib/rubygems/test_utilities.rb +++ b/lib/rubygems/test_utilities.rb @@ -104,7 +104,7 @@ class Gem::FakeFetcher def download spec, source_uri, install_dir = Gem.dir name = spec.file_name - path = File.join(install_dir, 'cache', name) + path = Gem.cache_gem(name, install_dir) Gem.ensure_gem_subdirectories install_dir @@ -119,6 +119,16 @@ class Gem::FakeFetcher path end + def download_to_cache dependency + found = Gem::SpecFetcher.fetcher.fetch dependency + + return if found.empty? + + spec, source_uri = found.first + + download spec, source_uri + end + end # :stopdoc: diff --git a/lib/rubygems/uninstaller.rb b/lib/rubygems/uninstaller.rb index df45771846..02976866b1 100644 --- a/lib/rubygems/uninstaller.rb +++ b/lib/rubygems/uninstaller.rb @@ -56,6 +56,7 @@ class Gem::Uninstaller @force_all = options[:all] @force_ignore = options[:ignore] @bin_dir = options[:bin_dir] + @format_executable = options[:format_executable] # only add user directory if install_dir is not set @user_install = false @@ -108,6 +109,13 @@ class Gem::Uninstaller def uninstall_gem(spec, specs) @spec = spec + unless dependencies_ok? spec + unless ask_if_ok(spec) + raise Gem::DependencyRemovalException, + "Uninstallation aborted due to dependent gem(s)" + end + end + Gem.pre_uninstall_hooks.each do |hook| hook.call self end @@ -161,8 +169,8 @@ class Gem::Uninstaller spec.executables.each do |exe_name| say "Removing #{exe_name}" - FileUtils.rm_f File.join(bindir, exe_name) - FileUtils.rm_f File.join(bindir, "#{exe_name}.bat") + FileUtils.rm_f File.join(bindir, formatted_program_filename(exe_name)) + FileUtils.rm_f File.join(bindir, "#{formatted_program_filename(exe_name)}.bat") end end end @@ -184,11 +192,6 @@ class Gem::Uninstaller # uninstalled a gem, it is removed from that list. def remove(spec, list) - unless dependencies_ok? spec then - raise Gem::DependencyRemovalException, - "Uninstallation aborted due to dependent gem(s)" - end - unless path_ok?(@gem_home, spec) or (@user_install and path_ok?(Gem.user_dir, spec)) then e = Gem::GemNotInHomeException.new \ @@ -215,11 +218,10 @@ class Gem::Uninstaller FileUtils.rm_rf gemspec - cache_dir = File.join spec.installation_path, 'cache' - gem = File.join cache_dir, spec.file_name + gem = Gem.cache_gem(spec.file_name, spec.installation_path) unless File.exist? gem then - gem = File.join cache_dir, "#{original_platform_name}.gem" + gem = Gem.cache_gem("#{original_platform_name}.gem", spec.installation_path) end FileUtils.rm_rf gem @@ -246,7 +248,7 @@ class Gem::Uninstaller deplist = Gem::DependencyList.from_source_index @source_index deplist.add(*@user_index.gems.values) if @user_install - deplist.ok_to_remove?(spec.full_name) || ask_if_ok(spec) + deplist.ok_to_remove?(spec.full_name) end def ask_if_ok(spec) @@ -263,5 +265,14 @@ class Gem::Uninstaller return ask_yes_no(msg.join("\n"), true) end + def formatted_program_filename(filename) + if @format_executable then + Gem::Installer.exec_format % File.basename(filename) + else + filename + end + end + + end diff --git a/lib/rubygems/user_interaction.rb b/lib/rubygems/user_interaction.rb index ff5800c116..538793181e 100644 --- a/lib/rubygems/user_interaction.rb +++ b/lib/rubygems/user_interaction.rb @@ -182,29 +182,24 @@ class Gem::StreamUI end end - qstr = case default - when nil - 'yn' - when true - 'Yn' - else - 'yN' - end + default_answer = case default + when nil + 'yn' + when true + 'Yn' + else + 'yN' + end result = nil - while result.nil? - result = ask("#{question} [#{qstr}]") - result = case result - when /^[Yy].*/ - true - when /^[Nn].*/ - false - when /^$/ - default - else - nil - end + while result.nil? do + result = case ask "#{question} [#{default_answer}]" + when /^y/i then true + when /^n/i then false + when /^$/ then default + else nil + end end return result @@ -531,20 +526,26 @@ end # SilentUI is a UI choice that is absolutely silent. class Gem::SilentUI < Gem::StreamUI - def initialize - reader, writer = nil, nil - if Gem.win_platform? - reader = File.open('nul', 'r') - writer = File.open('nul', 'w') - else + begin reader = File.open('/dev/null', 'r') writer = File.open('/dev/null', 'w') + rescue Errno::ENOENT + reader = File.open('nul', 'r') + writer = File.open('nul', 'w') end super reader, writer, writer end + + def download_reporter(*args) + SilentDownloadReporter.new(@outs, *args) + end + + def progress_reporter(*args) + SilentProgressReporter.new(@outs, *args) + end end diff --git a/lib/rubygems/validator.rb b/lib/rubygems/validator.rb index d2750f14ea..bc6c520d8b 100644 --- a/lib/rubygems/validator.rb +++ b/lib/rubygems/validator.rb @@ -94,7 +94,7 @@ class Gem::Validator next unless gems.include? gem_spec.name unless gems.empty? install_dir = gem_spec.installation_path - gem_path = File.join install_dir, "cache", gem_spec.file_name + gem_path = Gem.cache_gem(gem_spec.file_name, install_dir) spec_path = File.join install_dir, "specifications", gem_spec.spec_name gem_directory = gem_spec.full_gem_path -- cgit v1.2.3