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