bundler 1.8.9 → 1.9.0.pre
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of bundler might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.travis.yml +9 -7
- data/CHANGELOG.md +8 -33
- data/Rakefile +51 -8
- data/lib/bundler/cli/gem.rb +20 -1
- data/lib/bundler/cli/install.rb +1 -1
- data/lib/bundler/definition.rb +8 -12
- data/lib/bundler/dep_proxy.rb +2 -2
- data/lib/bundler/installer.rb +12 -18
- data/lib/bundler/resolver.rb +168 -383
- data/lib/bundler/rubygems_ext.rb +1 -1
- data/lib/bundler/rubygems_integration.rb +6 -14
- data/lib/bundler/runtime.rb +3 -0
- data/lib/bundler/shared_helpers.rb +12 -7
- data/lib/bundler/source.rb +0 -5
- data/lib/bundler/source/path.rb +2 -1
- data/lib/bundler/source/path/installer.rb +0 -2
- data/lib/bundler/source/rubygems.rb +9 -11
- data/lib/bundler/templates/newgem/Rakefile.tt +0 -1
- data/lib/bundler/templates/newgem/newgem.gemspec.tt +1 -1
- data/lib/bundler/vendor/Molinillo-0.2.1/lib/molinillo.rb +5 -0
- data/lib/bundler/vendor/Molinillo-0.2.1/lib/molinillo/dependency_graph.rb +266 -0
- data/lib/bundler/vendor/Molinillo-0.2.1/lib/molinillo/errors.rb +69 -0
- data/lib/bundler/vendor/Molinillo-0.2.1/lib/molinillo/gem_metadata.rb +3 -0
- data/lib/bundler/vendor/Molinillo-0.2.1/lib/molinillo/modules/specification_provider.rb +90 -0
- data/lib/bundler/vendor/Molinillo-0.2.1/lib/molinillo/modules/ui.rb +63 -0
- data/lib/bundler/vendor/Molinillo-0.2.1/lib/molinillo/resolution.rb +412 -0
- data/lib/bundler/vendor/Molinillo-0.2.1/lib/molinillo/resolver.rb +43 -0
- data/lib/bundler/vendor/Molinillo-0.2.1/lib/molinillo/state.rb +43 -0
- data/lib/bundler/vendor/{thor.rb → thor-0.19.1/lib/thor.rb} +57 -53
- data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/actions.rb +34 -34
- data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/actions/create_file.rb +7 -7
- data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/actions/create_link.rb +2 -2
- data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/actions/directory.rb +11 -11
- data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/actions/empty_directory.rb +2 -2
- data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/actions/file_manipulation.rb +14 -14
- data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/actions/inject_into_file.rb +24 -24
- data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/base.rb +71 -71
- data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/command.rb +8 -8
- data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/core_ext/hash_with_indifferent_access.rb +2 -2
- data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/core_ext/io_binary_read.rb +1 -1
- data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/core_ext/ordered_hash.rb +2 -2
- data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/error.rb +3 -3
- data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/group.rb +27 -27
- data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/invocation.rb +16 -11
- data/lib/bundler/vendor/thor-0.19.1/lib/thor/line_editor.rb +17 -0
- data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/line_editor/basic.rb +2 -2
- data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/line_editor/readline.rb +7 -7
- data/lib/bundler/vendor/thor-0.19.1/lib/thor/parser.rb +4 -0
- data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/parser/argument.rb +7 -7
- data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/parser/arguments.rb +10 -10
- data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/parser/option.rb +14 -10
- data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/parser/options.rb +12 -12
- data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/rake_compat.rb +14 -14
- data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/runner.rb +76 -76
- data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/shell.rb +18 -18
- data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/shell/basic.rb +31 -30
- data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/shell/color.rb +10 -10
- data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/shell/html.rb +28 -28
- data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/util.rb +61 -61
- data/lib/bundler/vendor/thor-0.19.1/lib/thor/version.rb +3 -0
- data/lib/bundler/vendored_molinillo.rb +5 -0
- data/lib/bundler/vendored_thor.rb +3 -6
- data/lib/bundler/version.rb +1 -1
- metadata +44 -35
- data/lib/bundler/vendor/.document +0 -0
- data/lib/bundler/vendor/thor/line_editor.rb +0 -17
- data/lib/bundler/vendor/thor/parser.rb +0 -4
- data/lib/bundler/vendor/thor/version.rb +0 -3
@@ -0,0 +1,69 @@
|
|
1
|
+
module Bundler::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,90 @@
|
|
1
|
+
module Bundler::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 Bundler::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
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Bundler::Molinillo
|
2
|
+
# Conveys information about the resolution process to a user.
|
3
|
+
module UI
|
4
|
+
# The {IO} object that should be used to print output. `STDOUT`, by default.
|
5
|
+
#
|
6
|
+
# @return [IO]
|
7
|
+
def output
|
8
|
+
STDOUT
|
9
|
+
end
|
10
|
+
|
11
|
+
# Called roughly every {#progress_rate}, this method should convey progress
|
12
|
+
# to the user.
|
13
|
+
#
|
14
|
+
# @return [void]
|
15
|
+
def indicate_progress
|
16
|
+
output.print '.' unless debug?
|
17
|
+
end
|
18
|
+
|
19
|
+
# How often progress should be conveyed to the user via
|
20
|
+
# {#indicate_progress}, in seconds. A third of a second, by default.
|
21
|
+
#
|
22
|
+
# @return [Float]
|
23
|
+
def progress_rate
|
24
|
+
0.33
|
25
|
+
end
|
26
|
+
|
27
|
+
# Called before resolution begins.
|
28
|
+
#
|
29
|
+
# @return [void]
|
30
|
+
def before_resolution
|
31
|
+
output.print 'Resolving dependencies...'
|
32
|
+
end
|
33
|
+
|
34
|
+
# Called after resolution ends (either successfully or with an error).
|
35
|
+
# By default, prints a newline.
|
36
|
+
#
|
37
|
+
# @return [void]
|
38
|
+
def after_resolution
|
39
|
+
output.puts
|
40
|
+
end
|
41
|
+
|
42
|
+
# Conveys debug information to the user.
|
43
|
+
#
|
44
|
+
# @param [Integer] depth the current depth of the resolution process.
|
45
|
+
# @return [void]
|
46
|
+
def debug(depth = 0)
|
47
|
+
if debug?
|
48
|
+
debug_info = yield
|
49
|
+
debug_info = debug_info.inspect unless debug_info.is_a?(String)
|
50
|
+
output.puts debug_info.split("\n").map { |s| ' ' * depth + s }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Whether or not debug messages should be printed.
|
55
|
+
# By default, whether or not the `MOLINILLO_DEBUG` environment variable is
|
56
|
+
# set.
|
57
|
+
#
|
58
|
+
# @return [Boolean]
|
59
|
+
def debug?
|
60
|
+
@debug_mode ||= ENV['MOLINILLO_DEBUG']
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,412 @@
|
|
1
|
+
module Bundler::Molinillo
|
2
|
+
class Resolver
|
3
|
+
# A specific resolution from a given {Resolver}
|
4
|
+
class Resolution
|
5
|
+
# A conflict that the resolution process encountered
|
6
|
+
# @attr [Object] requirement the requirement that immediately led to the conflict
|
7
|
+
# @attr [{String,Nil=>[Object]}] requirements the requirements that caused the conflict
|
8
|
+
# @attr [Object, nil] existing the existing spec that was in conflict with
|
9
|
+
# the {#possibility}
|
10
|
+
# @attr [Object] possibility the spec that was unable to be activated due
|
11
|
+
# to a conflict
|
12
|
+
# @attr [Object] locked_requirement the relevant locking requirement.
|
13
|
+
# @attr [Array<Array<Object>>] requirement_trees the different requirement
|
14
|
+
# trees that led to every requirement for the conflicting name.
|
15
|
+
Conflict = Struct.new(
|
16
|
+
:requirement,
|
17
|
+
:requirements,
|
18
|
+
:existing,
|
19
|
+
:possibility,
|
20
|
+
:locked_requirement,
|
21
|
+
:requirement_trees
|
22
|
+
)
|
23
|
+
|
24
|
+
# @return [SpecificationProvider] the provider that knows about
|
25
|
+
# dependencies, requirements, specifications, versions, etc.
|
26
|
+
attr_reader :specification_provider
|
27
|
+
|
28
|
+
# @return [UI] the UI that knows how to communicate feedback about the
|
29
|
+
# resolution process back to the user
|
30
|
+
attr_reader :resolver_ui
|
31
|
+
|
32
|
+
# @return [DependencyGraph] the base dependency graph to which
|
33
|
+
# dependencies should be 'locked'
|
34
|
+
attr_reader :base
|
35
|
+
|
36
|
+
# @return [Array] the dependencies that were explicitly required
|
37
|
+
attr_reader :original_requested
|
38
|
+
|
39
|
+
# @param [SpecificationProvider] specification_provider
|
40
|
+
# see {#specification_provider}
|
41
|
+
# @param [UI] resolver_ui see {#resolver_ui}
|
42
|
+
# @param [Array] requested see {#original_requested}
|
43
|
+
# @param [DependencyGraph] base see {#base}
|
44
|
+
def initialize(specification_provider, resolver_ui, requested, base)
|
45
|
+
@specification_provider = specification_provider
|
46
|
+
@resolver_ui = resolver_ui
|
47
|
+
@original_requested = requested
|
48
|
+
@base = base
|
49
|
+
@states = []
|
50
|
+
@iteration_counter = 0
|
51
|
+
end
|
52
|
+
|
53
|
+
# Resolves the {#original_requested} dependencies into a full dependency
|
54
|
+
# graph
|
55
|
+
# @raise [ResolverError] if successful resolution is impossible
|
56
|
+
# @return [DependencyGraph] the dependency graph of successfully resolved
|
57
|
+
# dependencies
|
58
|
+
def resolve
|
59
|
+
start_resolution
|
60
|
+
|
61
|
+
while state
|
62
|
+
break unless state.requirements.any? || state.requirement
|
63
|
+
indicate_progress
|
64
|
+
if state.respond_to?(:pop_possibility_state) # DependencyState
|
65
|
+
debug(depth) { "Creating possibility state for #{requirement} (#{possibilities.count} remaining)" }
|
66
|
+
state.pop_possibility_state.tap { |s| states.push(s) if s }
|
67
|
+
end
|
68
|
+
process_topmost_state
|
69
|
+
end
|
70
|
+
|
71
|
+
activated.freeze
|
72
|
+
ensure
|
73
|
+
end_resolution
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
# Sets up the resolution process
|
79
|
+
# @return [void]
|
80
|
+
def start_resolution
|
81
|
+
@started_at = Time.now
|
82
|
+
|
83
|
+
states.push(initial_state)
|
84
|
+
|
85
|
+
debug { "Starting resolution (#{@started_at})" }
|
86
|
+
resolver_ui.before_resolution
|
87
|
+
end
|
88
|
+
|
89
|
+
# Ends the resolution process
|
90
|
+
# @return [void]
|
91
|
+
def end_resolution
|
92
|
+
resolver_ui.after_resolution
|
93
|
+
debug do
|
94
|
+
"Finished resolution (#{@iteration_counter} steps) " \
|
95
|
+
"(Took #{(ended_at = Time.now) - @started_at} seconds) (#{ended_at})"
|
96
|
+
end
|
97
|
+
debug { 'Unactivated: ' + Hash[activated.vertices.reject { |_n, v| v.payload }].keys.join(', ') } if state
|
98
|
+
debug { 'Activated: ' + Hash[activated.vertices.select { |_n, v| v.payload }].keys.join(', ') } if state
|
99
|
+
end
|
100
|
+
|
101
|
+
require 'molinillo/state'
|
102
|
+
require 'molinillo/modules/specification_provider'
|
103
|
+
|
104
|
+
# @return [Integer] the number of resolver iterations in between calls to
|
105
|
+
# {#resolver_ui}'s {UI#indicate_progress} method
|
106
|
+
attr_accessor :iteration_rate
|
107
|
+
|
108
|
+
# @return [Time] the time at which resolution began
|
109
|
+
attr_accessor :started_at
|
110
|
+
|
111
|
+
# @return [Array<ResolutionState>] the stack of states for the resolution
|
112
|
+
attr_accessor :states
|
113
|
+
|
114
|
+
ResolutionState.new.members.each do |member|
|
115
|
+
define_method member do |*args, &block|
|
116
|
+
state.send(member, *args, &block)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
SpecificationProvider.instance_methods(false).each do |instance_method|
|
121
|
+
define_method instance_method do |*args, &block|
|
122
|
+
begin
|
123
|
+
specification_provider.send(instance_method, *args, &block)
|
124
|
+
rescue NoSuchDependencyError => error
|
125
|
+
if state
|
126
|
+
vertex = activated.vertex_named(name_for error.dependency)
|
127
|
+
error.required_by += vertex.incoming_edges.map { |e| e.origin.name }
|
128
|
+
error.required_by << name_for_explicit_dependency_source unless vertex.explicit_requirements.empty?
|
129
|
+
end
|
130
|
+
raise
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Processes the topmost available {RequirementState} on the stack
|
136
|
+
# @return [void]
|
137
|
+
def process_topmost_state
|
138
|
+
if possibility
|
139
|
+
attempt_to_activate
|
140
|
+
else
|
141
|
+
create_conflict if state.is_a? PossibilityState
|
142
|
+
unwind_for_conflict until possibility && state.is_a?(DependencyState)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# @return [Object] the current possibility that the resolution is trying
|
147
|
+
# to activate
|
148
|
+
def possibility
|
149
|
+
possibilities.last
|
150
|
+
end
|
151
|
+
|
152
|
+
# @return [RequirementState] the current state the resolution is
|
153
|
+
# operating upon
|
154
|
+
def state
|
155
|
+
states.last
|
156
|
+
end
|
157
|
+
|
158
|
+
# Creates the initial state for the resolution, based upon the
|
159
|
+
# {#requested} dependencies
|
160
|
+
# @return [DependencyState] the initial state for the resolution
|
161
|
+
def initial_state
|
162
|
+
graph = DependencyGraph.new.tap do |dg|
|
163
|
+
original_requested.each { |r| dg.add_root_vertex(name_for(r), nil).tap { |v| v.explicit_requirements << r } }
|
164
|
+
end
|
165
|
+
|
166
|
+
requirements = sort_dependencies(original_requested, graph, {})
|
167
|
+
initial_requirement = requirements.shift
|
168
|
+
DependencyState.new(
|
169
|
+
initial_requirement && name_for(initial_requirement),
|
170
|
+
requirements,
|
171
|
+
graph,
|
172
|
+
initial_requirement,
|
173
|
+
initial_requirement && search_for(initial_requirement),
|
174
|
+
0,
|
175
|
+
{}
|
176
|
+
)
|
177
|
+
end
|
178
|
+
|
179
|
+
# Unwinds the states stack because a conflict has been encountered
|
180
|
+
# @return [void]
|
181
|
+
def unwind_for_conflict
|
182
|
+
debug(depth) { "Unwinding for conflict: #{requirement}" }
|
183
|
+
conflicts.tap do |c|
|
184
|
+
states.slice!((state_index_for_unwind + 1)..-1)
|
185
|
+
raise VersionConflict.new(c) unless state
|
186
|
+
state.conflicts = c
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# @return [Integer] The index to which the resolution should unwind in the
|
191
|
+
# case of conflict.
|
192
|
+
def state_index_for_unwind
|
193
|
+
current_requirement = requirement
|
194
|
+
existing_requirement = requirement_for_existing_name(name)
|
195
|
+
until current_requirement.nil?
|
196
|
+
current_state = find_state_for(current_requirement)
|
197
|
+
return states.index(current_state) if state_any?(current_state)
|
198
|
+
current_requirement = parent_of(current_requirement)
|
199
|
+
end
|
200
|
+
|
201
|
+
until existing_requirement.nil?
|
202
|
+
existing_state = find_state_for(existing_requirement)
|
203
|
+
return states.index(existing_state) if state_any?(existing_state)
|
204
|
+
existing_requirement = parent_of(existing_requirement)
|
205
|
+
end
|
206
|
+
-1
|
207
|
+
end
|
208
|
+
|
209
|
+
# @return [Object] the requirement that led to `requirement` being added
|
210
|
+
# to the list of requirements.
|
211
|
+
def parent_of(requirement)
|
212
|
+
return nil unless requirement
|
213
|
+
seen = false
|
214
|
+
state = states.reverse_each.find do |s|
|
215
|
+
seen ||= s.requirement == requirement
|
216
|
+
seen && s.requirement != requirement && !s.requirements.include?(requirement)
|
217
|
+
end
|
218
|
+
state && state.requirement
|
219
|
+
end
|
220
|
+
|
221
|
+
# @return [Object] the requirement that led to a version of a possibility
|
222
|
+
# with the given name being activated.
|
223
|
+
def requirement_for_existing_name(name)
|
224
|
+
return nil unless activated.vertex_named(name).payload
|
225
|
+
states.reverse_each.find { |s| !s.activated.vertex_named(name).payload }.requirement
|
226
|
+
end
|
227
|
+
|
228
|
+
# @return [ResolutionState] the state whose `requirement` is the given
|
229
|
+
# `requirement`.
|
230
|
+
def find_state_for(requirement)
|
231
|
+
return nil unless requirement
|
232
|
+
states.reverse_each.find { |i| requirement == i.requirement && i.is_a?(DependencyState) }
|
233
|
+
end
|
234
|
+
|
235
|
+
# @return [Boolean] whether or not the given state has any possibilities
|
236
|
+
# left.
|
237
|
+
def state_any?(state)
|
238
|
+
state && state.possibilities.any?
|
239
|
+
end
|
240
|
+
|
241
|
+
# @return [Conflict] a {Conflict} that reflects the failure to activate
|
242
|
+
# the {#possibility} in conjunction with the current {#state}
|
243
|
+
def create_conflict
|
244
|
+
vertex = activated.vertex_named(name)
|
245
|
+
requirements = {
|
246
|
+
name_for_explicit_dependency_source => vertex.explicit_requirements,
|
247
|
+
name_for_locking_dependency_source => Array(locked_requirement_named(name)),
|
248
|
+
}
|
249
|
+
vertex.incoming_edges.each { |edge| (requirements[edge.origin.payload] ||= []).unshift(*edge.requirements) }
|
250
|
+
conflicts[name] = Conflict.new(
|
251
|
+
requirement,
|
252
|
+
Hash[requirements.select { |_, r| !r.empty? }],
|
253
|
+
vertex.payload,
|
254
|
+
possibility,
|
255
|
+
locked_requirement_named(name),
|
256
|
+
requirement_trees
|
257
|
+
)
|
258
|
+
end
|
259
|
+
|
260
|
+
# @return [Array<Array<Object>>] The different requirement
|
261
|
+
# trees that led to every requirement for the current spec.
|
262
|
+
def requirement_trees
|
263
|
+
activated.vertex_named(name).requirements.map { |r| requirement_tree_for(r) }
|
264
|
+
end
|
265
|
+
|
266
|
+
# @return [Array<Object>] the list of requirements that led to
|
267
|
+
# `requirement` being required.
|
268
|
+
def requirement_tree_for(requirement)
|
269
|
+
tree = []
|
270
|
+
while requirement
|
271
|
+
tree.unshift(requirement)
|
272
|
+
requirement = parent_of(requirement)
|
273
|
+
end
|
274
|
+
tree
|
275
|
+
end
|
276
|
+
|
277
|
+
# Indicates progress roughly once every second
|
278
|
+
# @return [void]
|
279
|
+
def indicate_progress
|
280
|
+
@iteration_counter += 1
|
281
|
+
@progress_rate ||= resolver_ui.progress_rate
|
282
|
+
if iteration_rate.nil?
|
283
|
+
if Time.now - started_at >= @progress_rate
|
284
|
+
self.iteration_rate = @iteration_counter
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
if iteration_rate && (@iteration_counter % iteration_rate) == 0
|
289
|
+
resolver_ui.indicate_progress
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
# Calls the {#resolver_ui}'s {UI#debug} method
|
294
|
+
# @param [Integer] depth the depth of the {#states} stack
|
295
|
+
# @param [Proc] block a block that yields a {#to_s}
|
296
|
+
# @return [void]
|
297
|
+
def debug(depth = 0, &block)
|
298
|
+
resolver_ui.debug(depth, &block)
|
299
|
+
end
|
300
|
+
|
301
|
+
# Attempts to activate the current {#possibility}
|
302
|
+
# @return [void]
|
303
|
+
def attempt_to_activate
|
304
|
+
debug(depth) { 'Attempting to activate ' + possibility.to_s }
|
305
|
+
existing_node = activated.vertex_named(name)
|
306
|
+
if existing_node.payload
|
307
|
+
debug(depth) { "Found existing spec (#{existing_node.payload})" }
|
308
|
+
attempt_to_activate_existing_spec(existing_node)
|
309
|
+
else
|
310
|
+
attempt_to_activate_new_spec
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
# Attempts to activate the current {#possibility} (given that it has
|
315
|
+
# already been activated)
|
316
|
+
# @return [void]
|
317
|
+
def attempt_to_activate_existing_spec(existing_node)
|
318
|
+
existing_spec = existing_node.payload
|
319
|
+
if requirement_satisfied_by?(requirement, activated, existing_spec)
|
320
|
+
new_requirements = requirements.dup
|
321
|
+
push_state_for_requirements(new_requirements)
|
322
|
+
else
|
323
|
+
return if attempt_to_swap_possibility
|
324
|
+
create_conflict
|
325
|
+
debug(depth) { "Unsatisfied by existing spec (#{existing_node.payload})" }
|
326
|
+
unwind_for_conflict
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
# Attempts to swp the current {#possibility} with the already-activated
|
331
|
+
# spec with the given name
|
332
|
+
# @return [Boolean] Whether the possibility was swapped into {#activated}
|
333
|
+
def attempt_to_swap_possibility
|
334
|
+
swapped = activated.dup
|
335
|
+
swapped.vertex_named(name).payload = possibility
|
336
|
+
return unless swapped.vertex_named(name).requirements.
|
337
|
+
all? { |r| requirement_satisfied_by?(r, swapped, possibility) }
|
338
|
+
attempt_to_activate_new_spec
|
339
|
+
end
|
340
|
+
|
341
|
+
# Attempts to activate the current {#possibility} (given that it hasn't
|
342
|
+
# already been activated)
|
343
|
+
# @return [void]
|
344
|
+
def attempt_to_activate_new_spec
|
345
|
+
satisfied = begin
|
346
|
+
locked_requirement = locked_requirement_named(name)
|
347
|
+
requested_spec_satisfied = requirement_satisfied_by?(requirement, activated, possibility)
|
348
|
+
locked_spec_satisfied = !locked_requirement ||
|
349
|
+
requirement_satisfied_by?(locked_requirement, activated, possibility)
|
350
|
+
debug(depth) { 'Unsatisfied by requested spec' } unless requested_spec_satisfied
|
351
|
+
debug(depth) { 'Unsatisfied by locked spec' } unless locked_spec_satisfied
|
352
|
+
requested_spec_satisfied && locked_spec_satisfied
|
353
|
+
end
|
354
|
+
if satisfied
|
355
|
+
activate_spec
|
356
|
+
else
|
357
|
+
create_conflict
|
358
|
+
unwind_for_conflict
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
# @param [String] requirement_name the spec name to search for
|
363
|
+
# @return [Object] the locked spec named `requirement_name`, if one
|
364
|
+
# is found on {#base}
|
365
|
+
def locked_requirement_named(requirement_name)
|
366
|
+
vertex = base.vertex_named(requirement_name)
|
367
|
+
vertex && vertex.payload
|
368
|
+
end
|
369
|
+
|
370
|
+
# Add the current {#possibility} to the dependency graph of the current
|
371
|
+
# {#state}
|
372
|
+
# @return [void]
|
373
|
+
def activate_spec
|
374
|
+
conflicts.delete(name)
|
375
|
+
debug(depth) { 'Activated ' + name + ' at ' + possibility.to_s }
|
376
|
+
vertex = activated.vertex_named(name)
|
377
|
+
vertex.payload = possibility
|
378
|
+
require_nested_dependencies_for(possibility)
|
379
|
+
end
|
380
|
+
|
381
|
+
# Requires the dependencies that the recently activated spec has
|
382
|
+
# @param [Object] activated_spec the specification that has just been
|
383
|
+
# activated
|
384
|
+
# @return [void]
|
385
|
+
def require_nested_dependencies_for(activated_spec)
|
386
|
+
nested_dependencies = dependencies_for(activated_spec)
|
387
|
+
debug(depth) { "Requiring nested dependencies (#{nested_dependencies.map(&:to_s).join(', ')})" }
|
388
|
+
nested_dependencies.each { |d| activated.add_child_vertex name_for(d), nil, [name_for(activated_spec)], d }
|
389
|
+
|
390
|
+
push_state_for_requirements(requirements + nested_dependencies)
|
391
|
+
end
|
392
|
+
|
393
|
+
# Pushes a new {DependencyState} that encapsulates both existing and new
|
394
|
+
# requirements
|
395
|
+
# @param [Array] new_requirements
|
396
|
+
# @return [void]
|
397
|
+
def push_state_for_requirements(new_requirements)
|
398
|
+
new_requirements = sort_dependencies(new_requirements.uniq, activated, conflicts)
|
399
|
+
new_requirement = new_requirements.shift
|
400
|
+
states.push DependencyState.new(
|
401
|
+
new_requirement ? name_for(new_requirement) : '',
|
402
|
+
new_requirements,
|
403
|
+
activated.dup,
|
404
|
+
new_requirement,
|
405
|
+
new_requirement ? search_for(new_requirement) : [],
|
406
|
+
depth,
|
407
|
+
conflicts.dup
|
408
|
+
)
|
409
|
+
end
|
410
|
+
end
|
411
|
+
end
|
412
|
+
end
|