/
path.rb
114 lines (97 loc) · 3.49 KB
/
path.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# frozen_string_literal: true
require_relative('path_scanner')
module Bootsnap
module LoadPathCache
class Path
# A path is considered 'stable' if it is part of a Gem.path or the ruby
# distribution. When adding or removing files in these paths, the cache
# must be cleared before the change will be noticed.
def stable?
stability == STABLE
end
# A path is considered volatile if it doesn't live under a Gem.path or
# the ruby distribution root. These paths are scanned for new additions
# more frequently.
def volatile?
stability == VOLATILE
end
attr_reader(:path)
def initialize(path)
@path = path.to_s.freeze
end
# True if the path exists, but represents a non-directory object
def non_directory?
!File.stat(path).directory?
rescue Errno::ENOENT, Errno::ENOTDIR
false
end
def relative?
!path.start_with?(SLASH)
end
# Return a list of all the requirable files and all of the subdirectories
# of this +Path+.
def entries_and_dirs(store)
if stable?
# the cached_mtime field is unused for 'stable' paths, but is
# set to zero anyway, just in case we change the stability heuristics.
_, entries, dirs = store.get(expanded_path)
return [entries, dirs] if entries # cache hit
entries, dirs = scan!
store.set(expanded_path, [0, entries, dirs])
return [entries, dirs]
end
cached_mtime, entries, dirs = store.get(expanded_path)
current_mtime = latest_mtime(expanded_path, dirs || [])
return [[], []] if current_mtime == -1 # path does not exist
return [entries, dirs] if cached_mtime == current_mtime
entries, dirs = scan!
store.set(expanded_path, [current_mtime, entries, dirs])
[entries, dirs]
end
def expanded_path
File.expand_path(path).freeze
end
private
def scan! # (expensive) returns [entries, dirs]
PathScanner.call(expanded_path)
end
# last time a directory was modified in this subtree. +dirs+ should be a
# list of relative paths to directories under +path+. e.g. for /a/b and
# /a/b/c, pass ('/a/b', ['c'])
def latest_mtime(path, dirs)
max = -1
["", *dirs].each do |dir|
curr = begin
File.mtime("#{path}/#{dir}").to_i
rescue Errno::ENOENT, Errno::ENOTDIR
-1
end
max = curr if curr > max
end
max
end
# a Path can be either stable of volatile, depending on how frequently we
# expect its contents may change. Stable paths aren't rescanned nearly as
# often.
STABLE = :stable
VOLATILE = :volatile
# Built-in ruby lib stuff doesn't change, but things can occasionally be
# installed into sitedir, which generally lives under libdir.
RUBY_LIBDIR = RbConfig::CONFIG['libdir']
RUBY_SITEDIR = RbConfig::CONFIG['sitedir']
def stability
@stability ||= begin
if Gem.path.detect { |p| expanded_path.start_with?(p.to_s) }
STABLE
elsif Bootsnap.bundler? && expanded_path.start_with?(Bundler.bundle_path.to_s)
STABLE
elsif expanded_path.start_with?(RUBY_LIBDIR) && !expanded_path.start_with?(RUBY_SITEDIR)
STABLE
else
VOLATILE
end
end
end
end
end
end