scint 0.6.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 -1
- 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 +1034 -124
- 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 +44 -2
- data/lib/scint/gemfile/parser.rb +31 -4
- data/lib/scint/installer/extension_builder.rb +60 -30
- data/lib/scint/installer/linker.rb +8 -24
- data/lib/scint/installer/planner.rb +30 -7
- data/lib/scint/installer/preparer.rb +2 -9
- 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 +58 -0
- data/lib/scint.rb +1 -0
- metadata +2 -1
|
@@ -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,7 +29,7 @@ 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)
|
|
@@ -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,7 +19,7 @@ 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
|
|
@@ -98,10 +99,6 @@ module Scint
|
|
|
98
99
|
!Dir.exist?(ext_install_dir)
|
|
99
100
|
end
|
|
100
101
|
|
|
101
|
-
def ruby_install_dir(bundle_path)
|
|
102
|
-
File.join(bundle_path, "ruby", RUBY_VERSION.split(".")[0, 2].join(".") + ".0")
|
|
103
|
-
end
|
|
104
|
-
|
|
105
102
|
# Rough size estimate for download ordering.
|
|
106
103
|
# If we don't know, use 0 so unknowns sort after large known gems.
|
|
107
104
|
def estimated_size(spec)
|
|
@@ -131,11 +128,37 @@ module Scint
|
|
|
131
128
|
return nil if source_str.end_with?(".git") || source_str.include?(".git/")
|
|
132
129
|
|
|
133
130
|
absolute = File.expand_path(source_str, Dir.pwd)
|
|
134
|
-
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
|
|
135
158
|
end
|
|
136
159
|
|
|
137
160
|
private_class_method :plan_one, :needs_ext_build?, :extension_link_missing?,
|
|
138
|
-
:
|
|
161
|
+
:estimated_size, :local_source_path
|
|
139
162
|
end
|
|
140
163
|
end
|
|
141
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,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,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Scint
|
|
4
|
+
module SpecUtils
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def name(spec)
|
|
8
|
+
spec.respond_to?(:name) ? spec.name : spec[:name]
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def version(spec)
|
|
12
|
+
spec.respond_to?(:version) ? spec.version : spec[:version]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def platform(spec)
|
|
16
|
+
return spec.platform if spec.respond_to?(:platform)
|
|
17
|
+
return spec[:platform] if spec.is_a?(Hash)
|
|
18
|
+
return nil unless spec.respond_to?(:[])
|
|
19
|
+
|
|
20
|
+
spec[:platform]
|
|
21
|
+
rescue NameError, ArgumentError
|
|
22
|
+
nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def platform_str(spec)
|
|
26
|
+
platform_value(platform(spec))
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def platform_value(platform)
|
|
30
|
+
value = platform.nil? ? "ruby" : platform.to_s
|
|
31
|
+
value.empty? ? "ruby" : value
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def full_name(spec)
|
|
35
|
+
base = "#{name(spec)}-#{version(spec)}"
|
|
36
|
+
plat = platform_str(spec)
|
|
37
|
+
return base if plat == "ruby"
|
|
38
|
+
|
|
39
|
+
"#{base}-#{plat}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def full_name_for(name, version, platform = "ruby")
|
|
43
|
+
base = "#{name}-#{version}"
|
|
44
|
+
plat = platform_value(platform)
|
|
45
|
+
return base if plat == "ruby"
|
|
46
|
+
|
|
47
|
+
"#{base}-#{plat}"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def full_key(spec)
|
|
51
|
+
full_key_for(name(spec), version(spec), platform(spec))
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def full_key_for(name, version, platform = "ruby")
|
|
55
|
+
"#{name}-#{version}-#{platform_value(platform)}"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|