librarianp 0.1.2

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 (100) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +10 -0
  5. data/CHANGELOG.md +255 -0
  6. data/Gemfile +8 -0
  7. data/Gemfile.lock +235 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +55 -0
  10. data/Rakefile +28 -0
  11. data/VERSION +1 -0
  12. data/lib/librarian/action/base.rb +24 -0
  13. data/lib/librarian/action/clean.rb +44 -0
  14. data/lib/librarian/action/ensure.rb +24 -0
  15. data/lib/librarian/action/install.rb +95 -0
  16. data/lib/librarian/action/persist_resolution_mixin.rb +51 -0
  17. data/lib/librarian/action/resolve.rb +46 -0
  18. data/lib/librarian/action/update.rb +44 -0
  19. data/lib/librarian/action.rb +5 -0
  20. data/lib/librarian/algorithms.rb +133 -0
  21. data/lib/librarian/cli/manifest_presenter.rb +89 -0
  22. data/lib/librarian/cli.rb +225 -0
  23. data/lib/librarian/config/database.rb +205 -0
  24. data/lib/librarian/config/file_source.rb +47 -0
  25. data/lib/librarian/config/hash_source.rb +33 -0
  26. data/lib/librarian/config/source.rb +149 -0
  27. data/lib/librarian/config.rb +7 -0
  28. data/lib/librarian/dependency.rb +153 -0
  29. data/lib/librarian/dsl/receiver.rb +42 -0
  30. data/lib/librarian/dsl/target.rb +171 -0
  31. data/lib/librarian/dsl.rb +102 -0
  32. data/lib/librarian/environment/runtime_cache.rb +101 -0
  33. data/lib/librarian/environment.rb +230 -0
  34. data/lib/librarian/error.rb +4 -0
  35. data/lib/librarian/helpers.rb +29 -0
  36. data/lib/librarian/linter/source_linter.rb +55 -0
  37. data/lib/librarian/lockfile/compiler.rb +66 -0
  38. data/lib/librarian/lockfile/parser.rb +123 -0
  39. data/lib/librarian/lockfile.rb +29 -0
  40. data/lib/librarian/logger.rb +46 -0
  41. data/lib/librarian/manifest.rb +146 -0
  42. data/lib/librarian/manifest_set.rb +150 -0
  43. data/lib/librarian/mock/cli.rb +19 -0
  44. data/lib/librarian/mock/dsl.rb +15 -0
  45. data/lib/librarian/mock/environment.rb +21 -0
  46. data/lib/librarian/mock/extension.rb +9 -0
  47. data/lib/librarian/mock/source/mock/registry.rb +83 -0
  48. data/lib/librarian/mock/source/mock.rb +80 -0
  49. data/lib/librarian/mock/source.rb +1 -0
  50. data/lib/librarian/mock/version.rb +5 -0
  51. data/lib/librarian/mock.rb +1 -0
  52. data/lib/librarian/posix.rb +129 -0
  53. data/lib/librarian/resolution.rb +46 -0
  54. data/lib/librarian/resolver/implementation.rb +238 -0
  55. data/lib/librarian/resolver.rb +94 -0
  56. data/lib/librarian/rspec/support/cli_macro.rb +120 -0
  57. data/lib/librarian/source/basic_api.rb +45 -0
  58. data/lib/librarian/source/git/repository.rb +193 -0
  59. data/lib/librarian/source/git.rb +172 -0
  60. data/lib/librarian/source/local.rb +54 -0
  61. data/lib/librarian/source/path.rb +56 -0
  62. data/lib/librarian/source.rb +2 -0
  63. data/lib/librarian/spec.rb +13 -0
  64. data/lib/librarian/spec_change_set.rb +173 -0
  65. data/lib/librarian/specfile.rb +19 -0
  66. data/lib/librarian/support/abstract_method.rb +21 -0
  67. data/lib/librarian/ui.rb +64 -0
  68. data/lib/librarian/version.rb +3 -0
  69. data/lib/librarian.rb +11 -0
  70. data/librarian.gemspec +47 -0
  71. data/spec/functional/cli_spec.rb +27 -0
  72. data/spec/functional/posix_spec.rb +32 -0
  73. data/spec/functional/source/git/repository_spec.rb +199 -0
  74. data/spec/functional/source/git_spec.rb +174 -0
  75. data/spec/support/fakefs.rb +37 -0
  76. data/spec/support/method_patch_macro.rb +30 -0
  77. data/spec/support/project_path_macro.rb +14 -0
  78. data/spec/support/with_env_macro.rb +22 -0
  79. data/spec/unit/action/base_spec.rb +18 -0
  80. data/spec/unit/action/clean_spec.rb +102 -0
  81. data/spec/unit/action/ensure_spec.rb +37 -0
  82. data/spec/unit/action/install_spec.rb +111 -0
  83. data/spec/unit/algorithms_spec.rb +131 -0
  84. data/spec/unit/config/database_spec.rb +320 -0
  85. data/spec/unit/dependency/requirement_spec.rb +12 -0
  86. data/spec/unit/dependency_spec.rb +212 -0
  87. data/spec/unit/dsl_spec.rb +173 -0
  88. data/spec/unit/environment/runtime_cache_spec.rb +73 -0
  89. data/spec/unit/environment_spec.rb +209 -0
  90. data/spec/unit/lockfile/parser_spec.rb +162 -0
  91. data/spec/unit/lockfile_spec.rb +65 -0
  92. data/spec/unit/manifest/version_spec.rb +11 -0
  93. data/spec/unit/manifest_set_spec.rb +202 -0
  94. data/spec/unit/manifest_spec.rb +36 -0
  95. data/spec/unit/mock/environment_spec.rb +25 -0
  96. data/spec/unit/mock/source/mock_spec.rb +22 -0
  97. data/spec/unit/resolver_spec.rb +299 -0
  98. data/spec/unit/source/git_spec.rb +29 -0
  99. data/spec/unit/spec_change_set_spec.rb +169 -0
  100. metadata +257 -0
@@ -0,0 +1,193 @@
1
+ require "pathname"
2
+
3
+ require "librarian/posix"
4
+
5
+ module Librarian
6
+ module Source
7
+ class Git
8
+ class Repository
9
+
10
+ class << self
11
+ def clone!(environment, path, repository_url)
12
+ path = Pathname.new(path)
13
+ path.mkpath
14
+ git = new(environment, path)
15
+ git.clone!(repository_url)
16
+ git
17
+ end
18
+
19
+ def bin
20
+ @bin ||= Posix.which!("git")
21
+ end
22
+
23
+ def git_version
24
+ command = %W[#{bin} version --silent]
25
+ Posix.run!(command).strip =~ /\Agit version (\d+(\.\d+)*)/ && $1
26
+ end
27
+ end
28
+
29
+ attr_accessor :environment, :path, :git_ops_history
30
+ private :environment=, :path=, :git_ops_history=
31
+
32
+ def initialize(environment, path)
33
+ self.environment = environment
34
+ self.path = Pathname.new(path)
35
+ self.git_ops_history = []
36
+ end
37
+
38
+ def git?
39
+ path.join('.git').exist?
40
+ end
41
+
42
+ def default_remote
43
+ "origin"
44
+ end
45
+
46
+ def clone!(repository_url)
47
+ command = %W(clone #{repository_url} . --quiet)
48
+ run!(command, :chdir => true)
49
+ end
50
+
51
+ def checkout!(reference, options ={ })
52
+ command = %W(checkout #{reference} --quiet)
53
+ command << "--force" if options[:force]
54
+ run!(command, :chdir => true)
55
+ end
56
+
57
+ def fetch!(remote, options = { })
58
+ command = %W(fetch #{remote} --quiet)
59
+ command << "--tags" if options[:tags]
60
+ run!(command, :chdir => true)
61
+ end
62
+
63
+ def reset_hard!
64
+ command = %W(reset --hard --quiet)
65
+ run!(command, :chdir => true)
66
+ end
67
+
68
+ def clean!
69
+ command = %w(clean -x -d --force --force)
70
+ run!(command, :chdir => true)
71
+ end
72
+
73
+ def has_commit?(sha)
74
+ command = %W(log -1 --no-color --format=tformat:%H #{sha})
75
+ run!(command, :chdir => true).strip == sha
76
+ rescue Posix::CommandFailure => e
77
+ false
78
+ end
79
+
80
+ def checked_out?(sha)
81
+ current_commit_hash == sha
82
+ end
83
+
84
+ def remote_names
85
+ command = %W(remote)
86
+ run!(command, :chdir => true).strip.lines.map(&:strip)
87
+ end
88
+
89
+ def remote_branch_names
90
+ remotes = remote_names.sort_by(&:length).reverse
91
+
92
+ command = %W(branch -r --no-color)
93
+ names = run!(command, :chdir => true).strip.lines.map(&:strip).to_a
94
+ names.each{|n| n.gsub!(/\s*->.*$/, "")}
95
+ names.reject!{|n| n =~ /\/HEAD$/}
96
+ Hash[remotes.map do |r|
97
+ matching_names = names.select{|n| n.start_with?("#{r}/")}
98
+ matching_names.each{|n| names.delete(n)}
99
+ matching_names.each{|n| n.slice!(0, r.size + 1)}
100
+ [r, matching_names]
101
+ end]
102
+ end
103
+
104
+ def hash_from(remote, reference)
105
+ branch_names = remote_branch_names[remote]
106
+ if branch_names.include?(reference)
107
+ reference = "#{remote}/#{reference}"
108
+ end
109
+
110
+ command = %W(rev-list #{reference} -1)
111
+ run!(command, :chdir => true).strip
112
+ end
113
+
114
+ def current_commit_hash
115
+ command = %W(rev-parse HEAD --quiet)
116
+ run!(command, :chdir => true).strip!
117
+ end
118
+
119
+ private
120
+
121
+ def bin
122
+ self.class.bin
123
+ end
124
+
125
+ def run!(args, options = { })
126
+ chdir = options.delete(:chdir)
127
+ chdir = path.to_s if chdir == true
128
+
129
+ silent = options.delete(:silent)
130
+ pwd = chdir || Dir.pwd
131
+ git_dir = File.join(path, ".git") if path
132
+ env = {"GIT_DIR" => git_dir}
133
+
134
+ command = [bin]
135
+ command.concat(args)
136
+
137
+ logging_command(command, :silent => silent, :pwd => pwd) do
138
+ Posix.run!(command, :chdir => chdir, :env => env)
139
+ end
140
+ end
141
+
142
+ def logging_command(command, options)
143
+ silent = options.delete(:silent)
144
+
145
+ pwd = Dir.pwd
146
+
147
+ out = yield
148
+
149
+ git_ops_history << command + [{:pwd => pwd}]
150
+
151
+ unless silent
152
+ if out.size > 0
153
+ out.lines.each do |line|
154
+ debug { " --> #{line}" }
155
+ end
156
+ else
157
+ debug { " --- No output" }
158
+ end
159
+ end
160
+
161
+ out
162
+
163
+ rescue Posix::CommandFailure => e
164
+
165
+ git_ops_history << command + [{:pwd => pwd}]
166
+
167
+ status, stderr = e.status, e.message
168
+ unless silent
169
+ debug { " --- Exited with #{status}" }
170
+ if stderr.size > 0
171
+ stderr.lines.each do |line|
172
+ debug { " --> #{line}" }
173
+ end
174
+ else
175
+ debug { " --- No output" }
176
+ end
177
+ end
178
+
179
+ raise e
180
+ end
181
+
182
+ def debug(*args, &block)
183
+ environment.logger.debug(*args, &block)
184
+ end
185
+
186
+ def relative_path_to(path)
187
+ environment.logger.relative_path_to(path)
188
+ end
189
+
190
+ end
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,172 @@
1
+ require 'fileutils'
2
+ require 'pathname'
3
+ require 'digest'
4
+
5
+ require 'librarian/error'
6
+ require 'librarian/source/basic_api'
7
+ require 'librarian/source/git/repository'
8
+ require 'librarian/source/local'
9
+
10
+ module Librarian
11
+ module Source
12
+ class Git
13
+ include BasicApi
14
+ include Local
15
+
16
+ lock_name 'GIT'
17
+ spec_options [:ref, :path]
18
+
19
+ DEFAULTS = {
20
+ :ref => 'master'
21
+ }
22
+
23
+ attr_accessor :environment
24
+ private :environment=
25
+
26
+ attr_accessor :uri, :ref, :sha, :path
27
+ private :uri=, :ref=, :sha=, :path=
28
+
29
+ def initialize(environment, uri, options)
30
+ self.environment = environment
31
+ self.uri = uri
32
+ self.ref = options[:ref] || DEFAULTS[:ref]
33
+ self.sha = options[:sha]
34
+ self.path = options[:path]
35
+
36
+ @repository = nil
37
+ @repository_cache_path = nil
38
+
39
+ ref.kind_of?(String) or raise TypeError, "ref must be a String"
40
+ end
41
+
42
+ def to_s
43
+ path ? "#{uri}##{ref}(#{path})" : "#{uri}##{ref}"
44
+ end
45
+
46
+ def ==(other)
47
+ other &&
48
+ self.class == other.class &&
49
+ self.uri == other.uri &&
50
+ self.ref == other.ref &&
51
+ self.path == other.path &&
52
+ (self.sha.nil? || other.sha.nil? || self.sha == other.sha)
53
+ end
54
+
55
+ def to_spec_args
56
+ options = {}
57
+ options.merge!(:ref => ref) if ref != DEFAULTS[:ref]
58
+ options.merge!(:path => path) if path
59
+ [uri, options]
60
+ end
61
+
62
+ def to_lock_options
63
+ options = {:remote => uri, :ref => ref, :sha => sha}
64
+ options.merge!(:path => path) if path
65
+ options
66
+ end
67
+
68
+ def pinned?
69
+ !!sha
70
+ end
71
+
72
+ def unpin!
73
+ @sha = nil
74
+ end
75
+
76
+ def cache!
77
+ repository_cached? and return or repository_cached!
78
+
79
+ unless repository.git?
80
+ repository.path.rmtree if repository.path.exist?
81
+ repository.path.mkpath
82
+ repository.clone!(uri)
83
+ raise Error, "failed to clone #{uri}" unless repository.git?
84
+ end
85
+
86
+ # Probably unnecessary: nobody should be writing to our cache but us.
87
+ # Just a precaution.
88
+ repository_clean_once!
89
+
90
+ unless sha
91
+ repository_update_once!
92
+ self.sha = fetch_sha_memo
93
+ end
94
+
95
+ unless repository.checked_out?(sha)
96
+ repository_update_once! unless repository.has_commit?(sha)
97
+ repository.checkout!(sha)
98
+ # Probably unnecessary: if git fails to checkout, it should exit
99
+ # nonzero, and we should expect Librarian::Posix::CommandFailure.
100
+ raise Error, "failed to checkout #{sha}" unless repository.checked_out?(sha)
101
+ end
102
+ end
103
+
104
+ # For tests
105
+ def git_ops_count
106
+ repository.git_ops_history.size
107
+ end
108
+
109
+ private
110
+
111
+ attr_accessor :repository_cached
112
+ alias repository_cached? repository_cached
113
+
114
+ def repository_cached!
115
+ self.repository_cached = true
116
+ end
117
+
118
+ def repository_cache_path
119
+ @repository_cache_path ||= begin
120
+ environment.cache_path + "source/git" + cache_key
121
+ end
122
+ end
123
+
124
+ def repository
125
+ @repository ||= begin
126
+ Repository.new(environment, repository_cache_path)
127
+ end
128
+ end
129
+
130
+ def filesystem_path
131
+ @filesystem_path ||= path ? repository.path.join(path) : repository.path
132
+ end
133
+
134
+ def repository_clean_once!
135
+ remote = repository.default_remote
136
+ runtime_cache.once ['repository-clean', uri, ref].to_s do
137
+ repository.reset_hard!
138
+ repository.clean!
139
+ end
140
+ end
141
+
142
+ def repository_update_once!
143
+ remote = repository.default_remote
144
+ runtime_cache.once ['repository-update', uri, remote, ref].to_s do
145
+ repository.fetch! remote
146
+ repository.fetch! remote, :tags => true
147
+ end
148
+ end
149
+
150
+ def fetch_sha_memo
151
+ remote = repository.default_remote
152
+ runtime_cache.memo ['fetch-sha', uri, remote, ref].to_s do
153
+ repository.hash_from(remote, ref)
154
+ end
155
+ end
156
+
157
+ def cache_key
158
+ @cache_key ||= begin
159
+ uri_part = uri
160
+ ref_part = "##{ref}"
161
+ key_source = [uri_part, ref_part].join
162
+ Digest::MD5.hexdigest(key_source)[0..15]
163
+ end
164
+ end
165
+
166
+ def runtime_cache
167
+ @runtime_cache ||= environment.runtime_cache.keyspace(self.class.name)
168
+ end
169
+
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,54 @@
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
+ # #environment
8
+ module Local
9
+
10
+ include Support::AbstractMethod
11
+
12
+ abstract_method :path, :fetch_version, :fetch_dependencies
13
+
14
+ def manifests(name)
15
+ manifest = Manifest.new(self, name)
16
+ [manifest].compact
17
+ end
18
+
19
+ def manifest_search_paths(name)
20
+ @manifest_search_paths ||= { }
21
+ @manifest_search_paths[name] ||= begin
22
+ cache!
23
+ paths = [filesystem_path, filesystem_path.join(name)]
24
+ paths.select{|s| s.exist?}
25
+ end
26
+ end
27
+
28
+ def found_path(name)
29
+ @_found_paths ||= { }
30
+ @_found_paths[name] ||= begin
31
+ paths = manifest_search_paths(name)
32
+ paths.find{|p| manifest?(name, p)}
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ abstract_method :manifest? # (name, path) -> boolean
39
+
40
+ def info(*args, &block)
41
+ environment.logger.info(*args, &block)
42
+ end
43
+
44
+ def debug(*args, &block)
45
+ environment.logger.debug(*args, &block)
46
+ end
47
+
48
+ def relative_path_to(path)
49
+ environment.logger.relative_path_to(path)
50
+ end
51
+
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,56 @@
1
+ require 'librarian/source/basic_api'
2
+ require 'librarian/source/local'
3
+
4
+ module Librarian
5
+ module Source
6
+ class Path
7
+ include BasicApi
8
+ include Local
9
+
10
+ lock_name 'PATH'
11
+ spec_options []
12
+
13
+ attr_accessor :environment
14
+ private :environment=
15
+ attr_reader :path
16
+
17
+ def initialize(environment, path, options)
18
+ self.environment = environment
19
+ @path = path
20
+ end
21
+
22
+ def to_s
23
+ path.to_s
24
+ end
25
+
26
+ def ==(other)
27
+ other &&
28
+ self.class == other.class &&
29
+ self.path == other.path
30
+ end
31
+
32
+ def to_spec_args
33
+ [path.to_s, {}]
34
+ end
35
+
36
+ def to_lock_options
37
+ {:remote => path}
38
+ end
39
+
40
+ def pinned?
41
+ false
42
+ end
43
+
44
+ def unpin!
45
+ end
46
+
47
+ def cache!
48
+ end
49
+
50
+ def filesystem_path
51
+ @filesystem_path ||= Pathname.new(path).expand_path(environment.project_path)
52
+ end
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,2 @@
1
+ require 'librarian/source/git'
2
+ require 'librarian/source/path'
@@ -0,0 +1,13 @@
1
+ module Librarian
2
+ class Spec
3
+
4
+ attr_accessor :sources, :dependencies
5
+ private :sources=, :dependencies=
6
+
7
+ def initialize(sources, dependencies)
8
+ self.sources = sources
9
+ self.dependencies = dependencies
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,173 @@
1
+ require 'librarian/helpers'
2
+
3
+ require 'librarian/manifest_set'
4
+ require 'librarian/resolution'
5
+ require 'librarian/spec'
6
+
7
+ module Librarian
8
+ class SpecChangeSet
9
+
10
+ attr_accessor :environment
11
+ private :environment=
12
+ attr_reader :spec, :lock
13
+
14
+ def initialize(environment, spec, lock)
15
+ self.environment = environment
16
+ raise TypeError, "can't convert #{spec.class} into #{Spec}" unless Spec === spec
17
+ raise TypeError, "can't convert #{lock.class} into #{Resolution}" unless Resolution === lock
18
+ @spec, @lock = spec, lock
19
+ end
20
+
21
+ def same?
22
+ @same ||= spec.dependencies.sort_by{|d| d.name} == lock.dependencies.sort_by{|d| d.name}
23
+ end
24
+
25
+ def changed?
26
+ !same?
27
+ end
28
+
29
+ def spec_dependencies
30
+ @spec_dependencies ||= spec.dependencies
31
+ end
32
+ def spec_dependency_names
33
+ @spec_dependency_names ||= Set.new(spec_dependencies.map{|d| d.name})
34
+ end
35
+ def spec_dependency_index
36
+ @spec_dependency_index ||= Hash[spec_dependencies.map{|d| [d.name, d]}]
37
+ end
38
+
39
+ def lock_dependencies
40
+ @lock_dependencies ||= lock.dependencies
41
+ end
42
+ def lock_dependency_names
43
+ @lock_dependency_names ||= Set.new(lock_dependencies.map{|d| d.name})
44
+ end
45
+ def lock_dependency_index
46
+ @lock_dependency_index ||= Hash[lock_dependencies.map{|d| [d.name, d]}]
47
+ end
48
+
49
+ def lock_manifests
50
+ @lock_manifests ||= lock.manifests
51
+ end
52
+ def lock_manifests_index
53
+ @lock_manifests_index ||= ManifestSet.new(lock_manifests).to_hash
54
+ end
55
+
56
+ def removed_dependency_names
57
+ @removed_dependency_names ||= lock_dependency_names - spec_dependency_names
58
+ end
59
+
60
+ # A dependency which is deleted from the specfile will, in the general case,
61
+ # be removed conservatively. This means it might not actually be removed.
62
+ # But if the dependency originally declared a source which is now non-
63
+ # default, it must be removed, even if another dependency has a transitive
64
+ # dependency on the one that was removed (which is the scenario in which
65
+ # a conservative removal would not remove it). In this case, we must also
66
+ # remove it explicitly so that it can be re-resolved from the default
67
+ # source.
68
+ def explicit_removed_dependency_names
69
+ @explicit_removed_dependency_names ||= removed_dependency_names.reject do |name|
70
+ lock_manifest = lock_manifests_index[name]
71
+ spec.sources.include?(lock_manifest.source)
72
+ end.to_set
73
+ end
74
+
75
+ def added_dependency_names
76
+ @added_dependency_names ||= spec_dependency_names - lock_dependency_names
77
+ end
78
+
79
+ def nonmatching_added_dependency_names
80
+ @nonmatching_added_dependency_names ||= added_dependency_names.reject do |name|
81
+ spec_dependency = spec_dependency_index[name]
82
+ lock_manifest = lock_manifests_index[name]
83
+ if lock_manifest
84
+ matching = true
85
+ matching &&= spec_dependency.satisfied_by?(lock_manifest)
86
+ matching &&= spec_dependency.source == lock_manifest.source
87
+ matching
88
+ else
89
+ false
90
+ end
91
+ end.to_set
92
+ end
93
+
94
+ def common_dependency_names
95
+ @common_dependency_names ||= lock_dependency_names & spec_dependency_names
96
+ end
97
+
98
+ def changed_dependency_names
99
+ @changed_dependency_names ||= common_dependency_names.reject do |name|
100
+ spec_dependency = spec_dependency_index[name]
101
+ lock_dependency = lock_dependency_index[name]
102
+ lock_manifest = lock_manifests_index[name]
103
+ same = true
104
+ same &&= spec_dependency.satisfied_by?(lock_manifest)
105
+ same &&= spec_dependency.source == lock_dependency.source
106
+ same
107
+ end.to_set
108
+ end
109
+
110
+ def deep_keep_manifest_names
111
+ @deep_keep_manifest_names ||= begin
112
+ lock_dependency_names - (
113
+ removed_dependency_names +
114
+ changed_dependency_names +
115
+ nonmatching_added_dependency_names
116
+ )
117
+ end
118
+ end
119
+
120
+ def shallow_strip_manifest_names
121
+ @shallow_strip_manifest_names ||= begin
122
+ explicit_removed_dependency_names + changed_dependency_names
123
+ end
124
+ end
125
+
126
+ def inspect
127
+ Helpers.strip_heredoc(<<-INSPECT)
128
+ <##{self.class.name}:
129
+ Removed: #{removed_dependency_names.to_a.join(", ")}
130
+ ExplicitRemoved: #{explicit_removed_dependency_names.to_a.join(", ")}
131
+ Added: #{added_dependency_names.to_a.join(", ")}
132
+ NonMatchingAdded: #{nonmatching_added_dependency_names.to_a.join(", ")}
133
+ Changed: #{changed_dependency_names.to_a.join(", ")}
134
+ DeepKeep: #{deep_keep_manifest_names.to_a.join(", ")}
135
+ ShallowStrip: #{shallow_strip_manifest_names.to_a.join(", ")}
136
+ >
137
+ INSPECT
138
+ end
139
+
140
+ # Returns an array of those manifests from the previous spec which should be kept,
141
+ # based on inspecting the new spec against the locked resolution from the previous spec.
142
+ def analyze
143
+ @analyze ||= begin
144
+ debug { "Analyzing spec and lock:" }
145
+
146
+ if same?
147
+ debug { " Same!" }
148
+ return lock.manifests
149
+ end
150
+
151
+ debug { " Removed:" } ; removed_dependency_names.each { |name| debug { " #{name}" } }
152
+ debug { " ExplicitRemoved:" } ; explicit_removed_dependency_names.each { |name| debug { " #{name}" } }
153
+ debug { " Added:" } ; added_dependency_names.each { |name| debug { " #{name}" } }
154
+ debug { " NonMatchingAdded:" } ; nonmatching_added_dependency_names.each { |name| debug { " #{name}" } }
155
+ debug { " Changed:" } ; changed_dependency_names.each { |name| debug { " #{name}" } }
156
+ debug { " DeepKeep:" } ; deep_keep_manifest_names.each { |name| debug { " #{name}" } }
157
+ debug { " ShallowStrip:" } ; shallow_strip_manifest_names.each { |name| debug { " #{name}" } }
158
+
159
+ manifests = ManifestSet.new(lock_manifests)
160
+ manifests.deep_keep!(deep_keep_manifest_names)
161
+ manifests.shallow_strip!(shallow_strip_manifest_names)
162
+ manifests.to_a
163
+ end
164
+ end
165
+
166
+ private
167
+
168
+ def debug(*args, &block)
169
+ environment.logger.debug(*args, &block)
170
+ end
171
+
172
+ end
173
+ end
@@ -0,0 +1,19 @@
1
+ require "pathname"
2
+
3
+ module Librarian
4
+ class Specfile
5
+
6
+ attr_accessor :environment, :path
7
+ private :environment=, :path=
8
+
9
+ def initialize(environment, path)
10
+ self.environment = environment
11
+ self.path = Pathname(path)
12
+ end
13
+
14
+ def read(precache_sources = [])
15
+ environment.dsl(path, precache_sources)
16
+ end
17
+
18
+ end
19
+ 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