RUBYGEMS_GEMDEPS environment variable to either the path
# of your gem dependencies file or "-" to auto-discover in parent
# directories.
#
# NOTE: Enabling automatic discovery on multiuser systems can lead to
# execution of arbitrary code when used from directories outside your
# control.
def self.use_gemdeps path = nil
raise_exception = path
path ||= ENV['RUBYGEMS_GEMDEPS']
return unless path
path = path.dup
if path == "-" then
Gem::Util.traverse_parents Dir.pwd do |directory|
dep_file = GEM_DEP_FILES.find { |f| File.file?(f) }
next unless dep_file
path = File.join directory, dep_file
break
end
end
path.untaint
unless File.file? path then
return unless raise_exception
raise ArgumentError, "Unable to find gem dependencies file at #{path}"
end
if USE_BUNDLER_FOR_GEMDEPS
ENV["BUNDLE_GEMFILE"] ||= File.expand_path(path)
require 'rubygems/user_interaction'
Gem::DefaultUserInteraction.use_ui(ui) do
require "bundler"
@gemdeps = Bundler.setup
Bundler.ui = nil
@gemdeps.requested_specs.map(&:to_spec).sort_by(&:name)
end
else
rs = Gem::RequestSet.new
@gemdeps = rs.load_gemdeps path
rs.resolve_current.map do |s|
s.full_spec.tap(&:activate)
end
end
rescue => e
case e
when Gem::LoadError, Gem::UnsatisfiableDependencyError, (defined?(Bundler::GemNotFound) ? Bundler::GemNotFound : Gem::LoadError)
warn e.message
warn "You may need to `gem install -g` to install missing gems"
warn ""
else
raise
end
end
class << self
##
# TODO remove with RubyGems 3.0
alias detect_gemdeps use_gemdeps # :nodoc:
end
# FIX: Almost everywhere else we use the `def self.` way of defining class
# methods, and then we switch over to `class << self` here. Pick one or the
# other.
class << self
##
# Hash of loaded Gem::Specification keyed by name
attr_reader :loaded_specs
##
# GemDependencyAPI object, which is set when .use_gemdeps is called.
# This contains all the information from the Gemfile.
attr_reader :gemdeps
##
# Register a Gem::Specification for default gem.
#
# Two formats for the specification are supported:
#
# * MRI 2.0 style, where spec.files contains unprefixed require names.
# The spec's filenames will be registered as-is.
# * New style, where spec.files contains files prefixed with paths
# from spec.require_paths. The prefixes are stripped before
# registering the spec's filenames. Unprefixed files are omitted.
#
def register_default_spec(spec)
new_format = Gem.default_gems_use_full_paths? || spec.require_paths.any? {|path| spec.files.any? {|f| f.start_with? path } }
if new_format
prefix_group = spec.require_paths.map {|f| f + "/"}.join("|")
prefix_pattern = /^(#{prefix_group})/
end
suffix_pattern = /#{Regexp.union(Gem.suffixes)}\z/
spec.files.each do |file|
if new_format
file = file.sub(prefix_pattern, "")
next unless $~
end
@path_to_default_spec_map[file] = spec
@path_to_default_spec_map[file.sub(suffix_pattern, "")] = spec
end
end
##
# Find a Gem::Specification of default gem from +path+
def find_unresolved_default_spec(path)
@path_to_default_spec_map[path]
end
##
# Remove needless Gem::Specification of default gem from
# unresolved default gem list
def remove_unresolved_default_spec(spec)
spec.files.each do |file|
@path_to_default_spec_map.delete(file)
end
end
##
# Clear default gem related variables. It is for test
def clear_default_specs
@path_to_default_spec_map.clear
end
##
# The list of hooks to be run after Gem::Installer#install extracts files
# and builds extensions
attr_reader :post_build_hooks
##
# The list of hooks to be run after Gem::Installer#install completes
# installation
attr_reader :post_install_hooks
##
# The list of hooks to be run after Gem::DependencyInstaller installs a
# set of gems
attr_reader :done_installing_hooks
##
# The list of hooks to be run after Gem::Specification.reset is run.
attr_reader :post_reset_hooks
##
# The list of hooks to be run after Gem::Uninstaller#uninstall completes
# installation
attr_reader :post_uninstall_hooks
##
# The list of hooks to be run before Gem::Installer#install does any work
attr_reader :pre_install_hooks
##
# The list of hooks to be run before Gem::Specification.reset is run.
attr_reader :pre_reset_hooks
##
# The list of hooks to be run before Gem::Uninstaller#uninstall does any
# work
attr_reader :pre_uninstall_hooks
end
##
# Location of Marshal quick gemspecs on remote repositories
MARSHAL_SPEC_DIR = "quick/Marshal.#{Gem.marshal_version}/"
autoload :BundlerVersionFinder, 'rubygems/bundler_version_finder'
autoload :ConfigFile, 'rubygems/config_file'
autoload :Dependency, 'rubygems/dependency'
autoload :DependencyList, 'rubygems/dependency_list'
autoload :DependencyResolver, 'rubygems/resolver'
autoload :Installer, 'rubygems/installer'
autoload :Licenses, 'rubygems/util/licenses'
autoload :PathSupport, 'rubygems/path_support'
autoload :Platform, 'rubygems/platform'
autoload :RequestSet, 'rubygems/request_set'
autoload :Requirement, 'rubygems/requirement'
autoload :Resolver, 'rubygems/resolver'
autoload :Source, 'rubygems/source'
autoload :SourceList, 'rubygems/source_list'
autoload :SpecFetcher, 'rubygems/spec_fetcher'
autoload :Specification, 'rubygems/specification'
autoload :Util, 'rubygems/util'
autoload :Version, 'rubygems/version'
require "rubygems/specification"
end
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.
require 'rubygems/defaults/operating_system'
rescue LoadError
end
if defined?(RUBY_ENGINE) then
begin
##
# Defaults the Ruby implementation wants to provide for RubyGems
require "rubygems/defaults/#{RUBY_ENGINE}"
rescue LoadError
end
end
end
##
# Loads the default specs.
Gem::Specification.load_defaults
require 'rubygems/core_ext/kernel_gem'
require 'rubygems/core_ext/kernel_require'
Gem.use_gemdeps
rubygems/mock_gem_ui.rb 0000644 00000002601 15263270206 0011206 0 ustar 00 # frozen_string_literal: true
require 'stringio'
require 'rubygems/user_interaction'
##
# This Gem::StreamUI subclass records input and output to StringIO for
# retrieval during tests.
class Gem::MockGemUi < Gem::StreamUI
##
# Raised when you haven't provided enough input to your MockGemUi
class InputEOFError < RuntimeError
def initialize question
super "Out of input for MockGemUi on #{question.inspect}"
end
end
class TermError < RuntimeError
attr_reader :exit_code
def initialize exit_code
super
@exit_code = exit_code
end
end
class SystemExitException < RuntimeError; end
module TTY
attr_accessor :tty
def tty?()
@tty = true unless defined?(@tty)
@tty
end
def noecho
yield self
end
end
def initialize(input = "")
ins = StringIO.new input
outs = StringIO.new
errs = StringIO.new
ins.extend TTY
outs.extend TTY
errs.extend TTY
super ins, outs, errs, true
@terminated = false
end
def ask question
raise InputEOFError, question if @ins.eof?
super
end
def input
@ins.string
end
def output
@outs.string
end
def error
@errs.string
end
def terminated?
@terminated
end
def terminate_interaction(status=0)
@terminated = true
raise TermError, status if status != 0
raise SystemExitException
end
end
rubygems/validator.rb 0000644 00000010352 15263270206 0010717 0 ustar 00 # frozen_string_literal: true
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'rubygems/package'
require 'rubygems/installer'
##
# Validator performs various gem file and gem database validation
class Gem::Validator
include Gem::UserInteraction
def initialize # :nodoc:
require 'find'
end
##
# Given a gem file's contents, validates against its own MD5 checksum
# gem_data:: [String] Contents of the gem file
def verify_gem(gem_data)
# TODO remove me? The code here only validate an MD5SUM that was
# in some old formatted gems, but hasn't been for a long time.
end
##
# Given the path to a gem file, validates against its own MD5 checksum
#
# gem_path:: [String] Path to gem file
def verify_gem_file(gem_path)
File.open gem_path, Gem.binary_mode do |file|
gem_data = file.read
verify_gem gem_data
end
rescue Errno::ENOENT, Errno::EINVAL
raise Gem::VerificationError, "missing gem file #{gem_path}"
end
private
def find_files_for_gem(gem_directory)
installed_files = []
Find.find gem_directory do |file_name|
fn = file_name[gem_directory.size..file_name.size-1].sub(/^\//, "")
installed_files << fn unless
fn =~ /CVS/ || fn.empty? || File.directory?(file_name)
end
installed_files
end
public
##
# Describes a problem with a file in a gem.
ErrorData = Struct.new :path, :problem do
def <=> other # :nodoc:
return nil unless self.class === other
[path, problem] <=> [other.path, other.problem]
end
end
##
# Checks the gem directory for the following potential
# inconsistencies/problems:
#
# * Checksum gem itself
# * For each file in each gem, check consistency of installed versions
# * Check for files that aren't part of the gem but are in the gems directory
# * 1 cache - 1 spec - 1 directory.
#
# returns a hash of ErrorData objects, keyed on the problem gem's name.
#--
# TODO needs further cleanup
def alien(gems=[])
errors = Hash.new { |h,k| h[k] = {} }
Gem::Specification.each do |spec|
next unless gems.include? spec.name unless gems.empty?
next if spec.default_gem?
gem_name = spec.file_name
gem_path = spec.cache_file
spec_path = spec.spec_file
gem_directory = spec.full_gem_path
unless File.directory? gem_directory then
errors[gem_name][spec.full_name] =
"Gem registered but doesn't exist at #{gem_directory}"
next
end
unless File.exist? spec_path then
errors[gem_name][spec_path] = "Spec file missing for installed gem"
end
begin
verify_gem_file(gem_path)
good, gone, unreadable = nil, nil, nil, nil
File.open gem_path, Gem.binary_mode do |file|
package = Gem::Package.new gem_path
good, gone = package.contents.partition { |file_name|
File.exist? File.join(gem_directory, file_name)
}
gone.sort.each do |path|
errors[gem_name][path] = "Missing file"
end
good, unreadable = good.partition { |file_name|
File.readable? File.join(gem_directory, file_name)
}
unreadable.sort.each do |path|
errors[gem_name][path] = "Unreadable file"
end
good.each do |entry, data|
begin
next unless data # HACK `gem check -a mkrf`
source = File.join gem_directory, entry['path']
File.open source, Gem.binary_mode do |f|
unless f.read == data then
errors[gem_name][entry['path']] = "Modified from original"
end
end
end
end
end
installed_files = find_files_for_gem(gem_directory)
extras = installed_files - good - unreadable
extras.each do |extra|
errors[gem_name][extra] = "Extra file"
end
rescue Gem::VerificationError => e
errors[gem_name][gem_path] = e.message
end
end
errors.each do |name, subhash|
errors[name] = subhash.map do |path, msg|
ErrorData.new path, msg
end.sort
end
errors
end
end
rubygems/test_utilities.rb 0000644 00000021212 15263270206 0012001 0 ustar 00 # frozen_string_literal: true
require 'tempfile'
require 'rubygems'
require 'rubygems/remote_fetcher'
##
# A fake Gem::RemoteFetcher for use in tests or to avoid real live HTTP
# requests when testing code that uses RubyGems.
#
# Example:
#
# @fetcher = Gem::FakeFetcher.new
# @fetcher.data['http://gems.example.com/yaml'] = source_index.to_yaml
# Gem::RemoteFetcher.fetcher = @fetcher
#
# # invoke RubyGems code
#
# paths = @fetcher.paths
# assert_equal 'http://gems.example.com/yaml', paths.shift
# assert paths.empty?, paths.join(', ')
#
# See RubyGems' tests for more examples of FakeFetcher.
class Gem::FakeFetcher
attr_reader :data
attr_reader :last_request
attr_reader :api_endpoints
attr_accessor :paths
def initialize
@data = {}
@paths = []
@api_endpoints = {}
end
def api_endpoint(uri)
@api_endpoints[uri] || uri
end
def find_data(path)
return File.read path.path if URI === path and 'file' == path.scheme
if URI === path and "URI::#{path.scheme.upcase}" != path.class.name then
raise ArgumentError,
"mismatch for scheme #{path.scheme} and class #{path.class}"
end
path = path.to_s
@paths << path
raise ArgumentError, 'need full URI' unless path =~ %r'^https?://'
unless @data.key? path then
raise Gem::RemoteFetcher::FetchError.new("no data for #{path}", path)
end
@data[path]
end
def fetch_path path, mtime = nil, head = false
data = find_data(path)
if data.respond_to?(:call) then
data.call
else
if path.to_s =~ /gz$/ and not data.nil? and not data.empty? then
data = Gem.gunzip data
end
data
end
end
def cache_update_path uri, path = nil, update = true
if data = fetch_path(uri)
open(path, 'wb') { |io| io.write data } if path and update
data
else
Gem.read_binary(path) if path
end
end
# Thanks, FakeWeb!
def open_uri_or_path(path)
data = find_data(path)
body, code, msg = data
response = Net::HTTPResponse.send(:response_class, code.to_s).new("1.0", code.to_s, msg)
response.instance_variable_set(:@body, body)
response.instance_variable_set(:@read, true)
response
end
def request(uri, request_class, last_modified = nil)
data = find_data(uri)
body, code, msg = data
@last_request = request_class.new uri.request_uri
yield @last_request if block_given?
response = Net::HTTPResponse.send(:response_class, code.to_s).new("1.0", code.to_s, msg)
response.instance_variable_set(:@body, body)
response.instance_variable_set(:@read, true)
response
end
def pretty_print q # :nodoc:
q.group 2, '[FakeFetcher', ']' do
q.breakable
q.text 'URIs:'
q.breakable
q.pp @data.keys
unless @api_endpoints.empty? then
q.breakable
q.text 'API endpoints:'
q.breakable
q.pp @api_endpoints.keys
end
end
end
def fetch_size(path)
path = path.to_s
@paths << path
raise ArgumentError, 'need full URI' unless path =~ %r'^http://'
unless @data.key? path then
raise Gem::RemoteFetcher::FetchError.new("no data for #{path}", path)
end
data = @data[path]
data.respond_to?(:call) ? data.call : data.length
end
def download spec, source_uri, install_dir = Gem.dir
name = File.basename spec.cache_file
path = if Dir.pwd == install_dir then # see fetch_command
install_dir
else
File.join install_dir, "cache"
end
path = File.join path, name
if source_uri =~ /^http/ then
File.open(path, "wb") do |f|
f.write fetch_path(File.join(source_uri, "gems", name))
end
else
FileUtils.cp source_uri, path
end
path
end
def download_to_cache dependency
found, _ = Gem::SpecFetcher.fetcher.spec_for_dependency dependency
return if found.empty?
spec, source = found.first
download spec, source.uri.to_s
end
end
# :stopdoc:
class Gem::RemoteFetcher
def self.fetcher=(fetcher)
@fetcher = fetcher
end
end
# :startdoc:
##
# The SpecFetcherSetup allows easy setup of a remote source in RubyGems tests:
#
# spec_fetcher do |f|
# f.gem 'a', 1
# f.spec 'a', 2
# f.gem 'b', 1' 'a' => '~> 1.0'
# end
#
# The above declaration creates two gems, a-1 and b-1, with a dependency from
# b to a. The declaration creates an additional spec a-2, but no gem for it
# (so it cannot be installed).
#
# After the gems are created they are removed from Gem.dir.
class Gem::TestCase::SpecFetcherSetup
##
# Executes a SpecFetcher setup block. Yields an instance then creates the
# gems and specifications defined in the instance.
def self.declare test, repository
setup = new test, repository
yield setup
setup.execute
end
def initialize test, repository # :nodoc:
@test = test
@repository = repository
@gems = {}
@downloaded = []
@installed = []
@operations = []
end
##
# Returns a Hash of created Specification full names and the corresponding
# Specification.
def created_specs
created = {}
@gems.keys.each do |spec|
created[spec.full_name] = spec
end
created
end
##
# Creates any defined gems or specifications
def execute # :nodoc:
execute_operations
setup_fetcher
created_specs
end
def execute_operations # :nodoc:
@operations.each do |operation, *arguments|
case operation
when :gem then
spec, gem = @test.util_gem(*arguments, &arguments.pop)
write_spec spec
@gems[spec] = gem
@installed << spec
when :download then
spec, gem = @test.util_gem(*arguments, &arguments.pop)
@gems[spec] = gem
@downloaded << spec
when :spec then
spec = @test.util_spec(*arguments, &arguments.pop)
write_spec spec
@gems[spec] = nil
@installed << spec
end
end
end
##
# Creates a gem with +name+, +version+ and +deps+. The created gem can be
# downloaded and installed.
#
# The specification will be yielded before gem creation for customization,
# but only the block or the dependencies may be set, not both.
def gem name, version, dependencies = nil, &block
@operations << [:gem, name, version, dependencies, block]
end
##
# Creates a gem with +name+, +version+ and +deps+. The created gem is
# downloaded in to the cache directory but is not installed
#
# The specification will be yielded before gem creation for customization,
# but only the block or the dependencies may be set, not both.
def download name, version, dependencies = nil, &block
@operations << [:download, name, version, dependencies, block]
end
##
# Creates a legacy platform spec with the name 'pl' and version 1
def legacy_platform
spec 'pl', 1 do |s|
s.platform = Gem::Platform.new 'i386-linux'
s.instance_variable_set :@original_platform, 'i386-linux'
end
end
def setup_fetcher # :nodoc:
require 'zlib'
require 'socket'
require 'rubygems/remote_fetcher'
unless @test.fetcher then
@test.fetcher = Gem::FakeFetcher.new
Gem::RemoteFetcher.fetcher = @test.fetcher
end
Gem::Specification.reset
begin
gem_repo, @test.gem_repo = @test.gem_repo, @repository
@test.uri = URI @repository
@test.util_setup_spec_fetcher(*@downloaded)
ensure
@test.gem_repo = gem_repo
@test.uri = URI gem_repo
end
@gems.each do |spec, gem|
next unless gem
@test.fetcher.data["#{@repository}gems/#{spec.file_name}"] =
Gem.read_binary(gem)
FileUtils.cp gem, spec.cache_file
end
end
##
# Creates a spec with +name+, +version+ and +deps+. The created gem can be
# downloaded and installed.
#
# The specification will be yielded before creation for customization,
# but only the block or the dependencies may be set, not both.
def spec name, version, dependencies = nil, &block
@operations << [:spec, name, version, dependencies, block]
end
def write_spec spec # :nodoc:
File.open spec.spec_file, 'w' do |io|
io.write spec.to_ruby_for_cache
end
end
end
##
# A StringIO duck-typed class that uses Tempfile instead of String as the
# backing store.
#
# This is available when rubygems/test_utilities is required.
#--
# This class was added to flush out problems in Rubinius' IO implementation.
class TempIO < Tempfile
##
# Creates a new TempIO that will be initialized to contain +string+.
def initialize(string = '')
super "TempIO"
binmode
write string
rewind
end
##
# The content of the TempIO as a String.
def string
flush
Gem.read_binary path
end
end
rubygems/remote_fetcher.rb 0000644 00000027231 15263270206 0011731 0 ustar 00 # frozen_string_literal: true
require 'rubygems'
require 'rubygems/request'
require 'rubygems/uri_formatter'
require 'rubygems/user_interaction'
require 'rubygems/request/connection_pools'
require 'resolv'
##
# RemoteFetcher handles the details of fetching gems and gem information from
# a remote source.
class Gem::RemoteFetcher
include Gem::UserInteraction
##
# A FetchError exception wraps up the various possible IO and HTTP failures
# that could happen while downloading from the internet.
class FetchError < Gem::Exception
##
# The URI which was being accessed when the exception happened.
attr_accessor :uri
def initialize(message, uri)
super message
begin
uri = URI(uri)
uri.password = 'REDACTED' if uri.password
@uri = uri.to_s
rescue URI::InvalidURIError, ArgumentError
@uri = uri
end
end
def to_s # :nodoc:
"#{super} (#{uri})"
end
end
##
# A FetchError that indicates that the reason for not being
# able to fetch data was that the host could not be contacted
class UnknownHostError < FetchError
end
@fetcher = nil
##
# Cached RemoteFetcher instance.
def self.fetcher
@fetcher ||= self.new Gem.configuration[:http_proxy]
end
attr_accessor :headers
##
# Initialize a remote fetcher using the source URI and possible proxy
# information.
#
# +proxy+
# * [String]: explicit specification of proxy; overrides any environment
# variable setting
# * nil: respect environment variables (HTTP_PROXY, HTTP_PROXY_USER,
# 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={})
require 'net/http'
require 'stringio'
require 'time'
require 'uri'
Socket.do_not_reverse_lookup = true
@proxy = proxy
@pools = {}
@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.
#--
# Should probably be integrated with #download below, but that will be a
# larger, more encompassing effort. -erikh
def download_to_cache dependency
found, _ = Gem::SpecFetcher.fetcher.spec_for_dependency dependency
return if found.empty?
spec, source = found.max_by { |(s,_)| s.version }
download spec, source.uri.to_s
end
##
# Moves the gem +spec+ from +source_uri+ to the cache dir unless it is
# already there. If the source_uri is local the gem cache dir copy is
# always replaced.
def download(spec, source_uri, install_dir = Gem.dir)
cache_dir =
if Dir.pwd == install_dir then # see fetch_command
install_dir
elsif File.writable? install_dir then
File.join install_dir, "cache"
else
File.join Gem.user_dir, "cache"
end
gem_file_name = File.basename spec.cache_file
local_gem_path = File.join cache_dir, gem_file_name
FileUtils.mkdir_p cache_dir rescue nil unless File.exist? cache_dir
# Always escape URI's to deal with potential spaces and such
# It should also be considered that source_uri may already be
# a valid URI with escaped characters. e.g. "{DESede}" is encoded
# as "%7BDESede%7D". If this is escaped again the percentage
# symbols will be escaped.
unless source_uri.is_a?(URI::Generic)
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))
end
end
scheme = source_uri.scheme
# URI.parse gets confused by MS Windows paths with forward slashes.
scheme = nil if scheme =~ /^[a-z]$/i
# REFACTOR: split this up and dispatch on scheme (eg download_http)
# 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
begin
verbose "Downloading gem #{gem_file_name}"
remote_gem_path = source_uri + "gems/#{gem_file_name}"
self.cache_update_path remote_gem_path, local_gem_path
rescue Gem::RemoteFetcher::FetchError
raise if spec.original_platform == spec.platform
alternate_name = "#{spec.original_name}.gem"
verbose "Failed, downloading gem #{alternate_name}"
remote_gem_path = source_uri + "gems/#{alternate_name}"
self.cache_update_path remote_gem_path, local_gem_path
end
end
when 'file' then
begin
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))
FileUtils.cp(remote_gem_path, local_gem_path)
rescue Errno::EACCES
local_gem_path = source_uri.to_s
end
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.scheme}:#{source_uri.path}"
else
source_uri.path
end
source_path = Gem::UriFormatter.new(source_path).unescape
begin
FileUtils.cp source_path, local_gem_path unless
File.identical?(source_path, local_gem_path)
rescue Errno::EACCES
local_gem_path = source_uri.to_s
end
verbose "Using local gem #{local_gem_path}"
else
raise ArgumentError, "unsupported URI scheme #{source_uri.scheme}"
end
local_gem_path
end
##
# File Fetcher. Dispatched by +fetch_path+. Use it instead.
def fetch_file uri, *_
Gem.read_binary 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
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) }
end
case response
when Net::HTTPOK, Net::HTTPNotModified then
response.uri = uri if response.respond_to? :uri
head ? response : response.body
when Net::HTTPMovedPermanently, Net::HTTPFound, Net::HTTPSeeOther,
Net::HTTPTemporaryRedirect then
raise FetchError.new('too many redirects', uri) if depth > 10
unless location = response['Location']
raise FetchError.new("redirecting but no redirect location was given", uri)
end
location = URI.parse response['Location']
if https?(uri) && !https?(location)
raise FetchError.new("redirecting to non-https resource: #{location}", uri)
end
fetch_http(location, last_modified, head, depth + 1)
else
raise FetchError.new("bad response #{response.message} #{response.code}", uri)
end
end
alias :fetch_https :fetch_http
##
# Downloads +uri+ and returns it as a String.
def fetch_path(uri, mtime = nil, head = false)
uri = URI.parse uri unless URI::Generic === uri
raise ArgumentError, "bad uri: #{uri}" unless uri
unless uri.scheme
raise ArgumentError, "uri scheme is invalid: #{uri.scheme.inspect}"
end
data = send "fetch_#{uri.scheme}", uri, mtime, head
if data and !head and uri.to_s =~ /\.gz$/
begin
data = Gem.gunzip data
rescue Zlib::GzipFile::Error
raise FetchError.new("server did not return a valid file", uri.to_s)
end
end
data
rescue FetchError
raise
rescue Timeout::Error
raise UnknownHostError.new('timed out', uri.to_s)
rescue IOError, SocketError, SystemCallError => e
if e.message =~ /getaddrinfo/
raise UnknownHostError.new('no such name', uri.to_s)
else
raise FetchError.new("#{e.class}: #{e}", uri.to_s)
end
end
def fetch_s3(uri, mtime = nil, head = false)
public_uri = sign_s3_url(uri)
fetch_https public_uri, mtime, head
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
mtime = path && File.stat(path).mtime rescue nil
data = fetch_path(uri, mtime)
if data == nil # indicates the server returned 304 Not Modified
return Gem.read_binary(path)
end
if update and path
Gem.write_binary(path, data)
end
data
end
##
# Returns the size of +uri+ in bytes.
def fetch_size(uri) # TODO: phase this out
response = fetch_path(uri, nil, true)
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
# connections to reduce connect overhead.
def request(uri, request_class, last_modified = nil)
proxy = proxy_for @proxy, uri
pool = pools_for(proxy).pool_for uri
request = Gem::Request.new uri, request_class, last_modified, pool
request.fetch do |req|
yield req if block_given?
end
end
def https?(uri)
uri.scheme.downcase == 'https'
end
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
Gem::Request.proxy_uri(proxy || Gem::Request.get_proxy_from_env(uri.scheme))
end
def pools_for proxy
@pool_lock.synchronize do
@pools[proxy] ||= Gem::Request::ConnectionPools.new proxy, @cert_files
end
end
end
rubygems/resolver/lock_specification.rb 0000644 00000003515 15263270206 0014426 0 ustar 00 # frozen_string_literal: true
##
# The LockSpecification comes from a lockfile (Gem::RequestSet::Lockfile).
#
# A LockSpecification's dependency information is pre-filled from the
# lockfile.
class Gem::Resolver::LockSpecification < Gem::Resolver::Specification
attr_reader :sources
def initialize set, name, version, sources, platform
super()
@name = name
@platform = platform
@set = set
@source = sources.first
@sources = sources
@version = version
@dependencies = []
@spec = nil
end
##
# This is a null install as a locked specification is considered installed.
# +options+ are ignored.
def install options = {}
destination = options[:install_dir] || Gem.dir
if File.exist? File.join(destination, 'specifications', spec.spec_name) then
yield nil
return
end
super
end
##
# Adds +dependency+ from the lockfile to this specification
def add_dependency dependency # :nodoc:
@dependencies << dependency
end
def pretty_print q # :nodoc:
q.group 2, '[LockSpecification', ']' do
q.breakable
q.text "name: #{@name}"
q.breakable
q.text "version: #{@version}"
unless @platform == Gem::Platform::RUBY then
q.breakable
q.text "platform: #{@platform}"
end
unless @dependencies.empty? then
q.breakable
q.text 'dependencies:'
q.breakable
q.pp @dependencies
end
end
end
##
# A specification constructed from the lockfile is returned
def spec
@spec ||= Gem::Specification.find { |spec|
spec.name == @name and spec.version == @version
}
@spec ||= Gem::Specification.new do |s|
s.name = @name
s.version = @version
s.platform = @platform
s.dependencies.concat @dependencies
end
end
end
rubygems/resolver/api_specification.rb 0000644 00000003717 15263270206 0014253 0 ustar 00 # frozen_string_literal: true
##
# Represents a specification retrieved via the rubygems.org API.
#
# This is used to avoid loading the full Specification object when all we need
# is the name, version, and dependencies.
class Gem::Resolver::APISpecification < Gem::Resolver::Specification
##
# Creates an APISpecification for the given +set+ from the rubygems.org
# +api_data+.
#
# See http://guides.rubygems.org/rubygems-org-api/#misc_methods for the
# format of the +api_data+.
def initialize(set, api_data)
super()
@set = set
@name = api_data[:name]
@version = Gem::Version.new api_data[:number]
@platform = Gem::Platform.new api_data[:platform]
@dependencies = api_data[:dependencies].map do |name, ver|
Gem::Dependency.new name, ver.split(/\s*,\s*/)
end
end
def == other # :nodoc:
self.class === other and
@set == other.set and
@name == other.name and
@version == other.version and
@platform == other.platform and
@dependencies == other.dependencies
end
def fetch_development_dependencies # :nodoc:
spec = source.fetch_spec Gem::NameTuple.new @name, @version, @platform
@dependencies = spec.dependencies
end
def installable_platform? # :nodoc:
Gem::Platform.match @platform
end
def pretty_print q # :nodoc:
q.group 2, '[APISpecification', ']' do
q.breakable
q.text "name: #{name}"
q.breakable
q.text "version: #{version}"
q.breakable
q.text "platform: #{platform}"
q.breakable
q.text 'dependencies:'
q.breakable
q.pp @dependencies
q.breakable
q.text "set uri: #{@set.dep_uri}"
end
end
##
# Fetches a Gem::Specification for this APISpecification.
def spec # :nodoc:
@spec ||=
begin
tuple = Gem::NameTuple.new @name, @version, @platform
source.fetch_spec tuple
end
end
def source # :nodoc:
@set.source
end
end
rubygems/resolver/local_specification.rb 0000644 00000001447 15263270206 0014572 0 ustar 00 # frozen_string_literal: true
##
# A LocalSpecification comes from a .gem file on the local filesystem.
class Gem::Resolver::LocalSpecification < Gem::Resolver::SpecSpecification
##
# Returns +true+ if this gem is installable for the current platform.
def installable_platform?
return true if @source.kind_of? Gem::Source::SpecificFile
super
end
def local? # :nodoc:
true
end
def pretty_print q # :nodoc:
q.group 2, '[LocalSpecification', ']' do
q.breakable
q.text "name: #{name}"
q.breakable
q.text "version: #{version}"
q.breakable
q.text "platform: #{platform}"
q.breakable
q.text 'dependencies:'
q.breakable
q.pp dependencies
q.breakable
q.text "source: #{@source.path}"
end
end
end
rubygems/resolver/specification.rb 0000644 00000004473 15263270206 0013422 0 ustar 00 # frozen_string_literal: true
##
# A Resolver::Specification contains a subset of the information
# contained in a Gem::Specification. Only the information necessary for
# dependency resolution in the resolver is included.
class Gem::Resolver::Specification
##
# The dependencies of the gem for this specification
attr_reader :dependencies
##
# The name of the gem for this specification
attr_reader :name
##
# The platform this gem works on.
attr_reader :platform
##
# The set this specification came from.
attr_reader :set
##
# The source for this specification
attr_reader :source
##
# The Gem::Specification for this Resolver::Specification.
#
# Implementers, note that #install updates @spec, so be sure to cache the
# Gem::Specification in @spec when overriding.
attr_reader :spec
##
# The version of the gem for this specification.
attr_reader :version
##
# Sets default instance variables for the specification.
def initialize
@dependencies = nil
@name = nil
@platform = nil
@set = nil
@source = nil
@version = nil
end
##
# Fetches development dependencies if the source does not provide them by
# default (see APISpecification).
def fetch_development_dependencies # :nodoc:
end
##
# The name and version of the specification.
#
# Unlike Gem::Specification#full_name, the platform is not included.
def full_name
"#{@name}-#{@version}"
end
##
# Installs this specification using the Gem::Installer +options+. The
# install method yields a Gem::Installer instance, which indicates the
# gem will be installed, or +nil+, which indicates the gem is already
# installed.
#
# After installation #spec is updated to point to the just-installed
# specification.
def install options = {}
require 'rubygems/installer'
destination = options[:install_dir] || Gem.dir
Gem.ensure_gem_subdirectories destination
gem = source.download spec, destination
installer = Gem::Installer.at gem, options
yield installer if block_given?
@spec = installer.install
end
##
# Returns true if this specification is installable on this platform.
def installable_platform?
Gem::Platform.match spec.platform
end
def local? # :nodoc:
false
end
end
rubygems/resolver/stats.rb 0000644 00000001666 15263270206 0011741 0 ustar 00 # frozen_string_literal: true
class Gem::Resolver::Stats
def initialize
@max_depth = 0
@max_requirements = 0
@requirements = 0
@backtracking = 0
@iterations = 0
end
def record_depth(stack)
if stack.size > @max_depth
@max_depth = stack.size
end
end
def record_requirements(reqs)
if reqs.size > @max_requirements
@max_requirements = reqs.size
end
end
def requirement!
@requirements += 1
end
def backtracking!
@backtracking += 1
end
def iteration!
@iterations += 1
end
PATTERN = "%20s: %d\n"
def display
$stdout.puts "=== Resolver Statistics ==="
$stdout.printf PATTERN, "Max Depth", @max_depth
$stdout.printf PATTERN, "Total Requirements", @requirements
$stdout.printf PATTERN, "Max Requirements", @max_requirements
$stdout.printf PATTERN, "Backtracking #", @backtracking
$stdout.printf PATTERN, "Iteration #", @iterations
end
end
rubygems/resolver/api_set.rb 0000644 00000004725 15263270206 0012226 0 ustar 00 # frozen_string_literal: true
##
# The global rubygems pool, available via the rubygems.org API.
# Returns instances of APISpecification.
class Gem::Resolver::APISet < Gem::Resolver::Set
##
# The URI for the dependency API this APISet uses.
attr_reader :dep_uri # :nodoc:
##
# The Gem::Source that gems are fetched from
attr_reader :source
##
# The corresponding place to fetch gems.
attr_reader :uri
##
# Creates a new APISet that will retrieve gems from +uri+ using the RubyGems
# 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'
super()
dep_uri = URI dep_uri unless URI === dep_uri # for ruby 1.8
@dep_uri = dep_uri
@uri = dep_uri + '../..'
@data = Hash.new { |h,k| h[k] = [] }
@source = Gem::Source.new @uri
@to_fetch = []
end
##
# Return an array of APISpecification objects matching
# DependencyRequest +req+.
def find_all req
res = []
return res unless @remote
if @to_fetch.include?(req.name)
prefetch_now
end
versions(req.name).each do |ver|
if req.dependency.match? req.name, ver[:number]
res << Gem::Resolver::APISpecification.new(self, ver)
end
end
res
end
##
# A hint run by the resolver to allow the Set to fetch
# data for DependencyRequests +reqs+.
def prefetch reqs
return unless @remote
names = reqs.map { |r| r.dependency.name }
needed = names - @data.keys - @to_fetch
@to_fetch += needed
end
def prefetch_now # :nodoc:
needed, @to_fetch = @to_fetch, []
uri = @dep_uri + "?gems=#{needed.sort.join ','}"
str = Gem::RemoteFetcher.fetcher.fetch_path uri
loaded = []
Marshal.load(str).each do |ver|
name = ver[:name]
@data[name] << ver
loaded << name
end
(needed - loaded).each do |missing|
@data[missing] = []
end
end
def pretty_print q # :nodoc:
q.group 2, '[APISet', ']' do
q.breakable
q.text "URI: #{@dep_uri}"
q.breakable
q.text 'gem names:'
q.pp @data.keys
end
end
##
# Return data for all versions of the gem +name+.
def versions name # :nodoc:
if @data.key?(name)
return @data[name]
end
uri = @dep_uri + "?gems=#{name}"
str = Gem::RemoteFetcher.fetcher.fetch_path uri
Marshal.load(str).each do |ver|
@data[ver[:name]] << ver
end
@data[name]
end
end
rubygems/resolver/molinillo/lib/molinillo/delegates/specification_provider.rb 0000644 00000005635 15263270206 0024034 0 ustar 00 # frozen_string_literal: true
module Gem::Resolver::Molinillo
module Delegates
# Delegates all {Gem::Resolver::Molinillo::SpecificationProvider} methods to a
# `#specification_provider` property.
module SpecificationProvider
# (see Gem::Resolver::Molinillo::SpecificationProvider#search_for)
def search_for(dependency)
with_no_such_dependency_error_handling do
specification_provider.search_for(dependency)
end
end
# (see Gem::Resolver::Molinillo::SpecificationProvider#dependencies_for)
def dependencies_for(specification)
with_no_such_dependency_error_handling do
specification_provider.dependencies_for(specification)
end
end
# (see Gem::Resolver::Molinillo::SpecificationProvider#requirement_satisfied_by?)
def requirement_satisfied_by?(requirement, activated, spec)
with_no_such_dependency_error_handling do
specification_provider.requirement_satisfied_by?(requirement, activated, spec)
end
end
# (see Gem::Resolver::Molinillo::SpecificationProvider#name_for)
def name_for(dependency)
with_no_such_dependency_error_handling do
specification_provider.name_for(dependency)
end
end
# (see Gem::Resolver::Molinillo::SpecificationProvider#name_for_explicit_dependency_source)
def name_for_explicit_dependency_source
with_no_such_dependency_error_handling do
specification_provider.name_for_explicit_dependency_source
end
end
# (see Gem::Resolver::Molinillo::SpecificationProvider#name_for_locking_dependency_source)
def name_for_locking_dependency_source
with_no_such_dependency_error_handling do
specification_provider.name_for_locking_dependency_source
end
end
# (see Gem::Resolver::Molinillo::SpecificationProvider#sort_dependencies)
def sort_dependencies(dependencies, activated, conflicts)
with_no_such_dependency_error_handling do
specification_provider.sort_dependencies(dependencies, activated, conflicts)
end
end
# (see Gem::Resolver::Molinillo::SpecificationProvider#allow_missing?)
def allow_missing?(dependency)
with_no_such_dependency_error_handling do
specification_provider.allow_missing?(dependency)
end
end
private
# Ensures any raised {NoSuchDependencyError} has its
# {NoSuchDependencyError#required_by} set.
# @yield
def with_no_such_dependency_error_handling
yield
rescue NoSuchDependencyError => error
if state
vertex = activated.vertex_named(name_for(error.dependency))
error.required_by += vertex.incoming_edges.map { |e| e.origin.name }
error.required_by << name_for_explicit_dependency_source unless vertex.explicit_requirements.empty?
end
raise
end
end
end
end
rubygems/resolver/molinillo/lib/molinillo/delegates/resolution_state.rb 0000644 00000003264 15263270206 0022701 0 ustar 00 # frozen_string_literal: true
module Gem::Resolver::Molinillo
# @!visibility private
module Delegates
# Delegates all {Gem::Resolver::Molinillo::ResolutionState} methods to a `#state` property.
module ResolutionState
# (see Gem::Resolver::Molinillo::ResolutionState#name)
def name
current_state = state || Gem::Resolver::Molinillo::ResolutionState.empty
current_state.name
end
# (see Gem::Resolver::Molinillo::ResolutionState#requirements)
def requirements
current_state = state || Gem::Resolver::Molinillo::ResolutionState.empty
current_state.requirements
end
# (see Gem::Resolver::Molinillo::ResolutionState#activated)
def activated
current_state = state || Gem::Resolver::Molinillo::ResolutionState.empty
current_state.activated
end
# (see Gem::Resolver::Molinillo::ResolutionState#requirement)
def requirement
current_state = state || Gem::Resolver::Molinillo::ResolutionState.empty
current_state.requirement
end
# (see Gem::Resolver::Molinillo::ResolutionState#possibilities)
def possibilities
current_state = state || Gem::Resolver::Molinillo::ResolutionState.empty
current_state.possibilities
end
# (see Gem::Resolver::Molinillo::ResolutionState#depth)
def depth
current_state = state || Gem::Resolver::Molinillo::ResolutionState.empty
current_state.depth
end
# (see Gem::Resolver::Molinillo::ResolutionState#conflicts)
def conflicts
current_state = state || Gem::Resolver::Molinillo::ResolutionState.empty
current_state.conflicts
end
end
end
end
rubygems/resolver/molinillo/lib/molinillo/dependency_graph/delete_edge.rb 0000644 00000003531 15263270206 0023063 0 ustar 00 # frozen_string_literal: true
require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/action'
module Gem::Resolver::Molinillo
class DependencyGraph
# @!visibility private
# (see DependencyGraph#delete_edge)
class DeleteEdge < Action
# @!group Action
# (see Action.action_name)
def self.action_name
:delete_edge
end
# (see Action#up)
def up(graph)
edge = make_edge(graph)
edge.origin.outgoing_edges.delete(edge)
edge.destination.incoming_edges.delete(edge)
end
# (see Action#down)
def down(graph)
edge = make_edge(graph)
edge.origin.outgoing_edges << edge
edge.destination.incoming_edges << edge
edge
end
# @!group DeleteEdge
# @return [String] the name of the origin of the edge
attr_reader :origin_name
# @return [String] the name of the destination of the edge
attr_reader :destination_name
# @return [Object] the requirement that the edge represents
attr_reader :requirement
# @param [DependencyGraph] graph the graph to find vertices from
# @return [Edge] The edge this action adds
def make_edge(graph)
Edge.new(
graph.vertex_named(origin_name),
graph.vertex_named(destination_name),
requirement
)
end
# Initialize an action to add an edge to a dependency graph
# @param [String] origin_name the name of the origin of the edge
# @param [String] destination_name the name of the destination of the edge
# @param [Object] requirement the requirement that the edge represents
def initialize(origin_name, destination_name, requirement)
@origin_name = origin_name
@destination_name = destination_name
@requirement = requirement
end
end
end
end
rubygems/resolver/molinillo/lib/molinillo/dependency_graph/vertex.rb 0000644 00000007540 15263270206 0022156 0 ustar 00 # frozen_string_literal: true
module Gem::Resolver::Molinillo
class DependencyGraph
# A vertex in a {DependencyGraph} that encapsulates a {#name} and a
# {#payload}
class Vertex
# @return [String] the name of the vertex
attr_accessor :name
# @return [Object] the payload the vertex holds
attr_accessor :payload
# @return [Array