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,178 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Scint::PubGrub
4
+ class VersionUnion
5
+ attr_reader :ranges
6
+
7
+ def self.normalize_ranges(ranges)
8
+ ranges = ranges.flat_map do |range|
9
+ range.ranges
10
+ end
11
+
12
+ ranges.reject!(&:empty?)
13
+
14
+ return [] if ranges.empty?
15
+
16
+ mins, ranges = ranges.partition { |r| !r.min }
17
+ original_ranges = mins + ranges.sort_by { |r| [r.min, r.include_min ? 0 : 1] }
18
+ ranges = [original_ranges.shift]
19
+ original_ranges.each do |range|
20
+ if ranges.last.contiguous_to?(range)
21
+ ranges << ranges.pop.span(range)
22
+ else
23
+ ranges << range
24
+ end
25
+ end
26
+
27
+ ranges
28
+ end
29
+
30
+ def self.union(ranges, normalize: true)
31
+ ranges = normalize_ranges(ranges) if normalize
32
+
33
+ if ranges.size == 0
34
+ VersionRange.empty
35
+ elsif ranges.size == 1
36
+ ranges[0]
37
+ else
38
+ new(ranges)
39
+ end
40
+ end
41
+
42
+ def initialize(ranges)
43
+ raise ArgumentError unless ranges.all? { |r| r.instance_of?(VersionRange) }
44
+ @ranges = ranges
45
+ end
46
+
47
+ def hash
48
+ ranges.hash
49
+ end
50
+
51
+ def eql?(other)
52
+ ranges.eql?(other.ranges)
53
+ end
54
+
55
+ def include?(version)
56
+ !!ranges.bsearch {|r| r.compare_version(version) }
57
+ end
58
+
59
+ def select_versions(all_versions)
60
+ versions = []
61
+ ranges.inject(all_versions) do |acc, range|
62
+ _, matching, higher = range.partition_versions(acc)
63
+ versions.concat matching
64
+ higher
65
+ end
66
+ versions
67
+ end
68
+
69
+ def intersects?(other)
70
+ my_ranges = ranges.dup
71
+ other_ranges = other.ranges.dup
72
+
73
+ my_range = my_ranges.shift
74
+ other_range = other_ranges.shift
75
+ while my_range && other_range
76
+ if my_range.intersects?(other_range)
77
+ return true
78
+ end
79
+
80
+ if !my_range.max || other_range.empty? || (other_range.max && other_range.max < my_range.max)
81
+ other_range = other_ranges.shift
82
+ else
83
+ my_range = my_ranges.shift
84
+ end
85
+ end
86
+ end
87
+ alias_method :allows_any?, :intersects?
88
+
89
+ def allows_all?(other)
90
+ my_ranges = ranges.dup
91
+
92
+ my_range = my_ranges.shift
93
+
94
+ other.ranges.all? do |other_range|
95
+ while my_range
96
+ break if my_range.allows_all?(other_range)
97
+ my_range = my_ranges.shift
98
+ end
99
+
100
+ !!my_range
101
+ end
102
+ end
103
+
104
+ def empty?
105
+ false
106
+ end
107
+
108
+ def any?
109
+ false
110
+ end
111
+
112
+ def intersect(other)
113
+ my_ranges = ranges.dup
114
+ other_ranges = other.ranges.dup
115
+ new_ranges = []
116
+
117
+ my_range = my_ranges.shift
118
+ other_range = other_ranges.shift
119
+ while my_range && other_range
120
+ new_ranges << my_range.intersect(other_range)
121
+
122
+ if !my_range.max || other_range.empty? || (other_range.max && other_range.max < my_range.max)
123
+ other_range = other_ranges.shift
124
+ else
125
+ my_range = my_ranges.shift
126
+ end
127
+ end
128
+ new_ranges.reject!(&:empty?)
129
+ VersionUnion.union(new_ranges, normalize: false)
130
+ end
131
+
132
+ def upper_invert
133
+ ranges.last.upper_invert
134
+ end
135
+
136
+ def invert
137
+ ranges.map(&:invert).inject(:intersect)
138
+ end
139
+
140
+ def union(other)
141
+ VersionUnion.union([self, other])
142
+ end
143
+
144
+ def to_s
145
+ output = []
146
+
147
+ ranges = self.ranges.dup
148
+ while !ranges.empty?
149
+ ne = []
150
+ range = ranges.shift
151
+ while !ranges.empty? && ranges[0].min.to_s == range.max.to_s
152
+ ne << range.max
153
+ range = range.span(ranges.shift)
154
+ end
155
+
156
+ ne.map! {|x| "!= #{x}" }
157
+ if ne.empty?
158
+ output << range.to_s
159
+ elsif range.any?
160
+ output << ne.join(', ')
161
+ else
162
+ output << "#{range}, #{ne.join(', ')}"
163
+ end
164
+ end
165
+
166
+ output.join(" OR ")
167
+ end
168
+
169
+ def inspect
170
+ "#<#{self.class} #{to_s}>"
171
+ end
172
+
173
+ def ==(other)
174
+ self.class == other.class &&
175
+ self.ranges == other.ranges
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Vendored PubGrub loader - loads all PubGrub modules under Scint::PubGrub
4
+ require "logger"
5
+
6
+ module Scint
7
+ module PubGrub
8
+ def self.logger
9
+ @logger ||= ::Logger.new($stderr, level: ::Logger::WARN)
10
+ end
11
+
12
+ def self.logger=(logger)
13
+ @logger = logger
14
+ end
15
+ end
16
+ end
17
+
18
+ require_relative "pub_grub/version"
19
+ require_relative "pub_grub/version_range"
20
+ require_relative "pub_grub/version_union"
21
+ require_relative "pub_grub/version_constraint"
22
+ require_relative "pub_grub/package"
23
+ require_relative "pub_grub/term"
24
+ require_relative "pub_grub/assignment"
25
+ require_relative "pub_grub/incompatibility"
26
+ require_relative "pub_grub/partial_solution"
27
+ require_relative "pub_grub/solve_failure"
28
+ require_relative "pub_grub/failure_writer"
29
+ require_relative "pub_grub/strategy"
30
+ require_relative "pub_grub/version_solver"
31
+ require_relative "pub_grub/basic_package_source"
32
+ require_relative "pub_grub/rubygems"
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Scint
4
+ class WorkerPool
5
+ POISON = :__scint_poison__
6
+
7
+ attr_reader :size
8
+
9
+ def initialize(size, name: "worker")
10
+ @max_size = size
11
+ @size = 0
12
+ @name = name
13
+ @queue = Thread::Queue.new
14
+ @workers = []
15
+ @started = false
16
+ @stopped = false
17
+ @mutex = Thread::Mutex.new
18
+ end
19
+
20
+ # Start worker threads. initial_count defaults to max_size for backward compat.
21
+ # The block receives (job) and should return a result or raise.
22
+ def start(initial_count = nil, &block)
23
+ raise "already started" if @started
24
+ raise "no block given" unless block
25
+
26
+ @started = true
27
+ @handler = block
28
+
29
+ count = [initial_count || @max_size, @max_size].min
30
+ spawn_workers(count)
31
+ end
32
+
33
+ # Grow the pool to target size (clamped to max). Never shrinks.
34
+ # Safe to call from any thread.
35
+ def grow_to(target)
36
+ @mutex.synchronize do
37
+ return unless @started && !@stopped
38
+ target = [target, @max_size].min
39
+ return if target <= @size
40
+
41
+ spawn_workers(target - @size)
42
+ end
43
+ end
44
+
45
+ # Enqueue a job. Returns the job hash for tracking.
46
+ def enqueue(payload, &callback)
47
+ raise "not started" unless @started
48
+ raise "pool is stopped" if @stopped
49
+
50
+ job = {
51
+ payload: payload,
52
+ state: :pending,
53
+ result: nil,
54
+ error: nil,
55
+ callback: callback,
56
+ }
57
+ @queue.push(job)
58
+ job
59
+ end
60
+
61
+ # Signal all workers to stop after finishing current work.
62
+ def stop
63
+ current_size = nil
64
+ @mutex.synchronize do
65
+ return if @stopped
66
+ @stopped = true
67
+ current_size = @size
68
+ end
69
+
70
+ current_size.times { @queue.push(POISON) }
71
+ @workers.each { |w| w.join }
72
+ end
73
+
74
+ def running?
75
+ @started && !@stopped
76
+ end
77
+
78
+ private
79
+
80
+ # Must be called inside @mutex (or during init before threads start)
81
+ def spawn_workers(count)
82
+ base = @workers.size
83
+ count.times do |i|
84
+ @workers << Thread.new do
85
+ Thread.current.name = "#{@name}-#{base + i}"
86
+ loop do
87
+ job = @queue.pop
88
+ break if job == POISON
89
+
90
+ # Never let an exception kill the worker thread while a job is
91
+ # in flight, or scheduler waiters can block forever.
92
+ begin
93
+ job[:result] = @handler.call(job[:payload])
94
+ job[:state] = :completed
95
+ rescue Exception => e
96
+ job[:error] = e
97
+ job[:state] = :failed
98
+ end
99
+
100
+ begin
101
+ job[:callback]&.call(job)
102
+ rescue Exception => e
103
+ $stderr.puts "Worker callback error: #{e.class}: #{e.message}"
104
+ $stderr.puts e.backtrace.first(5).map { |l| " #{l}" }.join("\n")
105
+ job[:error] ||= e
106
+ job[:state] = :failed
107
+ end
108
+ end
109
+ end
110
+ end
111
+ @size += count
112
+ end
113
+ end
114
+ end
data/lib/scint.rb ADDED
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Scint
4
+ VERSION = "0.1.0"
5
+
6
+ # Color support: respects NO_COLOR (https://no-color.org) and TERM=dumb.
7
+ COLOR = !ENV.key?("NO_COLOR") && ENV["TERM"] != "dumb" && $stderr.tty?
8
+
9
+ GREEN = COLOR ? "\e[32m" : ""
10
+ RED = COLOR ? "\e[31m" : ""
11
+ YELLOW = COLOR ? "\e[33m" : ""
12
+ BOLD = COLOR ? "\e[1m" : ""
13
+ DIM = COLOR ? "\e[2m" : ""
14
+ RESET = COLOR ? "\e[0m" : ""
15
+
16
+ # XDG-based cache root
17
+ def self.cache_root
18
+ @cache_root ||= File.join(
19
+ ENV.fetch("XDG_CACHE_HOME", File.join(Dir.home, ".cache")),
20
+ "scint"
21
+ )
22
+ end
23
+
24
+ def self.cache_root=(path)
25
+ @cache_root = path
26
+ end
27
+
28
+ # Shared data structures used across all modules
29
+ Dependency = Struct.new(
30
+ :name, :version_reqs, :source, :groups, :platforms, :require_paths,
31
+ keyword_init: true
32
+ )
33
+
34
+ LockedSpec = Struct.new(
35
+ :name, :version, :platform, :dependencies, :source, :checksum,
36
+ keyword_init: true
37
+ )
38
+
39
+ ResolvedSpec = Struct.new(
40
+ :name, :version, :platform, :dependencies, :source, :has_extensions,
41
+ :remote_uri, :checksum,
42
+ keyword_init: true
43
+ )
44
+
45
+ PlanEntry = Struct.new(
46
+ :spec, :action, :cached_path, :gem_path,
47
+ keyword_init: true
48
+ )
49
+
50
+ PreparedGem = Struct.new(
51
+ :spec, :extracted_path, :gemspec, :from_cache,
52
+ keyword_init: true
53
+ )
54
+
55
+ # Autoloads — each file is loaded on first reference
56
+ autoload :CLI, "scint/cli"
57
+ autoload :Scheduler, "scint/scheduler"
58
+ autoload :WorkerPool, "scint/worker_pool"
59
+ autoload :Progress, "scint/progress"
60
+ autoload :FS, "scint/fs"
61
+ autoload :Platform, "scint/platform"
62
+
63
+ # Errors
64
+ autoload :BundlerError, "scint/errors"
65
+ autoload :GemfileError, "scint/errors"
66
+ autoload :LockfileError, "scint/errors"
67
+ autoload :ResolveError, "scint/errors"
68
+ autoload :NetworkError, "scint/errors"
69
+ autoload :InstallError, "scint/errors"
70
+ autoload :ExtensionBuildError, "scint/errors"
71
+ autoload :PermissionError, "scint/errors"
72
+ autoload :PlatformError, "scint/errors"
73
+ autoload :CacheError, "scint/errors"
74
+
75
+ # Submodule namespaces — defined here so autoloads don't trigger
76
+ # when files inside these modules reference the module name.
77
+ module Gemfile; end
78
+ module Lockfile; end
79
+ module Resolver; end
80
+ module Index; end
81
+ module Downloader; end
82
+ module Cache; end
83
+ module Installer; end
84
+ module Source; end
85
+ module Runtime; end
86
+ # NOTE: we do NOT define `module Gem` here — it would shadow ::Gem
87
+ end
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: scint
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Scint Contributors
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: Scint is a Bundler-compatible dependency installer that uses a scheduler-driven,
13
+ phase-oriented architecture to maximize parallel throughput while preserving Gemfile/Gemfile.lock
14
+ behavior.
15
+ email:
16
+ - maintainers@example.com
17
+ executables:
18
+ - scint
19
+ - scint-io-summary
20
+ - scint-syscall-trace
21
+ extensions: []
22
+ extra_rdoc_files: []
23
+ files:
24
+ - FEATURES.md
25
+ - README.md
26
+ - bin/bundler-vs-scint
27
+ - bin/scint
28
+ - bin/scint-io-summary
29
+ - bin/scint-syscall-trace
30
+ - lib/bundler.rb
31
+ - lib/bundler/setup.rb
32
+ - lib/scint.rb
33
+ - lib/scint/cache/layout.rb
34
+ - lib/scint/cache/metadata_store.rb
35
+ - lib/scint/cache/prewarm.rb
36
+ - lib/scint/cli.rb
37
+ - lib/scint/cli/add.rb
38
+ - lib/scint/cli/cache.rb
39
+ - lib/scint/cli/exec.rb
40
+ - lib/scint/cli/install.rb
41
+ - lib/scint/cli/remove.rb
42
+ - lib/scint/commands/exec.rb
43
+ - lib/scint/commands/install.rb
44
+ - lib/scint/credentials.rb
45
+ - lib/scint/debug/io_trace.rb
46
+ - lib/scint/debug/sampler.rb
47
+ - lib/scint/downloader/fetcher.rb
48
+ - lib/scint/downloader/pool.rb
49
+ - lib/scint/errors.rb
50
+ - lib/scint/fs.rb
51
+ - lib/scint/gem/extractor.rb
52
+ - lib/scint/gem/package.rb
53
+ - lib/scint/gemfile/dependency.rb
54
+ - lib/scint/gemfile/editor.rb
55
+ - lib/scint/gemfile/parser.rb
56
+ - lib/scint/index/cache.rb
57
+ - lib/scint/index/client.rb
58
+ - lib/scint/index/parser.rb
59
+ - lib/scint/installer/extension_builder.rb
60
+ - lib/scint/installer/linker.rb
61
+ - lib/scint/installer/planner.rb
62
+ - lib/scint/installer/preparer.rb
63
+ - lib/scint/lockfile/parser.rb
64
+ - lib/scint/lockfile/writer.rb
65
+ - lib/scint/platform.rb
66
+ - lib/scint/progress.rb
67
+ - lib/scint/resolver/provider.rb
68
+ - lib/scint/resolver/resolver.rb
69
+ - lib/scint/runtime/exec.rb
70
+ - lib/scint/runtime/setup.rb
71
+ - lib/scint/scheduler.rb
72
+ - lib/scint/source/base.rb
73
+ - lib/scint/source/git.rb
74
+ - lib/scint/source/path.rb
75
+ - lib/scint/source/rubygems.rb
76
+ - lib/scint/vendor/pub_grub.rb
77
+ - lib/scint/vendor/pub_grub/assignment.rb
78
+ - lib/scint/vendor/pub_grub/basic_package_source.rb
79
+ - lib/scint/vendor/pub_grub/failure_writer.rb
80
+ - lib/scint/vendor/pub_grub/incompatibility.rb
81
+ - lib/scint/vendor/pub_grub/package.rb
82
+ - lib/scint/vendor/pub_grub/partial_solution.rb
83
+ - lib/scint/vendor/pub_grub/rubygems.rb
84
+ - lib/scint/vendor/pub_grub/solve_failure.rb
85
+ - lib/scint/vendor/pub_grub/static_package_source.rb
86
+ - lib/scint/vendor/pub_grub/strategy.rb
87
+ - lib/scint/vendor/pub_grub/term.rb
88
+ - lib/scint/vendor/pub_grub/version.rb
89
+ - lib/scint/vendor/pub_grub/version_constraint.rb
90
+ - lib/scint/vendor/pub_grub/version_range.rb
91
+ - lib/scint/vendor/pub_grub/version_solver.rb
92
+ - lib/scint/vendor/pub_grub/version_union.rb
93
+ - lib/scint/worker_pool.rb
94
+ homepage: https://example.com/scint
95
+ licenses:
96
+ - MIT
97
+ metadata:
98
+ homepage_uri: https://example.com/scint
99
+ rdoc_options: []
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '3.1'
107
+ required_rubygems_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ requirements: []
113
+ rubygems_version: 3.6.9
114
+ specification_version: 4
115
+ summary: Fast Bundler-compatible installer with phased parallel execution
116
+ test_files: []