123456789_123456789_123456789_123456789_123456789_

Class: Bundler::ParallelInstaller

Relationships & Source Files
Namespace Children
Classes:
Inherits: Object
Defined in: lib/bundler/installer/parallel_installer.rb

Class Method Summary

Instance Attribute Summary

Instance Method Summary

Constructor Details

.new(installer, all_specs, size, standalone, force, local: false, skip: nil) ⇒ ParallelInstaller

[ GitHub ]

  
# File 'lib/bundler/installer/parallel_installer.rb', line 63

def initialize(installer, all_specs, size, standalone, force, local: false, skip: nil)
  @installer = installer
  @size = size
  @standalone = standalone
  @force = force
  @local = local
  @specs = all_specs.map {|s| SpecInstallation.new(s) }
  specs_by_name = @specs.to_h {|s| [s.name, s] }
  @specs.each do |spec_install|
    spec_install.dependencies = spec_install.spec.dependencies.filter_map do |dep|
      specs_by_name[dep.name] unless dep.type == :development || dep.name == spec_install.name
    end
  end
  @specs.each do |spec_install|
    spec_install.state = :installed if skip.include?(spec_install.name)
  end if skip
  @spec_set = all_specs
  @rake = @specs.find {|s| s.name == "rake" unless s.installed? }
end

Class Method Details

.call(*args, **kwargs)

[ GitHub ]

  
# File 'lib/bundler/installer/parallel_installer.rb', line 57

def self.call(*args, **kwargs)
  new(*args, **kwargs).call
end

Instance Attribute Details

#bsd_make?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/bundler/installer/parallel_installer.rb', line 155

def bsd_make?
  return false unless Gem.freebsd_platform?
  make = ENV["MAKE"] || ENV["make"] || "make"
  !/\bgmake/i.match?(make)
end

#finished_installing?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/bundler/installer/parallel_installer.rb', line 239

def finished_installing?
  @specs.all? do |spec|
    return true if spec.failed?
    spec.installed?
  end
end

#nmake?Boolean (readonly, private)

Mirror how RubyGems' extension builder picks the make program so the jobserver is only set up when a GNU-compatible make will consume it.

[ GitHub ]

  
# File 'lib/bundler/installer/parallel_installer.rb', line 149

def nmake?
  make = ENV["MAKE"] || ENV["make"]
  make ||= "nmake" if RUBY_PLATFORM.include?("mswin")
  /\bnmake/i.match?(make.to_s)
end

#size (readonly)

[ GitHub ]

  
# File 'lib/bundler/installer/parallel_installer.rb', line 61

attr_reader :size

Instance Method Details

#call

[ GitHub ]

  
# File 'lib/bundler/installer/parallel_installer.rb', line 83

def call
  if @rake
    do_download(@rake, 0)
    do_install(@rake, 0)
    Gem::Specification.reset
  end

  if @size > 1
    install_with_worker
  else
    install_serially
  end

  handle_error if failed_specs.any?
  @specs
ensure
  @worker_pool&.stop
  @download_worker_pool&.stop
end

#do_download(spec_install, worker_num) (private)

[ GitHub ]

  
# File 'lib/bundler/installer/parallel_installer.rb', line 184

def do_download(spec_install, worker_num)
  Plugin.hook(Plugin::Events::GEM_BEFORE_INSTALL, spec_install)

  gem_installer = Bundler::GemInstaller.new(
    spec_install.spec, @installer, @standalone, worker_num, @force, @local
  )

  success, message = gem_installer.download

  if success
    spec_install.state = :downloaded
  else
    spec_install.error = "#{message}\n\n#{require_tree_for_spec(spec_install.spec)}"
    spec_install.state = :failed
  end

  spec_install
end

#do_install(spec_install, worker_num) (private)

[ GitHub ]

  
# File 'lib/bundler/installer/parallel_installer.rb', line 203

def do_install(spec_install, worker_num)
  gem_installer = Bundler::GemInstaller.new(
    spec_install.spec, @installer, @standalone, worker_num, @force, @local
  )
  success, message = gem_installer.install_from_spec
  if success
    spec_install.state = :installed
    spec_install.post_install_message = message unless message.nil?
  else
    spec_install.error = "#{message}\n\n#{require_tree_for_spec(spec_install.spec)}"
    spec_install.state = :failed
  end
  Plugin.hook(Plugin::Events::GEM_AFTER_INSTALL, spec_install)
  spec_install
end

#download_worker_pool (private)

[ GitHub ]

  
# File 'lib/bundler/installer/parallel_installer.rb', line 170

def download_worker_pool
  @download_worker_pool ||= Bundler::Worker.new(@size, "Gem Downloader",
    ->(spec_install, worker_num) { do_download(spec_install, worker_num) }, response_queue: response_queue)
end

#enqueue_specs(installed_specs) (private)

Queue every missing spec for download. #process_specs schedules each downloaded spec for installation once its dependencies are installed.

[ GitHub ]

  
# File 'lib/bundler/installer/parallel_installer.rb', line 277

def enqueue_specs(installed_specs)
  @specs.each do |spec|
    if spec.installed?
      installed_specs[spec.name] = true
      next
    end

    spec.state = :enqueued
    download_worker_pool.enq spec
  end
end

#failed_specs (private)

[ GitHub ]

  
# File 'lib/bundler/installer/parallel_installer.rb', line 105

def failed_specs
  @specs.select(&:failed?)
end

#handle_error (private)

[ GitHub ]

  
# File 'lib/bundler/installer/parallel_installer.rb', line 246

def handle_error
  errors = failed_specs.map(&:error)
  if exception = errors.find {|e| e.is_a?(Bundler::BundlerError) }
    raise exception
  end
  raise Bundler::InstallError, errors.join("\n\n")
end

#install_serially (private)

[ GitHub ]

  
# File 'lib/bundler/installer/parallel_installer.rb', line 161

def install_serially
  until finished_installing?
    raise "failed to find a spec to enqueue while installing serially" unless spec_install = @specs.find(&:ready_to_enqueue?)
    spec_install.state = :enqueued
    do_download(spec_install, 0)
    do_install(spec_install, 0)
  end
end

#install_with_worker (private)

[ GitHub ]

  
# File 'lib/bundler/installer/parallel_installer.rb', line 109

def install_with_worker
  with_jobserver do
    installed_specs = {}
    enqueue_specs(installed_specs)

    process_specs(installed_specs) until finished_installing?
  end
end

#process_specs(installed_specs) (private)

Process one completed download or installation. Downloads can finish before their dependencies are installed, so check all downloaded specs after each completion and enqueue any that are now installable.

[ GitHub ]

  
# File 'lib/bundler/installer/parallel_installer.rb', line 222

def process_specs(installed_specs)
  spec = worker_pool.deq

  if spec.installed?
    installed_specs[spec.name] = true
  elsif spec.failed?
    return
  end

  @specs.each do |candidate|
    next unless candidate.ready_to_install?(installed_specs)

    candidate.state = :installable
    worker_pool.enq(candidate, priority: candidate.enqueue_with_priority?)
  end
end

#require_tree_for_spec(spec) (private)

[ GitHub ]

  
# File 'lib/bundler/installer/parallel_installer.rb', line 254

def require_tree_for_spec(spec)
  tree = @spec_set.what_required(spec)
  gemfile_name = begin
    File.basename(SharedHelpers.default_gemfile)
  rescue GemfileNotFound
    # This runs while reporting an install error. When no Gemfile can be
    # located (e.g. Bundler used as a library), raising here would mask
    # the original error, so fall back to a generic header instead.
    "Gemfile"
  end
  t = String.new("In #{gemfile_name}:\n")
  tree.each_with_index do |s, depth|
    t << "  " * depth.succ << s.name
    unless tree.last == s
      t << %( was resolved to #{s.version}, which depends on)
    end
    t << %(\n)
  end
  t
end

#response_queue (private)

[ GitHub ]

  
# File 'lib/bundler/installer/parallel_installer.rb', line 180

def response_queue
  @response_queue ||= Thread::Queue.new
end

#with_jobserver (private)

[ GitHub ]

  
# File 'lib/bundler/installer/parallel_installer.rb', line 118

def with_jobserver
  # The jobserver hands tokens to child `make` processes through MAKEFLAGS
  # using the GNU make `--jobserver-auth` protocol. nmake, the default make
  # on mswin, instead reads MAKEFLAGS as bare option letters and aborts
  # every native extension build with `fatal error U1065: invalid option
  # '-'`. Skip the jobserver when nmake is in use. Other Windows toolchains
  # such as mingw use GNU make and keep working through the inherited pipe.
  return yield if nmake? || bsd_make?

  begin
    r, w = IO.pipe
    r.close_on_exec = false
    w.close_on_exec = false
    w.write("*" * @size)

    old_makeflags = ENV["MAKEFLAGS"]
    ENV["MAKEFLAGS"] = [old_makeflags, "--jobserver-auth=#{r.fileno},#{w.fileno}"].compact.join(" ")

    yield
  ensure
    # Restore MAKEFLAGS before closing the pipe so a close failure can't
    # leave the process with descriptors that point at a closed pipe.
    old_makeflags ? ENV["MAKEFLAGS"] = old_makeflags : ENV.delete("MAKEFLAGS")

    r&.close
    w&.close
  end
end

#worker_pool (private)

[ GitHub ]

  
# File 'lib/bundler/installer/parallel_installer.rb', line 175

def worker_pool
  @worker_pool ||= Bundler::Worker.new(@size, "Parallel Installer",
    ->(spec_install, worker_num) { do_install(spec_install, worker_num) }, response_queue: response_queue)
end