pub_grub 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 468d7bf24cf059751c1de78456163c371a4b28427f5c91c1185ff50a2e991a04
4
- data.tar.gz: 46a60ee9a399e4a6736cb729d5acbd6f03a23f392c7aad19070856277f191a9a
3
+ metadata.gz: 35ae2080ad05322250f9908df76885b83bc1b2578202fccb33c13457bd570925
4
+ data.tar.gz: 0dc16b3c3e66f06548a51fa03876090d070c67821e43e6c552789e881dd8feab
5
5
  SHA512:
6
- metadata.gz: 74ad2d2ea59a0f5fff3cf1121221d5c092f7056acb41eab9f2c3ac83399e7da3f7b3e0f3333486da68a381d707d9eb85e885621a87c68998220292a7701e43b5
7
- data.tar.gz: a081e3c55d491310631d992360c1e63ca5c5afd66e7db954a86962b805ded63fa2c918c6fb6bcd37ffd36efb5fe6d8d4495e257e4ef82e692bdc44c1e4725043
6
+ metadata.gz: e86e819b78f3b6a6e94ec3379f0cc4b61e827cbabd16940eb070156cef6b02b9333a3166616e82fe4efb9b3661c25918ed00641a8a8967550dd3315b1c7d2e7c
7
+ data.tar.gz: 441598b506f2d4d418bb9976911f248f86b1ccc1c1a13127ee9e5f8d8f408469ec48ea7c58b1356fdc9ff9f7c78eba9a266af9db7580bf24457d82465f8e878b
@@ -1,13 +1,17 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pub_grub (0.2.0)
4
+ pub_grub (0.3.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
9
  minitest (5.11.3)
10
+ minitest-stackprof (0.2.0)
11
+ minitest (>= 5.0)
12
+ stackprof (>= 0.2.7)
10
13
  rake (10.5.0)
14
+ stackprof (0.2.12)
11
15
 
12
16
  PLATFORMS
13
17
  ruby
@@ -15,8 +19,10 @@ PLATFORMS
15
19
  DEPENDENCIES
16
20
  bundler (~> 1.16)
17
21
  minitest (~> 5.0)
22
+ minitest-stackprof
18
23
  pub_grub!
19
24
  rake (~> 10.0)
25
+ stackprof (~> 0.2.12)
20
26
 
21
27
  BUNDLED WITH
22
- 1.16.1
28
+ 1.16.4
@@ -16,7 +16,7 @@ source =
16
16
  foo = source.get_package("foo")
17
17
  bar = source.get_package("bar")
18
18
 
19
- solver = VersionSolver.new(source: source)
19
+ solver = PubGrub::VersionSolver.new(source: source)
20
20
 
21
21
  require 'irb'
22
22
  IRB.setup(__FILE__)
@@ -1,7 +1,9 @@
1
1
  require "pub_grub/package"
2
2
  require "pub_grub/static_package_source"
3
3
  require "pub_grub/term"
4
+ require "pub_grub/version_range"
4
5
  require "pub_grub/version_constraint"
6
+ require "pub_grub/version_union"
5
7
  require "pub_grub/version_solver"
6
8
  require "pub_grub/incompatibility"
7
9
  require 'pub_grub/solve_failure'
@@ -8,8 +8,8 @@ module PubGrub
8
8
  @index = index
9
9
  end
10
10
 
11
- def self.decision(version, decision_level, index)
12
- term = Term.new(VersionConstraint.exact(version), true)
11
+ def self.decision(package, version, decision_level, index)
12
+ term = Term.new(VersionConstraint.exact(package, version), true)
13
13
  new(term, :decision, decision_level, index)
14
14
  end
15
15
 
@@ -5,6 +5,12 @@ module PubGrub
5
5
  alias_method :other, :satisfier
6
6
  end
7
7
 
8
+ InvalidDependency = Struct.new(:package, :constraint) do
9
+ end
10
+
11
+ NoVersions = Struct.new(:constraint) do
12
+ end
13
+
8
14
  attr_reader :terms, :cause
9
15
 
10
16
  def initialize(terms, cause:)
@@ -35,10 +41,16 @@ module PubGrub
35
41
 
36
42
  def to_s
37
43
  case cause
44
+ when :root
45
+ "(root dependency)"
38
46
  when :dependency
39
47
  raise unless terms.length == 2
40
48
  "#{terms[0].to_s(allow_every: true)} depends on #{terms[1].invert}"
41
- else
49
+ when PubGrub::Incompatibility::InvalidDependency
50
+ "#{terms[0].to_s(allow_every: true)} depends on unknown package #{cause.package}"
51
+ when PubGrub::Incompatibility::NoVersions
52
+ "no versions satisfy #{cause.constraint}"
53
+ when PubGrub::Incompatibility::ConflictCause
42
54
  if failure?
43
55
  "version solving has failed"
44
56
  elsif terms.length == 1
@@ -72,6 +84,8 @@ module PubGrub
72
84
  end
73
85
  end
74
86
  end
87
+ else
88
+ raise "unhandled cause: #{cause.inspect}"
75
89
  end
76
90
  end
77
91
 
@@ -79,6 +93,17 @@ module PubGrub
79
93
  "#<#{self.class} #{to_s}>"
80
94
  end
81
95
 
96
+ def pretty_print(q)
97
+ q.group 2, "#<#{self.class}", ">" do
98
+ q.breakable
99
+ q.text to_s
100
+
101
+ q.breakable
102
+ q.text " caused by "
103
+ q.pp @cause
104
+ end
105
+ end
106
+
82
107
  private
83
108
 
84
109
  def cleanup_terms(terms)
@@ -97,12 +122,9 @@ module PubGrub
97
122
  return terms if terms.length == 2 && terms[0].package != terms[1].package
98
123
 
99
124
  terms.group_by(&:package).map do |package, common_terms|
100
- term =
101
- common_terms.inject do |acc, term|
125
+ common_terms.inject do |acc, term|
102
126
  acc.intersect(term)
103
127
  end
104
-
105
- term
106
128
  end
107
129
  end
108
130
  end
@@ -2,78 +2,30 @@
2
2
 
3
3
  module PubGrub
4
4
  class Package
5
- class Version
6
- attr_reader :package, :id, :name
7
5
 
8
- def initialize(package, id, name)
9
- @package = package
10
- @id = id
11
- @name = name
12
- end
13
-
14
- def to_s
15
- name
16
- end
17
-
18
- def inspect
19
- "#<#{self.class} #{package.name} #{name} (#{id})>"
20
- end
21
-
22
- def <=>(other)
23
- [package, id] <=> [other.package, other.id]
24
- end
25
- end
26
-
27
- attr_reader :name, :versions
6
+ attr_reader :name
28
7
 
29
8
  def initialize(name)
30
9
  @name = name
31
- @versions = []
32
- yield self if block_given?
33
- end
34
-
35
- def version(version)
36
- @versions.detect { |v| v.name == version } ||
37
- raise("No such version of #{name.inspect}: #{version.inspect}")
38
- end
39
- alias_method :[], :version
40
-
41
- def add_version(name)
42
- Version.new(self, @versions.length, name).tap do |version|
43
- @versions << version
44
- end
45
10
  end
46
11
 
47
12
  def inspect
48
- "#<#{self.class} #{name.inspect} (#{versions.count} versions)>"
13
+ "#<#{self.class} #{name.inspect}>"
49
14
  end
50
15
 
51
16
  def <=>(other)
52
17
  name <=> other.name
53
18
  end
54
19
 
55
- class RootPackage < Package
56
- class Version < Package::Version
57
- def to_s
58
- "(root)"
59
- end
60
- end
61
-
62
- attr_reader :version
63
-
64
- def initialize
65
- super(:root)
66
- @version = Version.new(self, 0, "1.0.0")
67
- @versions = [@version].freeze
68
- end
69
- end
20
+ ROOT = Package.new(:root)
21
+ ROOT_VERSION = 0
70
22
 
71
23
  def self.root
72
- @root ||= RootPackage.new
24
+ ROOT
73
25
  end
74
26
 
75
27
  def self.root_version
76
- root.version
28
+ ROOT_VERSION
77
29
  end
78
30
  end
79
31
  end
@@ -59,12 +59,12 @@ module PubGrub
59
59
  end
60
60
  end
61
61
 
62
- def decide(version)
62
+ def decide(package, version)
63
63
  @attempted_solutions += 1 if @backtracking
64
64
  @backtracking = false;
65
65
 
66
- decisions[version.package] = version
67
- assignment = Assignment.decision(version, decision_level, assignments.length)
66
+ decisions[package] = version
67
+ assignment = Assignment.decision(package, version, decision_level, assignments.length)
68
68
  add_assignment(assignment)
69
69
  end
70
70
 
@@ -0,0 +1,50 @@
1
+ require 'rubygems/requirement'
2
+
3
+ module PubGrub
4
+ module RubyGems
5
+ extend self
6
+
7
+ def requirement_to_range(requirement)
8
+ ranges = requirement.requirements.map do |(op, ver)|
9
+ case op
10
+ when "~>"
11
+ # TODO: not sure this is correct for prereleases
12
+ VersionRange.new(min: ver, max: ver.bump, include_min: true)
13
+ when ">"
14
+ VersionRange.new(min: ver)
15
+ when ">="
16
+ if ver == Gem::Version.new("0")
17
+ VersionRange.any
18
+ else
19
+ VersionRange.new(min: ver, include_min: true)
20
+ end
21
+ when "<"
22
+ VersionRange.new(max: ver)
23
+ when "<="
24
+ VersionRange.new(max: ver, include_max: true)
25
+ when "="
26
+ VersionRange.new(min: ver, max: ver, include_min: true, include_max: true)
27
+ when "!="
28
+ VersionRange.new(min: ver, max: ver, include_min: true, include_max: true).invert
29
+ else
30
+ raise "bad version specifier: #{op}"
31
+ end
32
+ end
33
+
34
+ ranges.inject(&:intersect)
35
+ end
36
+
37
+ def requirement_to_constraint(package, requirement)
38
+ PubGrub::VersionConstraint.new(package, range: requirement_to_range(requirement))
39
+ end
40
+
41
+ def parse_range(dep)
42
+ requirement_to_range(Gem::Requirement.new(dep))
43
+ end
44
+
45
+ def parse_constraint(package, dep)
46
+ range = parse_range(dep)
47
+ PubGrub::VersionConstraint.new(package, range: range)
48
+ end
49
+ end
50
+ end
@@ -27,14 +27,21 @@ module PubGrub
27
27
  @packages = {
28
28
  root: Package.root
29
29
  }
30
- @deps_by_version = {
31
- Package.root_version => @root_deps
32
- }
30
+ @package_versions = Hash.new{ |h, k| h[k] = [] }
31
+
32
+ @deps_by_version = Hash.new { |h, k| h[k] = {} }
33
+
34
+ root_version = Package.root_version
35
+ @package_versions[Package.root] = [root_version]
36
+ @deps_by_version[Package.root][root_version] = @root_deps
33
37
 
34
38
  @package_list.each do |name, version, deps|
35
39
  @packages[name] ||= Package.new(name)
36
- version = @packages[name].add_version(version)
37
- @deps_by_version[version] = deps
40
+ package = @packages[name]
41
+
42
+ version = Gem::Version.new(version)
43
+ @package_versions[package] << version
44
+ @deps_by_version[package][version] = deps
38
45
  end
39
46
  end
40
47
 
@@ -44,37 +51,56 @@ module PubGrub
44
51
  end
45
52
  alias_method :package, :get_package
46
53
 
47
- def version(package_name, version_name)
48
- package(package_name).version(version_name)
54
+ def versions_for(package, range=VersionRange.any)
55
+ @package_versions[package].select do |version|
56
+ range.include?(version)
57
+ end
49
58
  end
50
59
 
51
- def incompatibilities_for(version)
52
- package = version.package
53
- @deps_by_version[version].map do |dep_package_name, dep_constraint_name|
54
- bitmap = VersionConstraint.bitmap_matching(package) do |requesting_version|
55
- deps = @deps_by_version[requesting_version]
56
- deps && deps[dep_package_name] && deps[dep_package_name] == dep_constraint_name
60
+ def incompatibilities_for(package, version)
61
+ package_deps = @deps_by_version[package]
62
+ package_versions = @package_versions[package]
63
+ package_deps[version].map do |dep_package_name, dep_constraint_name|
64
+ sorted_versions = package_versions.sort
65
+ low = high = sorted_versions.index(version)
66
+
67
+ # find version low such that all >= low share the same dep
68
+ while low > 0 &&
69
+ package_deps[sorted_versions[low - 1]][dep_package_name] == dep_constraint_name
70
+ low -= 1
57
71
  end
58
- description =
59
- if (bitmap == (1 << package.versions.length) - 1)
60
- "any"
61
- elsif (bitmap == 1 << version.id)
62
- version.name
72
+ low =
73
+ if low == 0
74
+ nil
63
75
  else
64
- "requiring #{dep_package_name} #{dep_constraint_name}"
76
+ sorted_versions[low]
65
77
  end
66
- self_constraint = VersionConstraint.new(package, description, bitmap: bitmap)
78
+
79
+ # find version high such that all < high share the same dep
80
+ while high < sorted_versions.length &&
81
+ package_deps[sorted_versions[high]][dep_package_name] == dep_constraint_name
82
+ high += 1
83
+ end
84
+ high =
85
+ if high == sorted_versions.length
86
+ nil
87
+ else
88
+ sorted_versions[high]
89
+ end
90
+
91
+ range = VersionRange.new(min: low, max: high, include_min: true)
92
+
93
+ self_constraint = VersionConstraint.new(package, range: range)
67
94
 
68
95
  dep_package = @packages[dep_package_name]
69
96
 
70
97
  if !dep_package
71
98
  # no such package -> this version is invalid
72
- description = "referencing invalid package #{dep_package_name.inspect}"
73
- constraint = VersionConstraint.new(package, description, bitmap: bitmap)
74
- return [Incompatibility.new([Term.new(constraint, true)], cause: :invalid_dependency)]
99
+ cause = PubGrub::Incompatibility::InvalidDependency.new(dep_package_name, dep_constraint_name)
100
+ return [Incompatibility.new([Term.new(self_constraint, true)], cause: cause)]
75
101
  end
76
102
 
77
- dep_constraint = VersionConstraint.new(dep_package, dep_constraint_name)
103
+ dep_constraint = PubGrub::RubyGems.parse_constraint(dep_package, dep_constraint_name)
78
104
 
79
105
  Incompatibility.new([Term.new(self_constraint, true), Term.new(dep_constraint, false)], cause: :dependency)
80
106
  end
@@ -82,9 +82,6 @@ module PubGrub
82
82
  relation(other) == :subset
83
83
  end
84
84
 
85
- extend Forwardable
86
- def_delegators :normalized_constraint, :versions
87
-
88
85
  def positive?
89
86
  @positive
90
87
  end
@@ -1,41 +1,28 @@
1
- require 'rubygems/requirement'
1
+ require 'pub_grub/version_range'
2
2
 
3
3
  module PubGrub
4
4
  class VersionConstraint
5
- attr_reader :package, :constraint
5
+ attr_reader :package, :range
6
6
 
7
7
  # @param package [PubGrub::Package]
8
- # @param constraint [String]
9
- def initialize(package, constraint = nil, bitmap: nil)
8
+ # @param range [PubGrub::VersionRange]
9
+ def initialize(package, range: nil)
10
10
  @package = package
11
- @constraint = Array(constraint)
12
- @bitmap = bitmap # Calculated lazily
11
+ @range = range
13
12
  end
14
13
 
15
- def self.exact(version)
16
- package = version.package
17
- new(package, version.name, bitmap: bitmap_matching(package) { |v| v == version })
18
- end
19
-
20
- def self.any(package)
21
- new(package, nil, bitmap: (1 << package.versions.count) - 1)
22
- end
23
-
24
- def self.bitmap_matching(package)
25
- package.versions.select do |version|
26
- yield version
27
- end.inject(0) do |acc, version|
28
- acc | (1 << version.id)
14
+ class << self
15
+ def exact(package, version)
16
+ range = VersionRange.new(min: version, max: version, include_min: true, include_max: true)
17
+ new(package, range: range)
29
18
  end
30
- end
31
19
 
32
- def bitmap
33
- return @bitmap if @bitmap
20
+ def any(package)
21
+ new(package, range: VersionRange.any)
22
+ end
34
23
 
35
- # TODO: Should not be hardcoded to rubygems semantics
36
- requirement = Gem::Requirement.new(constraint)
37
- @bitmap = self.class.bitmap_matching(package) do |version|
38
- requirement.satisfied_by?(Gem::Version.new(version.name))
24
+ def empty(package)
25
+ new(package, range: VersionRange.empty)
39
26
  end
40
27
  end
41
28
 
@@ -43,64 +30,33 @@ module PubGrub
43
30
  unless package == other.package
44
31
  raise ArgumentError, "Can only intersect between VersionConstraint of the same package"
45
32
  end
46
- if bitmap == other.bitmap
47
- self
48
- else
49
- self.class.new(package, constraint + other.constraint, bitmap: bitmap & other.bitmap)
50
- end
33
+
34
+ self.class.new(package, range: range.intersect(other.range))
51
35
  end
52
36
 
53
37
  def union(other)
54
38
  unless package == other.package
55
39
  raise ArgumentError, "Can only intersect between VersionConstraint of the same package"
56
40
  end
57
- if bitmap == other.bitmap
58
- self
59
- else
60
- self.class.new(package, "#{constraint_string} OR #{other.constraint_string}", bitmap: bitmap | other.bitmap)
61
- end
41
+
42
+ self.class.new(package, range: range.union(other.range))
62
43
  end
63
44
 
64
45
  def invert
65
- new_bitmap = bitmap ^ ((1 << package.versions.length) - 1)
66
- new_constraint =
67
- if constraint.length == 0
68
- ["not >= 0"]
69
- elsif constraint.length == 1
70
- ["not #{constraint[0]}"]
71
- else
72
- ["not (#{constraint_string})"]
73
- end
74
- self.class.new(package, new_constraint, bitmap: new_bitmap)
46
+ new_range = range.invert
47
+ self.class.new(package, range: new_range)
75
48
  end
76
49
 
77
50
  def difference(other)
78
51
  intersect(other.invert)
79
52
  end
80
53
 
81
- def versions
82
- package.versions.select do |version|
83
- bitmap[version.id] == 1
84
- end
54
+ def allows_all?(other)
55
+ range.allows_all?(other.range)
85
56
  end
86
57
 
87
- if RUBY_VERSION >= "2.5"
88
- def allows_all?(other)
89
- bitmap.allbits?(other.bitmap)
90
- end
91
-
92
- def allows_any?(other)
93
- bitmap.anybits?(other.bitmap)
94
- end
95
- else
96
- def allows_all?(other)
97
- other_bitmap = other.bitmap
98
- (bitmap & other_bitmap) == other_bitmap
99
- end
100
-
101
- def allows_any?(other)
102
- (bitmap & other.bitmap) != 0
103
- end
58
+ def allows_any?(other)
59
+ range.intersects?(other.range)
104
60
  end
105
61
 
106
62
  def subset?(other)
@@ -136,27 +92,24 @@ module PubGrub
136
92
  end
137
93
 
138
94
  def constraint_string
139
- case constraint.length
140
- when 0
95
+ if any?
141
96
  ">= 0"
142
- when 1
143
- "#{constraint[0]}"
144
97
  else
145
- "#{constraint.join(", ")}"
98
+ range.to_s
146
99
  end
147
100
  end
148
101
 
149
102
  def empty?
150
- bitmap == 0
103
+ range.empty?
151
104
  end
152
105
 
153
106
  # Does this match every version of the package
154
107
  def any?
155
- bitmap == self.class.any(package).bitmap
108
+ range.any?
156
109
  end
157
110
 
158
111
  def inspect
159
- "#<#{self.class} #{self} (#{bitmap.to_s(2).rjust(package.versions.count, "0")})>"
112
+ "#<#{self.class} #{self}>"
160
113
  end
161
114
  end
162
115
  end
@@ -0,0 +1,322 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PubGrub
4
+ class VersionRange
5
+ attr_reader :min, :max, :include_min, :include_max
6
+
7
+ alias_method :include_min?, :include_min
8
+ alias_method :include_max?, :include_max
9
+
10
+ class Empty < VersionRange
11
+ undef_method :min, :max
12
+ undef_method :include_min, :include_min?
13
+ undef_method :include_max, :include_max?
14
+
15
+ def initialize
16
+ end
17
+
18
+ def empty?
19
+ true
20
+ end
21
+
22
+ def intersects?(_)
23
+ false
24
+ end
25
+
26
+ def allows_all?(other)
27
+ other.empty?
28
+ end
29
+
30
+ def include?(_)
31
+ false
32
+ end
33
+
34
+ def any?
35
+ false
36
+ end
37
+
38
+ def constraints
39
+ ["(no versions)"]
40
+ end
41
+
42
+ def ==(other)
43
+ other.class == self.class
44
+ end
45
+
46
+ def invert
47
+ VersionRange.any
48
+ end
49
+ end
50
+
51
+ def self.empty
52
+ Empty.new
53
+ end
54
+
55
+ def self.any
56
+ new
57
+ end
58
+
59
+ def initialize(min: nil, max: nil, include_min: false, include_max: false)
60
+ @min = min
61
+ @max = max
62
+ @include_min = include_min
63
+ @include_max = include_max
64
+
65
+ if min && max
66
+ if min > max
67
+ raise ArgumentError, "min version #{min} must be less than max #{max}"
68
+ elsif min == max && (!include_min || !include_max)
69
+ raise ArgumentError, "include_min and include_max must be true when min == max"
70
+ end
71
+ end
72
+ end
73
+
74
+ def include?(version)
75
+ compare_version(version) == 0
76
+ end
77
+
78
+ def compare_version(version)
79
+ if min
80
+ case version <=> min
81
+ when -1
82
+ return -1
83
+ when 0
84
+ return -1 if !include_min
85
+ when 1
86
+ end
87
+ end
88
+
89
+ if max
90
+ case version <=> max
91
+ when -1
92
+ when 0
93
+ return 1 if !include_max
94
+ when 1
95
+ return 1
96
+ end
97
+ end
98
+
99
+ 0
100
+ end
101
+
102
+ def strictly_lower?(other)
103
+ return false if !max || !other.min
104
+
105
+ case max <=> other.min
106
+ when 0
107
+ !include_max || !other.include_min
108
+ when -1
109
+ true
110
+ when 1
111
+ false
112
+ end
113
+ end
114
+
115
+ def strictly_higher?(other)
116
+ other.strictly_lower?(self)
117
+ end
118
+
119
+ def intersects?(other)
120
+ return false if other.empty?
121
+ return other.intersects?(self) if other.is_a?(VersionUnion)
122
+ !strictly_lower?(other) && !strictly_higher?(other)
123
+ end
124
+ alias_method :allows_any?, :intersects?
125
+
126
+ def intersect(other)
127
+ return self.class.empty unless intersects?(other)
128
+ return other.intersect(self) if other.is_a?(VersionUnion)
129
+
130
+ min_range =
131
+ if !min
132
+ other
133
+ elsif !other.min
134
+ self
135
+ else
136
+ case min <=> other.min
137
+ when 0
138
+ include_min ? other : self
139
+ when -1
140
+ other
141
+ when 1
142
+ self
143
+ end
144
+ end
145
+
146
+ max_range =
147
+ if !max
148
+ other
149
+ elsif !other.max
150
+ self
151
+ else
152
+ case max <=> other.max
153
+ when 0
154
+ include_max ? other : self
155
+ when -1
156
+ self
157
+ when 1
158
+ other
159
+ end
160
+ end
161
+
162
+ self.class.new(
163
+ min: min_range.min,
164
+ include_min: min_range.include_min,
165
+ max: max_range.max,
166
+ include_max: max_range.include_max
167
+ )
168
+ end
169
+
170
+ # The span covered by two ranges
171
+ #
172
+ # If self and other are contiguous, this builds a union of the two ranges.
173
+ # (if they aren't you are probably calling the wrong method)
174
+ def span(other)
175
+ return self if other.empty?
176
+
177
+ min_range =
178
+ if !min
179
+ self
180
+ elsif !other.min
181
+ other
182
+ else
183
+ case min <=> other.min
184
+ when 0
185
+ include_min ? self : other
186
+ when -1
187
+ self
188
+ when 1
189
+ other
190
+ end
191
+ end
192
+
193
+ max_range =
194
+ if !max
195
+ self
196
+ elsif !other.max
197
+ other
198
+ else
199
+ case max <=> other.max
200
+ when 0
201
+ include_max ? self : other
202
+ when -1
203
+ other
204
+ when 1
205
+ self
206
+ end
207
+ end
208
+
209
+ self.class.new(
210
+ min: min_range.min,
211
+ include_min: min_range.include_min,
212
+ max: max_range.max,
213
+ include_max: max_range.include_max
214
+ )
215
+ end
216
+
217
+ def union(other)
218
+ return other.union(self) if other.is_a?(VersionUnion)
219
+
220
+ if contiguous_to?(other)
221
+ span(other)
222
+ else
223
+ VersionUnion.union([self, other])
224
+ end
225
+ end
226
+
227
+ def contiguous_to?(other)
228
+ return false if other.empty?
229
+
230
+ intersects?(other) ||
231
+ (min == other.max && (include_min || other.include_max)) ||
232
+ (max == other.min && (include_max || other.include_min))
233
+ end
234
+
235
+ def allows_all?(other)
236
+ return true if other.empty?
237
+
238
+ if other.is_a?(VersionUnion)
239
+ return other.ranges.all? { |r| allows_all?(r) }
240
+ end
241
+
242
+ return false if max && !other.max
243
+ return false if min && !other.min
244
+
245
+ if min
246
+ case min <=> other.min
247
+ when -1
248
+ when 0
249
+ return false if !include_min && other.include_min
250
+ when 1
251
+ return false
252
+ end
253
+ end
254
+
255
+ if max
256
+ case max <=> other.max
257
+ when -1
258
+ return false
259
+ when 0
260
+ return false if !include_max && other.include_max
261
+ when 1
262
+ end
263
+ end
264
+
265
+ true
266
+ end
267
+
268
+ def constraints
269
+ return ["any"] if any?
270
+ return ["#{min}"] if min == max
271
+
272
+ # FIXME: remove this
273
+ if min && max && include_min && !include_max && min.respond_to?(:bump) && min.bump == max
274
+ return ["~> #{min}"]
275
+ end
276
+
277
+ c = []
278
+ c << "#{include_min ? ">=" : ">"} #{min}" if min
279
+ c << "#{include_max ? "<=" : "<"} #{max}" if max
280
+ c
281
+ end
282
+
283
+ def any?
284
+ !min && !max
285
+ end
286
+
287
+ def empty?
288
+ false
289
+ end
290
+
291
+ def to_s
292
+ constraints.join(", ")
293
+ end
294
+
295
+ def inspect
296
+ "#<#{self.class} #{to_s}>"
297
+ end
298
+
299
+ def invert
300
+ return self.class.empty if any?
301
+
302
+ low = VersionRange.new(max: min, include_max: !include_min)
303
+ high = VersionRange.new(min: max, include_min: !include_max)
304
+
305
+ if !min
306
+ high
307
+ elsif !max
308
+ low
309
+ else
310
+ low.union(high)
311
+ end
312
+ end
313
+
314
+ def ==(other)
315
+ self.class == other.class &&
316
+ min == other.min &&
317
+ max == other.max &&
318
+ include_min == other.include_min &&
319
+ include_max == other.include_max
320
+ end
321
+ end
322
+ end
@@ -32,11 +32,11 @@ module PubGrub
32
32
  next_package = choose_package_version
33
33
  end
34
34
 
35
- result = solution.decisions.values
35
+ result = solution.decisions
36
36
 
37
37
  logger.info "Solution found after #{solution.attempted_solutions} attempts:"
38
- result.each do |version|
39
- logger.info "* #{version.package.name} #{version}"
38
+ result.each do |package, version|
39
+ logger.info "* #{package.name} #{version}"
40
40
  end
41
41
 
42
42
  result
@@ -93,32 +93,40 @@ module PubGrub
93
93
  return nil
94
94
  end
95
95
 
96
- unsatisfied_term = unsatisfied.min_by do |term|
97
- term.versions.count
98
- end
99
- version = unsatisfied_term.versions.first
96
+ unsatisfied_term, versions =
97
+ unsatisfied.map do |term|
98
+ range = term.constraint.range
99
+ [term, source.versions_for(term.package, range)]
100
+ end.min_by do |(_, v)|
101
+ v.count
102
+ end
103
+
104
+ package = unsatisfied_term.package
105
+ version = versions.first
100
106
 
101
107
  if version.nil?
102
- raise "required term with no versions: #{unsatisfied_term}"
108
+ cause = Incompatibility::NoVersions.new(unsatisfied_term)
109
+ add_incompatibility Incompatibility.new([unsatisfied_term], cause: cause)
110
+ return package
103
111
  end
104
112
 
105
113
  conflict = false
106
114
 
107
- source.incompatibilities_for(version).each do |incompatibility|
115
+ source.incompatibilities_for(package, version).each do |incompatibility|
108
116
  add_incompatibility incompatibility
109
117
 
110
118
  conflict ||= incompatibility.terms.all? do |term|
111
- term.package == version.package || solution.satisfies?(term)
119
+ term.package == package || solution.satisfies?(term)
112
120
  end
113
121
  end
114
122
 
115
123
  unless conflict
116
- logger.info("selecting #{version.package.name} #{version}")
124
+ logger.info("selecting #{package.name} #{version}")
117
125
 
118
- solution.decide(version)
126
+ solution.decide(package, version)
119
127
  end
120
128
 
121
- version.package
129
+ package
122
130
  end
123
131
 
124
132
  def resolve_conflict(incompatibility)
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PubGrub
4
+ class VersionUnion
5
+ attr_reader :ranges
6
+
7
+ def self.normalize_ranges(ranges)
8
+ ranges = ranges.flat_map do |range|
9
+ if range.is_a?(VersionUnion)
10
+ range.ranges
11
+ else
12
+ [range]
13
+ end
14
+ end
15
+
16
+ ranges.reject!(&:empty?)
17
+
18
+ return [] if ranges.empty?
19
+
20
+ mins, ranges = ranges.partition { |r| !r.min }
21
+ original_ranges = mins + ranges.sort_by { |r| [r.include_min ? 0 : 1, r.min] }
22
+ ranges = [original_ranges.shift]
23
+ original_ranges.each do |range|
24
+ if ranges.last.contiguous_to?(range)
25
+ ranges << ranges.pop.span(range)
26
+ else
27
+ ranges << range
28
+ end
29
+ end
30
+
31
+ ranges
32
+ end
33
+
34
+ def self.union(ranges)
35
+ ranges = normalize_ranges(ranges)
36
+
37
+ if ranges.size == 0
38
+ VersionRange.empty
39
+ elsif ranges.size == 1
40
+ ranges[0]
41
+ else
42
+ new(ranges)
43
+ end
44
+ end
45
+
46
+ def initialize(ranges)
47
+ raise ArgumentError unless ranges.all? { |r| r.instance_of?(VersionRange) }
48
+ @ranges = ranges
49
+ end
50
+
51
+ def include?(version)
52
+ !!ranges.bsearch {|r| r.compare_version(version) }
53
+ end
54
+
55
+ def intersects?(other)
56
+ my_ranges = ranges.dup
57
+ other_ranges =
58
+ if other.instance_of?(VersionRange)
59
+ [other]
60
+ else
61
+ other.ranges.dup
62
+ end
63
+
64
+ my_range = my_ranges.shift
65
+ other_range = other_ranges.shift
66
+ while my_range && other_range
67
+ if my_range.intersects?(other_range)
68
+ return true
69
+ end
70
+
71
+ if !my_range.max || (other_range.max && other_range.max < my_range.max)
72
+ other_range = other_ranges.shift
73
+ else
74
+ my_range = my_ranges.shift
75
+ end
76
+ end
77
+ end
78
+ alias_method :allows_any?, :intersects?
79
+
80
+ def allows_all?(other)
81
+ other_ranges =
82
+ if other.is_a?(VersionUnion)
83
+ other.ranges
84
+ else
85
+ [other]
86
+ end
87
+
88
+ other_ranges.all? do |other_range|
89
+ ranges.any? do |range|
90
+ range.allows_all?(other_range)
91
+ end
92
+ end
93
+ end
94
+
95
+ def empty?
96
+ false
97
+ end
98
+
99
+ def any?
100
+ false
101
+ end
102
+
103
+ def intersect(other)
104
+ new_ranges = ranges.map{ |r| r.intersect(other) }
105
+ VersionUnion.union(new_ranges)
106
+ end
107
+
108
+ def invert
109
+ ranges.map(&:invert).inject(:intersect)
110
+ end
111
+
112
+ def union(other)
113
+ VersionUnion.union([self, other])
114
+ end
115
+
116
+ def to_s
117
+ ranges.map(&:to_s).join(" OR ")
118
+ end
119
+
120
+ def inspect
121
+ "#<#{self.class} #{to_s}>"
122
+ end
123
+
124
+ def ==(other)
125
+ self.class == other.class &&
126
+ self.ranges == other.ranges
127
+ end
128
+ end
129
+ end
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "pub_grub"
7
- spec.version = "0.2.0"
7
+ spec.version = "0.3.0"
8
8
  spec.authors = ["John Hawthorn"]
9
9
  spec.email = ["john@hawthorn.email"]
10
10
 
@@ -23,4 +23,6 @@ Gem::Specification.new do |spec|
23
23
  spec.add_development_dependency "bundler", "~> 1.16"
24
24
  spec.add_development_dependency "rake", "~> 10.0"
25
25
  spec.add_development_dependency "minitest", "~> 5.0"
26
+ spec.add_development_dependency "stackprof", "~> 0.2.12"
27
+ spec.add_development_dependency "minitest-stackprof"
26
28
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pub_grub
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Hawthorn
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-07-04 00:00:00.000000000 Z
11
+ date: 2018-11-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -52,6 +52,34 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '5.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: stackprof
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.2.12
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.2.12
69
+ - !ruby/object:Gem::Dependency
70
+ name: minitest-stackprof
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
55
83
  description: A version solver based on dart's PubGrub
56
84
  email:
57
85
  - john@hawthorn.email
@@ -75,11 +103,14 @@ files:
75
103
  - lib/pub_grub/incompatibility.rb
76
104
  - lib/pub_grub/package.rb
77
105
  - lib/pub_grub/partial_solution.rb
106
+ - lib/pub_grub/rubygems.rb
78
107
  - lib/pub_grub/solve_failure.rb
79
108
  - lib/pub_grub/static_package_source.rb
80
109
  - lib/pub_grub/term.rb
81
110
  - lib/pub_grub/version_constraint.rb
111
+ - lib/pub_grub/version_range.rb
82
112
  - lib/pub_grub/version_solver.rb
113
+ - lib/pub_grub/version_union.rb
83
114
  - pub_grub.gemspec
84
115
  homepage: https://github.com/jhawthorn/pubgrub
85
116
  licenses:
@@ -101,7 +132,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
101
132
  version: '0'
102
133
  requirements: []
103
134
  rubyforge_project:
104
- rubygems_version: 2.7.3
135
+ rubygems_version: 2.7.7
105
136
  signing_key:
106
137
  specification_version: 4
107
138
  summary: A version solver based on dart's PubGrub