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.
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)