In Files

  • bundler/definition.rb

Class/Module Index [+]

Quicksearch

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
  @locked_specs_incomplete_for_platform = false
  @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, false).map(&:name)
  end

  @dependency_changes = converge_dependencies
  @local_changes = converge_locals

  @requires = compute_requires
end
            

Public Instance Methods

add_platform(platform) click to toggle source
 
               # File bundler/definition.rb, line 481
def add_platform(platform)
  @new_platform ||= !@platforms.include?(platform)
  @platforms |= [platform]
end
            
current_dependencies() click to toggle source
 
               # File bundler/definition.rb, line 251
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 262
def dependencies_for(groups)
  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 368
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}" }

  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 if lock_source.nil? || (dep && lock_source.can_lock?(dep))
    gemfile_source_name = (dep && dep.source) || "no specified source"
    lockfile_source_name = lock_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
            
gem_version_promoter() click to toggle source
 
               # File bundler/definition.rb, line 145
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 299
def groups
  dependencies.map(&:groups).flatten.uniq
end
            
has_rubygems_remotes?() click to toggle source
 
               # File bundler/definition.rb, line 291
def has_rubygems_remotes?
  sources.rubygems_sources.any? {|s| s.remotes.any? }
end
            
lock(file, preserve_unknown_sections = false) click to toggle source
 
               # File bundler/definition.rb, line 303
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 334
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_ruby_version() click to toggle source
 
               # File bundler/definition.rb, line 342
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 351
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 217
def missing_specs
  missing = []
  resolve.materialize(requested_dependencies, missing)
  missing
end
            
missing_specs?() click to toggle source
 
               # File bundler/definition.rb, line 223
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 491
def most_specific_locked_platform
  @platforms.min_by do |bundle_platform|
    platform_specificity_match(bundle_platform, local_platform)
  end
end
            
multisource_allowed?() click to toggle source
 
               # File bundler/definition.rb, line 160
def multisource_allowed?
  @multisource_allowed
end
            
new_specs() click to toggle source
 
               # File bundler/definition.rb, line 209
def new_specs
  specs - @locked_specs
end
            
nothing_changed?() click to toggle source
 
               # File bundler/definition.rb, line 500
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 486
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 213
def removed_specs
  @locked_specs - specs
end
            
requested_dependencies() click to toggle source
 
               # File bundler/definition.rb, line 245
def requested_dependencies
  groups = requested_groups
  groups.map!(&:to_sym)
  dependencies_for(groups)
end
            
requested_specs() click to toggle source
 
               # File bundler/definition.rb, line 237
def requested_specs
  @requested_specs ||= begin
    groups = requested_groups
    groups.map!(&: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 bundler/definition.rb, line 273
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 164
def resolve_only_locally!
  @remote = false
  sources.local_only!
  resolve
end
            
resolve_remotely!() click to toggle source
 
               # File bundler/definition.rb, line 175
def resolve_remotely!
  @remote = true
  sources.remote!
  resolve
end
            
resolve_with_cache!() click to toggle source
 
               # File bundler/definition.rb, line 170
def resolve_with_cache!
  sources.cached!
  resolve
end
            
spec_git_paths() click to toggle source
 
               # File bundler/definition.rb, line 295
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 187
def specs
  @specs ||= begin
    begin
      specs = resolve.materialize(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} from #{locked_gem.source}, but that version can " \
                         "no longer be found in that source. 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
            
specs_for(groups) click to toggle source
 
               # File bundler/definition.rb, line 257
def specs_for(groups)
  deps = dependencies_for(groups)
  SpecSet.new(specs.for(expand_dependencies(deps)))
end
            
to_lock() click to toggle source
 
               # File bundler/definition.rb, line 363
def to_lock
  require_relative "lockfile_generator"
  LockfileGenerator.generate(self)
end
            
unlocking?() click to toggle source
 
               # File bundler/definition.rb, line 504
def unlocking?
  @unlocking
end
            
validate_platforms!() click to toggle source
 
               # File bundler/definition.rb, line 473
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 448
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 443
def validate_runtime!
  validate_ruby!
  validate_platforms!
end