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.
Files changed (100) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +10 -0
  5. data/CHANGELOG.md +255 -0
  6. data/Gemfile +8 -0
  7. data/Gemfile.lock +235 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +55 -0
  10. data/Rakefile +28 -0
  11. data/VERSION +1 -0
  12. data/lib/librarian/action/base.rb +24 -0
  13. data/lib/librarian/action/clean.rb +44 -0
  14. data/lib/librarian/action/ensure.rb +24 -0
  15. data/lib/librarian/action/install.rb +95 -0
  16. data/lib/librarian/action/persist_resolution_mixin.rb +51 -0
  17. data/lib/librarian/action/resolve.rb +46 -0
  18. data/lib/librarian/action/update.rb +44 -0
  19. data/lib/librarian/action.rb +5 -0
  20. data/lib/librarian/algorithms.rb +133 -0
  21. data/lib/librarian/cli/manifest_presenter.rb +89 -0
  22. data/lib/librarian/cli.rb +225 -0
  23. data/lib/librarian/config/database.rb +205 -0
  24. data/lib/librarian/config/file_source.rb +47 -0
  25. data/lib/librarian/config/hash_source.rb +33 -0
  26. data/lib/librarian/config/source.rb +149 -0
  27. data/lib/librarian/config.rb +7 -0
  28. data/lib/librarian/dependency.rb +153 -0
  29. data/lib/librarian/dsl/receiver.rb +42 -0
  30. data/lib/librarian/dsl/target.rb +171 -0
  31. data/lib/librarian/dsl.rb +102 -0
  32. data/lib/librarian/environment/runtime_cache.rb +101 -0
  33. data/lib/librarian/environment.rb +230 -0
  34. data/lib/librarian/error.rb +4 -0
  35. data/lib/librarian/helpers.rb +29 -0
  36. data/lib/librarian/linter/source_linter.rb +55 -0
  37. data/lib/librarian/lockfile/compiler.rb +66 -0
  38. data/lib/librarian/lockfile/parser.rb +123 -0
  39. data/lib/librarian/lockfile.rb +29 -0
  40. data/lib/librarian/logger.rb +46 -0
  41. data/lib/librarian/manifest.rb +146 -0
  42. data/lib/librarian/manifest_set.rb +150 -0
  43. data/lib/librarian/mock/cli.rb +19 -0
  44. data/lib/librarian/mock/dsl.rb +15 -0
  45. data/lib/librarian/mock/environment.rb +21 -0
  46. data/lib/librarian/mock/extension.rb +9 -0
  47. data/lib/librarian/mock/source/mock/registry.rb +83 -0
  48. data/lib/librarian/mock/source/mock.rb +80 -0
  49. data/lib/librarian/mock/source.rb +1 -0
  50. data/lib/librarian/mock/version.rb +5 -0
  51. data/lib/librarian/mock.rb +1 -0
  52. data/lib/librarian/posix.rb +129 -0
  53. data/lib/librarian/resolution.rb +46 -0
  54. data/lib/librarian/resolver/implementation.rb +238 -0
  55. data/lib/librarian/resolver.rb +94 -0
  56. data/lib/librarian/rspec/support/cli_macro.rb +120 -0
  57. data/lib/librarian/source/basic_api.rb +45 -0
  58. data/lib/librarian/source/git/repository.rb +193 -0
  59. data/lib/librarian/source/git.rb +172 -0
  60. data/lib/librarian/source/local.rb +54 -0
  61. data/lib/librarian/source/path.rb +56 -0
  62. data/lib/librarian/source.rb +2 -0
  63. data/lib/librarian/spec.rb +13 -0
  64. data/lib/librarian/spec_change_set.rb +173 -0
  65. data/lib/librarian/specfile.rb +19 -0
  66. data/lib/librarian/support/abstract_method.rb +21 -0
  67. data/lib/librarian/ui.rb +64 -0
  68. data/lib/librarian/version.rb +3 -0
  69. data/lib/librarian.rb +11 -0
  70. data/librarian.gemspec +47 -0
  71. data/spec/functional/cli_spec.rb +27 -0
  72. data/spec/functional/posix_spec.rb +32 -0
  73. data/spec/functional/source/git/repository_spec.rb +199 -0
  74. data/spec/functional/source/git_spec.rb +174 -0
  75. data/spec/support/fakefs.rb +37 -0
  76. data/spec/support/method_patch_macro.rb +30 -0
  77. data/spec/support/project_path_macro.rb +14 -0
  78. data/spec/support/with_env_macro.rb +22 -0
  79. data/spec/unit/action/base_spec.rb +18 -0
  80. data/spec/unit/action/clean_spec.rb +102 -0
  81. data/spec/unit/action/ensure_spec.rb +37 -0
  82. data/spec/unit/action/install_spec.rb +111 -0
  83. data/spec/unit/algorithms_spec.rb +131 -0
  84. data/spec/unit/config/database_spec.rb +320 -0
  85. data/spec/unit/dependency/requirement_spec.rb +12 -0
  86. data/spec/unit/dependency_spec.rb +212 -0
  87. data/spec/unit/dsl_spec.rb +173 -0
  88. data/spec/unit/environment/runtime_cache_spec.rb +73 -0
  89. data/spec/unit/environment_spec.rb +209 -0
  90. data/spec/unit/lockfile/parser_spec.rb +162 -0
  91. data/spec/unit/lockfile_spec.rb +65 -0
  92. data/spec/unit/manifest/version_spec.rb +11 -0
  93. data/spec/unit/manifest_set_spec.rb +202 -0
  94. data/spec/unit/manifest_spec.rb +36 -0
  95. data/spec/unit/mock/environment_spec.rb +25 -0
  96. data/spec/unit/mock/source/mock_spec.rb +22 -0
  97. data/spec/unit/resolver_spec.rb +299 -0
  98. data/spec/unit/source/git_spec.rb +29 -0
  99. data/spec/unit/spec_change_set_spec.rb +169 -0
  100. metadata +257 -0
@@ -0,0 +1,44 @@
1
+ require "librarian/action/base"
2
+
3
+ module Librarian
4
+ module Action
5
+ class Clean < Base
6
+
7
+ def run
8
+ clean_cache_path
9
+ clean_install_path
10
+ end
11
+
12
+ private
13
+
14
+ def clean_cache_path
15
+ if cache_path.exist?
16
+ debug { "Deleting #{project_relative_path_to(cache_path)}" }
17
+ cache_path.rmtree
18
+ end
19
+ end
20
+
21
+ def clean_install_path
22
+ if install_path.exist?
23
+ install_path.children.each do |c|
24
+ debug { "Deleting #{project_relative_path_to(c)}" }
25
+ c.rmtree unless c.file?
26
+ end
27
+ end
28
+ end
29
+
30
+ def cache_path
31
+ environment.cache_path
32
+ end
33
+
34
+ def install_path
35
+ environment.install_path
36
+ end
37
+
38
+ def project_relative_path_to(path)
39
+ environment.project_relative_path_to(path)
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,24 @@
1
+ require "librarian/error"
2
+ require "librarian/action/base"
3
+
4
+ module Librarian
5
+ module Action
6
+ class Ensure < Base
7
+
8
+ def run
9
+ raise Error, "Cannot find #{specfile_name}!" unless project_path
10
+ end
11
+
12
+ private
13
+
14
+ def specfile_name
15
+ environment.specfile_name
16
+ end
17
+
18
+ def project_path
19
+ environment.project_path
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,95 @@
1
+ require "librarian/manifest_set"
2
+ require "librarian/spec_change_set"
3
+ require "librarian/action/base"
4
+
5
+ module Librarian
6
+ module Action
7
+ class Install < Base
8
+
9
+ def run
10
+ check_preconditions
11
+
12
+ perform_installation
13
+ end
14
+
15
+ private
16
+
17
+ def check_preconditions
18
+ check_specfile
19
+ check_lockfile
20
+ check_consistent
21
+ end
22
+
23
+ def check_specfile
24
+ raise Error, "#{specfile_name} missing!" unless specfile_path.exist?
25
+ end
26
+
27
+ def check_lockfile
28
+ raise Error, "#{lockfile_name} missing!" unless lockfile_path.exist?
29
+ end
30
+
31
+ def check_consistent
32
+ raise Error, "#{specfile_name} and #{lockfile_name} are out of sync!" unless spec_consistent_with_lock?
33
+ end
34
+
35
+ def perform_installation
36
+ manifests = sorted_manifests
37
+
38
+ create_install_path
39
+ install_manifests(manifests)
40
+ end
41
+
42
+ def create_install_path
43
+ install_path.rmtree if install_path.exist?
44
+ install_path.mkpath
45
+ end
46
+
47
+ def install_manifests(manifests)
48
+ manifests.each do |manifest|
49
+ manifest.install!
50
+ end
51
+ end
52
+
53
+ def sorted_manifests
54
+ ManifestSet.sort(lock.manifests)
55
+ end
56
+
57
+ def specfile_name
58
+ environment.specfile_name
59
+ end
60
+
61
+ def specfile_path
62
+ environment.specfile_path
63
+ end
64
+
65
+ def lockfile_name
66
+ environment.lockfile_name
67
+ end
68
+
69
+ def lockfile_path
70
+ environment.lockfile_path
71
+ end
72
+
73
+ def spec
74
+ environment.spec
75
+ end
76
+
77
+ def lock
78
+ environment.lock
79
+ end
80
+
81
+ def spec_change_set(spec, lock)
82
+ SpecChangeSet.new(environment, spec, lock)
83
+ end
84
+
85
+ def spec_consistent_with_lock?
86
+ spec_change_set(spec, lock).same?
87
+ end
88
+
89
+ def install_path
90
+ environment.install_path
91
+ end
92
+
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,51 @@
1
+ require "librarian/error"
2
+ require "librarian/spec_change_set"
3
+
4
+ module Librarian
5
+ module Action
6
+ module PersistResolutionMixin
7
+
8
+ private
9
+
10
+ def persist_resolution(resolution)
11
+ resolution && resolution.correct? or raise Error,
12
+ "Could not resolve the dependencies."
13
+
14
+ lockfile_text = lockfile.save(resolution)
15
+ debug { "Bouncing #{lockfile_name}" }
16
+ bounced_lockfile_text = lockfile.save(lockfile.load(lockfile_text))
17
+ unless bounced_lockfile_text == lockfile_text
18
+ debug { "lockfile_text: \n#{lockfile_text}" }
19
+ debug { "bounced_lockfile_text: \n#{bounced_lockfile_text}" }
20
+ raise Error, "Cannot bounce #{lockfile_name}!"
21
+ end
22
+ lockfile_path.open('wb') { |f| f.write(lockfile_text) }
23
+ end
24
+
25
+ def specfile_name
26
+ environment.specfile_name
27
+ end
28
+
29
+ def lockfile_name
30
+ environment.lockfile_name
31
+ end
32
+
33
+ def specfile_path
34
+ environment.specfile_path
35
+ end
36
+
37
+ def lockfile_path
38
+ environment.lockfile_path
39
+ end
40
+
41
+ def specfile
42
+ environment.specfile
43
+ end
44
+
45
+ def lockfile
46
+ environment.lockfile
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,46 @@
1
+ require "librarian/resolver"
2
+ require "librarian/spec_change_set"
3
+ require "librarian/action/base"
4
+ require "librarian/action/persist_resolution_mixin"
5
+
6
+ module Librarian
7
+ module Action
8
+ class Resolve < Base
9
+ include PersistResolutionMixin
10
+
11
+ def run
12
+ if force? || !lockfile_path.exist?
13
+ spec = specfile.read
14
+ manifests = []
15
+ else
16
+ lock = lockfile.read
17
+ spec = specfile.read(lock.sources)
18
+ changes = spec_change_set(spec, lock)
19
+ if changes.same?
20
+ debug { "The specfile is unchanged: nothing to do." }
21
+ return
22
+ end
23
+ manifests = changes.analyze
24
+ end
25
+
26
+ resolution = resolver.resolve(spec, manifests)
27
+ persist_resolution(resolution)
28
+ end
29
+
30
+ private
31
+
32
+ def force?
33
+ options[:force]
34
+ end
35
+
36
+ def resolver
37
+ Resolver.new(environment)
38
+ end
39
+
40
+ def spec_change_set(spec, lock)
41
+ SpecChangeSet.new(environment, spec, lock)
42
+ end
43
+
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,44 @@
1
+ require "librarian/manifest_set"
2
+ require "librarian/resolver"
3
+ require "librarian/spec_change_set"
4
+ require "librarian/action/base"
5
+ require "librarian/action/persist_resolution_mixin"
6
+
7
+ module Librarian
8
+ module Action
9
+ class Update < Base
10
+ include PersistResolutionMixin
11
+
12
+ def run
13
+ unless lockfile_path.exist?
14
+ raise Error, "Lockfile missing!"
15
+ end
16
+ previous_resolution = lockfile.load(lockfile_path.read)
17
+ spec = specfile.read(previous_resolution.sources)
18
+ changes = spec_change_set(spec, previous_resolution)
19
+ manifests = changes.same? ? previous_resolution.manifests : changes.analyze
20
+ partial_manifests = ManifestSet.deep_strip(manifests, dependency_names)
21
+ unpinnable_sources = previous_resolution.sources - partial_manifests.map(&:source)
22
+ unpinnable_sources.each(&:unpin!)
23
+
24
+ resolution = resolver.resolve(spec, partial_manifests)
25
+ persist_resolution(resolution)
26
+ end
27
+
28
+ private
29
+
30
+ def dependency_names
31
+ options[:names]
32
+ end
33
+
34
+ def resolver
35
+ Resolver.new(environment)
36
+ end
37
+
38
+ def spec_change_set(spec, lock)
39
+ SpecChangeSet.new(environment, spec, lock)
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,5 @@
1
+ require "librarian/action/clean"
2
+ require "librarian/action/ensure"
3
+ require "librarian/action/install"
4
+ require "librarian/action/resolve"
5
+ require "librarian/action/update"
@@ -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
@@ -0,0 +1,89 @@
1
+ module Librarian
2
+ class Cli
3
+ class ManifestPresenter
4
+
5
+ attr_accessor :cli, :manifests
6
+ private :cli=, :manifests=
7
+
8
+ def initialize(cli, manifests)
9
+ self.cli = cli or raise ArgumentError, "cli required"
10
+ self.manifests = manifests or raise ArgumentError, "manifests required"
11
+ self.manifests_index = Hash[manifests.map{|m| [m.name, m]}]
12
+
13
+ self.scope_level = 0
14
+ end
15
+
16
+ def present(names = [], options = { })
17
+ full = options[:detailed]
18
+ full = !names.empty? if full.nil?
19
+
20
+ names = manifests.map(&:name).sort if names.empty?
21
+ assert_no_manifests_missing!(names)
22
+
23
+ present_each(names, :detailed => full)
24
+ end
25
+
26
+ def present_one(manifest, options = { })
27
+ full = options[:detailed]
28
+
29
+ say "#{manifest.name} (#{manifest.version})" do
30
+ full or next
31
+
32
+ present_one_source(manifest)
33
+ present_one_dependencies(manifest)
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def present_each(names, options)
40
+ names.each do |name|
41
+ manifest = manifest(name)
42
+ present_one(manifest, options)
43
+ end
44
+ end
45
+
46
+ def present_one_source(manifest)
47
+ say "source: #{manifest.source}"
48
+ end
49
+
50
+ def present_one_dependencies(manifest)
51
+ manifest.dependencies.empty? and return
52
+
53
+ say "dependencies:" do
54
+ deps = manifest.dependencies.sort_by(&:name)
55
+ deps.each do |dependency|
56
+ say "#{dependency.name} (#{dependency.requirement})"
57
+ end
58
+ end
59
+ end
60
+
61
+ attr_accessor :scope_level, :manifests_index
62
+
63
+ def manifest(name)
64
+ manifests_index[name]
65
+ end
66
+
67
+ def say(string)
68
+ cli.say " " * scope_level << string
69
+ scope { yield } if block_given?
70
+ end
71
+
72
+ def scope
73
+ original_scope_level = scope_level
74
+ self.scope_level = scope_level + 1
75
+ yield
76
+ ensure
77
+ self.scope_level = original_scope_level
78
+ end
79
+
80
+ def assert_no_manifests_missing!(names)
81
+ missing_names = names.reject{|name| manifest(name)}
82
+ unless missing_names.empty?
83
+ raise Error, "not found: #{missing_names.map(&:inspect).join(', ')}"
84
+ end
85
+ end
86
+
87
+ end
88
+ end
89
+ end