diff options
Diffstat (limited to 'lib/rubygems/resolver')
21 files changed, 1207 insertions, 0 deletions
diff --git a/lib/rubygems/resolver/activation_request.rb b/lib/rubygems/resolver/activation_request.rb new file mode 100644 index 0000000000..ca82ac408a --- /dev/null +++ b/lib/rubygems/resolver/activation_request.rb @@ -0,0 +1,138 @@ +## +# Specifies a Specification object that should be activated. +# Also contains a dependency that was used to introduce this +# activation. + +class Gem::Resolver::ActivationRequest + + attr_reader :request + + attr_reader :spec + + def initialize spec, req, others_possible = true + @spec = spec + @request = req + @others_possible = others_possible + end + + def == other + case other + when Gem::Specification + @spec == other + when Gem::Resolver::ActivationRequest + @spec == other.spec && @request == other.request + else + false + end + end + + def download path + if @spec.respond_to? :source + source = @spec.source + else + source = Gem.sources.first + end + + Gem.ensure_gem_subdirectories path + + source.download full_spec, path + end + + def full_name + @spec.full_name + end + + def full_spec + Gem::Specification === @spec ? @spec : @spec.spec + end + + def inspect # :nodoc: + 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 + ] + end + + ## + # Indicates if the requested gem has already been installed. + + def installed? + case @spec + when Gem::Resolver::VendorSpecification then + true + else + this_spec = full_spec + + Gem::Specification.any? do |s| + s == this_spec + end + end + end + + def name + @spec.name + end + + ## + # Indicate if this activation is one of a set of possible + # requests for the same Dependency request. + + def others_possible? + case @others_possible + when true, false then + @others_possible + else + not @others_possible.empty? + end + end + + ## + # Return the ActivationRequest that contained the dependency + # that we were activated for. + + def parent + @request.requester + end + + def pretty_print q # :nodoc: + q.group 2, '[Activation request', ']' do + q.breakable + q.pp @spec + + q.breakable + q.text ' for ' + q.pp @request + + 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 + + def version + @spec.version + end + +end + diff --git a/lib/rubygems/resolver/api_set.rb b/lib/rubygems/resolver/api_set.rb new file mode 100644 index 0000000000..60bf911063 --- /dev/null +++ b/lib/rubygems/resolver/api_set.rb @@ -0,0 +1,75 @@ +## +# The global rubygems pool, available via the rubygems.org API. +# Returns instances of APISpecification. + +class Gem::Resolver::APISet < Gem::Resolver::Set + + ## + # The URI for the dependency API this APISet uses. + + attr_reader :dep_uri # :nodoc: + + ## + # 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 + end + + ## + # Return an array of APISpecification objects matching + # DependencyRequest +req+. + + def find_all req + res = [] + + versions(req.name).each do |ver| + if req.dependency.match? req.name, ver[:number] + res << Gem::Resolver::APISpecification.new(self, ver) + end + end + + res + end + + ## + # A hint run by the resolver to allow the Set to fetch + # data for DependencyRequests +reqs+. + + def prefetch reqs + names = reqs.map { |r| r.dependency.name } + needed = names.find_all { |d| !@data.key?(d) } + + return if needed.empty? + + uri = @dep_uri + "?gems=#{needed.sort.join ','}" + str = Gem::RemoteFetcher.fetcher.fetch_path uri + + Marshal.load(str).each do |ver| + @data[ver[:name]] << ver + end + end + + ## + # Return data for all versions of the gem +name+. + + def versions name # :nodoc: + if @data.key?(name) + return @data[name] + end + + uri = @dep_uri + "?gems=#{name}" + str = Gem::RemoteFetcher.fetcher.fetch_path uri + + Marshal.load(str).each do |ver| + @data[ver[:name]] << ver + end + + @data[name] + end + +end + diff --git a/lib/rubygems/resolver/api_specification.rb b/lib/rubygems/resolver/api_specification.rb new file mode 100644 index 0000000000..19611e17d8 --- /dev/null +++ b/lib/rubygems/resolver/api_specification.rb @@ -0,0 +1,38 @@ +## +# Represents a specification retrieved via the rubygems.org API. +# +# This is used to avoid loading the full Specification object when all we need +# is the name, version, and dependencies. + +class Gem::Resolver::APISpecification < Gem::Resolver::Specification + + ## + # Creates an APISpecification for the given +set+ from the rubygems.org + # +api_data+. + # + # See http://guides.rubygems.org/rubygems-org-api/#misc_methods for the + # format of the +api_data+. + + def initialize(set, api_data) + super() + + @set = set + @name = api_data[:name] + @version = Gem::Version.new api_data[:number] + @platform = api_data[:platform] + @dependencies = api_data[:dependencies].map do |name, ver| + Gem::Dependency.new name, ver.split(/\s*,\s*/) + end + end + + def == other # :nodoc: + self.class === other and + @set == other.set and + @name == other.name and + @version == other.version and + @platform == other.platform and + @dependencies == other.dependencies + end + +end + diff --git a/lib/rubygems/resolver/best_set.rb b/lib/rubygems/resolver/best_set.rb new file mode 100644 index 0000000000..533a0db58f --- /dev/null +++ b/lib/rubygems/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::Resolver::BestSet < Gem::Resolver::ComposedSet + + ## + # Creates a BestSet for the given +sources+ or Gem::sources if none are + # specified. +sources+ must be a Gem::SourceList. + + def initialize sources = Gem.sources + super() + + sources.each_source do |source| + @sets << source.dependency_resolver_set + end + end + +end + diff --git a/lib/rubygems/resolver/composed_set.rb b/lib/rubygems/resolver/composed_set.rb new file mode 100644 index 0000000000..e4aa15e4d0 --- /dev/null +++ b/lib/rubygems/resolver/composed_set.rb @@ -0,0 +1,20 @@ +class Gem::Resolver::ComposedSet < Gem::Resolver::Set + + attr_reader :sets # :nodoc: + + def initialize *sets + @sets = sets + end + + def find_all req + res = [] + @sets.each { |s| res += s.find_all(req) } + res + end + + def prefetch reqs + @sets.each { |s| s.prefetch(reqs) } + end + +end + diff --git a/lib/rubygems/resolver/conflict.rb b/lib/rubygems/resolver/conflict.rb new file mode 100644 index 0000000000..b081972658 --- /dev/null +++ b/lib/rubygems/resolver/conflict.rb @@ -0,0 +1,102 @@ +## +# Used internally to indicate that a dependency conflicted +# with a spec that would be activated. + +class Gem::Resolver::Conflict + + attr_reader :activated + + 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 + + def explain + "<Conflict wanted: #{@failed_dep}, had: #{activated.spec.full_name}>" + end + + ## + # Return the 2 dependency objects that conflicted + + def conflicting_dependencies + [@failed_dep.dependency, @activated.request.dependency] + end + + ## + # Explanation of the conflict used by exceptions to print useful messages + + def explanation + activated = @activated.spec.full_name + requirement = @failed_dep.dependency.requirement + + " Activated %s instead of (%s) via:\n %s\n" % [ + activated, requirement, request_path.join(', ') + ] + end + + def for_spec?(spec) + @dependency.name == spec.name + end + + def pretty_print q # :nodoc: + q.group 2, '[Dependency conflict: ', ']' do + q.breakable + + q.text 'activated ' + q.pp @activated + + q.breakable + q.text ' dependency ' + q.pp @dependency + + q.breakable + if @dependency == @failed_dep then + q.text ' failed' + else + q.text ' failed dependency ' + q.pp @failed_dep + end + end + end + + ## + # Path of specifications that requested this dependency + + def request_path + current = requester + path = [] + + while current do + path << current.spec.full_name + + current = current.request.requester + end + + path = ['user request (gem command or Gemfile)'] if path.empty? + + path + end + + ## + # Return the Specification that listed the dependency + + def requester + @failed_dep.requester + end + +end + +Gem::Resolver::DependencyConflict = Gem::Resolver::Conflict + diff --git a/lib/rubygems/resolver/current_set.rb b/lib/rubygems/resolver/current_set.rb new file mode 100644 index 0000000000..4e8d34026b --- /dev/null +++ b/lib/rubygems/resolver/current_set.rb @@ -0,0 +1,13 @@ +## +# A set which represents the installed gems. Respects +# all the normal settings that control where to look +# for installed gems. + +class Gem::Resolver::CurrentSet < Gem::Resolver::Set + + def find_all req + req.dependency.matching_specs + end + +end + diff --git a/lib/rubygems/resolver/dependency_request.rb b/lib/rubygems/resolver/dependency_request.rb new file mode 100644 index 0000000000..e63b443c62 --- /dev/null +++ b/lib/rubygems/resolver/dependency_request.rb @@ -0,0 +1,71 @@ +## +# Used Internally. Wraps a Dependency object to also track which spec +# contained the Dependency. + +class Gem::Resolver::DependencyRequest + + attr_reader :dependency + + attr_reader :requester + + def initialize(dep, act) + @dependency = dep + @requester = act + end + + def ==(other) + case other + when Gem::Dependency + @dependency == other + when Gem::Resolver::DependencyRequest + @dependency == other.dependency && @requester == other.requester + else + false + end + end + + def matches_spec?(spec) + @dependency.matches_spec? spec + end + + def name + @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 + q.text @dependency.to_s + + q.breakable + q.text ' requested by ' + q.pp @requester + end + end + + def requirement + @dependency.requirement + end + + def to_s # :nodoc: + @dependency.to_s + end + +end + 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/resolver/index_set.rb b/lib/rubygems/resolver/index_set.rb new file mode 100644 index 0000000000..0ba3c78a44 --- /dev/null +++ b/lib/rubygems/resolver/index_set.rb @@ -0,0 +1,50 @@ +## +# The global rubygems pool represented via the traditional +# source index. + +class Gem::Resolver::IndexSet < Gem::Resolver::Set + + 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] = [] } + + list, = @f.available_specs :released + + list.each do |uri, specs| + specs.each do |n| + @all[n.name] << [uri, n] + end + end + + @specs = {} + end + + ## + # Return an array of IndexSpecification objects matching + # DependencyRequest +req+. + + def find_all req + res = [] + + name = req.dependency.name + + @all[name].each do |uri, n| + if req.dependency.match? n then + res << Gem::Resolver::IndexSpecification.new( + self, n.name, n.version, uri, n.platform) + end + end + + res + end + +end + diff --git a/lib/rubygems/resolver/index_specification.rb b/lib/rubygems/resolver/index_specification.rb new file mode 100644 index 0000000000..56fecb5753 --- /dev/null +++ b/lib/rubygems/resolver/index_specification.rb @@ -0,0 +1,69 @@ +## +# Represents a possible Specification object returned from IndexSet. Used to +# delay needed to download full Specification objects when only the +name+ +# and +version+ are needed. + +class Gem::Resolver::IndexSpecification < Gem::Resolver::Specification + + ## + # 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 + @source = source + @platform = platform.to_s + + @spec = nil + end + + ## + # The dependencies of the gem for this specification + + def dependencies + spec.dependencies + end + + def inspect # :nodoc: + '#<%s %s source %s>' % [self.class, full_name, @source] + end + + def pretty_print q # :nodoc: + q.group 2, '[Index specification', ']' do + q.breakable + q.text full_name + + unless Gem::Platform::RUBY == @platform then + q.breakable + q.text @platform.to_s + end + + q.breakable + q.text 'source ' + q.pp @source + end + end + + ## + # 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/resolver/installed_specification.rb b/lib/rubygems/resolver/installed_specification.rb new file mode 100644 index 0000000000..647ff7499a --- /dev/null +++ b/lib/rubygems/resolver/installed_specification.rb @@ -0,0 +1,34 @@ +## +# An InstalledSpecification represents a gem that is already installed +# locally. + +class Gem::Resolver::InstalledSpecification < Gem::Resolver::SpecSpecification + + def == other # :nodoc: + self.class === other and + @set == other.set and + @spec == other.spec + end + + ## + # Returns +true+ if this gem is installable for the current platform. + + 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 + + ## + # The source for this specification + + def source + @source ||= Gem::Source::Installed.new + end + +end + diff --git a/lib/rubygems/resolver/installer_set.rb b/lib/rubygems/resolver/installer_set.rb new file mode 100644 index 0000000000..73d9e39651 --- /dev/null +++ b/lib/rubygems/resolver/installer_set.rb @@ -0,0 +1,152 @@ +## +# A set of gems for installation sourced from remote sources and local .gem +# files + +class Gem::Resolver::InstallerSet < Gem::Resolver::Set + + ## + # List of Gem::Specification objects that must always be installed. + + attr_reader :always_install # :nodoc: + + ## + # Only install gems in the always_install list + + 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 # :nodoc: + + def initialize domain + @domain = domain + + @f = Gem::SpecFetcher.fetcher + + @all = Hash.new { |h,k| h[k] = [] } + @always_install = [] + @ignore_dependencies = false + @ignore_installed = false + @loaded_remote_specs = [] + @specs = {} + end + + ## + # Should local gems should be considered? + + def consider_local? # :nodoc: + @domain == :both or @domain == :local + end + + ## + # Should remote gems should be considered? + + def consider_remote? # :nodoc: + @domain == :both or @domain == :remote + end + + ## + # Returns an array of IndexSpecification objects matching DependencyRequest + # +req+. + + def find_all req + res = [] + + dep = req.dependency + + return res if @ignore_dependencies and + @always_install.none? { |spec| dep.matches_spec? spec } + + name = dep.name + + dep.matching_specs.each do |gemspec| + next if @always_install.include? 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::Resolver::IndexSpecification.new( + self, spec.name, spec.version, local_source, spec.platform) + end + end + + if consider_remote? then + load_remote_specs dep + + @all[name].each do |remote_source, n| + if dep.match? n then + res << Gem::Resolver::IndexSpecification.new( + self, n.name, n.version, remote_source, n.platform) + end + end + end + + res + end + + def inspect # :nodoc: + always_install = @always_install.map { |s| s.full_name } + + '#<%s domain: %s specs: %p always install: %p>' % [ + self.class, @domain, @specs.keys, always_install, + ] + end + + ## + # Loads remote prerelease specs if +dep+ is a prerelease dependency + + def load_remote_specs dep # :nodoc: + types = [:released] + types << :prerelease if dep.prerelease? + + types.each do |type| + next if @loaded_remote_specs.include? type + @loaded_remote_specs << type + + list, = @f.available_specs type + + list.each do |uri, specs| + specs.each do |n| + @all[n.name] << [uri, n] + end + end + end + end + + ## + # Called from IndexSpecification to get a true Specification + # object. + + def load_spec name, ver, platform, source # :nodoc: + key = "#{name}-#{ver}-#{platform}" + + @specs.fetch key do + tuple = Gem::NameTuple.new name, ver, platform + + @specs[key] = source.fetch_spec tuple + end + end + + def pretty_print q # :nodoc: + q.group 2, '[InstallerSet', ']' do + q.breakable + q.text "domain: #{@domain}" + + q.breakable + q.text 'specs: ' + q.pp @specs.keys + + q.breakable + q.text 'always install: ' + q.pp @always_install + end + end + +end + diff --git a/lib/rubygems/resolver/lock_set.rb b/lib/rubygems/resolver/lock_set.rb new file mode 100644 index 0000000000..6885e70945 --- /dev/null +++ b/lib/rubygems/resolver/lock_set.rb @@ -0,0 +1,60 @@ +## +# A set of gems from a gem dependencies lockfile. + +class Gem::Resolver::LockSet < Gem::Resolver::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::Resolver::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/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/resolver/set.rb b/lib/rubygems/resolver/set.rb new file mode 100644 index 0000000000..32c137ef6b --- /dev/null +++ b/lib/rubygems/resolver/set.rb @@ -0,0 +1,27 @@ +## +# Resolver sets are used to look up specifications (and their +# dependencies) used in resolution. This set is abstract. + +class Gem::Resolver::Set + + ## + # The find_all method must be implemented. It returns all Resolver + # 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/resolver/spec_specification.rb b/lib/rubygems/resolver/spec_specification.rb new file mode 100644 index 0000000000..0c411bdf5f --- /dev/null +++ b/lib/rubygems/resolver/spec_specification.rb @@ -0,0 +1,58 @@ +## +# The Resolver::SpecSpecification contains common functionality for +# Resolver specifications that are backed by a Gem::Specification. + +class Gem::Resolver::SpecSpecification < Gem::Resolver::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/resolver/specification.rb b/lib/rubygems/resolver/specification.rb new file mode 100644 index 0000000000..7dd4c2e829 --- /dev/null +++ b/lib/rubygems/resolver/specification.rb @@ -0,0 +1,60 @@ +## +# A Resolver::Specification contains a subset of the information +# contained in a Gem::Specification. Only the information necessary for +# dependency resolution in the resolver is included. + +class Gem::Resolver::Specification + + ## + # The dependencies of the gem for this specification + + attr_reader :dependencies + + ## + # The name of the gem for this specification + + attr_reader :name + + ## + # The platform this gem works on. + + attr_reader :platform + + ## + # The set this specification came from. + + attr_reader :set + + ## + # The source for this specification + + attr_reader :source + + ## + # The 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/resolver/vendor_set.rb b/lib/rubygems/resolver/vendor_set.rb new file mode 100644 index 0000000000..e9cbcd8303 --- /dev/null +++ b/lib/rubygems/resolver/vendor_set.rb @@ -0,0 +1,66 @@ +## +# A VendorSet represents gems that have been unpacked into a specific +# directory that contains a gemspec. +# +# This is used for gem dependency file support. +# +# Example: +# +# 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::Resolver::VendorSet < Gem::Resolver::Set + + 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 # :nodoc: + gemspec = File.join directory, "#{name}.gemspec" + + spec = Gem::Specification.load gemspec + + raise Gem::GemNotFoundException, + "unable to find #{gemspec} for gem #{name}" unless spec + + key = "#{spec.name}-#{spec.version}-#{spec.platform}" + + @specs[key] = spec + @directories[spec] = directory + end + + ## + # Returns an Array of VendorSpecification objects matching the + # DependencyRequest +req+. + + def find_all req + @specs.values.select do |spec| + req.matches_spec? spec + end.map do |spec| + source = Gem::Source::Vendor.new @directories[spec] + Gem::Resolver::VendorSpecification.new self, spec, source + end + end + + ## + # Loads a spec with the given +name+, +version+ and +platform+. Since the + # +source+ is defined when the specification was added to index it is not + # used. + + def load_spec name, version, platform, source # :nodoc: + key = "#{name}-#{version}-#{platform}" + + @specs.fetch key + end + +end + diff --git a/lib/rubygems/resolver/vendor_specification.rb b/lib/rubygems/resolver/vendor_specification.rb new file mode 100644 index 0000000000..24e033d084 --- /dev/null +++ b/lib/rubygems/resolver/vendor_specification.rb @@ -0,0 +1,16 @@ +## +# 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. + +class Gem::Resolver::VendorSpecification < Gem::Resolver::SpecSpecification + + def == other # :nodoc: + self.class === other and + @set == other.set and + @spec == other.spec and + @source == other.source + end + +end + |