From c00e84327f14845bd484e76b5ee5dfeb1fa9ce3d Mon Sep 17 00:00:00 2001 From: hsbt Date: Sun, 8 Oct 2017 01:32:18 +0000 Subject: Merge rubygems master. This is RC version of Rubygems 2.7.0. https://github.com/rubygems/rubygems/commit/688fb7e83c13c3fe7c2bb03c49a2db4c82852aee git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@60133 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- lib/rubygems.rb | 113 +++-- lib/rubygems/basic_specification.rb | 8 +- lib/rubygems/bundler_version_finder.rb | 112 +++++ lib/rubygems/command.rb | 2 +- lib/rubygems/command_manager.rb | 4 +- lib/rubygems/commands/cert_command.rb | 37 +- lib/rubygems/commands/cleanup_command.rb | 2 +- lib/rubygems/commands/help_command.rb | 2 +- lib/rubygems/commands/owner_command.rb | 4 +- lib/rubygems/commands/pristine_command.rb | 21 +- lib/rubygems/commands/push_command.rb | 3 +- lib/rubygems/commands/query_command.rb | 27 +- lib/rubygems/commands/setup_command.rb | 193 +++++--- lib/rubygems/commands/signin_command.rb | 33 ++ lib/rubygems/commands/signout_command.rb | 33 ++ lib/rubygems/commands/uninstall_command.rb | 7 +- lib/rubygems/commands/unpack_command.rb | 20 +- lib/rubygems/commands/update_command.rb | 2 +- lib/rubygems/commands/which_command.rb | 2 +- lib/rubygems/commands/yank_command.rb | 15 +- lib/rubygems/config_file.rb | 37 +- lib/rubygems/core_ext/kernel_require.rb | 12 +- lib/rubygems/dependency.rb | 2 + lib/rubygems/dependency_installer.rb | 4 + lib/rubygems/errors.rb | 3 + lib/rubygems/exceptions.rb | 6 + lib/rubygems/ext/builder.rb | 2 +- lib/rubygems/gem_runner.rb | 6 +- lib/rubygems/install_update_options.rb | 33 +- lib/rubygems/installer.rb | 19 +- lib/rubygems/installer_test_case.rb | 9 +- lib/rubygems/package/old.rb | 2 +- lib/rubygems/request.rb | 2 +- lib/rubygems/request_set.rb | 23 +- lib/rubygems/request_set/gem_dependency_api.rb | 6 +- lib/rubygems/requirement.rb | 6 +- lib/rubygems/resolver.rb | 24 +- lib/rubygems/resolver/installer_set.rb | 10 +- lib/rubygems/security.rb | 13 +- lib/rubygems/security_option.rb | 43 ++ lib/rubygems/server.rb | 16 +- lib/rubygems/source.rb | 11 +- lib/rubygems/source/git.rb | 3 +- lib/rubygems/source/local.rb | 73 +-- lib/rubygems/source/lock.rb | 5 +- lib/rubygems/source_local.rb | 4 +- lib/rubygems/source_specific_file.rb | 5 +- lib/rubygems/spec_fetcher.rb | 10 +- lib/rubygems/specification.rb | 512 +++++++++++---------- lib/rubygems/stub_specification.rb | 5 +- lib/rubygems/test_case.rb | 11 +- lib/rubygems/user_interaction.rb | 28 +- lib/rubygems/util.rb | 23 +- lib/rubygems/version.rb | 20 +- lib/rubygems/version_option.rb | 7 +- test/rubygems/private3072_key.pem | 40 ++ test/rubygems/public3072_cert.pem | 25 + test/rubygems/test_config.rb | 2 +- test/rubygems/test_gem.rb | 86 +++- test/rubygems/test_gem_bundler_version_finder.rb | 125 +++++ test/rubygems/test_gem_command.rb | 2 +- test/rubygems/test_gem_commands_build_command.rb | 28 +- test/rubygems/test_gem_commands_cert_command.rb | 64 +++ test/rubygems/test_gem_commands_install_command.rb | 37 +- .../rubygems/test_gem_commands_pristine_command.rb | 2 +- test/rubygems/test_gem_commands_query_command.rb | 19 + test/rubygems/test_gem_commands_setup_command.rb | 17 + test/rubygems/test_gem_commands_signin_command.rb | 95 ++++ test/rubygems/test_gem_commands_signout_command.rb | 37 ++ .../test_gem_commands_uninstall_command.rb | 12 + test/rubygems/test_gem_commands_update_command.rb | 2 +- test/rubygems/test_gem_commands_which_command.rb | 6 +- test/rubygems/test_gem_dependency.rb | 28 ++ test/rubygems/test_gem_ext_builder.rb | 4 +- test/rubygems/test_gem_install_update_options.rb | 3 +- test/rubygems/test_gem_installer.rb | 56 +-- test/rubygems/test_gem_package.rb | 10 +- test/rubygems/test_gem_remote_fetcher.rb | 4 +- .../test_gem_request_set_gem_dependency_api.rb | 6 +- test/rubygems/test_gem_requirement.rb | 6 + test/rubygems/test_gem_resolver.rb | 26 ++ test/rubygems/test_gem_resolver_conflict.rb | 2 +- test/rubygems/test_gem_security.rb | 5 + test/rubygems/test_gem_security_policy.rb | 48 +- test/rubygems/test_gem_security_signer.rb | 12 +- test/rubygems/test_gem_security_trust_dir.rb | 4 +- test/rubygems/test_gem_server.rb | 19 +- test/rubygems/test_gem_source.rb | 9 + test/rubygems/test_gem_spec_fetcher.rb | 20 + test/rubygems/test_gem_specification.rb | 95 +++- test/rubygems/test_gem_stub_specification.rb | 8 +- test/rubygems/test_gem_util.rb | 1 + test/rubygems/test_gem_version.rb | 35 +- test/rubygems/test_gem_version_option.rb | 15 + test/rubygems/test_kernel.rb | 30 ++ test/rubygems/test_require.rb | 38 ++ 96 files changed, 2019 insertions(+), 699 deletions(-) create mode 100644 lib/rubygems/bundler_version_finder.rb create mode 100644 lib/rubygems/commands/signin_command.rb create mode 100644 lib/rubygems/commands/signout_command.rb create mode 100644 lib/rubygems/security_option.rb create mode 100644 test/rubygems/private3072_key.pem create mode 100644 test/rubygems/public3072_cert.pem create mode 100644 test/rubygems/test_gem_bundler_version_finder.rb create mode 100644 test/rubygems/test_gem_commands_signin_command.rb create mode 100644 test/rubygems/test_gem_commands_signout_command.rb diff --git a/lib/rubygems.rb b/lib/rubygems.rb index 55aa85b8b2..d819bdee02 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -39,7 +39,7 @@ require 'rubygems/errors' # Further RubyGems documentation can be found at: # # * {RubyGems Guides}[http://guides.rubygems.org] -# * {RubyGems API}[http://rubygems.rubyforge.org/rdoc] (also available from +# * {RubyGems API}[http://www.rubydoc.info/github/rubygems/rubygems] (also available from # gem server) # # == RubyGems Plugins @@ -47,15 +47,16 @@ require 'rubygems/errors' # As of RubyGems 1.3.2, RubyGems will load plugins installed in gems or # $LOAD_PATH. Plugins must be named 'rubygems_plugin' (.rb, .so, etc) and # placed at the root of your gem's #require_path. Plugins are discovered via -# Gem::find_files then loaded. Take care when implementing a plugin as your +# Gem::find_files and then loaded. Take care when implementing a plugin as your # plugin file may be loaded multiple times if multiple versions of your gem # are installed. # -# For an example plugin, see the graph gem which adds a `gem graph` command. +# For an example plugin, see the {Graph gem}[https://github.com/seattlerb/graph] +# which adds a `gem graph` command. # # == RubyGems Defaults, Packaging # -# RubyGems defaults are stored in rubygems/defaults.rb. If you're packaging +# RubyGems defaults are stored in lib/rubygems/defaults.rb. If you're packaging # RubyGems or implementing Ruby you can change RubyGems' defaults. # # For RubyGems packagers, provide lib/rubygems/defaults/operating_system.rb @@ -65,7 +66,7 @@ require 'rubygems/errors' # override any defaults from lib/rubygems/defaults.rb. # # If you need RubyGems to perform extra work on install or uninstall, your -# defaults override file can set pre and post install and uninstall hooks. +# defaults override file can set pre/post install and uninstall hooks. # See Gem::pre_install, Gem::pre_uninstall, Gem::post_install, # Gem::post_uninstall. # @@ -106,6 +107,8 @@ require 'rubygems/errors' # # (If your name is missing, PLEASE let us know!) # +# == License +# # See {LICENSE.txt}[rdoc-ref:lib/rubygems/LICENSE.txt] for permissions. # # Thanks! @@ -130,6 +133,7 @@ module Gem GEM_DEP_FILES = %w[ gem.deps.rb + gems.rb Gemfile Isolate ] @@ -159,7 +163,7 @@ module Gem # these are defined in Ruby 1.8.7, hence the need for this convoluted setup. READ_BINARY_ERRORS = begin - read_binary_errors = [Errno::EACCES] + read_binary_errors = [Errno::EACCES, Errno::EROFS] read_binary_errors << Errno::ENOTSUP if Errno.const_defined?(:ENOTSUP) read_binary_errors end.freeze @@ -174,6 +178,8 @@ module Gem write_binary_errors end.freeze + USE_BUNDLER_FOR_GEMDEPS = true # :nodoc: + @@win_platform = nil @configuration = nil @@ -266,17 +272,22 @@ module Gem return loaded if loaded && dep.matches_spec?(loaded) - specs = dep.matching_specs(true) - - raise Gem::GemNotFoundException, - "can't find gem #{dep}" if specs.empty? + find_specs = proc { dep.matching_specs(true) } + if dep.to_s == "bundler (>= 0.a)" + specs = Gem::BundlerVersionFinder.without_filtering(&find_specs) + else + specs = find_specs.call + end specs = specs.find_all { |spec| spec.executables.include? exec_name } if exec_name unless spec = specs.first - msg = "can't find gem #{name} (#{requirements}) with executable #{exec_name}" + msg = "can't find gem #{dep} with executable #{exec_name}" + if name == "bundler" && bundler_message = Gem::BundlerVersionFinder.missing_version_message + msg = bundler_message + end raise Gem::GemNotFoundException, msg end @@ -297,7 +308,10 @@ module Gem def self.activate_bin_path name, exec_name, requirement # :nodoc: spec = find_spec_for_exe name, exec_name, [requirement] - Gem::LOADED_SPECS_MUTEX.synchronize { spec.activate } + Gem::LOADED_SPECS_MUTEX.synchronize do + spec.activate + finish_resolve + end spec.bin_file exec_name end @@ -356,12 +370,16 @@ module Gem # package is not available as a gem, return nil. def self.datadir(gem_name) -# TODO: deprecate spec = @loaded_specs[gem_name] return nil if spec.nil? spec.datadir end + class << self + extend Gem::Deprecate + deprecate :datadir, "spec.datadir", 2016, 10 + end + ## # A Zlib::Deflate.deflate wrapper @@ -594,7 +612,6 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} # Zlib::GzipReader wrapper that unzips +data+. def self.gunzip(data) - require 'rubygems/util' Gem::Util.gunzip data end @@ -602,7 +619,6 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} # Zlib::GzipWriter wrapper that zips +data+. def self.gzip(data) - require 'rubygems/util' Gem::Util.gzip data end @@ -610,7 +626,6 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} # A Zlib::Inflate#inflate wrapper def self.inflate(data) - require 'rubygems/util' Gem::Util.inflate data end @@ -715,9 +730,20 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} ## # The file name and line number of the caller of the caller of this method. + # + # +depth+ is how many layers up the call stack it should go. + # + # e.g., + # + # def a; Gem.location_of_caller; end + # a #=> ["x.rb", 2] # (it'll vary depending on file name and line number) + # + # def b; c; end + # def c; Gem.location_of_caller(2); end + # b #=> ["x.rb", 6] # (it'll vary depending on file name and line number) - def self.location_of_caller - caller[1] =~ /(.*?):(\d+).*?$/i + def self.location_of_caller(depth = 1) + caller[depth] =~ /(.*?):(\d+).*?$/i file = $1 lineno = $2.to_i @@ -1148,8 +1174,6 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} path = path.dup if path == "-" then - require 'rubygems/util' - Gem::Util.traverse_parents Dir.pwd do |directory| dep_file = GEM_DEP_FILES.find { |f| File.file?(f) } @@ -1168,18 +1192,36 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} raise ArgumentError, "Unable to find gem dependencies file at #{path}" end - rs = Gem::RequestSet.new - @gemdeps = rs.load_gemdeps path + if USE_BUNDLER_FOR_GEMDEPS + + ENV["BUNDLE_GEMFILE"] ||= File.expand_path(path) + require 'rubygems/user_interaction' + Gem::DefaultUserInteraction.use_ui(ui) do + require "bundler" + @gemdeps = Bundler.setup + Bundler.ui = nil + @gemdeps.requested_specs.map(&:to_spec).sort_by(&:name) + end + + else + + rs = Gem::RequestSet.new + @gemdeps = rs.load_gemdeps path + + rs.resolve_current.map do |s| + s.full_spec.tap(&:activate) + end - rs.resolve_current.map do |s| - sp = s.full_spec - sp.activate - sp end - rescue Gem::LoadError, Gem::UnsatisfiableDependencyError => e - warn e.message - warn "You may need to `gem install -g` to install missing gems" - warn "" + rescue => e + case e + when Gem::LoadError, Gem::UnsatisfiableDependencyError, (defined?(Bundler::GemNotFound) ? Bundler::GemNotFound : Gem::LoadError) + warn e.message + warn "You may need to `gem install -g` to install missing gems" + warn "" + else + raise + end end class << self @@ -1225,6 +1267,8 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} prefix_pattern = /^(#{prefix_group})/ end + suffix_pattern = /#{Regexp.union(Gem.suffixes)}\z/ + spec.files.each do |file| if new_format file = file.sub(prefix_pattern, "") @@ -1232,6 +1276,7 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} end @path_to_default_spec_map[file] = spec + @path_to_default_spec_map[file.sub(suffix_pattern, "")] = spec end end @@ -1239,11 +1284,7 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} # Find a Gem::Specification of default gem from +path+ def find_unresolved_default_spec(path) - Gem.suffixes.each do |suffix| - spec = @path_to_default_spec_map["#{path}#{suffix}"] - return spec if spec - end - nil + @path_to_default_spec_map[path] end ## @@ -1314,6 +1355,7 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} MARSHAL_SPEC_DIR = "quick/Marshal.#{Gem.marshal_version}/" + autoload :BundlerVersionFinder, 'rubygems/bundler_version_finder' autoload :ConfigFile, 'rubygems/config_file' autoload :Dependency, 'rubygems/dependency' autoload :DependencyList, 'rubygems/dependency_list' @@ -1329,6 +1371,7 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} autoload :SourceList, 'rubygems/source_list' autoload :SpecFetcher, 'rubygems/spec_fetcher' autoload :Specification, 'rubygems/specification' + autoload :Util, 'rubygems/util' autoload :Version, 'rubygems/version' require "rubygems/specification" diff --git a/lib/rubygems/basic_specification.rb b/lib/rubygems/basic_specification.rb index 5aed17437e..0d50a93230 100644 --- a/lib/rubygems/basic_specification.rb +++ b/lib/rubygems/basic_specification.rb @@ -71,7 +71,7 @@ class Gem::BasicSpecification elsif missing_extensions? then @ignored = true - warn "Ignoring #{full_name} because its extensions are not built. " + + warn "Ignoring #{full_name} because its extensions are not built. " + "Try: gem pristine #{name} --version #{version}" return false end @@ -275,10 +275,10 @@ class Gem::BasicSpecification # for this spec. def lib_dirs_glob - dirs = if self.require_paths.size > 1 then - "{#{self.require_paths.join(',')}}" + dirs = if self.raw_require_paths.size > 1 then + "{#{self.raw_require_paths.join(',')}}" else - self.require_paths.first + self.raw_require_paths.first end "#{self.full_gem_path}/#{dirs}".dup.untaint diff --git a/lib/rubygems/bundler_version_finder.rb b/lib/rubygems/bundler_version_finder.rb new file mode 100644 index 0000000000..baca170840 --- /dev/null +++ b/lib/rubygems/bundler_version_finder.rb @@ -0,0 +1,112 @@ +module Gem::BundlerVersionFinder + @without_filtering = false + + def self.without_filtering + without_filtering, @without_filtering = true, @without_filtering + yield + ensure + @without_filtering = without_filtering + end + + def self.bundler_version + version, _ = bundler_version_with_reason + + return unless version + + Gem::Version.new(version) + end + + def self.bundler_version_with_reason + return if @without_filtering + + if v = ENV["BUNDLER_VERSION"] + return [v, "`$BUNDLER_VERSION`"] + end + if v = bundle_update_bundler_version + return if v == true + return [v, "`bundle update --bundler`"] + end + v, lockfile = lockfile_version + if v + return [v, "your #{lockfile}"] + end + end + + def self.missing_version_message + return unless vr = bundler_version_with_reason + <<-EOS +Could not find 'bundler' (#{vr.first}) required by #{vr.last}. +To update to the lastest version installed on your system, run `bundle update --bundler`. +To install the missing version, run `gem install bundler:#{vr.first}` + EOS + end + + def self.compatible?(spec) + return true unless spec.name == "bundler".freeze + return true unless bundler_version = self.bundler_version + if bundler_version.segments.first >= 2 + spec.version == bundler_version + else # 1.x + spec.version.segments.first < 2 + end + end + + def self.filter!(specs) + return unless bundler_version = self.bundler_version + if bundler_version.segments.first >= 2 + specs.reject! { |spec| spec.version != bundler_version } + else # 1.x + specs.reject! { |spec| spec.version.segments.first >= 2} + end + end + + def self.bundle_update_bundler_version + return unless File.basename($0) == "bundle".freeze + return unless "update".start_with?(ARGV.first || " ") + bundler_version = nil + update_index = nil + ARGV.each_with_index do |a, i| + if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN + bundler_version = a + end + next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ + bundler_version = $1 || true + update_index = i + end + bundler_version + end + private_class_method :bundle_update_bundler_version + + def self.lockfile_version + return unless lockfile = lockfile_contents + lockfile, contents = lockfile + lockfile ||= "lockfile" + regexp = /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ + return unless contents =~ regexp + [$1, lockfile] + end + private_class_method :lockfile_version + + def self.lockfile_contents + gemfile = ENV["BUNDLE_GEMFILE"] + gemfile = nil if gemfile && gemfile.empty? + Gem::Util.traverse_parents Dir.pwd do |directory| + next unless gemfile = Gem::GEM_DEP_FILES.find { |f| File.file?(f.untaint) } + + gemfile = File.join directory, gemfile + break + end unless gemfile + + return unless gemfile + + lockfile = case gemfile + when "gems.rb" then "gems.locked" + else "#{gemfile}.lock" + end.untaint + + return unless File.file?(lockfile) + + [lockfile, File.read(lockfile)] + end + private_class_method :lockfile_contents +end diff --git a/lib/rubygems/command.rb b/lib/rubygems/command.rb index 14b70b75ec..3bdca656b3 100644 --- a/lib/rubygems/command.rb +++ b/lib/rubygems/command.rb @@ -527,7 +527,7 @@ class Gem::Command end add_common_option("--silent", - "Silence rubygems output") do |value, options| + "Silence RubyGems output") do |value, options| options[:silent] = true end diff --git a/lib/rubygems/command_manager.rb b/lib/rubygems/command_manager.rb index 451b719c46..887272378e 100644 --- a/lib/rubygems/command_manager.rb +++ b/lib/rubygems/command_manager.rb @@ -58,6 +58,8 @@ class Gem::CommandManager :rdoc, :search, :server, + :signin, + :signout, :sources, :specification, :stale, @@ -161,7 +163,7 @@ class Gem::CommandManager say Gem::VERSION terminate_interaction 0 when /^-/ then - alert_error "Invalid option: #{args.first}. See 'gem --help'." + alert_error "Invalid option: #{args.first}. See 'gem --help'." terminate_interaction 1 else cmd_name = args.shift.downcase diff --git a/lib/rubygems/commands/cert_command.rb b/lib/rubygems/commands/cert_command.rb index 7adf5b01b1..5542262a50 100644 --- a/lib/rubygems/commands/cert_command.rb +++ b/lib/rubygems/commands/cert_command.rb @@ -84,6 +84,11 @@ class Gem::Commands::CertCommand < Gem::Command options[:sign] << cert_file end + + add_option('-d', '--days NUMBER_OF_DAYS', + 'Days before the certificate expires') do |days, options| + options[:expiration_length_days] = days.to_i + end end def add_certificate certificate # :nodoc: @@ -105,16 +110,20 @@ class Gem::Commands::CertCommand < Gem::Command list_certificates_matching filter end - options[:build].each do |name| - build name + options[:build].each do |email| + build email end sign_certificates unless options[:sign].empty? end - def build name + def build email + if !valid_email?(email) + raise Gem::CommandLineError, "Invalid email address #{email}" + end + key, key_path = build_key - cert_path = build_cert name, key + cert_path = build_cert email, key say "Certificate: #{cert_path}" @@ -124,8 +133,16 @@ class Gem::Commands::CertCommand < Gem::Command end end - def build_cert name, key # :nodoc: - cert = Gem::Security.create_cert_email name, key + def build_cert email, key # :nodoc: + expiration_length_days = options[:expiration_length_days] + age = + if expiration_length_days.nil? || expiration_length_days == 0 + Gem::Security::ONE_YEAR + else + Gem::Security::ONE_DAY * expiration_length_days + end + + cert = Gem::Security.create_cert_email email, key, age Gem::Security.write cert, "gem-public_cert.pem" end @@ -273,5 +290,13 @@ For further reading on signing gems see `ri Gem::Security`. end end + private + + def valid_email? email + # It's simple, but is all we need + email =~ /\A.+@.+\z/ + end + + end if defined?(OpenSSL::SSL) diff --git a/lib/rubygems/commands/cleanup_command.rb b/lib/rubygems/commands/cleanup_command.rb index 83ee6b5c7c..db1bf3a794 100644 --- a/lib/rubygems/commands/cleanup_command.rb +++ b/lib/rubygems/commands/cleanup_command.rb @@ -66,7 +66,7 @@ If no gems are named all gems in GEM_HOME are cleaned. clean_gems end - say "Clean Up Complete" + say "Clean up complete" verbose do skipped = @default_gems.map { |spec| spec.full_name } diff --git a/lib/rubygems/commands/help_command.rb b/lib/rubygems/commands/help_command.rb index de3f175fad..7d02022369 100644 --- a/lib/rubygems/commands/help_command.rb +++ b/lib/rubygems/commands/help_command.rb @@ -367,7 +367,7 @@ platform. elsif possibilities.size > 1 then alert_warning "Ambiguous command #{command_name} (#{possibilities.join(', ')})" else - alert_warning "Unknown command #{command_name}. Try: gem help commands" + alert_warning "Unknown command #{command_name}. Try: gem help commands" end end diff --git a/lib/rubygems/commands/owner_command.rb b/lib/rubygems/commands/owner_command.rb index 4b99434e87..8e2271657a 100644 --- a/lib/rubygems/commands/owner_command.rb +++ b/lib/rubygems/commands/owner_command.rb @@ -40,7 +40,9 @@ permission to. options[:remove] << value end - add_option '-h', '--host HOST', 'Use another gemcutter-compatible host' do |value, options| + add_option '-h', '--host HOST', + 'Use another gemcutter-compatible host', + ' (e.g. https://rubygems.org)' do |value, options| options[:host] = value end end diff --git a/lib/rubygems/commands/pristine_command.rb b/lib/rubygems/commands/pristine_command.rb index 6cab572b86..408672158f 100644 --- a/lib/rubygems/commands/pristine_command.rb +++ b/lib/rubygems/commands/pristine_command.rb @@ -125,14 +125,14 @@ extensions will be restored. next end - unless spec.extensions.empty? or options[:extensions] then + unless spec.extensions.empty? or options[:extensions] or options[:only_executables] then say "Skipped #{spec.full_name}, it needs to compile an extension" next end gem = spec.cache_file - unless File.exist? gem then + unless File.exist? gem or options[:only_executables] then require 'rubygems/remote_fetcher' say "Cached gem for #{spec.full_name} not found, attempting to fetch..." @@ -157,16 +157,19 @@ extensions will be restored. install_defaults.to_s['--env-shebang'] end - installer = Gem::Installer.at(gem, - :wrappers => true, - :force => true, - :install_dir => spec.base_dir, - :env_shebang => env_shebang, - :build_args => spec.build_args) - + installer_options = { + :wrappers => true, + :force => true, + :install_dir => spec.base_dir, + :env_shebang => env_shebang, + :build_args => spec.build_args, + } + if options[:only_executables] then + installer = Gem::Installer.for_spec(spec, installer_options) installer.generate_bin else + installer = Gem::Installer.at(gem, installer_options) installer.install end diff --git a/lib/rubygems/commands/push_command.rb b/lib/rubygems/commands/push_command.rb index 6adeff6b30..d294cbc8df 100644 --- a/lib/rubygems/commands/push_command.rb +++ b/lib/rubygems/commands/push_command.rb @@ -33,7 +33,8 @@ command. For further discussion see the help for the yank command. add_key_option add_option('--host HOST', - 'Push to another gemcutter-compatible host') do |value, options| + 'Push to another gemcutter-compatible host', + ' (e.g. https://rubygems.org)') do |value, options| options[:host] = value end diff --git a/lib/rubygems/commands/query_command.rb b/lib/rubygems/commands/query_command.rb index 44144203e0..4624e5a1e9 100644 --- a/lib/rubygems/commands/query_command.rb +++ b/lib/rubygems/commands/query_command.rb @@ -255,22 +255,21 @@ is too hard to use. name_tuples.map { |n| n.version }.uniq else platforms.sort.reverse.map do |version, pls| - if pls == [Gem::Platform::RUBY] then - if options[:domain] == :remote || specs.all? { |spec| spec.is_a? Gem::Source } - version - else - spec = specs.select { |s| s.version == version } - if spec.first.default_gem? - "default: #{version}" - else - version - end + out = version.to_s + + if options[:domain] == :local + default = specs.any? do |s| + !s.is_a?(Gem::Source) && s.version == version && s.default_gem? end - else - ruby = pls.delete Gem::Platform::RUBY - platform_list = [ruby, *pls.sort].compact - "#{version} #{platform_list.join ' '}" + out = "default: #{out}" if default + end + + if pls != [Gem::Platform::RUBY] then + platform_list = [pls.delete(Gem::Platform::RUBY), *pls.sort].compact + out = platform_list.unshift(out).join(' ') end + + out end end diff --git a/lib/rubygems/commands/setup_command.rb b/lib/rubygems/commands/setup_command.rb index 2c29786224..f322ca9df5 100644 --- a/lib/rubygems/commands/setup_command.rb +++ b/lib/rubygems/commands/setup_command.rb @@ -15,10 +15,11 @@ class Gem::Commands::SetupCommand < Gem::Command super 'setup', 'Install RubyGems', :format_executable => true, :document => %w[ri], :site_or_vendor => 'sitelibdir', - :destdir => '', :prefix => '', :previous_version => '' + :destdir => '', :prefix => '', :previous_version => '', + :regenerate_binstubs => true add_option '--previous-version=VERSION', - 'Previous version of rubygems', + 'Previous version of RubyGems', 'Used for changelog processing' do |version, options| options[:previous_version] = version end @@ -42,7 +43,7 @@ class Gem::Commands::SetupCommand < Gem::Command add_option '--[no-]format-executable', 'Makes `gem` match ruby', - 'If ruby is ruby18, gem will be gem18' do |value, options| + 'If Ruby is ruby18, gem will be gem18' do |value, options| options[:format_executable] = value end @@ -79,6 +80,15 @@ class Gem::Commands::SetupCommand < Gem::Command options[:document].uniq! end + add_option '--[no-]regenerate-binstubs', + 'Regenerate gem binstubs' do |value, options| + if value then + options[:regenerate_binstubs] = true + else + options.delete(:regenerate_binstubs) + end + end + @verbose = nil end @@ -92,7 +102,7 @@ class Gem::Commands::SetupCommand < Gem::Command end def defaults_str # :nodoc: - "--format-executable --document ri" + "--format-executable --document ri --regenerate-binstubs" end def description # :nodoc: @@ -142,8 +152,12 @@ By default, this RubyGems will install gem as: remove_old_lib_files lib_dir + install_default_bundler_gem + say "RubyGems #{Gem::VERSION} installed" + regenerate_binstubs + uninstall_old_gemcutter documentation_success = install_rdoc @@ -190,7 +204,7 @@ By default, this RubyGems will install gem as: if options[:document].include? 'ri' then say "Ruby Interactive (ri) documentation was installed. ri is kind of like man " - say "pages for ruby libraries. You may access it like this:" + say "pages for Ruby libraries. You may access it like this:" say " ri Classname" say " ri Classname.class_method" say " ri Classname#instance_method" @@ -202,59 +216,64 @@ By default, this RubyGems will install gem as: end end - def install_executables(bin_dir) - say "Installing gem executable" if @verbose + def install_executables(bin_dir) @bin_file_names = [] - Dir.chdir 'bin' do - bin_files = Dir['*'] + executables = { 'gem' => 'bin' } + executables['bundler'] = 'bundler/exe' if Gem::USE_BUNDLER_FOR_GEMDEPS + executables.each do |tool, path| + say "Installing #{tool} executable" if @verbose - bin_files.delete 'update_rubygems' + Dir.chdir path do + bin_files = Dir['*'] - bin_files.each do |bin_file| - bin_file_formatted = if options[:format_executable] then - Gem.default_exec_format % bin_file - else - bin_file - end + bin_files -= %w[update_rubygems bundler bundle_ruby] - dest_file = File.join bin_dir, bin_file_formatted - bin_tmp_file = File.join Dir.tmpdir, "#{bin_file}.#{$$}" + bin_files.each do |bin_file| + bin_file_formatted = if options[:format_executable] then + Gem.default_exec_format % bin_file + else + bin_file + end - begin - bin = File.readlines bin_file - bin[0] = "#!#{Gem.ruby}\n" + dest_file = File.join bin_dir, bin_file_formatted + bin_tmp_file = File.join Dir.tmpdir, "#{bin_file}.#{$$}" - File.open bin_tmp_file, 'w' do |fp| - fp.puts bin.join - end - - install bin_tmp_file, dest_file, :mode => 0755 - @bin_file_names << dest_file - ensure - rm bin_tmp_file - end - - next unless Gem.win_platform? + begin + bin = File.readlines bin_file + bin[0] = "#!#{Gem.ruby}\n" - begin - bin_cmd_file = File.join Dir.tmpdir, "#{bin_file}.bat" + File.open bin_tmp_file, 'w' do |fp| + fp.puts bin.join + end - File.open bin_cmd_file, 'w' do |file| - file.puts <<-TEXT -@ECHO OFF -IF NOT "%~f0" == "~f0" GOTO :WinNT -@"#{File.basename(Gem.ruby).chomp('"')}" "#{dest_file}" %1 %2 %3 %4 %5 %6 %7 %8 %9 -GOTO :EOF -:WinNT -@"#{File.basename(Gem.ruby).chomp('"')}" "%~dpn0" %* -TEXT + install bin_tmp_file, dest_file, :mode => 0755 + @bin_file_names << dest_file + ensure + rm bin_tmp_file end - install bin_cmd_file, "#{dest_file}.bat", :mode => 0755 - ensure - rm bin_cmd_file + next unless Gem.win_platform? + + begin + bin_cmd_file = File.join Dir.tmpdir, "#{bin_file}.bat" + + File.open bin_cmd_file, 'w' do |file| + file.puts <<-TEXT + @ECHO OFF + IF NOT "%~f0" == "~f0" GOTO :WinNT + @"#{File.basename(Gem.ruby).chomp('"')}" "#{dest_file}" %1 %2 %3 %4 %5 %6 %7 %8 %9 + GOTO :EOF + :WinNT + @"#{File.basename(Gem.ruby).chomp('"')}" "%~dpn0" %* + TEXT + end + + install bin_cmd_file, "#{dest_file}.bat", :mode => 0755 + ensure + rm bin_cmd_file + end end end end @@ -269,18 +288,22 @@ TEXT end def install_lib(lib_dir) - say "Installing RubyGems" if @verbose + libs = { 'RubyGems' => 'lib' } + libs['Bundler'] = 'bundler/lib' if Gem::USE_BUNDLER_FOR_GEMDEPS + libs.each do |tool, path| + say "Installing #{tool}" if @verbose - lib_files = rb_files_in 'lib' - pem_files = pem_files_in 'lib' + lib_files = rb_files_in path + pem_files = pem_files_in path - Dir.chdir 'lib' do - lib_files.each do |lib_file| - install_file lib_file, lib_dir - end + Dir.chdir path do + lib_files.each do |lib_file| + install_file lib_file, lib_dir + end - pem_files.each do |pem_file| - install_file pem_file, lib_dir + pem_files.each do |pem_file| + install_file pem_file, lib_dir + end end end end @@ -326,6 +349,29 @@ TEXT return false end + def install_default_bundler_gem + return unless Gem::USE_BUNDLER_FOR_GEMDEPS + + bundler_spec = Gem::Specification.load("bundler/bundler.gemspec") + bundler_spec.files = Dir["bundler/{*.md,{lib,exe,man}/**/*}"] + bundler_spec.executables -= %w[bundler bundle_ruby] + Dir.entries(Gem::Specification.default_specifications_dir). + select {|gs| gs.start_with?("bundler-") }. + each {|gs| File.delete(File.join(Gem::Specification.default_specifications_dir, gs)) } + + default_spec_path = File.join(Gem::Specification.default_specifications_dir, "#{bundler_spec.full_name}.gemspec") + Gem.write_binary(default_spec_path, bundler_spec.to_ruby) + + bundler_spec = Gem::Specification.load(default_spec_path) + + Dir.entries(bundler_spec.gems_dir). + select {|default_gem| default_gem.start_with?("bundler-") }. + each {|default_gem| rm_r File.join(bundler_spec.gems_dir, default_gem) } + + mkdir_p bundler_spec.bin_dir + bundler_spec.executables.each {|e| cp File.join("bundler", bundler_spec.bindir, e), File.join(bundler_spec.bin_dir, e) } + end + def make_destination_dirs(install_destdir) lib_dir, bin_dir = Gem.default_rubygems_dirs @@ -397,7 +443,7 @@ TEXT old_bin_path = File.join bin_dir, old_bin_file next unless File.exist? old_bin_path - deprecation_message = "`#{old_bin_file}` has been deprecated. Use `#{new_name}` instead." + deprecation_message = "`#{old_bin_file}` has been deprecated. Use `#{new_name}` instead." File.open old_bin_path, 'w' do |fp| fp.write <<-EOF @@ -416,23 +462,26 @@ abort "#{deprecation_message}" end def remove_old_lib_files lib_dir - rubygems_dir = File.join lib_dir, 'rubygems' - lib_files = rb_files_in 'lib/rubygems' + lib_dirs = { File.join(lib_dir, 'rubygems') => 'lib/rubygems' } + lib_dirs[File.join(lib_dir, 'bundler')] = 'bundler/lib/bundler' if Gem::USE_BUNDLER_FOR_GEMDEPS + lib_dirs.each do |old_lib_dir, new_lib_dir| + lib_files = rb_files_in(new_lib_dir) - old_lib_files = rb_files_in rubygems_dir + old_lib_files = rb_files_in(old_lib_dir) - to_remove = old_lib_files - lib_files + to_remove = old_lib_files - lib_files - to_remove.delete_if do |file| - file.start_with? 'defaults' - end + to_remove.delete_if do |file| + file.start_with? 'defaults' + end - Dir.chdir rubygems_dir do - to_remove.each do |file| - FileUtils.rm_f file + Dir.chdir old_lib_dir do + to_remove.each do |file| + FileUtils.rm_f file - warn "unable to remove old file #{file} please remove it by hand" if - File.exist? file + warn "unable to remove old file #{file} please remove it by hand" if + File.exist? file + end end end end @@ -480,5 +529,11 @@ abort "#{deprecation_message}" rescue Gem::InstallError end -end + def regenerate_binstubs + require "rubygems/commands/pristine_command" + say "Regenerating binstubs" + command = Gem::Commands::PristineCommand.new + command.invoke(*%w[--all --only-executables --silent]) + end +end diff --git a/lib/rubygems/commands/signin_command.rb b/lib/rubygems/commands/signin_command.rb new file mode 100644 index 0000000000..6556db5a89 --- /dev/null +++ b/lib/rubygems/commands/signin_command.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true +require 'rubygems/command' +require 'rubygems/gemcutter_utilities' + +class Gem::Commands::SigninCommand < Gem::Command + include Gem::GemcutterUtilities + + def initialize + super 'signin', 'Sign in to any gemcutter-compatible host. '\ + 'It defaults to https://rubygems.org' + + add_option('--host HOST', 'Push to another gemcutter-compatible host') do |value, options| + options[:host] = value + end + + end + + def description # :nodoc: + 'The signin command executes host sign in for a push server (the default is'\ + ' https://rubygems.org). The host can be provided with the host flag or can'\ + ' be inferred from the provided gem. Host resolution matches the resolution'\ + ' strategy for the push command.' + end + + def usage # :nodoc: + program_name + end + + def execute + sign_in options[:host] + end + +end diff --git a/lib/rubygems/commands/signout_command.rb b/lib/rubygems/commands/signout_command.rb new file mode 100644 index 0000000000..2452e8cae1 --- /dev/null +++ b/lib/rubygems/commands/signout_command.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true +require 'rubygems/command' + +class Gem::Commands::SignoutCommand < Gem::Command + + def initialize + super 'signout', 'Sign out from all the current sessions.' + end + + def description # :nodoc: + 'The `signout` command is used to sign out from all current sessions,'\ + ' allowing you to sign in using a different set of credentials.' + end + + def usage # :nodoc: + program_name + end + + def execute + credentials_path = Gem.configuration.credentials_path + + if !File.exist?(credentials_path) then + alert_error 'You are not currently signed in.' + elsif !File.writable?(credentials_path) then + alert_error "File '#{Gem.configuration.credentials_path}' is read-only."\ + ' Please make sure it is writable.' + else + Gem.configuration.unset_api_key! + say 'You have successfully signed out from all sessions.' + end + end + +end diff --git a/lib/rubygems/commands/uninstall_command.rb b/lib/rubygems/commands/uninstall_command.rb index fe97790194..20b3a7a1e4 100644 --- a/lib/rubygems/commands/uninstall_command.rb +++ b/lib/rubygems/commands/uninstall_command.rb @@ -30,7 +30,7 @@ class Gem::Commands::UninstallCommand < Gem::Command options[:ignore] = value end - add_option('-D', '--[no-]-check-development', + add_option('-D', '--[no-]check-development', 'Check development dependencies while uninstalling', '(default: false)') do |value, options| options[:check_dev] = value @@ -143,7 +143,9 @@ that is a dependency of an existing gem. You can use the deplist = Gem::DependencyList.new get_all_gem_names.uniq.each do |name| - Gem::Specification.find_all_by_name(name).each do |spec| + gem_specs = Gem::Specification.find_all_by_name(name) + say("Gem '#{name}' is not installed") if gem_specs.empty? + gem_specs.each do |spec| deplist.add spec end end @@ -162,4 +164,3 @@ that is a dependency of an existing gem. You can use the end end - diff --git a/lib/rubygems/commands/unpack_command.rb b/lib/rubygems/commands/unpack_command.rb index ffa429de6f..eb7f550673 100644 --- a/lib/rubygems/commands/unpack_command.rb +++ b/lib/rubygems/commands/unpack_command.rb @@ -2,11 +2,20 @@ require 'rubygems/command' require 'rubygems/installer' require 'rubygems/version_option' +require 'rubygems/security_option' require 'rubygems/remote_fetcher' +# forward-declare + +module Gem::Security # :nodoc: + class Policy # :nodoc: + end +end + class Gem::Commands::UnpackCommand < Gem::Command include Gem::VersionOption + include Gem::SecurityOption def initialize require 'fileutils' @@ -24,6 +33,7 @@ class Gem::Commands::UnpackCommand < Gem::Command options[:spec] = true end + add_security_option add_version_option end @@ -63,6 +73,8 @@ command help for an example. # at the same time.) def execute + security_policy = options[:security_policy] + get_all_gem_names.each do |name| dependency = Gem::Dependency.new name, options[:version] path = get_path dependency @@ -73,7 +85,7 @@ command help for an example. end if @options[:spec] then - spec, metadata = get_metadata path + spec, metadata = get_metadata path, security_policy if metadata.nil? then alert_error "--spec is unsupported on '#{name}' (old format gem)" @@ -89,7 +101,7 @@ command help for an example. basename = File.basename path, '.gem' target_dir = File.expand_path basename, options[:target] - package = Gem::Package.new path + package = Gem::Package.new path, security_policy package.extract_files target_dir say "Unpacked gem: '#{target_dir}'" @@ -158,8 +170,8 @@ command help for an example. #-- # TODO move to Gem::Package as #raw_spec or something - def get_metadata path - format = Gem::Package.new path + def get_metadata path, security_policy = nil + format = Gem::Package.new path, security_policy spec = format.spec metadata = nil diff --git a/lib/rubygems/commands/update_command.rb b/lib/rubygems/commands/update_command.rb index f3d70a92a6..93ee60e1ab 100644 --- a/lib/rubygems/commands/update_command.rb +++ b/lib/rubygems/commands/update_command.rb @@ -70,7 +70,7 @@ command to remove old versions. def check_latest_rubygems version # :nodoc: if Gem.rubygems_version == version then - say "Latest version currently installed. Aborting." + say "Latest version already installed. Done." terminate_interaction end diff --git a/lib/rubygems/commands/which_command.rb b/lib/rubygems/commands/which_command.rb index c028d5d49f..704d79fc60 100644 --- a/lib/rubygems/commands/which_command.rb +++ b/lib/rubygems/commands/which_command.rb @@ -56,7 +56,7 @@ requiring to see why it does not behave as you expect. paths = find_paths arg, dirs if paths.empty? then - alert_error "Can't find ruby library file or shared library #{arg}" + alert_error "Can't find Ruby library file or shared library #{arg}" found &&= false else diff --git a/lib/rubygems/commands/yank_command.rb b/lib/rubygems/commands/yank_command.rb index 0d6575b272..ebf24e5c77 100644 --- a/lib/rubygems/commands/yank_command.rb +++ b/lib/rubygems/commands/yank_command.rb @@ -11,19 +11,11 @@ class Gem::Commands::YankCommand < Gem::Command def description # :nodoc: <<-EOF -The yank command removes a gem you pushed to a server from the server's -index. - -Note that if you push a gem to rubygems.org the yank command does not -prevent other people from downloading the gem via the download link. +The yank command permanently removes a gem you pushed to a server. Once you have pushed a gem several downloads will happen automatically -via the webhooks. If you accidentally pushed passwords or other sensitive +via the webhooks. If you accidentally pushed passwords or other sensitive data you will need to change them immediately and yank your gem. - -If you are yanking a gem due to intellectual property reasons contact -http://help.rubygems.org for permanent removal. Be sure to mention this -as the reason for the removal request. EOF end @@ -42,7 +34,8 @@ as the reason for the removal request. add_platform_option("remove") add_option('--host HOST', - 'Yank from another gemcutter-compatible host') do |value, options| + 'Yank from another gemcutter-compatible host', + ' (e.g. https://rubygems.org)') do |value, options| options[:host] = value end diff --git a/lib/rubygems/config_file.rb b/lib/rubygems/config_file.rb index c95d7dd1f1..b98d30cc69 100644 --- a/lib/rubygems/config_file.rb +++ b/lib/rubygems/config_file.rb @@ -336,6 +336,15 @@ if you believe they were disclosed to a third party. load_api_keys # reload end + ## + # Remove the +~/.gem/credentials+ file to clear all the current sessions. + + def unset_api_key! + return false unless File.exist?(credentials_path) + + File.delete(credentials_path) + end + def load_file(filename) Gem.load_yaml @@ -419,31 +428,11 @@ if you believe they were disclosed to a third party. # to_yaml only overwrites things you can't override on the command line. def to_yaml # :nodoc: yaml_hash = {} - yaml_hash[:backtrace] = if @hash.key?(:backtrace) - @hash[:backtrace] - else - DEFAULT_BACKTRACE - end - - yaml_hash[:bulk_threshold] = if @hash.key?(:bulk_threshold) - @hash[:bulk_threshold] - else - DEFAULT_BULK_THRESHOLD - end - + yaml_hash[:backtrace] = @hash.fetch(:backtrace, DEFAULT_BACKTRACE) + yaml_hash[:bulk_threshold] = @hash.fetch(:bulk_threshold, DEFAULT_BULK_THRESHOLD) yaml_hash[:sources] = Gem.sources.to_a - - yaml_hash[:update_sources] = if @hash.key?(:update_sources) - @hash[:update_sources] - else - DEFAULT_UPDATE_SOURCES - end - - yaml_hash[:verbose] = if @hash.key?(:verbose) - @hash[:verbose] - else - DEFAULT_VERBOSITY - end + yaml_hash[:update_sources] = @hash.fetch(:update_sources, DEFAULT_UPDATE_SOURCES) + yaml_hash[:verbose] = @hash.fetch(:verbose, DEFAULT_VERBOSITY) yaml_hash[:ssl_verify_mode] = @hash[:ssl_verify_mode] if @hash.key? :ssl_verify_mode diff --git a/lib/rubygems/core_ext/kernel_require.rb b/lib/rubygems/core_ext/kernel_require.rb index 3172ef9be6..d3df9d85f9 100755 --- a/lib/rubygems/core_ext/kernel_require.rb +++ b/lib/rubygems/core_ext/kernel_require.rb @@ -41,8 +41,7 @@ module Kernel path = path.to_path if path.respond_to? :to_path - spec = Gem.find_unresolved_default_spec(path) - if spec + if spec = Gem.find_unresolved_default_spec(path) Gem.remove_unresolved_default_spec(spec) begin Kernel.send(:gem, spec.name) @@ -66,12 +65,10 @@ module Kernel #-- # TODO request access to the C implementation of this to speed up RubyGems - spec = Gem::Specification.find_active_stub_by_path path - - begin + if Gem::Specification.find_active_stub_by_path(path) RUBYGEMS_ACTIVATION_MONITOR.exit return gem_original_require(path) - end if spec + end # Attempt to find +path+ in any unresolved gems... @@ -109,7 +106,7 @@ module Kernel # Ok, now find a gem that has no conflicts, starting # at the highest version. - valid = found_specs.reject { |s| s.has_conflicts? }.first + valid = found_specs.find { |s| !s.has_conflicts? } unless valid then le = Gem::LoadError.new "unable to find a version of '#{names.first}' to activate" @@ -143,4 +140,3 @@ module Kernel private :require end - diff --git a/lib/rubygems/dependency.rb b/lib/rubygems/dependency.rb index bbdab7ccfa..55873c71e8 100644 --- a/lib/rubygems/dependency.rb +++ b/lib/rubygems/dependency.rb @@ -280,6 +280,8 @@ class Gem::Dependency requirement.satisfied_by?(spec.version) && env_req.satisfied_by?(spec.version) }.map(&:to_spec) + Gem::BundlerVersionFinder.filter!(matches) if name == "bundler".freeze + if platform_only matches.reject! { |spec| spec.nil? || !Gem::Platform.match(spec.platform) diff --git a/lib/rubygems/dependency_installer.rb b/lib/rubygems/dependency_installer.rb index 28848f7373..5a87f50956 100644 --- a/lib/rubygems/dependency_installer.rb +++ b/lib/rubygems/dependency_installer.rb @@ -7,6 +7,7 @@ require 'rubygems/spec_fetcher' require 'rubygems/user_interaction' require 'rubygems/source' require 'rubygems/available_set' +require 'rubygems/deprecate' ## # Installs a gem along with all its dependencies from local and remote gems. @@ -46,6 +47,9 @@ class Gem::DependencyInstaller attr_reader :gems_to_install # :nodoc: + extend Gem::Deprecate + deprecate :gems_to_install, :none, 2016, 10 + ## # List of gems installed by #install in alphabetic order diff --git a/lib/rubygems/errors.rb b/lib/rubygems/errors.rb index 5cd5b14c58..6f2847d548 100644 --- a/lib/rubygems/errors.rb +++ b/lib/rubygems/errors.rb @@ -58,6 +58,9 @@ module Gem private def build_message + if name == "bundler" && message = Gem::BundlerVersionFinder.missing_version_message + return message + end names = specs.map(&:full_name) "Could not find '#{name}' (#{requirement}) - did find: [#{names.join ','}]\n" end diff --git a/lib/rubygems/exceptions.rb b/lib/rubygems/exceptions.rb index 9089eae4d5..b7528761fc 100644 --- a/lib/rubygems/exceptions.rb +++ b/lib/rubygems/exceptions.rb @@ -154,6 +154,12 @@ class Gem::ImpossibleDependenciesError < Gem::Exception end class Gem::InstallError < Gem::Exception; end +class Gem::RuntimeRequirementNotMetError < Gem::InstallError + attr_accessor :suggestion + def message + [suggestion, super].compact.join("\n\t") + end +end ## # Potentially raised when a specification is validated. diff --git a/lib/rubygems/ext/builder.rb b/lib/rubygems/ext/builder.rb index 699903ab0e..a1619c97d7 100644 --- a/lib/rubygems/ext/builder.rb +++ b/lib/rubygems/ext/builder.rb @@ -183,7 +183,7 @@ EOF return if @spec.extensions.empty? if @build_args.empty? - say "Building native extensions. This could take a while..." + say "Building native extensions. This could take a while..." else say "Building native extensions with: '#{@build_args.join ' '}'" say "This could take a while..." diff --git a/lib/rubygems/gem_runner.rb b/lib/rubygems/gem_runner.rb index fec9e403da..349d49d66e 100644 --- a/lib/rubygems/gem_runner.rb +++ b/lib/rubygems/gem_runner.rb @@ -8,6 +8,7 @@ require 'rubygems' require 'rubygems/command_manager' require 'rubygems/config_file' +require 'rubygems/deprecate' ## # Load additional plugins from $LOAD_PATH @@ -26,7 +27,10 @@ Gem.load_env_plugins rescue nil class Gem::GemRunner def initialize(options={}) - # TODO: nuke these options + if !options.empty? && !Gem::Deprecate.skip + Kernel.warn "NOTE: passing options to Gem::GemRunner.new is deprecated with no replacement. It will be removed on or after 2016-10-01." + end + @command_manager_class = options[:command_manager] || Gem::CommandManager @config_file_class = options[:config_file] || Gem::ConfigFile end diff --git a/lib/rubygems/install_update_options.rb b/lib/rubygems/install_update_options.rb index 5559c94370..190372739b 100644 --- a/lib/rubygems/install_update_options.rb +++ b/lib/rubygems/install_update_options.rb @@ -6,37 +6,18 @@ #++ require 'rubygems' - -# forward-declare - -module Gem::Security # :nodoc: - class Policy # :nodoc: - end -end +require 'rubygems/security_option' ## # Mixin methods for install and update options for Gem::Commands module Gem::InstallUpdateOptions + include Gem::SecurityOption ## # Add the install/update options to the option parser. def add_install_update_options - # TODO: use @parser.accept - OptionParser.accept Gem::Security::Policy do |value| - require 'rubygems/security' - - raise OptionParser::InvalidArgument, 'OpenSSL not installed' unless - defined?(Gem::Security::HighSecurity) - - value = Gem::Security::Policies[value] - valid = Gem::Security::Policies.keys.sort - message = "#{value} (#{valid.join ', '} are valid)" - raise OptionParser::InvalidArgument, message if value.nil? - value - end - add_option(:"Install/Update", '-i', '--install-dir DIR', 'Gem repository directory to get installed', 'gems') do |value, options| @@ -124,11 +105,7 @@ module Gem::InstallUpdateOptions options[:wrappers] = value end - add_option(:"Install/Update", '-P', '--trust-policy POLICY', - Gem::Security::Policy, - 'Specify gem trust policy') do |value, options| - options[:security_policy] = value - end + add_security_option add_option(:"Install/Update", '--ignore-dependencies', 'Do not install any required dependent gems') do |value, options| @@ -136,8 +113,8 @@ module Gem::InstallUpdateOptions end add_option(:"Install/Update", '--[no-]format-executable', - 'Make installed executable names match ruby.', - 'If ruby is ruby18, foo_exec will be', + 'Make installed executable names match Ruby.', + 'If Ruby is ruby18, foo_exec will be', 'foo_exec18') do |value, options| options[:format_executable] = value end diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb index 6fd3399dd4..0cbca0791b 100644 --- a/lib/rubygems/installer.rb +++ b/lib/rubygems/installer.rb @@ -136,8 +136,9 @@ class Gem::Installer end ## - # Constructs an Installer instance that will install the gem located at - # +gem+. +options+ is a Hash with the following keys: + # Constructs an Installer instance that will install the gem at +package+ which + # can either be a path or an instance of Gem::Package. +options+ is a Hash + # with the following keys: # # :bin_dir:: Where to put a bin wrapper if needed. # :development:: Whether or not development dependencies should be installed. @@ -157,6 +158,7 @@ class Gem::Installer # :wrappers:: Install wrappers if true, symlinks if false. # :build_args:: An Array of arguments to pass to the extension builder # process. If not set, then Gem::Command.build_args is used + # :post_install_message:: Print gem post install message if true def initialize(package, options={}) require 'fileutils' @@ -471,7 +473,7 @@ class Gem::Installer unless File.exist? bin_path then # TODO change this to a more useful warning - warn "#{bin_path} maybe `gem pristine #{spec.name}` will fix it?" + warn "`#{bin_path}` does not exist, maybe `gem pristine #{spec.name}` will fix it?" next end @@ -608,7 +610,9 @@ class Gem::Installer def ensure_required_ruby_version_met # :nodoc: if rrv = spec.required_ruby_version then unless rrv.satisfied_by? Gem.ruby_version then - raise Gem::InstallError, "#{spec.name} requires Ruby version #{rrv}." + ruby_version = Gem.ruby_api_version + raise Gem::RuntimeRequirementNotMetError, + "#{spec.name} requires Ruby version #{rrv}. The current ruby version is #{ruby_version}." end end end @@ -616,8 +620,9 @@ class Gem::Installer def ensure_required_rubygems_version_met # :nodoc: if rrgv = spec.required_rubygems_version then unless rrgv.satisfied_by? Gem.rubygems_version then - raise Gem::InstallError, - "#{spec.name} requires RubyGems version #{rrgv}. " + + rg_version = Gem::VERSION + raise Gem::RuntimeRequirementNotMetError, + "#{spec.name} requires RubyGems version #{rrgv}. The current RubyGems version is #{rg_version}. " + "Try 'gem update --system' to update RubyGems itself." end end @@ -821,7 +826,7 @@ TEXT # # Version and dependency checks are skipped if this install is forced. # - # The dependent check will be skipped this install is ignoring dependencies. + # The dependent check will be skipped if the install is ignoring dependencies. def pre_install_checks verify_gem_home options[:unpack] diff --git a/lib/rubygems/installer_test_case.rb b/lib/rubygems/installer_test_case.rb index eccd5711c2..4cec5da3f4 100644 --- a/lib/rubygems/installer_test_case.rb +++ b/lib/rubygems/installer_test_case.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'rubygems/test_case' require 'rubygems/installer' +require 'rubygems/deprecate' class Gem::Installer @@ -72,7 +73,7 @@ class Gem::InstallerTestCase < Gem::TestCase # a spec named 'a', intended for regular installs # @user_spec:: # a spec named 'b', intended for user installs - + # # @gem:: # the path to a built gem from @spec # @user_spec:: @@ -107,15 +108,17 @@ class Gem::InstallerTestCase < Gem::TestCase end def util_gem_bindir spec = @spec # :nodoc: - # TODO: deprecate spec.bin_dir end def util_gem_dir spec = @spec # :nodoc: - # TODO: deprecate spec.gem_dir end + extend Gem::Deprecate + deprecate :util_gem_bindir, "@spec.bin_dir", 2016, 10 + deprecate :util_gem_dir, "@spec.gem_dir", 2016, 10 + ## # The path where installed executables live diff --git a/lib/rubygems/package/old.rb b/lib/rubygems/package/old.rb index 5e722baa35..88193b986d 100644 --- a/lib/rubygems/package/old.rb +++ b/lib/rubygems/package/old.rb @@ -124,7 +124,7 @@ class Gem::Package::Old < Gem::Package break unless line end - raise Gem::Exception, "Failed to find end of ruby script while reading gem" + raise Gem::Exception, "Failed to find end of Ruby script while reading gem" end ## diff --git a/lib/rubygems/request.rb b/lib/rubygems/request.rb index a0d766d9ae..81699b98fe 100644 --- a/lib/rubygems/request.rb +++ b/lib/rubygems/request.rb @@ -83,7 +83,7 @@ class Gem::Request e.message =~ / -- openssl$/ raise Gem::Exception.new( - 'Unable to require openssl, install OpenSSL and rebuild ruby (preferred) or use non-HTTPS sources') + 'Unable to require openssl, install OpenSSL and rebuild Ruby (preferred) or use non-HTTPS sources') end def self.verify_certificate store_context diff --git a/lib/rubygems/request_set.rb b/lib/rubygems/request_set.rb index 5541e64b88..95a8eed1af 100644 --- a/lib/rubygems/request_set.rb +++ b/lib/rubygems/request_set.rb @@ -163,9 +163,26 @@ class Gem::RequestSet end end - spec = req.spec.install options do |installer| - yield req, installer if block_given? - end + spec = + begin + req.spec.install options do |installer| + yield req, installer if block_given? + end + rescue Gem::RuntimeRequirementNotMetError => e + recent_match = req.spec.set.find_all(req.request).sort_by(&:version).reverse_each.find do |s| + s = s.spec + s.required_ruby_version.satisfied_by?(Gem.ruby_version) && s.required_rubygems_version.satisfied_by?(Gem.rubygems_version) + end + if recent_match + suggestion = "The last version of #{req.request} to support your Ruby & RubyGems was #{recent_match.version}. Try installing it with `gem install #{recent_match.name} -v #{recent_match.version}`" + suggestion += " and then running the current command again" unless @always_install.include?(req.spec.spec) + else + suggestion = "There are no versions of #{req.request} compatible with your Ruby & RubyGems" + suggestion += ". Maybe try installing an older version of the gem you're looking for?" unless @always_install.include?(req.spec.spec) + end + e.suggestion = suggestion + raise + end requests << spec end diff --git a/lib/rubygems/request_set/gem_dependency_api.rb b/lib/rubygems/request_set/gem_dependency_api.rb index 4b2699d7d2..867086cc0e 100644 --- a/lib/rubygems/request_set/gem_dependency_api.rb +++ b/lib/rubygems/request_set/gem_dependency_api.rb @@ -786,7 +786,7 @@ Gem dependencies file #{@path} includes git reference for both ref/branch and ta engine_version = options[:engine_version] raise ArgumentError, - 'you must specify engine_version along with the ruby engine' if + 'You must specify engine_version along with the Ruby engine' if engine and not engine_version return true if @installing @@ -799,7 +799,7 @@ Gem dependencies file #{@path} includes git reference for both ref/branch and ta end if engine and engine != Gem.ruby_engine then - message = "Your ruby engine is #{Gem.ruby_engine}, " + + message = "Your Ruby engine is #{Gem.ruby_engine}, " + "but your #{gem_deps_file} requires #{engine}" raise Gem::RubyVersionMismatch, message @@ -810,7 +810,7 @@ Gem dependencies file #{@path} includes git reference for both ref/branch and ta if engine_version != my_engine_version then message = - "Your ruby engine version is #{Gem.ruby_engine} #{my_engine_version}, " + + "Your Ruby engine version is #{Gem.ruby_engine} #{my_engine_version}, " + "but your #{gem_deps_file} requires #{engine} #{engine_version}" raise Gem::RubyVersionMismatch, message diff --git a/lib/rubygems/requirement.rb b/lib/rubygems/requirement.rb index 32dc769055..2a60c86e69 100644 --- a/lib/rubygems/requirement.rb +++ b/lib/rubygems/requirement.rb @@ -51,7 +51,11 @@ class Gem::Requirement # If the input is "weird", the default version requirement is # returned. - def self.create input + def self.create *inputs + return new inputs if inputs.length > 1 + + input = inputs.shift + case input when Gem::Requirement then input diff --git a/lib/rubygems/resolver.rb b/lib/rubygems/resolver.rb index 8f0db512a2..13ee035e4c 100644 --- a/lib/rubygems/resolver.rb +++ b/lib/rubygems/resolver.rb @@ -230,8 +230,28 @@ class Gem::Resolver exc.errors = @set.errors raise exc end - possibles.sort_by { |s| [s.source, s.version, Gem::Platform.local =~ s.platform ? 1 : 0] }. - map { |s| ActivationRequest.new s, dependency, [] } + + sources = [] + + groups = Hash.new { |hash, key| hash[key] = [] } + + # create groups & sources in the same loop + sources = possibles.map { |spec| + source = spec.source + groups[source] << spec + source + }.uniq.reverse + + activation_requests = [] + + sources.each do |source| + groups[source]. + sort_by { |spec| [spec.version, Gem::Platform.local =~ spec.platform ? 1 : 0] }. + map { |spec| ActivationRequest.new spec, dependency, [] }. + each { |activation_request| activation_requests << activation_request } + end + + activation_requests end def dependencies_for(specification) diff --git a/lib/rubygems/resolver/installer_set.rb b/lib/rubygems/resolver/installer_set.rb index 07fffeb150..f24293c0a0 100644 --- a/lib/rubygems/resolver/installer_set.rb +++ b/lib/rubygems/resolver/installer_set.rb @@ -41,6 +41,7 @@ class Gem::Resolver::InstallerSet < Gem::Resolver::Set @ignore_dependencies = false @ignore_installed = false @local = {} + @local_source = Gem::Source::Local.new @remote_set = Gem::Resolver::BestSet.new @specs = {} end @@ -136,13 +137,11 @@ class Gem::Resolver::InstallerSet < Gem::Resolver::Set res.concat matching_local - local_source = Gem::Source::Local.new - begin - if local_spec = local_source.find_gem(name, dep.requirement) then + if local_spec = @local_source.find_gem(name, dep.requirement) then res << Gem::Resolver::IndexSpecification.new( self, local_spec.name, local_spec.version, - local_source, local_spec.platform) + @local_source, local_spec.platform) end rescue Gem::Package::FormatError # ignore @@ -194,7 +193,7 @@ class Gem::Resolver::InstallerSet < Gem::Resolver::Set # Has a local gem for +dep_name+ been added to this set? def local? dep_name # :nodoc: - spec, = @local[dep_name] + spec, _ = @local[dep_name] spec end @@ -226,4 +225,3 @@ class Gem::Resolver::InstallerSet < Gem::Resolver::Set end end - diff --git a/lib/rubygems/security.rb b/lib/rubygems/security.rb index 6963ca156f..4690dd9230 100644 --- a/lib/rubygems/security.rb +++ b/lib/rubygems/security.rb @@ -340,7 +340,9 @@ module Gem::Security # Digest algorithm used to sign gems DIGEST_ALGORITHM = - if defined?(OpenSSL::Digest::SHA1) then + if defined?(OpenSSL::Digest::SHA256) then + OpenSSL::Digest::SHA256 + elsif defined?(OpenSSL::Digest::SHA1) then OpenSSL::Digest::SHA1 end @@ -363,7 +365,7 @@ module Gem::Security ## # Length of keys created by KEY_ALGORITHM - KEY_LENGTH = 2048 + KEY_LENGTH = 3072 ## # Cipher used to encrypt the key pair used to sign gems. @@ -371,10 +373,15 @@ module Gem::Security KEY_CIPHER = OpenSSL::Cipher.new('AES-256-CBC') if defined?(OpenSSL::Cipher) + ## + # One day in seconds + + ONE_DAY = 86400 + ## # One year in seconds - ONE_YEAR = 86400 * 365 + ONE_YEAR = ONE_DAY * 365 ## # The default set of extensions are: diff --git a/lib/rubygems/security_option.rb b/lib/rubygems/security_option.rb new file mode 100644 index 0000000000..4e3473acb4 --- /dev/null +++ b/lib/rubygems/security_option.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + +require 'rubygems' + +# forward-declare + +module Gem::Security # :nodoc: + class Policy # :nodoc: + end +end + +## +# Mixin methods for security option for Gem::Commands + +module Gem::SecurityOption + def add_security_option + # TODO: use @parser.accept + OptionParser.accept Gem::Security::Policy do |value| + require 'rubygems/security' + + raise OptionParser::InvalidArgument, 'OpenSSL not installed' unless + defined?(Gem::Security::HighSecurity) + + policy = Gem::Security::Policies[value] + unless policy + valid = Gem::Security::Policies.keys.sort + raise OptionParser::InvalidArgument, "#{value} (#{valid.join ', '} are valid)" + end + policy + end + + add_option(:"Install/Update", '-P', '--trust-policy POLICY', + Gem::Security::Policy, + 'Specify gem trust policy') do |value, options| + options[:security_policy] = value + end + end +end diff --git a/lib/rubygems/server.rb b/lib/rubygems/server.rb index df4eb566d3..93b3af36f8 100644 --- a/lib/rubygems/server.rb +++ b/lib/rubygems/server.rb @@ -573,19 +573,11 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; } add_date res case req.request_uri.path - when %r|^/quick/(Marshal.#{Regexp.escape Gem.marshal_version}/)?(.*?)-([0-9.]+[^-]*?)(-.*?)?\.gemspec\.rz$| then - marshal_format, name, version, platform = $1, $2, $3, $4 - specs = Gem::Specification.find_all_by_name name, version + when %r|^/quick/(Marshal.#{Regexp.escape Gem.marshal_version}/)?(.*?)\.gemspec\.rz$| then + marshal_format, full_name = $1, $2 + specs = Gem::Specification.find_all_by_full_name(full_name) - selector = [name, version, platform].map(&:inspect).join ' ' - - platform = if platform then - Gem::Platform.new platform.sub(/^-/, '') - else - Gem::Platform::RUBY - end - - specs = specs.select { |s| s.platform == platform } + selector = full_name.inspect if specs.empty? then res.status = 404 diff --git a/lib/rubygems/source.rb b/lib/rubygems/source.rb index 85f5268fa3..bd84c217a7 100644 --- a/lib/rubygems/source.rb +++ b/lib/rubygems/source.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'uri' -require 'fileutils' +autoload :FileUtils, 'fileutils' +autoload :URI, 'uri' ## # A Source knows how to list and fetch gems from a RubyGems marshal index. @@ -67,7 +67,11 @@ class Gem::Source return -1 if !other.uri - @uri.to_s <=> other.uri.to_s + # Returning 1 here ensures that when sorting a list of sources, the + # original ordering of sources supplied by the user is preserved. + return 1 unless @uri.to_s == other.uri.to_s + + 0 else nil end @@ -232,4 +236,3 @@ require 'rubygems/source/specific_file' require 'rubygems/source/local' require 'rubygems/source/lock' require 'rubygems/source/vendor' - diff --git a/lib/rubygems/source/git.rb b/lib/rubygems/source/git.rb index 0900da0cbc..23f8928a4e 100644 --- a/lib/rubygems/source/git.rb +++ b/lib/rubygems/source/git.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require 'digest' require 'rubygems/util' ## @@ -226,6 +225,8 @@ class Gem::Source::Git < Gem::Source # A hash for the git gem based on the git repository URI. def uri_hash # :nodoc: + require 'digest' # required here to avoid deadlocking in Gem.activate_bin_path (because digest is a gem on 2.5+) + normalized = if @repository =~ %r%^\w+://(\w+@)?% then uri = URI(@repository).normalize.to_s.sub %r%/$%,'' diff --git a/lib/rubygems/source/local.rb b/lib/rubygems/source/local.rb index 3227fb61b0..5ab7a467b5 100644 --- a/lib/rubygems/source/local.rb +++ b/lib/rubygems/source/local.rb @@ -9,6 +9,7 @@ class Gem::Source::Local < Gem::Source @specs = nil @api_uri = nil @uri = nil + @load_specs_names = {} end ## @@ -34,45 +35,47 @@ class Gem::Source::Local < Gem::Source end def load_specs type # :nodoc: - names = [] - - @specs = {} - - Dir["*.gem"].each do |file| - begin - pkg = Gem::Package.new(file) - rescue SystemCallError, Gem::Package::FormatError - # ignore - else - tup = pkg.spec.name_tuple - @specs[tup] = [File.expand_path(file), pkg] - - case type - when :released - unless pkg.spec.version.prerelease? - names << pkg.spec.name_tuple - end - when :prerelease - if pkg.spec.version.prerelease? - names << pkg.spec.name_tuple - end - when :latest - tup = pkg.spec.name_tuple + @load_specs_names[type] ||= begin + names = [] - cur = names.find { |x| x.name == tup.name } - if !cur - names << tup - elsif cur.version < tup.version - names.delete cur - names << tup - end + @specs = {} + + Dir["*.gem"].each do |file| + begin + pkg = Gem::Package.new(file) + rescue SystemCallError, Gem::Package::FormatError + # ignore else - names << pkg.spec.name_tuple + tup = pkg.spec.name_tuple + @specs[tup] = [File.expand_path(file), pkg] + + case type + when :released + unless pkg.spec.version.prerelease? + names << pkg.spec.name_tuple + end + when :prerelease + if pkg.spec.version.prerelease? + names << pkg.spec.name_tuple + end + when :latest + tup = pkg.spec.name_tuple + + cur = names.find { |x| x.name == tup.name } + if !cur + names << tup + elsif cur.version < tup.version + names.delete cur + names << tup + end + else + names << pkg.spec.name_tuple + end end end - end - names + names + end end def find_gem gem_name, version = Gem::Requirement.default, # :nodoc: @@ -88,7 +91,7 @@ class Gem::Source::Local < Gem::Source if version.satisfied_by?(s.version) if prerelease found << s - elsif !s.version.prerelease? + elsif !s.version.prerelease? || version.prerelease? found << s end end diff --git a/lib/rubygems/source/lock.rb b/lib/rubygems/source/lock.rb index 86b16e964c..59717be2c0 100644 --- a/lib/rubygems/source/lock.rb +++ b/lib/rubygems/source/lock.rb @@ -34,6 +34,10 @@ class Gem::Source::Lock < Gem::Source 0 == (self <=> other) end + def hash # :nodoc: + @wrapped.hash ^ 3 + end + ## # Delegates to the wrapped source's fetch_spec method. @@ -46,4 +50,3 @@ class Gem::Source::Lock < Gem::Source end end - diff --git a/lib/rubygems/source_local.rb b/lib/rubygems/source_local.rb index 07cb9e6e8f..9869158e7d 100644 --- a/lib/rubygems/source_local.rb +++ b/lib/rubygems/source_local.rb @@ -2,5 +2,7 @@ require 'rubygems/source' require 'rubygems/source_local' -# TODO warn upon require, this file is deprecated. +unless Gem::Deprecate.skip + Kernel.warn "#{Gem.location_of_caller(3).join(':')}: Warning: Requiring rubygems/source_local is deprecated; please use rubygems/source/local instead." +end diff --git a/lib/rubygems/source_specific_file.rb b/lib/rubygems/source_specific_file.rb index d42e6e7440..b676b1d3a2 100644 --- a/lib/rubygems/source_specific_file.rb +++ b/lib/rubygems/source_specific_file.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true require 'rubygems/source/specific_file' -# TODO warn upon require, this file is deprecated. - +unless Gem::Deprecate.skip + Kernel.warn "#{Gem.location_of_caller(3).join(':')}: Warning: Requiring rubygems/source_specific_file is deprecated; please use rubygems/source/specific_file instead." +end diff --git a/lib/rubygems/spec_fetcher.rb b/lib/rubygems/spec_fetcher.rb index 755d4be1eb..919276e113 100644 --- a/lib/rubygems/spec_fetcher.rb +++ b/lib/rubygems/spec_fetcher.rb @@ -184,10 +184,10 @@ class Gem::SpecFetcher # Suggests gems based on the supplied +gem_name+. Returns an array of # alternative gem names. - def suggest_gems_from_name gem_name + def suggest_gems_from_name(gem_name, type = :latest) gem_name = gem_name.downcase.tr('_-', '') max = gem_name.size / 2 - names = available_specs(:latest).first.values.flatten(1) + names = available_specs(type).first.values.flatten(1) matches = names.map { |n| next unless n.match_platform? @@ -201,7 +201,11 @@ class Gem::SpecFetcher [n.name, distance] }.compact - matches = matches.uniq.sort_by { |name, dist| dist } + matches = if matches.empty? && type != :prerelease + suggest_gems_from_name gem_name, :prerelease + else + matches.uniq.sort_by { |name, dist| dist } + end matches.first(5).map { |name, dist| name } end diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb index ea3e3ecf2b..c811ecec0e 100644 --- a/lib/rubygems/specification.rb +++ b/lib/rubygems/specification.rb @@ -30,6 +30,7 @@ require 'stringio' # s.email = 'rubycoder@example.com' # s.files = ["lib/example.rb"] # s.homepage = 'https://rubygems.org/gems/example' +# s.metadata = { "source_code_uri" => "https://github.com/example/example" } # end # # Starting in RubyGems 2.0, a Specification can hold arbitrary @@ -157,16 +158,20 @@ class Gem::Specification < Gem::BasicSpecification :summary => nil, :test_files => [], :version => nil, - } + }.freeze - Dupable = { } # :nodoc: + INITIALIZE_CODE_FOR_DEFAULTS = { } # :nodoc: @@default_value.each do |k,v| - case v - when Time, Numeric, Symbol, true, false, nil - Dupable[k] = false + INITIALIZE_CODE_FOR_DEFAULTS[k] = case v + when [], {}, true, false, nil, Numeric, Symbol + v.inspect + when String + v.dump + when Numeric + "default_value(:#{k})" else - Dupable[k] = true + "default_value(:#{k}).dup" end end @@ -209,43 +214,51 @@ class Gem::Specification < Gem::BasicSpecification attr_reader :version ## - # Paths in the gem to add to $LOAD_PATH when this gem is - # activated. - #-- - # See also #require_paths - #++ - # If you have an extension you do not need to add "ext" to the - # require path, the extension build process will copy the extension files - # into "lib" for you. + # A short summary of this gem's description. Displayed in `gem list -d`. # - # The default value is "lib" + # The #description should be more detailed than the summary. # # Usage: # - # # If all library files are in the root directory... - # spec.require_paths = ['.'] + # spec.summary = "This is a small summary of my gem" - def require_paths=(val) - @require_paths = Array(val) - end + attr_reader :summary ## - # The version of RubyGems used to create this gem. + # Files included in this gem. You cannot append to this accessor, you must + # assign to it. # - # Do not set this, it is set automatically when the gem is packaged. - - attr_accessor :rubygems_version - - ## - # A short summary of this gem's description. Displayed in `gem list -d`. + # Only add files you can require to this list, not directories, etc. # - # The #description should be more detailed than the summary. + # Directories are automatically stripped from this list when building a gem, + # other non-files cause an error. # # Usage: # - # spec.summary = "This is a small summary of my gem" + # require 'rake' + # spec.files = FileList['lib/**/*.rb', + # 'bin/*', + # '[A-Z]*', + # 'test/**/*'].to_a + # + # # or without Rake... + # spec.files = Dir['lib/**/*.rb'] + Dir['bin/*'] + # spec.files += Dir['[A-Z]*'] + Dir['test/**/*'] + # spec.files.reject! { |fn| fn.include? "CVS" } - attr_reader :summary + def files + # DO NOT CHANGE TO ||= ! This is not a normal accessor. (yes, it sucks) + # DOC: Why isn't it normal? Why does it suck? How can we fix this? + @files = [@files, + @test_files, + add_bindir(@executables), + @extra_rdoc_files, + @extensions, + ].flatten.compact.uniq.sort + end + + ###################################################################### + # :section: Recommended gemspec attributes ## # Singular writer for #authors @@ -269,6 +282,148 @@ class Gem::Specification < Gem::BasicSpecification @authors = Array(value).flatten.grep(String) end + ## + # A long description of this gem + # + # The description should be more detailed than the summary but not + # excessively long. A few paragraphs is a recommended length with no + # examples or formatting. + # + # Usage: + # + # spec.description = <<-EOF + # Rake is a Make-like program implemented in Ruby. Tasks and + # dependencies are specified in standard Ruby syntax. + # EOF + + attr_reader :description + + ## + # A contact email address (or addresses) for this gem + # + # Usage: + # + # spec.email = 'john.jones@example.com' + # spec.email = ['jack@example.com', 'jill@example.com'] + + attr_accessor :email + + ## + # The URL of this gem's home page + # + # Usage: + # + # spec.homepage = 'https://github.com/ruby/rake' + + attr_accessor :homepage + + ## + # The license for this gem. + # + # The license must be no more than 64 characters. + # + # This should just be the name of your license. The full text of the license + # should be inside of the gem (at the top level) when you build it. + # + # The simplest way, is to specify the standard SPDX ID + # https://spdx.org/licenses/ for the license. + # Ideally you should pick one that is OSI (Open Source Initiative) + # http://opensource.org/licenses/alphabetical approved. + # + # The most commonly used OSI approved licenses are MIT and Apache-2.0. + # GitHub also provides a license picker at http://choosealicense.com/. + # + # You should specify a license for your gem so that people know how they are + # permitted to use it, and any restrictions you're placing on it. Not + # specifying a license means all rights are reserved; others have no rights + # to use the code for any purpose. + # + # You can set multiple licenses with #licenses= + # + # Usage: + # spec.license = 'MIT' + + def license=o + self.licenses = [o] + end + + ## + # The license(s) for the library. + # + # Each license must be a short name, no more than 64 characters. + # + # This should just be the name of your license. The full + # text of the license should be inside of the gem when you build it. + # + # See #license= for more discussion + # + # Usage: + # spec.licenses = ['MIT', 'GPL-2.0'] + + def licenses= licenses + @licenses = Array licenses + end + + ## + # The metadata holds extra data for this gem that may be useful to other + # consumers and is settable by gem authors without requiring an update to + # the rubygems software. + # + # Metadata items have the following restrictions: + # + # * The metadata must be a Hash object + # * All keys and values must be Strings + # * Keys can be a maximum of 128 bytes and values can be a maximum of 1024 + # bytes + # * All strings must be UTF-8, no binary data is allowed + # + # You can use metadata to specify links to your gem's homepage, codebase, + # documentation, wiki, mailing list, issue tracker and changelog. + # + # s.metadata = { + # "bug_tracker_uri" => "https://example.com/user/bestgemever/issues", + # "changelog_uri" => "https://example.com/user/bestgemever/CHANGELOG.md", + # "documentation_uri" => "https://www.example.info/gems/bestgemever/0.0.1", + # "homepage_uri" => "https://bestgemever.example.io", + # "mailing_list_uri" => "https://groups.example.com/bestgemever", + # "source_code_uri" => "https://example.com/user/bestgemever", + # "wiki_uri" => "https://example.com/user/bestgemever/wiki" + # } + # + # These links will be used on your gem's page on rubygems.org and must pass + # validation against following regex. + # + # %r{\Ahttps?:\/\/([^\s:@]+:[^\s:@]*@)?[A-Za-z\d\-]+(\.[A-Za-z\d\-]+)+\.?(:\d{1,5})?([\/?]\S*)?\z} + + attr_accessor :metadata + + ###################################################################### + # :section: Optional gemspec attributes + + ## + # The path in the gem for executable scripts. Usually 'bin' + # + # Usage: + # + # spec.bindir = 'bin' + + attr_accessor :bindir + + ## + # The certificate chain used to sign this gem. See Gem::Security for + # details. + + attr_accessor :cert_chain + + ## + # A message that gets displayed after the gem is installed. + # + # Usage: + # + # spec.post_install_message = "Thanks for installing!" + + attr_accessor :post_install_message + ## # The platform this gem runs on. # @@ -327,104 +482,26 @@ class Gem::Specification < Gem::BasicSpecification end ## - # Files included in this gem. You cannot append to this accessor, you must - # assign to it. - # - # Only add files you can require to this list, not directories, etc. + # Paths in the gem to add to $LOAD_PATH when this gem is + # activated. + #-- + # See also #require_paths + #++ + # If you have an extension you do not need to add "ext" to the + # require path, the extension build process will copy the extension files + # into "lib" for you. # - # Directories are automatically stripped from this list when building a gem, - # other non-files cause an error. + # The default value is "lib" # # Usage: # - # require 'rake' - # spec.files = FileList['lib/**/*.rb', - # 'bin/*', - # '[A-Z]*', - # 'test/**/*'].to_a - # - # # or without Rake... - # spec.files = Dir['lib/**/*.rb'] + Dir['bin/*'] - # spec.files += Dir['[A-Z]*'] + Dir['test/**/*'] - # spec.files.reject! { |fn| fn.include? "CVS" } + # # If all library files are in the root directory... + # spec.require_paths = ['.'] - def files - # DO NOT CHANGE TO ||= ! This is not a normal accessor. (yes, it sucks) - # DOC: Why isn't it normal? Why does it suck? How can we fix this? - @files = [@files, - @test_files, - add_bindir(@executables), - @extra_rdoc_files, - @extensions, - ].flatten.compact.uniq.sort + def require_paths=(val) + @require_paths = Array(val) end - ###################################################################### - # :section: Optional gemspec attributes - - ## - # The path in the gem for executable scripts. Usually 'bin' - # - # Usage: - # - # spec.bindir = 'bin' - - attr_accessor :bindir - - ## - # The certificate chain used to sign this gem. See Gem::Security for - # details. - - attr_accessor :cert_chain - - ## - # A long description of this gem - # - # The description should be more detailed than the summary but not - # excessively long. A few paragraphs is a recommended length with no - # examples or formatting. - # - # Usage: - # - # spec.description = <<-EOF - # Rake is a Make-like program implemented in Ruby. Tasks and - # dependencies are specified in standard Ruby syntax. - # EOF - - attr_reader :description - - ## - # :category: Recommended gemspec attributes - # - # A contact email address (or addresses) for this gem - # - # Usage: - # - # spec.email = 'john.jones@example.com' - # spec.email = ['jack@example.com', 'jill@example.com'] - - attr_accessor :email - - ## - # :category: Recommended gemspec attributes - # - # The URL of this gem's home page - # - # Usage: - # - # spec.homepage = 'https://github.com/ruby/rake' - - attr_accessor :homepage - - ## - # A message that gets displayed after the gem is installed. - # - # Usage: - # - # spec.post_install_message = "Thanks for installing!" - - attr_accessor :post_install_message - ## # The version of Ruby required by this gem @@ -436,30 +513,16 @@ class Gem::Specification < Gem::BasicSpecification attr_reader :required_rubygems_version ## - # The key used to sign this gem. See Gem::Security for details. + # The version of RubyGems used to create this gem. + # + # Do not set this, it is set automatically when the gem is packaged. - attr_accessor :signing_key + attr_accessor :rubygems_version ## - # :attr_accessor: metadata - # - # The metadata holds extra data for this gem that may be useful to other - # consumers and is settable by gem authors without requiring an update to - # the rubygems software. - # - # Metadata items have the following restrictions: - # - # * The metadata must be a Hash object - # * All keys and values must be Strings - # * Keys can be a maximum of 128 bytes and values can be a maximum of 1024 - # bytes - # * All strings must be UTF-8, no binary data is allowed - # - # To add metadata for the location of a issue tracker: - # - # s.metadata = { "issue_tracker" => "https://example/issues" } + # The key used to sign this gem. See Gem::Security for details. - attr_accessor :metadata + attr_accessor :signing_key ## # Adds a development dependency named +gem+ with +requirements+ to this @@ -473,7 +536,7 @@ class Gem::Specification < Gem::BasicSpecification # activated when a gem is required. def add_development_dependency(gem, *requirements) - add_dependency_with_type(gem, :development, *requirements) + add_dependency_with_type(gem, :development, requirements) end ## @@ -484,7 +547,7 @@ class Gem::Specification < Gem::BasicSpecification # spec.add_runtime_dependency 'example', '~> 1.1', '>= 1.1.4' def add_runtime_dependency(gem, *requirements) - add_dependency_with_type(gem, :runtime, *requirements) + add_dependency_with_type(gem, :runtime, requirements) end ## @@ -556,56 +619,6 @@ class Gem::Specification < Gem::BasicSpecification @installed_by_version = Gem::Version.new version end - ## - # :category: Recommended gemspec attributes - # - # The license for this gem. - # - # The license must be no more than 64 characters. - # - # This should just be the name of your license. The full text of the license - # should be inside of the gem (at the top level) when you build it. - # - # The simplest way, is to specify the standard SPDX ID - # https://spdx.org/licenses/ for the license. - # Ideally you should pick one that is OSI (Open Source Initiative) - # http://opensource.org/licenses/alphabetical approved. - # - # The most commonly used OSI approved licenses are MIT and Apache-2.0. - # GitHub also provides a license picker at http://choosealicense.com/. - # - # You should specify a license for your gem so that people know how they are - # permitted to use it, and any restrictions you're placing on it. Not - # specifying a license means all rights are reserved; others have no rights - # to use the code for any purpose. - # - # You can set multiple licenses with #licenses= - # - # Usage: - # spec.license = 'MIT' - - def license=o - self.licenses = [o] - end - - ## - # :category: Recommended gemspec attributes - # The license(s) for the library. - # - # Each license must be a short name, no more than 64 characters. - # - # This should just be the name of your license. The full - # text of the license should be inside of the gem when you build it. - # - # See #license= for more discussion - # - # Usage: - # spec.licenses = ['MIT', 'GPL-2.0'] - - def licenses= licenses - @licenses = Array licenses - end - ## # Specifies the rdoc options to be used when generating API documentation. # @@ -885,7 +898,7 @@ class Gem::Specification < Gem::BasicSpecification # properly sorted. def self.add_spec spec - warn "Gem::Specification.add_spec is deprecated and will be removed in Rubygems 3.0" unless Gem::Deprecate.skip + warn "Gem::Specification.add_spec is deprecated and will be removed in RubyGems 3.0" unless Gem::Deprecate.skip # TODO: find all extraneous adds # puts # p :add_spec => [spec.full_name, caller.reject { |s| s =~ /minitest/ }] @@ -910,7 +923,7 @@ class Gem::Specification < Gem::BasicSpecification # Adds multiple specs to the known specifications. def self.add_specs *specs - warn "Gem::Specification.add_specs is deprecated and will be removed in Rubygems 3.0" unless Gem::Deprecate.skip + warn "Gem::Specification.add_specs is deprecated and will be removed in RubyGems 3.0" unless Gem::Deprecate.skip raise "nil spec!" if specs.any?(&:nil?) # TODO: remove once we're happy @@ -1022,6 +1035,13 @@ class Gem::Specification < Gem::BasicSpecification Gem::Dependency.new(name, *requirements).matching_specs end + ## + # Returns every spec that has the given +full_name+ + + def self.find_all_by_full_name(full_name) + stubs.select {|s| s.full_name == full_name }.map(&:to_spec) + end + ## # Find the best specification matching a +name+ and +requirements+. Raises # if the dependency doesn't resolve to a valid specification. @@ -1040,6 +1060,7 @@ class Gem::Specification < Gem::BasicSpecification def self.find_by_path path path = path.dup.freeze spec = @@spec_with_requirable_file[path] ||= (stubs.find { |s| + next unless Gem::BundlerVersionFinder.compatible?(s) s.contains_requirable_file? path } || NOT_FOUND) spec.to_spec @@ -1051,7 +1072,9 @@ class Gem::Specification < Gem::BasicSpecification def self.find_inactive_by_path path stub = stubs.find { |s| - s.contains_requirable_file? path unless s.activated? + next if s.activated? + next unless Gem::BundlerVersionFinder.compatible?(s) + s.contains_requirable_file? path } stub && stub.to_spec end @@ -1250,7 +1273,7 @@ class Gem::Specification < Gem::BasicSpecification # Removes +spec+ from the known specs. def self.remove_spec spec - warn "Gem::Specification.remove_spec is deprecated and will be removed in Rubygems 3.0" unless Gem::Deprecate.skip + warn "Gem::Specification.remove_spec is deprecated and will be removed in RubyGems 3.0" unless Gem::Deprecate.skip _all.delete spec stubs.delete_if { |s| s.full_name == spec.full_name } (@@stubs_by_name[spec.name] || []).delete_if { |s| s.full_name == spec.full_name } @@ -1520,7 +1543,7 @@ class Gem::Specification < Gem::BasicSpecification # +requirements+. Valid types are currently :runtime and # :development. - def add_dependency_with_type(dependency, type, *requirements) + def add_dependency_with_type(dependency, type, requirements) requirements = if requirements.empty? then Gem::Requirement.default else @@ -2026,6 +2049,20 @@ class Gem::Specification < Gem::BasicSpecification yaml_initialize coder.tag, coder.map end + + + eval <<-RB, binding, __FILE__, __LINE__ + 1 + def set_nil_attributes_to_nil + #{@@nil_attributes.map {|key| "@#{key} = nil" }.join "; "} + end + private :set_nil_attributes_to_nil + + def set_not_nil_attributes_to_default_values + #{@@non_nil_attributes.map {|key| "@#{key} = #{INITIALIZE_CODE_FOR_DEFAULTS[key]}" }.join ";"} + end + private :set_not_nil_attributes_to_default_values + RB + ## # Specification constructor. Assigns the default values to the attributes # and yields itself for further initialization. Optionally takes +name+ and @@ -2041,15 +2078,8 @@ class Gem::Specification < Gem::BasicSpecification @original_platform = nil @installed_by_version = nil - @@nil_attributes.each do |key| - instance_variable_set "@#{key}", nil - end - - @@non_nil_attributes.each do |key| - default = default_value(key) - value = Dupable[key] ? default.dup : default - instance_variable_set "@#{key}", value - end + set_nil_attributes_to_nil + set_not_nil_attributes_to_default_values @new_platform = Gem::Platform::RUBY @@ -2746,29 +2776,7 @@ class Gem::Specification < Gem::BasicSpecification 'metadata must be a hash' end - metadata.keys.each do |k| - if !k.kind_of?(String) - raise Gem::InvalidSpecificationException, - 'metadata keys must be a String' - end - - if k.size > 128 - raise Gem::InvalidSpecificationException, - "metadata key too large (#{k.size} > 128)" - end - end - - metadata.values.each do |k| - if !k.kind_of?(String) - raise Gem::InvalidSpecificationException, - 'metadata values must be a String' - end - - if k.size > 1024 - raise Gem::InvalidSpecificationException, - "metadata value too large (#{k.size} > 1024)" - end - end + validate_metadata licenses.each { |license| if license.length > 64 @@ -2822,7 +2830,7 @@ http://spdx.org/licenses or '#{Gem::Licenses::NONSTANDARD}' for a nonstandard li # Warnings - %w[author email homepage summary].each do |attribute| + %w[author homepage summary files].each do |attribute| value = self.send attribute warning "no #{attribute} specified" if value.nil? or value.empty? end @@ -2855,6 +2863,48 @@ http://spdx.org/licenses or '#{Gem::Licenses::NONSTANDARD}' for a nonstandard li end end + def validate_metadata + url_validation_regex = %r{\Ahttps?:\/\/([^\s:@]+:[^\s:@]*@)?[A-Za-z\d\-]+(\.[A-Za-z\d\-]+)+\.?(:\d{1,5})?([\/?]\S*)?\z} + link_keys = %w( + bug_tracker_uri + changelog_uri + documentation_uri + homepage_uri + mailing_list_uri + source_code_uri + wiki_uri + ) + + metadata.each do|key, value| + if !key.kind_of?(String) + raise Gem::InvalidSpecificationException, + "metadata keys must be a String" + end + + if key.size > 128 + raise Gem::InvalidSpecificationException, + "metadata key too large (#{key.size} > 128)" + end + + if !value.kind_of?(String) + raise Gem::InvalidSpecificationException, + "metadata values must be a String" + end + + if value.size > 1024 + raise Gem::InvalidSpecificationException, + "metadata value too large (#{value.size} > 1024)" + end + + if link_keys.include? key + if value !~ url_validation_regex + raise Gem::InvalidSpecificationException, + "metadata['#{key}'] has invalid link: #{value.inspect}" + end + end + end + end + ## # Checks that dependencies use requirements as we recommend. Warnings are # issued when dependencies are open-ended or overly strict for semantic diff --git a/lib/rubygems/stub_specification.rb b/lib/rubygems/stub_specification.rb index b741843124..8337375ab4 100644 --- a/lib/rubygems/stub_specification.rb +++ b/lib/rubygems/stub_specification.rb @@ -188,9 +188,8 @@ class Gem::StubSpecification < Gem::BasicSpecification def to_spec @spec ||= if @data then - Gem.loaded_specs.values.find { |spec| - spec.name == name and spec.version == version - } + loaded = Gem.loaded_specs[name] + loaded if loaded && loaded.version == version end @spec ||= Gem::Specification.load(loaded_from) diff --git a/lib/rubygems/test_case.rb b/lib/rubygems/test_case.rb index fee6875ef0..efc78edea2 100644 --- a/lib/rubygems/test_case.rb +++ b/lib/rubygems/test_case.rb @@ -25,6 +25,7 @@ unless Gem::Dependency.new('rdoc', '>= 3.10').matching_specs.empty? gem 'json' end +require 'bundler' require 'minitest/autorun' require 'rubygems/deprecate' @@ -222,6 +223,7 @@ class Gem::TestCase < MiniTest::Unit::TestCase @orig_gem_vendor = ENV['GEM_VENDOR'] @orig_gem_spec_cache = ENV['GEM_SPEC_CACHE'] @orig_rubygems_gemdeps = ENV['RUBYGEMS_GEMDEPS'] + @orig_bundle_gemfile = ENV['BUNDLE_GEMFILE'] @orig_rubygems_host = ENV['RUBYGEMS_HOST'] ENV.keys.find_all { |k| k.start_with?('GEM_REQUIREMENT_') }.each do |k| ENV.delete k @@ -232,7 +234,12 @@ class Gem::TestCase < MiniTest::Unit::TestCase @current_dir = Dir.pwd @fetcher = nil - @ui = Gem::MockGemUi.new + + Bundler.ui = Bundler::UI::Silent.new + @ui = Gem::MockGemUi.new + # This needs to be a new instance since we call use_ui(@ui) when we want to + # capture output + Gem::DefaultUserInteraction.ui = Gem::MockGemUi.new tmpdir = File.expand_path Dir.tmpdir tmpdir.untaint @@ -323,6 +330,7 @@ class Gem::TestCase < MiniTest::Unit::TestCase Gem.loaded_specs.clear Gem.clear_default_specs Gem::Specification.unresolved_deps.clear + Bundler.reset! Gem.configuration.verbose = true Gem.configuration.update_sources = true @@ -394,6 +402,7 @@ class Gem::TestCase < MiniTest::Unit::TestCase ENV['GEM_VENDOR'] = @orig_gem_vendor ENV['GEM_SPEC_CACHE'] = @orig_gem_spec_cache ENV['RUBYGEMS_GEMDEPS'] = @orig_rubygems_gemdeps + ENV['BUNDLE_GEMFILE'] = @orig_bundle_gemfile ENV['RUBYGEMS_HOST'] = @orig_rubygems_host Gem.ruby = @orig_ruby if @orig_ruby diff --git a/lib/rubygems/user_interaction.rb b/lib/rubygems/user_interaction.rb index 390d0f2aea..cacd782e08 100644 --- a/lib/rubygems/user_interaction.rb +++ b/lib/rubygems/user_interaction.rb @@ -7,11 +7,6 @@ require 'rubygems/util' -begin - require 'io/console' -rescue LoadError -end - ## # Module that defines the default UserInteraction. Any class including this # module will have access to the +ui+ method that returns the default UI. @@ -314,12 +309,21 @@ class Gem::StreamUI password end - if IO.method_defined?(:noecho) then - def _gets_noecho - @ins.noecho {@ins.gets} + def require_io_console + @require_io_console ||= begin + begin + require 'io/console' + rescue LoadError + end + true end - elsif Gem.win_platform? - def _gets_noecho + end + + def _gets_noecho + require_io_console + if IO.method_defined?(:noecho) then + @ins.noecho {@ins.gets} + elsif Gem.win_platform? require "Win32API" password = '' @@ -332,9 +336,7 @@ class Gem::StreamUI end end password - end - else - def _gets_noecho + else system "stty -echo" begin @ins.gets diff --git a/lib/rubygems/util.rb b/lib/rubygems/util.rb index ead2babc1f..2de45c900b 100644 --- a/lib/rubygems/util.rb +++ b/lib/rubygems/util.rb @@ -109,26 +109,15 @@ module Gem::Util ## # Enumerates the parents of +directory+. - def self.traverse_parents directory + def self.traverse_parents directory, &block return enum_for __method__, directory unless block_given? here = File.expand_path directory - start = here - - Dir.chdir start - - begin - loop do - yield here - - Dir.chdir '..' - - return if Dir.pwd == here # toplevel - - here = Dir.pwd - end - ensure - Dir.chdir start + loop do + Dir.chdir here, &block + new_here = File.expand_path('..', here) + return if new_here == here # toplevel + here = new_here end end diff --git a/lib/rubygems/version.rb b/lib/rubygems/version.rb index 17dd7b0795..ff5c1c1e72 100644 --- a/lib/rubygems/version.rb +++ b/lib/rubygems/version.rb @@ -170,7 +170,7 @@ class Gem::Version # True if the +version+ string matches RubyGems' requirements. def self.correct? version - version.to_s =~ ANCHORED_VERSION_PATTERN + !!(version.to_s =~ ANCHORED_VERSION_PATTERN) end ## @@ -241,7 +241,7 @@ class Gem::Version end def hash # :nodoc: - @version.hash + canonical_segments.hash end def init_with coder # :nodoc: @@ -335,7 +335,7 @@ class Gem::Version def <=> other return unless Gem::Version === other - return 0 if @version == other._version + return 0 if @version == other._version || canonical_segments == other.canonical_segments lhsegments = _segments rhsegments = other._segments @@ -360,6 +360,13 @@ class Gem::Version return 0 end + def canonical_segments + @canonical_segments ||= + _split_segments.map! do |segments| + segments.reverse_each.drop_while {|s| s == 0 }.reverse + end.reduce(&:concat) + end + protected def _version @@ -375,4 +382,11 @@ class Gem::Version /^\d+$/ =~ s ? s.to_i : s end.freeze end + + def _split_segments + string_start = _segments.index {|s| s.is_a?(String) } + string_segments = segments + numeric_segments = string_segments.slice!(0, string_start || string_segments.size) + return numeric_segments, string_segments + end end diff --git a/lib/rubygems/version_option.rb b/lib/rubygems/version_option.rb index 3209d95f0f..c4d3306652 100644 --- a/lib/rubygems/version_option.rb +++ b/lib/rubygems/version_option.rb @@ -58,7 +58,12 @@ module Gem::VersionOption add_option('-v', '--version VERSION', Gem::Requirement, "Specify version of gem to #{task}", *wrap) do |value, options| - options[:version] = value + # Allow handling for multiple --version operators + if options[:version] && !options[:version].none? + options[:version].concat([value]) + else + options[:version] = value + end explicit_prerelease_set = !options[:explicit_prerelease].nil? options[:explicit_prerelease] = false unless explicit_prerelease_set diff --git a/test/rubygems/private3072_key.pem b/test/rubygems/private3072_key.pem new file mode 100644 index 0000000000..e99abce69c --- /dev/null +++ b/test/rubygems/private3072_key.pem @@ -0,0 +1,40 @@ +-----BEGIN PRIVATE KEY----- +MIIG/gIBADANBgkqhkiG9w0BAQEFAASCBugwggbkAgEAAoIBgQDIl6Ne1eCreYAe +j+Qkbygv06KqtcarwjNpSPJFq+G/R6xzGfbiuJDGePnlzqyAN6sti8r/tDD8orMS +l3EUSQTvFAVJKxbBDnklHUozdaX+giw+fi7onRJ3sQE2j7GhjahXZB0vmx20wU9v +sazZ0IU/x7k8X7WbVRCYig99UVn28zsq586LN5u/w7qXo50156bfmeDS8tt+TKSK +PkQQYuO+AulWDVdwHycKSzCE7SPWiZ/scFUirN4Stfw0bJZbr8VQ6QBAhHcHgd6B +tcZ1vBDC656zsWgpGWNTCU9mSBWV3UMk8mdwkO/jz8mXYSBfbyLESkhc8slf47zd +dqs1iZZOmVVlz/TXYc9N9d8DTl3OR/YYy04mdrysf4ijB6HV9U7NrRbjcKqKwTjW +xMWsFDdc1d5WCTsXzKjvB582LspGYxv9zgs0KkhSChGTA0qCqGdl0ZVx1Q0suW5O +7XlcEWAp9gSww0RW3E1zkrUARcB4mQgyC89kvE4y9RW4/KzZDcsCAwEAAQKCAYEA +uhQJBkGLgCZe1nsO3UmMUwmVPZ0QpmapgIKd1EnFScb4T3IHl3w1ORgiYa9eUDbU +AZVLg/co9kMLsTRxPqsZ+5pr6Nsi6YY+lVJdce0yRi2FU5eEdl63MfcuM+oKkt4x +CpihhnbzkKk+wlNlEE2iPm9NA5eZhXXcxlRUWCEuPqqV+ZA+BuFYBwVPw7mQbd/t +6kD50VZejQQWIvPt+fFyaOKUiDIqKaWMdr0XTkgZ1bunchMmttr7ywms4wjUVktv +LWMmI8wEMXfxR+xOtigwolSuFn+djWgh02KEc4gSQD3KGKshps5cMrLxxkwIoaC8 +rDxKfdUtixBx6JzrN0Wmq7f/oLpvzxTXoQTu30BvpUOr9evZjE/3ZYXiGCxoSDEL +cvdZHvd+4r0TEkuxNBebq5106bJCBTuq6awwiwAodrsH5DCnqkkrv9tnkVi71NnV +UfnF/C4i5clpJK+Rx4bub9SiqJuyEfQBDlEHdCDwKVASk6B3gXD2mgkJu+EdShkp +AoHBAPx27s3iBN1gRGzD9GU0x2z7uMrgXzXh71kCvzzkfOli5fpIvCyoOR8CIfok +Ph8Kth4c+Bz4bmp6c859yxm5aBdQb3y34LDvCkqEeFCs9XX31QTMjJ1Y3R6Iyun5 +nJzzZ5BtJbLFTHw9/p/pCUmJscUP8nQLwBMLIk4HzmrVXVLKJyaakK4LbsCChBc5 +XwCqzWFKbMRZM3X8PaZp3DCo9iA5/TyzkQMTucLSYaxDDG+YNtYQPB8J5wkFWTD8 +5//WZQKBwQDLZr9CnPR8HcrG46+rDzoZO1nXqaaEQMZhmm25lpL5oSHLrm4elGDp +vH5SRcCPz986InRszMROI3xvYpGtFsAzff3UVY0M8uDQcEKucHtd4wmFHefzYmKo +cvR9PuGmotk6yYpb+cMITT6sCzC4OHPz3DQAclmswRZchHYd2Zkv7tjgE0dXS9nw +XKaH5vQC4wjWBc857scHz0WYJLlj8s8UM9Fludz7kuagXczrsFIONxvrFKNJXeYl +muFCQl8hOG8CgcEA4ZZkTBNpxWX/vjBacRR4HinPNXjHmp4IAMEzoHWKKAD2/m1/ +t2eZotuFAL7hw1sO4FmCWmCiSQKh+CDvGk1RdYOqGwcy/uaZi3xTBcOGkaKh9WfR +PcfpzR7uMaOZDaVxJNxikxs4/MtoefsBEXS4JB3bx1W4i0unm5HeIBgHC7MWyKfU +H7CXhe0Zmqbo/O+iFQ0ro0cRdJuvesOcvN49Dw7B+Tt6mAVIN41FOWev9QdN+HkJ +P7LZfnYI/Hz/0NsBAoHADm2+eZI6wac3YD58kqzk2S9doy/UsSMLL5dN21F0IaMt +i45XH3I1Ib+OUnXCQDFly3DwQ1uPPV/FDv22CcpIXh6859gdxmJgUkj0Yf12suVN +IpVJg/lhuENXVp8kULbSpBnx565jCG66WGf+z8Kpbw4a3kE+XUPhOzTmUB3EgSL3 +XYXglK+7yRI5egCHJMFIOi51Uc2/bq1kaXOJdy6dQ/idDRNPOsVj+NJOnBWI7Js6 +LsXrA2RW1CoVeqbMqsWfAoHAAabWXkQ74BuTo+P7lbfnFa6qLfTRnkWBkKKpWSAZ +ZBLfGsw2F5ZzrfwXtLriPcQJNA+3q3u1WPiGZkjV4QnFMt+Kgcg8ahW94vDEgfex +OSQyrlioT2m9DLJNXXHxo3+0ePNQkmoMQbNRUwdYjBuK7dqNDmFQ8Oo8SxtlV2sL +ntLc47NvFaxDlOvnj9ftQv6ZhdzXQmKGiuZWxtrrjFgHJm8KhMS8IF9xHM3d0uYb +sbykscVdmz3lOmUZrxCeIJvk +-----END PRIVATE KEY----- diff --git a/test/rubygems/public3072_cert.pem b/test/rubygems/public3072_cert.pem new file mode 100644 index 0000000000..8f769aa8b9 --- /dev/null +++ b/test/rubygems/public3072_cert.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIENDCCApygAwIBAgIBATANBgkqhkiG9w0BAQsFADAqMQ8wDQYDVQQDDAZub2Jv +ZHkxFzAVBgoJkiaJk/IsZAEZFgdleGFtcGxlMCAXDTE2MDEwMTAwMDAwMFoYDzk5 +OTkxMjMxMjM1OTU5WjAqMQ8wDQYDVQQDDAZub2JvZHkxFzAVBgoJkiaJk/IsZAEZ +FgdleGFtcGxlMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyJejXtXg +q3mAHo/kJG8oL9OiqrXGq8IzaUjyRavhv0escxn24riQxnj55c6sgDerLYvK/7Qw +/KKzEpdxFEkE7xQFSSsWwQ55JR1KM3Wl/oIsPn4u6J0Sd7EBNo+xoY2oV2QdL5sd +tMFPb7Gs2dCFP8e5PF+1m1UQmIoPfVFZ9vM7KufOizebv8O6l6OdNeem35ng0vLb +fkykij5EEGLjvgLpVg1XcB8nCkswhO0j1omf7HBVIqzeErX8NGyWW6/FUOkAQIR3 +B4HegbXGdbwQwuues7FoKRljUwlPZkgVld1DJPJncJDv48/Jl2EgX28ixEpIXPLJ +X+O83XarNYmWTplVZc/012HPTfXfA05dzkf2GMtOJna8rH+Ioweh1fVOza0W43Cq +isE41sTFrBQ3XNXeVgk7F8yo7wefNi7KRmMb/c4LNCpIUgoRkwNKgqhnZdGVcdUN +LLluTu15XBFgKfYEsMNEVtxNc5K1AEXAeJkIMgvPZLxOMvUVuPys2Q3LAgMBAAGj +YzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgIEMB0GA1UdDgQWBBSf +hxDG5kMk4VhQNMcuLhULi3gMpTAfBgNVHSMEGDAWgBSfhxDG5kMk4VhQNMcuLhUL +i3gMpTANBgkqhkiG9w0BAQsFAAOCAYEAuvY1Nc8lkjCWEnVlAB0yfx85+xa/6zoQ +9w4cG/Nk+M2XNXRmp0c6FQgy7Y/aRxIobJnJfo2S1yJIPfzBuxb/oOy4ikYGmrYV +JUJFs4KaMPz8nM13YVI+KtskNEs0Pb8kcb0ZO640f0ptkgFDN/rvezu2uxqTlaD+ +NSy+O+2Xr5T1Qq2eT2ui3mint26sA2g2cZqkqIdeEWHz/wf5ECMANvgCvE4efduI +oSwFbdb32UKKzppGW+usUbCgEH++EVNWN7VG8F7bvsnPDmuW2J2p2jjvg76H5eK2 +OtnI180JV2Qb80d2lKOS24Mq9edhCzh9AUFsTAfaQ1iBUE4P353G67RF88ZNvV1A +n9DIgbMBf97bByUmp+5MWMXWJ9AcqyXQFsQutEQMudor8P9UqwpCVUkxijpfFxvM +HOBVArYRsdhbjNRGpVAVHdzpJ2AQNTQVeSS7YdzHAzGIVksKHL3K5QJuUJCgNa52 +9H5201wSTxSAhlhoPTT06OHmIGiTvXZS +-----END CERTIFICATE----- diff --git a/test/rubygems/test_config.rb b/test/rubygems/test_config.rb index 79f300fd9c..f8aadb4a23 100644 --- a/test/rubygems/test_config.rb +++ b/test/rubygems/test_config.rb @@ -8,7 +8,7 @@ class TestConfig < Gem::TestCase util_make_gems spec = Gem::Specification.find_by_name("a") spec.activate - assert_equal "#{spec.full_gem_path}/data/a", Gem.datadir('a') + assert_equal "#{spec.full_gem_path}/data/a", spec.datadir end def test_good_rake_path_is_escaped diff --git a/test/rubygems/test_gem.rb b/test/rubygems/test_gem.rb index 8b15debb49..2ec7044d7a 100644 --- a/test/rubygems/test_gem.rb +++ b/test/rubygems/test_gem.rb @@ -180,6 +180,35 @@ class TestGem < Gem::TestCase assert_match 'a-2/bin/exec', Gem.bin_path('a', 'exec', '>= 0') end + def test_activate_bin_path_resolves_eagerly + a1 = util_spec 'a', '1' do |s| + s.executables = ['exec'] + s.add_dependency 'b' + end + + b1 = util_spec 'b', '1' do |s| + s.add_dependency 'c', '2' + end + + b2 = util_spec 'b', '2' do |s| + s.add_dependency 'c', '1' + end + + c1 = util_spec 'c', '1' + c2 = util_spec 'c', '2' + + install_specs c1, c2, b1, b2, a1 + + Gem.activate_bin_path("a", "exec", ">= 0") + + # If we didn't eagerly resolve, this would activate c-2 and then the + # finish_resolve would cause a conflict + gem 'c' + Gem.finish_resolve + + assert_equal %w(a-1 b-2 c-1), loaded_spec_names + end + def test_self_bin_path_no_exec_name e = assert_raises ArgumentError do Gem.bin_path 'a' @@ -275,11 +304,13 @@ class TestGem < Gem::TestCase expected = File.join @gemhome, 'gems', foo.full_name, 'data', 'foo' - assert_equal expected, Gem.datadir('foo') + assert_equal expected, Gem::Specification.find_by_name("foo").datadir end def test_self_datadir_nonexistent_package - assert_nil Gem.datadir('xyzzy') + assert_raises(Gem::MissingSpecError) do + Gem::Specification.find_by_name("xyzzy").datadir + end end def test_self_default_exec_format @@ -368,7 +399,7 @@ class TestGem < Gem::TestCase begin Dir.chdir 'detect/a/b' - assert_empty Gem.detect_gemdeps + assert_equal add_bundler_full_name([]), Gem.detect_gemdeps.map(&:full_name) ensure Dir.chdir @tempdir end @@ -1068,7 +1099,7 @@ class TestGem < Gem::TestCase refute Gem.try_activate 'nonexistent' end - expected = "Ignoring ext-1 because its extensions are not built. " + + expected = "Ignoring ext-1 because its extensions are not built. " + "Try: gem pristine ext --version 1\n" assert_equal expected, err @@ -1079,7 +1110,8 @@ class TestGem < Gem::TestCase orig_path = ENV.delete 'GEM_PATH' Gem.use_paths nil, nil assert_equal Gem.default_dir, Gem.paths.home - assert_equal (Gem.default_path + [Gem.paths.home]).uniq, Gem.paths.path + path = (Gem.default_path + [Gem.paths.home]).uniq + assert_equal path, Gem.paths.path ensure ENV['GEM_HOME'] = orig_home ENV['GEM_PATH'] = orig_path @@ -1420,7 +1452,7 @@ class TestGem < Gem::TestCase Gem.detect_gemdeps - assert_equal %w!a-1 b-1 c-1!, loaded_spec_names + assert_equal add_bundler_full_name(%W(a-1 b-1 c-1)), loaded_spec_names end def test_auto_activation_of_detected_gemdeps_file @@ -1443,10 +1475,20 @@ class TestGem < Gem::TestCase ENV['RUBYGEMS_GEMDEPS'] = "-" - assert_equal [a,b,c], Gem.detect_gemdeps.sort_by { |s| s.name } + expected_specs = [a, b, (Gem::USE_BUNDLER_FOR_GEMDEPS || nil) && util_spec("bundler", Bundler::VERSION), c].compact + assert_equal expected_specs, Gem.detect_gemdeps.sort_by { |s| s.name } end LIB_PATH = File.expand_path "../../../lib".dup.untaint, __FILE__.dup.untaint + BUNDLER_LIB_PATH = File.expand_path $LOAD_PATH.find {|lp| File.file?(File.join(lp, "bundler.rb")) }.dup.untaint + BUNDLER_FULL_NAME = "bundler-#{Bundler::VERSION}" + + def add_bundler_full_name(names) + return names unless Gem::USE_BUNDLER_FOR_GEMDEPS + names << BUNDLER_FULL_NAME + names.sort! + names + end def test_looks_for_gemdeps_files_automatically_on_start util_clear_gems @@ -1466,7 +1508,8 @@ class TestGem < Gem::TestCase ENV['RUBYGEMS_GEMDEPS'] = "-" path = File.join @tempdir, "gem.deps.rb" - cmd = [Gem.ruby.dup.untaint, "-I#{LIB_PATH.untaint}", "-rrubygems"] + cmd = [Gem.ruby.dup.untaint, "-I#{LIB_PATH.untaint}", + "-I#{BUNDLER_LIB_PATH.untaint}", "-rrubygems"] if RUBY_VERSION < '1.9' cmd << "-e 'puts Gem.loaded_specs.values.map(&:full_name).sort'" cmd = cmd.join(' ') @@ -1508,7 +1551,8 @@ class TestGem < Gem::TestCase Dir.mkdir "sub1" path = File.join @tempdir, "gem.deps.rb" - cmd = [Gem.ruby.dup.untaint, "-Csub1", "-I#{LIB_PATH.untaint}", "-rrubygems"] + cmd = [Gem.ruby.dup.untaint, "-Csub1", "-I#{LIB_PATH.untaint}", + "-I#{BUNDLER_LIB_PATH.untaint}", "-rrubygems"] if RUBY_VERSION < '1.9' cmd << "-e 'puts Gem.loaded_specs.values.map(&:full_name).sort'" cmd = cmd.join(' ') @@ -1603,7 +1647,7 @@ class TestGem < Gem::TestCase Gem.use_gemdeps gem_deps_file - assert spec.activated? + assert_equal add_bundler_full_name(%W(a-1)), loaded_spec_names refute_nil Gem.gemdeps end @@ -1664,7 +1708,7 @@ class TestGem < Gem::TestCase Gem.use_gemdeps - assert spec.activated? + assert_equal add_bundler_full_name(%W(a-1)), loaded_spec_names ensure ENV['RUBYGEMS_GEMDEPS'] = rubygems_gemdeps end @@ -1706,11 +1750,25 @@ class TestGem < Gem::TestCase io.write 'gem "a"' end - expected = <<-EXPECTED + platform = Bundler::GemHelpers.generic_local_platform + if platform == Gem::Platform::RUBY + platform = '' + else + platform = " #{platform}" + end + expected = if Gem::USE_BUNDLER_FOR_GEMDEPS + <<-EXPECTED +Could not find gem 'a#{platform}' in any of the gem sources listed in your Gemfile. +You may need to `gem install -g` to install missing gems + + EXPECTED + else + <<-EXPECTED Unable to resolve dependency: user requested 'a (>= 0)' You may need to `gem install -g` to install missing gems - EXPECTED + EXPECTED + end assert_output nil, expected do Gem.use_gemdeps @@ -1735,7 +1793,7 @@ You may need to `gem install -g` to install missing gems Gem.use_gemdeps - assert spec.activated? + assert_equal add_bundler_full_name(%W(a-1)), loaded_spec_names ensure ENV['RUBYGEMS_GEMDEPS'] = rubygems_gemdeps end diff --git a/test/rubygems/test_gem_bundler_version_finder.rb b/test/rubygems/test_gem_bundler_version_finder.rb new file mode 100644 index 0000000000..fff52e8186 --- /dev/null +++ b/test/rubygems/test_gem_bundler_version_finder.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true +require 'rubygems/test_case' + +class TestGemBundlerVersionFinder < Gem::TestCase + def setup + @argv = ARGV.dup + @env = ENV.to_hash.clone + ENV.delete("BUNDLER_VERSION") + @dollar_0 = $0 + end + + def teardown + ARGV.replace @argv + ENV.replace @env + $0 = @dollar_0 + end + + def bvf + Gem::BundlerVersionFinder + end + + def test_bundler_version_defaults_to_nil + assert_nil bvf.bundler_version + end + + def test_bundler_version_with_env_var + ENV["BUNDLER_VERSION"] = "1.1.1.1" + assert_equal v("1.1.1.1"), bvf.bundler_version + end + + def test_bundler_version_with_bundle_update_bundler + ARGV.replace %w[update --bundler] + assert_nil bvf.bundler_version + $0 = "/foo/bar/bundle" + assert_nil bvf.bundler_version + ARGV.replace %w[update --bundler=1.1.1.1 gem_name] + assert_equal v("1.1.1.1"), bvf.bundler_version + ARGV.replace %w[update --bundler 1.1.1.1 gem_name] + assert_equal v("1.1.1.1"), bvf.bundler_version + ARGV.replace %w[update --bundler\ 1.1.1.1 gem_name] + assert_equal v("1.1.1.1"), bvf.bundler_version + ARGV.replace %w[update --bundler\ 1.1.1.2 --bundler --bundler 1.1.1.1 gem_name] + assert_equal v("1.1.1.1"), bvf.bundler_version + $0 = "other" + assert_nil bvf.bundler_version + end + + def test_bundler_version_with_lockfile + bvf.stub(:lockfile_contents, [nil, ""]) do + assert_nil bvf.bundler_version + end + bvf.stub(:lockfile_contents, [nil, "\n\nBUNDLED WITH\n 1.1.1.1\n"]) do + assert_equal v("1.1.1.1"), bvf.bundler_version + end + bvf.stub(:lockfile_contents, [nil, "\n\nBUNDLED WITH\n fjdkslfjdkslfjsldk\n"]) do + assert_nil bvf.bundler_version + end + end + + def test_bundler_version_with_reason + assert_nil bvf.bundler_version_with_reason + bvf.stub(:lockfile_contents, [nil, "\n\nBUNDLED WITH\n 1.1.1.1\n"]) do + assert_equal ["1.1.1.1", "your lockfile"], bvf.bundler_version_with_reason + + $0 = "bundle" + ARGV.replace %w[update --bundler] + assert_nil bvf.bundler_version_with_reason + ARGV.replace %w[update --bundler=1.1.1.2] + assert_equal ["1.1.1.2", "`bundle update --bundler`"], bvf.bundler_version_with_reason + + ENV["BUNDLER_VERSION"] = "1.1.1.3" + assert_equal ["1.1.1.3", "`$BUNDLER_VERSION`"], bvf.bundler_version_with_reason + end + end + + def test_compatible + assert bvf.compatible?(util_spec("foo")) + assert bvf.compatible?(util_spec("bundler", 1.1)) + + bvf.stub(:bundler_version, v("1.1.1.1")) do + assert bvf.compatible?(util_spec("foo")) + assert bvf.compatible?(util_spec("bundler", "1.1.1.1")) + assert bvf.compatible?(util_spec("bundler", "1.1.1.a")) + assert bvf.compatible?(util_spec("bundler", "1.999")) + refute bvf.compatible?(util_spec("bundler", "2.999")) + end + + bvf.stub(:bundler_version, v("2.1.1.1")) do + assert bvf.compatible?(util_spec("foo")) + assert bvf.compatible?(util_spec("bundler", "2.1.1.1")) + refute bvf.compatible?(util_spec("bundler", "2.1.1.a")) + refute bvf.compatible?(util_spec("bundler", "1.999")) + refute bvf.compatible?(util_spec("bundler", "2.999")) + end + end + + def test_filter + versions = %w[1 1.0 1.0.1.1 2.a 3 3.0] + specs = versions.map { |v| util_spec("bundler", v) } + + assert_equal %w[1 1.0 1.0.1.1 2.a 3 3.0], util_filter_specs(specs).map(&:version).map(&:to_s) + + bvf.stub(:bundler_version, v("2.1.1.1")) do + assert_empty util_filter_specs(specs).map(&:version).map(&:to_s) + end + bvf.stub(:bundler_version, v("1.1.1.1")) do + assert_equal %w[1 1.0 1.0.1.1], util_filter_specs(specs).map(&:version).map(&:to_s) + end + bvf.stub(:bundler_version, v("1")) do + assert_equal %w[1 1.0 1.0.1.1], util_filter_specs(specs).map(&:version).map(&:to_s) + end + bvf.stub(:bundler_version, v("2.a")) do + assert_equal %w[2.a], util_filter_specs(specs).map(&:version).map(&:to_s) + end + bvf.stub(:bundler_version, v("3")) do + assert_equal %w[3 3.0], util_filter_specs(specs).map(&:version).map(&:to_s) + end + end + + def util_filter_specs(specs) + specs = specs.dup + bvf.filter!(specs) + specs + end +end \ No newline at end of file diff --git a/test/rubygems/test_gem_command.rb b/test/rubygems/test_gem_command.rb index 8100a34c25..913cd72aa0 100644 --- a/test/rubygems/test_gem_command.rb +++ b/test/rubygems/test_gem_command.rb @@ -170,7 +170,7 @@ class TestGemCommand < Gem::TestCase @cmd.add_option('-f', '--file FILE', 'File option') do |value, options| options[:help] = true end - @cmd.add_option('--silent', 'Silence rubygems output') do |value, options| + @cmd.add_option('--silent', 'Silence RubyGems output') do |value, options| options[:silent] = true end assert @cmd.handles?(['-x']) diff --git a/test/rubygems/test_gem_commands_build_command.rb b/test/rubygems/test_gem_commands_build_command.rb index 703512ecba..08854f0bf1 100644 --- a/test/rubygems/test_gem_commands_build_command.rb +++ b/test/rubygems/test_gem_commands_build_command.rb @@ -1,3 +1,4 @@ + # frozen_string_literal: true require 'rubygems/test_case' require 'rubygems/commands/build_command' @@ -117,5 +118,30 @@ class TestGemCommandsBuildCommand < Gem::TestCase util_test_build_gem @gem, gemspec_file, false end -end + CERT_FILE = cert_path 'public3072' + SIGNING_KEY = key_path 'private3072' + + def test_build_signed_gem + trust_dir = Gem::Security.trust_dir + + spec = util_spec 'some_gem' do |s| + s.signing_key = SIGNING_KEY + s.cert_chain = [CERT_FILE] + end + + gemspec_file = File.join(@tempdir, spec.spec_name) + + File.open gemspec_file, 'w' do |gs| + gs.write spec.to_ruby + end + + util_test_build_gem spec, gemspec_file + + trust_dir.trust_cert OpenSSL::X509::Certificate.new(File.read(CERT_FILE)) + gem = Gem::Package.new(File.join(@tempdir, spec.file_name), + Gem::Security::HighSecurity) + assert gem.verify + end + +end diff --git a/test/rubygems/test_gem_commands_cert_command.rb b/test/rubygems/test_gem_commands_cert_command.rb index a9a78336aa..2d38a57bc4 100644 --- a/test/rubygems/test_gem_commands_cert_command.rb +++ b/test/rubygems/test_gem_commands_cert_command.rb @@ -130,6 +130,70 @@ Added '/CN=alternate/DC=example' assert_path_exists File.join(@tempdir, 'gem-public_cert.pem') end + def test_execute_build_bad_email_address + passphrase = 'Foo bar' + email = "nobody@" + + @cmd.handle_options %W[--build #{email}] + + @build_ui = Gem::MockGemUi.new "#{passphrase}\n#{passphrase}" + + use_ui @build_ui do + + e = assert_raises Gem::CommandLineError do + @cmd.execute + end + + assert_equal "Invalid email address #{email}", + e.message + + refute_path_exists File.join(@tempdir, 'gem-private_key.pem') + refute_path_exists File.join(@tempdir, 'gem-public_cert.pem') + end + end + + def test_execute_build_expiration_days + passphrase = 'Foo bar' + + @cmd.handle_options %W[ + --build nobody@example.com + --days 26 + ] + + @build_ui = Gem::MockGemUi.new "#{passphrase}\n#{passphrase}" + + use_ui @build_ui do + @cmd.execute + end + + output = @build_ui.output.squeeze("\n").split "\n" + + assert_equal "Passphrase for your Private Key: ", + output.shift + assert_equal "Please repeat the passphrase for your Private Key: ", + output.shift + assert_equal "Certificate: #{File.join @tempdir, 'gem-public_cert.pem'}", + output.shift + assert_equal "Private Key: #{File.join @tempdir, 'gem-private_key.pem'}", + output.shift + + assert_equal "Don't forget to move the key file to somewhere private!", + output.shift + + assert_empty output + assert_empty @build_ui.error + + assert_path_exists File.join(@tempdir, 'gem-private_key.pem') + assert_path_exists File.join(@tempdir, 'gem-public_cert.pem') + + pem = File.read("#{@tempdir}/gem-public_cert.pem") + cert = OpenSSL::X509::Certificate.new(pem) + + test = (cert.not_after - cert.not_before).to_i / (24 * 60 * 60) + assert_equal(test, 26) + + end + def test_execute_build_bad_passphrase_confirmation passphrase = 'Foo bar' passphrase_confirmation = 'Fu bar' diff --git a/test/rubygems/test_gem_commands_install_command.rb b/test/rubygems/test_gem_commands_install_command.rb index 13b9a7bbad..dd86a85038 100644 --- a/test/rubygems/test_gem_commands_install_command.rb +++ b/test/rubygems/test_gem_commands_install_command.rb @@ -96,6 +96,39 @@ class TestGemCommandsInstallCommand < Gem::TestCase assert_match "1 gem installed", @ui.output end + def test_execute_local_transitive_prerelease + specs = spec_fetcher do |fetcher| + fetcher.download 'a', 2, 'b' => "2.a", 'c' => '3' + fetcher.download 'b', '2.a' + fetcher.download 'c', '3' + end + + @cmd.options[:domain] = :local + + FileUtils.mv specs['a-2'].cache_file, @tempdir + FileUtils.mv specs['b-2.a'].cache_file, @tempdir + FileUtils.mv specs['c-3'].cache_file, @tempdir + + @cmd.options[:args] = %w[a] + + use_ui @ui do + orig_dir = Dir.pwd + begin + Dir.chdir @tempdir + FileUtils.rm_r [@gemhome, "gems"] + assert_raises Gem::MockGemUi::SystemExitException, @ui.error do + @cmd.execute + end + ensure + Dir.chdir orig_dir + end + end + + assert_equal %w[a-2 b-2.a c-3], @cmd.installed_specs.map { |spec| spec.full_name }.sort + + assert_match "3 gems installed", @ui.output + end + def test_execute_no_user_install skip 'skipped on MS Windows (chmod has no effect)' if win_platform? @@ -588,7 +621,7 @@ ERROR: Possible alternatives: non_existent_with_hint done_installing = true end - spec = quick_spec 'a', 2 + spec = util_spec 'a', 2 util_build_gem spec @@ -616,7 +649,7 @@ ERROR: Possible alternatives: non_existent_with_hint end def test_install_gem_ignore_dependencies_specific_file - spec = quick_spec 'a', 2 + spec = util_spec 'a', 2 util_build_gem spec diff --git a/test/rubygems/test_gem_commands_pristine_command.rb b/test/rubygems/test_gem_commands_pristine_command.rb index dfec78b848..d2f8b10bb8 100644 --- a/test/rubygems/test_gem_commands_pristine_command.rb +++ b/test/rubygems/test_gem_commands_pristine_command.rb @@ -145,7 +145,7 @@ class TestGemCommandsPristineCommand < Gem::TestCase out = @ui.output.split "\n" assert_equal 'Restoring gems to pristine condition...', out.shift - assert_equal 'Building native extensions. This could take a while...', + assert_equal 'Building native extensions. This could take a while...', out.shift assert_equal "Restored #{a.full_name}", out.shift assert_empty out, out.inspect diff --git a/test/rubygems/test_gem_commands_query_command.rb b/test/rubygems/test_gem_commands_query_command.rb index 469223c6c0..5471ecadcc 100644 --- a/test/rubygems/test_gem_commands_query_command.rb +++ b/test/rubygems/test_gem_commands_query_command.rb @@ -637,6 +637,25 @@ EOF assert_equal expected, @ui.output end + def test_execute_show_default_gems_with_platform + a1 = new_default_spec 'a', 1 + a1.platform = 'java' + install_default_specs a1 + + use_ui @ui do + @cmd.execute + end + + expected = <<-EOF + +*** LOCAL GEMS *** + +a (default: 1 java) +EOF + + assert_equal expected, @ui.output + end + def test_execute_default_details spec_fetcher do |fetcher| fetcher.spec 'a', 2 diff --git a/test/rubygems/test_gem_commands_setup_command.rb b/test/rubygems/test_gem_commands_setup_command.rb index ae3d0500dc..22c831fa5e 100644 --- a/test/rubygems/test_gem_commands_setup_command.rb +++ b/test/rubygems/test_gem_commands_setup_command.rb @@ -20,6 +20,13 @@ class TestGemCommandsSetupCommand < Gem::TestCase open 'lib/rubygems.rb', 'w' do |io| io.puts '# rubygems.rb' end open 'lib/rubygems/test_case.rb', 'w' do |io| io.puts '# test_case.rb' end open 'lib/rubygems/ssl_certs/rubygems.org/foo.pem', 'w' do |io| io.puts 'PEM' end + + FileUtils.mkdir_p 'bundler/exe' + FileUtils.mkdir_p 'bundler/lib/bundler' + + open 'bundler/exe/bundle', 'w' do |io| io.puts '# bundle' end + open 'bundler/lib/bundler.rb', 'w' do |io| io.puts '# bundler.rb' end + open 'bundler/lib/bundler/b.rb', 'w' do |io| io.puts '# b.rb' end end def test_pem_files_in @@ -40,12 +47,18 @@ class TestGemCommandsSetupCommand < Gem::TestCase assert_path_exists File.join(dir, 'rubygems.rb') assert_path_exists File.join(dir, 'rubygems/ssl_certs/rubygems.org/foo.pem') + + if Gem::USE_BUNDLER_FOR_GEMDEPS + assert_path_exists File.join(dir, 'bundler.rb') + assert_path_exists File.join(dir, 'bundler/b.rb') + end end end def test_remove_old_lib_files lib = File.join @install_dir, 'lib' lib_rubygems = File.join lib, 'rubygems' + lib_bundler = File.join lib, 'bundler' lib_rubygems_defaults = File.join lib_rubygems, 'defaults' securerandom_rb = File.join lib, 'securerandom.rb' @@ -55,13 +68,16 @@ class TestGemCommandsSetupCommand < Gem::TestCase old_builder_rb = File.join lib_rubygems, 'builder.rb' old_format_rb = File.join lib_rubygems, 'format.rb' + old_bundler_c_rb = File.join lib_bundler, 'c.rb' FileUtils.mkdir_p lib_rubygems_defaults + FileUtils.mkdir_p lib_bundler open securerandom_rb, 'w' do |io| io.puts '# securerandom.rb' end open old_builder_rb, 'w' do |io| io.puts '# builder.rb' end open old_format_rb, 'w' do |io| io.puts '# format.rb' end + open old_bundler_c_rb, 'w' do |io| io.puts '# c.rb' end open engine_defaults_rb, 'w' do |io| io.puts '# jruby.rb' end open os_defaults_rb, 'w' do |io| io.puts '# operating_system.rb' end @@ -70,6 +86,7 @@ class TestGemCommandsSetupCommand < Gem::TestCase refute_path_exists old_builder_rb refute_path_exists old_format_rb + refute_path_exists old_bundler_c_rb if Gem::USE_BUNDLER_FOR_GEMDEPS assert_path_exists securerandom_rb assert_path_exists engine_defaults_rb diff --git a/test/rubygems/test_gem_commands_signin_command.rb b/test/rubygems/test_gem_commands_signin_command.rb new file mode 100644 index 0000000000..ec40b71ada --- /dev/null +++ b/test/rubygems/test_gem_commands_signin_command.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true +require 'rubygems/test_case' +require 'rubygems/commands/signin_command' +require 'rubygems/installer' + +class TestGemCommandsSigninCommand < Gem::TestCase + + def setup + super + + Gem.configuration.rubygems_api_key = nil + Gem.configuration.api_keys.clear + + @cmd = Gem::Commands::SigninCommand.new + end + + def teardown + credentials_path = Gem.configuration.credentials_path + File.delete(credentials_path) if File.exist?(credentials_path) + super + end + + def test_execute_when_not_already_signed_in + sign_in_ui = util_capture() { @cmd.execute } + assert_match %r{Signed in.}, sign_in_ui.output + end + + def test_execute_when_already_signed_in_with_same_host + host = 'http://some-gemcutter-compatible-host.org' + sign_in_ui = util_capture(nil, host) { @cmd.execute } + old_credentials = YAML.load_file Gem.configuration.credentials_path + + sign_in_ui = util_capture(nil, host) { @cmd.execute } + new_credentials = YAML.load_file Gem.configuration.credentials_path + + assert_equal old_credentials[host], new_credentials[host] + end + + def test_execute_when_already_signed_in_with_different_host + api_key = 'a5fdbb6ba150cbb83aad2bb2fede64cf04045xxxx' + sign_in_ui = util_capture(nil, nil, api_key) { @cmd.execute } + host = 'http://some-gemcutter-compatible-host.org' + sign_in_ui = util_capture(nil, host, api_key) { @cmd.execute } + credentials = YAML.load_file Gem.configuration.credentials_path + + assert_equal credentials[:rubygems_api_key], api_key + + assert_equal credentials[host], nil + end + + def test_execute_with_host_supplied + host = 'http://some-gemcutter-compatible-host.org' + + sign_in_ui = util_capture(nil, host) { @cmd.execute } + assert_match %r{Enter your #{host} credentials.}, sign_in_ui.output + assert_match %r{Signed in.}, sign_in_ui.output + + api_key = 'a5fdbb6ba150cbb83aad2bb2fede64cf040453903' + credentials = YAML.load_file Gem.configuration.credentials_path + assert_equal api_key, credentials[host] + end + + def test_execute_with_valid_creds_set_for_default_host + util_capture {@cmd.execute} + + api_key = 'a5fdbb6ba150cbb83aad2bb2fede64cf040453903' + credentials = YAML.load_file Gem.configuration.credentials_path + + assert_equal api_key, credentials[:rubygems_api_key] + end + + # Utility method to capture IO/UI within the block passed + + def util_capture ui_stub = nil, host = nil, api_key = nil + api_key ||= 'a5fdbb6ba150cbb83aad2bb2fede64cf040453903' + response = [api_key, 200, 'OK'] + email = 'you@example.com' + password = 'secret' + fetcher = Gem::FakeFetcher.new + + # Set the expected response for the Web-API supplied + ENV['RUBYGEMS_HOST'] = host || Gem::DEFAULT_HOST + data_key = "#{ENV['RUBYGEMS_HOST']}/api/v1/api_key" + fetcher.data[data_key] = response + Gem::RemoteFetcher.fetcher = fetcher + + sign_in_ui = ui_stub || Gem::MockGemUi.new("#{email}\n#{password}\n") + + use_ui sign_in_ui do + yield + end + + sign_in_ui + end +end diff --git a/test/rubygems/test_gem_commands_signout_command.rb b/test/rubygems/test_gem_commands_signout_command.rb new file mode 100644 index 0000000000..814b55cf4f --- /dev/null +++ b/test/rubygems/test_gem_commands_signout_command.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'rubygems/test_case' +require 'rubygems/commands/signout_command' +require 'rubygems/installer' + +class TestGemCommandsSignoutCommand < Gem::TestCase + + def setup + super + @cmd = Gem::Commands::SignoutCommand.new + end + + def teardown + super + File.delete Gem.configuration.credentials_path if File.exist?(Gem.configuration.credentials_path) + end + + def test_execute_when_user_is_signed_in + FileUtils.mkdir_p File.dirname(Gem.configuration.credentials_path) + FileUtils::touch Gem.configuration.credentials_path + + @sign_out_ui = Gem::MockGemUi.new + use_ui(@sign_out_ui) { @cmd.execute } + + assert_match %r{You have successfully signed out}, @sign_out_ui.output + assert_equal false, File.exist?(Gem.configuration.credentials_path) + end + + def test_execute_when_not_signed_in # i.e. no credential file created + @sign_out_ui = Gem::MockGemUi.new + use_ui(@sign_out_ui) { @cmd.execute } + + assert_match %r{You are not currently signed in}, @sign_out_ui.error + end + +end diff --git a/test/rubygems/test_gem_commands_uninstall_command.rb b/test/rubygems/test_gem_commands_uninstall_command.rb index e64bc77123..cca135afde 100644 --- a/test/rubygems/test_gem_commands_uninstall_command.rb +++ b/test/rubygems/test_gem_commands_uninstall_command.rb @@ -279,5 +279,17 @@ WARNING: Use your OS package manager to uninstall vendor gems RbConfig::CONFIG['vendordir'] = orig_vendordir end + def test_execute_with_gem_not_installed + @cmd.options[:args] = ['d'] + + use_ui ui do + @cmd.execute + end + + output = ui.output.split "\n" + + assert_equal output.first, "Gem 'd' is not installed" + end + end diff --git a/test/rubygems/test_gem_commands_update_command.rb b/test/rubygems/test_gem_commands_update_command.rb index 4a9f496cbe..245d05727d 100644 --- a/test/rubygems/test_gem_commands_update_command.rb +++ b/test/rubygems/test_gem_commands_update_command.rb @@ -107,7 +107,7 @@ class TestGemCommandsUpdateCommand < Gem::TestCase end out = @ui.output.split "\n" - assert_equal "Latest version currently installed. Aborting.", out.shift + assert_equal "Latest version already installed. Done.", out.shift assert_empty out end diff --git a/test/rubygems/test_gem_commands_which_command.rb b/test/rubygems/test_gem_commands_which_command.rb index c55bb370cd..0c2b177273 100644 --- a/test/rubygems/test_gem_commands_which_command.rb +++ b/test/rubygems/test_gem_commands_which_command.rb @@ -33,7 +33,7 @@ class TestGemCommandsWhichCommand < Gem::TestCase end assert_equal '', @ui.output - assert_match %r%Can.t find ruby library file or shared library directory\n%, + assert_match %r%Can.t find Ruby library file or shared library directory\n%, @ui.error end @@ -51,7 +51,7 @@ class TestGemCommandsWhichCommand < Gem::TestCase end assert_equal "#{@foo_bar.full_gem_path}/lib/foo_bar.rb\n", @ui.output - assert_match %r%Can.t find ruby library file or shared library missinglib\n%, + assert_match %r%Can.t find Ruby library file or shared library missinglib\n%, @ui.error end @@ -65,7 +65,7 @@ class TestGemCommandsWhichCommand < Gem::TestCase end assert_equal '', @ui.output - assert_match %r%Can.t find ruby library file or shared library missinglib\n%, + assert_match %r%Can.t find Ruby library file or shared library missinglib\n%, @ui.error end diff --git a/test/rubygems/test_gem_dependency.rb b/test/rubygems/test_gem_dependency.rb index 0d501f567f..d7eec3c090 100644 --- a/test/rubygems/test_gem_dependency.rb +++ b/test/rubygems/test_gem_dependency.rb @@ -338,6 +338,34 @@ class TestGemDependency < Gem::TestCase assert_match "Could not find 'a' (= 2.0) - did find: [a-1.0]", e.message end + def test_to_specs_respects_bundler_version + b = util_spec 'bundler', '2.0.0.pre.1' + b_1 = util_spec 'bundler', '1' + install_specs b, b_1 + + b_file = File.join b.gem_dir, 'lib', 'bundler', 'setup.rb' + + write_file b_file do |io| + io.puts '# setup.rb' + end + + dep = Gem::Dependency.new "bundler", ">= 0.a" + + assert_equal [b, b_1], dep.to_specs + + Gem::BundlerVersionFinder.stub(:bundler_version_with_reason, ["3.5", "reason"]) do + e = assert_raises Gem::MissingSpecVersionError do + dep.to_specs + end + + assert_match "Could not find 'bundler' (3.5) required by reason.\nTo update to the lastest version installed on your system, run `bundle update --bundler`.\nTo install the missing version, run `gem install bundler:3.5`\n", e.message + end + + Gem::BundlerVersionFinder.stub(:bundler_version_with_reason, ["2.0.0.pre.1", "reason"]) do + assert_equal [b], dep.to_specs + end + end + def test_to_specs_indicates_total_gem_set_size a = util_spec 'a', '1.0' install_specs a diff --git a/test/rubygems/test_gem_ext_builder.rb b/test/rubygems/test_gem_ext_builder.rb index 370ebf8d14..d142ef28da 100644 --- a/test/rubygems/test_gem_ext_builder.rb +++ b/test/rubygems/test_gem_ext_builder.rb @@ -239,7 +239,7 @@ install: assert_match(/\AERROR: Failed to build gem native extension.$/, e.message) - assert_equal "Building native extensions. This could take a while...\n", + assert_equal "Building native extensions. This could take a while...\n", @ui.output assert_equal '', @ui.error @@ -272,7 +272,7 @@ install: assert_match(/^\s*No builder for extension ''$/, e.message) - assert_equal "Building native extensions. This could take a while...\n", + assert_equal "Building native extensions. This could take a while...\n", @ui.output assert_equal '', @ui.error diff --git a/test/rubygems/test_gem_install_update_options.rb b/test/rubygems/test_gem_install_update_options.rb index e25259f4d1..e2d546307d 100644 --- a/test/rubygems/test_gem_install_update_options.rb +++ b/test/rubygems/test_gem_install_update_options.rb @@ -121,9 +121,10 @@ class TestGemInstallUpdateOptions < Gem::InstallerTestCase def test_security_policy_unknown @cmd.add_install_update_options - assert_raises OptionParser::InvalidArgument do + e = assert_raises OptionParser::InvalidArgument do @cmd.handle_options %w[-P UnknownSecurity] end + assert_includes e.message, "UnknownSecurity" end def test_user_install_enabled diff --git a/test/rubygems/test_gem_installer.rb b/test/rubygems/test_gem_installer.rb index dd049214fb..39095c7dee 100644 --- a/test/rubygems/test_gem_installer.rb +++ b/test/rubygems/test_gem_installer.rb @@ -87,7 +87,7 @@ end end util_make_exec - @installer.gem_dir = util_gem_dir @spec + @installer.gem_dir = @spec.gem_dir @installer.wrappers = true @installer.generate_bin @@ -304,7 +304,7 @@ gem 'other', version def test_extract_files @installer.extract_files - assert_path_exists File.join util_gem_dir, 'bin/executable' + assert_path_exists File.join @spec.gem_dir, 'bin/executable' end def test_generate_bin_bindir @@ -314,12 +314,12 @@ gem 'other', version @spec.bindir = '.' exec_file = @installer.formatted_program_filename 'executable' - exec_path = File.join util_gem_dir(@spec), exec_file + exec_path = File.join @spec.gem_dir, exec_file File.open exec_path, 'w' do |f| f.puts '#!/usr/bin/ruby' end - @installer.gem_dir = util_gem_dir + @installer.gem_dir = @spec.gem_dir @installer.generate_bin @@ -355,7 +355,7 @@ gem 'other', version def test_generate_bin_script @installer.wrappers = true util_make_exec - @installer.gem_dir = util_gem_dir + @installer.gem_dir = @spec.gem_dir @installer.generate_bin assert File.directory? util_inst_bindir @@ -371,7 +371,7 @@ gem 'other', version @installer.format_executable = true @installer.wrappers = true util_make_exec - @installer.gem_dir = util_gem_dir + @installer.gem_dir = @spec.gem_dir Gem::Installer.exec_format = 'foo-%s-bar' @installer.generate_bin @@ -385,7 +385,7 @@ gem 'other', version def test_generate_bin_script_format_disabled @installer.wrappers = true util_make_exec - @installer.gem_dir = util_gem_dir + @installer.gem_dir = @spec.gem_dir Gem::Installer.exec_format = 'foo-%s-bar' @installer.generate_bin @@ -474,10 +474,10 @@ gem 'other', version def test_generate_bin_script_wrappers @installer.wrappers = true util_make_exec - @installer.gem_dir = util_gem_dir + @installer.gem_dir = @spec.gem_dir installed_exec = File.join(util_inst_bindir, 'executable') - real_exec = File.join util_gem_dir, 'bin', 'executable' + real_exec = File.join @spec.gem_dir, 'bin', 'executable' # fake --no-wrappers for previous install unless Gem.win_platform? then @@ -501,13 +501,13 @@ gem 'other', version @installer.wrappers = false util_make_exec - @installer.gem_dir = util_gem_dir + @installer.gem_dir = @spec.gem_dir @installer.generate_bin assert_equal true, File.directory?(util_inst_bindir) installed_exec = File.join util_inst_bindir, 'executable' assert_equal true, File.symlink?(installed_exec) - assert_equal(File.join(util_gem_dir, 'bin', 'executable'), + assert_equal(File.join(@spec.gem_dir, 'bin', 'executable'), File.readlink(installed_exec)) end @@ -523,7 +523,7 @@ gem 'other', version def test_generate_bin_symlink_no_perms @installer.wrappers = false util_make_exec - @installer.gem_dir = util_gem_dir + @installer.gem_dir = @spec.gem_dir Dir.mkdir util_inst_bindir @@ -545,11 +545,11 @@ gem 'other', version @installer.wrappers = false util_make_exec - @installer.gem_dir = util_gem_dir + @installer.gem_dir = @spec.gem_dir @installer.generate_bin installed_exec = File.join(util_inst_bindir, 'executable') - assert_equal(File.join(util_gem_dir, 'bin', 'executable'), + assert_equal(File.join(@spec.gem_dir, 'bin', 'executable'), File.readlink(installed_exec)) @spec = Gem::Specification.new do |s| @@ -562,7 +562,7 @@ gem 'other', version end util_make_exec - @installer.gem_dir = util_gem_dir @spec + @installer.gem_dir = @spec.gem_dir @installer.generate_bin installed_exec = File.join(util_inst_bindir, 'executable') assert_equal(@spec.bin_file('executable'), @@ -575,11 +575,11 @@ gem 'other', version @installer.wrappers = false util_make_exec - @installer.gem_dir = util_gem_dir + @installer.gem_dir = @spec.gem_dir @installer.generate_bin installed_exec = File.join(util_inst_bindir, 'executable') - assert_equal(File.join(util_gem_dir, 'bin', 'executable'), + assert_equal(File.join(@spec.gem_dir, 'bin', 'executable'), File.readlink(installed_exec)) spec = Gem::Specification.new do |s| @@ -595,12 +595,12 @@ gem 'other', version one = @spec.dup one.version = 1 @installer = Gem::Installer.for_spec spec - @installer.gem_dir = util_gem_dir one + @installer.gem_dir = one.gem_dir @installer.generate_bin installed_exec = File.join util_inst_bindir, 'executable' - expected = File.join util_gem_dir, 'bin', 'executable' + expected = File.join @spec.gem_dir, 'bin', 'executable' assert_equal(expected, File.readlink(installed_exec), "Ensure symlink not moved") @@ -611,7 +611,7 @@ gem 'other', version @installer.wrappers = true util_make_exec - @installer.gem_dir = util_gem_dir + @installer.gem_dir = @spec.gem_dir @installer.generate_bin @@ -630,7 +630,7 @@ gem 'other', version util_installer @spec, @gemhome @installer.wrappers = false - @installer.gem_dir = util_gem_dir + @installer.gem_dir = @spec.gem_dir @installer.generate_bin @@ -648,7 +648,7 @@ gem 'other', version File.const_set(:ALT_SEPARATOR, '\\') @installer.wrappers = false util_make_exec - @installer.gem_dir = util_gem_dir + @installer.gem_dir = @spec.gem_dir use_ui @ui do @installer.generate_bin @@ -1421,10 +1421,11 @@ gem 'other', version def test_pre_install_checks_ruby_version use_ui @ui do installer = Gem::Installer.at old_ruby_required - e = assert_raises Gem::InstallError do + e = assert_raises Gem::RuntimeRequirementNotMetError do installer.pre_install_checks end - assert_equal 'old_ruby_required requires Ruby version = 1.4.6.', + rv = Gem.ruby_api_version + assert_equal "old_ruby_required requires Ruby version = 1.4.6. The current ruby version is #{rv}.", e.message end end @@ -1440,10 +1441,11 @@ gem 'other', version use_ui @ui do @installer = Gem::Installer.at gem - e = assert_raises Gem::InstallError do + e = assert_raises Gem::RuntimeRequirementNotMetError do @installer.pre_install_checks end - assert_equal 'old_rubygems_required requires RubyGems version < 0. ' + + rgv = Gem::VERSION + assert_equal "old_rubygems_required requires RubyGems version < 0. The current RubyGems version is #{rgv}. " + "Try 'gem update --system' to update RubyGems itself.", e.message end end @@ -1739,7 +1741,7 @@ gem 'other', version @installer.wrappers = true @installer.options[:install_as_default] = true - @installer.gem_dir = util_gem_dir @spec + @installer.gem_dir = @spec.gem_dir @installer.generate_bin use_ui @ui do diff --git a/test/rubygems/test_gem_package.rb b/test/rubygems/test_gem_package.rb index 9d47f0dea4..cec1981c4c 100644 --- a/test/rubygems/test_gem_package.rb +++ b/test/rubygems/test_gem_package.rb @@ -1,4 +1,4 @@ -# coding: UTF-8 +# coding: utf-8 # frozen_string_literal: true require 'rubygems/package/tar_test_case' @@ -84,7 +84,7 @@ class TestGemPackage < Gem::Package::TarTestCase io.write spec.to_yaml end - metadata_sha1 = Digest::SHA1.hexdigest s.string + metadata_sha256 = Digest::SHA256.hexdigest s.string metadata_sha512 = Digest::SHA512.hexdigest s.string expected = { @@ -95,9 +95,9 @@ class TestGemPackage < Gem::Package::TarTestCase } if defined?(OpenSSL::Digest) then - expected['SHA1'] = { - 'metadata.gz' => metadata_sha1, - 'data.tar.gz' => Digest::SHA1.hexdigest(tar), + expected['SHA256'] = { + 'metadata.gz' => metadata_sha256, + 'data.tar.gz' => Digest::SHA256.hexdigest(tar), } end diff --git a/test/rubygems/test_gem_remote_fetcher.rb b/test/rubygems/test_gem_remote_fetcher.rb index fbb7d89019..ee5ac77717 100644 --- a/test/rubygems/test_gem_remote_fetcher.rb +++ b/test/rubygems/test_gem_remote_fetcher.rb @@ -555,7 +555,7 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== nil end - assert_equal nil, fetcher.fetch_path(@uri + 'foo.gz', Time.at(0)) + assert_nil fetcher.fetch_path(@uri + 'foo.gz', Time.at(0)) end def test_fetch_path_io_error @@ -621,7 +621,7 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== nil end - assert_equal nil, fetcher.fetch_path(URI.parse(@gem_repo), Time.at(0)) + assert_nil fetcher.fetch_path(URI.parse(@gem_repo), Time.at(0)) end def test_implicit_no_proxy diff --git a/test/rubygems/test_gem_request_set_gem_dependency_api.rb b/test/rubygems/test_gem_request_set_gem_dependency_api.rb index 509d49890d..e8bb1d4a6d 100644 --- a/test/rubygems/test_gem_request_set_gem_dependency_api.rb +++ b/test/rubygems/test_gem_request_set_gem_dependency_api.rb @@ -753,7 +753,7 @@ end @gda.ruby RUBY_VERSION, :engine => 'jruby', :engine_version => '1.7.4' end - assert_equal 'Your ruby engine is ruby, but your gem.deps.rb requires jruby', + assert_equal 'Your Ruby engine is ruby, but your gem.deps.rb requires jruby', e.message end end @@ -764,7 +764,7 @@ end @gda.ruby RUBY_VERSION, :engine => 'jruby', :engine_version => '1.7.4' end - assert_equal 'Your ruby engine version is jruby 1.7.6, but your gem.deps.rb requires jruby 1.7.4', + assert_equal 'Your Ruby engine version is jruby 1.7.6, but your gem.deps.rb requires jruby 1.7.4', e.message end end @@ -774,7 +774,7 @@ end @gda.ruby RUBY_VERSION, :engine => 'jruby' end - assert_equal 'you must specify engine_version along with the ruby engine', + assert_equal 'You must specify engine_version along with the Ruby engine', e.message end diff --git a/test/rubygems/test_gem_requirement.rb b/test/rubygems/test_gem_requirement.rb index c110009840..ea354f7b1d 100644 --- a/test/rubygems/test_gem_requirement.rb +++ b/test/rubygems/test_gem_requirement.rb @@ -30,6 +30,12 @@ class TestGemRequirement < Gem::TestCase assert_requirement_equal "= 2", v(2) end + def test_create + assert_equal req("= 1"), Gem::Requirement.create("= 1") + assert_equal req(">= 1.2", "<= 1.3"), Gem::Requirement.create([">= 1.2", "<= 1.3"]) + assert_equal req(">= 1.2", "<= 1.3"), Gem::Requirement.create(">= 1.2", "<= 1.3") + end + def test_empty_requirements_is_none r = Gem::Requirement.new assert_equal true, r.none? diff --git a/test/rubygems/test_gem_resolver.rb b/test/rubygems/test_gem_resolver.rb index e95a37162d..417f0580f7 100644 --- a/test/rubygems/test_gem_resolver.rb +++ b/test/rubygems/test_gem_resolver.rb @@ -683,6 +683,32 @@ class TestGemResolver < Gem::TestCase assert_resolves_to [b1, c1, d2], r end + def test_sorts_by_source_then_version + sourceA = Gem::Source.new 'http://example.com/a' + sourceB = Gem::Source.new 'http://example.com/b' + sourceC = Gem::Source.new 'http://example.com/c' + + spec_A_1 = new_spec 'some-dep', '0.0.1' + spec_A_2 = new_spec 'some-dep', '1.0.0' + spec_B_1 = new_spec 'some-dep', '0.0.1' + spec_B_2 = new_spec 'some-dep', '0.0.2' + spec_C_1 = new_spec 'some-dep', '0.1.0' + + set = StaticSet.new [ + Gem::Resolver::SpecSpecification.new(nil, spec_B_1, sourceB), + Gem::Resolver::SpecSpecification.new(nil, spec_B_2, sourceB), + Gem::Resolver::SpecSpecification.new(nil, spec_C_1, sourceC), + Gem::Resolver::SpecSpecification.new(nil, spec_A_2, sourceA), + Gem::Resolver::SpecSpecification.new(nil, spec_A_1, sourceA), + ] + + dependency = make_dep 'some-dep', '> 0' + + resolver = Gem::Resolver.new [dependency], set + + assert_resolves_to [spec_B_2], resolver + end + def test_select_local_platforms r = Gem::Resolver.new nil, nil diff --git a/test/rubygems/test_gem_resolver_conflict.rb b/test/rubygems/test_gem_resolver_conflict.rb index 56f048d91b..d4b3455570 100644 --- a/test/rubygems/test_gem_resolver_conflict.rb +++ b/test/rubygems/test_gem_resolver_conflict.rb @@ -15,7 +15,7 @@ class TestGemResolverConflict < Gem::TestCase dep = Gem::Resolver::DependencyRequest.new dep('net-ssh', '>= 2.0.13'), nil - spec = quick_spec 'net-ssh', '2.2.2' + spec = util_spec 'net-ssh', '2.2.2' active = Gem::Resolver::ActivationRequest.new spec, dep diff --git a/test/rubygems/test_gem_security.rb b/test/rubygems/test_gem_security.rb index acd83f4dca..47010e088c 100644 --- a/test/rubygems/test_gem_security.rb +++ b/test/rubygems/test_gem_security.rb @@ -61,6 +61,7 @@ class TestGemSecurity < Gem::TestCase cert = @SEC.create_cert_self_signed subject, PRIVATE_KEY, 60 assert_equal '/CN=nobody/DC=example', cert.issuer.to_s + assert_equal "sha256WithRSAEncryption", cert.signature_algorithm end def test_class_create_cert_email @@ -120,6 +121,7 @@ class TestGemSecurity < Gem::TestCase end def test_class_re_sign + assert_equal "sha1WithRSAEncryption", EXPIRED_CERT.signature_algorithm re_signed = Gem::Security.re_sign EXPIRED_CERT, PRIVATE_KEY, 60 assert_in_delta Time.now, re_signed.not_before, 10 @@ -127,6 +129,7 @@ class TestGemSecurity < Gem::TestCase assert_equal EXPIRED_CERT.serial + 1, re_signed.serial assert re_signed.verify PUBLIC_KEY + assert_equal "sha256WithRSAEncryption", re_signed.signature_algorithm end def test_class_re_sign_not_self_signed @@ -218,6 +221,8 @@ class TestGemSecurity < Gem::TestCase assert_in_delta Time.now, signed.not_before, 10 assert_in_delta Time.now + 60, signed.not_after, 10 + assert_equal "sha256WithRSAEncryption", signed.signature_algorithm + assert_equal 5, signed.extensions.length, signed.extensions.map { |e| e.to_a.first } diff --git a/test/rubygems/test_gem_security_policy.rb b/test/rubygems/test_gem_security_policy.rb index bee0973f64..4108551dca 100644 --- a/test/rubygems/test_gem_security_policy.rb +++ b/test/rubygems/test_gem_security_policy.rb @@ -1,4 +1,4 @@ -# coding: UTF-8 +# coding: utf-8 # frozen_string_literal: true require 'rubygems/test_case' @@ -34,7 +34,7 @@ class TestGemSecurityPolicy < Gem::TestCase s.files = %w[lib/code.rb] end - @sha1 = OpenSSL::Digest::SHA1 + @digest = Gem::Security::DIGEST_ALGORITHM @trust_dir = Gem::Security.trust_dir.dir # HACK use the object @no = Gem::Security::NoSecurity @@ -69,7 +69,7 @@ class TestGemSecurityPolicy < Gem::TestCase signature = sign data - assert @almost_no.check_data(PUBLIC_KEY, @sha1, signature, data) + assert @almost_no.check_data(PUBLIC_KEY, @digest, signature, data) end def test_check_data_invalid @@ -80,7 +80,7 @@ class TestGemSecurityPolicy < Gem::TestCase invalid = digest 'hello!' e = assert_raises Gem::Security::Exception do - @almost_no.check_data PUBLIC_KEY, @sha1, signature, invalid + @almost_no.check_data PUBLIC_KEY, @digest, signature, invalid end assert_equal 'invalid signature', e.message @@ -238,18 +238,18 @@ class TestGemSecurityPolicy < Gem::TestCase def test_check_trust Gem::Security.trust_dir.trust_cert PUBLIC_CERT - assert @high.check_trust [PUBLIC_CERT], @sha1, @trust_dir + assert @high.check_trust [PUBLIC_CERT], @digest, @trust_dir end def test_check_trust_child Gem::Security.trust_dir.trust_cert PUBLIC_CERT - assert @high.check_trust [PUBLIC_CERT, CHILD_CERT], @sha1, @trust_dir + assert @high.check_trust [PUBLIC_CERT, CHILD_CERT], @digest, @trust_dir end def test_check_trust_empty_chain e = assert_raises Gem::Security::Exception do - @chain.check_trust [], @sha1, @trust_dir + @chain.check_trust [], @digest, @trust_dir end assert_equal 'missing root certificate', e.message @@ -259,7 +259,7 @@ class TestGemSecurityPolicy < Gem::TestCase Gem::Security.trust_dir.trust_cert PUBLIC_CERT e = assert_raises Gem::Security::Exception do - @high.check_trust [WRONG_KEY_CERT], @sha1, @trust_dir + @high.check_trust [WRONG_KEY_CERT], @digest, @trust_dir end assert_equal "trusted root certificate #{PUBLIC_CERT.subject} checksum " + @@ -268,7 +268,7 @@ class TestGemSecurityPolicy < Gem::TestCase def test_check_trust_no_chain e = assert_raises Gem::Security::Exception do - @chain.check_trust nil, @sha1, @trust_dir + @chain.check_trust nil, @digest, @trust_dir end assert_equal 'missing signing chain', e.message @@ -276,7 +276,7 @@ class TestGemSecurityPolicy < Gem::TestCase def test_check_trust_no_trust e = assert_raises Gem::Security::Exception do - @high.check_trust [PUBLIC_CERT], @sha1, @trust_dir + @high.check_trust [PUBLIC_CERT], @digest, @trust_dir end assert_equal "root cert #{PUBLIC_CERT.subject} is not trusted", e.message @@ -284,7 +284,7 @@ class TestGemSecurityPolicy < Gem::TestCase def test_check_trust_no_trust_child e = assert_raises Gem::Security::Exception do - @high.check_trust [PUBLIC_CERT, CHILD_CERT], @sha1, @trust_dir + @high.check_trust [PUBLIC_CERT, CHILD_CERT], @digest, @trust_dir end assert_equal "root cert #{PUBLIC_CERT.subject} is not trusted " + @@ -370,7 +370,7 @@ class TestGemSecurityPolicy < Gem::TestCase data = digest 'goodbye' - signatures[1] = PRIVATE_KEY.sign @sha1.new, data.digest + signatures[1] = PRIVATE_KEY.sign @digest.new, data.digest e = assert_raises Gem::Security::Exception do @almost_no.verify [PUBLIC_CERT], nil, digests, signatures @@ -453,17 +453,17 @@ class TestGemSecurityPolicy < Gem::TestCase metadata_gz = Gem.gzip @spec.to_yaml package = Gem::Package.new 'nonexistent.gem' - package.checksums['SHA1'] = {} + package.checksums[Gem::Security::DIGEST_NAME] = {} s = StringIO.new metadata_gz def s.full_name() 'metadata.gz' end digests = package.digest s - metadata_gz_digest = digests['SHA1']['metadata.gz'] + metadata_gz_digest = digests[Gem::Security::DIGEST_NAME]['metadata.gz'] signatures = {} signatures['metadata.gz'] = - PRIVATE_KEY.sign @sha1.new, metadata_gz_digest.digest + PRIVATE_KEY.sign @digest.new, metadata_gz_digest.digest assert @high.verify_signatures @spec, digests, signatures end @@ -476,19 +476,19 @@ class TestGemSecurityPolicy < Gem::TestCase metadata_gz = Gem.gzip @spec.to_yaml package = Gem::Package.new 'nonexistent.gem' - package.checksums['SHA1'] = {} + package.checksums[Gem::Security::DIGEST_NAME] = {} s = StringIO.new metadata_gz def s.full_name() 'metadata.gz' end digests = package.digest s - digests['SHA1']['data.tar.gz'] = OpenSSL::Digest.new 'SHA1', 'hello' + digests[Gem::Security::DIGEST_NAME]['data.tar.gz'] = @digest.new 'hello' - metadata_gz_digest = digests['SHA1']['metadata.gz'] + metadata_gz_digest = digests[Gem::Security::DIGEST_NAME]['metadata.gz'] signatures = {} signatures['metadata.gz'] = - PRIVATE_KEY.sign @sha1.new, metadata_gz_digest.digest + PRIVATE_KEY.sign @digest.new, metadata_gz_digest.digest e = assert_raises Gem::Security::Exception do @high.verify_signatures @spec, digests, signatures @@ -505,13 +505,13 @@ class TestGemSecurityPolicy < Gem::TestCase metadata_gz = Gem.gzip @spec.to_yaml package = Gem::Package.new 'nonexistent.gem' - package.checksums['SHA1'] = {} + package.checksums[Gem::Security::DIGEST_NAME] = {} s = StringIO.new metadata_gz def s.full_name() 'metadata.gz' end digests = package.digest s - digests['SHA1']['data.tar.gz'] = OpenSSL::Digest.new 'SHA1', 'hello' + digests[Gem::Security::DIGEST_NAME]['data.tar.gz'] = @digest.new 'hello' assert_raises Gem::Security::Exception do @high.verify_signatures @spec, digests, {} @@ -519,19 +519,19 @@ class TestGemSecurityPolicy < Gem::TestCase end def digest data - digester = @sha1.new + digester = @digest.new digester << data digester end def sign data, key = PRIVATE_KEY - key.sign @sha1.new, data.digest + key.sign @digest.new, data.digest end def dummy_signatures key = PRIVATE_KEY data = digest 'hello' - digests = { 'SHA1' => { 0 => data } } + digests = { Gem::Security::DIGEST_NAME => { 0 => data } } signatures = { 0 => sign(data, key) } return digests, signatures diff --git a/test/rubygems/test_gem_security_signer.rb b/test/rubygems/test_gem_security_signer.rb index 685a13949e..79dfea099d 100644 --- a/test/rubygems/test_gem_security_signer.rb +++ b/test/rubygems/test_gem_security_signer.rb @@ -121,12 +121,12 @@ class TestGemSecuritySigner < Gem::TestCase signature = signer.sign 'hello' expected = <<-EXPECTED -pxSf9ScaghbMNmNp8fqSJj7BiIGpbuoOVYCOM3TJNH9STLILA5z3xKp3gM6w -VJ7aGsh9KCP485ftS3J9Kb/lKJsyoSkkRSQ5QG+LnyZwMuWlThPDR5o7q6al -0oxE7vvbbqxFqcT4ojWIkwxJxOluFWmt2D8I6QTX2vLAn09y+Kl66AOrT7R5 -UinbXkz04VwcNvkBqJyko3yWxFKiGNpntZQg4jIw4L+h97EOaZp8H96udzQH -Da3K0YZ6FsqLDFNnWAFhve3kmpE3CludpvDqH0piq0zKqnOiqAcvICIpPaJP -c7NM7KZZjj7G++SXjYTEI1PHSA7aFQ/i/+qSUvx+Pg== +cHze2sEfRysoUMCfGVAx/7o8jxj5liJJ2ptNxe2jf3l+EZvyjdqpXo9Ndzxx +6xLp2rxLG4K2//ip4aCH5Sh7hnia+F5u6iuLBETPlklPrmw5dnuKZxolz+vM +0O1aOZsQHcVzQoESTGjkms3KZk+gn3lg0sSBbAV5/LyDYoHCEjxlcA5D+Olb +rDmRyBMOnMS+q489OZ5Hr6B2YJJ3QbUwIZNhUeNmOxIBEYTrrKkZ92qkxbRN +qhlqFP4jR6zXFeyBCOr0KpTiWBNuxBFXDsxmhGyt2BOIjD6qmKn7RSIfYg/U +toqvglr0kdbknSRRjBVLK6tsgr07aLT9gNP7mTW2PA== EXPECTED assert_equal expected, [signature].pack('m') diff --git a/test/rubygems/test_gem_security_trust_dir.rb b/test/rubygems/test_gem_security_trust_dir.rb index a0fbef809a..ab02ceb772 100644 --- a/test/rubygems/test_gem_security_trust_dir.rb +++ b/test/rubygems/test_gem_security_trust_dir.rb @@ -18,7 +18,7 @@ class TestGemSecurityTrustDir < Gem::TestCase end def test_cert_path - digest = OpenSSL::Digest::SHA1.hexdigest PUBLIC_CERT.subject.to_s + digest = Gem::Security::DIGEST_ALGORITHM.hexdigest PUBLIC_CERT.subject.to_s expected = File.join @dest_dir, "cert-#{digest}.pem" @@ -42,7 +42,7 @@ class TestGemSecurityTrustDir < Gem::TestCase end def test_name_path - digest = OpenSSL::Digest::SHA1.hexdigest PUBLIC_CERT.subject.to_s + digest = Gem::Security::DIGEST_ALGORITHM.hexdigest PUBLIC_CERT.subject.to_s expected = File.join @dest_dir, "cert-#{digest}.pem" diff --git a/test/rubygems/test_gem_server.rb b/test/rubygems/test_gem_server.rb index 4873fac5b6..6fe02e480f 100644 --- a/test/rubygems/test_gem_server.rb +++ b/test/rubygems/test_gem_server.rb @@ -222,7 +222,7 @@ class TestGemServer < Gem::TestCase assert_equal 404, @res.status, @res.body assert_match %r| \d\d:\d\d:\d\d |, @res['date'] assert_equal 'text/plain', @res['content-type'] - assert_equal 'No gems found matching "z" "9" nil', @res.body + assert_equal 'No gems found matching "z-9"', @res.body assert_equal 404, @res.status end @@ -291,6 +291,23 @@ class TestGemServer < Gem::TestCase assert_equal v('3.a'), spec.version end + def test_quick_marshal_a_b_1_3_a_gemspec_rz + quick_gem 'a-b-1', '3.a' + + data = StringIO.new "GET /quick/Marshal.#{Gem.marshal_version}/a-b-1-3.a.gemspec.rz HTTP/1.0\r\n\r\n" + @req.parse data + + @server.quick @req, @res + + assert_equal 200, @res.status, @res.body + assert @res['date'] + assert_equal 'application/x-deflate', @res['content-type'] + + spec = Marshal.load Gem.inflate(@res.body) + assert_equal 'a-b-1', spec.name + assert_equal v('3.a'), spec.version + end + def test_rdoc data = StringIO.new "GET /rdoc?q=a HTTP/1.0\r\n\r\n" @req.parse data diff --git a/test/rubygems/test_gem_source.rb b/test/rubygems/test_gem_source.rb index 64dfa42468..4a93e222f8 100644 --- a/test/rubygems/test_gem_source.rb +++ b/test/rubygems/test_gem_source.rb @@ -228,6 +228,15 @@ class TestGemSource < Gem::TestCase assert_equal(-1, remote. <=>(no_uri), 'remote <=> no_uri') end + def test_spaceship_order_is_preserved_when_uri_differs + sourceA = Gem::Source.new "http://example.com/a" + sourceB = Gem::Source.new "http://example.com/b" + + assert_equal( 0, sourceA. <=>(sourceA), 'sourceA <=> sourceA') + assert_equal( 1, sourceA. <=>(sourceB), 'sourceA <=> sourceB') + assert_equal( 1, sourceB. <=>(sourceA), 'sourceB <=> sourceA') + end + def test_update_cache_eh assert @source.update_cache? end diff --git a/test/rubygems/test_gem_spec_fetcher.rb b/test/rubygems/test_gem_spec_fetcher.rb index 53bb31a910..558869fe99 100644 --- a/test/rubygems/test_gem_spec_fetcher.rb +++ b/test/rubygems/test_gem_spec_fetcher.rb @@ -169,6 +169,26 @@ class TestGemSpecFetcher < Gem::TestCase assert_equal "bad news from the internet (#{@gem_repo})", sfp.error.message end + def test_suggest_gems_from_name_latest + spec_fetcher do|fetcher| + fetcher.spec 'example', 1 + fetcher.spec 'other-example', 1 + end + + suggestions = @sf.suggest_gems_from_name('examplw') + assert_equal ['example'], suggestions + end + + def test_suggest_gems_from_name_prerelease + spec_fetcher do|fetcher| + fetcher.spec 'example', '1.a' + fetcher.spec 'other-example', 1 + end + + suggestions = @sf.suggest_gems_from_name('examplw') + assert_equal ['example'], suggestions + end + def test_available_specs_latest spec_fetcher do |fetcher| fetcher.spec 'a', 1 diff --git a/test/rubygems/test_gem_specification.rb b/test/rubygems/test_gem_specification.rb index 0fcc11e78f..bb6acbc7de 100644 --- a/test/rubygems/test_gem_specification.rb +++ b/test/rubygems/test_gem_specification.rb @@ -1586,7 +1586,7 @@ dependencies: [] refute @ext.contains_requirable_file? 'nonexistent' end - expected = "Ignoring ext-1 because its extensions are not built. " + + expected = "Ignoring ext-1 because its extensions are not built. " + "Try: gem pristine ext --version 1\n" assert_equal expected, err @@ -2748,14 +2748,6 @@ duplicate dependency on c (>= 1.2.3, development), (~> 1.2) use: util_setup_validate Dir.chdir @tempdir do - @a1.email = "" - - use_ui @ui do - @a1.validate - end - - assert_match "#{w}: no email specified\n", @ui.error, "error" - @a1.email = "FIxxxXME (your e-mail)".sub(/xxx/, "") e = assert_raises Gem::InvalidSpecificationException do @@ -2977,6 +2969,43 @@ Did you mean 'Ruby'? warning end + def test_validate_empty_files + util_setup_validate + + use_ui @ui do + # we have to set all of these for #files to be empty + @a1.files = [] + @a1.test_files = [] + @a1.executables = [] + + @a1.validate + end + + assert_match "no files specified", @ui.error + end + + def test_validate_empty_homepage + util_setup_validate + + use_ui @ui do + @a1.homepage = nil + @a1.validate + end + + assert_match "no homepage specified", @ui.error + end + + def test_validate_empty_summary + util_setup_validate + + use_ui @ui do + @a1.summary = nil + @a1.validate + end + + assert_match "no summary specified", @ui.error + end + def test_validate_name util_setup_validate @@ -3252,7 +3281,11 @@ Did you mean 'Ruby'? Dir.chdir @tempdir do @m1 = quick_gem 'm', '1' do |s| s.files = %w[lib/code.rb] - s.metadata = { 'one' => "two", 'two' => "three" } + s.metadata = { + "one" => "two", + "home" => "three", + "homepage_uri" => "https://example.com/user/repo" + } end use_ui @ui do @@ -3329,6 +3362,23 @@ Did you mean 'Ruby'? end end + def test_metadata_link_validation_fails + util_setup_validate + + Dir.chdir @tempdir do + @m2 = quick_gem 'm', '2' do |s| + s.files = %w[lib/code.rb] + s.metadata = { 'homepage_uri' => 'http:/example.com' } + end + + e = assert_raises Gem::InvalidSpecificationException do + @m2.validate + end + + assert_equal "metadata['homepage_uri'] has invalid link: \"http:/example.com\"", e.message + end + end + def test_metadata_specs valid_ruby_spec = <<-EOF # -*- encoding: utf-8 -*- @@ -3406,6 +3456,31 @@ end refute @a1.missing_extensions? end + def test_find_all_by_full_name + pl = Gem::Platform.new 'i386-linux' + + a1 = util_spec "a", "1" + a1_pre = util_spec "a", "1.0.0.pre.1" + a_1_platform = util_spec("a", "1") {|s| s.platform = pl } + a_b_1 = util_spec "a-b", "1" + a_b_1_platform = util_spec("a-b", "1") {|s| s.platform = pl } + + a_b_1_1 = util_spec "a-b-1", "1" + a_b_1_1_platform = util_spec("a-b-1", "1") {|s| s.platform = pl } + + install_specs(a1, a1_pre, a_1_platform, a_b_1, a_b_1_platform, + a_b_1_1, a_b_1_1_platform) + + assert_equal [a1], Gem::Specification.find_all_by_full_name("a-1") + assert_equal [a1_pre], Gem::Specification.find_all_by_full_name("a-1.0.0.pre.1") + assert_equal [a_1_platform], Gem::Specification.find_all_by_full_name("a-1-x86-linux") + assert_equal [a_b_1_1], Gem::Specification.find_all_by_full_name("a-b-1-1") + assert_equal [a_b_1_1_platform], Gem::Specification.find_all_by_full_name("a-b-1-1-x86-linux") + + assert_equal [], Gem::Specification.find_all_by_full_name("monkeys") + assert_equal [], Gem::Specification.find_all_by_full_name("a-1-foo") + end + def test_find_by_name install_specs util_spec "a" install_specs util_spec "a", 1 diff --git a/test/rubygems/test_gem_stub_specification.rb b/test/rubygems/test_gem_stub_specification.rb index 5316449fba..53b9dab02d 100644 --- a/test/rubygems/test_gem_stub_specification.rb +++ b/test/rubygems/test_gem_stub_specification.rb @@ -71,7 +71,7 @@ class TestStubSpecification < Gem::TestCase refute stub.contains_requirable_file? 'nonexistent' end - expected = "Ignoring stub_e-2 because its extensions are not built. " + + expected = "Ignoring stub_e-2 because its extensions are not built. " + "Try: gem pristine stub_e --version 2\n" assert_equal expected, err @@ -95,6 +95,12 @@ class TestStubSpecification < Gem::TestCase assert_equal File.join(stub.full_gem_path, 'lib'), stub.lib_dirs_glob end + def test_lib_dirs_glob_with_extension + stub = stub_with_extension + + assert_equal File.join(stub.full_gem_path, 'lib'), stub.lib_dirs_glob + end + def test_matches_for_glob stub = stub_without_extension code_rb = File.join stub.gem_dir, 'lib', 'code.rb' diff --git a/test/rubygems/test_gem_util.rb b/test/rubygems/test_gem_util.rb index eb974427aa..b85db44d51 100644 --- a/test/rubygems/test_gem_util.rb +++ b/test/rubygems/test_gem_util.rb @@ -26,6 +26,7 @@ class TestGemUtil < Gem::TestCase assert_equal File.join(@tempdir, 'a/b/c'), enum.next assert_equal File.join(@tempdir, 'a/b'), enum.next assert_equal File.join(@tempdir, 'a'), enum.next + loop { break if enum.next.nil? } # exhaust the enumerator end def test_linked_list_find diff --git a/test/rubygems/test_gem_version.rb b/test/rubygems/test_gem_version.rb index 1897d44905..56c818663e 100644 --- a/test/rubygems/test_gem_version.rb +++ b/test/rubygems/test_gem_version.rb @@ -41,6 +41,11 @@ class TestGemVersion < Gem::TestCase assert_equal v('1.1'), Gem::Version.create(ver) end + def test_class_correct + assert_equal true, Gem::Version.correct?("5.1") + assert_equal false, Gem::Version.correct?("an incorrect version") + end + def test_class_new_subclass v1 = Gem::Version.new '1' v2 = V.new '1' @@ -65,7 +70,8 @@ class TestGemVersion < Gem::TestCase def test_hash assert_equal v("1.2").hash, v("1.2").hash refute_equal v("1.2").hash, v("1.3").hash - refute_equal v("1.2").hash, v("1.2.0").hash + assert_equal v("1.2").hash, v("1.2.0").hash + assert_equal v("1.2.pre.1").hash, v("1.2.0.pre.1.0").hash end def test_initialize @@ -76,18 +82,23 @@ class TestGemVersion < Gem::TestCase assert_version_equal "1", 1 end - def test_initialize_bad - %W[ + def test_initialize_invalid + invalid_versions = %W[ junk 1.0\n2.0 1..2 1.2\ 3.4 - ].each do |bad| - e = assert_raises ArgumentError, bad do - Gem::Version.new bad + ] + + # DON'T TOUCH THIS WITHOUT CHECKING CVE-2013-4287 + invalid_versions << "2.3422222.222.222222222.22222.ads0as.dasd0.ddd2222.2.qd3e." + + invalid_versions.each do |invalid| + e = assert_raises ArgumentError, invalid do + Gem::Version.new invalid end - assert_equal "Malformed version number string #{bad}", e.message, bad + assert_equal "Malformed version number string #{invalid}", e.message, invalid end end @@ -105,6 +116,9 @@ class TestGemVersion < Gem::TestCase assert_prerelease '1.A' + assert_prerelease '1-1' + assert_prerelease '1-a' + refute_prerelease "1.2.0" refute_prerelease "2.9" refute_prerelease "22.1.50.0" @@ -160,6 +174,12 @@ class TestGemVersion < Gem::TestCase assert_equal [9,8,7], v("9.8.7").segments end + def test_canonical_segments + assert_equal [1], v("1.0.0").canonical_segments + assert_equal [1, "a", 1], v("1.0.0.a.1.0").canonical_segments + assert_equal [1, 2, 3, "pre", 1], v("1.2.3-1").canonical_segments + end + # Asserts that +version+ is a prerelease. def assert_prerelease version @@ -189,6 +209,7 @@ class TestGemVersion < Gem::TestCase def assert_version_equal expected, actual assert_equal v(expected), v(actual) + assert_equal v(expected).hash, v(actual).hash, "since #{actual} == #{expected}, they must have the same hash" end # Assert that two versions are eql?. Checks both directions. diff --git a/test/rubygems/test_gem_version_option.rb b/test/rubygems/test_gem_version_option.rb index d4699313c2..c06c716616 100644 --- a/test/rubygems/test_gem_version_option.rb +++ b/test/rubygems/test_gem_version_option.rb @@ -106,6 +106,21 @@ class TestGemVersionOption < Gem::TestCase assert_equal expected, @cmd.options end + def test_multiple_version_operator_option_compound + @cmd.add_version_option + + @cmd.handle_options ['--version', '< 1', '--version', '> 0.9'] + + expected = { + :args => [], + :explicit_prerelease => false, + :prerelease => false, + :version => Gem::Requirement.new('< 1', '> 0.9'), + } + + assert_equal expected, @cmd.options + end + def test_version_option_explicit_prerelease @cmd.add_prerelease_option @cmd.add_version_option diff --git a/test/rubygems/test_kernel.rb b/test/rubygems/test_kernel.rb index b565c62f4a..f7d3988ce3 100644 --- a/test/rubygems/test_kernel.rb +++ b/test/rubygems/test_kernel.rb @@ -90,4 +90,34 @@ class TestKernel < Gem::TestCase assert gem('a', '= 1'), "Should load" refute $:.any? { |p| %r{a-1/bin} =~ p } end + + def test_gem_bundler + quick_gem 'bundler', '1' + quick_gem 'bundler', '2.a' + + assert gem('bundler') + assert $:.any? { |p| %r{bundler-1/lib} =~ p } + end + + def test_gem_bundler_missing_bundler_version + Gem::BundlerVersionFinder.stub(:bundler_version_with_reason, ["55", "reason"]) do + quick_gem 'bundler', '1' + quick_gem 'bundler', '2.a' + + e = assert_raises Gem::MissingSpecVersionError do + gem('bundler') + end + assert_match "Could not find 'bundler' (55) required by reason.", e.message + end + end + + def test_gem_bundler_inferred_bundler_version + Gem::BundlerVersionFinder.stub(:bundler_version_with_reason, ["1", "reason"]) do + quick_gem 'bundler', '1' + quick_gem 'bundler', '2.a' + + assert gem('bundler', '>= 0.a') + assert $:.any? { |p| %r{bundler-1/lib} =~ p } + end + end end diff --git a/test/rubygems/test_require.rb b/test/rubygems/test_require.rb index edb36f1f32..a846f46833 100644 --- a/test/rubygems/test_require.rb +++ b/test/rubygems/test_require.rb @@ -383,6 +383,44 @@ class TestGemRequire < Gem::TestCase assert_equal %w(a-1), loaded_spec_names end + + def test_require_bundler + b1 = new_spec('bundler', '1', nil, "lib/bundler/setup.rb") + b2a = new_spec('bundler', '2.a', nil, "lib/bundler/setup.rb") + install_specs b1, b2a + + require "rubygems/bundler_version_finder" + $:.clear + assert_require 'bundler/setup' + assert_equal %w[bundler-2.a], loaded_spec_names + assert_empty unresolved_names + end + + def test_require_bundler_missing_bundler_version + Gem::BundlerVersionFinder.stub(:bundler_version_with_reason, ["55", "reason"]) do + b1 = new_spec('bundler', '1.999999999', nil, "lib/bundler/setup.rb") + b2a = new_spec('bundler', '2.a', nil, "lib/bundler/setup.rb") + install_specs b1, b2a + + e = assert_raises Gem::MissingSpecVersionError do + gem('bundler') + end + assert_match "Could not find 'bundler' (55) required by reason.", e.message + end + end + + def test_require_bundler_with_bundler_version + Gem::BundlerVersionFinder.stub(:bundler_version_with_reason, ["1", "reason"]) do + b1 = new_spec('bundler', '1.999999999', nil, "lib/bundler/setup.rb") + b2 = new_spec('bundler', '2', nil, "lib/bundler/setup.rb") + install_specs b1, b2 + + $:.clear + assert_require 'bundler/setup' + assert_equal %w[bundler-1.999999999], loaded_spec_names + end + end + def silence_warnings old_verbose, $VERBOSE = $VERBOSE, false yield -- cgit v1.2.3