class Bundler::Definition

属性

no_lock[RW]

不创建或修改 lockfile (使 lock 成为空操作)

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]

公共类方法

build(gemfile, lockfile, unlock) 点击切换源代码

给定一个 gemfile 和 lockfile,创建一个 Bundler 定义

@param gemfile [Pathname] Gemfile 的路径 @param lockfile [Pathname,nil] Gemfile.lock 的路径 @param unlock [Hash, Boolean, nil] 已请求的 gems

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 = []) 点击切换源代码

新系统如何工作?

  • 从 Gemfile 和 Lockfile 加载信息

  • 使过时的锁定规范失效

  • 来自过时源的所有规范都过时

  • 所有只能通过过时依赖项访问的规范都过时。

  • 如果锁定的规范满足所有新的依赖项

specs, then we can try to resolve locally.

@param lockfile [Pathname] Gemfile.lock 的路径 @param dependencies [Array(Bundler::Dependency)] 来自 Gemfile 的依赖项数组 @param sources [Bundler::SourceList] @param unlock [Hash, Boolean, nil] 已请求的 gems

to be updated or true if all gems should be updated

@param ruby_version [Bundler::RubyVersion, nil] 请求的 Ruby 版本 @param optional_groups [Array(String)] 可选组的列表

# 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

公共实例方法

add_checksums() 点击切换源代码
# File bundler/definition.rb, line 529
def add_checksums
  @locked_checksums = true

  setup_domain!(add_checksums: true)

  specs # force materialization to real specifications, so that checksums are fetched
end
add_platform(platform) 点击切换源代码
# 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() 点击切换源代码
# File bundler/definition.rb, line 396
def bundler_version_to_lock
  @resolved_bundler_version || Bundler.gem_version
end
check!() 点击切换源代码
# 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() 点击切换源代码
# File bundler/definition.rb, line 271
def current_dependencies
  filter_relevant(dependencies)
end
current_locked_dependencies() 点击切换源代码
# File bundler/definition.rb, line 275
def current_locked_dependencies
  filter_relevant(locked_dependencies)
end
deleted_deps() 点击切换源代码
# File bundler/definition.rb, line 294
def deleted_deps
  @deleted_deps ||= locked_dependencies - @dependencies
end
dependencies_for(groups) 点击切换源代码
# 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) 点击切换源代码
# 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) 点击切换源代码
# 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() 点击切换源代码
# File bundler/definition.rb, line 164
def gem_version_promoter
  @gem_version_promoter ||= GemVersionPromoter.new
end
groups() 点击切换源代码
# 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) 点击切换源代码
# 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() 点击切换源代码
# File bundler/definition.rb, line 286
def locked_dependencies
  @locked_deps.values
end
locked_ruby_version() 点击切换源代码
# 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() 点击切换源代码
# 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() 点击切换源代码
# File bundler/definition.rb, line 243
def missing_specs
  resolve.missing_specs_for(requested_dependencies)
end
missing_specs?() 点击切换源代码
# 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() 点击切换源代码
# File bundler/definition.rb, line 290
def new_deps
  @new_deps ||= @dependencies - locked_dependencies
end
new_specs() 点击切换源代码
# File bundler/definition.rb, line 235
def new_specs
  specs - @locked_specs
end
no_resolve_needed?() 点击切换源代码
# File bundler/definition.rb, line 519
def no_resolve_needed?
  !resolve_needed?
end
normalize_platforms() 点击切换源代码
# 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?() 点击切换源代码
# File bundler/definition.rb, line 515
def nothing_changed?
  !something_changed?
end
prefer_local!() 点击切换源代码
# File bundler/definition.rb, line 221
def prefer_local!
  @prefer_local = true
end
remotely!() 点击切换源代码
# File bundler/definition.rb, line 216
def remotely!
  sources.cached!
  sources.remote!
end
remove_platform(platform) 点击切换源代码
# 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() 点击切换源代码
# File bundler/definition.rb, line 239
def removed_specs
  @locked_specs - specs
end
requested_dependencies() 点击切换源代码
# File bundler/definition.rb, line 267
def requested_dependencies
  dependencies_for(requested_groups)
end
requested_specs() 点击切换源代码
# File bundler/definition.rb, line 263
def requested_specs
  specs_for(requested_groups)
end
resolve() 点击切换源代码

解析 Gemfile 中指定的所有依赖项。它确保在解析依赖项时重用已通过锁定文件解析并且是最新的依赖项

@return [SpecSet] 解析的依赖项

# 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!() 点击切换源代码
# File bundler/definition.rb, line 210
def resolve_remotely!
  remotely!

  resolve
end
resolve_with_cache!() 点击切换源代码
# File bundler/definition.rb, line 199
def resolve_with_cache!
  with_cache!

  resolve
end
setup_domain!(options = {}) 点击切换源代码

根据给定选项和定义的状态设置源。

@return [Boolean] 是否有必要获取远程信息

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

  if options[:add_checksums] || (!options[:local] && install_needed?)
    remotely!
    true
  else
    Bundler.settings.set_command_option(:jobs, 1) unless install_needed? # to avoid the overhead of Bundler::Worker
    with_cache!
    false
  end
end
spec_git_paths() 点击切换源代码
# 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() 点击切换源代码

对于给定的依赖项列表,返回一个包含所有必需依赖项的 Gemspec 的 SpecSet

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) 点击切换源代码
# File bundler/definition.rb, line 298
def specs_for(groups)
  return specs if groups.empty?
  deps = dependencies_for(groups)
  materialize(deps)
end
to_lock() 点击切换源代码
# File bundler/definition.rb, line 400
def to_lock
  require_relative "lockfile_generator"
  LockfileGenerator.generate(self)
end
unlocking?() 点击切换源代码
# File bundler/definition.rb, line 523
def unlocking?
  @unlocking
end
validate_platforms!() 点击切换源代码
# 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!() 点击切换源代码
# 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!() 点击切换源代码
# File bundler/definition.rb, line 457
def validate_runtime!
  validate_ruby!
  validate_platforms!
end
with_cache!() 点击切换源代码
# File bundler/definition.rb, line 205
def with_cache!
  sources.local!
  sources.cached!
end

私有实例方法

add_current_platform() 点击切换源代码
# File bundler/definition.rb, line 758
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) 点击切换源代码
# File bundler/definition.rb, line 1107
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) 点击切换源代码
# File bundler/definition.rb, line 1098
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() 点击切换源代码
# File bundler/definition.rb, line 774
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() 点击切换源代码
# File bundler/definition.rb, line 854
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() 点击切换源代码
# File bundler/definition.rb, line 937
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() 点击切换源代码

获取所有本地源并覆盖它们匹配的源。如果任何本地源发生更改(例如,它们指向新的修订版)或依赖于新的规范,则返回 true。

# File bundler/definition.rb, line 836
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() 点击切换源代码

从锁定的规范中删除过期的元素。如果 Gemfile 自上次生成 lockfile 以来已更改,则最常发生这种情况

# File bundler/definition.rb, line 969
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) 点击以切换源代码
# File bundler/definition.rb, line 893
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() 点击以切换源代码
# File bundler/definition.rb, line 899
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() 点击以切换源代码
# File bundler/definition.rb, line 887
def converge_paths
  sources.path_sources.any? do |source|
    specs_changed?(source)
  end
end
converge_sources() 点击以切换源代码
# File bundler/definition.rb, line 911
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) 点击以切换源代码
# File bundler/definition.rb, line 989
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?() 点击以切换源代码
# File bundler/definition.rb, line 752
def current_platform_locked?
  @platforms.any? do |bundle_platform|
    MatchPlatform.platforms_match?(bundle_platform, local_platform)
  end
end
default_source() 点击以切换源代码
# File bundler/definition.rb, line 1074
def default_source
  sources.default_source
end
dependencies_for_source_changed?(source, locked_source = source) 点击以切换源代码
# File bundler/definition.rb, line 816
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() 点击以切换源代码
# File bundler/definition.rb, line 612
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() 点击以切换源代码
# File bundler/definition.rb, line 1117
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() 点击以切换源代码
# File bundler/definition.rb, line 608
def expanded_dependencies
  dependencies_with_bundler + metadata_dependencies
end
filter_specs(specs, deps, skips: []) 点击以切换源代码
# File bundler/definition.rb, line 630
def filter_specs(specs, deps, skips: [])
  SpecSet.new(specs).for(deps, platforms, skips: skips)
end
find_most_specific_locked_ruby_platform() 点击以切换源代码
# File bundler/definition.rb, line 768
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() 点击以切换源代码
# File bundler/definition.rb, line 1047
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
install_needed?() 点击以切换源代码
# File bundler/definition.rb, line 539
def install_needed?
  resolve_needed? || missing_specs?
end
lockfile_exists?() 点击以切换源代码
# File bundler/definition.rb, line 566
def lockfile_exists?
  lockfile && File.exist?(lockfile)
end
lockfiles_equal?(current, proposed, preserve_unknown_sections) 点击以切换源代码
# File bundler/definition.rb, line 1084
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) 点击以切换源代码
# File bundler/definition.rb, line 634
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() 点击以切换源代码
# File bundler/definition.rb, line 1036
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?() 点击以切换源代码
# File bundler/definition.rb, line 748
def precompute_source_requirements_for_indirect_dependencies?
  sources.non_global_rubygems_sources.all?(&:dependency_api_available?) && !sources.aggregate_global_source?
end
pretty_dep(dep) 点击以切换源代码
# File bundler/definition.rb, line 804
def pretty_dep(dep)
  SharedHelpers.pretty_dependency(dep)
end
remove_invalid_platforms!() 点击以切换源代码
# File bundler/definition.rb, line 1128
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() 点击以切换源代码
# File bundler/definition.rb, line 1078
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) 点击以切换源代码
# File bundler/definition.rb, line 721
def reresolve_without(incomplete_specs)
  resolution_packages.delete(incomplete_specs)
  @resolve = start_resolution
end
resolution_packages() 点击以切换源代码
# File bundler/definition.rb, line 619
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
resolve_needed?() 点击以切换源代码
# File bundler/definition.rb, line 558
def resolve_needed?
  unlocking? || something_changed?
end
resolver() 点击以切换源代码
# File bundler/definition.rb, line 604
def resolver
  @resolver ||= Resolver.new(resolution_packages, gem_version_promoter, @most_specific_locked_platform)
end
should_add_extra_platforms?() 点击以切换源代码
# File bundler/definition.rb, line 562
def should_add_extra_platforms?
  !lockfile_exists? && generic_local_platform_is_ruby? && !Bundler.settings[:force_ruby_platform]
end
something_changed?() 点击以切换源代码
# File bundler/definition.rb, line 543
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_deps ||
    @locked_spec_with_invalid_deps
end
source_map() 点击以切换源代码
# File bundler/definition.rb, line 1147
def source_map
  @source_map ||= SourceMap.new(sources, dependencies, @locked_specs)
end
source_requirements() 点击以切换源代码
# File bundler/definition.rb, line 1043
def source_requirements
  @source_requirements ||= find_source_requirements
end
spec_set_incomplete_for_platform?(spec_set, platform) 点击以切换源代码
# File bundler/definition.rb, line 1143
def spec_set_incomplete_for_platform?(spec_set, platform)
  spec_set.incomplete_for_platform?(current_dependencies, platform)
end
specs_changed?(source) 点击以切换源代码

检查给定源的规范是否根据锁定的源发生了更改。

# File bundler/definition.rb, line 810
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) 点击以切换源代码
# File bundler/definition.rb, line 823
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() 点击以切换源代码
# File bundler/definition.rb, line 726
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) 点击以切换源代码
# File bundler/definition.rb, line 570
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