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.
- data/.gitignore +5 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +20 -0
- data/README.md +46 -0
- data/Rakefile +13 -0
- data/bin/librarian-chef +7 -0
- data/bin/librarian-mock +7 -0
- data/config/cucumber.yaml +1 -0
- data/features/chef/cli/install.feature +69 -0
- data/features/support/env.rb +5 -0
- data/lib/librarian.rb +191 -0
- data/lib/librarian/chef.rb +1 -0
- data/lib/librarian/chef/cli.rb +14 -0
- data/lib/librarian/chef/dsl.rb +14 -0
- data/lib/librarian/chef/extension.rb +24 -0
- data/lib/librarian/chef/manifest.rb +43 -0
- data/lib/librarian/chef/particularity.rb +9 -0
- data/lib/librarian/chef/source.rb +3 -0
- data/lib/librarian/chef/source/git.rb +14 -0
- data/lib/librarian/chef/source/local.rb +80 -0
- data/lib/librarian/chef/source/path.rb +14 -0
- data/lib/librarian/chef/source/site.rb +271 -0
- data/lib/librarian/cli.rb +76 -0
- data/lib/librarian/dependency.rb +44 -0
- data/lib/librarian/dsl.rb +76 -0
- data/lib/librarian/dsl/receiver.rb +46 -0
- data/lib/librarian/dsl/target.rb +164 -0
- data/lib/librarian/helpers.rb +13 -0
- data/lib/librarian/helpers/debug.rb +35 -0
- data/lib/librarian/lockfile.rb +31 -0
- data/lib/librarian/lockfile/compiler.rb +69 -0
- data/lib/librarian/lockfile/parser.rb +102 -0
- data/lib/librarian/manifest.rb +88 -0
- data/lib/librarian/manifest_set.rb +131 -0
- data/lib/librarian/mock.rb +1 -0
- data/lib/librarian/mock/cli.rb +14 -0
- data/lib/librarian/mock/dsl.rb +14 -0
- data/lib/librarian/mock/extension.rb +28 -0
- data/lib/librarian/mock/particularity.rb +7 -0
- data/lib/librarian/mock/source.rb +1 -0
- data/lib/librarian/mock/source/mock.rb +88 -0
- data/lib/librarian/mock/source/mock/registry.rb +79 -0
- data/lib/librarian/particularity.rb +7 -0
- data/lib/librarian/resolution.rb +36 -0
- data/lib/librarian/resolver.rb +139 -0
- data/lib/librarian/source.rb +2 -0
- data/lib/librarian/source/git.rb +91 -0
- data/lib/librarian/source/git/repository.rb +82 -0
- data/lib/librarian/source/local.rb +33 -0
- data/lib/librarian/source/path.rb +52 -0
- data/lib/librarian/spec.rb +11 -0
- data/lib/librarian/spec_change_set.rb +169 -0
- data/lib/librarian/specfile.rb +16 -0
- data/lib/librarian/support/abstract_method.rb +21 -0
- data/lib/librarian/ui.rb +64 -0
- data/lib/librarian/version.rb +3 -0
- data/librarian.gemspec +29 -0
- data/spec/chef/git_source_spec.rb +93 -0
- data/spec/dsl_spec.rb +167 -0
- data/spec/lockfile_spec.rb +44 -0
- data/spec/meta/requires_spec.rb +27 -0
- data/spec/resolver_spec.rb +172 -0
- data/spec/spec_change_set_spec.rb +165 -0
- 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,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,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
|