solve 0.8.2 → 1.0.0.rc1
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 +4 -4
- data/.gitignore +2 -0
- data/.travis.yml +4 -0
- data/README.md +8 -11
- data/lib/solve.rb +3 -8
- data/lib/solve/artifact.rb +44 -80
- data/lib/solve/constraint.rb +62 -46
- data/lib/solve/demand.rb +6 -21
- data/lib/solve/dependency.rb +10 -22
- data/lib/solve/errors.rb +43 -17
- data/lib/solve/gem_version.rb +1 -1
- data/lib/solve/graph.rb +43 -123
- data/lib/solve/solver.rb +134 -262
- data/lib/solve/solver/serializer.rb +1 -1
- data/solve.gemspec +3 -1
- data/spec/acceptance/benchmark.rb +45 -0
- data/spec/acceptance/large_graph_no_solution.rb +18730 -0
- data/spec/acceptance/opscode_ci_graph.rb +18600 -0
- data/spec/acceptance/solutions_spec.rb +117 -76
- data/spec/spec_helper.rb +3 -0
- data/spec/unit/solve/artifact_spec.rb +49 -64
- data/spec/unit/solve/demand_spec.rb +19 -56
- data/spec/unit/solve/dependency_spec.rb +7 -46
- data/spec/unit/solve/graph_spec.rb +72 -209
- data/spec/unit/solve/solver/serializer_spec.rb +3 -4
- data/spec/unit/solve/solver_spec.rb +103 -247
- metadata +43 -22
- data/.ruby-version +0 -1
- data/lib/solve/solver/constraint_row.rb +0 -25
- data/lib/solve/solver/constraint_table.rb +0 -31
- data/lib/solve/solver/variable_row.rb +0 -43
- data/lib/solve/solver/variable_table.rb +0 -55
- data/lib/solve/tracers.rb +0 -50
- data/lib/solve/tracers/human_readable.rb +0 -67
- data/lib/solve/tracers/silent.rb +0 -17
- data/lib/solve/version.rb +0 -140
- data/spec/unit/solve/constraint_spec.rb +0 -708
- data/spec/unit/solve/version_spec.rb +0 -355
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ae30420202e6609e3ee6c924b9bb0be3eff51195
|
4
|
+
data.tar.gz: 253757447932569a84b96aded37c0cba007e5847
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d8c10b4a7b18fd20515cf99de348923104746f5297ac56bbd03f9ff56006d05f678d742a039047f065820cea39335dd73c41d0eab6508f8890a2673027abfa3b
|
7
|
+
data.tar.gz: e7fff54c7b0d5801e3ae8885db96cbcd12afd86c30bf84dbdedfa68c06189bf97fcf4964815a164a125ca07b6674aa7975a55dcf0dff578c183555150ecda237
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# Solve
|
2
|
-
[][gem]
|
3
|
+
[][travis]
|
4
|
+
|
5
|
+
[gem]: https://rubygems.org/gems/solve
|
6
|
+
[travis]: http://travis-ci.org/berkshelf/solve
|
4
7
|
|
5
8
|
A Ruby versioning constraint solver implementing [Semantic Versioning 2.0.0](http://semver.org).
|
6
9
|
|
@@ -16,15 +19,15 @@ Create a new graph
|
|
16
19
|
|
17
20
|
Add an artifact to the graph
|
18
21
|
|
19
|
-
graph.
|
22
|
+
graph.artifact("nginx", "1.0.0")
|
20
23
|
|
21
24
|
Now add another artifact that has a dependency
|
22
25
|
|
23
|
-
graph.
|
26
|
+
graph.artifact("mysql", "1.2.4-alpha.1").depends("openssl", "~> 1.0.0")
|
24
27
|
|
25
28
|
Dependencies can be chained, too
|
26
29
|
|
27
|
-
graph.
|
30
|
+
graph.artifact("ntp", "1.0.0").depends("build-essential").depends("yum")
|
28
31
|
|
29
32
|
And now solve the graph with some demands
|
30
33
|
|
@@ -35,12 +38,6 @@ NOTE: This will raise Solve::Errors::UnsortableSolutionError if the solution con
|
|
35
38
|
|
36
39
|
Solve.it!(graph, ['nginx', '>= 0.100.0'], sorted: true)
|
37
40
|
|
38
|
-
### Removing an artifact, or dependency from the graph
|
39
|
-
|
40
|
-
graph.artifacts("nginx", "1.0.0").delete
|
41
|
-
|
42
|
-
artifact.dependencies("nginx", "~> 1.0.0").delete
|
43
|
-
|
44
41
|
## Authors
|
45
42
|
|
46
43
|
* [Jamie Winsor](https://github.com/reset) (<jamie@vialstudios.com>)
|
data/lib/solve.rb
CHANGED
@@ -1,14 +1,13 @@
|
|
1
|
+
require 'semverse'
|
2
|
+
|
1
3
|
module Solve
|
2
4
|
require_relative 'solve/artifact'
|
3
|
-
require_relative 'solve/constraint'
|
4
5
|
require_relative 'solve/demand'
|
5
6
|
require_relative 'solve/dependency'
|
6
7
|
require_relative 'solve/gem_version'
|
7
8
|
require_relative 'solve/errors'
|
8
9
|
require_relative 'solve/graph'
|
9
10
|
require_relative 'solve/solver'
|
10
|
-
require_relative 'solve/version'
|
11
|
-
require_relative 'solve/tracers'
|
12
11
|
|
13
12
|
class << self
|
14
13
|
# @return [Solve::Formatter]
|
@@ -24,10 +23,7 @@ module Solve
|
|
24
23
|
# @param [Array<Solve::Demand>, Array<String, String>] demands
|
25
24
|
#
|
26
25
|
# @option options [#say] :ui (nil)
|
27
|
-
# a ui object for output
|
28
|
-
# no other tracer is provided in options[:tracer]
|
29
|
-
# @option options [AbstractTracer] :tracer (nil)
|
30
|
-
# a Tracer object that is used to format and output tracing information
|
26
|
+
# a ui object for output
|
31
27
|
# @option options [Boolean] :sorted (false)
|
32
28
|
# should the output be a sorted list rather than a Hash
|
33
29
|
#
|
@@ -35,7 +31,6 @@ module Solve
|
|
35
31
|
#
|
36
32
|
# @return [Hash]
|
37
33
|
def it!(graph, demands, options = {})
|
38
|
-
@tracer = options[:tracer] || Solve::Tracers.build(options[:ui])
|
39
34
|
Solver.new(graph, demands, options[:ui]).resolve(options)
|
40
35
|
end
|
41
36
|
end
|
data/lib/solve/artifact.rb
CHANGED
@@ -14,70 +14,72 @@ module Solve
|
|
14
14
|
|
15
15
|
# The version of this artifact
|
16
16
|
#
|
17
|
-
# @return [
|
17
|
+
# @return [Semverse::Version]
|
18
18
|
attr_reader :version
|
19
19
|
|
20
20
|
# @param [Solve::Graph] graph
|
21
21
|
# @param [#to_s] name
|
22
|
-
# @param [
|
22
|
+
# @param [Semverse::Version, #to_s] version
|
23
23
|
def initialize(graph, name, version)
|
24
|
-
@graph
|
25
|
-
@name
|
26
|
-
@version
|
27
|
-
@dependencies =
|
24
|
+
@graph = graph
|
25
|
+
@name = name
|
26
|
+
@version = Semverse::Version.new(version)
|
27
|
+
@dependencies = {}
|
28
28
|
end
|
29
29
|
|
30
|
-
#
|
31
|
-
#
|
30
|
+
# Check if the artifact has a dependency with the matching name and
|
31
|
+
# constraint
|
32
32
|
#
|
33
33
|
# @param [#to_s] name
|
34
|
-
# @param [
|
34
|
+
# @param [#to_s] constraint
|
35
35
|
#
|
36
|
-
# @
|
37
|
-
|
38
|
-
|
36
|
+
# @return [Boolean]
|
37
|
+
def dependency?(name, constraint)
|
38
|
+
!dependency(name, constraint).nil?
|
39
|
+
end
|
40
|
+
alias_method :has_dependency?, :dependency?
|
41
|
+
|
42
|
+
# Retrieve the dependency from the artifact with the matching name and constraint
|
39
43
|
#
|
40
|
-
# @
|
41
|
-
#
|
44
|
+
# @param [#to_s] name
|
45
|
+
# @param [#to_s] constraint
|
42
46
|
#
|
43
|
-
# @return [Solve::Artifact]
|
44
|
-
def
|
45
|
-
|
46
|
-
raise ArgumentError, "A name must be specified. You gave: #{args}."
|
47
|
-
end
|
48
|
-
|
49
|
-
dependency = Dependency.new(self, name, constraint)
|
50
|
-
add_dependency(dependency)
|
51
|
-
|
52
|
-
self
|
47
|
+
# @return [Solve::Artifact, nil]
|
48
|
+
def dependency(name, constraint)
|
49
|
+
@dependencies["#{name}-#{constraint}"] = Dependency.new(self, name, constraint)
|
53
50
|
end
|
54
51
|
|
55
52
|
# Return the collection of dependencies on this instance of artifact
|
56
53
|
#
|
57
54
|
# @return [Array<Solve::Dependency>]
|
58
55
|
def dependencies
|
59
|
-
@dependencies.
|
56
|
+
@dependencies.values
|
60
57
|
end
|
61
58
|
|
62
|
-
#
|
59
|
+
# Return the Solve::Dependency from the collection of
|
60
|
+
# dependencies with the given name and constraint.
|
63
61
|
#
|
64
62
|
# @param [#to_s] name
|
65
|
-
# @param [
|
63
|
+
# @param [String] constraint
|
66
64
|
#
|
67
|
-
# @
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
# Remove this artifact from the graph it belongs to
|
65
|
+
# @example Adding dependencies
|
66
|
+
# artifact.depends('nginx')
|
67
|
+
# #=> #<Dependency nginx (>= 0.0.0)>
|
68
|
+
# artifact.depends('ntp', '= 1.0.0')
|
69
|
+
# #=> #<Dependency ntp (= 1.0.0)>
|
73
70
|
#
|
74
|
-
# @
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
71
|
+
# @example Chaining dependencies
|
72
|
+
# artifact
|
73
|
+
# .depends('nginx')
|
74
|
+
# .depends('ntp', '~> 1.3')
|
75
|
+
#
|
76
|
+
# @return [Solve::Artifact]
|
77
|
+
def depends(name, constraint = '>= 0.0.0')
|
78
|
+
unless dependency?(name, constraint)
|
79
|
+
@dependencies["#{name}-#{constraint}"] = Dependency.new(self, name, constraint)
|
80
80
|
end
|
81
|
+
|
82
|
+
self
|
81
83
|
end
|
82
84
|
|
83
85
|
def to_s
|
@@ -89,54 +91,16 @@ module Solve
|
|
89
91
|
# @return [Boolean]
|
90
92
|
def ==(other)
|
91
93
|
other.is_a?(self.class) &&
|
92
|
-
|
93
|
-
|
94
|
+
self.name == other.name &&
|
95
|
+
self.version == other.version
|
94
96
|
end
|
95
97
|
alias_method :eql?, :==
|
96
98
|
|
97
|
-
# @param [
|
99
|
+
# @param [Semverse::Version] other
|
98
100
|
#
|
99
101
|
# @return [Integer]
|
100
102
|
def <=>(other)
|
101
103
|
self.version <=> other.version
|
102
104
|
end
|
103
|
-
|
104
|
-
private
|
105
|
-
|
106
|
-
# Add a Solve::Dependency to the collection of dependencies
|
107
|
-
# and return the added Solve::Dependency. No change will be
|
108
|
-
# made if the dependency is already a member of the collection.
|
109
|
-
#
|
110
|
-
# @param [Solve::Dependency] dependency
|
111
|
-
#
|
112
|
-
# @return [Solve::Dependency]
|
113
|
-
def add_dependency(dependency)
|
114
|
-
unless has_dependency?(dependency.name, dependency.constraint)
|
115
|
-
@dependencies[Graph.key_for(dependency)] = dependency
|
116
|
-
end
|
117
|
-
|
118
|
-
get_dependency(dependency.name, dependency.constraint)
|
119
|
-
end
|
120
|
-
|
121
|
-
# Remove the matching dependency from the artifact
|
122
|
-
#
|
123
|
-
# @param [Solve::Dependency] dependency
|
124
|
-
#
|
125
|
-
# @return [Solve::Dependency, nil]
|
126
|
-
def remove_dependency(dependency)
|
127
|
-
if has_dependency?(dependency)
|
128
|
-
@dependencies.delete(Graph.key_for(dependency))
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
# Check if the artifact has a dependency with the matching name and constraint
|
133
|
-
#
|
134
|
-
# @param [#to_s] name
|
135
|
-
# @param [#to_s] constraint
|
136
|
-
#
|
137
|
-
# @return [Boolean]
|
138
|
-
def has_dependency?(name, constraint)
|
139
|
-
@dependencies.has_key?(Graph.dependency_key(name, constraint))
|
140
|
-
end
|
141
105
|
end
|
142
106
|
end
|
data/lib/solve/constraint.rb
CHANGED
@@ -1,6 +1,19 @@
|
|
1
1
|
module Solve
|
2
2
|
class Constraint
|
3
3
|
class << self
|
4
|
+
# Coerce the object into a constraint.
|
5
|
+
#
|
6
|
+
# @param [Constraint, String]
|
7
|
+
#
|
8
|
+
# @return [Constraint]
|
9
|
+
def coerce(object)
|
10
|
+
if object.nil?
|
11
|
+
Semverse::DEFAULT_CONSTRAINT
|
12
|
+
else
|
13
|
+
object.is_a?(self) ? object : new(object)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
4
17
|
# Split a constraint string into an Array of two elements. The first
|
5
18
|
# element being the operator and second being the version string.
|
6
19
|
#
|
@@ -50,64 +63,64 @@ module Solve
|
|
50
63
|
[ operator, split_version ].flatten
|
51
64
|
end
|
52
65
|
|
53
|
-
# @param [
|
54
|
-
# @param [
|
66
|
+
# @param [Semverse::Constraint] constraint
|
67
|
+
# @param [Semverse::Version] target_version
|
55
68
|
#
|
56
69
|
# @return [Boolean]
|
57
70
|
def compare_equal(constraint, target_version)
|
58
71
|
target_version == constraint.version
|
59
72
|
end
|
60
73
|
|
61
|
-
# @param [
|
62
|
-
# @param [
|
74
|
+
# @param [Semverse::Constraint] constraint
|
75
|
+
# @param [Semverse::Version] target_version
|
63
76
|
#
|
64
77
|
# @return [Boolean]
|
65
78
|
def compare_gt(constraint, target_version)
|
66
79
|
target_version > constraint.version
|
67
80
|
end
|
68
81
|
|
69
|
-
# @param [
|
70
|
-
# @param [
|
82
|
+
# @param [Semverse::Constraint] constraint
|
83
|
+
# @param [Semverse::Version] target_version
|
71
84
|
#
|
72
85
|
# @return [Boolean]
|
73
86
|
def compare_lt(constraint, target_version)
|
74
87
|
target_version < constraint.version
|
75
88
|
end
|
76
89
|
|
77
|
-
# @param [
|
78
|
-
# @param [
|
90
|
+
# @param [Semverse::Constraint] constraint
|
91
|
+
# @param [Semverse::Version] target_version
|
79
92
|
#
|
80
93
|
# @return [Boolean]
|
81
94
|
def compare_gte(constraint, target_version)
|
82
95
|
target_version >= constraint.version
|
83
96
|
end
|
84
97
|
|
85
|
-
# @param [
|
86
|
-
# @param [
|
98
|
+
# @param [Semverse::Constraint] constraint
|
99
|
+
# @param [Semverse::Version] target_version
|
87
100
|
#
|
88
101
|
# @return [Boolean]
|
89
102
|
def compare_lte(constraint, target_version)
|
90
103
|
target_version <= constraint.version
|
91
104
|
end
|
92
105
|
|
93
|
-
# @param [
|
94
|
-
# @param [
|
106
|
+
# @param [Semverse::Constraint] constraint
|
107
|
+
# @param [Semverse::Version] target_version
|
95
108
|
#
|
96
109
|
# @return [Boolean]
|
97
110
|
def compare_approx(constraint, target_version)
|
98
111
|
min = constraint.version
|
99
112
|
max = if constraint.patch.nil?
|
100
|
-
Version.new([min.major + 1, 0, 0, 0])
|
113
|
+
Semverse::Version.new([min.major + 1, 0, 0, 0])
|
101
114
|
elsif constraint.build
|
102
115
|
identifiers = constraint.version.identifiers(:build)
|
103
116
|
replace = identifiers.last.to_i.to_s == identifiers.last.to_s ? "-" : nil
|
104
|
-
Version.new([min.major, min.minor, min.patch, min.pre_release, identifiers.fill(replace, -1).join('.')])
|
117
|
+
Semverse::Version.new([min.major, min.minor, min.patch, min.pre_release, identifiers.fill(replace, -1).join('.')])
|
105
118
|
elsif constraint.pre_release
|
106
119
|
identifiers = constraint.version.identifiers(:pre_release)
|
107
120
|
replace = identifiers.last.to_i.to_s == identifiers.last.to_s ? "-" : nil
|
108
|
-
Version.new([min.major, min.minor, min.patch, identifiers.fill(replace, -1).join('.')])
|
121
|
+
Semverse::Version.new([min.major, min.minor, min.patch, identifiers.fill(replace, -1).join('.')])
|
109
122
|
else
|
110
|
-
Version.new([min.major, min.minor + 1, 0, 0])
|
123
|
+
Semverse::Version.new([min.major, min.minor + 1, 0, 0])
|
111
124
|
end
|
112
125
|
min <= target_version && target_version < max
|
113
126
|
end
|
@@ -141,10 +154,17 @@ module Solve
|
|
141
154
|
attr_reader :pre_release
|
142
155
|
attr_reader :build
|
143
156
|
|
144
|
-
#
|
157
|
+
# Return the Semverse::Version representation of the major, minor, and patch
|
158
|
+
# attributes of this instance
|
159
|
+
#
|
160
|
+
# @return [Semverse::Version]
|
161
|
+
attr_reader :version
|
162
|
+
|
163
|
+
# @param [#to_s] constraint
|
145
164
|
def initialize(constraint = nil)
|
165
|
+
constraint = constraint.to_s
|
146
166
|
if constraint.nil? || constraint.empty?
|
147
|
-
constraint =
|
167
|
+
constraint = '>= 0.0.0'
|
148
168
|
end
|
149
169
|
|
150
170
|
@operator, @major, @minor, @patch, @pre_release, @build = self.class.split(constraint)
|
@@ -153,22 +173,14 @@ module Solve
|
|
153
173
|
@minor ||= 0
|
154
174
|
@patch ||= 0
|
155
175
|
end
|
156
|
-
end
|
157
176
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
self.major,
|
166
|
-
self.minor,
|
167
|
-
self.patch,
|
168
|
-
self.pre_release,
|
169
|
-
self.build
|
170
|
-
]
|
171
|
-
)
|
177
|
+
@version = Semverse::Version.new([
|
178
|
+
self.major,
|
179
|
+
self.minor,
|
180
|
+
self.patch,
|
181
|
+
self.pre_release,
|
182
|
+
self.build,
|
183
|
+
])
|
172
184
|
end
|
173
185
|
|
174
186
|
# @return [Symbol]
|
@@ -183,17 +195,21 @@ module Solve
|
|
183
195
|
# Returns true or false if the given version would be satisfied by
|
184
196
|
# the version constraint.
|
185
197
|
#
|
186
|
-
# @param [#to_s]
|
198
|
+
# @param [Version, #to_s] target
|
187
199
|
#
|
188
200
|
# @return [Boolean]
|
189
|
-
def satisfies?(
|
190
|
-
|
201
|
+
def satisfies?(target)
|
202
|
+
target = Version.coerce(target)
|
191
203
|
|
192
|
-
return false if !version.zero? && greedy_match?(
|
204
|
+
return false if !version.zero? && greedy_match?(target)
|
193
205
|
|
194
|
-
compare(
|
206
|
+
compare(target)
|
195
207
|
end
|
196
208
|
|
209
|
+
# dep-selector uses include? to determine if a version matches the
|
210
|
+
# constriant.
|
211
|
+
alias_method :include?, :satisfies?
|
212
|
+
|
197
213
|
# @param [Object] other
|
198
214
|
#
|
199
215
|
# @return [Boolean]
|
@@ -209,12 +225,12 @@ module Solve
|
|
209
225
|
end
|
210
226
|
|
211
227
|
def to_s
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
228
|
+
out = "#{operator} #{major}"
|
229
|
+
out << ".#{minor}" if minor
|
230
|
+
out << ".#{patch}" if patch
|
231
|
+
out << "-#{pre_release}" if pre_release
|
232
|
+
out << "+#{build}" if build
|
233
|
+
out
|
218
234
|
end
|
219
235
|
|
220
236
|
private
|
@@ -223,13 +239,13 @@ module Solve
|
|
223
239
|
# does not include a pre-release and if the operator isn't < or <=.
|
224
240
|
# This avoids greedy matches, e.g. 2.0.0.alpha won't satisfy >= 1.0.0.
|
225
241
|
#
|
226
|
-
# @param [
|
242
|
+
# @param [Semverse::Version] target_version
|
227
243
|
#
|
228
244
|
def greedy_match?(target_version)
|
229
245
|
operator_type !~ /less/ && target_version.pre_release? && !version.pre_release?
|
230
246
|
end
|
231
247
|
|
232
|
-
# @param [
|
248
|
+
# @param [Semverse::Version] target
|
233
249
|
#
|
234
250
|
# @return [Boolean]
|
235
251
|
def compare(target)
|