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 28
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
             
            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 55
def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, optional_groups = [], gemfiles = [])
  if [true, false].include?(unlock)
    @unlocking_bundler = false
    @unlocking = unlock
  else
    unlock = unlock.dup
    @unlocking_bundler = unlock.delete(:bundler)
    unlock.delete_if {|_k, v| Array(v).empty? }
    @unlocking = !unlock.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
  @locked_specs_incomplete_for_platform = false
  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
  @unlock[:gems] ||= []
  @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
  unless @unlock[:lock_shared_dependencies]
    eager_unlock = expand_dependencies(@unlock[:gems], true)
    @unlock[:gems] = @locked_specs.for(eager_unlock, [], false, false, false).map(&:name)
  end
  @dependency_changes = converge_dependencies
  @local_changes = converge_locals
  @requires = compute_requires
end
             
             
               # File bundler/definition.rb, line 521
def add_current_platform
  current_platform = Bundler.local_platform
  add_platform(current_platform) if Bundler.feature_flag.specific_platform?
  add_platform(generic(current_platform))
end
             
             
               # File bundler/definition.rb, line 511
def add_platform(platform)
  @new_platform ||= !@platforms.include?(platform)
  @platforms |= [platform]
end
             
             
               # File bundler/definition.rb, line 230
def current_dependencies
  dependencies.select(&:should_include?)
end
             
             
               # File bundler/definition.rb, line 392
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")[:global]
      "bundle config --delete frozen"
    elsif Bundler.settings.locations("deployment").keys.&([:global, :local]).any?
      "bundle config --delete deployment"
    else
      "bundle install --no-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}" }
  gemfile_sources = sources.lock_sources
  new_sources = gemfile_sources - @locked_sources
  deleted_sources = @locked_sources - gemfile_sources
  new_deps = @dependencies - @locked_deps.values
  deleted_deps = @locked_deps.values - @dependencies
  # Check if it is possible that the source is only changed thing
  if (new_deps.empty? && deleted_deps.empty?) && (!new_sources.empty? && !deleted_sources.empty?)
    new_sources.reject! {|source| (source.path? && source.path.exist?) || equivalent_rubygems_remotes?(source) }
    deleted_sources.reject! {|source| (source.path? && source.path.exist?) || equivalent_rubygems_remotes?(source) }
  end
  if @locked_sources != gemfile_sources
    if new_sources.any?
      added.concat new_sources.map {|source| "* source: #{source}" }
    end
    if deleted_sources.any?
      deleted.concat deleted_sources.map {|source| "* source: #{source}" }
    end
  end
  added.concat new_deps.map {|d| "* #{pretty_dep(d)}" } if new_deps.any?
  if deleted_deps.any?
    deleted.concat deleted_deps.map {|d| "* #{pretty_dep(d)}" }
  end
  both_sources = Hash.new {|h, k| h[k] = [] }
  @dependencies.each {|d| both_sources[d.name][0] = d }
  @locked_deps.each  {|name, d| both_sources[name][1] = d.source }
  both_sources.each do |name, (dep, lock_source)|
    next unless (dep.nil? && !lock_source.nil?) || (!dep.nil? && !lock_source.nil? && !lock_source.can_lock?(dep))
    gemfile_source_name = (dep && dep.source) || "no specified source"
    lockfile_source_name = lock_source || "no specified source"
    changed << "* #{name} from `#{gemfile_source_name}` to `#{lockfile_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
             
             
               # File bundler/definition.rb, line 531
def find_indexed_specs(current_spec)
  index[current_spec.name].select {|spec| spec.match_platform(current_spec.platform) }.sort_by(&:version)
end
             
             
               # File bundler/definition.rb, line 527
def find_resolved_spec(current_spec)
  specs.find_by_name_and_platform(current_spec.name, current_spec.platform)
end
             
             
               # File bundler/definition.rb, line 133
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
             
             
               # File bundler/definition.rb, line 323
def groups
  dependencies.map(&:groups).flatten.uniq
end
             
             
               # File bundler/definition.rb, line 315
def has_local_dependencies?
  !sources.path_sources.empty? || !sources.git_sources.empty?
end
             
             
               # File bundler/definition.rb, line 311
def has_rubygems_remotes?
  sources.rubygems_sources.any? {|s| s.remotes.any? }
end
             
             
               # File bundler/definition.rb, line 267
def index
  @index ||= Index.build do |idx|
    dependency_names = @dependencies.map(&:name)
    sources.all_sources.each do |source|
      source.dependency_names = dependency_names - pinned_spec_names(source)
      idx.add_source source.specs
      dependency_names.concat(source.unmet_deps).uniq!
    end
    double_check_for_index(idx, dependency_names)
  end
end
             
             
               # File bundler/definition.rb, line 327
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
             
             
               # File bundler/definition.rb, line 358
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
             
             
               # File bundler/definition.rb, line 366
def locked_ruby_version
  return unless ruby_version
  if @unlock[:ruby] || !@locked_ruby_version
    Bundler::RubyVersion.system
  else
    @locked_ruby_version
  end
end
             
             
               # File bundler/definition.rb, line 375
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
             
             
               # File bundler/definition.rb, line 201
def missing_specs
  missing = []
  resolve.materialize(requested_dependencies, missing)
  missing
end
             
             
               # File bundler/definition.rb, line 207
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
  @index = nil
  @resolve = nil
  @specs = nil
  @gem_version_promoter = nil
  Bundler.ui.debug "The definition is missing dependencies, failed to resolve & materialize locally (#{e})"
  true
end
             
             
               # File bundler/definition.rb, line 197
def new_platform?
  @new_platform
end
             
             
               # File bundler/definition.rb, line 189
def new_specs
  specs - @locked_specs
end
             
             
               # File bundler/definition.rb, line 538
def nothing_changed?
  !@source_changes && !@dependency_changes && !@new_platform && !@path_changes && !@local_changes && !@locked_specs_incomplete_for_platform
end
             
             
               # File bundler/definition.rb, line 516
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
             
             
               # File bundler/definition.rb, line 193
def removed_specs
  @locked_specs - specs
end
             
             
               # File bundler/definition.rb, line 222
def requested_specs
  @requested_specs ||= begin
    groups = requested_groups
    groups.map!(&:to_sym)
    specs_for(groups)
  end
end
             
            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 245
def resolve
  @resolve ||= begin
    last_resolve = converge_locked_specs
    resolve =
      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}")
        last_resolve.merge Resolver.resolve(expanded_dependencies, index, source_requirements, last_resolve, gem_version_promoter, additional_base_requirements_for_resolve, platforms)
      end
    # filter out gems that _can_ be installed on multiple platforms, but don't need
    # to be
    resolve.for(expand_dependencies(dependencies, true), [], false, false, false)
  end
end
             
             
               # File bundler/definition.rb, line 154
def resolve_remotely!
  raise "Specs already loaded" if @specs
  @remote = true
  sources.remote!
  specs
end
             
             
               # File bundler/definition.rb, line 148
def resolve_with_cache!
  raise "Specs already loaded" if @specs
  sources.cached!
  specs
end
             
             
               # File bundler/definition.rb, line 319
def spec_git_paths
  sources.git_sources.map {|s| s.path.to_s }
end
             
            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 167
def specs
  @specs ||= begin
    begin
      specs = resolve.materialize(Bundler.settings[:cache_all_platforms] ? dependencies : requested_dependencies)
    rescue GemNotFound => e # Handle yanked gem
      gem_name, gem_version = extract_gem_info(e)
      locked_gem = @locked_specs[gem_name].last
      raise if locked_gem.nil? || locked_gem.version.to_s != gem_version || !@remote
      raise GemNotFound, "Your bundle is locked to #{locked_gem}, but that version could not " \
                         "be found in any of the sources listed in your Gemfile. If you haven't changed sources, " \
                         "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
    unless specs["bundler"].any?
      bundler = sources.metadata_source.specs.search(Gem::Dependency.new("bundler", VERSION)).last
      specs["bundler"] = bundler
    end
    specs
  end
end
             
             
               # File bundler/definition.rb, line 234
def specs_for(groups)
  deps = dependencies.select {|d| (d.groups & groups).any? }
  deps.delete_if {|d| !d.should_include? }
  specs.for(expand_dependencies(deps))
end
             
             
               # File bundler/definition.rb, line 387
def to_lock
  require "bundler/lockfile_generator"
  LockfileGenerator.generate(self)
end
             
             
               # File bundler/definition.rb, line 542
def unlocking?
  @unlocking
end
             
             
               # File bundler/definition.rb, line 499
def validate_platforms!
  return if @platforms.any? do |bundle_platform|
    Bundler.rubygems.platforms.any? do |local_platform|
      MatchPlatform.platforms_match?(bundle_platform, local_platform)
    end
  end
  raise ProductionError, "Your bundle only supports platforms #{@platforms.map(&:to_s)} " \
    "but your local platforms are #{Bundler.rubygems.platforms.map(&:to_s)}, and " \
    "there's no compatible match between those two lists."
end
             
             
               # File bundler/definition.rb, line 474
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