diff --git a/lib/ruby/stdlib/rubygems.rb b/lib/ruby/stdlib/rubygems.rb index c090ed808005..0d90c89b628c 100644 --- a/lib/ruby/stdlib/rubygems.rb +++ b/lib/ruby/stdlib/rubygems.rb @@ -7,10 +7,9 @@ #++ require 'rbconfig' -require 'thread' module Gem - VERSION = "2.7.10" + VERSION = "3.0.6".freeze end # Must be first since it unloads the prelude from 1.9.2 @@ -19,6 +18,7 @@ module Gem require 'rubygems/defaults' require 'rubygems/deprecate' require 'rubygems/errors' +require 'rubygems/path_support' ## # RubyGems is the Ruby standard for publishing and managing third party @@ -127,14 +127,14 @@ module Gem /mingw/i, /mswin/i, /wince/i, - ] + ].freeze GEM_DEP_FILES = %w[ gem.deps.rb gems.rb Gemfile Isolate - ] + ].freeze ## # Subdirectories in a gem repository @@ -146,7 +146,7 @@ module Gem extensions gems specifications - ] + ].freeze ## # Subdirectories in a gem repository for default gems @@ -154,7 +154,7 @@ module Gem REPOSITORY_DEFAULT_GEM_SUBDIRECTORIES = %w[ gems specifications/default - ] + ].freeze ## # Exception classes used in a Gem.read_binary +rescue+ statement. Not all of @@ -203,7 +203,7 @@ module Gem # activation succeeded or wasn't needed because it was already # activated. Returns false if it can't find the path in a gem. - def self.try_activate path + def self.try_activate(path) # finds the _latest_ version... regardless of loaded specs and their deps # if another gem had a requirement that would mean we shouldn't # activate the latest version, then either it would already be activated @@ -263,7 +263,7 @@ def self.bin_path(name, exec_name = nil, *requirements) find_spec_for_exe(name, exec_name, requirements).bin_file exec_name end - def self.find_spec_for_exe name, exec_name, requirements + def self.find_spec_for_exe(name, exec_name, requirements) dep = Gem::Dependency.new name, requirements loaded = Gem.loaded_specs[name] @@ -299,7 +299,7 @@ def self.find_spec_for_exe name, exec_name, requirements # # This method should *only* be used in bin stub files. - def self.activate_bin_path name, exec_name, requirement # :nodoc: + def self.activate_bin_path(name, exec_name, requirement) # :nodoc: spec = find_spec_for_exe name, exec_name, [requirement] Gem::LOADED_SPECS_MUTEX.synchronize do spec.activate @@ -368,11 +368,6 @@ def self.datadir(gem_name) spec.datadir end - class << self - extend Gem::Deprecate - deprecate :datadir, "spec.datadir", 2016, 10 - end - ## # A Zlib::Deflate.deflate wrapper @@ -447,7 +442,7 @@ def self.spec_cache_dir # # World-writable directories will never be created. - def self.ensure_gem_subdirectories dir = Gem.dir, mode = nil + def self.ensure_gem_subdirectories(dir = Gem.dir, mode = nil) ensure_subdirectories(dir, mode, REPOSITORY_SUBDIRECTORIES) end @@ -460,11 +455,11 @@ def self.ensure_gem_subdirectories dir = Gem.dir, mode = nil # # World-writable directories will never be created. - def self.ensure_default_gem_subdirectories dir = Gem.dir, mode = nil + def self.ensure_default_gem_subdirectories(dir = Gem.dir, mode = nil) ensure_subdirectories(dir, mode, REPOSITORY_DEFAULT_GEM_SUBDIRECTORIES) end - def self.ensure_subdirectories dir, mode, subdirs # :nodoc: + def self.ensure_subdirectories(dir, mode, subdirs) # :nodoc: old_umask = File.umask File.umask old_umask | 002 @@ -488,7 +483,7 @@ def self.ensure_subdirectories dir, mode, subdirs # :nodoc: # distinction as extensions cannot be shared between the two. def self.extension_api_version # :nodoc: - if 'no' == RbConfig::CONFIG['ENABLE_SHARED'] then + if 'no' == RbConfig::CONFIG['ENABLE_SHARED'] "#{ruby_api_version}-static" else ruby_api_version @@ -525,9 +520,10 @@ def self.find_files(glob, check_load_path=true) return files end - def self.find_files_from_load_path glob # :nodoc: + def self.find_files_from_load_path(glob) # :nodoc: + glob_with_suffixes = "#{glob}#{Gem.suffix_pattern}" $LOAD_PATH.map { |load_path| - Dir["#{File.expand_path glob, load_path}#{Gem.suffix_pattern}"] + Gem::Util.glob_files_in_dir(glob_with_suffixes, load_path) }.flatten.select { |file| File.file? file.untaint } end @@ -572,25 +568,12 @@ def self.find_latest_files(glob, check_load_path=true) #++ #-- # - # FIXME move to pathsupport - # #++ def self.find_home - windows = File::ALT_SEPARATOR - if not windows or RUBY_VERSION >= '1.9' then - File.expand_path "~" - else - ['HOME', 'USERPROFILE'].each do |key| - return File.expand_path ENV[key] if ENV[key] - end - - if ENV['HOMEDRIVE'] && ENV['HOMEPATH'] then - File.expand_path "#{ENV['HOMEDRIVE']}#{ENV['HOMEPATH']}" - end - end + Dir.home.dup rescue - if windows then + if Gem.win_platform? File.expand_path File.join(ENV['HOMEDRIVE'] || ENV['SystemDrive'], '/') else File.expand_path "/" @@ -645,7 +628,7 @@ class << self # Fetching: minitest-3.0.1.gem (100%) # => [#] - def self.install name, version = Gem::Requirement.default, *options + def self.install(name, version = Gem::Requirement.default, *options) require "rubygems/dependency_installer" inst = Gem::DependencyInstaller.new(*options) inst.install name, version @@ -657,14 +640,12 @@ def self.install name, version = Gem::Requirement.default, *options # https://rubygems.org. def self.host - # TODO: move to utils @host ||= Gem::DEFAULT_HOST end ## Set the default RubyGems API host. - def self.host= host - # TODO: move to utils + def self.host=(host) @host = host end @@ -691,45 +672,32 @@ def self.load_yaml return if @yaml_loaded return unless defined?(gem) - test_syck = ENV['TEST_SYCK'] - - # Only Ruby 1.8 and 1.9 have syck - test_syck = false unless /^1\./ =~ RUBY_VERSION + begin + gem 'psych', '>= 2.0.0' + rescue Gem::LoadError + # It's OK if the user does not have the psych gem installed. We will + # attempt to require the stdlib version + end - unless test_syck - begin - gem 'psych', '>= 2.0.0' - rescue Gem::LoadError - # It's OK if the user does not have the psych gem installed. We will - # attempt to require the stdlib version + begin + # Try requiring the gem version *or* stdlib version of psych. + require 'psych' + rescue ::LoadError + # If we can't load psych, thats fine, go on. + else + # If 'yaml' has already been required, then we have to + # be sure to switch it over to the newly loaded psych. + if defined?(YAML::ENGINE) && YAML::ENGINE.yamler != "psych" + YAML::ENGINE.yamler = "psych" end - begin - # Try requiring the gem version *or* stdlib version of psych. - require 'psych' - rescue ::LoadError - # If we can't load psych, thats fine, go on. - else - # If 'yaml' has already been required, then we have to - # be sure to switch it over to the newly loaded psych. - if defined?(YAML::ENGINE) && YAML::ENGINE.yamler != "psych" - YAML::ENGINE.yamler = "psych" - end - - require 'rubygems/psych_additions' - require 'rubygems/psych_tree' - end + require 'rubygems/psych_additions' + require 'rubygems/psych_tree' end require 'yaml' require 'rubygems/safe_yaml' - # If we're supposed to be using syck, then we may have to force - # activate it via the YAML::ENGINE API. - if test_syck and defined?(YAML::ENGINE) - YAML::ENGINE.yamler = "syck" unless YAML::ENGINE.syck? - end - # Now that we're sure some kind of yaml library is loaded, pull # in our hack to deal with Syck's DefaultKey ugliness. require 'rubygems/syck_hack' @@ -865,7 +833,7 @@ def self.prefix if prefix != File.expand_path(RbConfig::CONFIG['sitelibdir']) and prefix != File.expand_path(RbConfig::CONFIG['libdir']) and - 'lib' == File.basename(RUBYGEMS_DIR) then + 'lib' == File.basename(RUBYGEMS_DIR) prefix end end @@ -923,7 +891,7 @@ def self.write_binary(path, data) # The path to the running Ruby interpreter. def self.ruby - if @ruby.nil? then + if @ruby.nil? @ruby = File.join(RbConfig::CONFIG['bindir'], "#{RbConfig::CONFIG['ruby_install_name']}#{RbConfig::CONFIG['EXEEXT']}") @@ -952,7 +920,7 @@ def self.env_requirement(gem_name) ## # Returns the latest release-version specification for the gem +name+. - def self.latest_spec_for name + def self.latest_spec_for(name) dependency = Gem::Dependency.new name fetcher = Gem::SpecFetcher.fetcher spec_tuples, = fetcher.spec_for_dependency dependency @@ -973,7 +941,7 @@ def self.latest_rubygems_version ## # Returns the version of the latest release-version of gem +name+ - def self.latest_version_for name + def self.latest_version_for(name) spec = latest_spec_for name spec and spec.version end @@ -985,10 +953,15 @@ def self.ruby_version return @ruby_version if defined? @ruby_version version = RUBY_VERSION.dup - if defined?(RUBY_PATCHLEVEL) && RUBY_PATCHLEVEL != -1 then + if defined?(RUBY_PATCHLEVEL) && RUBY_PATCHLEVEL != -1 version << ".#{RUBY_PATCHLEVEL}" - elsif defined?(RUBY_REVISION) then - version << ".dev.#{RUBY_REVISION}" + elsif defined?(RUBY_DESCRIPTION) + if RUBY_ENGINE == "ruby" + desc = RUBY_DESCRIPTION[/\Aruby #{Regexp.quote(RUBY_VERSION)}([^ ]+) /, 1] + else + desc = RUBY_DESCRIPTION[/\A#{RUBY_ENGINE} #{Regexp.quote(RUBY_ENGINE_VERSION)} \(#{RUBY_VERSION}([^ ]+)\) /, 1] + end + version << ".#{desc}" if desc end @ruby_version = Gem::Version.new version @@ -1018,7 +991,7 @@ def self.sources # DOC: This comment is not documentation about the method itself, it's # more of a code comment about the implementation. - def self.sources= new_sources + def self.sources=(new_sources) if !new_sources @sources = nil else @@ -1095,7 +1068,7 @@ def self.user_home # Is this a windows platform? def self.win_platform? - if @@win_platform.nil? then + if @@win_platform.nil? ruby_platform = RbConfig::CONFIG['host_os'] @@win_platform = !!WIN_PATTERNS.find { |r| ruby_platform =~ r } end @@ -1106,7 +1079,7 @@ def self.win_platform? ## # Load +plugins+ as Ruby files - def self.load_plugin_files plugins # :nodoc: + def self.load_plugin_files(plugins) # :nodoc: plugins.each do |plugin| # Skip older versions of the GemCutter plugin: Its commands are in @@ -1143,8 +1116,9 @@ def self.load_env_plugins path = "rubygems_plugin" files = [] + glob = "#{path}#{Gem.suffix_pattern}" $LOAD_PATH.each do |load_path| - globbed = Dir["#{File.expand_path path, load_path}#{Gem.suffix_pattern}"] + globbed = Gem::Util.glob_files_in_dir(glob, load_path) globbed.each do |load_path_file| files << load_path_file if File.file?(load_path_file.untaint) @@ -1174,7 +1148,7 @@ def self.load_env_plugins # execution of arbitrary code when used from directories outside your # control. - def self.use_gemdeps path = nil + def self.use_gemdeps(path = nil) raise_exception = path path ||= ENV['RUBYGEMS_GEMDEPS'] @@ -1182,7 +1156,7 @@ def self.use_gemdeps path = nil path = path.dup - if path == "-" then + if path == "-" Gem::Util.traverse_parents Dir.pwd do |directory| dep_file = GEM_DEP_FILES.find { |f| File.file?(f) } @@ -1195,7 +1169,7 @@ def self.use_gemdeps path = nil path.untaint - unless File.file? path then + unless File.file? path return unless raise_exception raise ArgumentError, "Unable to find gem dependencies file at #{path}" @@ -1365,13 +1339,12 @@ def clear_default_specs ## # Location of Marshal quick gemspecs on remote repositories - MARSHAL_SPEC_DIR = "quick/Marshal.#{Gem.marshal_version}/" + MARSHAL_SPEC_DIR = "quick/Marshal.#{Gem.marshal_version}/".freeze autoload :BundlerVersionFinder, 'rubygems/bundler_version_finder' autoload :ConfigFile, 'rubygems/config_file' autoload :Dependency, 'rubygems/dependency' autoload :DependencyList, 'rubygems/dependency_list' - autoload :DependencyResolver, 'rubygems/resolver' autoload :Installer, 'rubygems/installer' autoload :Licenses, 'rubygems/util/licenses' autoload :PathSupport, 'rubygems/path_support' @@ -1392,25 +1365,20 @@ def clear_default_specs require 'rubygems/exceptions' # REFACTOR: This should be pulled out into some kind of hacks file. -gem_preluded = Gem::GEM_PRELUDE_SUCKAGE and defined? Gem -unless gem_preluded then # TODO: remove guard after 1.9.2 dropped - begin - ## - # Defaults the operating system (or packager) wants to provide for RubyGems. +begin + ## + # Defaults the operating system (or packager) wants to provide for RubyGems. - require 'rubygems/defaults/operating_system' - rescue LoadError - end + require 'rubygems/defaults/operating_system' +rescue LoadError +end - if defined?(RUBY_ENGINE) then - begin - ## - # Defaults the Ruby implementation wants to provide for RubyGems +begin + ## + # Defaults the Ruby implementation wants to provide for RubyGems - require "rubygems/defaults/#{RUBY_ENGINE}" - rescue LoadError - end - end + require "rubygems/defaults/#{RUBY_ENGINE}" +rescue LoadError end ## @@ -1419,5 +1387,6 @@ def clear_default_specs require 'rubygems/core_ext/kernel_gem' require 'rubygems/core_ext/kernel_require' +require 'rubygems/core_ext/kernel_warn' Gem.use_gemdeps diff --git a/lib/ruby/stdlib/rubygems/available_set.rb b/lib/ruby/stdlib/rubygems/available_set.rb index 49b5d5fd06d6..8334a73ecb67 100644 --- a/lib/ruby/stdlib/rubygems/available_set.rb +++ b/lib/ruby/stdlib/rubygems/available_set.rb @@ -103,7 +103,7 @@ def source_for(spec) # Other options are :shallow for only direct development dependencies of the # gems in this set or :all for all development dependencies. - def to_request_set development = :none + def to_request_set(development = :none) request_set = Gem::RequestSet.new request_set.development = :all == development diff --git a/lib/ruby/stdlib/rubygems/basic_specification.rb b/lib/ruby/stdlib/rubygems/basic_specification.rb index 72954a7863d1..d1878021bafc 100644 --- a/lib/ruby/stdlib/rubygems/basic_specification.rb +++ b/lib/ruby/stdlib/rubygems/basic_specification.rb @@ -65,10 +65,10 @@ def base_dir ## # Return true if this spec can require +file+. - def contains_requirable_file? file - if @ignored then + def contains_requirable_file?(file) + if @ignored return false - elsif missing_extensions? then + elsif missing_extensions? @ignored = true warn "Ignoring #{full_name} because its extensions are not built. " + @@ -124,7 +124,7 @@ def full_gem_path # default Ruby platform. def full_name - if platform == Gem::Platform::RUBY or platform.nil? then + if platform == Gem::Platform::RUBY or platform.nil? "#{name}-#{version}".dup.untaint else "#{name}-#{version}-#{platform}".dup.untaint @@ -152,7 +152,7 @@ def full_require_paths # The path to the data directory for this gem. def datadir -# TODO: drop the extra ", gem_name" which is uselessly redundant + # TODO: drop the extra ", gem_name" which is uselessly redundant File.expand_path(File.join(gems_dir, full_name, "data", name)).untaint end @@ -160,8 +160,8 @@ def datadir # Full path of the target library file. # If the file is not in this gem, return nil. - def to_fullpath path - if activated? then + def to_fullpath(path) + if activated? @paths_map ||= {} @paths_map[path] ||= begin @@ -249,7 +249,7 @@ def require_paths def source_paths paths = raw_require_paths.dup - if have_extensions? then + if have_extensions? ext_dirs = extensions.map do |extension| extension.split(File::SEPARATOR, 2).first end.uniq @@ -263,7 +263,7 @@ def source_paths ## # Return all files in this gem that match for +glob+. - def matches_for_glob glob # TODO: rename? + def matches_for_glob(glob) # TODO: rename? # TODO: do we need these?? Kill it glob = File.join(self.lib_dirs_glob, glob) @@ -276,13 +276,13 @@ def matches_for_glob glob # TODO: rename? def lib_dirs_glob dirs = if self.raw_require_paths - if self.raw_require_paths.size > 1 then + if self.raw_require_paths.size > 1 "{#{self.raw_require_paths.join(',')}}" else self.raw_require_paths.first end else - "lib" # default value for require_paths for bundler/inline + "lib" # default value for require_paths for bundler/inline end "#{self.full_gem_path}/#{dirs}".dup.untaint @@ -316,7 +316,7 @@ def this; self; end def have_extensions?; !extensions.empty?; end - def have_file? file, suffixes + def have_file?(file, suffixes) return true if raw_require_paths.any? do |path| base = File.join(gems_dir, full_name, path.untaint, file).untaint suffixes.any? { |suf| File.file? base + suf } diff --git a/lib/ruby/stdlib/rubygems/bundler_version_finder.rb b/lib/ruby/stdlib/rubygems/bundler_version_finder.rb index f1824523329d..e74baca1ee67 100644 --- a/lib/ruby/stdlib/rubygems/bundler_version_finder.rb +++ b/lib/ruby/stdlib/rubygems/bundler_version_finder.rb @@ -87,9 +87,9 @@ def self.lockfile_contents return unless gemfile lockfile = case gemfile - when "gems.rb" then "gems.locked" - else "#{gemfile}.lock" - end.dup.untaint + when "gems.rb" then "gems.locked" + else "#{gemfile}.lock" + end.dup.untaint return unless File.file?(lockfile) diff --git a/lib/ruby/stdlib/rubygems/command.rb b/lib/ruby/stdlib/rubygems/command.rb index a7ec212e51be..5b8868b0cdd2 100644 --- a/lib/ruby/stdlib/rubygems/command.rb +++ b/lib/ruby/stdlib/rubygems/command.rb @@ -152,16 +152,24 @@ def execute #-- # TODO: replace +domain+ with a parameter to suppress suggestions - def show_lookup_failure(gem_name, version, errors, domain) + def show_lookup_failure(gem_name, version, errors, domain, required_by = nil) + gem = "'#{gem_name}' (#{version})" + msg = String.new "Could not find a valid gem #{gem}" + if errors and !errors.empty? - msg = "Could not find a valid gem '#{gem_name}' (#{version}), here is why:\n".dup + msg << ", here is why:\n" errors.each { |x| msg << " #{x.wordy}\n" } - alert_error msg else - alert_error "Could not find a valid gem '#{gem_name}' (#{version}) in any repository" + if required_by and gem != required_by + msg << " (required by #{required_by}) in any repository" + else + msg << " in any repository" + end end - unless domain == :local then # HACK + alert_error msg + + unless domain == :local # HACK suggestions = Gem::SpecFetcher.fetcher.suggest_gems_from_name gem_name unless suggestions.empty? @@ -176,7 +184,7 @@ def show_lookup_failure(gem_name, version, errors, domain) def get_all_gem_names args = options[:args] - if args.nil? or args.empty? then + if args.nil? or args.empty? raise Gem::CommandLineError, "Please specify at least one gem name (e.g. gem build GEMNAME)" end @@ -206,12 +214,12 @@ def get_all_gem_names_and_versions def get_one_gem_name args = options[:args] - if args.nil? or args.empty? then + if args.nil? or args.empty? raise Gem::CommandLineError, "Please specify a gem name on the command line (e.g. gem build GEMNAME)" end - if args.size > 1 then + if args.size > 1 raise Gem::CommandLineError, "Too many gem names (#{args.join(', ')}); please specify only one" end @@ -305,9 +313,9 @@ def invoke_with_build_args(args, build_args) self.ui = ui = Gem::SilentUI.new end - if options[:help] then + if options[:help] show_help - elsif @when_invoked then + elsif @when_invoked @when_invoked.call options else execute @@ -353,7 +361,7 @@ def add_option(*opts, &handler) # :yields: value, options def remove_option(name) @option_groups.each do |_, option_list| - option_list.reject! { |args, _| args.any? { |x| x =~ /^#{name}/ } } + option_list.reject! { |args, _| args.any? { |x| x.is_a?(String) && x =~ /^#{name}/ } } end end @@ -443,7 +451,7 @@ def add_parser_options # :nodoc: # Adds a section with +title+ and +content+ to the parser help view. Used # for adding command arguments and default arguments. - def add_parser_run_info title, content + def add_parser_run_info(title, content) return if content.empty? @parser.separator nil @@ -496,7 +504,6 @@ def configure_options(header, option_list) @parser.separator " #{header}Options:" option_list.each do |args, handler| - args.select { |arg| arg =~ /^-/ } @parser.on(*args) do |value| handler.call(value, @options) end @@ -523,7 +530,7 @@ def wrap(text, width) # :doc: add_common_option('-V', '--[no-]verbose', 'Set the verbose level of output') do |value, options| # Set us to "really verbose" so the progress meter works - if Gem.configuration.verbose and value then + if Gem.configuration.verbose and value Gem.configuration.verbose = 1 else Gem.configuration.verbose = value @@ -562,7 +569,7 @@ def wrap(text, width) # :doc: # :stopdoc: - HELP = <<-HELP + HELP = <<-HELP.freeze RubyGems is a sophisticated package manager for Ruby. This is a basic help message containing pointers to more information. diff --git a/lib/ruby/stdlib/rubygems/command_manager.rb b/lib/ruby/stdlib/rubygems/command_manager.rb index 3bee1c30a4a3..8ab0d98ed490 100644 --- a/lib/ruby/stdlib/rubygems/command_manager.rb +++ b/lib/ruby/stdlib/rubygems/command_manager.rb @@ -47,6 +47,7 @@ class Gem::CommandManager :fetch, :generate_index, :help, + :info, :install, :list, :lock, @@ -70,7 +71,11 @@ class Gem::CommandManager :update, :which, :yank, - ] + ].freeze + + ALIAS_COMMANDS = { + 'i' => 'install' + }.freeze ## # Return the authoritative instance of the command manager. @@ -152,7 +157,7 @@ def run(args, build_args=nil) end def process_args(args, build_args=nil) - if args.empty? then + if args.empty? say Gem::Command::HELP terminate_interaction 1 end @@ -164,6 +169,12 @@ def process_args(args, build_args=nil) when '-v', '--version' then say Gem::VERSION terminate_interaction 0 + when '--no-ri', '--no-rdoc' then + # This was added to compensate for a deprecation warning not being shown + # in Rubygems 2.x.x. + # TODO: Remove when Rubygems 3.1 is released. + alert_error "Invalid option: #{args.first}. Use --no-document instead." + terminate_interaction 1 when /^-/ then alert_error clean_text("Invalid option: #{args.first}. See 'gem --help'.") terminate_interaction 1 @@ -175,18 +186,25 @@ def process_args(args, build_args=nil) end def find_command(cmd_name) + cmd_name = find_alias_command cmd_name + possibilities = find_command_possibilities cmd_name - if possibilities.size > 1 then + if possibilities.size > 1 raise Gem::CommandLineError, "Ambiguous command #{cmd_name} matches [#{possibilities.join(', ')}]" - elsif possibilities.empty? then + elsif possibilities.empty? raise Gem::CommandLineError, "Unknown command #{cmd_name}" end self[possibilities.first] end + def find_alias_command(cmd_name) + alias_name = ALIAS_COMMANDS[cmd_name] + alias_name ? alias_name : cmd_name + end + def find_command_possibilities(cmd_name) len = cmd_name.length @@ -220,4 +238,3 @@ def load_and_instantiate(command_name) end end - diff --git a/lib/ruby/stdlib/rubygems/commands/build_command.rb b/lib/ruby/stdlib/rubygems/commands/build_command.rb index 38c45e46f058..761b80ee94ff 100644 --- a/lib/ruby/stdlib/rubygems/commands/build_command.rb +++ b/lib/ruby/stdlib/rubygems/commands/build_command.rb @@ -10,6 +10,18 @@ def initialize add_option '--force', 'skip validation of the spec' do |value, options| options[:force] = true end + + add_option '--strict', 'consider warnings as errors when validating the spec' do |value, options| + options[:strict] = true + end + + add_option '-o', '--output FILE', 'output gem with the given filename' do |value, options| + options[:output] = value + end + + add_option '-C PATH', '', 'Run as if gem build was started in instead of the current working directory.' do |value, options| + options[:build_path] = value + end end def arguments # :nodoc: @@ -32,6 +44,11 @@ def description # :nodoc: $ cd my_gem-1.0 [edit gem contents] $ gem build my_gem-1.0.gemspec + +Gems can be saved to a specified filename with the output option: + + $ gem build my_gem-1.0.gemspec --output=release.gem + EOF end @@ -46,20 +63,37 @@ def execute gemspec += '.gemspec' if File.exist? gemspec + '.gemspec' end - if File.exist? gemspec then - spec = Gem::Specification.load gemspec + if File.exist? gemspec + spec = Gem::Specification.load(gemspec) - if spec then - Gem::Package.build spec, options[:force] + if options[:build_path] + Dir.chdir(File.dirname(gemspec)) do + spec = Gem::Specification.load File.basename(gemspec) + build_package(spec) + end else - alert_error "Error loading gemspec. Aborting." - terminate_interaction 1 + build_package(spec) end + else alert_error "Gemspec file not found: #{gemspec}" terminate_interaction 1 end end -end + private + def build_package(spec) + if spec + Gem::Package.build( + spec, + options[:force], + options[:strict], + options[:output] + ) + else + alert_error "Error loading gemspec. Aborting." + terminate_interaction 1 + end + end +end diff --git a/lib/ruby/stdlib/rubygems/commands/cert_command.rb b/lib/ruby/stdlib/rubygems/commands/cert_command.rb index 5542262a50c2..5695460d9470 100644 --- a/lib/ruby/stdlib/rubygems/commands/cert_command.rb +++ b/lib/ruby/stdlib/rubygems/commands/cert_command.rb @@ -14,15 +14,16 @@ def initialize super 'cert', 'Manage RubyGems certificates and signing settings', :add => [], :remove => [], :list => [], :build => [], :sign => [] - OptionParser.accept OpenSSL::X509::Certificate do |certificate| + OptionParser.accept OpenSSL::X509::Certificate do |certificate_file| begin - OpenSSL::X509::Certificate.new File.read certificate + certificate = OpenSSL::X509::Certificate.new File.read certificate_file rescue Errno::ENOENT - raise OptionParser::InvalidArgument, "#{certificate}: does not exist" + raise OptionParser::InvalidArgument, "#{certificate_file}: does not exist" rescue OpenSSL::X509::CertificateError raise OptionParser::InvalidArgument, - "#{certificate}: invalid X509 certificate" + "#{certificate_file}: invalid X509 certificate" end + [certificate, certificate_file] end OptionParser.accept OpenSSL::PKey::RSA do |key_file| @@ -42,7 +43,7 @@ def initialize end add_option('-a', '--add CERT', OpenSSL::X509::Certificate, - 'Add a trusted certificate.') do |cert, options| + 'Add a trusted certificate.') do |(cert, _), options| options[:add] << cert end @@ -67,8 +68,9 @@ def initialize end add_option('-C', '--certificate CERT', OpenSSL::X509::Certificate, - 'Signing certificate for --sign') do |cert, options| + 'Signing certificate for --sign') do |(cert, cert_file), options| options[:issuer_cert] = cert + options[:issuer_cert_file] = cert_file end add_option('-K', '--private-key KEY', OpenSSL::PKey::RSA, @@ -87,11 +89,16 @@ def initialize add_option('-d', '--days NUMBER_OF_DAYS', 'Days before the certificate expires') do |days, options| - options[:expiration_length_days] = days.to_i + options[:expiration_length_days] = days.to_i + end + + add_option('-R', '--re-sign', + 'Re-signs the certificate from -C with the key from -K') do |resign, options| + options[:resign] = resign end end - def add_certificate certificate # :nodoc: + def add_certificate(certificate) # :nodoc: Gem::Security.trust_dir.trust_cert certificate say "Added '#{certificate.subject}'" @@ -114,10 +121,18 @@ def execute build email end + if options[:resign] + re_sign_cert( + options[:issuer_cert], + options[:issuer_cert_file], + options[:key] + ) + end + sign_certificates unless options[:sign].empty? end - def build email + def build(email) if !valid_email?(email) raise Gem::CommandLineError, "Invalid email address #{email}" end @@ -133,16 +148,16 @@ def build email end end - 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 + def build_cert(email, key) # :nodoc: + expiration_length_days = options[:expiration_length_days] || + Gem.configuration.cert_expiration_length_days + + cert = Gem::Security.create_cert_email( + email, + key, + (Gem::Security::ONE_DAY * expiration_length_days) + ) - cert = Gem::Security.create_cert_email email, key, age Gem::Security.write cert, "gem-public_cert.pem" end @@ -164,7 +179,7 @@ def build_key # :nodoc: return key, key_path end - def certificates_matching filter + def certificates_matching(filter) return enum_for __method__, filter unless block_given? Gem::Security.trusted_certificates.select do |certificate, _| @@ -216,7 +231,7 @@ def description # :nodoc: EOF end - def list_certificates_matching filter # :nodoc: + def list_certificates_matching(filter) # :nodoc: certificates_matching filter do |certificate, _| # this could probably be formatted more gracefully say certificate.subject.to_s @@ -261,14 +276,14 @@ def load_defaults # :nodoc: load_default_key unless options[:key] end - def remove_certificates_matching filter # :nodoc: + def remove_certificates_matching(filter) # :nodoc: certificates_matching filter do |certificate, path| FileUtils.rm path say "Removed '#{certificate.subject}'" end end - def sign cert_file + def sign(cert_file) cert = File.read cert_file cert = OpenSSL::X509::Certificate.new cert @@ -290,13 +305,19 @@ def sign_certificates # :nodoc: end end + def re_sign_cert(cert, cert_path, private_key) + Gem::Security::Signer.re_sign_cert(cert, cert_path, private_key) do |expired_cert_path, new_expired_cert_path| + alert("Your certificate #{expired_cert_path} has been re-signed") + alert("Your expired certificate will be located at: #{new_expired_cert_path}") + end + end + private - def valid_email? email + 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/ruby/stdlib/rubygems/commands/check_command.rb b/lib/ruby/stdlib/rubygems/commands/check_command.rb index 818cb05f5583..7905b8ab69a1 100644 --- a/lib/ruby/stdlib/rubygems/commands/check_command.rb +++ b/lib/ruby/stdlib/rubygems/commands/check_command.rb @@ -44,7 +44,7 @@ def check_gems gems = get_all_gem_names rescue [] Gem::Validator.new.alien(gems).sort.each do |key, val| - unless val.empty? then + unless val.empty? say "#{key} has #{val.size} problems" val.each do |error_entry| say " #{error_entry.path}:" diff --git a/lib/ruby/stdlib/rubygems/commands/cleanup_command.rb b/lib/ruby/stdlib/rubygems/commands/cleanup_command.rb index 79c23c840d11..aeb4d82fae2f 100644 --- a/lib/ruby/stdlib/rubygems/commands/cleanup_command.rb +++ b/lib/ruby/stdlib/rubygems/commands/cleanup_command.rb @@ -22,6 +22,12 @@ def initialize options[:check_dev] = value end + add_option('--[no-]user-install', + 'Cleanup in user\'s home directory instead', + 'of GEM_HOME.') do |value, options| + options[:user_install] = value + end + @candidate_gems = nil @default_gems = [] @full = nil @@ -56,7 +62,7 @@ def usage # :nodoc: def execute say "Cleaning up installed gems..." - if options[:args].empty? then + if options[:args].empty? done = false last_set = nil @@ -105,7 +111,7 @@ def clean_gems end def get_candidate_gems - @candidate_gems = unless options[:args].empty? then + @candidate_gems = unless options[:args].empty? options[:args].map do |gem_name| Gem::Specification.find_all_by_name gem_name end.flatten @@ -115,7 +121,6 @@ def get_candidate_gems end def get_gems_to_cleanup - gems_to_cleanup = @candidate_gems.select { |spec| @primary_gems[spec.name].version != spec.version } @@ -124,8 +129,10 @@ def get_gems_to_cleanup spec.default_gem? } + uninstall_from = options[:user_install] ? Gem.user_dir : @original_home + gems_to_cleanup = gems_to_cleanup.select { |spec| - spec.base_dir == @original_home + spec.base_dir == uninstall_from } @default_gems += default_gems @@ -138,16 +145,16 @@ def get_primary_gems Gem::Specification.each do |spec| if @primary_gems[spec.name].nil? or - @primary_gems[spec.name].version < spec.version then + @primary_gems[spec.name].version < spec.version @primary_gems[spec.name] = spec end end end - def uninstall_dep spec + def uninstall_dep(spec) return unless @full.ok_to_remove?(spec.full_name, options[:check_dev]) - if options[:dryrun] then + if options[:dryrun] say "Dry Run Mode: Would uninstall #{spec.full_name}" return end diff --git a/lib/ruby/stdlib/rubygems/commands/contents_command.rb b/lib/ruby/stdlib/rubygems/commands/contents_command.rb index e0f2eedb5d21..6da89a136ec4 100644 --- a/lib/ruby/stdlib/rubygems/commands/contents_command.rb +++ b/lib/ruby/stdlib/rubygems/commands/contents_command.rb @@ -73,7 +73,7 @@ def execute names.each do |name| found = - if options[:show_install_dir] then + if options[:show_install_dir] gem_install_dir name else gem_contents name @@ -83,15 +83,15 @@ def execute end end - def files_in spec - if spec.default_gem? then + def files_in(spec) + if spec.default_gem? files_in_default_gem spec else files_in_gem spec end end - def files_in_gem spec + def files_in_gem(spec) gem_path = spec.full_gem_path extra = "/{#{spec.require_paths.join ','}}" if options[:lib_only] glob = "#{gem_path}#{extra}/**/*" @@ -102,7 +102,7 @@ def files_in_gem spec end end - def files_in_default_gem spec + def files_in_default_gem(spec) spec.files.map do |file| case file when /\A#{spec.bindir}\// @@ -115,7 +115,7 @@ def files_in_default_gem spec end end - def gem_contents name + def gem_contents(name) spec = spec_for name return false unless spec @@ -127,7 +127,7 @@ def gem_contents name true end - def gem_install_dir name + def gem_install_dir(name) spec = spec_for name return false unless spec @@ -138,27 +138,27 @@ def gem_install_dir name end def gem_names # :nodoc: - if options[:all] then + if options[:all] Gem::Specification.map(&:name) else get_all_gem_names end end - def path_description spec_dirs # :nodoc: - if spec_dirs.empty? then + def path_description(spec_dirs) # :nodoc: + if spec_dirs.empty? "default gem paths" else "specified path" end end - def show_files files + def show_files(files) files.sort.each do |prefix, basename| absolute_path = File.join(prefix, basename) next if File.directory? absolute_path - if options[:prefix] then + if options[:prefix] say absolute_path else say basename @@ -166,14 +166,14 @@ def show_files files end end - def spec_for name + def spec_for(name) spec = Gem::Specification.find_all_by_name(name, @version).last return spec if spec say "Unable to find gem '#{name}' in #{@path_kind}" - if Gem.configuration.verbose then + if Gem.configuration.verbose say "\nDirectories searched:" @spec_dirs.sort.each { |dir| say dir } end @@ -188,4 +188,3 @@ def specification_directories # :nodoc: end end - diff --git a/lib/ruby/stdlib/rubygems/commands/dependency_command.rb b/lib/ruby/stdlib/rubygems/commands/dependency_command.rb index 97fd812ffa8b..9e88b04ea57e 100644 --- a/lib/ruby/stdlib/rubygems/commands/dependency_command.rb +++ b/lib/ruby/stdlib/rubygems/commands/dependency_command.rb @@ -54,7 +54,7 @@ def usage # :nodoc: "#{program_name} REGEXP" end - def fetch_remote_specs dependency # :nodoc: + def fetch_remote_specs(dependency) # :nodoc: fetcher = Gem::SpecFetcher.fetcher ss, = fetcher.spec_for_dependency dependency @@ -62,7 +62,7 @@ def fetch_remote_specs dependency # :nodoc: ss.map { |spec, _| spec } end - def fetch_specs name_pattern, dependency # :nodoc: + def fetch_specs(name_pattern, dependency) # :nodoc: specs = [] if local? @@ -79,7 +79,7 @@ def fetch_specs name_pattern, dependency # :nodoc: specs.uniq.sort end - def gem_dependency pattern, version, prerelease # :nodoc: + def gem_dependency(pattern, version, prerelease) # :nodoc: dependency = Gem::Deprecate.skip_during { Gem::Dependency.new pattern, version } @@ -89,9 +89,9 @@ def gem_dependency pattern, version, prerelease # :nodoc: dependency end - def display_pipe specs # :nodoc: + def display_pipe(specs) # :nodoc: specs.each do |spec| - unless spec.dependencies.empty? then + unless spec.dependencies.empty? spec.dependencies.sort_by { |dep| dep.name }.each do |dep| say "#{dep.name} --version '#{dep.requirement}'" end @@ -99,12 +99,12 @@ def display_pipe specs # :nodoc: end end - def display_readable specs, reverse # :nodoc: + def display_readable(specs, reverse) # :nodoc: response = String.new specs.each do |spec| response << print_dependencies(spec) - unless reverse[spec.full_name].empty? then + unless reverse[spec.full_name].empty? response << " Used by\n" reverse[spec.full_name].each do |sp, dep| response << " #{sp} (#{dep})\n" @@ -128,7 +128,7 @@ def execute reverse = reverse_dependencies specs - if options[:pipe_format] then + if options[:pipe_format] display_pipe specs else display_readable specs, reverse @@ -136,13 +136,13 @@ def execute end def ensure_local_only_reverse_dependencies # :nodoc: - if options[:reverse_dependencies] and remote? and not local? then + if options[:reverse_dependencies] and remote? and not local? alert_error 'Only reverse dependencies for local gems are supported.' terminate_interaction 1 end end - def ensure_specs specs # :nodoc: + def ensure_specs(specs) # :nodoc: return unless specs.empty? patterns = options[:args].join ',' @@ -155,7 +155,7 @@ def ensure_specs specs # :nodoc: def print_dependencies(spec, level = 0) # :nodoc: response = String.new response << ' ' * level + "Gem #{spec.full_name}\n" - unless spec.dependencies.empty? then + unless spec.dependencies.empty? spec.dependencies.sort_by { |dep| dep.name }.each do |dep| response << ' ' * level + " #{dep}\n" end @@ -163,7 +163,7 @@ def print_dependencies(spec, level = 0) # :nodoc: response end - def remote_specs dependency # :nodoc: + def remote_specs(dependency) # :nodoc: fetcher = Gem::SpecFetcher.fetcher ss, _ = fetcher.spec_for_dependency dependency @@ -171,7 +171,7 @@ def remote_specs dependency # :nodoc: ss.map { |s,o| s } end - def reverse_dependencies specs # :nodoc: + def reverse_dependencies(specs) # :nodoc: reverse = Hash.new { |h, k| h[k] = [] } return reverse unless options[:reverse_dependencies] @@ -186,7 +186,7 @@ def reverse_dependencies specs # :nodoc: ## # Returns an Array of [specification, dep] that are satisfied by +spec+. - def find_reverse_dependencies spec # :nodoc: + def find_reverse_dependencies(spec) # :nodoc: result = [] Gem::Specification.each do |sp| @@ -194,7 +194,7 @@ def find_reverse_dependencies spec # :nodoc: dep = Gem::Dependency.new(*dep) unless Gem::Dependency === dep if spec.name == dep.name and - dep.requirement.satisfied_by?(spec.version) then + dep.requirement.satisfied_by?(spec.version) result << [sp.full_name, dep] end end @@ -205,10 +205,10 @@ def find_reverse_dependencies spec # :nodoc: private - def name_pattern args + def name_pattern(args) args << '' if args.empty? - if args.length == 1 and args.first =~ /\A\/(.*)\/(i)?\z/m then + if args.length == 1 and args.first =~ /\A\/(.*)\/(i)?\z/m flags = $2 ? Regexp::IGNORECASE : nil Regexp.new $1, flags else diff --git a/lib/ruby/stdlib/rubygems/commands/environment_command.rb b/lib/ruby/stdlib/rubygems/commands/environment_command.rb index e825c761ad28..11fb45f68d26 100644 --- a/lib/ruby/stdlib/rubygems/commands/environment_command.rb +++ b/lib/ruby/stdlib/rubygems/commands/environment_command.rb @@ -97,7 +97,7 @@ def execute true end - def add_path out, path + def add_path(out, path) path.each do |component| out << " - #{component}\n" end @@ -120,6 +120,8 @@ def show_environment # :nodoc: out << " - RUBY EXECUTABLE: #{Gem.ruby}\n" + out << " - GIT EXECUTABLE: #{git_path}\n" + out << " - EXECUTABLE DIRECTORY: #{Gem.bindir}\n" out << " - SPEC CACHE DIRECTORY: #{Gem.spec_cache_dir}\n" @@ -157,4 +159,21 @@ def show_environment # :nodoc: out end + private + + ## + # Git binary path + + def git_path + exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [""] + ENV["PATH"].split(File::PATH_SEPARATOR).each do |path| + exts.each do |ext| + exe = File.join(path, "git#{ext}") + return exe if File.executable?(exe) && !File.directory?(exe) + end + end + + return nil + end + end diff --git a/lib/ruby/stdlib/rubygems/commands/fetch_command.rb b/lib/ruby/stdlib/rubygems/commands/fetch_command.rb index 19559a777410..66562d7fb73a 100644 --- a/lib/ruby/stdlib/rubygems/commands/fetch_command.rb +++ b/lib/ruby/stdlib/rubygems/commands/fetch_command.rb @@ -56,14 +56,14 @@ def execute specs_and_sources, errors = Gem::SpecFetcher.fetcher.spec_for_dependency dep - if platform then + if platform filtered = specs_and_sources.select { |s,| s.platform == platform } specs_and_sources = filtered unless filtered.empty? end spec, source = specs_and_sources.max_by { |s,| s.version } - if spec.nil? then + if spec.nil? show_lookup_failure gem_name, version, errors, options[:domain] next end @@ -75,4 +75,3 @@ def execute end end - diff --git a/lib/ruby/stdlib/rubygems/commands/generate_index_command.rb b/lib/ruby/stdlib/rubygems/commands/generate_index_command.rb index 0b677b73a956..941637ea9c8f 100644 --- a/lib/ruby/stdlib/rubygems/commands/generate_index_command.rb +++ b/lib/ruby/stdlib/rubygems/commands/generate_index_command.rb @@ -67,13 +67,13 @@ def execute options[:build_modern] = true if not File.exist?(options[:directory]) or - not File.directory?(options[:directory]) then + not File.directory?(options[:directory]) alert_error "unknown directory name #{options[:directory]}." terminate_interaction 1 else indexer = Gem::Indexer.new options.delete(:directory), options - if options[:update] then + if options[:update] indexer.update_index else indexer.generate_index @@ -82,4 +82,3 @@ def execute end end - diff --git a/lib/ruby/stdlib/rubygems/commands/help_command.rb b/lib/ruby/stdlib/rubygems/commands/help_command.rb index 7d020223696b..9f14e22f901d 100644 --- a/lib/ruby/stdlib/rubygems/commands/help_command.rb +++ b/lib/ruby/stdlib/rubygems/commands/help_command.rb @@ -4,7 +4,7 @@ class Gem::Commands::HelpCommand < Gem::Command # :stopdoc: - EXAMPLES = <<-EOF + EXAMPLES = <<-EOF.freeze Some examples of 'gem' usage. * Install 'rake', either from local directory or remote server: @@ -53,7 +53,7 @@ class Gem::Commands::HelpCommand < Gem::Command gem update --system EOF - GEM_DEPENDENCIES = <<-EOF + GEM_DEPENDENCIES = <<-EOF.freeze A gem dependencies file allows installation of a consistent set of gems across multiple environments. The RubyGems implementation is designed to be compatible with Bundler's Gemfile format. You can see additional @@ -230,7 +230,7 @@ class Gem::Commands::HelpCommand < Gem::Command EOF - PLATFORMS = <<-'EOF' + PLATFORMS = <<-'EOF'.freeze RubyGems platforms are composed of three parts, a CPU, an OS, and a version. These values are taken from values in rbconfig.rb. You can view your current platform by running `gem environment`. @@ -277,7 +277,7 @@ class Gem::Commands::HelpCommand < Gem::Command ["examples", EXAMPLES], ["gem_dependencies", GEM_DEPENDENCIES], ["platforms", PLATFORMS], - ] + ].freeze # :startdoc: def initialize @@ -297,8 +297,8 @@ def execute begins? command, arg end - if help then - if Symbol === help then + if help + if Symbol === help send help else say help @@ -306,10 +306,10 @@ def execute return end - if options[:help] then + if options[:help] show_help - elsif arg then + elsif arg show_command_help arg else @@ -334,7 +334,7 @@ def show_commands # :nodoc: command = @command_manager[cmd_name] summary = - if command then + if command command.summary else "[No command found for #{cmd_name}]" @@ -356,15 +356,15 @@ def show_commands # :nodoc: say out.join("\n") end - def show_command_help command_name # :nodoc: + def show_command_help(command_name) # :nodoc: command_name = command_name.downcase possibilities = @command_manager.find_command_possibilities command_name - if possibilities.size == 1 then + if possibilities.size == 1 command = @command_manager[possibilities.first] command.invoke("--help") - elsif possibilities.size > 1 then + elsif possibilities.size > 1 alert_warning "Ambiguous command #{command_name} (#{possibilities.join(', ')})" else alert_warning "Unknown command #{command_name}. Try: gem help commands" @@ -372,4 +372,3 @@ def show_command_help command_name # :nodoc: end end - diff --git a/lib/ruby/stdlib/rubygems/commands/info_command.rb b/lib/ruby/stdlib/rubygems/commands/info_command.rb new file mode 100644 index 000000000000..8d9611a9574d --- /dev/null +++ b/lib/ruby/stdlib/rubygems/commands/info_command.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'rubygems/command' +require 'rubygems/commands/query_command' + +class Gem::Commands::InfoCommand < Gem::Commands::QueryCommand + def initialize + super "info", "Show information for the given gem" + + remove_option('--name-matches') + remove_option('-d') + + defaults[:details] = true + defaults[:exact] = true + end + + def description # :nodoc: + "Info prints information about the gem such as name,"\ + " description, website, license and installed paths" + end + + def usage # :nodoc: + "#{program_name} GEMNAME" + end + + def arguments # :nodoc: + "GEMNAME name of the gem to print information about" + end + + def defaults_str + "--local" + end +end diff --git a/lib/ruby/stdlib/rubygems/commands/install_command.rb b/lib/ruby/stdlib/rubygems/commands/install_command.rb index 57d5328f86a3..776b58651f79 100644 --- a/lib/ruby/stdlib/rubygems/commands/install_command.rb +++ b/lib/ruby/stdlib/rubygems/commands/install_command.rb @@ -132,7 +132,7 @@ def usage # :nodoc: end def check_install_dir # :nodoc: - if options[:install_dir] and options[:user_install] then + if options[:install_dir] and options[:user_install] alert_error "Use --install-dir or --user-install but not both" terminate_interaction 1 end @@ -140,22 +140,22 @@ def check_install_dir # :nodoc: def check_version # :nodoc: if options[:version] != Gem::Requirement.default and - get_all_gem_names.size > 1 then - alert_error "Can't use --version w/ multiple gems. Use name:ver instead." + get_all_gem_names.size > 1 + alert_error "Can't use --version with multiple gems. You can specify multiple gems with" \ + " version requirements using `gem install 'my_gem:1.0.0' 'my_other_gem:~>2.0.0'`" terminate_interaction 1 end end def execute - - if options.include? :gemdeps then + if options.include? :gemdeps install_from_gemdeps return # not reached end @installed_specs = [] - ENV.delete 'GEM_PATH' if options[:install_dir].nil? and RUBY_VERSION > '1.9' + ENV.delete 'GEM_PATH' if options[:install_dir].nil? check_install_dir check_version @@ -188,13 +188,13 @@ def install_from_gemdeps # :nodoc: terminate_interaction end - def install_gem name, version # :nodoc: + def install_gem(name, version) # :nodoc: return if options[:conservative] and not Gem::Dependency.new(name, version).matching_specs.empty? req = Gem::Requirement.create(version) - if options[:ignore_dependencies] then + if options[:ignore_dependencies] install_gem_without_dependencies name, req else inst = Gem::DependencyInstaller.new options @@ -216,11 +216,11 @@ def install_gem name, version # :nodoc: end end - def install_gem_without_dependencies name, req # :nodoc: + def install_gem_without_dependencies(name, req) # :nodoc: gem = nil - if local? then - if name =~ /\.gem$/ and File.file? name then + if local? + if name =~ /\.gem$/ and File.file? name source = Gem::Source::SpecificFile.new name spec = source.spec else @@ -230,7 +230,7 @@ def install_gem_without_dependencies name, req # :nodoc: gem = source.download spec if spec end - if remote? and not gem then + if remote? and not gem dependency = Gem::Dependency.new name, req dependency.prerelease = options[:prerelease] @@ -257,17 +257,22 @@ def install_gems # :nodoc: get_all_gem_names_and_versions.each do |gem_name, gem_version| gem_version ||= options[:version] + domain = options[:domain] + domain = :local unless options[:suggest_alternate] begin install_gem gem_name, gem_version rescue Gem::InstallError => e alert_error "Error installing #{gem_name}:\n\t#{e.message}" exit_code |= 1 - rescue Gem::GemNotFoundException, Gem::UnsatisfiableDependencyError => e - domain = options[:domain] - domain = :local unless options[:suggest_alternate] + rescue Gem::GemNotFoundException => e show_lookup_failure e.name, e.version, e.errors, domain + exit_code |= 2 + rescue Gem::UnsatisfiableDependencyError => e + show_lookup_failure e.name, e.version, e.errors, domain, + "'#{gem_name}' (#{gem_version})" + exit_code |= 2 end end @@ -287,7 +292,7 @@ def load_hooks # :nodoc: require 'rubygems/rdoc' end - def show_install_errors errors # :nodoc: + def show_install_errors(errors) # :nodoc: return unless errors errors.each do |x| @@ -307,4 +312,3 @@ def show_installed # :nodoc: end end - diff --git a/lib/ruby/stdlib/rubygems/commands/list_command.rb b/lib/ruby/stdlib/rubygems/commands/list_command.rb index 1acb49e5fbcf..cd2154353777 100644 --- a/lib/ruby/stdlib/rubygems/commands/list_command.rb +++ b/lib/ruby/stdlib/rubygems/commands/list_command.rb @@ -38,4 +38,3 @@ def usage # :nodoc: end end - diff --git a/lib/ruby/stdlib/rubygems/commands/lock_command.rb b/lib/ruby/stdlib/rubygems/commands/lock_command.rb index 3eebfadc055a..7f02e9feda96 100644 --- a/lib/ruby/stdlib/rubygems/commands/lock_command.rb +++ b/lib/ruby/stdlib/rubygems/commands/lock_command.rb @@ -59,7 +59,7 @@ def usage # :nodoc: end def complain(message) - if options[:strict] then + if options[:strict] raise Gem::Exception, message else say "# #{message}" @@ -78,7 +78,7 @@ def execute spec = Gem::Specification.load spec_path(full_name) - if spec.nil? then + if spec.nil? complain "Could not find gem #{full_name}, try using the full name" next end @@ -90,7 +90,7 @@ def execute next if locked[dep.name] candidates = dep.matching_specs - if candidates.empty? then + if candidates.empty? complain "Unable to satisfy '#{dep}' from currently installed gems" else pending << candidates.last.full_name @@ -108,4 +108,3 @@ def spec_path(gem_full_name) end end - diff --git a/lib/ruby/stdlib/rubygems/commands/open_command.rb b/lib/ruby/stdlib/rubygems/commands/open_command.rb index 059635e83540..2794fe05e650 100644 --- a/lib/ruby/stdlib/rubygems/commands/open_command.rb +++ b/lib/ruby/stdlib/rubygems/commands/open_command.rb @@ -11,9 +11,9 @@ class Gem::Commands::OpenCommand < Gem::Command def initialize super 'open', 'Open gem sources in editor' - add_option('-e', '--editor EDITOR', String, - "Opens gem sources in EDITOR") do |editor, options| - options[:editor] = editor || get_env_editor + add_option('-e', '--editor COMMAND', String, + "Prepends COMMAND to gem path. Could be used to specify editor.") do |command, options| + options[:editor] = command || get_env_editor end add_option('-v', '--version VERSION', String, "Opens specific gem version") do |version| @@ -32,14 +32,14 @@ def defaults_str # :nodoc: def description # :nodoc: <<-EOF The open command opens gem in editor and changes current path - to gem's source directory. Editor can be specified with -e option, - otherwise rubygems will look for editor in $EDITOR, $VISUAL and - $GEM_EDITOR variables. + to gem's source directory. + Editor command can be specified with -e option, otherwise rubygems + will look for editor in $EDITOR, $VISUAL and $GEM_EDITOR variables. EOF end def usage # :nodoc: - "#{program_name} GEMNAME [-e EDITOR]" + "#{program_name} GEMNAME [-e COMMAND]" end def get_env_editor @@ -58,20 +58,26 @@ def execute terminate_interaction 1 unless found end - def open_gem name + def open_gem(name) spec = spec_for name + return false unless spec + if spec.default_gem? + say "'#{name}' is a default gem and can't be opened." + return false + end + open_editor(spec.full_gem_path) end - def open_editor path + def open_editor(path) Dir.chdir(path) do system(*@editor.split(/\s+/) + [path]) end end - def spec_for name + def spec_for(name) spec = Gem::Specification.find_all_by_name(name, @version).first return spec if spec diff --git a/lib/ruby/stdlib/rubygems/commands/owner_command.rb b/lib/ruby/stdlib/rubygems/commands/owner_command.rb index cac6c5a17d3d..e88b5b7b55a3 100644 --- a/lib/ruby/stdlib/rubygems/commands/owner_command.rb +++ b/lib/ruby/stdlib/rubygems/commands/owner_command.rb @@ -33,6 +33,7 @@ def initialize super 'owner', 'Manage gem owners of a gem on the push server' add_proxy_option add_key_option + add_otp_option defaults.merge! :add => [], :remove => [] add_option '-a', '--add EMAIL', 'Add an owner' do |value, options| @@ -61,7 +62,9 @@ def execute show_owners name end - def show_owners name + def show_owners(name) + Gem.load_yaml + response = rubygems_api_request :get, "api/v1/gems/#{name}/owners.yaml" do |request| request.add_field "Authorization", api_key end @@ -76,20 +79,21 @@ def show_owners name end end - def add_owners name, owners + def add_owners(name, owners) manage_owners :post, name, owners end - def remove_owners name, owners + def remove_owners(name, owners) manage_owners :delete, name, owners end - def manage_owners method, name, owners + def manage_owners(method, name, owners) owners.each do |owner| begin - response = rubygems_api_request method, "api/v1/gems/#{name}/owners" do |request| - request.set_form_data 'email' => owner - request.add_field "Authorization", api_key + response = send_owner_request(method, name, owner) + + if need_otp? response + response = send_owner_request(method, name, owner, true) end action = method == :delete ? "Removing" : "Adding" @@ -101,4 +105,14 @@ def manage_owners method, name, owners end end + private + + def send_owner_request(method, name, owner, use_otp = false) + rubygems_api_request method, "api/v1/gems/#{name}/owners" do |request| + request.set_form_data 'email' => owner + request.add_field "Authorization", api_key + request.add_field "OTP", options[:otp] if use_otp + end + end + end diff --git a/lib/ruby/stdlib/rubygems/commands/pristine_command.rb b/lib/ruby/stdlib/rubygems/commands/pristine_command.rb index fafe35bec1cc..41decde85616 100644 --- a/lib/ruby/stdlib/rubygems/commands/pristine_command.rb +++ b/lib/ruby/stdlib/rubygems/commands/pristine_command.rb @@ -24,7 +24,8 @@ def initialize add_option('--skip=gem_name', 'used on --all, skip if name == gem_name') do |value, options| - options[:skip] = value + options[:skip] ||= [] + options[:skip] << value end add_option('--[no-]extensions', @@ -45,6 +46,12 @@ def initialize options[:env_shebang] = value end + add_option('-n', '--bindir DIR', + 'Directory where executables are', + 'located') do |value, options| + options[:bin_dir] = File.expand_path(value) + end + add_version_option('restore to', 'pristine condition') end @@ -81,13 +88,13 @@ def usage # :nodoc: end def execute - specs = if options[:all] then + specs = if options[:all] Gem::Specification.map # `--extensions` must be explicitly given to pristine only gems # with extensions. elsif options[:extensions_set] and - options[:extensions] and options[:args].empty? then + options[:extensions] and options[:args].empty? Gem::Specification.select do |spec| spec.extensions and not spec.extensions.empty? end @@ -97,7 +104,7 @@ def execute end.flatten end - if specs.to_a.empty? then + if specs.to_a.empty? raise Gem::Exception, "Failed to find gems #{options[:args]} #{options[:version]}" end @@ -115,24 +122,21 @@ def execute next end - if spec.name == options[:skip] - say "Skipped #{spec.full_name}, it was given through options" - next - end - - if spec.bundled_gem_in_old_ruby? - say "Skipped #{spec.full_name}, it is bundled with old Ruby" - next + if options.has_key? :skip + if options[:skip].include? spec.name + say "Skipped #{spec.full_name}, it was given through options" + next + end end - unless spec.extensions.empty? or options[:extensions] or options[:only_executables] then + unless spec.extensions.empty? or options[:extensions] or options[:only_executables] say "Skipped #{spec.full_name}, it needs to compile an extension" next end gem = spec.cache_file - unless File.exist? gem or options[:only_executables] then + unless File.exist? gem or options[:only_executables] require 'rubygems/remote_fetcher' say "Cached gem for #{spec.full_name} not found, attempting to fetch..." @@ -150,22 +154,25 @@ def execute end env_shebang = - if options.include? :env_shebang then + if options.include? :env_shebang options[:env_shebang] else install_defaults = Gem::ConfigFile::PLATFORM_DEFAULTS['install'] install_defaults.to_s['--env-shebang'] end + bin_dir = options[:bin_dir] if options[:bin_dir] + installer_options = { :wrappers => true, :force => true, :install_dir => spec.base_dir, :env_shebang => env_shebang, :build_args => spec.build_args, + :bin_dir => bin_dir } - if options[:only_executables] then + if options[:only_executables] installer = Gem::Installer.for_spec(spec, installer_options) installer.generate_bin else diff --git a/lib/ruby/stdlib/rubygems/commands/push_command.rb b/lib/ruby/stdlib/rubygems/commands/push_command.rb index 83c7131afc93..55ee3ae73f9b 100644 --- a/lib/ruby/stdlib/rubygems/commands/push_command.rb +++ b/lib/ruby/stdlib/rubygems/commands/push_command.rb @@ -15,6 +15,8 @@ def description # :nodoc: The gem can be removed from the index and deleted from the server using the yank command. For further discussion see the help for the yank command. + +The push command will use ~/.gem/credentials to authenticate to a server, but you can use the RubyGems environment variable GEM_HOST_API_KEY to set the api key to authenticate. EOF end @@ -33,6 +35,7 @@ def initialize add_proxy_option add_key_option + add_otp_option add_option('--host HOST', 'Push to another gemcutter-compatible host', @@ -79,7 +82,7 @@ def send_gem(name) if latest_rubygems_version < Gem.rubygems_version and Gem.rubygems_version.prerelease? and - Gem::Version.new('2.0.0.rc.2') != Gem.rubygems_version then + Gem::Version.new('2.0.0.rc.2') != Gem.rubygems_version alert_error <<-ERROR You are using a beta release of RubyGems (#{Gem::VERSION}) which is not allowed to push gems. Please downgrade or upgrade to a release version. @@ -96,7 +99,7 @@ def send_gem(name) gem_data = Gem::Package.new(name) - unless @host then + unless @host @host = gem_data.spec.metadata['default_gem_server'] end @@ -113,11 +116,10 @@ def send_gem(name) say "Pushing gem to #{@host || Gem.host}..." - response = rubygems_api_request(*args) do |request| - request.body = Gem.read_binary name - request.add_field "Content-Length", request.body.size - request.add_field "Content-Type", "application/octet-stream" - request.add_field "Authorization", api_key + response = send_push_request(name, args) + + if need_otp? response + response = send_push_request(name, args, true) end with_response response @@ -125,6 +127,16 @@ def send_gem(name) private + def send_push_request(name, args, use_otp = false) + rubygems_api_request(*args) do |request| + request.body = Gem.read_binary name + request.add_field "Content-Length", request.body.size + request.add_field "Content-Type", "application/octet-stream" + request.add_field "Authorization", api_key + request.add_field "OTP", options[:otp] if use_otp + end + end + def get_hosts_for(name) gem_metadata = Gem::Package.new(name).spec.metadata @@ -134,4 +146,3 @@ def get_hosts_for(name) ] end end - diff --git a/lib/ruby/stdlib/rubygems/commands/query_command.rb b/lib/ruby/stdlib/rubygems/commands/query_command.rb index 4624e5a1e917..d947d588ef0e 100644 --- a/lib/ruby/stdlib/rubygems/commands/query_command.rb +++ b/lib/ruby/stdlib/rubygems/commands/query_command.rb @@ -91,8 +91,8 @@ def execute prerelease = options[:prerelease] - unless options[:installed].nil? then - if no_name then + unless options[:installed].nil? + if no_name alert_error "You must specify a gem name" exit_code |= 4 elsif name.count > 1 @@ -102,7 +102,7 @@ def execute installed = installed? name.first, options[:version] installed = !installed unless options[:installed] - if installed then + if installed say "true" else say "false" @@ -119,8 +119,8 @@ def execute private - def display_header type - if (ui.outs.tty? and Gem.configuration.verbose) or both? then + def display_header(type) + if (ui.outs.tty? and Gem.configuration.verbose) or both? say say "*** #{type} GEMS ***" say @@ -128,14 +128,14 @@ def display_header type end #Guts of original execute - def show_gems name, prerelease + def show_gems(name, prerelease) req = Gem::Requirement.default # TODO: deprecate for real dep = Gem::Deprecate.skip_during { Gem::Dependency.new name, req } dep.prerelease = prerelease - if local? then - if prerelease and not both? then + if local? + if prerelease and not both? alert_warning "prereleases are always shown locally" end @@ -152,7 +152,7 @@ def show_gems name, prerelease output_query_results spec_tuples end - if remote? then + if remote? display_header 'REMOTE' fetcher = Gem::SpecFetcher.fetcher @@ -205,7 +205,7 @@ def output_query_results(spec_tuples) say output.join(options[:details] ? "\n\n" : "\n") end - def output_versions output, versions + def output_versions(output, versions) versions.each do |gem_name, matching_tuples| matching_tuples = matching_tuples.sort_by { |n,_| n.version }.reverse @@ -218,7 +218,7 @@ def output_versions output, versions seen = {} matching_tuples.delete_if do |n,_| - if seen[n.version] then + if seen[n.version] true else seen[n.version] = true @@ -230,7 +230,7 @@ def output_versions output, versions end end - def entry_details entry, detail_tuple, specs, platforms + def entry_details(entry, detail_tuple, specs, platforms) return unless options[:details] name_tuple, spec = detail_tuple @@ -247,11 +247,11 @@ def entry_details entry, detail_tuple, specs, platforms spec_summary entry, spec end - def entry_versions entry, name_tuples, platforms, specs + def entry_versions(entry, name_tuples, platforms, specs) return unless options[:versions] list = - if platforms.empty? or options[:details] then + if platforms.empty? or options[:details] name_tuples.map { |n| n.version }.uniq else platforms.sort.reverse.map do |version, pls| @@ -264,7 +264,7 @@ def entry_versions entry, name_tuples, platforms, specs out = "default: #{out}" if default end - if pls != [Gem::Platform::RUBY] then + if pls != [Gem::Platform::RUBY] platform_list = [pls.delete(Gem::Platform::RUBY), *pls.sort].compact out = platform_list.unshift(out).join(' ') end @@ -276,7 +276,7 @@ def entry_versions entry, name_tuples, platforms, specs entry << " (#{list.join ', '})" end - def make_entry entry_tuples, platforms + def make_entry(entry_tuples, platforms) detail_tuple = entry_tuples.first name_tuples, specs = entry_tuples.flatten.partition do |item| @@ -291,19 +291,19 @@ def make_entry entry_tuples, platforms entry.join end - def spec_authors entry, spec + def spec_authors(entry, spec) authors = "Author#{spec.authors.length > 1 ? 's' : ''}: ".dup authors << spec.authors.join(', ') entry << format_text(authors, 68, 4) end - def spec_homepage entry, spec + def spec_homepage(entry, spec) return if spec.homepage.nil? or spec.homepage.empty? entry << "\n" << format_text("Homepage: #{spec.homepage}", 68, 4) end - def spec_license entry, spec + def spec_license(entry, spec) return if spec.license.nil? or spec.license.empty? licenses = "License#{spec.licenses.length > 1 ? 's' : ''}: ".dup @@ -311,10 +311,10 @@ def spec_license entry, spec entry << "\n" << format_text(licenses, 68, 4) end - def spec_loaded_from entry, spec, specs + def spec_loaded_from(entry, spec, specs) return unless spec.loaded_from - if specs.length == 1 then + if specs.length == 1 default = spec.default_gem? ? ' (default)' : nil entry << "\n" << " Installed at#{default}: #{spec.base_dir}" else @@ -328,14 +328,14 @@ def spec_loaded_from entry, spec, specs end end - def spec_platforms entry, platforms + def spec_platforms(entry, platforms) non_ruby = platforms.any? do |_, pls| pls.any? { |pl| pl != Gem::Platform::RUBY } end return unless non_ruby - if platforms.length == 1 then + if platforms.length == 1 title = platforms.values.length == 1 ? 'Platform' : 'Platforms' entry << " #{title}: #{platforms.values.sort.join ', '}\n" else @@ -351,7 +351,7 @@ def spec_platforms entry, platforms end end - def spec_summary entry, spec + def spec_summary(entry, spec) summary = truncate_text(spec.summary, "the summary for #{spec.full_name}") entry << "\n\n" << format_text(summary, 68, 4) end diff --git a/lib/ruby/stdlib/rubygems/commands/rdoc_command.rb b/lib/ruby/stdlib/rubygems/commands/rdoc_command.rb index 6992040dcaee..5f8b72eb7ac8 100644 --- a/lib/ruby/stdlib/rubygems/commands/rdoc_command.rb +++ b/lib/ruby/stdlib/rubygems/commands/rdoc_command.rb @@ -60,7 +60,7 @@ def usage # :nodoc: end def execute - specs = if options[:all] then + specs = if options[:all] Gem::Specification.to_a else get_all_gem_names.map do |name| @@ -68,7 +68,7 @@ def execute end.flatten.uniq end - if specs.empty? then + if specs.empty? alert_error 'No matching gems found' terminate_interaction 1 end @@ -78,7 +78,7 @@ def execute doc.force = options[:overwrite] - if options[:overwrite] then + if options[:overwrite] FileUtils.rm_rf File.join(spec.doc_dir, 'ri') FileUtils.rm_rf File.join(spec.doc_dir, 'rdoc') end @@ -94,4 +94,3 @@ def execute end end - diff --git a/lib/ruby/stdlib/rubygems/commands/search_command.rb b/lib/ruby/stdlib/rubygems/commands/search_command.rb index 933436d84d8e..c9cb1f2d43b0 100644 --- a/lib/ruby/stdlib/rubygems/commands/search_command.rb +++ b/lib/ruby/stdlib/rubygems/commands/search_command.rb @@ -38,4 +38,3 @@ def usage # :nodoc: end end - diff --git a/lib/ruby/stdlib/rubygems/commands/server_command.rb b/lib/ruby/stdlib/rubygems/commands/server_command.rb index 245156d50dc4..e91a8e5176c2 100644 --- a/lib/ruby/stdlib/rubygems/commands/server_command.rb +++ b/lib/ruby/stdlib/rubygems/commands/server_command.rb @@ -9,7 +9,7 @@ def initialize :port => 8808, :gemdir => [], :daemon => false OptionParser.accept :Port do |port| - if port =~ /\A\d+\z/ then + if port =~ /\A\d+\z/ port = Integer port raise OptionParser::InvalidArgument, "#{port}: not a port number" if port > 65535 @@ -84,4 +84,3 @@ def execute end end - diff --git a/lib/ruby/stdlib/rubygems/commands/setup_command.rb b/lib/ruby/stdlib/rubygems/commands/setup_command.rb index b6690c9d54c6..c9c00b5be400 100644 --- a/lib/ruby/stdlib/rubygems/commands/setup_command.rb +++ b/lib/ruby/stdlib/rubygems/commands/setup_command.rb @@ -6,8 +6,10 @@ # RubyGems checkout or tarball. class Gem::Commands::SetupCommand < Gem::Command - HISTORY_HEADER = /^===\s*[\d.]+\s*\/\s*\d{4}-\d{2}-\d{2}\s*$/ - VERSION_MATCHER = /^===\s*([\d.]+)\s*\/\s*\d{4}-\d{2}-\d{2}\s*$/ + HISTORY_HEADER = /^===\s*[\d.a-zA-Z]+\s*\/\s*\d{4}-\d{2}-\d{2}\s*$/.freeze + VERSION_MATCHER = /^===\s*([\d.a-zA-Z]+)\s*\/\s*\d{4}-\d{2}-\d{2}\s*$/.freeze + + ENV_PATHS = %w[/usr/bin/env /bin/env].freeze def initialize require 'tmpdir' @@ -60,7 +62,7 @@ def initialize add_option '--[no-]rdoc', 'Generate RDoc documentation for RubyGems' do |value, options| - if value then + if value options[:document] << 'rdoc' else options[:document].delete 'rdoc' @@ -71,7 +73,7 @@ def initialize add_option '--[no-]ri', 'Generate RI documentation for RubyGems' do |value, options| - if value then + if value options[:document] << 'ri' else options[:document].delete 'ri' @@ -83,7 +85,13 @@ def initialize add_option '--[no-]regenerate-binstubs', 'Regenerate gem binstubs' do |value, options| options[:regenerate_binstubs] = value - end + end + + add_option('-E', '--[no-]env-shebang', + 'Rewrite executables with a shebang', + 'of /usr/bin/env') do |value, options| + options[:env_shebang] = value + end @verbose = nil end @@ -91,7 +99,7 @@ def initialize def check_ruby_version required_version = Gem::Requirement.new '>= 1.8.7' - unless required_version.satisfied_by? Gem.ruby_version then + unless required_version.satisfied_by? Gem.ruby_version alert_error "Expected Ruby version #{required_version}, is #{Gem.ruby_version}" terminate_interaction 1 end @@ -119,12 +127,19 @@ def description # :nodoc: EOF end + module MakeDirs + def mkdir_p(path, *opts) + super + (@mkdirs ||= []) << path + end + end + def execute @verbose = Gem.configuration.really_verbose install_destdir = options[:destdir] - unless install_destdir.empty? then + unless install_destdir.empty? ENV['GEM_HOME'] ||= File.join(install_destdir, Gem.default_dir.gsub(/^[a-zA-Z]:/, '')) end @@ -132,11 +147,12 @@ def execute check_ruby_version require 'fileutils' - if Gem.configuration.really_verbose then + if Gem.configuration.really_verbose extend FileUtils::Verbose else extend FileUtils end + extend MakeDirs lib_dir, bin_dir = make_destination_dirs install_destdir @@ -150,6 +166,11 @@ def execute install_default_bundler_gem + if mode = options[:dir_mode] + @mkdirs.uniq! + File.chmod(mode, @mkdirs) + end + say "RubyGems #{Gem::VERSION} installed" regenerate_binstubs if options[:regenerate_binstubs] @@ -159,7 +180,7 @@ def execute documentation_success = install_rdoc say - if @verbose then + if @verbose say "-" * 78 say end @@ -180,14 +201,14 @@ def execute say @bin_file_names.map { |name| "\t#{name}\n" } say - unless @bin_file_names.grep(/#{File::SEPARATOR}gem$/) then + unless @bin_file_names.grep(/#{File::SEPARATOR}gem$/) say "If `gem` was installed by a previous RubyGems installation, you may need" say "to remove it by hand." say end if documentation_success - if options[:document].include? 'rdoc' then + if options[:document].include? 'rdoc' say "Rdoc documentation was installed. You may now invoke:" say " gem server" say "and then peruse beautifully formatted documentation for your gems" @@ -198,7 +219,7 @@ def execute say end - if options[:document].include? 'ri' then + if options[:document].include? 'ri' 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 " ri Classname" @@ -216,6 +237,8 @@ def execute def install_executables(bin_dir) @bin_file_names = [] + prog_mode = options[:prog_mode] || 0755 + executables = { 'gem' => 'bin' } executables['bundler'] = 'bundler/exe' if Gem::USE_BUNDLER_FOR_GEMDEPS executables.each do |tool, path| @@ -227,7 +250,7 @@ def install_executables(bin_dir) bin_files -= %w[update_rubygems bundler bundle_ruby] bin_files.each do |bin_file| - bin_file_formatted = if options[:format_executable] then + bin_file_formatted = if options[:format_executable] Gem.default_exec_format % bin_file else bin_file @@ -238,13 +261,13 @@ def install_executables(bin_dir) begin bin = File.readlines bin_file - bin[0] = "#!#{Gem.ruby}\n" + bin[0] = shebang File.open bin_tmp_file, 'w' do |fp| fp.puts bin.join end - install bin_tmp_file, dest_file, :mode => 0755 + install bin_tmp_file, dest_file, :mode => prog_mode @bin_file_names << dest_file ensure rm bin_tmp_file @@ -266,7 +289,7 @@ def install_executables(bin_dir) TEXT end - install bin_cmd_file, "#{dest_file}.bat", :mode => 0755 + install bin_cmd_file, "#{dest_file}.bat", :mode => prog_mode ensure rm bin_cmd_file end @@ -275,12 +298,24 @@ def install_executables(bin_dir) end end - def install_file file, dest_dir + def shebang + if options[:env_shebang] + ruby_name = RbConfig::CONFIG['ruby_install_name'] + @env_path ||= ENV_PATHS.find {|env_path| File.executable? env_path } + "#!#{@env_path} #{ruby_name}\n" + else + "#!#{Gem.ruby}\n" + end + end + + def install_file(file, dest_dir) dest_file = File.join dest_dir, file dest_dir = File.dirname dest_file - mkdir_p dest_dir unless File.directory? dest_dir + unless File.directory? dest_dir + mkdir_p dest_dir, :mode => 0755 + end - install file, dest_file, :mode => 0644 + install file, dest_file, :mode => options[:data_mode] || 0644 end def install_lib(lib_dir) @@ -319,7 +354,7 @@ def install_rdoc if File.writable? gem_doc_dir and (not File.exist? rubygems_doc_dir or - File.writable? rubygems_doc_dir) then + File.writable? rubygems_doc_dir) say "Removing old RubyGems RDoc and ri" if @verbose Dir[File.join(Gem.dir, 'doc', 'rubygems-[0-9]*')].each do |dir| rm_rf dir @@ -339,7 +374,7 @@ def fake_spec.full_gem_path rdoc.generate return true - elsif @verbose then + elsif @verbose say "Skipping RDoc generation, #{gem_doc_dir} not writable" say "Set the GEM_HOME environment variable if you want RDoc generated" end @@ -352,7 +387,7 @@ def install_default_bundler_gem specs_dir = Gem::Specification.default_specifications_dir specs_dir = File.join(options[:destdir], specs_dir) unless Gem.win_platform? - mkdir_p specs_dir + mkdir_p specs_dir, :mode => 0755 # Workaround for non-git environment. gemspec = File.open('bundler/bundler.gemspec', 'rb'){|f| f.read.gsub(/`git ls-files -z`/, "''") } @@ -387,7 +422,7 @@ def install_default_bundler_gem bundler_bin_dir = bundler_spec.bin_dir bundler_bin_dir = File.join(options[:destdir], bundler_bin_dir) unless Gem.win_platform? - mkdir_p bundler_bin_dir + mkdir_p bundler_bin_dir, :mode => 0755 bundler_spec.executables.each do |e| cp File.join("bundler", bundler_spec.bindir, e), File.join(bundler_bin_dir, e) end @@ -411,8 +446,8 @@ def make_destination_dirs(install_destdir) lib_dir, bin_dir = generate_default_dirs(install_destdir) end - mkdir_p lib_dir - mkdir_p bin_dir + mkdir_p lib_dir, :mode => 0755 + mkdir_p bin_dir, :mode => 0755 return lib_dir, bin_dir end @@ -421,7 +456,7 @@ def generate_default_dirs(install_destdir) prefix = options[:prefix] site_or_vendor = options[:site_or_vendor] - if prefix.empty? then + if prefix.empty? lib_dir = RbConfig::CONFIG[site_or_vendor] bin_dir = RbConfig::CONFIG['bindir'] else @@ -432,16 +467,16 @@ def generate_default_dirs(install_destdir) # just in case Apple and RubyGems don't get this patched up proper. (prefix == RbConfig::CONFIG['libdir'] or # this one is important - prefix == File.join(RbConfig::CONFIG['libdir'], 'ruby')) then - lib_dir = RbConfig::CONFIG[site_or_vendor] - bin_dir = RbConfig::CONFIG['bindir'] + prefix == File.join(RbConfig::CONFIG['libdir'], 'ruby')) + lib_dir = RbConfig::CONFIG[site_or_vendor] + bin_dir = RbConfig::CONFIG['bindir'] else lib_dir = File.join prefix, 'lib' bin_dir = File.join prefix, 'bin' end end - unless install_destdir.empty? then + unless install_destdir.empty? lib_dir = File.join install_destdir, lib_dir.gsub(/^[a-zA-Z]:/, '') bin_dir = File.join install_destdir, bin_dir.gsub(/^[a-zA-Z]:/, '') end @@ -449,13 +484,13 @@ def generate_default_dirs(install_destdir) [lib_dir, bin_dir] end - def pem_files_in dir + def pem_files_in(dir) Dir.chdir dir do Dir[File.join('**', '*pem')] end end - def rb_files_in dir + def rb_files_in(dir) Dir.chdir dir do Dir[File.join('**', '*rb')] end @@ -470,7 +505,7 @@ def template_files end # for cleanup old bundler files - def template_files_in dir + def template_files_in(dir) Dir.chdir dir do (Dir[File.join('templates', '**', '{*,.*}')]). select{|f| !File.directory?(f)} @@ -509,7 +544,7 @@ def remove_old_bin_files(bin_dir) end end - def remove_old_lib_files lib_dir + def remove_old_lib_files(lib_dir) 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| @@ -540,11 +575,10 @@ def show_release_notes release_notes = File.join Dir.pwd, 'History.txt' release_notes = - if File.exist? release_notes then + if File.exist? release_notes history = File.read release_notes - history.force_encoding Encoding::UTF_8 if - Object.const_defined? :Encoding + history.force_encoding Encoding::UTF_8 history = history.sub(/^# coding:.*?(?=^=)/m, '') @@ -582,8 +616,14 @@ def uninstall_old_gemcutter def regenerate_binstubs require "rubygems/commands/pristine_command" say "Regenerating binstubs" + + args = %w[--all --only-executables --silent] + if options[:env_shebang] + args << "--env-shebang" + end + command = Gem::Commands::PristineCommand.new - command.invoke(*%w[--all --only-executables --silent]) + command.invoke(*args) end end diff --git a/lib/ruby/stdlib/rubygems/commands/signin_command.rb b/lib/ruby/stdlib/rubygems/commands/signin_command.rb index 6556db5a89d8..0d527fc33980 100644 --- a/lib/ruby/stdlib/rubygems/commands/signin_command.rb +++ b/lib/ruby/stdlib/rubygems/commands/signin_command.rb @@ -10,9 +10,10 @@ def initialize 'It defaults to https://rubygems.org' add_option('--host HOST', 'Push to another gemcutter-compatible host') do |value, options| - options[:host] = value + options[:host] = value end + add_otp_option end def description # :nodoc: diff --git a/lib/ruby/stdlib/rubygems/commands/signout_command.rb b/lib/ruby/stdlib/rubygems/commands/signout_command.rb index 2452e8cae1ca..2d7329c59090 100644 --- a/lib/ruby/stdlib/rubygems/commands/signout_command.rb +++ b/lib/ruby/stdlib/rubygems/commands/signout_command.rb @@ -19,9 +19,9 @@ def usage # :nodoc: def execute credentials_path = Gem.configuration.credentials_path - if !File.exist?(credentials_path) then + if !File.exist?(credentials_path) alert_error 'You are not currently signed in.' - elsif !File.writable?(credentials_path) then + elsif !File.writable?(credentials_path) alert_error "File '#{Gem.configuration.credentials_path}' is read-only."\ ' Please make sure it is writable.' else diff --git a/lib/ruby/stdlib/rubygems/commands/sources_command.rb b/lib/ruby/stdlib/rubygems/commands/sources_command.rb index 7e46963a4c33..af145fd7b4b3 100644 --- a/lib/ruby/stdlib/rubygems/commands/sources_command.rb +++ b/lib/ruby/stdlib/rubygems/commands/sources_command.rb @@ -38,13 +38,13 @@ def initialize add_proxy_option end - def add_source source_uri # :nodoc: + def add_source(source_uri) # :nodoc: check_rubygems_https source_uri source = Gem::Source.new source_uri begin - if Gem.sources.include? source then + if Gem.sources.include? source say "source #{source_uri} already present in the cache" else source.load_specs :released @@ -62,11 +62,11 @@ def add_source source_uri # :nodoc: end end - def check_rubygems_https source_uri # :nodoc: + def check_rubygems_https(source_uri) # :nodoc: uri = URI source_uri if uri.scheme and uri.scheme.downcase == 'http' and - uri.host.downcase == 'rubygems.org' then + uri.host.downcase == 'rubygems.org' question = <<-QUESTION.chomp https://rubygems.org is recommended for security over #{uri} @@ -81,10 +81,10 @@ def clear_all # :nodoc: path = Gem.spec_cache_dir FileUtils.rm_rf path - unless File.exist? path then + unless File.exist? path say "*** Removed specs cache ***" else - unless File.writable? path then + unless File.writable? path say "*** Unable to remove source cache (write protected) ***" else say "*** Unable to remove source cache ***" @@ -175,8 +175,8 @@ def execute list if list? end - def remove_source source_uri # :nodoc: - unless Gem.sources.include? source_uri then + def remove_source(source_uri) # :nodoc: + unless Gem.sources.include? source_uri say "source #{source_uri} not present in cache" else Gem.sources.delete source_uri @@ -195,12 +195,12 @@ def update # :nodoc: say "source cache successfully updated" end - def remove_cache_file desc, path # :nodoc: + def remove_cache_file(desc, path) # :nodoc: FileUtils.rm_rf path - if not File.exist?(path) then + if not File.exist?(path) say "*** Removed #{desc} source cache ***" - elsif not File.writable?(path) then + elsif not File.writable?(path) say "*** Unable to remove #{desc} source cache (write protected) ***" else say "*** Unable to remove #{desc} source cache ***" @@ -208,4 +208,3 @@ def remove_cache_file desc, path # :nodoc: end end - diff --git a/lib/ruby/stdlib/rubygems/commands/specification_command.rb b/lib/ruby/stdlib/rubygems/commands/specification_command.rb index ad8840adc2bc..56b9371686ba 100644 --- a/lib/ruby/stdlib/rubygems/commands/specification_command.rb +++ b/lib/ruby/stdlib/rubygems/commands/specification_command.rb @@ -75,7 +75,7 @@ def execute specs = [] gem = options[:args].shift - unless gem then + unless gem raise Gem::CommandLineError, "Please specify a gem name or file on the command line" end @@ -105,29 +105,29 @@ def execute raise Gem::CommandLineError, "--ruby and FIELD are mutually exclusive" if field and options[:format] == :ruby - if local? then - if File.exist? gem then + if local? + if File.exist? gem specs << Gem::Package.new(gem).spec rescue nil end - if specs.empty? then + if specs.empty? specs.push(*dep.matching_specs) end end - if remote? then + if remote? dep.prerelease = options[:prerelease] found, _ = Gem::SpecFetcher.fetcher.spec_for_dependency dep specs.push(*found.map { |spec,| spec }) end - if specs.empty? then + if specs.empty? alert_error "No gem matching '#{dep}' found" terminate_interaction 1 end - unless options[:all] then + unless options[:all] specs = [specs.max_by { |s| s.version }] end diff --git a/lib/ruby/stdlib/rubygems/commands/uninstall_command.rb b/lib/ruby/stdlib/rubygems/commands/uninstall_command.rb index f8dedef93093..6c5c8f0be9dc 100644 --- a/lib/ruby/stdlib/rubygems/commands/uninstall_command.rb +++ b/lib/ruby/stdlib/rubygems/commands/uninstall_command.rb @@ -20,7 +20,7 @@ def initialize add_option('-a', '--[no-]all', 'Uninstall all matching versions' - ) do |value, options| + ) do |value, options| options[:all] = value end @@ -81,7 +81,7 @@ def initialize add_option('--vendor', 'Uninstall gem from the vendor directory.', 'Only for use by gem repackagers.') do |value, options| - unless Gem.vendor_dir then + unless Gem.vendor_dir raise OptionParser::InvalidOption.new 'your platform is not supported' end @@ -114,10 +114,21 @@ def usage # :nodoc: "#{program_name} GEMNAME [GEMNAME ...]" end + def check_version # :nodoc: + if options[:version] != Gem::Requirement.default and + get_all_gem_names.size > 1 + alert_error "Can't use --version with multiple gems. You can specify multiple gems with" \ + " version requirements using `gem uninstall 'my_gem:1.0.0' 'my_other_gem:~>2.0.0'`" + terminate_interaction 1 + end + end + def execute - if options[:all] and not options[:args].empty? then + check_version + + if options[:all] and not options[:args].empty? uninstall_specific - elsif options[:all] then + elsif options[:all] uninstall_all else uninstall_specific @@ -129,11 +140,7 @@ def uninstall_all specs.each do |spec| options[:version] = spec.version - - begin - Gem::Uninstaller.new(spec.name, options).uninstall - rescue Gem::InstallError - end + uninstall_gem spec.name end alert "Uninstalled all gems in #{options[:install_dir]}" @@ -141,9 +148,13 @@ def uninstall_all def uninstall_specific deplist = Gem::DependencyList.new + original_gem_version = {} + + get_all_gem_names_and_versions.each do |name, version| + original_gem_version[name] = version || options[:version] + + gem_specs = Gem::Specification.find_all_by_name(name, original_gem_version[name]) - get_all_gem_names.uniq.each do |name| - 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 @@ -152,15 +163,36 @@ def uninstall_specific deps = deplist.strongly_connected_components.flatten.reverse - deps.map(&:name).uniq.each do |gem_name| - begin - Gem::Uninstaller.new(gem_name, options).uninstall - rescue Gem::GemNotInHomeException => e - spec = e.spec - alert("In order to remove #{spec.name}, please execute:\n" + - "\tgem uninstall #{spec.name} --install-dir=#{spec.installation_path}") + gems_to_uninstall = {} + + deps.each do |dep| + unless gems_to_uninstall[dep.name] + gems_to_uninstall[dep.name] = true + + unless original_gem_version[dep.name] == Gem::Requirement.default + options[:version] = dep.version + end + + uninstall_gem(dep.name) end end end + def uninstall_gem(gem_name) + uninstall(gem_name) + rescue Gem::GemNotInHomeException => e + spec = e.spec + alert("In order to remove #{spec.name}, please execute:\n" + + "\tgem uninstall #{spec.name} --install-dir=#{spec.installation_path}") + rescue Gem::UninstallError => e + spec = e.spec + alert_error("Error: unable to successfully uninstall '#{spec.name}' which is " + + "located at '#{spec.full_gem_path}'. This is most likely because" + + "the current user does not have the appropriate permissions") + terminate_interaction 1 + end + + def uninstall(gem_name) + Gem::Uninstaller.new(gem_name, options).uninstall + end end diff --git a/lib/ruby/stdlib/rubygems/commands/unpack_command.rb b/lib/ruby/stdlib/rubygems/commands/unpack_command.rb index bdcfd524f142..4a1bd8a0d6d5 100644 --- a/lib/ruby/stdlib/rubygems/commands/unpack_command.rb +++ b/lib/ruby/stdlib/rubygems/commands/unpack_command.rb @@ -79,22 +79,32 @@ def execute dependency = Gem::Dependency.new name, options[:version] path = get_path dependency - unless path then + unless path alert_error "Gem '#{name}' not installed nor fetchable." next end - if @options[:spec] then + if @options[:spec] spec, metadata = get_metadata path, security_policy - if metadata.nil? then + if metadata.nil? alert_error "--spec is unsupported on '#{name}' (old format gem)" next end spec_file = File.basename spec.spec_file - File.open spec_file, 'w' do |io| + FileUtils.mkdir_p @options[:target] if @options[:target] + + destination = begin + if @options[:target] + File.join @options[:target], spec_file + else + spec_file + end + end + + File.open destination, 'w' do |io| io.write metadata end else @@ -142,7 +152,7 @@ def find_in_cache(filename) # TODO: It just uses Gem.dir for now. What's an easy way to get the list of # source directories? - def get_path dependency + def get_path(dependency) return dependency.name if dependency.name =~ /\.gem$/i specs = dependency.matching_specs @@ -170,7 +180,7 @@ def get_path dependency #-- # TODO move to Gem::Package as #raw_spec or something - def get_metadata path, security_policy = nil + def get_metadata(path, security_policy = nil) format = Gem::Package.new path, security_policy spec = format.spec @@ -192,4 +202,3 @@ def get_metadata path, security_policy = nil end end - diff --git a/lib/ruby/stdlib/rubygems/commands/update_command.rb b/lib/ruby/stdlib/rubygems/commands/update_command.rb index 93ee60e1abd2..9ab3b80e96cd 100644 --- a/lib/ruby/stdlib/rubygems/commands/update_command.rb +++ b/lib/ruby/stdlib/rubygems/commands/update_command.rb @@ -68,8 +68,8 @@ def usage # :nodoc: "#{program_name} GEMNAME [GEMNAME ...]" end - def check_latest_rubygems version # :nodoc: - if Gem.rubygems_version == version then + def check_latest_rubygems(version) # :nodoc: + if Gem.rubygems_version == version say "Latest version already installed. Done." terminate_interaction end @@ -78,31 +78,40 @@ def check_latest_rubygems version # :nodoc: end def check_update_arguments # :nodoc: - unless options[:args].empty? then + unless options[:args].empty? alert_error "Gem names are not allowed with the --system option" terminate_interaction 1 end end def execute - - if options[:system] then + if options[:system] update_rubygems return end - say "Updating installed gems" - hig = highest_installed_gems gems_to_update = which_to_update hig, options[:args].uniq + if options[:explain] + say "Gems to update:" + + gems_to_update.each do |(name, version)| + say " #{name}-#{version}" + end + + return + end + + say "Updating installed gems" + updated = update_gems gems_to_update updated_names = updated.map { |spec| spec.name } not_updated_names = options[:args].uniq - updated_names - if updated.empty? then + if updated.empty? say "Nothing to update" else say "Gems updated: #{updated_names.join(' ')}" @@ -110,7 +119,7 @@ def execute end end - def fetch_remote_gems spec # :nodoc: + def fetch_remote_gems(spec) # :nodoc: dependency = Gem::Dependency.new spec.name, "> #{spec.version}" dependency.prerelease = options[:prerelease] @@ -129,7 +138,7 @@ def highest_installed_gems # :nodoc: hig = {} # highest installed gems Gem::Specification.each do |spec| - if hig[spec.name].nil? or hig[spec.name].version < spec.version then + if hig[spec.name].nil? or hig[spec.name].version < spec.version hig[spec.name] = spec end end @@ -137,7 +146,7 @@ def highest_installed_gems # :nodoc: hig end - def highest_remote_version spec # :nodoc: + def highest_remote_version(spec) # :nodoc: spec_tuples = fetch_remote_gems spec matching_gems = spec_tuples.select do |g,_| @@ -151,7 +160,7 @@ def highest_remote_version spec # :nodoc: highest_remote_gem.first.version end - def install_rubygems version # :nodoc: + def install_rubygems(version) # :nodoc: args = update_rubygems_arguments update_dir = File.join Gem.dir, 'gems', "rubygems-update-#{version}" @@ -159,12 +168,8 @@ def install_rubygems version # :nodoc: Dir.chdir update_dir do say "Installing RubyGems #{version}" - # Make sure old rubygems isn't loaded - old = ENV["RUBYOPT"] - ENV.delete("RUBYOPT") if old - installed = system Gem.ruby, 'setup.rb', *args + installed = system Gem.ruby, '--disable-gems', 'setup.rb', *args say "RubyGems system software updated" if installed - ENV["RUBYOPT"] = old if old end end @@ -172,7 +177,7 @@ def rubygems_target_version version = options[:system] update_latest = version == true - if update_latest then + if update_latest version = Gem::Version.new Gem::VERSION requirement = Gem::Requirement.new ">= #{Gem::VERSION}" else @@ -191,7 +196,7 @@ def rubygems_target_version gems_to_update = which_to_update hig, options[:args], :system _, up_ver = gems_to_update.first - target = if update_latest then + target = if update_latest up_ver else version @@ -200,7 +205,7 @@ def rubygems_target_version return target, requirement end - def update_gem name, version = Gem::Requirement.default + def update_gem(name, version = Gem::Requirement.default) return if @updated.any? { |spec| spec.name == name } update_options = options.dup @@ -220,7 +225,7 @@ def update_gem name, version = Gem::Requirement.default end end - def update_gems gems_to_update + def update_gems(gems_to_update) gems_to_update.uniq.sort.each do |(name, version)| update_gem name, version end @@ -259,7 +264,7 @@ def update_rubygems_arguments # :nodoc: args end - def which_to_update highest_installed_gems, gem_names, system = false + def which_to_update(highest_installed_gems, gem_names, system = false) result = [] highest_installed_gems.each do |l_name, l_spec| @@ -268,7 +273,7 @@ def which_to_update highest_installed_gems, gem_names, system = false highest_remote_ver = highest_remote_version l_spec - if system or (l_spec.version < highest_remote_ver) then + if system or (l_spec.version < highest_remote_ver) result << [l_spec.name, [l_spec.version, highest_remote_ver].max] end end diff --git a/lib/ruby/stdlib/rubygems/commands/which_command.rb b/lib/ruby/stdlib/rubygems/commands/which_command.rb index 704d79fc60ca..0f88dd7123dd 100644 --- a/lib/ruby/stdlib/rubygems/commands/which_command.rb +++ b/lib/ruby/stdlib/rubygems/commands/which_command.rb @@ -44,21 +44,19 @@ def execute spec = Gem::Specification.find_by_path arg - if spec then - if options[:search_gems_first] then + if spec + if options[:search_gems_first] dirs = spec.full_require_paths + $LOAD_PATH else dirs = $LOAD_PATH + spec.full_require_paths end end - # TODO: this is totally redundant and stupid paths = find_paths arg, dirs - if paths.empty? then + if paths.empty? alert_error "Can't find Ruby library file or shared library #{arg}" - - found &&= false + found = false else say paths end @@ -73,7 +71,7 @@ def find_paths(package_name, dirs) dirs.each do |dir| Gem.suffixes.each do |ext| full_path = File.join dir, "#{package_name}#{ext}" - if File.exist? full_path and not File.directory? full_path then + if File.exist? full_path and not File.directory? full_path result << full_path return result unless options[:show_all] end @@ -88,4 +86,3 @@ def usage # :nodoc: end end - diff --git a/lib/ruby/stdlib/rubygems/commands/yank_command.rb b/lib/ruby/stdlib/rubygems/commands/yank_command.rb index ebf24e5c771e..d13b674b488a 100644 --- a/lib/ruby/stdlib/rubygems/commands/yank_command.rb +++ b/lib/ruby/stdlib/rubygems/commands/yank_command.rb @@ -51,7 +51,7 @@ def execute version = get_version_from_requirements(options[:version]) platform = get_platform_from_requirements(options) - if version then + if version yank_gem(version, platform) else say "A version argument is required: #{usage}" @@ -93,4 +93,3 @@ def get_platform_from_requirements(requirements) end end - diff --git a/lib/ruby/stdlib/rubygems/compatibility.rb b/lib/ruby/stdlib/rubygems/compatibility.rb index 2056b5b53a22..b4332eb9f166 100644 --- a/lib/ruby/stdlib/rubygems/compatibility.rb +++ b/lib/ruby/stdlib/rubygems/compatibility.rb @@ -9,26 +9,6 @@ # Ruby 1.9.x has introduced some things that are awkward, and we need to # support them, so we define some constants to use later. #++ -module Gem - # Only MRI 1.9.2 has the custom prelude. - GEM_PRELUDE_SUCKAGE = RUBY_VERSION =~ /^1\.9\.2/ and RUBY_ENGINE == "ruby" -end - -# Gem::QuickLoader exists in the gem prelude code in ruby 1.9.2 itself. -# We gotta get rid of it if it's there, before we do anything else. -if Gem::GEM_PRELUDE_SUCKAGE and defined?(Gem::QuickLoader) then - Gem::QuickLoader.remove - - $LOADED_FEATURES.delete Gem::QuickLoader.path_to_full_rubygems_library - - if path = $LOADED_FEATURES.find {|n| n.end_with? '/rubygems.rb'} then - raise LoadError, "another rubygems is already loaded from #{path}" - end - - class << Gem - remove_method :try_activate if Gem.respond_to?(:try_activate, true) - end -end module Gem RubyGemsVersion = VERSION @@ -42,7 +22,7 @@ module Gem EXEEXT RUBY_SO_NAME arch bindir datadir libdir ruby_install_name ruby_version rubylibprefix sitedir sitelibdir vendordir vendorlibdir rubylibdir - ] + ].freeze unless defined?(ConfigMap) ## diff --git a/lib/ruby/stdlib/rubygems/config_file.rb b/lib/ruby/stdlib/rubygems/config_file.rb index c0d19dbfc273..e02655fc5156 100644 --- a/lib/ruby/stdlib/rubygems/config_file.rb +++ b/lib/ruby/stdlib/rubygems/config_file.rb @@ -27,6 +27,7 @@ # +:backtrace+:: See #backtrace # +:sources+:: Sets Gem::sources # +:verbose+:: See #verbose +# +:concurrent_downloads+:: See #concurrent_downloads # # gemrc files may exist in various locations and are read and merged in # the following order: @@ -43,12 +44,14 @@ class Gem::ConfigFile DEFAULT_BULK_THRESHOLD = 1000 DEFAULT_VERBOSITY = true DEFAULT_UPDATE_SOURCES = true + DEFAULT_CONCURRENT_DOWNLOADS = 8 + DEFAULT_CERT_EXPIRATION_LENGTH_DAYS = 365 ## # For Ruby packagers to set configuration defaults. Set in # rubygems/defaults/operating_system.rb - OPERATING_SYSTEM_DEFAULTS = {} + OPERATING_SYSTEM_DEFAULTS = Gem.operating_system_defaults ## # For Ruby implementers to set configuration defaults. Set in @@ -63,26 +66,7 @@ class Gem::ConfigFile require "etc" Etc.sysconfdir rescue LoadError, NoMethodError - begin - # TODO: remove after we drop 1.8.7 and 1.9.1 - require 'Win32API' - - CSIDL_COMMON_APPDATA = 0x0023 - path = 0.chr * 260 - if RUBY_VERSION > '1.9' then - SHGetFolderPath = Win32API.new 'shell32', 'SHGetFolderPath', 'PLPLP', - 'L', :stdcall - SHGetFolderPath.call nil, CSIDL_COMMON_APPDATA, nil, 1, path - else - SHGetFolderPath = Win32API.new 'shell32', 'SHGetFolderPath', 'LLLLP', - 'L' - SHGetFolderPath.call 0, CSIDL_COMMON_APPDATA, 0, 1, path - end - - path.strip - rescue LoadError - RbConfig::CONFIG["sysconfdir"] || "/etc" - end + RbConfig::CONFIG["sysconfdir"] || "/etc" end # :startdoc: @@ -123,6 +107,11 @@ class Gem::ConfigFile attr_accessor :verbose + ## + # Number of gem downloads that should be performed concurrently. + + attr_accessor :concurrent_downloads + ## # True if we want to update the SourceInfoCache every time, false otherwise @@ -147,6 +136,11 @@ class Gem::ConfigFile # sources to look for gems attr_accessor :sources + ## + # Expiration length to sign a certificate + + attr_accessor :cert_expiration_length_days + ## # Path name of directory or file of openssl client certificate, used for remote https connection with client authentication @@ -180,12 +174,12 @@ def initialize(args) arg_list = [] args.each do |arg| - if need_config_file_name then + if need_config_file_name @config_file_name = arg need_config_file_name = false - elsif arg =~ /^--config-file=(.*)/ then + elsif arg =~ /^--config-file=(.*)/ @config_file_name = $1 - elsif arg =~ /^--config-file$/ then + elsif arg =~ /^--config-file$/ need_config_file_name = true else arg_list << arg @@ -196,6 +190,8 @@ def initialize(args) @bulk_threshold = DEFAULT_BULK_THRESHOLD @verbose = DEFAULT_VERBOSITY @update_sources = DEFAULT_UPDATE_SOURCES + @concurrent_downloads = DEFAULT_CONCURRENT_DOWNLOADS + @cert_expiration_length_days = DEFAULT_CERT_EXPIRATION_LENGTH_DAYS operating_system_config = Marshal.load Marshal.dump(OPERATING_SYSTEM_DEFAULTS) platform_config = Marshal.load Marshal.dump(PLATFORM_DEFAULTS) @@ -213,14 +209,15 @@ def initialize(args) end # HACK these override command-line args, which is bad - @backtrace = @hash[:backtrace] if @hash.key? :backtrace - @bulk_threshold = @hash[:bulk_threshold] if @hash.key? :bulk_threshold - @home = @hash[:gemhome] if @hash.key? :gemhome - @path = @hash[:gempath] if @hash.key? :gempath - @update_sources = @hash[:update_sources] if @hash.key? :update_sources - @verbose = @hash[:verbose] if @hash.key? :verbose - @disable_default_gem_server = @hash[:disable_default_gem_server] if @hash.key? :disable_default_gem_server - @sources = @hash[:sources] if @hash.key? :sources + @backtrace = @hash[:backtrace] if @hash.key? :backtrace + @bulk_threshold = @hash[:bulk_threshold] if @hash.key? :bulk_threshold + @home = @hash[:gemhome] if @hash.key? :gemhome + @path = @hash[:gempath] if @hash.key? :gempath + @update_sources = @hash[:update_sources] if @hash.key? :update_sources + @verbose = @hash[:verbose] if @hash.key? :verbose + @disable_default_gem_server = @hash[:disable_default_gem_server] if @hash.key? :disable_default_gem_server + @sources = @hash[:sources] if @hash.key? :sources + @cert_expiration_length_days = @hash[:cert_expiration_length_days] if @hash.key? :cert_expiration_length_days @ssl_verify_mode = @hash[:ssl_verify_mode] if @hash.key? :ssl_verify_mode @ssl_ca_cert = @hash[:ssl_ca_cert] if @hash.key? :ssl_ca_cert @@ -284,13 +281,13 @@ def credentials_path def load_api_keys check_credentials_permissions - @api_keys = if File.exist? credentials_path then + @api_keys = if File.exist? credentials_path load_file(credentials_path) else @hash end - if @api_keys.key? :rubygems_api_key then + if @api_keys.key? :rubygems_api_key @rubygems_api_key = @api_keys[:rubygems_api_key] @api_keys[:rubygems] = @api_keys.delete :rubygems_api_key unless @api_keys.key? :rubygems @@ -309,7 +306,7 @@ def rubygems_api_key ## # Sets the RubyGems.org API key to +api_key+ - def rubygems_api_key= api_key + def rubygems_api_key=(api_key) set_api_key :rubygems_api_key, api_key @rubygems_api_key = api_key @@ -318,7 +315,7 @@ def rubygems_api_key= api_key ## # Set a specific host's API key to +api_key+ - def set_api_key host, api_key + def set_api_key(host, api_key) check_credentials_permissions config = load_file(credentials_path).merge(host => api_key) @@ -434,6 +431,9 @@ def to_yaml # :nodoc: yaml_hash[:update_sources] = @hash.fetch(:update_sources, DEFAULT_UPDATE_SOURCES) yaml_hash[:verbose] = @hash.fetch(:verbose, DEFAULT_VERBOSITY) + yaml_hash[:concurrent_downloads] = + @hash.fetch(:concurrent_downloads, DEFAULT_CONCURRENT_DOWNLOADS) + yaml_hash[:ssl_verify_mode] = @hash[:ssl_verify_mode] if @hash.key? :ssl_verify_mode diff --git a/lib/ruby/stdlib/rubygems/core_ext/kernel_require.rb b/lib/ruby/stdlib/rubygems/core_ext/kernel_require.rb index e93cd7c7282b..3b7801161978 100644 --- a/lib/ruby/stdlib/rubygems/core_ext/kernel_require.rb +++ b/lib/ruby/stdlib/rubygems/core_ext/kernel_require.rb @@ -31,7 +31,7 @@ module Kernel # The normal require functionality of returning false if # that file has already been loaded is preserved. - def require path + def require(path) RUBYGEMS_ACTIVATION_MONITOR.enter path = path.to_path if path.respond_to? :to_path @@ -49,7 +49,7 @@ def require path # If there are no unresolved deps, then we can use just try # normal require handle loading a gem from the rescue below. - if Gem::Specification.unresolved_deps.empty? then + if Gem::Specification.unresolved_deps.empty? RUBYGEMS_ACTIVATION_MONITOR.exit return gem_original_require(path) end @@ -79,7 +79,7 @@ def require path # requested, then find_in_unresolved_tree will find d.rb in d because # it's a dependency of c. # - if found_specs.empty? then + if found_specs.empty? found_specs = Gem::Specification.find_in_unresolved_tree path found_specs.each do |found_spec| @@ -94,7 +94,7 @@ def require path # versions of the same gem names = found_specs.map(&:name).uniq - if names.size > 1 then + if names.size > 1 RUBYGEMS_ACTIVATION_MONITOR.exit raise Gem::LoadError, "#{path} found in multiple gems: #{names.join ', '}" end @@ -103,7 +103,7 @@ def require path # at the highest version. valid = found_specs.find { |s| !s.has_conflicts? } - unless valid then + unless valid le = Gem::LoadError.new "unable to find a version of '#{names.first}' to activate" le.name = names.first RUBYGEMS_ACTIVATION_MONITOR.exit @@ -120,7 +120,7 @@ def require path begin if load_error.message.start_with?("Could not find") or - (load_error.message.end_with?(path) and Gem.try_activate(path)) then + (load_error.message.end_with?(path) and Gem.try_activate(path)) require_again = true end ensure diff --git a/lib/ruby/stdlib/rubygems/core_ext/kernel_warn.rb b/lib/ruby/stdlib/rubygems/core_ext/kernel_warn.rb new file mode 100644 index 000000000000..3e531441edc3 --- /dev/null +++ b/lib/ruby/stdlib/rubygems/core_ext/kernel_warn.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +# `uplevel` keyword argument of Kernel#warn is available since ruby 2.5. +if RUBY_VERSION >= "2.5" + + module Kernel + path = "#{__dir__}/" # Frames to be skipped start with this path. + + # Suppress "method redefined" warning + original_warn = instance_method(:warn) + Module.new {define_method(:warn, original_warn)} + + original_warn = method(:warn) + + module_function define_method(:warn) {|*messages, uplevel: nil| + unless uplevel + return original_warn.call(*messages) + end + + # Ensure `uplevel` fits a `long` + uplevel, = [uplevel].pack("l!").unpack("l!") + + if uplevel >= 0 + start = 0 + while uplevel >= 0 + loc, = caller_locations(start, 1) + unless loc + # No more backtrace + start += uplevel + break + end + + start += 1 + + unless loc.path.start_with?(path) + # Non-rubygems frames + uplevel -= 1 + end + end + uplevel = start + end + original_warn.call(*messages, uplevel: uplevel) + } + end +end diff --git a/lib/ruby/stdlib/rubygems/defaults.rb b/lib/ruby/stdlib/rubygems/defaults.rb index 43d57fc80827..28b74a8c2f82 100644 --- a/lib/ruby/stdlib/rubygems/defaults.rb +++ b/lib/ruby/stdlib/rubygems/defaults.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true module Gem - DEFAULT_HOST = "https://rubygems.org" + DEFAULT_HOST = "https://rubygems.org".freeze @post_install_hooks ||= [] @done_installing_hooks ||= [] @@ -28,17 +28,17 @@ def self.default_spec_cache_dir # specified in the environment def self.default_dir - path = if defined? RUBY_FRAMEWORK_VERSION then + path = if defined? RUBY_FRAMEWORK_VERSION [ File.dirname(RbConfig::CONFIG['sitedir']), 'Gems', RbConfig::CONFIG['ruby_version'] ] - elsif RbConfig::CONFIG['rubylibprefix'] then + elsif RbConfig::CONFIG['rubylibprefix'] [ - RbConfig::CONFIG['rubylibprefix'], - 'gems', - RbConfig::CONFIG['ruby_version'] + RbConfig::CONFIG['rubylibprefix'], + 'gems', + RbConfig::CONFIG['ruby_version'] ] else [ @@ -59,7 +59,7 @@ def self.default_dir # By default, the binary extensions are located side by side with their # Ruby counterparts, therefore nil is returned - def self.default_ext_dir_for base_dir + def self.default_ext_dir_for(base_dir) nil end @@ -103,7 +103,7 @@ def self.default_path def self.default_exec_format exec_format = RbConfig::CONFIG['ruby_install_name'].sub('ruby', '%s') rescue '%s' - unless exec_format =~ /%s/ then + unless exec_format =~ /%s/ raise Gem::Exception, "[BUG] invalid exec_format #{exec_format.inspect}, no %s" end @@ -115,22 +115,15 @@ def self.default_exec_format # The default directory for binaries def self.default_bindir - if defined? RUBY_FRAMEWORK_VERSION then # mac framework support + if defined? RUBY_FRAMEWORK_VERSION # mac framework support '/usr/bin' else # generic install RbConfig::CONFIG['bindir'] end end - ## - # A wrapper around RUBY_ENGINE const that may not be defined - def self.ruby_engine - if defined? RUBY_ENGINE then - RUBY_ENGINE - else - 'ruby' - end + RUBY_ENGINE end ## @@ -165,7 +158,7 @@ def self.install_extension_in_lib # :nodoc: # Directory where vendor gems are installed. def self.vendor_dir # :nodoc: - if vendor_dir = ENV['GEM_VENDOR'] then + if vendor_dir = ENV['GEM_VENDOR'] return vendor_dir.dup end @@ -176,7 +169,26 @@ def self.vendor_dir # :nodoc: end ## - # Default options for gem commands. + # Default options for gem commands for Ruby packagers. + # + # The options here should be structured as an array of string "gem" + # command names as keys and a string of the default options as values. + # + # Example: + # + # def self.operating_system_defaults + # { + # 'install' => '--no-rdoc --no-ri --env-shebang', + # 'update' => '--no-rdoc --no-ri --env-shebang' + # } + # end + + def self.operating_system_defaults + {} + end + + ## + # Default options for gem commands for Ruby implementers. # # The options here should be structured as an array of string "gem" # command names as keys and a string of the default options as values. diff --git a/lib/ruby/stdlib/rubygems/dependency.rb b/lib/ruby/stdlib/rubygems/dependency.rb index 346566679039..33ba1968d10c 100644 --- a/lib/ruby/stdlib/rubygems/dependency.rb +++ b/lib/ruby/stdlib/rubygems/dependency.rb @@ -19,7 +19,7 @@ class Gem::Dependency TYPES = [ :development, :runtime, - ] + ].freeze ## # Dependency name or regular expression. @@ -36,7 +36,7 @@ class Gem::Dependency # argument can optionally be the dependency type, which defaults to # :runtime. - def initialize name, *requirements + def initialize(name, *requirements) case name when String then # ok when Regexp then @@ -76,7 +76,7 @@ def hash # :nodoc: end def inspect # :nodoc: - if prerelease? then + if prerelease? "<%s type=%p name=%p requirements=%p prerelease=ok>" % [self.class, self.type, self.name, requirement.to_s] else @@ -100,7 +100,7 @@ def latest_version? @requirement.none? end - def pretty_print q # :nodoc: + def pretty_print(q) # :nodoc: q.group 1, 'Gem::Dependency.new(', ')' do q.pp name q.text ',' @@ -152,7 +152,7 @@ def requirements_list end def to_s # :nodoc: - if type != :runtime then + if type != :runtime "#{name} (#{requirement}, #{type})" else "#{name} (#{requirement})" @@ -170,7 +170,7 @@ def runtime? @type == :runtime || !@type end - def == other # :nodoc: + def ==(other) # :nodoc: Gem::Dependency === other && self.name == other.name && self.type == other.type && @@ -180,7 +180,7 @@ def == other # :nodoc: ## # Dependencies are ordered by name. - def <=> other + def <=>(other) self.name <=> other.name end @@ -190,7 +190,7 @@ def <=> other # other has only an equal version requirement that satisfies this # dependency. - def =~ other + def =~(other) unless Gem::Dependency === other return unless other.respond_to?(:name) && other.respond_to?(:version) other = Gem::Dependency.new other.name, other.version @@ -222,7 +222,7 @@ def =~ other # NOTE: Unlike #matches_spec? this method does not return true when the # version is a prerelease version unless this is a prerelease dependency. - def match? obj, version=nil, allow_prerelease=false + def match?(obj, version=nil, allow_prerelease=false) if !version name = obj.name version = obj.version @@ -249,7 +249,7 @@ def match? obj, version=nil, allow_prerelease=false # returns true when +spec+ is a prerelease version even if this dependency # is not a prerelease dependency. - def matches_spec? spec + def matches_spec?(spec) return false unless name === spec.name return true if requirement.none? @@ -259,8 +259,8 @@ def matches_spec? spec ## # Merges the requirements of +other+ into this dependency - def merge other - unless name == other.name then + def merge(other) + unless name == other.name raise ArgumentError, "#{self} and #{other} have different names" end @@ -275,7 +275,7 @@ def merge other self.class.new name, self_req.as_list.concat(other_req.as_list) end - def matching_specs platform_only = false + def matching_specs(platform_only = false) env_req = Gem.env_requirement(name) matches = Gem::Specification.stubs_for(name).find_all { |spec| requirement.satisfied_by?(spec.version) && env_req.satisfied_by?(spec.version) @@ -304,7 +304,7 @@ def to_specs # TODO: check Gem.activated_spec[self.name] in case matches falls outside - if matches.empty? then + if matches.empty? specs = Gem::Specification.stubs_for name if specs.empty? diff --git a/lib/ruby/stdlib/rubygems/dependency_installer.rb b/lib/ruby/stdlib/rubygems/dependency_installer.rb index 879e08535941..3cbcf6a05bee 100644 --- a/lib/ruby/stdlib/rubygems/dependency_installer.rb +++ b/lib/ruby/stdlib/rubygems/dependency_installer.rb @@ -15,6 +15,7 @@ class Gem::DependencyInstaller include Gem::UserInteraction + extend Gem::Deprecate DEFAULT_OPTIONS = { # :nodoc: :env_shebang => false, @@ -41,15 +42,6 @@ class Gem::DependencyInstaller attr_reader :errors - ## - #-- - # TODO remove, no longer used - - 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 @@ -74,7 +66,7 @@ class Gem::DependencyInstaller # :wrappers:: See Gem::Installer::new # :build_args:: See Gem::Installer::new - def initialize options = {} + def initialize(options = {}) @only_install_dir = !!options[:install_dir] @install_dir = options[:install_dir] || Gem.dir @build_root = options[:build_root] @@ -97,6 +89,9 @@ def initialize options = {} @build_args = options[:build_args] @build_docs_in_background = options[:build_docs_in_background] @install_as_default = options[:install_as_default] + @dir_mode = options[:dir_mode] + @data_mode = options[:data_mode] + @prog_mode = options[:prog_mode] # Indicates that we should not try to update any deps unless # we absolutely must. @@ -115,7 +110,7 @@ def initialize options = {} #-- # TODO remove at RubyGems 4, no longer used - def add_found_dependencies to_do, dependency_list # :nodoc: + def add_found_dependencies(to_do, dependency_list) # :nodoc: seen = {} dependencies = Hash.new { |h, name| h[name] = Gem::Dependency.new name } @@ -169,8 +164,8 @@ def add_found_dependencies to_do, dependency_list # :nodoc: # Creates an AvailableSet to install from based on +dep_or_name+ and # +version+ - def available_set_for dep_or_name, version # :nodoc: - if String === dep_or_name then + def available_set_for(dep_or_name, version) # :nodoc: + if String === dep_or_name find_spec_by_name_and_version dep_or_name, version, @prerelease else dep = dep_or_name.dup @@ -203,7 +198,7 @@ def consider_remote? # sources. Gems are sorted with newer gems preferred over older gems, and # local gems preferred over remote gems. - def find_gems_with_sources dep, best_only=false # :nodoc: + def find_gems_with_sources(dep, best_only=false) # :nodoc: set = Gem::AvailableSet.new if consider_local? @@ -218,9 +213,8 @@ def find_gems_with_sources dep, best_only=false # :nodoc: if consider_remote? begin - # TODO this is pulled from #spec_for_dependency to allow + # This is pulled from #spec_for_dependency to allow # us to filter tuples before fetching specs. - # tuples, errors = Gem::SpecFetcher.fetcher.search_for_dependency dep if best_only && !tuples.empty? @@ -277,16 +271,16 @@ def find_gems_with_sources dep, best_only=false # :nodoc: # +version+. Returns an Array of specs and sources required for # installation of the gem. - def find_spec_by_name_and_version gem_name, + def find_spec_by_name_and_version(gem_name, version = Gem::Requirement.default, - prerelease = false + prerelease = false) set = Gem::AvailableSet.new if consider_local? - if gem_name =~ /\.gem$/ and File.file? gem_name then + if gem_name =~ /\.gem$/ and File.file? gem_name src = Gem::Source::SpecificFile.new(gem_name) set.add src.spec, src - elsif gem_name =~ /\.gem$/ then + elsif gem_name =~ /\.gem$/ Dir[gem_name].each do |name| begin src = Gem::Source::SpecificFile.new name @@ -346,7 +340,7 @@ def gather_dependencies # :nodoc: Gem::Specification.include?(spec) } - unless dependency_list.ok? or @ignore_dependencies or @force then + unless dependency_list.ok? or @ignore_dependencies or @force reason = dependency_list.why_not_ok?.map { |k,v| "#{k} requires #{v.join(", ")}" }.join("; ") @@ -357,7 +351,7 @@ def gather_dependencies # :nodoc: end deprecate :gather_dependencies, :none, 2018, 12 - def in_background what # :nodoc: + def in_background(what) # :nodoc: fork_happened = false if @build_docs_in_background and Process.respond_to?(:fork) begin @@ -386,7 +380,7 @@ def in_background what # :nodoc: # c-1.a, b-1 and a-1.a will be installed. b-1.a will need to be installed # separately. - def install dep_or_name, version = Gem::Requirement.default + def install(dep_or_name, version = Gem::Requirement.default) request_set = resolve_dependencies dep_or_name, version @installed_gems = [] @@ -404,7 +398,10 @@ def install dep_or_name, version = Gem::Requirement.default :user_install => @user_install, :wrappers => @wrappers, :build_root => @build_root, - :install_as_default => @install_as_default + :install_as_default => @install_as_default, + :dir_mode => @dir_mode, + :data_mode => @data_mode, + :prog_mode => @prog_mode, } options[:install_dir] = @install_dir if @only_install_dir @@ -427,16 +424,16 @@ def install dep_or_name, version = Gem::Requirement.default end def install_development_deps # :nodoc: - if @development and @dev_shallow then + if @development and @dev_shallow :shallow - elsif @development then + elsif @development :all else :none end end - def resolve_dependencies dep_or_name, version # :nodoc: + def resolve_dependencies(dep_or_name, version) # :nodoc: request_set = Gem::RequestSet.new request_set.development = @development request_set.development_shallow = @dev_shallow @@ -448,11 +445,11 @@ def resolve_dependencies dep_or_name, version # :nodoc: installer_set.ignore_installed = @only_install_dir if consider_local? - if dep_or_name =~ /\.gem$/ and File.file? dep_or_name then + if dep_or_name =~ /\.gem$/ and File.file? dep_or_name src = Gem::Source::SpecificFile.new dep_or_name installer_set.add_local dep_or_name, src.spec, src version = src.spec.version if version == Gem::Requirement.default - elsif dep_or_name =~ /\.gem$/ then + elsif dep_or_name =~ /\.gem$/ Dir[dep_or_name].each do |name| begin src = Gem::Source::SpecificFile.new name @@ -460,14 +457,14 @@ def resolve_dependencies dep_or_name, version # :nodoc: rescue Gem::Package::FormatError end end - # else This is a dependency. InstallerSet handles this case + # else This is a dependency. InstallerSet handles this case end end dependency = - if spec = installer_set.local?(dep_or_name) then + if spec = installer_set.local?(dep_or_name) Gem::Dependency.new spec.name, version - elsif String === dep_or_name then + elsif String === dep_or_name Gem::Dependency.new dep_or_name, version else dep_or_name @@ -481,7 +478,7 @@ def resolve_dependencies dep_or_name, version # :nodoc: request_set.always_install = installer_set.always_install - if @ignore_dependencies then + if @ignore_dependencies installer_set.ignore_dependencies = true request_set.ignore_dependencies = true request_set.soft_missing = true diff --git a/lib/ruby/stdlib/rubygems/dependency_list.rb b/lib/ruby/stdlib/rubygems/dependency_list.rb index d8314eaf60f5..7e507f3b77c1 100644 --- a/lib/ruby/stdlib/rubygems/dependency_list.rb +++ b/lib/ruby/stdlib/rubygems/dependency_list.rb @@ -40,7 +40,7 @@ def self.from_specs # Creates a new DependencyList. If +development+ is true, development # dependencies will be included. - def initialize development = false + def initialize(development = false) @specs = [] @development = development @@ -79,8 +79,8 @@ def dependency_order seen = {} sorted.each do |spec| - if index = seen[spec.name] then - if result[index].version < spec.version then + if index = seen[spec.name] + if result[index].version < spec.version result[index] = spec end else @@ -114,7 +114,7 @@ def ok? why_not_ok?(:quick).empty? end - def why_not_ok? quick = false + def why_not_ok?(quick = false) unsatisfied = Hash.new { |h,k| h[k] = [] } each do |spec| spec.runtime_dependencies.each do |dep| @@ -123,7 +123,7 @@ def why_not_ok? quick = false dep.requirement.satisfied_by? installed_spec.version } - unless inst or @specs.find { |s| s.satisfies_requirement? dep } then + unless inst or @specs.find { |s| s.satisfies_requirement? dep } unsatisfied[spec.name] << dep return unsatisfied if quick end @@ -134,7 +134,7 @@ def why_not_ok? quick = false end ## - # Is is ok to remove a gemspec from the dependency list? + # It is ok to remove a gemspec from the dependency list? # # If removing the gemspec creates breaks a currently ok dependency, then it # is NOT ok to remove the gemspec. @@ -172,7 +172,7 @@ def ok_to_remove?(full_name, check_dev=true) # satisfy items in +dependencies+ (a hash of gem names to arrays of # dependencies). - def remove_specs_unsatisfied_by dependencies + def remove_specs_unsatisfied_by(dependencies) specs.reject! { |spec| dep = dependencies[spec.name] dep and not dep.requirement.satisfied_by? spec.version @@ -200,7 +200,7 @@ def spec_predecessors next if spec == other other.dependencies.each do |dep| - if spec.satisfies_requirement? dep then + if spec.satisfies_requirement? dep result[spec] << other end end @@ -222,7 +222,7 @@ def tsort_each_child(node) dependencies.each do |dep| specs.each do |spec| - if spec.satisfies_requirement? dep then + if spec.satisfies_requirement? dep yield spec break end @@ -241,4 +241,3 @@ def active_count(specs, ignored) end end - diff --git a/lib/ruby/stdlib/rubygems/deprecate.rb b/lib/ruby/stdlib/rubygems/deprecate.rb index 375194c1e83c..815f42ae8c5c 100644 --- a/lib/ruby/stdlib/rubygems/deprecate.rb +++ b/lib/ruby/stdlib/rubygems/deprecate.rb @@ -27,7 +27,7 @@ def self.skip # :nodoc: @skip ||= false end - def self.skip= v # :nodoc: + def self.skip=(v) # :nodoc: @skip = v end @@ -47,7 +47,7 @@ def skip_during # telling the user of +repl+ (unless +repl+ is :none) and the # year/month that it is planned to go away. - def deprecate name, repl, year, month + def deprecate(name, repl, year, month) class_eval { old = "_deprecated_#{name}" alias_method old, name @@ -68,4 +68,3 @@ def deprecate name, repl, year, month module_function :deprecate, :skip_during end - diff --git a/lib/ruby/stdlib/rubygems/doctor.rb b/lib/ruby/stdlib/rubygems/doctor.rb index ec4a16c3f890..661ae5a4e10d 100644 --- a/lib/ruby/stdlib/rubygems/doctor.rb +++ b/lib/ruby/stdlib/rubygems/doctor.rb @@ -26,7 +26,7 @@ class Gem::Doctor ['doc', ''], ['extensions', ''], ['gems', ''], - ] + ].freeze missing = Gem::REPOSITORY_SUBDIRECTORIES.sort - @@ -41,7 +41,7 @@ class Gem::Doctor # # If +dry_run+ is true no files or directories will be removed. - def initialize gem_repository, dry_run = false + def initialize(gem_repository, dry_run = false) @gem_repository = gem_repository @dry_run = dry_run @@ -73,7 +73,7 @@ def doctor Gem.use_paths @gem_repository.to_s - unless gem_repository? then + unless gem_repository? say 'This directory does not appear to be a RubyGems repository, ' + 'skipping' say @@ -99,7 +99,7 @@ def doctor_children # :nodoc: ## # Removes files in +sub_directory+ with +extension+ - def doctor_child sub_directory, extension # :nodoc: + def doctor_child(sub_directory, extension) # :nodoc: directory = File.join(@gem_repository, sub_directory) Dir.entries(directory).sort.each do |ent| @@ -115,7 +115,7 @@ def doctor_child sub_directory, extension # :nodoc: type = File.directory?(child) ? 'directory' : 'file' - action = if @dry_run then + action = if @dry_run 'Extra' else FileUtils.rm_r(child) @@ -129,4 +129,3 @@ def doctor_child sub_directory, extension # :nodoc: end end - diff --git a/lib/ruby/stdlib/rubygems/errors.rb b/lib/ruby/stdlib/rubygems/errors.rb index 6f2847d548a3..6773bbcd2690 100644 --- a/lib/ruby/stdlib/rubygems/errors.rb +++ b/lib/ruby/stdlib/rubygems/errors.rb @@ -25,7 +25,7 @@ class LoadError < ::LoadError # system. Instead of rescuing from this class, make sure to rescue from the # superclass Gem::LoadError to catch all types of load errors. class MissingSpecError < Gem::LoadError - def initialize name, requirement + def initialize(name, requirement) @name = name @requirement = requirement end @@ -50,7 +50,7 @@ def build_message class MissingSpecVersionError < MissingSpecError attr_reader :specs - def initialize name, requirement, specs + def initialize(name, requirement, specs) super(name, requirement) @specs = specs end @@ -81,7 +81,7 @@ class ConflictError < LoadError attr_reader :target - def initialize target, conflicts + def initialize(target, conflicts) @target = target @conflicts = conflicts @name = target.name diff --git a/lib/ruby/stdlib/rubygems/exceptions.rb b/lib/ruby/stdlib/rubygems/exceptions.rb index 328bd5f4768d..d93a74a65ca8 100644 --- a/lib/ruby/stdlib/rubygems/exceptions.rb +++ b/lib/ruby/stdlib/rubygems/exceptions.rb @@ -1,8 +1,4 @@ # frozen_string_literal: true -# TODO: the documentation in here is terrible. -# -# Each exception needs a brief description and the scenarios where it is -# likely to be raised require 'rubygems/deprecate' @@ -36,7 +32,7 @@ class Gem::DependencyResolutionError < Gem::DependencyError attr_reader :conflict - def initialize conflict + def initialize(conflict) @conflict = conflict a, b = conflicting_dependencies @@ -56,6 +52,13 @@ class Gem::GemNotInHomeException < Gem::Exception attr_accessor :spec end +### +# Raised when removing a gem with the uninstall command fails + +class Gem::UninstallError < Gem::Exception + attr_accessor :spec +end + class Gem::DocumentError < Gem::Exception; end ## @@ -70,7 +73,7 @@ class Gem::FilePermissionError < Gem::Exception attr_reader :directory - def initialize directory + def initialize(directory) @directory = directory super "You don't have write permissions for the #{directory} directory." @@ -130,7 +133,7 @@ class Gem::ImpossibleDependenciesError < Gem::Exception attr_reader :conflicts attr_reader :request - def initialize request, conflicts + def initialize(request, conflicts) @request = request @conflicts = conflicts @@ -242,7 +245,7 @@ class Gem::UnsatisfiableDependencyError < Gem::DependencyError # Creates a new UnsatisfiableDependencyError for the unsatisfiable # Gem::Resolver::DependencyRequest +dep+ - def initialize dep, platform_mismatch=nil + def initialize(dep, platform_mismatch=nil) if platform_mismatch and !platform_mismatch.empty? plats = platform_mismatch.map { |x| x.platform.to_s }.sort.uniq super "Unable to resolve dependency: No match for '#{dep}' on this platform. Found: #{plats.join(', ')}" diff --git a/lib/ruby/stdlib/rubygems/ext.rb b/lib/ruby/stdlib/rubygems/ext.rb index 18d2bc233ae4..35a486606abb 100644 --- a/lib/ruby/stdlib/rubygems/ext.rb +++ b/lib/ruby/stdlib/rubygems/ext.rb @@ -16,4 +16,3 @@ module Gem::Ext; end require 'rubygems/ext/ext_conf_builder' require 'rubygems/ext/rake_builder' require 'rubygems/ext/cmake_builder' - diff --git a/lib/ruby/stdlib/rubygems/ext/build_error.rb b/lib/ruby/stdlib/rubygems/ext/build_error.rb index 0b3c17a9a0d5..6dffddb5cc7b 100644 --- a/lib/ruby/stdlib/rubygems/ext/build_error.rb +++ b/lib/ruby/stdlib/rubygems/ext/build_error.rb @@ -4,4 +4,3 @@ class Gem::Ext::BuildError < Gem::InstallError end - diff --git a/lib/ruby/stdlib/rubygems/ext/builder.rb b/lib/ruby/stdlib/rubygems/ext/builder.rb index eb9db199d56f..f578e7feee43 100644 --- a/lib/ruby/stdlib/rubygems/ext/builder.rb +++ b/lib/ruby/stdlib/rubygems/ext/builder.rb @@ -6,7 +6,6 @@ #++ require 'rubygems/user_interaction' -require 'thread' class Gem::Ext::Builder @@ -28,18 +27,18 @@ def self.class_name end def self.make(dest_path, results) - unless File.exist? 'Makefile' then + unless File.exist? 'Makefile' raise Gem::InstallError, 'Makefile not found' end # try to find make program from Ruby configure arguments first RbConfig::CONFIG['configure_args'] =~ /with-make-prog\=(\w+)/ make_program = ENV['MAKE'] || ENV['make'] || $1 - unless make_program then + unless make_program make_program = (/mswin/ =~ RUBY_PLATFORM) ? 'nmake' : 'make' end - destdir = '"DESTDIR=%s"' % ENV['DESTDIR'] if RUBY_VERSION > '2.0' + destdir = '"DESTDIR=%s"' % ENV['DESTDIR'] ['clean', '', 'install'].each do |target| # Pass DESTDIR via command line to override what's in MAKEFLAGS @@ -57,6 +56,7 @@ def self.make(dest_path, results) end def self.redirector + warn "#{caller[0]}: Use IO.popen(..., err: [:child, :out])" '2>&1' end @@ -64,28 +64,35 @@ def self.run(command, results, command_name = nil) verbose = Gem.configuration.really_verbose begin - # TODO use Process.spawn when ruby 1.8 support is dropped. rubygems_gemdeps, ENV['RUBYGEMS_GEMDEPS'] = ENV['RUBYGEMS_GEMDEPS'], nil if verbose puts("current directory: #{Dir.pwd}") - puts(command) - system(command) - else - results << "current directory: #{Dir.pwd}" - results << command - results << `#{command} #{redirector}` + p(command) end + results << "current directory: #{Dir.pwd}" + results << (command.respond_to?(:shelljoin) ? command.shelljoin : command) + + redirections = verbose ? {} : {err: [:child, :out]} + IO.popen(command, "r", redirections) do |io| + if verbose + IO.copy_stream(io, $stdout) + else + results << io.read + end + end + rescue => error + raise Gem::InstallError, "#{command_name || class_name} failed#{error.message}" ensure ENV['RUBYGEMS_GEMDEPS'] = rubygems_gemdeps end - unless $?.success? then + unless $?.success? results << "Building has failed. See above output for more information on the failure." if verbose exit_reason = - if $?.exited? then + if $?.exited? ", exit code #{$?.exitstatus}" - elsif $?.signaled? then + elsif $?.signaled? ", uncaught signal #{$?.termsig}" end @@ -98,7 +105,7 @@ def self.run(command, results, command_name = nil) # have build arguments, saved, set +build_args+ which is an ARGV-style # array. - def initialize spec, build_args = spec.build_args + def initialize(spec, build_args = spec.build_args) @spec = spec @build_args = build_args @gem_dir = spec.full_gem_path @@ -109,7 +116,7 @@ def initialize spec, build_args = spec.build_args ## # Chooses the extension builder class for +extension+ - def builder_for extension # :nodoc: + def builder_for(extension) # :nodoc: case extension when /extconf/ then Gem::Ext::ExtConfBuilder @@ -131,7 +138,7 @@ def builder_for extension # :nodoc: ## # Logs the build +output+ in +build_dir+, then raises Gem::Ext::BuildError. - def build_error build_dir, output, backtrace = nil # :nodoc: + def build_error(build_dir, output, backtrace = nil) # :nodoc: gem_make_out = write_gem_make_out output message = <<-EOF @@ -146,12 +153,24 @@ def build_error build_dir, output, backtrace = nil # :nodoc: raise Gem::Ext::BuildError, message, backtrace end - def build_extension extension, dest_path # :nodoc: + def build_extension(extension, dest_path) # :nodoc: results = [] + # FIXME: Determine if this line is necessary and, if so, why. + # Notes: + # 1. As far as I can tell, this method is only called by +build_extensions+. + # 2. The existence of this line implies +extension+ is, or previously was, + # sometimes +false+ or +nil+. + # 3. #1 and #2 combined suggests, but does not confirm, that + # +@specs.extensions+ sometimes contained +false+ or +nil+ values. + # 4. Nothing seems to explicitly handle +extension+ being empty, + # which makes me wonder both what it should do and what it does. + # + # - @duckinator extension ||= '' # I wish I knew why this line existed + extension_dir = - File.expand_path File.join @gem_dir, File.dirname(extension) + File.expand_path File.join(@gem_dir, File.dirname(extension)) lib_dir = File.join @spec.full_gem_path, @spec.raw_require_paths.first builder = builder_for extension @@ -160,11 +179,19 @@ def build_extension extension, dest_path # :nodoc: FileUtils.mkdir_p dest_path CHDIR_MUTEX.synchronize do - Dir.chdir extension_dir do - results = builder.build(extension, @gem_dir, dest_path, + pwd = Dir.getwd + Dir.chdir extension_dir + begin + results = builder.build(extension, dest_path, results, @build_args, lib_dir) verbose { results.join("\n") } + ensure + begin + Dir.chdir pwd + rescue SystemCallError + Dir.chdir dest_path + end end end @@ -193,6 +220,7 @@ def build_extensions FileUtils.rm_f @spec.gem_build_complete_path + # FIXME: action at a distance: @ran_rake modified deep in build_extension(). - @duckinator @ran_rake = false # only run rake once @spec.extensions.each do |extension| @@ -207,7 +235,7 @@ def build_extensions ## # Writes +output+ to gem_make.out in the extension install directory. - def write_gem_make_out output # :nodoc: + def write_gem_make_out(output) # :nodoc: destination = File.join @spec.extension_dir, 'gem_make.out' FileUtils.mkdir_p @spec.extension_dir @@ -218,4 +246,3 @@ def write_gem_make_out output # :nodoc: end end - diff --git a/lib/ruby/stdlib/rubygems/ext/cmake_builder.rb b/lib/ruby/stdlib/rubygems/ext/cmake_builder.rb index efa3bd1d8881..ab226733d974 100644 --- a/lib/ruby/stdlib/rubygems/ext/cmake_builder.rb +++ b/lib/ruby/stdlib/rubygems/ext/cmake_builder.rb @@ -2,8 +2,8 @@ require 'rubygems/command' class Gem::Ext::CmakeBuilder < Gem::Ext::Builder - def self.build(extension, directory, dest_path, results, args=[], lib_dir=nil) - unless File.exist?('Makefile') then + def self.build(extension, dest_path, results, args=[], lib_dir=nil) + unless File.exist?('Makefile') cmd = "cmake . -DCMAKE_INSTALL_PREFIX=#{dest_path}" cmd << " #{Gem::Command.build_args.join ' '}" unless Gem::Command.build_args.empty? diff --git a/lib/ruby/stdlib/rubygems/ext/configure_builder.rb b/lib/ruby/stdlib/rubygems/ext/configure_builder.rb index 8b42bf7ee96c..7d105c9bd3d4 100644 --- a/lib/ruby/stdlib/rubygems/ext/configure_builder.rb +++ b/lib/ruby/stdlib/rubygems/ext/configure_builder.rb @@ -7,8 +7,8 @@ class Gem::Ext::ConfigureBuilder < Gem::Ext::Builder - def self.build(extension, directory, dest_path, results, args=[], lib_dir=nil) - unless File.exist?('Makefile') then + def self.build(extension, dest_path, results, args=[], lib_dir=nil) + unless File.exist?('Makefile') cmd = "sh ./configure --prefix=#{dest_path}" cmd << " #{args.join ' '}" unless args.empty? @@ -21,4 +21,3 @@ def self.build(extension, directory, dest_path, results, args=[], lib_dir=nil) end end - diff --git a/lib/ruby/stdlib/rubygems/ext/ext_conf_builder.rb b/lib/ruby/stdlib/rubygems/ext/ext_conf_builder.rb index 965b72827880..5a2b3eb5330b 100644 --- a/lib/ruby/stdlib/rubygems/ext/ext_conf_builder.rb +++ b/lib/ruby/stdlib/rubygems/ext/ext_conf_builder.rb @@ -7,11 +7,12 @@ require 'fileutils' require 'tempfile' +require 'shellwords' class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder FileEntry = FileUtils::Entry_ # :nodoc: - def self.build(extension, directory, dest_path, results, args=[], lib_dir=nil) + def self.build(extension, dest_path, results, args=[], lib_dir=nil) tmp_dest = Dir.mktmpdir(".gem.", ".") # Some versions of `mktmpdir` return absolute paths, which will break make @@ -23,9 +24,7 @@ def self.build(extension, directory, dest_path, results, args=[], lib_dir=nil) # spaces do not work. # # Details: https://github.com/rubygems/rubygems/issues/977#issuecomment-171544940 - # - # TODO: Make this unconditional when rubygems no longer supports Ruby 1.9.x. - tmp_dest = get_relative_path(tmp_dest) unless Gem.win_platform? && RUBY_VERSION <= '2.0' + tmp_dest = get_relative_path(tmp_dest) Tempfile.open %w"siteconf .rb", "." do |siteconf| siteconf.puts "require 'rbconfig'" @@ -40,13 +39,15 @@ def self.build(extension, directory, dest_path, results, args=[], lib_dir=nil) destdir = ENV["DESTDIR"] begin - cmd = [Gem.ruby, "-r", get_relative_path(siteconf.path), File.basename(extension), *args].join ' ' + cmd = Gem.ruby.shellsplit << "-I" << File.expand_path("../../..", __FILE__) << + "-r" << get_relative_path(siteconf.path) << File.basename(extension) + cmd.push(*args) begin run cmd, results ensure if File.exist? 'mkmf.log' - unless $?.success? then + unless $?.success? results << "To see why this extension failed to compile, please check" \ " the mkmf.log which can be found here:\n" results << " " + File.join(dest_path, 'mkmf.log') + "\n" @@ -62,7 +63,7 @@ def self.build(extension, directory, dest_path, results, args=[], lib_dir=nil) if tmp_dest # TODO remove in RubyGems 3 - if Gem.install_extension_in_lib and lib_dir then + if Gem.install_extension_in_lib and lib_dir FileUtils.mkdir_p lib_dir entries = Dir.entries(tmp_dest) - %w[. ..] entries = entries.map { |entry| File.join tmp_dest, entry } diff --git a/lib/ruby/stdlib/rubygems/ext/rake_builder.rb b/lib/ruby/stdlib/rubygems/ext/rake_builder.rb index 4b3534bf358b..52041a2713df 100644 --- a/lib/ruby/stdlib/rubygems/ext/rake_builder.rb +++ b/lib/ruby/stdlib/rubygems/ext/rake_builder.rb @@ -5,33 +5,31 @@ # See LICENSE.txt for permissions. #++ +require "shellwords" + class Gem::Ext::RakeBuilder < Gem::Ext::Builder - def self.build(extension, directory, dest_path, results, args=[], lib_dir=nil) - if File.basename(extension) =~ /mkrf_conf/i then - cmd = "#{Gem.ruby} #{File.basename extension}".dup - cmd << " #{args.join " "}" unless args.empty? - run cmd, results + def self.build(extension, dest_path, results, args=[], lib_dir=nil) + if File.basename(extension) =~ /mkrf_conf/i + run([Gem.ruby, File.basename(extension), *args], results) end - # Deal with possible spaces in the path, e.g. C:/Program Files - dest_path = '"' + dest_path.to_s + '"' if dest_path.to_s.include?(' ') - rake = ENV['rake'] - rake ||= begin - "#{Gem.ruby} -rrubygems #{Gem.bin_path('rake', 'rake')}" - rescue Gem::Exception - end - - rake ||= Gem.default_exec_format % 'rake' - - cmd = "#{rake} RUBYARCHDIR=#{dest_path} RUBYLIBDIR=#{dest_path}" # ENV is frozen + if rake + rake = rake.shellsplit + else + begin + rake = [Gem.ruby, "-rrubygems", Gem.bin_path('rake', 'rake')] + rescue Gem::Exception + rake = [Gem.default_exec_format % 'rake'] + end + end - run cmd, results + rake_args = ["RUBYARCHDIR=#{dest_path}", "RUBYLIBDIR=#{dest_path}", *args] + run(rake + rake_args, results) results end end - diff --git a/lib/ruby/stdlib/rubygems/gem_runner.rb b/lib/ruby/stdlib/rubygems/gem_runner.rb index 349d49d66e69..4159d81389ce 100644 --- a/lib/ruby/stdlib/rubygems/gem_runner.rb +++ b/lib/ruby/stdlib/rubygems/gem_runner.rb @@ -38,7 +38,7 @@ def initialize(options={}) ## # Run the gem command with the following arguments. - def run args + def run(args) build_args = extract_build_args args do_configuration args @@ -63,7 +63,7 @@ def run args # Separates the build arguments (those following --) from the # other arguments in the list. - def extract_build_args args # :nodoc: + def extract_build_args(args) # :nodoc: return [] unless offset = args.index('--') build_args = args.slice!(offset...args.length) diff --git a/lib/ruby/stdlib/rubygems/gemcutter_utilities.rb b/lib/ruby/stdlib/rubygems/gemcutter_utilities.rb index 623d9301b598..7f977ffdc7e8 100644 --- a/lib/ruby/stdlib/rubygems/gemcutter_utilities.rb +++ b/lib/ruby/stdlib/rubygems/gemcutter_utilities.rb @@ -7,6 +7,8 @@ module Gem::GemcutterUtilities + ERROR_CODE = 1 + include Gem::Text # TODO: move to Gem::Command @@ -27,11 +29,23 @@ def add_key_option end end + ## + # Add the --otp option + + def add_otp_option + add_option('--otp CODE', + 'Digit code for multifactor authentication') do |value, options| + options[:otp] = value + end + end + ## # The API key from the command options or from the user's configuration. def api_key - if options[:key] then + if ENV["GEM_HOST_API_KEY"] + ENV["GEM_HOST_API_KEY"] + elsif options[:key] verify_api_key options[:key] elsif Gem.configuration.api_keys.key?(host) Gem.configuration.api_keys[host] @@ -69,7 +83,7 @@ def rubygems_api_request(method, path, host = nil, allowed_push_host = nil, &blo self.host = host if host unless self.host alert_error "You must specify a gem server" - terminate_interaction 1 # TODO: question this + terminate_interaction(ERROR_CODE) end if allowed_push_host @@ -78,7 +92,7 @@ def rubygems_api_request(method, path, host = nil, allowed_push_host = nil, &blo unless (host_uri.scheme == allowed_host_uri.scheme) && (host_uri.host == allowed_host_uri.host) alert_error "#{self.host.inspect} is not allowed by the gemspec, which only allows #{allowed_push_host.inspect}" - terminate_interaction 1 + terminate_interaction(ERROR_CODE) end end @@ -93,11 +107,11 @@ def rubygems_api_request(method, path, host = nil, allowed_push_host = nil, &blo # Signs in with the RubyGems API at +sign_in_host+ and sets the rubygems API # key. - def sign_in sign_in_host = nil + def sign_in(sign_in_host = nil) sign_in_host ||= self.host return if api_key - pretty_host = if Gem::DEFAULT_HOST == sign_in_host then + pretty_host = if Gem::DEFAULT_HOST == sign_in_host 'RubyGems.org' else sign_in_host @@ -116,6 +130,13 @@ def sign_in sign_in_host = nil request.basic_auth email, password end + if need_otp? response + response = rubygems_api_request(:get, "api/v1/api_key", sign_in_host) do |request| + request.basic_auth email, password + request.add_field "OTP", options[:otp] + end + end + with_response response do |resp| say "Signed in." set_api_key host, resp.body @@ -127,11 +148,11 @@ def sign_in sign_in_host = nil # an error. def verify_api_key(key) - if Gem.configuration.api_keys.key? key then + if Gem.configuration.api_keys.key? key Gem.configuration.api_keys[key] else alert_error "No such API key. Please add it to your configuration (done automatically on initial `gem push`)." - terminate_interaction 1 # TODO: question this + terminate_interaction(ERROR_CODE) end end @@ -142,10 +163,10 @@ def verify_api_key(key) # If the response was not successful, shows an error to the user including # the +error_prefix+ and the response body. - def with_response response, error_prefix = nil + def with_response(response, error_prefix = nil) case response when Net::HTTPSuccess then - if block_given? then + if block_given? yield response else say clean_text(response.body) @@ -155,11 +176,25 @@ def with_response response, error_prefix = nil message = "#{error_prefix}: #{message}" if error_prefix say clean_text(message) - terminate_interaction 1 # TODO: question this + terminate_interaction(ERROR_CODE) end end - def set_api_key host, key + ## + # Returns true when the user has enabled multifactor authentication from + # +response+ text. + + def need_otp?(response) + return unless response.kind_of?(Net::HTTPUnauthorized) && + response.body.start_with?('You have enabled multifactor authentication') + return true if options[:otp] + + say 'You have enabled multi-factor authentication. Please enter OTP code.' + options[:otp] = ask 'Code: ' + true + end + + def set_api_key(host, key) if host == Gem::DEFAULT_HOST Gem.configuration.rubygems_api_key = key else @@ -168,4 +203,3 @@ def set_api_key host, key end end - diff --git a/lib/ruby/stdlib/rubygems/indexer.rb b/lib/ruby/stdlib/rubygems/indexer.rb index 2e59e790d42f..b97347177db7 100644 --- a/lib/ruby/stdlib/rubygems/indexer.rb +++ b/lib/ruby/stdlib/rubygems/indexer.rb @@ -4,10 +4,17 @@ require 'time' require 'tmpdir' +rescue_exceptions = [LoadError] +begin + require 'bundler/errors' +rescue LoadError # this rubygems + old ruby +else # this rubygems + ruby trunk with bundler + rescue_exceptions << Bundler::GemfileNotFound +end begin gem 'builder' require 'builder/xchar' -rescue LoadError +rescue *rescue_exceptions end ## @@ -55,7 +62,7 @@ def initialize(directory, options = {}) require 'tmpdir' require 'zlib' - unless defined?(Builder::XChar) then + unless defined?(Builder::XChar) raise "Gem::Indexer requires that the XML Builder library be installed:" + "\n\tgem install builder" end @@ -109,7 +116,7 @@ def build_indices ## # Builds Marshal quick index gemspecs. - def build_marshal_gemspecs specs + def build_marshal_gemspecs(specs) count = specs.count progress = ui.progress_reporter count, "Generating Marshal quick index gemspecs for #{count} gems", @@ -154,7 +161,7 @@ def build_modern_index(index, file, name) platform = spec.original_platform # win32-api-1.0.4-x86-mswin32-60 - unless String === platform then + unless String === platform alert_warning "Skipping invalid platform in gem: #{spec.full_name}" next end @@ -172,7 +179,7 @@ def build_modern_index(index, file, name) ## # Builds indices for RubyGems 1.2 and newer. Handles full, latest, prerelease - def build_modern_indices specs + def build_modern_indices(specs) prerelease, released = specs.partition { |s| s.version.prerelease? } @@ -192,9 +199,9 @@ def build_modern_indices specs "#{@prerelease_specs_index}.gz"] end - def map_gems_to_specs gems + def map_gems_to_specs(gems) gems.map { |gemfile| - if File.size(gemfile) == 0 then + if File.size(gemfile) == 0 alert_warning "Skipping zero-length gem: #{gemfile}" next end @@ -228,7 +235,7 @@ def compress_indices say "Compressing indices" Gem.time 'Compressed indices' do - if @build_modern then + if @build_modern gzip @specs_index gzip @latest_specs_index gzip @prerelease_specs_index @@ -271,7 +278,7 @@ def compress(filename, extension) # List of gem file names to index. def gem_file_list - Dir[File.join(@dest_directory, "gems", '*.gem')] + Gem::Util.glob_files_in_dir("*.gem", File.join(@dest_directory, "gems")) end ## @@ -306,7 +313,7 @@ def install_indices files = @files files.delete @quick_marshal_dir if files.include? @quick_dir - if files.include? @quick_marshal_dir and not files.include? @quick_dir then + if files.include? @quick_marshal_dir and not files.include? @quick_dir files.delete @quick_marshal_dir dst_name = File.join(@dest_directory, @quick_marshal_dir_base) @@ -347,7 +354,7 @@ def paranoid(path, extension) data = Gem.read_binary path compressed_data = Gem.read_binary "#{path}.#{extension}" - unless data == Gem::Util.inflate(compressed_data) then + unless data == Gem::Util.inflate(compressed_data) raise "Compressed file #{compressed_path} does not match uncompressed file #{path}" end end @@ -367,7 +374,7 @@ def update_index gem_mtime >= specs_mtime end - if updated_gems.empty? then + if updated_gems.empty? say 'No new gems' terminate_interaction 0 end diff --git a/lib/ruby/stdlib/rubygems/install_default_message.rb b/lib/ruby/stdlib/rubygems/install_default_message.rb index dc73fd962b9c..f68fd2fd0430 100644 --- a/lib/ruby/stdlib/rubygems/install_default_message.rb +++ b/lib/ruby/stdlib/rubygems/install_default_message.rb @@ -10,4 +10,3 @@ ui = Gem::DefaultUserInteraction.ui ui.say "Successfully installed #{installer.spec.full_name} as a default gem" end - diff --git a/lib/ruby/stdlib/rubygems/install_message.rb b/lib/ruby/stdlib/rubygems/install_message.rb index 6880db583ed8..3c13888a84a3 100644 --- a/lib/ruby/stdlib/rubygems/install_message.rb +++ b/lib/ruby/stdlib/rubygems/install_message.rb @@ -10,4 +10,3 @@ ui = Gem::DefaultUserInteraction.ui ui.say "Successfully installed #{installer.spec.full_name}" end - diff --git a/lib/ruby/stdlib/rubygems/install_update_options.rb b/lib/ruby/stdlib/rubygems/install_update_options.rb index 75968605f191..38a0682958c1 100644 --- a/lib/ruby/stdlib/rubygems/install_update_options.rb +++ b/lib/ruby/stdlib/rubygems/install_update_options.rb @@ -30,7 +30,7 @@ def add_install_update_options options[:bin_dir] = File.expand_path(value) end - add_option(:"Install/Update", '--[no-]document [TYPES]', Array, + add_option(:"Install/Update", '--document [TYPES]', Array, 'Generate documentation for installed gems', 'List the documentation types you wish to', 'generate. For example: rdoc,ri') do |value, options| @@ -50,7 +50,7 @@ def add_install_update_options add_option(:"Install/Update", '--vendor', 'Install gem into the vendor directory.', 'Only for use by gem repackagers.') do |value, options| - unless Gem.vendor_dir then + unless Gem.vendor_dir raise OptionParser::InvalidOption.new 'your platform is not supported' end @@ -63,30 +63,6 @@ def add_install_update_options options[:document] = [] end - add_option(:Deprecated, '--[no-]rdoc', - 'Generate RDoc for installed gems', - 'Use --document instead') do |value, options| - if value then - options[:document] << 'rdoc' - else - options[:document].delete 'rdoc' - end - - options[:document].uniq! - end - - add_option(:Deprecated, '--[no-]ri', - 'Generate ri data for installed gems.', - 'Use --document instead') do |value, options| - if value then - options[:document] << 'ri' - else - options[:document].delete 'ri' - end - - options[:document].uniq! - end - add_option(:"Install/Update", '-E', '--[no-]env-shebang', "Rewrite the shebang line on installed", "scripts to use /usr/bin/env") do |value, options| @@ -164,7 +140,7 @@ def add_install_update_options File.exist? file end unless v - unless v then + unless v message = v ? v : "(tried #{Gem::GEM_DEP_FILES.join ', '})" raise OptionParser::InvalidArgument, @@ -202,7 +178,6 @@ def add_install_update_options 'Suggest alternates when gems are not found') do |v,o| options[:suggest_alternate] = v end - end ## @@ -213,4 +188,3 @@ def install_update_defaults_str end end - diff --git a/lib/ruby/stdlib/rubygems/installer.rb b/lib/ruby/stdlib/rubygems/installer.rb index feb6249cd38a..9283e66c3fa5 100644 --- a/lib/ruby/stdlib/rubygems/installer.rb +++ b/lib/ruby/stdlib/rubygems/installer.rb @@ -34,7 +34,7 @@ class Gem::Installer # Paths where env(1) might live. Some systems are broken and have it in # /bin - ENV_PATHS = %w[/usr/bin/env /bin/env] + ENV_PATHS = %w[/usr/bin/env /bin/env].freeze ## # Deprecated in favor of Gem::Ext::BuildError @@ -101,7 +101,7 @@ def exec_format ## # Construct an installer object for the gem file located at +path+ - def self.at path, options = {} + def self.at(path, options = {}) security_policy = options[:security_policy] package = Gem::Package.new path, security_policy new package, options @@ -110,11 +110,15 @@ def self.at path, options = {} class FakePackage attr_accessor :spec + attr_accessor :dir_mode + attr_accessor :prog_mode + attr_accessor :data_mode + def initialize(spec) @spec = spec end - def extract_files destination_dir, pattern = '*' + def extract_files(destination_dir, pattern = '*') FileUtils.mkdir_p destination_dir spec.files.each do |file| @@ -125,7 +129,7 @@ def extract_files destination_dir, pattern = '*' end end - def copy_to path + def copy_to(path) end end @@ -133,7 +137,7 @@ def copy_to path # Construct an installer object for an ephemeral gem (one where we don't # actually have a .gem file, just a spec) - def self.for_spec spec, options = {} + def self.for_spec(spec, options = {}) # FIXME: we should have a real Package class for this new FakePackage.new(spec), options end @@ -179,7 +183,13 @@ def initialize(package, options={}) process_options - if options[:user_install] and not options[:unpack] then + @package.dir_mode = options[:dir_mode] + @package.prog_mode = options[:prog_mode] + @package.data_mode = options[:data_mode] + + @bin_dir = options[:bin_dir] if options[:bin_dir] + + if options[:user_install] and not options[:unpack] @gem_home = Gem.user_dir @bin_dir = Gem.bindir gem_home unless options[:bin_dir] check_that_user_bin_dir_is_in_path @@ -199,7 +209,7 @@ def initialize(package, options={}) # # Otherwise +filename+ is overwritten. - def check_executable_overwrite filename # :nodoc: + def check_executable_overwrite(filename) # :nodoc: return if @force generated_bin = File.join @bin_dir, formatted_program_filename(filename) @@ -235,7 +245,7 @@ def check_executable_overwrite filename # :nodoc: question = "#{spec.name}'s executable \"#{filename}\" conflicts with ".dup - if ruby_executable then + if ruby_executable question << (existing || 'an unknown executable') return if ask_yes_no "#{question}\nOverwrite the executable?", false @@ -288,7 +298,7 @@ def install run_pre_install_hooks # Set loaded_from to ensure extension_dir is correct - if @options[:install_as_default] then + if @options[:install_as_default] spec.loaded_from = default_spec_file else spec.loaded_from = spec_file @@ -298,9 +308,10 @@ def install FileUtils.rm_rf gem_dir FileUtils.rm_rf spec.extension_dir - FileUtils.mkdir_p gem_dir + dir_mode = options[:dir_mode] + FileUtils.mkdir_p gem_dir, :mode => dir_mode && 0755 - if @options[:install_as_default] then + if @options[:install_as_default] extract_bin write_default_spec else @@ -315,6 +326,8 @@ def install write_cache_file end + File.chmod(dir_mode, gem_dir) if dir_mode + say spec.post_install_message if options[:post_install_message] && !spec.post_install_message.nil? Gem::Installer.install_lock.synchronize { Gem::Specification.reset } @@ -331,7 +344,7 @@ def install def run_pre_install_hooks # :nodoc: Gem.pre_install_hooks.each do |hook| - if hook.call(self) == false then + if hook.call(self) == false location = " at #{$1}" if hook.inspect =~ /@(.*:\d+)/ message = "pre-install hook#{location} failed for #{spec.full_name}" @@ -342,7 +355,7 @@ def run_pre_install_hooks # :nodoc: def run_post_build_hooks # :nodoc: Gem.post_build_hooks.each do |hook| - if hook.call(self) == false then + if hook.call(self) == false FileUtils.rm_rf gem_dir location = " at #{$1}" if hook.inspect =~ /@(.*:\d+)/ @@ -368,7 +381,7 @@ def installed_specs @specs ||= begin specs = [] - Dir[File.join(gem_home, "specifications", "*.gemspec")].each do |path| + Gem::Util.glob_files_in_dir("*.gemspec", File.join(gem_home, "specifications")).each do |path| spec = Gem::Specification.load path.untaint specs << spec if spec end @@ -385,7 +398,7 @@ def installed_specs # dependency :: Gem::Dependency def ensure_dependency(spec, dependency) - unless installation_satisfies_dependency? dependency then + unless installation_satisfies_dependency? dependency raise Gem::InstallError, "#{spec.name} requires #{dependency}" end true @@ -453,7 +466,7 @@ def write_default_spec # Creates windows .bat files for easy running of commands def generate_windows_script(filename, bindir) - if Gem.win_platform? then + if Gem.win_platform? script_name = filename + ".bat" script_path = File.join bindir, File.basename(script_name) File.open script_path, 'w' do |file| @@ -468,7 +481,7 @@ def generate_bin # :nodoc: return if spec.executables.nil? or spec.executables.empty? begin - Dir.mkdir @bin_dir + Dir.mkdir @bin_dir, *[options[:dir_mode] && 0755].compact rescue SystemCallError raise unless File.directory? @bin_dir end @@ -479,18 +492,19 @@ def generate_bin # :nodoc: filename.untaint bin_path = File.join gem_dir, spec.bindir, filename - unless File.exist? bin_path then + unless File.exist? bin_path # TODO change this to a more useful warning warn "`#{bin_path}` does not exist, maybe `gem pristine #{spec.name}` will fix it?" next end mode = File.stat(bin_path).mode - FileUtils.chmod mode | 0111, bin_path unless (mode | 0111) == mode + dir_mode = options[:prog_mode] || (mode | 0111) + FileUtils.chmod dir_mode, bin_path unless dir_mode == mode check_executable_overwrite filename - if @wrappers then + if @wrappers generate_bin_script filename, @bin_dir else generate_bin_symlink filename, @bin_dir @@ -513,6 +527,7 @@ def generate_bin_script(filename, bindir) File.open bin_script_path, 'wb', 0755 do |file| file.print app_script_text(filename) + file.chmod(options[:prog_mode] || 0755) end verbose bin_script_path @@ -528,8 +543,8 @@ def generate_bin_symlink(filename, bindir) src = File.join gem_dir, spec.bindir, filename dst = File.join bindir, formatted_program_filename(filename) - if File.exist? dst then - if File.symlink? dst then + if File.exist? dst + if File.symlink? dst link = File.readlink(dst).split File::SEPARATOR cur_version = Gem::Version.create(link[-3].sub(/^.*-/, '')) return if spec.version < cur_version @@ -563,7 +578,7 @@ def shebang(bin_file_name) path = File.join gem_dir, spec.bindir, bin_file_name first_line = File.open(path, "rb") {|file| file.gets} - if /\A#!/ =~ first_line then + if /\A#!/ =~ first_line # Preserve extra words on shebang line, like "-w". Thanks RPA. shebang = first_line.sub(/\A\#!.*?ruby\S*((\s+\S+)+)/, "#!#{Gem.ruby}") opts = $1 @@ -588,9 +603,9 @@ def shebang(bin_file_name) end "#!#{which}" - elsif not ruby_name then + elsif not ruby_name "#!#{Gem.ruby}#{opts}" - elsif opts then + elsif opts "#!/bin/sh\n'exec' #{ruby_name.dump} '-x' \"$0\" \"$@\"\n#{shebang}" else # Create a plain shebang line. @@ -616,9 +631,9 @@ def ensure_loadable_spec end def ensure_required_ruby_version_met # :nodoc: - if rrv = spec.required_ruby_version then - unless rrv.satisfied_by? Gem.ruby_version then - ruby_version = Gem.ruby_api_version + if rrv = spec.required_ruby_version + ruby_version = Gem.ruby_version + unless rrv.satisfied_by? ruby_version raise Gem::RuntimeRequirementNotMetError, "#{spec.name} requires Ruby version #{rrv}. The current ruby version is #{ruby_version}." end @@ -626,8 +641,8 @@ def ensure_required_ruby_version_met # :nodoc: end def ensure_required_rubygems_version_met # :nodoc: - if rrgv = spec.required_rubygems_version then - unless rrgv.satisfied_by? Gem.rubygems_version then + if rrgv = spec.required_rubygems_version + unless rrgv.satisfied_by? Gem.rubygems_version rg_version = Gem::VERSION raise Gem::RuntimeRequirementNotMetError, "#{spec.name} requires RubyGems version #{rrgv}. The current RubyGems version is #{rg_version}. " + @@ -687,16 +702,16 @@ def check_that_user_bin_dir_is_in_path # :nodoc: File::ALT_SEPARATOR path = ENV['PATH'] - if Gem.win_platform? then + if Gem.win_platform? path = path.downcase user_bin_dir = user_bin_dir.downcase end path = path.split(File::PATH_SEPARATOR) - unless path.include? user_bin_dir then + unless path.include? user_bin_dir unless !Gem.win_platform? && (path.include? user_bin_dir.sub(ENV['HOME'], '~')) - unless self.class.path_warning then + unless self.class.path_warning alert_warning "You don't have #{user_bin_dir} in your PATH,\n\t gem executables will not run." self.class.path_warning = true end @@ -705,7 +720,7 @@ def check_that_user_bin_dir_is_in_path # :nodoc: end def verify_gem_home(unpack = false) # :nodoc: - FileUtils.mkdir_p gem_home + FileUtils.mkdir_p gem_home, :mode => options[:dir_mode] && 0755 raise Gem::FilePermissionError, gem_home unless unpack or File.writable?(gem_home) end @@ -715,11 +730,11 @@ def verify_spec raise Gem::InstallError, "#{spec} has an invalid name" end - if spec.raw_require_paths.any?{|path| path =~ /\r\n|\r|\n/ } + if spec.raw_require_paths.any?{|path| path =~ /\R/ } raise Gem::InstallError, "#{spec} has an invalid require_paths" end - if spec.extensions.any?{|ext| ext =~ /\r\n|\r|\n/ } + if spec.extensions.any?{|ext| ext =~ /\R/ } raise Gem::InstallError, "#{spec} has an invalid extensions" end @@ -727,7 +742,7 @@ def verify_spec raise Gem::InstallError, "#{spec} has an invalid specification_version" end - if spec.dependencies.any? {|dep| dep.type =~ /\r\n|\r|\n/ || dep.name =~ /\r\n|\r|\n/ } + if spec.dependencies.any? {|dep| dep.type =~ /\R/ || dep.name =~ /\R/ } raise Gem::InstallError, "#{spec} has an invalid dependencies" end end @@ -751,11 +766,11 @@ def app_script_text(bin_file_name) version = "#{Gem::Requirement.default}.a" -if ARGV.first - str = ARGV.first - str = str.dup.force_encoding("BINARY") if str.respond_to? :force_encoding - if str =~ /\\A_(.*)_\\z/ and Gem::Version.correct?($1) then - version = $1 +str = ARGV.first +if str + str = str.b[/\\A_(.*)_\\z/, 1] + if str and Gem::Version.correct?(str) + version = str ARGV.shift end end @@ -773,17 +788,38 @@ def app_script_text(bin_file_name) # return the stub script text used to launch the true Ruby script def windows_stub_script(bindir, bin_file_name) - ruby = Gem.ruby.gsub(/^\"|\"$/, "").tr(File::SEPARATOR, "\\") - return <<-TEXT + rb_config = RbConfig::CONFIG + rb_topdir = RbConfig::TOPDIR || File.dirname(rb_config["bindir"]) + + # get ruby executable file name from RbConfig + ruby_exe = "#{rb_config['RUBY_INSTALL_NAME']}#{rb_config['EXEEXT']}" + ruby_exe = "ruby.exe" if ruby_exe.empty? + + if File.exist?(File.join bindir, ruby_exe) + # stub & ruby.exe withing same folder. Portable + <<-TEXT @ECHO OFF -IF NOT "%~f0" == "~f0" GOTO :WinNT -@"#{ruby}" "#{File.join(bindir, bin_file_name)}" %1 %2 %3 %4 %5 %6 %7 %8 %9 -GOTO :EOF -:WinNT -@"#{ruby}" "%~dpn0" %* -TEXT +@"%~dp0ruby.exe" "%~dpn0" %* + TEXT + elsif bindir.downcase.start_with? rb_topdir.downcase + # stub within ruby folder, but not standard bin. Portable + require 'pathname' + from = Pathname.new bindir + to = Pathname.new "#{rb_topdir}/bin" + rel = to.relative_path_from from + <<-TEXT +@ECHO OFF +@"%~dp0#{rel}/ruby.exe" "%~dpn0" %* + TEXT + else + # outside ruby folder, maybe -user-install or bundler. Portable, but ruby + # is dependent on PATH + <<-TEXT +@ECHO OFF +@ruby.exe "%~dpn0" %* + TEXT + end end - ## # Builds extensions. Valid types of extensions are extconf.rb files, # configure scripts and rakefiles or mkrf_conf files. @@ -821,14 +857,14 @@ def extract_files # without the full gem installed. def extract_bin - @package.extract_files gem_dir, "bin/*" + @package.extract_files gem_dir, "#{spec.bindir}/*" end ## # Prefix and suffix the program filename the same as ruby. def formatted_program_filename(filename) - if @format_executable then + if @format_executable self.class.exec_format % File.basename(filename) else filename @@ -887,7 +923,8 @@ def write_build_info_file build_info_dir = File.join gem_home, 'build_info' - FileUtils.mkdir_p build_info_dir + dir_mode = options[:dir_mode] + FileUtils.mkdir_p build_info_dir, :mode => dir_mode && 0755 build_info_file = File.join build_info_dir, "#{spec.full_name}.info" @@ -896,6 +933,8 @@ def write_build_info_file io.puts arg end end + + File.chmod(dir_mode, build_info_dir) if dir_mode end ## diff --git a/lib/ruby/stdlib/rubygems/installer_test_case.rb b/lib/ruby/stdlib/rubygems/installer_test_case.rb index 4cec5da3f4ce..c0ac6c2821db 100644 --- a/lib/ruby/stdlib/rubygems/installer_test_case.rb +++ b/lib/ruby/stdlib/rubygems/installer_test_case.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require 'rubygems/test_case' require 'rubygems/installer' -require 'rubygems/deprecate' class Gem::Installer @@ -107,18 +106,6 @@ def setup Gem::Installer.path_warning = false end - def util_gem_bindir spec = @spec # :nodoc: - spec.bin_dir - end - - def util_gem_dir spec = @spec # :nodoc: - 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 @@ -132,9 +119,9 @@ def util_inst_bindir # The executable is also written to the bin dir in @tmpdir and the installed # gem directory for +spec+. - def util_make_exec(spec = @spec, shebang = "#!/usr/bin/ruby") + def util_make_exec(spec = @spec, shebang = "#!/usr/bin/ruby", bindir = "bin") spec.executables = %w[executable] - spec.files << 'bin/executable' + spec.bindir = bindir exec_path = spec.bin_file "executable" write_file exec_path do |io| @@ -196,4 +183,3 @@ def util_installer(spec, gem_home, user=false) end end - diff --git a/lib/ruby/stdlib/rubygems/local_remote_options.rb b/lib/ruby/stdlib/rubygems/local_remote_options.rb index 597b87ea0340..9fa256b08a0b 100644 --- a/lib/ruby/stdlib/rubygems/local_remote_options.rb +++ b/lib/ruby/stdlib/rubygems/local_remote_options.rb @@ -24,8 +24,10 @@ def accept_uri_http raise OptionParser::InvalidArgument, value end - unless ['http', 'https', 'file', 's3'].include?(uri.scheme) - raise OptionParser::InvalidArgument, value + valid_uri_schemes = ["http", "https", "file", "s3"] + unless valid_uri_schemes.include?(uri.scheme) + msg = "Invalid uri scheme for #{value}\nPreface URLs with one of #{valid_uri_schemes.map{|s| "#{s}://"}}" + raise ArgumentError, msg end value @@ -106,7 +108,7 @@ def add_source_option source << '/' if source !~ /\/\z/ - if options.delete :sources_cleared then + if options.delete :sources_cleared Gem.sources = [source] else Gem.sources << source unless Gem.sources.include?(source) @@ -146,4 +148,3 @@ def remote? end end - diff --git a/lib/ruby/stdlib/rubygems/mock_gem_ui.rb b/lib/ruby/stdlib/rubygems/mock_gem_ui.rb index 0223f8c35d5c..92ec85625c4f 100644 --- a/lib/ruby/stdlib/rubygems/mock_gem_ui.rb +++ b/lib/ruby/stdlib/rubygems/mock_gem_ui.rb @@ -12,7 +12,7 @@ class Gem::MockGemUi < Gem::StreamUI class InputEOFError < RuntimeError - def initialize question + def initialize(question) super "Out of input for MockGemUi on #{question.inspect}" end @@ -21,7 +21,7 @@ def initialize question class TermError < RuntimeError attr_reader :exit_code - def initialize exit_code + def initialize(exit_code) super @exit_code = exit_code end @@ -56,7 +56,7 @@ def initialize(input = "") @terminated = false end - def ask question + def ask(question) raise InputEOFError, question if @ins.eof? super @@ -86,4 +86,3 @@ def terminate_interaction(status=0) end end - diff --git a/lib/ruby/stdlib/rubygems/name_tuple.rb b/lib/ruby/stdlib/rubygems/name_tuple.rb index 316329a0bd35..e948fb3d865a 100644 --- a/lib/ruby/stdlib/rubygems/name_tuple.rb +++ b/lib/ruby/stdlib/rubygems/name_tuple.rb @@ -24,7 +24,7 @@ def initialize(name, version, platform="ruby") # Turn an array of [name, version, platform] into an array of # NameTuple objects. - def self.from_list list + def self.from_list(list) list.map { |t| new(*t) } end @@ -32,7 +32,7 @@ def self.from_list list # Turn an array of NameTuple objects back into an array of # [name, version, platform] tuples. - def self.to_basic list + def self.to_basic(list) list.map { |t| t.to_a } end @@ -90,7 +90,7 @@ def inspect # :nodoc: alias to_s inspect # :nodoc: - def <=> other + def <=>(other) [@name, @version, @platform == Gem::Platform::RUBY ? -1 : 1] <=> [other.name, other.version, other.platform == Gem::Platform::RUBY ? -1 : 1] @@ -102,7 +102,7 @@ def <=> other # Compare with +other+. Supports another NameTuple or an Array # in the [name, version, platform] format. - def == other + def ==(other) case other when self.class @name == other.name and diff --git a/lib/ruby/stdlib/rubygems/package.rb b/lib/ruby/stdlib/rubygems/package.rb index 1498de050e7c..3bf4f8fd62b1 100644 --- a/lib/ruby/stdlib/rubygems/package.rb +++ b/lib/ruby/stdlib/rubygems/package.rb @@ -55,7 +55,7 @@ class Error < Gem::Exception; end class FormatError < Error attr_reader :path - def initialize message, source = nil + def initialize(message, source = nil) if source @path = source.path @@ -68,7 +68,7 @@ def initialize message, source = nil end class PathError < Error - def initialize destination, destination_dir + def initialize(destination, destination_dir) super "installing into parent path %s of %s is not allowed" % [destination, destination_dir] end @@ -107,12 +107,24 @@ class TarInvalidError < Error; end attr_writer :spec - def self.build spec, skip_validation=false - gem_file = spec.file_name + ## + # Permission for directories + attr_accessor :dir_mode + + ## + # Permission for program files + attr_accessor :prog_mode + + ## + # Permission for other files + attr_accessor :data_mode + + def self.build(spec, skip_validation = false, strict_validation = false, file_name = nil) + gem_file = file_name || spec.file_name package = new gem_file package.spec = spec - package.build skip_validation + package.build skip_validation, strict_validation gem_file end @@ -124,7 +136,7 @@ def self.build spec, skip_validation=false # If +gem+ is an existing file in the old format a Gem::Package::Old will be # returned. - def self.new gem, security_policy = nil + def self.new(gem, security_policy = nil) gem = if gem.is_a?(Gem::Package::Source) gem elsif gem.respond_to? :read @@ -145,10 +157,10 @@ def self.new gem, security_policy = nil ## # Creates a new package that will read or write to the file +gem+. - def initialize gem, security_policy # :notnew: + def initialize(gem, security_policy) # :notnew: @gem = gem - @build_time = Time.now + @build_time = ENV["SOURCE_DATE_EPOCH"] ? Time.at(ENV["SOURCE_DATE_EPOCH"].to_i).utc : Time.now @checksums = {} @contents = nil @digests = Hash.new { |h, algorithm| h[algorithm] = {} } @@ -162,14 +174,14 @@ def initialize gem, security_policy # :notnew: ## # Copies this package to +path+ (if possible) - def copy_to path + def copy_to(path) FileUtils.cp @gem.path, path unless File.exist? path end ## # Adds a checksum for each entry in the gem to checksums.yaml.gz. - def add_checksums tar + def add_checksums(tar) Gem.load_yaml checksums_by_algorithm = Hash.new { |h, algorithm| h[algorithm] = {} } @@ -191,7 +203,7 @@ def add_checksums tar # Adds the files listed in the packages's Gem::Specification to data.tar.gz # and adds this file to the +tar+. - def add_contents tar # :nodoc: + def add_contents(tar) # :nodoc: digests = tar.add_file_signed 'data.tar.gz', 0444, @signer do |io| gzip_to io do |gz_io| Gem::Package::TarWriter.new gz_io do |data_tar| @@ -206,13 +218,18 @@ def add_contents tar # :nodoc: ## # Adds files included the package's Gem::Specification to the +tar+ file - def add_files tar # :nodoc: + def add_files(tar) # :nodoc: @spec.files.each do |file| stat = File.lstat file if stat.symlink? - relative_dir = File.dirname(file).sub("#{Dir.pwd}/", '') - target_path = File.join(relative_dir, File.readlink(file)) + target_path = File.readlink(file) + + unless target_path.start_with? '.' + relative_dir = File.dirname(file).sub("#{Dir.pwd}/", '') + target_path = File.join(relative_dir, target_path) + end + tar.add_symlink file, target_path, stat.mode end @@ -229,7 +246,7 @@ def add_files tar # :nodoc: ## # Adds the package's Gem::Specification to the +tar+ file - def add_metadata tar # :nodoc: + def add_metadata(tar) # :nodoc: digests = tar.add_file_signed 'metadata.gz', 0444, @signer do |io| gzip_to io do |gz_io| gz_io.write @spec.to_yaml @@ -242,14 +259,20 @@ def add_metadata tar # :nodoc: ## # Builds this package based on the specification set by #spec= - def build skip_validation = false + def build(skip_validation = false, strict_validation = false) + raise ArgumentError, "skip_validation = true and strict_validation = true are incompatible" if skip_validation && strict_validation + Gem.load_yaml require 'rubygems/security' @spec.mark_version - @spec.validate unless skip_validation + @spec.validate true, strict_validation unless skip_validation - setup_signer + setup_signer( + signer_options: { + expiration_length_days: Gem.configuration.cert_expiration_length_days + } + ) @gem.with_write_io do |gem_io| Gem::Package::TarWriter.new gem_io do |gem| @@ -263,7 +286,7 @@ def build skip_validation = false Successfully built RubyGem Name: #{@spec.name} Version: #{@spec.version} - File: #{File.basename @spec.cache_file} + File: #{File.basename @gem.path} EOM ensure @signer = nil @@ -300,8 +323,8 @@ def contents # Creates a digest of the TarEntry +entry+ from the digest algorithm set by # the security policy. - def digest entry # :nodoc: - algorithms = if @checksums then + def digest(entry) # :nodoc: + algorithms = if @checksums @checksums.keys else [Gem::Security::DIGEST_NAME].compact @@ -309,7 +332,7 @@ def digest entry # :nodoc: algorithms.each do |algorithm| digester = - if defined?(OpenSSL::Digest) then + if defined?(OpenSSL::Digest) OpenSSL::Digest.new algorithm else Digest.const_get(algorithm).new @@ -331,10 +354,10 @@ def digest entry # :nodoc: # If +pattern+ is specified, only entries matching that glob will be # extracted. - def extract_files destination_dir, pattern = "*" + def extract_files(destination_dir, pattern = "*") verify unless @spec - FileUtils.mkdir_p destination_dir + FileUtils.mkdir_p destination_dir, :mode => dir_mode && 0755 @gem.with_read_io do |io| reader = Gem::Package::TarReader.new io @@ -360,7 +383,8 @@ def extract_files destination_dir, pattern = "*" # If +pattern+ is specified, only entries matching that glob will be # extracted. - def extract_tar_gz io, destination_dir, pattern = "*" # :nodoc: + def extract_tar_gz(io, destination_dir, pattern = "*") # :nodoc: + directories = [] if dir_mode open_tar_gz io do |tar| tar.each do |entry| next unless File.fnmatch pattern, entry.full_name, File::FNM_DOTMATCH @@ -370,19 +394,20 @@ def extract_tar_gz io, destination_dir, pattern = "*" # :nodoc: FileUtils.rm_rf destination mkdir_options = {} - mkdir_options[:mode] = entry.header.mode if entry.directory? + mkdir_options[:mode] = dir_mode ? 0755 : (entry.header.mode if entry.directory?) mkdir = - if entry.directory? then + if entry.directory? destination else File.dirname destination end + directories << mkdir if directories mkdir_p_safe mkdir, mkdir_options, destination_dir, entry.full_name File.open destination, 'wb' do |out| out.write entry.read - FileUtils.chmod entry.header.mode, destination + FileUtils.chmod file_mode(entry.header.mode), destination end if entry.file? File.symlink(entry.header.linkname, destination) if entry.symlink? @@ -390,6 +415,15 @@ def extract_tar_gz io, destination_dir, pattern = "*" # :nodoc: verbose destination end end + + if directories + directories.uniq! + File.chmod(dir_mode, *directories) + end + end + + def file_mode(mode) # :nodoc: + ((mode & 0111).zero? ? data_mode : prog_mode) || mode end ## @@ -398,7 +432,7 @@ def extract_tar_gz io, destination_dir, pattern = "*" # :nodoc: # Also sets the gzip modification time to the package build time to ease # testing. - def gzip_to io # :yields: gz_io + def gzip_to(io) # :yields: gz_io gz_io = Zlib::GzipWriter.new io, Zlib::BEST_COMPRESSION gz_io.mtime = @build_time @@ -412,15 +446,12 @@ def gzip_to io # :yields: gz_io # # If +filename+ is not inside +destination_dir+ an exception is raised. - def install_location filename, destination_dir # :nodoc: + def install_location(filename, destination_dir) # :nodoc: raise Gem::Package::PathError.new(filename, destination_dir) if filename.start_with? '/' - destination_dir = realpath destination_dir - destination_dir = File.expand_path destination_dir - - destination = File.join destination_dir, filename - destination = File.expand_path destination + destination_dir = File.expand_path(File.realpath(destination_dir)) + destination = File.expand_path(File.join(destination_dir, filename)) raise Gem::Package::PathError.new(destination, destination_dir) unless destination.start_with? destination_dir + '/' @@ -447,11 +478,11 @@ def normalize_path(pathname) end end - def mkdir_p_safe mkdir, mkdir_options, destination_dir, file_name - destination_dir = realpath File.expand_path(destination_dir) + def mkdir_p_safe(mkdir, mkdir_options, destination_dir, file_name) + destination_dir = File.realpath(File.expand_path(destination_dir)) parts = mkdir.split(File::SEPARATOR) parts.reduce do |path, basename| - path = realpath path unless path == "" + path = File.realpath(path) unless path == "" path = File.expand_path(path + File::SEPARATOR + basename) lstat = File.lstat path rescue nil if !lstat || !lstat.directory? @@ -466,15 +497,14 @@ def mkdir_p_safe mkdir, mkdir_options, destination_dir, file_name ## # Loads a Gem::Specification from the TarEntry +entry+ - def load_spec entry # :nodoc: + def load_spec(entry) # :nodoc: case entry.full_name when 'metadata' then @spec = Gem::Specification.from_yaml entry.read when 'metadata.gz' then args = [entry] args << { :external_encoding => Encoding::UTF_8 } if - Object.const_defined?(:Encoding) && - Zlib::GzipReader.method(:wrap).arity != 1 + Zlib::GzipReader.method(:wrap).arity != 1 Zlib::GzipReader.wrap(*args) do |gzio| @spec = Gem::Specification.from_yaml gzio.read @@ -485,7 +515,7 @@ def load_spec entry # :nodoc: ## # Opens +io+ as a gzipped tar archive - def open_tar_gz io # :nodoc: + def open_tar_gz(io) # :nodoc: Zlib::GzipReader.wrap io do |gzio| tar = Gem::Package::TarReader.new gzio @@ -496,7 +526,7 @@ def open_tar_gz io # :nodoc: ## # Reads and loads checksums.yaml.gz from the tar file +gem+ - def read_checksums gem + def read_checksums(gem) Gem.load_yaml @checksums = gem.seek 'checksums.yaml.gz' do |entry| @@ -510,10 +540,17 @@ def read_checksums gem # Prepares the gem for signing and checksum generation. If a signing # certificate and key are not present only checksum generation is set up. - def setup_signer + def setup_signer(signer_options: {}) passphrase = ENV['GEM_PRIVATE_KEY_PASSPHRASE'] - if @spec.signing_key then - @signer = Gem::Security::Signer.new @spec.signing_key, @spec.cert_chain, passphrase + if @spec.signing_key + @signer = + Gem::Security::Signer.new( + @spec.signing_key, + @spec.cert_chain, + passphrase, + signer_options + ) + @spec.signing_key = nil @spec.cert_chain = @signer.cert_chain.map { |cert| cert.to_s } else @@ -578,14 +615,14 @@ def verify # Verifies the +checksums+ against the +digests+. This check is not # cryptographically secure. Missing checksums are ignored. - def verify_checksums digests, checksums # :nodoc: + def verify_checksums(digests, checksums) # :nodoc: return unless checksums checksums.sort.each do |algorithm, gem_digests| gem_digests.sort.each do |file_name, gem_hexdigest| computed_digest = digests[algorithm][file_name] - unless computed_digest.hexdigest == gem_hexdigest then + unless computed_digest.hexdigest == gem_hexdigest raise Gem::Package::FormatError.new \ "#{algorithm} checksum mismatch for #{file_name}", @gem end @@ -596,7 +633,7 @@ def verify_checksums digests, checksums # :nodoc: ## # Verifies +entry+ in a .gem file. - def verify_entry entry + def verify_entry(entry) file_name = entry.full_name @files << file_name @@ -623,16 +660,16 @@ def verify_entry entry ## # Verifies the files of the +gem+ - def verify_files gem + def verify_files(gem) gem.each do |entry| verify_entry entry end - unless @spec then + unless @spec raise Gem::Package::FormatError.new 'package metadata is missing', @gem end - unless @files.include? 'data.tar.gz' then + unless @files.include? 'data.tar.gz' raise Gem::Package::FormatError.new \ 'package content (data.tar.gz) is missing', @gem end @@ -645,7 +682,7 @@ def verify_files gem ## # Verifies that +entry+ is a valid gzipped file. - def verify_gz entry # :nodoc: + def verify_gz(entry) # :nodoc: Zlib::GzipReader.wrap entry do |gzio| gzio.read 16384 until gzio.eof? # gzip checksum verification end @@ -653,16 +690,6 @@ def verify_gz entry # :nodoc: raise Gem::Package::FormatError.new(e.message, entry.full_name) end - if File.respond_to? :realpath - def realpath file - File.realpath file - end - else - def realpath file - file - end - end - end require 'rubygems/package/digest_io' diff --git a/lib/ruby/stdlib/rubygems/package/digest_io.rb b/lib/ruby/stdlib/rubygems/package/digest_io.rb index 4930c9aa7d84..d9e6c3c0219a 100644 --- a/lib/ruby/stdlib/rubygems/package/digest_io.rb +++ b/lib/ruby/stdlib/rubygems/package/digest_io.rb @@ -31,7 +31,7 @@ class Gem::Package::DigestIO # digests['SHA1'].hexdigest #=> "aaf4c61d[...]" # digests['SHA512'].hexdigest #=> "9b71d224[...]" - def self.wrap io, digests + def self.wrap(io, digests) digest_io = new io, digests yield digest_io @@ -43,7 +43,7 @@ def self.wrap io, digests # Creates a new DigestIO instance. Using ::wrap is recommended, see the # ::wrap documentation for documentation of +io+ and +digests+. - def initialize io, digests + def initialize(io, digests) @io = io @digests = digests end @@ -51,7 +51,7 @@ def initialize io, digests ## # Writes +data+ to the underlying IO and updates the digests - def write data + def write(data) result = @io.write data @digests.each do |_, digest| @@ -62,4 +62,3 @@ def write data end end - diff --git a/lib/ruby/stdlib/rubygems/package/file_source.rb b/lib/ruby/stdlib/rubygems/package/file_source.rb index ecc3a686774f..8a4f9da6f2e2 100644 --- a/lib/ruby/stdlib/rubygems/package/file_source.rb +++ b/lib/ruby/stdlib/rubygems/package/file_source.rb @@ -10,7 +10,7 @@ class Gem::Package::FileSource < Gem::Package::Source # :nodoc: all attr_reader :path - def initialize path + def initialize(path) @path = path end @@ -22,13 +22,12 @@ def present? File.exist? path end - def with_write_io &block + def with_write_io(&block) File.open path, 'wb', &block end - def with_read_io &block + def with_read_io(&block) File.open path, 'rb', &block end end - diff --git a/lib/ruby/stdlib/rubygems/package/io_source.rb b/lib/ruby/stdlib/rubygems/package/io_source.rb index ee79a21083ff..669a859d0aeb 100644 --- a/lib/ruby/stdlib/rubygems/package/io_source.rb +++ b/lib/ruby/stdlib/rubygems/package/io_source.rb @@ -11,7 +11,7 @@ class Gem::Package::IOSource < Gem::Package::Source # :nodoc: all attr_reader :io - def initialize io + def initialize(io) @io = io end @@ -43,4 +43,3 @@ def path end end - diff --git a/lib/ruby/stdlib/rubygems/package/old.rb b/lib/ruby/stdlib/rubygems/package/old.rb index 322d682ca890..f574b989aa55 100644 --- a/lib/ruby/stdlib/rubygems/package/old.rb +++ b/lib/ruby/stdlib/rubygems/package/old.rb @@ -19,7 +19,7 @@ class Gem::Package::Old < Gem::Package # Creates a new old-format package reader for +gem+. Old-format packages # cannot be written. - def initialize gem, security_policy + def initialize(gem, security_policy) require 'fileutils' require 'zlib' Gem.load_yaml @@ -49,7 +49,7 @@ def contents ## # Extracts the files in this package into +destination_dir+ - def extract_files destination_dir + def extract_files(destination_dir) verify errstr = "Error reading files from gem" @@ -78,9 +78,9 @@ def extract_files destination_dir FileUtils.rm_rf destination - FileUtils.mkdir_p File.dirname destination + FileUtils.mkdir_p File.dirname(destination), :mode => dir_mode && 0755 - File.open destination, 'wb', entry['mode'] do |out| + File.open destination, 'wb', file_mode(entry['mode']) do |out| out.write file_data end @@ -94,7 +94,7 @@ def extract_files destination_dir ## # Reads the file list section from the old-format gem +io+ - def file_list io # :nodoc: + def file_list(io) # :nodoc: header = String.new read_until_dashes io do |line| @@ -107,7 +107,7 @@ def file_list io # :nodoc: ## # Reads lines until a "---" separator is found - def read_until_dashes io # :nodoc: + def read_until_dashes(io) # :nodoc: while (line = io.gets) && line.chomp.strip != "---" do yield line if block_given? end @@ -116,7 +116,7 @@ def read_until_dashes io # :nodoc: ## # Skips the Ruby self-install header in +io+. - def skip_ruby io # :nodoc: + def skip_ruby(io) # :nodoc: loop do line = io.gets @@ -144,17 +144,9 @@ def spec end end - yaml_error = if RUBY_VERSION < '1.9' then - YAML::ParseError - elsif YAML.const_defined?(:ENGINE) && YAML::ENGINE.yamler == 'syck' then - YAML::ParseError - else - YAML::SyntaxError - end - begin @spec = Gem::Specification.from_yaml yaml - rescue yaml_error + rescue YAML::SyntaxError raise Gem::Exception, "Failed to parse gem specification out of gem file" end rescue ArgumentError diff --git a/lib/ruby/stdlib/rubygems/package/source.rb b/lib/ruby/stdlib/rubygems/package/source.rb index fe19776c385a..69701e55e9cf 100644 --- a/lib/ruby/stdlib/rubygems/package/source.rb +++ b/lib/ruby/stdlib/rubygems/package/source.rb @@ -1,4 +1,3 @@ # frozen_string_literal: true class Gem::Package::Source # :nodoc: end - diff --git a/lib/ruby/stdlib/rubygems/package/tar_header.rb b/lib/ruby/stdlib/rubygems/package/tar_header.rb index fa1f5b6e46f3..c37612772ab1 100644 --- a/lib/ruby/stdlib/rubygems/package/tar_header.rb +++ b/lib/ruby/stdlib/rubygems/package/tar_header.rb @@ -50,7 +50,7 @@ class Gem::Package::TarHeader :uid, :uname, :version, - ] + ].freeze ## # Pack format for a tar header @@ -107,8 +107,8 @@ def self.from(stream) new :name => fields.shift, :mode => strict_oct(fields.shift), - :uid => strict_oct(fields.shift), - :gid => strict_oct(fields.shift), + :uid => oct_or_256based(fields.shift), + :gid => oct_or_256based(fields.shift), :size => strict_oct(fields.shift), :mtime => strict_oct(fields.shift), :checksum => strict_oct(fields.shift), @@ -130,11 +130,20 @@ def self.strict_oct(str) raise ArgumentError, "#{str.inspect} is not an octal string" end + def self.oct_or_256based(str) + # \x80 flags a positive 256-based number + # \ff flags a negative 256-based number + # In case we have a match, parse it as a signed binary value + # in big-endian order, except that the high-order bit is ignored. + return str.unpack('N2').last if str =~ /\A[\x80\xff]/n + strict_oct(str) + end + ## # Creates a new TarHeader using +vals+ def initialize(vals) - unless vals[:name] && vals[:size] && vals[:prefix] && vals[:mode] then + unless vals[:name] && vals[:size] && vals[:prefix] && vals[:mode] raise ArgumentError, ":name, :size, :prefix and :mode required" end diff --git a/lib/ruby/stdlib/rubygems/package/tar_reader.rb b/lib/ruby/stdlib/rubygems/package/tar_reader.rb index 1098336e36ee..f64915eaeabb 100644 --- a/lib/ruby/stdlib/rubygems/package/tar_reader.rb +++ b/lib/ruby/stdlib/rubygems/package/tar_reader.rb @@ -92,11 +92,9 @@ def each # NOTE: Do not call #rewind during #each def rewind - if @init_pos == 0 then - raise Gem::Package::NonSeekableIO unless @io.respond_to? :rewind + if @init_pos == 0 @io.rewind else - raise Gem::Package::NonSeekableIO unless @io.respond_to? :pos= @io.pos = @init_pos end end @@ -106,7 +104,7 @@ def rewind # yields it. Rewinds the tar file to the beginning when the block # terminates. - def seek name # :yields: entry + def seek(name) # :yields: entry found = find do |entry| entry.full_name == name end diff --git a/lib/ruby/stdlib/rubygems/package/tar_reader/entry.rb b/lib/ruby/stdlib/rubygems/package/tar_reader/entry.rb index 5f958edc2f55..19054c16356a 100644 --- a/lib/ruby/stdlib/rubygems/package/tar_reader/entry.rb +++ b/lib/ruby/stdlib/rubygems/package/tar_reader/entry.rb @@ -64,7 +64,7 @@ def eof? # Full name of the tar entry def full_name - if @header.prefix != "" then + if @header.prefix != "" File.join @header.prefix, @header.name else @header.name @@ -119,6 +119,12 @@ def pos bytes_read end + def size + @header.size + end + + alias length size + ## # Reads +len+ bytes from the tar file entry, or the rest of the entry if # nil @@ -137,7 +143,19 @@ def read(len = nil) ret end - alias readpartial read # :nodoc: + def readpartial(maxlen = nil, outbuf = "".b) + check_closed + + raise EOFError if @read >= @header.size + + maxlen ||= @header.size - @read + max_read = [maxlen, @header.size - @read].min + + @io.readpartial(max_read, outbuf) + @read += outbuf.size + + outbuf + end ## # Rewinds to the beginning of the tar file entry @@ -145,8 +163,6 @@ def read(len = nil) def rewind check_closed - raise Gem::Package::NonSeekableIO unless @io.respond_to? :pos= - @io.pos = @orig_pos @read = 0 end diff --git a/lib/ruby/stdlib/rubygems/package/tar_test_case.rb b/lib/ruby/stdlib/rubygems/package/tar_test_case.rb index 46ac949587f0..75978c8ed00a 100644 --- a/lib/ruby/stdlib/rubygems/package/tar_test_case.rb +++ b/lib/ruby/stdlib/rubygems/package/tar_test_case.rb @@ -52,7 +52,7 @@ def assert_headers_equal(expected, actual) name = fields.shift length = fields.shift.to_i - if name == "checksum" then + if name == "checksum" chksum_off = offset offset += length next @@ -94,13 +94,7 @@ def header(type, fname, dname, length, mode, mtime, checksum = nil, linkname = " ASCIIZ(dname, 155) # char prefix[155]; ASCII + (Z unless filled) ] - format = "C100C8C8C8C12C12C8CC100C6C2C32C32C8C8C155" - h = if RUBY_VERSION >= "1.9" then - arr.join - else - arr = arr.join("").split(//).map{|x| x[0]} - arr.pack format - end + h = arr.join ret = h + "\0" * (512 - h.size) assert_equal(512, ret.size) ret diff --git a/lib/ruby/stdlib/rubygems/package/tar_writer.rb b/lib/ruby/stdlib/rubygems/package/tar_writer.rb index 6ff619be3071..87ee39a94498 100644 --- a/lib/ruby/stdlib/rubygems/package/tar_writer.rb +++ b/lib/ruby/stdlib/rubygems/package/tar_writer.rb @@ -106,8 +106,6 @@ def initialize(io) def add_file(name, mode) # :yields: io check_closed - raise Gem::Package::NonSeekableIO unless @io.respond_to? :pos= - name, prefix = split_name name init_pos = @io.pos @@ -125,7 +123,7 @@ def add_file(name, mode) # :yields: io header = Gem::Package::TarHeader.new :name => name, :mode => mode, :size => size, :prefix => prefix, - :mtime => Time.now + :mtime => ENV["SOURCE_DATE_EPOCH"] ? Time.at(ENV["SOURCE_DATE_EPOCH"].to_i).utc : Time.now @io.write header @io.pos = final_pos @@ -141,11 +139,11 @@ def add_file(name, mode) # :yields: io # # The created digest object is returned. - def add_file_digest name, mode, digest_algorithms # :yields: io + def add_file_digest(name, mode, digest_algorithms) # :yields: io digests = digest_algorithms.map do |digest_algorithm| digest = digest_algorithm.new digest_name = - if digest.respond_to? :name then + if digest.respond_to? :name digest.name else /::([^:]+)$/ =~ digest_algorithm.name @@ -174,7 +172,7 @@ def add_file_digest name, mode, digest_algorithms # :yields: io # # Returns the digest. - def add_file_signed name, mode, signer + def add_file_signed(name, mode, signer) digest_algorithms = [ signer.digest_algorithm, Digest::SHA512, @@ -186,7 +184,7 @@ def add_file_signed name, mode, signer signature_digest = digests.values.compact.find do |digest| digest_name = - if digest.respond_to? :name then + if digest.respond_to? :name digest.name else digest.class.name[/::([^:]+)\z/, 1] @@ -197,7 +195,7 @@ def add_file_signed name, mode, signer raise "no #{signer.digest_name} in #{digests.values.compact}" unless signature_digest - if signer.key then + if signer.key signature = signer.sign signature_digest.digest add_file_simple "#{name}.sig", 0444, signature.length do |io| @@ -219,7 +217,7 @@ def add_file_simple(name, mode, size) # :yields: io header = Gem::Package::TarHeader.new(:name => name, :mode => mode, :size => size, :prefix => prefix, - :mtime => Time.now).to_s + :mtime => ENV["SOURCE_DATE_EPOCH"] ? Time.at(ENV["SOURCE_DATE_EPOCH"].to_i).utc : Time.now).to_s @io.write header os = BoundedStream.new @io, size @@ -247,7 +245,7 @@ def add_symlink(name, target, mode) :size => 0, :typeflag => "2", :linkname => target, :prefix => prefix, - :mtime => Time.now).to_s + :mtime => ENV["SOURCE_DATE_EPOCH"] ? Time.at(ENV["SOURCE_DATE_EPOCH"].to_i).utc : Time.now).to_s @io.write header @@ -300,7 +298,7 @@ def mkdir(name, mode) header = Gem::Package::TarHeader.new :name => name, :mode => mode, :typeflag => "5", :size => 0, :prefix => prefix, - :mtime => Time.now + :mtime => ENV["SOURCE_DATE_EPOCH"] ? Time.at(ENV["SOURCE_DATE_EPOCH"].to_i).utc : Time.now @io.write header @@ -311,12 +309,12 @@ def mkdir(name, mode) # Splits +name+ into a name and prefix that can fit in the TarHeader def split_name(name) # :nodoc: - if name.bytesize > 256 then + if name.bytesize > 256 raise Gem::Package::TooLongFileName.new("File \"#{name}\" has a too long path (should be 256 or less)") end prefix = '' - if name.bytesize > 100 then + if name.bytesize > 100 parts = name.split('/', -1) # parts are never empty here name = parts.pop # initially empty for names with a trailing slash ("foo/.../bar/") prefix = parts.join('/') # if empty, then it's impossible to split (parts is empty too) @@ -325,11 +323,11 @@ def split_name(name) # :nodoc: prefix = parts.join('/') end - if name.bytesize > 100 or prefix.empty? then + if name.bytesize > 100 or prefix.empty? raise Gem::Package::TooLongFileName.new("File \"#{prefix}/#{name}\" has a too long name (should be 100 or less)") end - if prefix.bytesize > 155 then + if prefix.bytesize > 155 raise Gem::Package::TooLongFileName.new("File \"#{prefix}/#{name}\" has a too long base path (should be 155 or less)") end end diff --git a/lib/ruby/stdlib/rubygems/package_task.rb b/lib/ruby/stdlib/rubygems/package_task.rb index d554e3697b86..a11d09fb2190 100644 --- a/lib/ruby/stdlib/rubygems/package_task.rb +++ b/lib/ruby/stdlib/rubygems/package_task.rb @@ -126,4 +126,3 @@ def define end end - diff --git a/lib/ruby/stdlib/rubygems/path_support.rb b/lib/ruby/stdlib/rubygems/path_support.rb index 618bc793c4e1..ed680d655371 100644 --- a/lib/ruby/stdlib/rubygems/path_support.rb +++ b/lib/ruby/stdlib/rubygems/path_support.rb @@ -23,12 +23,14 @@ class Gem::PathSupport # hashtable, or defaults to ENV, the system environment. # def initialize(env) - @home = env["GEM_HOME"] || Gem.default_dir + @home = env["GEM_HOME"] || Gem.default_dir - if File::ALT_SEPARATOR then - @home = @home.gsub(File::ALT_SEPARATOR, File::SEPARATOR) + if File::ALT_SEPARATOR + @home = @home.gsub(File::ALT_SEPARATOR, File::SEPARATOR) end + @home = expand(@home) + @path = split_gem_path env["GEM_PATH"], @home @spec_cache_dir = env["GEM_SPEC_CACHE"] || Gem.default_spec_cache_dir @@ -41,7 +43,7 @@ def initialize(env) ## # Split the Gem search path (as reported by Gem.path). - def split_gem_path gpaths, home + def split_gem_path(gpaths, home) # FIX: it should be [home, *path], not [*path, home] gem_path = [] @@ -54,7 +56,7 @@ def split_gem_path gpaths, home gem_path += default_path end - if File::ALT_SEPARATOR then + if File::ALT_SEPARATOR gem_path.map! do |this_path| this_path.gsub File::ALT_SEPARATOR, File::SEPARATOR end @@ -65,7 +67,7 @@ def split_gem_path gpaths, home gem_path = default_path end - gem_path.uniq + gem_path.map { |path| expand(path) }.uniq end # Return the default Gem path @@ -77,4 +79,12 @@ def default_path end gem_path end + + def expand(path) + if File.directory?(path) + File.realpath(path) + else + path + end + end end diff --git a/lib/ruby/stdlib/rubygems/platform.rb b/lib/ruby/stdlib/rubygems/platform.rb index 2dd9ed5782c7..f8fe4dad546a 100644 --- a/lib/ruby/stdlib/rubygems/platform.rb +++ b/lib/ruby/stdlib/rubygems/platform.rb @@ -56,7 +56,7 @@ def initialize(arch) when String then arch = arch.split '-' - if arch.length > 2 and arch.last !~ /\d/ then # reassemble x86-linux-gnu + if arch.length > 2 and arch.last !~ /\d/ # reassemble x86-linux-gnu extra = arch.pop arch.last << "-#{extra}" end @@ -68,7 +68,7 @@ def initialize(arch) else cpu end - if arch.length == 2 and arch.last =~ /^\d+(\.\d+)?$/ then # for command-line + if arch.length == 2 and arch.last =~ /^\d+(\.\d+)?$/ # for command-line @os, @version = arch return end @@ -195,12 +195,11 @@ def =~(other) # A pure-Ruby gem that may use Gem::Specification#extensions to build # binary files. - RUBY = 'ruby' + RUBY = 'ruby'.freeze ## # A platform-specific gem that is built for the packaging Ruby's platform. # This will be replaced with Gem::Platform::local. - CURRENT = 'current' + CURRENT = 'current'.freeze end - diff --git a/lib/ruby/stdlib/rubygems/psych_tree.rb b/lib/ruby/stdlib/rubygems/psych_tree.rb index 41a7314b5360..6f399a289efc 100644 --- a/lib/ruby/stdlib/rubygems/psych_tree.rb +++ b/lib/ruby/stdlib/rubygems/psych_tree.rb @@ -18,7 +18,7 @@ def register(target, obj) end # This is ported over from the yaml_tree in 1.9.3 - def format_time time + def format_time(time) if time.utc? time.strftime("%Y-%m-%d %H:%M:%S.%9N Z") else diff --git a/lib/ruby/stdlib/rubygems/rdoc.rb b/lib/ruby/stdlib/rubygems/rdoc.rb index 7043bd2a3169..4e16fbb86fea 100644 --- a/lib/ruby/stdlib/rubygems/rdoc.rb +++ b/lib/ruby/stdlib/rubygems/rdoc.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true require 'rubygems' -require 'rubygems/user_interaction' -require 'fileutils' begin gem 'rdoc' @@ -15,321 +13,12 @@ Gem.finish_resolve end -loaded_hook = false - begin require 'rdoc/rubygems_hook' - loaded_hook = true module Gem RDoc = ::RDoc::RubygemsHook end + + Gem.done_installing(&Gem::RDoc.method(:generation_hook)) rescue LoadError end - -## -# Gem::RDoc provides methods to generate RDoc and ri data for installed gems. -# It works for RDoc 1.0.1 (in Ruby 1.8) up to RDoc 3.6. -# -# This implementation is considered obsolete. The RDoc project is the -# appropriate location to find this functionality. This file provides the -# hooks to load RDoc generation code from the "rdoc" gem and a fallback in -# case the installed version of RDoc does not have them. - -class Gem::RDoc # :nodoc: all - - include Gem::UserInteraction - extend Gem::UserInteraction - - @rdoc_version = nil - @specs = [] - - ## - # Force installation of documentation? - - attr_accessor :force - - ## - # Generate rdoc? - - attr_accessor :generate_rdoc - - ## - # Generate ri data? - - attr_accessor :generate_ri - - class << self - - ## - # Loaded version of RDoc. Set by ::load_rdoc - - attr_reader :rdoc_version - - end - - ## - # Post installs hook that generates documentation for each specification in - # +specs+ - - def self.generation_hook installer, specs - start = Time.now - types = installer.document - - generate_rdoc = types.include? 'rdoc' - generate_ri = types.include? 'ri' - - specs.each do |spec| - new(spec, generate_rdoc, generate_ri).generate - end - - return unless generate_rdoc or generate_ri - - duration = (Time.now - start).to_i - names = specs.map(&:name).join ', ' - - say "Done installing documentation for #{names} after #{duration} seconds" - end - - ## - # Loads the RDoc generator - - def self.load_rdoc - return if @rdoc_version - - require 'rdoc/rdoc' - - @rdoc_version = if ::RDoc.const_defined? :VERSION then - Gem::Version.new ::RDoc::VERSION - else - Gem::Version.new '1.0.1' - end - - rescue LoadError => e - raise Gem::DocumentError, "RDoc is not installed: #{e}" - end - - ## - # Creates a new documentation generator for +spec+. RDoc and ri data - # generation can be enabled or disabled through +generate_rdoc+ and - # +generate_ri+ respectively. - # - # Only +generate_ri+ is enabled by default. - - def initialize spec, generate_rdoc = true, generate_ri = true - @doc_dir = spec.doc_dir - @file_info = nil - @force = false - @rdoc = nil - @spec = spec - - @generate_rdoc = generate_rdoc - @generate_ri = generate_ri - - @rdoc_dir = spec.doc_dir 'rdoc' - @ri_dir = spec.doc_dir 'ri' - end - - ## - # Removes legacy rdoc arguments from +args+ - #-- - # TODO move to RDoc::Options - - def delete_legacy_args args - args.delete '--inline-source' - args.delete '--promiscuous' - args.delete '-p' - args.delete '--one-file' - end - - ## - # Generates documentation using the named +generator+ ("darkfish" or "ri") - # and following the given +options+. - # - # Documentation will be generated into +destination+ - - def document generator, options, destination - generator_name = generator - - options = options.dup - options.exclude ||= [] # TODO maybe move to RDoc::Options#finish - options.setup_generator generator - options.op_dir = destination - options.finish - - generator = options.generator.new @rdoc.store, options - - @rdoc.options = options - @rdoc.generator = generator - - say "Installing #{generator_name} documentation for #{@spec.full_name}" - - FileUtils.mkdir_p options.op_dir - - Dir.chdir options.op_dir do - begin - @rdoc.class.current = @rdoc - @rdoc.generator.generate @file_info - ensure - @rdoc.class.current = nil - end - end - end - - ## - # Generates RDoc and ri data - - def generate - return unless @generate_ri or @generate_rdoc - - setup - - options = nil - - if Gem::Requirement.new('< 3').satisfied_by? self.class.rdoc_version then - generate_legacy - return - end - - ::RDoc::TopLevel.reset # TODO ::RDoc::RDoc.reset - ::RDoc::Parser::C.reset - - args = @spec.rdoc_options - args.concat @spec.source_paths - args.concat @spec.extra_rdoc_files - - case config_args = Gem.configuration[:rdoc] - when String then - args = args.concat config_args.split - when Array then - args = args.concat config_args - end - - delete_legacy_args args - - Dir.chdir @spec.full_gem_path do - options = ::RDoc::Options.new - options.default_title = "#{@spec.full_name} Documentation" - options.parse args - end - - options.quiet = !Gem.configuration.really_verbose - - @rdoc = new_rdoc - @rdoc.options = options - - say "Parsing documentation for #{@spec.full_name}" - - Dir.chdir @spec.full_gem_path do - @file_info = @rdoc.parse_files options.files - end - - document 'ri', options, @ri_dir if - @generate_ri and (@force or not File.exist? @ri_dir) - - document 'darkfish', options, @rdoc_dir if - @generate_rdoc and (@force or not File.exist? @rdoc_dir) - end - - ## - # Generates RDoc and ri data for legacy RDoc versions. This method will not - # exist in future versions. - - def generate_legacy - if @generate_rdoc then - FileUtils.rm_rf @rdoc_dir - say "Installing RDoc documentation for #{@spec.full_name}" - legacy_rdoc '--op', @rdoc_dir - end - - if @generate_ri then - FileUtils.rm_rf @ri_dir - say "Installing ri documentation for #{@spec.full_name}" - legacy_rdoc '--ri', '--op', @ri_dir - end - end - - ## - # Generates RDoc using a legacy version of RDoc from the ARGV-like +args+. - # This method will not exist in future versions. - - def legacy_rdoc *args - args << @spec.rdoc_options - args << '--quiet' - args << @spec.require_paths.clone - args << @spec.extra_rdoc_files - args << '--title' << "#{@spec.full_name} Documentation" - args = args.flatten.map do |arg| arg.to_s end - - delete_legacy_args args if - Gem::Requirement.new('>= 2.4.0') =~ self.class.rdoc_version - - r = new_rdoc - verbose { "rdoc #{args.join ' '}" } - - Dir.chdir @spec.full_gem_path do - begin - r.document args - rescue Errno::EACCES => e - dirname = File.dirname e.message.split("-")[1].strip - raise Gem::FilePermissionError, dirname - rescue Interrupt => e - raise e - rescue Exception => ex - alert_error "While generating documentation for #{@spec.full_name}" - ui.errs.puts "... MESSAGE: #{ex}" - ui.errs.puts "... RDOC args: #{args.join(' ')}" - ui.backtrace ex - ui.errs.puts "(continuing with the rest of the installation)" - end - end - end - - ## - # #new_rdoc creates a new RDoc instance. This method is provided only to - # make testing easier. - - def new_rdoc # :nodoc: - ::RDoc::RDoc.new - end - - ## - # Is rdoc documentation installed? - - def rdoc_installed? - File.exist? @rdoc_dir - end - - ## - # Removes generated RDoc and ri data - - def remove - base_dir = @spec.base_dir - - raise Gem::FilePermissionError, base_dir unless File.writable? base_dir - - FileUtils.rm_rf @rdoc_dir - FileUtils.rm_rf @ri_dir - end - - ## - # Is ri data installed? - - def ri_installed? - File.exist? @ri_dir - end - - ## - # Prepares the spec for documentation generation - - def setup - self.class.load_rdoc - - raise Gem::FilePermissionError, @doc_dir if - File.exist?(@doc_dir) and not File.writable?(@doc_dir) - - FileUtils.mkdir_p @doc_dir unless File.exist? @doc_dir - end - -end unless loaded_hook - -Gem.done_installing(&Gem::RDoc.method(:generation_hook)) diff --git a/lib/ruby/stdlib/rubygems/remote_fetcher.rb b/lib/ruby/stdlib/rubygems/remote_fetcher.rb index 80fb1aaaa5c1..43cfca963422 100644 --- a/lib/ruby/stdlib/rubygems/remote_fetcher.rb +++ b/lib/ruby/stdlib/rubygems/remote_fetcher.rb @@ -1,9 +1,10 @@ # frozen_string_literal: true require 'rubygems' require 'rubygems/request' +require 'rubygems/request/connection_pools' +require 'rubygems/s3_uri_signer' require 'rubygems/uri_formatter' require 'rubygems/user_interaction' -require 'rubygems/request/connection_pools' require 'resolv' ## @@ -71,13 +72,10 @@ def self.fetcher # HTTP_PROXY_PASS) # * :no_proxy: ignore environment variables and _don't_ use a proxy # - # +dns+: An object to use for DNS resolution of the API endpoint. - # By default, use Resolv::DNS. - # # +headers+: A set of additional HTTP headers to be sent to the server when # fetching the gem. - def initialize(proxy=nil, dns=Resolv::DNS.new, headers={}) + def initialize(proxy=nil, dns=nil, headers={}) require 'net/http' require 'stringio' require 'time' @@ -90,34 +88,9 @@ def initialize(proxy=nil, dns=Resolv::DNS.new, headers={}) @pool_lock = Mutex.new @cert_files = Gem::Request.get_cert_files - @dns = dns @headers = headers end - ## - # Given a source at +uri+, calculate what hostname to actually - # connect to query the data for it. - - def api_endpoint(uri) - host = uri.host - - begin - res = @dns.getresource "_rubygems._tcp.#{host}", - Resolv::DNS::Resource::IN::SRV - rescue Resolv::ResolvError => e - verbose "Getting SRV record failed: #{e}" - uri - else - target = res.target.to_s.strip - - if URI("http://" + target).host.end_with?(".#{host}") - return URI.parse "#{uri.scheme}://#{target}#{uri.path}" - end - - uri - end - end - ## # Given a name and requirement, downloads this gem into cache and returns the # filename. Returns nil if the gem cannot be located. @@ -125,7 +98,7 @@ def api_endpoint(uri) # Should probably be integrated with #download below, but that will be a # larger, more encompassing effort. -erikh - def download_to_cache dependency + def download_to_cache(dependency) found, _ = Gem::SpecFetcher.fetcher.spec_for_dependency dependency return if found.empty? @@ -142,9 +115,9 @@ def download_to_cache dependency def download(spec, source_uri, install_dir = Gem.dir) cache_dir = - if Dir.pwd == install_dir then # see fetch_command + if Dir.pwd == install_dir # see fetch_command install_dir - elsif File.writable? install_dir then + elsif File.writable? install_dir File.join install_dir, "cache" else File.join Gem.user_dir, "cache" @@ -164,9 +137,7 @@ def download(spec, source_uri, install_dir = Gem.dir) begin source_uri = URI.parse(source_uri) rescue - source_uri = URI.parse(URI.const_defined?(:DEFAULT_PARSER) ? - URI::DEFAULT_PARSER.escape(source_uri.to_s) : - URI.escape(source_uri.to_s)) + source_uri = URI.parse(URI::DEFAULT_PARSER.escape(source_uri.to_s)) end end @@ -179,7 +150,7 @@ def download(spec, source_uri, install_dir = Gem.dir) # REFACTOR: be sure to clean up fake fetcher when you do this... cleaner case scheme when 'http', 'https', 's3' then - unless File.exist? local_gem_path then + unless File.exist? local_gem_path begin verbose "Downloading gem #{gem_file_name}" @@ -203,7 +174,7 @@ def download(spec, source_uri, install_dir = Gem.dir) path = source_uri.path path = File.dirname(path) if File.extname(path) == '.gem' - remote_gem_path = correct_for_windows_path(File.join(path, 'gems', gem_file_name)) + remote_gem_path = Gem::Util.correct_for_windows_path(File.join(path, 'gems', gem_file_name)) FileUtils.cp(remote_gem_path, local_gem_path) rescue Errno::EACCES @@ -213,7 +184,7 @@ def download(spec, source_uri, install_dir = Gem.dir) verbose "Using local gem #{local_gem_path}" when nil then # TODO test for local overriding cache source_path = if Gem.win_platform? && source_uri.scheme && - !source_uri.path.include?(':') then + !source_uri.path.include?(':') "#{source_uri.scheme}:#{source_uri.path}" else source_uri.path @@ -239,14 +210,14 @@ def download(spec, source_uri, install_dir = Gem.dir) ## # File Fetcher. Dispatched by +fetch_path+. Use it instead. - def fetch_file uri, *_ - Gem.read_binary correct_for_windows_path uri.path + def fetch_file(uri, *_) + Gem.read_binary Gem::Util.correct_for_windows_path uri.path end ## # HTTP Fetcher. Dispatched by +fetch_path+. Use it instead. - def fetch_http uri, last_modified = nil, head = false, depth = 0 + def fetch_http(uri, last_modified = nil, head = false, depth = 0) fetch_type = head ? Net::HTTP::Head : Net::HTTP::Get response = request uri, fetch_type, last_modified do |req| headers.each { |k,v| req.add_field(k,v) } @@ -304,7 +275,8 @@ def fetch_path(uri, mtime = nil, head = false) raise rescue Timeout::Error raise UnknownHostError.new('timed out', uri.to_s) - rescue IOError, SocketError, SystemCallError => e + rescue IOError, SocketError, SystemCallError, + *(OpenSSL::SSL::SSLError if defined?(OpenSSL)) => e if e.message =~ /getaddrinfo/ raise UnknownHostError.new('no such name', uri.to_s) else @@ -313,15 +285,24 @@ def fetch_path(uri, mtime = nil, head = false) end def fetch_s3(uri, mtime = nil, head = false) - public_uri = sign_s3_url(uri) + begin + public_uri = s3_uri_signer(uri).sign + rescue Gem::S3URISigner::ConfigurationError, Gem::S3URISigner::InstanceProfileError => e + raise FetchError.new(e.message, "s3://#{uri.host}") + end fetch_https public_uri, mtime, head end + # we have our own signing code here to avoid a dependency on the aws-sdk gem + def s3_uri_signer(uri) + Gem::S3URISigner.new(uri) + end + ## # Downloads +uri+ to +path+ if necessary. If no path is given, it just # passes the data. - def cache_update_path uri, path = nil, update = true + def cache_update_path(uri, path = nil, update = true) mtime = path && File.stat(path).mtime rescue nil data = fetch_path(uri, mtime) @@ -346,14 +327,6 @@ def fetch_size(uri) # TODO: phase this out response['content-length'].to_i end - def correct_for_windows_path(path) - if path[0].chr == '/' && path[1].chr =~ /[a-z]/i && path[2].chr == ':' - path[1..-1] - else - path - end - end - ## # Performs a Net::HTTP request of type +request_class+ on +uri+ returning # a Net::HTTP response object. request maintains a table of persistent @@ -378,42 +351,16 @@ def close_all @pools.each_value {|pool| pool.close_all} end - protected - - # we have our own signing code here to avoid a dependency on the aws-sdk gem - # fortunately, a simple GET request isn't too complex to sign properly - def sign_s3_url(uri, expiration = nil) - require 'base64' - require 'openssl' - - unless uri.user && uri.password - raise FetchError.new("credentials needed in s3 source, like s3://key:secret@bucket-name/", uri.to_s) - end - - expiration ||= s3_expiration - canonical_path = "/#{uri.host}#{uri.path}" - payload = "GET\n\n\n#{expiration}\n#{canonical_path}" - digest = OpenSSL::HMAC.digest('sha1', uri.password, payload) - # URI.escape is deprecated, and there isn't yet a replacement that does quite what we want - signature = Base64.encode64(digest).gsub("\n", '').gsub(/[\+\/=]/) { |c| BASE64_URI_TRANSLATE[c] } - URI.parse("https://#{uri.host}.s3.amazonaws.com#{uri.path}?AWSAccessKeyId=#{uri.user}&Expires=#{expiration}&Signature=#{signature}") - end - - def s3_expiration - (Time.now + 3600).to_i # one hour from now - end - - BASE64_URI_TRANSLATE = { '+' => '%2B', '/' => '%2F', '=' => '%3D' }.freeze - private - def proxy_for proxy, uri + def proxy_for(proxy, uri) Gem::Request.proxy_uri(proxy || Gem::Request.get_proxy_from_env(uri.scheme)) end - def pools_for proxy + def pools_for(proxy) @pool_lock.synchronize do @pools[proxy] ||= Gem::Request::ConnectionPools.new proxy, @cert_files end end + end diff --git a/lib/ruby/stdlib/rubygems/request.rb b/lib/ruby/stdlib/rubygems/request.rb index 81699b98fef0..800960cfdc9a 100644 --- a/lib/ruby/stdlib/rubygems/request.rb +++ b/lib/ruby/stdlib/rubygems/request.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true require 'net/http' -require 'thread' require 'time' require 'rubygems/user_interaction' @@ -11,7 +10,7 @@ class Gem::Request ### # Legacy. This is used in tests. - def self.create_with_proxy uri, request_class, last_modified, proxy # :nodoc: + def self.create_with_proxy(uri, request_class, last_modified, proxy) # :nodoc: cert_files = get_cert_files proxy ||= get_proxy_from_env(uri.scheme) pool = ConnectionPools.new proxy_uri(proxy), cert_files @@ -19,7 +18,7 @@ def self.create_with_proxy uri, request_class, last_modified, proxy # :nodoc: new(uri, request_class, last_modified, pool.pool_for(uri)) end - def self.proxy_uri proxy # :nodoc: + def self.proxy_uri(proxy) # :nodoc: case proxy when :no_proxy then nil when URI::HTTP then proxy @@ -52,7 +51,7 @@ def self.configure_connection_for_https(connection, cert_files) Gem.configuration.ssl_verify_mode || OpenSSL::SSL::VERIFY_PEER store = OpenSSL::X509::Store.new - if Gem.configuration.ssl_client_cert then + if Gem.configuration.ssl_client_cert pem = File.read Gem.configuration.ssl_client_cert connection.cert = OpenSSL::X509::Certificate.new pem connection.key = OpenSSL::PKey::RSA.new pem @@ -86,7 +85,7 @@ def self.configure_connection_for_https(connection, cert_files) 'Unable to require openssl, install OpenSSL and rebuild Ruby (preferred) or use non-HTTPS sources') end - def self.verify_certificate store_context + def self.verify_certificate(store_context) depth = store_context.error_depth error = store_context.error_string number = store_context.error @@ -99,7 +98,7 @@ def self.verify_certificate store_context ui.alert_error extra_message if extra_message end - def self.verify_certificate_message error_number, cert + def self.verify_certificate_message(error_number, cert) return unless cert case error_number when OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED then @@ -118,9 +117,11 @@ def self.verify_certificate_message error_number, cert "Certificate #{cert.subject} has an invalid purpose" when OpenSSL::X509::V_ERR_SELF_SIGNED_CERT_IN_CHAIN then "Root certificate is not trusted (#{cert.subject})" - when OpenSSL::X509::V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, - OpenSSL::X509::V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE then + when OpenSSL::X509::V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY then "You must add #{cert.issuer} to your local trusted store" + when + OpenSSL::X509::V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE then + "Cannot verify certificate issued by #{cert.issuer}" end end @@ -138,7 +139,7 @@ def connection_for(uri) def fetch request = @request_class.new @uri.request_uri - unless @uri.nil? || @uri.user.nil? || @uri.user.empty? then + unless @uri.nil? || @uri.user.nil? || @uri.user.empty? request.basic_auth Gem::UriFormatter.new(@uri.user).unescape, Gem::UriFormatter.new(@uri.password).unescape end @@ -147,7 +148,7 @@ def fetch request.add_field 'Connection', 'keep-alive' request.add_field 'Keep-Alive', '30' - if @last_modified then + if @last_modified request.add_field 'If-Modified-Since', @last_modified.httpdate end @@ -160,7 +161,7 @@ def fetch # Returns a proxy URI for the given +scheme+ if one is set in the # environment variables. - def self.get_proxy_from_env scheme = 'http' + def self.get_proxy_from_env(scheme = 'http') _scheme = scheme.downcase _SCHEME = scheme.upcase env_proxy = ENV["#{_scheme}_proxy"] || ENV["#{_SCHEME}_PROXY"] @@ -172,7 +173,7 @@ def self.get_proxy_from_env scheme = 'http' uri = URI(Gem::UriFormatter.new(env_proxy).normalize) - if uri and uri.user.nil? and uri.password.nil? then + if uri and uri.user.nil? and uri.password.nil? user = ENV["#{_scheme}_proxy_user"] || ENV["#{_SCHEME}_PROXY_USER"] password = ENV["#{_scheme}_proxy_pass"] || ENV["#{_SCHEME}_PROXY_PASS"] @@ -183,7 +184,7 @@ def self.get_proxy_from_env scheme = 'http' uri end - def perform_request request # :nodoc: + def perform_request(request) # :nodoc: connection = connection_for @uri retried = false @@ -275,14 +276,14 @@ def user_agent ruby_version += 'dev' if RUBY_PATCHLEVEL == -1 ua << " Ruby/#{ruby_version} (#{RUBY_RELEASE_DATE}" - if RUBY_PATCHLEVEL >= 0 then + if RUBY_PATCHLEVEL >= 0 ua << " patchlevel #{RUBY_PATCHLEVEL}" - elsif defined?(RUBY_REVISION) then + elsif defined?(RUBY_REVISION) ua << " revision #{RUBY_REVISION}" end ua << ")" - ua << " #{RUBY_ENGINE}" if defined?(RUBY_ENGINE) and RUBY_ENGINE != 'ruby' + ua << " #{RUBY_ENGINE}" if RUBY_ENGINE != 'ruby' ua end diff --git a/lib/ruby/stdlib/rubygems/request/connection_pools.rb b/lib/ruby/stdlib/rubygems/request/connection_pools.rb index 31fc609800ca..c02f083b638b 100644 --- a/lib/ruby/stdlib/rubygems/request/connection_pools.rb +++ b/lib/ruby/stdlib/rubygems/request/connection_pools.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require 'thread' class Gem::Request::ConnectionPools # :nodoc: @@ -9,19 +8,19 @@ class << self attr_accessor :client end - def initialize proxy_uri, cert_files + def initialize(proxy_uri, cert_files) @proxy_uri = proxy_uri @cert_files = cert_files @pools = {} @pool_mutex = Mutex.new end - def pool_for uri + def pool_for(uri) http_args = net_http_args(uri, @proxy_uri) key = http_args + [https?(uri)] @pool_mutex.synchronize do @pools[key] ||= - if https? uri then + if https? uri Gem::Request::HTTPSPool.new(http_args, @cert_files, @proxy_uri) else Gem::Request::HTTPPool.new(http_args, @cert_files, @proxy_uri) @@ -46,22 +45,35 @@ def get_no_proxy_from_env env_no_proxy.split(/\s*,\s*/) end - def https? uri + def https?(uri) uri.scheme.downcase == 'https' end - def no_proxy? host, env_no_proxy + def no_proxy?(host, env_no_proxy) host = host.downcase env_no_proxy.any? do |pattern| - pattern = pattern.downcase + env_no_proxy_pattern = pattern.downcase.dup - host[-pattern.length, pattern.length] == pattern or - (pattern.start_with? '.' and pattern[1..-1] == host) + # Remove dot in front of pattern for wildcard matching + env_no_proxy_pattern[0] = "" if env_no_proxy_pattern[0] == "." + + host_tokens = host.split(".") + pattern_tokens = env_no_proxy_pattern.split(".") + + intersection = (host_tokens - pattern_tokens) | (pattern_tokens - host_tokens) + + # When we do the split into tokens we miss a dot character, so add it back if we need it + missing_dot = intersection.length > 0 ? 1 : 0 + start = intersection.join(".").size + missing_dot + + no_proxy_host = host[start..-1] + + env_no_proxy_pattern == no_proxy_host end end - def net_http_args uri, proxy_uri + def net_http_args(uri, proxy_uri) # URI::Generic#hostname was added in ruby 1.9.3, use it if exists, otherwise # don't support IPv6 literals and use host. hostname = uri.respond_to?(:hostname) ? uri.hostname : uri.host @@ -69,7 +81,7 @@ def net_http_args uri, proxy_uri no_proxy = get_no_proxy_from_env - if proxy_uri and not no_proxy?(hostname, no_proxy) then + if proxy_uri and not no_proxy?(hostname, no_proxy) proxy_hostname = proxy_uri.respond_to?(:hostname) ? proxy_uri.hostname : proxy_uri.host net_http_args + [ proxy_hostname, @@ -77,7 +89,7 @@ def net_http_args uri, proxy_uri Gem::UriFormatter.new(proxy_uri.user).unescape, Gem::UriFormatter.new(proxy_uri.password).unescape, ] - elsif no_proxy? hostname, no_proxy then + elsif no_proxy? hostname, no_proxy net_http_args + [nil, nil] else net_http_args @@ -85,4 +97,3 @@ def net_http_args uri, proxy_uri end end - diff --git a/lib/ruby/stdlib/rubygems/request/http_pool.rb b/lib/ruby/stdlib/rubygems/request/http_pool.rb index bfcd15399d5b..a85fc2bdf651 100644 --- a/lib/ruby/stdlib/rubygems/request/http_pool.rb +++ b/lib/ruby/stdlib/rubygems/request/http_pool.rb @@ -8,7 +8,7 @@ class Gem::Request::HTTPPool # :nodoc: attr_reader :cert_files, :proxy_uri - def initialize http_args, cert_files, proxy_uri + def initialize(http_args, cert_files, proxy_uri) @http_args = http_args @cert_files = cert_files @proxy_uri = proxy_uri @@ -20,7 +20,7 @@ def checkout @queue.pop || make_connection end - def checkin connection + def checkin(connection) @queue.push connection end @@ -39,10 +39,9 @@ def make_connection setup_connection Gem::Request::ConnectionPools.client.new(*@http_args) end - def setup_connection connection + def setup_connection(connection) connection.start connection end end - diff --git a/lib/ruby/stdlib/rubygems/request/https_pool.rb b/lib/ruby/stdlib/rubygems/request/https_pool.rb index e82c2440e1c2..50f42d9e0d16 100644 --- a/lib/ruby/stdlib/rubygems/request/https_pool.rb +++ b/lib/ruby/stdlib/rubygems/request/https_pool.rb @@ -2,10 +2,8 @@ class Gem::Request::HTTPSPool < Gem::Request::HTTPPool # :nodoc: private - def setup_connection connection + def setup_connection(connection) Gem::Request.configure_connection_for_https(connection, @cert_files) super end end - - diff --git a/lib/ruby/stdlib/rubygems/request_set.rb b/lib/ruby/stdlib/rubygems/request_set.rb index 89f47616eca1..6017d15d13eb 100644 --- a/lib/ruby/stdlib/rubygems/request_set.rb +++ b/lib/ruby/stdlib/rubygems/request_set.rb @@ -91,7 +91,7 @@ class Gem::RequestSet # # set = Gem::RequestSet.new nokogiri, pg - def initialize *deps + def initialize(*deps) @dependencies = deps @always_install = [] @@ -119,8 +119,8 @@ def initialize *deps ## # Declare that a gem of name +name+ with +reqs+ requirements is needed. - def gem name, *reqs - if dep = @dependency_names[name] then + def gem(name, *reqs) + if dep = @dependency_names[name] dep.requirement.concat reqs else dep = Gem::Dependency.new name, *reqs @@ -132,7 +132,7 @@ def gem name, *reqs ## # Add +deps+ Gem::Dependency objects to the set. - def import deps + def import(deps) @dependencies.concat deps end @@ -143,7 +143,7 @@ def import deps # The +installer+ will be +nil+ if a gem matching the request was already # installed. - def install options, &block # :yields: request, installer + def install(options, &block) # :yields: request, installer if dir = options[:install_dir] requests = install_into dir, false, options, &block return requests @@ -152,12 +152,39 @@ def install options, &block # :yields: request, installer @prerelease = options[:prerelease] requests = [] + download_queue = Queue.new + # Create a thread-safe list of gems to download sorted_requests.each do |req| - if req.installed? then + download_queue << req + end + + # Create N threads in a pool, have them download all the gems + threads = Gem.configuration.concurrent_downloads.times.map do + # When a thread pops this item, it knows to stop running. The symbol + # is queued here so that there will be one symbol per thread. + download_queue << :stop + + Thread.new do + # The pop method will block waiting for items, so the only way + # to stop a thread from running is to provide a final item that + # means the thread should stop. + while req = download_queue.pop + break if req == :stop + req.spec.download options unless req.installed? + end + end + end + + # Wait for all the downloads to finish before continuing + threads.each(&:value) + + # Install requested gems after they have been downloaded + sorted_requests.each do |req| + if req.installed? req.spec.spec.build_extensions - if @always_install.none? { |spec| spec == req.spec.spec } then + if @always_install.none? { |spec| spec == req.spec.spec } yield req, nil if block_given? next end @@ -203,7 +230,7 @@ def install options, &block # :yields: request, installer # If +:without_groups+ is given in the +options+, those groups in the gem # dependencies file are not used. See Gem::Installer for other +options+. - def install_from_gemdeps options, &block + def install_from_gemdeps(options, &block) gemdeps = options[:gemdeps] @install_dir = options[:install_dir] || Gem.dir @@ -228,7 +255,7 @@ def install_from_gemdeps options, &block else installed = install options, &block - if options.fetch :lock, true then + if options.fetch :lock, true lockfile = Gem::RequestSet::Lockfile.build self, gemdeps, gem_deps_api.dependencies lockfile.write @@ -238,7 +265,7 @@ def install_from_gemdeps options, &block end end - def install_into dir, force = true, options = {} + def install_into(dir, force = true, options = {}) gem_home, ENV['GEM_HOME'] = ENV['GEM_HOME'], dir existing = force ? [] : specs_in(dir) @@ -256,7 +283,7 @@ def install_into dir, force = true, options = {} sorted_requests.each do |request| spec = request.spec - if existing.find { |s| s.full_name == spec.full_name } then + if existing.find { |s| s.full_name == spec.full_name } yield request, nil if block_given? next end @@ -278,7 +305,7 @@ def install_into dir, force = true, options = {} ## # Call hooks on installed gems - def install_hooks requests, options + def install_hooks(requests, options) specs = requests.map do |request| case request when Gem::Resolver::ActivationRequest then @@ -300,7 +327,7 @@ def install_hooks requests, options ## # Load a dependency management file. - def load_gemdeps path, without_groups = [], installing = false + def load_gemdeps(path, without_groups = [], installing = false) @git_set = Gem::Resolver::GitSet.new @vendor_set = Gem::Resolver::VendorSet.new @source_set = Gem::Resolver::SourceSet.new @@ -321,29 +348,29 @@ def load_gemdeps path, without_groups = [], installing = false gf.load end - def pretty_print q # :nodoc: + def pretty_print(q) # :nodoc: q.group 2, '[RequestSet:', ']' do q.breakable - if @remote then + if @remote q.text 'remote' q.breakable end - if @prerelease then + if @prerelease q.text 'prerelease' q.breakable end - if @development_shallow then + if @development_shallow q.text 'shallow development' q.breakable - elsif @development then + elsif @development q.text 'development' q.breakable end - if @soft_missing then + if @soft_missing q.text 'soft missing' end @@ -367,7 +394,7 @@ def pretty_print q # :nodoc: # Resolve the requested dependencies and return an Array of Specification # objects to be activated. - def resolve set = Gem::Resolver::BestSet.new + def resolve(set = Gem::Resolver::BestSet.new) @sets << set @sets << @git_set @sets << @vendor_set @@ -416,17 +443,17 @@ def specs @specs ||= @requests.map { |r| r.full_spec } end - def specs_in dir - Dir["#{dir}/specifications/*.gemspec"].map do |g| + def specs_in(dir) + Gem::Util.glob_files_in_dir("*.gemspec", File.join(dir, "specifications")).map do |g| Gem::Specification.load g end end - def tsort_each_node &block # :nodoc: + def tsort_each_node(&block) # :nodoc: @requests.each(&block) end - def tsort_each_child node # :nodoc: + def tsort_each_child(node) # :nodoc: node.spec.dependencies.each do |dep| next if dep.type == :development and not @development @@ -434,7 +461,7 @@ def tsort_each_child node # :nodoc: dep.match? r.spec.name, r.spec.version, @prerelease } - unless match then + unless match next if dep.type == :development and @development_shallow next if @soft_missing raise Gem::DependencyError, diff --git a/lib/ruby/stdlib/rubygems/request_set/gem_dependency_api.rb b/lib/ruby/stdlib/rubygems/request_set/gem_dependency_api.rb index 867086cc0e3d..6d11e4f29994 100644 --- a/lib/ruby/stdlib/rubygems/request_set/gem_dependency_api.rb +++ b/lib/ruby/stdlib/rubygems/request_set/gem_dependency_api.rb @@ -43,12 +43,13 @@ class Gem::RequestSet::GemDependencyAPI :mri_20 => %w[ruby], :mri_21 => %w[ruby], :rbx => %w[rbx], - :ruby => %w[ruby rbx maglev], - :ruby_18 => %w[ruby rbx maglev], - :ruby_19 => %w[ruby rbx maglev], - :ruby_20 => %w[ruby rbx maglev], - :ruby_21 => %w[ruby rbx maglev], - } + :truffleruby => %w[truffleruby], + :ruby => %w[ruby rbx maglev truffleruby], + :ruby_18 => %w[ruby rbx maglev truffleruby], + :ruby_19 => %w[ruby rbx maglev truffleruby], + :ruby_20 => %w[ruby rbx maglev truffleruby], + :ruby_21 => %w[ruby rbx maglev truffleruby], + }.freeze mswin = Gem::Platform.new 'x86-mswin32' mswin64 = Gem::Platform.new 'x64-mswin64' @@ -85,10 +86,11 @@ class Gem::RequestSet::GemDependencyAPI :ruby_19 => Gem::Platform::RUBY, :ruby_20 => Gem::Platform::RUBY, :ruby_21 => Gem::Platform::RUBY, + :truffleruby => Gem::Platform::RUBY, :x64_mingw => x64_mingw, :x64_mingw_20 => x64_mingw, :x64_mingw_21 => x64_mingw - } + }.freeze gt_eq_0 = Gem::Requirement.new '>= 0' tilde_gt_1_8_0 = Gem::Requirement.new '~> 1.8.0' @@ -126,10 +128,11 @@ class Gem::RequestSet::GemDependencyAPI :ruby_19 => tilde_gt_1_9_0, :ruby_20 => tilde_gt_2_0_0, :ruby_21 => tilde_gt_2_1_0, + :truffleruby => gt_eq_0, :x64_mingw => gt_eq_0, :x64_mingw_20 => tilde_gt_2_0_0, :x64_mingw_21 => tilde_gt_2_1_0, - } + }.freeze WINDOWS = { # :nodoc: :mingw => :only, @@ -160,7 +163,7 @@ class Gem::RequestSet::GemDependencyAPI :x64_mingw => :only, :x64_mingw_20 => :only, :x64_mingw_21 => :only, - } + }.freeze ## # The gems required by #gem statements in the gem.deps.rb file @@ -191,7 +194,7 @@ class Gem::RequestSet::GemDependencyAPI # Creates a new GemDependencyAPI that will add dependencies to the # Gem::RequestSet +set+ based on the dependency API description in +path+. - def initialize set, path + def initialize(set, path) @set = set @path = path @@ -228,7 +231,7 @@ def initialize set, path # Adds +dependencies+ to the request set if any of the +groups+ are allowed. # This is used for gemspec dependencies. - def add_dependencies groups, dependencies # :nodoc: + def add_dependencies(groups, dependencies) # :nodoc: return unless (groups & @without_groups).empty? dependencies.each do |dep| @@ -241,7 +244,7 @@ def add_dependencies groups, dependencies # :nodoc: ## # Finds a gemspec with the given +name+ that lives at +path+. - def find_gemspec name, path # :nodoc: + def find_gemspec(name, path) # :nodoc: glob = File.join path, "#{name}.gemspec" spec_files = Dir[glob] @@ -269,7 +272,7 @@ def find_gemspec name, path # :nodoc: # In installing mode certain restrictions are ignored such as ruby version # mismatch checks. - def installing= installing # :nodoc: + def installing=(installing) # :nodoc: @installing = installing end @@ -353,7 +356,7 @@ def load # tag: :: # Use the given tag for git:, gist: and github: dependencies. - def gem name, *requirements + def gem(name, *requirements) options = requirements.pop if requirements.last.kind_of?(Hash) options ||= {} @@ -369,9 +372,9 @@ def gem name, *requirements duplicate = @dependencies.include? name @dependencies[name] = - if requirements.empty? and not source_set then + if requirements.empty? and not source_set Gem::Requirement.default - elsif source_set then + elsif source_set Gem::Requirement.source_set else Gem::Requirement.create requirements @@ -387,7 +390,7 @@ def gem name, *requirements gem_requires name, options - if duplicate then + if duplicate warn <<-WARNING Gem dependencies file #{@path} requires #{name} more than once. WARNING @@ -401,8 +404,8 @@ def gem name, *requirements # # Returns +true+ if the gist or git option was handled. - def gem_git name, options # :nodoc: - if gist = options.delete(:gist) then + def gem_git(name, options) # :nodoc: + if gist = options.delete(:gist) options[:git] = "https://gist.github.com/#{gist}.git" end @@ -424,7 +427,7 @@ def gem_git name, options # :nodoc: # # Returns reference for the git gem. - def gem_git_reference options # :nodoc: + def gem_git_reference(options) # :nodoc: ref = options.delete :ref branch = options.delete :branch tag = options.delete :tag @@ -457,7 +460,7 @@ def gem_git_reference options # :nodoc: # # Returns +true+ if the custom source option was handled. - def gem_git_source name, options # :nodoc: + def gem_git_source(name, options) # :nodoc: return unless git_source = (@git_sources.keys & options.keys).last source_callback = @git_sources[git_source] @@ -478,7 +481,7 @@ def gem_git_source name, options # :nodoc: # Handles the :group and :groups +options+ for the gem with the given # +name+. - def gem_group name, options # :nodoc: + def gem_group(name, options) # :nodoc: g = options.delete :group all_groups = g ? Array(g) : [] @@ -497,7 +500,7 @@ def gem_group name, options # :nodoc: # # Returns +true+ if the path option was handled. - def gem_path name, options # :nodoc: + def gem_path(name, options) # :nodoc: return unless directory = options.delete(:path) pin_gem_source name, :path, directory @@ -514,7 +517,7 @@ def gem_path name, options # :nodoc: # # Returns +true+ if the source option was handled. - def gem_source name, options # :nodoc: + def gem_source(name, options) # :nodoc: return unless source = options.delete(:source) pin_gem_source name, :source, source @@ -530,7 +533,7 @@ def gem_source name, options # :nodoc: # Handles the platforms: option from +options+. Returns true if the # platform matches the current platform. - def gem_platforms options # :nodoc: + def gem_platforms(options) # :nodoc: platform_names = Array(options.delete :platform) platform_names.concat Array(options.delete :platforms) platform_names.concat @current_platforms if @current_platforms @@ -543,7 +546,7 @@ def gem_platforms options # :nodoc: next false unless Gem::Platform.match platform - if engines = ENGINE_MAP[platform_name] then + if engines = ENGINE_MAP[platform_name] next false unless engines.include? Gem.ruby_engine end @@ -564,9 +567,9 @@ def gem_platforms options # :nodoc: # Records the require: option from +options+ and adds those files, or the # default file to the require list for +name+. - def gem_requires name, options # :nodoc: - if options.include? :require then - if requires = options.delete(:require) then + def gem_requires(name, options) # :nodoc: + if options.include? :require + if requires = options.delete(:require) @requires[name].concat Array requires end else @@ -587,7 +590,7 @@ def gem_requires name, options # :nodoc: # gem 'activerecord' # end - def git repository + def git(repository) @current_repository = repository yield @@ -601,7 +604,7 @@ def git repository # for use in gems built from git repositories. You must provide a block # that accepts a git repository name for expansion. - def git_source name, &callback + def git_source(name, &callback) @git_sources[name] = callback end @@ -634,7 +637,7 @@ def gem_deps_file # :nodoc: # The group to add development dependencies to. By default this is # :development. Only one group may be specified. - def gemspec options = {} + def gemspec(options = {}) name = options.delete(:name) || '{,*}' path = options.delete(:path) || '.' development_group = options.delete(:development_group) || :development @@ -679,7 +682,7 @@ def gemspec options = {} # development`. See `gem help install` and `gem help gem_dependencies` for # further details. - def group *groups + def group(*groups) @current_groups = groups yield @@ -692,7 +695,7 @@ def group *groups # Pins the gem +name+ to the given +source+. Adding a gem with the same # name from a different +source+ will raise an exception. - def pin_gem_source name, type = :default, source = nil + def pin_gem_source(name, type = :default, source = nil) source_description = case type when :default then '(default)' @@ -754,7 +757,7 @@ def pin_gem_source name, type = :default, source = nil # NOTE: There is inconsistency in what environment a platform matches. You # may need to read the source to know the exact details. - def platform *platforms + def platform(*platforms) @current_platforms = platforms yield @@ -779,9 +782,9 @@ def platform *platforms # You may also provide +engine:+ and +engine_version:+ options to restrict # this gem dependencies file to a particular ruby engine and its engine # version. This matching is performed by using the RUBY_ENGINE and - # engine_specific VERSION constants. (For JRuby, JRUBY_VERSION). + # RUBY_ENGINE_VERSION constants. - def ruby version, options = {} + def ruby(version, options = {}) engine = options[:engine] engine_version = options[:engine_version] @@ -791,26 +794,24 @@ def ruby version, options = {} return true if @installing - unless RUBY_VERSION == version then + unless RUBY_VERSION == version message = "Your Ruby version is #{RUBY_VERSION}, " + "but your #{gem_deps_file} requires #{version}" raise Gem::RubyVersionMismatch, message end - if engine and engine != Gem.ruby_engine then + if engine and engine != Gem.ruby_engine message = "Your Ruby engine is #{Gem.ruby_engine}, " + "but your #{gem_deps_file} requires #{engine}" raise Gem::RubyVersionMismatch, message end - if engine_version then - my_engine_version = Object.const_get "#{Gem.ruby_engine.upcase}_VERSION" - - if engine_version != my_engine_version then + if engine_version + if engine_version != RUBY_ENGINE_VERSION message = - "Your Ruby engine version is #{Gem.ruby_engine} #{my_engine_version}, " + + "Your Ruby engine version is #{Gem.ruby_engine} #{RUBY_ENGINE_VERSION}, " + "but your #{gem_deps_file} requires #{engine} #{engine_version}" raise Gem::RubyVersionMismatch, message @@ -834,7 +835,7 @@ def ruby version, options = {} # * The +prepend:+ option is not supported. If you wish to order sources # then list them in your preferred order. - def source url + def source(url) Gem.sources.clear if @default_sources @default_sources = false @@ -842,8 +843,4 @@ def source url Gem.sources << url end - # TODO: remove this typo name at RubyGems 3.0 - - Gem::RequestSet::GemDepedencyAPI = self # :nodoc: - end diff --git a/lib/ruby/stdlib/rubygems/request_set/lockfile.rb b/lib/ruby/stdlib/rubygems/request_set/lockfile.rb index 76ad17d4862b..1b374660f0f8 100644 --- a/lib/ruby/stdlib/rubygems/request_set/lockfile.rb +++ b/lib/ruby/stdlib/rubygems/request_set/lockfile.rb @@ -29,7 +29,7 @@ class ParseError < Gem::Exception # Raises a ParseError with the given +message+ which was encountered at a # +line+ and +column+ while parsing. - def initialize message, column, line, path + def initialize(message, column, line, path) @line = line @column = column @path = path @@ -41,13 +41,13 @@ def initialize message, column, line, path # Creates a new Lockfile for the given +request_set+ and +gem_deps_file+ # location. - def self.build request_set, gem_deps_file, dependencies = nil + def self.build(request_set, gem_deps_file, dependencies = nil) request_set.resolve dependencies ||= requests_to_deps request_set.sorted_requests new request_set, gem_deps_file, dependencies end - def self.requests_to_deps requests # :nodoc: + def self.requests_to_deps(requests) # :nodoc: deps = {} requests.each do |request| @@ -56,7 +56,7 @@ def self.requests_to_deps requests # :nodoc: requirement = request.request.dependency.requirement deps[name] = if [Gem::Resolver::VendorSpecification, - Gem::Resolver::GitSpecification].include? spec.class then + Gem::Resolver::GitSpecification].include? spec.class Gem::Requirement.source_set else requirement @@ -71,7 +71,7 @@ def self.requests_to_deps requests # :nodoc: attr_reader :platforms - def initialize request_set, gem_deps_file, dependencies + def initialize(request_set, gem_deps_file, dependencies) @set = request_set @dependencies = dependencies @gem_deps_file = File.expand_path(gem_deps_file) @@ -82,7 +82,7 @@ def initialize request_set, gem_deps_file, dependencies @platforms = [] end - def add_DEPENDENCIES out # :nodoc: + def add_DEPENDENCIES(out) # :nodoc: out << "DEPENDENCIES" out.concat @dependencies.sort_by { |name,| name }.map { |name, requirement| @@ -92,7 +92,7 @@ def add_DEPENDENCIES out # :nodoc: out << nil end - def add_GEM out, spec_groups # :nodoc: + def add_GEM(out, spec_groups) # :nodoc: return if spec_groups.empty? source_groups = spec_groups.values.flatten.group_by do |request| @@ -122,7 +122,7 @@ def add_GEM out, spec_groups # :nodoc: end end - def add_GIT out, git_requests + def add_GIT(out, git_requests) return if git_requests.empty? by_repository_revision = git_requests.group_by do |request| @@ -148,11 +148,11 @@ def add_GIT out, git_requests end end - def relative_path_from dest, base # :nodoc: + def relative_path_from(dest, base) # :nodoc: dest = File.expand_path(dest) base = File.expand_path(base) - if dest.index(base) == 0 then + if dest.index(base) == 0 offset = dest[base.size+1..-1] return '.' unless offset @@ -163,7 +163,7 @@ def relative_path_from dest, base # :nodoc: end end - def add_PATH out, path_requests # :nodoc: + def add_PATH(out, path_requests) # :nodoc: return if path_requests.empty? out << "PATH" @@ -178,7 +178,7 @@ def add_PATH out, path_requests # :nodoc: out << nil end - def add_PLATFORMS out # :nodoc: + def add_PLATFORMS(out) # :nodoc: out << "PLATFORMS" platforms = requests.map { |request| request.spec.platform }.uniq diff --git a/lib/ruby/stdlib/rubygems/request_set/lockfile/parser.rb b/lib/ruby/stdlib/rubygems/request_set/lockfile/parser.rb index ebea94018823..0fe0405e3217 100644 --- a/lib/ruby/stdlib/rubygems/request_set/lockfile/parser.rb +++ b/lib/ruby/stdlib/rubygems/request_set/lockfile/parser.rb @@ -3,7 +3,7 @@ class Gem::RequestSet::Lockfile::Parser ### # Parses lockfiles - def initialize tokenizer, set, platforms, filename = nil + def initialize(tokenizer, set, platforms, filename = nil) @tokens = tokenizer @filename = filename @set = set @@ -41,10 +41,10 @@ def parse ## # Gets the next token for a Lockfile - def get expected_types = nil, expected_value = nil # :nodoc: + def get(expected_types = nil, expected_value = nil) # :nodoc: token = @tokens.shift - if expected_types and not Array(expected_types).include? token.type then + if expected_types and not Array(expected_types).include? token.type unget token message = "unexpected token [#{token.type.inspect}, #{token.value.inspect}], " + @@ -53,7 +53,7 @@ def get expected_types = nil, expected_value = nil # :nodoc: raise Gem::RequestSet::Lockfile::ParseError.new message, token.column, token.line, @filename end - if expected_value and expected_value != token.value then + if expected_value and expected_value != token.value unget token message = "unexpected token [#{token.type.inspect}, #{token.value.inspect}], " + @@ -93,7 +93,7 @@ def parse_DEPENDENCIES # :nodoc: get :r_paren - if peek[0] == :bang then + if peek[0] == :bang requirements.clear requirements << pinned_requirement(token.value) @@ -144,7 +144,7 @@ def parse_GEM # :nodoc: type = token.type data = token.value - if type == :text and column == 4 then + if type == :text and column == 4 version, platform = data.split '-', 2 platform = @@ -183,7 +183,7 @@ def parse_GIT # :nodoc: type = peek.type value = peek.value - if type == :entry and %w[branch ref tag].include? value then + if type == :entry and %w[branch ref tag].include? value get get :text @@ -214,7 +214,7 @@ def parse_GIT # :nodoc: type = token.type data = token.value - if type == :text and column == 4 then + if type == :text and column == 4 last_spec = set.add_git_spec name, data, repository, revision, true else dependency = parse_dependency name, data @@ -261,7 +261,7 @@ def parse_PATH # :nodoc: type = token.type data = token.value - if type == :text and column == 4 then + if type == :text and column == 4 last_spec = set.add_vendor_gem name, directory else dependency = parse_dependency name, data @@ -294,7 +294,7 @@ def parse_PLATFORMS # :nodoc: # Parses the requirements following the dependency +name+ and the +op+ for # the first token of the requirements and returns a Gem::Dependency object. - def parse_dependency name, op # :nodoc: + def parse_dependency(name, op) # :nodoc: return Gem::Dependency.new name, op unless peek[0] == :text version = get(:text).value @@ -314,7 +314,7 @@ def parse_dependency name, op # :nodoc: private - def skip type # :nodoc: + def skip(type) # :nodoc: @tokens.skip type end @@ -325,30 +325,19 @@ def peek # :nodoc: @tokens.peek end - if [].respond_to? :flat_map - def pinned_requirement name # :nodoc: - requirement = Gem::Dependency.new name - specification = @set.sets.flat_map { |set| - set.find_all(requirement) - }.compact.first + def pinned_requirement(name) # :nodoc: + requirement = Gem::Dependency.new name + specification = @set.sets.flat_map { |set| + set.find_all(requirement) + }.compact.first - specification && specification.version - end - else # FIXME: remove when 1.8 is dropped - def pinned_requirement name # :nodoc: - requirement = Gem::Dependency.new name - specification = @set.sets.map { |set| - set.find_all(requirement) - }.flatten(1).compact.first - - specification && specification.version - end + specification && specification.version end ## # Ungets the last token retrieved by #get - def unget token # :nodoc: + def unget(token) # :nodoc: @tokens.unshift token end end diff --git a/lib/ruby/stdlib/rubygems/request_set/lockfile/tokenizer.rb b/lib/ruby/stdlib/rubygems/request_set/lockfile/tokenizer.rb index a758743dda8d..bb69c85fb45e 100644 --- a/lib/ruby/stdlib/rubygems/request_set/lockfile/tokenizer.rb +++ b/lib/ruby/stdlib/rubygems/request_set/lockfile/tokenizer.rb @@ -5,11 +5,11 @@ class Gem::RequestSet::Lockfile::Tokenizer Token = Struct.new :type, :value, :column, :line EOF = Token.new :EOF - def self.from_file file + def self.from_file(file) new File.read(file), file end - def initialize input, filename = nil, line = 0, pos = 0 + def initialize(input, filename = nil, line = 0, pos = 0) @line = line @line_pos = pos @tokens = [] @@ -17,7 +17,7 @@ def initialize input, filename = nil, line = 0, pos = 0 tokenize input end - def make_parser set, platforms + def make_parser(set, platforms) Gem::RequestSet::Lockfile::Parser.new self, set, platforms, @filename end @@ -25,7 +25,7 @@ def to_a @tokens.map { |token| [token.type, token.value, token.column, token.line] } end - def skip type + def skip(type) @tokens.shift while not @tokens.empty? and peek.type == type end @@ -33,7 +33,7 @@ def skip type # Calculates the column (by byte) and the line of the current token based on # +byte_offset+. - def token_pos byte_offset # :nodoc: + def token_pos(byte_offset) # :nodoc: [byte_offset - @line_pos, @line] end @@ -41,7 +41,7 @@ def empty? @tokens.empty? end - def unshift token + def unshift(token) @tokens.unshift token end @@ -56,7 +56,7 @@ def peek private - def tokenize input + def tokenize(input) require 'strscan' s = StringScanner.new input @@ -65,7 +65,7 @@ def tokenize input pos = s.pos if leading_whitespace = s.scan(/ +/) - if s.scan(/[<|=>]{7}/) then + if s.scan(/[<|=>]{7}/) message = "your #{@filename} contains merge conflict markers" column, line = token_pos pos @@ -80,7 +80,7 @@ def tokenize input @line += 1 token when s.scan(/[A-Z]+/) then - if leading_whitespace then + if leading_whitespace text = s.matched text += s.scan(/[^\s)]*/).to_s # in case of no match Token.new(:text, text, *token_pos(pos)) diff --git a/lib/ruby/stdlib/rubygems/requirement.rb b/lib/ruby/stdlib/rubygems/requirement.rb index 430f3512808d..2e4ce000218c 100644 --- a/lib/ruby/stdlib/rubygems/requirement.rb +++ b/lib/ruby/stdlib/rubygems/requirement.rb @@ -2,10 +2,6 @@ require "rubygems/version" require "rubygems/deprecate" -# If we're being loaded after yaml was already required, then -# load our yaml + workarounds now. -Gem.load_yaml if defined? ::YAML - ## # A Requirement is a set of one or more version restrictions. It supports a # few (=, !=, >, <, >=, <=, ~>) different restriction operators. @@ -22,22 +18,22 @@ class Gem::Requirement ">=" => lambda { |v, r| v >= r }, "<=" => lambda { |v, r| v <= r }, "~>" => lambda { |v, r| v >= r && v.release < r.bump } - } + }.freeze SOURCE_SET_REQUIREMENT = Struct.new(:for_lockfile).new "!" # :nodoc: quoted = OPS.keys.map { |k| Regexp.quote k }.join "|" - PATTERN_RAW = "\\s*(#{quoted})?\\s*(#{Gem::Version::VERSION_PATTERN})\\s*" # :nodoc: + PATTERN_RAW = "\\s*(#{quoted})?\\s*(#{Gem::Version::VERSION_PATTERN})\\s*".freeze # :nodoc: ## # A regular expression that matches a requirement - PATTERN = /\A#{PATTERN_RAW}\z/ + PATTERN = /\A#{PATTERN_RAW}\z/.freeze ## # The default requirement matches any version - DefaultRequirement = [">=", Gem::Version.new(0)] + DefaultRequirement = [">=", Gem::Version.new(0)].freeze ## # Raised when a bad requirement is encountered @@ -51,7 +47,7 @@ class BadRequirementError < ArgumentError; end # If the input is "weird", the default version requirement is # returned. - def self.create *inputs + def self.create(*inputs) return new inputs if inputs.length > 1 input = inputs.shift @@ -64,7 +60,7 @@ def self.create *inputs when '!' then source_set else - if input.respond_to? :to_str then + if input.respond_to? :to_str new [input.to_str] else default @@ -98,7 +94,7 @@ def self.source_set # :nodoc: # parse("1.0") # => ["=", Gem::Version.new("1.0")] # parse(Gem::Version.new("1.0")) # => ["=, Gem::Version.new("1.0")] - def self.parse obj + def self.parse(obj) return ["=", obj] if Gem::Version === obj unless PATTERN =~ obj.to_s @@ -124,7 +120,7 @@ def self.parse obj # requirements are ignored. An empty set of +requirements+ is the # same as ">= 0". - def initialize *requirements + def initialize(*requirements) requirements = requirements.flatten requirements.compact! requirements.uniq! @@ -140,7 +136,7 @@ def initialize *requirements ## # Concatenates the +new+ requirements onto this requirement. - def concat new + def concat(new) new = new.flatten new.compact! new.uniq! @@ -198,7 +194,7 @@ def marshal_dump # :nodoc: [@requirements] end - def marshal_load array # :nodoc: + def marshal_load(array) # :nodoc: @requirements = array[0] fix_syck_default_key_in_requirements @@ -213,7 +209,7 @@ def yaml_initialize(tag, vals) # :nodoc: fix_syck_default_key_in_requirements end - def init_with coder # :nodoc: + def init_with(coder) # :nodoc: yaml_initialize coder.tag, coder.map end @@ -221,7 +217,7 @@ def to_yaml_properties # :nodoc: ["@requirements"] end - def encode_with coder # :nodoc: + def encode_with(coder) # :nodoc: coder.add 'requirements', @requirements end @@ -233,7 +229,7 @@ def prerelease? requirements.any? { |r| r.last.prerelease? } end - def pretty_print q # :nodoc: + def pretty_print(q) # :nodoc: q.group 1, 'Gem::Requirement.new(', ')' do q.pp as_list end @@ -242,7 +238,7 @@ def pretty_print q # :nodoc: ## # True if +version+ satisfies this Requirement. - def satisfied_by? version + def satisfied_by?(version) raise ArgumentError, "Need a Gem::Version: #{version.inspect}" unless Gem::Version === version # #28965: syck has a bug with unquoted '=' YAML.loading as YAML::DefaultKey @@ -265,9 +261,24 @@ def to_s # :nodoc: as_list.join ", " end - def == other # :nodoc: + def ==(other) # :nodoc: return unless Gem::Requirement === other - requirements == other.requirements + + # An == check is always necessary + return false unless requirements == other.requirements + + # An == check is sufficient unless any requirements use ~> + return true unless _tilde_requirements.any? + + # If any requirements use ~> we use the stricter `#eql?` that also checks + # that version precision is the same + _tilde_requirements.eql?(other._tilde_requirements) + end + + protected + + def _tilde_requirements + requirements.select { |r| r.first == "~>" } end private @@ -284,7 +295,7 @@ def fix_syck_default_key_in_requirements # :nodoc: end def sort_requirements! # :nodoc: - @requirements.sort! do |l, r| + @requirements.sort! do |l, r| comp = l.last <=> r.last # first, sort by the requirement's version next comp unless comp == 0 l.first <=> r.first # then, sort by the operator (for stability) diff --git a/lib/ruby/stdlib/rubygems/resolver.rb b/lib/ruby/stdlib/rubygems/resolver.rb index 13ee035e4c2a..46276f3260eb 100644 --- a/lib/ruby/stdlib/rubygems/resolver.rb +++ b/lib/ruby/stdlib/rubygems/resolver.rb @@ -59,7 +59,7 @@ class Gem::Resolver # uniform manner. If one of the +sets+ is itself a ComposedSet its sets are # flattened into the result ComposedSet. - def self.compose_sets *sets + def self.compose_sets(*sets) sets.compact! sets = sets.map do |set| @@ -87,7 +87,7 @@ def self.compose_sets *sets # Creates a Resolver that queries only against the already installed gems # for the +needed+ dependencies. - def self.for_current_gems needed + def self.for_current_gems(needed) new needed, Gem::Resolver::CurrentSet.new end @@ -99,7 +99,7 @@ def self.for_current_gems needed # satisfy the Dependencies. This defaults to IndexSet, which will query # rubygems.org. - def initialize needed, set = nil + def initialize(needed, set = nil) @set = set || Gem::Resolver::IndexSet.new @needed = needed @@ -112,14 +112,14 @@ def initialize needed, set = nil @stats = Gem::Resolver::Stats.new end - def explain stage, *data # :nodoc: + def explain(stage, *data) # :nodoc: return unless DEBUG_RESOLVER d = data.map { |x| x.pretty_inspect }.join(", ") $stderr.printf "%10s %s\n", stage.to_s.upcase, d end - def explain_list stage # :nodoc: + def explain_list(stage) # :nodoc: return unless DEBUG_RESOLVER data = yield @@ -133,7 +133,7 @@ def explain_list stage # :nodoc: # # Returns the Specification and the ActivationRequest - def activation_request dep, possible # :nodoc: + def activation_request(dep, possible) # :nodoc: spec = possible.pop explain :activate, [spec.full_name, possible.size] @@ -145,7 +145,7 @@ def activation_request dep, possible # :nodoc: return spec, activation_request end - def requests s, act, reqs=[] # :nodoc: + def requests(s, act, reqs=[]) # :nodoc: return reqs if @ignore_dependencies s.fetch_development_dependencies if @development @@ -171,7 +171,7 @@ def requests s, act, reqs=[] # :nodoc: include Molinillo::UI def output - @output ||= debug? ? $stdout : File.open(Gem::Util::NULL_DEVICE, 'w') + @output ||= debug? ? $stdout : File.open(IO::NULL, 'w') end def debug? @@ -197,7 +197,7 @@ def resolve # Extracts the specifications that may be able to fulfill +dependency+ and # returns those that match the local platform and all those that match. - def find_possible dependency # :nodoc: + def find_possible(dependency) # :nodoc: all = @set.find_all dependency if (skip_dep_gems = skip_gems[dependency.name]) && !skip_dep_gems.empty? @@ -216,7 +216,7 @@ def find_possible dependency # :nodoc: ## # Returns the gems in +specs+ that match the local platform. - def select_local_platforms specs # :nodoc: + def select_local_platforms(specs) # :nodoc: specs.select do |spec| Gem::Platform.installable? spec end @@ -314,11 +314,6 @@ def amount_constrained(dependency) end -## -# TODO remove in RubyGems 3 - -Gem::DependencyResolver = Gem::Resolver # :nodoc: - require 'rubygems/resolver/activation_request' require 'rubygems/resolver/conflict' require 'rubygems/resolver/dependency_request' diff --git a/lib/ruby/stdlib/rubygems/resolver/activation_request.rb b/lib/ruby/stdlib/rubygems/resolver/activation_request.rb index 135d75d6bc7d..b28e1bef32f5 100644 --- a/lib/ruby/stdlib/rubygems/resolver/activation_request.rb +++ b/lib/ruby/stdlib/rubygems/resolver/activation_request.rb @@ -22,13 +22,13 @@ class Gem::Resolver::ActivationRequest # +others_possible+ indicates that other specifications may also match this # activation request. - def initialize spec, request, others_possible = true + def initialize(spec, request, others_possible = true) @spec = spec @request = request @others_possible = others_possible end - def == other # :nodoc: + def ==(other) # :nodoc: case other when Gem::Specification @spec == other @@ -49,7 +49,7 @@ def development? ## # Downloads a gem at +path+ and returns the file path. - def download path + def download(path) Gem.ensure_gem_subdirectories path if @spec.respond_to? :sources @@ -97,7 +97,7 @@ def inspect # :nodoc: when false then # TODO remove at RubyGems 3 nil else - unless @others_possible.empty? then + unless @others_possible.empty? others = @others_possible.map { |s| s.full_name } " (others possible: #{others.join ', '})" end @@ -152,7 +152,7 @@ def parent @request.requester end - def pretty_print q # :nodoc: + def pretty_print(q) # :nodoc: q.group 2, '[Activation request', ']' do q.breakable q.pp @spec @@ -167,7 +167,7 @@ def pretty_print q # :nodoc: q.breakable q.text 'others possible' else - unless @others_possible.empty? then + unless @others_possible.empty? q.breakable q.text 'others ' q.pp @others_possible.map { |s| s.full_name } diff --git a/lib/ruby/stdlib/rubygems/resolver/api_set.rb b/lib/ruby/stdlib/rubygems/resolver/api_set.rb index ee3046af637a..6fd91e3b7300 100644 --- a/lib/ruby/stdlib/rubygems/resolver/api_set.rb +++ b/lib/ruby/stdlib/rubygems/resolver/api_set.rb @@ -25,7 +25,7 @@ class Gem::Resolver::APISet < Gem::Resolver::Set # API URL +dep_uri+ which is described at # http://guides.rubygems.org/rubygems-org-api - def initialize dep_uri = 'https://rubygems.org/api/v1/dependencies' + def initialize(dep_uri = 'https://rubygems.org/api/v1/dependencies') super() dep_uri = URI dep_uri unless URI === dep_uri # for ruby 1.8 @@ -43,7 +43,7 @@ def initialize dep_uri = 'https://rubygems.org/api/v1/dependencies' # Return an array of APISpecification objects matching # DependencyRequest +req+. - def find_all req + def find_all(req) res = [] return res unless @remote @@ -65,7 +65,7 @@ def find_all req # A hint run by the resolver to allow the Set to fetch # data for DependencyRequests +reqs+. - def prefetch reqs + def prefetch(reqs) return unless @remote names = reqs.map { |r| r.dependency.name } needed = names - @data.keys - @to_fetch @@ -93,7 +93,7 @@ def prefetch_now # :nodoc: end end - def pretty_print q # :nodoc: + def pretty_print(q) # :nodoc: q.group 2, '[APISet', ']' do q.breakable q.text "URI: #{@dep_uri}" @@ -107,7 +107,7 @@ def pretty_print q # :nodoc: ## # Return data for all versions of the gem +name+. - def versions name # :nodoc: + def versions(name) # :nodoc: if @data.key?(name) return @data[name] end @@ -123,4 +123,3 @@ def versions name # :nodoc: end end - diff --git a/lib/ruby/stdlib/rubygems/resolver/api_specification.rb b/lib/ruby/stdlib/rubygems/resolver/api_specification.rb index c4e8a7cb5467..9bbc09578865 100644 --- a/lib/ruby/stdlib/rubygems/resolver/api_specification.rb +++ b/lib/ruby/stdlib/rubygems/resolver/api_specification.rb @@ -27,7 +27,7 @@ def initialize(set, api_data) end end - def == other # :nodoc: + def ==(other) # :nodoc: self.class === other and @set == other.set and @name == other.name and @@ -46,7 +46,7 @@ def installable_platform? # :nodoc: Gem::Platform.match @platform end - def pretty_print q # :nodoc: + def pretty_print(q) # :nodoc: q.group 2, '[APISpecification', ']' do q.breakable q.text "name: #{name}" @@ -88,4 +88,3 @@ def source # :nodoc: end end - diff --git a/lib/ruby/stdlib/rubygems/resolver/best_set.rb b/lib/ruby/stdlib/rubygems/resolver/best_set.rb index 4479535abe3a..cc91b65c0bca 100644 --- a/lib/ruby/stdlib/rubygems/resolver/best_set.rb +++ b/lib/ruby/stdlib/rubygems/resolver/best_set.rb @@ -10,7 +10,7 @@ class Gem::Resolver::BestSet < Gem::Resolver::ComposedSet # Creates a BestSet for the given +sources+ or Gem::sources if none are # specified. +sources+ must be a Gem::SourceList. - def initialize sources = Gem.sources + def initialize(sources = Gem.sources) super() @sources = sources @@ -25,7 +25,7 @@ def pick_sets # :nodoc: end end - def find_all req # :nodoc: + def find_all(req) # :nodoc: pick_sets if @remote and @sets.empty? super @@ -35,13 +35,13 @@ def find_all req # :nodoc: retry end - def prefetch reqs # :nodoc: + def prefetch(reqs) # :nodoc: pick_sets if @remote and @sets.empty? super end - def pretty_print q # :nodoc: + def pretty_print(q) # :nodoc: q.group 2, '[BestSet', ']' do q.breakable q.text 'sets:' @@ -58,7 +58,7 @@ def pretty_print q # :nodoc: # # The calling method must retry the exception to repeat the lookup. - def replace_failed_api_set error # :nodoc: + def replace_failed_api_set(error) # :nodoc: uri = error.uri uri = URI uri unless URI === uri uri.query = nil @@ -76,4 +76,3 @@ def replace_failed_api_set error # :nodoc: end end - diff --git a/lib/ruby/stdlib/rubygems/resolver/composed_set.rb b/lib/ruby/stdlib/rubygems/resolver/composed_set.rb index 0b65942dcae0..4baac9c75bab 100644 --- a/lib/ruby/stdlib/rubygems/resolver/composed_set.rb +++ b/lib/ruby/stdlib/rubygems/resolver/composed_set.rb @@ -16,7 +16,7 @@ class Gem::Resolver::ComposedSet < Gem::Resolver::Set # Creates a new ComposedSet containing +sets+. Use # Gem::Resolver::compose_sets instead. - def initialize *sets + def initialize(*sets) super() @sets = sets @@ -26,7 +26,7 @@ def initialize *sets # When +allow_prerelease+ is set to +true+ prereleases gems are allowed to # match dependencies. - def prerelease= allow_prerelease + def prerelease=(allow_prerelease) super sets.each do |set| @@ -37,7 +37,7 @@ def prerelease= allow_prerelease ## # Sets the remote network access for all composed sets. - def remote= remote + def remote=(remote) super @sets.each { |set| set.remote = remote } @@ -50,7 +50,7 @@ def errors ## # Finds all specs matching +req+ in all sets. - def find_all req + def find_all(req) @sets.map do |s| s.find_all req end.flatten @@ -59,9 +59,8 @@ def find_all req ## # Prefetches +reqs+ in all sets. - def prefetch reqs + def prefetch(reqs) @sets.each { |s| s.prefetch(reqs) } end end - diff --git a/lib/ruby/stdlib/rubygems/resolver/conflict.rb b/lib/ruby/stdlib/rubygems/resolver/conflict.rb index 7997f92950d5..fb1e661b214f 100644 --- a/lib/ruby/stdlib/rubygems/resolver/conflict.rb +++ b/lib/ruby/stdlib/rubygems/resolver/conflict.rb @@ -27,7 +27,7 @@ def initialize(dependency, activated, failed_dep=dependency) @failed_dep = failed_dep end - def == other # :nodoc: + def ==(other) # :nodoc: self.class === other and @dependency == other.dependency and @activated == other.activated and @@ -57,7 +57,7 @@ def explanation requirement = dependency.requirement alternates = dependency.matching_specs.map { |spec| spec.full_name } - unless alternates.empty? then + unless alternates.empty? matching = <<-MATCHING.chomp Gems matching %s: @@ -97,7 +97,7 @@ def for_spec?(spec) @dependency.name == spec.name end - def pretty_print q # :nodoc: + def pretty_print(q) # :nodoc: q.group 2, '[Dependency conflict: ', ']' do q.breakable @@ -109,7 +109,7 @@ def pretty_print q # :nodoc: q.pp @dependency q.breakable - if @dependency == @failed_dep then + if @dependency == @failed_dep q.text ' failed' else q.text ' failed dependency ' @@ -121,7 +121,7 @@ def pretty_print q # :nodoc: ## # Path of activations from the +current+ list. - def request_path current + def request_path(current) path = [] while current do diff --git a/lib/ruby/stdlib/rubygems/resolver/current_set.rb b/lib/ruby/stdlib/rubygems/resolver/current_set.rb index 265c639f1530..d60e46389d13 100644 --- a/lib/ruby/stdlib/rubygems/resolver/current_set.rb +++ b/lib/ruby/stdlib/rubygems/resolver/current_set.rb @@ -6,9 +6,8 @@ class Gem::Resolver::CurrentSet < Gem::Resolver::Set - def find_all req + def find_all(req) req.dependency.matching_specs end end - diff --git a/lib/ruby/stdlib/rubygems/resolver/dependency_request.rb b/lib/ruby/stdlib/rubygems/resolver/dependency_request.rb index c2918911cd30..1984aa9ddc63 100644 --- a/lib/ruby/stdlib/rubygems/resolver/dependency_request.rb +++ b/lib/ruby/stdlib/rubygems/resolver/dependency_request.rb @@ -19,12 +19,12 @@ class Gem::Resolver::DependencyRequest # Creates a new DependencyRequest for +dependency+ from +requester+. # +requester may be nil if the request came from a user. - def initialize dependency, requester + def initialize(dependency, requester) @dependency = dependency @requester = requester end - def == other # :nodoc: + def ==(other) # :nodoc: case other when Gem::Dependency @dependency == other @@ -48,7 +48,7 @@ def development? # NOTE: #match? only matches prerelease versions when #dependency is a # prerelease dependency. - def match? spec, allow_prerelease = false + def match?(spec, allow_prerelease = false) @dependency.match? spec, nil, allow_prerelease end @@ -95,7 +95,7 @@ def request_context @requester ? @requester.request : "(unknown)" end - def pretty_print q # :nodoc: + def pretty_print(q) # :nodoc: q.group 2, '[Dependency request ', ']' do q.breakable q.text @dependency.to_s diff --git a/lib/ruby/stdlib/rubygems/resolver/git_set.rb b/lib/ruby/stdlib/rubygems/resolver/git_set.rb index 723a202d7aa6..6340b92faecf 100644 --- a/lib/ruby/stdlib/rubygems/resolver/git_set.rb +++ b/lib/ruby/stdlib/rubygems/resolver/git_set.rb @@ -43,7 +43,7 @@ def initialize # :nodoc: @specs = {} end - def add_git_gem name, repository, reference, submodules # :nodoc: + def add_git_gem(name, repository, reference, submodules) # :nodoc: @repositories[name] = [repository, reference] @need_submodules[repository] = submodules end @@ -56,7 +56,7 @@ def add_git_gem name, repository, reference, submodules # :nodoc: # This fills in the prefetch information as enough information about the gem # is present in the arguments. - def add_git_spec name, version, repository, reference, submodules # :nodoc: + def add_git_spec(name, version, repository, reference, submodules) # :nodoc: add_git_gem name, repository, reference, submodules source = Gem::Source::Git.new name, repository, reference @@ -77,7 +77,7 @@ def add_git_spec name, version, repository, reference, submodules # :nodoc: ## # Finds all git gems matching +req+ - def find_all req + def find_all(req) prefetch nil specs.values.select do |spec| @@ -88,7 +88,7 @@ def find_all req ## # Prefetches specifications from the git repositories in this set. - def prefetch reqs + def prefetch(reqs) return unless @specs.empty? @repositories.each do |name, (repository, reference)| @@ -104,7 +104,7 @@ def prefetch reqs end end - def pretty_print q # :nodoc: + def pretty_print(q) # :nodoc: q.group 2, '[GitSet', ']' do next if @repositories.empty? q.breakable @@ -120,4 +120,3 @@ def pretty_print q # :nodoc: end end - diff --git a/lib/ruby/stdlib/rubygems/resolver/git_specification.rb b/lib/ruby/stdlib/rubygems/resolver/git_specification.rb index 2448797d3fd1..f43cfba853e9 100644 --- a/lib/ruby/stdlib/rubygems/resolver/git_specification.rb +++ b/lib/ruby/stdlib/rubygems/resolver/git_specification.rb @@ -6,14 +6,14 @@ class Gem::Resolver::GitSpecification < Gem::Resolver::SpecSpecification - def == other # :nodoc: + def ==(other) # :nodoc: self.class === other and @set == other.set and @spec == other.spec and @source == other.source end - def add_dependency dependency # :nodoc: + def add_dependency(dependency) # :nodoc: spec.dependencies << dependency end @@ -21,7 +21,7 @@ def add_dependency dependency # :nodoc: # Installing a git gem only involves building the extensions and generating # the executables. - def install options = {} + def install(options = {}) require 'rubygems/installer' installer = Gem::Installer.for_spec spec, options @@ -35,7 +35,7 @@ def install options = {} installer.run_post_install_hooks end - def pretty_print q # :nodoc: + def pretty_print(q) # :nodoc: q.group 2, '[GitSpecification', ']' do q.breakable q.text "name: #{name}" @@ -56,4 +56,3 @@ def pretty_print q # :nodoc: end end - diff --git a/lib/ruby/stdlib/rubygems/resolver/index_set.rb b/lib/ruby/stdlib/rubygems/resolver/index_set.rb index 2450f14b4f8a..e32e1fa5baff 100644 --- a/lib/ruby/stdlib/rubygems/resolver/index_set.rb +++ b/lib/ruby/stdlib/rubygems/resolver/index_set.rb @@ -5,11 +5,11 @@ class Gem::Resolver::IndexSet < Gem::Resolver::Set - def initialize source = nil # :nodoc: + def initialize(source = nil) # :nodoc: super() @f = - if source then + if source sources = Gem::SourceList.from [source] Gem::SpecFetcher.new sources @@ -36,7 +36,7 @@ def initialize source = nil # :nodoc: # Return an array of IndexSpecification objects matching # DependencyRequest +req+. - def find_all req + def find_all(req) res = [] return res unless @remote @@ -44,7 +44,7 @@ def find_all req name = req.dependency.name @all[name].each do |uri, n| - if req.match? n, @prerelease then + if req.match? n, @prerelease res << Gem::Resolver::IndexSpecification.new( self, n.name, n.version, uri, n.platform) end @@ -53,7 +53,7 @@ def find_all req res end - def pretty_print q # :nodoc: + def pretty_print(q) # :nodoc: q.group 2, '[IndexSet', ']' do q.breakable q.text 'sources:' @@ -78,4 +78,3 @@ def pretty_print q # :nodoc: end end - diff --git a/lib/ruby/stdlib/rubygems/resolver/index_specification.rb b/lib/ruby/stdlib/rubygems/resolver/index_specification.rb index 4340f46943a7..ed9423791c4b 100644 --- a/lib/ruby/stdlib/rubygems/resolver/index_specification.rb +++ b/lib/ruby/stdlib/rubygems/resolver/index_specification.rb @@ -15,7 +15,7 @@ class Gem::Resolver::IndexSpecification < Gem::Resolver::Specification # The +name+, +version+ and +platform+ are the name, version and platform of # the gem. - def initialize set, name, version, source, platform + def initialize(set, name, version, source, platform) super() @set = set @@ -38,12 +38,12 @@ def inspect # :nodoc: '#<%s %s source %s>' % [self.class, full_name, @source] end - def pretty_print q # :nodoc: + def pretty_print(q) # :nodoc: q.group 2, '[Index specification', ']' do q.breakable q.text full_name - unless Gem::Platform::RUBY == @platform then + unless Gem::Platform::RUBY == @platform q.breakable q.text @platform.to_s end @@ -67,4 +67,3 @@ def spec # :nodoc: end end - diff --git a/lib/ruby/stdlib/rubygems/resolver/installed_specification.rb b/lib/ruby/stdlib/rubygems/resolver/installed_specification.rb index d9c6a5e5cf7b..9d996fc1dafb 100644 --- a/lib/ruby/stdlib/rubygems/resolver/installed_specification.rb +++ b/lib/ruby/stdlib/rubygems/resolver/installed_specification.rb @@ -5,7 +5,7 @@ class Gem::Resolver::InstalledSpecification < Gem::Resolver::SpecSpecification - def == other # :nodoc: + def ==(other) # :nodoc: self.class === other and @set == other.set and @spec == other.spec @@ -15,7 +15,7 @@ def == other # :nodoc: # This is a null install as this specification is already installed. # +options+ are ignored. - def install options = {} + def install(options = {}) yield nil end @@ -30,7 +30,7 @@ def installable_platform? super end - def pretty_print q # :nodoc: + def pretty_print(q) # :nodoc: q.group 2, '[InstalledSpecification', ']' do q.breakable q.text "name: #{name}" @@ -56,4 +56,3 @@ def source end end - diff --git a/lib/ruby/stdlib/rubygems/resolver/installer_set.rb b/lib/ruby/stdlib/rubygems/resolver/installer_set.rb index f24293c0a03e..f3827ad4e9c9 100644 --- a/lib/ruby/stdlib/rubygems/resolver/installer_set.rb +++ b/lib/ruby/stdlib/rubygems/resolver/installer_set.rb @@ -29,7 +29,7 @@ class Gem::Resolver::InstallerSet < Gem::Resolver::Set ## # Creates a new InstallerSet that will look for gems in +domain+. - def initialize domain + def initialize(domain) super() @domain = domain @@ -50,7 +50,7 @@ def initialize domain # Looks up the latest specification for +dependency+ and adds it to the # always_install list. - def add_always_install dependency + def add_always_install(dependency) request = Gem::Resolver::DependencyRequest.new dependency, nil found = find_all request @@ -65,7 +65,7 @@ def add_always_install dependency Gem::Platform.local === s.platform end - if found.empty? then + if found.empty? exc = Gem::UnsatisfiableDependencyError.new request exc.errors = errors @@ -83,7 +83,7 @@ def add_always_install dependency # Adds a local gem requested using +dep_name+ with the given +spec+ that can # be loaded and installed using the +source+. - def add_local dep_name, spec, source + def add_local(dep_name, spec, source) @local[dep_name] = [spec, source] end @@ -112,7 +112,7 @@ def errors # Returns an array of IndexSpecification objects matching DependencyRequest # +req+. - def find_all req + def find_all(req) res = [] dep = req.dependency @@ -128,7 +128,7 @@ def find_all req res << Gem::Resolver::InstalledSpecification.new(self, gemspec) end unless @ignore_installed - if consider_local? then + if consider_local? matching_local = @local.values.select do |spec, _| req.match? spec end.map do |spec, source| @@ -138,7 +138,7 @@ def find_all req res.concat matching_local begin - if local_spec = @local_source.find_gem(name, dep.requirement) then + if local_spec = @local_source.find_gem(name, dep.requirement) res << Gem::Resolver::IndexSpecification.new( self, local_spec.name, local_spec.version, @local_source, local_spec.platform) @@ -161,7 +161,7 @@ def prefetch(reqs) @remote_set.prefetch(reqs) if consider_remote? end - def prerelease= allow_prerelease + def prerelease=(allow_prerelease) super @remote_set.prerelease = allow_prerelease @@ -179,7 +179,7 @@ def inspect # :nodoc: # Called from IndexSpecification to get a true Specification # object. - def load_spec name, ver, platform, source # :nodoc: + def load_spec(name, ver, platform, source) # :nodoc: key = "#{name}-#{ver}-#{platform}" @specs.fetch key do @@ -192,13 +192,13 @@ def load_spec name, ver, platform, source # :nodoc: ## # Has a local gem for +dep_name+ been added to this set? - def local? dep_name # :nodoc: + def local?(dep_name) # :nodoc: spec, _ = @local[dep_name] spec end - def pretty_print q # :nodoc: + def pretty_print(q) # :nodoc: q.group 2, '[InstallerSet', ']' do q.breakable q.text "domain: #{@domain}" @@ -213,7 +213,7 @@ def pretty_print q # :nodoc: end end - def remote= remote # :nodoc: + def remote=(remote) # :nodoc: case @domain when :local then @domain = :both if remote diff --git a/lib/ruby/stdlib/rubygems/resolver/local_specification.rb b/lib/ruby/stdlib/rubygems/resolver/local_specification.rb index 1d9d22f0acb5..7418cfcc8641 100644 --- a/lib/ruby/stdlib/rubygems/resolver/local_specification.rb +++ b/lib/ruby/stdlib/rubygems/resolver/local_specification.rb @@ -17,7 +17,7 @@ def local? # :nodoc: true end - def pretty_print q # :nodoc: + def pretty_print(q) # :nodoc: q.group 2, '[LocalSpecification', ']' do q.breakable q.text "name: #{name}" @@ -39,4 +39,3 @@ def pretty_print q # :nodoc: end end - diff --git a/lib/ruby/stdlib/rubygems/resolver/lock_set.rb b/lib/ruby/stdlib/rubygems/resolver/lock_set.rb index 7fddc93e1c15..4002a963a467 100644 --- a/lib/ruby/stdlib/rubygems/resolver/lock_set.rb +++ b/lib/ruby/stdlib/rubygems/resolver/lock_set.rb @@ -9,7 +9,7 @@ class Gem::Resolver::LockSet < Gem::Resolver::Set ## # Creates a new LockSet from the given +sources+ - def initialize sources + def initialize(sources) super() @sources = sources.map do |source| @@ -26,7 +26,7 @@ def initialize sources # The specification's set will be the current set, and the source will be # the current set's source. - def add name, version, platform # :nodoc: + def add(name, version, platform) # :nodoc: version = Gem::Version.new version specs = [ Gem::Resolver::LockSpecification.new(self, name, version, @sources, platform) @@ -41,7 +41,7 @@ def add name, version, platform # :nodoc: # Returns an Array of IndexSpecification objects matching the # DependencyRequest +req+. - def find_all req + def find_all(req) @specs.select do |spec| req.match? spec end @@ -51,7 +51,7 @@ def find_all req # Loads a Gem::Specification with the given +name+, +version+ and # +platform+. +source+ is ignored. - def load_spec name, version, platform, source # :nodoc: + def load_spec(name, version, platform, source) # :nodoc: dep = Gem::Dependency.new name, version found = @specs.find do |spec| @@ -63,7 +63,7 @@ def load_spec name, version, platform, source # :nodoc: found.source.fetch_spec tuple end - def pretty_print q # :nodoc: + def pretty_print(q) # :nodoc: q.group 2, '[LockSet', ']' do q.breakable q.text 'source:' @@ -80,4 +80,3 @@ def pretty_print q # :nodoc: end end - diff --git a/lib/ruby/stdlib/rubygems/resolver/lock_specification.rb b/lib/ruby/stdlib/rubygems/resolver/lock_specification.rb index f48567567313..e29b567de4eb 100644 --- a/lib/ruby/stdlib/rubygems/resolver/lock_specification.rb +++ b/lib/ruby/stdlib/rubygems/resolver/lock_specification.rb @@ -9,7 +9,7 @@ class Gem::Resolver::LockSpecification < Gem::Resolver::Specification attr_reader :sources - def initialize set, name, version, sources, platform + def initialize(set, name, version, sources, platform) super() @name = name @@ -27,10 +27,10 @@ def initialize set, name, version, sources, platform # This is a null install as a locked specification is considered installed. # +options+ are ignored. - def install options = {} + def install(options = {}) destination = options[:install_dir] || Gem.dir - if File.exist? File.join(destination, 'specifications', spec.spec_name) then + if File.exist? File.join(destination, 'specifications', spec.spec_name) yield nil return end @@ -41,11 +41,11 @@ def install options = {} ## # Adds +dependency+ from the lockfile to this specification - def add_dependency dependency # :nodoc: + def add_dependency(dependency) # :nodoc: @dependencies << dependency end - def pretty_print q # :nodoc: + def pretty_print(q) # :nodoc: q.group 2, '[LockSpecification', ']' do q.breakable q.text "name: #{@name}" @@ -53,12 +53,12 @@ def pretty_print q # :nodoc: q.breakable q.text "version: #{@version}" - unless @platform == Gem::Platform::RUBY then + unless @platform == Gem::Platform::RUBY q.breakable q.text "platform: #{@platform}" end - unless @dependencies.empty? then + unless @dependencies.empty? q.breakable q.text 'dependencies:' q.breakable @@ -85,4 +85,3 @@ def spec end end - diff --git a/lib/ruby/stdlib/rubygems/resolver/requirement_list.rb b/lib/ruby/stdlib/rubygems/resolver/requirement_list.rb index 2768c80170d5..98d086e63cd8 100644 --- a/lib/ruby/stdlib/rubygems/resolver/requirement_list.rb +++ b/lib/ruby/stdlib/rubygems/resolver/requirement_list.rb @@ -18,7 +18,7 @@ def initialize @list = [] end - def initialize_copy other # :nodoc: + def initialize_copy(other) # :nodoc: @exact = @exact.dup @list = @list.dup end diff --git a/lib/ruby/stdlib/rubygems/resolver/set.rb b/lib/ruby/stdlib/rubygems/resolver/set.rb index 11704d5c4c3e..242f9cd3dc9a 100644 --- a/lib/ruby/stdlib/rubygems/resolver/set.rb +++ b/lib/ruby/stdlib/rubygems/resolver/set.rb @@ -31,7 +31,7 @@ def initialize # :nodoc: # The find_all method must be implemented. It returns all Resolver # Specification objects matching the given DependencyRequest +req+. - def find_all req + def find_all(req) raise NotImplementedError end @@ -43,7 +43,7 @@ def find_all req # When overridden, the #prefetch method should look up specifications # matching +reqs+. - def prefetch reqs + def prefetch(reqs) end ## diff --git a/lib/ruby/stdlib/rubygems/resolver/source_set.rb b/lib/ruby/stdlib/rubygems/resolver/source_set.rb index 66f5963e54a4..8e799514fd62 100644 --- a/lib/ruby/stdlib/rubygems/resolver/source_set.rb +++ b/lib/ruby/stdlib/rubygems/resolver/source_set.rb @@ -16,7 +16,7 @@ def initialize @sets = {} end - def find_all req # :nodoc: + def find_all(req) # :nodoc: if set = get_set(req.dependency.name) set.find_all req else @@ -25,7 +25,7 @@ def find_all req # :nodoc: end # potentially no-op - def prefetch reqs # :nodoc: + def prefetch(reqs) # :nodoc: reqs.each do |req| if set = get_set(req.dependency.name) set.prefetch reqs @@ -33,11 +33,11 @@ def prefetch reqs # :nodoc: end end - def add_source_gem name, source + def add_source_gem(name, source) @links[name] = source end -private + private def get_set(name) link = @links[name] @@ -45,4 +45,3 @@ def get_set(name) end end - diff --git a/lib/ruby/stdlib/rubygems/resolver/spec_specification.rb b/lib/ruby/stdlib/rubygems/resolver/spec_specification.rb index 35ee8cc247a8..d0e744f3a7bf 100644 --- a/lib/ruby/stdlib/rubygems/resolver/spec_specification.rb +++ b/lib/ruby/stdlib/rubygems/resolver/spec_specification.rb @@ -10,7 +10,7 @@ class Gem::Resolver::SpecSpecification < Gem::Resolver::Specification # +spec+. The +source+ is either where the +spec+ came from, or should be # loaded from. - def initialize set, spec, source = nil + def initialize(set, spec, source = nil) @set = set @source = source @spec = spec @@ -54,4 +54,3 @@ def version end end - diff --git a/lib/ruby/stdlib/rubygems/resolver/specification.rb b/lib/ruby/stdlib/rubygems/resolver/specification.rb index 44989d39ae73..7c1e9be702cb 100644 --- a/lib/ruby/stdlib/rubygems/resolver/specification.rb +++ b/lib/ruby/stdlib/rubygems/resolver/specification.rb @@ -81,14 +81,10 @@ def full_name # After installation #spec is updated to point to the just-installed # specification. - def install options = {} + def install(options = {}) require 'rubygems/installer' - destination = options[:install_dir] || Gem.dir - - Gem.ensure_gem_subdirectories destination - - gem = source.download spec, destination + gem = download options installer = Gem::Installer.at gem, options @@ -97,6 +93,14 @@ def install options = {} @spec = installer.install end + def download(options) + dir = options[:install_dir] || Gem.dir + + Gem.ensure_gem_subdirectories dir + + source.download spec, dir + end + ## # Returns true if this specification is installable on this platform. @@ -108,4 +112,3 @@ def local? # :nodoc: false end end - diff --git a/lib/ruby/stdlib/rubygems/resolver/stats.rb b/lib/ruby/stdlib/rubygems/resolver/stats.rb index 3b95efebf727..64b458f50407 100644 --- a/lib/ruby/stdlib/rubygems/resolver/stats.rb +++ b/lib/ruby/stdlib/rubygems/resolver/stats.rb @@ -32,7 +32,7 @@ def iteration! @iterations += 1 end - PATTERN = "%20s: %d\n" + PATTERN = "%20s: %d\n".freeze def display $stdout.puts "=== Resolver Statistics ===" diff --git a/lib/ruby/stdlib/rubygems/resolver/vendor_set.rb b/lib/ruby/stdlib/rubygems/resolver/vendor_set.rb index f30ce534af35..7e2e917d5c3e 100644 --- a/lib/ruby/stdlib/rubygems/resolver/vendor_set.rb +++ b/lib/ruby/stdlib/rubygems/resolver/vendor_set.rb @@ -32,7 +32,7 @@ def initialize # :nodoc: # Adds a specification to the set with the given +name+ which has been # unpacked into the given +directory+. - def add_vendor_gem name, directory # :nodoc: + def add_vendor_gem(name, directory) # :nodoc: gemspec = File.join directory, "#{name}.gemspec" spec = Gem::Specification.load gemspec @@ -52,7 +52,7 @@ def add_vendor_gem name, directory # :nodoc: # Returns an Array of VendorSpecification objects matching the # DependencyRequest +req+. - def find_all req + def find_all(req) @specs.values.select do |spec| req.match? spec end.map do |spec| @@ -65,11 +65,11 @@ def find_all req # Loads a spec with the given +name+. +version+, +platform+ and +source+ are # ignored. - def load_spec name, version, platform, source # :nodoc: + def load_spec(name, version, platform, source) # :nodoc: @specs.fetch name end - def pretty_print q # :nodoc: + def pretty_print(q) # :nodoc: q.group 2, '[VendorSet', ']' do next if @directories.empty? q.breakable @@ -85,4 +85,3 @@ def pretty_print q # :nodoc: end end - diff --git a/lib/ruby/stdlib/rubygems/resolver/vendor_specification.rb b/lib/ruby/stdlib/rubygems/resolver/vendor_specification.rb index c624f3e8347b..56f2e6eb2cf5 100644 --- a/lib/ruby/stdlib/rubygems/resolver/vendor_specification.rb +++ b/lib/ruby/stdlib/rubygems/resolver/vendor_specification.rb @@ -6,7 +6,7 @@ class Gem::Resolver::VendorSpecification < Gem::Resolver::SpecSpecification - def == other # :nodoc: + def ==(other) # :nodoc: self.class === other and @set == other.set and @spec == other.spec and @@ -17,9 +17,8 @@ def == other # :nodoc: # This is a null install as this gem was unpacked into a directory. # +options+ are ignored. - def install options = {} + def install(options = {}) yield nil end end - diff --git a/lib/ruby/stdlib/rubygems/s3_uri_signer.rb b/lib/ruby/stdlib/rubygems/s3_uri_signer.rb new file mode 100644 index 000000000000..4caf07131f46 --- /dev/null +++ b/lib/ruby/stdlib/rubygems/s3_uri_signer.rb @@ -0,0 +1,175 @@ +require 'base64' +require 'digest' +require 'openssl' + +## +# S3URISigner implements AWS SigV4 for S3 Source to avoid a dependency on the aws-sdk-* gems +# More on AWS SigV4: https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html +class Gem::S3URISigner + + class ConfigurationError < Gem::Exception + + def initialize(message) + super message + end + + def to_s # :nodoc: + "#{super}" + end + + end + + class InstanceProfileError < Gem::Exception + + def initialize(message) + super message + end + + def to_s # :nodoc: + "#{super}" + end + + end + + attr_accessor :uri + + def initialize(uri) + @uri = uri + end + + ## + # Signs S3 URI using query-params according to the reference: https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html + def sign(expiration = 86400) + s3_config = fetch_s3_config + + current_time = Time.now.utc + date_time = current_time.strftime("%Y%m%dT%H%m%SZ") + date = date_time[0,8] + + credential_info = "#{date}/#{s3_config.region}/s3/aws4_request" + canonical_host = "#{uri.host}.s3.#{s3_config.region}.amazonaws.com" + + query_params = generate_canonical_query_params(s3_config, date_time, credential_info, expiration) + canonical_request = generate_canonical_request(canonical_host, query_params) + string_to_sign = generate_string_to_sign(date_time, credential_info, canonical_request) + signature = generate_signature(s3_config, date, string_to_sign) + + URI.parse("https://#{canonical_host}#{uri.path}?#{query_params}&X-Amz-Signature=#{signature}") + end + + private + + S3Config = Struct.new :access_key_id, :secret_access_key, :security_token, :region + + def generate_canonical_query_params(s3_config, date_time, credential_info, expiration) + canonical_params = {} + canonical_params["X-Amz-Algorithm"] = "AWS4-HMAC-SHA256" + canonical_params["X-Amz-Credential"] = "#{s3_config.access_key_id}/#{credential_info}" + canonical_params["X-Amz-Date"] = date_time + canonical_params["X-Amz-Expires"] = expiration.to_s + canonical_params["X-Amz-SignedHeaders"] = "host" + canonical_params["X-Amz-Security-Token"] = s3_config.security_token if s3_config.security_token + + # Sorting is required to generate proper signature + canonical_params.sort.to_h.map do |key, value| + "#{base64_uri_escape(key)}=#{base64_uri_escape(value)}" + end.join("&") + end + + def generate_canonical_request(canonical_host, query_params) + [ + "GET", + uri.path, + query_params, + "host:#{canonical_host}", + "", # empty params + "host", + "UNSIGNED-PAYLOAD", + ].join("\n") + end + + def generate_string_to_sign(date_time, credential_info, canonical_request) + [ + "AWS4-HMAC-SHA256", + date_time, + credential_info, + Digest::SHA256.hexdigest(canonical_request) + ].join("\n") + end + + def generate_signature(s3_config, date, string_to_sign) + date_key = OpenSSL::HMAC.digest("sha256", "AWS4" + s3_config.secret_access_key, date) + date_region_key = OpenSSL::HMAC.digest("sha256", date_key, s3_config.region) + date_region_service_key = OpenSSL::HMAC.digest("sha256", date_region_key, "s3") + signing_key = OpenSSL::HMAC.digest("sha256", date_region_service_key, "aws4_request") + OpenSSL::HMAC.hexdigest("sha256", signing_key, string_to_sign) + end + + ## + # Extracts S3 configuration for S3 bucket + def fetch_s3_config + return S3Config.new(uri.user, uri.password, nil, "us-east-1") if uri.user && uri.password + + s3_source = Gem.configuration[:s3_source] || Gem.configuration["s3_source"] + host = uri.host + raise ConfigurationError.new("no s3_source key exists in .gemrc") unless s3_source + + auth = s3_source[host] || s3_source[host.to_sym] + raise ConfigurationError.new("no key for host #{host} in s3_source in .gemrc") unless auth + + provider = auth[:provider] || auth["provider"] + case provider + when "env" + id = ENV["AWS_ACCESS_KEY_ID"] + secret = ENV["AWS_SECRET_ACCESS_KEY"] + security_token = ENV["AWS_SESSION_TOKEN"] + when "instance_profile" + credentials = ec2_metadata_credentials_json + id = credentials["AccessKeyId"] + secret = credentials["SecretAccessKey"] + security_token = credentials["Token"] + else + id = auth[:id] || auth["id"] + secret = auth[:secret] || auth["secret"] + security_token = auth[:security_token] || auth["security_token"] + end + + raise ConfigurationError.new("s3_source for #{host} missing id or secret") unless id && secret + + region = auth[:region] || auth["region"] || "us-east-1" + S3Config.new(id, secret, security_token, region) + end + + def base64_uri_escape(str) + str.gsub(/[\+\/=\n]/, BASE64_URI_TRANSLATE) + end + + def ec2_metadata_credentials_json + require 'net/http' + require 'rubygems/request' + require 'rubygems/request/connection_pools' + require 'json' + + metadata_uri = URI(EC2_METADATA_CREDENTIALS) + @request_pool ||= create_request_pool(metadata_uri) + request = Gem::Request.new(metadata_uri, Net::HTTP::Get, nil, @request_pool) + response = request.fetch + + case response + when Net::HTTPOK then + JSON.parse(response.body) + else + raise InstanceProfileError.new("Unable to fetch AWS credentials from #{metadata_uri}: #{response.message} #{response.code}") + end + end + + def create_request_pool(uri) + proxy_uri = Gem::Request.proxy_uri(Gem::Request.get_proxy_from_env(uri.scheme)) + certs = Gem::Request.get_cert_files + Gem::Request::ConnectionPools.new(proxy_uri, certs).pool_for(uri) + end + + BASE64_URI_TRANSLATE = { "+" => "%2B", "/" => "%2F", "=" => "%3D", "\n" => "" }.freeze + EC2_METADATA_CREDENTIALS = "http://169.254.169.254/latest/meta-data/identity-credentials/ec2/security-credentials/ec2-instance".freeze + +end diff --git a/lib/ruby/stdlib/rubygems/safe_yaml.rb b/lib/ruby/stdlib/rubygems/safe_yaml.rb index 789bb5e25af3..3540fd74ddee 100644 --- a/lib/ruby/stdlib/rubygems/safe_yaml.rb +++ b/lib/ruby/stdlib/rubygems/safe_yaml.rb @@ -7,7 +7,7 @@ module Gem # Psych.safe_load module SafeYAML - WHITELISTED_CLASSES = %w( + PERMITTED_CLASSES = %w( Symbol Time Date @@ -19,31 +19,39 @@ module SafeYAML Gem::Version::Requirement YAML::Syck::DefaultKey Syck::DefaultKey - ) + ).freeze - WHITELISTED_SYMBOLS = %w( + PERMITTED_SYMBOLS = %w( development runtime - ) + ).freeze if ::YAML.respond_to? :safe_load - def self.safe_load input - ::YAML.safe_load(input, WHITELISTED_CLASSES, WHITELISTED_SYMBOLS, true) + def self.safe_load(input) + if Gem::Version.new(Psych::VERSION) >= Gem::Version.new('3.1.0.pre1') + ::YAML.safe_load(input, permitted_classes: PERMITTED_CLASSES, permitted_symbols: PERMITTED_SYMBOLS, aliases: true) + else + ::YAML.safe_load(input, PERMITTED_CLASSES, PERMITTED_SYMBOLS, true) + end end - def self.load input - ::YAML.safe_load(input, [::Symbol]) + def self.load(input) + if Gem::Version.new(Psych::VERSION) >= Gem::Version.new('3.1.0.pre1') + ::YAML.safe_load(input, permitted_classes: [::Symbol]) + else + ::YAML.safe_load(input, [::Symbol]) + end end else unless Gem::Deprecate.skip warn "YAML safe loading is not available. Please upgrade psych to a version that supports safe loading (>= 2.0)." end - def self.safe_load input, *args + def self.safe_load(input, *args) ::YAML.load input end - def self.load input + def self.load(input) ::YAML.load input end end diff --git a/lib/ruby/stdlib/rubygems/security.rb b/lib/ruby/stdlib/rubygems/security.rb index dc5e91a6f489..7b0a0b3c6af1 100644 --- a/lib/ruby/stdlib/rubygems/security.rb +++ b/lib/ruby/stdlib/rubygems/security.rb @@ -340,9 +340,9 @@ class Exception < Gem::Exception; end # Digest algorithm used to sign gems DIGEST_ALGORITHM = - if defined?(OpenSSL::Digest::SHA256) then + if defined?(OpenSSL::Digest::SHA256) OpenSSL::Digest::SHA256 - elsif defined?(OpenSSL::Digest::SHA1) then + elsif defined?(OpenSSL::Digest::SHA1) OpenSSL::Digest::SHA1 else require 'digest' @@ -353,7 +353,7 @@ class Exception < Gem::Exception; end # Used internally to select the signing digest from all computed digests DIGEST_NAME = # :nodoc: - if DIGEST_ALGORITHM.method_defined? :name then + if DIGEST_ALGORITHM.method_defined? :name DIGEST_ALGORITHM.new.name else DIGEST_ALGORITHM.name[/::([^:]+)\z/, 1] @@ -363,7 +363,7 @@ class Exception < Gem::Exception; end # Algorithm for creating the key pair used to sign gems KEY_ALGORITHM = - if defined?(OpenSSL::PKey::RSA) then + if defined?(OpenSSL::PKey::RSA) OpenSSL::PKey::RSA end @@ -401,9 +401,9 @@ class Exception < Gem::Exception; end 'keyUsage' => 'keyEncipherment,dataEncipherment,digitalSignature', 'subjectKeyIdentifier' => 'hash', - } + }.freeze - def self.alt_name_or_x509_entry certificate, x509_entry + def self.alt_name_or_x509_entry(certificate, x509_entry) alt_name = certificate.extensions.find do |extension| extension.oid == "#{x509_entry}AltName" end @@ -419,8 +419,8 @@ def self.alt_name_or_x509_entry certificate, x509_entry # # The +extensions+ restrict the key to the indicated uses. - def self.create_cert subject, key, age = ONE_YEAR, extensions = EXTENSIONS, - serial = 1 + def self.create_cert(subject, key, age = ONE_YEAR, extensions = EXTENSIONS, + serial = 1) cert = OpenSSL::X509::Certificate.new cert.public_key = key.public_key @@ -446,7 +446,7 @@ def self.create_cert subject, key, age = ONE_YEAR, extensions = EXTENSIONS, # a subject alternative name of +email+ and the given +extensions+ for the # +key+. - def self.create_cert_email email, key, age = ONE_YEAR, extensions = EXTENSIONS + def self.create_cert_email(email, key, age = ONE_YEAR, extensions = EXTENSIONS) subject = email_to_name email extensions = extensions.merge "subjectAltName" => "email:#{email}" @@ -458,8 +458,8 @@ def self.create_cert_email email, key, age = ONE_YEAR, extensions = EXTENSIONS # Creates a self-signed certificate with an issuer and subject of +subject+ # and the given +extensions+ for the +key+. - def self.create_cert_self_signed subject, key, age = ONE_YEAR, - extensions = EXTENSIONS, serial = 1 + def self.create_cert_self_signed(subject, key, age = ONE_YEAR, + extensions = EXTENSIONS, serial = 1) certificate = create_cert subject, key, age, extensions sign certificate, key, certificate, age, extensions, serial @@ -469,14 +469,14 @@ def self.create_cert_self_signed subject, key, age = ONE_YEAR, # Creates a new key pair of the specified +length+ and +algorithm+. The # default is a 3072 bit RSA key. - def self.create_key length = KEY_LENGTH, algorithm = KEY_ALGORITHM + def self.create_key(length = KEY_LENGTH, algorithm = KEY_ALGORITHM) algorithm.new length end ## # Turns +email_address+ into an OpenSSL::X509::Name - def self.email_to_name email_address + def self.email_to_name(email_address) email_address = email_address.gsub(/[^\w@.-]+/i, '_') cn, dcs = email_address.split '@' @@ -494,15 +494,15 @@ def self.email_to_name email_address #-- # TODO increment serial - def self.re_sign expired_certificate, private_key, age = ONE_YEAR, - extensions = EXTENSIONS + def self.re_sign(expired_certificate, private_key, age = ONE_YEAR, + extensions = EXTENSIONS) raise Gem::Security::Exception, "incorrect signing key for re-signing " + "#{expired_certificate.subject}" unless expired_certificate.public_key.to_pem == private_key.public_key.to_pem unless expired_certificate.subject.to_s == - expired_certificate.issuer.to_s then + expired_certificate.issuer.to_s subject = alt_name_or_x509_entry expired_certificate, :subject issuer = alt_name_or_x509_entry expired_certificate, :issuer @@ -531,8 +531,8 @@ def self.reset # # Returns the newly signed certificate. - def self.sign certificate, signing_key, signing_cert, - age = ONE_YEAR, extensions = EXTENSIONS, serial = 1 + def self.sign(certificate, signing_key, signing_cert, + age = ONE_YEAR, extensions = EXTENSIONS, serial = 1) signee_subject = certificate.subject signee_key = certificate.public_key @@ -571,7 +571,7 @@ def self.trust_dir ## # Enumerates the trusted certificates via Gem::Security::TrustDir. - def self.trusted_certificates &block + def self.trusted_certificates(&block) trust_dir.each_certificate(&block) end @@ -580,7 +580,7 @@ def self.trusted_certificates &block # +permissions+. If passed +cipher+ and +passphrase+ those arguments will be # passed to +to_pem+. - def self.write pemmable, path, permissions = 0600, passphrase = nil, cipher = KEY_CIPHER + def self.write(pemmable, path, permissions = 0600, passphrase = nil, cipher = KEY_CIPHER) path = File.expand_path path File.open path, 'wb', permissions do |io| @@ -598,11 +598,10 @@ def self.write pemmable, path, permissions = 0600, passphrase = nil, cipher = KE end -if defined?(OpenSSL::SSL) then +if defined?(OpenSSL::SSL) require 'rubygems/security/policy' require 'rubygems/security/policies' require 'rubygems/security/trust_dir' end require 'rubygems/security/signer' - diff --git a/lib/ruby/stdlib/rubygems/security/policies.rb b/lib/ruby/stdlib/rubygems/security/policies.rb index f16c46306aa7..8f6ad9931608 100644 --- a/lib/ruby/stdlib/rubygems/security/policies.rb +++ b/lib/ruby/stdlib/rubygems/security/policies.rb @@ -110,7 +110,6 @@ module Gem::Security 'MediumSecurity' => MediumSecurity, 'HighSecurity' => HighSecurity, # SigningPolicy is not intended for use by `gem -P` so do not list it - } + }.freeze end - diff --git a/lib/ruby/stdlib/rubygems/security/policy.rb b/lib/ruby/stdlib/rubygems/security/policy.rb index f43e6c8c9695..1aa6eab18caa 100644 --- a/lib/ruby/stdlib/rubygems/security/policy.rb +++ b/lib/ruby/stdlib/rubygems/security/policy.rb @@ -24,7 +24,7 @@ class Gem::Security::Policy # Create a new Gem::Security::Policy object with the given mode and # options. - def initialize name, policy = {}, opt = {} + def initialize(name, policy = {}, opt = {}) require 'openssl' @name = name @@ -55,7 +55,7 @@ def initialize name, policy = {}, opt = {} # Verifies each certificate in +chain+ has signed the following certificate # and is valid for the given +time+. - def check_chain chain, time + def check_chain(chain, time) raise Gem::Security::Exception, 'missing signing chain' unless chain raise Gem::Security::Exception, 'empty signing chain' if chain.empty? @@ -74,7 +74,7 @@ def check_chain chain, time # Verifies that +data+ matches the +signature+ created by +public_key+ and # the +digest+ algorithm. - def check_data public_key, digest, signature, data + def check_data(public_key, digest, signature, data) raise Gem::Security::Exception, "invalid signature" unless public_key.verify digest.new, signature, data.digest @@ -85,22 +85,22 @@ def check_data public_key, digest, signature, data # Ensures that +signer+ is valid for +time+ and was signed by the +issuer+. # If the +issuer+ is +nil+ no verification is performed. - def check_cert signer, issuer, time + def check_cert(signer, issuer, time) raise Gem::Security::Exception, 'missing signing certificate' unless signer message = "certificate #{signer.subject}" - if not_before = signer.not_before and not_before > time then + if not_before = signer.not_before and not_before > time raise Gem::Security::Exception, "#{message} not valid before #{not_before}" end - if not_after = signer.not_after and not_after < time then + if not_after = signer.not_after and not_after < time raise Gem::Security::Exception, "#{message} not valid after #{not_after}" end - if issuer and not signer.verify issuer.public_key then + if issuer and not signer.verify issuer.public_key raise Gem::Security::Exception, "#{message} was not issued by #{issuer.subject}" end @@ -111,8 +111,8 @@ def check_cert signer, issuer, time ## # Ensures the public key of +key+ matches the public key in +signer+ - def check_key signer, key - unless signer and key then + def check_key(signer, key) + unless signer and key return true unless @only_signed raise Gem::Security::Exception, 'missing key or signature' @@ -129,7 +129,7 @@ def check_key signer, key # Ensures the root certificate in +chain+ is self-signed and valid for # +time+. - def check_root chain, time + def check_root(chain, time) raise Gem::Security::Exception, 'missing signing chain' unless chain root = chain.first @@ -148,7 +148,7 @@ def check_root chain, time # Ensures the root of +chain+ has a trusted certificate in +trust_dir+ and # the digests of the two certificates match according to +digester+ - def check_trust chain, digester, trust_dir + def check_trust(chain, digester, trust_dir) raise Gem::Security::Exception, 'missing signing chain' unless chain root = chain.first @@ -157,7 +157,7 @@ def check_trust chain, digester, trust_dir path = Gem::Security.trust_dir.cert_path root - unless File.exist? path then + unless File.exist? path message = "root cert #{root.subject} is not trusted".dup message << " (root of signing cert #{chain.last.subject})" if @@ -183,7 +183,7 @@ def check_trust chain, digester, trust_dir ## # Extracts the email or subject from +certificate+ - def subject certificate # :nodoc: + def subject(certificate) # :nodoc: certificate.extensions.each do |extension| next unless extension.oid == 'subjectAltName' @@ -196,9 +196,9 @@ def subject certificate # :nodoc: def inspect # :nodoc: ("[Policy: %s - data: %p signer: %p chain: %p root: %p " + "signed-only: %p trusted-only: %p]") % [ - @name, @verify_chain, @verify_data, @verify_root, @verify_signer, - @only_signed, @only_trusted, - ] + @name, @verify_chain, @verify_data, @verify_root, @verify_signer, + @only_signed, @only_trusted, + ] end ## @@ -208,13 +208,13 @@ def inspect # :nodoc: # # If +key+ is given it is used to validate the signing certificate. - def verify chain, key = nil, digests = {}, signatures = {}, - full_name = '(unknown)' - if signatures.empty? then - if @only_signed then + def verify(chain, key = nil, digests = {}, signatures = {}, + full_name = '(unknown)') + if signatures.empty? + if @only_signed raise Gem::Security::Exception, "unsigned gems are not allowed by the #{name} policy" - elsif digests.empty? then + elsif digests.empty? # lack of signatures is irrelevant if there is nothing to check # against else @@ -232,7 +232,7 @@ def verify chain, key = nil, digests = {}, signatures = {}, file_digests.values.first.name == Gem::Security::DIGEST_NAME end - if @verify_data then + if @verify_data raise Gem::Security::Exception, 'no digests provided (probable bug)' if signer_digests.nil? or signer_digests.empty? else @@ -249,9 +249,9 @@ def verify chain, key = nil, digests = {}, signatures = {}, check_root chain, time if @verify_root - if @only_trusted then + if @only_trusted check_trust chain, digester, trust_dir - elsif signatures.empty? and digests.empty? then + elsif signatures.empty? and digests.empty? # trust is irrelevant if there's no signatures to verify else alert_warning "#{subject signer} is not trusted for #{full_name}" @@ -280,7 +280,7 @@ def verify chain, key = nil, digests = {}, signatures = {}, # Extracts the certificate chain from the +spec+ and calls #verify to ensure # the signatures and certificate chain is valid according to the policy.. - def verify_signatures spec, digests, signatures + def verify_signatures(spec, digests, signatures) chain = spec.cert_chain.map do |cert_pem| OpenSSL::X509::Certificate.new cert_pem end diff --git a/lib/ruby/stdlib/rubygems/security/signer.rb b/lib/ruby/stdlib/rubygems/security/signer.rb index 0c6ef60a9ac6..34e86e921a4f 100644 --- a/lib/ruby/stdlib/rubygems/security/signer.rb +++ b/lib/ruby/stdlib/rubygems/security/signer.rb @@ -2,8 +2,12 @@ ## # Basic OpenSSL-based package signing class. +require "rubygems/user_interaction" + class Gem::Security::Signer + include Gem::UserInteraction + ## # The chain of certificates for signing including the signing certificate @@ -25,21 +29,54 @@ class Gem::Security::Signer attr_reader :digest_name # :nodoc: + ## + # Gem::Security::Signer options + + attr_reader :options + + DEFAULT_OPTIONS = { + expiration_length_days: 365 + }.freeze + + ## + # Attemps to re-sign an expired cert with a given private key + def self.re_sign_cert(expired_cert, expired_cert_path, private_key) + return unless expired_cert.not_after < Time.now + + expiry = expired_cert.not_after.strftime('%Y%m%d%H%M%S') + expired_cert_file = "#{File.basename(expired_cert_path)}.expired.#{expiry}" + new_expired_cert_path = File.join(Gem.user_home, ".gem", expired_cert_file) + + Gem::Security.write(expired_cert, new_expired_cert_path) + + re_signed_cert = Gem::Security.re_sign( + expired_cert, + private_key, + (Gem::Security::ONE_DAY * Gem.configuration.cert_expiration_length_days) + ) + + Gem::Security.write(re_signed_cert, expired_cert_path) + + yield(expired_cert_path, new_expired_cert_path) if block_given? + end + ## # Creates a new signer with an RSA +key+ or path to a key, and a certificate # +chain+ containing X509 certificates, encoding certificates or paths to # certificates. - def initialize key, cert_chain, passphrase = nil + def initialize(key, cert_chain, passphrase = nil, options = {}) @cert_chain = cert_chain @key = key + @passphrase = passphrase + @options = DEFAULT_OPTIONS.merge(options) - unless @key then + unless @key default_key = File.join Gem.default_key_path @key = default_key if File.exist? default_key end - unless @cert_chain then + unless @cert_chain default_cert = File.join Gem.default_cert_path @cert_chain = [default_cert] if File.exist? default_cert end @@ -47,10 +84,12 @@ def initialize key, cert_chain, passphrase = nil @digest_algorithm = Gem::Security::DIGEST_ALGORITHM @digest_name = Gem::Security::DIGEST_NAME - @key = OpenSSL::PKey::RSA.new File.read(@key), passphrase if - @key and not OpenSSL::PKey::RSA === @key + if @key && !@key.is_a?(OpenSSL::PKey::RSA) + @passphrase ||= ask_for_password("Enter PEM pass phrase:") + @key = OpenSSL::PKey::RSA.new(File.read(@key), @passphrase) + end - if @cert_chain then + if @cert_chain @cert_chain = @cert_chain.compact.map do |cert| next cert if OpenSSL::X509::Certificate === cert @@ -67,10 +106,10 @@ def initialize key, cert_chain, passphrase = nil # Extracts the full name of +cert+. If the certificate has a subjectAltName # this value is preferred, otherwise the subject is used. - def extract_name cert # :nodoc: + def extract_name(cert) # :nodoc: subject_alt_name = cert.extensions.find { |e| 'subjectAltName' == e.oid } - if subject_alt_name then + if subject_alt_name /\Aemail:/ =~ subject_alt_name.value $' || subject_alt_name.value @@ -99,13 +138,15 @@ def load_cert_chain # :nodoc: ## # Sign data with given digest algorithm - def sign data + def sign(data) return unless @key raise Gem::Security::Exception, 'no certs provided' if @cert_chain.empty? - if @cert_chain.length == 1 and @cert_chain.last.not_after < Time.now then - re_sign_key + if @cert_chain.length == 1 and @cert_chain.last.not_after < Time.now + re_sign_key( + expiration_length: (Gem::Security::ONE_DAY * options[:expiration_length_days]) + ) end full_name = extract_name @cert_chain.last @@ -121,6 +162,7 @@ def sign data # The key will be re-signed if: # * The expired certificate is self-signed # * The expired certificate is saved at ~/.gem/gem-public_cert.pem + # and the private key is saved at ~/.gem/gem-private_key.pem # * There is no file matching the expiry date at # ~/.gem/gem-public_cert.pem.expired.%Y%m%d%H%M%S # @@ -128,25 +170,32 @@ def sign data # be saved as ~/.gem/gem-public_cert.pem.expired.%Y%m%d%H%M%S where the # expiry time (not after) is used for the timestamp. - def re_sign_key # :nodoc: + def re_sign_key(expiration_length: Gem::Security::ONE_YEAR) # :nodoc: old_cert = @cert_chain.last - disk_cert_path = File.join Gem.default_cert_path - disk_cert = File.read disk_cert_path rescue nil - disk_key = - File.read File.join(Gem.default_key_path) rescue nil + disk_cert_path = File.join(Gem.default_cert_path) + disk_cert = File.read(disk_cert_path) rescue nil - if disk_key == @key.to_pem and disk_cert == old_cert.to_pem then - expiry = old_cert.not_after.strftime '%Y%m%d%H%M%S' + disk_key_path = File.join(Gem.default_key_path) + disk_key = + OpenSSL::PKey::RSA.new(File.read(disk_key_path), @passphrase) rescue nil + + return unless disk_key + + if disk_key.to_pem == @key.to_pem && disk_cert == old_cert.to_pem + expiry = old_cert.not_after.strftime('%Y%m%d%H%M%S') old_cert_file = "gem-public_cert.pem.expired.#{expiry}" - old_cert_path = File.join Gem.user_home, ".gem", old_cert_file + old_cert_path = File.join(Gem.user_home, ".gem", old_cert_file) - unless File.exist? old_cert_path then - Gem::Security.write old_cert, old_cert_path + unless File.exist?(old_cert_path) + Gem::Security.write(old_cert, old_cert_path) - cert = Gem::Security.re_sign old_cert, @key + cert = Gem::Security.re_sign(old_cert, @key, expiration_length) - Gem::Security.write cert, disk_cert_path + Gem::Security.write(cert, disk_cert_path) + + alert("Your cert: #{disk_cert_path} has been auto re-signed with the key: #{disk_key_path}") + alert("Your expired cert will be located at: #{old_cert_path}") @cert_chain = [cert] end @@ -154,4 +203,3 @@ def re_sign_key # :nodoc: end end - diff --git a/lib/ruby/stdlib/rubygems/security/trust_dir.rb b/lib/ruby/stdlib/rubygems/security/trust_dir.rb index 849cf3cd3e8c..98031ea22b6d 100644 --- a/lib/ruby/stdlib/rubygems/security/trust_dir.rb +++ b/lib/ruby/stdlib/rubygems/security/trust_dir.rb @@ -11,7 +11,7 @@ class Gem::Security::TrustDir DEFAULT_PERMISSIONS = { :trust_dir => 0700, :trusted_cert => 0600, - } + }.freeze ## # The directory where trusted certificates will be stored. @@ -22,7 +22,7 @@ class Gem::Security::TrustDir # Creates a new TrustDir using +dir+ where the directory and file # permissions will be checked according to +permissions+ - def initialize dir, permissions = DEFAULT_PERMISSIONS + def initialize(dir, permissions = DEFAULT_PERMISSIONS) @dir = dir @permissions = permissions @@ -32,7 +32,7 @@ def initialize dir, permissions = DEFAULT_PERMISSIONS ## # Returns the path to the trusted +certificate+ - def cert_path certificate + def cert_path(certificate) name_path certificate.subject end @@ -59,7 +59,7 @@ def each_certificate # Returns the issuer certificate of the given +certificate+ if it exists in # the trust directory. - def issuer_of certificate + def issuer_of(certificate) path = name_path certificate.issuer return unless File.exist? path @@ -70,7 +70,7 @@ def issuer_of certificate ## # Returns the path to the trusted certificate with the given ASN.1 +name+ - def name_path name + def name_path(name) digest = @digester.hexdigest name.to_s File.join @dir, "cert-#{digest}.pem" @@ -79,7 +79,7 @@ def name_path name ## # Loads the given +certificate_file+ - def load_certificate certificate_file + def load_certificate(certificate_file) pem = File.read certificate_file OpenSSL::X509::Certificate.new pem @@ -88,13 +88,14 @@ def load_certificate certificate_file ## # Add a certificate to trusted certificate list. - def trust_cert certificate + def trust_cert(certificate) verify destination = cert_path certificate - File.open destination, 'wb', @permissions[:trusted_cert] do |io| + File.open destination, 'wb', 0600 do |io| io.write certificate.to_pem + io.chmod(@permissions[:trusted_cert]) end end @@ -104,7 +105,7 @@ def trust_cert certificate # permissions. def verify - if File.exist? @dir then + if File.exist? @dir raise Gem::Security::Exception, "trust directory #{@dir} is not a directory" unless File.directory? @dir @@ -116,4 +117,3 @@ def verify end end - diff --git a/lib/ruby/stdlib/rubygems/security_option.rb b/lib/ruby/stdlib/rubygems/security_option.rb index 4e3473acb48b..3403aaaf05f8 100644 --- a/lib/ruby/stdlib/rubygems/security_option.rb +++ b/lib/ruby/stdlib/rubygems/security_option.rb @@ -19,7 +19,6 @@ class Policy # :nodoc: module Gem::SecurityOption def add_security_option - # TODO: use @parser.accept OptionParser.accept Gem::Security::Policy do |value| require 'rubygems/security' diff --git a/lib/ruby/stdlib/rubygems/server.rb b/lib/ruby/stdlib/rubygems/server.rb index 9ebd2f5e44ff..1453bf232354 100644 --- a/lib/ruby/stdlib/rubygems/server.rb +++ b/lib/ruby/stdlib/rubygems/server.rb @@ -35,7 +35,7 @@ class Gem::Server include ERB::Util include Gem::UserInteraction - SEARCH = <<-ERB + SEARCH = <<-ERB.freeze