librarian 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -2
  3. data/CHANGELOG.md +15 -0
  4. data/Gemfile +2 -0
  5. data/VERSION +1 -0
  6. data/lib/librarian/algorithms.rb +133 -0
  7. data/lib/librarian/cli/manifest_presenter.rb +1 -5
  8. data/lib/librarian/dependency.rb +7 -1
  9. data/lib/librarian/environment.rb +20 -2
  10. data/lib/librarian/environment/runtime_cache.rb +101 -0
  11. data/lib/librarian/manifest.rb +7 -1
  12. data/lib/librarian/manifest_set.rb +11 -12
  13. data/lib/librarian/posix.rb +14 -5
  14. data/lib/librarian/resolver.rb +22 -9
  15. data/lib/librarian/resolver/implementation.rb +64 -49
  16. data/lib/librarian/source/git.rb +47 -11
  17. data/lib/librarian/source/git/repository.rb +33 -3
  18. data/lib/librarian/version.rb +1 -1
  19. data/librarian.gemspec +8 -6
  20. data/spec/functional/cli_spec.rb +1 -1
  21. data/spec/functional/posix_spec.rb +6 -8
  22. data/spec/functional/source/git/repository_spec.rb +55 -27
  23. data/spec/functional/source/git_spec.rb +152 -8
  24. data/spec/support/project_path_macro.rb +14 -0
  25. data/spec/unit/action/base_spec.rb +1 -1
  26. data/spec/unit/action/clean_spec.rb +6 -6
  27. data/spec/unit/action/install_spec.rb +5 -5
  28. data/spec/unit/algorithms_spec.rb +131 -0
  29. data/spec/unit/config/database_spec.rb +38 -38
  30. data/spec/unit/dependency/requirement_spec.rb +12 -0
  31. data/spec/unit/dsl_spec.rb +49 -49
  32. data/spec/unit/environment/runtime_cache_spec.rb +73 -0
  33. data/spec/unit/environment_spec.rb +28 -28
  34. data/spec/unit/lockfile/parser_spec.rb +18 -18
  35. data/spec/unit/lockfile_spec.rb +3 -3
  36. data/spec/unit/manifest/version_spec.rb +11 -0
  37. data/spec/unit/manifest_set_spec.rb +20 -20
  38. data/spec/unit/mock/environment_spec.rb +4 -4
  39. data/spec/unit/resolver_spec.rb +61 -20
  40. data/spec/unit/spec_change_set_spec.rb +19 -19
  41. metadata +19 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e028910687d6a6448f8f51929c7048701c10c768
4
- data.tar.gz: f3d3ea0d6bffa41369b7bd224a2a93d5fe314fc8
3
+ metadata.gz: fddbebc33025412f23b5f1eb199eb9dafb89ee8c
4
+ data.tar.gz: 6c39767d153ee125b535f620aa70a8b7af1bc00c
5
5
  SHA512:
6
- metadata.gz: b5b069d30eb1d8a067d7f7cd95d04e92570776e4ee7f08f9a6c4d572b0f3bc25026f5324d5686933bafed0e96247e23bea499f90c49a7c93397abc14241ddc9d
7
- data.tar.gz: 02c8c5b25f217db0d2188cc2e4f48675fce74f9ff29ceb1fc30ea894c5ca511fd61c4e59a9e40a76c863c3307eeea5fa2f8344f281db086b3a4ce7d2ae158383
6
+ metadata.gz: 734712ab6b294554583731f8c7a023ac1414ab548166f7908eca04262887880e98a1e75e8e3a37d80a9ae0420ac955c44f8b642b45355408a4bc351195d39463
7
+ data.tar.gz: c1dc05cf757f64da608313b4aab5f7b29afaaf056015fc910b3efe3467c81f05df386f58368cec2fbc04b7540dca6afae86c2da40fddfedaf42fbf8885d1f17a
@@ -5,8 +5,7 @@ rvm:
5
5
  - 1.9.2
6
6
  - 1.9.3
7
7
  - 2.0.0
8
- - rbx-18mode
9
- - rbx-19mode
8
+ - rbx-2.2
10
9
  - jruby-18mode
11
10
  - jruby-19mode
12
11
  - ruby-head
@@ -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
@@ -4,3 +4,5 @@ source "http://rubygems.org"
4
4
  gemspec
5
5
 
6
6
  gem "fakefs"
7
+
8
+ gem "rubysl", :platforms => %w[rbx]
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
@@ -66,11 +66,7 @@ module Librarian
66
66
 
67
67
  def say(string)
68
68
  cli.say " " * scope_level << string
69
- if block_given?
70
- scope do
71
- yield
72
- end
73
- end
69
+ scope { yield } if block_given?
74
70
  end
75
71
 
76
72
  def scope
@@ -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
- raise ArgumentError, "name (#{name.inspect}) must be sensible" unless name =~ /\A\S(?:.*\S)?\z/
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
@@ -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
- raise ArgumentError, "name (#{name.inspect}) must be sensible" unless name =~ /\A\S(?:.*\S)?\z/
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 'set'
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 = GraphHash[manifests.map{|k, m| [k, m.dependencies.map{|d| d.name}]}]
31
- manifest_names = manifest_pairs.tsort
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)