librarian 0.0.25 → 0.0.26

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.
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'