diff options
Diffstat (limited to 'lib/rubygems/request_set.rb')
| -rw-r--r-- | lib/rubygems/request_set.rb | 255 |
1 files changed, 164 insertions, 91 deletions
diff --git a/lib/rubygems/request_set.rb b/lib/rubygems/request_set.rb index 95a8eed1af..eb8b4658f3 100644 --- a/lib/rubygems/request_set.rb +++ b/lib/rubygems/request_set.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true -require 'tsort' + +require_relative "vendored_tsort" ## # A RequestSet groups a request to activate a set of dependencies. @@ -15,8 +16,7 @@ require 'tsort' # #=> ["nokogiri-1.6.0", "mini_portile-0.5.1", "pg-0.17.0"] class Gem::RequestSet - - include TSort + include Gem::TSort ## # Array of gems to install even if already installed @@ -91,7 +91,7 @@ class Gem::RequestSet # # set = Gem::RequestSet.new nokogiri, pg - def initialize *deps + def initialize(*deps) @dependencies = deps @always_install = [] @@ -108,7 +108,7 @@ class Gem::RequestSet @requests = [] @sets = [] @soft_missing = false - @sorted = nil + @sorted_requests = nil @specs = nil @vendor_set = nil @source_set = nil @@ -119,8 +119,8 @@ class Gem::RequestSet ## # Declare that a gem of name +name+ with +reqs+ requirements is needed. - def gem name, *reqs - if dep = @dependency_names[name] then + def gem(name, *reqs) + if dep = @dependency_names[name] dep.requirement.concat reqs else dep = Gem::Dependency.new name, *reqs @@ -132,7 +132,7 @@ class Gem::RequestSet ## # Add +deps+ Gem::Dependency objects to the set. - def import deps + def import(deps) @dependencies.concat deps end @@ -143,7 +143,7 @@ class Gem::RequestSet # The +installer+ will be +nil+ if a gem matching the request was already # installed. - def install options, &block # :yields: request, installer + def install(options, &block) # :yields: request, installer if dir = options[:install_dir] requests = install_into dir, false, options, &block return requests @@ -152,16 +152,40 @@ class Gem::RequestSet @prerelease = options[:prerelease] requests = [] + download_queue = Thread::Queue.new + # Create a thread-safe list of gems to download sorted_requests.each do |req| - if req.installed? then - req.spec.spec.build_extensions + download_queue << req + end - if @always_install.none? { |spec| spec == req.spec.spec } then - yield req, nil if block_given? - next + # Create N threads in a pool, have them download all the gems + threads = Array.new(Gem.configuration.concurrent_downloads) do + # When a thread pops this item, it knows to stop running. The symbol + # is queued here so that there will be one symbol per thread. + download_queue << :stop + + Thread.new do + # The pop method will block waiting for items, so the only way + # to stop a thread from running is to provide a final item that + # means the thread should stop. + while req = download_queue.pop + break if req == :stop + req.spec.download options unless req.installed? end end + end + + # Wait for all the downloads to finish before continuing + threads.each(&:value) + + # Install requested gems after they have been downloaded + sorted_requests.each do |req| + if req.installed? && @always_install.none? {|spec| spec == req.spec.spec } + req.spec.spec.build_extensions unless options[:build_extension] == false + yield req, nil if block_given? + next + end spec = begin @@ -169,17 +193,8 @@ class Gem::RequestSet yield req, installer if block_given? end rescue Gem::RuntimeRequirementNotMetError => e - recent_match = req.spec.set.find_all(req.request).sort_by(&:version).reverse_each.find do |s| - s = s.spec - s.required_ruby_version.satisfied_by?(Gem.ruby_version) && s.required_rubygems_version.satisfied_by?(Gem.rubygems_version) - end - if recent_match - suggestion = "The last version of #{req.request} to support your Ruby & RubyGems was #{recent_match.version}. Try installing it with `gem install #{recent_match.name} -v #{recent_match.version}`" - suggestion += " and then running the current command again" unless @always_install.include?(req.spec.spec) - else - suggestion = "There are no versions of #{req.request} compatible with your Ruby & RubyGems" - suggestion += ". Maybe try installing an older version of the gem you're looking for?" unless @always_install.include?(req.spec.spec) - end + suggestion = "There are no versions of #{req.request} compatible with your Ruby & RubyGems" + suggestion += ". Maybe try installing an older version of the gem you're looking for?" unless @always_install.include?(req.spec.spec) e.suggestion = suggestion raise end @@ -189,22 +204,7 @@ class Gem::RequestSet return requests if options[:gemdeps] - specs = requests.map do |request| - case request - when Gem::Resolver::ActivationRequest then - request.spec.spec - else - request - end - end - - require 'rubygems/dependency_installer' - inst = Gem::DependencyInstaller.new options - inst.installed_gems.replace specs - - Gem.done_installing_hooks.each do |hook| - hook.call inst, specs - end unless Gem.done_installing_hooks.empty? + install_hooks requests, options requests end @@ -216,7 +216,7 @@ class Gem::RequestSet # If +:without_groups+ is given in the +options+, those groups in the gem # dependencies file are not used. See Gem::Installer for other +options+. - def install_from_gemdeps options, &block + def install_from_gemdeps(options, &block) gemdeps = options[:gemdeps] @install_dir = options[:install_dir] || Gem.dir @@ -234,14 +234,10 @@ class Gem::RequestSet sorted_requests.each do |spec| puts " #{spec.full_name}" end - - if Gem.configuration.really_verbose - @resolver.stats.display - end else installed = install options, &block - if options.fetch :lock, true then + if options.fetch :lock, true lockfile = Gem::RequestSet::Lockfile.build self, gemdeps, gem_deps_api.dependencies lockfile.write @@ -251,11 +247,12 @@ class Gem::RequestSet end end - def install_into dir, force = true, options = {} - gem_home, ENV['GEM_HOME'] = ENV['GEM_HOME'], dir + def install_into(dir, force = true, options = {}) + gem_home = ENV["GEM_HOME"] + ENV["GEM_HOME"] = dir existing = force ? [] : specs_in(dir) - existing.delete_if { |s| @always_install.include? s } + existing.delete_if {|s| @always_install.include? s } dir = File.expand_path dir @@ -269,7 +266,7 @@ class Gem::RequestSet sorted_requests.each do |request| spec = request.spec - if existing.find { |s| s.full_name == spec.full_name } then + if existing.find {|s| s.full_name == spec.full_name } yield request, nil if block_given? next end @@ -281,27 +278,48 @@ class Gem::RequestSet installed << request end + install_hooks installed, options + installed ensure - ENV['GEM_HOME'] = gem_home + ENV["GEM_HOME"] = gem_home + end + + ## + # Call hooks on installed gems + + def install_hooks(requests, options) + specs = requests.map do |request| + case request + when Gem::Resolver::ActivationRequest then + request.spec.spec + else + request + end + end + + require_relative "dependency_installer" + inst = Gem::DependencyInstaller.new options + inst.installed_gems.replace specs + + Gem.done_installing_hooks.each do |hook| + hook.call inst, specs + end unless Gem.done_installing_hooks.empty? end ## # Load a dependency management file. - def load_gemdeps path, without_groups = [], installing = false + def load_gemdeps(path, without_groups = [], installing = false) @git_set = Gem::Resolver::GitSet.new @vendor_set = Gem::Resolver::VendorSet.new @source_set = Gem::Resolver::SourceSet.new @git_set.root_dir = @install_dir - lock_file = "#{File.expand_path(path)}.lock".dup.untaint - begin - tokenizer = Gem::RequestSet::Lockfile::Tokenizer.from_file lock_file - parser = tokenizer.make_parser self, [] - parser.parse - rescue Errno::ENOENT + lock_file = "#{File.expand_path(path)}.lock" + if File.exist?(lock_file) + load_lockfile lock_file end gf = Gem::RequestSet::GemDependencyAPI.new self, path @@ -310,33 +328,90 @@ class Gem::RequestSet gf.load end - def pretty_print q # :nodoc: - q.group 2, '[RequestSet:', ']' do + def load_lockfile(lock_file) # :nodoc: + require "bundler" + require "bundler/lockfile_parser" + + # Bundler::Source::Path resolves relative `remote:` paths against + # Bundler.root, which raises when there is no Gemfile in the working + # directory. Anchor it to the lockfile's directory so PATH sections in a + # `gem install -g` lockfile can be parsed without a Bundler environment. + previous_root = Bundler.instance_variable_get(:@root) + Bundler.instance_variable_set(:@root, Pathname.new(File.expand_path(File.dirname(lock_file)))) + + parser = Bundler::LockfileParser.new(File.read(lock_file), lockfile_path: lock_file) + + parser.specs.group_by(&:source).each do |source, specs| + case source + when Bundler::Source::Rubygems + remotes = source.remotes.map {|remote| Gem::Source.new(remote.to_s) } + remotes << Gem::Source.new(Gem::DEFAULT_HOST) if remotes.empty? + lock_set = Gem::Resolver::LockSet.new(remotes) + specs.each do |spec| + added = lock_set.add(spec.name, spec.version.to_s, spec.platform) + spec.dependencies.each do |dep| + added.each {|s| s.add_dependency dep } + end + end + @sets << lock_set + when Bundler::Source::Git + git_set = Gem::Resolver::GitSet.new + git_set.root_dir = @install_dir + specs.each do |spec| + git_spec = git_set.add_git_spec( + spec.name, + spec.version.to_s, + source.uri.to_s, + source.revision, + source.submodules || false + ) + spec.dependencies.each {|dep| git_spec.add_dependency dep } + end + @sets << git_set + when Bundler::Source::Path + vendor_set = Gem::Resolver::VendorSet.new + specs.each do |spec| + loaded = vendor_set.add_vendor_gem(spec.name, source.path.to_s) + spec.dependencies.each {|dep| loaded.dependencies << dep } + end + @sets << vendor_set + end + end + + parser.dependencies.each_value do |dep| + gem dep.name, *dep.requirement.as_list + end + ensure + Bundler.instance_variable_set(:@root, previous_root) if defined?(previous_root) + end + + def pretty_print(q) # :nodoc: + q.group 2, "[RequestSet:", "]" do q.breakable - if @remote then - q.text 'remote' + if @remote + q.text "remote" q.breakable end - if @prerelease then - q.text 'prerelease' + if @prerelease + q.text "prerelease" q.breakable end - if @development_shallow then - q.text 'shallow development' + if @development_shallow + q.text "shallow development" q.breakable - elsif @development then - q.text 'development' + elsif @development + q.text "development" q.breakable end - if @soft_missing then - q.text 'soft missing' + if @soft_missing + q.text "soft missing" end - q.group 2, '[dependencies:', ']' do + q.group 2, "[dependencies:", "]" do q.breakable @dependencies.map do |dep| q.text dep.to_s @@ -345,10 +420,10 @@ class Gem::RequestSet end q.breakable - q.text 'sets:' + q.text "sets:" q.breakable - q.pp @sets.map { |set| set.class } + q.pp @sets.map(&:class) end end @@ -356,7 +431,7 @@ class Gem::RequestSet # Resolve the requested dependencies and return an Array of Specification # objects to be activated. - def resolve set = Gem::Resolver::BestSet.new + def resolve(set = Gem::Resolver::BestSet.new) @sets << set @sets << @git_set @sets << @vendor_set @@ -398,33 +473,33 @@ class Gem::RequestSet end def sorted_requests - @sorted ||= strongly_connected_components.flatten + @sorted_requests ||= strongly_connected_components.flatten end def specs - @specs ||= @requests.map { |r| r.full_spec } + @specs ||= @requests.map(&:full_spec) end - def specs_in dir - Dir["#{dir}/specifications/*.gemspec"].map do |g| + def specs_in(dir) + Gem::Util.glob_files_in_dir("*.gemspec", File.join(dir, "specifications")).map do |g| Gem::Specification.load g end end - def tsort_each_node &block # :nodoc: + def tsort_each_node(&block) # :nodoc: @requests.each(&block) end - def tsort_each_child node # :nodoc: + def tsort_each_child(node) # :nodoc: node.spec.dependencies.each do |dep| - next if dep.type == :development and not @development + next if dep.type == :development && !@development - match = @requests.find { |r| - dep.match? r.spec.name, r.spec.version, @prerelease - } + match = @requests.find do |r| + dep.match?(r.spec.name, r.spec.version, r.spec.is_a?(Gem::Resolver::InstalledSpecification) || @prerelease) + end - unless match then - next if dep.type == :development and @development_shallow + unless match + next if dep.type == :development && @development_shallow next if @soft_missing raise Gem::DependencyError, "Unresolved dependency found during sorting - #{dep} (requested by #{node.spec.full_name})" @@ -433,9 +508,7 @@ class Gem::RequestSet yield match end end - end -require 'rubygems/request_set/gem_dependency_api' -require 'rubygems/request_set/lockfile' -require 'rubygems/request_set/lockfile/tokenizer' +require_relative "request_set/gem_dependency_api" +require_relative "request_set/lockfile" |
