123456789_123456789_123456789_123456789_123456789_

Class: Bundler::Definition

Relationships & Source Files
Inherits: Object
Defined in: lib/bundler/definition.rb

Class Attribute Summary

  • .no_lock rw

    Do not create or modify a lockfile (Makes #lock a noop).

Class Method Summary

Instance Attribute Summary

Instance Method Summary

Constructor Details

.new(lockfile, dependencies, sources, unlock, ruby_version = nil, optional_groups = [], gemfiles = [], overrides = []) ⇒ 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 65

def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, optional_groups = [], gemfiles = [], overrides = [])
  unlock ||= {}

  if unlock == true
    @unlocking_all = true
    strict = false
    @unlocking_bundler = false
    @unlocking = unlock
    @sources_to_unlock = []
    @unlocking_ruby = false
    @explicit_unlocks = []
    conservative = false
  else
    @unlocking_all = false
    strict = unlock.delete(:strict)
    @unlocking_bundler = unlock.delete(:bundler)
    @unlocking = unlock.any? {|_k, v| !Array(v).empty? }
    @sources_to_unlock = unlock.delete(:sources) || []
    @unlocking_ruby = unlock.delete(:ruby)
    @explicit_unlocks = unlock.delete(:gems) || []
    conservative = unlock.delete(:conservative)
  end

  @dependencies    = dependencies
  @sources         = sources
  @optional_groups = optional_groups
  @prefer_local    = false
  @specs           = nil
  @ruby_version    = ruby_version
  @gemfiles        = gemfiles
  @overrides       = overrides

  @lockfile               = lockfile
  @lockfile_contents      = String.new

  @locked_bundler_version = nil
  @resolved_bundler_version = nil

  @locked_ruby_version = nil
  @new_platforms = []
  @removed_platforms = []
  @originally_invalid_platforms = []

  if lockfile_exists?
    @lockfile_contents = Bundler.read_file(lockfile)
    @locked_gems = LockfileParser.new(@lockfile_contents, strict: strict)
    @locked_platforms = @locked_gems.platforms
    @most_specific_locked_platform = @locked_gems.most_specific_locked_platform
    @platforms = @locked_platforms.dup
    @locked_bundler_version = @locked_gems.bundler_version
    @locked_ruby_version = @locked_gems.ruby_version
    @locked_deps = @locked_gems.dependencies
    Override.attach(@locked_gems.specs, @overrides)
    @originally_locked_specs = SpecSet.new(@locked_gems.specs)
    @originally_locked_sources = @locked_gems.sources
    @locked_checksums = @locked_gems.checksums

    if @unlocking_all
      @locked_specs   = SpecSet.new([])
      @locked_sources = []
    else
      @locked_specs   = @originally_locked_specs
      @locked_sources = @originally_locked_sources
    end

    locked_gem_sources = @originally_locked_sources.select {|s| s.is_a?(Source::Rubygems) }
    multisource_lockfile = locked_gem_sources.size == 1 && locked_gem_sources.first.multiple_remotes?

    if multisource_lockfile
      msg = "Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure."

      Bundler::SharedHelpers.feature_removed! msg
    end
  else
    @locked_gems = nil
    @locked_platforms = []
    @most_specific_locked_platform = nil
    @platforms      = []
    @locked_deps    = {}
    @locked_specs   = SpecSet.new([])
    @locked_sources = []
    @originally_locked_specs = @locked_specs
    @originally_locked_sources = @locked_sources
    @locked_checksums = Bundler.settings[:lockfile_checksums]
  end

  @unlocking_ruby ||= if @ruby_version && locked_ruby_version_object
    @ruby_version.diff(locked_ruby_version_object)
  end
  @unlocking ||= @unlocking_ruby ||= (!@locked_ruby_version ^ !@ruby_version)

  @current_platform_missing = add_current_platform unless Bundler.frozen_bundle?

  @source_changes = converge_sources
  @path_changes = converge_paths

  if conservative
    @gems_to_unlock = @explicit_unlocks.any? ? @explicit_unlocks : @dependencies.map(&:name)
  else
    eager_unlock = @explicit_unlocks.map {|name| Dependency.new(name, ">= 0") }
    @gems_to_unlock = @locked_specs.for(eager_unlock, platforms).map(&:name).uniq
  end

  @dependency_changes = converge_dependencies
  @local_changes = converge_locals
  @override_changes = converge_overrides

  check_lockfile
end

Class Attribute Details

.no_lock (rw)

Do not create or modify a lockfile (Makes #lock a noop)

[ GitHub ]

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

attr_accessor :no_lock

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 35

def self.build(gemfile, lockfile, unlock)
  unlock ||= {}
  gemfile = Pathname.new(gemfile).expand_path

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

  Plugin.hook(Plugin::Events::GEM_BEFORE_EVAL, gemfile, lockfile)
  Dsl.evaluate(gemfile, lockfile, unlock).tap do |definition|
    Plugin.hook(Plugin::Events::GEM_AFTER_EVAL, definition)
  end
end

Instance Attribute Details

#current_platform_locked? ⇒ Boolean (readonly, private)

[ GitHub ]

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

def current_platform_locked?
  @platforms.any? do |bundle_platform|
    Bundler.generic_local_platform == bundle_platform || Bundler.local_platform === bundle_platform
  end
end

#dependencies (readonly)

[ GitHub ]

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

attr_reader(
  :dependencies,
  :locked_checksums,
  :locked_deps,
  :locked_gems,
  :overrides,
  :platforms,
  :ruby_version,
  :lockfile,
  :gemfiles,
  :sources
)

#empty_checksums_actionable? ⇒ Boolean (readonly, private)

Only a remote fetch can fill an empty CHECKSUMS entry, so it justifies a resolution only when one is coming. Resolving locally for it would repeat on every Bundler.setup without changing the lockfile. Frozen mode still has to refuse the entry.

[ GitHub ]

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

def empty_checksums_actionable?
  return false unless @locked_spec_with_empty_checksums

  Bundler.frozen_bundle? || !sources.local_mode?
end

#gemfiles (readonly)

[ GitHub ]

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

attr_reader(
  :dependencies,
  :locked_checksums,
  :locked_deps,
  :locked_gems,
  :overrides,
  :platforms,
  :ruby_version,
  :lockfile,
  :gemfiles,
  :sources
)

#install_needed? ⇒ Boolean (readonly, private)

[ GitHub ]

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

def install_needed?
  resolve_needed? || missing_specs?
end

#locked_checksums (readonly)

[ GitHub ]

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

attr_reader(
  :dependencies,
  :locked_checksums,
  :locked_deps,
  :locked_gems,
  :overrides,
  :platforms,
  :ruby_version,
  :lockfile,
  :gemfiles,
  :sources
)

#locked_deps (readonly)

[ GitHub ]

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

attr_reader(
  :dependencies,
  :locked_checksums,
  :locked_deps,
  :locked_gems,
  :overrides,
  :platforms,
  :ruby_version,
  :lockfile,
  :gemfiles,
  :sources
)

#locked_gems (readonly)

[ GitHub ]

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

attr_reader(
  :dependencies,
  :locked_checksums,
  :locked_deps,
  :locked_gems,
  :overrides,
  :platforms,
  :ruby_version,
  :lockfile,
  :gemfiles,
  :sources
)

#lockfile (rw)

[ GitHub ]

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

attr_reader(
  :dependencies,
  :locked_checksums,
  :locked_deps,
  :locked_gems,
  :overrides,
  :platforms,
  :ruby_version,
  :lockfile,
  :gemfiles,
  :sources
)

#lockfile=(value) (rw)

[ GitHub ]

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

attr_writer :lockfile, :overrides

#lockfile_exists? ⇒ Boolean (readonly, private)

[ GitHub ]

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

def lockfile_exists?
  lockfile && File.exist?(lockfile)
end

#missing_specs (readonly)

[ GitHub ]

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

def missing_specs
  preload_git_sources
  resolve.missing_specs_for(requested_dependencies)
end

#missing_specs? ⇒ Boolean (readonly)

[ GitHub ]

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

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
  @resolve = nil
  @resolver = nil
  @resolution_base = nil
  @source_requirements = nil
  @specs = nil

  Bundler.ui.debug "The definition is missing dependencies, failed to resolve & materialize locally (#{e})"
  true
end

#no_resolve_needed? ⇒ Boolean (readonly)

[ GitHub ]

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

def no_resolve_needed?
  !resolve_needed?
end

#nothing_changed? ⇒ Boolean (readonly)

[ GitHub ]

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

def nothing_changed?
  !something_changed?
end

#overrides (rw)

[ GitHub ]

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

attr_reader(
  :dependencies,
  :locked_checksums,
  :locked_deps,
  :locked_gems,
  :overrides,
  :platforms,
  :ruby_version,
  :lockfile,
  :gemfiles,
  :sources
)

#overrides=(value) (rw)

[ GitHub ]

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

attr_writer :lockfile, :overrides

#platforms (readonly)

[ GitHub ]

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

attr_reader(
  :dependencies,
  :locked_checksums,
  :locked_deps,
  :locked_gems,
  :overrides,
  :platforms,
  :ruby_version,
  :lockfile,
  :gemfiles,
  :sources
)

#precompute_source_requirements_for_indirect_dependencies? ⇒ Boolean (readonly, private)

[ GitHub ]

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

def precompute_source_requirements_for_indirect_dependencies?
  if sources.non_global_rubygems_sources.all?(&:dependency_api_available?)
    true
  else
    non_dependency_api_warning
    false
  end
end

#resolve_needed? ⇒ Boolean (readonly, private)

[ GitHub ]

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

def resolve_needed?
  unlocking? || something_changed?
end

#ruby_version (readonly)

[ GitHub ]

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

attr_reader(
  :dependencies,
  :locked_checksums,
  :locked_deps,
  :locked_gems,
  :overrides,
  :platforms,
  :ruby_version,
  :lockfile,
  :gemfiles,
  :sources
)

#should_add_extra_platforms? ⇒ Boolean (readonly, private)

[ GitHub ]

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

def should_add_extra_platforms?
  !lockfile_exists? && Bundler::MatchPlatform.generic_local_platform_is_ruby? && !Bundler.settings[:force_ruby_platform]
end

#something_changed? ⇒ Boolean (readonly, private)

[ GitHub ]

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

def something_changed?
  return true unless lockfile_exists?

  @source_changes ||
    @dependency_changes ||
    @current_platform_missing ||
    @new_platforms.any? ||
    @path_changes ||
    @local_changes ||
    @missing_lockfile_dep ||
    @unlocking_bundler ||
    @locked_spec_with_missing_checksums ||
    empty_checksums_actionable? ||
    @locked_spec_with_missing_deps ||
    @locked_spec_with_invalid_deps ||
    @override_changes
end

#sources (readonly)

[ GitHub ]

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

attr_reader(
  :dependencies,
  :locked_checksums,
  :locked_deps,
  :locked_gems,
  :overrides,
  :platforms,
  :ruby_version,
  :lockfile,
  :gemfiles,
  :sources
)

#unlocking? ⇒ Boolean (readonly)

[ GitHub ]

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

def unlocking?
  @unlocking
end

Instance Method Details

#add_checksums

[ GitHub ]

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

def add_checksums
  require "rubygems/package"

  @locked_checksums = true

  setup_domain!(add_checksums: true)

  # force materialization to real specifications, so that checksums are fetched
  specs.each do |spec|
    next unless spec.source.is_a?(Bundler::Source::Rubygems)
    # Checksum was fetched from the compact index API.
    next if !spec.source.checksum_store.missing?(spec) && !spec.source.checksum_store.empty?(spec)
    # The gem isn't installed, can't compute the checksum.
    next unless spec.loaded_from

    package = Gem::Package.new(spec.source.cached_built_in_gem(spec))
    checksum = Checksum.from_gem_package(package)
    spec.source.checksum_store.register(spec, checksum)
  end
end

#add_current_platform (private)

[ GitHub ]

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

def add_current_platform
  return if @platforms.include?(Bundler.local_platform)

  @most_specific_non_local_locked_platform = find_most_specific_locked_platform
  return if @most_specific_non_local_locked_platform

  @platforms << Bundler.local_platform
  true
end

#add_platform(platform)

[ GitHub ]

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

def add_platform(platform)
  return if @platforms.include?(platform)

  @new_platforms << platform
  @platforms << platform
end

#additional_base_requirements_to_force_updates(resolution_base) (private)

[ GitHub ]

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

def additional_base_requirements_to_force_updates(resolution_base)
  return resolution_base if @explicit_unlocks.empty?
  full_update = SpecSet.new(new_resolver_for_full_update.start)
  @explicit_unlocks.each do |name|
    version = full_update.version_for(name)
    resolution_base.base_requirements[name] = Gem::Requirement.new("= #{version}") if version
  end
  resolution_base
end

#additional_base_requirements_to_prevent_downgrades(resolution_base) (private)

[ GitHub ]

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

def additional_base_requirements_to_prevent_downgrades(resolution_base)
  return resolution_base unless @locked_gems
  @originally_locked_specs.each do |locked_spec|
    next if locked_spec.source.is_a?(Source::Path) || locked_spec.source_changed?

    name = locked_spec.name
    next if @changed_dependencies.include?(name)

    resolution_base.base_requirements[name] = Gem::Requirement.new(">= #{locked_spec.version}")
  end
  resolution_base
end

#apply_override_to(dep) (private)

[ GitHub ]

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

def apply_override_to(dep)
  override = Override.find_for(@overrides, dep.name, :version)
  return dep unless override
  new_dep = dep.dup
  new_dep.instance_variable_set(:@requirement, override.apply_to(dep.requirement))
  new_dep
end

#apply_overrides_to(deps) (private)

[ GitHub ]

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

def apply_overrides_to(deps)
  return deps if @overrides.empty?
  deps.map {|dep| apply_override_to(dep) }
end

#bundler_version_to_lock

[ GitHub ]

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

def bundler_version_to_lock
  @resolved_bundler_version || Bundler.gem_version
end

#change_reason (private)

[ GitHub ]

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

def change_reason
  if resolve_needed?
    if unlocking?
      unlocking_reason
    else
      lockfile_changed_reason
    end
  else
    "some dependencies were deleted from your gemfile"
  end
end

#check!

[ GitHub ]

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

def check!
  # If dependencies have changed, we need to resolve remotely. Otherwise,
  # since we'll be resolving with a single local source, we may end up
  # locking gems under the wrong source in the lockfile, and missing lockfile
  # checksums
  resolve_remotely! if @dependency_changes

  # Now do a local only resolve, to verify if any gems are missing locally
  sources.local_only!
  resolve
end

#check_lockfile (private)

[ GitHub ]

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

def check_lockfile
  @locked_spec_with_invalid_deps = nil
  @locked_spec_with_missing_deps = nil
  @locked_spec_with_missing_checksums = nil
  @locked_spec_with_empty_checksums = nil

  missing_deps = []
  missing_checksums = []
  empty_checksums = []
  invalid = []

  @locked_specs.each do |s|
    if @locked_checksums
      checksum_store = s.source.checksum_store

      if checksum_store.missing?(s)
        missing_checksums << s
      elsif checksum_store.empty?(s)
        empty_checksums << s
      end
    end

    validation = @locked_specs.validate_deps(s)

    missing_deps << s if validation == :missing
    invalid << s if validation == :invalid
  end

  @locked_spec_with_missing_checksums = missing_checksums.first.name if missing_checksums.any?
  @locked_spec_with_empty_checksums = empty_checksums.first.name if empty_checksums.any?

  if missing_deps.any?
    @locked_specs.delete(missing_deps)

    @locked_spec_with_missing_deps = missing_deps.first.name
  end

  if invalid.any?
    @locked_specs.delete(invalid)

    @locked_spec_with_invalid_deps = invalid.first.name
  end
end

#converge_dependencies (private)

[ GitHub ]

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

def converge_dependencies
  @missing_lockfile_dep = nil
  @changed_dependencies = []

  @dependencies.each do |dep|
    if dep.source
      dep.source = sources.get(dep.source)
    end
    next unless relevant_deps?(dep)

    name = dep.name

    dep_changed = @locked_deps[name].nil?

    unless name == "bundler"
      locked_specs = @originally_locked_specs[name]

      if locked_specs.empty?
        @missing_lockfile_dep = name if dep_changed == false
      else
        if locked_specs.map(&:source).uniq.size > 1
          @locked_specs.delete(locked_specs.select {|s| s.source != dep.source })
        end

        unless apply_override_to(dep).matches_spec?(locked_specs.first)
          @gems_to_unlock << name
          dep_changed = true
        end
      end
    end

    @changed_dependencies << name if dep_changed
  end

  converge_overrides_outside_dependencies
  converge_rewritten_dependencies

  @changed_dependencies.any?
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 994

def converge_locals
  locals = []

  Bundler.settings.local_overrides.map do |k, v|
    spec   = @dependencies.find {|s| s.name == k }
    source = spec&.source
    if source&.respond_to?(:local_override!)
      source.unlock! if @gems_to_unlock.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| @sources_to_unlock << 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 1203

def converge_locked_specs
  converged = converge_specs(@locked_specs)

  resolve = SpecSet.new(converged)

  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_overrides (private)

The lockfile records the Gemfile's overrides, so a change to them is a change to the lockfile even when every locked version still satisfies them.

[ GitHub ]

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

def converge_overrides
  return false unless @locked_gems

  overrides_to_lock.sort != locked_overrides_to_lock.sort
end

#converge_overrides_outside_dependencies (private)

[ GitHub ]

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

def converge_overrides_outside_dependencies
  @overrides.each do |override|
    # :all and metadata overrides are intentionally not pre-unlocked.
    # Unlocking for them would re-resolve gems the user did not ask to
    # move. Install-time compatibility checks already honor them for locked
    # specs, and they affect resolution on a fresh lock or when the user
    # runs `bundle update`. Changing one still rewrites the OVERRIDES
    # section (see #converge_overrides).
    next unless override.target.is_a?(String) && override.field == :version

    name = override.target
    # Overrides on direct deps are detected in the per-dep
    # converge_dependencies loop via apply_override_to + matches_spec?.
    next if @dependencies.any? {|d| d.name == name }

    locked_specs = @originally_locked_specs[name]
    next if locked_specs.empty?
    # Only a version string constrains on its own. :ignore_upper and nil
    # loosen requirements the lockfile already satisfies.
    next if override.apply_to(Gem::Requirement.default).satisfied_by?(locked_specs.first.version)

    @gems_to_unlock << name
    @changed_dependencies << name
  end
end

#converge_paths (private)

[ GitHub ]

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

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

#converge_rewritten_dependencies (private)

The lockfile keeps the upstream dependencies of every gem, so a gem that a from:/to: override cut loose stays locked until a resolution drops it.

[ GitHub ]

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

def converge_rewritten_dependencies
  @overrides.each do |override|
    next unless override.from

    name = override.target
    next if @originally_locked_specs[name].empty?
    next if @dependencies.any? {|d| d.name == name }

    still_required = @originally_locked_specs.any? do |s|
      Override.rewrite_dependencies(@overrides, s.name, s.runtime_dependencies).any? {|d| d.name == name }
    end
    next if still_required

    @gems_to_unlock << name
    @changed_dependencies << name
  end
end

#converge_sources (private)

[ GitHub ]

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

def converge_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|
    # has to be done separately, because we want to keep the locked checksum
    # store for a source, even when doing a full update
    if @locked_checksums && @locked_gems && locked_source = @originally_locked_sources.find {|s| s == source && !s.equal?(source) }
      source.checksum_store.merge!(locked_source.checksum_store)
    end
    # 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!) && @sources_to_unlock.include?(source.name)
      source.unlock!
      changes = true
    end
  end

  sources..checksum_store.merge!(@locked_gems..checksum_store) if @locked_gems

  changes
end

#converge_specs(specs) (private)

[ GitHub ]

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

def converge_specs(specs)
  converged = []
  deps = []

  specs.each do |s|
    name = s.name
    next if @gems_to_unlock.include?(name)

    dep = @dependencies.find {|d| s.satisfies?(d) }
    lockfile_source = s.source

    if dep
      replacement_source = dep.source

      deps << dep if !replacement_source || lockfile_source.include?(replacement_source) || new_deps.include?(dep)
    else
      parent_dep = @dependencies.find do |d|
        next unless d.source && d.source != lockfile_source
        next if d.source.is_a?(Source::Gemspec)

        parent_locked_specs = @originally_locked_specs[d.name]

        parent_locked_specs.any? do |parent_spec|
          parent_spec.runtime_dependencies.any? {|rd| rd.name == s.name }
        end
      end

      if parent_dep && parent_dep.source.is_a?(Source::Path) && parent_dep.source.specs[s]&.any?
        replacement_source = parent_dep.source
      else
        replacement_source = sources.get(lockfile_source)
      end
    end

    # Replace the locked dependency's source with the equivalent source from the Gemfile
    s.source = replacement_source || default_source
    next if s.source_changed?

    source = s.source
    next if @sources_to_unlock.include?(source.name)

    # Path sources have special logic
    if source.is_a?(Source::Path)
      new_spec = source.specs[s].first
      if new_spec
        s.runtime_dependencies.replace(new_spec.runtime_dependencies)
      else
        # If the spec is no longer in the path source, unlock it. This
        # commonly happens if the version changed in the gemspec
        @gems_to_unlock << name
      end
    end

    converged << s
  end

  filter_specs(converged, deps, skips: @gems_to_unlock)
end

#cooldown_skipped

Versions excluded by cooldown during the last resolution, one entry per gem with the newest skipped version. Empty when no resolution ran.

[ GitHub ]

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

def cooldown_skipped
  @cooldown_skipped || []
end

#current_dependencies

[ GitHub ]

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

def current_dependencies
  filter_relevant(dependencies)
end

#current_locked_dependencies

[ GitHub ]

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

def current_locked_dependencies
  filter_relevant(locked_dependencies)
end

#default_source (private)

[ GitHub ]

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

def default_source
  sources.default_source
end

#deleted_deps

[ GitHub ]

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

def deleted_deps
  @deleted_deps ||= locked_dependencies - @dependencies
end

#dependencies_for(groups)

[ GitHub ]

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

def dependencies_for(groups)
  groups.map!(&:to_sym)
  deps = current_dependencies # always returns a new array
  deps.select! do |d|
    d.groups.intersect?(groups)
  end
  deps
end

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

[ GitHub ]

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

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

  deps_for_source.uniq.sort != locked_deps_for_source.sort
end

#dependencies_with_bundler (private)

[ GitHub ]

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

def dependencies_with_bundler
  return dependencies unless @unlocking_bundler
  return dependencies if dependencies.any? {|d| d.name == "bundler" }

  [Dependency.new("bundler", @unlocking_bundler)] + dependencies
end

#ensure_equivalent_gemfile_and_lockfile(explicit_flag = false)

Raises:

[ GitHub ]

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

def ensure_equivalent_gemfile_and_lockfile(explicit_flag = false)
  return unless Bundler.frozen_bundle?

  raise ProductionError, "Frozen mode is set, but there's no lockfile" unless lockfile_exists?

  msg = lockfile_changes_summary("frozen mode is set")
  return unless msg

  unless explicit_flag
    suggested_command = unless Bundler.settings.locations("frozen").keys.include?(:env)
      "bundle config set frozen false"
    end
    msg << "\n\nIf this is a development machine, remove the #{SharedHelpers.relative_lockfile_path} " \
           "freeze by running `#{suggested_command}`." if suggested_command
  end

  raise ProductionError, msg
end

#excluded_git_sources (private)

Git sources that should be excluded (only used by --without groups)

[ GitHub ]

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

def excluded_git_sources
  sources.git_sources - needed_git_sources
end

#expanded_dependencies (private)

[ GitHub ]

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

def expanded_dependencies
  apply_overrides_to(dependencies_with_bundler) + 
end

#filter_relevant(dependencies)

[ GitHub ]

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

def filter_relevant(dependencies)
  dependencies.select do |d|
    relevant_deps?(d)
  end
end

#filter_specs(specs, deps, skips: []) (private)

[ GitHub ]

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

def filter_specs(specs, deps, skips: [])
  SpecSet.new(specs).for(deps, platforms, skips: skips)
end

#find_most_specific_locked_platform (private)

[ GitHub ]

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

def find_most_specific_locked_platform
  return unless current_platform_locked?

  @most_specific_locked_platform
end

#find_source_requirements (private)

[ GitHub ]

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

def find_source_requirements
  preload_git_sources

  # Only safe to exclude when locked_requirements (merged below) backfills the gap.
  nothing_changed = nothing_changed?
  excluded = nothing_changed ? excluded_git_sources : []

  # 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)
  source_requirements = if precompute_source_requirements_for_indirect_dependencies?
    all_requirements = source_map.all_requirements(excluded)
    { default: default_source }.merge(all_requirements)
  else
    { default: Source::RubygemsAggregate.new(sources, source_map, excluded) }.merge(source_map.direct_requirements)
  end
  source_requirements.merge!(source_map.locked_requirements) if nothing_changed
  .each do |dep|
    source_requirements[dep.name] = sources.
  end

  default_bundler_source = source_requirements["bundler"] || default_source

  if @unlocking_bundler
    default_bundler_source.add_dependency_names("bundler")
  else
    source_requirements[:default_bundler] = default_bundler_source
    source_requirements["bundler"] = sources. # needs to come last to override
  end

  source_requirements
end

#gem_version_promoter

[ GitHub ]

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

def gem_version_promoter
  @gem_version_promoter ||= GemVersionPromoter.new
end

#groups

[ GitHub ]

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

def groups
  dependencies.flat_map(&:groups).uniq
end

#lock(file_or_preserve_unknown_sections = false, preserve_unknown_sections_or_unused = false)

[ GitHub ]

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

def lock(file_or_preserve_unknown_sections = false, preserve_unknown_sections_or_unused = false)
  if [true, false, nil].include?(file_or_preserve_unknown_sections)
    target_lockfile = lockfile
    preserve_unknown_sections = file_or_preserve_unknown_sections
  else
    target_lockfile = file_or_preserve_unknown_sections
    preserve_unknown_sections = preserve_unknown_sections_or_unused

    suggestion = if target_lockfile == lockfile
      "To fix this warning, remove it from the `Definition#lock` call."
    else
      "Instead, instantiate a new definition passing `#{target_lockfile}`, and call `lock` without a file argument on that definition"
    end

    msg = "`Definition#lock` was passed a target file argument. #{suggestion}"

    Bundler::SharedHelpers.feature_removed! msg
  end

  write_lock(target_lockfile, preserve_unknown_sections)
end

#locked_dependencies

[ GitHub ]

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

def locked_dependencies
  @locked_deps.values
end

#locked_overrides_to_lock (private)

[ GitHub ]

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

def locked_overrides_to_lock
  Array(@locked_gems&.overrides).map(&:to_lock).uniq
end

#locked_ruby_version

[ GitHub ]

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

def locked_ruby_version
  return unless ruby_version
  if @unlocking_ruby || !@locked_ruby_version
    Bundler::RubyVersion.system
  else
    @locked_ruby_version
  end
end

#locked_ruby_version_object

[ GitHub ]

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

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

#lockfile_changed_reason (private)

[ GitHub ]

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

def lockfile_changed_reason
  [
    [@source_changes, "the list of sources changed"],
    [@dependency_changes, "the dependencies in your gemfile changed"],
    [@current_platform_missing, "your lockfile is missing the current platform"],
    [@new_platforms.any?, "you are adding a new platform to your lockfile"],
    [@path_changes, "the gemspecs for path gems changed"],
    [@local_changes, "the gemspecs for git local gems changed"],
    [@missing_lockfile_dep, "your lockfile is missing \"#{@missing_lockfile_dep}\""],
    [@unlocking_bundler, "an update to the version of Bundler itself was requested"],
    [@locked_spec_with_missing_checksums, "your lockfile is missing a CHECKSUMS entry for \"#{@locked_spec_with_missing_checksums}\""],
    [@locked_spec_with_empty_checksums, "your lockfile has an empty CHECKSUMS entry for \"#{@locked_spec_with_empty_checksums}\""],
    [@locked_spec_with_missing_deps, "your lockfile includes \"#{@locked_spec_with_missing_deps}\" but not some of its dependencies"],
    [@locked_spec_with_invalid_deps, "your lockfile does not satisfy dependencies of \"#{@locked_spec_with_invalid_deps}\""],
    [@override_changes, "the overrides in your gemfile changed"],
  ].select(&:first).map(&:last).join(", ")
end

#lockfile_changes_summary(update_refused_reason) (private)

[ GitHub ]

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

def lockfile_changes_summary(update_refused_reason)
  added =   []
  deleted = []
  changed = []

  added.concat @new_platforms.map {|p| "* platform: #{p}" }
  deleted.concat @removed_platforms.map {|p| "* platform: #{p}" }

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

  if @override_changes
    added.concat((overrides_to_lock - locked_overrides_to_lock).sort.map {|o| "* override #{o}" })
    deleted.concat((locked_overrides_to_lock - overrides_to_lock).sort.map {|o| "* override #{o}" })
  end

  both_sources = Hash.new {|h, k| h[k] = [] }
  current_dependencies.each {|d| both_sources[d.name][0] = d }
  current_locked_dependencies.each {|d| both_sources[d.name][1] = d }

  both_sources.each do |name, (dep, lock_dep)|
    next if dep.nil? || lock_dep.nil?

    gemfile_source = dep.source || default_source
    lock_source = lock_dep.source || default_source
    next if lock_source.include?(gemfile_source)

    gemfile_source_name = dep.source ? gemfile_source.to_gemfile : "no specified source"
    lockfile_source_name = lock_dep.source ? lock_source.to_gemfile : "no specified source"
    changed << "* #{name} from `#{lockfile_source_name}` to `#{gemfile_source_name}`"
  end

  return unless added.any? || deleted.any? || changed.any? || resolve_needed?

  msg = String.new("#{change_reason[0].upcase}#{change_reason[1..-1].strip}, but ")
  msg << "the lockfile " unless msg.start_with?("Your lockfile")
  msg << "can't be updated because #{update_refused_reason}"
  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\nRun `bundle install` elsewhere and add the updated #{SharedHelpers.relative_lockfile_path} to version control.\n" unless unlocking?
  msg
end

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

[ GitHub ]

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

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::RUBY
    sections_to_ignore << LockfileParser::BUNDLED unless @unlocking_bundler
    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

#materialize(dependencies) (private)

[ GitHub ]

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

def materialize(dependencies)
  specs = begin
    resolve.materialize(dependencies)
  rescue IncorrectLockfileDependencies => e
    raise if Bundler.frozen_bundle?

    reresolve_without([e.spec])
    retry
  end

  missing_specs = resolve.missing_specs

  if missing_specs.any?
    missing_specs.each do |s|
      locked_gem = @locked_specs[s.name].last
      next if locked_gem.nil? || locked_gem.version != s.version || sources.local_mode?

      message = if sources.implicit_global_source?
        "Because your Gemfile specifies no global remote source, your bundle is locked to " \
        "#{locked_gem} from #{locked_gem.source}. However, #{locked_gem} is not installed. You'll " \
        "need to either add a global remote source to your Gemfile or make sure #{locked_gem} is " \
        "available locally before rerunning Bundler."
      else
        "Your bundle is locked to #{locked_gem} from #{locked_gem.source}, but that version can " \
        "no longer be found in that source. That means either the author of #{locked_gem} has removed it, " \
        "or you no longer have access to that source. You'll need to update your bundle to a version other " \
        "than #{locked_gem} that hasn't been removed, or check your credentials and access rights for " \
        "#{locked_gem.source}, in order to install."
      end

      raise GemNotFound, message
    end

    missing_specs_list = missing_specs.group_by(&:source).map do |source, missing_specs_for_source|
      "#{missing_specs_for_source.map(&:full_name).join(", ")} in #{source}"
    end

    raise GemNotFound, "Could not find #{missing_specs_list.join(" nor ")}"
  end

  partially_missing_specs = resolve.partially_missing_specs

  if partially_missing_specs.any? && !sources.local_mode?
    Bundler.ui.warn "Some locked specs have possibly been yanked (#{partially_missing_specs.map(&:full_name).join(", ")}). Ignoring them..."

    resolve.delete(partially_missing_specs)
  end

  incomplete_specs = resolve.incomplete_specs
  loop do
    break if incomplete_specs.empty?

    Bundler.ui.debug("The lockfile does not have all gems needed for the current platform though, Bundler will still re-resolve dependencies")
    sources.remote!
    reresolve_without(incomplete_specs)
    specs = resolve.materialize(dependencies)

    still_incomplete_specs = resolve.incomplete_specs

    if still_incomplete_specs == incomplete_specs
      resolver.raise_incomplete! incomplete_specs
    end

    incomplete_specs = still_incomplete_specs
  end

  insecurely_materialized_specs = resolve.insecurely_materialized_specs

  if insecurely_materialized_specs.any?
    Bundler.ui.warn "The following platform specific gems are getting installed, yet the lockfile includes only their generic ruby version:\n" \
                    " * #{insecurely_materialized_specs.map(&:full_name).join("\n * ")}\n" \
                    "Please run `bundle lock --normalize-platforms` and commit the resulting lockfile.\n" \
                    "Alternatively, you may run `bundle lock --add-platform <list-of-platforms-that-you-want-to-support>`"
  end

  bundler = sources..specs.search(["bundler", Bundler.gem_version]).last
  specs["bundler"] = bundler

  specs
end

#metadata_dependencies (private)

[ GitHub ]

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

def 
  @metadata_dependencies ||= [
    Dependency.new("Ruby\0", Bundler::RubyVersion.system.gem_version),
    Dependency.new("RubyGems\0", Gem::VERSION),
  ]
end

#needed_git_sources (private)

Git sources needed for the requested groups (excludes sources only used by --without groups)

[ GitHub ]

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

def needed_git_sources
  needed_deps = dependencies_for(requested_groups)
  sources.git_sources.select do |source|
    needed_deps.any? {|d| d.source == source }
  end
end

#new_deps

[ GitHub ]

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

def new_deps
  @new_deps ||= @dependencies - locked_dependencies
end

#new_resolution_base(last_resolve:, unlock:) (private)

[ GitHub ]

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

def new_resolution_base(last_resolve:, unlock:)
  new_resolution_platforms = @current_platform_missing ? @new_platforms + [Bundler.local_platform] : @new_platforms
  Resolver::Base.new(source_requirements, expanded_dependencies, last_resolve, @platforms, locked_specs: @originally_locked_specs, unlock: unlock, prerelease: gem_version_promoter.pre?, prefer_local: @prefer_local, new_platforms: new_resolution_platforms, overrides: @overrides, explicit_unlocks: @explicit_unlocks)
end

#new_resolver(base) (private)

[ GitHub ]

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

def new_resolver(base)
  Resolver.new(base, gem_version_promoter, @most_specific_locked_platform)
end

#new_resolver_for_full_update (private)

[ GitHub ]

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

def new_resolver_for_full_update
  new_resolver(unlocked_resolution_base)
end

#new_specs

[ GitHub ]

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

def new_specs
  specs - @locked_specs
end

#non_dependency_api_warning (private)

[ GitHub ]

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

def non_dependency_api_warning
  non_api_sources = sources.non_global_rubygems_sources.reject(&:dependency_api_available?)
  non_api_source_names = non_api_sources.map {|d| "  * #{d}" }.join("\n")

  msg = String.new
  msg << "Your Gemfile contains scoped sources that don't implement a dependency API, namely:\n\n"
  msg << non_api_source_names
  msg << "\n\nUsing the above gem servers may result in installing unexpected gems. " \
    "To resolve this warning, make sure you use gem servers that implement dependency APIs, " \
    "such as gemstash or geminabox gem servers."
  Bundler.ui.warn msg
end

#normalize_platforms

[ GitHub ]

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

def normalize_platforms
  resolve.normalize_platforms!(current_dependencies, platforms)

  @resolve = SpecSet.new(resolve.for(current_dependencies, @platforms))
end

#overrides_to_lock

The lines for the OVERRIDES section of Gemfile.lock.

[ GitHub ]

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

def overrides_to_lock
  @overrides.map(&:to_lock).uniq
end

#prefer_local!

[ GitHub ]

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

def prefer_local!
  @prefer_local = true

  sources.prefer_local!
end

#preload_git_source_worker (private)

[ GitHub ]

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

def preload_git_source_worker
  workers = Bundler.settings.installation_parallelization

  @preload_git_source_worker ||= Bundler::Worker.new(workers, "Git source preloading", ->(sources, _) { sources.each(&:specs) })
end

#preload_git_sources (private)

[ GitHub ]

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

def preload_git_sources
  if Gem.ruby_version < Gem::Version.new("3.3")
    # Ruby 3.2 has a bug that incorrectly triggers a circular dependency warning. This version will continue to
    # fetch git repositories one by one.
    return
  end

  begin
    # Sources from the same repository share one cache directory, and
    # running git in that directory from two workers at once makes the
    # fetches collide. Preload sources that share a cache in one job, one
    # after the other, and run the separate caches in parallel.
    needed_git_sources.group_by(&:cache_path).each_value do |sources|
      preload_git_source_worker.enq(sources)
    end
  ensure
    preload_git_source_worker.stop
  end
end

#pretty_dep(dep) (private)

[ GitHub ]

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

def pretty_dep(dep)
  SharedHelpers.pretty_dependency(dep)
end

#refetch_needed?(options) ⇒ Boolean (private)

Reinstalling installs from the cached archive, and bundle cache copies it into the app cache, so a cache emptied by the prune setting or by hand has to go back to the remotes to refill it.

[ GitHub ]

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

def refetch_needed?(options)
  return false unless options[:force] || options["cache-archives"]

  resolve.for(requested_dependencies, [Bundler.local_platform]).any? do |spec|
    spec.source.is_a?(Source::Rubygems) && spec.source.uncached?(spec)
  end
end

#release_resolution_memory!

Releases memory only needed during resolution, such as remote spec indexes and resolver state. Only safe to call once resolution is complete and the result has been materialized, since any further resolution will need to refetch remote specs.

[ GitHub ]

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

def release_resolution_memory!
  @resolver = nil
  @resolution_base = nil
  sources.release_resolution_memory!
end

#relevant_deps?(dep) ⇒ Boolean

[ GitHub ]

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

def relevant_deps?(dep)
  platforms_array = [Bundler.generic_local_platform].freeze

  dep.should_include? && !dep.gem_platforms(platforms_array).empty?
end

#remotely!

[ GitHub ]

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

def remotely!
  sources.cached!
  sources.remote!
end

#remove_invalid_platforms! (private)

[ GitHub ]

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

def remove_invalid_platforms!
  return if Bundler.frozen_bundle?

  skips = (@new_platforms + [Bundler.local_platform]).uniq

  # We should probably avoid removing non-ruby platforms, since that means
  # lockfile will no longer install on those platforms, so a error to give
  # heads up to the user may be better. However, we have tests expecting
  # non ruby platform autoremoval to work, so leaving that in place for
  # now.
  skips |= platforms - [Gem::Platform::RUBY] if @dependency_changes

  @originally_invalid_platforms = @originally_locked_specs.remove_invalid_platforms!(current_dependencies, platforms, skips: skips)
end

#remove_platform(platform)

Raises:

[ GitHub ]

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

def remove_platform(platform)
  raise InvalidOption, "Unable to remove the platform `#{platform}` since the only platforms are #{@platforms.join ", "}" unless @platforms.include?(platform)

  @removed_platforms << platform
  @platforms.delete(platform)
end

#removed_specs

[ GitHub ]

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

def removed_specs
  @locked_specs - specs
end

#requested_dependencies

[ GitHub ]

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

def requested_dependencies
  dependencies_for(requested_groups)
end

#requested_groups (private)

[ GitHub ]

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

def requested_groups
  values = groups - Bundler.settings[:without] - @optional_groups + Bundler.settings[:with]
  values &= Bundler.settings[:only] unless Bundler.settings[:only].empty?
  values
end

#requested_specs

[ GitHub ]

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

def requested_specs
  specs_for(requested_groups)
end

#reresolve_without(incomplete_specs) (private)

[ GitHub ]

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

def reresolve_without(incomplete_specs)
  resolution_base.delete(incomplete_specs)
  @resolve = start_resolution
end

#resolution_base (private)

[ GitHub ]

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

def resolution_base
  @resolution_base ||= begin
    last_resolve = converge_locked_specs
    remove_invalid_platforms!
    base = new_resolution_base(last_resolve: last_resolve, unlock: @unlocking_all || @gems_to_unlock)
    base = additional_base_requirements_to_prevent_downgrades(base)
    base = additional_base_requirements_to_force_updates(base)
    base
  end
end

#resolve ⇒ SpecSet

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 349

def resolve
  return @resolve if @resolve

  @resolve = if Bundler.frozen_bundle?
    Bundler.ui.debug "Frozen, using resolution from the lockfile"
    @locked_specs
  elsif no_resolve_needed?
    if deleted_deps.any?
      Bundler.ui.debug "Some dependencies were deleted, using a subset of the resolution from the lockfile"
      SpecSet.new(filter_specs(@locked_specs, @dependencies - deleted_deps))
    else
      Bundler.ui.debug "Found no changes, using resolution from the lockfile"
      if @removed_platforms.any? || @locked_gems.may_include_redundant_platform_specific_gems?
        SpecSet.new(filter_specs(@locked_specs, @dependencies))
      else
        @locked_specs
      end
    end
  else
    Bundler.ui.debug resolve_needed_reason

    start_resolution
  end
  warn_inert_overrides(@resolve)
  @resolve
end

#resolve_needed_reason (private)

[ GitHub ]

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

def resolve_needed_reason
  if lockfile_exists?
    if unlocking?
      "Re-resolving dependencies because #{unlocking_reason}"
    else
      "Found changes from the lockfile, re-resolving dependencies because #{lockfile_changed_reason}"
    end
  else
    "Resolving dependencies because there's no lockfile"
  end
end

#resolve_remotely!

[ GitHub ]

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

def resolve_remotely!
  remotely!

  resolve
end

#resolve_with_cache!

[ GitHub ]

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

def resolve_with_cache!
  with_cache!

  resolve
end

#resolver (private)

[ GitHub ]

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

def resolver
  @resolver ||= new_resolver(resolution_base)
end

#setup_domain!(options = {}) ⇒ Boolean

Setup sources according to the given options and the state of the definition.

Returns:

  • (Boolean) —

    Whether fetching remote information will be necessary or not

[ GitHub ]

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

def setup_domain!(options = {})
  prefer_local! if options[:"prefer-local"]

  sources.cached!

  if options[:add_checksums] || (!options[:local] && (install_needed? || refetch_needed?(options) || @locked_spec_with_empty_checksums))
    sources.remote!
    true
  else
    Bundler.settings.set_command_option(:jobs, 1) unless install_needed? # to avoid the overhead of Bundler::Worker
    sources.local!
    false
  end
end

#source_map (private)

[ GitHub ]

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

def source_map
  @source_map ||= SourceMap.new(sources, dependencies, @locked_specs, @overrides.filter_map {|o| o.operation if o.from })
end

#source_requirements (private)

[ GitHub ]

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

def source_requirements
  @source_requirements ||= find_source_requirements
end

#spec_git_paths

[ GitHub ]

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

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

#specs ⇒ Bundler::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 256

def specs
  @specs ||= materialize(requested_dependencies)
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 968

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 329

def specs_for(groups)
  return specs if groups.empty?
  deps = dependencies_for(groups)
  materialize(deps)
end

#specs_for_source_changed?(source) ⇒ Boolean (private)

[ GitHub ]

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

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

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

#start_resolution (private)

[ GitHub ]

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

def start_resolution
  local_platform_needed_for_resolvability = @most_specific_non_local_locked_platform && !@platforms.include?(Bundler.local_platform)
  @platforms << Bundler.local_platform if local_platform_needed_for_resolvability

  result = SpecSet.new(resolver.start)

  @cooldown_skipped = resolver.cooldown_skipped

  @resolved_bundler_version = result.find {|spec| spec.name == "bundler" }&.version

  @new_platforms.each do |platform|
    incomplete_specs = result.incomplete_specs_for_platform(current_dependencies, platform)

    if incomplete_specs.any?
      resolver.raise_incomplete! incomplete_specs
    end
  end

  if @most_specific_non_local_locked_platform
    if result.incomplete_for_platform?(current_dependencies, @most_specific_non_local_locked_platform)
      @platforms.delete(@most_specific_non_local_locked_platform)
    elsif local_platform_needed_for_resolvability
      @platforms.delete(Bundler.local_platform)
    end
  end

  if should_add_extra_platforms?
    result.add_extra_platforms!(platforms)
  elsif @originally_invalid_platforms.any?
    result.add_originally_invalid_platforms!(platforms, @originally_invalid_platforms)
  end

  SpecSet.new(result.for(dependencies, @platforms | [Gem::Platform::RUBY]))
end

#to_lock

[ GitHub ]

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

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

#unlocked_resolution_base (private)

[ GitHub ]

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

def unlocked_resolution_base
  new_resolution_base(last_resolve: SpecSet.new([]), unlock: true)
end

#unlocking_reason (private)

[ GitHub ]

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

def unlocking_reason
  unlock_targets = if @gems_to_unlock.any?
    ["gems", @gems_to_unlock]
  elsif @sources_to_unlock.any?
    ["sources", @sources_to_unlock]
  end

  unlock_reason = if unlock_targets
    "#{unlock_targets.first}: (#{unlock_targets.last.join(", ")})"
  else
    @unlocking_ruby ? "ruby" : ""
  end

  "bundler is unlocking #{unlock_reason}"
end

#validate_platforms!

Raises:

[ GitHub ]

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

def validate_platforms!
  return if current_platform_locked? || @platforms.include?(Gem::Platform::RUBY)

  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\n`bundle lock --add-platform #{Bundler.local_platform}` and try again."
end

#validate_ruby!

[ GitHub ]

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

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}"
    end

    raise RubyVersionMismatch, msg
  end
end

#validate_runtime!

[ GitHub ]

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

def validate_runtime!
  validate_ruby!
  validate_platforms!
end

#warn_inert_overrides(specs) (private)

A misspelled from: gem leaves the override with nothing to rewrite.

[ GitHub ]

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

def warn_inert_overrides(specs)
  @overrides.each do |override|
    next unless override.from
    next if specs[override.from].any? {|s| s.dependencies.any? {|d| d.name == override.target } }

    Bundler.ui.warn "#{override} has no effect because the bundle has no #{override.from} that depends on #{override.target}"
  end
end

#with_cache!

[ GitHub ]

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

def with_cache!
  sources.local!
  sources.cached!
end

#write_lock(file, preserve_unknown_sections)

[ GitHub ]

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

def write_lock(file, preserve_unknown_sections)
  return if Definition.no_lock || !lockfile || file.nil?

  contents = to_lock

  if @locked_bundler_version
    locked_major = @locked_bundler_version.segments.first
    current_major = bundler_version_to_lock.segments.first

    updating_major = locked_major < current_major
  end

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

  if File.exist?(file) && lockfiles_equal?(@lockfile_contents, contents, preserve_unknown_sections)
    return if Bundler.frozen_bundle?
    SharedHelpers.filesystem_access(file) { FileUtils.touch(file) }
    return
  end

  if Bundler.frozen_bundle?
    msg = lockfile_changes_summary("frozen mode is set") ||
          "Your lockfile needs to be updated, but it can't be because frozen mode is set.\n\n" \
          "Run `bundle install` elsewhere and add the updated #{SharedHelpers.relative_lockfile_path} to version control."
    raise ProductionError, msg
  end

  # Convert to \r\n if the existing lock has them, i.e., Windows with
  # `git config core.autocrlf=true`. Detect from the bytes on disk because
  # reading in text mode strips carriage returns on Windows, which would
  # otherwise defeat this check and rewrite a `\r\n` lockfile with `\n`.
  if File.exist?(file) && SharedHelpers.filesystem_access(file, :read) {|p| File.binread(p).include?("\r\n") }
    contents.gsub!(/\n/, "\r\n")
  end

  begin
    SharedHelpers.filesystem_access(file) do |p|
      File.open(p, "wb") {|f| f.puts(contents) }
    end
  rescue ReadOnlyFileSystemError
    raise ProductionError, lockfile_changes_summary("file system is read-only")
  end
end