class Bundler::Definition

Attributes

no_lock[RW]

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

dependencies[R]
gemfiles[R]
locked_checksums[R]
locked_deps[R]
locked_gems[R]
lockfile[R]
platforms[R]
ruby_version[R]
source_requirements[W]
sources[R]

Public Class Methods

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

Given a gemfile and lockfile creates a Bundler definition

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

to be updated or true if all gems should be updated

@return [Bundler::Definition]

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

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

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

How does the new system work?

  • Load information from Gemfile and Lockfile

  • Invalidate stale locked specs

  • All specs from stale source are stale

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

  • If all fresh dependencies are satisfied by the locked

specs, then we can try to resolve locally.

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

to be updated or true if all gems should be updated

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

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

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

  @lockfile               = lockfile
  @lockfile_contents      = String.new

  @locked_bundler_version = nil
  @resolved_bundler_version = nil

  @locked_ruby_version = nil
  @new_platforms = []
  @removed_platform = nil

  if lockfile_exists?
    @lockfile_contents = Bundler.read_file(lockfile)
    @locked_gems = LockfileParser.new(@lockfile_contents)
    @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
    @originally_locked_deps = @locked_gems.dependencies
    @originally_locked_specs = SpecSet.new(@locked_gems.specs)
    @locked_checksums = @locked_gems.checksums

    if unlock != true
      @locked_deps    = @originally_locked_deps
      @locked_specs   = @originally_locked_specs
      @locked_sources = @locked_gems.sources
    else
      @unlock         = {}
      @locked_deps    = {}
      @locked_specs   = SpecSet.new([])
      @locked_sources = []
    end
  else
    @unlock         = {}
    @locked_gems    = nil
    @locked_platforms = []
    @most_specific_locked_platform = nil
    @platforms      = []
    @locked_deps    = {}
    @locked_specs   = SpecSet.new([])
    @originally_locked_deps = {}
    @originally_locked_specs = @locked_specs
    @locked_sources = []
    @locked_checksums = Bundler.feature_flag.lockfile_checksums?
  end

  locked_gem_sources = @locked_sources.select {|s| s.is_a?(Source::Rubygems) }
  @multisource_allowed = locked_gem_sources.size == 1 && locked_gem_sources.first.multiple_remotes? && Bundler.frozen_bundle?

  if @multisource_allowed
    unless sources.aggregate_global_source?
      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.major_deprecation 2, msg
    end

    @sources.merged_gem_lockfile_sections!(locked_gem_sources.first)
  end

  @sources_to_unlock = @unlock.delete(: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)

  @current_platform_missing = add_current_platform unless Bundler.frozen_bundle?

  converge_path_sources_to_gemspec_sources
  @path_changes = converge_paths
  @source_changes = converge_sources

  @explicit_unlocks = @unlock.delete(:gems) || []

  if @unlock[: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

  check_lockfile
end

Public Instance Methods

add_checksums() click to toggle source
# File bundler/definition.rb, line 544
def add_checksums
  @locked_checksums = true

  setup_domain!

  specs # force materialization to real specifications, so that checksums are fetched
end
add_platform(platform) click to toggle source
# File bundler/definition.rb, line 501
def add_platform(platform)
  return if @platforms.include?(platform)

  @new_platforms << platform
  @platforms << platform
end
bundler_version_to_lock() click to toggle source
# File bundler/definition.rb, line 396
def bundler_version_to_lock
  @resolved_bundler_version || Bundler.gem_version
end
check!() click to toggle source
# File bundler/definition.rb, line 168
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
current_dependencies() click to toggle source
# File bundler/definition.rb, line 271
def current_dependencies
  filter_relevant(dependencies)
end
current_locked_dependencies() click to toggle source
# File bundler/definition.rb, line 275
def current_locked_dependencies
  filter_relevant(locked_dependencies)
end
deleted_deps() click to toggle source
# File bundler/definition.rb, line 294
def deleted_deps
  @deleted_deps ||= locked_dependencies - @dependencies
end
dependencies_for(groups) click to toggle source
# File bundler/definition.rb, line 304
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
ensure_equivalent_gemfile_and_lockfile(explicit_flag = false) click to toggle source
# File bundler/definition.rb, line 405
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?

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

  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?

  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

  reason = nothing_changed? ? "some dependencies were deleted from your gemfile" : change_reason
  msg = String.new
  msg << "#{reason.capitalize.strip}, but the lockfile can't be updated because frozen mode is set"
  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_gemfile_path} to version control.\n"

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

  raise ProductionError, msg if added.any? || deleted.any? || changed.any? || !nothing_changed?
end
filter_relevant(dependencies) click to toggle source
# File bundler/definition.rb, line 279
def filter_relevant(dependencies)
  platforms_array = [generic_local_platform].freeze
  dependencies.select do |d|
    d.should_include? && !d.gem_platforms(platforms_array).empty?
  end
end
gem_version_promoter() click to toggle source
# File bundler/definition.rb, line 164
def gem_version_promoter
  @gem_version_promoter ||= GemVersionPromoter.new
end
groups() click to toggle source
# File bundler/definition.rb, line 349
def groups
  dependencies.flat_map(&:groups).uniq
end
lock(file_or_preserve_unknown_sections = false, preserve_unknown_sections_or_unused = false) click to toggle source
# File bundler/definition.rb, line 353
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.major_deprecation 2, msg
  end

  write_lock(target_lockfile, preserve_unknown_sections)
end
locked_dependencies() click to toggle source
# File bundler/definition.rb, line 286
def locked_dependencies
  @locked_deps.values
end
locked_ruby_version() click to toggle source
# File bundler/definition.rb, line 375
def locked_ruby_version
  return unless ruby_version
  if @unlock[:ruby] || !@locked_ruby_version
    Bundler::RubyVersion.system
  else
    @locked_ruby_version
  end
end
locked_ruby_version_object() click to toggle source
# File bundler/definition.rb, line 384
def locked_ruby_version_object
  return unless @locked_ruby_version
  @locked_ruby_version_object ||= begin
    unless version = RubyVersion.from_string(@locked_ruby_version)
      raise LockfileError, "The Ruby version #{@locked_ruby_version} from " \
        "#{@lockfile} could not be parsed. " \
        "Try running bundle update --ruby to resolve this."
    end
    version
  end
end
missing_specs() click to toggle source
# File bundler/definition.rb, line 243
def missing_specs
  resolve.missing_specs_for(requested_dependencies)
end
missing_specs?() click to toggle source
# File bundler/definition.rb, line 247
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_packages = nil
  @source_requirements = nil
  @specs = nil

  Bundler.ui.debug "The definition is missing dependencies, failed to resolve & materialize locally (#{e})"
  true
end
new_deps() click to toggle source
# File bundler/definition.rb, line 290
def new_deps
  @new_deps ||= @dependencies - locked_dependencies
end
new_specs() click to toggle source
# File bundler/definition.rb, line 235
def new_specs
  specs - @locked_specs
end
no_install_needed?() click to toggle source
# File bundler/definition.rb, line 530
def no_install_needed?
  no_resolve_needed? && !missing_specs?
end
no_resolve_needed?() click to toggle source
# File bundler/definition.rb, line 534
def no_resolve_needed?
  !unlocking? && nothing_changed?
end
normalize_platforms() click to toggle source
# File bundler/definition.rb, line 495
def normalize_platforms
  @platforms = resolve.normalize_platforms!(current_dependencies, platforms)

  @resolve = SpecSet.new(resolve.for(current_dependencies, @platforms))
end
nothing_changed?() click to toggle source
# File bundler/definition.rb, line 515
def nothing_changed?
  return false unless lockfile_exists?

  !@source_changes &&
    !@dependency_changes &&
    !@current_platform_missing &&
    @new_platforms.empty? &&
    !@path_changes &&
    !@local_changes &&
    !@missing_lockfile_dep &&
    !@unlocking_bundler &&
    !@locked_spec_with_missing_deps &&
    !@locked_spec_with_invalid_deps
end
prefer_local!() click to toggle source
# File bundler/definition.rb, line 221
def prefer_local!
  @prefer_local = true
end
remotely!() click to toggle source
# File bundler/definition.rb, line 216
def remotely!
  sources.cached!
  sources.remote!
end
remove_platform(platform) click to toggle source
# File bundler/definition.rb, line 508
def remove_platform(platform)
  removed_platform = @platforms.delete(Gem::Platform.new(platform))
  @removed_platform ||= removed_platform
  return if removed_platform
  raise InvalidOption, "Unable to remove the platform `#{platform}` since the only platforms are #{@platforms.join ", "}"
end
removed_specs() click to toggle source
# File bundler/definition.rb, line 239
def removed_specs
  @locked_specs - specs
end
requested_dependencies() click to toggle source
# File bundler/definition.rb, line 267
def requested_dependencies
  dependencies_for(requested_groups)
end
requested_specs() click to toggle source
# File bundler/definition.rb, line 263
def requested_specs
  specs_for(requested_groups)
end
resolve() click to toggle source

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

@return [SpecSet] resolved dependencies

# File bundler/definition.rb, line 318
def 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_platform || @locked_gems.may_include_redundant_platform_specific_gems?
        SpecSet.new(filter_specs(@locked_specs, @dependencies))
      else
        @locked_specs
      end
    end
  else
    if lockfile_exists?
      Bundler.ui.debug "Found changes from the lockfile, re-resolving dependencies because #{change_reason}"
    else
      Bundler.ui.debug "Resolving dependencies because there's no lockfile"
    end

    start_resolution
  end
end
resolve_remotely!() click to toggle source
# File bundler/definition.rb, line 210
def resolve_remotely!
  remotely!

  resolve
end
resolve_with_cache!() click to toggle source
# File bundler/definition.rb, line 199
def resolve_with_cache!
  with_cache!

  resolve
end
setup_domain!(options = {}) click to toggle source

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

@return [Boolean] Whether fetching remote information will be necessary or not

# File bundler/definition.rb, line 186
def setup_domain!(options = {})
  prefer_local! if options[:"prefer-local"]

  if options[:local] || no_install_needed?
    Bundler.settings.set_command_option(:jobs, 1) if no_install_needed? # to avoid the overhead of Bundler::Worker
    with_cache!
    false
  else
    remotely!
    true
  end
end
spec_git_paths() click to toggle source
# File bundler/definition.rb, line 345
def spec_git_paths
  sources.git_sources.filter_map {|s| File.realpath(s.path) if File.exist?(s.path) }
end
specs() click to toggle source

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

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

@return [Bundler::SpecSet]

# File bundler/definition.rb, line 231
def specs
  @specs ||= materialize(requested_dependencies)
end
specs_for(groups) click to toggle source
# File bundler/definition.rb, line 298
def specs_for(groups)
  return specs if groups.empty?
  deps = dependencies_for(groups)
  materialize(deps)
end
to_lock() click to toggle source
# File bundler/definition.rb, line 400
def to_lock
  require_relative "lockfile_generator"
  LockfileGenerator.generate(self)
end
unlocking?() click to toggle source
# File bundler/definition.rb, line 538
def unlocking?
  @unlocking
end
validate_platforms!() click to toggle source
# File bundler/definition.rb, line 487
def validate_platforms!
  return if current_platform_locked?

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

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

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

    raise RubyVersionMismatch, msg
  end
end
validate_runtime!() click to toggle source
# File bundler/definition.rb, line 457
def validate_runtime!
  validate_ruby!
  validate_platforms!
end
with_cache!() click to toggle source
# File bundler/definition.rb, line 205
def with_cache!
  sources.local!
  sources.cached!
end

Private Instance Methods

add_current_platform() click to toggle source
# File bundler/definition.rb, line 750
def add_current_platform
  return if @platforms.include?(local_platform)

  @most_specific_non_local_locked_ruby_platform = find_most_specific_locked_ruby_platform
  return if @most_specific_non_local_locked_ruby_platform

  @platforms << local_platform
  true
end
additional_base_requirements_to_force_updates(resolution_packages) click to toggle source
# File bundler/definition.rb, line 1099
def additional_base_requirements_to_force_updates(resolution_packages)
  return resolution_packages if @explicit_unlocks.empty?
  full_update = dup_for_full_unlock.resolve
  @explicit_unlocks.each do |name|
    version = full_update[name].first&.version
    resolution_packages.base_requirements[name] = Gem::Requirement.new("= #{version}") if version
  end
  resolution_packages
end
additional_base_requirements_to_prevent_downgrades(resolution_packages, last_resolve) click to toggle source
# File bundler/definition.rb, line 1090
def additional_base_requirements_to_prevent_downgrades(resolution_packages, last_resolve)
  return resolution_packages unless @locked_gems && !sources.expired_sources?(@locked_gems.sources)
  converge_specs(@originally_locked_specs - last_resolve).each do |locked_spec|
    next if locked_spec.source.is_a?(Source::Path)
    resolution_packages.base_requirements[locked_spec.name] = Gem::Requirement.new(">= #{locked_spec.version}")
  end
  resolution_packages
end
change_reason() click to toggle source
# File bundler/definition.rb, line 766
def change_reason
  if unlocking?
    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
      @unlock[:ruby] ? "ruby" : ""
    end

    return "bundler is unlocking #{unlock_reason}"
  end
  [
    [@source_changes, "the list of sources changed"],
    [@dependency_changes, "the dependencies in your gemfile changed"],
    [@current_platform_missing, "your lockfile does not include the current platform"],
    [@new_platforms.any?, "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"],
    [@missing_lockfile_dep, "your lock file is missing \"#{@missing_lockfile_dep}\""],
    [@unlocking_bundler, "an update to the version of Bundler itself was requested"],
    [@locked_spec_with_missing_deps, "your lock file 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}\""],
  ].select(&:first).map(&:last).join(", ")
end
check_lockfile() click to toggle source
# File bundler/definition.rb, line 846
def check_lockfile
  @missing_lockfile_dep = nil

  @locked_spec_with_invalid_deps = nil
  @locked_spec_with_missing_deps = nil

  missing = []
  invalid = []

  @locked_specs.each do |s|
    validation = @locked_specs.validate_deps(s)

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

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

    @locked_spec_with_missing_deps = missing.first.name
  elsif !@dependency_changes
    @missing_lockfile_dep = current_dependencies.find do |d|
      @locked_specs[d.name].empty? && d.name != "bundler"
    end&.name
  end

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

    @locked_spec_with_invalid_deps = invalid.first.name
  end
end
converge_dependencies() click to toggle source
# File bundler/definition.rb, line 929
def converge_dependencies
  changes = false

  @dependencies.each do |dep|
    if dep.source
      dep.source = sources.get(dep.source)
    end

    unless locked_dep = @originally_locked_deps[dep.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, dep.type)

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

  changes
end
converge_locals() click to toggle source

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.

# File bundler/definition.rb, line 828
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() click to toggle source

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

# File bundler/definition.rb, line 961
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_path_source_to_gemspec_source(source) click to toggle source
# File bundler/definition.rb, line 885
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() click to toggle source
# File bundler/definition.rb, line 891
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() click to toggle source
# File bundler/definition.rb, line 879
def converge_paths
  sources.path_sources.any? do |source|
    specs_changed?(source)
  end
end
converge_sources() click to toggle source
# File bundler/definition.rb, line 903
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 = @locked_gems.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

  changes
end
converge_specs(specs) click to toggle source
# File bundler/definition.rb, line 981
def converge_specs(specs)
  converged = []
  deps = []

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

    if dep
      gemfile_source = dep.source || default_source

      deps << dep if !dep.source || lockfile_source.include?(dep.source) || new_deps.include?(dep)

      # Replace the locked dependency's source with the equivalent source from the Gemfile
      s.source = gemfile_source
    else
      # Replace the locked dependency's source with the default source, if the locked source is no longer in the Gemfile
      s.source = default_source unless sources.get(lockfile_source)
    end

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

    # Path sources have special logic
    if source.instance_of?(Source::Path) || source.instance_of?(Source::Gemspec) || (source.instance_of?(Source::Git) && !@gems_to_unlock.include?(name) && deps.include?(dep))
      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

    if dep.nil? && requested_dep = requested_dependencies.find {|d| name == d.name }
      @gems_to_unlock << name
      deps << requested_dep
    end

    converged << s
  end

  filter_specs(converged, deps, skips: @gems_to_unlock)
end
current_platform_locked?() click to toggle source
# File bundler/definition.rb, line 744
def current_platform_locked?
  @platforms.any? do |bundle_platform|
    MatchPlatform.platforms_match?(bundle_platform, local_platform)
  end
end
default_source() click to toggle source
# File bundler/definition.rb, line 1066
def default_source
  sources.default_source
end
dependencies_for_source_changed?(source, locked_source = source) click to toggle source
# File bundler/definition.rb, line 808
def dependencies_for_source_changed?(source, locked_source = source)
  deps_for_source = @dependencies.select {|s| s.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() click to toggle source
# File bundler/definition.rb, line 604
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
dup_for_full_unlock() click to toggle source
# File bundler/definition.rb, line 1109
def dup_for_full_unlock
  unlocked_definition = self.class.new(@lockfile, @dependencies, @sources, true, @ruby_version, @optional_groups, @gemfiles)
  unlocked_definition.source_requirements = source_requirements
  unlocked_definition.gem_version_promoter.tap do |gvp|
    gvp.level = gem_version_promoter.level
    gvp.strict = gem_version_promoter.strict
    gvp.pre = gem_version_promoter.pre
  end
  unlocked_definition
end
expanded_dependencies() click to toggle source
# File bundler/definition.rb, line 600
def expanded_dependencies
  dependencies_with_bundler + metadata_dependencies
end
filter_specs(specs, deps, skips: []) click to toggle source
# File bundler/definition.rb, line 622
def filter_specs(specs, deps, skips: [])
  SpecSet.new(specs).for(deps, platforms, skips: skips)
end
find_most_specific_locked_ruby_platform() click to toggle source
# File bundler/definition.rb, line 760
def find_most_specific_locked_ruby_platform
  return unless generic_local_platform_is_ruby? && current_platform_locked?

  @most_specific_locked_platform
end
find_source_requirements() click to toggle source
# File bundler/definition.rb, line 1039
def find_source_requirements
  # 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
    { default: default_source }.merge(all_requirements)
  else
    { default: Source::RubygemsAggregate.new(sources, source_map) }.merge(source_map.direct_requirements)
  end
  source_requirements.merge!(source_map.locked_requirements) if nothing_changed?
  metadata_dependencies.each do |dep|
    source_requirements[dep.name] = sources.metadata_source
  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.metadata_source # needs to come last to override
  end

  source_requirements
end
lockfile_exists?() click to toggle source
# File bundler/definition.rb, line 558
def lockfile_exists?
  lockfile && File.exist?(lockfile)
end
lockfiles_equal?(current, proposed, preserve_unknown_sections) click to toggle source
# File bundler/definition.rb, line 1076
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) click to toggle source
# File bundler/definition.rb, line 626
def materialize(dependencies)
  # Tracks potential endless loops trying to re-resolve.
  # TODO: Remove as dead code if not reports are received in a while
  incorrect_spec = nil

  specs = begin
    resolve.materialize(dependencies)
  rescue IncorrectLockfileDependencies => e
    spec = e.spec
    raise "Infinite loop while fixing lockfile dependencies" if incorrect_spec == spec

    incorrect_spec = spec
    reresolve_without([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 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

      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
      package = resolution_packages.get_package(incomplete_specs.first.name)
      resolver.raise_not_found! package
    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.metadata_source.specs.search(["bundler", Bundler.gem_version]).last
  specs["bundler"] = bundler

  specs
end
metadata_dependencies() click to toggle source
# File bundler/definition.rb, line 1028
def metadata_dependencies
  @metadata_dependencies ||= [
    Dependency.new("Ruby\0", Bundler::RubyVersion.system.gem_version),
    Dependency.new("RubyGems\0", Gem::VERSION),
  ]
end
precompute_source_requirements_for_indirect_dependencies?() click to toggle source
# File bundler/definition.rb, line 740
def precompute_source_requirements_for_indirect_dependencies?
  sources.non_global_rubygems_sources.all?(&:dependency_api_available?) && !sources.aggregate_global_source?
end
pretty_dep(dep) click to toggle source
# File bundler/definition.rb, line 796
def pretty_dep(dep)
  SharedHelpers.pretty_dependency(dep)
end
remove_invalid_platforms!() click to toggle source
# File bundler/definition.rb, line 1120
def remove_invalid_platforms!
  return if Bundler.frozen_bundle?

  platforms.reverse_each do |platform|
    next if local_platform == platform ||
            @new_platforms.include?(platform) ||
            @path_changes ||
            @dependency_changes ||
            @locked_spec_with_invalid_deps ||
            !spec_set_incomplete_for_platform?(@originally_locked_specs, platform)

    remove_platform(platform)
  end
end
requested_groups() click to toggle source
# File bundler/definition.rb, line 1070
def requested_groups
  values = groups - Bundler.settings[:without] - @optional_groups + Bundler.settings[:with]
  values &= Bundler.settings[:only] unless Bundler.settings[:only].empty?
  values
end
reresolve_without(incomplete_specs) click to toggle source
# File bundler/definition.rb, line 713
def reresolve_without(incomplete_specs)
  resolution_packages.delete(incomplete_specs)
  @resolve = start_resolution
end
resolution_packages() click to toggle source
# File bundler/definition.rb, line 611
def resolution_packages
  @resolution_packages ||= begin
    last_resolve = converge_locked_specs
    remove_invalid_platforms!
    packages = Resolver::Base.new(source_requirements, expanded_dependencies, last_resolve, @platforms, locked_specs: @originally_locked_specs, unlock: @gems_to_unlock, prerelease: gem_version_promoter.pre?, prefer_local: @prefer_local)
    packages = additional_base_requirements_to_prevent_downgrades(packages, last_resolve)
    packages = additional_base_requirements_to_force_updates(packages)
    packages
  end
end
resolver() click to toggle source
# File bundler/definition.rb, line 596
def resolver
  @resolver ||= Resolver.new(resolution_packages, gem_version_promoter, @most_specific_locked_platform)
end
should_add_extra_platforms?() click to toggle source
# File bundler/definition.rb, line 554
def should_add_extra_platforms?
  !lockfile_exists? && generic_local_platform_is_ruby? && !Bundler.settings[:force_ruby_platform]
end
source_map() click to toggle source
# File bundler/definition.rb, line 1139
def source_map
  @source_map ||= SourceMap.new(sources, dependencies, @locked_specs)
end
source_requirements() click to toggle source
# File bundler/definition.rb, line 1035
def source_requirements
  @source_requirements ||= find_source_requirements
end
spec_set_incomplete_for_platform?(spec_set, platform) click to toggle source
# File bundler/definition.rb, line 1135
def spec_set_incomplete_for_platform?(spec_set, platform)
  spec_set.incomplete_for_platform?(current_dependencies, platform)
end
specs_changed?(source) click to toggle source

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

# File bundler/definition.rb, line 802
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_source_changed?(source) click to toggle source
# File bundler/definition.rb, line 815
def specs_for_source_changed?(source)
  locked_index = Index.new
  locked_index.use(@locked_specs.select {|s| source.can_lock?(s) })

  !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() click to toggle source
# File bundler/definition.rb, line 718
def start_resolution
  local_platform_needed_for_resolvability = @most_specific_non_local_locked_ruby_platform && !@platforms.include?(local_platform)
  @platforms << local_platform if local_platform_needed_for_resolvability
  add_platform(Gem::Platform::RUBY) if RUBY_ENGINE == "truffleruby"

  result = SpecSet.new(resolver.start)

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

  if @most_specific_non_local_locked_ruby_platform
    if spec_set_incomplete_for_platform?(result, @most_specific_non_local_locked_ruby_platform)
      @platforms.delete(@most_specific_non_local_locked_ruby_platform)
    elsif local_platform_needed_for_resolvability
      @platforms.delete(local_platform)
    end
  end

  @platforms = result.add_extra_platforms!(platforms) if should_add_extra_platforms?

  SpecSet.new(result.for(dependencies, @platforms))
end
write_lock(file, preserve_unknown_sections) click to toggle source
# File bundler/definition.rb, line 562
def write_lock(file, preserve_unknown_sections)
  return if Definition.no_lock || file.nil?

  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 = bundler_version_to_lock.segments.first

    updating_major = locked_major < current_major
  end

  preserve_unknown_sections ||= !updating_major && (Bundler.frozen_bundle? || !(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?
    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