librarian 0.0.1

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 (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