class Bundler::Definition

Attributes

dependencies[R]
gemfiles[R]
locked_deps[R]
locked_gems[R]
lockfile[R]
platforms[R]
requires[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 27
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 54
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
  @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

  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

    if unlock != true
      @locked_deps    = @locked_gems.dependencies
      @locked_specs   = SpecSet.new(@locked_gems.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([])
    @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 current_ruby_platform_locked? || 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 = expand_dependencies(@unlock[:gems] || [], true)
    @unlock[:gems] = @locked_specs.for(eager_unlock, false, false).map(&:name)
  end

  @dependency_changes = converge_dependencies
  @local_changes = converge_locals

  @locked_specs_incomplete_for_platform = !@locked_specs.for(requested_dependencies & expand_dependencies(locked_dependencies), true, true)

  @requires = compute_requires
end

Public Instance Methods

add_platform(platform) click to toggle source
# File bundler/definition.rb, line 437
def add_platform(platform)
  @new_platform ||= !@platforms.include?(platform)
  @platforms |= [platform]
end
current_dependencies() click to toggle source
# File bundler/definition.rb, line 222
def current_dependencies
  dependencies.select do |d|
    d.should_include? && !d.gem_platforms(@platforms).empty?
  end
end
dependencies_for(groups) click to toggle source
# File bundler/definition.rb, line 238
def dependencies_for(groups)
  groups.map!(&:to_sym)
  deps = current_dependencies.reject do |d|
    (d.groups & groups).empty?
  end
  expand_dependencies(deps)
end
ensure_equivalent_gemfile_and_lockfile(explicit_flag = false) click to toggle source
# File bundler/definition.rb, line 342
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}`."
  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}" }

  new_deps = @dependencies - locked_dependencies
  deleted_deps = locked_dependencies - @dependencies

  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 {|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 || 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 146
def gem_version_promoter
  @gem_version_promoter ||= begin
    locked_specs =
      if unlocking? && @locked_specs.empty? && !@lockfile_contents.empty?
        # Definition uses an empty set of locked_specs to indicate all gems
        # are unlocked, but GemVersionPromoter needs the locked_specs
        # for conservative comparison.
        Bundler::SpecSet.new(@locked_gems.specs)
      else
        @locked_specs
      end
    GemVersionPromoter.new(locked_specs, @unlock[:gems])
  end
end
groups() click to toggle source
# File bundler/definition.rb, line 273
def groups
  dependencies.map(&:groups).flatten.uniq
end
lock(file, preserve_unknown_sections = false) click to toggle source
# File bundler/definition.rb, line 277
def lock(file, preserve_unknown_sections = false)
  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 = Gem::Version.create(Bundler::VERSION).segments.first

    if updating_major = locked_major < current_major
      Bundler.ui.warn "Warning: the lockfile is being updated to Bundler #{current_major}, " \
                      "after which you will be unable to return to Bundler #{@locked_bundler_version.segments.first}."
    end
  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_bundler_version() click to toggle source
# File bundler/definition.rb, line 308
def locked_bundler_version
  if @locked_bundler_version && @locked_bundler_version < Gem::Version.new(Bundler::VERSION)
    new_version = Bundler::VERSION
  end

  new_version || @locked_bundler_version || Bundler::VERSION
end
locked_dependencies() click to toggle source
# File bundler/definition.rb, line 228
def locked_dependencies
  @locked_deps.values
end
locked_ruby_version() click to toggle source
# File bundler/definition.rb, line 316
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 325
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 196
def missing_specs
  resolve.materialize(requested_dependencies).missing_specs
end
missing_specs?() click to toggle source
# File bundler/definition.rb, line 200
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
  @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 447
def most_specific_locked_platform
  @platforms.min_by do |bundle_platform|
    platform_specificity_match(bundle_platform, local_platform)
  end
end
new_specs() click to toggle source
# File bundler/definition.rb, line 188
def new_specs
  specs - @locked_specs
end
nothing_changed?() click to toggle source
# File bundler/definition.rb, line 456
def nothing_changed?
  !@source_changes && !@dependency_changes && !@new_platform && !@path_changes && !@local_changes && !@locked_specs_incomplete_for_platform
end
remove_platform(platform) click to toggle source
# File bundler/definition.rb, line 442
def remove_platform(platform)
  return if @platforms.delete(Gem::Platform.new(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 192
def removed_specs
  @locked_specs - specs
end
requested_dependencies() click to toggle source
# File bundler/definition.rb, line 218
def requested_dependencies
  dependencies_for(requested_groups)
end
requested_specs() click to toggle source
# File bundler/definition.rb, line 214
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 251
def resolve
  @resolve ||= begin
    last_resolve = converge_locked_specs
    if Bundler.frozen_bundle?
      Bundler.ui.debug "Frozen, using resolution from the lockfile"
      last_resolve
    elsif !unlocking? && nothing_changed?
      Bundler.ui.debug("Found no changes, using resolution from the lockfile")
      last_resolve
    else
      # Run a resolve against the locally available gems
      Bundler.ui.debug("Found changes from the lockfile, re-resolving dependencies because #{change_reason}")
      expanded_dependencies = expand_dependencies(dependencies + metadata_dependencies, @remote)
      Resolver.resolve(expanded_dependencies, source_requirements, last_resolve, gem_version_promoter, additional_base_requirements_for_resolve, platforms)
    end
  end
end
resolve_only_locally!() click to toggle source
# File bundler/definition.rb, line 161
def resolve_only_locally!
  @remote = false
  sources.local_only!
  resolve
end
resolve_remotely!() click to toggle source
# File bundler/definition.rb, line 172
def resolve_remotely!
  @remote = true
  sources.remote!
  resolve
end
resolve_with_cache!() click to toggle source
# File bundler/definition.rb, line 167
def resolve_with_cache!
  sources.cached!
  resolve
end
spec_git_paths() click to toggle source
# File bundler/definition.rb, line 269
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 184
def specs
  @specs ||= materialize(requested_dependencies)
end
specs_for(groups) click to toggle source
# File bundler/definition.rb, line 232
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 337
def to_lock
  require_relative "lockfile_generator"
  LockfileGenerator.generate(self)
end
unlocking?() click to toggle source
# File bundler/definition.rb, line 460
def unlocking?
  @unlocking
end
validate_platforms!() click to toggle source
# File bundler/definition.rb, line 429
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 `bundle lock --add-platform #{Bundler.local_platform}` and try again."
end
validate_ruby!() click to toggle source
# File bundler/definition.rb, line 404
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 399
def validate_runtime!
  validate_ruby!
  validate_platforms!
end