aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCarl Lerche <carllerche@mac.com>2010-06-03 15:32:32 -0700
committerCarl Lerche <carllerche@mac.com>2010-06-03 17:09:14 -0700
commit3edfddbf7ebe4b942e1e2b6d693afd582e9a8147 (patch)
treed3c9c1da2a060c23f00d58172830089e1e7d0089
parentabf42df5e20d11ea381848655edc71df4449fe84 (diff)
downloadbundler-3edfddbf7ebe4b942e1e2b6d693afd582e9a8147.tar.gz
Hacked together some crap that doesn't really work
-rw-r--r--lib/bundler.rb12
-rw-r--r--lib/bundler/index.rb15
-rw-r--r--lib/bundler/resolver.rb171
-rw-r--r--spec/resolver/basic_spec.rb11
-rw-r--r--spec/resolver/platform_spec.rb43
-rw-r--r--spec/support/builders.rb22
-rw-r--r--spec/support/indexes.rb43
7 files changed, 278 insertions, 39 deletions
diff --git a/lib/bundler.rb b/lib/bundler.rb
index 14784dee..1faf2b6f 100644
--- a/lib/bundler.rb
+++ b/lib/bundler.rb
@@ -39,7 +39,6 @@ module Bundler
class GemfileNotFound < BundlerError; status_code(10) ; end
class GemNotFound < BundlerError; status_code(7) ; end
- class VersionConflict < BundlerError; status_code(6) ; end
class GemfileError < BundlerError; status_code(4) ; end
class GemfileChanged < GemfileError; status_code(4) ; end
class PathError < BundlerError; status_code(13) ; end
@@ -50,6 +49,17 @@ module Bundler
class GemspecError < BundlerError; status_code(14) ; end
class InvalidOption < BundlerError; status_code(15) ; end
+ class VersionConflict < BundlerError
+ attr_reader :conflicts
+
+ def initialize(conflicts, msg = nil)
+ super(msg)
+ @conflicts = conflicts
+ end
+
+ status_code(6)
+ end
+
# Internal errors, should be rescued
class InvalidSpecSet < StandardError; end
diff --git a/lib/bundler/index.rb b/lib/bundler/index.rb
index 95c6d70e..8b7975cc 100644
--- a/lib/bundler/index.rb
+++ b/lib/bundler/index.rb
@@ -31,6 +31,20 @@ module Bundler
end
end
+ def search_for_all_platforms(dependency)
+ specs = @specs[dependency.name]
+
+ wants_prerelease = dependency.requirement.prerelease?
+ only_prerelease = specs.all? {|spec| spec.version.prerelease? }
+ found = specs.select { |spec| dependency =~ spec }
+
+ unless wants_prerelease || only_prerelease
+ found.reject! { |spec| spec.version.prerelease? }
+ end
+
+ found.sort_by {|s| [s.version, s.platform.to_s == 'ruby' ? "\0" : s.platform.to_s] }
+ end
+
def sources
@specs.values.map do |specs|
specs.map{|s| s.source.class }
@@ -88,6 +102,5 @@ module Bundler
found.sort_by {|s| [s.version, s.platform.to_s == 'ruby' ? "\0" : s.platform.to_s] }
end
end
-
end
end \ No newline at end of file
diff --git a/lib/bundler/resolver.rb b/lib/bundler/resolver.rb
index 9cbb0167..ab523ef8 100644
--- a/lib/bundler/resolver.rb
+++ b/lib/bundler/resolver.rb
@@ -7,20 +7,108 @@ require 'set'
# Extending Gem classes to add necessary tracking information
module Gem
- class Dependency
+ class Platform
+ def hash
+ Platform.hash
+ end
+ alias eql? ==
+ end
+ class Specification
def required_by
@required_by ||= []
end
+ def match_platform(p)
+ platform.nil? or p == platform or (p != Gem::Platform::RUBY and p =~ platform)
+ end
end
- class Specification
+ class Dependency
def required_by
@required_by ||= []
end
+ alias eql? ==
end
end
module Bundler
class Resolver
+ RUBY = Gem::Platform.new('ruby')
+ JAVA = Gem::Platform.new('java')
+ MSWIN = Gem::Platform.new('mswin32')
+ MING = Gem::Platform.new('mingw32')
+
+ class DepProxy
+
+ undef to_s
+ undef type
+
+ attr_reader :required_by, :__platform, :dep
+
+ def initialize(dep, platform)
+ @dep, @__platform, @required_by = dep, platform, []
+ end
+
+ private
+
+ def method_missing(*args)
+ @dep.send(*args)
+ end
+
+ end
+
+ class SpecGroup < Array
+ attr_reader :activated, :required_by
+
+ def initialize(a)
+ super
+ @required_by = []
+ @activated = []
+ @dependencies = {}
+
+ [RUBY, JAVA, MSWIN, MING].each do |p|
+ deps = []
+ spec = find { |s| s.match_platform(p) }
+ deps = spec.dependencies.select { |d| d.type != :development } if spec
+ @dependencies[p] = deps
+ end
+ end
+
+ def initialize_copy(o)
+ super
+ @required_by = o.required_by.dup
+ @activated = o.activated.dup
+ end
+
+ def to_specs
+ @activated.map do |p|
+ find { |s| s.match_platform(p) }
+ end.compact
+ end
+
+ def activate_platform(req, platforms)
+ platforms -= @activated
+ deps = dependencies_for(platforms) - dependencies_for(@activated)
+ @activated.concat platforms
+ deps.map { |d| DepProxy.new(d, req.__platform) }
+ end
+
+ def name
+ @name ||= first.name
+ end
+
+ def version
+ @version ||= first.version
+ end
+
+ private
+
+ def dependencies_for(platforms)
+ deps = []
+ platforms.each do |p|
+ deps |= @dependencies[p]
+ end
+ deps
+ end
+ end
attr_reader :errors
@@ -34,22 +122,25 @@ module Bundler
# ==== Returns
# <GemBundle>,nil:: If the list of dependencies can be resolved, a
# collection of gemspecs is returned. Otherwise, nil is returned.
- def self.resolve(requirements, index, source_requirements = {}, base = [])
- resolver = new(index, source_requirements)
+ def self.resolve(requirements, index, source_requirements = {}, base = [], platforms = [])
+ resolver = new(index, source_requirements, platforms.any? ? platforms : [RUBY])
result = catch(:success) do
activated = {}
- base.each { |s| activated[s.name] = s }
- resolver.resolve(requirements, activated)
- raise VersionConflict, "No compatible versions could be found for required dependencies:\n #{resolver.error_message}"
+ # base.each { |s| activated[s.name] = s }
+ requirements = requirements.dup
+ base.each { |s| requirements << Gem::Dependency.new(s.name, s.version) }
+ resolver.resolve(requirements.map { |d| DepProxy.new(d, nil) }, activated)
+ raise resolver.version_conflict
nil
end
- SpecSet.new(result.values)
+ SpecSet.new(result)
end
- def initialize(index, source_requirements)
+ def initialize(index, source_requirements, platforms)
@errors = {}
@stack = []
@index = index
+ @platforms = platforms
@source_requirements = source_requirements
end
@@ -61,10 +152,14 @@ module Bundler
end
end
+ def successify(activated)
+ activated.values.map { |s| s.to_specs }.flatten.compact
+ end
+
def resolve(reqs, activated)
# If the requirements are empty, then we are in a success state. Aka, all
# gem dependencies have been resolved.
- throw :success, activated if reqs.empty?
+ throw :success, successify(activated) if reqs.empty?
debug { print "\e[2J\e[f" ; "==== Iterating ====\n\n" }
@@ -85,14 +180,8 @@ module Bundler
activated = activated.dup
- if reqs.first.name == "bundler" && !activated["bundler"]
- # activate the current version of bundler before other versions
- bundler_version = ENV["BUNDLER_VERSION"] || Bundler::VERSION
- current = Gem::Dependency.new("bundler", bundler_version, reqs.first.type)
- else
- # Pull off the first requirement so that we can resolve it
- current = reqs.shift
- end
+ # Pull off the first requirement so that we can resolve it
+ current = reqs.shift
debug { "Attempting:\n #{current.name} (#{current.requirement})"}
@@ -104,6 +193,12 @@ module Bundler
@errors.delete(existing.name)
# Since the current requirement is satisfied, we can continue resolving
# the remaining requirements.
+
+ # I have no idea if this is the right way to do it, but let's see if it works
+ # The current requirement might activate some other platforms, so let's try
+ # adding those requirements here.
+ reqs.concat existing.activate_platform(current, Array(current.__platform || @platforms))
+
resolve(reqs, activated)
else
debug { " * [FAIL] Already activated" }
@@ -127,7 +222,7 @@ module Bundler
else
# The original set of dependencies conflict with the base set of specs
# passed to the resolver. This is by definition an impossible resolve.
- raise VersionConflict, "No compatible versions could be found for required dependencies:\n #{error_message}"
+ raise version_conflict
end
end
else
@@ -168,8 +263,8 @@ module Bundler
end
end
- matching_versions.reverse_each do |spec|
- conflict = resolve_requirement(spec, current, reqs.dup, activated.dup)
+ matching_versions.reverse_each do |spec_group|
+ conflict = resolve_requirement(spec_group, current, reqs.dup, activated.dup)
conflicts << conflict if conflict
end
# If the current requirement is a root level gem and we have conflicts, we
@@ -189,20 +284,22 @@ module Bundler
end
end
- def resolve_requirement(spec, requirement, reqs, activated)
+ def resolve_requirement(spec_group, requirement, reqs, activated)
# We are going to try activating the spec. We need to keep track of stack of
# requirements that got us to the point of activating this gem.
- spec.required_by.replace requirement.required_by
- spec.required_by << requirement
+ spec_group.required_by.replace requirement.required_by
+ spec_group.required_by << requirement
- activated[spec.name] = spec
+ activated[spec_group.name] = spec_group
debug { " Activating: #{spec.name} (#{spec.version})" }
debug { spec.required_by.map { |d| " * #{d.name} (#{d.requirement})" }.join("\n") }
+ dependencies = spec_group.activate_platform(requirement, Array(requirement.__platform || @platforms))
+
# Now, we have to loop through all child dependencies and add them to our
# array of requirements.
debug { " Dependencies"}
- spec.dependencies.each do |dep|
+ dependencies.each do |dep|
next if dep.type == :development
debug { " * #{dep.name} (#{dep.requirement})" }
dep.required_by.replace(requirement.required_by)
@@ -227,7 +324,27 @@ module Bundler
def search(dep)
index = @source_requirements[dep.name] || @index
- index.search(dep)
+ results = index.search_for_all_platforms(dep.dep)
+ if results.any?
+ version = results.first.version
+ nested = [[]]
+ results.each do |spec|
+ if spec.version != version
+ nested << []
+ version = spec.version
+ end
+ nested.last << spec
+ end
+ nested.map { |a| SpecGroup.new(a) }
+ else
+ []
+ end
+ end
+
+ def version_conflict
+ VersionConflict.new(
+ errors.keys,
+ "No compatible versions could be found for required dependencies:\n #{error_message}")
end
def error_message
diff --git a/spec/resolver/basic_spec.rb b/spec/resolver/basic_spec.rb
index a202db66..177a7b0c 100644
--- a/spec/resolver/basic_spec.rb
+++ b/spec/resolver/basic_spec.rb
@@ -3,13 +3,18 @@ require "spec_helper"
describe "Resolving" do
before :each do
- @deps = []
@index = an_awesome_index
end
- it "resolves" do
+ it "resolves a single gem" do
dep "rack"
- should_resolve_as [gem("rack", "1.1")]
+ should_resolve_as %w(rack-1.1)
+ end
+
+ it "resolves a gem with dependencies" do
+ dep "actionpack"
+
+ should_resolve_as %w(actionpack-2.3.5 activesupport-2.3.5 rack-1.0)
end
end \ No newline at end of file
diff --git a/spec/resolver/platform_spec.rb b/spec/resolver/platform_spec.rb
new file mode 100644
index 00000000..bc7bce45
--- /dev/null
+++ b/spec/resolver/platform_spec.rb
@@ -0,0 +1,43 @@
+require "spec_helper"
+
+describe "Resolving platform craziness" do
+ describe "with semi real cases" do
+ before :each do
+ @index = an_awesome_index
+ end
+
+ it "resolves a simple multi platform gem" do
+ dep "nokogiri"
+ platforms "ruby", "java"
+
+ should_resolve_as %w(nokogiri-1.4.2.1 nokogiri-1.4.2.1-java weakling-0.0.3)
+ end
+ end
+
+ describe "with conflicting cases" do
+ before :each do
+ @index = build_index do
+ gem "foo", "1.0.0" do
+ dep "bar", ">= 0"
+ end
+
+ gem 'bar', "1.0.0" do
+ dep "baz", "~> 1.0.0"
+ end
+
+ gem "bar", "1.0.0", "java" do
+ dep "baz", " ~> 1.1.0"
+ end
+
+ gem "baz", %w(1.0.0 1.1.0 1.2.0)
+ end
+ end
+
+ it "does something" do
+ platforms "ruby", "java"
+ dep "foo"
+
+ should_conflict_on "baz"
+ end
+ end
+end \ No newline at end of file
diff --git a/spec/support/builders.rb b/spec/support/builders.rb
index e5072fa5..9459b299 100644
--- a/spec/support/builders.rb
+++ b/spec/support/builders.rb
@@ -8,6 +8,10 @@ module Spec
Gem::Version.new(version)
end
+ def pl(platform)
+ Gem::Platform.new(pl)
+ end
+
def build_repo1
build_repo gem_repo1 do
build_gem "rack", %w(0.9.1 1.0.0) do |s|
@@ -239,8 +243,9 @@ module Spec
def build_spec(name, version, platform = nil, &block)
Array(version).map do |v|
Gem::Specification.new do |s|
- s.name = name
- s.version = Gem::Version.new(v)
+ s.name = name
+ s.version = Gem::Version.new(v)
+ s.platform = platform
DepBuilder.run(s, &block) if block_given?
end
end
@@ -308,6 +313,19 @@ module Spec
end
end
+ def platforms(platforms)
+ platforms.split(/\s+/).each do |platform|
+ platform = 'x86-mswin32' if platform == 'mswin32'
+ platform = Gem::Platform.new(platform)
+ if String === platform
+ class << platform
+ alias =~ ==
+ end
+ end
+ yield Gem::Platform.new(platform)
+ end
+ end
+
def versions(versions)
versions.split(/\s+/).each { |version| yield v(version) }
end
diff --git a/spec/support/indexes.rb b/spec/support/indexes.rb
index da168ada..7afa88ff 100644
--- a/spec/support/indexes.rb
+++ b/spec/support/indexes.rb
@@ -1,14 +1,35 @@
module Spec
module Indexes
def dep(name, reqs = nil)
+ @deps ||= []
@deps << Bundler::Dependency.new(name, :version => reqs)
end
+ def platform(*args)
+ @platforms ||= []
+ @platforms.concat args.map { |p| Gem::Platform.new(p) }
+ end
+
+ alias platforms platform
+
+ def resolve
+ Bundler::Resolver.resolve(@deps, @index, {}, [], @platforms || ['ruby'])
+ end
+
def should_resolve_as(specs)
- got = Bundler::Resolver.resolve(@deps, @index)
- got = got.map { |s| s.full_name }
+ got = resolve
+ got = got.map { |s| s.full_name }.sort
+
+ got.should == specs.sort
+ end
- got.should == specs.flatten.map { |s| s.full_name }
+ def should_conflict_on(names)
+ begin
+ got = resolve
+ flunk "The resolve succeeded with: #{got.map { |s| s.full_name }.sort.inspect}"
+ rescue Bundler::VersionConflict => e
+ names.sort.should == e.conflicts.sort
+ end
end
def gem(*args, &blk)
@@ -28,8 +49,8 @@ module Spec
if version >= v('3.0.0.beta')
dep "rack", '~> 1.1'
dep "rack-mount", ">= 0.5"
- elsif version > v('2.3.5') then dep "rack", '~> 1.0'
- elsif version > v('2.0.0') then dep "rack", '~> 0.9'
+ elsif version > v('2.3') then dep "rack", '~> 1.0.0'
+ elsif version > v('2.0.0') then dep "rack", '~> 0.9.0'
end
end
gem "activerecord", version do
@@ -59,6 +80,18 @@ module Spec
end
end
+ versions '1.0 1.2 1.2.1 1.2.2 1.3 1.3.0.1 1.3.5 1.4.0 1.4.2 1.4.2.1' do |version|
+ platforms "ruby java mswin32" do |platform|
+ gem "nokogiri", version, platform do
+ dep "weakling", ">= 0.0.3" if platform =~ 'java'
+ end
+ end
+ end
+
+ versions '0.0.1 0.0.2 0.0.3' do |version|
+ gem "weakling", version #, pl('java')
+ end
+
# --- Rails related
versions '1.2.3 2.2.3 2.3.5' do |version|
gem "activemerchant", version do