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.
- checksums.yaml +4 -4
- data/FEATURES.md +4 -0
- data/README.md +161 -41
- data/VERSION +1 -1
- data/bin/scint +9 -0
- data/lib/bundler.rb +106 -0
- data/lib/scint/cache/layout.rb +72 -14
- data/lib/scint/cache/manifest.rb +120 -0
- data/lib/scint/cache/metadata_store.rb +4 -11
- data/lib/scint/cache/prewarm.rb +445 -33
- data/lib/scint/cache/validity.rb +134 -0
- data/lib/scint/cli/cache.rb +36 -7
- data/lib/scint/cli/exec.rb +13 -25
- data/lib/scint/cli/install.rb +1452 -164
- 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 +215 -26
- data/lib/scint/gem/package.rb +6 -2
- data/lib/scint/gemfile/parser.rb +44 -10
- data/lib/scint/installer/extension_builder.rb +80 -55
- data/lib/scint/installer/linker.rb +51 -26
- data/lib/scint/installer/planner.rb +53 -34
- data/lib/scint/installer/preparer.rb +170 -47
- data/lib/scint/installer/promoter.rb +97 -0
- data/lib/scint/linker.sh +137 -0
- data/lib/scint/lockfile/parser.rb +2 -1
- data/lib/scint/lockfile/writer.rb +85 -36
- data/lib/scint/platform.rb +8 -0
- data/lib/scint/resolver/provider.rb +15 -2
- 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 +133 -0
- data/lib/scint.rb +1 -0
- metadata +6 -1
|
@@ -1,12 +1,14 @@
|
|
|
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.
|
|
6
8
|
# Produces output compatible with stock bundler.
|
|
7
9
|
#
|
|
8
10
|
# Sections in order: source blocks (GEM/GIT/PATH), PLATFORMS,
|
|
9
|
-
# DEPENDENCIES, CHECKSUMS (if present), RUBY VERSION.
|
|
11
|
+
# DEPENDENCIES, CHECKSUMS (if present), RUBY VERSION, BUNDLED WITH.
|
|
10
12
|
class Writer
|
|
11
13
|
def self.write(lockfile_data)
|
|
12
14
|
new(lockfile_data).generate
|
|
@@ -24,6 +26,7 @@ module Scint
|
|
|
24
26
|
add_dependencies(out)
|
|
25
27
|
add_checksums(out)
|
|
26
28
|
add_ruby_version(out)
|
|
29
|
+
add_bundled_with(out)
|
|
27
30
|
|
|
28
31
|
out
|
|
29
32
|
end
|
|
@@ -32,67 +35,95 @@ module Scint
|
|
|
32
35
|
|
|
33
36
|
def add_sources(out)
|
|
34
37
|
# Group specs by source, preserving source order.
|
|
35
|
-
# Specs store source as a URI string; sources are Source objects.
|
|
36
|
-
# Match by checking if the spec's source URI matches any remote.
|
|
37
38
|
specs_by_source = {}
|
|
38
39
|
@data.sources.each { |s| specs_by_source[s] = [] }
|
|
39
40
|
|
|
40
41
|
@data.specs.each do |spec|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
matched = @data.sources.find do |source|
|
|
45
|
-
if source.respond_to?(:remotes)
|
|
46
|
-
source.remotes.any? { |r| normalize_source_uri(r) == spec_uri }
|
|
47
|
-
elsif source.respond_to?(:uri)
|
|
48
|
-
normalize_source_uri(source.uri) == spec_uri
|
|
49
|
-
else
|
|
50
|
-
source == spec_src
|
|
51
|
-
end
|
|
52
|
-
end
|
|
42
|
+
target = match_source_for_spec(spec) || @data.sources.first
|
|
43
|
+
next unless target
|
|
53
44
|
|
|
54
|
-
target = matched || @data.sources.first
|
|
55
45
|
specs_by_source[target] ||= []
|
|
56
46
|
specs_by_source[target] << spec
|
|
57
47
|
end
|
|
58
48
|
|
|
59
|
-
|
|
49
|
+
emitted = false
|
|
60
50
|
@data.sources.each do |source|
|
|
61
|
-
|
|
62
|
-
|
|
51
|
+
source_specs = specs_by_source[source] || []
|
|
52
|
+
next if source_specs.empty?
|
|
53
|
+
|
|
54
|
+
out << "\n" if emitted
|
|
55
|
+
emitted = true
|
|
63
56
|
|
|
64
57
|
out << source.to_lock
|
|
65
|
-
add_specs(out,
|
|
58
|
+
add_specs(out, source_specs)
|
|
66
59
|
end
|
|
67
60
|
end
|
|
68
61
|
|
|
69
|
-
def
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
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
|
|
72
105
|
end
|
|
73
106
|
|
|
74
107
|
def add_specs(out, specs)
|
|
75
108
|
# Sort by full name (name-version-platform) for consistency
|
|
76
109
|
sorted = specs.sort_by do |s|
|
|
77
110
|
if s.is_a?(Hash)
|
|
78
|
-
|
|
79
|
-
v = s[:version]
|
|
80
|
-
p = s[:platform]
|
|
81
|
-
p == "ruby" ? "#{n}-#{v}" : "#{n}-#{v}-#{p}"
|
|
111
|
+
SpecUtils.full_name_for(s[:name], s[:version], s[:platform])
|
|
82
112
|
else
|
|
83
|
-
|
|
113
|
+
SpecUtils.full_name_for(s.name, s.version, s.platform)
|
|
84
114
|
end
|
|
85
115
|
end
|
|
86
116
|
|
|
87
117
|
sorted.each do |spec|
|
|
88
|
-
name, version,
|
|
89
|
-
[spec[:name], spec[:version], spec[:
|
|
118
|
+
name, version, deps = if spec.is_a?(Hash)
|
|
119
|
+
[spec[:name], spec[:version], spec[:dependencies] || []]
|
|
90
120
|
else
|
|
91
|
-
[spec.name, spec.version, spec.
|
|
121
|
+
[spec.name, spec.version, spec.dependencies || []]
|
|
92
122
|
end
|
|
93
123
|
|
|
94
124
|
# Format: " name (version)" or " name (version-platform)"
|
|
95
|
-
|
|
125
|
+
platform_str = SpecUtils.platform_str(spec)
|
|
126
|
+
version_str = platform_str == "ruby" ? version.to_s : "#{version}-#{platform_str}"
|
|
96
127
|
out << " #{name} (#{version_str})\n"
|
|
97
128
|
|
|
98
129
|
# Dependencies of this spec (6-space indent)
|
|
@@ -152,19 +183,37 @@ module Scint
|
|
|
152
183
|
return unless @data.checksums
|
|
153
184
|
out << "\nCHECKSUMS\n"
|
|
154
185
|
|
|
155
|
-
@data.checksums.
|
|
186
|
+
@data.checksums.each do |key, values|
|
|
187
|
+
rendered_key = format_checksum_key(key)
|
|
156
188
|
if values && !values.empty?
|
|
157
|
-
out << " #{
|
|
189
|
+
out << " #{rendered_key} #{values.join(",")}\n"
|
|
158
190
|
else
|
|
159
|
-
out << " #{
|
|
191
|
+
out << " #{rendered_key}\n"
|
|
160
192
|
end
|
|
161
193
|
end
|
|
162
194
|
end
|
|
163
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
|
+
|
|
164
207
|
def add_ruby_version(out)
|
|
165
208
|
return unless @data.ruby_version
|
|
166
209
|
out << "\nRUBY VERSION\n"
|
|
167
|
-
out << "
|
|
210
|
+
out << " #{@data.ruby_version}\n"
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def add_bundled_with(out)
|
|
214
|
+
return unless @data.bundler_version
|
|
215
|
+
out << "\nBUNDLED WITH\n"
|
|
216
|
+
out << " #{@data.bundler_version}\n"
|
|
168
217
|
end
|
|
169
218
|
|
|
170
219
|
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
|
|
@@ -86,7 +86,7 @@ module Scint
|
|
|
86
86
|
pg = @path_gems[name]
|
|
87
87
|
deps = {}
|
|
88
88
|
(pg[:dependencies] || []).each do |dep_name, dep_req_str|
|
|
89
|
-
deps[dep_name] =
|
|
89
|
+
deps[dep_name] = build_requirement(dep_req_str)
|
|
90
90
|
end
|
|
91
91
|
deps
|
|
92
92
|
else
|
|
@@ -99,7 +99,7 @@ module Scint
|
|
|
99
99
|
next unless requirements_match?(reqs)
|
|
100
100
|
dep_hash.each do |dep_name, dep_req_str|
|
|
101
101
|
# Merge constraints from all matching platform entries
|
|
102
|
-
req =
|
|
102
|
+
req = build_requirement(dep_req_str)
|
|
103
103
|
deps[dep_name] = if deps[dep_name]
|
|
104
104
|
merge_requirements(deps[dep_name], req)
|
|
105
105
|
else
|
|
@@ -276,6 +276,19 @@ module Scint
|
|
|
276
276
|
|
|
277
277
|
Gem::Requirement.new(filtered.map { |op, version| "#{op} #{version}" })
|
|
278
278
|
end
|
|
279
|
+
|
|
280
|
+
def build_requirement(value)
|
|
281
|
+
case value
|
|
282
|
+
when Gem::Requirement
|
|
283
|
+
value
|
|
284
|
+
when Array
|
|
285
|
+
parts = value.flatten.compact.map(&:to_s).reject(&:empty?)
|
|
286
|
+
Gem::Requirement.new(*(parts.empty? ? [">= 0"] : parts))
|
|
287
|
+
else
|
|
288
|
+
parts = value.to_s.split(",").map(&:strip).reject(&:empty?)
|
|
289
|
+
Gem::Requirement.new(*(parts.empty? ? [">= 0"] : parts))
|
|
290
|
+
end
|
|
291
|
+
end
|
|
279
292
|
end
|
|
280
293
|
end
|
|
281
294
|
end
|
data/lib/scint/runtime/exec.rb
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "setup"
|
|
4
4
|
require_relative "../fs"
|
|
5
|
+
require_relative "../platform"
|
|
5
6
|
require "base64"
|
|
6
7
|
require "pathname"
|
|
8
|
+
require "rbconfig"
|
|
7
9
|
|
|
8
10
|
module Scint
|
|
9
11
|
module Runtime
|
|
@@ -19,40 +21,40 @@ module Scint
|
|
|
19
21
|
# lock_path: path to .bundle/scint.lock.marshal
|
|
20
22
|
def exec(command, args, lock_path)
|
|
21
23
|
original_env = ENV.to_hash
|
|
22
|
-
|
|
24
|
+
Setup.load_lock(lock_path)
|
|
25
|
+
command, args = rewrite_bundle_exec(command, args)
|
|
26
|
+
passthrough_bundle = bundle_command?(command)
|
|
23
27
|
|
|
24
28
|
bundle_dir = File.dirname(lock_path)
|
|
25
29
|
scint_lib_dir = File.expand_path("../..", __dir__)
|
|
26
|
-
ruby_dir =
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
#
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
ruby_dir = Platform.ruby_install_dir(bundle_dir)
|
|
31
|
+
|
|
32
|
+
# Set RUBYLIB to make our Bundler shim loadable. We intentionally avoid
|
|
33
|
+
# injecting all gem load paths here because large apps can exceed exec
|
|
34
|
+
# argument/environment limits when RUBYLIB gets too long.
|
|
35
|
+
# Gem load paths are still activated via Scint::Runtime::Setup from
|
|
36
|
+
# `-rbundler/setup`.
|
|
37
|
+
unless passthrough_bundle
|
|
38
|
+
existing = ENV["RUBYLIB"]
|
|
39
|
+
rubylib = scint_lib_dir
|
|
40
|
+
rubylib = "#{rubylib}#{File::PATH_SEPARATOR}#{existing}" if existing && !existing.empty?
|
|
41
|
+
ENV["RUBYLIB"] = rubylib
|
|
35
42
|
end
|
|
36
43
|
|
|
37
|
-
# Ensure our bundler shim wins over global bundler.
|
|
38
|
-
# Order matters: scint lib first, then gem load paths.
|
|
39
|
-
paths.unshift(scint_lib_dir)
|
|
40
|
-
|
|
41
|
-
# Set RUBYLIB so the child process inherits load paths.
|
|
42
|
-
existing = ENV["RUBYLIB"]
|
|
43
|
-
rubylib = paths.join(File::PATH_SEPARATOR)
|
|
44
|
-
rubylib = "#{rubylib}#{File::PATH_SEPARATOR}#{existing}" if existing && !existing.empty?
|
|
45
|
-
ENV["RUBYLIB"] = rubylib
|
|
46
|
-
|
|
47
44
|
ENV["SCINT_RUNTIME_LOCK"] = lock_path
|
|
48
45
|
ENV["GEM_HOME"] = ruby_dir
|
|
49
|
-
ENV["GEM_PATH"] = ruby_dir
|
|
46
|
+
ENV["GEM_PATH"] = build_gem_path(ruby_dir, original_env["GEM_PATH"])
|
|
50
47
|
ENV["BUNDLE_PATH"] = bundle_dir
|
|
51
48
|
ENV["BUNDLE_APP_CONFIG"] = bundle_dir
|
|
52
49
|
ENV["BUNDLE_GEMFILE"] = find_gemfile(bundle_dir)
|
|
53
|
-
|
|
50
|
+
ruby_interpreter_bin = File.dirname(RbConfig.ruby)
|
|
51
|
+
|
|
52
|
+
# Keep interpreter/bin ahead of .bundle/bin so `#!/usr/bin/env ruby`
|
|
53
|
+
# resolves to the interpreter, not a gem-provided "ruby" executable.
|
|
54
54
|
ENV["PATH"] = prepend_path(File.join(bundle_dir, "bin"), ENV["PATH"])
|
|
55
|
-
|
|
55
|
+
ENV["PATH"] = prepend_path(File.join(ruby_dir, "bin"), ENV["PATH"])
|
|
56
|
+
ENV["PATH"] = prepend_path(ruby_interpreter_bin, ENV["PATH"])
|
|
57
|
+
prepend_rubyopt("-rbundler/setup") unless passthrough_bundle
|
|
56
58
|
export_original_env(original_env)
|
|
57
59
|
|
|
58
60
|
command = resolve_command(command, bundle_dir, ruby_dir)
|
|
@@ -76,9 +78,9 @@ module Scint
|
|
|
76
78
|
|
|
77
79
|
def prepend_path(prefix, current_path)
|
|
78
80
|
return prefix unless current_path && !current_path.empty?
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
parts = current_path.split(File::PATH_SEPARATOR).reject(&:empty?)
|
|
82
|
+
parts.delete(prefix)
|
|
83
|
+
([prefix] + parts).join(File::PATH_SEPARATOR)
|
|
82
84
|
end
|
|
83
85
|
|
|
84
86
|
def export_original_env(original_env)
|
|
@@ -87,6 +89,29 @@ module Scint
|
|
|
87
89
|
# Non-fatal: shim can fallback to current ENV.
|
|
88
90
|
end
|
|
89
91
|
|
|
92
|
+
def build_gem_path(bundle_ruby_dir, original_gem_path)
|
|
93
|
+
paths = [bundle_ruby_dir]
|
|
94
|
+
if defined?(Gem) && Gem.respond_to?(:default_path)
|
|
95
|
+
paths.concat(Array(Gem.default_path))
|
|
96
|
+
end
|
|
97
|
+
if original_gem_path && !original_gem_path.empty?
|
|
98
|
+
paths.concat(original_gem_path.split(File::PATH_SEPARATOR))
|
|
99
|
+
end
|
|
100
|
+
paths.reject(&:empty?).uniq.join(File::PATH_SEPARATOR)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def bundle_command?(command)
|
|
104
|
+
File.basename(command.to_s) == "bundle"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def rewrite_bundle_exec(command, args)
|
|
108
|
+
return [command, args] unless bundle_command?(command)
|
|
109
|
+
return [command, args] unless args.first == "exec"
|
|
110
|
+
return [command, args] if args.length < 2
|
|
111
|
+
|
|
112
|
+
[args[1], args[2..] || []]
|
|
113
|
+
end
|
|
114
|
+
|
|
90
115
|
def resolve_command(command, bundle_dir, ruby_dir)
|
|
91
116
|
return command if command.include?(File::SEPARATOR)
|
|
92
117
|
|
|
@@ -135,6 +160,7 @@ module Scint
|
|
|
135
160
|
end
|
|
136
161
|
|
|
137
162
|
private_class_method :find_gemfile, :prepend_rubyopt, :prepend_path, :export_original_env,
|
|
163
|
+
:bundle_command?, :rewrite_bundle_exec, :build_gem_path,
|
|
138
164
|
:resolve_command, :find_gem_executable, :write_bundle_exec_wrapper
|
|
139
165
|
end
|
|
140
166
|
end
|
data/lib/scint/runtime/setup.rb
CHANGED
|
@@ -29,6 +29,7 @@ module Scint
|
|
|
29
29
|
$LOAD_PATH.unshift(*paths)
|
|
30
30
|
|
|
31
31
|
ENV["BUNDLE_GEMFILE"] ||= find_gemfile(File.dirname(lock_path))
|
|
32
|
+
hydrate_loaded_specs(lock_data)
|
|
32
33
|
|
|
33
34
|
lock_data
|
|
34
35
|
end
|
|
@@ -39,7 +40,34 @@ module Scint
|
|
|
39
40
|
File.exist?(gemfile) ? gemfile : nil
|
|
40
41
|
end
|
|
41
42
|
|
|
42
|
-
|
|
43
|
+
def hydrate_loaded_specs(lock_data)
|
|
44
|
+
return unless defined?(Gem) && Gem.respond_to?(:loaded_specs)
|
|
45
|
+
|
|
46
|
+
lock_data.each do |name, info|
|
|
47
|
+
gem_name = name.to_s
|
|
48
|
+
next if gem_name.empty? || Gem.loaded_specs[gem_name]
|
|
49
|
+
|
|
50
|
+
version = info.is_a?(Hash) ? info[:version] : nil
|
|
51
|
+
spec = find_installed_spec(gem_name, version)
|
|
52
|
+
Gem.loaded_specs[gem_name] = spec if spec
|
|
53
|
+
end
|
|
54
|
+
rescue StandardError
|
|
55
|
+
# Best-effort compatibility with gems expecting Gem.loaded_specs.
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def find_installed_spec(gem_name, version)
|
|
59
|
+
version_req = version.to_s.strip
|
|
60
|
+
if !version_req.empty?
|
|
61
|
+
exact = Gem::Specification.find_all_by_name(gem_name, version_req)
|
|
62
|
+
return exact.find { |spec| spec.version.to_s == version_req } || exact.first
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
Gem::Specification.find_all_by_name(gem_name).max_by(&:version)
|
|
66
|
+
rescue StandardError
|
|
67
|
+
nil
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private_class_method :find_gemfile, :hydrate_loaded_specs, :find_installed_spec
|
|
43
71
|
end
|
|
44
72
|
end
|
|
45
73
|
end
|
data/lib/scint/scheduler.rb
CHANGED
|
@@ -329,7 +329,12 @@ module Scint
|
|
|
329
329
|
job.error = error
|
|
330
330
|
@failed[job.id] = job
|
|
331
331
|
@errors << { job_id: job.id, type: job.type, name: job.name, error: error }
|
|
332
|
-
|
|
332
|
+
if @fail_fast
|
|
333
|
+
@aborted = true
|
|
334
|
+
# Drop queued work immediately; wait_all will return once current
|
|
335
|
+
# in-flight jobs drain.
|
|
336
|
+
@pending.clear
|
|
337
|
+
end
|
|
333
338
|
else
|
|
334
339
|
job.state = :completed
|
|
335
340
|
@completed[job.id] = job
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "open3"
|
|
3
|
+
require "rbconfig"
|
|
4
|
+
|
|
5
|
+
module Scint
|
|
6
|
+
module SpecUtils
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
GEMSPEC_LOAD_MUTEX = Thread::Mutex.new
|
|
10
|
+
|
|
11
|
+
GEMSPEC_SUBPROCESS_LOADER = <<~'RUBY'
|
|
12
|
+
path = ARGV.fetch(0)
|
|
13
|
+
begin
|
|
14
|
+
Dir.chdir(File.dirname(path)) do
|
|
15
|
+
spec = Gem::Specification.load(path)
|
|
16
|
+
exit(2) unless spec
|
|
17
|
+
STDOUT.binmode
|
|
18
|
+
STDOUT.write(Marshal.dump(spec))
|
|
19
|
+
end
|
|
20
|
+
rescue Exception => e
|
|
21
|
+
warn("#{e.class}: #{e.message}")
|
|
22
|
+
exit(1)
|
|
23
|
+
end
|
|
24
|
+
RUBY
|
|
25
|
+
|
|
26
|
+
def name(spec)
|
|
27
|
+
spec.respond_to?(:name) ? spec.name : spec[:name]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def version(spec)
|
|
31
|
+
spec.respond_to?(:version) ? spec.version : spec[:version]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def platform(spec)
|
|
35
|
+
return spec.platform if spec.respond_to?(:platform)
|
|
36
|
+
return spec[:platform] if spec.is_a?(Hash)
|
|
37
|
+
return nil unless spec.respond_to?(:[])
|
|
38
|
+
|
|
39
|
+
spec[:platform]
|
|
40
|
+
rescue NameError, ArgumentError
|
|
41
|
+
nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def platform_str(spec)
|
|
45
|
+
platform_value(platform(spec))
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def platform_value(platform)
|
|
49
|
+
value = platform.nil? ? "ruby" : platform.to_s
|
|
50
|
+
value.empty? ? "ruby" : value
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def full_name(spec)
|
|
54
|
+
base = "#{name(spec)}-#{version(spec)}"
|
|
55
|
+
plat = platform_str(spec)
|
|
56
|
+
return base if plat == "ruby"
|
|
57
|
+
|
|
58
|
+
"#{base}-#{plat}"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def full_name_for(name, version, platform = "ruby")
|
|
62
|
+
base = "#{name}-#{version}"
|
|
63
|
+
plat = platform_value(platform)
|
|
64
|
+
return base if plat == "ruby"
|
|
65
|
+
|
|
66
|
+
"#{base}-#{plat}"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def full_key(spec)
|
|
70
|
+
full_key_for(name(spec), version(spec), platform(spec))
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def full_key_for(name, version, platform = "ruby")
|
|
74
|
+
"#{name}-#{version}-#{platform_value(platform)}"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Load a gemspec with path-sensitive fallback.
|
|
78
|
+
#
|
|
79
|
+
# Some gemspecs depend on process cwd (e.g. `require "./lib/..."`,
|
|
80
|
+
# `File.read("README")`). A direct load can fail when evaluated from a
|
|
81
|
+
# different working directory. For those failures, evaluate in a dedicated
|
|
82
|
+
# subprocess that can safely chdir without affecting install worker threads.
|
|
83
|
+
def load_gemspec(path, isolate: false)
|
|
84
|
+
absolute = File.expand_path(path.to_s)
|
|
85
|
+
return nil unless File.file?(absolute)
|
|
86
|
+
return load_gemspec_in_subprocess(absolute) if isolate
|
|
87
|
+
|
|
88
|
+
spec = load_gemspec_direct(absolute)
|
|
89
|
+
return spec if spec
|
|
90
|
+
|
|
91
|
+
load_gemspec_in_subprocess(absolute)
|
|
92
|
+
rescue StandardError, ScriptError => e
|
|
93
|
+
return load_gemspec_in_subprocess(absolute) if gemspec_path_context_error?(e)
|
|
94
|
+
|
|
95
|
+
nil
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def gemspec_path_context_error?(error)
|
|
99
|
+
return true if error.is_a?(LoadError)
|
|
100
|
+
return true if error.is_a?(Errno::ENOENT)
|
|
101
|
+
|
|
102
|
+
message = error.message.to_s
|
|
103
|
+
message.include?("conflicting chdir during another chdir block") ||
|
|
104
|
+
message.include?("cannot load such file -- ./") ||
|
|
105
|
+
message.include?("cannot load such file -- ../")
|
|
106
|
+
end
|
|
107
|
+
private_class_method :gemspec_path_context_error?
|
|
108
|
+
|
|
109
|
+
def load_gemspec_direct(absolute_path)
|
|
110
|
+
GEMSPEC_LOAD_MUTEX.synchronize do
|
|
111
|
+
::Gem::Specification.load(absolute_path)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
private_class_method :load_gemspec_direct
|
|
115
|
+
|
|
116
|
+
def load_gemspec_in_subprocess(absolute_path)
|
|
117
|
+
out, _err, status = Open3.capture3(
|
|
118
|
+
RbConfig.ruby,
|
|
119
|
+
"-rrubygems",
|
|
120
|
+
"-e",
|
|
121
|
+
GEMSPEC_SUBPROCESS_LOADER,
|
|
122
|
+
absolute_path,
|
|
123
|
+
)
|
|
124
|
+
return nil unless status.success?
|
|
125
|
+
return nil if out.nil? || out.empty?
|
|
126
|
+
|
|
127
|
+
Marshal.load(out)
|
|
128
|
+
rescue StandardError
|
|
129
|
+
nil
|
|
130
|
+
end
|
|
131
|
+
private_class_method :load_gemspec_in_subprocess
|
|
132
|
+
end
|
|
133
|
+
end
|
data/lib/scint.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: scint
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tobi Lutke
|
|
@@ -27,8 +27,10 @@ files:
|
|
|
27
27
|
- lib/bundler/setup.rb
|
|
28
28
|
- lib/scint.rb
|
|
29
29
|
- lib/scint/cache/layout.rb
|
|
30
|
+
- lib/scint/cache/manifest.rb
|
|
30
31
|
- lib/scint/cache/metadata_store.rb
|
|
31
32
|
- lib/scint/cache/prewarm.rb
|
|
33
|
+
- lib/scint/cache/validity.rb
|
|
32
34
|
- lib/scint/cli.rb
|
|
33
35
|
- lib/scint/cli/add.rb
|
|
34
36
|
- lib/scint/cli/cache.rb
|
|
@@ -56,6 +58,8 @@ files:
|
|
|
56
58
|
- lib/scint/installer/linker.rb
|
|
57
59
|
- lib/scint/installer/planner.rb
|
|
58
60
|
- lib/scint/installer/preparer.rb
|
|
61
|
+
- lib/scint/installer/promoter.rb
|
|
62
|
+
- lib/scint/linker.sh
|
|
59
63
|
- lib/scint/lockfile/parser.rb
|
|
60
64
|
- lib/scint/lockfile/writer.rb
|
|
61
65
|
- lib/scint/platform.rb
|
|
@@ -69,6 +73,7 @@ files:
|
|
|
69
73
|
- lib/scint/source/git.rb
|
|
70
74
|
- lib/scint/source/path.rb
|
|
71
75
|
- lib/scint/source/rubygems.rb
|
|
76
|
+
- lib/scint/spec_utils.rb
|
|
72
77
|
- lib/scint/vendor/pub_grub.rb
|
|
73
78
|
- lib/scint/vendor/pub_grub/assignment.rb
|
|
74
79
|
- lib/scint/vendor/pub_grub/basic_package_source.rb
|