lib/bundler/definition.rb in bundler-0.9.26 vs lib/bundler/definition.rb in bundler-1.0.0.beta.1
- old
+ new
@@ -1,75 +1,244 @@
require "digest/sha1"
module Bundler
class Definition
- def self.from_gemfile(gemfile)
+ attr_reader :dependencies, :platforms
+
+ 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)
+ # TODO: move this back into DSL
+ builder = Dsl.new
+ builder.instance_eval(File.read(gemfile.to_s), gemfile.to_s, 1)
+ builder.to_definition(lockfile, unlock)
end
- def self.from_lock(lockfile)
- begin
- locked_definition = Locked.new(YAML.load_file(lockfile))
- rescue ArgumentError
- raise GemfileError, "Your Gemfile.lock was generated by Bundler 0.10.\n" +
- "You must delete it if you wish to use Bundler 0.9."
+=begin
+ 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.
+=end
+
+ def initialize(lockfile, dependencies, sources, unlock)
+ @dependencies, @sources, @unlock = dependencies, sources, unlock
+ @specs = nil
+ @unlock[:gems] ||= []
+ @unlock[:sources] ||= []
+
+ if lockfile && File.exists?(lockfile)
+ locked = LockfileParser.new(File.read(lockfile))
+ @platforms = locked.platforms
+ @locked_deps = locked.dependencies
+ @last_resolve = SpecSet.new(locked.specs)
+ @locked_sources = locked.sources
+ else
+ @platforms = []
+ @locked_deps = []
+ @last_resolve = SpecSet.new([])
+ @locked_sources = []
end
- hash = Digest::SHA1.hexdigest(File.read("#{Bundler.root}/Gemfile"))
- unless locked_definition.hash == hash
- raise GemfileChanged, "You changed your Gemfile after locking. Please relock using `bundle lock`"
+ current_platform = Gem.platforms.map { |p| p.to_generic }.compact.last
+ @platforms |= [current_platform]
+
+ converge
+ end
+
+ def resolve_remotely!
+ raise "Specs already loaded" if @specs
+ @sources.each { |s| s.remote! }
+ specs
+ end
+
+ def specs
+ @specs ||= resolve.materialize(requested_dependencies)
+ end
+
+ def missing_specs
+ missing = []
+ resolve.materialize(requested_dependencies, missing)
+ missing
+ end
+
+ def requested_specs
+ @requested_specs ||= begin
+ groups = self.groups - Bundler.settings.without
+ groups.map! { |g| g.to_sym }
+ specs_for(groups)
end
+ end
- locked_definition
+ def current_dependencies
+ dependencies.reject { |d| !d.should_include? }
end
- attr_reader :dependencies, :sources
+ 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
- alias actual_dependencies dependencies
+ def resolve
+ @resolve ||= begin
+ if @last_resolve.valid_for?(expanded_dependencies)
+ @last_resolve
+ else
+ source_requirements = {}
+ dependencies.each do |dep|
+ next unless dep.source
+ source_requirements[dep.name] = dep.source.specs
+ end
- def initialize(dependencies, sources)
- @dependencies = dependencies
- @sources = sources
+ # Run a resolve against the locally available gems
+ Resolver.resolve(expanded_dependencies, index, source_requirements, @last_resolve)
+ end
+ end
end
+ def index
+ @index ||= Index.build do |idx|
+ @sources.each do |s|
+ idx.use s.specs
+ end
+ end
+ end
+
+ def no_sources?
+ @sources.length == 1 && @sources.first.remotes.empty?
+ end
+
def groups
dependencies.map { |d| d.groups }.flatten.uniq
end
- class Locked < Definition
- def initialize(details)
- @details = details
+ 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 }.
+ sort_by { |s| [s.name, s.platform.to_s == 'ruby' ? "\0" : s.platform.to_s] }.
+ each do |spec|
+ out << spec.to_lock
+ end
+ out << "\n"
end
- def hash
- @details["hash"]
+ out << "PLATFORMS\n"
+
+ platforms.map { |p| p.to_s }.sort.each do |p|
+ out << " #{p}\n"
end
- def sources
- @sources ||= @details["sources"].map do |args|
- name, options = args.to_a.flatten
- Bundler::Source.const_get(name).new(options)
+ out << "\n"
+ out << "DEPENDENCIES\n"
+
+ dependencies.
+ sort_by { |d| d.name }.
+ each do |dep|
+ out << dep.to_lock
+ end
+
+ out
+ end
+
+ private
+
+ def converge
+ converge_sources
+ converge_dependencies
+ converge_locked_specs
+ end
+
+ def converge_sources
+ @sources = (@locked_sources & @sources) | @sources
+ @sources.each do |source|
+ source.unlock! if source.respond_to?(:unlock!) && @unlock[:sources].include?(source.name)
+ end
+ end
+
+ def converge_dependencies
+ (@dependencies + @locked_deps).each do |dep|
+ if dep.source
+ source = @sources.find { |s| dep.source == s }
+ raise "Something went wrong, there is no matching source" unless source
+ dep.source = source
end
end
+ end
- def actual_dependencies
- @actual_dependencies ||= @details["specs"].map do |args|
- name, details = args.to_a.flatten
- details["source"] = sources[details["source"]] if details.include?("source")
- Bundler::Dependency.new(name, details.delete("version"), details)
+ def converge_locked_specs
+ deps = []
+
+ @dependencies.each do |dep|
+ if in_locked_deps?(dep) || satisfies_locked_spec?(dep)
+ deps << dep
end
end
- def dependencies
- @dependencies ||= @details["dependencies"].map do |dep, opts|
- Bundler::Dependency.new(dep, opts.delete("version"), opts)
+ converged = []
+ @last_resolve.each do |s|
+ s.source = @sources.find { |src| s.source == src }
+
+ next if s.source.nil? || @unlock[:sources].include?(s.name)
+
+ converged << s
+ end
+
+ resolve = SpecSet.new(converged)
+ resolve = resolve.for(expand_dependencies(deps), @unlock[:gems])
+ @last_resolve.select!(resolve.names)
+ end
+
+ def in_locked_deps?(dep)
+ @locked_deps.any? do |d|
+ dep == d && dep.source == d.source
+ end
+ end
+
+ def satisfies_locked_spec?(dep)
+ @last_resolve.any? { |s| s.satisfies?(dep) }
+ end
+
+ def expanded_dependencies
+ @expanded_dependencies ||= expand_dependencies(dependencies)
+ end
+
+ def expand_dependencies(dependencies)
+ deps = []
+ dependencies.each do |dep|
+ dep.gem_platforms(@platforms).each do |p|
+ deps << DepProxy.new(dep, p)
end
end
+ deps
+ end
+
+ def sorted_sources
+ @sources.sort_by do |s|
+ # Place GEM at the top
+ [ s.is_a?(Source::Rubygems) ? 1 : 0, s.to_s ]
+ end
+ end
+
+ def requested_dependencies
+ groups = self.groups - Bundler.settings.without
+ groups.map! { |g| g.to_sym }
+ dependencies.reject { |d| !d.should_include? || (d.groups & groups).empty? }
end
end
end