molinillo 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f58da410ac680b6b1dcb44c90cff7ee899e05e2d
4
+ data.tar.gz: dfa4c21e605ee3fd74bcec2ea167fb0e1c1a1ae4
5
+ SHA512:
6
+ metadata.gz: bdfa7ee068ed2f379406f047d4f6ed72935c29046ff314b772dceee1568fbe9bce54a7a2a7524adec384760c483f5b1f800467726574871267e55df56321ed3d
7
+ data.tar.gz: e6070563c8b8020cf71c23e518522423769fe2248790bc7b5e88e90dd66eb321a6e6a01176a5957fd912b1052abb171a4f83a208cbdde59db926ae091ab45ecd
data/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+ This project is licensed under the MIT license.
2
+
3
+ Copyright (c) 2014 Samuel E. Giddins segiddins@segiddins.me
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # Molinillo
2
+
3
+ [![Build Status](https://img.shields.io/travis/CocoaPods/Molinillo/master.svg?style=flat)](https://travis-ci.org/CocoaPods/Molinillo)
4
+ [![Coverage](https://img.shields.io/codeclimate/coverage/github/CocoaPods/Molinillo.svg?style=flat)](https://codeclimate.com/github/CocoaPods/Molinillo)
5
+ [![Code Climate](https://img.shields.io/codeclimate/github/CocoaPods/Molinillo.svg?style=flat)](https://codeclimate.com/github/CocoaPods/Molinillo)
6
+
7
+ A generic dependency-resolution implementation.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'molinillo', :git => 'https://github.com/CocoaPods/Molinillo'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ ```bash
20
+ $ bundle install
21
+ ```
22
+
23
+ Or install it yourself as:
24
+
25
+ ```bash
26
+ $ gem install molinillo
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ Look at the test suite for example usage. Better documentation and examples are
32
+ forthcoming.
33
+
34
+ ## Contributing
35
+
36
+ 1. Fork it
37
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
38
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
39
+ 4. Push to the branch (`git push origin my-new-feature`)
40
+ 5. Create a pull request
41
+
42
+ ## The Name
43
+
44
+ [Molinillo](http://en.wikipedia.org/wiki/Molinillo_(whisk)) is a special whisk used in Mexico in the preparation of beverages such as hot chocolate.
45
+ Much like a dependency resolver, a molinillo helps take a list of ingredients and turn it into a delicious concoction!
data/lib/molinillo.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'molinillo/gem_metadata'
2
+ require 'molinillo/errors'
3
+ require 'molinillo/resolver'
4
+ require 'molinillo/modules/ui'
5
+ require 'molinillo/modules/specification_provider'
@@ -0,0 +1,243 @@
1
+ require 'set'
2
+
3
+ module Molinillo
4
+ # A directed acyclic graph that is tuned to hold named dependencies
5
+ class DependencyGraph
6
+ include Enumerable
7
+
8
+ def each
9
+ vertices.values.each { |v| yield v }
10
+ end
11
+
12
+ # A directed edge of a {DependencyGraph}
13
+ # @attr [Vertex] origin The origin of the directed edge
14
+ # @attr [Vertex] destination The destination of the directed edge
15
+ # @attr [Array] requirements The requirements the directed edge represents
16
+ Edge = Struct.new(:origin, :destination, :requirements)
17
+
18
+ # @return [{String => Vertex}] vertices that have no {Vertex#predecessors},
19
+ # keyed by by {Vertex#name}
20
+ attr_reader :root_vertices
21
+ # @return [{String => Vertex}] the vertices of the dependency graph, keyed
22
+ # by {Vertex#name}
23
+ attr_reader :vertices
24
+ # @return [Set<Edge>] the edges of the dependency graph
25
+ attr_reader :edges
26
+
27
+ def initialize
28
+ @vertices = {}
29
+ @edges = Set.new
30
+ @root_vertices = {}
31
+ end
32
+
33
+ # Initializes a copy of a {DependencyGraph}, ensuring that all {#vertices}
34
+ # have the correct {Vertex#graph} set
35
+ def initialize_copy(other)
36
+ super
37
+ @vertices = other.vertices.reduce({}) do |vertices, (name, vertex)|
38
+ vertices.tap do |hash|
39
+ hash[name] = vertex.dup.tap { |v| v.graph = self }
40
+ end
41
+ end
42
+ @root_vertices = Hash[vertices.select { |n, _v| other.root_vertices[n] }]
43
+ @edges = other.edges.map do |edge|
44
+ Edge.new(
45
+ vertex_named(edge.origin.name),
46
+ vertex_named(edge.destination.name),
47
+ edge.requirements.dup
48
+ )
49
+ end
50
+ end
51
+
52
+ # @return [String] a string suitable for debugging
53
+ def inspect
54
+ "#{self.class}:#{vertices.values.inspect}"
55
+ end
56
+
57
+ # @return [Boolean] whether the two dependency graphs are equal, determined
58
+ # by a recursive traversal of each {#root_vertices} and its
59
+ # {Vertex#successors}
60
+ def ==(other)
61
+ root_vertices == other.root_vertices
62
+ end
63
+
64
+ # @param [String] name
65
+ # @param [Object] payload
66
+ # @param [Array<String>] parent_names
67
+ # @param [Object] requirement the requirement that is requiring the child
68
+ # @return [void]
69
+ def add_child_vertex(name, payload, parent_names, requirement)
70
+ is_root = parent_names.include?(nil)
71
+ parent_nodes = parent_names.compact.map { |n| vertex_named(n) }
72
+ vertex = vertex_named(name) || if is_root
73
+ add_root_vertex(name, payload)
74
+ else
75
+ add_vertex(name, payload)
76
+ end
77
+ vertex.payload ||= payload
78
+ parent_nodes.each do |parent_node|
79
+ add_edge(parent_node, vertex, requirement)
80
+ end
81
+ vertex
82
+ end
83
+
84
+ # @param [String] name
85
+ # @param [Object] payload
86
+ # @return [Vertex] the vertex that was added to `self`
87
+ def add_vertex(name, payload)
88
+ vertex = vertices[name] ||= Vertex.new(self, name, payload)
89
+ vertex.tap { |v| v.payload = payload }
90
+ end
91
+
92
+ # @param [String] name
93
+ # @param [Object] payload
94
+ # @return [Vertex] the vertex that was added to `self`
95
+ def add_root_vertex(name, payload)
96
+ add_vertex(name, payload).tap { |v| root_vertices[name] = v }
97
+ end
98
+
99
+ # Detaches the {#vertex_named} `name` {Vertex} from the graph, recursively
100
+ # removing any non-root vertices that were orphaned in the process
101
+ # @param [String] name
102
+ # @return [void]
103
+ def detach_vertex_named(name)
104
+ vertex = vertex_named(name)
105
+ successors = vertex.successors
106
+ vertices.delete(name)
107
+ edges.reject! { |e| e.origin == vertex || e.destination == vertex }
108
+ successors.each { |v| detach_vertex_named(v.name) unless root_vertices[v.name] || v.predecessors.any? }
109
+ end
110
+
111
+ # @param [String] name
112
+ # @return [Vertex,nil] the vertex with the given name
113
+ def vertex_named(name)
114
+ vertices[name]
115
+ end
116
+
117
+ # @param [String] name
118
+ # @return [Vertex,nil] the root vertex with the given name
119
+ def root_vertex_named(name)
120
+ root_vertices[name]
121
+ end
122
+
123
+ # Adds a new {Edge} to the dependency graph
124
+ # @param [Vertex] origin
125
+ # @param [Vertex] destination
126
+ # @param [Object] requirement the requirement that this edge represents
127
+ # @return [Edge] the added edge
128
+ def add_edge(origin, destination, requirement)
129
+ if origin == destination || destination.path_to?(origin)
130
+ raise CircularDependencyError.new([origin, destination])
131
+ end
132
+ Edge.new(origin, destination, [requirement]).tap { |e| edges << e }
133
+ end
134
+
135
+ # A vertex in a {DependencyGraph} that encapsulates a {#name} and a
136
+ # {#payload}
137
+ class Vertex
138
+ # @return [DependencyGraph] the graph this vertex is a node of
139
+ attr_accessor :graph
140
+
141
+ # @return [String] the name of the vertex
142
+ attr_accessor :name
143
+
144
+ # @return [Object] the payload the vertex holds
145
+ attr_accessor :payload
146
+
147
+ # @return [Arrary<Object>] the explicit requirements that required
148
+ # this vertex
149
+ attr_reader :explicit_requirements
150
+
151
+ # @param [DependencyGraph] graph see {#graph}
152
+ # @param [String] name see {#name}
153
+ # @param [Object] payload see {#payload}
154
+ def initialize(graph, name, payload)
155
+ @graph = graph
156
+ @name = name
157
+ @payload = payload
158
+ @explicit_requirements = []
159
+ end
160
+
161
+ # @return [Array<Object>] all of the requirements that required
162
+ # this vertex
163
+ def requirements
164
+ incoming_edges.flat_map(&:requirements) + explicit_requirements
165
+ end
166
+
167
+ # @return [Array<Edge>] the edges of {#graph} that have `self` as their
168
+ # {Edge#origin}
169
+ def outgoing_edges
170
+ graph.edges.select { |e| e.origin.shallow_eql?(self) }
171
+ end
172
+
173
+ # @return [Array<Edge>] the edges of {#graph} that have `self` as their
174
+ # {Edge#destination}
175
+ def incoming_edges
176
+ graph.edges.select { |e| e.destination.shallow_eql?(self) }
177
+ end
178
+
179
+ # @return [Set<Vertex>] the vertices of {#graph} that have an edge with
180
+ # `self` as their {Edge#destination}
181
+ def predecessors
182
+ incoming_edges.map(&:origin).to_set
183
+ end
184
+
185
+ # @return [Set<Vertex>] the vertices of {#graph} that have an edge with
186
+ # `self` as their {Edge#origin}
187
+ def successors
188
+ outgoing_edges.map(&:destination).to_set
189
+ end
190
+
191
+ # @return [Set<Vertex>] the vertices of {#graph} where `self` is an
192
+ # {#ancestor?}
193
+ def recursive_successors
194
+ successors + successors.map(&:recursive_successors).reduce(Set.new, &:+)
195
+ end
196
+
197
+ # @return [String] a string suitable for debugging
198
+ def inspect
199
+ "#{self.class}:#{name}(#{payload.inspect})"
200
+ end
201
+
202
+ # @return [Boolean] whether the two vertices are equal, determined
203
+ # by a recursive traversal of each {Vertex#successors}
204
+ def ==(other)
205
+ shallow_eql?(other) &&
206
+ successors == other.successors
207
+ end
208
+
209
+ # @return [Boolean] whether the two vertices are equal, determined
210
+ # solely by {#name} and {#payload} equality
211
+ def shallow_eql?(other)
212
+ other &&
213
+ name == other.name &&
214
+ payload == other.payload
215
+ end
216
+
217
+ alias_method :eql?, :==
218
+
219
+ # @return [Fixnum] a hash for the vertex based upon its {#name}
220
+ def hash
221
+ name.hash
222
+ end
223
+
224
+ # Is there a path from `self` to `other` following edges in the
225
+ # dependency graph?
226
+ # @return true iff there is a path following edges within this {#graph}
227
+ def path_to?(other)
228
+ successors.include?(other) || successors.any? { |v| v.path_to?(other) }
229
+ end
230
+
231
+ alias_method :descendent?, :path_to?
232
+
233
+ # Is there a path from `other` to `self` following edges in the
234
+ # dependency graph?
235
+ # @return true iff there is a path following edges within this {#graph}
236
+ def ancestor?(other)
237
+ predecessors.include?(other) || predecessors.any? { |v| v.ancestor?(other) }
238
+ end
239
+
240
+ alias_method :is_reachable_from?, :ancestor?
241
+ end
242
+ end
243
+ end
@@ -0,0 +1,69 @@
1
+ module Molinillo
2
+ # An error that occurred during the resolution process
3
+ class ResolverError < StandardError; end
4
+
5
+ # An error caused by searching for a dependency that is completely unknown,
6
+ # i.e. has no versions available whatsoever.
7
+ class NoSuchDependencyError < ResolverError
8
+ # @return [Object] the dependency that could not be found
9
+ attr_accessor :dependency
10
+
11
+ # @return [Array<Object>] the specifications that depended upon {#dependency}
12
+ attr_accessor :required_by
13
+
14
+ # @param [Object] dependency @see {#dependency}
15
+ # @param [Array<Object>] required_by @see {#required_by}
16
+ def initialize(dependency, required_by = [])
17
+ @dependency = dependency
18
+ @required_by = required_by
19
+ super()
20
+ end
21
+
22
+ def message
23
+ sources = required_by.map { |r| "`#{r}`" }.join(' and ')
24
+ message = "Unable to find a specification for `#{dependency}`"
25
+ message << " depended upon by #{sources}" unless sources.empty?
26
+ message
27
+ end
28
+ end
29
+
30
+ # An error caused by attempting to fulfil a dependency that was circular
31
+ #
32
+ # @note This exception will be thrown iff a {Vertex} is added to a
33
+ # {DependencyGraph} that has a {DependencyGraph::Vertex#path_to?} an
34
+ # existing {DependencyGraph::Vertex}
35
+ class CircularDependencyError < ResolverError
36
+ # [Set<Object>] the dependencies responsible for causing the error
37
+ attr_reader :dependencies
38
+
39
+ # @param [Array<DependencyGraph::Vertex>] nodes the nodes in the dependency
40
+ # that caused the error
41
+ def initialize(nodes)
42
+ super "There is a circular dependency between #{nodes.map(&:name).join(' and ')}"
43
+ @dependencies = nodes.map(&:payload).to_set
44
+ end
45
+ end
46
+
47
+ # An error caused by conflicts in version
48
+ class VersionConflict < ResolverError
49
+ # @return [{String => Resolution::Conflict}] the conflicts that caused
50
+ # resolution to fail
51
+ attr_reader :conflicts
52
+
53
+ # @param [{String => Resolution::Conflict}] conflicts see {#conflicts}
54
+ def initialize(conflicts)
55
+ pairs = []
56
+ conflicts.values.flatten.map(&:requirements).flatten.each do |conflicting|
57
+ conflicting.each do |source, conflict_requirements|
58
+ conflict_requirements.each do |c|
59
+ pairs << [c, source]
60
+ end
61
+ end
62
+ end
63
+
64
+ super "Unable to satisfy the following requirements:\n\n" \
65
+ "#{pairs.map { |r, d| "- `#{r}` required by `#{d}`" }.join("\n")}"
66
+ @conflicts = conflicts
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,3 @@
1
+ module Molinillo
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,90 @@
1
+ module Molinillo
2
+ # Provides information about specifcations and dependencies to the resolver,
3
+ # allowing the {Resolver} class to remain generic while still providing power
4
+ # and flexibility.
5
+ #
6
+ # This module contains the methods that users of Molinillo must to implement,
7
+ # using knowledge of their own model classes.
8
+ module SpecificationProvider
9
+ # Search for the specifications that match the given dependency.
10
+ # The specifications in the returned array will be considered in reverse
11
+ # order, so the latest version ought to be last.
12
+ # @note This method should be 'pure', i.e. the return value should depend
13
+ # only on the `dependency` parameter.
14
+ #
15
+ # @param [Object] dependency
16
+ # @return [Array<Object>] the specifications that satisfy the given
17
+ # `dependency`.
18
+ def search_for(dependency)
19
+ []
20
+ end
21
+
22
+ # Returns the dependencies of `specification`.
23
+ # @note This method should be 'pure', i.e. the return value should depend
24
+ # only on the `specification` parameter.
25
+ #
26
+ # @param [Object] specification
27
+ # @return [Array<Object>] the dependencies that are required by the given
28
+ # `specification`.
29
+ def dependencies_for(specification)
30
+ []
31
+ end
32
+
33
+ # Determines whether the given `requirement` is satisfied by the given
34
+ # `spec`, in the context of the current `activated` dependency graph.
35
+ #
36
+ # @param [Object] requirement
37
+ # @param [DependencyGraph] activated the current dependency graph in the
38
+ # resolution process.
39
+ # @param [Object] spec
40
+ # @return [Boolean] whether `requirement` is satisfied by `spec` in the
41
+ # context of the current `activated` dependency graph.
42
+ def requirement_satisfied_by?(requirement, activated, spec)
43
+ true
44
+ end
45
+
46
+ # Returns the name for the given `dependency`.
47
+ # @note This method should be 'pure', i.e. the return value should depend
48
+ # only on the `dependency` parameter.
49
+ #
50
+ # @param [Object] dependency
51
+ # @return [String] the name for the given `dependency`.
52
+ def name_for(dependency)
53
+ dependency.to_s
54
+ end
55
+
56
+ # @return [String] the name of the source of explicit dependencies, i.e.
57
+ # those passed to {Resolver#resolve} directly.
58
+ def name_for_explicit_dependency_source
59
+ 'user-specified dependency'
60
+ end
61
+
62
+ # @return [String] the name of the source of 'locked' dependencies, i.e.
63
+ # those passed to {Resolver#resolve} directly as the `base`
64
+ def name_for_locking_dependency_source
65
+ 'Lockfile'
66
+ end
67
+
68
+ # Sort dependencies so that the ones that are easiest to resolve are first.
69
+ # Easiest to resolve is (usually) defined by:
70
+ # 1) Is this dependency already activated?
71
+ # 2) How relaxed are the requirements?
72
+ # 3) Are there any conflicts for this dependency?
73
+ # 4) How many possibilities are there to satisfy this dependency?
74
+ #
75
+ # @param [Array<Object>] dependencies
76
+ # @param [DependencyGraph] activated the current dependency graph in the
77
+ # resolution process.
78
+ # @param [{String => Array<Conflict>}] conflicts
79
+ # @return [Array<Object>] a sorted copy of `dependencies`.
80
+ def sort_dependencies(dependencies, activated, conflicts)
81
+ dependencies.sort_by do |dependency|
82
+ name = name_for(dependency)
83
+ [
84
+ activated.vertex_named(name).payload ? 0 : 1,
85
+ conflicts[name] ? 0 : 1,
86
+ ]
87
+ end
88
+ end
89
+ end
90
+ end