librarian 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/.gitignore +5 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +4 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +46 -0
  6. data/Rakefile +13 -0
  7. data/bin/librarian-chef +7 -0
  8. data/bin/librarian-mock +7 -0
  9. data/config/cucumber.yaml +1 -0
  10. data/features/chef/cli/install.feature +69 -0
  11. data/features/support/env.rb +5 -0
  12. data/lib/librarian.rb +191 -0
  13. data/lib/librarian/chef.rb +1 -0
  14. data/lib/librarian/chef/cli.rb +14 -0
  15. data/lib/librarian/chef/dsl.rb +14 -0
  16. data/lib/librarian/chef/extension.rb +24 -0
  17. data/lib/librarian/chef/manifest.rb +43 -0
  18. data/lib/librarian/chef/particularity.rb +9 -0
  19. data/lib/librarian/chef/source.rb +3 -0
  20. data/lib/librarian/chef/source/git.rb +14 -0
  21. data/lib/librarian/chef/source/local.rb +80 -0
  22. data/lib/librarian/chef/source/path.rb +14 -0
  23. data/lib/librarian/chef/source/site.rb +271 -0
  24. data/lib/librarian/cli.rb +76 -0
  25. data/lib/librarian/dependency.rb +44 -0
  26. data/lib/librarian/dsl.rb +76 -0
  27. data/lib/librarian/dsl/receiver.rb +46 -0
  28. data/lib/librarian/dsl/target.rb +164 -0
  29. data/lib/librarian/helpers.rb +13 -0
  30. data/lib/librarian/helpers/debug.rb +35 -0
  31. data/lib/librarian/lockfile.rb +31 -0
  32. data/lib/librarian/lockfile/compiler.rb +69 -0
  33. data/lib/librarian/lockfile/parser.rb +102 -0
  34. data/lib/librarian/manifest.rb +88 -0
  35. data/lib/librarian/manifest_set.rb +131 -0
  36. data/lib/librarian/mock.rb +1 -0
  37. data/lib/librarian/mock/cli.rb +14 -0
  38. data/lib/librarian/mock/dsl.rb +14 -0
  39. data/lib/librarian/mock/extension.rb +28 -0
  40. data/lib/librarian/mock/particularity.rb +7 -0
  41. data/lib/librarian/mock/source.rb +1 -0
  42. data/lib/librarian/mock/source/mock.rb +88 -0
  43. data/lib/librarian/mock/source/mock/registry.rb +79 -0
  44. data/lib/librarian/particularity.rb +7 -0
  45. data/lib/librarian/resolution.rb +36 -0
  46. data/lib/librarian/resolver.rb +139 -0
  47. data/lib/librarian/source.rb +2 -0
  48. data/lib/librarian/source/git.rb +91 -0
  49. data/lib/librarian/source/git/repository.rb +82 -0
  50. data/lib/librarian/source/local.rb +33 -0
  51. data/lib/librarian/source/path.rb +52 -0
  52. data/lib/librarian/spec.rb +11 -0
  53. data/lib/librarian/spec_change_set.rb +169 -0
  54. data/lib/librarian/specfile.rb +16 -0
  55. data/lib/librarian/support/abstract_method.rb +21 -0
  56. data/lib/librarian/ui.rb +64 -0
  57. data/lib/librarian/version.rb +3 -0
  58. data/librarian.gemspec +29 -0
  59. data/spec/chef/git_source_spec.rb +93 -0
  60. data/spec/dsl_spec.rb +167 -0
  61. data/spec/lockfile_spec.rb +44 -0
  62. data/spec/meta/requires_spec.rb +27 -0
  63. data/spec/resolver_spec.rb +172 -0
  64. data/spec/spec_change_set_spec.rb +165 -0
  65. metadata +172 -0
@@ -0,0 +1,79 @@
1
+ module Librarian
2
+ module Mock
3
+ module Source
4
+ class Mock
5
+ class Registry
6
+
7
+ module Dsl
8
+
9
+ class Top
10
+ def initialize(sources)
11
+ @sources = sources
12
+ end
13
+ def source(name, &block)
14
+ @sources[name] ||= {}
15
+ Source.new(@sources[name]).instance_eval(&block) if block
16
+ end
17
+ end
18
+
19
+ class Source
20
+ def initialize(source)
21
+ @source = source
22
+ end
23
+ def spec(name, version = nil, &block)
24
+ @source[name] ||= []
25
+ unless version
26
+ Spec.new(@source[name]).instance_eval(&block) if block
27
+ else
28
+ Spec.new(@source[name]).version(version, &block)
29
+ end
30
+ @source[name] = @source[name].sort_by{|a| Manifest::Version.new(a[:version])}.reverse
31
+ end
32
+ end
33
+
34
+ class Spec
35
+ def initialize(spec)
36
+ @spec = spec
37
+ end
38
+ def version(name, &block)
39
+ @spec << { :version => name, :dependencies => {} }
40
+ Version.new(@spec.last[:dependencies]).instance_eval(&block) if block
41
+ end
42
+ end
43
+
44
+ class Version
45
+ def initialize(version)
46
+ @version = version
47
+ end
48
+ def dependency(name, *requirement)
49
+ @version[name] = requirement
50
+ end
51
+ end
52
+
53
+ class << self
54
+ def run!(sources, &block)
55
+ Top.new(sources).instance_eval(&block) if block
56
+ end
57
+ end
58
+
59
+ end
60
+
61
+ class << self
62
+ def clear!
63
+ @sources = nil
64
+ end
65
+ def merge!(&block)
66
+ @sources ||= {}
67
+ Dsl.run!(@sources, &block) if block
68
+ end
69
+ def [](name)
70
+ @sources ||= {}
71
+ @sources[name] ||= {}
72
+ end
73
+ end
74
+
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,7 @@
1
+ module Librarian
2
+ module Particularity
3
+ def root_module
4
+ nil
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,36 @@
1
+ module Librarian
2
+ class Resolution
3
+ attr_reader :dependencies, :manifests, :manifests_index
4
+
5
+ def initialize(dependencies, manifests)
6
+ @dependencies, @manifests = dependencies, manifests
7
+ @manifests_index = build_manifests_index(manifests)
8
+ end
9
+
10
+ def correct?
11
+ manifests && manifests_consistent_with_dependencies? && manifests_internally_consistent?
12
+ end
13
+
14
+ def sources
15
+ manifests.map{|m| m.source}.uniq
16
+ end
17
+
18
+ private
19
+
20
+ def build_manifests_index(manifests)
21
+ Hash[manifests.map{|m| [m.name, m]}] if manifests
22
+ end
23
+
24
+ def manifests_consistent_with_dependencies?
25
+ dependencies.all? do |dependency|
26
+ manifest = manifests_index[dependency.name]
27
+ dependency.satisfied_by?(manifest)
28
+ end
29
+ end
30
+
31
+ def manifests_internally_consistent?
32
+ ManifestSet.new(manifests).consistent?
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,139 @@
1
+ require 'librarian/helpers/debug'
2
+
3
+ require 'librarian/dependency'
4
+ require 'librarian/manifest_set'
5
+ require 'librarian/resolution'
6
+
7
+ module Librarian
8
+ class Resolver
9
+
10
+ class Implementation
11
+ include Helpers::Debug
12
+
13
+ attr_reader :resolver, :source, :dependency_source_map
14
+
15
+ def initialize(resolver, spec)
16
+ @resolver = resolver
17
+ @source = spec.source
18
+ @dependency_source_map = Hash[spec.dependencies.map{|d| [d.name, d.source]}]
19
+ @level = 0
20
+ end
21
+
22
+ def resolve(dependencies, manifests = {})
23
+ resolution = recursive_resolve([], manifests, dependencies.dup)
24
+ resolution ? resolution[1] : nil
25
+ end
26
+
27
+ def recursive_resolve(dependencies, manifests, queue)
28
+ if dependencies.empty?
29
+ queue.each do |dependency|
30
+ debug { "Scheduling #{dependency}" }
31
+ end
32
+ end
33
+ failure = false
34
+ until failure || queue.empty?
35
+ dependency = queue.shift
36
+ dependencies << dependency
37
+ debug { "Resolving #{dependency}" }
38
+ scope do
39
+ if manifests.key?(dependency.name)
40
+ unless dependency.satisfied_by?(manifests[dependency.name])
41
+ debug { "Conflicts with #{manifests[dependency.name]}" }
42
+ failure = true
43
+ else
44
+ debug { "Accords with all prior constraints" }
45
+ # nothing left to do
46
+ end
47
+ else
48
+ debug { "No known prior constraints" }
49
+ resolution = nil
50
+ related_dependencies = dependencies.select{|d| d.name == dependency.name}
51
+ unless dependency.manifests && dependency.manifests.first
52
+ debug { "No known manifests" }
53
+ else
54
+ debug { "Checking manifests" }
55
+ scope do
56
+ dependency.manifests.each do |manifest|
57
+ unless resolution
58
+ debug { "Checking #{manifest}" }
59
+ scope do
60
+ if related_dependencies.all?{|d| d.satisfied_by?(manifest)}
61
+ m = manifests.merge(dependency.name => manifest)
62
+ a = manifest.dependencies.map { |d|
63
+ d.source ? d :
64
+ !dependency_source_map.key?(d.name) ?
65
+ Dependency.new(d.name, d.requirement, source) :
66
+ Dependency.new(d.name, d.requirement, dependency_source_map[d.name])
67
+ }
68
+ a.each do |d|
69
+ debug { "Scheduling #{d}" }
70
+ end
71
+ q = queue + a
72
+ resolution = recursive_resolve(dependencies.dup, m, q)
73
+ end
74
+ if resolution
75
+ debug { "Resolved #{dependency} at #{manifest}" }
76
+ else
77
+ debug { "Backtracking from #{manifest}" }
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ if resolution
84
+ debug { "Resolved #{dependency}" }
85
+ else
86
+ debug { "Failed to resolve #{dependency}" }
87
+ end
88
+ end
89
+ unless resolution
90
+ failure = true
91
+ else
92
+ dependencies, manifests, queue = *resolution
93
+ end
94
+ end
95
+ end
96
+ end
97
+ failure ? nil : [dependencies, manifests, queue]
98
+ end
99
+
100
+ private
101
+
102
+ def scope
103
+ @level += 1
104
+ yield
105
+ ensure
106
+ @level -= 1
107
+ end
108
+
109
+ def debug
110
+ super { ' ' * @level + yield }
111
+ end
112
+
113
+ def root_module
114
+ resolver.root_module
115
+ end
116
+ end
117
+
118
+ include Helpers::Debug
119
+
120
+ attr_reader :root_module
121
+
122
+ def initialize(root_module)
123
+ @root_module = root_module
124
+ end
125
+
126
+ def resolve(spec, partial_manifests = [])
127
+ implementation = Implementation.new(self, spec)
128
+ partial_manifests_index = Hash[partial_manifests.map{|m| [m.name, m]}]
129
+ manifests = implementation.resolve(spec.dependencies, partial_manifests_index)
130
+ manifests = sort(manifests) if manifests
131
+ Resolution.new(spec.dependencies, manifests)
132
+ end
133
+
134
+ def sort(manifests)
135
+ ManifestSet.sort(manifests)
136
+ end
137
+
138
+ end
139
+ end
@@ -0,0 +1,2 @@
1
+ require 'librarian/source/git'
2
+ require 'librarian/source/path'
@@ -0,0 +1,91 @@
1
+ require 'fileutils'
2
+ require 'pathname'
3
+ require 'digest'
4
+
5
+ require 'librarian/particularity'
6
+ require 'librarian/source/git/repository'
7
+ require 'librarian/source/local'
8
+
9
+ module Librarian
10
+ module Source
11
+ class Git
12
+
13
+ include Particularity
14
+ include Local
15
+
16
+ class << self
17
+ LOCK_NAME = 'GIT'
18
+ def lock_name
19
+ LOCK_NAME
20
+ end
21
+ def from_lock_options(options)
22
+ new(options[:remote], options.reject{|k, v| k == :remote})
23
+ end
24
+ end
25
+
26
+ DEFAULTS = {
27
+ :ref => 'master'
28
+ }
29
+
30
+ attr_reader :uri, :ref, :sha
31
+
32
+ def initialize(uri, options = {})
33
+ @uri = uri
34
+ @ref = options[:ref] || DEFAULTS[:ref]
35
+ @sha = options[:sha]
36
+ @repository = nil
37
+ @repository_cache_path = nil
38
+ end
39
+
40
+ def to_s
41
+ "#{uri}##{ref}"
42
+ end
43
+
44
+ def ==(other)
45
+ other &&
46
+ self.class == other.class &&
47
+ self.uri == other.uri &&
48
+ self.ref == other.ref &&
49
+ (self.sha.nil? || other.sha.nil? || self.sha == other.sha)
50
+ end
51
+
52
+ def to_spec_args
53
+ [uri, {:ref => ref}]
54
+ end
55
+
56
+ def to_lock_options
57
+ {:remote => uri, :ref => ref, :sha => sha}
58
+ end
59
+
60
+ def cache!(dependencies)
61
+ unless repository.git?
62
+ repository.path.rmtree if repository.path.exist?
63
+ repository.path.mkpath
64
+ repository.clone!(uri)
65
+ end
66
+ unless sha == repository.current_commit_hash
67
+ repository.checkout!(sha || ref)
68
+ @sha ||= repository.current_commit_hash
69
+ end
70
+ end
71
+
72
+ def repository_cache_path
73
+ @repository_cache_path ||= begin
74
+ dir = Digest::MD5.hexdigest(uri)
75
+ root_module.cache_path.join("source/git/#{dir}")
76
+ end
77
+ end
78
+
79
+ def repository
80
+ @repository ||= begin
81
+ Repository.new(root_module, repository_cache_path)
82
+ end
83
+ end
84
+
85
+ def path
86
+ @path ||= repository.path
87
+ end
88
+
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,82 @@
1
+ require 'open3'
2
+
3
+ require 'librarian/helpers/debug'
4
+
5
+ module Librarian
6
+ module Source
7
+ class Git
8
+ class Repository
9
+
10
+ class << self
11
+ def clone!(root_module, path, repository_url)
12
+ path = Pathname.new(path)
13
+ path.mkpath
14
+ git = new(root_module, path)
15
+ git.clone!(repository_url)
16
+ git
17
+ end
18
+ end
19
+
20
+ include Helpers::Debug
21
+
22
+ attr_reader :root_module, :path
23
+
24
+ def initialize(root_module, path)
25
+ path = Pathname.new(path)
26
+ @root_module = root_module
27
+ @path = path
28
+ end
29
+
30
+ def git?
31
+ path.join('.git').exist?
32
+ end
33
+
34
+ def clone!(repository_url)
35
+ within do
36
+ command = "clone #{repository_url} ."
37
+ run!(command)
38
+ end
39
+ end
40
+
41
+ def checkout!(reference)
42
+ within do
43
+ command = "checkout #{reference}"
44
+ run!(command)
45
+ end
46
+ end
47
+
48
+ def hash_from(reference)
49
+ within do
50
+ command = "rev-parse #{reference}"
51
+ run!(command)
52
+ end
53
+ end
54
+
55
+ def current_commit_hash
56
+ within do
57
+ command = "rev-parse HEAD"
58
+ run!(command).strip!
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def run!(text)
65
+ text = "git #{text} --quiet"
66
+ debug { "Running `#{text}` in #{relative_path_to(Dir.pwd)}" }
67
+ out = Open3.popen3(text) do |i, o, e, t|
68
+ raise StandardError, e.read unless (t ? t.value : $?).success?
69
+ o.read
70
+ end
71
+ debug { " -> #{out}" } if out.size > 0
72
+ out
73
+ end
74
+
75
+ def within
76
+ Dir.chdir(path) { yield }
77
+ end
78
+
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,33 @@
1
+ require 'librarian/support/abstract_method'
2
+
3
+ module Librarian
4
+ module Source
5
+ # Requires that the including source class have methods:
6
+ # #path
7
+ # #root_module
8
+ module Local
9
+
10
+ include Support::AbstractMethod
11
+
12
+ abstract_method :path
13
+
14
+ def manifests(dependency)
15
+ manifest = manifest_class.create(self, dependency, path)
16
+ [manifest].compact
17
+ end
18
+
19
+ def manifest(name, version, dependencies)
20
+ manifest = manifest_class.create(self, Dependency.new(name, nil, nil), path)
21
+ manifest.version = version
22
+ manifest.dependencies = dependencies
23
+ manifest
24
+ end
25
+
26
+ def manifest_search_paths(dependency)
27
+ paths = [path, path.join(dependency.name)]
28
+ paths.select{|s| s.exist?}
29
+ end
30
+
31
+ end
32
+ end
33
+ end