librarian 0.1.1 → 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 +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)
|