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.
@@ -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
- spec_src = spec.is_a?(Hash) ? spec[:source] : spec.source
42
- spec_uri = normalize_source_uri(spec_src)
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
- first = true
49
+ emitted = false
60
50
  @data.sources.each do |source|
61
- out << "\n" unless first
62
- first = false
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, specs_by_source[source] || [])
58
+ add_specs(out, source_specs)
66
59
  end
67
60
  end
68
61
 
69
- def normalize_source_uri(uri)
70
- s = uri.to_s.chomp("/")
71
- s.sub(%r{^https?://}, "").downcase
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
- n = s[:name]
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
- "#{s.name}-#{s.version}#{"-#{s.platform}" if s.platform != "ruby"}"
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, platform, deps = if spec.is_a?(Hash)
89
- [spec[:name], spec[:version], spec[:platform], spec[:dependencies] || []]
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.platform, spec.dependencies || []]
121
+ [spec.name, spec.version, spec.dependencies || []]
92
122
  end
93
123
 
94
124
  # Format: " name (version)" or " name (version-platform)"
95
- version_str = platform && platform != "ruby" ? "#{version}-#{platform}" : version.to_s
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.sort.each do |key, values|
186
+ @data.checksums.each do |key, values|
187
+ rendered_key = format_checksum_key(key)
156
188
  if values && !values.empty?
157
- out << " #{key} #{values.join(",")}\n"
189
+ out << " #{rendered_key} #{values.join(",")}\n"
158
190
  else
159
- out << " #{key}\n"
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 << " #{@data.ruby_version}\n"
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
@@ -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] = Gem::Requirement.new(dep_req_str || ">= 0")
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 = Gem::Requirement.new(dep_req_str.split(", "))
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
@@ -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
- lock_data = Setup.load_lock(lock_path)
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 = File.join(bundle_dir, "ruby",
27
- RUBY_VERSION.split(".")[0, 2].join(".") + ".0")
28
-
29
- # Collect all load paths from the runtime config
30
- paths = []
31
- lock_data.each_value do |info|
32
- Array(info[:load_paths]).each do |p|
33
- paths << p if File.directory?(p)
34
- end
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
- ENV["PATH"] = prepend_path(File.join(ruby_dir, "bin"), ENV["PATH"])
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
- prepend_rubyopt("-rbundler/setup")
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
- return current_path if current_path.split(File::PATH_SEPARATOR).include?(prefix)
80
-
81
- "#{prefix}#{File::PATH_SEPARATOR}#{current_path}"
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
@@ -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
- private_class_method :find_gemfile
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
@@ -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
- @aborted = true if @fail_fast
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
@@ -59,6 +59,7 @@ module Scint
59
59
  autoload :Progress, "scint/progress"
60
60
  autoload :FS, "scint/fs"
61
61
  autoload :Platform, "scint/platform"
62
+ autoload :SpecUtils, "scint/spec_utils"
62
63
 
63
64
  # Errors
64
65
  autoload :BundlerError, "scint/errors"
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.6.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