pub_grub 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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