molinillo 0.5.7 → 0.6.0

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 (38) hide show
  1. checksums.yaml +5 -5
  2. data/ARCHITECTURE.md +102 -0
  3. data/CHANGELOG.md +352 -0
  4. data/lib/molinillo.rb +2 -0
  5. data/lib/molinillo/compatibility.rb +26 -0
  6. data/lib/molinillo/delegates/resolution_state.rb +7 -0
  7. data/lib/molinillo/delegates/specification_provider.rb +1 -0
  8. data/lib/molinillo/dependency_graph.rb +3 -2
  9. data/lib/molinillo/dependency_graph/action.rb +1 -0
  10. data/lib/molinillo/dependency_graph/add_edge_no_circular.rb +1 -0
  11. data/lib/molinillo/dependency_graph/add_vertex.rb +1 -0
  12. data/lib/molinillo/dependency_graph/delete_edge.rb +1 -0
  13. data/lib/molinillo/dependency_graph/detach_vertex_named.rb +1 -0
  14. data/lib/molinillo/dependency_graph/log.rb +1 -0
  15. data/lib/molinillo/dependency_graph/set_payload.rb +1 -0
  16. data/lib/molinillo/dependency_graph/tag.rb +1 -0
  17. data/lib/molinillo/dependency_graph/vertex.rb +3 -2
  18. data/lib/molinillo/errors.rb +69 -6
  19. data/lib/molinillo/gem_metadata.rb +2 -1
  20. data/lib/molinillo/modules/specification_provider.rb +1 -0
  21. data/lib/molinillo/modules/ui.rb +3 -1
  22. data/lib/molinillo/resolution.rb +495 -145
  23. data/lib/molinillo/resolver.rb +1 -0
  24. data/lib/molinillo/state.rb +8 -4
  25. metadata +8 -30
  26. data/spec/dependency_graph/log_spec.rb +0 -29
  27. data/spec/dependency_graph_spec.rb +0 -74
  28. data/spec/errors_spec.rb +0 -26
  29. data/spec/fuzz_spec.rb +0 -94
  30. data/spec/resolver_integration_specs/index_from_rubygems.rb +0 -76
  31. data/spec/resolver_spec.rb +0 -235
  32. data/spec/spec_helper.rb +0 -44
  33. data/spec/spec_helper/equal_dependency_graph.rb +0 -16
  34. data/spec/spec_helper/index.rb +0 -186
  35. data/spec/spec_helper/naive_resolver.rb +0 -48
  36. data/spec/spec_helper/specification.rb +0 -28
  37. data/spec/spec_helper/ui.rb +0 -14
  38. data/spec/state_spec.rb +0 -30
@@ -1,44 +0,0 @@
1
- # frozen_string_literal: true
2
- require 'bundler/setup'
3
-
4
- # Set up coverage analysis
5
- #-----------------------------------------------------------------------------#
6
-
7
- if (ENV['CI'] || ENV['GENERATE_COVERAGE']) && RUBY_VERSION >= '2.0.0' && Bundler.current_ruby.mri?
8
- require 'simplecov'
9
- require 'codeclimate-test-reporter'
10
-
11
- if ENV['CI']
12
- SimpleCov.formatter = CodeClimate::TestReporter::Formatter
13
- elsif ENV['GENERATE_COVERAGE']
14
- SimpleCov.formatter = SimpleCov::Formatter::HTMLFormatter
15
- end
16
- SimpleCov.start do
17
- add_filter '/vendor/'
18
- add_filter '/lib/molinillo/modules/'
19
- end
20
- CodeClimate::TestReporter.start
21
- end
22
-
23
- # Set up
24
- #-----------------------------------------------------------------------------#
25
-
26
- require 'pathname'
27
- require 'json'
28
- ROOT = Pathname.new(File.expand_path('../../', __FILE__))
29
- $LOAD_PATH.unshift((ROOT + 'lib').to_s)
30
- $LOAD_PATH.unshift((ROOT + 'spec').to_s)
31
-
32
- require 'molinillo'
33
-
34
- require 'spec_helper/index'
35
- require 'spec_helper/specification'
36
- require 'spec_helper/ui'
37
- require 'spec_helper/equal_dependency_graph'
38
-
39
- RSpec.configure do |config|
40
- # Enable flags like --only-failures and --next-failure
41
- config.example_status_persistence_file_path = '.rspec_status'
42
- config.filter_run :focus => true
43
- config.run_all_when_everything_filtered = true
44
- end
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
- RSpec::Matchers.define :equal_dependency_graph do |expected|
3
- diffable
4
- attr_reader :actual, :expected
5
-
6
- match do |actual|
7
- eql = actual == expected
8
- @expected = expected.to_dot(:edge_label => proc { |e| e.destination.payload.version })
9
- @actual = actual.to_dot(:edge_label => proc { |e| e.destination.payload.version })
10
- eql
11
- end
12
-
13
- failure_message do
14
- 'Expected the two dependency graphs to be equal'
15
- end
16
- end
@@ -1,186 +0,0 @@
1
- # frozen_string_literal: true
2
- module Molinillo
3
- FIXTURE_DIR = Pathname.new('spec/resolver_integration_specs')
4
- FIXTURE_INDEX_DIR = FIXTURE_DIR + 'index'
5
-
6
- class TestIndex
7
- attr_accessor :specs
8
- include SpecificationProvider
9
-
10
- def self.from_fixture(fixture_name)
11
- File.open(FIXTURE_INDEX_DIR + (fixture_name + '.json'), 'r') do |fixture|
12
- sorted_specs = JSON.load(fixture).reduce(Hash.new([])) do |specs_by_name, (name, versions)|
13
- specs_by_name.tap do |specs|
14
- specs[name] = versions.map { |s| TestSpecification.new s }.sort_by(&:version)
15
- end
16
- end
17
- return new(sorted_specs)
18
- end
19
- end
20
-
21
- def initialize(specs_by_name)
22
- self.specs = specs_by_name
23
- end
24
-
25
- def requirement_satisfied_by?(requirement, _activated, spec)
26
- case requirement
27
- when TestSpecification
28
- requirement.version == spec.version
29
- when Gem::Dependency
30
- requirement.requirement.satisfied_by?(spec.version)
31
- end
32
- end
33
-
34
- def search_for(dependency)
35
- @search_for ||= {}
36
- @search_for[dependency] ||= begin
37
- prerelease = dependency_prerelease?(dependency)
38
- Array(specs[dependency.name]).select do |spec|
39
- (prerelease ? true : !spec.version.prerelease?) &&
40
- dependency.requirement.satisfied_by?(spec.version)
41
- end
42
- end
43
- @search_for[dependency].dup
44
- end
45
-
46
- def name_for(dependency)
47
- dependency.name
48
- end
49
-
50
- def dependencies_for(dependency)
51
- dependency.dependencies
52
- end
53
-
54
- def sort_dependencies(dependencies, activated, conflicts)
55
- dependencies.sort_by do |d|
56
- [
57
- activated.vertex_named(d.name).payload ? 0 : 1,
58
- dependency_prerelease?(d) ? 0 : 1,
59
- conflicts[d.name] ? 0 : 1,
60
- activated.vertex_named(d.name).payload ? 0 : search_for(d).count,
61
- ]
62
- end
63
- end
64
-
65
- private
66
-
67
- def dependency_prerelease?(dependency)
68
- dependency.prerelease?
69
- end
70
- end
71
-
72
- class BundlerIndex < TestIndex
73
- # Some bugs we want to write a regression test for only occurs when
74
- # Molinillo processes dependencies in a specific order for the given
75
- # index and demands. This sorting logic ensures we hit the repro case
76
- def sort_dependencies(dependencies, activated, conflicts)
77
- dependencies.sort_by do |dependency|
78
- name = name_for(dependency)
79
- [
80
- activated.vertex_named(name).payload ? 0 : 1,
81
- amount_constrained(dependency),
82
- conflicts[name] ? 0 : 1,
83
- activated.vertex_named(name).payload ? 0 : search_for(dependency).count,
84
- ]
85
- end
86
- end
87
-
88
- def amount_constrained(dependency)
89
- @amount_constrained ||= {}
90
- @amount_constrained[dependency.name] ||= begin
91
- all = specs[dependency.name].size
92
- if all <= 1
93
- all - all_leq_one_penalty
94
- else
95
- search = search_for(dependency).size
96
- search - all
97
- end
98
- end
99
- end
100
-
101
- def all_leq_one_penalty
102
- 1_000_000
103
- end
104
- end
105
-
106
- class BundlerSingleAllNoPenaltyIndex < BundlerIndex
107
- def all_leq_one_penalty
108
- 0
109
- end
110
- end
111
-
112
- class ReverseBundlerIndex < BundlerIndex
113
- def sort_dependencies(*)
114
- super.reverse
115
- end
116
- end
117
-
118
- class RandomSortIndex < TestIndex
119
- def sort_dependencies(dependencies, _activated, _conflicts)
120
- dependencies.shuffle
121
- end
122
- end
123
-
124
- class CocoaPodsIndex < TestIndex
125
- def sort_dependencies(dependencies, activated, conflicts)
126
- dependencies.sort_by do |d|
127
- [
128
- activated.vertex_named(d.name).payload ? 0 : 1,
129
- dependency_prerelease?(d) ? 0 : 1,
130
- conflicts[d.name] ? 0 : 1,
131
- search_for(d).count,
132
- ]
133
- end
134
- end
135
-
136
- def requirement_satisfied_by?(requirement, activated, spec)
137
- requirement = case requirement
138
- when TestSpecification
139
- Gem::Dependency.new(requirement.name, requirement.version)
140
- when Gem::Dependency
141
- requirement
142
- end
143
-
144
- existing_vertices = activated.vertices.values.select do |v|
145
- v.name.split('/').first == requirement.name.split('/').first
146
- end
147
- existing = existing_vertices.map(&:payload).compact.first
148
- if existing
149
- existing.version == spec.version && requirement.requirement.satisfied_by?(spec.version)
150
- else
151
- requirement.requirement.satisfied_by? spec.version
152
- end
153
- end
154
- end
155
-
156
- class BerkshelfIndex < TestIndex
157
- # The bug we want to write a regression test for only occurs when
158
- # Molinillo processes dependencies in a specific order for the given
159
- # index and demands. This sorting logic ensures we hit the repro case
160
- # when using the index file "swap_child_with_successors"
161
- def sort_dependencies(dependencies, activated, conflicts)
162
- dependencies.sort_by do |dependency|
163
- name = name_for(dependency)
164
- [
165
- activated.vertex_named(name).payload ? 0 : 1,
166
- conflicts[name] ? 0 : 1,
167
- activated.vertex_named(name).payload ? 0 : versions_of(name),
168
- ]
169
- end
170
- end
171
-
172
- def versions_of(dependency_name)
173
- Array(specs[dependency_name]).count
174
- end
175
- end
176
-
177
- INDICES = [
178
- TestIndex,
179
- BundlerIndex,
180
- ReverseBundlerIndex,
181
- BundlerSingleAllNoPenaltyIndex,
182
- # RandomSortIndex, this isn't yet always passing
183
- CocoaPodsIndex,
184
- BerkshelfIndex,
185
- ].freeze
186
- end
@@ -1,48 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # rubocop:disable Metrics/CyclomaticComplexity
4
- # rubocop:disable Metrics/LineLength
5
- # rubocop:disable Style/Semicolon
6
-
7
- module Molinillo
8
- class NaiveResolver
9
- def self.resolve(index, dependencies)
10
- activated = Molinillo::DependencyGraph.new
11
- level = 0
12
- dependencies.each { |d| activated.add_child_vertex(d.name, nil, [nil], d) }
13
- activated.tag(level)
14
- possibilities_by_level = {}
15
- loop do
16
- vertex = activated.find { |a| !a.requirements.empty? && a.payload.nil? }
17
- break unless vertex
18
- possibilities = possibilities_by_level[level] ||= index.search_for(Gem::Dependency.new(vertex.name, '>= 0.0.0-a'))
19
- possibilities.select! do |spec|
20
- vertex.requirements.all? { |r| r.requirement.satisfied_by?(spec.version) && (!spec.version.prerelease? || index.send(:dependency_prerelease?, r)) } &&
21
- spec.dependencies.all? { |d| v = activated.vertex_named(d.name); !v || !v.payload || d.satisfied_by?(v.payload.version) }
22
- end
23
- warn "level = #{level} possibilities = #{possibilities.map(&:to_s)} requirements = #{vertex.requirements.map(&:to_s)}"
24
- if spec = possibilities.pop
25
- warn "trying #{spec}"
26
- activated.set_payload(vertex.name, spec)
27
- spec.dependencies.each do |d|
28
- activated.add_child_vertex(d.name, nil, [spec.name], d)
29
- end
30
- level += 1
31
- warn "tagging level #{level}"
32
- activated.tag(level)
33
- next
34
- end
35
- level = possibilities_by_level.reverse_each.find(proc { [-1, nil] }) { |_l, p| !p.empty? }.first
36
- warn "going back to level #{level}"
37
- possibilities_by_level.reject! { |l, _| l > level }
38
- return nil if level < 0
39
- activated.rewind_to(level)
40
- activated.tag(level)
41
- end
42
-
43
- activated
44
- end
45
-
46
- def self.warn(*); end
47
- end
48
- end
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
- module Molinillo
3
- class TestSpecification
4
- attr_accessor :name, :version, :dependencies
5
- def initialize(hash)
6
- self.name = hash['name']
7
- self.version = Gem::Version.new(hash['version'])
8
- self.dependencies = hash.fetch('dependencies') { Hash.new }.map do |(name, requirement)|
9
- requirements = requirement.split(',').map(&:chomp)
10
- Gem::Dependency.new(name, requirements)
11
- end
12
- end
13
-
14
- def ==(other)
15
- name == other.name &&
16
- version == other.version &&
17
- dependencies == other.dependencies
18
- end
19
-
20
- def to_s
21
- "#{name} (#{version})"
22
- end
23
-
24
- def inspect
25
- "#<#{self.class} name=#{name} version=#{version} dependencies=[#{dependencies.join(', ')}]>"
26
- end
27
- end
28
- end
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
- module Molinillo
3
- class TestUI
4
- include UI
5
-
6
- def output
7
- @output ||= if debug?
8
- $stderr
9
- else
10
- File.open('/dev/null', 'w')
11
- end
12
- end
13
- end
14
- end
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
- require File.expand_path('../spec_helper', __FILE__)
3
-
4
- module Molinillo
5
- describe ResolutionState do
6
- describe DependencyState do
7
- before do
8
- @state = described_class.new(
9
- 'name',
10
- %w(requirement1 requirement2 requirement3),
11
- DependencyGraph.new,
12
- 'requirement',
13
- %w(possibility1 possibility),
14
- 0,
15
- {}
16
- )
17
- end
18
-
19
- it 'pops a possibility state' do
20
- possibility_state = @state.pop_possibility_state
21
- %w(name requirements activated requirement conflicts).each do |attr|
22
- expect(possibility_state.send(attr)).to eq(@state.send(attr))
23
- end
24
- expect(possibility_state).to be_a(PossibilityState)
25
- expect(possibility_state.depth).to eq(@state.depth + 1)
26
- expect(possibility_state.possibilities).to eq(%w(possibility))
27
- end
28
- end
29
- end
30
- end