class Bundler::Definition

Attributes

no_lock[RW]

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

dependencies[R]
gemfiles[R]
locked_deps[R]
locked_gems[R]
lockfile[R]
platforms[R]
ruby_version[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 31
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 58
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
  @remote          = false
  @prefer_local    = false
  @specs           = nil
  @ruby_version    = ruby_version
  @gemfiles        = gemfiles

  @lockfile               = lockfile
  @lockfile_contents      = String.new
  @locked_bundler_version = nil
  @locked_ruby_version    = nil
  @new_platform = nil
  @removed_platform = nil

  if lockfile && File.exist?(lockfile)
    @lockfile_contents = Bundler.read_file(lockfile)
    @locked_gems = LockfileParser.new(@lockfile_contents)
    @locked_platforms = @locked_gems.platforms
    @platforms = @locked_platforms.dup
    @locked_bundler_version = @locked_gems.bundler_version
    @locked_ruby_version = @locked_gems.ruby_version
    @originally_locked_specs = SpecSet.new(@locked_gems.specs)

    if unlock != true
      @locked_deps    = @locked_gems.dependencies
      @locked_specs   = @originally_locked_specs
      @locked_sources = @locked_gems.sources
    else
      @unlock         = {}
      @locked_deps    = {}
      @locked_specs   = SpecSet.new([])
      @locked_sources = []
    end
  else
    @unlock         = {}
    @platforms      = []
    @locked_gems    = nil
    @locked_deps    = {}
    @locked_specs   = SpecSet.new([])
    @originally_locked_specs = @locked_specs
    @locked_sources = []
    @locked_platforms = []
  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

  @unlock[:sources] ||= []
  @unlock[:ruby] ||= if @ruby_version && locked_ruby_version_object
    @ruby_version.diff(locked_ruby_version_object)
  end
  @unlocking ||= @unlock[:ruby] ||= (!@locked_ruby_version ^ !@ruby_version)

  add_current_platform unless Bundler.frozen_bundle?

  converge_path_sources_to_gemspec_sources
  @path_changes = converge_paths
  @source_changes = converge_sources

  if @unlock[:conservative]
    @unlock[:gems] ||= @dependencies.map(&:name)
  else
    eager_unlock = (@unlock[:gems] || []).map {|name| Dependency.new(name, ">= 0") }
    @unlock[:gems] = @locked_specs.for(eager_unlock, false, platforms).map(&:name).uniq
  end

  @dependency_changes = converge_dependencies
  @local_changes = converge_locals

  @incomplete_lockfile = check_missing_lockfile_specs
end

Public Instance Methods

add_platform(platform) click to toggle source
# File bundler/definition.rb, line 454
def add_platform(platform)
  @new_platform ||= !@platforms.include?(platform)
  @platforms |= [platform]
end
current_dependencies() click to toggle source
# File bundler/definition.rb, line 235
def current_dependencies
  dependencies.select do |d|
    d.should_include? && !d.gem_platforms([generic_local_platform]).empty?
  end
end
deleted_deps() click to toggle source
# File bundler/definition.rb, line 249
def deleted_deps
  @deleted_deps ||= locked_dependencies - @dependencies
end
dependencies_for(groups) click to toggle source
# File bundler/definition.rb, line 259
def dependencies_for(groups)
  groups.map!(&:to_sym)
  current_dependencies.reject do |d|
    (d.groups & groups).empty?
  end
end
ensure_equivalent_gemfile_and_lockfile(explicit_flag = false) click to toggle source
# File bundler/definition.rb, line 357
def ensure_equivalent_gemfile_and_lockfile(explicit_flag = false)
  msg = String.new
  msg << "You are trying to install in deployment mode after changing\n" \
         "your Gemfile. Run `bundle install` elsewhere and add the\n" \
         "updated #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)} to version control."

  unless explicit_flag
    suggested_command = if Bundler.settings.locations("frozen").keys.&([:global, :local]).any?
      "bundle config unset frozen"
    elsif Bundler.settings.locations("deployment").keys.&([:global, :local]).any?
      "bundle config unset deployment"
    end
    msg << "\n\nIf this is a development machine, remove the #{Bundler.default_gemfile} " \
           "freeze \nby running `#{suggested_command}`." if suggested_command
  end

  added =   []
  deleted = []
  changed = []

  new_platforms = @platforms - @locked_platforms
  deleted_platforms = @locked_platforms - @platforms
  added.concat new_platforms.map {|p| "* platform: #{p}" }
  deleted.concat deleted_platforms.map {|p| "* platform: #{p}" }

  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] = [] }
  @dependencies.each {|d| both_sources[d.name][0] = d }

  locked_dependencies.each do |d|
    next if !Bundler.feature_flag.bundler_3_mode? && @locked_specs[d.name].empty?

    both_sources[d.name][1] = d
  end

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

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

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

  reason = change_reason
  msg << "\n\n#{reason.split(", ").map(&:capitalize).join("\n")}" unless reason.strip.empty?
  msg << "\n\nYou have added to the Gemfile:\n" << added.join("\n") if added.any?
  msg << "\n\nYou have deleted from the Gemfile:\n" << deleted.join("\n") if deleted.any?
  msg << "\n\nYou have changed in the Gemfile:\n" << changed.join("\n") if changed.any?
  msg << "\n"

  raise ProductionError, msg if added.any? || deleted.any? || changed.any? || !nothing_changed?
end
gem_version_promoter() click to toggle source
# File bundler/definition.rb, line 152
def gem_version_promoter
  @gem_version_promoter ||= GemVersionPromoter.new
end
groups() click to toggle source
# File bundler/definition.rb, line 297
def groups
  dependencies.map(&:groups).flatten.uniq
end
lock(file, preserve_unknown_sections = false) click to toggle source
# File bundler/definition.rb, line 301
def lock(file, preserve_unknown_sections = false)
  return if Definition.no_lock

  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.gem_version.segments.first

    updating_major = locked_major < current_major
  end

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

  return if file && File.exist?(file) && lockfiles_equal?(@lockfile_contents, contents, preserve_unknown_sections)

  if Bundler.frozen_bundle?
    Bundler.ui.error "Cannot write a changed lockfile while frozen."
    return
  end

  SharedHelpers.filesystem_access(file) do |p|
    File.open(p, "wb") {|f| f.puts(contents) }
  end
end
locked_dependencies() click to toggle source
# File bundler/definition.rb, line 241
def locked_dependencies
  @locked_deps.values
end
locked_ruby_version() click to toggle source
# File bundler/definition.rb, line 331
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 340
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 208
def missing_specs
  resolve.materialize(requested_dependencies).missing_specs
end
missing_specs?() click to toggle source
# File bundler/definition.rb, line 212
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
  @specs = nil
  @gem_version_promoter = nil

  Bundler.ui.debug "The definition is missing dependencies, failed to resolve & materialize locally (#{e})"
  true
end
most_specific_locked_platform() click to toggle source
# File bundler/definition.rb, line 466
def most_specific_locked_platform
  @platforms.min_by do |bundle_platform|
    platform_specificity_match(bundle_platform, local_platform)
  end
end
new_deps() click to toggle source
# File bundler/definition.rb, line 245
def new_deps
  @new_deps ||= @dependencies - locked_dependencies
end
new_specs() click to toggle source
# File bundler/definition.rb, line 200
def new_specs
  specs - @locked_specs
end
nothing_changed?() click to toggle source
# File bundler/definition.rb, line 475
def nothing_changed?
  !@source_changes && !@dependency_changes && !@new_platform && !@path_changes && !@local_changes && !@incomplete_lockfile
end
remove_platform(platform) click to toggle source
# File bundler/definition.rb, line 459
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 204
def removed_specs
  @locked_specs - specs
end
requested_dependencies() click to toggle source
# File bundler/definition.rb, line 231
def requested_dependencies
  dependencies_for(requested_groups)
end
requested_specs() click to toggle source
# File bundler/definition.rb, line 227
def requested_specs
  specs_for(requested_groups)
end
resolution_mode=(options) click to toggle source
# File bundler/definition.rb, line 173
def resolution_mode=(options)
  if options["local"]
    @remote = false
  else
    @remote = true
    @prefer_local = options["prefer-local"]
  end
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 271
def resolve
  @resolve ||= if Bundler.frozen_bundle?
    Bundler.ui.debug "Frozen, using resolution from the lockfile"
    @locked_specs
  elsif !unlocking? && nothing_changed?
    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
    Bundler.ui.debug "Found changes from the lockfile, re-resolving dependencies because #{change_reason}"
    start_resolution
  end
end
resolve_only_locally!() click to toggle source
# File bundler/definition.rb, line 156
def resolve_only_locally!
  @remote = false
  sources.local_only!
  resolve
end
resolve_remotely!() click to toggle source
# File bundler/definition.rb, line 167
def resolve_remotely!
  @remote = true
  sources.remote!
  resolve
end
resolve_with_cache!() click to toggle source
# File bundler/definition.rb, line 162
def resolve_with_cache!
  sources.cached!
  resolve
end
setup_sources_for_resolve() click to toggle source
# File bundler/definition.rb, line 182
def setup_sources_for_resolve
  if @remote == false
    sources.cached!
  else
    sources.remote!
  end
end
spec_git_paths() click to toggle source
# File bundler/definition.rb, line 293
def spec_git_paths
  sources.git_sources.map {|s| File.realpath(s.path) if File.exist?(s.path) }.compact
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 196
def specs
  @specs ||= materialize(requested_dependencies)
end
specs_for(groups) click to toggle source
# File bundler/definition.rb, line 253
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 352
def to_lock
  require_relative "lockfile_generator"
  LockfileGenerator.generate(self)
end
unlocking?() click to toggle source
# File bundler/definition.rb, line 479
def unlocking?
  @unlocking
end
validate_platforms!() click to toggle source
# File bundler/definition.rb, line 446
def validate_platforms!
  return if current_platform_locked?

  raise ProductionError, "Your bundle only supports platforms #{@platforms.map(&:to_s)} " \
    "but your local platform is #{Bundler.local_platform}. " \
    "Add the current platform to the lockfile with\n`bundle lock --add-platform #{Bundler.local_platform}` and try again."
end
validate_ruby!() click to toggle source
# File bundler/definition.rb, line 421
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 416
def validate_runtime!
  validate_ruby!
  validate_platforms!
end