librarian 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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,52 @@
|
|
1
|
+
require 'librarian/particularity'
|
2
|
+
require 'librarian/source/local'
|
3
|
+
|
4
|
+
module Librarian
|
5
|
+
module Source
|
6
|
+
class Path
|
7
|
+
|
8
|
+
include Particularity
|
9
|
+
include Local
|
10
|
+
|
11
|
+
class << self
|
12
|
+
LOCK_NAME = 'PATH'
|
13
|
+
def lock_name
|
14
|
+
LOCK_NAME
|
15
|
+
end
|
16
|
+
def from_lock_options(options)
|
17
|
+
new(options[:remote], options.reject{|k, v| k == :remote})
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :path
|
22
|
+
|
23
|
+
def initialize(path, options)
|
24
|
+
@path = Pathname.new(path).expand_path(root_module.project_path)
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_s
|
28
|
+
path.to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
def ==(other)
|
32
|
+
other &&
|
33
|
+
self.class == other.class &&
|
34
|
+
self.path == other.path
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_spec_args
|
38
|
+
[path.to_s, {}]
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_lock_options
|
42
|
+
absolute_path = path.absolute? ? path : path.expand_path(root_module.project_path)
|
43
|
+
relative_path = path.relative? ? path : path.relative_path_from(root_module.project_path)
|
44
|
+
{:remote => relative_path.to_s[0, 3] == '../' ? absolute_path : relative_path}
|
45
|
+
end
|
46
|
+
|
47
|
+
def cache!(dependencies)
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'librarian/helpers'
|
2
|
+
require 'librarian/helpers/debug'
|
3
|
+
|
4
|
+
require 'librarian/manifest_set'
|
5
|
+
require 'librarian/resolution'
|
6
|
+
require 'librarian/spec'
|
7
|
+
|
8
|
+
module Librarian
|
9
|
+
class SpecChangeSet
|
10
|
+
|
11
|
+
include Helpers::Debug
|
12
|
+
|
13
|
+
attr_reader :root_module
|
14
|
+
attr_reader :spec, :lock
|
15
|
+
|
16
|
+
def initialize(root_module, spec, lock)
|
17
|
+
@root_module = root_module
|
18
|
+
raise TypeError, "can't convert #{spec.class} into Spec" unless Spec === spec
|
19
|
+
raise TypeError, "can't convert #{lock.class} into Resolution" unless Resolution === lock
|
20
|
+
@spec, @lock = spec, lock
|
21
|
+
end
|
22
|
+
|
23
|
+
def same?
|
24
|
+
@same ||= spec.dependencies.sort_by{|d| d.name} == lock.dependencies.sort_by{|d| d.name}
|
25
|
+
end
|
26
|
+
|
27
|
+
def changed?
|
28
|
+
!same?
|
29
|
+
end
|
30
|
+
|
31
|
+
def spec_dependencies
|
32
|
+
@spec_dependencies ||= spec.dependencies
|
33
|
+
end
|
34
|
+
def spec_dependency_names
|
35
|
+
@spec_dependency_names ||= Set.new(spec_dependencies.map{|d| d.name})
|
36
|
+
end
|
37
|
+
def spec_dependency_index
|
38
|
+
@spec_dependency_index ||= Hash[spec_dependencies.map{|d| [d.name, d]}]
|
39
|
+
end
|
40
|
+
|
41
|
+
def lock_dependencies
|
42
|
+
@lock_dependencies ||= lock.dependencies
|
43
|
+
end
|
44
|
+
def lock_dependency_names
|
45
|
+
@lock_dependency_names ||= Set.new(lock_dependencies.map{|d| d.name})
|
46
|
+
end
|
47
|
+
def lock_dependency_index
|
48
|
+
@lock_dependency_index ||= Hash[lock_dependencies.map{|d| [d.name, d]}]
|
49
|
+
end
|
50
|
+
|
51
|
+
def lock_manifests
|
52
|
+
@lock_manifests ||= lock.manifests
|
53
|
+
end
|
54
|
+
def lock_manifests_index
|
55
|
+
@lock_manifests_index ||= ManifestSet.new(lock_manifests).to_hash
|
56
|
+
end
|
57
|
+
|
58
|
+
def removed_dependency_names
|
59
|
+
@removed_dependency_names ||= lock_dependency_names - spec_dependency_names
|
60
|
+
end
|
61
|
+
|
62
|
+
# A dependency which is deleted from the specfile will, in the general case,
|
63
|
+
# be removed conservatively. This means it might not actually be removed.
|
64
|
+
# But if the dependency originally declared a source which is now non-
|
65
|
+
# default, it must be removed, even if another dependency has a transitive
|
66
|
+
# dependency on the one that was removed (which is the scenario in which
|
67
|
+
# a conservative removal would not remove it). In this case, we must also
|
68
|
+
# remove it explicitly so that it can be re-resolved from the default
|
69
|
+
# source.
|
70
|
+
def explicit_removed_dependency_names
|
71
|
+
@explicit_removed_dependency_names ||= removed_dependency_names.reject do |name|
|
72
|
+
lock_manifest = lock_manifests_index[name]
|
73
|
+
lock_manifest.source == spec.source
|
74
|
+
end.to_set
|
75
|
+
end
|
76
|
+
|
77
|
+
def added_dependency_names
|
78
|
+
@added_dependency_names ||= spec_dependency_names - lock_dependency_names
|
79
|
+
end
|
80
|
+
|
81
|
+
def nonmatching_added_dependency_names
|
82
|
+
@nonmatching_added_dependency_names ||= added_dependency_names.reject do |name|
|
83
|
+
spec_dependency = spec_dependency_index[name]
|
84
|
+
lock_manifest = lock_manifests_index[name]
|
85
|
+
if lock_manifest
|
86
|
+
matching = true
|
87
|
+
matching &&= spec_dependency.satisfied_by?(lock_manifest)
|
88
|
+
matching &&= spec_dependency.source == lock_manifest.source
|
89
|
+
matching
|
90
|
+
else
|
91
|
+
false
|
92
|
+
end
|
93
|
+
end.to_set
|
94
|
+
end
|
95
|
+
|
96
|
+
def common_dependency_names
|
97
|
+
@common_dependency_names ||= lock_dependency_names & spec_dependency_names
|
98
|
+
end
|
99
|
+
|
100
|
+
def changed_dependency_names
|
101
|
+
@changed_dependency_names ||= common_dependency_names.reject do |name|
|
102
|
+
spec_dependency = spec_dependency_index[name]
|
103
|
+
lock_dependency = lock_dependency_index[name]
|
104
|
+
lock_manifest = lock_manifests_index[name]
|
105
|
+
same = true
|
106
|
+
same &&= spec_dependency.satisfied_by?(lock_manifest)
|
107
|
+
same &&= spec_dependency.source == lock_dependency.source
|
108
|
+
same
|
109
|
+
end.to_set
|
110
|
+
end
|
111
|
+
|
112
|
+
def deep_keep_manifest_names
|
113
|
+
@deep_keep_manifest_names ||= begin
|
114
|
+
lock_dependency_names - (
|
115
|
+
removed_dependency_names +
|
116
|
+
changed_dependency_names +
|
117
|
+
nonmatching_added_dependency_names
|
118
|
+
)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def shallow_strip_manifest_names
|
123
|
+
@shallow_strip_manifest_names ||= begin
|
124
|
+
explicit_removed_dependency_names + changed_dependency_names
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def inspect
|
129
|
+
Helpers.strip_heredoc(<<-INSPECT)
|
130
|
+
<##{self.class.name}:
|
131
|
+
Removed: #{removed_dependency_names.to_a.join(", ")}
|
132
|
+
ExplicitRemoved: #{explicit_removed_dependency_names.to_a.join(", ")}
|
133
|
+
Added: #{added_dependency_names.to_a.join(", ")}
|
134
|
+
NonMatchingAdded: #{nonmatching_added_dependency_names.to_a.join(", ")}
|
135
|
+
Changed: #{changed_dependency_names.to_a.join(", ")}
|
136
|
+
DeepKeep: #{deep_keep_manifest_names.to_a.join(", ")}
|
137
|
+
ShallowStrip: #{shallow_strip_manifest_names.to_a.join(", ")}
|
138
|
+
>
|
139
|
+
INSPECT
|
140
|
+
end
|
141
|
+
|
142
|
+
# Returns an array of those manifests from the previous spec which should be kept,
|
143
|
+
# based on inspecting the new spec against the locked resolution from the previous spec.
|
144
|
+
def analyze
|
145
|
+
@analyze ||= begin
|
146
|
+
debug { "Analyzing spec and lock:" }
|
147
|
+
|
148
|
+
if same?
|
149
|
+
debug { " Same!" }
|
150
|
+
return lock.manifests
|
151
|
+
end
|
152
|
+
|
153
|
+
debug { " Removed:" } ; removed_dependency_names.each { |name| debug { " #{name}" } }
|
154
|
+
debug { " ExplicitRemoved:" } ; explicit_removed_dependency_names.each { |name| debug { " #{name}" } }
|
155
|
+
debug { " Added:" } ; added_dependency_names.each { |name| debug { " #{name}" } }
|
156
|
+
debug { " NonMatchingAdded:" } ; nonmatching_added_dependency_names.each { |name| debug { " #{name}" } }
|
157
|
+
debug { " Changed:" } ; changed_dependency_names.each { |name| debug { " #{name}" } }
|
158
|
+
debug { " DeepKeep:" } ; deep_keep_manifest_names.each { |name| debug { " #{name}" } }
|
159
|
+
debug { " ShallowStrip:" } ; shallow_strip_manifest_names.each { |name| debug { " #{name}" } }
|
160
|
+
|
161
|
+
manifests = ManifestSet.new(lock_manifests)
|
162
|
+
manifests.deep_keep!(deep_keep_manifest_names)
|
163
|
+
manifests.shallow_strip!(shallow_strip_manifest_names)
|
164
|
+
manifests.to_a
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Librarian
|
2
|
+
class Specfile
|
3
|
+
|
4
|
+
attr_reader :root_module, :path, :dependencies, :source
|
5
|
+
|
6
|
+
def initialize(root_module, path)
|
7
|
+
@root_module = root_module
|
8
|
+
@path = path
|
9
|
+
end
|
10
|
+
|
11
|
+
def read(precache_sources = [])
|
12
|
+
root_module.dsl_class.run(self, precache_sources)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Librarian
|
2
|
+
module Support
|
3
|
+
module AbstractMethod
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def included(base)
|
7
|
+
base.extend ClassMethods
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def abstract_method(*names)
|
13
|
+
names.reject{|name| respond_to?(name)}.each do |name, *args|
|
14
|
+
define_method(name) { raise Exception, "Method #{self.class.name}##{name} is abstract!" }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/librarian/ui.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'rubygems/user_interaction'
|
2
|
+
|
3
|
+
module Librarian
|
4
|
+
class UI
|
5
|
+
def warn(message = nil)
|
6
|
+
end
|
7
|
+
|
8
|
+
def debug(message = nil)
|
9
|
+
end
|
10
|
+
|
11
|
+
def error(message = nil)
|
12
|
+
end
|
13
|
+
|
14
|
+
def info(message = nil)
|
15
|
+
end
|
16
|
+
|
17
|
+
def confirm(message = nil)
|
18
|
+
end
|
19
|
+
|
20
|
+
class Shell < UI
|
21
|
+
attr_writer :shell
|
22
|
+
attr_reader :debug_line_numbers
|
23
|
+
|
24
|
+
def initialize(shell)
|
25
|
+
@shell = shell
|
26
|
+
@quiet = false
|
27
|
+
@debug = ENV['DEBUG']
|
28
|
+
@debug_line_numbers = false
|
29
|
+
end
|
30
|
+
|
31
|
+
def debug(message = nil)
|
32
|
+
@shell.say(message || yield) if @debug && !@quiet
|
33
|
+
end
|
34
|
+
|
35
|
+
def info(message = nil)
|
36
|
+
@shell.say(message || yield) if !@quiet
|
37
|
+
end
|
38
|
+
|
39
|
+
def confirm(message = nil)
|
40
|
+
@shell.say(message || yield, :green) if !@quiet
|
41
|
+
end
|
42
|
+
|
43
|
+
def warn(message = nil)
|
44
|
+
@shell.say(message || yield, :yellow)
|
45
|
+
end
|
46
|
+
|
47
|
+
def error(message = nil)
|
48
|
+
@shell.say(message || yield, :red)
|
49
|
+
end
|
50
|
+
|
51
|
+
def be_quiet!
|
52
|
+
@quiet = true
|
53
|
+
end
|
54
|
+
|
55
|
+
def debug!
|
56
|
+
@debug = true
|
57
|
+
end
|
58
|
+
|
59
|
+
def debug_line_numbers!
|
60
|
+
@debug_line_numbers = true
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/librarian.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "librarian/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "librarian"
|
7
|
+
s.version = Librarian::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Jay Feldblum"]
|
10
|
+
s.email = ["y_feldblum@yahoo.com"]
|
11
|
+
s.homepage = ""
|
12
|
+
s.summary = %q{Librarian}
|
13
|
+
s.description = %q{Librarian}
|
14
|
+
|
15
|
+
s.rubyforge_project = "librarian"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.add_dependency "thor"
|
23
|
+
|
24
|
+
s.add_development_dependency "rspec"
|
25
|
+
s.add_development_dependency "cucumber"
|
26
|
+
s.add_development_dependency "aruba"
|
27
|
+
|
28
|
+
s.add_development_dependency "chef", ">= 0.10"
|
29
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
require 'librarian'
|
5
|
+
require 'librarian/helpers'
|
6
|
+
require 'librarian/chef'
|
7
|
+
|
8
|
+
module Librarian
|
9
|
+
module Chef
|
10
|
+
module Source
|
11
|
+
describe Git do
|
12
|
+
|
13
|
+
project_path = Pathname.new(__FILE__).expand_path
|
14
|
+
project_path = project_path.dirname until project_path.join("Rakefile").exist?
|
15
|
+
tmp_path = project_path.join("tmp/spec/chef/git-source")
|
16
|
+
sample_path = tmp_path.join('sample')
|
17
|
+
sample_metadata = Helpers.strip_heredoc(<<-METADATA)
|
18
|
+
version '0.6.5'
|
19
|
+
METADATA
|
20
|
+
|
21
|
+
before :all do
|
22
|
+
sample_path.rmtree if sample_path.exist?
|
23
|
+
sample_path.mkpath
|
24
|
+
sample_path.join('metadata.rb').open('wb') { |f| f.write(sample_metadata) }
|
25
|
+
Dir.chdir(sample_path) do
|
26
|
+
`git init`
|
27
|
+
`git add metadata.rb`
|
28
|
+
`git commit -m "Initial commit."`
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "a single dependency with a git source" do
|
33
|
+
|
34
|
+
it "should resolve" do
|
35
|
+
repo_path = tmp_path.join('repo/resolve')
|
36
|
+
repo_path.rmtree if repo_path.exist?
|
37
|
+
repo_path.mkpath
|
38
|
+
repo_path.join('cookbooks').mkpath
|
39
|
+
cheffile = Helpers.strip_heredoc(<<-CHEFFILE)
|
40
|
+
#!/usr/bin/env ruby
|
41
|
+
cookbook "sample", :git => #{sample_path.to_s.inspect}
|
42
|
+
CHEFFILE
|
43
|
+
repo_path.join('Cheffile').open('wb') { |f| f.write(cheffile) }
|
44
|
+
Chef.stub!(:project_path) { repo_path }
|
45
|
+
|
46
|
+
Chef.resolve!
|
47
|
+
repo_path.join('Cheffile.lock').should be_exist
|
48
|
+
repo_path.join('cookbooks/sample').should_not be_exist
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should install" do
|
52
|
+
repo_path = tmp_path.join('repo/install')
|
53
|
+
repo_path.rmtree if repo_path.exist?
|
54
|
+
repo_path.mkpath
|
55
|
+
repo_path.join('cookbooks').mkpath
|
56
|
+
cheffile = Helpers.strip_heredoc(<<-CHEFFILE)
|
57
|
+
#!/usr/bin/env ruby
|
58
|
+
cookbook "sample", :git => #{sample_path.to_s.inspect}
|
59
|
+
CHEFFILE
|
60
|
+
repo_path.join('Cheffile').open('wb') { |f| f.write(cheffile) }
|
61
|
+
Chef.stub!(:project_path) { repo_path }
|
62
|
+
|
63
|
+
Chef.install!
|
64
|
+
repo_path.join('Cheffile.lock').should be_exist
|
65
|
+
repo_path.join('cookbooks/sample').should be_exist
|
66
|
+
repo_path.join('cookbooks/sample/metadata.rb').should be_exist
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should resolve and separately install" do
|
70
|
+
repo_path = tmp_path.join('repo/resolve-install')
|
71
|
+
repo_path.rmtree if repo_path.exist?
|
72
|
+
repo_path.mkpath
|
73
|
+
repo_path.join('cookbooks').mkpath
|
74
|
+
cheffile = Helpers.strip_heredoc(<<-CHEFFILE)
|
75
|
+
#!/usr/bin/env ruby
|
76
|
+
cookbook "sample", :git => #{sample_path.to_s.inspect}
|
77
|
+
CHEFFILE
|
78
|
+
repo_path.join('Cheffile').open('wb') { |f| f.write(cheffile) }
|
79
|
+
Chef.stub!(:project_path) { repo_path }
|
80
|
+
|
81
|
+
Chef.resolve!
|
82
|
+
repo_path.join('tmp').rmtree if repo_path.join('tmp').exist?
|
83
|
+
Chef.install!
|
84
|
+
repo_path.join('cookbooks/sample').should be_exist
|
85
|
+
repo_path.join('cookbooks/sample/metadata.rb').should be_exist
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|