In Files

  • bundler/definition.rb

Class/Module Index [+]

Quicksearch

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]
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 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
  @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 226
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 242
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 337
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 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 150
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 277
def groups
  dependencies.map(&:groups).flatten.uniq
end
            
lock(file, preserve_unknown_sections = false) click to toggle source
 
               # File bundler/definition.rb, line 281
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 = Gem::Version.create(Bundler::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 232
def locked_dependencies
  @locked_deps.values
end
            
locked_ruby_version() click to toggle source
 
               # File bundler/definition.rb, line 311
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 320
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 200
def missing_specs
  resolve.materialize(requested_dependencies).missing_specs
end
            
missing_specs?() click to toggle source
 
               # File bundler/definition.rb, line 204
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 192
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 196
def removed_specs
  @locked_specs - specs
end
            
requested_dependencies() click to toggle source
 
               # File bundler/definition.rb, line 222
def requested_dependencies
  dependencies_for(requested_groups)
end
            
requested_specs() click to toggle source
 
               # File bundler/definition.rb, line 218
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 255
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, true)
      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 165
def resolve_only_locally!
  @remote = false
  sources.local_only!
  resolve
end
            
resolve_remotely!() click to toggle source
 
               # File bundler/definition.rb, line 176
def resolve_remotely!
  @remote = true
  sources.remote!
  resolve
end
            
resolve_with_cache!() click to toggle source
 
               # File bundler/definition.rb, line 171
def resolve_with_cache!
  sources.cached!
  resolve
end
            
spec_git_paths() click to toggle source
 
               # File bundler/definition.rb, line 273
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 188
def specs
  @specs ||= materialize(requested_dependencies)
end
            
specs_for(groups) click to toggle source
 
               # File bundler/definition.rb, line 236
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 332
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
            
There is an updated format of the API docs for this version here.