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 17 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
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
# File lib/bundler/definition.rb, line 46 def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil) @unlocking = unlock == true || !unlock.empty? @dependencies, @sources, @unlock = dependencies, sources, unlock @remote = false @specs = nil @lockfile_contents = "" @ruby_version = ruby_version if lockfile && File.exist?(lockfile) @lockfile_contents = Bundler.read_file(lockfile) locked = LockfileParser.new(@lockfile_contents) @platforms = locked.platforms 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
# File lib/bundler/definition.rb, line 170 def current_dependencies dependencies.reject { |d| !d.should_include? } end
# File lib/bundler/definition.rb, line 300 def ensure_equivalent_gemfile_and_lockfile(explicit_flag = false) changes = 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.all_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 changes = true end both_sources = Hash.new { |h,k| h[k] = ["no specified source", "no specified source"] } @dependencies.each { |d| both_sources[d.name][0] = d.source if d.source } @locked_deps.each { |d| both_sources[d.name][1] = d.source if d.source } both_sources.delete_if { |k,v| v[0] == v[1] } if @dependencies != @locked_deps 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.each do |name, sources| changed << "* #{name} from `#{sources[0]}` to `#{sources[1]}`" end changes = true 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
# File lib/bundler/definition.rb, line 96 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
# File lib/bundler/definition.rb, line 233 def groups dependencies.map { |d| d.groups }.flatten.uniq end
# File lib/bundler/definition.rb, line 225 def has_local_dependencies? !sources.path_sources.empty? || !sources.git_sources.empty? end
# File lib/bundler/definition.rb, line 221 def has_rubygems_remotes? sources.rubygems_sources.any? {|s| s.remotes.any? } end
# File lib/bundler/definition.rb, line 198 def index @index ||= Index.build do |idx| dependency_names = @dependencies.dup || [] dependency_names.map! {|d| d.name } sources.all_sources.each do |s| s.dependency_names = dependency_names idx.add_source s.specs dependency_names.push(*s.unmet_deps).uniq! end end end
# File lib/bundler/definition.rb, line 237 def lock(file) 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") return if @lockfile_contents == contents 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
# File lib/bundler/definition.rb, line 156 def missing_specs missing = [] resolve.materialize(requested_dependencies, missing) missing end
# File lib/bundler/definition.rb, line 152 def new_platform? @new_platform end
# File lib/bundler/definition.rb, line 144 def new_specs specs - @locked_specs end
# File lib/bundler/definition.rb, line 148 def removed_specs @locked_specs - specs end
# File lib/bundler/definition.rb, line 162 def requested_specs @requested_specs ||= begin groups = self.groups - Bundler.settings.without groups.map! { |g| g.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 lib/bundler/definition.rb, line 185 def resolve @resolve ||= begin if Bundler.settings[:frozen] || (!@unlocking && nothing_changed?) @locked_specs else last_resolve = converge_locked_specs # Run a resolve against the locally available gems last_resolve.merge Resolver.resolve(expanded_dependencies, index, source_requirements, last_resolve) end end end
# File lib/bundler/definition.rb, line 117 def resolve_remotely! raise "Specs already loaded" if @specs @remote = true sources.remote! specs end
# File lib/bundler/definition.rb, line 111 def resolve_with_cache! raise "Specs already loaded" if @specs sources.cached! specs end
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 213 def rubygems_index @rubygems_index ||= Index.build do |idx| sources.rubygems_sources.each do |rubygems| idx.add_source rubygems.specs end end end
# File lib/bundler/definition.rb, line 229 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 lib/bundler/definition.rb, line 130 def specs @specs ||= begin specs = resolve.materialize(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
# File lib/bundler/definition.rb, line 174 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 lib/bundler/definition.rb, line 259 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 out end
# File lib/bundler/definition.rb, line 364 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
Generated with the Darkfish Rdoc Generator 2.