librarian 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -2
- data/CHANGELOG.md +15 -0
- data/Gemfile +2 -0
- data/VERSION +1 -0
- data/lib/librarian/algorithms.rb +133 -0
- data/lib/librarian/cli/manifest_presenter.rb +1 -5
- data/lib/librarian/dependency.rb +7 -1
- data/lib/librarian/environment.rb +20 -2
- data/lib/librarian/environment/runtime_cache.rb +101 -0
- data/lib/librarian/manifest.rb +7 -1
- data/lib/librarian/manifest_set.rb +11 -12
- data/lib/librarian/posix.rb +14 -5
- data/lib/librarian/resolver.rb +22 -9
- data/lib/librarian/resolver/implementation.rb +64 -49
- data/lib/librarian/source/git.rb +47 -11
- data/lib/librarian/source/git/repository.rb +33 -3
- data/lib/librarian/version.rb +1 -1
- data/librarian.gemspec +8 -6
- data/spec/functional/cli_spec.rb +1 -1
- data/spec/functional/posix_spec.rb +6 -8
- data/spec/functional/source/git/repository_spec.rb +55 -27
- data/spec/functional/source/git_spec.rb +152 -8
- data/spec/support/project_path_macro.rb +14 -0
- data/spec/unit/action/base_spec.rb +1 -1
- data/spec/unit/action/clean_spec.rb +6 -6
- data/spec/unit/action/install_spec.rb +5 -5
- data/spec/unit/algorithms_spec.rb +131 -0
- data/spec/unit/config/database_spec.rb +38 -38
- data/spec/unit/dependency/requirement_spec.rb +12 -0
- data/spec/unit/dsl_spec.rb +49 -49
- data/spec/unit/environment/runtime_cache_spec.rb +73 -0
- data/spec/unit/environment_spec.rb +28 -28
- data/spec/unit/lockfile/parser_spec.rb +18 -18
- data/spec/unit/lockfile_spec.rb +3 -3
- data/spec/unit/manifest/version_spec.rb +11 -0
- data/spec/unit/manifest_set_spec.rb +20 -20
- data/spec/unit/mock/environment_spec.rb +4 -4
- data/spec/unit/resolver_spec.rb +61 -20
- data/spec/unit/spec_change_set_spec.rb +19 -19
- metadata +19 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fddbebc33025412f23b5f1eb199eb9dafb89ee8c
|
4
|
+
data.tar.gz: 6c39767d153ee125b535f620aa70a8b7af1bc00c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 734712ab6b294554583731f8c7a023ac1414ab548166f7908eca04262887880e98a1e75e8e3a37d80a9ae0420ac955c44f8b642b45355408a4bc351195d39463
|
7
|
+
data.tar.gz: c1dc05cf757f64da608313b4aab5f7b29afaaf056015fc910b3efe3467c81f05df386f58368cec2fbc04b7540dca6afae86c2da40fddfedaf42fbf8885d1f17a
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,20 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
## 0.1.2
|
4
|
+
|
5
|
+
* \#153. Mark the license in the gemspec.
|
6
|
+
|
7
|
+
* \#158, \#160, \#159. Handle cyclic dependencies for adapters which opt in to
|
8
|
+
cyclic dependencies.
|
9
|
+
|
10
|
+
* \#154. Cache git repository sources which are identical but for their :path
|
11
|
+
attributes (where to look inside the repository for the manifest). Thanks
|
12
|
+
@bcatlin.
|
13
|
+
|
14
|
+
* \#161. Fix JRuby exception in posix run! method. Thanks @justenwalker.
|
15
|
+
|
16
|
+
* \#163. Don't fetch git repositories multiple times per run. Thanks @njam.
|
17
|
+
|
3
18
|
## 0.1.1
|
4
19
|
|
5
20
|
* \#147. Handle the case where HOME is not in the environment. Thanks @bradleyd.
|
data/Gemfile
CHANGED
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.2
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require "set"
|
2
|
+
require "tsort"
|
3
|
+
|
4
|
+
module Librarian
|
5
|
+
module Algorithms
|
6
|
+
|
7
|
+
class GraphHash < Hash
|
8
|
+
include TSort
|
9
|
+
def tsort_each_node(&block)
|
10
|
+
keys.sort.each(&block) # demand determinism
|
11
|
+
end
|
12
|
+
def tsort_each_child(node, &block)
|
13
|
+
children = self[node]
|
14
|
+
children && children.sort.each(&block) # demand determinism
|
15
|
+
end
|
16
|
+
class << self
|
17
|
+
def from(hash)
|
18
|
+
o = new
|
19
|
+
hash.each{|k, v| o[k] = v}
|
20
|
+
o
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module AdjacencyListDirectedGraph
|
26
|
+
extend self
|
27
|
+
|
28
|
+
def cyclic?(graph)
|
29
|
+
each_cyclic_strongly_connected_component_set(graph).any?
|
30
|
+
end
|
31
|
+
|
32
|
+
# Topological sort of the graph with an approximately minimal feedback arc
|
33
|
+
# set removed.
|
34
|
+
def tsort_cyclic(graph)
|
35
|
+
fag = feedback_arc_graph(graph)
|
36
|
+
reduced_graph = subtract_edges_graph(graph, fag)
|
37
|
+
GraphHash.from(reduced_graph).tsort
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns an approximately minimal feedback arc set, lifted into a graph.
|
41
|
+
def feedback_arc_graph(graph)
|
42
|
+
edges_to_graph(feedback_arc_set(graph))
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns an approximately minimal feedback arc set.
|
46
|
+
def feedback_arc_set(graph)
|
47
|
+
fas = feedback_arc_set_step0(graph)
|
48
|
+
feedback_arc_set_step1(graph, fas)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def edges_to_graph(edges)
|
54
|
+
graph = {}
|
55
|
+
edges.each do |(u, v)|
|
56
|
+
graph[u] ||= []
|
57
|
+
graph[u] << v
|
58
|
+
graph[v] ||= nil
|
59
|
+
end
|
60
|
+
graph
|
61
|
+
end
|
62
|
+
|
63
|
+
def subtract_edges_graph(graph, edges_graph)
|
64
|
+
xgraph = {}
|
65
|
+
graph.each do |n, vs|
|
66
|
+
dests = edges_graph[n]
|
67
|
+
xgraph[n] = !vs ? vs : !dests ? vs : vs - dests
|
68
|
+
end
|
69
|
+
xgraph
|
70
|
+
end
|
71
|
+
|
72
|
+
def each_cyclic_strongly_connected_component_set(graph)
|
73
|
+
return enum_for(__method__, graph) unless block_given?
|
74
|
+
GraphHash.from(graph).each_strongly_connected_component do |scc|
|
75
|
+
if scc.size == 1
|
76
|
+
n = scc.first
|
77
|
+
vs = graph[n] or next
|
78
|
+
vs.include?(n) or next
|
79
|
+
end
|
80
|
+
yield scc
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Partitions the graph into its strongly connected component subgraphs,
|
85
|
+
# removes the acyclic single-vertex components (multi-vertex components
|
86
|
+
# are guaranteed to be cyclic), and yields each cyclic strongly connected
|
87
|
+
# component.
|
88
|
+
def each_cyclic_strongly_connected_component_graph(graph)
|
89
|
+
return enum_for(__method__, graph) unless block_given?
|
90
|
+
each_cyclic_strongly_connected_component_set(graph) do |scc|
|
91
|
+
sccs = scc.to_set
|
92
|
+
sccg = GraphHash.new
|
93
|
+
scc.each do |n|
|
94
|
+
vs = graph[n]
|
95
|
+
sccg[n] = vs && vs.select{|v| sccs.include?(v)}
|
96
|
+
end
|
97
|
+
yield sccg
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# The 0th step of computing a feedback arc set: pick out vertices whose
|
102
|
+
# removals will make the graph acyclic.
|
103
|
+
def feedback_arc_set_step0(graph)
|
104
|
+
fas = []
|
105
|
+
each_cyclic_strongly_connected_component_graph(graph) do |scc|
|
106
|
+
scc.keys.sort.each do |n| # demand determinism
|
107
|
+
vs = scc[n] or next
|
108
|
+
vs.size > 0 or next
|
109
|
+
vs.sort! # demand determinism
|
110
|
+
fas << [n, vs.shift]
|
111
|
+
break
|
112
|
+
end
|
113
|
+
end
|
114
|
+
fas
|
115
|
+
end
|
116
|
+
|
117
|
+
# The 1st step of computing a feedback arc set: pick out vertices from the
|
118
|
+
# 0th step whose removals turn out to be unnecessary.
|
119
|
+
def feedback_arc_set_step1(graph, fas)
|
120
|
+
reduced_graph = subtract_edges_graph(graph, edges_to_graph(fas))
|
121
|
+
fas.select do |(u, v)|
|
122
|
+
reduced_graph[u] ||= []
|
123
|
+
reduced_graph[u] << v
|
124
|
+
cyclic = cyclic?(reduced_graph)
|
125
|
+
reduced_graph[u].pop if cyclic
|
126
|
+
cyclic
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
end
|
data/lib/librarian/dependency.rb
CHANGED
@@ -26,6 +26,10 @@ module Librarian
|
|
26
26
|
to_gem_requirement.to_s
|
27
27
|
end
|
28
28
|
|
29
|
+
def inspect
|
30
|
+
"#<#{self.class} #{to_s}>"
|
31
|
+
end
|
32
|
+
|
29
33
|
COMPATS_TABLE = {
|
30
34
|
%w(= = ) => lambda{|s, o| s == o},
|
31
35
|
%w(= !=) => lambda{|s, o| s != o},
|
@@ -140,7 +144,9 @@ module Librarian
|
|
140
144
|
private
|
141
145
|
|
142
146
|
def assert_name_valid!(name)
|
143
|
-
|
147
|
+
name =~ /\A\S(?:.*\S)?\z/ and return
|
148
|
+
|
149
|
+
raise ArgumentError, "name (#{name.inspect}) must be sensible"
|
144
150
|
end
|
145
151
|
|
146
152
|
end
|
@@ -3,6 +3,7 @@ require 'net/http'
|
|
3
3
|
require "uri"
|
4
4
|
require "etc"
|
5
5
|
|
6
|
+
require "librarian/helpers"
|
6
7
|
require "librarian/support/abstract_method"
|
7
8
|
|
8
9
|
require "librarian/error"
|
@@ -14,6 +15,7 @@ require "librarian/resolver"
|
|
14
15
|
require "librarian/dsl"
|
15
16
|
require "librarian/source"
|
16
17
|
require "librarian/version"
|
18
|
+
require "librarian/environment/runtime_cache"
|
17
19
|
|
18
20
|
module Librarian
|
19
21
|
class Environment
|
@@ -21,6 +23,7 @@ module Librarian
|
|
21
23
|
include Support::AbstractMethod
|
22
24
|
|
23
25
|
attr_accessor :ui
|
26
|
+
attr_reader :runtime_cache
|
24
27
|
|
25
28
|
abstract_method :specfile_name, :dsl_class, :install_path
|
26
29
|
|
@@ -29,6 +32,7 @@ module Librarian
|
|
29
32
|
@env = options.fetch(:env) { ENV.to_hash }
|
30
33
|
@home = options.fetch(:home) { default_home }
|
31
34
|
@project_path = options[:project_path]
|
35
|
+
@runtime_cache = RuntimeCache.new
|
32
36
|
end
|
33
37
|
|
34
38
|
def logger
|
@@ -101,8 +105,18 @@ module Librarian
|
|
101
105
|
Lockfile.new(self, nil)
|
102
106
|
end
|
103
107
|
|
104
|
-
def resolver
|
105
|
-
Resolver.new(self)
|
108
|
+
def resolver(options = { })
|
109
|
+
Resolver.new(self, resolver_options.merge(options))
|
110
|
+
end
|
111
|
+
|
112
|
+
def resolver_options
|
113
|
+
{
|
114
|
+
:cyclic => resolver_permit_cyclic_reslutions?,
|
115
|
+
}
|
116
|
+
end
|
117
|
+
|
118
|
+
def resolver_permit_cyclic_reslutions?
|
119
|
+
false
|
106
120
|
end
|
107
121
|
|
108
122
|
def tmp_path
|
@@ -174,6 +188,10 @@ module Librarian
|
|
174
188
|
no_proxy?(host) ? Net::HTTP : net_http_default_class
|
175
189
|
end
|
176
190
|
|
191
|
+
def inspect
|
192
|
+
"#<#{self.class}:0x#{__id__.to_s(16)}>"
|
193
|
+
end
|
194
|
+
|
177
195
|
private
|
178
196
|
|
179
197
|
def environment
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require "librarian/error"
|
2
|
+
|
3
|
+
module Librarian
|
4
|
+
class Environment
|
5
|
+
class RuntimeCache
|
6
|
+
|
7
|
+
class KeyspaceCache
|
8
|
+
|
9
|
+
class << self
|
10
|
+
private
|
11
|
+
|
12
|
+
def delegate_to_backing_cache(*methods)
|
13
|
+
methods.each do |method|
|
14
|
+
define_method "#{method}" do |*args, &block|
|
15
|
+
# TODO: When we drop ruby-1.8.7 support, use #public_send.
|
16
|
+
runtime_cache.send(method, keyspace, *args, &block)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :runtime_cache, :keyspace
|
23
|
+
|
24
|
+
def initialize(runtime_cache, keyspace)
|
25
|
+
self.runtime_cache = runtime_cache
|
26
|
+
self.keyspace = keyspace
|
27
|
+
end
|
28
|
+
|
29
|
+
delegate_to_backing_cache *[
|
30
|
+
:include?,
|
31
|
+
:get,
|
32
|
+
:put,
|
33
|
+
:delete,
|
34
|
+
:memo,
|
35
|
+
:once,
|
36
|
+
:[],
|
37
|
+
:[]=,
|
38
|
+
]
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
attr_writer :runtime_cache, :keyspace
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize
|
47
|
+
self.data = {}
|
48
|
+
end
|
49
|
+
|
50
|
+
def include?(keyspace, key)
|
51
|
+
data.include?(combined_key(keyspace, key))
|
52
|
+
end
|
53
|
+
|
54
|
+
def get(keyspace, key)
|
55
|
+
data[combined_key(keyspace, key)]
|
56
|
+
end
|
57
|
+
|
58
|
+
def put(keyspace, key, value = nil)
|
59
|
+
data[combined_key(keyspace, key)] = block_given? ? yield : value
|
60
|
+
end
|
61
|
+
|
62
|
+
def delete(keyspace, key)
|
63
|
+
data.delete(combined_key(keyspace, key))
|
64
|
+
end
|
65
|
+
|
66
|
+
def memo(keyspace, key)
|
67
|
+
put(keyspace, key, yield) unless include?(keyspace, key)
|
68
|
+
get(keyspace, key)
|
69
|
+
end
|
70
|
+
|
71
|
+
def once(keyspace, key)
|
72
|
+
memo(keyspace, key) { yield ; nil }
|
73
|
+
end
|
74
|
+
|
75
|
+
def [](keyspace, key)
|
76
|
+
get(keyspace, key)
|
77
|
+
end
|
78
|
+
|
79
|
+
def []=(keyspace, key, value)
|
80
|
+
put(keyspace, key, value)
|
81
|
+
end
|
82
|
+
|
83
|
+
def keyspace(keyspace)
|
84
|
+
KeyspaceCache.new(self, keyspace)
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
attr_accessor :data
|
90
|
+
|
91
|
+
def combined_key(keyspace, key)
|
92
|
+
keyspace.kind_of?(String) or raise Error, "keyspace must be a string"
|
93
|
+
keyspace.size > 0 or raise Error, "keyspace must not be empty"
|
94
|
+
keyspace.size < 2**16 or raise Error, "keyspace must not be too large"
|
95
|
+
key.kind_of?(String) or raise Error, "key must be a string"
|
96
|
+
[keyspace.size.to_s(16).rjust(4, "0"), keyspace, key].join
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
data/lib/librarian/manifest.rb
CHANGED
@@ -24,6 +24,10 @@ module Librarian
|
|
24
24
|
to_gem_version.to_s
|
25
25
|
end
|
26
26
|
|
27
|
+
def inspect
|
28
|
+
"#<#{self.class} #{to_s}>"
|
29
|
+
end
|
30
|
+
|
27
31
|
private
|
28
32
|
|
29
33
|
def initialize_normalize_args(args)
|
@@ -133,7 +137,9 @@ module Librarian
|
|
133
137
|
end
|
134
138
|
|
135
139
|
def assert_name_valid!(name)
|
136
|
-
|
140
|
+
name =~ /\A\S(?:.*\S)?\z/ and return
|
141
|
+
|
142
|
+
raise ArgumentError, "name (#{name.inspect}) must be sensible"
|
137
143
|
end
|
138
144
|
|
139
145
|
end
|
@@ -1,17 +1,8 @@
|
|
1
|
-
require
|
2
|
-
require 'tsort'
|
1
|
+
require "librarian/algorithms"
|
3
2
|
|
4
3
|
module Librarian
|
5
4
|
class ManifestSet
|
6
5
|
|
7
|
-
class GraphHash < Hash
|
8
|
-
include TSort
|
9
|
-
alias tsort_each_node each_key
|
10
|
-
def tsort_each_child(node, &block)
|
11
|
-
self[node].each(&block)
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
6
|
class << self
|
16
7
|
def shallow_strip(manifests, names)
|
17
8
|
new(manifests).shallow_strip!(names).send(method_for(manifests))
|
@@ -25,10 +16,15 @@ module Librarian
|
|
25
16
|
def deep_keep(manifests, names)
|
26
17
|
new(manifests).deep_keep!(names).send(method_for(manifests))
|
27
18
|
end
|
19
|
+
def cyclic?(manifests)
|
20
|
+
manifests = Hash[manifests.map{|m| [m.name, m]}] if Array === manifests
|
21
|
+
manifest_pairs = Hash[manifests.map{|k, m| [k, m.dependencies.map{|d| d.name}]}]
|
22
|
+
adj_algs.cyclic?(manifest_pairs)
|
23
|
+
end
|
28
24
|
def sort(manifests)
|
29
25
|
manifests = Hash[manifests.map{|m| [m.name, m]}] if Array === manifests
|
30
|
-
manifest_pairs =
|
31
|
-
manifest_names = manifest_pairs
|
26
|
+
manifest_pairs = Hash[manifests.map{|k, m| [k, m.dependencies.map{|d| d.name}]}]
|
27
|
+
manifest_names = adj_algs.tsort_cyclic(manifest_pairs)
|
32
28
|
manifest_names.map{|n| manifests[n]}
|
33
29
|
end
|
34
30
|
private
|
@@ -40,6 +36,9 @@ module Librarian
|
|
40
36
|
:to_a
|
41
37
|
end
|
42
38
|
end
|
39
|
+
def adj_algs
|
40
|
+
Algorithms::AdjacencyListDirectedGraph
|
41
|
+
end
|
43
42
|
end
|
44
43
|
|
45
44
|
def initialize(manifests)
|