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.
- checksums.yaml +7 -0
- data/FEATURES.md +13 -0
- data/README.md +216 -0
- data/bin/bundler-vs-scint +233 -0
- data/bin/scint +35 -0
- data/bin/scint-io-summary +46 -0
- data/bin/scint-syscall-trace +41 -0
- data/lib/bundler/setup.rb +5 -0
- data/lib/bundler.rb +168 -0
- data/lib/scint/cache/layout.rb +131 -0
- data/lib/scint/cache/metadata_store.rb +75 -0
- data/lib/scint/cache/prewarm.rb +192 -0
- data/lib/scint/cli/add.rb +85 -0
- data/lib/scint/cli/cache.rb +316 -0
- data/lib/scint/cli/exec.rb +150 -0
- data/lib/scint/cli/install.rb +1047 -0
- data/lib/scint/cli/remove.rb +60 -0
- data/lib/scint/cli.rb +77 -0
- data/lib/scint/commands/exec.rb +17 -0
- data/lib/scint/commands/install.rb +17 -0
- data/lib/scint/credentials.rb +153 -0
- data/lib/scint/debug/io_trace.rb +218 -0
- data/lib/scint/debug/sampler.rb +138 -0
- data/lib/scint/downloader/fetcher.rb +113 -0
- data/lib/scint/downloader/pool.rb +112 -0
- data/lib/scint/errors.rb +63 -0
- data/lib/scint/fs.rb +119 -0
- data/lib/scint/gem/extractor.rb +86 -0
- data/lib/scint/gem/package.rb +62 -0
- data/lib/scint/gemfile/dependency.rb +30 -0
- data/lib/scint/gemfile/editor.rb +93 -0
- data/lib/scint/gemfile/parser.rb +275 -0
- data/lib/scint/index/cache.rb +166 -0
- data/lib/scint/index/client.rb +301 -0
- data/lib/scint/index/parser.rb +142 -0
- data/lib/scint/installer/extension_builder.rb +264 -0
- data/lib/scint/installer/linker.rb +226 -0
- data/lib/scint/installer/planner.rb +140 -0
- data/lib/scint/installer/preparer.rb +207 -0
- data/lib/scint/lockfile/parser.rb +251 -0
- data/lib/scint/lockfile/writer.rb +178 -0
- data/lib/scint/platform.rb +71 -0
- data/lib/scint/progress.rb +579 -0
- data/lib/scint/resolver/provider.rb +230 -0
- data/lib/scint/resolver/resolver.rb +249 -0
- data/lib/scint/runtime/exec.rb +141 -0
- data/lib/scint/runtime/setup.rb +45 -0
- data/lib/scint/scheduler.rb +392 -0
- data/lib/scint/source/base.rb +46 -0
- data/lib/scint/source/git.rb +92 -0
- data/lib/scint/source/path.rb +70 -0
- data/lib/scint/source/rubygems.rb +79 -0
- data/lib/scint/vendor/pub_grub/assignment.rb +20 -0
- data/lib/scint/vendor/pub_grub/basic_package_source.rb +169 -0
- data/lib/scint/vendor/pub_grub/failure_writer.rb +182 -0
- data/lib/scint/vendor/pub_grub/incompatibility.rb +150 -0
- data/lib/scint/vendor/pub_grub/package.rb +43 -0
- data/lib/scint/vendor/pub_grub/partial_solution.rb +121 -0
- data/lib/scint/vendor/pub_grub/rubygems.rb +45 -0
- data/lib/scint/vendor/pub_grub/solve_failure.rb +19 -0
- data/lib/scint/vendor/pub_grub/static_package_source.rb +61 -0
- data/lib/scint/vendor/pub_grub/strategy.rb +42 -0
- data/lib/scint/vendor/pub_grub/term.rb +105 -0
- data/lib/scint/vendor/pub_grub/version.rb +3 -0
- data/lib/scint/vendor/pub_grub/version_constraint.rb +129 -0
- data/lib/scint/vendor/pub_grub/version_range.rb +423 -0
- data/lib/scint/vendor/pub_grub/version_solver.rb +236 -0
- data/lib/scint/vendor/pub_grub/version_union.rb +178 -0
- data/lib/scint/vendor/pub_grub.rb +32 -0
- data/lib/scint/worker_pool.rb +114 -0
- data/lib/scint.rb +87 -0
- metadata +116 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require_relative "../cache/layout"
|
|
5
|
+
require_relative "../cache/prewarm"
|
|
6
|
+
require_relative "../credentials"
|
|
7
|
+
require_relative "../gemfile/dependency"
|
|
8
|
+
require_relative "../gemfile/parser"
|
|
9
|
+
require_relative "../lockfile/parser"
|
|
10
|
+
require_relative "install"
|
|
11
|
+
require_relative "../fs"
|
|
12
|
+
|
|
13
|
+
module Scint
|
|
14
|
+
module CLI
|
|
15
|
+
class Cache
|
|
16
|
+
def initialize(argv = [])
|
|
17
|
+
@argv = argv.dup
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def run
|
|
21
|
+
subcommand = @argv.shift || "help"
|
|
22
|
+
|
|
23
|
+
case subcommand
|
|
24
|
+
when "add"
|
|
25
|
+
add
|
|
26
|
+
when "clean", "clear"
|
|
27
|
+
clean
|
|
28
|
+
when "dir", "path"
|
|
29
|
+
dir
|
|
30
|
+
when "size"
|
|
31
|
+
size
|
|
32
|
+
when "help", "-h", "--help"
|
|
33
|
+
help
|
|
34
|
+
0
|
|
35
|
+
else
|
|
36
|
+
$stderr.puts "Unknown cache subcommand: #{subcommand}"
|
|
37
|
+
$stderr.puts "Run 'scint cache help' for usage."
|
|
38
|
+
1
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def cache_root
|
|
45
|
+
@cache_root ||= Scint::Cache::Layout.new.root
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# scint cache clean [package...]
|
|
49
|
+
# With no args, clear everything. With args, clear only matching entries.
|
|
50
|
+
def clean
|
|
51
|
+
packages = @argv.dup
|
|
52
|
+
|
|
53
|
+
unless Dir.exist?(cache_root)
|
|
54
|
+
$stdout.puts "No cache to clear (#{cache_root} does not exist)"
|
|
55
|
+
return 0
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
if packages.empty?
|
|
59
|
+
removed = clear_all
|
|
60
|
+
$stdout.puts "Cleared #{removed} entries from #{cache_root}"
|
|
61
|
+
else
|
|
62
|
+
removed = clear_packages(packages)
|
|
63
|
+
$stdout.puts "Removed #{removed} entries matching: #{packages.join(", ")}"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
0
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# scint cache dir
|
|
70
|
+
def dir
|
|
71
|
+
$stdout.puts cache_root
|
|
72
|
+
0
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# scint cache size
|
|
76
|
+
def size
|
|
77
|
+
unless Dir.exist?(cache_root)
|
|
78
|
+
$stdout.puts "0 B (cache is empty)"
|
|
79
|
+
return 0
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
total_bytes = 0
|
|
83
|
+
total_files = 0
|
|
84
|
+
subdirs = {}
|
|
85
|
+
|
|
86
|
+
Dir.children(cache_root).sort.each do |subdir|
|
|
87
|
+
subdir_path = File.join(cache_root, subdir)
|
|
88
|
+
next unless File.directory?(subdir_path)
|
|
89
|
+
|
|
90
|
+
bytes = 0
|
|
91
|
+
files = 0
|
|
92
|
+
Dir.glob("**/*", base: subdir_path).each do |rel|
|
|
93
|
+
full = File.join(subdir_path, rel)
|
|
94
|
+
next unless File.file?(full)
|
|
95
|
+
bytes += File.size(full)
|
|
96
|
+
files += 1
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
subdirs[subdir] = { bytes: bytes, files: files }
|
|
100
|
+
total_bytes += bytes
|
|
101
|
+
total_files += files
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Print breakdown with aligned columns
|
|
105
|
+
all_rows = subdirs.map { |name, info| [name, info[:bytes], info[:files]] }
|
|
106
|
+
all_rows << ["total", total_bytes, total_files]
|
|
107
|
+
|
|
108
|
+
nw = all_rows.map { |r| r[0].length }.max
|
|
109
|
+
sw = all_rows.map { |r| format_size(r[1]).length }.max
|
|
110
|
+
fw = all_rows.map { |r| "#{r[2]} files".length }.max
|
|
111
|
+
|
|
112
|
+
all_rows.each do |name, bytes, files|
|
|
113
|
+
$stdout.printf " %-*s %*s %*s\n", nw, name, sw, format_size(bytes), fw, "#{files} files"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
0
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def help
|
|
120
|
+
$stdout.puts <<~HELP
|
|
121
|
+
Manage scint's cache
|
|
122
|
+
|
|
123
|
+
Usage: scint cache <COMMAND>
|
|
124
|
+
|
|
125
|
+
Commands:
|
|
126
|
+
add Prewarm cache from gem names and/or Gemfile/Gemfile.lock
|
|
127
|
+
clean Clear the cache, removing all entries or those linked to specific packages
|
|
128
|
+
dir Show the cache directory
|
|
129
|
+
size Show the cache size
|
|
130
|
+
HELP
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# scint cache add GEM [GEM...]
|
|
134
|
+
# scint cache add --lockfile Gemfile.lock
|
|
135
|
+
# scint cache add --gemfile Gemfile
|
|
136
|
+
def add
|
|
137
|
+
options = parse_add_options
|
|
138
|
+
if options[:gems].empty? && !options[:lockfile] && !options[:gemfile]
|
|
139
|
+
$stderr.puts "Usage: scint cache add GEM [GEM...] [--lockfile FILE] [--gemfile FILE] [--jobs N] [--force]"
|
|
140
|
+
return 1
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
specs = collect_specs_for_add(options)
|
|
144
|
+
prewarm = Scint::Cache::Prewarm.new(
|
|
145
|
+
cache_layout: Scint::Cache::Layout.new,
|
|
146
|
+
jobs: options[:jobs],
|
|
147
|
+
credentials: options[:credentials],
|
|
148
|
+
force: options[:force],
|
|
149
|
+
)
|
|
150
|
+
result = prewarm.run(specs)
|
|
151
|
+
|
|
152
|
+
if result[:failed] > 0
|
|
153
|
+
$stderr.puts "Cache prewarm failed for #{result[:failed]} gem(s):"
|
|
154
|
+
result[:failures].each do |failure|
|
|
155
|
+
spec = failure[:spec]
|
|
156
|
+
$stderr.puts " #{spec.name}: #{failure[:error].message}"
|
|
157
|
+
end
|
|
158
|
+
$stdout.puts "Cache add: #{result[:warmed]} warmed, #{result[:skipped]} skipped, #{result[:ignored]} ignored."
|
|
159
|
+
return 1
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
$stdout.puts "Cache add complete: #{result[:warmed]} warmed, #{result[:skipped]} skipped, #{result[:ignored]} ignored."
|
|
163
|
+
0
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# -- Implementation -------------------------------------------------------
|
|
167
|
+
|
|
168
|
+
def clear_all
|
|
169
|
+
entries = Dir.children(cache_root)
|
|
170
|
+
entries.each do |entry|
|
|
171
|
+
FileUtils.rm_rf(File.join(cache_root, entry))
|
|
172
|
+
end
|
|
173
|
+
entries.size
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def clear_packages(packages)
|
|
177
|
+
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
|
+
|
|
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))
|
|
185
|
+
removed += 1
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
removed
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def format_size(bytes)
|
|
193
|
+
if bytes < 1024
|
|
194
|
+
"#{bytes} B"
|
|
195
|
+
elsif bytes < 1024 * 1024
|
|
196
|
+
"#{(bytes / 1024.0).round(1)} KiB"
|
|
197
|
+
elsif bytes < 1024 * 1024 * 1024
|
|
198
|
+
"#{(bytes / (1024.0 * 1024)).round(1)} MiB"
|
|
199
|
+
else
|
|
200
|
+
"#{(bytes / (1024.0 * 1024 * 1024)).round(1)} GiB"
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def parse_add_options
|
|
205
|
+
opts = {
|
|
206
|
+
gems: [],
|
|
207
|
+
lockfile: nil,
|
|
208
|
+
gemfile: nil,
|
|
209
|
+
jobs: nil,
|
|
210
|
+
force: false,
|
|
211
|
+
version: nil,
|
|
212
|
+
source: "https://rubygems.org",
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
i = 0
|
|
216
|
+
while i < @argv.length
|
|
217
|
+
token = @argv[i]
|
|
218
|
+
case token
|
|
219
|
+
when "--lockfile"
|
|
220
|
+
opts[:lockfile] = @argv[i + 1]
|
|
221
|
+
i += 2
|
|
222
|
+
when "--gemfile"
|
|
223
|
+
opts[:gemfile] = @argv[i + 1]
|
|
224
|
+
i += 2
|
|
225
|
+
when "--jobs", "-j"
|
|
226
|
+
opts[:jobs] = @argv[i + 1]&.to_i
|
|
227
|
+
i += 2
|
|
228
|
+
when "--force", "-f"
|
|
229
|
+
opts[:force] = true
|
|
230
|
+
i += 1
|
|
231
|
+
when "--version"
|
|
232
|
+
opts[:version] = @argv[i + 1]
|
|
233
|
+
i += 2
|
|
234
|
+
when "--source"
|
|
235
|
+
opts[:source] = @argv[i + 1]
|
|
236
|
+
i += 2
|
|
237
|
+
else
|
|
238
|
+
if token.start_with?("-")
|
|
239
|
+
raise CacheError, "Unknown option for cache add: #{token}"
|
|
240
|
+
end
|
|
241
|
+
opts[:gems] << token
|
|
242
|
+
i += 1
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
opts[:credentials] = Credentials.new
|
|
247
|
+
opts
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def collect_specs_for_add(options)
|
|
251
|
+
specs = []
|
|
252
|
+
install = CLI::Install.new([])
|
|
253
|
+
install.instance_variable_set(:@credentials, options[:credentials])
|
|
254
|
+
|
|
255
|
+
if options[:lockfile]
|
|
256
|
+
lockfile = Scint::Lockfile::Parser.parse(options[:lockfile])
|
|
257
|
+
options[:credentials].register_lockfile_sources(lockfile.sources)
|
|
258
|
+
specs.concat(install.send(:lockfile_to_resolved, lockfile))
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
if options[:gemfile]
|
|
262
|
+
specs.concat(resolve_from_gemfile(install, options[:gemfile], options[:credentials]))
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
unless options[:gems].empty?
|
|
266
|
+
specs.concat(resolve_from_names(install, options))
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
dedupe_specs(specs)
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def resolve_from_gemfile(install, gemfile_path, credentials)
|
|
273
|
+
gemfile = Scint::Gemfile::Parser.parse(gemfile_path)
|
|
274
|
+
credentials.register_sources(gemfile.sources)
|
|
275
|
+
credentials.register_dependencies(gemfile.dependencies)
|
|
276
|
+
|
|
277
|
+
lockfile_path = File.join(File.dirname(File.expand_path(gemfile_path)), "Gemfile.lock")
|
|
278
|
+
lockfile = File.exist?(lockfile_path) ? Scint::Lockfile::Parser.parse(lockfile_path) : nil
|
|
279
|
+
credentials.register_lockfile_sources(lockfile.sources) if lockfile
|
|
280
|
+
|
|
281
|
+
if lockfile && install.send(:lockfile_current?, gemfile, lockfile)
|
|
282
|
+
install.send(:lockfile_to_resolved, lockfile)
|
|
283
|
+
else
|
|
284
|
+
install.send(:resolve, gemfile, lockfile, Scint::Cache::Layout.new)
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def resolve_from_names(install, options)
|
|
289
|
+
deps = options[:gems].map do |name|
|
|
290
|
+
source_options = {}
|
|
291
|
+
source_options[:source] = options[:source] if options[:source]
|
|
292
|
+
reqs = options[:version] ? [options[:version]] : [">= 0"]
|
|
293
|
+
Scint::Gemfile::Dependency.new(name, version_reqs: reqs, source_options: source_options)
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
gemfile = Scint::Gemfile::ParseResult.new(
|
|
297
|
+
dependencies: deps,
|
|
298
|
+
sources: [{ type: :rubygems, uri: options[:source] }],
|
|
299
|
+
ruby_version: nil,
|
|
300
|
+
platforms: [],
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
install.send(:resolve, gemfile, nil, Scint::Cache::Layout.new)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def dedupe_specs(specs)
|
|
307
|
+
seen = {}
|
|
308
|
+
specs.each do |spec|
|
|
309
|
+
key = "#{spec.name}-#{spec.version}-#{spec.platform}"
|
|
310
|
+
seen[key] ||= spec
|
|
311
|
+
end
|
|
312
|
+
seen.values
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
end
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../runtime/exec"
|
|
4
|
+
require_relative "../fs"
|
|
5
|
+
require_relative "../platform"
|
|
6
|
+
require_relative "../lockfile/parser"
|
|
7
|
+
|
|
8
|
+
module Scint
|
|
9
|
+
module CLI
|
|
10
|
+
class Exec
|
|
11
|
+
RUNTIME_LOCK = "scint.lock.marshal"
|
|
12
|
+
|
|
13
|
+
def initialize(argv = [])
|
|
14
|
+
@argv = argv
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def run
|
|
18
|
+
if @argv.empty?
|
|
19
|
+
$stderr.puts "scint exec requires a command to run"
|
|
20
|
+
return 1
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
command = @argv.first
|
|
24
|
+
args = @argv[1..] || []
|
|
25
|
+
|
|
26
|
+
lock_path = find_lock_path
|
|
27
|
+
unless lock_path
|
|
28
|
+
$stderr.puts "No runtime lock found. Run `scint install` first."
|
|
29
|
+
return 1
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# This calls Kernel.exec and never returns on success
|
|
33
|
+
Runtime::Exec.exec(command, args, lock_path)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def find_lock_path
|
|
39
|
+
# Walk up from cwd looking for .bundle/scint.lock.marshal.
|
|
40
|
+
# If missing but Gemfile.lock + installed gems exist, rebuild it.
|
|
41
|
+
dir = Dir.pwd
|
|
42
|
+
loop do
|
|
43
|
+
bundle_dir = File.join(dir, ".bundle")
|
|
44
|
+
candidate = File.join(dir, ".bundle", RUNTIME_LOCK)
|
|
45
|
+
return candidate if File.exist?(candidate)
|
|
46
|
+
|
|
47
|
+
rebuilt = rebuild_runtime_lock(dir, bundle_dir, candidate)
|
|
48
|
+
return rebuilt if rebuilt
|
|
49
|
+
|
|
50
|
+
parent = File.dirname(dir)
|
|
51
|
+
break if parent == dir # reached root
|
|
52
|
+
dir = parent
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def rebuild_runtime_lock(project_dir, bundle_dir, lock_path)
|
|
59
|
+
return nil unless Dir.exist?(File.join(bundle_dir, "ruby"))
|
|
60
|
+
|
|
61
|
+
gemfile_lock = File.join(project_dir, "Gemfile.lock")
|
|
62
|
+
return nil unless File.exist?(gemfile_lock)
|
|
63
|
+
|
|
64
|
+
ruby_dir = detect_ruby_dir(bundle_dir)
|
|
65
|
+
return nil unless ruby_dir
|
|
66
|
+
|
|
67
|
+
lockfile = Scint::Lockfile::Parser.parse(gemfile_lock)
|
|
68
|
+
data = {}
|
|
69
|
+
|
|
70
|
+
lockfile.specs.each do |spec|
|
|
71
|
+
full = spec_full_name(spec)
|
|
72
|
+
gem_dir = File.join(ruby_dir, "gems", full)
|
|
73
|
+
next unless Dir.exist?(gem_dir)
|
|
74
|
+
|
|
75
|
+
spec_file = File.join(ruby_dir, "specifications", "#{full}.gemspec")
|
|
76
|
+
require_paths = read_require_paths(spec_file)
|
|
77
|
+
load_paths = require_paths
|
|
78
|
+
.map { |rp| File.join(gem_dir, rp) }
|
|
79
|
+
.select { |path| Dir.exist?(path) }
|
|
80
|
+
|
|
81
|
+
lib_path = File.join(gem_dir, "lib")
|
|
82
|
+
load_paths << lib_path if load_paths.empty? && Dir.exist?(lib_path)
|
|
83
|
+
load_paths.concat(detect_nested_lib_paths(gem_dir))
|
|
84
|
+
load_paths.uniq!
|
|
85
|
+
|
|
86
|
+
ext_path = File.join(ruby_dir, "extensions",
|
|
87
|
+
Platform.gem_arch, Platform.extension_api_version, full)
|
|
88
|
+
load_paths << ext_path if Dir.exist?(ext_path)
|
|
89
|
+
|
|
90
|
+
data[spec[:name]] = {
|
|
91
|
+
version: spec[:version].to_s,
|
|
92
|
+
load_paths: load_paths,
|
|
93
|
+
}
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
return nil if data.empty?
|
|
97
|
+
|
|
98
|
+
FS.atomic_write(lock_path, Marshal.dump(data))
|
|
99
|
+
lock_path
|
|
100
|
+
rescue StandardError
|
|
101
|
+
nil
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def detect_ruby_dir(bundle_dir)
|
|
105
|
+
target = RUBY_VERSION.split(".")[0, 2].join(".") + ".0"
|
|
106
|
+
preferred = File.join(bundle_dir, "ruby", target)
|
|
107
|
+
return preferred if Dir.exist?(preferred)
|
|
108
|
+
|
|
109
|
+
dirs = Dir.glob(File.join(bundle_dir, "ruby", "*")).select { |path| Dir.exist?(path) }
|
|
110
|
+
dirs.sort.first
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
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}"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def read_require_paths(spec_file)
|
|
124
|
+
return ["lib"] unless File.exist?(spec_file)
|
|
125
|
+
|
|
126
|
+
gemspec = Gem::Specification.load(spec_file)
|
|
127
|
+
paths = Array(gemspec&.require_paths).reject(&:empty?)
|
|
128
|
+
paths.empty? ? ["lib"] : paths
|
|
129
|
+
rescue StandardError
|
|
130
|
+
["lib"]
|
|
131
|
+
end
|
|
132
|
+
|
|
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
|
|
143
|
+
|
|
144
|
+
children
|
|
145
|
+
.map { |entry| File.join(lib_dir, entry) }
|
|
146
|
+
.select { |path| File.directory?(path) }
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|