summaryrefslogtreecommitdiff
path: root/lib/rubygems
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rubygems')
-rw-r--r--lib/rubygems/LICENSE.txt75
-rw-r--r--lib/rubygems/available_set.rb4
-rw-r--r--lib/rubygems/basic_specification.rb8
-rw-r--r--lib/rubygems/commands/install_command.rb23
-rw-r--r--lib/rubygems/commands/which_command.rb5
-rw-r--r--lib/rubygems/dependency_installer.rb32
-rw-r--r--lib/rubygems/doctor.rb20
-rw-r--r--lib/rubygems/errors.rb17
-rw-r--r--lib/rubygems/exceptions.rb10
-rw-r--r--lib/rubygems/ext/builder.rb3
-rw-r--r--lib/rubygems/ext/cmake_builder.rb2
-rw-r--r--lib/rubygems/ext/configure_builder.rb2
-rw-r--r--lib/rubygems/ext/ext_conf_builder.rb8
-rw-r--r--lib/rubygems/ext/rake_builder.rb2
-rw-r--r--lib/rubygems/remote_fetcher.rb2
-rw-r--r--lib/rubygems/request_set.rb19
-rw-r--r--lib/rubygems/request_set/gem_dependency_api.rb149
-rw-r--r--lib/rubygems/request_set/lockfile.rb27
-rw-r--r--lib/rubygems/resolver.rb (renamed from lib/rubygems/dependency_resolver.rb)104
-rw-r--r--lib/rubygems/resolver/activation_request.rb (renamed from lib/rubygems/dependency_resolver/activation_request.rb)6
-rw-r--r--lib/rubygems/resolver/api_set.rb (renamed from lib/rubygems/dependency_resolver/api_set.rb)4
-rw-r--r--lib/rubygems/resolver/api_specification.rb (renamed from lib/rubygems/dependency_resolver/api_specification.rb)2
-rw-r--r--lib/rubygems/resolver/best_set.rb (renamed from lib/rubygems/dependency_resolver/best_set.rb)2
-rw-r--r--lib/rubygems/resolver/composed_set.rb (renamed from lib/rubygems/dependency_resolver/composed_set.rb)2
-rw-r--r--lib/rubygems/resolver/conflict.rb (renamed from lib/rubygems/dependency_resolver/dependency_conflict.rb)8
-rw-r--r--lib/rubygems/resolver/current_set.rb (renamed from lib/rubygems/dependency_resolver/current_set.rb)2
-rw-r--r--lib/rubygems/resolver/dependency_request.rb (renamed from lib/rubygems/dependency_resolver/dependency_request.rb)4
-rw-r--r--lib/rubygems/resolver/git_set.rb81
-rw-r--r--lib/rubygems/resolver/git_specification.rb16
-rw-r--r--lib/rubygems/resolver/index_set.rb (renamed from lib/rubygems/dependency_resolver/index_set.rb)4
-rw-r--r--lib/rubygems/resolver/index_specification.rb (renamed from lib/rubygems/dependency_resolver/index_specification.rb)2
-rw-r--r--lib/rubygems/resolver/installed_specification.rb (renamed from lib/rubygems/dependency_resolver/installed_specification.rb)2
-rw-r--r--lib/rubygems/resolver/installer_set.rb (renamed from lib/rubygems/dependency_resolver/installer_set.rb)8
-rw-r--r--lib/rubygems/resolver/lock_set.rb (renamed from lib/rubygems/dependency_resolver/lock_set.rb)6
-rw-r--r--lib/rubygems/resolver/requirement_list.rb40
-rw-r--r--lib/rubygems/resolver/set.rb (renamed from lib/rubygems/dependency_resolver/set.rb)9
-rw-r--r--lib/rubygems/resolver/spec_specification.rb (renamed from lib/rubygems/dependency_resolver/spec_specification.rb)6
-rw-r--r--lib/rubygems/resolver/specification.rb (renamed from lib/rubygems/dependency_resolver/specification.rb)4
-rw-r--r--lib/rubygems/resolver/vendor_set.rb (renamed from lib/rubygems/dependency_resolver/vendor_set.rb)6
-rw-r--r--lib/rubygems/resolver/vendor_specification.rb (renamed from lib/rubygems/dependency_resolver/vendor_specification.rb)2
-rw-r--r--lib/rubygems/source.rb25
-rw-r--r--lib/rubygems/source/git.rb189
-rw-r--r--lib/rubygems/source/installed.rb7
-rw-r--r--lib/rubygems/source/local.rb17
-rw-r--r--lib/rubygems/source/specific_file.rb17
-rw-r--r--lib/rubygems/source/vendor.rb18
-rw-r--r--lib/rubygems/source_list.rb15
-rw-r--r--lib/rubygems/spec_fetcher.rb26
-rw-r--r--lib/rubygems/specification.rb5
-rw-r--r--lib/rubygems/test_case.rb70
-rw-r--r--lib/rubygems/util.rb65
-rw-r--r--lib/rubygems/util/stringio.rb34
52 files changed, 1012 insertions, 204 deletions
diff --git a/lib/rubygems/LICENSE.txt b/lib/rubygems/LICENSE.txt
index db88c5e118..8a0a51dec1 100644
--- a/lib/rubygems/LICENSE.txt
+++ b/lib/rubygems/LICENSE.txt
@@ -1,53 +1,54 @@
RubyGems is copyrighted free software by Chad Fowler, Rich Kilmer, Jim
Weirich and others. You can redistribute it and/or modify it under
-either the terms of the GPL (see the GPL.txt file), or the conditions
-below:
+either the terms of the MIT license (see the file MIT.txt), or the
+conditions below:
- 1. You may make and give away verbatim copies of the source form of the
- software without restriction, provided that you duplicate all of the
- original copyright notices and associated disclaimers.
+1. You may make and give away verbatim copies of the source form of the
+ software without restriction, provided that you duplicate all of the
+ original copyright notices and associated disclaimers.
- 2. You may modify your copy of the software in any way, provided that
- you do at least ONE of the following:
+2. You may modify your copy of the software in any way, provided that
+ you do at least ONE of the following:
- a) place your modifications in the Public Domain or otherwise
- make them Freely Available, such as by posting said
- modifications to Usenet or an equivalent medium, or by allowing
- the author to include your modifications in the software.
+ a. place your modifications in the Public Domain or otherwise
+ make them Freely Available, such as by posting said
+ modifications to Usenet or an equivalent medium, or by allowing
+ the author to include your modifications in the software.
- b) use the modified software only within your corporation or
- organization.
+ b. use the modified software only within your corporation or
+ organization.
- c) rename any non-standard executables so the names do not conflict
- with standard executables, which must also be provided.
+ c. give non-standard executables non-standard names, with
+ instructions on where to get the original software distribution.
- d) make other distribution arrangements with the author.
+ d. make other distribution arrangements with the author.
- 3. You may distribute the software in object code or executable
- form, provided that you do at least ONE of the following:
+3. You may distribute the software in object code or executable
+ form, provided that you do at least ONE of the following:
- a) distribute the executables and library files of the software,
- together with instructions (in the manual page or equivalent)
- on where to get the original distribution.
+ a. distribute the executables and library files of the software,
+ together with instructions (in the manual page or equivalent)
+ on where to get the original distribution.
- b) accompany the distribution with the machine-readable source of
- the software.
+ b. accompany the distribution with the machine-readable source of
+ the software.
- c) give non-standard executables non-standard names, with
- instructions on where to get the original software distribution.
+ c. give non-standard executables non-standard names, with
+ instructions on where to get the original software distribution.
- d) make other distribution arrangements with the author.
+ d. make other distribution arrangements with the author.
- 4. You may modify and include the part of the software into any other
- software (possibly commercial).
+4. You may modify and include the part of the software into any other
+ software (possibly commercial).
- 5. The scripts and library files supplied as input to or produced as
- output from the software do not automatically fall under the
- copyright of the software, but belong to whomever generated them,
- and may be sold commercially, and may be aggregated with this
- software.
+5. The scripts and library files supplied as input to or produced as
+ output from the software do not automatically fall under the
+ copyright of the software, but belong to whomever generated them,
+ and may be sold commercially, and may be aggregated with this
+ software.
+
+6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ PURPOSE.
- 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
- IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- PURPOSE.
diff --git a/lib/rubygems/available_set.rb b/lib/rubygems/available_set.rb
index bb0b3a3abe..4ab4d77716 100644
--- a/lib/rubygems/available_set.rb
+++ b/lib/rubygems/available_set.rb
@@ -116,7 +116,7 @@ class Gem::AvailableSet
##
#
- # Used by the DependencyResolver, the protocol to use a AvailableSet as a
+ # Used by the Resolver, the protocol to use a AvailableSet as a
# search Set.
def find_all(req)
@@ -127,7 +127,7 @@ class Gem::AvailableSet
end
match.map do |t|
- Gem::DependencyResolver::InstalledSpecification.new(self, t.spec, t.source)
+ Gem::Resolver::InstalledSpecification.new(self, t.spec, t.source)
end
end
diff --git a/lib/rubygems/basic_specification.rb b/lib/rubygems/basic_specification.rb
index a29ed0aa6d..4f96fcac3d 100644
--- a/lib/rubygems/basic_specification.rb
+++ b/lib/rubygems/basic_specification.rb
@@ -107,7 +107,7 @@ class Gem::BasicSpecification
File.join full_gem_path, path
end
- full_paths << extension_install_dir unless @extensions.empty?
+ full_paths.unshift extension_install_dir unless @extensions.empty?
full_paths
end
@@ -155,6 +155,10 @@ class Gem::BasicSpecification
raise NotImplementedError
end
+ def raw_require_paths # :nodoc:
+ @require_paths
+ end
+
##
# Paths in the gem to add to <code>$LOAD_PATH</code> when this gem is
# activated.
@@ -179,7 +183,7 @@ class Gem::BasicSpecification
File.join '..', '..', 'extensions', Gem::Platform.local.to_s,
Gem.extension_api_version, full_name
- @require_paths + [relative_extension_install_dir]
+ [relative_extension_install_dir].concat @require_paths
end
##
diff --git a/lib/rubygems/commands/install_command.rb b/lib/rubygems/commands/install_command.rb
index ad90b37fdc..68a2fad129 100644
--- a/lib/rubygems/commands/install_command.rb
+++ b/lib/rubygems/commands/install_command.rb
@@ -56,6 +56,12 @@ class Gem::Commands::InstallCommand < Gem::Command
o[:install_as_default] = v
end
+ add_option(:"Install/Update", '--explain',
+ 'Rather than install the gems, indicate which would',
+ 'be installed') do |v,o|
+ o[:explain] = v
+ end
+
@installed_specs = nil
end
@@ -185,8 +191,23 @@ to write the specification by hand. For example:
return if options[:conservative] and
not Gem::Dependency.new(name, version).matching_specs.empty?
+ req = Gem::Requirement.create(version)
+
inst = Gem::DependencyInstaller.new options
- inst.install name, Gem::Requirement.create(version)
+
+ if options[:explain]
+ request_set = inst.resolve_dependencies name, req
+
+ puts "Gems to install:"
+
+ request_set.specs.map { |s| s.full_name }.sort.each do |s|
+ puts " #{s}"
+ end
+
+ return
+ else
+ inst.install name, req
+ end
@installed_specs.push(*inst.installed_gems)
diff --git a/lib/rubygems/commands/which_command.rb b/lib/rubygems/commands/which_command.rb
index 18706afdf5..96eeb86288 100644
--- a/lib/rubygems/commands/which_command.rb
+++ b/lib/rubygems/commands/which_command.rb
@@ -35,7 +35,7 @@ requiring to see why it does not behave as you expect.
end
def execute
- found = false
+ found = true
options[:args].each do |arg|
arg = arg.sub(/#{Regexp.union(*Gem.suffixes)}$/, '')
@@ -56,9 +56,10 @@ requiring to see why it does not behave as you expect.
if paths.empty? then
alert_error "Can't find ruby library file or shared library #{arg}"
+
+ found &&= false
else
say paths
- found = true
end
end
diff --git a/lib/rubygems/dependency_installer.rb b/lib/rubygems/dependency_installer.rb
index 22ff6f5cb7..e404d42b3a 100644
--- a/lib/rubygems/dependency_installer.rb
+++ b/lib/rubygems/dependency_installer.rb
@@ -1,6 +1,5 @@
require 'rubygems'
require 'rubygems/dependency_list'
-require 'rubygems/dependency_resolver'
require 'rubygems/package'
require 'rubygems/installer'
require 'rubygems/spec_fetcher'
@@ -196,7 +195,7 @@ class Gem::DependencyInstaller
# sources. Gems are sorted with newer gems preferred over older gems, and
# local gems preferred over remote gems.
- def find_gems_with_sources dep # :nodoc:
+ def find_gems_with_sources dep, best_only=false # :nodoc:
set = Gem::AvailableSet.new
if consider_local?
@@ -211,7 +210,26 @@ class Gem::DependencyInstaller
if consider_remote?
begin
- found, errors = Gem::SpecFetcher.fetcher.spec_for_dependency dep
+ # TODO 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?
+ tuples.sort! { |a,b| b[0].version <=> a[0].version }
+ tuples = [tuples.first]
+ end
+
+ specs = []
+ tuples.each do |tup, source|
+ begin
+ spec = source.fetch_spec(tup)
+ rescue Gem::RemoteFetcher::FetchError => e
+ errors << Gem::SourceFetchProblem.new(source, e)
+ else
+ specs << [spec, source]
+ end
+ end
if @errors
@errors += errors
@@ -219,7 +237,7 @@ class Gem::DependencyInstaller
@errors = errors
end
- set << found
+ set << specs
rescue Gem::RemoteFetcher::FetchError => e
# FIX if there is a problem talking to the network, we either need to always tell
@@ -271,7 +289,7 @@ class Gem::DependencyInstaller
dep = Gem::Dependency.new gem_name, version
dep.prerelease = true if prerelease
- set = find_gems_with_sources(dep)
+ set = find_gems_with_sources(dep, true)
set.match_platform!
end
@@ -402,7 +420,7 @@ class Gem::DependencyInstaller
request_set = as.to_request_set install_development_deps
request_set.soft_missing = @force
- installer_set = Gem::DependencyResolver::InstallerSet.new @domain
+ installer_set = Gem::Resolver::InstallerSet.new @domain
installer_set.always_install.concat request_set.always_install
installer_set.ignore_installed = @only_install_dir
@@ -411,7 +429,7 @@ class Gem::DependencyInstaller
request_set.soft_missing = true
end
- composed_set = Gem::DependencyResolver.compose_sets as, installer_set
+ composed_set = Gem::Resolver.compose_sets as, installer_set
request_set.resolve composed_set
diff --git a/lib/rubygems/doctor.rb b/lib/rubygems/doctor.rb
index 0de337f7de..2cb8901b4d 100644
--- a/lib/rubygems/doctor.rb
+++ b/lib/rubygems/doctor.rb
@@ -1,6 +1,5 @@
require 'rubygems'
require 'rubygems/user_interaction'
-require 'pathname'
##
# Cleans up after a partially-failed uninstall or for an invalid
@@ -39,7 +38,7 @@ class Gem::Doctor
# If +dry_run+ is true no files or directories will be removed.
def initialize gem_repository, dry_run = false
- @gem_repository = Pathname(gem_repository)
+ @gem_repository = gem_repository
@dry_run = dry_run
@installed_specs = nil
@@ -97,26 +96,29 @@ class Gem::Doctor
# Removes files in +sub_directory+ with +extension+
def doctor_child sub_directory, extension # :nodoc:
- directory = @gem_repository + sub_directory
+ directory = File.join(@gem_repository, sub_directory)
- directory.children.sort.each do |child|
- next unless child.exist?
+ Dir.entries(directory).sort.each do |ent|
+ next if ent == "." || ent == ".."
- basename = child.basename(extension).to_s
+ child = File.join(directory, ent)
+ next unless File.exists?(child)
+
+ basename = File.basename(child, extension)
next if installed_specs.include? basename
next if /^rubygems-\d/ =~ basename
next if 'specifications' == sub_directory and 'default' == basename
- type = child.directory? ? 'directory' : 'file'
+ type = File.directory?(child) ? 'directory' : 'file'
action = if @dry_run then
'Extra'
else
- child.rmtree
+ FileUtils.rm_r(child)
'Removed'
end
- say "#{action} #{type} #{sub_directory}/#{child.basename}"
+ say "#{action} #{type} #{sub_directory}/#{File.basename(child)}"
end
rescue Errno::ENOENT
# ignore
diff --git a/lib/rubygems/errors.rb b/lib/rubygems/errors.rb
index 3c5486a800..fc9bfbc0dc 100644
--- a/lib/rubygems/errors.rb
+++ b/lib/rubygems/errors.rb
@@ -73,12 +73,27 @@ module Gem
# data from a source
class SourceFetchProblem < ErrorReason
+
+ ##
+ # Creates a new SourceFetchProblem for the given +source+ and +error+.
+
def initialize(source, error)
@source = source
@error = error
end
- attr_reader :source, :error
+ ##
+ # The source that had the fetch problem.
+
+ attr_reader :source
+
+ ##
+ # The fetch error which is an Exception subclass.
+
+ attr_reader :error
+
+ ##
+ # An English description of the error.
def wordy
"Unable to download data from #{@source.uri} - #{@error.message}"
diff --git a/lib/rubygems/exceptions.rb b/lib/rubygems/exceptions.rb
index 6d92b144b6..ee3d8fecdf 100644
--- a/lib/rubygems/exceptions.rb
+++ b/lib/rubygems/exceptions.rb
@@ -23,7 +23,7 @@ class Gem::DependencyError < Gem::Exception; end
class Gem::DependencyRemovalException < Gem::Exception; end
##
-# Raised by Gem::DependencyResolver when a Gem::DependencyConflict reaches the
+# Raised by Gem::Resolver when a Gem::Dependency::Conflict reaches the
# toplevel. Indicates which dependencies were incompatible through #conflict
# and #conflicting_dependencies
@@ -117,7 +117,7 @@ class Gem::SpecificGemNotFoundException < Gem::GemNotFoundException
end
##
-# Raised by Gem::DependencyResolver when dependencies conflict and create the
+# Raised by Gem::Resolver when dependencies conflict and create the
# inability to find a valid possible spec for a request.
class Gem::ImpossibleDependenciesError < Gem::Exception
@@ -211,20 +211,20 @@ class Gem::SystemExitException < SystemExit
end
##
-# Raised by DependencyResolver when a dependency requests a gem for which
+# Raised by Resolver when a dependency requests a gem for which
# there is no spec.
class Gem::UnsatisfiableDependencyError < Gem::Exception
##
# The unsatisfiable dependency. This is a
- # Gem::DependencyResolver::DependencyRequest, not a Gem::Dependency
+ # Gem::Resolver::DependencyRequest, not a Gem::Dependency
attr_reader :dependency
##
# Creates a new UnsatisfiableDepedencyError for the unsatisfiable
- # Gem::DependencyResolver::DependencyRequest +dep+
+ # Gem::Resolver::DependencyRequest +dep+
def initialize dep, platform_mismatch=nil
if platform_mismatch and !platform_mismatch.empty?
diff --git a/lib/rubygems/ext/builder.rb b/lib/rubygems/ext/builder.rb
index 761505636c..e9244c760c 100644
--- a/lib/rubygems/ext/builder.rb
+++ b/lib/rubygems/ext/builder.rb
@@ -149,6 +149,7 @@ EOF
extension ||= '' # I wish I knew why this line existed
extension_dir =
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
@@ -158,7 +159,7 @@ EOF
CHDIR_MUTEX.synchronize do
Dir.chdir extension_dir do
results = builder.build(extension, @gem_dir, dest_path,
- results, @build_args)
+ results, @build_args, lib_dir)
say results.join("\n") if Gem.configuration.really_verbose
end
diff --git a/lib/rubygems/ext/cmake_builder.rb b/lib/rubygems/ext/cmake_builder.rb
index 17bd6c296e..24531bc75c 100644
--- a/lib/rubygems/ext/cmake_builder.rb
+++ b/lib/rubygems/ext/cmake_builder.rb
@@ -1,7 +1,7 @@
require 'rubygems/command'
class Gem::Ext::CmakeBuilder < Gem::Ext::Builder
- def self.build(extension, directory, dest_path, results)
+ def self.build(extension, directory, dest_path, results, args=[], lib_dir=nil)
unless File.exist?('Makefile') then
cmd = "cmake . -DCMAKE_INSTALL_PREFIX=#{dest_path}"
cmd << " #{Gem::Command.build_args.join ' '}" unless Gem::Command.build_args.empty?
diff --git a/lib/rubygems/ext/configure_builder.rb b/lib/rubygems/ext/configure_builder.rb
index 2a542e6bd0..f66e39387a 100644
--- a/lib/rubygems/ext/configure_builder.rb
+++ b/lib/rubygems/ext/configure_builder.rb
@@ -6,7 +6,7 @@
class Gem::Ext::ConfigureBuilder < Gem::Ext::Builder
- def self.build(extension, directory, dest_path, results, args=[])
+ def self.build(extension, directory, dest_path, results, args=[], lib_dir=nil)
unless File.exist?('Makefile') then
cmd = "sh ./configure --prefix=#{dest_path}"
cmd << " #{args.join ' '}" unless args.empty?
diff --git a/lib/rubygems/ext/ext_conf_builder.rb b/lib/rubygems/ext/ext_conf_builder.rb
index 9a656a30a3..6e736d8062 100644
--- a/lib/rubygems/ext/ext_conf_builder.rb
+++ b/lib/rubygems/ext/ext_conf_builder.rb
@@ -10,7 +10,7 @@ require 'tempfile'
class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder
FileEntry = FileUtils::Entry_ # :nodoc:
- def self.build(extension, directory, dest_path, results, args=[])
+ def self.build(extension, directory, dest_path, results, args=[], lib_dir=nil)
tmp_dest = Dir.mktmpdir(".gem.", ".")
t = nil
@@ -44,6 +44,12 @@ class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder
if tmp_dest
FileEntry.new(tmp_dest).traverse do |ent|
+ # TODO remove in RubyGems 3
+ if lib_dir then
+ libent = ent.class.new lib_dir, ent.rel
+ libent.exist? or ent.copy libent.path
+ end
+
destent = ent.class.new(dest_path, ent.rel)
destent.exist? or File.rename(ent.path, destent.path)
end
diff --git a/lib/rubygems/ext/rake_builder.rb b/lib/rubygems/ext/rake_builder.rb
index 984c54bc1b..2093bcabdd 100644
--- a/lib/rubygems/ext/rake_builder.rb
+++ b/lib/rubygems/ext/rake_builder.rb
@@ -6,7 +6,7 @@
class Gem::Ext::RakeBuilder < Gem::Ext::Builder
- def self.build(extension, directory, dest_path, results, args=[])
+ 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}"
cmd << " #{args.join " "}" unless args.empty?
diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb
index b96c77033a..c6816e8f0f 100644
--- a/lib/rubygems/remote_fetcher.rb
+++ b/lib/rubygems/remote_fetcher.rb
@@ -90,7 +90,7 @@ class Gem::RemoteFetcher
rescue Resolv::ResolvError
uri
else
- URI.parse "#{res.target}#{uri.path}"
+ URI.parse "#{uri.scheme}://#{res.target}#{uri.path}"
end
end
diff --git a/lib/rubygems/request_set.rb b/lib/rubygems/request_set.rb
index 3a997f32ee..42d457063f 100644
--- a/lib/rubygems/request_set.rb
+++ b/lib/rubygems/request_set.rb
@@ -1,6 +1,5 @@
require 'rubygems'
require 'rubygems/dependency'
-require 'rubygems/dependency_resolver'
require 'rubygems/dependency_list'
require 'rubygems/installer'
require 'tsort'
@@ -32,6 +31,11 @@ class Gem::RequestSet
attr_accessor :development
##
+ # The set of git gems imported via load_gemdeps.
+
+ attr_reader :git_set # :nodoc:
+
+ ##
# Sets used for resolution
attr_reader :sets # :nodoc:
@@ -61,6 +65,7 @@ class Gem::RequestSet
@always_install = []
@dependency_names = {}
@development = false
+ @git_set = nil
@requests = []
@sets = []
@soft_missing = false
@@ -184,7 +189,8 @@ class Gem::RequestSet
# Load a dependency management file.
def load_gemdeps path, without_groups = []
- @vendor_set = Gem::DependencyResolver::VendorSet.new
+ @git_set = Gem::Resolver::GitSet.new
+ @vendor_set = Gem::Resolver::VendorSet.new
gf = Gem::RequestSet::GemDependencyAPI.new self, path
gf.without_groups = without_groups if without_groups
@@ -195,13 +201,14 @@ class Gem::RequestSet
# Resolve the requested dependencies and return an Array of Specification
# objects to be activated.
- def resolve set = Gem::DependencyResolver::IndexSet.new
+ def resolve set = Gem::Resolver::IndexSet.new
@sets << set
+ @sets << @git_set
@sets << @vendor_set
- set = Gem::DependencyResolver.compose_sets(*@sets)
+ set = Gem::Resolver.compose_sets(*@sets)
- resolver = Gem::DependencyResolver.new @dependencies, set
+ resolver = Gem::Resolver.new @dependencies, set
resolver.development = @development
resolver.soft_missing = @soft_missing
@@ -213,7 +220,7 @@ class Gem::RequestSet
# and return an Array of Specification objects to be activated.
def resolve_current
- resolve Gem::DependencyResolver::CurrentSet.new
+ resolve Gem::Resolver::CurrentSet.new
end
def sorted_requests
diff --git a/lib/rubygems/request_set/gem_dependency_api.rb b/lib/rubygems/request_set/gem_dependency_api.rb
index e8f3138990..8e29eb87e5 100644
--- a/lib/rubygems/request_set/gem_dependency_api.rb
+++ b/lib/rubygems/request_set/gem_dependency_api.rb
@@ -108,6 +108,11 @@ class Gem::RequestSet::GemDependencyAPI
}
##
+ # A set of gems that are loaded via the +:git+ option to #gem
+
+ attr_reader :git_set # :nodoc:
+
+ ##
# A Hash containing gem names and files to require from those gems.
attr_reader :requires
@@ -130,13 +135,55 @@ class Gem::RequestSet::GemDependencyAPI
@set = set
@path = path
- @current_groups = nil
- @current_platform = nil
- @default_sources = true
- @requires = Hash.new { |h, name| h[name] = [] }
- @vendor_set = @set.vendor_set
- @gem_sources = {}
- @without_groups = []
+ @current_groups = nil
+ @current_platform = nil
+ @current_repository = nil
+ @default_sources = true
+ @git_set = @set.git_set
+ @requires = Hash.new { |h, name| h[name] = [] }
+ @vendor_set = @set.vendor_set
+ @gem_sources = {}
+ @without_groups = []
+ end
+
+ ##
+ # 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:
+ return unless (groups & @without_groups).empty?
+
+ dependencies.each do |dep|
+ @set.gem dep.name, *dep.requirement
+ end
+ end
+
+ private :add_dependencies
+
+ ##
+ # Finds a gemspec with the given +name+ that lives at +path+.
+
+ def find_gemspec name, path # :nodoc:
+ glob = File.join path, "#{name}.gemspec"
+
+ spec_files = Dir[glob]
+
+ case spec_files.length
+ when 1 then
+ spec_file = spec_files.first
+
+ spec = Gem::Specification.load spec_file
+
+ return spec if spec
+
+ raise ArgumentError, "invalid gemspec #{spec_file}"
+ when 0 then
+ raise ArgumentError, "no gemspecs found at #{Dir.pwd}"
+ else
+ raise ArgumentError,
+ "found multiple gemspecs at #{Dir.pwd}, " +
+ "use the name: option to specify the one you want"
+ end
end
##
@@ -160,7 +207,13 @@ class Gem::RequestSet::GemDependencyAPI
options = requirements.pop if requirements.last.kind_of?(Hash)
options ||= {}
- source_set = gem_path name, options
+ options[:git] = @current_repository if @current_repository
+
+ source_set = false
+
+ source_set ||= gem_path name, options
+ source_set ||= gem_git name, options
+ source_set ||= gem_github name, options
return unless gem_platforms options
@@ -182,6 +235,54 @@ class Gem::RequestSet::GemDependencyAPI
end
##
+ # Handles the git: option from +options+ for gem +name+.
+ #
+ # Returns +true+ if the path option was handled.
+
+ def gem_git name, options # :nodoc:
+ if gist = options.delete(:gist) then
+ options[:git] = "https://gist.github.com/#{gist}.git"
+ end
+
+ return unless repository = options.delete(:git)
+
+ raise ArgumentError,
+ "duplicate source git: #{repository} for gem #{name}" if
+ @gem_sources.include? name
+
+ reference = nil
+ reference ||= options.delete :ref
+ reference ||= options.delete :branch
+ reference ||= options.delete :tag
+ reference ||= 'master'
+
+ submodules = options.delete :submodules
+
+ @git_set.add_git_gem name, repository, reference, submodules
+
+ @gem_sources[name] = repository
+
+ true
+ end
+
+ private :gem_git
+
+ ##
+ # Handles the github: option from +options+ for gem +name+.
+ #
+ # Returns +true+ if the path option was handled.
+
+ def gem_github name, options # :nodoc:
+ return unless path = options.delete(:github)
+
+ options[:git] = "git://github.com/#{path}.git"
+
+ gem_git name, options
+
+ true
+ end
+
+ ##
# Handles the :group and :groups +options+ for the gem with the given
# +name+.
@@ -269,6 +370,15 @@ class Gem::RequestSet::GemDependencyAPI
private :gem_requires
+ def git repository
+ @current_repository = repository
+
+ yield
+
+ ensure
+ @current_repository = nil
+ end
+
##
# Returns the basename of the file the dependencies were loaded from
@@ -278,6 +388,29 @@ class Gem::RequestSet::GemDependencyAPI
##
# :category: Gem Dependencies DSL
+ #
+ # Loads dependencies from a gemspec file.
+
+ def gemspec options = {}
+ name = options.delete(:name) || '{,*}'
+ path = options.delete(:path) || '.'
+ development_group = options.delete(:development_group) || :development
+
+ spec = find_gemspec name, path
+
+ groups = gem_group spec.name, {}
+
+ add_dependencies groups, spec.runtime_dependencies
+
+ groups << development_group
+
+ add_dependencies groups, spec.development_dependencies
+
+ gem_requires spec.name, options
+ end
+
+ ##
+ # :category: Gem Dependencies DSL
# Block form for placing a dependency in the given +groups+.
def group *groups
diff --git a/lib/rubygems/request_set/lockfile.rb b/lib/rubygems/request_set/lockfile.rb
index a9c419549d..0073bfdcc5 100644
--- a/lib/rubygems/request_set/lockfile.rb
+++ b/lib/rubygems/request_set/lockfile.rb
@@ -1,5 +1,3 @@
-require 'pathname'
-
class Gem::RequestSet::Lockfile
##
@@ -46,8 +44,8 @@ class Gem::RequestSet::Lockfile
def initialize request_set, gem_deps_file
@set = request_set
- @gem_deps_file = Pathname(gem_deps_file).expand_path
- @gem_deps_dir = @gem_deps_file.dirname
+ @gem_deps_file = File.expand_path(gem_deps_file)
+ @gem_deps_dir = File.dirname(@gem_deps_file)
@current_token = nil
@line = 0
@@ -62,7 +60,7 @@ class Gem::RequestSet::Lockfile
@set.dependencies.sort.map do |dependency|
source = @requests.find do |req|
req.name == dependency.name and
- req.spec.class == Gem::DependencyResolver::VendorSpecification
+ req.spec.class == Gem::Resolver::VendorSpecification
end
source_dep = '!' if source
@@ -102,15 +100,26 @@ class Gem::RequestSet::Lockfile
out << nil
end
+ def relative_path_from(dest, base)
+ dest = File.expand_path(dest)
+ base = File.expand_path(base)
+
+ if dest.index(base) == 0
+ return dest[base.size+1..-1]
+ else
+ dest
+ end
+ end
+
def add_PATH out # :nodoc:
return unless path_requests =
- @spec_groups.delete(Gem::DependencyResolver::VendorSpecification)
+ @spec_groups.delete(Gem::Resolver::VendorSpecification)
out << "PATH"
path_requests.each do |request|
- directory = Pathname(request.spec.source.uri).expand_path
+ directory = File.expand_path(request.spec.source.uri)
- out << " remote: #{directory.relative_path_from @gem_deps_dir}"
+ out << " remote: #{relative_path_from directory, @gem_deps_dir}"
out << " specs:"
out << " #{request.name} (#{request.version})"
end
@@ -208,7 +217,7 @@ class Gem::RequestSet::Lockfile
skip :newline
- set = Gem::DependencyResolver::LockSet.new source
+ set = Gem::Resolver::LockSet.new source
while not @tokens.empty? and :text == peek.first do
_, name, = get :text
diff --git a/lib/rubygems/dependency_resolver.rb b/lib/rubygems/resolver.rb
index 35fbe925ad..2669cc4f24 100644
--- a/lib/rubygems/dependency_resolver.rb
+++ b/lib/rubygems/resolver.rb
@@ -12,7 +12,7 @@ require 'net/http'
# objects which indicate all the specs that should be activated to meet the
# all the requirements.
-class Gem::DependencyResolver
+class Gem::Resolver
##
# Contains all the conflicts encountered while doing resolution
@@ -38,20 +38,20 @@ class Gem::DependencyResolver
when 1 then
sets.first
else
- Gem::DependencyResolver::ComposedSet.new(*sets)
+ Gem::Resolver::ComposedSet.new(*sets)
end
end
##
- # Provide a DependencyResolver that queries only against the already
+ # Provide a Resolver that queries only against the already
# installed gems.
def self.for_current_gems needed
- new needed, Gem::DependencyResolver::CurrentSet.new
+ new needed, Gem::Resolver::CurrentSet.new
end
##
- # Create DependencyResolver object which will resolve the tree starting
+ # Create Resolver object which will resolve the tree starting
# with +needed+ Dependency objects.
#
# +set+ is an object that provides where to look for specifications to
@@ -59,7 +59,7 @@ class Gem::DependencyResolver
# rubygems.org.
def initialize needed, set = nil
- @set = set || Gem::DependencyResolver::IndexSet.new
+ @set = set || Gem::Resolver::IndexSet.new
@needed = needed
@conflicts = []
@@ -68,6 +68,15 @@ class Gem::DependencyResolver
@soft_missing = false
end
+ DEBUG_RESOLVER = !ENV['DEBUG_RESOLVER'].nil?
+
+ def explain(stage, *data)
+ if DEBUG_RESOLVER
+ d = data.map { |x| x.inspect }.join(", ")
+ STDOUT.printf "%20s %s\n", stage.to_s.upcase, d
+ end
+ end
+
##
# Creates an ActivationRequest for the given +dep+ and the last +possible+
# specification.
@@ -77,8 +86,10 @@ class Gem::DependencyResolver
def activation_request dep, possible # :nodoc:
spec = possible.pop
+ explain :activate, [spec.full_name, possible.size]
+
activation_request =
- Gem::DependencyResolver::ActivationRequest.new spec, dep, possible
+ Gem::Resolver::ActivationRequest.new spec, dep, possible
return spec, activation_request
end
@@ -86,7 +97,7 @@ class Gem::DependencyResolver
def requests s, act, reqs=nil
s.dependencies.reverse_each do |d|
next if d.type == :development and not @development
- reqs = Gem::List.new Gem::DependencyResolver::DependencyRequest.new(d, act), reqs
+ reqs.add Gem::Resolver::DependencyRequest.new(d, act)
end
@set.prefetch reqs
@@ -100,18 +111,18 @@ class Gem::DependencyResolver
def resolve
@conflicts = []
- needed = nil
+ needed = RequirementList.new
@needed.reverse_each do |n|
- request = Gem::DependencyResolver::DependencyRequest.new n, nil
+ request = Gem::Resolver::DependencyRequest.new n, nil
- needed = Gem::List.new request, needed
+ needed.add request
end
res = resolve_for needed, nil
raise Gem::DependencyResolutionError, res if
- res.kind_of? Gem::DependencyResolver::DependencyConflict
+ res.kind_of? Gem::Resolver::Conflict
res.to_a
end
@@ -128,6 +139,8 @@ class Gem::DependencyResolver
until states.empty? do
state = states.pop
+ explain :consider, state.dep, conflict.failed_dep
+
if conflict.for_spec? state.spec
state.conflicts << [state.spec, conflict]
return state
@@ -162,11 +175,11 @@ class Gem::DependencyResolver
# Otherwise, issue it on the requester's request itself.
if existing.others_possible? or existing.request.requester.nil? then
conflict =
- Gem::DependencyResolver::DependencyConflict.new dep, existing
+ Gem::Resolver::Conflict.new dep, existing
else
- depreq = existing.request.requester.request
+ depreq = dep.requester.request
conflict =
- Gem::DependencyResolver::DependencyConflict.new depreq, existing, dep
+ Gem::Resolver::Conflict.new depreq, existing, dep
end
@conflicts << conflict unless @conflicts.include? conflict
@@ -182,7 +195,7 @@ class Gem::DependencyResolver
# +spec+ is the Specification for this state.
# +possible+ is List of DependencyRequest objects that can be tried to
# find a complete set.
- # +conflicts+ is a [DependencyRequest, DependencyConflict] hit tried to
+ # +conflicts+ is a [DependencyRequest, Conflict] hit tried to
# activate the state.
#
State = Struct.new(:needed, :specs, :dep, :spec, :possibles, :conflicts) do
@@ -218,9 +231,9 @@ class Gem::DependencyResolver
# The State objects that are used to attempt the activation tree.
states = []
- while needed
- dep = needed.value
- needed = needed.tail
+ while !needed.empty?
+ dep = needed.remove
+ explain :try, [dep, dep.requester ? dep.requester.request : :toplevel]
# If there is already a spec activated for the requested name...
if specs && existing = specs.find { |s| dep.name == s.name }
@@ -228,6 +241,7 @@ class Gem::DependencyResolver
next if dep.matches_spec? existing
conflict = handle_conflict dep, existing
+ explain :conflict, conflict.explain
state = find_conflict_state conflict, states
@@ -292,7 +306,9 @@ class Gem::DependencyResolver
# We may need to try all of +possible+, so we setup state to unwind back
# to current +needed+ and +specs+ so we can try another. This is code is
# what makes conflict resolution possible.
- states << State.new(needed, specs, dep, spec, possible, [])
+ states << State.new(needed.dup, specs, dep, spec, possible, [])
+
+ explain :states, states.map { |s| s.dep }
needed = requests spec, act, needed
specs = Gem::List.prepend specs, act
@@ -341,24 +357,32 @@ class Gem::DependencyResolver
end
-require 'rubygems/dependency_resolver/activation_request'
-require 'rubygems/dependency_resolver/dependency_conflict'
-require 'rubygems/dependency_resolver/dependency_request'
-
-require 'rubygems/dependency_resolver/set'
-require 'rubygems/dependency_resolver/api_set'
-require 'rubygems/dependency_resolver/composed_set'
-require 'rubygems/dependency_resolver/best_set'
-require 'rubygems/dependency_resolver/current_set'
-require 'rubygems/dependency_resolver/index_set'
-require 'rubygems/dependency_resolver/installer_set'
-require 'rubygems/dependency_resolver/lock_set'
-require 'rubygems/dependency_resolver/vendor_set'
-
-require 'rubygems/dependency_resolver/specification'
-require 'rubygems/dependency_resolver/spec_specification'
-require 'rubygems/dependency_resolver/api_specification'
-require 'rubygems/dependency_resolver/index_specification'
-require 'rubygems/dependency_resolver/installed_specification'
-require 'rubygems/dependency_resolver/vendor_specification'
+##
+# TODO remove in RubyGems 3
+
+Gem::DependencyResolver = Gem::Resolver # :nodoc:
+
+require 'rubygems/resolver/activation_request'
+require 'rubygems/resolver/conflict'
+require 'rubygems/resolver/dependency_request'
+require 'rubygems/resolver/requirement_list'
+
+require 'rubygems/resolver/set'
+require 'rubygems/resolver/api_set'
+require 'rubygems/resolver/composed_set'
+require 'rubygems/resolver/best_set'
+require 'rubygems/resolver/current_set'
+require 'rubygems/resolver/git_set'
+require 'rubygems/resolver/index_set'
+require 'rubygems/resolver/installer_set'
+require 'rubygems/resolver/lock_set'
+require 'rubygems/resolver/vendor_set'
+
+require 'rubygems/resolver/specification'
+require 'rubygems/resolver/spec_specification'
+require 'rubygems/resolver/api_specification'
+require 'rubygems/resolver/git_specification'
+require 'rubygems/resolver/index_specification'
+require 'rubygems/resolver/installed_specification'
+require 'rubygems/resolver/vendor_specification'
diff --git a/lib/rubygems/dependency_resolver/activation_request.rb b/lib/rubygems/resolver/activation_request.rb
index c5d1e24d85..ca82ac408a 100644
--- a/lib/rubygems/dependency_resolver/activation_request.rb
+++ b/lib/rubygems/resolver/activation_request.rb
@@ -3,7 +3,7 @@
# Also contains a dependency that was used to introduce this
# activation.
-class Gem::DependencyResolver::ActivationRequest
+class Gem::Resolver::ActivationRequest
attr_reader :request
@@ -19,7 +19,7 @@ class Gem::DependencyResolver::ActivationRequest
case other
when Gem::Specification
@spec == other
- when Gem::DependencyResolver::ActivationRequest
+ when Gem::Resolver::ActivationRequest
@spec == other.spec && @request == other.request
else
false
@@ -70,7 +70,7 @@ class Gem::DependencyResolver::ActivationRequest
def installed?
case @spec
- when Gem::DependencyResolver::VendorSpecification then
+ when Gem::Resolver::VendorSpecification then
true
else
this_spec = full_spec
diff --git a/lib/rubygems/dependency_resolver/api_set.rb b/lib/rubygems/resolver/api_set.rb
index 9dd34562b1..60bf911063 100644
--- a/lib/rubygems/dependency_resolver/api_set.rb
+++ b/lib/rubygems/resolver/api_set.rb
@@ -2,7 +2,7 @@
# The global rubygems pool, available via the rubygems.org API.
# Returns instances of APISpecification.
-class Gem::DependencyResolver::APISet < Gem::DependencyResolver::Set
+class Gem::Resolver::APISet < Gem::Resolver::Set
##
# The URI for the dependency API this APISet uses.
@@ -28,7 +28,7 @@ class Gem::DependencyResolver::APISet < Gem::DependencyResolver::Set
versions(req.name).each do |ver|
if req.dependency.match? req.name, ver[:number]
- res << Gem::DependencyResolver::APISpecification.new(self, ver)
+ res << Gem::Resolver::APISpecification.new(self, ver)
end
end
diff --git a/lib/rubygems/dependency_resolver/api_specification.rb b/lib/rubygems/resolver/api_specification.rb
index 5178d7c28e..19611e17d8 100644
--- a/lib/rubygems/dependency_resolver/api_specification.rb
+++ b/lib/rubygems/resolver/api_specification.rb
@@ -4,7 +4,7 @@
# This is used to avoid loading the full Specification object when all we need
# is the name, version, and dependencies.
-class Gem::DependencyResolver::APISpecification < Gem::DependencyResolver::Specification
+class Gem::Resolver::APISpecification < Gem::Resolver::Specification
##
# Creates an APISpecification for the given +set+ from the rubygems.org
diff --git a/lib/rubygems/dependency_resolver/best_set.rb b/lib/rubygems/resolver/best_set.rb
index 987eea552e..533a0db58f 100644
--- a/lib/rubygems/dependency_resolver/best_set.rb
+++ b/lib/rubygems/resolver/best_set.rb
@@ -3,7 +3,7 @@
#
# It combines IndexSet and APISet
-class Gem::DependencyResolver::BestSet < Gem::DependencyResolver::ComposedSet
+class Gem::Resolver::BestSet < Gem::Resolver::ComposedSet
##
# Creates a BestSet for the given +sources+ or Gem::sources if none are
diff --git a/lib/rubygems/dependency_resolver/composed_set.rb b/lib/rubygems/resolver/composed_set.rb
index aeecf047b8..e4aa15e4d0 100644
--- a/lib/rubygems/dependency_resolver/composed_set.rb
+++ b/lib/rubygems/resolver/composed_set.rb
@@ -1,4 +1,4 @@
-class Gem::DependencyResolver::ComposedSet < Gem::DependencyResolver::Set
+class Gem::Resolver::ComposedSet < Gem::Resolver::Set
attr_reader :sets # :nodoc:
diff --git a/lib/rubygems/dependency_resolver/dependency_conflict.rb b/lib/rubygems/resolver/conflict.rb
index 092f000cdb..b081972658 100644
--- a/lib/rubygems/dependency_resolver/dependency_conflict.rb
+++ b/lib/rubygems/resolver/conflict.rb
@@ -2,7 +2,7 @@
# Used internally to indicate that a dependency conflicted
# with a spec that would be activated.
-class Gem::DependencyResolver::DependencyConflict
+class Gem::Resolver::Conflict
attr_reader :activated
@@ -23,6 +23,10 @@ class Gem::DependencyResolver::DependencyConflict
@failed_dep == other.failed_dep
end
+ def explain
+ "<Conflict wanted: #{@failed_dep}, had: #{activated.spec.full_name}>"
+ end
+
##
# Return the 2 dependency objects that conflicted
@@ -94,3 +98,5 @@ class Gem::DependencyResolver::DependencyConflict
end
+Gem::Resolver::DependencyConflict = Gem::Resolver::Conflict
+
diff --git a/lib/rubygems/dependency_resolver/current_set.rb b/lib/rubygems/resolver/current_set.rb
index ef15c9d7f3..4e8d34026b 100644
--- a/lib/rubygems/dependency_resolver/current_set.rb
+++ b/lib/rubygems/resolver/current_set.rb
@@ -3,7 +3,7 @@
# all the normal settings that control where to look
# for installed gems.
-class Gem::DependencyResolver::CurrentSet < Gem::DependencyResolver::Set
+class Gem::Resolver::CurrentSet < Gem::Resolver::Set
def find_all req
req.dependency.matching_specs
diff --git a/lib/rubygems/dependency_resolver/dependency_request.rb b/lib/rubygems/resolver/dependency_request.rb
index 36b77ab558..e63b443c62 100644
--- a/lib/rubygems/dependency_resolver/dependency_request.rb
+++ b/lib/rubygems/resolver/dependency_request.rb
@@ -2,7 +2,7 @@
# Used Internally. Wraps a Dependency object to also track which spec
# contained the Dependency.
-class Gem::DependencyResolver::DependencyRequest
+class Gem::Resolver::DependencyRequest
attr_reader :dependency
@@ -17,7 +17,7 @@ class Gem::DependencyResolver::DependencyRequest
case other
when Gem::Dependency
@dependency == other
- when Gem::DependencyResolver::DependencyRequest
+ when Gem::Resolver::DependencyRequest
@dependency == other.dependency && @requester == other.requester
else
false
diff --git a/lib/rubygems/resolver/git_set.rb b/lib/rubygems/resolver/git_set.rb
new file mode 100644
index 0000000000..3c38d3dca0
--- /dev/null
+++ b/lib/rubygems/resolver/git_set.rb
@@ -0,0 +1,81 @@
+##
+# A GitSet represents gems that are sourced from git repositories.
+#
+# This is used for gem dependency file support.
+#
+# Example:
+#
+# set = Gem::Resolver::GitSet.new
+# set.add_git_gem 'rake', 'git://example/rake.git', tag: 'rake-10.1.0'
+
+class Gem::Resolver::GitSet < Gem::Resolver::Set
+
+ ##
+ # Contains repositories needing submodules
+
+ attr_reader :need_submodules # :nodoc:
+
+ ##
+ # A Hash containing git gem names for keys and a Hash of repository and
+ # git commit reference as values.
+
+ attr_reader :repositories # :nodoc:
+
+ ##
+ # A hash of gem names to Gem::Resolver::GitSpecifications
+
+ attr_reader :specs # :nodoc:
+
+ def initialize # :nodoc:
+ @git = ENV['git'] || 'git'
+ @need_submodules = {}
+ @repositories = {}
+ @specs = {}
+ end
+
+ def add_git_gem name, repository, reference, submodules # :nodoc:
+ @repositories[name] = [repository, reference]
+ @need_submodules[repository] = submodules
+ end
+
+ ##
+ # Finds all git gems matching +req+
+
+ def find_all req
+ @repositories.keys.select do |name|
+ name == req.name
+ end.map do |name|
+ @specs[name] || load_spec(name)
+ end.select do |spec|
+ req.matches_spec? spec
+ end
+ end
+
+ def load_spec name
+ repository, reference = @repositories[name]
+
+ source = Gem::Source::Git.new name, repository, reference
+
+ spec = source.load_spec name
+
+ git_spec =
+ Gem::Resolver::GitSpecification.new self, spec, source
+
+ @specs[name] = git_spec
+ end
+
+ ##
+ # Prefetches specifications from the git repositories in this set.
+
+ def prefetch reqs
+ names = reqs.map { |req| req.name }
+
+ @repositories.each_key do |name|
+ next unless names.include? name
+
+ load_spec name
+ end
+ end
+
+end
+
diff --git a/lib/rubygems/resolver/git_specification.rb b/lib/rubygems/resolver/git_specification.rb
new file mode 100644
index 0000000000..ac8d4e9aeb
--- /dev/null
+++ b/lib/rubygems/resolver/git_specification.rb
@@ -0,0 +1,16 @@
+##
+# A GitSpecification represents a gem that is sourced from a git repository
+# and is being loaded through a gem dependencies file through the +git:+
+# option.
+
+class Gem::Resolver::GitSpecification < Gem::Resolver::SpecSpecification
+
+ def == other # :nodoc:
+ self.class === other and
+ @set == other.set and
+ @spec == other.spec and
+ @source == other.source
+ end
+
+end
+
diff --git a/lib/rubygems/dependency_resolver/index_set.rb b/lib/rubygems/resolver/index_set.rb
index 04d6ec816f..0ba3c78a44 100644
--- a/lib/rubygems/dependency_resolver/index_set.rb
+++ b/lib/rubygems/resolver/index_set.rb
@@ -2,7 +2,7 @@
# The global rubygems pool represented via the traditional
# source index.
-class Gem::DependencyResolver::IndexSet < Gem::DependencyResolver::Set
+class Gem::Resolver::IndexSet < Gem::Resolver::Set
def initialize source = nil # :nodoc:
@f =
@@ -38,7 +38,7 @@ class Gem::DependencyResolver::IndexSet < Gem::DependencyResolver::Set
@all[name].each do |uri, n|
if req.dependency.match? n then
- res << Gem::DependencyResolver::IndexSpecification.new(
+ res << Gem::Resolver::IndexSpecification.new(
self, n.name, n.version, uri, n.platform)
end
end
diff --git a/lib/rubygems/dependency_resolver/index_specification.rb b/lib/rubygems/resolver/index_specification.rb
index 9b4057f0c8..56fecb5753 100644
--- a/lib/rubygems/dependency_resolver/index_specification.rb
+++ b/lib/rubygems/resolver/index_specification.rb
@@ -3,7 +3,7 @@
# delay needed to download full Specification objects when only the +name+
# and +version+ are needed.
-class Gem::DependencyResolver::IndexSpecification < Gem::DependencyResolver::Specification
+class Gem::Resolver::IndexSpecification < Gem::Resolver::Specification
##
# An IndexSpecification is created from the index format described in `gem
diff --git a/lib/rubygems/dependency_resolver/installed_specification.rb b/lib/rubygems/resolver/installed_specification.rb
index 4b591661a8..647ff7499a 100644
--- a/lib/rubygems/dependency_resolver/installed_specification.rb
+++ b/lib/rubygems/resolver/installed_specification.rb
@@ -2,7 +2,7 @@
# An InstalledSpecification represents a gem that is already installed
# locally.
-class Gem::DependencyResolver::InstalledSpecification < Gem::DependencyResolver::SpecSpecification
+class Gem::Resolver::InstalledSpecification < Gem::Resolver::SpecSpecification
def == other # :nodoc:
self.class === other and
diff --git a/lib/rubygems/dependency_resolver/installer_set.rb b/lib/rubygems/resolver/installer_set.rb
index 801b60a8bb..73d9e39651 100644
--- a/lib/rubygems/dependency_resolver/installer_set.rb
+++ b/lib/rubygems/resolver/installer_set.rb
@@ -2,7 +2,7 @@
# A set of gems for installation sourced from remote sources and local .gem
# files
-class Gem::DependencyResolver::InstallerSet < Gem::DependencyResolver::Set
+class Gem::Resolver::InstallerSet < Gem::Resolver::Set
##
# List of Gem::Specification objects that must always be installed.
@@ -64,14 +64,14 @@ class Gem::DependencyResolver::InstallerSet < Gem::DependencyResolver::Set
dep.matching_specs.each do |gemspec|
next if @always_install.include? gemspec
- res << Gem::DependencyResolver::InstalledSpecification.new(self, gemspec)
+ res << Gem::Resolver::InstalledSpecification.new(self, gemspec)
end unless @ignore_installed
if consider_local? then
local_source = Gem::Source::Local.new
if spec = local_source.find_gem(name, dep.requirement) then
- res << Gem::DependencyResolver::IndexSpecification.new(
+ res << Gem::Resolver::IndexSpecification.new(
self, spec.name, spec.version, local_source, spec.platform)
end
end
@@ -81,7 +81,7 @@ class Gem::DependencyResolver::InstallerSet < Gem::DependencyResolver::Set
@all[name].each do |remote_source, n|
if dep.match? n then
- res << Gem::DependencyResolver::IndexSpecification.new(
+ res << Gem::Resolver::IndexSpecification.new(
self, n.name, n.version, remote_source, n.platform)
end
end
diff --git a/lib/rubygems/dependency_resolver/lock_set.rb b/lib/rubygems/resolver/lock_set.rb
index f95c7f0fd6..6885e70945 100644
--- a/lib/rubygems/dependency_resolver/lock_set.rb
+++ b/lib/rubygems/resolver/lock_set.rb
@@ -1,7 +1,7 @@
##
# A set of gems from a gem dependencies lockfile.
-class Gem::DependencyResolver::LockSet < Gem::DependencyResolver::Set
+class Gem::Resolver::LockSet < Gem::Resolver::Set
attr_reader :specs # :nodoc:
@@ -24,8 +24,8 @@ class Gem::DependencyResolver::LockSet < Gem::DependencyResolver::Set
version = Gem::Version.new version
spec =
- Gem::DependencyResolver::IndexSpecification.new self, name, version,
- @source, platform
+ Gem::Resolver::IndexSpecification.new self, name, version, @source,
+ platform
@specs << spec
end
diff --git a/lib/rubygems/resolver/requirement_list.rb b/lib/rubygems/resolver/requirement_list.rb
new file mode 100644
index 0000000000..8123e84fc7
--- /dev/null
+++ b/lib/rubygems/resolver/requirement_list.rb
@@ -0,0 +1,40 @@
+##
+# Used internally to hold the requirements being considered
+# while attempting to find a proper activation set.
+
+class Gem::Resolver::RequirementList
+
+ include Enumerable
+
+ def initialize
+ @list = []
+ end
+
+ def initialize_copy(other)
+ @list = @list.dup
+ end
+
+ def add(req)
+ @list.push req
+ req
+ end
+
+ ##
+ # Enumerates requirements in the list
+
+ def each # :nodoc:
+ return enum_for __method__ unless block_given?
+
+ @list.each do |requirement|
+ yield requirement
+ end
+ end
+
+ def empty?
+ @list.empty?
+ end
+
+ def remove
+ @list.shift
+ end
+end
diff --git a/lib/rubygems/dependency_resolver/set.rb b/lib/rubygems/resolver/set.rb
index 65801871ac..32c137ef6b 100644
--- a/lib/rubygems/dependency_resolver/set.rb
+++ b/lib/rubygems/resolver/set.rb
@@ -1,13 +1,12 @@
##
-# DependencyResolver sets are used to look up specifications (and their
+# Resolver sets are used to look up specifications (and their
# dependencies) used in resolution. This set is abstract.
-class Gem::DependencyResolver::Set
+class Gem::Resolver::Set
##
- # The find_all method must be implemented. It returns all
- # DependencyResolver Specification objects matching the given
- # DependencyRequest +req+.
+ # The find_all method must be implemented. It returns all Resolver
+ # Specification objects matching the given DependencyRequest +req+.
def find_all req
raise NotImplementedError
diff --git a/lib/rubygems/dependency_resolver/spec_specification.rb b/lib/rubygems/resolver/spec_specification.rb
index cca1d58b9f..0c411bdf5f 100644
--- a/lib/rubygems/dependency_resolver/spec_specification.rb
+++ b/lib/rubygems/resolver/spec_specification.rb
@@ -1,8 +1,8 @@
##
-# The DependencyResolver::SpecSpecification contains common functionality for
-# DependencyResolver specifications that are backed by a Gem::Specification.
+# The Resolver::SpecSpecification contains common functionality for
+# Resolver specifications that are backed by a Gem::Specification.
-class Gem::DependencyResolver::SpecSpecification < Gem::DependencyResolver::Specification
+class Gem::Resolver::SpecSpecification < Gem::Resolver::Specification
attr_reader :spec # :nodoc:
diff --git a/lib/rubygems/dependency_resolver/specification.rb b/lib/rubygems/resolver/specification.rb
index 6fbd241316..7dd4c2e829 100644
--- a/lib/rubygems/dependency_resolver/specification.rb
+++ b/lib/rubygems/resolver/specification.rb
@@ -1,9 +1,9 @@
##
-# A DependencyResolver::Specification contains a subset of the information
+# 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::DependencyResolver::Specification
+class Gem::Resolver::Specification
##
# The dependencies of the gem for this specification
diff --git a/lib/rubygems/dependency_resolver/vendor_set.rb b/lib/rubygems/resolver/vendor_set.rb
index 87eb6fd818..e9cbcd8303 100644
--- a/lib/rubygems/dependency_resolver/vendor_set.rb
+++ b/lib/rubygems/resolver/vendor_set.rb
@@ -6,14 +6,14 @@
#
# Example:
#
-# set = Gem::DependencyResolver::VendorSet.new
+# set = Gem::Resolver::VendorSet.new
#
# set.add_vendor_gem 'rake', 'vendor/rake'
#
# The directory vendor/rake must contain an unpacked rake gem along with a
# rake.gemspec (watching the given name).
-class Gem::DependencyResolver::VendorSet < Gem::DependencyResolver::Set
+class Gem::Resolver::VendorSet < Gem::Resolver::Set
def initialize # :nodoc:
@directories = {}
@@ -47,7 +47,7 @@ class Gem::DependencyResolver::VendorSet < Gem::DependencyResolver::Set
req.matches_spec? spec
end.map do |spec|
source = Gem::Source::Vendor.new @directories[spec]
- Gem::DependencyResolver::VendorSpecification.new self, spec, source
+ Gem::Resolver::VendorSpecification.new self, spec, source
end
end
diff --git a/lib/rubygems/dependency_resolver/vendor_specification.rb b/lib/rubygems/resolver/vendor_specification.rb
index 27b2fd6df2..24e033d084 100644
--- a/lib/rubygems/dependency_resolver/vendor_specification.rb
+++ b/lib/rubygems/resolver/vendor_specification.rb
@@ -3,7 +3,7 @@
# and is being loaded through a gem dependencies file through the +path:+
# option.
-class Gem::DependencyResolver::VendorSpecification < Gem::DependencyResolver::SpecSpecification
+class Gem::Resolver::VendorSpecification < Gem::Resolver::SpecSpecification
def == other # :nodoc:
self.class === other and
diff --git a/lib/rubygems/source.rb b/lib/rubygems/source.rb
index b39b3ae69d..a40a27594b 100644
--- a/lib/rubygems/source.rb
+++ b/lib/rubygems/source.rb
@@ -28,7 +28,9 @@ class Gem::Source
case other
when Gem::Source::Installed,
Gem::Source::Local,
- Gem::Source::SpecificFile then
+ Gem::Source::SpecificFile,
+ Gem::Source::Git,
+ Gem::Source::Vendor then
-1
when Gem::Source then
if !@uri
@@ -62,9 +64,9 @@ class Gem::Source
fetcher = Gem::RemoteFetcher.fetcher
fetcher.fetch_path bundler_api_uri, nil, true
rescue Gem::RemoteFetcher::FetchError
- Gem::DependencyResolver::IndexSet.new self
+ Gem::Resolver::IndexSet.new self
else
- Gem::DependencyResolver::APISet.new bundler_api_uri
+ Gem::Resolver::APISet.new bundler_api_uri
end
end
@@ -90,12 +92,15 @@ class Gem::Source
end
end
- def fetch_spec(name)
+ ##
+ # Fetches a specification for the given +name_tuple+.
+
+ def fetch_spec name_tuple
fetcher = Gem::RemoteFetcher.fetcher
- spec_file_name = name.spec_name
+ spec_file_name = name_tuple.spec_name
- uri = @uri + "#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}"
+ uri = api_uri + "#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}"
cache_dir = cache_dir uri
@@ -139,7 +144,7 @@ class Gem::Source
file = FILES[type]
fetcher = Gem::RemoteFetcher.fetcher
file_name = "#{file}.#{Gem.marshal_version}"
- spec_path = @uri + "#{file_name}.gz"
+ spec_path = api_uri + "#{file_name}.gz"
cache_dir = cache_dir spec_path
local_file = File.join(cache_dir, file_name)
retried = false
@@ -163,18 +168,22 @@ class Gem::Source
def download(spec, dir=Dir.pwd)
fetcher = Gem::RemoteFetcher.fetcher
- fetcher.download spec, @uri.to_s, dir
+ fetcher.download spec, api_uri.to_s, dir
end
def pretty_print q # :nodoc:
q.group 2, '[Remote:', ']' do
q.breakable
q.text @uri.to_s
+ if api = api_uri
+ g.text api
+ end
end
end
end
+require 'rubygems/source/git'
require 'rubygems/source/installed'
require 'rubygems/source/specific_file'
require 'rubygems/source/local'
diff --git a/lib/rubygems/source/git.rb b/lib/rubygems/source/git.rb
new file mode 100644
index 0000000000..8453aa5fd7
--- /dev/null
+++ b/lib/rubygems/source/git.rb
@@ -0,0 +1,189 @@
+require 'digest'
+require 'rubygems/util'
+
+##
+# A git gem for use in a gem dependencies file.
+#
+# Example:
+#
+# source =
+# Gem::Source::Git.new 'rake', 'git@example:rake.git', 'rake-10.1.0', false
+#
+# spec = source.load_spec 'rake'
+#
+# source.checkout
+
+class Gem::Source::Git < Gem::Source
+
+ ##
+ # The name of the gem created by this git gem.
+
+ attr_reader :name
+
+ ##
+ # The commit reference used for checking out this git gem.
+
+ attr_reader :reference
+
+ ##
+ # The git repository this gem is sourced from.
+
+ attr_reader :repository
+
+ ##
+ # Does this repository need submodules checked out too?
+
+ attr_reader :need_submodules
+
+ ##
+ # Creates a new git gem source for a gem with the given +name+ that will be
+ # loaded from +reference+ in +repository+. If +submodules+ is true,
+ # submodules will be checked out when the gem is installed.
+
+ def initialize name, repository, reference, submodules = false
+ super(nil)
+
+ @name = name
+ @repository = repository
+ @reference = reference
+ @need_submodules = submodules
+
+ @git = ENV['git'] || 'git'
+ end
+
+ def <=> other
+ case other
+ when Gem::Source::Git then
+ 0
+ when Gem::Source::Installed then
+ -1
+ when Gem::Source then
+ 1
+ else
+ nil
+ end
+ end
+
+ def == other # :nodoc:
+ super and
+ @name == other.name and
+ @repository == other.repository and
+ @reference == other.reference and
+ @need_submodules == other.need_submodules
+ end
+
+ ##
+ # Checks out the files for the repository into the install_dir.
+
+ def checkout # :nodoc:
+ cache
+
+ unless File.exist? install_dir then
+ system @git, 'clone', '--quiet', '--no-checkout',
+ repo_cache_dir, install_dir
+ end
+
+ Dir.chdir install_dir do
+ system @git, 'fetch', '--quiet', '--force', '--tags', install_dir
+
+ success = system @git, 'reset', '--quiet', '--hard', @reference
+
+ success &&=
+ system @git, 'submodule', 'update',
+ '--quiet', '--init', '--recursive' if @need_submodules
+
+ success
+ end
+ end
+
+ ##
+ # Creates a local cache repository for the git gem.
+
+ def cache # :nodoc:
+ if File.exist? repo_cache_dir then
+ Dir.chdir repo_cache_dir do
+ system @git, 'fetch', '--quiet', '--force', '--tags',
+ @repository, 'refs/heads/*:refs/heads/*'
+ end
+ else
+ system @git, 'clone', '--quiet', '--bare', '--no-hardlinks',
+ @repository, repo_cache_dir
+ end
+ end
+
+ ##
+ # A short reference for use in git gem directories
+
+ def dir_shortref # :nodoc:
+ rev_parse[0..11]
+ end
+
+ ##
+ # The directory where the git gem will be installed.
+
+ def install_dir # :nodoc:
+ File.join Gem.dir, 'bundler', 'gems', "#{@name}-#{dir_shortref}"
+ end
+
+ ##
+ # Loads a Gem::Specification for +name+ from this git repository.
+
+ def load_spec name
+ cache
+
+ gemspec_reference = "#{@reference}:#{name}.gemspec"
+
+ Dir.chdir repo_cache_dir do
+ source = Gem::Util.popen @git, 'show', gemspec_reference
+
+ source.force_encoding Encoding::UTF_8 if Object.const_defined? :Encoding
+ source.untaint
+
+ begin
+ spec = eval source, binding, gemspec_reference
+
+ return spec if Gem::Specification === spec
+
+ warn "git gem specification for #{@repository} #{gemspec_reference} is not a Gem::Specification (#{spec.class} instead)."
+ rescue SignalException, SystemExit
+ raise
+ rescue SyntaxError, Exception
+ warn "invalid git gem specification for #{@repository} #{gemspec_reference}"
+ end
+ end
+ end
+
+ ##
+ # The directory where the git gem's repository will be cached.
+
+ def repo_cache_dir # :nodoc:
+ File.join Gem.dir, 'cache', 'bundler', 'git', "#{@name}-#{uri_hash}"
+ end
+
+ ##
+ # Converts the git reference for the repository into a commit hash.
+
+ def rev_parse # :nodoc:
+ # HACK no safe equivalent of ` exists on 1.8.7
+ Dir.chdir repo_cache_dir do
+ Gem::Util.popen(@git, 'rev-parse', @reference).strip
+ end
+ end
+
+ ##
+ # A hash for the git gem based on the git repository URI.
+
+ def uri_hash # :nodoc:
+ normalized =
+ if @repository =~ %r%^\w+://(\w+@)?% then
+ uri = URI(@repository).normalize.to_s.sub %r%/$%,''
+ uri.sub(/\A(\w+)/) { $1.downcase }
+ else
+ @repository
+ end
+
+ Digest::SHA1.hexdigest normalized
+ end
+
+end
+
diff --git a/lib/rubygems/source/installed.rb b/lib/rubygems/source/installed.rb
index 8e3a3560bf..2661dd6844 100644
--- a/lib/rubygems/source/installed.rb
+++ b/lib/rubygems/source/installed.rb
@@ -1,6 +1,9 @@
+##
+# Represents an installed gem. This is used for dependency resolution.
+
class Gem::Source::Installed < Gem::Source
- def initialize
+ def initialize # :nodoc:
@uri = nil
end
@@ -9,6 +12,8 @@ class Gem::Source::Installed < Gem::Source
def <=> other
case other
+ when Gem::Source::Vendor then
+ -1
when Gem::Source::Installed then
0
when Gem::Source then
diff --git a/lib/rubygems/source/local.rb b/lib/rubygems/source/local.rb
index 16a028b2f3..3aae20c8ed 100644
--- a/lib/rubygems/source/local.rb
+++ b/lib/rubygems/source/local.rb
@@ -1,5 +1,10 @@
+##
+# The local source finds gems in the current directory for fulfilling
+# dependencies.
+
class Gem::Source::Local < Gem::Source
- def initialize
+
+ def initialize # :nodoc:
@specs = nil
@api_uri = nil
@uri = nil
@@ -26,7 +31,7 @@ class Gem::Source::Local < Gem::Source
"#<%s specs: %p>" % [self.class, keys]
end
- def load_specs(type)
+ def load_specs type # :nodoc:
names = []
@specs = {}
@@ -68,8 +73,8 @@ class Gem::Source::Local < Gem::Source
names
end
- def find_gem(gem_name, version=Gem::Requirement.default,
- prerelease=false)
+ def find_gem gem_name, version = Gem::Requirement.default, # :nodoc:
+ prerelease = false
load_specs :complete
found = []
@@ -91,7 +96,7 @@ class Gem::Source::Local < Gem::Source
found.max_by { |s| s.version }
end
- def fetch_spec(name)
+ def fetch_spec name # :nodoc:
load_specs :complete
if data = @specs[name]
@@ -101,7 +106,7 @@ class Gem::Source::Local < Gem::Source
end
end
- def download(spec, cache_dir=nil)
+ def download spec, cache_dir = nil # :nodoc:
load_specs :complete
@specs.each do |name, data|
diff --git a/lib/rubygems/source/specific_file.rb b/lib/rubygems/source/specific_file.rb
index 8d328b38c2..a7b6c53542 100644
--- a/lib/rubygems/source/specific_file.rb
+++ b/lib/rubygems/source/specific_file.rb
@@ -1,4 +1,12 @@
+##
+# A source representing a single .gem file. This is used for installation of
+# local gems.
+
class Gem::Source::SpecificFile < Gem::Source
+
+ ##
+ # Creates a new SpecificFile for the gem in +file+
+
def initialize(file)
@uri = nil
@path = ::File.expand_path(file)
@@ -8,19 +16,22 @@ class Gem::Source::SpecificFile < Gem::Source
@name = @spec.name_tuple
end
+ ##
+ # The Gem::Specification extracted from this .gem.
+
attr_reader :spec
- def load_specs(*a)
+ def load_specs *a # :nodoc:
[@name]
end
- def fetch_spec(name)
+ def fetch_spec name # :nodoc:
return @spec if name == @name
raise Gem::Exception, "Unable to find '#{name}'"
@spec
end
- def download(spec, dir=nil)
+ def download spec, dir = nil # :nodoc:
return @path if spec == @spec
raise Gem::Exception, "Unable to download '#{spec.full_name}'"
end
diff --git a/lib/rubygems/source/vendor.rb b/lib/rubygems/source/vendor.rb
index f2cf540c8d..244c4201d8 100644
--- a/lib/rubygems/source/vendor.rb
+++ b/lib/rubygems/source/vendor.rb
@@ -3,8 +3,22 @@
class Gem::Source::Vendor < Gem::Source::Installed
- def initialize uri
- @uri = uri
+ ##
+ # Creates a new Vendor source for a gem that was unpacked at +path+.
+
+ def initialize path
+ @uri = path
+ end
+
+ def <=> other
+ case other
+ when Gem::Source::Vendor then
+ 0
+ when Gem::Source then
+ 1
+ else
+ nil
+ end
end
end
diff --git a/lib/rubygems/source_list.rb b/lib/rubygems/source_list.rb
index e6da50c2e5..e01f11cc1e 100644
--- a/lib/rubygems/source_list.rb
+++ b/lib/rubygems/source_list.rb
@@ -1,5 +1,18 @@
require 'rubygems/source'
+##
+# The SourceList represents the sources rubygems has been configured to use.
+# A source may be created from an array of sources:
+#
+# Gem::SourceList.from %w[https://rubygems.example https://internal.example]
+#
+# Or by adding them:
+#
+# sources = Gem::SourceList.new
+# sources.add 'https://rubygems.example'
+#
+# The most common way to get a SourceList is Gem.sources.
+
class Gem::SourceList
include Enumerable
@@ -91,7 +104,7 @@ class Gem::SourceList
@sources.empty?
end
- def ==(other)
+ def == other # :nodoc:
to_a == other
end
diff --git a/lib/rubygems/spec_fetcher.rb b/lib/rubygems/spec_fetcher.rb
index 22fa68db69..12b8fb27d8 100644
--- a/lib/rubygems/spec_fetcher.rb
+++ b/lib/rubygems/spec_fetcher.rb
@@ -34,6 +34,10 @@ class Gem::SpecFetcher
@fetcher = nil
+ ##
+ # Default fetcher instance. Use this instead of ::new to reduce object
+ # allocation.
+
def self.fetcher
@fetcher ||= new
end
@@ -43,8 +47,8 @@ class Gem::SpecFetcher
end
##
- # Creates a new SpecFetcher. Ordinarily you want to use
- # Gem::SpecFetcher::fetcher which uses the Gem.sources.
+ # Creates a new SpecFetcher. Ordinarily you want to use the default fetcher
+ # from Gem::SpecFetcher::fetcher which uses the Gem.sources.
#
# If you need to retrieve specifications from a different +source+, you can
# send it as an argument.
@@ -84,7 +88,11 @@ class Gem::SpecFetcher
rejected_specs = {}
if dependency.prerelease?
- type = :complete
+ if dependency.specific?
+ type = :complete
+ else
+ type = :abs_latest
+ end
elsif dependency.latest_version?
type = :latest
else
@@ -224,6 +232,12 @@ class Gem::SpecFetcher
tuples_for(source, :released)
names.sort
+ when :abs_latest
+ names =
+ tuples_for(source, :prerelease, true) +
+ tuples_for(source, :latest)
+
+ names.sort
when :prerelease
tuples_for(source, :prerelease)
else
@@ -239,7 +253,11 @@ class Gem::SpecFetcher
[list, errors]
end
- def tuples_for(source, type, gracefully_ignore=false)
+ ##
+ # Retrieves NameTuples from +source+ of the given +type+ (:prerelease,
+ # etc.). If +gracefully_ignore+ is true, errors are ignored.
+
+ def tuples_for(source, type, gracefully_ignore=false) # :nodoc:
cache = @caches[type]
tuples =
diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb
index 308aa6f011..b95e2c0699 100644
--- a/lib/rubygems/specification.rb
+++ b/lib/rubygems/specification.rb
@@ -12,6 +12,7 @@ require 'rubygems/platform'
require 'rubygems/deprecate'
require 'rubygems/basic_specification'
require 'rubygems/stub_specification'
+require 'rubygems/util/stringio'
# :stopdoc:
# date.rb can't be loaded for `make install` due to miniruby
@@ -2165,7 +2166,7 @@ class Gem::Specification < Gem::BasicSpecification
end
##
- # Used by Gem::DependencyResolver to order Gem::Specification objects
+ # Used by Gem::Resolver to order Gem::Specification objects
def source # :nodoc:
self
@@ -2363,7 +2364,7 @@ class Gem::Specification < Gem::BasicSpecification
builder << self
ast = builder.tree
- io = StringIO.new
+ io = Gem::StringSink.new
io.set_encoding Encoding::UTF_8 if Object.const_defined? :Encoding
Psych::Visitors::Emitter.new(io).accept(ast)
diff --git a/lib/rubygems/test_case.rb b/lib/rubygems/test_case.rb
index b04cbfc1f3..d1b471f619 100644
--- a/lib/rubygems/test_case.rb
+++ b/lib/rubygems/test_case.rb
@@ -233,6 +233,8 @@ class Gem::TestCase < MiniTest::Unit::TestCase
ruby
end
+ @git = ENV['GIT'] || 'git'
+
Gem.ensure_gem_subdirectories @gemhome
@orig_LOAD_PATH = $LOAD_PATH.dup
@@ -373,6 +375,64 @@ class Gem::TestCase < MiniTest::Unit::TestCase
end
##
+ # A git_gem is used with a gem dependencies file. The gem created here
+ # has no files, just a gem specification for the given +name+ and +version+.
+ #
+ # Yields the +specification+ to the block, if given
+
+ def git_gem name = 'a', version = 1
+ have_git?
+
+ directory = File.join 'git', name
+ directory = File.expand_path directory
+
+ git_spec = Gem::Specification.new name, version do |specification|
+ yield specification if block_given?
+ end
+
+ FileUtils.mkdir_p directory
+
+ gemspec = "#{name}.gemspec"
+
+ open File.join(directory, gemspec), 'w' do |io|
+ io.write git_spec.to_ruby
+ end
+
+ head = nil
+
+ Dir.chdir directory do
+ unless File.exist? '.git' then
+ system @git, 'init', '--quiet'
+ system @git, 'config', 'user.name', 'RubyGems Tests'
+ system @git, 'config', 'user.email', 'rubygems@example'
+ end
+
+ system @git, 'add', gemspec
+ system @git, 'commit', '-a', '-m', 'a non-empty commit message', '--quiet'
+ head = Gem::Util.popen('git', 'rev-parse', 'master').strip
+ end
+
+ return name, git_spec.version, directory, head
+ end
+
+ ##
+ # Skips this test unless you have a git executable
+
+ def have_git?
+ return if in_path? @git
+
+ skip 'cannot find git executable, use GIT environment variable to set'
+ end
+
+ def in_path? executable # :nodoc:
+ return true if %r%\A([A-Z]:|/)% =~ executable and File.exist? executable
+
+ ENV['PATH'].split(File::PATH_SEPARATOR).any? do |directory|
+ File.exist? File.join directory, executable
+ end
+ end
+
+ ##
# Builds and installs the Gem::Specification +spec+
def install_gem spec, options = {}
@@ -1082,21 +1142,21 @@ Also, a list:
end
##
- # Constructs a Gem::DependencyResolver::DependencyRequest from a
+ # Constructs a Gem::Resolver::DependencyRequest from a
# Gem::Dependency +dep+, a +from_name+ and +from_version+ requesting the
# dependency and a +parent+ DependencyRequest
def dependency_request dep, from_name, from_version, parent = nil
remote = Gem::Source.new @uri
- parent ||= Gem::DependencyResolver::DependencyRequest.new \
+ parent ||= Gem::Resolver::DependencyRequest.new \
dep, nil
- spec = Gem::DependencyResolver::IndexSpecification.new \
+ spec = Gem::Resolver::IndexSpecification.new \
nil, from_name, from_version, remote, Gem::Platform::RUBY
- activation = Gem::DependencyResolver::ActivationRequest.new spec, parent
+ activation = Gem::Resolver::ActivationRequest.new spec, parent
- Gem::DependencyResolver::DependencyRequest.new dep, activation
+ Gem::Resolver::DependencyRequest.new dep, activation
end
##
diff --git a/lib/rubygems/util.rb b/lib/rubygems/util.rb
new file mode 100644
index 0000000000..e862458d21
--- /dev/null
+++ b/lib/rubygems/util.rb
@@ -0,0 +1,65 @@
+module Gem::Util
+ ##
+ # Zlib::GzipReader wrapper that unzips +data+.
+
+ def self.gunzip(data)
+ require 'zlib'
+ require 'rubygems/util/stringio'
+ data = Gem::StringSource.new data
+
+ unzipped = Zlib::GzipReader.new(data).read
+ unzipped.force_encoding Encoding::BINARY if Object.const_defined? :Encoding
+ unzipped
+ end
+
+ ##
+ # Zlib::GzipWriter wrapper that zips +data+.
+
+ def self.gzip(data)
+ require 'zlib'
+ require 'rubygems/util/stringio'
+ zipped = Gem::StringSink.new
+ zipped.set_encoding Encoding::BINARY if Object.const_defined? :Encoding
+
+ Zlib::GzipWriter.wrap zipped do |io| io.write data end
+
+ zipped.string
+ end
+
+ ##
+ # A Zlib::Inflate#inflate wrapper
+
+ def self.inflate(data)
+ require 'zlib'
+ Zlib::Inflate.inflate data
+ end
+
+ ##
+ # This calls IO.popen where it accepts an array for a +command+ (Ruby 1.9+)
+ # and implements an IO.popen-like behavior where it does not accept an array
+ # for a command.
+
+ def self.popen *command
+ begin
+ r, = IO.popen command
+ rescue TypeError # ruby 1.8 only supports string command
+ r, w = IO.pipe
+
+ pid = fork do
+ STDIN.close
+ STDOUT.reopen w
+
+ exec(*command)
+ end
+
+ w.close
+
+ Process.wait pid
+
+ r
+ end
+
+ r.read
+ end
+
+end
diff --git a/lib/rubygems/util/stringio.rb b/lib/rubygems/util/stringio.rb
new file mode 100644
index 0000000000..2ea69617bc
--- /dev/null
+++ b/lib/rubygems/util/stringio.rb
@@ -0,0 +1,34 @@
+class Gem::StringSink
+ def initialize
+ @string = ""
+ end
+
+ attr_reader :string
+
+ def write(s)
+ @string += s
+ s.size
+ end
+
+ def set_encoding(enc)
+ @string.force_encoding enc
+ end
+end
+
+class Gem::StringSource
+ def initialize(str)
+ @string = str.dup
+ end
+
+ def read(count=nil)
+ if count
+ @string.slice!(0,count)
+ else
+ s = @string
+ @string = ""
+ s
+ end
+ end
+
+ alias_method :readpartial, :read
+end