rubylens 0.2.0 → 0.2.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/README.md +3 -3
- data/lib/rubylens/art_model_builder.rb +184 -94
- data/lib/rubylens/index/constant_reference_collector.rb +150 -0
- data/lib/rubylens/index/declaration_collector.rb +288 -0
- data/lib/rubylens/index/git_package_source.rb +202 -0
- data/lib/rubylens/index/location_index.rb +103 -0
- data/lib/rubylens/index/manifest.rb +54 -150
- data/lib/rubylens/index/rubydex_adapter.rb +109 -449
- data/lib/rubylens/model/dependency_aggregation.rb +3 -1
- data/lib/rubylens/version.rb +1 -1
- metadata +7 -3
|
@@ -2,33 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
require "bundler"
|
|
4
4
|
require "bundler/lockfile_parser"
|
|
5
|
-
require "find"
|
|
6
5
|
require "pathname"
|
|
7
6
|
require "set"
|
|
8
7
|
require_relative "../dependency_warning"
|
|
8
|
+
require_relative "git_package_source"
|
|
9
9
|
|
|
10
10
|
module RubyLens
|
|
11
11
|
module Index
|
|
12
12
|
class Manifest
|
|
13
13
|
Package = Data.define(:name, :version, :role, :location, :root, :files)
|
|
14
14
|
DependencySystem = Data.define(:id, :package_indexes, :label_package_index)
|
|
15
|
-
GitPackagePaths = Data.define(:logical_root, :canonical_root, :logical_canonical_root)
|
|
16
15
|
|
|
17
|
-
class NixStoreProvider
|
|
18
|
-
STORE_ROOT = Pathname("/nix/store").freeze
|
|
19
|
-
STORE_OBJECT_PATTERN = /\A[0123456789abcdfghijklmnpqrsvwxyz]{32}-.+\z/
|
|
20
|
-
|
|
21
|
-
def trusted?(path)
|
|
22
|
-
path = Pathname(path).expand_path
|
|
23
|
-
return false unless Paths.inside?(path, STORE_ROOT)
|
|
24
|
-
|
|
25
|
-
first_component = path.relative_path_from(STORE_ROOT).each_filename.first
|
|
26
|
-
first_component && STORE_OBJECT_PATTERN.match?(first_component)
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
UnsafeGitRequirePath = Class.new(StandardError)
|
|
31
|
-
UnsafeGitPackageFile = Class.new(StandardError)
|
|
32
16
|
GIT_SKIP_REASONS = DependencyWarning::REASONS
|
|
33
17
|
|
|
34
18
|
attr_reader :root, :files, :workspace_files, :packages, :dependency_systems, :warnings, :dependency_warnings
|
|
@@ -40,7 +24,7 @@ module RubyLens
|
|
|
40
24
|
def initialize(root:, lockfile: nil)
|
|
41
25
|
@root = Pathname(root).expand_path.realpath
|
|
42
26
|
@lockfile = Pathname(lockfile || @root.join("Gemfile.lock")).expand_path
|
|
43
|
-
@
|
|
27
|
+
@git_package_source = GitPackageSource.new(lockfile: @lockfile)
|
|
44
28
|
@warnings = []
|
|
45
29
|
@dependency_warnings = []
|
|
46
30
|
@packages = []
|
|
@@ -49,7 +33,7 @@ module RubyLens
|
|
|
49
33
|
@package_index_by_file = {}
|
|
50
34
|
@package_index_cache = {}
|
|
51
35
|
@relative_workspace_path_cache = {}
|
|
52
|
-
@
|
|
36
|
+
@workspace_path_cache = {}
|
|
53
37
|
end
|
|
54
38
|
|
|
55
39
|
def build
|
|
@@ -80,7 +64,10 @@ module RubyLens
|
|
|
80
64
|
end
|
|
81
65
|
|
|
82
66
|
def workspace_path?(path)
|
|
83
|
-
|
|
67
|
+
path = path.to_s
|
|
68
|
+
@workspace_path_cache.fetch(path) do
|
|
69
|
+
@workspace_path_cache[path] = Paths.inside?(path, @root)
|
|
70
|
+
end
|
|
84
71
|
end
|
|
85
72
|
|
|
86
73
|
def relative_workspace_path(path)
|
|
@@ -189,134 +176,17 @@ module RubyLens
|
|
|
189
176
|
end
|
|
190
177
|
|
|
191
178
|
def git_package(locked, role)
|
|
192
|
-
|
|
193
|
-
return skip_git_dependency(locked,
|
|
194
|
-
|
|
195
|
-
checkout = git_checkout_path(source)
|
|
196
|
-
return skip_git_dependency(locked, :checkout_unavailable) unless checkout.directory?
|
|
197
|
-
|
|
198
|
-
specification = (@git_spec_indexes[source] ||= git_specifications(source, checkout)).search(locked).first
|
|
199
|
-
return skip_git_dependency(locked, :specification_unavailable) unless specification
|
|
200
|
-
|
|
201
|
-
paths = git_package_paths(specification, checkout)
|
|
202
|
-
return skip_git_dependency(locked, :unsafe_specification_root) unless paths
|
|
203
|
-
|
|
204
|
-
files = git_indexable_files(paths, specification.require_paths)
|
|
179
|
+
resolution = @git_package_source.resolve(locked)
|
|
180
|
+
return skip_git_dependency(locked, resolution) if resolution.is_a?(Symbol)
|
|
205
181
|
|
|
206
182
|
Package.new(
|
|
207
183
|
name: locked.name,
|
|
208
184
|
version: locked.version.to_s,
|
|
209
185
|
role: role,
|
|
210
186
|
location: "external",
|
|
211
|
-
root:
|
|
212
|
-
files: files
|
|
187
|
+
root: resolution.root,
|
|
188
|
+
files: resolution.files,
|
|
213
189
|
)
|
|
214
|
-
rescue UnsafeGitRequirePath
|
|
215
|
-
skip_git_dependency(locked, :unsafe_require_paths)
|
|
216
|
-
rescue UnsafeGitPackageFile
|
|
217
|
-
skip_git_dependency(locked, :unsafe_package_files)
|
|
218
|
-
rescue Bundler::BundlerError, Gem::Exception
|
|
219
|
-
skip_git_dependency(locked, :specification_unreadable)
|
|
220
|
-
rescue Errno::ENOENT, Errno::EACCES, Errno::ELOOP
|
|
221
|
-
skip_git_dependency(locked, :checkout_unavailable)
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
def git_checkout_path(source)
|
|
225
|
-
bundle_root = @lockfile.dirname
|
|
226
|
-
app_config = ENV["BUNDLE_APP_CONFIG"]
|
|
227
|
-
app_config_path = if app_config
|
|
228
|
-
Pathname(app_config).expand_path(bundle_root)
|
|
229
|
-
else
|
|
230
|
-
bundle_root.join(".bundle")
|
|
231
|
-
end
|
|
232
|
-
bundle_path = Pathname(Bundler::Settings.new(app_config_path).path.path).expand_path(bundle_root)
|
|
233
|
-
bundle_path.join("bundler/gems", source.extension_dir_name)
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
def git_specifications(source, checkout)
|
|
237
|
-
source.__send__(:set_install_path!, checkout)
|
|
238
|
-
source.specs
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
def git_package_paths(specification, checkout)
|
|
242
|
-
logical_root = Pathname(specification.full_gem_path).expand_path
|
|
243
|
-
logical_gemspec = Pathname(specification.loaded_from).expand_path
|
|
244
|
-
return unless Paths.inside?(logical_root, checkout)
|
|
245
|
-
return unless logical_gemspec.dirname == logical_root
|
|
246
|
-
return unless logical_gemspec.file? && logical_root.directory?
|
|
247
|
-
|
|
248
|
-
canonical_checkout = checkout.realpath
|
|
249
|
-
resolved_gemspec_root = logical_gemspec.realpath.dirname
|
|
250
|
-
logical_canonical_root = logical_root.realpath
|
|
251
|
-
return unless resolved_gemspec_root.directory?
|
|
252
|
-
normal_checkout = !checkout.symlink? &&
|
|
253
|
-
Paths.inside?(resolved_gemspec_root, canonical_checkout) &&
|
|
254
|
-
Paths.inside?(logical_canonical_root, canonical_checkout)
|
|
255
|
-
return unless normal_checkout || @nix_store_provider.trusted?(resolved_gemspec_root)
|
|
256
|
-
|
|
257
|
-
canonical_root = normal_checkout ? logical_canonical_root : resolved_gemspec_root
|
|
258
|
-
|
|
259
|
-
GitPackagePaths.new(
|
|
260
|
-
logical_root: logical_root,
|
|
261
|
-
canonical_root: canonical_root,
|
|
262
|
-
logical_canonical_root: logical_canonical_root,
|
|
263
|
-
)
|
|
264
|
-
rescue TypeError, ArgumentError
|
|
265
|
-
nil
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
def git_indexable_files(paths, require_paths)
|
|
269
|
-
logical_require_paths = require_paths.map do |relative_path|
|
|
270
|
-
raise UnsafeGitRequirePath unless relative_path.is_a?(String)
|
|
271
|
-
|
|
272
|
-
relative = Pathname(relative_path)
|
|
273
|
-
raise UnsafeGitRequirePath if relative.absolute? || relative.each_filename.include?("..")
|
|
274
|
-
|
|
275
|
-
candidate = paths.logical_root.join(relative).cleanpath
|
|
276
|
-
raise UnsafeGitRequirePath unless Paths.inside?(candidate, paths.logical_root)
|
|
277
|
-
|
|
278
|
-
candidate
|
|
279
|
-
rescue ArgumentError, EncodingError
|
|
280
|
-
raise UnsafeGitRequirePath
|
|
281
|
-
end
|
|
282
|
-
|
|
283
|
-
files = []
|
|
284
|
-
visited_directories = Set.new
|
|
285
|
-
logical_require_paths.each do |path|
|
|
286
|
-
next unless path.exist? || path.symlink?
|
|
287
|
-
|
|
288
|
-
traverse_git_package_path(path, paths, files, visited_directories, Set.new)
|
|
289
|
-
end
|
|
290
|
-
files.uniq.sort
|
|
291
|
-
end
|
|
292
|
-
|
|
293
|
-
def traverse_git_package_path(path, paths, files, visited_directories, active_directories)
|
|
294
|
-
resolved = path.realpath
|
|
295
|
-
if resolved.file?
|
|
296
|
-
raise UnsafeGitPackageFile unless Paths.inside?(resolved, paths.canonical_root)
|
|
297
|
-
|
|
298
|
-
files << resolved.to_s if indexable?(path)
|
|
299
|
-
return
|
|
300
|
-
end
|
|
301
|
-
return unless resolved.directory?
|
|
302
|
-
|
|
303
|
-
unless Paths.inside?(resolved, paths.logical_canonical_root) || Paths.inside?(resolved, paths.canonical_root)
|
|
304
|
-
raise UnsafeGitPackageFile
|
|
305
|
-
end
|
|
306
|
-
raise UnsafeGitPackageFile if active_directories.include?(resolved)
|
|
307
|
-
return if visited_directories.include?(resolved)
|
|
308
|
-
|
|
309
|
-
active_directories.add(resolved)
|
|
310
|
-
begin
|
|
311
|
-
path.children.sort_by(&:to_s).each do |child|
|
|
312
|
-
traverse_git_package_path(child, paths, files, visited_directories, active_directories)
|
|
313
|
-
end
|
|
314
|
-
visited_directories.add(resolved)
|
|
315
|
-
ensure
|
|
316
|
-
active_directories.delete(resolved)
|
|
317
|
-
end
|
|
318
|
-
rescue Errno::ENOENT, Errno::EACCES, Errno::ELOOP
|
|
319
|
-
raise UnsafeGitPackageFile
|
|
320
190
|
end
|
|
321
191
|
|
|
322
192
|
def skip_git_dependency(locked, reason_code)
|
|
@@ -429,20 +299,54 @@ module RubyLens
|
|
|
429
299
|
end.flat_map { |path| enumerate(path, root) }.uniq.sort
|
|
430
300
|
end
|
|
431
301
|
|
|
302
|
+
# Walks an already-canonical directory (the caller resolved and containment-checked
|
|
303
|
+
# it) without re-resolving every entry: descending only non-symlink directories
|
|
304
|
+
# keeps each joined path canonical, so regular files join the list with a cheap
|
|
305
|
+
# prefix containment check and only symlinked entries pay for a realpath. Symlinked
|
|
306
|
+
# directories are never descended, matching Find.find's lstat-based traversal.
|
|
432
307
|
def enumerate(path, root)
|
|
433
308
|
return [path.to_s] if path.file? && indexable?(path) && Paths.inside?(path.realpath, root)
|
|
434
309
|
return [] unless path.directory?
|
|
310
|
+
return [] if path.symlink?
|
|
435
311
|
|
|
436
312
|
files = []
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
313
|
+
root_prefix = "#{root}#{File::SEPARATOR}"
|
|
314
|
+
start = path.to_s
|
|
315
|
+
# Child names must come back in the starting path's encoding. A package
|
|
316
|
+
# root that is not valid UTF-8 otherwise yields UTF-8 entry names that
|
|
317
|
+
# cannot be joined to it, and the whole build aborts on a tree the
|
|
318
|
+
# Find-based walk indexed fine.
|
|
319
|
+
encoding = start.encoding == Encoding::US_ASCII ? Encoding.find("filesystem") : start.encoding
|
|
320
|
+
pending = [start]
|
|
321
|
+
while (directory = pending.pop)
|
|
322
|
+
entries = begin
|
|
323
|
+
Dir.children(directory, encoding: encoding)
|
|
324
|
+
rescue Errno::ENOENT, Errno::EACCES, Errno::ENOTDIR, Errno::ELOOP, Errno::ENAMETOOLONG, Errno::EINVAL
|
|
325
|
+
next
|
|
326
|
+
end
|
|
327
|
+
entries.each do |entry|
|
|
328
|
+
candidate = File.join(directory, entry)
|
|
329
|
+
stat = begin
|
|
330
|
+
File.lstat(candidate)
|
|
331
|
+
rescue Errno::ENOENT, Errno::EACCES, Errno::ENOTDIR, Errno::ELOOP, Errno::ENAMETOOLONG, Errno::EINVAL
|
|
332
|
+
next
|
|
333
|
+
end
|
|
334
|
+
if stat.symlink?
|
|
335
|
+
next if File.directory?(candidate)
|
|
336
|
+
next unless INDEXABLE_EXTENSIONS.include?(File.extname(entry))
|
|
337
|
+
|
|
338
|
+
begin
|
|
339
|
+
resolved = File.realpath(candidate)
|
|
340
|
+
files << resolved if Paths.inside?(resolved, root)
|
|
341
|
+
rescue Errno::ENOENT, Errno::EACCES, Errno::ELOOP
|
|
342
|
+
next
|
|
343
|
+
end
|
|
344
|
+
elsif stat.directory?
|
|
345
|
+
pending << candidate
|
|
346
|
+
elsif INDEXABLE_EXTENSIONS.include?(File.extname(entry))
|
|
347
|
+
files << candidate if candidate.start_with?(root_prefix)
|
|
348
|
+
end
|
|
349
|
+
end
|
|
446
350
|
end
|
|
447
351
|
files
|
|
448
352
|
end
|