solve 3.1.1 → 4.0.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 +4 -4
- data/.travis.yml +10 -7
- data/Gemfile +20 -17
- data/Guardfile +6 -6
- data/Rakefile +11 -1
- data/Thorfile +3 -3
- data/lib/solve.rb +9 -11
- data/lib/solve/artifact.rb +10 -10
- data/lib/solve/constraint.rb +41 -41
- data/lib/solve/demand.rb +2 -2
- data/lib/solve/dependency.rb +4 -2
- data/lib/solve/errors.rb +1 -1
- data/lib/solve/gecode_solver.rb +94 -94
- data/lib/solve/graph.rb +3 -3
- data/lib/solve/ruby_solver.rb +125 -62
- data/lib/solve/solver/serializer.rb +53 -53
- data/lib/solve/version.rb +1 -1
- data/solve.gemspec +7 -7
- data/spec/acceptance/benchmark.rb +4 -4
- data/spec/acceptance/large_graph_no_solution.rb +18727 -18727
- data/spec/acceptance/opscode_ci_graph.rb +18598 -18598
- data/spec/acceptance/ruby_solver_solutions_spec.rb +14 -15
- data/spec/acceptance/solutions_spec.rb +14 -15
- data/spec/spec_helper.rb +15 -8
- data/spec/unit/solve/artifact_spec.rb +5 -5
- data/spec/unit/solve/demand_spec.rb +2 -2
- data/spec/unit/solve/dependency_spec.rb +3 -3
- data/spec/unit/solve/gecode_solver_spec.rb +6 -7
- data/spec/unit/solve/graph_spec.rb +7 -7
- data/spec/unit/solve/ruby_solver_spec.rb +5 -6
- data/spec/unit/solve/solver/serializer_spec.rb +2 -2
- data/spec/unit/solve_spec.rb +1 -1
- metadata +6 -6
data/lib/solve/graph.rb
CHANGED
@@ -67,7 +67,7 @@ module Solve
|
|
67
67
|
return false unless other.is_a?(Graph)
|
68
68
|
return false unless artifacts.size == other.artifacts.size
|
69
69
|
|
70
|
-
self_artifacts =
|
70
|
+
self_artifacts = artifacts
|
71
71
|
other_artifacts = other.artifacts
|
72
72
|
|
73
73
|
self_dependencies = self_artifacts.inject([]) do |list, artifact|
|
@@ -79,8 +79,8 @@ module Solve
|
|
79
79
|
end.flatten
|
80
80
|
|
81
81
|
self_dependencies.size == other_dependencies.size &&
|
82
|
-
|
83
|
-
|
82
|
+
self_artifacts.all? { |artifact| other_artifacts.include?(artifact) } &&
|
83
|
+
self_dependencies.all? { |dependency| other_dependencies.include?(dependency) }
|
84
84
|
end
|
85
85
|
alias_method :eql?, :==
|
86
86
|
end
|
data/lib/solve/ruby_solver.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
|
1
|
+
require "set"
|
2
|
+
require "molinillo"
|
3
|
+
require "molinillo/modules/specification_provider"
|
4
|
+
require_relative "solver/serializer"
|
4
5
|
|
5
6
|
module Solve
|
6
7
|
class RubySolver
|
@@ -14,7 +15,7 @@ module Solve
|
|
14
15
|
seconds.to_i * 1_000
|
15
16
|
end
|
16
17
|
|
17
|
-
# For
|
18
|
+
# For optional solver engines, this attempts to load depenencies. The
|
18
19
|
# RubySolver is a non-optional component, so this is a no-op
|
19
20
|
def activate
|
20
21
|
true
|
@@ -45,7 +46,7 @@ module Solve
|
|
45
46
|
@timeout_ms = self.class.timeout
|
46
47
|
|
47
48
|
@ui = options[:ui] # could be nil, but that's okay
|
48
|
-
@dependency_source = options[:dependency_source] ||
|
49
|
+
@dependency_source = options[:dependency_source] || "user-specified dependency"
|
49
50
|
|
50
51
|
@molinillo_graph = Molinillo::DependencyGraph.new
|
51
52
|
@resolver = Molinillo::Resolver.new(self, self)
|
@@ -74,10 +75,10 @@ module Solve
|
|
74
75
|
|
75
76
|
solved_graph = resolve_with_error_wrapping
|
76
77
|
|
77
|
-
solution =
|
78
|
+
solution = solved_graph.map(&:payload)
|
78
79
|
|
79
80
|
unsorted_solution = solution.inject({}) do |stringified_soln, artifact|
|
80
|
-
stringified_soln[artifact.name] = artifact.version.to_s
|
81
|
+
stringified_soln[artifact.name] = artifact.version.to_s
|
81
82
|
stringified_soln
|
82
83
|
end
|
83
84
|
|
@@ -106,13 +107,13 @@ module Solve
|
|
106
107
|
# Callback required by Molinillo, called when the solve starts
|
107
108
|
# @return nil
|
108
109
|
def before_resolution
|
109
|
-
@ui.say(
|
110
|
+
@ui.say("Starting dependency resolution") if @ui
|
110
111
|
end
|
111
112
|
|
112
113
|
# Callback required by Molinillo, called when the solve is complete.
|
113
114
|
# @return nil
|
114
115
|
def after_resolution
|
115
|
-
@ui.say(
|
116
|
+
@ui.say("Finished dependency resolution") if @ui
|
116
117
|
end
|
117
118
|
|
118
119
|
# Callback required by Molinillo, called when resolving every progress_rate
|
@@ -123,32 +124,22 @@ module Solve
|
|
123
124
|
|
124
125
|
# Callback required by Molinillo, gives debug information about the solution
|
125
126
|
# @return nil
|
126
|
-
def debug(current_resolver_depth)
|
127
|
+
def debug(current_resolver_depth = 0)
|
127
128
|
# debug info will be returned if you call yield here, but it seems to be
|
128
129
|
# broken in current Molinillo
|
129
130
|
@ui.say(yield) if @ui
|
130
131
|
end
|
131
132
|
|
132
|
-
|
133
|
-
# @return [String] the dependency's name
|
134
|
-
def name_for(dependency)
|
135
|
-
dependency.name
|
136
|
-
end
|
137
|
-
|
138
|
-
# Callback required by Molinillo
|
139
|
-
# @return [Array<Solve::Dependency>] the dependencies sorted by preference.
|
140
|
-
def sort_dependencies(dependencies, activated, conflicts)
|
141
|
-
dependencies.sort_by do |dependency|
|
142
|
-
name = name_for(dependency)
|
143
|
-
[
|
144
|
-
activated.vertex_named(name).payload ? 0 : 1,
|
145
|
-
conflicts[name] ? 0 : 1,
|
146
|
-
activated.vertex_named(name).payload ? 0 : graph.versions(dependency.name).count,
|
147
|
-
]
|
148
|
-
end
|
149
|
-
end
|
133
|
+
include Molinillo::SpecificationProvider
|
150
134
|
|
151
135
|
# Callback required by Molinillo
|
136
|
+
# Search for the specifications that match the given dependency.
|
137
|
+
# The specifications in the returned array will be considered in reverse
|
138
|
+
# order, so the latest version ought to be last.
|
139
|
+
# @note This method should be 'pure', i.e. the return value should depend
|
140
|
+
# only on the `dependency` parameter.
|
141
|
+
#
|
142
|
+
# @param [Object] dependency
|
152
143
|
# @return [Array<Solve::Artifact>] the artifacts that match the dependency.
|
153
144
|
def search_for(dependency)
|
154
145
|
# This array gets mutated by Molinillo; it's okay because sort returns a
|
@@ -157,29 +148,101 @@ module Solve
|
|
157
148
|
end
|
158
149
|
|
159
150
|
# Callback required by Molinillo
|
160
|
-
#
|
151
|
+
# Returns the dependencies of `specification`.
|
152
|
+
# @note This method should be 'pure', i.e. the return value should depend
|
153
|
+
# only on the `specification` parameter.
|
154
|
+
#
|
155
|
+
# @param [Object] specification
|
156
|
+
# @return [Array<Solve::Dependency>] the dependencies of the given artifact
|
157
|
+
def dependencies_for(specification)
|
158
|
+
specification.dependencies
|
159
|
+
end
|
160
|
+
|
161
|
+
# Callback required by Molinillo
|
162
|
+
# Determines whether the given `requirement` is satisfied by the given
|
163
|
+
# `spec`, in the context of the current `activated` dependency graph.
|
164
|
+
#
|
165
|
+
# @param [Object] requirement
|
166
|
+
# @param [DependencyGraph] activated the current dependency graph in the
|
167
|
+
# resolution process.
|
168
|
+
# @param [Object] spec
|
169
|
+
# @return [Boolean] whether `requirement` is satisfied by `spec` in the
|
170
|
+
# context of the current `activated` dependency graph.
|
161
171
|
def requirement_satisfied_by?(requirement, activated, spec)
|
162
|
-
|
172
|
+
version = spec.version
|
173
|
+
return false unless requirement.constraint.satisfies?(version)
|
174
|
+
shared_possibility_versions = possibility_versions(requirement, activated)
|
175
|
+
return false if !shared_possibility_versions.empty? && !shared_possibility_versions.include?(version)
|
176
|
+
true
|
177
|
+
end
|
178
|
+
|
179
|
+
# Searches the current dependency graph to find previously activated
|
180
|
+
# requirements for the current artifact.
|
181
|
+
#
|
182
|
+
# @param [Object] requirement
|
183
|
+
# @param [DependencyGraph] activated the current dependency graph in the
|
184
|
+
# resolution process.
|
185
|
+
# @return [Array<Semverse::Version> the list of currently activated versions
|
186
|
+
# of this requirement
|
187
|
+
def possibility_versions(requirement, activated)
|
188
|
+
activated.vertices.values.flat_map do |vertex|
|
189
|
+
|
190
|
+
next unless vertex.payload
|
191
|
+
|
192
|
+
next unless vertex.name == requirement.name
|
193
|
+
|
194
|
+
if vertex.payload.respond_to?(:possibilities)
|
195
|
+
vertex.payload.possibilities.map(&:version)
|
196
|
+
else
|
197
|
+
vertex.payload.version
|
198
|
+
end
|
199
|
+
end.compact
|
163
200
|
end
|
201
|
+
private :possibility_versions
|
164
202
|
|
165
203
|
# Callback required by Molinillo
|
166
|
-
#
|
167
|
-
|
168
|
-
|
204
|
+
# Returns the name for the given `dependency`.
|
205
|
+
# @note This method should be 'pure', i.e. the return value should depend
|
206
|
+
# only on the `dependency` parameter.
|
207
|
+
#
|
208
|
+
# @param [Object] dependency
|
209
|
+
# @return [String] the name for the given `dependency`.
|
210
|
+
def name_for(dependency)
|
211
|
+
dependency.name
|
169
212
|
end
|
170
213
|
|
214
|
+
# Callback required by Molinillo
|
171
215
|
# @return [String] the name of the source of explicit dependencies, i.e.
|
172
216
|
# those passed to {Resolver#resolve} directly.
|
173
217
|
def name_for_explicit_dependency_source
|
174
218
|
@dependency_source
|
175
219
|
end
|
176
220
|
|
177
|
-
#
|
178
|
-
#
|
179
|
-
|
180
|
-
|
221
|
+
# Callback required by Molinillo
|
222
|
+
# Sort dependencies so that the ones that are easiest to resolve are first.
|
223
|
+
# Easiest to resolve is (usually) defined by:
|
224
|
+
# 1) Is this dependency already activated?
|
225
|
+
# 2) How relaxed are the requirements?
|
226
|
+
# 3) Are there any conflicts for this dependency?
|
227
|
+
# 4) How many possibilities are there to satisfy this dependency?
|
228
|
+
#
|
229
|
+
# @param [Array<Object>] dependencies
|
230
|
+
# @param [DependencyGraph] activated the current dependency graph in the
|
231
|
+
# resolution process.
|
232
|
+
# @param [{String => Array<Conflict>}] conflicts
|
233
|
+
# @return [Array<Solve::Dependency>] the dependencies sorted by preference.
|
234
|
+
def sort_dependencies(dependencies, activated, conflicts)
|
235
|
+
dependencies.sort_by do |dependency|
|
236
|
+
name = name_for(dependency)
|
237
|
+
[
|
238
|
+
activated.vertex_named(name).payload ? 0 : 1,
|
239
|
+
conflicts[name] ? 0 : 1,
|
240
|
+
search_for(dependency).count,
|
241
|
+
]
|
242
|
+
end
|
181
243
|
end
|
182
244
|
|
245
|
+
# Callback required by Molinillo
|
183
246
|
# Returns whether this dependency, which has no possible matching
|
184
247
|
# specifications, can safely be ignored.
|
185
248
|
#
|
@@ -191,35 +254,35 @@ module Solve
|
|
191
254
|
|
192
255
|
private
|
193
256
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
257
|
+
def resolve_with_error_wrapping
|
258
|
+
@resolver.resolve(demands, @molinillo_graph)
|
259
|
+
rescue Molinillo::VersionConflict, Molinillo::CircularDependencyError => e
|
260
|
+
raise Solve::Errors::NoSolutionError.new(e.message)
|
261
|
+
end
|
199
262
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
263
|
+
def build_sorted_solution(unsorted_solution)
|
264
|
+
nodes = Hash.new
|
265
|
+
unsorted_solution.each do |name, version|
|
266
|
+
nodes[name] = @graph.artifact(name, version).dependencies.map(&:name)
|
267
|
+
end
|
205
268
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
end
|
213
|
-
end
|
214
|
-
begin
|
215
|
-
sorted_names = nodes.tsort
|
216
|
-
rescue TSort::Cyclic => e
|
217
|
-
raise Solve::Errors::UnsortableSolutionError.new(e, unsorted_solution)
|
269
|
+
# Modified from http://ruby-doc.org/stdlib-1.9.3/libdoc/tsort/rdoc/TSort.html
|
270
|
+
class << nodes
|
271
|
+
include TSort
|
272
|
+
alias tsort_each_node each_key
|
273
|
+
def tsort_each_child(node, &block)
|
274
|
+
fetch(node).each(&block)
|
218
275
|
end
|
276
|
+
end
|
277
|
+
begin
|
278
|
+
sorted_names = nodes.tsort
|
279
|
+
rescue TSort::Cyclic => e
|
280
|
+
raise Solve::Errors::UnsortableSolutionError.new(e, unsorted_solution)
|
281
|
+
end
|
219
282
|
|
220
|
-
|
221
|
-
|
222
|
-
end
|
283
|
+
sorted_names.map do |artifact|
|
284
|
+
[artifact, unsorted_solution[artifact]]
|
223
285
|
end
|
286
|
+
end
|
224
287
|
end
|
225
288
|
end
|
@@ -1,5 +1,5 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "json"
|
2
|
+
require "solve/graph"
|
3
3
|
|
4
4
|
module Solve
|
5
5
|
|
@@ -58,71 +58,71 @@ module Solve
|
|
58
58
|
|
59
59
|
private
|
60
60
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
end
|
65
|
-
{ "graph" => artifacts }
|
61
|
+
def format_graph(graph)
|
62
|
+
artifacts = graph.artifacts.inject([]) do |list, artifact|
|
63
|
+
list << format_artifact(artifact)
|
66
64
|
end
|
65
|
+
{ "graph" => artifacts }
|
66
|
+
end
|
67
67
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
end
|
72
|
-
|
73
|
-
{
|
74
|
-
"name" => artifact.name,
|
75
|
-
"version" => artifact.version.to_s,
|
76
|
-
"dependencies" => dependencies
|
77
|
-
}
|
68
|
+
def format_artifact(artifact)
|
69
|
+
dependencies = artifact.dependencies.inject([]) do |list, dependency|
|
70
|
+
list << format_dependency(dependency)
|
78
71
|
end
|
79
72
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
73
|
+
{
|
74
|
+
"name" => artifact.name,
|
75
|
+
"version" => artifact.version.to_s,
|
76
|
+
"dependencies" => dependencies,
|
77
|
+
}
|
78
|
+
end
|
86
79
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
80
|
+
def format_dependency(dependency)
|
81
|
+
{
|
82
|
+
"name" => dependency.name,
|
83
|
+
"constraint" => dependency.constraint.to_s,
|
84
|
+
}
|
85
|
+
end
|
93
86
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
"constraint" => demand[1]
|
98
|
-
}
|
87
|
+
def format_demands(demands)
|
88
|
+
demands_list = demands.inject([]) do |list, demand|
|
89
|
+
list << format_demand(demand)
|
99
90
|
end
|
91
|
+
{ "demands" => demands_list }
|
92
|
+
end
|
100
93
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
end
|
94
|
+
def format_demand(demand)
|
95
|
+
{
|
96
|
+
"name" => demand[0],
|
97
|
+
"constraint" => demand[1],
|
98
|
+
}
|
99
|
+
end
|
108
100
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
end
|
114
|
-
artifact
|
101
|
+
def load_graph(artifacts_list)
|
102
|
+
graph = Solve::Graph.new
|
103
|
+
artifacts_list.each do |artifact_spec|
|
104
|
+
load_artifact(graph, artifact_spec)
|
115
105
|
end
|
106
|
+
graph
|
107
|
+
end
|
116
108
|
|
117
|
-
|
118
|
-
|
109
|
+
def load_artifact(graph, artifact_spec)
|
110
|
+
artifact = graph.artifact(artifact_spec["name"], artifact_spec["version"])
|
111
|
+
artifact_spec["dependencies"].each do |dependency_spec|
|
112
|
+
load_dependency(artifact, dependency_spec)
|
119
113
|
end
|
114
|
+
artifact
|
115
|
+
end
|
120
116
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
117
|
+
def load_dependency(artifact, dependency_spec)
|
118
|
+
artifact.depends(dependency_spec["name"], dependency_spec["constraint"])
|
119
|
+
end
|
120
|
+
|
121
|
+
def load_demands(demand_specs)
|
122
|
+
demand_specs.inject([]) do |list, demand_spec|
|
123
|
+
list << [demand_spec["name"], demand_spec["constraint"]]
|
125
124
|
end
|
125
|
+
end
|
126
126
|
end
|
127
127
|
end
|
128
128
|
end
|
data/lib/solve/version.rb
CHANGED
data/solve.gemspec
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
require File.expand_path(
|
2
|
+
require File.expand_path("../lib/solve/version", __FILE__)
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.authors = ["Jamie Winsor", "Andrew Garson", "Thibaud Guillaume-Gentil"]
|
@@ -9,7 +9,7 @@ Gem::Specification.new do |s|
|
|
9
9
|
s.homepage = "https://github.com/berkshelf/solve"
|
10
10
|
s.license = "Apache 2.0"
|
11
11
|
s.files = `git ls-files`.split($\)
|
12
|
-
s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
12
|
+
s.executables = s.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
13
13
|
s.test_files = s.files.grep(%r{^spec/})
|
14
14
|
s.name = "solve"
|
15
15
|
s.require_paths = ["lib"]
|
@@ -17,10 +17,10 @@ Gem::Specification.new do |s|
|
|
17
17
|
s.required_ruby_version = ">= 2.1.0"
|
18
18
|
|
19
19
|
s.add_dependency "semverse", ">= 1.1", "< 3.0"
|
20
|
-
s.add_dependency "molinillo", "
|
20
|
+
s.add_dependency "molinillo", "~> 0.6"
|
21
21
|
|
22
|
-
s.add_development_dependency
|
23
|
-
s.add_development_dependency
|
24
|
-
s.add_development_dependency
|
25
|
-
s.add_development_dependency
|
22
|
+
s.add_development_dependency "thor"
|
23
|
+
s.add_development_dependency "rake"
|
24
|
+
s.add_development_dependency "spork"
|
25
|
+
s.add_development_dependency "rspec"
|
26
26
|
end
|