123456789_123456789_123456789_123456789_123456789_

Class: Bundler::Definition

Relationships & Source Files
Super Chains via Extension / Inclusion / Inheritance
Instance Chain:
self, GemHelpers
Inherits: Object
Defined in: lib/bundler/definition.rb

Constant Summary

GemHelpers - Included

GENERICS, GENERIC_CACHE

Class Method Summary

Instance Attribute Summary

Instance Method Summary

GemHelpers - Included

Constructor Details

.new(lockfile, dependencies, sources, unlock, ruby_version = nil, optional_groups = [], gemfiles = []) ⇒ Definition

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.

Parameters:

  • lockfile (Pathname)

    Path to Gemfile.lock

  • dependencies (Array(Bundler::Dependency))

    array of dependencies from Gemfile

  • sources (Bundler::SourceList)
  • unlock (Hash, Boolean, nil)

    Gems that have been requested to be updated or true if all gems should be updated

  • ruby_version (Bundler::RubyVersion, nil) (defaults to: nil)

    Requested Ruby Version

  • optional_groups (Array(String)) (defaults to: [])

    A list of optional groups

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 55

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

  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

  @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 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

Class Method Details

.build(gemfile, lockfile, unlock) ⇒ Definition

Given a gemfile and lockfile creates a ::Bundler definition

Parameters:

  • gemfile (Pathname)

    Path to Gemfile

  • lockfile (Pathname, nil)

    Path to Gemfile.lock

  • unlock (Hash, Boolean, nil)

    Gems that have been requested to be updated or true if all gems should be updated

Raises:

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 28

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

Instance Attribute Details

#dependencies (readonly)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 10

attr_reader(
  :dependencies,
  :locked_deps,
  :locked_gems,
  :platforms,
  :requires,
  :ruby_version,
  :lockfile,
  :gemfiles
)

#gemfiles (readonly)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 10

attr_reader(
  :dependencies,
  :locked_deps,
  :locked_gems,
  :platforms,
  :requires,
  :ruby_version,
  :lockfile,
  :gemfiles
)

#has_local_dependencies?Boolean (readonly)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 315

def has_local_dependencies?
  !sources.path_sources.empty? || !sources.git_sources.empty?
end

#has_rubygems_remotes?Boolean (readonly)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 311

def has_rubygems_remotes?
  sources.rubygems_sources.any? {|s| s.remotes.any? }
end

#locked_deps (readonly)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 10

attr_reader(
  :dependencies,
  :locked_deps,
  :locked_gems,
  :platforms,
  :requires,
  :ruby_version,
  :lockfile,
  :gemfiles
)

#locked_gems (readonly)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 10

attr_reader(
  :dependencies,
  :locked_deps,
  :locked_gems,
  :platforms,
  :requires,
  :ruby_version,
  :lockfile,
  :gemfiles
)

#lockfile (readonly)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 10

attr_reader(
  :dependencies,
  :locked_deps,
  :locked_gems,
  :platforms,
  :requires,
  :ruby_version,
  :lockfile,
  :gemfiles
)

#missing_specs (readonly)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 201

def missing_specs
  missing = []
  resolve.materialize(requested_dependencies, missing)
  missing
end

#missing_specs?Boolean (readonly)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 207

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

#new_platform?Boolean (readonly)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 197

def new_platform?
  @new_platform
end

#nothing_changed?Boolean (readonly)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 536

def nothing_changed?
  !@source_changes && !@dependency_changes && !@new_platform && !@path_changes && !@local_changes && !@locked_specs_incomplete_for_platform
end

#platforms (readonly)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 10

attr_reader(
  :dependencies,
  :locked_deps,
  :locked_gems,
  :platforms,
  :requires,
  :ruby_version,
  :lockfile,
  :gemfiles
)

#requires (readonly)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 10

attr_reader(
  :dependencies,
  :locked_deps,
  :locked_gems,
  :platforms,
  :requires,
  :ruby_version,
  :lockfile,
  :gemfiles
)

#ruby_version (readonly)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 10

attr_reader(
  :dependencies,
  :locked_deps,
  :locked_gems,
  :platforms,
  :requires,
  :ruby_version,
  :lockfile,
  :gemfiles
)

#sources (readonly, private)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 533

attr_reader :sources

#unlocking?Boolean (readonly)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 540

def unlocking?
  @unlocking
end

Instance Method Details

#add_current_platform

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 521

def add_current_platform
  current_platforms.each {|platform| add_platform(platform) }
end

#add_platform(platform)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 511

def add_platform(platform)
  @new_platform ||= !@platforms.include?(platform)
  @platforms |= [platform]
end

#additional_base_requirements_for_resolve (private)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 982

def additional_base_requirements_for_resolve
  return [] unless @locked_gems && Bundler.feature_flag.only_update_to_newer_versions?
  dependencies_by_name = dependencies.inject({}) {|memo, dep| memo.update(dep.name => dep) }
  @locked_gems.specs.reduce({}) do |requirements, locked_spec|
    name = locked_spec.name
    dependency = dependencies_by_name[name]
    next requirements if @locked_gems.dependencies[name] != dependency
    next requirements if dependency && dependency.source.is_a?(Source::Path)
    dep = Gem::Dependency.new(name, ">= #{locked_spec.version}")
    requirements[name] = DepProxy.new(dep, locked_spec.platform)
    requirements
  end.values
end

#change_reason (private)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 554

def change_reason
  if unlocking?
    unlock_reason = @unlock.reject {|_k, v| Array(v).empty? }.map do |k, v|
      if v == true
        k.to_s
      else
        v = Array(v)
        "#{k}: (#{v.join(", ")})"
      end
    end.join(", ")
    return "bundler is unlocking #{unlock_reason}"
  end
  [
    [@source_changes, "the list of sources changed"],
    [@dependency_changes, "the dependencies in your gemfile changed"],
    [@new_platform, "you added a new platform to your gemfile"],
    [@path_changes, "the gemspecs for path gems changed"],
    [@local_changes, "the gemspecs for git local gems changed"],
    [@locked_specs_incomplete_for_platform, "the lockfile does not have all gems needed for the current platform"],
  ].select(&:first).map(&:last).join(", ")
end

#compute_requires (private)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 971

def compute_requires
  dependencies.reduce({}) do |requires, dep|
    next requires unless dep.should_include?
    requires[dep.name] = Array(dep.autorequire || dep.name).map do |file|
      # Allow `require: true` as an alias for `require: <name>`
      file == true ? dep.name : file
    end
    requires
  end
end

#concat_ruby_version_requirements(ruby_version, ruby_versions = []) (private)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 871

def concat_ruby_version_requirements(ruby_version, ruby_versions = [])
  return ruby_versions unless ruby_version
  if ruby_version.patchlevel
    ruby_versions << ruby_version.to_gem_version_with_patchlevel
  else
    ruby_versions.concat(ruby_version.versions.map do |version|
      requirement = Gem::Requirement.new(version)
      if requirement.exact?
        "~> #{version}.0"
      else
        requirement
      end
    end)
  end
end

#converge_dependencies (private)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 697

def converge_dependencies
  frozen = Bundler.frozen_bundle?
  (@dependencies + @locked_deps.values).each do |dep|
    locked_source = @locked_deps[dep.name]
    # This is to make sure that if bundler is installing in deployment mode and
    # after locked_source and sources don't match, we still use locked_source.
    if frozen && !locked_source.nil? &&
        locked_source.respond_to?(:source) && locked_source.source.instance_of?(Source::Path) && locked_source.source.path.exist?
      dep.source = locked_source.source
    elsif dep.source
      dep.source = sources.get(dep.source)
    end
    if dep.source.is_a?(Source::Gemspec)
      dep.platforms.concat(@platforms.map {|p| Dependency::REVERSE_PLATFORM_MAP[p] }.flatten(1)).uniq!
    end
  end

  changes = false
  # We want to know if all match, but don't want to check all entries
  # This means we need to return false if any dependency doesn't match
  # the lock or doesn't exist in the lock.
  @dependencies.each do |dependency|
    unless locked_dep = @locked_deps[dependency.name]
      changes = true
      next
    end

    # Gem::Dependency#== matches Gem::Dependency#type. As the lockfile
    # doesn't carry a notion of the dependency type, if you use
    # add_development_dependency in a gemspec that's loaded with the gemspec
    # directive, the lockfile dependencies and resolved dependencies end up
    # with a mismatch on #type. Work around that by setting the type on the
    # dep from the lockfile.
    locked_dep.instance_variable_set(:@type, dependency.type)

    # We already know the name matches from the hash lookup
    # so we only need to check the requirement now
    changes ||= dependency.requirement != locked_dep.requirement
  end

  changes
end

#converge_locals (private)

Get all locals and override their matching sources. Return true if any of the locals changed (for example, they point to a new revision) or depend on new specs.

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 609

def converge_locals
  locals = []

  Bundler.settings.local_overrides.map do |k, v|
    spec   = @dependencies.find {|s| s.name == k }
    source = spec && spec.source
    if source && source.respond_to?(:local_override!)
      source.unlock! if @unlock[:gems].include?(spec.name)
      locals << [source, source.local_override!(v)]
    end
  end

  sources_with_changes = locals.select do |source, changed|
    changed || specs_changed?(source)
  end.map(&:first)
  !sources_with_changes.each {|source| @unlock[:sources] << source.name }.empty?
end

#converge_locked_specs (private)

Remove elements from the locked specs that are expired. This will most commonly happen if the Gemfile has changed since the lockfile was last generated

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 743

def converge_locked_specs
  deps = []

  # Build a list of dependencies that are the same in the Gemfile
  # and Gemfile.lock. If the Gemfile modified a dependency, but
  # the gem in the Gemfile.lock still satisfies it, this is fine
  # too.
  @dependencies.each do |dep|
    locked_dep = @locked_deps[dep.name]

    # If the locked_dep doesn't match the dependency we're looking for then we ignore the locked_dep
    locked_dep = nil unless locked_dep == dep

    if in_locked_deps?(dep, locked_dep) || satisfies_locked_spec?(dep)
      deps << dep
    elsif dep.source.is_a?(Source::Path) && dep.current_platform? && (!locked_dep || dep.source != locked_dep.source)
      @locked_specs.each do |s|
        @unlock[:gems] << s.name if s.source == dep.source
      end

      dep.source.unlock! if dep.source.respond_to?(:unlock!)
      dep.source.specs.each {|s| @unlock[:gems] << s.name }
    end
  end

  unlock_source_unlocks_spec = Bundler.feature_flag.unlock_source_unlocks_spec?

  converged = []
  @locked_specs.each do |s|
    # Replace the locked dependency's source with the equivalent source from the Gemfile
    dep = @dependencies.find {|d| s.satisfies?(d) }
    s.source = (dep && dep.source) || sources.get(s.source)

    # Don't add a spec to the list if its source is expired. For example,
    # if you change a Git gem to RubyGems.
    next if s.source.nil?
    next if @unlock[:sources].include?(s.source.name)

    # XXX This is a backwards-compatibility fix to preserve the ability to
    # unlock a single gem by passing its name via `--source`. See issue #3759
    # TODO: delete in Bundler 2
    next if unlock_source_unlocks_spec && @unlock[:sources].include?(s.name)

    # If the spec is from a path source and it doesn't exist anymore
    # then we unlock it.

    # Path sources have special logic
    if s.source.instance_of?(Source::Path) || s.source.instance_of?(Source::Gemspec)
      new_specs = begin
        s.source.specs
      rescue PathError, GitError
        # if we won't need the source (according to the lockfile),
        # don't error if the path/git source isn't available
        next if @locked_specs.
                for(requested_dependencies, [], false, true, false).
                none? {|locked_spec| locked_spec.source == s.source }

        raise
      end

      new_spec = new_specs[s].first

      # If the spec is no longer in the path source, unlock it. This
      # commonly happens if the version changed in the gemspec
      next unless new_spec

      new_runtime_deps = new_spec.dependencies.select {|d| d.type != :development }
      old_runtime_deps = s.dependencies.select {|d| d.type != :development }
      # If the dependencies of the path source have changed and locked spec can't satisfy new dependencies, unlock it
      next unless new_runtime_deps.sort == old_runtime_deps.sort || new_runtime_deps.all? {|d| satisfies_locked_spec?(d) }

      s.dependencies.replace(new_spec.dependencies)
    end

    converged << s
  end

  resolve = SpecSet.new(converged)
  @locked_specs_incomplete_for_platform = !resolve.for(expand_dependencies(deps), @unlock[:gems], true, true)
  resolve = resolve.for(expand_dependencies(deps, true), @unlock[:gems], false, false, false)
  diff    = nil

  # Now, we unlock any sources that do not have anymore gems pinned to it
  sources.all_sources.each do |source|
    next unless source.respond_to?(:unlock!)

    unless resolve.any? {|s| s.source == source }
      diff ||= @locked_specs.to_a - resolve.to_a
      source.unlock! if diff.any? {|s| s.source == source }
    end
  end

  resolve
end

#converge_path_source_to_gemspec_source(source) (private)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 633

def converge_path_source_to_gemspec_source(source)
  return source unless source.instance_of?(Source::Path)
  gemspec_source = sources.path_sources.find {|s| s.is_a?(Source::Gemspec) && s.as_path_source == source }
  gemspec_source || source
end

#converge_path_sources_to_gemspec_sources (private)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 639

def converge_path_sources_to_gemspec_sources
  @locked_sources.map! do |source|
    converge_path_source_to_gemspec_source(source)
  end
  @locked_specs.each do |spec|
    spec.source &&= converge_path_source_to_gemspec_source(spec.source)
  end
  @locked_deps.each do |_, dep|
    dep.source &&= converge_path_source_to_gemspec_source(dep.source)
  end
end

#converge_paths (private)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 627

def converge_paths
  sources.path_sources.any? do |source|
    specs_changed?(source)
  end
end

#converge_rubygems_sources (private)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 651

def converge_rubygems_sources
  return false if Bundler.feature_flag.disable_multisource?

  changes = false

  # Get the RubyGems sources from the Gemfile.lock
  locked_gem_sources = @locked_sources.select {|s| s.is_a?(Source::Rubygems) }
  # Get the RubyGems remotes from the Gemfile
  actual_remotes = sources.rubygems_remotes

  # If there is a RubyGems source in both
  if !locked_gem_sources.empty? && !actual_remotes.empty?
    locked_gem_sources.each do |locked_gem|
      # Merge the remotes from the Gemfile into the Gemfile.lock
      changes |= locked_gem.replace_remotes(actual_remotes, Bundler.settings[:allow_deployment_source_credential_changes])
    end
  end

  changes
end

#converge_sources (private)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 672

def converge_sources
  changes = false

  changes |= converge_rubygems_sources

  # Replace the sources from the Gemfile with the sources from the Gemfile.lock,
  # if they exist in the Gemfile.lock and are `==`. If you can't find an equivalent
  # source in the Gemfile.lock, use the one from the Gemfile.
  changes |= sources.replace_sources!(@locked_sources)

  sources.all_sources.each do |source|
    # If the source is unlockable and the current command allows an unlock of
    # the source (for example, you are doing a `bundle update <foo>` of a git-pinned
    # gem), unlock it. For git sources, this means to unlock the revision, which
    # will cause the `ref` used to be the most recent for the branch (or master) if
    # an explicit `ref` is not used.
    if source.respond_to?(:unlock!) && @unlock[:sources].include?(source.name)
      source.unlock!
      changes = true
    end
  end

  changes
end

#current_dependencies

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 230

def current_dependencies
  dependencies.select(&:should_include?)
end

#current_platforms (private)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 546

def current_platforms
  current_platform = Bundler.local_platform
  [].tap do |platforms|
    platforms << current_platform if Bundler.feature_flag.specific_platform?
    platforms << generic(current_platform)
  end
end

#dependencies_for_source_changed?(source, locked_source = source) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 588

def dependencies_for_source_changed?(source, locked_source = source)
  deps_for_source = @dependencies.select {|s| s.source == source }
  locked_deps_for_source = @locked_deps.values.select {|dep| dep.source == locked_source }

  Set.new(deps_for_source) != Set.new(locked_deps_for_source)
end

#double_check_for_index(idx, dependency_names) (private)

Suppose the gem Foo depends on the gem Bar. Foo exists in Source A. Bar has some versions that exist in both sources A and B. At this point, the API request will have found all the versions of Bar in source A, but will not have found any versions of Bar from source B, which is a problem if the requested version of Foo specifically depends on a version of Bar that is only found in source B. This ensures that for each spec we found, we add all possible versions from all sources to the index.

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 286

def double_check_for_index(idx, dependency_names)
  pinned_names = pinned_spec_names
  loop do
    idxcount = idx.size

    names = :names # do this so we only have to traverse to get dependency_names from the index once
    unmet_dependency_names = lambda do
      return names unless names == :names
      new_names = sources.all_sources.map(&:dependency_names_to_double_check)
      return names = nil if new_names.compact!
      names = new_names.flatten(1).concat(dependency_names)
      names.uniq!
      names -= pinned_names
      names
    end

    sources.all_sources.each do |source|
      source.double_check_for(unmet_dependency_names)
    end

    break if idxcount == idx.size
  end
end

#ensure_equivalent_gemfile_and_lockfile(explicit_flag = false)

Raises:

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 392

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")[:global]
      "bundle config unset frozen"
    elsif Bundler.settings.locations("deployment").keys.&([:global, :local]).any?
      "bundle config unset deployment"
    else
      "bundle install --no-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 unless (dep.nil? && !lock_source.nil?) || (!dep.nil? && !lock_source.nil? && !lock_source.can_lock?(dep))
    gemfile_source_name = (dep && dep.source) || "no specified source"
    lockfile_source_name = lock_source || "no specified 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

#equivalent_rubygems_remotes?(source) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 996

def equivalent_rubygems_remotes?(source)
  return false unless source.is_a?(Source::Rubygems)

  Bundler.settings[:allow_deployment_source_credential_changes] && source.equivalent_remotes?(sources.rubygems_remotes)
end

#expand_dependencies(dependencies, remote = false) (private)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 887

def expand_dependencies(dependencies, remote = false)
  sorted_platforms = Resolver.sort_platforms(@platforms)
  deps = []
  dependencies.each do |dep|
    dep = Dependency.new(dep, ">= 0") unless dep.respond_to?(:name)
    next if !remote && !dep.current_platform?
    platforms = dep.gem_platforms(sorted_platforms)
    if platforms.empty? && !Bundler.settings[:disable_platform_warnings]
      mapped_platforms = dep.expanded_platforms
      Bundler.ui.warn \
        "The dependency #{dep} will be unused by any of the platforms Bundler is installing for. " \
        "Bundler is installing for #{@platforms.join ", "} but the dependency " \
        "is only for #{mapped_platforms.join ", "}. " \
        "To add those platforms to the bundle, " \
        "run `bundle lock --add-platform #{mapped_platforms.join " "}`."
    end
    platforms.each do |p|
      deps << DepProxy.new(dep, p) if remote || p == generic_local_platform
    end
  end
  deps
end

#expanded_dependencies (private)

This list of dependencies is only used in #resolve, so it’s OK to add the metadata dependencies here

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 851

def expanded_dependencies
  @expanded_dependencies ||= begin
    expand_dependencies(dependencies + , @remote)
  end
end

#extract_gem_info(error) (private)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 965

def extract_gem_info(error)
  # This method will extract the error message like "Could not find foo-1.2.3 in any of the sources"
  # to an array. The first element will be the gem name (e.g. foo), the second will be the version number.
  error.message.scan(/Could not find (\w)-(\d(?:\.\d))/).flatten
end

#find_indexed_specs(current_spec)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 529

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)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 525

def find_resolved_spec(current_spec)
  specs.find_by_name_and_platform(current_spec.name, current_spec.platform)
end

#gem_version_promoter

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 133

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

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 323

def groups
  dependencies.map(&:groups).flatten.uniq
end

#in_locked_deps?(dep, locked_dep) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 838

def in_locked_deps?(dep, locked_dep)
  # Because the lockfile can't link a dep to a specific remote, we need to
  # treat sources as equivalent anytime the locked dep has all the remotes
  # that the Gemfile dep does.
  locked_dep && locked_dep.source && dep.source && locked_dep.source.include?(dep.source)
end

#index

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 267

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)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 327

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

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 358

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

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 366

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

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 375

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

#lockfiles_equal?(current, proposed, preserve_unknown_sections) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 952

def lockfiles_equal?(current, proposed, preserve_unknown_sections)
  if preserve_unknown_sections
    sections_to_ignore = LockfileParser.sections_to_ignore(@locked_bundler_version)
    sections_to_ignore += LockfileParser.unknown_sections_in_lockfile(current)
    sections_to_ignore += LockfileParser::ENVIRONMENT_VERSION_SECTIONS
    pattern = /#{Regexp.union(sections_to_ignore)}\n(\s{2,}.*\n)+/
    whitespace_cleanup = /\n{2,}/
    current = current.gsub(pattern, "\n").gsub(whitespace_cleanup, "\n\n").strip
    proposed = proposed.gsub(pattern, "\n").gsub(whitespace_cleanup, "\n\n").strip
  end
  current == proposed
end

#metadata_dependencies (private)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 857

def 
  @metadata_dependencies ||= begin
    ruby_versions = concat_ruby_version_requirements(@ruby_version)
    if ruby_versions.empty? || !@ruby_version.exact?
      concat_ruby_version_requirements(RubyVersion.system)
      concat_ruby_version_requirements(locked_ruby_version_object) unless @unlock[:ruby]
    end
    [
      Dependency.new("Ruby\0", ruby_versions),
      Dependency.new("RubyGems\0", Gem::VERSION),
    ]
  end
end

#new_specs

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 189

def new_specs
  specs - @locked_specs
end

#pinned_spec_names(skip = nil) (private)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 937

def pinned_spec_names(skip = nil)
  pinned_names = []
  default = Bundler.feature_flag.disable_multisource? && sources.default_source
  @dependencies.each do |dep|
    next unless dep_source = dep.source || default
    next if dep_source == skip
    pinned_names << dep.name
  end
  pinned_names
end

#pretty_dep(dep, source = false) (private)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 576

def pretty_dep(dep, source = false)
  SharedHelpers.pretty_dependency(dep, source)
end

#remove_platform(platform)

Raises:

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 516

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

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 193

def removed_specs
  @locked_specs - specs
end

#requested_dependencies (private)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 910

def requested_dependencies
  groups = requested_groups
  groups.map!(&:to_sym)
  dependencies.reject {|d| !d.should_include? || (d.groups & groups).empty? }
end

#requested_groups (private)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 948

def requested_groups
  groups - Bundler.settings[:without] - @optional_groups + Bundler.settings[:with]
end

#requested_specs

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 222

def requested_specs
  @requested_specs ||= begin
    groups = requested_groups
    groups.map!(&:to_sym)
    specs_for(groups)
  end
end

#resolveSpecSet

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

Returns:

  • (SpecSet)

    resolved dependencies

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 245

def resolve
  @resolve ||= begin
    last_resolve = converge_locked_specs
    resolve =
      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}")
        last_resolve.merge Resolver.resolve(expanded_dependencies, index, source_requirements, last_resolve, gem_version_promoter, additional_base_requirements_for_resolve, platforms)
      end

    # filter out gems that _can_ be installed on multiple platforms, but don't need
    # to be
    resolve.for(expand_dependencies(dependencies, true), [], false, false, false)
  end
end

#resolve_remotely!

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 154

def resolve_remotely!
  raise "Specs already loaded" if @specs
  @remote = true
  sources.remote!
  specs
end

#resolve_with_cache!

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 148

def resolve_with_cache!
  raise "Specs already loaded" if @specs
  sources.cached!
  specs
end

#satisfies_locked_spec?(dep) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 845

def satisfies_locked_spec?(dep)
  @locked_specs[dep].any? {|s| s.satisfies?(dep) && (!dep.source || s.source.include?(dep.source)) }
end

#source_requirements (private)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 916

def source_requirements
  # Load all specs from remote sources
  index

  # Record the specs available in each gem's source, so that those
  # specs will be available later when the resolver knows where to
  # look for that gemspec (or its dependencies)
  default = sources.default_source
  source_requirements = { :default => default }
  default = nil unless Bundler.feature_flag.disable_multisource?
  dependencies.each do |dep|
    next unless source = dep.source || default
    source_requirements[dep.name] = source
  end
  .each do |dep|
    source_requirements[dep.name] = sources.
  end
  source_requirements["bundler"] = sources. # needs to come last to override
  source_requirements
end

#spec_git_paths

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 319

def spec_git_paths
  sources.git_sources.map {|s| File.realpath(s.path) if File.exist?(s.path) }.compact
end

#specsBundler::SpecSet

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
[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 167

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..specs.search(Gem::Dependency.new("bundler", VERSION)).last
      specs["bundler"] = bundler
    end

    specs
  end
end

#specs_changed?(source) ⇒ Boolean (private)

Check if the specs of the given source changed according to the locked source.

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 582

def specs_changed?(source)
  locked = @locked_sources.find {|s| s == source }

  !locked || dependencies_for_source_changed?(source, locked) || specs_for_source_changed?(source)
end

#specs_for(groups)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 234

def specs_for(groups)
  deps = dependencies.select {|d| (d.groups & groups).any? }
  deps.delete_if {|d| !d.should_include? }
  specs.for(expand_dependencies(deps))
end

#specs_for_source_changed?(source) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 595

def specs_for_source_changed?(source)
  locked_index = Index.new
  locked_index.use(@locked_specs.select {|s| source.can_lock?(s) })

  # order here matters, since Index#== is checking source.specs.include?(locked_index)
  locked_index != source.specs
rescue PathError, GitError => e
  Bundler.ui.debug "Assuming that #{source} has not changed since fetching its specs errored (#{e})"
  false
end

#to_lock

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 387

def to_lock
  require_relative "lockfile_generator"
  LockfileGenerator.generate(self)
end

#validate_platforms!

Raises:

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 499

def validate_platforms!
  return if @platforms.any? do |bundle_platform|
    Bundler.rubygems.platforms.any? do |local_platform|
      MatchPlatform.platforms_match?(bundle_platform, local_platform)
    end
  end

  raise ProductionError, "Your bundle only supports platforms #{@platforms.map(&:to_s)} " \
    "but your local platforms are #{Bundler.rubygems.platforms.map(&:to_s)}, and " \
    "there's no compatible match between those two lists."
end

#validate_ruby!

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 474

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!

[ GitHub ]

  
# File 'lib/bundler/definition.rb', line 469

def validate_runtime!
  validate_ruby!
  validate_platforms!
end