pub_grub 0.1.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 +7 -0
- data/.gitignore +8 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +22 -0
- data/LICENSE.txt +21 -0
- data/README.md +28 -0
- data/Rakefile +10 -0
- data/bin/console +24 -0
- data/bin/setup +8 -0
- data/lib/pub_grub.rb +18 -0
- data/lib/pub_grub/assignment.rb +20 -0
- data/lib/pub_grub/failure_writer.rb +179 -0
- data/lib/pub_grub/incompatibility.rb +109 -0
- data/lib/pub_grub/package.rb +79 -0
- data/lib/pub_grub/partial_solution.rb +125 -0
- data/lib/pub_grub/solve_failure.rb +13 -0
- data/lib/pub_grub/static_package_source.rb +83 -0
- data/lib/pub_grub/term.rb +104 -0
- data/lib/pub_grub/version_constraint.rb +162 -0
- data/lib/pub_grub/version_solver.rb +209 -0
- data/pub_grub.gemspec +26 -0
- metadata +108 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
require 'rubygems/requirement'
|
|
2
|
+
|
|
3
|
+
module PubGrub
|
|
4
|
+
class VersionConstraint
|
|
5
|
+
attr_reader :package, :constraint
|
|
6
|
+
|
|
7
|
+
# @param package [PubGrub::Package]
|
|
8
|
+
# @param constraint [String]
|
|
9
|
+
def initialize(package, constraint = nil, bitmap: nil)
|
|
10
|
+
@package = package
|
|
11
|
+
@constraint = Array(constraint)
|
|
12
|
+
@bitmap = bitmap # Calculated lazily
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.exact(version)
|
|
16
|
+
package = version.package
|
|
17
|
+
new(package, version.name, bitmap: bitmap_matching(package) { |v| v == version })
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.any(package)
|
|
21
|
+
new(package, nil, bitmap: (1 << package.versions.count) - 1)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.bitmap_matching(package)
|
|
25
|
+
package.versions.select do |version|
|
|
26
|
+
yield version
|
|
27
|
+
end.inject(0) do |acc, version|
|
|
28
|
+
acc | (1 << version.id)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def bitmap
|
|
33
|
+
return @bitmap if @bitmap
|
|
34
|
+
|
|
35
|
+
# TODO: Should not be hardcoded to rubygems semantics
|
|
36
|
+
requirement = Gem::Requirement.new(constraint)
|
|
37
|
+
@bitmap = self.class.bitmap_matching(package) do |version|
|
|
38
|
+
requirement.satisfied_by?(Gem::Version.new(version.name))
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def intersect(other)
|
|
43
|
+
unless package == other.package
|
|
44
|
+
raise ArgumentError, "Can only intersect between VersionConstraint of the same package"
|
|
45
|
+
end
|
|
46
|
+
if bitmap == other.bitmap
|
|
47
|
+
self
|
|
48
|
+
else
|
|
49
|
+
self.class.new(package, constraint + other.constraint, bitmap: bitmap & other.bitmap)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def union(other)
|
|
54
|
+
unless package == other.package
|
|
55
|
+
raise ArgumentError, "Can only intersect between VersionConstraint of the same package"
|
|
56
|
+
end
|
|
57
|
+
if bitmap == other.bitmap
|
|
58
|
+
self
|
|
59
|
+
else
|
|
60
|
+
self.class.new(package, "#{constraint_string} OR #{other.constraint_string}", bitmap: bitmap | other.bitmap)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def invert
|
|
65
|
+
new_bitmap = bitmap ^ ((1 << package.versions.length) - 1)
|
|
66
|
+
new_constraint =
|
|
67
|
+
if constraint.length == 0
|
|
68
|
+
["not >= 0"]
|
|
69
|
+
elsif constraint.length == 1
|
|
70
|
+
["not #{constraint[0]}"]
|
|
71
|
+
else
|
|
72
|
+
["not (#{constraint_string})"]
|
|
73
|
+
end
|
|
74
|
+
self.class.new(package, new_constraint, bitmap: new_bitmap)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def difference(other)
|
|
78
|
+
intersect(other.invert)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def versions
|
|
82
|
+
package.versions.select do |version|
|
|
83
|
+
bitmap[version.id] == 1
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
if RUBY_VERSION >= "2.5"
|
|
88
|
+
def allows_all?(other)
|
|
89
|
+
bitmap.allbits?(other.bitmap)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def allows_any?(other)
|
|
93
|
+
bitmap.anybits?(other.bitmap)
|
|
94
|
+
end
|
|
95
|
+
else
|
|
96
|
+
def allows_all?(other)
|
|
97
|
+
other_bitmap = other.bitmap
|
|
98
|
+
(bitmap & other_bitmap) == other_bitmap
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def allows_any?(other)
|
|
102
|
+
(bitmap & other.bitmap) != 0
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def subset?(other)
|
|
107
|
+
other.allows_all?(self)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def overlap?(other)
|
|
111
|
+
other.allows_any?(self)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def disjoint?(other)
|
|
115
|
+
!overlap?(other)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def relation(other)
|
|
119
|
+
if subset?(other)
|
|
120
|
+
:subset
|
|
121
|
+
elsif overlap?(other)
|
|
122
|
+
:overlap
|
|
123
|
+
else
|
|
124
|
+
:disjoint
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def to_s(allow_every: false)
|
|
129
|
+
if package == Package.root
|
|
130
|
+
"root"
|
|
131
|
+
elsif allow_every && any?
|
|
132
|
+
"every version of #{package.name}"
|
|
133
|
+
else
|
|
134
|
+
"#{package.name} #{constraint_string}"
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def constraint_string
|
|
139
|
+
case constraint.length
|
|
140
|
+
when 0
|
|
141
|
+
">= 0"
|
|
142
|
+
when 1
|
|
143
|
+
"#{constraint[0]}"
|
|
144
|
+
else
|
|
145
|
+
"#{constraint.join(", ")}"
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def empty?
|
|
150
|
+
bitmap == 0
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Does this match every version of the package
|
|
154
|
+
def any?
|
|
155
|
+
bitmap == self.class.any(package).bitmap
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def inspect
|
|
159
|
+
"#<#{self.class} #{self} (#{bitmap.to_s(2).rjust(package.versions.count, "0")})>"
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
require 'pub_grub/partial_solution'
|
|
2
|
+
require 'pub_grub/term'
|
|
3
|
+
require 'pub_grub/incompatibility'
|
|
4
|
+
require 'pub_grub/solve_failure'
|
|
5
|
+
|
|
6
|
+
module PubGrub
|
|
7
|
+
class VersionSolver
|
|
8
|
+
attr_reader :source
|
|
9
|
+
attr_reader :solution
|
|
10
|
+
|
|
11
|
+
def initialize(source:)
|
|
12
|
+
@source = source
|
|
13
|
+
|
|
14
|
+
# { package => [incompatibility, ...]}
|
|
15
|
+
@incompatibilities = Hash.new do |h, k|
|
|
16
|
+
h[k] = []
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
@solution = PartialSolution.new
|
|
20
|
+
|
|
21
|
+
add_incompatibility Incompatibility.new([
|
|
22
|
+
Term.new(VersionConstraint.any(Package.root), false)
|
|
23
|
+
], cause: :root)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def solve
|
|
27
|
+
next_package = Package.root
|
|
28
|
+
|
|
29
|
+
while next_package
|
|
30
|
+
propagate(next_package)
|
|
31
|
+
|
|
32
|
+
next_package = choose_package_version
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
result = solution.decisions.values
|
|
36
|
+
|
|
37
|
+
logger.info "Solution found after #{solution.attempted_solutions} attempts:"
|
|
38
|
+
result.each do |version|
|
|
39
|
+
logger.info "* #{version.package.name} #{version}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
result
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def propagate(initial_package)
|
|
48
|
+
changed = [initial_package]
|
|
49
|
+
while package = changed.shift
|
|
50
|
+
@incompatibilities[package].reverse_each do |incompatibility|
|
|
51
|
+
result = propagate_incompatibility(incompatibility)
|
|
52
|
+
if result == :conflict
|
|
53
|
+
root_cause = resolve_conflict(incompatibility)
|
|
54
|
+
changed.clear
|
|
55
|
+
changed << propagate_incompatibility(root_cause)
|
|
56
|
+
elsif result # should be a Package
|
|
57
|
+
changed << result
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
changed.uniq!
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def propagate_incompatibility(incompatibility)
|
|
65
|
+
unsatisfied = nil
|
|
66
|
+
incompatibility.terms.each do |term|
|
|
67
|
+
relation = solution.relation(term)
|
|
68
|
+
if relation == :disjoint
|
|
69
|
+
return nil
|
|
70
|
+
elsif relation == :overlap
|
|
71
|
+
# If more than one term is inconclusive, we can't deduce anything
|
|
72
|
+
return nil if unsatisfied
|
|
73
|
+
unsatisfied = term
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
if !unsatisfied
|
|
78
|
+
return :conflict
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
logger.debug("derived: #{unsatisfied.invert}")
|
|
82
|
+
|
|
83
|
+
solution.derive(unsatisfied.invert, incompatibility)
|
|
84
|
+
|
|
85
|
+
unsatisfied.package
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def choose_package_version
|
|
89
|
+
unsatisfied = solution.unsatisfied
|
|
90
|
+
|
|
91
|
+
if unsatisfied.empty?
|
|
92
|
+
logger.info "No packages unsatisfied. Solving complete!"
|
|
93
|
+
return nil
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
unsatisfied_term = unsatisfied.min_by do |term|
|
|
97
|
+
term.versions.count
|
|
98
|
+
end
|
|
99
|
+
version = unsatisfied_term.versions.first
|
|
100
|
+
|
|
101
|
+
if version.nil?
|
|
102
|
+
raise "required term with no versions: #{unsatisfied_term}"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
conflict = false
|
|
106
|
+
|
|
107
|
+
source.incompatibilities_for(version).each do |incompatibility|
|
|
108
|
+
add_incompatibility incompatibility
|
|
109
|
+
|
|
110
|
+
conflict ||= incompatibility.terms.all? do |term|
|
|
111
|
+
term.package == version.package || solution.satisfies?(term)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
unless conflict
|
|
116
|
+
logger.info("selecting #{version.package.name} #{version}")
|
|
117
|
+
|
|
118
|
+
solution.decide(version)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
version.package
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def resolve_conflict(incompatibility)
|
|
125
|
+
logger.info "conflict: #{incompatibility}"
|
|
126
|
+
|
|
127
|
+
new_incompatibility = false
|
|
128
|
+
|
|
129
|
+
while !incompatibility.failure?
|
|
130
|
+
most_recent_term = nil
|
|
131
|
+
most_recent_satisfier = nil
|
|
132
|
+
difference = nil
|
|
133
|
+
|
|
134
|
+
previous_level = 1
|
|
135
|
+
|
|
136
|
+
incompatibility.terms.each do |term|
|
|
137
|
+
satisfier = solution.satisfier(term)
|
|
138
|
+
|
|
139
|
+
if most_recent_satisfier.nil?
|
|
140
|
+
most_recent_term = term
|
|
141
|
+
most_recent_satisfier = satisfier
|
|
142
|
+
elsif most_recent_satisfier.index < satisfier.index
|
|
143
|
+
previous_level = [previous_level, most_recent_satisfier.decision_level].max
|
|
144
|
+
most_recent_term = term
|
|
145
|
+
most_recent_satisfier = satisfier
|
|
146
|
+
difference = nil
|
|
147
|
+
else
|
|
148
|
+
previous_level = [previous_level, satisfier.decision_level].max
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
if most_recent_term == term
|
|
152
|
+
difference = most_recent_satisfier.term.difference(most_recent_term)
|
|
153
|
+
if difference.empty?
|
|
154
|
+
difference = nil
|
|
155
|
+
else
|
|
156
|
+
difference_satisfier = solution.satisfier(difference.inverse)
|
|
157
|
+
previous_level = [previous_level, difference_satisfier.decision_level].max
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
if previous_level < most_recent_satisfier.decision_level ||
|
|
163
|
+
most_recent_satisfier.decision?
|
|
164
|
+
|
|
165
|
+
logger.info "backtracking to #{previous_level}"
|
|
166
|
+
solution.backtrack(previous_level)
|
|
167
|
+
|
|
168
|
+
if new_incompatibility
|
|
169
|
+
add_incompatibility(incompatibility)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
return incompatibility
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
new_terms = []
|
|
176
|
+
new_terms += incompatibility.terms - [most_recent_term]
|
|
177
|
+
new_terms += most_recent_satisfier.cause.terms.reject { |term|
|
|
178
|
+
term.package == most_recent_satisfier.term.package
|
|
179
|
+
}
|
|
180
|
+
if difference
|
|
181
|
+
new_terms << difference.invert
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
incompatibility = Incompatibility.new(new_terms, cause: Incompatibility::ConflictCause.new(incompatibility, most_recent_satisfier.cause))
|
|
185
|
+
|
|
186
|
+
new_incompatibility = true
|
|
187
|
+
|
|
188
|
+
partially = difference ? " partially" : ""
|
|
189
|
+
logger.info "! #{most_recent_term} is#{partially} satisfied by #{most_recent_satisfier.term}"
|
|
190
|
+
logger.info "! which is caused by #{most_recent_satisfier.cause}"
|
|
191
|
+
logger.info "! thus #{incompatibility}"
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
raise SolveFailure.new(incompatibility)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def add_incompatibility(incompatibility)
|
|
198
|
+
logger.debug("fact: #{incompatibility}");
|
|
199
|
+
incompatibility.terms.each do |term|
|
|
200
|
+
package = term.package
|
|
201
|
+
@incompatibilities[package] << incompatibility
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def logger
|
|
206
|
+
PubGrub.logger
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
data/pub_grub.gemspec
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
|
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "pub_grub"
|
|
7
|
+
spec.version = "0.1.0"
|
|
8
|
+
spec.authors = ["John Hawthorn"]
|
|
9
|
+
spec.email = ["john@hawthorn.email"]
|
|
10
|
+
|
|
11
|
+
spec.summary = %q{A version solver based on dart's PubGrub}
|
|
12
|
+
spec.description = %q{A version solver based on dart's PubGrub}
|
|
13
|
+
spec.homepage = "https://github.com/jhawthorn/pubgrub"
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
|
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
|
18
|
+
end
|
|
19
|
+
spec.bindir = "exe"
|
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
21
|
+
spec.require_paths = ["lib"]
|
|
22
|
+
|
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.16"
|
|
24
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
|
25
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
|
26
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: pub_grub
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- John Hawthorn
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2018-07-04 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bundler
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.16'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.16'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '10.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '10.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: minitest
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '5.0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '5.0'
|
|
55
|
+
description: A version solver based on dart's PubGrub
|
|
56
|
+
email:
|
|
57
|
+
- john@hawthorn.email
|
|
58
|
+
executables: []
|
|
59
|
+
extensions: []
|
|
60
|
+
extra_rdoc_files: []
|
|
61
|
+
files:
|
|
62
|
+
- ".gitignore"
|
|
63
|
+
- ".travis.yml"
|
|
64
|
+
- CODE_OF_CONDUCT.md
|
|
65
|
+
- Gemfile
|
|
66
|
+
- Gemfile.lock
|
|
67
|
+
- LICENSE.txt
|
|
68
|
+
- README.md
|
|
69
|
+
- Rakefile
|
|
70
|
+
- bin/console
|
|
71
|
+
- bin/setup
|
|
72
|
+
- lib/pub_grub.rb
|
|
73
|
+
- lib/pub_grub/assignment.rb
|
|
74
|
+
- lib/pub_grub/failure_writer.rb
|
|
75
|
+
- lib/pub_grub/incompatibility.rb
|
|
76
|
+
- lib/pub_grub/package.rb
|
|
77
|
+
- lib/pub_grub/partial_solution.rb
|
|
78
|
+
- lib/pub_grub/solve_failure.rb
|
|
79
|
+
- lib/pub_grub/static_package_source.rb
|
|
80
|
+
- lib/pub_grub/term.rb
|
|
81
|
+
- lib/pub_grub/version_constraint.rb
|
|
82
|
+
- lib/pub_grub/version_solver.rb
|
|
83
|
+
- pub_grub.gemspec
|
|
84
|
+
homepage: https://github.com/jhawthorn/pubgrub
|
|
85
|
+
licenses:
|
|
86
|
+
- MIT
|
|
87
|
+
metadata: {}
|
|
88
|
+
post_install_message:
|
|
89
|
+
rdoc_options: []
|
|
90
|
+
require_paths:
|
|
91
|
+
- lib
|
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ">="
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '0'
|
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
98
|
+
requirements:
|
|
99
|
+
- - ">="
|
|
100
|
+
- !ruby/object:Gem::Version
|
|
101
|
+
version: '0'
|
|
102
|
+
requirements: []
|
|
103
|
+
rubyforge_project:
|
|
104
|
+
rubygems_version: 2.7.3
|
|
105
|
+
signing_key:
|
|
106
|
+
specification_version: 4
|
|
107
|
+
summary: A version solver based on dart's PubGrub
|
|
108
|
+
test_files: []
|