# File lib/bundler/definition.rb, line 10 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.
# File lib/bundler/definition.rb, line 33 def initialize(lockfile, dependencies, sources, unlock) @unlocking = unlock == true || !unlock.empty? @dependencies, @sources, @unlock = dependencies, sources, unlock @remote = false @specs = nil @lockfile_contents = "" if lockfile && File.exists?(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 = @sources.any? do |source| next unless source.instance_of?(Source::Path) locked = @locked_sources.find do |ls| ls.class == source.class && ls.path == source.path end if locked unlocking = locked.specs.any? do |spec| @locked_specs.any? do |locked_spec| locked_spec.source != locked end end end !locked || unlocking || source.specs != locked.specs end 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 fixup_dependency_types! end
# File lib/bundler/definition.rb, line 165 def current_dependencies dependencies.reject { |d| !d.should_include? } end
# File lib/bundler/definition.rb, line 290 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 = [] if @locked_sources != @sources new_sources = @sources - @locked_sources deleted_sources = @locked_sources - @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 97 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 228 def groups dependencies.map { |d| d.groups }.flatten.uniq end
# File lib/bundler/definition.rb, line 197 def index @index ||= Index.build do |idx| dependency_names = @dependencies.dup || [] dependency_names.map! {|d| d.name } @sources.each do |s| if s.is_a?(Bundler::Source::Rubygems) s.dependency_names = dependency_names.uniq idx.add_source s.specs else source_index = s.specs dependency_names += source_index.unmet_dependency_names idx.add_source source_index end end end end
# File lib/bundler/definition.rb, line 232 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!(%r\n/, "\r\n") if @lockfile_contents.match("\r\n") return if @lockfile_contents == contents if Bundler.settings[:frozen] # TODO: Warn here if we got here. return end File.open(file, 'wb'){|f| f.puts(contents) } end
# File lib/bundler/definition.rb, line 151 def missing_specs missing = [] resolve.materialize(requested_dependencies, missing) missing end
# File lib/bundler/definition.rb, line 147 def new_platform? @new_platform end
# File lib/bundler/definition.rb, line 139 def new_specs specs - @locked_specs end
# File lib/bundler/definition.rb, line 224 def no_sources? @sources.length == 1 && @sources.first.remotes.empty? end
# File lib/bundler/definition.rb, line 143 def removed_specs @locked_specs - specs end
# File lib/bundler/definition.rb, line 157 def requested_specs @requested_specs ||= begin groups = self.groups - Bundler.settings.without groups.map! { |g| g.to_sym } specs_for(groups) end end
# File lib/bundler/definition.rb, line 175 def resolve @resolve ||= begin if Bundler.settings[:frozen] || (!@unlocking && !@source_changes && !@dependency_changes && !@new_platform && !@path_changes) @locked_specs else last_resolve = converge_locked_specs # Record the specs available in each gem's source, so that those # specs will be available later when the resolver knows where to # look for that gemspec (or its dependencies) source_requirements = {} dependencies.each do |dep| next unless dep.source source_requirements[dep.name] = dep.source.specs end # 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 118 def resolve_remotely! raise "Specs already loaded" if @specs @remote = true @sources.each { |s| s.remote! } specs end
# File lib/bundler/definition.rb, line 112 def resolve_with_cache! raise "Specs already loaded" if @specs @sources.each { |s| s.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 217 def rubygems_index @rubygems_index ||= Index.build do |idx| rubygems = @sources.find{|s| s.is_a?(Source::Rubygems) } idx.add_source rubygems.specs end end
# File lib/bundler/definition.rb, line 125 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 169 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 249 def to_lock out = "" sorted_sources.each do |source| # Add the source header out << source.to_lock # Find all specs for this source resolve. select { |s| s.source == source }. # This needs to be sorted by full name so that # gems with the same name, but different platform # are ordered consistantly 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