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.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.rspec +1 -0
- data/.travis.yml +10 -0
- data/CHANGELOG.md +255 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +235 -0
- data/LICENSE.txt +22 -0
- data/README.md +55 -0
- data/Rakefile +28 -0
- data/VERSION +1 -0
- data/lib/librarian/action/base.rb +24 -0
- data/lib/librarian/action/clean.rb +44 -0
- data/lib/librarian/action/ensure.rb +24 -0
- data/lib/librarian/action/install.rb +95 -0
- data/lib/librarian/action/persist_resolution_mixin.rb +51 -0
- data/lib/librarian/action/resolve.rb +46 -0
- data/lib/librarian/action/update.rb +44 -0
- data/lib/librarian/action.rb +5 -0
- data/lib/librarian/algorithms.rb +133 -0
- data/lib/librarian/cli/manifest_presenter.rb +89 -0
- data/lib/librarian/cli.rb +225 -0
- data/lib/librarian/config/database.rb +205 -0
- data/lib/librarian/config/file_source.rb +47 -0
- data/lib/librarian/config/hash_source.rb +33 -0
- data/lib/librarian/config/source.rb +149 -0
- data/lib/librarian/config.rb +7 -0
- data/lib/librarian/dependency.rb +153 -0
- data/lib/librarian/dsl/receiver.rb +42 -0
- data/lib/librarian/dsl/target.rb +171 -0
- data/lib/librarian/dsl.rb +102 -0
- data/lib/librarian/environment/runtime_cache.rb +101 -0
- data/lib/librarian/environment.rb +230 -0
- data/lib/librarian/error.rb +4 -0
- data/lib/librarian/helpers.rb +29 -0
- data/lib/librarian/linter/source_linter.rb +55 -0
- data/lib/librarian/lockfile/compiler.rb +66 -0
- data/lib/librarian/lockfile/parser.rb +123 -0
- data/lib/librarian/lockfile.rb +29 -0
- data/lib/librarian/logger.rb +46 -0
- data/lib/librarian/manifest.rb +146 -0
- data/lib/librarian/manifest_set.rb +150 -0
- data/lib/librarian/mock/cli.rb +19 -0
- data/lib/librarian/mock/dsl.rb +15 -0
- data/lib/librarian/mock/environment.rb +21 -0
- data/lib/librarian/mock/extension.rb +9 -0
- data/lib/librarian/mock/source/mock/registry.rb +83 -0
- data/lib/librarian/mock/source/mock.rb +80 -0
- data/lib/librarian/mock/source.rb +1 -0
- data/lib/librarian/mock/version.rb +5 -0
- data/lib/librarian/mock.rb +1 -0
- data/lib/librarian/posix.rb +129 -0
- data/lib/librarian/resolution.rb +46 -0
- data/lib/librarian/resolver/implementation.rb +238 -0
- data/lib/librarian/resolver.rb +94 -0
- data/lib/librarian/rspec/support/cli_macro.rb +120 -0
- data/lib/librarian/source/basic_api.rb +45 -0
- data/lib/librarian/source/git/repository.rb +193 -0
- data/lib/librarian/source/git.rb +172 -0
- data/lib/librarian/source/local.rb +54 -0
- data/lib/librarian/source/path.rb +56 -0
- data/lib/librarian/source.rb +2 -0
- data/lib/librarian/spec.rb +13 -0
- data/lib/librarian/spec_change_set.rb +173 -0
- data/lib/librarian/specfile.rb +19 -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/lib/librarian.rb +11 -0
- data/librarian.gemspec +47 -0
- data/spec/functional/cli_spec.rb +27 -0
- data/spec/functional/posix_spec.rb +32 -0
- data/spec/functional/source/git/repository_spec.rb +199 -0
- data/spec/functional/source/git_spec.rb +174 -0
- data/spec/support/fakefs.rb +37 -0
- data/spec/support/method_patch_macro.rb +30 -0
- data/spec/support/project_path_macro.rb +14 -0
- data/spec/support/with_env_macro.rb +22 -0
- data/spec/unit/action/base_spec.rb +18 -0
- data/spec/unit/action/clean_spec.rb +102 -0
- data/spec/unit/action/ensure_spec.rb +37 -0
- data/spec/unit/action/install_spec.rb +111 -0
- data/spec/unit/algorithms_spec.rb +131 -0
- data/spec/unit/config/database_spec.rb +320 -0
- data/spec/unit/dependency/requirement_spec.rb +12 -0
- data/spec/unit/dependency_spec.rb +212 -0
- data/spec/unit/dsl_spec.rb +173 -0
- data/spec/unit/environment/runtime_cache_spec.rb +73 -0
- data/spec/unit/environment_spec.rb +209 -0
- data/spec/unit/lockfile/parser_spec.rb +162 -0
- data/spec/unit/lockfile_spec.rb +65 -0
- data/spec/unit/manifest/version_spec.rb +11 -0
- data/spec/unit/manifest_set_spec.rb +202 -0
- data/spec/unit/manifest_spec.rb +36 -0
- data/spec/unit/mock/environment_spec.rb +25 -0
- data/spec/unit/mock/source/mock_spec.rb +22 -0
- data/spec/unit/resolver_spec.rb +299 -0
- data/spec/unit/source/git_spec.rb +29 -0
- data/spec/unit/spec_change_set_spec.rb +169 -0
- 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,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
|