scint 0.1.0 → 0.7.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 +4 -4
- data/README.md +90 -41
- data/VERSION +1 -0
- data/bin/scint +9 -0
- data/lib/bundler.rb +106 -0
- data/lib/scint/cache/layout.rb +16 -14
- data/lib/scint/cache/metadata_store.rb +4 -11
- data/lib/scint/cli/cache.rb +2 -1
- data/lib/scint/cli/exec.rb +12 -24
- data/lib/scint/cli/install.rb +1214 -134
- data/lib/scint/credentials.rb +78 -15
- data/lib/scint/debug/io_trace.rb +26 -7
- data/lib/scint/downloader/fetcher.rb +25 -1
- data/lib/scint/downloader/pool.rb +67 -15
- data/lib/scint/errors.rb +10 -0
- data/lib/scint/fs.rb +90 -3
- data/lib/scint/gemfile/parser.rb +31 -4
- data/lib/scint/index/client.rb +1 -1
- data/lib/scint/installer/extension_builder.rb +95 -30
- data/lib/scint/installer/linker.rb +9 -25
- data/lib/scint/installer/planner.rb +37 -13
- data/lib/scint/installer/preparer.rb +2 -9
- data/lib/scint/lockfile/parser.rb +2 -1
- data/lib/scint/lockfile/writer.rb +78 -35
- data/lib/scint/platform.rb +8 -0
- data/lib/scint/progress.rb +128 -73
- data/lib/scint/resolver/provider.rb +71 -7
- data/lib/scint/runtime/exec.rb +52 -26
- data/lib/scint/runtime/setup.rb +29 -1
- data/lib/scint/scheduler.rb +6 -1
- data/lib/scint/spec_utils.rb +58 -0
- data/lib/scint/vendor/pub_grub/version.rb +5 -1
- data/lib/scint/version.rb +5 -0
- data/lib/scint.rb +3 -2
- metadata +5 -7
- data/bin/bundler-vs-scint +0 -233
- data/bin/scint-io-summary +0 -46
- data/bin/scint-syscall-trace +0 -41
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require_relative "../fs"
|
|
4
4
|
require_relative "../platform"
|
|
5
5
|
require_relative "../errors"
|
|
6
|
+
require_relative "../spec_utils"
|
|
6
7
|
require "open3"
|
|
7
8
|
|
|
8
9
|
module Scint
|
|
@@ -16,7 +17,7 @@ module Scint
|
|
|
16
17
|
# abi_key: e.g. "ruby-3.3.0-arm64-darwin24" (defaults to Platform.abi_key)
|
|
17
18
|
def build(prepared_gem, bundle_path, cache_layout, abi_key: Platform.abi_key, compile_slots: 1, output_tail: nil)
|
|
18
19
|
spec = prepared_gem.spec
|
|
19
|
-
ruby_dir = ruby_install_dir(bundle_path)
|
|
20
|
+
ruby_dir = Platform.ruby_install_dir(bundle_path)
|
|
20
21
|
build_ruby_dir = cache_layout.install_ruby_dir
|
|
21
22
|
|
|
22
23
|
# Check global extension cache first
|
|
@@ -28,17 +29,28 @@ module Scint
|
|
|
28
29
|
|
|
29
30
|
# Build in a temp dir, then cache
|
|
30
31
|
src_dir = prepared_gem.extracted_path
|
|
31
|
-
ext_dirs = find_extension_dirs(src_dir)
|
|
32
|
-
raise ExtensionBuildError, "No extension directories found for #{spec.name}" if ext_dirs.empty?
|
|
33
|
-
|
|
34
32
|
FS.with_tempdir("scint-ext") do |tmpdir|
|
|
35
|
-
|
|
33
|
+
# Stage the full gem source tree in an isolated workspace.
|
|
34
|
+
# Many extconf scripts use paths like ../../vendor relative to ext/,
|
|
35
|
+
# which only work when the full gem layout is preserved.
|
|
36
|
+
staged_src_dir = File.join(tmpdir, "source")
|
|
37
|
+
FS.clone_tree(src_dir, staged_src_dir)
|
|
38
|
+
|
|
39
|
+
ext_dirs = find_extension_dirs(staged_src_dir)
|
|
40
|
+
raise ExtensionBuildError, "No extension directories found for #{spec.name}" if ext_dirs.empty?
|
|
41
|
+
|
|
42
|
+
build_root = File.join(tmpdir, "build")
|
|
36
43
|
install_dir = File.join(tmpdir, "install")
|
|
37
|
-
FS.mkdir_p(
|
|
44
|
+
FS.mkdir_p(build_root)
|
|
38
45
|
FS.mkdir_p(install_dir)
|
|
39
46
|
|
|
40
|
-
ext_dirs.
|
|
41
|
-
|
|
47
|
+
ext_dirs.each_with_index do |ext_dir, idx|
|
|
48
|
+
# Keep isolated build trees per extension directory. Some gems
|
|
49
|
+
# invoke multiple CMake projects under ext/ and CMake caches are
|
|
50
|
+
# source-tree specific.
|
|
51
|
+
ext_build_dir = File.join(build_root, idx.to_s)
|
|
52
|
+
FS.mkdir_p(ext_build_dir)
|
|
53
|
+
compile_extension(ext_dir, ext_build_dir, install_dir, staged_src_dir, spec, build_ruby_dir, compile_slots, output_tail)
|
|
42
54
|
end
|
|
43
55
|
|
|
44
56
|
# Write marker
|
|
@@ -65,12 +77,47 @@ module Scint
|
|
|
65
77
|
spec = prepared_gem.spec
|
|
66
78
|
return false unless cached_build_available?(spec, cache_layout, abi_key: abi_key)
|
|
67
79
|
|
|
68
|
-
ruby_dir = ruby_install_dir(bundle_path)
|
|
80
|
+
ruby_dir = Platform.ruby_install_dir(bundle_path)
|
|
69
81
|
cached_ext = cache_layout.ext_path(spec, abi_key)
|
|
70
82
|
link_extensions(cached_ext, ruby_dir, spec, abi_key)
|
|
71
83
|
true
|
|
72
84
|
end
|
|
73
85
|
|
|
86
|
+
# True when a gem has native extension sources that need compiling.
|
|
87
|
+
# Platform-specific gems usually ship precompiled binaries and should
|
|
88
|
+
# not be compiled from ext/ unless they lack support for this Ruby.
|
|
89
|
+
def needs_build?(spec, gem_dir)
|
|
90
|
+
platform = spec.respond_to?(:platform) ? spec.platform : nil
|
|
91
|
+
if platform && !platform.to_s.empty? && platform.to_s != "ruby"
|
|
92
|
+
return prebuilt_missing_for_ruby?(gem_dir) && buildable_source_dir?(gem_dir)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
buildable_source_dir?(gem_dir)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Detect versioned prebuilt extension folders like:
|
|
99
|
+
# lib/sqlite3/3.1, lib/sqlite3/3.2 ...
|
|
100
|
+
# If present, the current Ruby minor must exist or we need a build.
|
|
101
|
+
def prebuilt_missing_for_ruby?(gem_dir)
|
|
102
|
+
ruby_minor = RUBY_VERSION[/\d+\.\d+/]
|
|
103
|
+
lib_dir = File.join(gem_dir, "lib")
|
|
104
|
+
return false unless Dir.exist?(lib_dir)
|
|
105
|
+
|
|
106
|
+
Dir.children(lib_dir).each do |child|
|
|
107
|
+
child_path = File.join(lib_dir, child)
|
|
108
|
+
next unless File.directory?(child_path)
|
|
109
|
+
|
|
110
|
+
version_dirs = Dir.children(child_path).select do |entry|
|
|
111
|
+
File.directory?(File.join(child_path, entry)) && entry.match?(/\A\d+\.\d+\z/)
|
|
112
|
+
end
|
|
113
|
+
next if version_dirs.empty?
|
|
114
|
+
|
|
115
|
+
return true unless version_dirs.include?(ruby_minor)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
false
|
|
119
|
+
end
|
|
120
|
+
|
|
74
121
|
# --- private ---
|
|
75
122
|
|
|
76
123
|
def buildable_source_dir?(gem_dir)
|
|
@@ -85,9 +132,18 @@ module Scint
|
|
|
85
132
|
dirs << File.dirname(path)
|
|
86
133
|
end
|
|
87
134
|
|
|
88
|
-
# CMakeLists.txt in ext
|
|
89
|
-
|
|
90
|
-
|
|
135
|
+
# CMakeLists.txt in ext/. Keep only top-level CMake roots, so vendored
|
|
136
|
+
# subprojects (e.g. deps/*) are not built standalone.
|
|
137
|
+
cmake_dirs = Dir.glob(File.join(gem_dir, "ext", "**", "CMakeLists.txt"))
|
|
138
|
+
.map { |path| File.dirname(path) }
|
|
139
|
+
.uniq
|
|
140
|
+
.sort_by { |dir| [dir.length, dir] }
|
|
141
|
+
cmake_roots = []
|
|
142
|
+
cmake_dirs.each do |dir|
|
|
143
|
+
next if cmake_roots.any? { |root| dir.start_with?("#{root}/") }
|
|
144
|
+
cmake_roots << dir
|
|
145
|
+
end
|
|
146
|
+
cmake_roots.each do |dir|
|
|
91
147
|
dirs << dir unless dirs.include?(dir)
|
|
92
148
|
end
|
|
93
149
|
|
|
@@ -106,7 +162,7 @@ module Scint
|
|
|
106
162
|
env = build_env(gem_dir, build_ruby_dir, make_jobs)
|
|
107
163
|
|
|
108
164
|
if File.exist?(File.join(ext_dir, "extconf.rb"))
|
|
109
|
-
compile_extconf(ext_dir, build_dir, install_dir, env, make_jobs, output_tail)
|
|
165
|
+
compile_extconf(ext_dir, gem_dir, build_dir, install_dir, env, make_jobs, output_tail)
|
|
110
166
|
elsif File.exist?(File.join(ext_dir, "CMakeLists.txt"))
|
|
111
167
|
compile_cmake(ext_dir, build_dir, install_dir, env, make_jobs, output_tail)
|
|
112
168
|
elsif File.exist?(File.join(ext_dir, "Rakefile"))
|
|
@@ -116,13 +172,18 @@ module Scint
|
|
|
116
172
|
end
|
|
117
173
|
end
|
|
118
174
|
|
|
119
|
-
def compile_extconf(ext_dir, build_dir, install_dir, env, make_jobs, output_tail = nil)
|
|
175
|
+
def compile_extconf(ext_dir, gem_dir, build_dir, install_dir, env, make_jobs, output_tail = nil)
|
|
176
|
+
# Build in-place within the staged ext directory so extconf scripts
|
|
177
|
+
# that navigate relative paths (../../vendor, ../..) behave like
|
|
178
|
+
# Bundler's install layout.
|
|
179
|
+
_ = gem_dir
|
|
180
|
+
_ = build_dir
|
|
120
181
|
run_cmd(env, RbConfig.ruby, File.join(ext_dir, "extconf.rb"),
|
|
121
182
|
"--with-opt-dir=#{RbConfig::CONFIG["prefix"]}",
|
|
122
|
-
chdir:
|
|
123
|
-
run_cmd(env, "make", "-j#{make_jobs}", "-C",
|
|
183
|
+
chdir: ext_dir, output_tail: output_tail)
|
|
184
|
+
run_cmd(env, "make", "-j#{make_jobs}", "-C", ext_dir, output_tail: output_tail)
|
|
124
185
|
run_cmd(env, "make", "install", "DESTDIR=", "sitearchdir=#{install_dir}", "sitelibdir=#{install_dir}",
|
|
125
|
-
chdir:
|
|
186
|
+
chdir: ext_dir, output_tail: output_tail)
|
|
126
187
|
end
|
|
127
188
|
|
|
128
189
|
def compile_cmake(ext_dir, build_dir, install_dir, env, make_jobs, output_tail = nil)
|
|
@@ -173,9 +234,21 @@ module Scint
|
|
|
173
234
|
ext_install_dir = File.join(ruby_dir, "extensions",
|
|
174
235
|
Platform.gem_arch, Platform.extension_api_version,
|
|
175
236
|
spec_full_name(spec))
|
|
176
|
-
|
|
237
|
+
FS.clone_tree(cached_ext, ext_install_dir) unless Dir.exist?(ext_install_dir)
|
|
238
|
+
sync_extension_artifacts_into_gem(ext_install_dir, ruby_dir, spec)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def sync_extension_artifacts_into_gem(ext_install_dir, ruby_dir, spec)
|
|
242
|
+
gem_dir = File.join(ruby_dir, "gems", spec_full_name(spec))
|
|
243
|
+
lib_dir = File.join(gem_dir, "lib")
|
|
244
|
+
return unless Dir.exist?(lib_dir)
|
|
177
245
|
|
|
178
|
-
|
|
246
|
+
Dir.glob(File.join(ext_install_dir, "**", "*.{so,bundle,dll,dylib}")).each do |artifact|
|
|
247
|
+
rel = artifact.delete_prefix("#{ext_install_dir}/")
|
|
248
|
+
dest = File.join(lib_dir, rel)
|
|
249
|
+
FS.mkdir_p(File.dirname(dest))
|
|
250
|
+
FS.clonefile(artifact, dest)
|
|
251
|
+
end
|
|
179
252
|
end
|
|
180
253
|
|
|
181
254
|
def build_env(gem_dir, build_ruby_dir, make_jobs)
|
|
@@ -244,21 +317,13 @@ module Scint
|
|
|
244
317
|
end
|
|
245
318
|
|
|
246
319
|
def spec_full_name(spec)
|
|
247
|
-
|
|
248
|
-
version = spec.version
|
|
249
|
-
plat = spec.respond_to?(:platform) ? spec.platform : nil
|
|
250
|
-
base = "#{name}-#{version}"
|
|
251
|
-
(plat.nil? || plat.to_s == "ruby" || plat.to_s.empty?) ? base : "#{base}-#{plat}"
|
|
252
|
-
end
|
|
253
|
-
|
|
254
|
-
def ruby_install_dir(bundle_path)
|
|
255
|
-
File.join(bundle_path, "ruby", RUBY_VERSION.split(".")[0, 2].join(".") + ".0")
|
|
320
|
+
SpecUtils.full_name(spec)
|
|
256
321
|
end
|
|
257
322
|
|
|
258
323
|
private_class_method :find_extension_dirs, :compile_extension,
|
|
259
324
|
:compile_extconf, :compile_cmake, :compile_rake,
|
|
260
|
-
:find_rake_executable, :link_extensions, :
|
|
261
|
-
:
|
|
325
|
+
:find_rake_executable, :link_extensions, :sync_extension_artifacts_into_gem,
|
|
326
|
+
:build_env, :run_cmd, :prebuilt_missing_for_ruby?
|
|
262
327
|
end
|
|
263
328
|
end
|
|
264
329
|
end
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../fs"
|
|
4
4
|
require_relative "../platform"
|
|
5
|
+
require_relative "../spec_utils"
|
|
5
6
|
require "pathname"
|
|
6
7
|
|
|
7
8
|
module Scint
|
|
@@ -20,7 +21,7 @@ module Scint
|
|
|
20
21
|
# Link gem files + gemspec only (no binstubs).
|
|
21
22
|
# This allows binstubs to be scheduled as a separate DAG task.
|
|
22
23
|
def link_files(prepared_gem, bundle_path)
|
|
23
|
-
ruby_dir = ruby_install_dir(bundle_path)
|
|
24
|
+
ruby_dir = Platform.ruby_install_dir(bundle_path)
|
|
24
25
|
link_files_to_ruby_dir(prepared_gem, ruby_dir)
|
|
25
26
|
end
|
|
26
27
|
|
|
@@ -28,12 +29,12 @@ module Scint
|
|
|
28
29
|
# This is used for the install-time hermetic build environment.
|
|
29
30
|
def link_files_to_ruby_dir(prepared_gem, ruby_dir)
|
|
30
31
|
spec = prepared_gem.spec
|
|
31
|
-
full_name =
|
|
32
|
+
full_name = SpecUtils.full_name(spec)
|
|
32
33
|
|
|
33
34
|
# 1. Link gem files into gems/{full_name}/
|
|
34
35
|
gem_dest = File.join(ruby_dir, "gems", full_name)
|
|
35
36
|
unless Dir.exist?(gem_dest)
|
|
36
|
-
FS.
|
|
37
|
+
FS.clone_tree(prepared_gem.extracted_path, gem_dest)
|
|
37
38
|
end
|
|
38
39
|
|
|
39
40
|
# 2. Write gemspec into specifications/
|
|
@@ -42,9 +43,9 @@ module Scint
|
|
|
42
43
|
|
|
43
44
|
# Write binstubs for one already-linked gem.
|
|
44
45
|
def write_binstubs(prepared_gem, bundle_path)
|
|
45
|
-
ruby_dir = ruby_install_dir(bundle_path)
|
|
46
|
+
ruby_dir = Platform.ruby_install_dir(bundle_path)
|
|
46
47
|
spec = prepared_gem.spec
|
|
47
|
-
full_name =
|
|
48
|
+
full_name = SpecUtils.full_name(spec)
|
|
48
49
|
gem_dest = File.join(ruby_dir, "gems", full_name)
|
|
49
50
|
return unless Dir.exist?(gem_dest)
|
|
50
51
|
|
|
@@ -101,7 +102,7 @@ module Scint
|
|
|
101
102
|
#!/usr/bin/env ruby
|
|
102
103
|
# frozen_string_literal: true
|
|
103
104
|
#
|
|
104
|
-
# This file was generated by scint for #{
|
|
105
|
+
# This file was generated by scint for #{SpecUtils.full_name(spec)}
|
|
105
106
|
#
|
|
106
107
|
gem "#{spec.name}", "#{spec.version}"
|
|
107
108
|
load Gem.bin_path("#{spec.name}", "#{exe_name}", "#{spec.version}")
|
|
@@ -193,34 +194,17 @@ module Scint
|
|
|
193
194
|
Gem::Specification.new do |s|
|
|
194
195
|
s.name = #{spec.name.inspect}
|
|
195
196
|
s.version = #{spec.version.to_s.inspect}
|
|
196
|
-
s.platform = #{platform_str(spec).inspect}
|
|
197
|
+
s.platform = #{SpecUtils.platform_str(spec).inspect}
|
|
197
198
|
s.authors = ["scint"]
|
|
198
199
|
s.summary = "Installed by scint"
|
|
199
200
|
end
|
|
200
201
|
RUBY
|
|
201
202
|
end
|
|
202
203
|
|
|
203
|
-
def spec_full_name(spec)
|
|
204
|
-
name = spec.name
|
|
205
|
-
version = spec.version
|
|
206
|
-
plat = platform_str(spec)
|
|
207
|
-
base = "#{name}-#{version}"
|
|
208
|
-
(plat == "ruby" || plat.empty?) ? base : "#{base}-#{plat}"
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
def platform_str(spec)
|
|
212
|
-
p = spec.respond_to?(:platform) ? spec.platform : nil
|
|
213
|
-
p.nil? ? "ruby" : p.to_s
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
def ruby_install_dir(bundle_path)
|
|
217
|
-
File.join(bundle_path, "ruby", RUBY_VERSION.split(".")[0, 2].join(".") + ".0")
|
|
218
|
-
end
|
|
219
|
-
|
|
220
204
|
private_class_method :write_gemspec, :write_binstubs_impl, :write_ruby_bin_stub,
|
|
221
205
|
:write_bundle_bin_wrapper, :extract_executables,
|
|
222
206
|
:detect_executables_from_files, :augment_executable_metadata, :infer_bindir,
|
|
223
|
-
:minimal_gemspec
|
|
207
|
+
:minimal_gemspec
|
|
224
208
|
end
|
|
225
209
|
end
|
|
226
210
|
end
|
|
@@ -7,6 +7,7 @@ module Scint
|
|
|
7
7
|
module Installer
|
|
8
8
|
module Planner
|
|
9
9
|
module_function
|
|
10
|
+
PATH_GLOB_DEFAULT = "{,*,*/*}.gemspec"
|
|
10
11
|
|
|
11
12
|
# Compare resolved specs against what's already installed.
|
|
12
13
|
# Returns an Array of PlanEntry with action set to one of:
|
|
@@ -18,16 +19,17 @@ module Scint
|
|
|
18
19
|
# Download entries are sorted largest-first so big gems start early,
|
|
19
20
|
# keeping the pipeline saturated while small gems fill in gaps.
|
|
20
21
|
def plan(resolved_specs, bundle_path, cache_layout)
|
|
21
|
-
ruby_dir = ruby_install_dir(bundle_path)
|
|
22
|
+
ruby_dir = Platform.ruby_install_dir(bundle_path)
|
|
22
23
|
entries = resolved_specs.map do |spec|
|
|
23
24
|
plan_one(spec, ruby_dir, cache_layout)
|
|
24
25
|
end
|
|
25
26
|
|
|
26
|
-
#
|
|
27
|
-
|
|
27
|
+
# Keep built-ins first, then downloads (big->small), then the rest.
|
|
28
|
+
builtins, non_builtins = entries.partition { |e| e.action == :builtin }
|
|
29
|
+
downloads, rest = non_builtins.partition { |e| e.action == :download }
|
|
28
30
|
downloads.sort_by! { |e| -(estimated_size(e.spec)) }
|
|
29
31
|
|
|
30
|
-
downloads + rest
|
|
32
|
+
builtins + downloads + rest
|
|
31
33
|
end
|
|
32
34
|
|
|
33
35
|
def plan_one(spec, ruby_dir, cache_layout)
|
|
@@ -58,7 +60,7 @@ module Scint
|
|
|
58
60
|
# Local path sources are linked directly from their source tree.
|
|
59
61
|
local_source = local_source_path(spec)
|
|
60
62
|
if local_source
|
|
61
|
-
action = ExtensionBuilder.
|
|
63
|
+
action = ExtensionBuilder.needs_build?(spec, local_source) ? :build_ext : :link
|
|
62
64
|
return PlanEntry.new(spec: spec, action: action, cached_path: local_source, gem_path: gem_path)
|
|
63
65
|
end
|
|
64
66
|
|
|
@@ -75,7 +77,7 @@ module Scint
|
|
|
75
77
|
|
|
76
78
|
def needs_ext_build?(spec, cache_layout)
|
|
77
79
|
extracted = cache_layout.extracted_path(spec)
|
|
78
|
-
return false unless ExtensionBuilder.
|
|
80
|
+
return false unless ExtensionBuilder.needs_build?(spec, extracted)
|
|
79
81
|
|
|
80
82
|
!ExtensionBuilder.cached_build_available?(spec, cache_layout)
|
|
81
83
|
end
|
|
@@ -83,7 +85,7 @@ module Scint
|
|
|
83
85
|
def extension_link_missing?(spec, ruby_dir, cache_layout)
|
|
84
86
|
extracted = cache_layout.extracted_path(spec)
|
|
85
87
|
return false unless Dir.exist?(extracted)
|
|
86
|
-
return false unless ExtensionBuilder.
|
|
88
|
+
return false unless ExtensionBuilder.needs_build?(spec, extracted)
|
|
87
89
|
|
|
88
90
|
full = cache_layout.full_name(spec)
|
|
89
91
|
ext_install_dir = File.join(
|
|
@@ -97,10 +99,6 @@ module Scint
|
|
|
97
99
|
!Dir.exist?(ext_install_dir)
|
|
98
100
|
end
|
|
99
101
|
|
|
100
|
-
def ruby_install_dir(bundle_path)
|
|
101
|
-
File.join(bundle_path, "ruby", RUBY_VERSION.split(".")[0, 2].join(".") + ".0")
|
|
102
|
-
end
|
|
103
|
-
|
|
104
102
|
# Rough size estimate for download ordering.
|
|
105
103
|
# If we don't know, use 0 so unknowns sort after large known gems.
|
|
106
104
|
def estimated_size(spec)
|
|
@@ -130,11 +128,37 @@ module Scint
|
|
|
130
128
|
return nil if source_str.end_with?(".git") || source_str.include?(".git/")
|
|
131
129
|
|
|
132
130
|
absolute = File.expand_path(source_str, Dir.pwd)
|
|
133
|
-
Dir.exist?(absolute)
|
|
131
|
+
return nil unless Dir.exist?(absolute)
|
|
132
|
+
|
|
133
|
+
spec_name =
|
|
134
|
+
if spec.respond_to?(:name)
|
|
135
|
+
spec.name.to_s
|
|
136
|
+
else
|
|
137
|
+
spec[:name].to_s
|
|
138
|
+
end
|
|
139
|
+
return absolute if spec_name.empty?
|
|
140
|
+
return absolute if File.exist?(File.join(absolute, "#{spec_name}.gemspec"))
|
|
141
|
+
|
|
142
|
+
glob =
|
|
143
|
+
if source.respond_to?(:glob) && !source.glob.to_s.empty?
|
|
144
|
+
source.glob.to_s
|
|
145
|
+
else
|
|
146
|
+
PATH_GLOB_DEFAULT
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
Dir.glob(File.join(absolute, glob)).each do |path|
|
|
150
|
+
return File.dirname(path) if File.basename(path, ".gemspec") == spec_name
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
Dir.glob(File.join(absolute, "**", "*.gemspec")).each do |path|
|
|
154
|
+
return File.dirname(path) if File.basename(path, ".gemspec") == spec_name
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
absolute
|
|
134
158
|
end
|
|
135
159
|
|
|
136
160
|
private_class_method :plan_one, :needs_ext_build?, :extension_link_missing?,
|
|
137
|
-
:
|
|
161
|
+
:estimated_size, :local_source_path
|
|
138
162
|
end
|
|
139
163
|
end
|
|
140
164
|
end
|
|
@@ -6,6 +6,7 @@ require_relative "../gem/extractor"
|
|
|
6
6
|
require_relative "../cache/layout"
|
|
7
7
|
require_relative "../fs"
|
|
8
8
|
require_relative "../errors"
|
|
9
|
+
require_relative "../spec_utils"
|
|
9
10
|
|
|
10
11
|
module Scint
|
|
11
12
|
module Installer
|
|
@@ -182,15 +183,7 @@ module Scint
|
|
|
182
183
|
|
|
183
184
|
def gem_download_uri(entry)
|
|
184
185
|
spec = entry.spec
|
|
185
|
-
|
|
186
|
-
version = spec.respond_to?(:version) ? spec.version : spec[:version]
|
|
187
|
-
platform = spec.respond_to?(:platform) ? spec.platform : spec[:platform]
|
|
188
|
-
|
|
189
|
-
filename = if platform && platform.to_s != "ruby" && platform.to_s != ""
|
|
190
|
-
"#{name}-#{version}-#{platform}.gem"
|
|
191
|
-
else
|
|
192
|
-
"#{name}-#{version}.gem"
|
|
193
|
-
end
|
|
186
|
+
filename = "#{SpecUtils.full_name(spec)}.gem"
|
|
194
187
|
|
|
195
188
|
# Use cached_path if provided, otherwise construct from source
|
|
196
189
|
if entry.cached_path
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require_relative "../source/rubygems"
|
|
4
4
|
require_relative "../source/git"
|
|
5
5
|
require_relative "../source/path"
|
|
6
|
+
require_relative "../spec_utils"
|
|
6
7
|
|
|
7
8
|
module Scint
|
|
8
9
|
module Lockfile
|
|
@@ -209,7 +210,7 @@ module Scint
|
|
|
209
210
|
platform = $4 || "ruby"
|
|
210
211
|
checksums_str = $6
|
|
211
212
|
|
|
212
|
-
key =
|
|
213
|
+
key = SpecUtils.full_name_for(name, version, platform)
|
|
213
214
|
|
|
214
215
|
if checksums_str
|
|
215
216
|
@checksums[key] = checksums_str.split(",").map(&:strip)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../spec_utils"
|
|
4
|
+
|
|
3
5
|
module Scint
|
|
4
6
|
module Lockfile
|
|
5
7
|
# Writes a standard Gemfile.lock file from structured data.
|
|
@@ -33,67 +35,95 @@ module Scint
|
|
|
33
35
|
|
|
34
36
|
def add_sources(out)
|
|
35
37
|
# Group specs by source, preserving source order.
|
|
36
|
-
# Specs store source as a URI string; sources are Source objects.
|
|
37
|
-
# Match by checking if the spec's source URI matches any remote.
|
|
38
38
|
specs_by_source = {}
|
|
39
39
|
@data.sources.each { |s| specs_by_source[s] = [] }
|
|
40
40
|
|
|
41
41
|
@data.specs.each do |spec|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
matched = @data.sources.find do |source|
|
|
46
|
-
if source.respond_to?(:remotes)
|
|
47
|
-
source.remotes.any? { |r| normalize_source_uri(r) == spec_uri }
|
|
48
|
-
elsif source.respond_to?(:uri)
|
|
49
|
-
normalize_source_uri(source.uri) == spec_uri
|
|
50
|
-
else
|
|
51
|
-
source == spec_src
|
|
52
|
-
end
|
|
53
|
-
end
|
|
42
|
+
target = match_source_for_spec(spec) || @data.sources.first
|
|
43
|
+
next unless target
|
|
54
44
|
|
|
55
|
-
target = matched || @data.sources.first
|
|
56
45
|
specs_by_source[target] ||= []
|
|
57
46
|
specs_by_source[target] << spec
|
|
58
47
|
end
|
|
59
48
|
|
|
60
|
-
|
|
49
|
+
emitted = false
|
|
61
50
|
@data.sources.each do |source|
|
|
62
|
-
|
|
63
|
-
|
|
51
|
+
source_specs = specs_by_source[source] || []
|
|
52
|
+
next if source_specs.empty?
|
|
53
|
+
|
|
54
|
+
out << "\n" if emitted
|
|
55
|
+
emitted = true
|
|
64
56
|
|
|
65
57
|
out << source.to_lock
|
|
66
|
-
add_specs(out,
|
|
58
|
+
add_specs(out, source_specs)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def match_source_for_spec(spec)
|
|
63
|
+
spec_source = spec.is_a?(Hash) ? spec[:source] : spec.source
|
|
64
|
+
return nil unless spec_source
|
|
65
|
+
|
|
66
|
+
@data.sources.find { |source| source_matches?(source, spec_source) }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def source_matches?(source, spec_source)
|
|
70
|
+
return true if source.equal?(spec_source)
|
|
71
|
+
return true if source == spec_source
|
|
72
|
+
|
|
73
|
+
spec_key = normalize_source_key(spec_source)
|
|
74
|
+
return false unless spec_key
|
|
75
|
+
|
|
76
|
+
if source.respond_to?(:remotes)
|
|
77
|
+
source.remotes.any? { |remote| normalize_source_key(remote) == spec_key }
|
|
78
|
+
elsif source.respond_to?(:uri)
|
|
79
|
+
normalize_source_key(source.uri) == spec_key
|
|
80
|
+
else
|
|
81
|
+
normalize_source_key(source) == spec_key
|
|
67
82
|
end
|
|
68
83
|
end
|
|
69
84
|
|
|
70
|
-
def
|
|
71
|
-
|
|
72
|
-
|
|
85
|
+
def normalize_source_key(source_ref)
|
|
86
|
+
raw =
|
|
87
|
+
if source_ref.respond_to?(:uri)
|
|
88
|
+
source_ref.uri.to_s
|
|
89
|
+
elsif source_ref.respond_to?(:path)
|
|
90
|
+
source_ref.path.to_s
|
|
91
|
+
else
|
|
92
|
+
source_ref.to_s
|
|
93
|
+
end
|
|
94
|
+
return nil if raw.empty?
|
|
95
|
+
|
|
96
|
+
if raw.match?(%r{\Ahttps?://}i)
|
|
97
|
+
raw = raw.sub(%r{\Ahttps?://}i, "")
|
|
98
|
+
raw = raw.sub(%r{\.git/?\z}i, "")
|
|
99
|
+
raw.chomp("/").downcase
|
|
100
|
+
elsif raw.start_with?("/") || raw.start_with?(".")
|
|
101
|
+
File.expand_path(raw)
|
|
102
|
+
else
|
|
103
|
+
raw.sub(%r{\.git/?\z}i, "").chomp("/").downcase
|
|
104
|
+
end
|
|
73
105
|
end
|
|
74
106
|
|
|
75
107
|
def add_specs(out, specs)
|
|
76
108
|
# Sort by full name (name-version-platform) for consistency
|
|
77
109
|
sorted = specs.sort_by do |s|
|
|
78
110
|
if s.is_a?(Hash)
|
|
79
|
-
|
|
80
|
-
v = s[:version]
|
|
81
|
-
p = s[:platform]
|
|
82
|
-
p == "ruby" ? "#{n}-#{v}" : "#{n}-#{v}-#{p}"
|
|
111
|
+
SpecUtils.full_name_for(s[:name], s[:version], s[:platform])
|
|
83
112
|
else
|
|
84
|
-
|
|
113
|
+
SpecUtils.full_name_for(s.name, s.version, s.platform)
|
|
85
114
|
end
|
|
86
115
|
end
|
|
87
116
|
|
|
88
117
|
sorted.each do |spec|
|
|
89
|
-
name, version,
|
|
90
|
-
[spec[:name], spec[:version], spec[:
|
|
118
|
+
name, version, deps = if spec.is_a?(Hash)
|
|
119
|
+
[spec[:name], spec[:version], spec[:dependencies] || []]
|
|
91
120
|
else
|
|
92
|
-
[spec.name, spec.version, spec.
|
|
121
|
+
[spec.name, spec.version, spec.dependencies || []]
|
|
93
122
|
end
|
|
94
123
|
|
|
95
124
|
# Format: " name (version)" or " name (version-platform)"
|
|
96
|
-
|
|
125
|
+
platform_str = SpecUtils.platform_str(spec)
|
|
126
|
+
version_str = platform_str == "ruby" ? version.to_s : "#{version}-#{platform_str}"
|
|
97
127
|
out << " #{name} (#{version_str})\n"
|
|
98
128
|
|
|
99
129
|
# Dependencies of this spec (6-space indent)
|
|
@@ -153,19 +183,31 @@ module Scint
|
|
|
153
183
|
return unless @data.checksums
|
|
154
184
|
out << "\nCHECKSUMS\n"
|
|
155
185
|
|
|
156
|
-
@data.checksums.
|
|
186
|
+
@data.checksums.each do |key, values|
|
|
187
|
+
rendered_key = format_checksum_key(key)
|
|
157
188
|
if values && !values.empty?
|
|
158
|
-
out << " #{
|
|
189
|
+
out << " #{rendered_key} #{values.join(",")}\n"
|
|
159
190
|
else
|
|
160
|
-
out << " #{
|
|
191
|
+
out << " #{rendered_key}\n"
|
|
161
192
|
end
|
|
162
193
|
end
|
|
163
194
|
end
|
|
164
195
|
|
|
196
|
+
def format_checksum_key(key)
|
|
197
|
+
match = key.to_s.match(/\A(.+)-(\d[^-]*)(?:-(.+))?\z/)
|
|
198
|
+
return key unless match
|
|
199
|
+
|
|
200
|
+
name = match[1]
|
|
201
|
+
version = match[2]
|
|
202
|
+
platform = match[3]
|
|
203
|
+
version_str = platform ? "#{version}-#{platform}" : version
|
|
204
|
+
"#{name} (#{version_str})"
|
|
205
|
+
end
|
|
206
|
+
|
|
165
207
|
def add_ruby_version(out)
|
|
166
208
|
return unless @data.ruby_version
|
|
167
209
|
out << "\nRUBY VERSION\n"
|
|
168
|
-
out << "
|
|
210
|
+
out << " #{@data.ruby_version}\n"
|
|
169
211
|
end
|
|
170
212
|
|
|
171
213
|
def add_bundled_with(out)
|
|
@@ -173,6 +215,7 @@ module Scint
|
|
|
173
215
|
out << "\nBUNDLED WITH\n"
|
|
174
216
|
out << " #{@data.bundler_version}\n"
|
|
175
217
|
end
|
|
218
|
+
|
|
176
219
|
end
|
|
177
220
|
end
|
|
178
221
|
end
|
data/lib/scint/platform.rb
CHANGED
|
@@ -40,6 +40,14 @@ module Scint
|
|
|
40
40
|
RUBY_VERSION
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
+
def ruby_minor_version_dir
|
|
44
|
+
@ruby_minor_version_dir ||= RUBY_VERSION.split(".")[0, 2].join(".") + ".0"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def ruby_install_dir(base)
|
|
48
|
+
File.join(base, "ruby", ruby_minor_version_dir)
|
|
49
|
+
end
|
|
50
|
+
|
|
43
51
|
def extension_api_version
|
|
44
52
|
::Gem.extension_api_version
|
|
45
53
|
end
|