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.
- checksums.yaml +5 -5
- data/ARCHITECTURE.md +102 -0
- data/CHANGELOG.md +352 -0
- data/lib/molinillo.rb +2 -0
- data/lib/molinillo/compatibility.rb +26 -0
- data/lib/molinillo/delegates/resolution_state.rb +7 -0
- data/lib/molinillo/delegates/specification_provider.rb +1 -0
- data/lib/molinillo/dependency_graph.rb +3 -2
- data/lib/molinillo/dependency_graph/action.rb +1 -0
- data/lib/molinillo/dependency_graph/add_edge_no_circular.rb +1 -0
- data/lib/molinillo/dependency_graph/add_vertex.rb +1 -0
- data/lib/molinillo/dependency_graph/delete_edge.rb +1 -0
- data/lib/molinillo/dependency_graph/detach_vertex_named.rb +1 -0
- data/lib/molinillo/dependency_graph/log.rb +1 -0
- data/lib/molinillo/dependency_graph/set_payload.rb +1 -0
- data/lib/molinillo/dependency_graph/tag.rb +1 -0
- data/lib/molinillo/dependency_graph/vertex.rb +3 -2
- data/lib/molinillo/errors.rb +69 -6
- data/lib/molinillo/gem_metadata.rb +2 -1
- data/lib/molinillo/modules/specification_provider.rb +1 -0
- data/lib/molinillo/modules/ui.rb +3 -1
- data/lib/molinillo/resolution.rb +495 -145
- data/lib/molinillo/resolver.rb +1 -0
- data/lib/molinillo/state.rb +8 -4
- metadata +8 -30
- data/spec/dependency_graph/log_spec.rb +0 -29
- data/spec/dependency_graph_spec.rb +0 -74
- data/spec/errors_spec.rb +0 -26
- data/spec/fuzz_spec.rb +0 -94
- data/spec/resolver_integration_specs/index_from_rubygems.rb +0 -76
- data/spec/resolver_spec.rb +0 -235
- data/spec/spec_helper.rb +0 -44
- data/spec/spec_helper/equal_dependency_graph.rb +0 -16
- data/spec/spec_helper/index.rb +0 -186
- data/spec/spec_helper/naive_resolver.rb +0 -48
- data/spec/spec_helper/specification.rb +0 -28
- data/spec/spec_helper/ui.rb +0 -14
- data/spec/state_spec.rb +0 -30
data/spec/spec_helper.rb
DELETED
@@ -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
|
data/spec/spec_helper/index.rb
DELETED
@@ -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
|
data/spec/spec_helper/ui.rb
DELETED
data/spec/state_spec.rb
DELETED
@@ -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
|