librarian 0.0.25 → 0.0.26

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/.gitignore +4 -0
  2. data/CHANGELOG.md +21 -0
  3. data/README.md +6 -1
  4. data/lib/librarian/action/persist_resolution_mixin.rb +51 -0
  5. data/lib/librarian/action/resolve.rb +3 -38
  6. data/lib/librarian/action/update.rb +4 -38
  7. data/lib/librarian/chef/dsl.rb +1 -0
  8. data/lib/librarian/chef/source.rb +1 -0
  9. data/lib/librarian/chef/source/github.rb +27 -0
  10. data/lib/librarian/chef/source/site.rb +51 -51
  11. data/lib/librarian/cli.rb +31 -23
  12. data/lib/librarian/cli/manifest_presenter.rb +36 -22
  13. data/lib/librarian/dependency.rb +60 -0
  14. data/lib/librarian/environment.rb +13 -1
  15. data/lib/librarian/linter/source_linter.rb +55 -0
  16. data/lib/librarian/lockfile/parser.rb +39 -16
  17. data/lib/librarian/manifest.rb +8 -0
  18. data/lib/librarian/manifest_set.rb +5 -7
  19. data/lib/librarian/mock/source/mock.rb +4 -21
  20. data/lib/librarian/resolution.rb +1 -1
  21. data/lib/librarian/resolver.rb +15 -12
  22. data/lib/librarian/resolver/implementation.rb +166 -75
  23. data/lib/librarian/source/basic_api.rb +45 -0
  24. data/lib/librarian/source/git.rb +4 -22
  25. data/lib/librarian/source/git/repository.rb +1 -1
  26. data/lib/librarian/source/local.rb +0 -7
  27. data/lib/librarian/source/path.rb +4 -22
  28. data/lib/librarian/version.rb +1 -1
  29. data/librarian.gemspec +3 -3
  30. data/spec/functional/chef/source/site_spec.rb +150 -100
  31. data/spec/functional/source/git/repository_spec.rb +2 -1
  32. data/spec/{functional → integration}/chef/source/git_spec.rb +12 -3
  33. data/spec/integration/chef/source/site_spec.rb +217 -0
  34. data/spec/support/cli_macro.rb +4 -12
  35. data/spec/support/method_patch_macro.rb +30 -0
  36. data/spec/unit/config/database_spec.rb +8 -0
  37. data/spec/unit/dependency_spec.rb +176 -0
  38. data/spec/unit/environment_spec.rb +76 -7
  39. data/spec/unit/resolver_spec.rb +2 -2
  40. metadata +52 -46
@@ -1,32 +1,15 @@
1
1
  require 'librarian/manifest'
2
+ require 'librarian/source/basic_api'
2
3
  require 'librarian/mock/source/mock/registry'
3
4
 
4
5
  module Librarian
5
6
  module Mock
6
7
  module Source
7
8
  class Mock
9
+ include Librarian::Source::BasicApi
8
10
 
9
- class << self
10
-
11
- LOCK_NAME = 'MOCK'
12
-
13
- def lock_name
14
- LOCK_NAME
15
- end
16
-
17
- def from_lock_options(environment, options)
18
- new(environment, options[:remote], options.reject{|k, v| k == :remote})
19
- end
20
-
21
- def from_spec_args(environment, name, options)
22
- recognized_options = []
23
- unrecognized_options = options.keys - recognized_options
24
- unrecognized_options.empty? or raise Error, "unrecognized options: #{unrecognized_options.join(", ")}"
25
-
26
- new(environment, name, options)
27
- end
28
-
29
- end
11
+ lock_name 'MOCK'
12
+ spec_options []
30
13
 
31
14
  attr_accessor :environment
32
15
  private :environment=
@@ -25,7 +25,7 @@ module Librarian
25
25
  end
26
26
 
27
27
  def sources
28
- manifests.map{|m| m.source}.uniq
28
+ manifests.map(&:source).uniq
29
29
  end
30
30
 
31
31
  private
@@ -13,19 +13,24 @@ module Librarian
13
13
  end
14
14
 
15
15
  def resolve(spec, partial_manifests = [])
16
- implementation = Implementation.new(self, spec)
17
- partial_manifests_index = Hash[partial_manifests.map{|m| [m.name, m]}]
18
- manifests = implementation.resolve(spec.dependencies, partial_manifests_index)
19
- enforce_consistency!(spec.dependencies, manifests) if manifests
20
- manifests = sort(manifests) if manifests
21
- Resolution.new(spec.dependencies, manifests)
16
+ manifests = implementation(spec).resolve(partial_manifests)
17
+ if manifests
18
+ enforce_consistency!(spec.dependencies, manifests)
19
+ manifests = sort(manifests)
20
+ Resolution.new(spec.dependencies, manifests)
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def implementation(spec)
27
+ Implementation.new(self, spec)
22
28
  end
23
29
 
24
30
  def enforce_consistency!(dependencies, manifests)
25
- return if dependencies.all?{|d|
26
- m = manifests[d.name]
27
- m && d.satisfied_by?(m)
28
- } && ManifestSet.new(manifests).consistent?
31
+ manifest_set = ManifestSet.new(manifests)
32
+ return if manifest_set.in_compliance_with?(dependencies)
33
+ return if manifest_set.consistent?
29
34
 
30
35
  debug { "Resolver Malfunctioned!" }
31
36
  errors = []
@@ -68,8 +73,6 @@ module Librarian
68
73
  ManifestSet.sort(manifests)
69
74
  end
70
75
 
71
- private
72
-
73
76
  def debug(*args, &block)
74
77
  environment.logger.debug(*args, &block)
75
78
  end
@@ -10,29 +10,114 @@ module Librarian
10
10
  self.sources = sources
11
11
  end
12
12
  def manifests(name)
13
- sources.reverse.map{|source| source.manifests(name)}.flatten(1)
13
+ sources.reverse.map{|source| source.manifests(name)}.flatten(1).compact
14
+ end
15
+ def to_s
16
+ "(no source specified)"
14
17
  end
15
18
  end
16
19
 
17
- attr_reader :resolver, :spec, :dependency_source_map
20
+ attr_accessor :resolver, :spec
21
+ private :resolver=, :spec=
18
22
 
19
23
  def initialize(resolver, spec)
20
- @resolver = resolver
21
- @spec = spec
22
- @dependency_source_map = Hash[spec.dependencies.map{|d| [d.name, d.source]}]
24
+ self.resolver = resolver
25
+ self.spec = spec
23
26
  @level = 0
24
27
  end
25
28
 
26
- def resolve(dependencies, manifests = {})
27
- dependencies += manifests.values.map { |m|
28
- m.dependencies.map { |d| sourced_dependency_for(d) }
29
- }.flatten(1)
30
- resolution = recursive_resolve([], manifests, dependencies)
31
- resolution ? resolution[1] : nil
29
+ def resolve(manifests)
30
+ manifests = index_by(manifests, &:name) if manifests.kind_of?(Array)
31
+ addtl = spec.dependencies + sourced_dependencies_for_manifests(manifests)
32
+ recursive_resolve([], manifests, [], addtl)
33
+ end
34
+
35
+ private
36
+
37
+ def find_inconsistency(dep, deps, mans)
38
+ m = mans[dep.name]
39
+ dep.satisfied_by?(m) or return m if m
40
+ deps.find{|d| !dep.consistent_with?(d)}
41
+ end
42
+
43
+ def recursive_resolve(dependencies, manifests, queue, addtl)
44
+ dependencies = dependencies.dup
45
+ manifests = manifests.dup
46
+ queue = queue.dup
47
+
48
+ return unless enqueue_dependencies(queue, addtl, dependencies, manifests)
49
+ return unless shift_resolved_enqueued_dependencies(dependencies, manifests, queue)
50
+ return manifests if queue.empty?
51
+
52
+ dependency = queue.shift
53
+ dependencies << dependency
54
+ all_deps = dependencies + queue
55
+
56
+ resolving_dependency_map_find_manifests(dependency) do |manifest|
57
+ next unless check_manifest(manifest, all_deps)
58
+
59
+ m = manifests.merge(dependency.name => manifest)
60
+ a = sourced_dependencies_for_manifest(manifest)
61
+
62
+ recursive_resolve(dependencies, m, queue, a)
63
+ end
64
+ end
65
+
66
+ # When using this method, you are required to check the return value.
67
+ # Returns +true+ if the enqueueables could all be enqueued.
68
+ # Returns +false+ if there was an inconsistency when trying to enqueue one
69
+ # or more of them.
70
+ # This modifies +queue+ but does not modify any other arguments.
71
+ def enqueue_dependencies(queue, enqueueables, dependencies, manifests)
72
+ enqueueables.each do |d|
73
+ if q = find_inconsistency(d, dependencies + queue, manifests)
74
+ debug_conflict d, q
75
+ return false
76
+ end
77
+ debug_schedule d
78
+ queue << d
79
+ end
80
+ true
81
+ end
82
+
83
+ # When using this method, you are required to check the return value.
84
+ # Returns +true+ if the resolved enqueued dependencies at the front of the
85
+ # queue could all be moved to the resolved dependencies list.
86
+ # Returns +false+ if there was an inconsistency when trying to move one or
87
+ # more of them.
88
+ # This modifies +queue+ and +dependencies+.
89
+ def shift_resolved_enqueued_dependencies(dependencies, manifests, queue)
90
+ all_deps = dependencies + queue
91
+ while (dependency = queue.first) && manifests[dependency.name]
92
+ if q = find_inconsistency(dependency, all_deps, manifests)
93
+ debug_conflict dependency, q
94
+ return false
95
+ end
96
+ dependencies << queue.shift
97
+ end
98
+ true
99
+ end
100
+
101
+ # When using this method, you are required to check the return value.
102
+ # Returns +true+ if the manifest satisfies all of the dependencies.
103
+ # Returns +false+ if there was a dependency that the manifest does not
104
+ # satisfy.
105
+ def check_manifest(manifest, all_deps)
106
+ related = all_deps.select{|d| d.name == manifest.name}
107
+ if q = related.find{|d| !d.satisfied_by?(manifest)}
108
+ debug_conflict manifest, q
109
+ return false
110
+ end
111
+ true
32
112
  end
33
113
 
34
114
  def default_source
35
- MultiSource.new(spec.sources)
115
+ @default_source ||= MultiSource.new(spec.sources)
116
+ end
117
+
118
+ def dependency_source_map
119
+ @dependency_source_map ||=
120
+ Hash[spec.dependencies.map{|d| [d.name, d.source]}]
36
121
  end
37
122
 
38
123
  def sourced_dependency_for(dependency)
@@ -42,75 +127,81 @@ module Librarian
42
127
  Dependency.new(dependency.name, dependency.requirement, source)
43
128
  end
44
129
 
45
- def recursive_resolve(dependencies, manifests, queue)
46
- if dependencies.empty?
47
- queue.each do |dependency|
48
- debug { "Scheduling #{dependency}" }
130
+ def sourced_dependencies_for_manifest(manifest)
131
+ manifest.dependencies.map{|d| sourced_dependency_for(d)}
132
+ end
133
+
134
+ def sourced_dependencies_for_manifests(manifests)
135
+ manifests = manifests.values if manifests.kind_of?(Hash)
136
+ manifests.map{|m| sourced_dependencies_for_manifest(m)}.flatten(1)
137
+ end
138
+
139
+ def resolving_dependency_map_find_manifests(dependency)
140
+ scope_resolving_dependency dependency do
141
+ map_find(dependency.manifests) do |manifest|
142
+ scope_checking_manifest dependency, manifest do
143
+ yield manifest
144
+ end
49
145
  end
50
146
  end
51
- failure = false
52
- until failure || queue.empty?
53
- dependency = queue.shift
54
- dependencies << dependency
55
- debug { "Resolving #{dependency}" }
56
- scope do
57
- if manifests.key?(dependency.name)
58
- unless dependency.satisfied_by?(manifests[dependency.name])
59
- debug { "Conflicts with #{manifests[dependency.name]}" }
60
- failure = true
61
- else
62
- debug { "Accords with all prior constraints" }
63
- # nothing left to do
64
- end
65
- else
66
- debug { "No known prior constraints" }
67
- resolution = nil
68
- related_dependencies = dependencies.select{|d| d.name == dependency.name}
69
- unless dependency.manifests && dependency.manifests.first
70
- debug { "No known manifests" }
71
- else
72
- debug { "Checking manifests" }
73
- scope do
74
- dependency.manifests.each do |manifest|
75
- break if resolution
76
-
77
- debug { "Checking #{manifest}" }
78
- scope do
79
- if related_dependencies.all?{|d| d.satisfied_by?(manifest)}
80
- m = manifests.merge(dependency.name => manifest)
81
- a = manifest.dependencies.map { |d| sourced_dependency_for(d) }
82
- a.each do |d|
83
- debug { "Scheduling #{d}" }
84
- end
85
- q = queue + a
86
- resolution = recursive_resolve(dependencies.dup, m, q)
87
- end
88
- if resolution
89
- debug { "Resolved #{dependency} at #{manifest}" }
90
- else
91
- debug { "Backtracking from #{manifest}" }
92
- end
93
- end
94
- end
95
- end
96
- if resolution
97
- debug { "Resolved #{dependency}" }
98
- else
99
- debug { "Failed to resolve #{dependency}" }
100
- end
101
- end
102
- unless resolution
103
- failure = true
104
- else
105
- dependencies, manifests, queue = *resolution
106
- end
107
- end
147
+ end
148
+
149
+ def scope_resolving_dependency(dependency)
150
+ debug { "Resolving #{dependency}" }
151
+ resolution = nil
152
+ scope do
153
+ scope_checking_manifests do
154
+ resolution = yield
155
+ end
156
+ if resolution
157
+ debug { "Resolved #{dependency}" }
158
+ else
159
+ debug { "Failed to resolve #{dependency}" }
108
160
  end
109
161
  end
110
- failure ? nil : [dependencies, manifests, queue]
162
+ resolution
111
163
  end
112
164
 
113
- private
165
+ def scope_checking_manifests
166
+ debug { "Checking manifests" }
167
+ scope do
168
+ yield
169
+ end
170
+ end
171
+
172
+ def scope_checking_manifest(dependency, manifest)
173
+ debug { "Checking #{manifest}" }
174
+ resolution = nil
175
+ scope do
176
+ resolution = yield
177
+ if resolution
178
+ debug { "Resolved #{dependency} at #{manifest}" }
179
+ else
180
+ debug { "Backtracking from #{manifest}" }
181
+ end
182
+ end
183
+ resolution
184
+ end
185
+
186
+ def debug_schedule(dependency)
187
+ debug { "Scheduling #{dependency}" }
188
+ end
189
+
190
+ def debug_conflict(dependency, conflict)
191
+ debug { "Conflict between #{dependency} and #{conflict}" }
192
+ end
193
+
194
+ def map_find(enum)
195
+ enum.each do |obj|
196
+ res = yield(obj)
197
+ res.nil? or return res
198
+ end
199
+ nil
200
+ end
201
+
202
+ def index_by(enum)
203
+ Hash[enum.map{|obj| [yield(obj), obj]}]
204
+ end
114
205
 
115
206
  def scope
116
207
  @level += 1
@@ -0,0 +1,45 @@
1
+ module Librarian
2
+ module Source
3
+ module BasicApi
4
+
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+ class << base
8
+ def lock_name(name)
9
+ def_sclass_prop(:lock_name, name)
10
+ end
11
+
12
+ def spec_options(keys)
13
+ def_sclass_prop(:spec_options, keys)
14
+ end
15
+
16
+ private
17
+
18
+ def def_sclass_prop(name, arg)
19
+ sclass = class << self ; self ; end
20
+ sclass.module_exec do
21
+ remove_method(name)
22
+ define_method(name) { arg }
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ module ClassMethods
29
+ def from_lock_options(environment, options)
30
+ new(environment, options[:remote], options.reject{|k, v| k == :remote})
31
+ end
32
+
33
+ def from_spec_args(environment, param, options)
34
+ recognized_options = spec_options
35
+ unrecognized_options = options.keys - recognized_options
36
+ unrecognized_options.empty? or raise Error,
37
+ "unrecognized options: #{unrecognized_options.join(", ")}"
38
+
39
+ new(environment, param, options)
40
+ end
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -2,36 +2,18 @@ require 'fileutils'
2
2
  require 'pathname'
3
3
  require 'digest'
4
4
 
5
+ require 'librarian/source/basic_api'
5
6
  require 'librarian/source/git/repository'
6
7
  require 'librarian/source/local'
7
8
 
8
9
  module Librarian
9
10
  module Source
10
11
  class Git
11
-
12
+ include BasicApi
12
13
  include Local
13
14
 
14
- class << self
15
-
16
- LOCK_NAME = 'GIT'
17
-
18
- def lock_name
19
- LOCK_NAME
20
- end
21
-
22
- def from_lock_options(environment, options)
23
- new(environment, options[:remote], options.reject{|k, v| k == :remote})
24
- end
25
-
26
- def from_spec_args(environment, uri, options)
27
- recognized_options = [:ref, :path]
28
- unrecognized_options = options.keys - recognized_options
29
- unrecognized_options.empty? or raise Error, "unrecognized options: #{unrecognized_options.join(", ")}"
30
-
31
- new(environment, uri, options)
32
- end
33
-
34
- end
15
+ lock_name 'GIT'
16
+ spec_options [:ref, :path]
35
17
 
36
18
  DEFAULTS = {
37
19
  :ref => 'master'