In Files

  • bundler/definition.rb

Class/Module Index [+]

Quicksearch

Bundler::Definition

Attributes

dependencies[R]
gemfiles[R]
locked_deps[R]
locked_gems[R]
lockfile[R]
platforms[R]
requires[R]
ruby_version[R]

Public Class Methods

build(gemfile, lockfile, unlock) click to toggle source

Given a gemfile and lockfile creates a Bundler definition

@param gemfile [Pathname] Path to Gemfile @param lockfile [Pathname,nil] Path to Gemfile.lock @param unlock [Hash, Boolean, nil] Gems that have been requested

to be updated or true if all gems should be updated

@return [Bundler::Definition]

 
               # File bundler/definition.rb, line 27
def self.build(gemfile, lockfile, unlock)
  unlock ||= {}
  gemfile = Pathname.new(gemfile).expand_path

  raise GemfileNotFound, "#{gemfile} not found" unless gemfile.file?

  Dsl.evaluate(gemfile, lockfile, unlock)
end
            
new(lockfile, dependencies, sources, unlock, ruby_version = nil, optional_groups = [], gemfiles = []) click to toggle source

How does the new system work?

  • Load information from Gemfile and Lockfile

  • Invalidate stale locked specs

  • All specs from stale source are stale

  • All specs that are reachable only through a stale dependency are stale.

  • If all fresh dependencies are satisfied by the locked

specs, then we can try to resolve locally.

@param lockfile [Pathname] Path to Gemfile.lock @param dependencies [Array(Bundler::Dependency)] array of dependencies from Gemfile @param sources [Bundler::SourceList] @param unlock [Hash, Boolean, nil] Gems that have been requested

to be updated or true if all gems should be updated

@param #ruby_version [Bundler::RubyVersion, nil] Requested Ruby Version @param optional_groups [Array(String)] A list of optional groups

 
               # File bundler/definition.rb, line 54
def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, optional_groups = [], gemfiles = [])
  if [true, false].include?(unlock)
    @unlocking_bundler = false
    @unlocking = unlock
  else
    unlock = unlock.dup
    @unlocking_bundler = unlock.delete(:bundler)
    unlock.delete_if {|_k, v| Array(v).empty? }
    @unlocking = !unlock.empty?
  end

  @dependencies    = dependencies
  @sources         = sources
  @unlock          = unlock
  @optional_groups = optional_groups
  @remote          = false
  @specs           = nil
  @ruby_version    = ruby_version
  @gemfiles        = gemfiles

  @lockfile               = lockfile
  @lockfile_contents      = String.new
  @locked_bundler_version = nil
  @locked_ruby_version    = nil
  @locked_specs_incomplete_for_platform = false
  @new_platform = nil

  if lockfile && File.exist?(lockfile)
    @lockfile_contents = Bundler.read_file(lockfile)
    @locked_gems = LockfileParser.new(@lockfile_contents)
    @locked_platforms = @locked_gems.platforms
    @platforms = @locked_platforms.dup
    @locked_bundler_version = @locked_gems.bundler_version
    @locked_ruby_version = @locked_gems.ruby_version

    if unlock != true
      @locked_deps    = @locked_gems.dependencies
      @locked_specs   = SpecSet.new(@locked_gems.specs)
      @locked_sources = @locked_gems.sources
    else
      @unlock         = {}
      @locked_deps    = {}
      @locked_specs   = SpecSet.new([])
      @locked_sources = []
    end
  else
    @unlock         = {}
    @platforms      = []
    @locked_gems    = nil
    @locked_deps    = {}
    @locked_specs   = SpecSet.new([])
    @locked_sources = []
    @locked_platforms = []
  end

  @locked_gem_sources = @locked_sources.select {|s| s.is_a?(Source::Rubygems) }
  @disable_multisource = @locked_gem_sources.all?(&:disable_multisource?)

  unless @disable_multisource
    msg = "Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. You should run `bundle update` or generate your lockfile from scratch."

    Bundler::SharedHelpers.major_deprecation 2, msg

    @sources.merged_gem_lockfile_sections!
  end

  @unlock[:gems] ||= []
  @unlock[:sources] ||= []
  @unlock[:ruby] ||= if @ruby_version && locked_ruby_version_object
    @ruby_version.diff(locked_ruby_version_object)
  end
  @unlocking ||= @unlock[:ruby] ||= (!@locked_ruby_version ^ !@ruby_version)

  add_current_platform unless current_ruby_platform_locked? || Bundler.frozen_bundle?

  converge_path_sources_to_gemspec_sources
  @path_changes = converge_paths
  @source_changes = converge_sources

  unless @unlock[:lock_shared_dependencies]
    eager_unlock = expand_dependencies(@unlock[:gems], true)
    @unlock[:gems] = @locked_specs.for(eager_unlock, [], false, false, false).map(&:name)
  end

  @dependency_changes = converge_dependencies
  @local_changes = converge_locals

  @requires = compute_requires
end
            

Public Instance Methods

add_platform(platform) click to toggle source
 
               # File bundler/definition.rb, line 521
def add_platform(platform)
  @new_platform ||= !@platforms.include?(platform)
  @platforms |= [platform]
end
            
current_dependencies() click to toggle source
 
               # File bundler/definition.rb, line 247
def current_dependencies
  dependencies.select do |d|
    d.should_include? && !d.gem_platforms(@platforms).empty?
  end
end
            
dependencies_for(groups) click to toggle source
 
               # File bundler/definition.rb, line 258
def dependencies_for(groups)
  current_dependencies.reject do |d|
    (d.groups & groups).empty?
  end
end
            
disable_multisource?() click to toggle source
 
               # File bundler/definition.rb, line 159
def disable_multisource?
  @disable_multisource
end
            
ensure_equivalent_gemfile_and_lockfile(explicit_flag = false) click to toggle source
 
               # File bundler/definition.rb, line 408
def ensure_equivalent_gemfile_and_lockfile(explicit_flag = false)
  msg = String.new
  msg << "You are trying to install in deployment mode after changing\n"               "your Gemfile. Run `bundle install` elsewhere and add the\n"               "updated #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)} to version control."

  unless explicit_flag
    suggested_command = if Bundler.settings.locations("frozen").keys.&([:global, :local]).any?
      "bundle config unset frozen"
    elsif Bundler.settings.locations("deployment").keys.&([:global, :local]).any?
      "bundle config unset deployment"
    end
    msg << "\n\nIf this is a development machine, remove the #{Bundler.default_gemfile} "                 "freeze \nby running `#{suggested_command}`."
  end

  added =   []
  deleted = []
  changed = []

  new_platforms = @platforms - @locked_platforms
  deleted_platforms = @locked_platforms - @platforms
  added.concat new_platforms.map {|p| "* platform: #{p}" }
  deleted.concat deleted_platforms.map {|p| "* platform: #{p}" }

  gemfile_sources = sources.lock_sources

  new_sources = gemfile_sources - @locked_sources
  deleted_sources = @locked_sources - gemfile_sources

  new_deps = @dependencies - @locked_deps.values
  deleted_deps = @locked_deps.values - @dependencies

  # Check if it is possible that the source is only changed thing
  if (new_deps.empty? && deleted_deps.empty?) && (!new_sources.empty? && !deleted_sources.empty?)
    new_sources.reject! {|source| (source.path? && source.path.exist?) || equivalent_rubygems_remotes?(source) }
    deleted_sources.reject! {|source| (source.path? && source.path.exist?) || equivalent_rubygems_remotes?(source) }
  end

  if @locked_sources != gemfile_sources
    if new_sources.any?
      added.concat new_sources.map {|source| "* source: #{source}" }
    end

    if deleted_sources.any?
      deleted.concat deleted_sources.map {|source| "* source: #{source}" }
    end
  end

  added.concat new_deps.map {|d| "* #{pretty_dep(d)}" } if new_deps.any?
  if deleted_deps.any?
    deleted.concat deleted_deps.map {|d| "* #{pretty_dep(d)}" }
  end

  both_sources = Hash.new {|h, k| h[k] = [] }
  @dependencies.each {|d| both_sources[d.name][0] = d }
  @locked_deps.each  {|name, d| both_sources[name][1] = d.source }

  both_sources.each do |name, (dep, lock_source)|
    next if lock_source.nil? || (dep && lock_source.can_lock?(dep))
    gemfile_source_name = (dep && dep.source) || "no specified source"
    lockfile_source_name = lock_source
    changed << "* #{name} from `#{gemfile_source_name}` to `#{lockfile_source_name}`"
  end

  reason = change_reason
  msg << "\n\n#{reason.split(", ").map(&:capitalize).join("\n")}" unless reason.strip.empty?
  msg << "\n\nYou have added to the Gemfile:\n" << added.join("\n") if added.any?
  msg << "\n\nYou have deleted from the Gemfile:\n" << deleted.join("\n") if deleted.any?
  msg << "\n\nYou have changed in the Gemfile:\n" << changed.join("\n") if changed.any?
  msg << "\n"

  raise ProductionError, msg if added.any? || deleted.any? || changed.any? || !nothing_changed?
end
            
find_indexed_specs(current_spec) click to toggle source
 
               # File bundler/definition.rb, line 541
def find_indexed_specs(current_spec)
  index[current_spec.name].select {|spec| spec.match_platform(current_spec.platform) }.sort_by(&:version)
end
            
find_resolved_spec(current_spec) click to toggle source
 
               # File bundler/definition.rb, line 537
def find_resolved_spec(current_spec)
  specs.find_by_name_and_platform(current_spec.name, current_spec.platform)
end
            
gem_version_promoter() click to toggle source
 
               # File bundler/definition.rb, line 144
def gem_version_promoter
  @gem_version_promoter ||= begin
    locked_specs =
      if unlocking? && @locked_specs.empty? && !@lockfile_contents.empty?
        # Definition uses an empty set of locked_specs to indicate all gems
        # are unlocked, but GemVersionPromoter needs the locked_specs
        # for conservative comparison.
        Bundler::SpecSet.new(@locked_gems.specs)
      else
        @locked_specs
      end
    GemVersionPromoter.new(locked_specs, @unlock[:gems])
  end
end
            
groups() click to toggle source
 
               # File bundler/definition.rb, line 339
def groups
  dependencies.map(&:groups).flatten.uniq
end
            
has_rubygems_remotes?() click to toggle source
 
               # File bundler/definition.rb, line 331
def has_rubygems_remotes?
  sources.rubygems_sources.any? {|s| s.remotes.any? }
end
            
index() click to toggle source
 
               # File bundler/definition.rb, line 287
def index
  @index ||= Index.build do |idx|
    dependency_names = @dependencies.map(&:name)

    sources.all_sources.each do |source|
      source.dependency_names = dependency_names - pinned_spec_names(source)
      idx.add_source source.specs
      dependency_names.concat(source.unmet_deps).uniq!
    end

    double_check_for_index(idx, dependency_names)
  end
end
            
lock(file, preserve_unknown_sections = false) click to toggle source
 
               # File bundler/definition.rb, line 343
def lock(file, preserve_unknown_sections = false)
  contents = to_lock

  # Convert to \r\n if the existing lock has them
  # i.e., Windows with `git config core.autocrlf=true`
  contents.gsub!(/\n/, "\r\n") if @lockfile_contents.match("\r\n")

  if @locked_bundler_version
    locked_major = @locked_bundler_version.segments.first
    current_major = Gem::Version.create(Bundler::VERSION).segments.first

    if updating_major = locked_major < current_major
      Bundler.ui.warn "Warning: the lockfile is being updated to Bundler #{current_major}, "                            "after which you will be unable to return to Bundler #{@locked_bundler_version.segments.first}."
    end
  end

  preserve_unknown_sections ||= !updating_major && (Bundler.frozen_bundle? || !(unlocking? || @unlocking_bundler))

  return if file && File.exist?(file) && lockfiles_equal?(@lockfile_contents, contents, preserve_unknown_sections)

  if Bundler.frozen_bundle?
    Bundler.ui.error "Cannot write a changed lockfile while frozen."
    return
  end

  SharedHelpers.filesystem_access(file) do |p|
    File.open(p, "wb") {|f| f.puts(contents) }
  end
end
            
locked_bundler_version() click to toggle source
 
               # File bundler/definition.rb, line 374
def locked_bundler_version
  if @locked_bundler_version && @locked_bundler_version < Gem::Version.new(Bundler::VERSION)
    new_version = Bundler::VERSION
  end

  new_version || @locked_bundler_version || Bundler::VERSION
end
            
locked_ruby_version() click to toggle source
 
               # File bundler/definition.rb, line 382
def locked_ruby_version
  return unless ruby_version
  if @unlock[:ruby] || !@locked_ruby_version
    Bundler::RubyVersion.system
  else
    @locked_ruby_version
  end
end
            
locked_ruby_version_object() click to toggle source
 
               # File bundler/definition.rb, line 391
def locked_ruby_version_object
  return unless @locked_ruby_version
  @locked_ruby_version_object ||= begin
    unless version = RubyVersion.from_string(@locked_ruby_version)
      raise LockfileError, "The Ruby version #{@locked_ruby_version} from "              "#{@lockfile} could not be parsed. "              "Try running bundle update --ruby to resolve this."
    end
    version
  end
end
            
missing_specs() click to toggle source
 
               # File bundler/definition.rb, line 212
def missing_specs
  missing = []
  resolve.materialize(requested_dependencies, missing)
  missing
end
            
missing_specs?() click to toggle source
 
               # File bundler/definition.rb, line 218
def missing_specs?
  missing = missing_specs
  return false if missing.empty?
  Bundler.ui.debug "The definition is missing #{missing.map(&:full_name)}"
  true
rescue BundlerError => e
  @index = nil
  @resolve = nil
  @specs = nil
  @gem_version_promoter = nil

  Bundler.ui.debug "The definition is missing dependencies, failed to resolve & materialize locally (#{e})"
  true
end
            
most_specific_locked_platform() click to toggle source
 
               # File bundler/definition.rb, line 531
def most_specific_locked_platform
  @platforms.min_by do |bundle_platform|
    platform_specificity_match(bundle_platform, local_platform)
  end
end
            
new_specs() click to toggle source
 
               # File bundler/definition.rb, line 204
def new_specs
  specs - @locked_specs
end
            
nothing_changed?() click to toggle source
 
               # File bundler/definition.rb, line 551
def nothing_changed?
  !@source_changes && !@dependency_changes && !@new_platform && !@path_changes && !@local_changes && !@locked_specs_incomplete_for_platform
end
            
remove_platform(platform) click to toggle source
 
               # File bundler/definition.rb, line 526
def remove_platform(platform)
  return if @platforms.delete(Gem::Platform.new(platform))
  raise InvalidOption, "Unable to remove the platform `#{platform}` since the only platforms are #{@platforms.join ", "}"
end
            
removed_specs() click to toggle source
 
               # File bundler/definition.rb, line 208
def removed_specs
  @locked_specs - specs
end
            
requested_dependencies() click to toggle source
 
               # File bundler/definition.rb, line 241
def requested_dependencies
  groups = requested_groups
  groups.map!(&:to_sym)
  dependencies_for(groups)
end
            
requested_specs() click to toggle source
 
               # File bundler/definition.rb, line 233
def requested_specs
  @requested_specs ||= begin
    groups = requested_groups
    groups.map!(&:to_sym)
    specs_for(groups)
  end
end
            
resolve() click to toggle source

Resolve all the dependencies specified in Gemfile. It ensures that dependencies that have been already resolved via locked file and are fresh are reused when resolving dependencies

@return [SpecSet] resolved dependencies

 
               # File bundler/definition.rb, line 269
def resolve
  @resolve ||= begin
    last_resolve = converge_locked_specs
    if Bundler.frozen_bundle?
      Bundler.ui.debug "Frozen, using resolution from the lockfile"
      last_resolve
    elsif !unlocking? && nothing_changed?
      Bundler.ui.debug("Found no changes, using resolution from the lockfile")
      last_resolve
    else
      # Run a resolve against the locally available gems
      Bundler.ui.debug("Found changes from the lockfile, re-resolving dependencies because #{change_reason}")
      expanded_dependencies = expand_dependencies(dependencies + metadata_dependencies, @remote)
      Resolver.resolve(expanded_dependencies, source_requirements, last_resolve, gem_version_promoter, additional_base_requirements_for_resolve, platforms)
    end
  end
end
            
resolve_remotely!() click to toggle source
 
               # File bundler/definition.rb, line 169
def resolve_remotely!
  return if @specs
  @remote = true
  sources.remote!
  specs
end
            
resolve_with_cache!() click to toggle source
 
               # File bundler/definition.rb, line 163
def resolve_with_cache!
  raise "Specs already loaded" if @specs
  sources.cached!
  specs
end
            
spec_git_paths() click to toggle source
 
               # File bundler/definition.rb, line 335
def spec_git_paths
  sources.git_sources.map {|s| File.realpath(s.path) if File.exist?(s.path) }.compact
end
            
specs() click to toggle source

For given dependency list returns a SpecSet with Gemspec of all the required dependencies.

1. The method first resolves the dependencies specified in Gemfile
2. After that it tries and fetches gemspec of resolved dependencies

@return [Bundler::SpecSet]

 
               # File bundler/definition.rb, line 182
def specs
  @specs ||= begin
    begin
      specs = resolve.materialize(requested_dependencies)
    rescue GemNotFound => e # Handle yanked gem
      gem_name, gem_version = extract_gem_info(e)
      locked_gem = @locked_specs[gem_name].last
      raise if locked_gem.nil? || locked_gem.version.to_s != gem_version || !@remote
      raise GemNotFound, "Your bundle is locked to #{locked_gem}, but that version could not "                               "be found in any of the sources listed in your Gemfile. If you haven't changed sources, "                               "that means the author of #{locked_gem} has removed it. You'll need to update your bundle "                               "to a version other than #{locked_gem} that hasn't been removed in order to install."
    end
    unless specs["bundler"].any?
      bundler = sources.metadata_source.specs.search(Gem::Dependency.new("bundler", VERSION)).last
      specs["bundler"] = bundler
    end

    specs
  end
end
            
specs_for(groups) click to toggle source
 
               # File bundler/definition.rb, line 253
def specs_for(groups)
  deps = dependencies_for(groups)
  specs.for(expand_dependencies(deps))
end
            
to_lock() click to toggle source
 
               # File bundler/definition.rb, line 403
def to_lock
  require_relative "lockfile_generator"
  LockfileGenerator.generate(self)
end
            
unlocking?() click to toggle source
 
               # File bundler/definition.rb, line 555
def unlocking?
  @unlocking
end
            
validate_platforms!() click to toggle source
 
               # File bundler/definition.rb, line 513
def validate_platforms!
  return if current_platform_locked?

  raise ProductionError, "Your bundle only supports platforms #{@platforms.map(&:to_s)} "          "but your local platform is #{Bundler.local_platform}. "          "Add the current platform to the lockfile with `bundle lock --add-platform #{Bundler.local_platform}` and try again."
end
            
validate_ruby!() click to toggle source
 
               # File bundler/definition.rb, line 488
def validate_ruby!
  return unless ruby_version

  if diff = ruby_version.diff(Bundler::RubyVersion.system)
    problem, expected, actual = diff

    msg = case problem
          when :engine
            "Your Ruby engine is #{actual}, but your Gemfile specified #{expected}"
          when :version
            "Your Ruby version is #{actual}, but your Gemfile specified #{expected}"
          when :engine_version
            "Your #{Bundler::RubyVersion.system.engine} version is #{actual}, but your Gemfile specified #{ruby_version.engine} #{expected}"
          when :patchlevel
            if !expected.is_a?(String)
              "The Ruby patchlevel in your Gemfile must be a string"
            else
              "Your Ruby patchlevel is #{actual}, but your Gemfile specified #{expected}"
            end
    end

    raise RubyVersionMismatch, msg
  end
end
            
validate_runtime!() click to toggle source
 
               # File bundler/definition.rb, line 483
def validate_runtime!
  validate_ruby!
  validate_platforms!
end