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