class Bundler::Definition

Attributes

dependencies[R]
locked_deps[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 lib/bundler/definition.rb, line 18
def self.build(gemfile, lockfile, unlock)
  unlock ||= {}
  gemfile = Pathname.new(gemfile).expand_path

  unless gemfile.file?
    raise GemfileNotFound, "#{gemfile} not found"
  end

  Dsl.evaluate(gemfile, lockfile, unlock)
end
new(lockfile, dependencies, sources, unlock, ruby_version = nil, optional_groups = []) 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 lib/bundler/definition.rb, line 48
def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, optional_groups = [])
  @unlocking = unlock == true || !unlock.empty?

  @dependencies    = dependencies
  @sources         = sources
  @unlock          = unlock
  @optional_groups = optional_groups
  @remote          = false
  @specs           = nil
  @ruby_version    = ruby_version

  @lockfile_contents      = ""
  @locked_bundler_version = nil

  if lockfile && File.exist?(lockfile)
    @lockfile_contents = Bundler.read_file(lockfile)
    locked = LockfileParser.new(@lockfile_contents)
    @platforms      = locked.platforms
    @locked_bundler_version = locked.bundler_version

    if unlock != true
      @locked_deps    = locked.dependencies
      @locked_specs   = SpecSet.new(locked.specs)
      @locked_sources = locked.sources
    else
      @unlock         = {}
      @locked_deps    = []
      @locked_specs   = SpecSet.new([])
      @locked_sources = []
    end
  else
    @unlock         = {}
    @platforms      = []
    @locked_deps    = []
    @locked_specs   = SpecSet.new([])
    @locked_sources = []
  end

  @unlock[:gems] ||= []
  @unlock[:sources] ||= []

  current_platform = Bundler.rubygems.platforms.map { |p| generic(p) }.compact.last
  @new_platform = !@platforms.include?(current_platform)
  @platforms |= [current_platform]

  @path_changes = converge_paths
  eager_unlock = expand_dependencies(@unlock[:gems])
  @unlock[:gems] = @locked_specs.for(eager_unlock).map { |s| s.name }

  @source_changes = converge_sources
  @dependency_changes = converge_dependencies
  @local_changes = converge_locals

  fixup_dependency_types!
end

Public Instance Methods

current_dependencies() click to toggle source
# File lib/bundler/definition.rb, line 178
def current_dependencies
  dependencies.reject { |d| !d.should_include? }
end
ensure_equivalent_gemfile_and_lockfile(explicit_flag = false) click to toggle source
# File lib/bundler/definition.rb, line 332
def ensure_equivalent_gemfile_and_lockfile(explicit_flag = false)
  msg = "You are trying to install in deployment mode after changing\n"              "your Gemfile. Run `bundle install` elsewhere and add the\n"              "updated Gemfile.lock to version control."

  unless explicit_flag
    msg += "\n\nIf this is a development machine, remove the Gemfile "                 "freeze \nby running `bundle install --no-deployment`."
  end

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

  gemfile_sources = sources.lock_sources
  if @locked_sources != gemfile_sources
    new_sources = gemfile_sources - @locked_sources
    deleted_sources = @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

  new_deps = @dependencies - @locked_deps
  deleted_deps = @locked_deps - @dependencies

  if new_deps.any?
    added.concat new_deps.map { |d| "* #{pretty_dep(d)}" }
  end

  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  { |d| both_sources[d.name][1] = d.source }

  both_sources.each do |name, (dep, lock_source)|
    if (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
  end

  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?
end
fixup_dependency_types!() click to toggle source
# File lib/bundler/definition.rb, line 104
def fixup_dependency_types!
  # XXX This is a temporary workaround for a bug when using rubygems 1.8.15
  # where 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.
  # Test coverage to catch a regression on this is in gemspec_spec.rb
  @dependencies.each do |d|
    if ld = @locked_deps.find { |l| l.name == d.name }
      ld.instance_variable_set(:@type, d.type)
    end
  end
end
groups() click to toggle source
# File lib/bundler/definition.rb, line 240
def groups
  dependencies.map { |d| d.groups }.flatten.uniq
end
has_local_dependencies?() click to toggle source
# File lib/bundler/definition.rb, line 232
def has_local_dependencies?
  !sources.path_sources.empty? || !sources.git_sources.empty?
end
has_rubygems_remotes?() click to toggle source
# File lib/bundler/definition.rb, line 228
def has_rubygems_remotes?
  sources.rubygems_sources.any? {|s| s.remotes.any? }
end
index() click to toggle source
# File lib/bundler/definition.rb, line 205
def index
  @index ||= Index.build do |idx|
    dependency_names = @dependencies.map { |d| d.name }

    sources.all_sources.each do |source|
      source.dependency_names = dependency_names.dup
      idx.add_source source.specs
      dependency_names -= pinned_spec_names(source.specs)
      dependency_names.push(*source.unmet_deps).uniq!
    end
  end
end
lock(file, preserve_bundled_with = false) click to toggle source
# File lib/bundler/definition.rb, line 244
def lock(file, preserve_bundled_with = 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_bundled_with ||= !updating_major && (Bundler.settings[:frozen] || !@unlocking)
  return if lockfiles_equal?(@lockfile_contents, contents, preserve_bundled_with)

  if Bundler.settings[:frozen]
    Bundler.ui.error "Cannot write a changed lockfile while frozen."
    return
  end

  File.open(file, 'wb'){|f| f.puts(contents) }
rescue Errno::EACCES
  raise Bundler::InstallError,
    "There was an error while trying to write to Gemfile.lock. It is likely that \n"          "you need to allow write permissions for the file at path: \n"          "#{File.expand_path(file)}"
end
lock_version() click to toggle source

Returns the version of Bundler that is creating or has created Gemfile.lock. Used in to_lock.

# File lib/bundler/definition.rb, line 279
def lock_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
missing_specs() click to toggle source
# File lib/bundler/definition.rb, line 164
def missing_specs
  missing = []
  resolve.materialize(requested_dependencies, missing)
  missing
end
new_platform?() click to toggle source
# File lib/bundler/definition.rb, line 160
def new_platform?
  @new_platform
end
new_specs() click to toggle source
# File lib/bundler/definition.rb, line 152
def new_specs
  specs - @locked_specs
end
removed_specs() click to toggle source
# File lib/bundler/definition.rb, line 156
def removed_specs
  @locked_specs - specs
end
requested_specs() click to toggle source
# File lib/bundler/definition.rb, line 170
def requested_specs
  @requested_specs ||= begin
    groups = requested_groups
    groups.map! { |g| g.to_sym }
    specs_for(groups)
  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 lib/bundler/definition.rb, line 193
def resolve
  @resolve ||= begin
    last_resolve = converge_locked_specs
    if Bundler.settings[:frozen] || (!@unlocking && nothing_changed?)
      last_resolve
    else
      # Run a resolve against the locally available gems
      last_resolve.merge Resolver.resolve(expanded_dependencies, index, source_requirements, last_resolve)
    end
  end
end
resolve_remotely!() click to toggle source
# File lib/bundler/definition.rb, line 125
def resolve_remotely!
  raise "Specs already loaded" if @specs
  @remote = true
  sources.remote!
  specs
end
resolve_with_cache!() click to toggle source
# File lib/bundler/definition.rb, line 119
def resolve_with_cache!
  raise "Specs already loaded" if @specs
  sources.cached!
  specs
end
rubygems_index() click to toggle source

used when frozen is enabled so we can find the bundler spec, even if (say) a git gem is not checked out.

# File lib/bundler/definition.rb, line 220
def rubygems_index
  @rubygems_index ||= Index.build do |idx|
    sources.rubygems_sources.each do |rubygems|
      idx.add_source rubygems.specs
    end
  end
end
spec_git_paths() click to toggle source
# File lib/bundler/definition.rb, line 236
def spec_git_paths
  sources.git_sources.map {|s| s.path.to_s }
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 lib/bundler/definition.rb, line 138
def specs
  @specs ||= begin
    specs = resolve.materialize(Bundler.settings[:cache_all_platforms] ? dependencies : requested_dependencies)

    unless specs["bundler"].any?
      local = Bundler.settings[:frozen] ? rubygems_index : index
      bundler = local.search(Gem::Dependency.new('bundler', VERSION)).last
      specs["bundler"] = bundler if bundler
    end

    specs
  end
end
specs_for(groups) click to toggle source
# File lib/bundler/definition.rb, line 182
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
to_lock() click to toggle source
# File lib/bundler/definition.rb, line 287
def to_lock
  out = ""

  sources.lock_sources.each do |source|
    # Add the source header
    out << source.to_lock
    # Find all specs for this source
    resolve.
      select { |s| source.can_lock?(s) }.
      # This needs to be sorted by full name so that
      # gems with the same name, but different platform
      # are ordered consistently
      sort_by { |s| s.full_name }.
      each do |spec|
        next if spec.name == 'bundler'
        out << spec.to_lock
      end
    out << "\n"
  end

  out << "PLATFORMS\n"

  platforms.map { |p| p.to_s }.sort.each do |p|
    out << "  #{p}\n"
  end

  out << "\n"
  out << "DEPENDENCIES\n"

  handled = []
  dependencies.
    sort_by { |d| d.to_s }.
    each do |dep|
      next if handled.include?(dep.name)
      out << dep.to_lock
      handled << dep.name
  end

  # Record the version of Bundler that was used to create the lockfile
  out << "\nBUNDLED WITH\n"
  out << "   #{lock_version}\n"

  out
end
validate_ruby!() click to toggle source
# File lib/bundler/definition.rb, line 391
def validate_ruby!
  return unless ruby_version

  if diff = ruby_version.diff(Bundler.ruby_version)
    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.ruby_version.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