class Bundler::Definition - bundler: Ruby Standard Library Documentation

# 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 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) 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_resolve_needed?() click to toggle source

    # File bundler/definition.rb, line 519
    def no_resolve_needed?
      !resolve_needed?
    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?
      !something_changed?
    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[: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() 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 523
    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 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) click to toggle source

    # 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) click to toggle source

    # 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() click to toggle source

    # 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() click to toggle source

    # 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() click to toggle source

    # 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() 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 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() 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 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) click to toggle 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() click to toggle source

    # 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() click to toggle source

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

    # 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?() click to toggle source

    # 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() click to toggle source

    # File bundler/definition.rb, line 1074
    def default_source
      sources.default_source
    end

dependencies_for_source_changed?(source, locked_source = source) click to toggle 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() click to toggle source

    # 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() click to toggle source

    # 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() click to toggle source

    # File bundler/definition.rb, line 608
    def expanded_dependencies
      dependencies_with_bundler + metadata_dependencies
    end

filter_specs(specs, deps, skips: []) click to toggle source

    # 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() click to toggle source

    # 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() click to toggle source

    # 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?() click to toggle source

    # File bundler/definition.rb, line 539
    def install_needed?
      resolve_needed? || missing_specs?
    end

lockfile_exists?() click to toggle source

    # File bundler/definition.rb, line 566
    def lockfile_exists?
      lockfile && File.exist?(lockfile)
    end

lockfiles_equal?(current, proposed, preserve_unknown_sections) click to toggle source

    # 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) click to toggle source

    # 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() click to toggle source

    # 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?() click to toggle source

    # 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) click to toggle source

    # File bundler/definition.rb, line 804
    def pretty_dep(dep)
      SharedHelpers.pretty_dependency(dep)
    end

remove_invalid_platforms!() click to toggle source

    # 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() click to toggle source

    # 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) click to toggle source

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

    # File bundler/definition.rb, line 558
    def resolve_needed?
      unlocking? || something_changed?
    end

resolver() click to toggle source

    # 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?() click to toggle source

    # 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?() click to toggle source

    # 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() click to toggle source

    # File bundler/definition.rb, line 1147
    def source_map
      @source_map ||= SourceMap.new(sources, dependencies, @locked_specs)
    end

source_requirements() click to toggle source

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

    # 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) click to toggle source

    # 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