scint 0.6.0 → 0.7.1

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.
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require_relative "../platform"
5
+ require_relative "../installer/extension_builder"
6
+
7
+ module Scint
8
+ module Cache
9
+ class Telemetry
10
+ def initialize
11
+ @counts = Hash.new(0)
12
+ @mutex = Thread::Mutex.new
13
+ end
14
+
15
+ def increment(key, by = 1)
16
+ @mutex.synchronize do
17
+ @counts[key] += by
18
+ end
19
+ end
20
+
21
+ def counts
22
+ @mutex.synchronize { @counts.dup }
23
+ end
24
+
25
+ def warn_if_needed(cache_root:, io: $stderr)
26
+ snapshot = counts
27
+ return if snapshot.empty?
28
+
29
+ header = "Warning: legacy cache fallback used in #{cache_root}"
30
+ io.puts "#{YELLOW}#{header}#{RESET}"
31
+ snapshot.sort.each do |key, value|
32
+ io.puts " #{key}=#{value}"
33
+ end
34
+ end
35
+ end
36
+
37
+ module Validity
38
+ module_function
39
+
40
+ SUPPORTED_MANIFEST_VERSION = 1
41
+
42
+ def cached_valid?(spec, layout, abi_key: Platform.abi_key, telemetry: nil)
43
+ cached_dir = layout.cached_path(spec, abi_key)
44
+ spec_path = layout.cached_spec_path(spec, abi_key)
45
+ manifest_path = layout.cached_manifest_path(spec, abi_key)
46
+
47
+ return false unless Dir.exist?(cached_dir)
48
+ return false unless File.exist?(spec_path)
49
+
50
+ manifest = read_manifest(manifest_path, telemetry: telemetry)
51
+ if manifest
52
+ return false unless manifest_matches?(manifest, spec, abi_key, layout)
53
+ else
54
+ return false unless legacy_spec_loadable?(spec_path)
55
+ end
56
+
57
+ true
58
+ end
59
+
60
+ def source_path_for(spec, layout, abi_key: Platform.abi_key, telemetry: nil, allow_legacy: false)
61
+ cached_dir = layout.cached_path(spec, abi_key)
62
+ return cached_dir if cached_valid?(spec, layout, abi_key: abi_key, telemetry: telemetry)
63
+
64
+ return nil unless allow_legacy
65
+
66
+ legacy = layout.extracted_path(spec)
67
+ return nil unless legacy_extracted_valid?(spec, legacy, layout)
68
+
69
+ telemetry&.increment("cache.legacy.extracted")
70
+ legacy
71
+ end
72
+
73
+ def legacy_extracted_valid?(spec, extracted_dir, layout)
74
+ return false unless Dir.exist?(extracted_dir)
75
+
76
+ legacy_spec_path = layout.spec_cache_path(spec)
77
+ return true if File.exist?(legacy_spec_path)
78
+
79
+ return true if Dir.glob(File.join(extracted_dir, "*.gemspec")).any?
80
+
81
+ true
82
+ rescue StandardError
83
+ false
84
+ end
85
+
86
+ def read_manifest(path, telemetry: nil)
87
+ unless File.exist?(path)
88
+ telemetry&.increment("cache.manifest.missing")
89
+ return nil
90
+ end
91
+
92
+ raw = File.read(path)
93
+ raw.force_encoding("UTF-8") unless raw.encoding == Encoding::UTF_8
94
+ data = JSON.parse(raw)
95
+ return nil unless data.is_a?(Hash)
96
+
97
+ version = data["version"]
98
+ return data if version == SUPPORTED_MANIFEST_VERSION
99
+
100
+ telemetry&.increment("cache.manifest.unsupported")
101
+ nil
102
+ rescue StandardError
103
+ telemetry&.increment("cache.manifest.unsupported")
104
+ nil
105
+ end
106
+
107
+ def manifest_matches?(manifest, spec, abi_key, layout)
108
+ manifest["full_name"] == layout.full_name(spec) && manifest["abi"] == abi_key
109
+ rescue StandardError
110
+ false
111
+ end
112
+
113
+ def legacy_spec_loadable?(path)
114
+ Marshal.load(File.binread(path))
115
+ true
116
+ rescue StandardError
117
+ false
118
+ end
119
+
120
+ def extensions_required?(spec, cached_dir)
121
+ Installer::ExtensionBuilder.needs_build?(spec, cached_dir)
122
+ rescue StandardError
123
+ false
124
+ end
125
+
126
+ def extension_build_complete?(spec, layout, abi_key)
127
+ marker = File.join(layout.cached_path(spec, abi_key), Installer::ExtensionBuilder::BUILD_MARKER)
128
+ File.exist?(marker)
129
+ rescue StandardError
130
+ false
131
+ end
132
+ end
133
+ end
134
+ end
@@ -9,6 +9,7 @@ require_relative "../gemfile/parser"
9
9
  require_relative "../lockfile/parser"
10
10
  require_relative "install"
11
11
  require_relative "../fs"
12
+ require_relative "../spec_utils"
12
13
 
13
14
  module Scint
14
15
  module CLI
@@ -175,17 +176,45 @@ module Scint
175
176
 
176
177
  def clear_packages(packages)
177
178
  removed = 0
178
- %w[inbound extracted ext].each do |subdir|
179
- subdir_path = File.join(cache_root, subdir)
180
- next unless Dir.exist?(subdir_path)
181
179
 
182
- Dir.children(subdir_path).each do |entry|
183
- if packages.any? { |pkg| entry.start_with?(pkg) }
184
- FileUtils.rm_rf(File.join(subdir_path, entry))
180
+ packages.each do |pkg|
181
+ # inbound gems
182
+ Dir.glob(File.join(cache_root, "inbound", "gems", "#{pkg}-*.gem")).each do |path|
183
+ FileUtils.rm_rf(path)
184
+ removed += 1
185
+ end
186
+
187
+ # assembling + cached (per-ABI)
188
+ %w[assembling cached].each do |subdir|
189
+ Dir.glob(File.join(cache_root, subdir, "*", "#{pkg}-*")) do |path|
190
+ FileUtils.rm_rf(path)
191
+ removed += 1
192
+ end
193
+ Dir.glob(File.join(cache_root, subdir, "*", "#{pkg}-*.spec.marshal")) do |path|
194
+ FileUtils.rm_rf(path)
195
+ removed += 1
196
+ end
197
+ Dir.glob(File.join(cache_root, subdir, "*", "#{pkg}-*.manifest")) do |path|
198
+ FileUtils.rm_rf(path)
185
199
  removed += 1
186
200
  end
187
201
  end
202
+
203
+ # legacy directories (extracted/ext)
204
+ Dir.glob(File.join(cache_root, "extracted", "#{pkg}-*")) do |path|
205
+ FileUtils.rm_rf(path)
206
+ removed += 1
207
+ end
208
+ Dir.glob(File.join(cache_root, "extracted", "#{pkg}-*.spec.marshal")) do |path|
209
+ FileUtils.rm_rf(path)
210
+ removed += 1
211
+ end
212
+ Dir.glob(File.join(cache_root, "ext", "*", "#{pkg}-*")) do |path|
213
+ FileUtils.rm_rf(path)
214
+ removed += 1
215
+ end
188
216
  end
217
+
189
218
  removed
190
219
  end
191
220
 
@@ -306,7 +335,7 @@ module Scint
306
335
  def dedupe_specs(specs)
307
336
  seen = {}
308
337
  specs.each do |spec|
309
- key = "#{spec.name}-#{spec.version}-#{spec.platform}"
338
+ key = SpecUtils.full_key(spec)
310
339
  seen[key] ||= spec
311
340
  end
312
341
  seen.values
@@ -3,7 +3,9 @@
3
3
  require_relative "../runtime/exec"
4
4
  require_relative "../fs"
5
5
  require_relative "../platform"
6
+ require_relative "../spec_utils"
6
7
  require_relative "../lockfile/parser"
8
+ require "pathname"
7
9
 
8
10
  module Scint
9
11
  module CLI
@@ -68,19 +70,18 @@ module Scint
68
70
  data = {}
69
71
 
70
72
  lockfile.specs.each do |spec|
71
- full = spec_full_name(spec)
73
+ full = SpecUtils.full_name(spec)
72
74
  gem_dir = File.join(ruby_dir, "gems", full)
73
75
  next unless Dir.exist?(gem_dir)
74
76
 
75
77
  spec_file = File.join(ruby_dir, "specifications", "#{full}.gemspec")
76
78
  require_paths = read_require_paths(spec_file)
77
79
  load_paths = require_paths
78
- .map { |rp| File.join(gem_dir, rp) }
80
+ .map { |rp| expand_require_path(gem_dir, rp) }
79
81
  .select { |path| Dir.exist?(path) }
80
82
 
81
83
  lib_path = File.join(gem_dir, "lib")
82
84
  load_paths << lib_path if load_paths.empty? && Dir.exist?(lib_path)
83
- load_paths.concat(detect_nested_lib_paths(gem_dir))
84
85
  load_paths.uniq!
85
86
 
86
87
  ext_path = File.join(ruby_dir, "extensions",
@@ -102,7 +103,7 @@ module Scint
102
103
  end
103
104
 
104
105
  def detect_ruby_dir(bundle_dir)
105
- target = RUBY_VERSION.split(".")[0, 2].join(".") + ".0"
106
+ target = Platform.ruby_minor_version_dir
106
107
  preferred = File.join(bundle_dir, "ruby", target)
107
108
  return preferred if Dir.exist?(preferred)
108
109
 
@@ -111,39 +112,26 @@ module Scint
111
112
  end
112
113
 
113
114
  def spec_full_name(spec)
114
- name = spec[:name]
115
- version = spec[:version]
116
- platform = spec[:platform]
117
- base = "#{name}-#{version}"
118
- return base if platform.nil? || platform.to_s == "ruby" || platform.to_s.empty?
119
-
120
- "#{base}-#{platform}"
115
+ SpecUtils.full_name(spec)
121
116
  end
122
117
 
123
118
  def read_require_paths(spec_file)
124
119
  return ["lib"] unless File.exist?(spec_file)
125
120
 
126
- gemspec = Gem::Specification.load(spec_file)
121
+ gemspec = SpecUtils.load_gemspec(spec_file)
127
122
  paths = Array(gemspec&.require_paths).reject(&:empty?)
128
123
  paths.empty? ? ["lib"] : paths
129
124
  rescue StandardError
130
125
  ["lib"]
131
126
  end
132
127
 
133
- def detect_nested_lib_paths(gem_dir)
134
- lib_dir = File.join(gem_dir, "lib")
135
- return [] unless Dir.exist?(lib_dir)
136
-
137
- children = Dir.children(lib_dir)
138
- top_level_rb = children.any? do |entry|
139
- path = File.join(lib_dir, entry)
140
- File.file?(path) && entry.end_with?(".rb")
141
- end
142
- return [] if top_level_rb
128
+ def expand_require_path(gem_dir, require_path)
129
+ value = require_path.to_s
130
+ return value if Pathname.new(value).absolute?
143
131
 
144
- children
145
- .map { |entry| File.join(lib_dir, entry) }
146
- .select { |path| File.directory?(path) }
132
+ File.join(gem_dir, value)
133
+ rescue StandardError
134
+ File.join(gem_dir, require_path.to_s)
147
135
  end
148
136
  end
149
137
  end