scint 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.
Files changed (72) hide show
  1. checksums.yaml +7 -0
  2. data/FEATURES.md +13 -0
  3. data/README.md +216 -0
  4. data/bin/bundler-vs-scint +233 -0
  5. data/bin/scint +35 -0
  6. data/bin/scint-io-summary +46 -0
  7. data/bin/scint-syscall-trace +41 -0
  8. data/lib/bundler/setup.rb +5 -0
  9. data/lib/bundler.rb +168 -0
  10. data/lib/scint/cache/layout.rb +131 -0
  11. data/lib/scint/cache/metadata_store.rb +75 -0
  12. data/lib/scint/cache/prewarm.rb +192 -0
  13. data/lib/scint/cli/add.rb +85 -0
  14. data/lib/scint/cli/cache.rb +316 -0
  15. data/lib/scint/cli/exec.rb +150 -0
  16. data/lib/scint/cli/install.rb +1047 -0
  17. data/lib/scint/cli/remove.rb +60 -0
  18. data/lib/scint/cli.rb +77 -0
  19. data/lib/scint/commands/exec.rb +17 -0
  20. data/lib/scint/commands/install.rb +17 -0
  21. data/lib/scint/credentials.rb +153 -0
  22. data/lib/scint/debug/io_trace.rb +218 -0
  23. data/lib/scint/debug/sampler.rb +138 -0
  24. data/lib/scint/downloader/fetcher.rb +113 -0
  25. data/lib/scint/downloader/pool.rb +112 -0
  26. data/lib/scint/errors.rb +63 -0
  27. data/lib/scint/fs.rb +119 -0
  28. data/lib/scint/gem/extractor.rb +86 -0
  29. data/lib/scint/gem/package.rb +62 -0
  30. data/lib/scint/gemfile/dependency.rb +30 -0
  31. data/lib/scint/gemfile/editor.rb +93 -0
  32. data/lib/scint/gemfile/parser.rb +275 -0
  33. data/lib/scint/index/cache.rb +166 -0
  34. data/lib/scint/index/client.rb +301 -0
  35. data/lib/scint/index/parser.rb +142 -0
  36. data/lib/scint/installer/extension_builder.rb +264 -0
  37. data/lib/scint/installer/linker.rb +226 -0
  38. data/lib/scint/installer/planner.rb +140 -0
  39. data/lib/scint/installer/preparer.rb +207 -0
  40. data/lib/scint/lockfile/parser.rb +251 -0
  41. data/lib/scint/lockfile/writer.rb +178 -0
  42. data/lib/scint/platform.rb +71 -0
  43. data/lib/scint/progress.rb +579 -0
  44. data/lib/scint/resolver/provider.rb +230 -0
  45. data/lib/scint/resolver/resolver.rb +249 -0
  46. data/lib/scint/runtime/exec.rb +141 -0
  47. data/lib/scint/runtime/setup.rb +45 -0
  48. data/lib/scint/scheduler.rb +392 -0
  49. data/lib/scint/source/base.rb +46 -0
  50. data/lib/scint/source/git.rb +92 -0
  51. data/lib/scint/source/path.rb +70 -0
  52. data/lib/scint/source/rubygems.rb +79 -0
  53. data/lib/scint/vendor/pub_grub/assignment.rb +20 -0
  54. data/lib/scint/vendor/pub_grub/basic_package_source.rb +169 -0
  55. data/lib/scint/vendor/pub_grub/failure_writer.rb +182 -0
  56. data/lib/scint/vendor/pub_grub/incompatibility.rb +150 -0
  57. data/lib/scint/vendor/pub_grub/package.rb +43 -0
  58. data/lib/scint/vendor/pub_grub/partial_solution.rb +121 -0
  59. data/lib/scint/vendor/pub_grub/rubygems.rb +45 -0
  60. data/lib/scint/vendor/pub_grub/solve_failure.rb +19 -0
  61. data/lib/scint/vendor/pub_grub/static_package_source.rb +61 -0
  62. data/lib/scint/vendor/pub_grub/strategy.rb +42 -0
  63. data/lib/scint/vendor/pub_grub/term.rb +105 -0
  64. data/lib/scint/vendor/pub_grub/version.rb +3 -0
  65. data/lib/scint/vendor/pub_grub/version_constraint.rb +129 -0
  66. data/lib/scint/vendor/pub_grub/version_range.rb +423 -0
  67. data/lib/scint/vendor/pub_grub/version_solver.rb +236 -0
  68. data/lib/scint/vendor/pub_grub/version_union.rb +178 -0
  69. data/lib/scint/vendor/pub_grub.rb +32 -0
  70. data/lib/scint/worker_pool.rb +114 -0
  71. data/lib/scint.rb +87 -0
  72. metadata +116 -0
@@ -0,0 +1,423 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Scint::PubGrub
4
+ class VersionRange
5
+ attr_reader :min, :max, :include_min, :include_max
6
+
7
+ alias_method :include_min?, :include_min
8
+ alias_method :include_max?, :include_max
9
+
10
+ class Empty < VersionRange
11
+ undef_method :min, :max
12
+ undef_method :include_min, :include_min?
13
+ undef_method :include_max, :include_max?
14
+
15
+ def initialize
16
+ end
17
+
18
+ def empty?
19
+ true
20
+ end
21
+
22
+ def eql?(other)
23
+ other.empty?
24
+ end
25
+
26
+ def hash
27
+ [].hash
28
+ end
29
+
30
+ def intersects?(_)
31
+ false
32
+ end
33
+
34
+ def intersect(other)
35
+ self
36
+ end
37
+
38
+ def allows_all?(other)
39
+ other.empty?
40
+ end
41
+
42
+ def include?(_)
43
+ false
44
+ end
45
+
46
+ def any?
47
+ false
48
+ end
49
+
50
+ def to_s
51
+ "(no versions)"
52
+ end
53
+
54
+ def ==(other)
55
+ other.class == self.class
56
+ end
57
+
58
+ def invert
59
+ VersionRange.any
60
+ end
61
+
62
+ def select_versions(_)
63
+ []
64
+ end
65
+ end
66
+
67
+ EMPTY = Empty.new
68
+ Empty.singleton_class.undef_method(:new)
69
+
70
+ def self.empty
71
+ EMPTY
72
+ end
73
+
74
+ def self.any
75
+ new
76
+ end
77
+
78
+ def initialize(min: nil, max: nil, include_min: false, include_max: false, name: nil)
79
+ raise ArgumentError, "Ranges without a lower bound cannot have include_min == true" if !min && include_min == true
80
+ raise ArgumentError, "Ranges without an upper bound cannot have include_max == true" if !max && include_max == true
81
+
82
+ @min = min
83
+ @max = max
84
+ @include_min = include_min
85
+ @include_max = include_max
86
+ @name = name
87
+ end
88
+
89
+ def hash
90
+ @hash ||= min.hash ^ max.hash ^ include_min.hash ^ include_max.hash
91
+ end
92
+
93
+ def eql?(other)
94
+ if other.is_a?(VersionRange)
95
+ !other.empty? &&
96
+ min.eql?(other.min) &&
97
+ max.eql?(other.max) &&
98
+ include_min.eql?(other.include_min) &&
99
+ include_max.eql?(other.include_max)
100
+ else
101
+ ranges.eql?(other.ranges)
102
+ end
103
+ end
104
+
105
+ def ranges
106
+ [self]
107
+ end
108
+
109
+ def include?(version)
110
+ compare_version(version) == 0
111
+ end
112
+
113
+ # Partitions passed versions into [lower, within, higher]
114
+ #
115
+ # versions must be sorted
116
+ def partition_versions(versions)
117
+ min_index =
118
+ if !min || versions.empty?
119
+ 0
120
+ elsif include_min?
121
+ (0..versions.size).bsearch { |i| versions[i].nil? || versions[i] >= min }
122
+ else
123
+ (0..versions.size).bsearch { |i| versions[i].nil? || versions[i] > min }
124
+ end
125
+
126
+ lower = versions.slice(0, min_index)
127
+ versions = versions.slice(min_index, versions.size)
128
+
129
+ max_index =
130
+ if !max || versions.empty?
131
+ versions.size
132
+ elsif include_max?
133
+ (0..versions.size).bsearch { |i| versions[i].nil? || versions[i] > max }
134
+ else
135
+ (0..versions.size).bsearch { |i| versions[i].nil? || versions[i] >= max }
136
+ end
137
+
138
+ [
139
+ lower,
140
+ versions.slice(0, max_index),
141
+ versions.slice(max_index, versions.size)
142
+ ]
143
+ end
144
+
145
+ # Returns versions which are included by this range.
146
+ #
147
+ # versions must be sorted
148
+ def select_versions(versions)
149
+ return versions if any?
150
+
151
+ partition_versions(versions)[1]
152
+ end
153
+
154
+ def compare_version(version)
155
+ if min
156
+ case version <=> min
157
+ when -1
158
+ return -1
159
+ when 0
160
+ return -1 if !include_min
161
+ when 1
162
+ end
163
+ end
164
+
165
+ if max
166
+ case version <=> max
167
+ when -1
168
+ when 0
169
+ return 1 if !include_max
170
+ when 1
171
+ return 1
172
+ end
173
+ end
174
+
175
+ 0
176
+ end
177
+
178
+ def strictly_lower?(other)
179
+ return false if !max || !other.min
180
+
181
+ case max <=> other.min
182
+ when 0
183
+ !include_max || !other.include_min
184
+ when -1
185
+ true
186
+ when 1
187
+ false
188
+ end
189
+ end
190
+
191
+ def strictly_higher?(other)
192
+ other.strictly_lower?(self)
193
+ end
194
+
195
+ def intersects?(other)
196
+ return false if other.empty?
197
+ return other.intersects?(self) if other.is_a?(VersionUnion)
198
+ !strictly_lower?(other) && !strictly_higher?(other)
199
+ end
200
+ alias_method :allows_any?, :intersects?
201
+
202
+ def intersect(other)
203
+ return other if other.empty?
204
+ return other.intersect(self) if other.is_a?(VersionUnion)
205
+
206
+ min_range =
207
+ if !min
208
+ other
209
+ elsif !other.min
210
+ self
211
+ else
212
+ case min <=> other.min
213
+ when 0
214
+ include_min ? other : self
215
+ when -1
216
+ other
217
+ when 1
218
+ self
219
+ end
220
+ end
221
+
222
+ max_range =
223
+ if !max
224
+ other
225
+ elsif !other.max
226
+ self
227
+ else
228
+ case max <=> other.max
229
+ when 0
230
+ include_max ? other : self
231
+ when -1
232
+ self
233
+ when 1
234
+ other
235
+ end
236
+ end
237
+
238
+ if !min_range.equal?(max_range) && min_range.min && max_range.max
239
+ case min_range.min <=> max_range.max
240
+ when -1
241
+ when 0
242
+ if !min_range.include_min || !max_range.include_max
243
+ return EMPTY
244
+ end
245
+ when 1
246
+ return EMPTY
247
+ end
248
+ end
249
+
250
+ VersionRange.new(
251
+ min: min_range.min,
252
+ include_min: min_range.include_min,
253
+ max: max_range.max,
254
+ include_max: max_range.include_max
255
+ )
256
+ end
257
+
258
+ # The span covered by two ranges
259
+ #
260
+ # If self and other are contiguous, this builds a union of the two ranges.
261
+ # (if they aren't you are probably calling the wrong method)
262
+ def span(other)
263
+ return self if other.empty?
264
+
265
+ min_range =
266
+ if !min
267
+ self
268
+ elsif !other.min
269
+ other
270
+ else
271
+ case min <=> other.min
272
+ when 0
273
+ include_min ? self : other
274
+ when -1
275
+ self
276
+ when 1
277
+ other
278
+ end
279
+ end
280
+
281
+ max_range =
282
+ if !max
283
+ self
284
+ elsif !other.max
285
+ other
286
+ else
287
+ case max <=> other.max
288
+ when 0
289
+ include_max ? self : other
290
+ when -1
291
+ other
292
+ when 1
293
+ self
294
+ end
295
+ end
296
+
297
+ VersionRange.new(
298
+ min: min_range.min,
299
+ include_min: min_range.include_min,
300
+ max: max_range.max,
301
+ include_max: max_range.include_max
302
+ )
303
+ end
304
+
305
+ def union(other)
306
+ return other.union(self) if other.is_a?(VersionUnion)
307
+
308
+ if contiguous_to?(other)
309
+ span(other)
310
+ else
311
+ VersionUnion.union([self, other])
312
+ end
313
+ end
314
+
315
+ def contiguous_to?(other)
316
+ return false if other.empty?
317
+ return true if any?
318
+
319
+ intersects?(other) || contiguous_below?(other) || contiguous_above?(other)
320
+ end
321
+
322
+ def contiguous_below?(other)
323
+ return false if !max || !other.min
324
+
325
+ max == other.min && (include_max || other.include_min)
326
+ end
327
+
328
+ def contiguous_above?(other)
329
+ other.contiguous_below?(self)
330
+ end
331
+
332
+ def allows_all?(other)
333
+ return true if other.empty?
334
+
335
+ if other.is_a?(VersionUnion)
336
+ return VersionUnion.new([self]).allows_all?(other)
337
+ end
338
+
339
+ return false if max && !other.max
340
+ return false if min && !other.min
341
+
342
+ if min
343
+ case min <=> other.min
344
+ when -1
345
+ when 0
346
+ return false if !include_min && other.include_min
347
+ when 1
348
+ return false
349
+ end
350
+ end
351
+
352
+ if max
353
+ case max <=> other.max
354
+ when -1
355
+ return false
356
+ when 0
357
+ return false if !include_max && other.include_max
358
+ when 1
359
+ end
360
+ end
361
+
362
+ true
363
+ end
364
+
365
+ def any?
366
+ !min && !max
367
+ end
368
+
369
+ def empty?
370
+ false
371
+ end
372
+
373
+ def to_s
374
+ @name ||= constraints.join(", ")
375
+ end
376
+
377
+ def inspect
378
+ "#<#{self.class} #{to_s}>"
379
+ end
380
+
381
+ def upper_invert
382
+ return self.class.empty unless max
383
+
384
+ VersionRange.new(min: max, include_min: !include_max)
385
+ end
386
+
387
+ def invert
388
+ return self.class.empty if any?
389
+
390
+ low = -> { VersionRange.new(max: min, include_max: !include_min) }
391
+ high = -> { VersionRange.new(min: max, include_min: !include_max) }
392
+
393
+ if !min
394
+ high.call
395
+ elsif !max
396
+ low.call
397
+ else
398
+ low.call.union(high.call)
399
+ end
400
+ end
401
+
402
+ def ==(other)
403
+ self.class == other.class &&
404
+ min == other.min &&
405
+ max == other.max &&
406
+ include_min == other.include_min &&
407
+ include_max == other.include_max
408
+ end
409
+
410
+ private
411
+
412
+ def constraints
413
+ return ["any"] if any?
414
+ return ["= #{min}"] if min.to_s == max.to_s
415
+
416
+ c = []
417
+ c << "#{include_min ? ">=" : ">"} #{min}" if min
418
+ c << "#{include_max ? "<=" : "<"} #{max}" if max
419
+ c
420
+ end
421
+
422
+ end
423
+ end
@@ -0,0 +1,236 @@
1
+ require_relative 'partial_solution'
2
+ require_relative 'term'
3
+ require_relative 'incompatibility'
4
+ require_relative 'solve_failure'
5
+ require_relative 'strategy'
6
+
7
+ module Scint::PubGrub
8
+ class VersionSolver
9
+ attr_reader :logger
10
+ attr_reader :source
11
+ attr_reader :solution
12
+ attr_reader :strategy
13
+
14
+ def initialize(source:, root: Package.root, strategy: Strategy.new(source), logger: Scint::PubGrub.logger)
15
+ @logger = logger
16
+
17
+ @source = source
18
+ @strategy = strategy
19
+
20
+ # { package => [incompatibility, ...]}
21
+ @incompatibilities = Hash.new do |h, k|
22
+ h[k] = []
23
+ end
24
+
25
+ @seen_incompatibilities = {}
26
+
27
+ @solution = PartialSolution.new
28
+
29
+ add_incompatibility Incompatibility.new([
30
+ Term.new(VersionConstraint.any(root), false)
31
+ ], cause: :root)
32
+
33
+ propagate(root)
34
+ end
35
+
36
+ def solved?
37
+ solution.unsatisfied.empty?
38
+ end
39
+
40
+ # Returns true if there is more work to be done, false otherwise
41
+ def work
42
+ unsatisfied_terms = solution.unsatisfied
43
+ if unsatisfied_terms.empty?
44
+ logger.info { "Solution found after #{solution.attempted_solutions} attempts:" }
45
+ solution.decisions.each do |package, version|
46
+ next if Package.root?(package)
47
+ logger.info { "* #{package} #{version}" }
48
+ end
49
+
50
+ return false
51
+ end
52
+
53
+ next_package = choose_package_version_from(unsatisfied_terms)
54
+ propagate(next_package)
55
+
56
+ true
57
+ end
58
+
59
+ def solve
60
+ while work; end
61
+
62
+ solution.decisions
63
+ end
64
+
65
+ alias_method :result, :solve
66
+
67
+ private
68
+
69
+ def propagate(initial_package)
70
+ changed = [initial_package]
71
+ while package = changed.shift
72
+ @incompatibilities[package].reverse_each do |incompatibility|
73
+ result = propagate_incompatibility(incompatibility)
74
+ if result == :conflict
75
+ root_cause = resolve_conflict(incompatibility)
76
+ changed.clear
77
+ changed << propagate_incompatibility(root_cause)
78
+ elsif result # should be a Package
79
+ changed << result
80
+ end
81
+ end
82
+ changed.uniq!
83
+ end
84
+ end
85
+
86
+ def propagate_incompatibility(incompatibility)
87
+ unsatisfied = nil
88
+ incompatibility.terms.each do |term|
89
+ relation = solution.relation(term)
90
+ if relation == :disjoint
91
+ return nil
92
+ elsif relation == :overlap
93
+ # If more than one term is inconclusive, we can't deduce anything
94
+ return nil if unsatisfied
95
+ unsatisfied = term
96
+ end
97
+ end
98
+
99
+ if !unsatisfied
100
+ return :conflict
101
+ end
102
+
103
+ logger.debug { "derived: #{unsatisfied.invert}" }
104
+
105
+ solution.derive(unsatisfied.invert, incompatibility)
106
+
107
+ unsatisfied.package
108
+ end
109
+
110
+ def choose_package_version_from(unsatisfied_terms)
111
+ remaining = unsatisfied_terms.map { |t| [t.package, t.constraint.range] }.to_h
112
+
113
+ package, version = strategy.next_package_and_version(remaining)
114
+
115
+ logger.debug { "attempting #{package} #{version}" }
116
+
117
+ if version.nil?
118
+ unsatisfied_term = unsatisfied_terms.find { |t| t.package == package }
119
+ add_incompatibility source.no_versions_incompatibility_for(package, unsatisfied_term)
120
+ return package
121
+ end
122
+
123
+ conflict = false
124
+
125
+ source.incompatibilities_for(package, version).each do |incompatibility|
126
+ if @seen_incompatibilities.include?(incompatibility)
127
+ logger.debug { "knew: #{incompatibility}" }
128
+ next
129
+ end
130
+ @seen_incompatibilities[incompatibility] = true
131
+
132
+ add_incompatibility incompatibility
133
+
134
+ conflict ||= incompatibility.terms.all? do |term|
135
+ term.package == package || solution.satisfies?(term)
136
+ end
137
+ end
138
+
139
+ unless conflict
140
+ logger.info { "selected #{package} #{version}" }
141
+
142
+ solution.decide(package, version)
143
+ else
144
+ logger.info { "conflict: #{conflict.inspect}" }
145
+ end
146
+
147
+ package
148
+ end
149
+
150
+ def resolve_conflict(incompatibility)
151
+ logger.info { "conflict: #{incompatibility}" }
152
+
153
+ new_incompatibility = nil
154
+
155
+ while !incompatibility.failure?
156
+ most_recent_term = nil
157
+ most_recent_satisfier = nil
158
+ difference = nil
159
+
160
+ previous_level = 1
161
+
162
+ incompatibility.terms.each do |term|
163
+ satisfier = solution.satisfier(term)
164
+
165
+ if most_recent_satisfier.nil?
166
+ most_recent_term = term
167
+ most_recent_satisfier = satisfier
168
+ elsif most_recent_satisfier.index < satisfier.index
169
+ previous_level = [previous_level, most_recent_satisfier.decision_level].max
170
+ most_recent_term = term
171
+ most_recent_satisfier = satisfier
172
+ difference = nil
173
+ else
174
+ previous_level = [previous_level, satisfier.decision_level].max
175
+ end
176
+
177
+ if most_recent_term == term
178
+ difference = most_recent_satisfier.term.difference(most_recent_term)
179
+ if difference.empty?
180
+ difference = nil
181
+ else
182
+ difference_satisfier = solution.satisfier(difference.inverse)
183
+ previous_level = [previous_level, difference_satisfier.decision_level].max
184
+ end
185
+ end
186
+ end
187
+
188
+ if previous_level < most_recent_satisfier.decision_level ||
189
+ most_recent_satisfier.decision?
190
+
191
+ logger.info { "backtracking to #{previous_level}" }
192
+ solution.backtrack(previous_level)
193
+
194
+ if new_incompatibility
195
+ add_incompatibility(new_incompatibility)
196
+ end
197
+
198
+ return incompatibility
199
+ end
200
+
201
+ new_terms = []
202
+ new_terms += incompatibility.terms - [most_recent_term]
203
+ new_terms += most_recent_satisfier.cause.terms.reject { |term|
204
+ term.package == most_recent_satisfier.term.package
205
+ }
206
+ if difference
207
+ new_terms << difference.invert
208
+ end
209
+
210
+ new_incompatibility = Incompatibility.new(new_terms, cause: Incompatibility::ConflictCause.new(incompatibility, most_recent_satisfier.cause))
211
+
212
+ if incompatibility.to_s == new_incompatibility.to_s
213
+ logger.info { "!! failed to resolve conflicts, this shouldn't have happened" }
214
+ break
215
+ end
216
+
217
+ incompatibility = new_incompatibility
218
+
219
+ partially = difference ? " partially" : ""
220
+ logger.info { "! #{most_recent_term} is#{partially} satisfied by #{most_recent_satisfier.term}" }
221
+ logger.info { "! which is caused by #{most_recent_satisfier.cause}" }
222
+ logger.info { "! thus #{incompatibility}" }
223
+ end
224
+
225
+ raise SolveFailure.new(incompatibility)
226
+ end
227
+
228
+ def add_incompatibility(incompatibility)
229
+ logger.debug { "fact: #{incompatibility}" }
230
+ incompatibility.terms.each do |term|
231
+ package = term.package
232
+ @incompatibilities[package] << incompatibility
233
+ end
234
+ end
235
+ end
236
+ end