summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authordrbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2013-11-10 17:51:40 +0000
committerdrbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2013-11-10 17:51:40 +0000
commit4f6779bac7b4e294bc473782d60cbd071f0d0f8d (patch)
treed37b54da20f8c0adf2d98e810aacc8259b0602ff /lib
parent31d355aaa9436e2b24efd5e6501cabd876267c46 (diff)
* lib/rubygems: Update to RubyGems master 4bdc4f2. Important changes
in this commit: RubyGems now chooses the test server port reliably. Patch by akr. Partial implementation of bundler's Gemfile format. Refactorings to improve the new resolver. Fixes bugs in the resolver. * test/rubygems: Tests for the above. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@43643 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib')
-rw-r--r--lib/rubygems.rb2
-rw-r--r--lib/rubygems/basic_specification.rb8
-rw-r--r--lib/rubygems/commands/install_command.rb19
-rw-r--r--lib/rubygems/commands/update_command.rb2
-rw-r--r--lib/rubygems/dependency_installer.rb8
-rw-r--r--lib/rubygems/dependency_resolver.rb135
-rw-r--r--lib/rubygems/dependency_resolver/activation_request.rb49
-rw-r--r--lib/rubygems/dependency_resolver/api_set.rb18
-rw-r--r--lib/rubygems/dependency_resolver/api_specification.rb27
-rw-r--r--lib/rubygems/dependency_resolver/best_set.rb21
-rw-r--r--lib/rubygems/dependency_resolver/composed_set.rb4
-rw-r--r--lib/rubygems/dependency_resolver/current_set.rb5
-rw-r--r--lib/rubygems/dependency_resolver/dependency_conflict.rb11
-rw-r--r--lib/rubygems/dependency_resolver/dependency_request.rb20
-rw-r--r--lib/rubygems/dependency_resolver/index_set.rb34
-rw-r--r--lib/rubygems/dependency_resolver/index_specification.rb38
-rw-r--r--lib/rubygems/dependency_resolver/installed_specification.rb40
-rw-r--r--lib/rubygems/dependency_resolver/installer_set.rb22
-rw-r--r--lib/rubygems/dependency_resolver/lock_set.rb60
-rw-r--r--lib/rubygems/dependency_resolver/set.rb28
-rw-r--r--lib/rubygems/dependency_resolver/spec_specification.rb58
-rw-r--r--lib/rubygems/dependency_resolver/specification.rb60
-rw-r--r--lib/rubygems/dependency_resolver/vendor_set.rb23
-rw-r--r--lib/rubygems/dependency_resolver/vendor_specification.rb42
-rw-r--r--lib/rubygems/errors.rb2
-rw-r--r--lib/rubygems/exceptions.rb17
-rw-r--r--lib/rubygems/platform.rb8
-rw-r--r--lib/rubygems/remote_fetcher.rb1
-rw-r--r--lib/rubygems/request_set.rb65
-rw-r--r--lib/rubygems/request_set/gem_dependency_api.rb279
-rw-r--r--lib/rubygems/request_set/lockfile.rb347
-rw-r--r--lib/rubygems/requirement.rb50
-rw-r--r--lib/rubygems/server.rb18
-rw-r--r--lib/rubygems/source.rb18
-rw-r--r--lib/rubygems/source/vendor.rb5
-rw-r--r--lib/rubygems/source_list.rb71
-rw-r--r--lib/rubygems/spec_fetcher.rb18
-rw-r--r--lib/rubygems/specification.rb94
-rw-r--r--lib/rubygems/ssl_certs/DigiCertHighAssuranceEVRootCA.pem23
-rw-r--r--lib/rubygems/test_case.rb95
-rw-r--r--lib/rubygems/version.rb2
41 files changed, 1537 insertions, 310 deletions
diff --git a/lib/rubygems.rb b/lib/rubygems.rb
index 1c84356bd7..1c8acda74d 100644
--- a/lib/rubygems.rb
+++ b/lib/rubygems.rb
@@ -8,7 +8,7 @@
require 'rbconfig'
module Gem
- VERSION = '2.2.0'
+ VERSION = '2.2.0.preview.2'
end
# Must be first since it unloads the prelude from 1.9.2
diff --git a/lib/rubygems/basic_specification.rb b/lib/rubygems/basic_specification.rb
index 7d0469000b..a29ed0aa6d 100644
--- a/lib/rubygems/basic_specification.rb
+++ b/lib/rubygems/basic_specification.rb
@@ -196,5 +196,13 @@ class Gem::BasicSpecification
raise NotImplementedError
end
+ ##
+ # Whether this specification is stubbed - i.e. we have information
+ # about the gem from a stub line, without having to evaluate the
+ # entire gemspec file.
+ def stubbed?
+ raise NotImplementedError
+ end
+
end
diff --git a/lib/rubygems/commands/install_command.rb b/lib/rubygems/commands/install_command.rb
index 1f3210ff5d..ad90b37fdc 100644
--- a/lib/rubygems/commands/install_command.rb
+++ b/lib/rubygems/commands/install_command.rb
@@ -22,6 +22,7 @@ class Gem::Commands::InstallCommand < Gem::Command
defaults = Gem::DependencyInstaller::DEFAULT_OPTIONS.merge({
:format_executable => false,
:version => Gem::Requirement.default,
+ :without_groups => [],
})
super 'install', 'Install a gem into the local repository', defaults
@@ -42,6 +43,13 @@ class Gem::Commands::InstallCommand < Gem::Command
o[:gemdeps] = v
end
+ add_option(:"Install/Update", '--without GROUPS', Array,
+ 'Omit the named groups (comma separated)',
+ 'when installing from a gem dependencies',
+ 'file') do |v,o|
+ o[:without_groups].concat v.map { |without| without.intern }
+ end
+
add_option(:"Install/Update", '--default',
'Add the gem\'s full specification to',
'specifications/default and extract only its bin') do |v,o|
@@ -133,8 +141,8 @@ to write the specification by hand. For example:
end
def execute
- if gf = options[:gemdeps] then
- install_from_gemdeps gf
+ if options.include? :gemdeps then
+ install_from_gemdeps
return # not reached
end
@@ -154,14 +162,11 @@ to write the specification by hand. For example:
terminate_interaction exit_code
end
- def install_from_gemdeps gf # :nodoc:
+ def install_from_gemdeps # :nodoc:
require 'rubygems/request_set'
rs = Gem::RequestSet.new
- rs.load_gemdeps gf
-
- rs.resolve
- specs = rs.install options do |req, inst|
+ specs = rs.install_from_gemdeps options do |req, inst|
s = req.full_spec
if inst
diff --git a/lib/rubygems/commands/update_command.rb b/lib/rubygems/commands/update_command.rb
index c3532841c3..b4ee59b3bb 100644
--- a/lib/rubygems/commands/update_command.rb
+++ b/lib/rubygems/commands/update_command.rb
@@ -112,7 +112,7 @@ command to remove old versions.
spec_tuples, errors = fetcher.search_for_dependency dependency
- error = errors.find { |errors| errors.respond_to? :exception }
+ error = errors.find { |e| e.respond_to? :exception }
raise error if error
diff --git a/lib/rubygems/dependency_installer.rb b/lib/rubygems/dependency_installer.rb
index c6985b27c0..22ff6f5cb7 100644
--- a/lib/rubygems/dependency_installer.rb
+++ b/lib/rubygems/dependency_installer.rb
@@ -250,6 +250,14 @@ class Gem::DependencyInstaller
if gem_name =~ /\.gem$/ and File.file? gem_name then
src = Gem::Source::SpecificFile.new(gem_name)
set.add src.spec, src
+ elsif gem_name =~ /\.gem$/ then
+ Dir[gem_name].each do |name|
+ begin
+ src = Gem::Source::SpecificFile.new name
+ set.add src.spec, src
+ rescue Gem::Package::FormatError
+ end
+ end
else
local = Gem::Source::Local.new
diff --git a/lib/rubygems/dependency_resolver.rb b/lib/rubygems/dependency_resolver.rb
index 10cecf7972..35fbe925ad 100644
--- a/lib/rubygems/dependency_resolver.rb
+++ b/lib/rubygems/dependency_resolver.rb
@@ -30,7 +30,16 @@ class Gem::DependencyResolver
attr_accessor :soft_missing
def self.compose_sets *sets
- Gem::DependencyResolver::ComposedSet.new(*sets)
+ sets.compact!
+
+ case sets.length
+ when 0 then
+ raise ArgumentError, 'one set in the composition must be non-nil'
+ when 1 then
+ sets.first
+ else
+ Gem::DependencyResolver::ComposedSet.new(*sets)
+ end
end
##
@@ -53,12 +62,27 @@ class Gem::DependencyResolver
@set = set || Gem::DependencyResolver::IndexSet.new
@needed = needed
- @conflicts = nil
+ @conflicts = []
@development = false
@missing = []
@soft_missing = false
end
+ ##
+ # Creates an ActivationRequest for the given +dep+ and the last +possible+
+ # specification.
+ #
+ # Returns the Specification and the ActivationRequest
+
+ def activation_request dep, possible # :nodoc:
+ spec = possible.pop
+
+ activation_request =
+ Gem::DependencyResolver::ActivationRequest.new spec, dep, possible
+
+ return spec, activation_request
+ end
+
def requests s, act, reqs=nil
s.dependencies.reverse_each do |d|
next if d.type == :development and not @development
@@ -95,27 +119,38 @@ class Gem::DependencyResolver
##
# Finds the State in +states+ that matches the +conflict+ so that we can try
# other possible sets.
+ #
+ # If no good candidate is found, the first state is tried.
def find_conflict_state conflict, states # :nodoc:
+ rejected = []
+
until states.empty? do
- if conflict.for_spec? states.last.spec
- state = states.last
+ state = states.pop
+
+ if conflict.for_spec? state.spec
state.conflicts << [state.spec, conflict]
return state
- else
- states.pop
end
+
+ rejected << state
end
- nil
+ return rejected.shift
+ ensure
+ rejected = rejected.concat states
+ states.replace rejected
end
##
- # Extracts the specifications that may be able to fulfill +dependency+
+ # Extracts the specifications that may be able to fulfill +dependency+ and
+ # returns those that match the local platform and all those that match.
def find_possible dependency # :nodoc:
- possible = @set.find_all dependency
- select_local_platforms possible
+ all = @set.find_all dependency
+ matching_platform = select_local_platforms all
+
+ return matching_platform, all
end
def handle_conflict(dep, existing)
@@ -134,7 +169,7 @@ class Gem::DependencyResolver
Gem::DependencyResolver::DependencyConflict.new depreq, existing, dep
end
- @conflicts << conflict
+ @conflicts << conflict unless @conflicts.include? conflict
return conflict
end
@@ -150,7 +185,29 @@ class Gem::DependencyResolver
# +conflicts+ is a [DependencyRequest, DependencyConflict] hit tried to
# activate the state.
#
- State = Struct.new(:needed, :specs, :dep, :spec, :possibles, :conflicts)
+ State = Struct.new(:needed, :specs, :dep, :spec, :possibles, :conflicts) do
+ def summary # :nodoc:
+ nd = needed.map { |s| s.to_s }.sort if nd
+
+ if specs then
+ ss = specs.map { |s| s.full_name }.sort
+ ss.unshift ss.length
+ end
+
+ d = dep.to_s
+ d << " from #{dep.requester.full_name}" if dep.requester
+
+ ps = possibles.map { |p| p.full_name }.sort
+ ps.unshift ps.length
+
+ cs = conflicts.map do |(s, c)|
+ [s.full_name, c.conflicting_dependencies.map { |cd| cd.to_s }]
+ end
+
+ { :needed => nd, :specs => ss, :dep => d, :spec => spec.full_name,
+ :possibles => ps, :conflicts => cs }
+ end
+ end
##
# The meat of the algorithm. Given +needed+ DependencyRequest objects and
@@ -178,20 +235,22 @@ class Gem::DependencyResolver
needed, specs = resolve_for_conflict needed, specs, state
+ states << state unless state.possibles.empty?
+
next
end
- possible = find_possible dep
+ matching, all = find_possible dep
- case possible.size
+ case matching.size
when 0
- resolve_for_zero dep
+ resolve_for_zero dep, all
when 1
needed, specs =
- resolve_for_single needed, specs, dep, possible
+ resolve_for_single needed, specs, dep, matching
else
needed, specs =
- resolve_for_multiple needed, specs, states, dep, possible
+ resolve_for_multiple needed, specs, states, dep, matching
end
end
@@ -208,10 +267,8 @@ class Gem::DependencyResolver
raise Gem::ImpossibleDependenciesError.new state.dep, state.conflicts if
state.possibles.empty?
- spec = state.possibles.pop
-
# Retry resolution with this spec and add it's dependencies
- act = Gem::DependencyResolver::ActivationRequest.new spec, state.dep
+ spec, act = activation_request state.dep, state.possibles
needed = requests spec, act, state.needed
specs = Gem::List.prepend state.specs, act
@@ -230,19 +287,11 @@ class Gem::DependencyResolver
[s.source, s.version, s.platform == Gem::Platform::RUBY ? -1 : 1]
end
- # To figure out which to pick, we keep resolving given each one being
- # activated and if there isn't a conflict, we know we've found a full set.
- #
- # We use an until loop rather than reverse_each to keep the stack short
- # since we're using a recursive algorithm.
- spec = possible.pop
+ spec, act = activation_request dep, possible
# 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.
-
- act = Gem::DependencyResolver::ActivationRequest.new spec, dep
-
states << State.new(needed, specs, dep, spec, possible, [])
needed = requests spec, act, needed
@@ -256,8 +305,7 @@ class Gem::DependencyResolver
# dependencies by adding them to +needed+.
def resolve_for_single needed, specs, dep, possible # :nodoc:
- spec = possible.first
- act = Gem::DependencyResolver::ActivationRequest.new spec, dep, false
+ spec, act = activation_request dep, possible
specs = Gem::List.prepend specs, act
@@ -274,11 +322,11 @@ class Gem::DependencyResolver
##
# When there are no possible specifications for +dep+ our work is done.
- def resolve_for_zero dep # :nodoc:
+ def resolve_for_zero dep, platform_mismatch # :nodoc:
@missing << dep
unless @soft_missing
- raise Gem::UnsatisfiableDependencyError, dep
+ raise Gem::UnsatisfiableDependencyError.new(dep, platform_mismatch)
end
end
@@ -287,23 +335,30 @@ class Gem::DependencyResolver
def select_local_platforms specs # :nodoc:
specs.select do |spec|
- Gem::Platform.match spec.platform
+ Gem::Platform.installable? spec
end
end
end
-require 'rubygems/dependency_resolver/api_set'
-require 'rubygems/dependency_resolver/api_specification'
require 'rubygems/dependency_resolver/activation_request'
-require 'rubygems/dependency_resolver/composed_set'
-require 'rubygems/dependency_resolver/current_set'
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/index_specification'
-require 'rubygems/dependency_resolver/installed_specification'
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'
diff --git a/lib/rubygems/dependency_resolver/activation_request.rb b/lib/rubygems/dependency_resolver/activation_request.rb
index 25af6378ac..c5d1e24d85 100644
--- a/lib/rubygems/dependency_resolver/activation_request.rb
+++ b/lib/rubygems/dependency_resolver/activation_request.rb
@@ -47,11 +47,21 @@ class Gem::DependencyResolver::ActivationRequest
end
def inspect # :nodoc:
- others_possible = nil
- others_possible = ' (others possible)' if @others_possible
+ others =
+ case @others_possible
+ when true then # TODO remove at RubyGems 3
+ ' (others possible)'
+ when false then # TODO remove at RubyGems 3
+ nil
+ else
+ unless @others_possible.empty? then
+ others = @others_possible.map { |s| s.full_name }
+ " (others possible: #{others.join ', '})"
+ end
+ end
'#<%s for %p from %s%s>' % [
- self.class, @spec, @request, others_possible
+ self.class, @spec, @request, others
]
end
@@ -59,10 +69,15 @@ class Gem::DependencyResolver::ActivationRequest
# Indicates if the requested gem has already been installed.
def installed?
- this_spec = full_spec
+ case @spec
+ when Gem::DependencyResolver::VendorSpecification then
+ true
+ else
+ this_spec = full_spec
- Gem::Specification.any? do |s|
- s == this_spec
+ Gem::Specification.any? do |s|
+ s == this_spec
+ end
end
end
@@ -75,7 +90,12 @@ class Gem::DependencyResolver::ActivationRequest
# requests for the same Dependency request.
def others_possible?
- @others_possible
+ case @others_possible
+ when true, false then
+ @others_possible
+ else
+ not @others_possible.empty?
+ end
end
##
@@ -95,9 +115,18 @@ class Gem::DependencyResolver::ActivationRequest
q.text ' for '
q.pp @request
-
- q.breakable
- q.text ' (other possible)' if @others_possible
+ case @others_possible
+ when false then
+ when true then
+ q.breakable
+ q.text 'others possible'
+ else
+ unless @others_possible.empty? then
+ q.breakable
+ q.text 'others '
+ q.pp @others_possible.map { |s| s.full_name }
+ end
+ end
end
end
diff --git a/lib/rubygems/dependency_resolver/api_set.rb b/lib/rubygems/dependency_resolver/api_set.rb
index 469c005a09..9dd34562b1 100644
--- a/lib/rubygems/dependency_resolver/api_set.rb
+++ b/lib/rubygems/dependency_resolver/api_set.rb
@@ -2,11 +2,21 @@
# The global rubygems pool, available via the rubygems.org API.
# Returns instances of APISpecification.
-class Gem::DependencyResolver::APISet
+class Gem::DependencyResolver::APISet < Gem::DependencyResolver::Set
- def initialize
+ ##
+ # The URI for the dependency API this APISet uses.
+
+ attr_reader :dep_uri # :nodoc:
+
+ ##
+ # Creates a new APISet that will retrieve gems from +uri+ using the RubyGems
+ # API described at http://guides.rubygems.org/rubygems-org-api
+
+ def initialize uri = 'https://rubygems.org/api/v1/dependencies'
+ uri = URI uri unless URI === uri # for ruby 1.8
@data = Hash.new { |h,k| h[k] = [] }
- @dep_uri = URI 'https://rubygems.org/api/v1/dependencies'
+ @dep_uri = uri
end
##
@@ -46,7 +56,7 @@ class Gem::DependencyResolver::APISet
##
# Return data for all versions of the gem +name+.
- def versions name
+ def versions name # :nodoc:
if @data.key?(name)
return @data[name]
end
diff --git a/lib/rubygems/dependency_resolver/api_specification.rb b/lib/rubygems/dependency_resolver/api_specification.rb
index ae688780dd..5178d7c28e 100644
--- a/lib/rubygems/dependency_resolver/api_specification.rb
+++ b/lib/rubygems/dependency_resolver/api_specification.rb
@@ -1,18 +1,21 @@
##
-# Represents a specification retrieved via the rubygems.org
-# API. This is used to avoid having to load the full
-# Specification object when all we need is the name, version,
-# and dependencies.
+# 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::DependencyResolver::APISpecification
+class Gem::DependencyResolver::APISpecification < Gem::DependencyResolver::Specification
- attr_reader :dependencies
- attr_reader :name
- attr_reader :platform
- attr_reader :set # :nodoc:
- attr_reader :version
+ ##
+ # 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]
@@ -31,9 +34,5 @@ class Gem::DependencyResolver::APISpecification
@dependencies == other.dependencies
end
- def full_name
- "#{@name}-#{@version}"
- end
-
end
diff --git a/lib/rubygems/dependency_resolver/best_set.rb b/lib/rubygems/dependency_resolver/best_set.rb
new file mode 100644
index 0000000000..987eea552e
--- /dev/null
+++ b/lib/rubygems/dependency_resolver/best_set.rb
@@ -0,0 +1,21 @@
+##
+# The BestSet chooses the best available method to query a remote index.
+#
+# It combines IndexSet and APISet
+
+class Gem::DependencyResolver::BestSet < Gem::DependencyResolver::ComposedSet
+
+ ##
+ # Creates a BestSet for the given +sources+ or Gem::sources if none are
+ # specified. +sources+ must be a Gem::SourceList.
+
+ def initialize sources = Gem.sources
+ super()
+
+ sources.each_source do |source|
+ @sets << source.dependency_resolver_set
+ end
+ end
+
+end
+
diff --git a/lib/rubygems/dependency_resolver/composed_set.rb b/lib/rubygems/dependency_resolver/composed_set.rb
index fb38128bb0..aeecf047b8 100644
--- a/lib/rubygems/dependency_resolver/composed_set.rb
+++ b/lib/rubygems/dependency_resolver/composed_set.rb
@@ -1,4 +1,6 @@
-class Gem::DependencyResolver::ComposedSet
+class Gem::DependencyResolver::ComposedSet < Gem::DependencyResolver::Set
+
+ attr_reader :sets # :nodoc:
def initialize *sets
@sets = sets
diff --git a/lib/rubygems/dependency_resolver/current_set.rb b/lib/rubygems/dependency_resolver/current_set.rb
index 13bc490e9e..ef15c9d7f3 100644
--- a/lib/rubygems/dependency_resolver/current_set.rb
+++ b/lib/rubygems/dependency_resolver/current_set.rb
@@ -3,14 +3,11 @@
# all the normal settings that control where to look
# for installed gems.
-class Gem::DependencyResolver::CurrentSet
+class Gem::DependencyResolver::CurrentSet < Gem::DependencyResolver::Set
def find_all req
req.dependency.matching_specs
end
- def prefetch gems
- end
-
end
diff --git a/lib/rubygems/dependency_resolver/dependency_conflict.rb b/lib/rubygems/dependency_resolver/dependency_conflict.rb
index 1755d910c3..092f000cdb 100644
--- a/lib/rubygems/dependency_resolver/dependency_conflict.rb
+++ b/lib/rubygems/dependency_resolver/dependency_conflict.rb
@@ -8,12 +8,21 @@ class Gem::DependencyResolver::DependencyConflict
attr_reader :dependency
+ attr_reader :failed_dep # :nodoc:
+
def initialize(dependency, activated, failed_dep=dependency)
@dependency = dependency
@activated = activated
@failed_dep = failed_dep
end
+ def == other
+ self.class === other and
+ @dependency == other.dependency and
+ @activated == other.activated and
+ @failed_dep == other.failed_dep
+ end
+
##
# Return the 2 dependency objects that conflicted
@@ -71,6 +80,8 @@ class Gem::DependencyResolver::DependencyConflict
current = current.request.requester
end
+ path = ['user request (gem command or Gemfile)'] if path.empty?
+
path
end
diff --git a/lib/rubygems/dependency_resolver/dependency_request.rb b/lib/rubygems/dependency_resolver/dependency_request.rb
index 05e447c3be..36b77ab558 100644
--- a/lib/rubygems/dependency_resolver/dependency_request.rb
+++ b/lib/rubygems/dependency_resolver/dependency_request.rb
@@ -32,6 +32,22 @@ class Gem::DependencyResolver::DependencyRequest
@dependency.name
end
+ # Indicate that the request is for a gem explicitly requested by the user
+ def explicit?
+ @requester.nil?
+ end
+
+ # Indicate that the requset is for a gem requested as a dependency of another gem
+ def implicit?
+ !explicit?
+ end
+
+ # Return a String indicating who caused this request to be added (only
+ # valid for implicit requests)
+ def request_context
+ @requester ? @requester.request : "(unknown)"
+ end
+
def pretty_print q # :nodoc:
q.group 2, '[Dependency request ', ']' do
q.breakable
@@ -43,6 +59,10 @@ class Gem::DependencyResolver::DependencyRequest
end
end
+ def requirement
+ @dependency.requirement
+ end
+
def to_s # :nodoc:
@dependency.to_s
end
diff --git a/lib/rubygems/dependency_resolver/index_set.rb b/lib/rubygems/dependency_resolver/index_set.rb
index 8c8bc4319d..04d6ec816f 100644
--- a/lib/rubygems/dependency_resolver/index_set.rb
+++ b/lib/rubygems/dependency_resolver/index_set.rb
@@ -2,10 +2,17 @@
# The global rubygems pool represented via the traditional
# source index.
-class Gem::DependencyResolver::IndexSet
+class Gem::DependencyResolver::IndexSet < Gem::DependencyResolver::Set
- def initialize
- @f = Gem::SpecFetcher.fetcher
+ def initialize source = nil # :nodoc:
+ @f =
+ if source then
+ sources = Gem::SourceList.from [source]
+
+ Gem::SpecFetcher.new sources
+ else
+ Gem::SpecFetcher.fetcher
+ end
@all = Hash.new { |h,k| h[k] = [] }
@@ -39,26 +46,5 @@ class Gem::DependencyResolver::IndexSet
res
end
- ##
- # Called from IndexSpecification to get a true Specification
- # object.
-
- def load_spec name, ver, platform, source
- key = "#{name}-#{ver}-#{platform}"
-
- @specs.fetch key do
- tuple = Gem::NameTuple.new name, ver, platform
-
- @specs[key] = source.fetch_spec tuple
- end
- end
-
- ##
- # No prefetching needed since we load the whole index in
- # initially.
-
- def prefetch gems
- end
-
end
diff --git a/lib/rubygems/dependency_resolver/index_specification.rb b/lib/rubygems/dependency_resolver/index_specification.rb
index 6cf267dac1..9b4057f0c8 100644
--- a/lib/rubygems/dependency_resolver/index_specification.rb
+++ b/lib/rubygems/dependency_resolver/index_specification.rb
@@ -3,17 +3,20 @@
# delay needed to download full Specification objects when only the +name+
# and +version+ are needed.
-class Gem::DependencyResolver::IndexSpecification
+class Gem::DependencyResolver::IndexSpecification < Gem::DependencyResolver::Specification
- attr_reader :name
-
- attr_reader :platform
-
- attr_reader :source
-
- attr_reader :version
+ ##
+ # An IndexSpecification is created from the index format described in `gem
+ # help generate_index`.
+ #
+ # The +set+ contains other specifications for this (URL) +source+.
+ #
+ # The +name+, +version+ and +platform+ are the name, version and platform of
+ # the gem.
def initialize set, name, version, source, platform
+ super()
+
@set = set
@name = name
@version = version
@@ -23,14 +26,13 @@ class Gem::DependencyResolver::IndexSpecification
@spec = nil
end
+ ##
+ # The dependencies of the gem for this specification
+
def dependencies
spec.dependencies
end
- def full_name
- "#{@name}-#{@version}"
- end
-
def inspect # :nodoc:
'#<%s %s source %s>' % [self.class, full_name, @source]
end
@@ -51,8 +53,16 @@ class Gem::DependencyResolver::IndexSpecification
end
end
- def spec
- @spec ||= @set.load_spec(@name, @version, @platform, @source)
+ ##
+ # Fetches a Gem::Specification for this IndexSpecification from the #source.
+
+ def spec # :nodoc:
+ @spec ||=
+ begin
+ tuple = Gem::NameTuple.new @name, @version, @platform
+
+ @source.fetch_spec tuple
+ end
end
end
diff --git a/lib/rubygems/dependency_resolver/installed_specification.rb b/lib/rubygems/dependency_resolver/installed_specification.rb
index ca20ace61e..4b591661a8 100644
--- a/lib/rubygems/dependency_resolver/installed_specification.rb
+++ b/lib/rubygems/dependency_resolver/installed_specification.rb
@@ -1,12 +1,8 @@
-class Gem::DependencyResolver::InstalledSpecification
+##
+# An InstalledSpecification represents a gem that is already installed
+# locally.
- attr_reader :spec
-
- def initialize set, spec, source=nil
- @set = set
- @source = source
- @spec = spec
- end
+class Gem::DependencyResolver::InstalledSpecification < Gem::DependencyResolver::SpecSpecification
def == other # :nodoc:
self.class === other and
@@ -14,29 +10,25 @@ class Gem::DependencyResolver::InstalledSpecification
@spec == other.spec
end
- def dependencies
- @spec.dependencies
- end
-
- def full_name
- "#{@spec.name}-#{@spec.version}"
- end
+ ##
+ # Returns +true+ if this gem is installable for the current platform.
- def name
- @spec.name
+ def installable_platform?
+ # BACKCOMPAT If the file is coming out of a specified file, then we
+ # ignore the platform. This code can be removed in RG 3.0.
+ if @source.kind_of? Gem::Source::SpecificFile
+ return true
+ else
+ Gem::Platform.match @spec.platform
+ end
end
- def platform
- @spec.platform
- end
+ ##
+ # The source for this specification
def source
@source ||= Gem::Source::Installed.new
end
- def version
- @spec.version
- end
-
end
diff --git a/lib/rubygems/dependency_resolver/installer_set.rb b/lib/rubygems/dependency_resolver/installer_set.rb
index 2993766d3a..801b60a8bb 100644
--- a/lib/rubygems/dependency_resolver/installer_set.rb
+++ b/lib/rubygems/dependency_resolver/installer_set.rb
@@ -2,23 +2,23 @@
# A set of gems for installation sourced from remote sources and local .gem
# files
-class Gem::DependencyResolver::InstallerSet
+class Gem::DependencyResolver::InstallerSet < Gem::DependencyResolver::Set
##
# List of Gem::Specification objects that must always be installed.
- attr_reader :always_install
+ attr_reader :always_install # :nodoc:
##
# Only install gems in the always_install list
- attr_accessor :ignore_dependencies
+ attr_accessor :ignore_dependencies # :nodoc:
##
# Do not look in the installed set when finding specifications. This is
# used by the --install-dir option to `gem install`
- attr_accessor :ignore_installed
+ attr_accessor :ignore_installed # :nodoc:
def initialize domain
@domain = domain
@@ -36,14 +36,14 @@ class Gem::DependencyResolver::InstallerSet
##
# Should local gems should be considered?
- def consider_local?
+ def consider_local? # :nodoc:
@domain == :both or @domain == :local
end
##
# Should remote gems should be considered?
- def consider_remote?
+ def consider_remote? # :nodoc:
@domain == :both or @domain == :remote
end
@@ -101,7 +101,7 @@ class Gem::DependencyResolver::InstallerSet
##
# Loads remote prerelease specs if +dep+ is a prerelease dependency
- def load_remote_specs dep
+ def load_remote_specs dep # :nodoc:
types = [:released]
types << :prerelease if dep.prerelease?
@@ -123,7 +123,7 @@ class Gem::DependencyResolver::InstallerSet
# Called from IndexSpecification to get a true Specification
# object.
- def load_spec name, ver, platform, source
+ def load_spec name, ver, platform, source # :nodoc:
key = "#{name}-#{ver}-#{platform}"
@specs.fetch key do
@@ -133,12 +133,6 @@ class Gem::DependencyResolver::InstallerSet
end
end
- ##
- # No prefetching needed since we load the whole index in initially.
-
- def prefetch(reqs)
- end
-
def pretty_print q # :nodoc:
q.group 2, '[InstallerSet', ']' do
q.breakable
diff --git a/lib/rubygems/dependency_resolver/lock_set.rb b/lib/rubygems/dependency_resolver/lock_set.rb
new file mode 100644
index 0000000000..f95c7f0fd6
--- /dev/null
+++ b/lib/rubygems/dependency_resolver/lock_set.rb
@@ -0,0 +1,60 @@
+##
+# A set of gems from a gem dependencies lockfile.
+
+class Gem::DependencyResolver::LockSet < Gem::DependencyResolver::Set
+
+ attr_reader :specs # :nodoc:
+
+ ##
+ # Creates a new LockSet from the given +source+
+
+ def initialize source
+ @source = source
+ @specs = []
+ end
+
+ ##
+ # Creates a new IndexSpecification in this set using the given +name+,
+ # +version+ and +platform+.
+ #
+ # The specification's set will be the current set, and the source will be
+ # the current set's source.
+
+ def add name, version, platform # :nodoc:
+ version = Gem::Version.new version
+
+ spec =
+ Gem::DependencyResolver::IndexSpecification.new self, name, version,
+ @source, platform
+
+ @specs << spec
+ end
+
+ ##
+ # Returns an Array of IndexSpecification objects matching the
+ # DependencyRequest +req+.
+
+ def find_all req
+ @specs.select do |spec|
+ req.matches_spec? spec
+ end
+ end
+
+ ##
+ # Loads a Gem::Specification with the given +name+, +version+ and
+ # +platform+. +source+ is ignored.
+
+ def load_spec name, version, platform, source # :nodoc:
+ dep = Gem::Dependency.new name, version
+
+ found = @specs.find do |spec|
+ dep.matches_spec? spec and spec.platform == platform
+ end
+
+ tuple = Gem::NameTuple.new found.name, found.version, found.platform
+
+ found.source.fetch_spec tuple
+ end
+
+end
+
diff --git a/lib/rubygems/dependency_resolver/set.rb b/lib/rubygems/dependency_resolver/set.rb
new file mode 100644
index 0000000000..65801871ac
--- /dev/null
+++ b/lib/rubygems/dependency_resolver/set.rb
@@ -0,0 +1,28 @@
+##
+# DependencyResolver sets are used to look up specifications (and their
+# dependencies) used in resolution. This set is abstract.
+
+class Gem::DependencyResolver::Set
+
+ ##
+ # The find_all method must be implemented. It returns all
+ # DependencyResolver Specification objects matching the given
+ # DependencyRequest +req+.
+
+ def find_all req
+ raise NotImplementedError
+ end
+
+ ##
+ # The #prefetch method may be overridden, but this is not necessary. This
+ # default implementation does nothing, which is suitable for sets where
+ # looking up a specification is cheap (such as installed gems).
+ #
+ # When overridden, the #prefetch method should look up specifications
+ # matching +reqs+.
+
+ def prefetch reqs
+ end
+
+end
+
diff --git a/lib/rubygems/dependency_resolver/spec_specification.rb b/lib/rubygems/dependency_resolver/spec_specification.rb
new file mode 100644
index 0000000000..cca1d58b9f
--- /dev/null
+++ b/lib/rubygems/dependency_resolver/spec_specification.rb
@@ -0,0 +1,58 @@
+##
+# The DependencyResolver::SpecSpecification contains common functionality for
+# DependencyResolver specifications that are backed by a Gem::Specification.
+
+class Gem::DependencyResolver::SpecSpecification < Gem::DependencyResolver::Specification
+
+ attr_reader :spec # :nodoc:
+
+ ##
+ # A SpecSpecification is created for a +set+ for a Gem::Specification in
+ # +spec+. The +source+ is either where the +spec+ came from, or should be
+ # loaded from.
+
+ def initialize set, spec, source = nil
+ @set = set
+ @source = source
+ @spec = spec
+ end
+
+ ##
+ # The dependencies of the gem for this specification
+
+ def dependencies
+ spec.dependencies
+ end
+
+ ##
+ # The name and version of the specification.
+ #
+ # Unlike Gem::Specification#full_name, the platform is not included.
+
+ def full_name
+ "#{spec.name}-#{spec.version}"
+ end
+
+ ##
+ # The name of the gem for this specification
+
+ def name
+ spec.name
+ end
+
+ ##
+ # The platform this gem works on.
+
+ def platform
+ spec.platform
+ end
+
+ ##
+ # The version of the gem for this specification.
+
+ def version
+ spec.version
+ end
+
+end
+
diff --git a/lib/rubygems/dependency_resolver/specification.rb b/lib/rubygems/dependency_resolver/specification.rb
new file mode 100644
index 0000000000..6fbd241316
--- /dev/null
+++ b/lib/rubygems/dependency_resolver/specification.rb
@@ -0,0 +1,60 @@
+##
+# A DependencyResolver::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
+
+ ##
+ # 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 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
+
+ ##
+ # The name and version of the specification.
+ #
+ # Unlike Gem::Specification#full_name, the platform is not included.
+
+ def full_name
+ "#{@name}-#{@version}"
+ end
+
+end
+
diff --git a/lib/rubygems/dependency_resolver/vendor_set.rb b/lib/rubygems/dependency_resolver/vendor_set.rb
index 716c2a8e26..87eb6fd818 100644
--- a/lib/rubygems/dependency_resolver/vendor_set.rb
+++ b/lib/rubygems/dependency_resolver/vendor_set.rb
@@ -13,17 +13,18 @@
# The directory vendor/rake must contain an unpacked rake gem along with a
# rake.gemspec (watching the given name).
-class Gem::DependencyResolver::VendorSet
+class Gem::DependencyResolver::VendorSet < Gem::DependencyResolver::Set
- def initialize
- @specs = {}
+ def initialize # :nodoc:
+ @directories = {}
+ @specs = {}
end
##
# Adds a specification to the set with the given +name+ which has been
# unpacked into the given +directory+.
- def add_vendor_gem name, directory
+ def add_vendor_gem name, directory # :nodoc:
gemspec = File.join directory, "#{name}.gemspec"
spec = Gem::Specification.load gemspec
@@ -33,7 +34,8 @@ class Gem::DependencyResolver::VendorSet
key = "#{spec.name}-#{spec.version}-#{spec.platform}"
- @specs[key] = spec
+ @specs[key] = spec
+ @directories[spec] = directory
end
##
@@ -44,7 +46,8 @@ class Gem::DependencyResolver::VendorSet
@specs.values.select do |spec|
req.matches_spec? spec
end.map do |spec|
- Gem::DependencyResolver::VendorSpecification.new self, spec, nil
+ source = Gem::Source::Vendor.new @directories[spec]
+ Gem::DependencyResolver::VendorSpecification.new self, spec, source
end
end
@@ -53,17 +56,11 @@ class Gem::DependencyResolver::VendorSet
# +source+ is defined when the specification was added to index it is not
# used.
- def load_spec name, version, platform, source
+ def load_spec name, version, platform, source # :nodoc:
key = "#{name}-#{version}-#{platform}"
@specs.fetch key
end
- ##
- # No prefetch is needed as the index is loaded at creation time.
-
- def prefetch gems
- end
-
end
diff --git a/lib/rubygems/dependency_resolver/vendor_specification.rb b/lib/rubygems/dependency_resolver/vendor_specification.rb
index 2f18fab4ce..27b2fd6df2 100644
--- a/lib/rubygems/dependency_resolver/vendor_specification.rb
+++ b/lib/rubygems/dependency_resolver/vendor_specification.rb
@@ -1,43 +1,15 @@
-class Gem::DependencyResolver::VendorSpecification
+##
+# A VendorSpecification represents a gem that has been unpacked into a project
+# and is being loaded through a gem dependencies file through the +path:+
+# option.
- attr_reader :spec
-
- attr_reader :set
-
- def initialize set, spec, source=nil
- @set = set
- @source = source
- @spec = spec
- end
+class Gem::DependencyResolver::VendorSpecification < Gem::DependencyResolver::SpecSpecification
def == other # :nodoc:
self.class === other and
@set == other.set and
- @spec == other.spec
- end
-
- def dependencies
- @spec.dependencies
- end
-
- def full_name
- "#{@spec.name}-#{@spec.version}"
- end
-
- def name
- @spec.name
- end
-
- def platform
- @spec.platform
- end
-
- def source
- @source ||= Gem::Source::Vendor.new
- end
-
- def version
- @spec.version
+ @spec == other.spec and
+ @source == other.source
end
end
diff --git a/lib/rubygems/errors.rb b/lib/rubygems/errors.rb
index 4a92011e8b..3c5486a800 100644
--- a/lib/rubygems/errors.rb
+++ b/lib/rubygems/errors.rb
@@ -19,8 +19,6 @@ module Gem
attr_accessor :requirement
end
- # FIX: does this need to exist? The subclass is the only other reference
- # I can find.
class ErrorReason; end
# Generated when trying to lookup a gem to indicate that the gem
diff --git a/lib/rubygems/exceptions.rb b/lib/rubygems/exceptions.rb
index 30d9880d8a..6d92b144b6 100644
--- a/lib/rubygems/exceptions.rb
+++ b/lib/rubygems/exceptions.rb
@@ -35,7 +35,7 @@ class Gem::DependencyResolutionError < Gem::Exception
@conflict = conflict
a, b = conflicting_dependencies
- super "unable to resolve conflicting dependencies '#{a}' and '#{b}'"
+ super "conflicting dependencies #{a} and #{b}\n#{@conflict.explanation}"
end
def conflicting_dependencies
@@ -226,10 +226,17 @@ class Gem::UnsatisfiableDependencyError < Gem::Exception
# Creates a new UnsatisfiableDepedencyError for the unsatisfiable
# Gem::DependencyResolver::DependencyRequest +dep+
- def initialize dep
- requester = dep.requester ? dep.requester.request : '(unknown)'
-
- super "Unable to resolve dependency: #{requester} requires #{dep}"
+ def initialize dep, platform_mismatch=nil
+ if platform_mismatch and !platform_mismatch.empty?
+ plats = platform_mismatch.map { |x| x.platform.to_s }.sort.uniq
+ super "Unable to resolve dependency: No match for '#{dep}' on this platform. Found: #{plats.join(', ')}"
+ else
+ if dep.explicit?
+ super "Unable to resolve dependency: user requested '#{dep}'"
+ else
+ super "Unable to resolve dependency: '#{dep.request_context}' requires '#{dep}'"
+ end
+ end
@dependency = dep
end
diff --git a/lib/rubygems/platform.rb b/lib/rubygems/platform.rb
index 247ee6ed3e..e050959dc6 100644
--- a/lib/rubygems/platform.rb
+++ b/lib/rubygems/platform.rb
@@ -29,6 +29,14 @@ class Gem::Platform
end
end
+ def self.installable?(spec)
+ if spec.respond_to? :installable_platform?
+ spec.installable_platform?
+ else
+ match spec.platform
+ end
+ end
+
def self.new(arch) # :nodoc:
case arch
when Gem::Platform::CURRENT then
diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb
index bab96cba1a..b96c77033a 100644
--- a/lib/rubygems/remote_fetcher.rb
+++ b/lib/rubygems/remote_fetcher.rb
@@ -78,7 +78,6 @@ class Gem::RemoteFetcher
end
##
- #
# Given a source at +uri+, calculate what hostname to actually
# connect to query the data for it.
diff --git a/lib/rubygems/request_set.rb b/lib/rubygems/request_set.rb
index 9a7ac83e91..3a997f32ee 100644
--- a/lib/rubygems/request_set.rb
+++ b/lib/rubygems/request_set.rb
@@ -32,6 +32,11 @@ class Gem::RequestSet
attr_accessor :development
##
+ # Sets used for resolution
+
+ attr_reader :sets # :nodoc:
+
+ ##
# Treat missing dependencies as silent errors
attr_accessor :soft_missing
@@ -53,13 +58,15 @@ class Gem::RequestSet
def initialize *deps
@dependencies = deps
- @always_install = []
- @development = false
- @requests = []
- @soft_missing = false
- @sorted = nil
- @specs = nil
- @vendor_set = nil
+ @always_install = []
+ @dependency_names = {}
+ @development = false
+ @requests = []
+ @sets = []
+ @soft_missing = false
+ @sorted = nil
+ @specs = nil
+ @vendor_set = nil
yield self if block_given?
end
@@ -68,7 +75,13 @@ class Gem::RequestSet
# Declare that a gem of name +name+ with +reqs+ requirements is needed.
def gem name, *reqs
- @dependencies << Gem::Dependency.new(name, reqs)
+ if dep = @dependency_names[name] then
+ dep.requirement.concat reqs
+ else
+ dep = Gem::Dependency.new name, reqs
+ @dependency_names[name] = dep
+ @dependencies << dep
+ end
end
##
@@ -78,7 +91,14 @@ class Gem::RequestSet
@dependencies.concat deps
end
- def install options, &block
+ ##
+ # Installs gems for this RequestSet using the Gem::Installer +options+.
+ #
+ # If a +block+ is given an activation +request+ and +installer+ are yielded.
+ # The +installer+ will be +nil+ if a gem matching the request was already
+ # installed.
+
+ def install options, &block # :yields: request, installer
if dir = options[:install_dir]
return install_into dir, false, options, &block
end
@@ -109,6 +129,21 @@ class Gem::RequestSet
specs
end
+ ##
+ # Installs from the gem dependencies files in the +:gemdeps+ option in
+ # +options+, yielding to the +block+ as in #install.
+ #
+ # If +:without_groups+ is given in the +options+, those groups in the gem
+ # dependencies file are not used. See Gem::Installer for other +options+.
+
+ def install_from_gemdeps options, &block
+ load_gemdeps options[:gemdeps], options[:without_groups]
+
+ resolve
+
+ install options, &block
+ end
+
def install_into dir, force = true, options = {}
existing = force ? [] : specs_in(dir)
existing.delete_if { |s| @always_install.include? s }
@@ -148,10 +183,11 @@ class Gem::RequestSet
##
# Load a dependency management file.
- def load_gemdeps path
+ def load_gemdeps path, without_groups = []
@vendor_set = Gem::DependencyResolver::VendorSet.new
gf = Gem::RequestSet::GemDependencyAPI.new self, path
+ gf.without_groups = without_groups if without_groups
gf.load
end
@@ -160,13 +196,10 @@ class Gem::RequestSet
# objects to be activated.
def resolve set = Gem::DependencyResolver::IndexSet.new
- sets = [set, @vendor_set].compact
+ @sets << set
+ @sets << @vendor_set
- set = if sets.size == 1 then
- sets.first
- else
- Gem::DependencyResolver.compose_sets(*sets)
- end
+ set = Gem::DependencyResolver.compose_sets(*@sets)
resolver = Gem::DependencyResolver.new @dependencies, set
resolver.development = @development
diff --git a/lib/rubygems/request_set/gem_dependency_api.rb b/lib/rubygems/request_set/gem_dependency_api.rb
index f11ffb12c3..e8f3138990 100644
--- a/lib/rubygems/request_set/gem_dependency_api.rb
+++ b/lib/rubygems/request_set/gem_dependency_api.rb
@@ -3,10 +3,114 @@
class Gem::RequestSet::GemDependencyAPI
+ ENGINE_MAP = { # :nodoc:
+ :jruby => %w[jruby],
+ :jruby_18 => %w[jruby],
+ :jruby_19 => %w[jruby],
+ :maglev => %w[maglev],
+ :mri => %w[ruby],
+ :mri_18 => %w[ruby],
+ :mri_19 => %w[ruby],
+ :mri_20 => %w[ruby],
+ :mri_21 => %w[ruby],
+ :rbx => %w[rbx],
+ :ruby => %w[ruby rbx maglev],
+ :ruby_18 => %w[ruby rbx maglev],
+ :ruby_19 => %w[ruby rbx maglev],
+ :ruby_20 => %w[ruby rbx maglev],
+ :ruby_21 => %w[ruby rbx maglev],
+ }
+
+ x86_mingw = Gem::Platform.new 'x86-mingw32'
+ x64_mingw = Gem::Platform.new 'x64-mingw32'
+
+ PLATFORM_MAP = { # :nodoc:
+ :jruby => Gem::Platform::RUBY,
+ :jruby_18 => Gem::Platform::RUBY,
+ :jruby_19 => Gem::Platform::RUBY,
+ :maglev => Gem::Platform::RUBY,
+ :mingw => x86_mingw,
+ :mingw_18 => x86_mingw,
+ :mingw_19 => x86_mingw,
+ :mingw_20 => x86_mingw,
+ :mingw_21 => x86_mingw,
+ :mri => Gem::Platform::RUBY,
+ :mri_18 => Gem::Platform::RUBY,
+ :mri_19 => Gem::Platform::RUBY,
+ :mri_20 => Gem::Platform::RUBY,
+ :mri_21 => Gem::Platform::RUBY,
+ :mswin => Gem::Platform::RUBY,
+ :rbx => Gem::Platform::RUBY,
+ :ruby => Gem::Platform::RUBY,
+ :ruby_18 => Gem::Platform::RUBY,
+ :ruby_19 => Gem::Platform::RUBY,
+ :ruby_20 => Gem::Platform::RUBY,
+ :ruby_21 => Gem::Platform::RUBY,
+ :x64_mingw => x64_mingw,
+ :x64_mingw_20 => x64_mingw,
+ :x64_mingw_21 => x64_mingw
+ }
+
+ gt_eq_0 = Gem::Requirement.new '>= 0'
+ tilde_gt_1_8_0 = Gem::Requirement.new '~> 1.8.0'
+ tilde_gt_1_9_0 = Gem::Requirement.new '~> 1.9.0'
+ tilde_gt_2_0_0 = Gem::Requirement.new '~> 2.0.0'
+ tilde_gt_2_1_0 = Gem::Requirement.new '~> 2.1.0'
+
+ VERSION_MAP = { # :nodoc:
+ :jruby => gt_eq_0,
+ :jruby_18 => tilde_gt_1_8_0,
+ :jruby_19 => tilde_gt_1_9_0,
+ :maglev => gt_eq_0,
+ :mingw => gt_eq_0,
+ :mingw_18 => tilde_gt_1_8_0,
+ :mingw_19 => tilde_gt_1_9_0,
+ :mingw_20 => tilde_gt_2_0_0,
+ :mingw_21 => tilde_gt_2_1_0,
+ :mri => gt_eq_0,
+ :mri_18 => tilde_gt_1_8_0,
+ :mri_19 => tilde_gt_1_9_0,
+ :mri_20 => tilde_gt_2_0_0,
+ :mri_21 => tilde_gt_2_1_0,
+ :mswin => gt_eq_0,
+ :rbx => gt_eq_0,
+ :ruby => gt_eq_0,
+ :ruby_18 => tilde_gt_1_8_0,
+ :ruby_19 => tilde_gt_1_9_0,
+ :ruby_20 => tilde_gt_2_0_0,
+ :ruby_21 => tilde_gt_2_1_0,
+ :x64_mingw => gt_eq_0,
+ :x64_mingw_20 => tilde_gt_2_0_0,
+ :x64_mingw_21 => tilde_gt_2_1_0,
+ }
+
+ WINDOWS = { # :nodoc:
+ :mingw => :only,
+ :mingw_18 => :only,
+ :mingw_19 => :only,
+ :mingw_20 => :only,
+ :mingw_21 => :only,
+ :mri => :never,
+ :mri_18 => :never,
+ :mri_19 => :never,
+ :mri_20 => :never,
+ :mri_21 => :never,
+ :mswin => :only,
+ :rbx => :never,
+ :ruby => :never,
+ :ruby_18 => :never,
+ :ruby_19 => :never,
+ :ruby_20 => :never,
+ :ruby_21 => :never,
+ :x64_mingw => :only,
+ :x64_mingw_20 => :only,
+ :x64_mingw_21 => :only,
+ }
+
##
- # The dependency groups created by #group in the dependency API file.
+ # A Hash containing gem names and files to require from those gems.
- attr_reader :dependency_groups
+ attr_reader :requires
##
# A set of gems that are loaded via the +:path+ option to #gem
@@ -14,6 +118,11 @@ class Gem::RequestSet::GemDependencyAPI
attr_reader :vendor_set # :nodoc:
##
+ # The groups of gems to exclude from installation
+
+ attr_accessor :without_groups
+
+ ##
# Creates a new GemDependencyAPI that will add dependencies to the
# Gem::RequestSet +set+ based on the dependency API description in +path+.
@@ -21,9 +130,13 @@ class Gem::RequestSet::GemDependencyAPI
@set = set
@path = path
- @current_groups = nil
- @dependency_groups = Hash.new { |h, group| h[group] = [] }
- @vendor_set = @set.vendor_set
+ @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 = []
end
##
@@ -47,10 +160,32 @@ class Gem::RequestSet::GemDependencyAPI
options = requirements.pop if requirements.last.kind_of?(Hash)
options ||= {}
- if directory = options.delete(:path) then
- @vendor_set.add_vendor_gem name, directory
+ source_set = gem_path name, options
+
+ return unless gem_platforms options
+
+ groups = gem_group name, options
+
+ return unless (groups & @without_groups).empty?
+
+ unless source_set then
+ raise ArgumentError,
+ "duplicate source (default) for gem #{name}" if
+ @gem_sources.include? name
+
+ @gem_sources[name] = :default
end
+ gem_requires name, options
+
+ @set.gem name, *requirements
+ end
+
+ ##
+ # Handles the :group and :groups +options+ for the gem with the given
+ # +name+.
+
+ def gem_group name, options # :nodoc:
g = options.delete :group
all_groups = g ? Array(g) : []
@@ -59,19 +194,81 @@ class Gem::RequestSet::GemDependencyAPI
all_groups |= @current_groups if @current_groups
- unless all_groups.empty? then
- all_groups.each do |group|
- gem_arguments = [name, *requirements]
- gem_arguments << options unless options.empty?
- @dependency_groups[group] << gem_arguments
+ all_groups
+ end
+
+ private :gem_group
+
+ ##
+ # Handles the path: option from +options+ for gem +name+.
+ #
+ # Returns +true+ if the path option was handled.
+
+ def gem_path name, options # :nodoc:
+ return unless directory = options.delete(:path)
+
+ raise ArgumentError,
+ "duplicate source path: #{directory} for gem #{name}" if
+ @gem_sources.include? name
+
+ @vendor_set.add_vendor_gem name, directory
+
+ @gem_sources[name] = directory
+
+ true
+ end
+
+ private :gem_path
+
+ ##
+ # Handles the platforms: option from +options+. Returns true if the
+ # platform matches the current platform.
+
+ def gem_platforms options # :nodoc:
+ platform_names = Array(options.delete :platforms)
+ platform_names << @current_platform if @current_platform
+
+ return true if platform_names.empty?
+
+ platform_names.any? do |platform_name|
+ raise ArgumentError, "unknown platform #{platform_name.inspect}" unless
+ platform = PLATFORM_MAP[platform_name]
+
+ next false unless Gem::Platform.match platform
+
+ if engines = ENGINE_MAP[platform_name] then
+ next false unless engines.include? Gem.ruby_engine
+ end
+
+ case WINDOWS[platform_name]
+ when :only then
+ next false unless Gem.win_platform?
+ when :never then
+ next false if Gem.win_platform?
end
- return
+ VERSION_MAP[platform_name].satisfied_by? Gem.ruby_version
end
+ end
- @set.gem name, *requirements
+ private :gem_platforms
+
+ ##
+ # Handles the require: option from +options+ and adds those files, or the
+ # default file to the require list for +name+.
+
+ def gem_requires name, options # :nodoc:
+ if options.include? :require then
+ if requires = options.delete(:require) then
+ @requires[name].concat requires
+ end
+ else
+ @requires[name] << name
+ end
end
+ private :gem_requires
+
##
# Returns the basename of the file the dependencies were loaded from
@@ -96,9 +293,12 @@ class Gem::RequestSet::GemDependencyAPI
# :category: Gem Dependencies DSL
def platform what
- if what == :ruby
- yield
- end
+ @current_platform = what
+
+ yield
+
+ ensure
+ @current_platform = nil
end
##
@@ -112,23 +312,58 @@ class Gem::RequestSet::GemDependencyAPI
# +:engine+ options from Bundler are currently ignored.
def ruby version, options = {}
- return true if version == RUBY_VERSION
+ engine = options[:engine]
+ engine_version = options[:engine_version]
+
+ raise ArgumentError,
+ 'you must specify engine_version along with the ruby engine' if
+ engine and not engine_version
+
+ unless RUBY_VERSION == version then
+ message = "Your Ruby version is #{RUBY_VERSION}, " +
+ "but your #{gem_deps_file} requires #{version}"
+
+ raise Gem::RubyVersionMismatch, message
+ end
+
+ if engine and engine != Gem.ruby_engine then
+ message = "Your ruby engine is #{Gem.ruby_engine}, " +
+ "but your #{gem_deps_file} requires #{engine}"
+
+ raise Gem::RubyVersionMismatch, message
+ end
- message = "Your Ruby version is #{RUBY_VERSION}, " +
- "but your #{gem_deps_file} specified #{version}"
+ if engine_version then
+ my_engine_version = Object.const_get "#{Gem.ruby_engine.upcase}_VERSION"
- raise Gem::RubyVersionMismatch, message
+ if engine_version != my_engine_version then
+ message =
+ "Your ruby engine version is #{Gem.ruby_engine} #{my_engine_version}, " +
+ "but your #{gem_deps_file} requires #{engine} #{engine_version}"
+
+ raise Gem::RubyVersionMismatch, message
+ end
+ end
+
+ return true
end
##
# :category: Gem Dependencies DSL
+ #
+ # Sets +url+ as a source for gems for this dependency API.
def source url
+ Gem.sources.clear if @default_sources
+
+ @default_sources = false
+
+ Gem.sources << url
end
# TODO: remove this typo name at RubyGems 3.0
- Gem::RequestSet::DepedencyAPI = self # :nodoc:
+ Gem::RequestSet::GemDepedencyAPI = self # :nodoc:
end
diff --git a/lib/rubygems/request_set/lockfile.rb b/lib/rubygems/request_set/lockfile.rb
new file mode 100644
index 0000000000..a9c419549d
--- /dev/null
+++ b/lib/rubygems/request_set/lockfile.rb
@@ -0,0 +1,347 @@
+require 'pathname'
+
+class Gem::RequestSet::Lockfile
+
+ ##
+ # Raised when a lockfile cannot be parsed
+
+ class ParseError < Gem::Exception
+
+ ##
+ # The column where the error was encountered
+
+ attr_reader :column
+
+ ##
+ # The line where the error was encountered
+
+ attr_reader :line
+
+ ##
+ # The location of the lock file
+
+ attr_reader :path
+
+ ##
+ # Raises a ParseError with the given +message+ which was encountered at a
+ # +line+ and +column+ while parsing.
+
+ def initialize message, line, column, path
+ @line = line
+ @column = column
+ @path = path
+ super "#{message} (at #{line}:#{column})"
+ end
+
+ end
+
+ ##
+ # The platforms for this Lockfile
+
+ attr_reader :platforms
+
+ ##
+ # Creates a new Lockfile for the given +request_set+ and +gem_deps_file+
+ # location.
+
+ 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
+
+ @current_token = nil
+ @line = 0
+ @line_pos = 0
+ @platforms = []
+ @tokens = []
+ end
+
+ def add_DEPENDENCIES out # :nodoc:
+ out << "DEPENDENCIES"
+
+ @set.dependencies.sort.map do |dependency|
+ source = @requests.find do |req|
+ req.name == dependency.name and
+ req.spec.class == Gem::DependencyResolver::VendorSpecification
+ end
+
+ source_dep = '!' if source
+
+ requirement = dependency.requirement
+
+ out << " #{dependency.name}#{source_dep}#{requirement.for_lockfile}"
+ end
+
+ out << nil
+ end
+
+ def add_GEM out # :nodoc:
+ out << "GEM"
+
+ source_groups = @spec_groups.values.flatten.group_by do |request|
+ request.spec.source.uri
+ end
+
+ source_groups.map do |group, requests|
+ out << " remote: #{group}"
+ out << " specs:"
+
+ requests.sort_by { |request| request.name }.each do |request|
+ platform = "-#{request.spec.platform}" unless
+ Gem::Platform::RUBY == request.spec.platform
+
+ out << " #{request.name} (#{request.version}#{platform})"
+
+ request.full_spec.dependencies.sort.each do |dependency|
+ requirement = dependency.requirement
+ out << " #{dependency.name}#{requirement.for_lockfile}"
+ end
+ end
+ end
+
+ out << nil
+ end
+
+ def add_PATH out # :nodoc:
+ return unless path_requests =
+ @spec_groups.delete(Gem::DependencyResolver::VendorSpecification)
+
+ out << "PATH"
+ path_requests.each do |request|
+ directory = Pathname(request.spec.source.uri).expand_path
+
+ out << " remote: #{directory.relative_path_from @gem_deps_dir}"
+ out << " specs:"
+ out << " #{request.name} (#{request.version})"
+ end
+
+ out << nil
+ end
+
+ def add_PLATFORMS out # :nodoc:
+ out << "PLATFORMS"
+
+ platforms = @requests.map { |request| request.spec.platform }.uniq
+ platforms.delete Gem::Platform::RUBY if platforms.length > 1
+
+ platforms.each do |platform|
+ out << " #{platform}"
+ end
+
+ out << nil
+ end
+
+ ##
+ # Gets the next token for a Lockfile
+
+ def get expected_type = nil, expected_value = nil # :nodoc:
+ @current_token = @tokens.shift
+
+ type, value, line, column = @current_token
+
+ if expected_type and expected_type != type then
+ unget
+
+ message = "unexpected token [#{type.inspect}, #{value.inspect}], " +
+ "expected #{expected_type.inspect}"
+
+ raise ParseError.new message, line, column, "#{@gem_deps_file}.lock"
+ end
+
+ if expected_value and expected_value != value then
+ unget
+
+ message = "unexpected token [#{type.inspect}, #{value.inspect}], " +
+ "expected [#{expected_type.inspect}, #{expected_value.inspect}]"
+
+ raise ParseError.new message, line, column, "#{@gem_deps_file}.lock"
+ end
+
+ @current_token
+ end
+
+ def parse # :nodoc:
+ tokenize
+
+ until @tokens.empty? do
+ type, data, column, line = get
+
+ case type
+ when :section then
+ skip :newline
+
+ case data
+ when 'DEPENDENCIES' then
+ parse_DEPENDENCIES
+ when 'GEM' then
+ parse_GEM
+ when 'PLATFORMS' then
+ parse_PLATFORMS
+ else
+ type, = get until @tokens.empty? or peek.first == :section
+ end
+ else
+ raise "BUG: unhandled token #{type} (#{data.inspect}) at #{line}:#{column}"
+ end
+ end
+ end
+
+ def parse_DEPENDENCIES # :nodoc:
+ while not @tokens.empty? and :text == peek.first do
+ _, name, = get :text
+
+ @set.gem name
+
+ skip :newline
+ end
+ end
+
+ def parse_GEM # :nodoc:
+ get :entry, 'remote'
+ _, data, = get :text
+
+ source = Gem::Source.new data
+
+ skip :newline
+
+ get :entry, 'specs'
+
+ skip :newline
+
+ set = Gem::DependencyResolver::LockSet.new source
+
+ while not @tokens.empty? and :text == peek.first do
+ _, name, = get :text
+
+ case peek[0]
+ when :newline then # ignore
+ when :l_paren then
+ get :l_paren
+
+ _, version, = get :text
+
+ get :r_paren
+
+ set.add name, version, Gem::Platform::RUBY
+ else
+ raise "BUG: unknown token #{peek}"
+ end
+
+ skip :newline
+ end
+
+ @set.sets << set
+ end
+
+ def parse_PLATFORMS # :nodoc:
+ while not @tokens.empty? and :text == peek.first do
+ _, name, = get :text
+
+ @platforms << name
+
+ skip :newline
+ end
+ end
+
+ ##
+ # Peeks at the next token for Lockfile
+
+ def peek # :nodoc:
+ @tokens.first
+ end
+
+ def skip type # :nodoc:
+ get while not @tokens.empty? and peek.first == type
+ end
+
+ def to_s
+ @set.resolve
+
+ out = []
+
+ @requests = @set.sorted_requests
+
+ @spec_groups = @requests.group_by do |request|
+ request.spec.class
+ end
+
+ add_PATH out
+
+ add_GEM out
+
+ add_PLATFORMS out
+
+ add_DEPENDENCIES out
+
+ out.join "\n"
+ end
+
+ ##
+ # Calculates the column (by byte) and the line of the current token based on
+ # +byte_offset+.
+
+ def token_pos byte_offset # :nodoc:
+ [byte_offset - @line_pos, @line]
+ end
+
+ def tokenize # :nodoc:
+ @line = 0
+ @line_pos = 0
+
+ @platforms = []
+ @tokens = []
+ @current_token = nil
+
+ lock_file = "#{@gem_deps_file}.lock"
+
+ @input = File.read lock_file
+ s = StringScanner.new @input
+
+ until s.eos? do
+ pos = s.pos
+
+ # leading whitespace is for the user's convenience
+ next if s.scan(/ +/)
+
+ if s.scan(/[<|=>]{7}/) then
+ message = "your #{lock_file} contains merge conflict markers"
+ line, column = token_pos pos
+
+ raise ParseError.new message, line, column, lock_file
+ end
+
+ @tokens <<
+ case
+ when s.scan(/\r?\n/) then
+ token = [:newline, nil, *token_pos(pos)]
+ @line_pos = s.pos
+ @line += 1
+ token
+ when s.scan(/[A-Z]+/) then
+ [:section, s.matched, *token_pos(pos)]
+ when s.scan(/([a-z]+):\s/) then
+ s.pos -= 1 # rewind for possible newline
+ [:entry, s[1], *token_pos(pos)]
+ when s.scan(/\(/) then
+ [:l_paren, nil, *token_pos(pos)]
+ when s.scan(/\)/) then
+ [:r_paren, nil, *token_pos(pos)]
+ when s.scan(/[^\s)]*/) then
+ [:text, s.matched, *token_pos(pos)]
+ else
+ raise "BUG: can't create token for: #{s.string[s.pos..-1].inspect}"
+ end
+ end
+
+ @tokens
+ end
+
+ ##
+ # Ungets the last token retrieved by #get
+
+ def unget # :nodoc:
+ @tokens.unshift @current_token
+ end
+
+end
+
diff --git a/lib/rubygems/requirement.rb b/lib/rubygems/requirement.rb
index 4bf1ed914f..2b112a8022 100644
--- a/lib/rubygems/requirement.rb
+++ b/lib/rubygems/requirement.rb
@@ -21,11 +21,21 @@ class Gem::Requirement
}
quoted = OPS.keys.map { |k| Regexp.quote k }.join "|"
- PATTERN_RAW = "\\s*(#{quoted})?\\s*(#{Gem::Version::VERSION_PATTERN})\\s*"
+ PATTERN_RAW = "\\s*(#{quoted})?\\s*(#{Gem::Version::VERSION_PATTERN})\\s*" # :nodoc:
+
+ ##
+ # A regular expression that matches a requirement
+
PATTERN = /\A#{PATTERN_RAW}\z/
+ ##
+ # The default requirement matches any version
+
DefaultRequirement = [">=", Gem::Version.new(0)]
+ ##
+ # Raised when a bad requirement is encountered
+
class BadRequirementError < ArgumentError; end
##
@@ -108,9 +118,27 @@ class Gem::Requirement
end
##
+ # Concatenates the +new+ requirements onto this requirement.
+
+ def concat new
+ new = new.flatten
+ new.compact!
+ new.uniq!
+ new = new.map { |r| self.class.parse r }
+
+ @requirements.concat new
+ end
+
+ ##
+ # Formats this requirement for use in a Gem::RequestSet::Lockfile.
+
+ def for_lockfile # :nodoc:
+ " (#{to_s})" unless [DefaultRequirement] == @requirements
+ end
+
+ ##
# true if this gem has no requirements.
- # FIX: maybe this should be using #default ?
def none?
if @requirements.size == 1
@requirements[0] == DefaultRequirement
@@ -152,11 +180,11 @@ class Gem::Requirement
yaml_initialize coder.tag, coder.map
end
- def to_yaml_properties
+ def to_yaml_properties # :nodoc:
["@requirements"]
end
- def encode_with(coder)
+ def encode_with coder # :nodoc:
coder.add 'requirements', @requirements
end
@@ -200,15 +228,13 @@ class Gem::Requirement
as_list.join ", "
end
- # DOC: this should probably be :nodoc'd
- def == other
+ def == other # :nodoc:
Gem::Requirement === other and to_s == other.to_s
end
private
- # DOC: this should probably be :nodoc'd
- def fix_syck_default_key_in_requirements
+ def fix_syck_default_key_in_requirements # :nodoc:
Gem.load_yaml
# Fixup the Syck DefaultKey bug
@@ -220,9 +246,9 @@ class Gem::Requirement
end
end
-# This is needed for compatibility with older yaml
-# gemspecs.
-
class Gem::Version
- Requirement = Gem::Requirement
+ # This is needed for compatibility with older yaml
+ # gemspecs.
+
+ Requirement = Gem::Requirement # :nodoc:
end
diff --git a/lib/rubygems/server.rb b/lib/rubygems/server.rb
index 3ca588ae92..ca6dc683f5 100644
--- a/lib/rubygems/server.rb
+++ b/lib/rubygems/server.rb
@@ -445,7 +445,7 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; }
@spec_dirs = @gem_dirs.map { |gem_dir| File.join gem_dir, 'specifications' }
@spec_dirs.reject! { |spec_dir| !File.directory? spec_dir }
- Gem::Specification.dirs = @gem_dirs
+ reset_gems
@have_rdoc_4_plus = nil
end
@@ -470,7 +470,7 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; }
end
def latest_specs(req, res)
- Gem::Specification.reset
+ reset_gems
res['content-type'] = 'application/x-gzip'
@@ -531,7 +531,7 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; }
end
def quick(req, res)
- Gem::Specification.reset
+ reset_gems
res['content-type'] = 'text/plain'
add_date res
@@ -567,7 +567,8 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; }
end
def root(req, res)
- Gem::Specification.reset
+ reset_gems
+
add_date res
raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found." unless
@@ -698,6 +699,13 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; }
end
##
+ # Updates the server to use the latest installed gems.
+
+ def reset_gems # :nodoc:
+ Gem::Specification.dirs = @gem_dirs
+ end
+
+ ##
# Returns true and prepares http response, if rdoc for the requested gem
# name pattern was found.
#
@@ -787,7 +795,7 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; }
end
def specs(req, res)
- Gem::Specification.reset
+ reset_gems
add_date res
diff --git a/lib/rubygems/source.rb b/lib/rubygems/source.rb
index 394ea6cf22..bcf814b010 100644
--- a/lib/rubygems/source.rb
+++ b/lib/rubygems/source.rb
@@ -52,6 +52,24 @@ class Gem::Source
alias_method :eql?, :==
+ ##
+ # Returns a Set that can fetch specifications from this source.
+
+ def dependency_resolver_set # :nodoc:
+ uri = api_uri
+
+ bundler_api_uri = api_uri + './api/v1/dependencies'
+
+ begin
+ fetcher = Gem::RemoteFetcher.fetcher
+ fetcher.fetch_path bundler_api_uri, nil, true
+ rescue Gem::RemoteFetcher::FetchError
+ Gem::DependencyResolver::IndexSet.new self
+ else
+ Gem::DependencyResolver::APISet.new bundler_api_uri
+ end
+ end
+
def hash
@uri.hash
end
diff --git a/lib/rubygems/source/vendor.rb b/lib/rubygems/source/vendor.rb
index 701bc7fe84..f2cf540c8d 100644
--- a/lib/rubygems/source/vendor.rb
+++ b/lib/rubygems/source/vendor.rb
@@ -2,5 +2,10 @@
# This represents a vendored source that is similar to an installed gem.
class Gem::Source::Vendor < Gem::Source::Installed
+
+ def initialize uri
+ @uri = uri
+ end
+
end
diff --git a/lib/rubygems/source_list.rb b/lib/rubygems/source_list.rb
index 7bd8ef0b78..e6da50c2e5 100644
--- a/lib/rubygems/source_list.rb
+++ b/lib/rubygems/source_list.rb
@@ -1,28 +1,40 @@
require 'rubygems/source'
class Gem::SourceList
+
+ include Enumerable
+
+ ##
+ # Creates a new SourceList
+
def initialize
@sources = []
end
+ ##
+ # The sources in this list
+
attr_reader :sources
+ ##
+ # Creates a new SourceList from an array of sources.
+
def self.from(ary)
list = new
- if ary
- ary.each do |x|
- list << x
- end
- end
+ list.replace ary
return list
end
- def initialize_copy(other)
+ def initialize_copy(other) # :nodoc:
@sources = @sources.dup
end
+ ##
+ # Appends +obj+ to the source list which may be a Gem::Source, URI or URI
+ # String.
+
def <<(obj)
src = case obj
when URI
@@ -37,8 +49,12 @@ class Gem::SourceList
src
end
+ ##
+ # Replaces this SourceList with the sources in +other+ See #<< for
+ # acceptable items in +other+.
+
def replace(other)
- @sources.clear
+ clear
other.each do |x|
self << x
@@ -47,28 +63,58 @@ class Gem::SourceList
self
end
+ ##
+ # Removes all sources from the SourceList.
+
+ def clear
+ @sources.clear
+ end
+
+ ##
+ # Yields each source URI in the list.
+
def each
@sources.each { |s| yield s.uri.to_s }
end
+ ##
+ # Yields each source in the list.
+
def each_source(&b)
@sources.each(&b)
end
+ ##
+ # Returns true if there are no sources in this SourceList.
+
+ def empty?
+ @sources.empty?
+ end
+
def ==(other)
to_a == other
end
+ ##
+ # Returns an Array of source URI Strings.
+
def to_a
@sources.map { |x| x.uri.to_s }
end
alias_method :to_ary, :to_a
+ ##
+ # Returns the first source in the list.
+
def first
@sources.first
end
+ ##
+ # Returns true if this source list includes +other+ which may be a
+ # Gem::Source or a source URI.
+
def include?(other)
if other.kind_of? Gem::Source
@sources.include? other
@@ -77,11 +123,14 @@ class Gem::SourceList
end
end
- def delete(uri)
- if uri.kind_of? Gem::Source
- @sources.delete uri
+ ##
+ # Deletes +source+ from the source list which may be a Gem::Source or a URI.
+
+ def delete source
+ if source.kind_of? Gem::Source
+ @sources.delete source
else
- @sources.delete_if { |x| x.uri.to_s == uri.to_s }
+ @sources.delete_if { |x| x.uri.to_s == source.to_s }
end
end
end
diff --git a/lib/rubygems/spec_fetcher.rb b/lib/rubygems/spec_fetcher.rb
index 4bef93351f..22fa68db69 100644
--- a/lib/rubygems/spec_fetcher.rb
+++ b/lib/rubygems/spec_fetcher.rb
@@ -18,6 +18,11 @@ class Gem::SpecFetcher
attr_reader :latest_specs # :nodoc:
##
+ # Sources for this SpecFetcher
+
+ attr_reader :sources # :nodoc:
+
+ ##
# Cache of all released specs
attr_reader :specs # :nodoc:
@@ -37,7 +42,16 @@ class Gem::SpecFetcher
@fetcher = fetcher
end
- def initialize
+ ##
+ # Creates a new SpecFetcher. Ordinarily you want to use
+ # 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.
+
+ def initialize sources = nil
+ @sources = sources || Gem.sources
+
@update_cache =
begin
File.stat(Gem.user_home).uid == Process.uid
@@ -197,7 +211,7 @@ class Gem::SpecFetcher
errors = []
list = {}
- Gem.sources.each_source do |source|
+ @sources.each_source do |source|
begin
names = case type
when :latest
diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb
index 6b8528f238..5330c108ba 100644
--- a/lib/rubygems/specification.rb
+++ b/lib/rubygems/specification.rb
@@ -856,12 +856,8 @@ class Gem::Specification < Gem::BasicSpecification
# this resets the list of known specs.
def self.dirs= dirs
- # TODO: find extra calls to dir=
- # warn "NOTE: dirs= called from #{caller.first} for #{dirs.inspect}"
-
self.reset
- # ugh
@@dirs = Array(dirs).map { |dir| File.join dir, "specifications" }
end
@@ -1105,9 +1101,6 @@ class Gem::Specification < Gem::BasicSpecification
# Removes +spec+ from the known specs.
def self.remove_spec spec
- # TODO: beat on the tests
- raise "wtf: #{spec.full_name} not in #{all_names.inspect}" unless
- _all.include? spec
_all.delete spec
stubs.delete_if { |s| s.full_name == spec.full_name }
end
@@ -1400,7 +1393,7 @@ class Gem::Specification < Gem::BasicSpecification
# Returns the build_args used to install the gem
def build_args
- if File.exist? build_info_file
+ if File.exists? build_info_file
File.readlines(build_info_file).map { |x| x.strip }
else
[]
@@ -1788,6 +1781,7 @@ class Gem::Specification < Gem::BasicSpecification
end
def init_with coder # :nodoc:
+ @installed_by_version ||= nil
yaml_initialize coder.tag, coder.map
end
@@ -2293,9 +2287,9 @@ class Gem::Specification < Gem::BasicSpecification
end
end
- if defined?(@installed_by_version) && @installed_by_version then
+ if @installed_by_version then
result << nil
- result << " s.installed_by_version = \"#{Gem::VERSION}\""
+ result << " s.installed_by_version = \"#{Gem::VERSION}\" if s.respond_to? :installed_by_version"
end
unless dependencies.empty? then
@@ -2488,7 +2482,6 @@ class Gem::Specification < Gem::BasicSpecification
end
end
- # FIX: uhhhh single element array.each?
[:authors].each do |field|
val = self.send field
raise Gem::InvalidSpecificationException, "#{field} may not be empty" if
@@ -2540,7 +2533,6 @@ licenses is empty. Use a license abbreviation from:
# reject lazy developers:
- # FIX: Doesn't this just evaluate to "FIXME" or "TODO"?
lazy = '"FIxxxXME" or "TOxxxDO"'.gsub(/xxx/, '')
unless authors.grep(/FI XME|TO DO/x).empty? then
@@ -2586,19 +2578,80 @@ licenses is empty. Use a license abbreviation from:
warning "#{executable_path} is missing #! line" unless shebang
end
+ validate_dependencies
+
+ true
+ ensure
+ if $! or @warnings > 0 then
+ alert_warning "See http://guides.rubygems.org/specification-reference/ for help"
+ end
+ end
+
+ ##
+ # Checks that dependencies use requirements as we recommend. Warnings are
+ # issued when dependencies are open-ended or overly strict for semantic
+ # versioning.
+
+ def validate_dependencies # :nodoc:
+ seen = {}
+
dependencies.each do |dep|
+ if prev = seen[dep.name] then
+ raise Gem::InvalidSpecificationException, <<-MESSAGE
+duplicate dependency on #{dep}, (#{prev.requirement}) use:
+ add_runtime_dependency '#{dep.name}', '#{dep.requirement}', '#{prev.requirement}'
+ MESSAGE
+ end
+
+ seen[dep.name] = dep
+
prerelease_dep = dep.requirements_list.any? do |req|
Gem::Requirement.new(req).prerelease?
end
warning "prerelease dependency on #{dep} is not recommended" if
prerelease_dep
- end
- true
- ensure
- if $! or @warnings > 0 then
- alert_warning "See http://guides.rubygems.org/specification-reference/ for help"
+ overly_strict = dep.requirement.requirements.length == 1 &&
+ dep.requirement.requirements.any? do |op, version|
+ op == '~>' and
+ not version.prerelease? and
+ version.segments.length > 2
+ end
+
+ if overly_strict then
+ _, dep_version = dep.requirement.requirements.first
+
+ base = dep_version.segments.first 2
+
+ warning <<-WARNING
+pessimistic dependency on #{dep} may be overly strict
+ if #{dep.name} is semantically versioned, use:
+ add_#{dep.type}_dependency '#{dep.name}', '~> #{base.join '.'}', '>= #{dep_version}'
+ WARNING
+ end
+
+ open_ended = dep.requirement.requirements.all? do |op, version|
+ not version.prerelease? and (op == '>' or op == '>=')
+ end
+
+ if open_ended then
+ op, dep_version = dep.requirement.requirements.first
+
+ base = dep_version.segments.first 2
+
+ bugfix = if op == '>' then
+ ", '> #{dep_version}'"
+ elsif op == '>=' and base != dep_version.segments then
+ ", '>= #{dep_version}'"
+ end
+
+ warning <<-WARNING
+open-ended dependency on #{dep} is not recommended
+ if #{dep.name} is semantically versioned, use:
+ add_#{dep.type}_dependency '#{dep.name}', '~> #{base.join '.'}'#{bugfix}
+ WARNING
+ end
end
end
@@ -2633,7 +2686,10 @@ licenses is empty. Use a license abbreviation from:
return @version
end
- # FIX: have this handle the platform/new_platform/original_platform bullshit
+ def stubbed?
+ false
+ end
+
def yaml_initialize(tag, vals) # :nodoc:
vals.each do |ivar, val|
case ivar
@@ -2667,6 +2723,8 @@ licenses is empty. Use a license abbreviation from:
instance_variable_set "@#{attribute}", value
end
+
+ @installed_by_version ||= nil
end
def warning statement # :nodoc:
diff --git a/lib/rubygems/ssl_certs/DigiCertHighAssuranceEVRootCA.pem b/lib/rubygems/ssl_certs/DigiCertHighAssuranceEVRootCA.pem
new file mode 100644
index 0000000000..9e6810ab70
--- /dev/null
+++ b/lib/rubygems/ssl_certs/DigiCertHighAssuranceEVRootCA.pem
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
+ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
+MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
+LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
+RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
+PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
+xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
+Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
+hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
+EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
+MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
+FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
+nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
+eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
+hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
+Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
+vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
++OkuE6N36B9K
+-----END CERTIFICATE-----
diff --git a/lib/rubygems/test_case.rb b/lib/rubygems/test_case.rb
index de85c0fceb..08109c6405 100644
--- a/lib/rubygems/test_case.rb
+++ b/lib/rubygems/test_case.rb
@@ -241,6 +241,8 @@ class Gem::TestCase < MiniTest::Unit::TestCase
@orig_ENV_HOME = ENV['HOME']
ENV['HOME'] = @userhome
Gem.instance_variable_set :@user_home, nil
+ Gem.send :remove_instance_variable, :@ruby_version if
+ Gem.instance_variables.include? :@ruby_version
FileUtils.mkdir_p @gemhome
FileUtils.mkdir_p @userhome
@@ -376,7 +378,7 @@ class Gem::TestCase < MiniTest::Unit::TestCase
gem = File.join @tempdir, "gems", "#{spec.full_name}.gem"
- unless File.exist? gem
+ unless File.exists? gem
use_ui Gem::MockGemUi.new do
Dir.chdir @tempdir do
Gem::Package.build spec
@@ -898,14 +900,35 @@ Also, a list:
spec_fetcher.prerelease_specs[@uri] << spec.name_tuple
end
- v = Gem.marshal_version
+ # HACK for test_download_to_cache
+ unless Gem::RemoteFetcher === @fetcher then
+ v = Gem.marshal_version
- Gem::Specification.each do |spec|
- path = "#{@gem_repo}quick/Marshal.#{v}/#{spec.original_name}.gemspec.rz"
- data = Marshal.dump spec
- data_deflate = Zlib::Deflate.deflate data
- @fetcher.data[path] = data_deflate
- end unless Gem::RemoteFetcher === @fetcher # HACK for test_download_to_cache
+ specs = all.map { |spec| spec.name_tuple }
+ s_zip = util_gzip Marshal.dump Gem::NameTuple.to_basic specs
+
+ latest_specs = Gem::Specification.latest_specs.map do |spec|
+ spec.name_tuple
+ end
+
+ l_zip = util_gzip Marshal.dump Gem::NameTuple.to_basic latest_specs
+
+ prerelease_specs = prerelease.map { |spec| spec.name_tuple }
+ p_zip = util_gzip Marshal.dump Gem::NameTuple.to_basic prerelease_specs
+
+ @fetcher.data["#{@gem_repo}specs.#{v}.gz"] = s_zip
+ @fetcher.data["#{@gem_repo}latest_specs.#{v}.gz"] = l_zip
+ @fetcher.data["#{@gem_repo}prerelease_specs.#{v}.gz"] = p_zip
+
+ v = Gem.marshal_version
+
+ Gem::Specification.each do |spec|
+ path = "#{@gem_repo}quick/Marshal.#{v}/#{spec.original_name}.gemspec.rz"
+ data = Marshal.dump spec
+ data_deflate = Zlib::Deflate.deflate data
+ @fetcher.data[path] = data_deflate
+ end
+ end
nil # force errors
end
@@ -1089,6 +1112,62 @@ Also, a list:
end
##
+ # Creates a SpecFetcher pre-filled with the gems or specs defined in the
+ # block.
+ #
+ # Yields a +fetcher+ object that responds to +spec+ and +gem+. +spec+ adds
+ # a specification to the SpecFetcher while +gem+ adds both a specification
+ # and the gem data to the RemoteFetcher so the built gem can be downloaded.
+ #
+ # If only the a-3 gem is supposed to be downloaded you can save setup
+ # time by creating only specs for the other versions:
+ #
+ # spec_fetcher do |fetcher|
+ # fetcher.spec 'a', 1
+ # fetcher.spec 'a', 2, 'b' => 3 # dependency on b = 3
+ # fetcher.gem 'a', 3 do |spec|
+ # # spec is a Gem::Specification
+ # # ...
+ # end
+ # end
+
+ def spec_fetcher
+ gems = {}
+
+ fetcher = Object.new
+ fetcher.instance_variable_set :@test, self
+ fetcher.instance_variable_set :@gems, gems
+
+ def fetcher.gem name, version, dependencies = nil, &block
+ spec, gem = @test.util_gem name, version, dependencies, &block
+
+ @gems[spec] = gem
+
+ spec
+ end
+
+ def fetcher.spec name, version, dependencies = nil, &block
+ spec = @test.util_spec name, version, dependencies, &block
+
+ @gems[spec] = nil
+
+ spec
+ end
+
+ yield fetcher
+
+ util_setup_fake_fetcher unless @fetcher
+ util_setup_spec_fetcher(*gems.keys)
+
+ gems.each do |spec, gem|
+ next unless gem
+
+ @fetcher.data["http://gems.example.com/gems/#{spec.file_name}"] =
+ Gem.read_binary(gem)
+ end
+ end
+
+ ##
# Construct a new Gem::Version.
def v string
diff --git a/lib/rubygems/version.rb b/lib/rubygems/version.rb
index 15e72292b1..fda8b0b5d4 100644
--- a/lib/rubygems/version.rb
+++ b/lib/rubygems/version.rb
@@ -145,8 +145,6 @@ class Gem::Version
include Comparable
- # FIX: These are only used once, in .correct?. Do they deserve to be
- # constants?
VERSION_PATTERN = '[0-9]+(?>\.[0-9a-zA-Z]+)*(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?' # :nodoc:
ANCHORED_VERSION_PATTERN = /\A\s*(#{VERSION_PATTERN})?\s*\z/ # :nodoc: