Skip to content

Commit

Permalink
Merge pull request #348 from Shopify/load-path-cache-encoding
Browse files Browse the repository at this point in the history
Set encoding and dedup cached paths so they can be directly used by MRI
  • Loading branch information
casperisfine committed Feb 4, 2021
2 parents 4869281 + 3a5b6af commit 47df251
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 12 deletions.
33 changes: 25 additions & 8 deletions lib/bootsnap/load_path_cache/cache.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def initialize(store, path_obj, development_mode: false)
@development_mode = development_mode
@store = store
@mutex = defined?(::Mutex) ? ::Mutex.new : ::Thread::Mutex.new # TODO: Remove once Ruby 2.2 support is dropped.
@path_obj = path_obj.map! { |f| File.exist?(f) ? File.realpath(f) : f }
@path_obj = path_obj.map! { |f| PathScanner.os_path(File.exist?(f) ? File.realpath(f) : f.dup) }
@has_relative_paths = nil
reinitialize
end
Expand Down Expand Up @@ -178,25 +178,42 @@ def now

if DLEXT2
def search_index(f)
try_index("#{f}#{DOT_RB}") || try_index("#{f}#{DLEXT}") || try_index("#{f}#{DLEXT2}") || try_index(f)
try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f + DLEXT2) || try_index(f)
end

def maybe_append_extension(f)
try_ext("#{f}#{DOT_RB}") || try_ext("#{f}#{DLEXT}") || try_ext("#{f}#{DLEXT2}") || f
try_ext(f + DOT_RB) || try_ext(f + DLEXT) || try_ext(f + DLEXT2) || f
end
else
def search_index(f)
try_index("#{f}#{DOT_RB}") || try_index("#{f}#{DLEXT}") || try_index(f)
try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f)
end

def maybe_append_extension(f)
try_ext("#{f}#{DOT_RB}") || try_ext("#{f}#{DLEXT}") || f
try_ext(f + DOT_RB) || try_ext(f + DLEXT) || f
end
end

def try_index(f)
if (p = @index[f])
"#{p}/#{f}"
s = rand.to_s.force_encoding(Encoding::US_ASCII).freeze
if s.respond_to?(:-@)
if (-s).equal?(s) && (-s.dup).equal?(s)
def try_index(f)
if (p = @index[f])
-(File.join(p, f).freeze)
end
end
else
def try_index(f)
if (p = @index[f])
-File.join(p, f).untaint
end
end
end
else
def try_index(f)
if (p = @index[f])
File.join(p, f)
end
end
end

Expand Down
17 changes: 14 additions & 3 deletions lib/bootsnap/load_path_cache/path_scanner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ def call(path)
requirables = []
walk(path, nil) do |relative_path, absolute_path, is_directory|
if is_directory
dirs << relative_path
dirs << os_path(relative_path)
!contains_bundle_path || !absolute_path.start_with?(BUNDLE_PATH)
elsif relative_path.end_with?(*REQUIRABLE_EXTENSIONS)
requirables << relative_path
requirables << os_path(relative_path)
end
end
[requirables, dirs]
Expand All @@ -45,7 +45,7 @@ def call(path)
def walk(absolute_dir_path, relative_dir_path, &block)
Dir.foreach(absolute_dir_path) do |name|
next if name.start_with?('.')
relative_path = relative_dir_path ? "#{relative_dir_path}/#{name}" : name.freeze
relative_path = relative_dir_path ? File.join(relative_dir_path, name) : name

absolute_path = "#{absolute_dir_path}/#{name}"
if File.directory?(absolute_path)
Expand All @@ -57,6 +57,17 @@ def walk(absolute_dir_path, relative_dir_path, &block)
end
end
end

if RUBY_VERSION >= '3.1'
def os_path(path)
path.freeze
end
else
def os_path(path)
path.force_encoding(Encoding::US_ASCII) if path.ascii_only?
path.freeze
end
end
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/bootsnap/load_path_cache/store.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def dump_data
# `encoding:` looks redundant wrt `binwrite`, but necessary on windows
# because binary is part of mode.
File.open(tmp, mode: exclusive_write, encoding: Encoding::BINARY) do |io|
MessagePack.dump(@data, io)
MessagePack.dump(@data, io, freeze: true)
end
FileUtils.mv(tmp, @store_path)
rescue Errno::EEXIST
Expand Down
31 changes: 31 additions & 0 deletions test/load_path_cache/cache_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def setup
FileUtils.touch("#{@dir1}/dl#{DLEXT}")
FileUtils.touch("#{@dir1}/both.rb")
FileUtils.touch("#{@dir1}/both#{DLEXT}")
FileUtils.touch("#{@dir1}/béé.rb")
end

def teardown
Expand Down Expand Up @@ -137,6 +138,36 @@ def test_path_obj_equal?

assert_equal("#{@dir1}/a.rb", cache.find('a'))
end

if RUBY_VERSION >= '2.5' && !(RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/)
# https://github.com/ruby/ruby/pull/4061
# https://bugs.ruby-lang.org/issues/17517
OS_ASCII_PATH_ENCODING = RUBY_VERSION >= '3.1' ? Encoding::UTF_8 : Encoding::US_ASCII

def test_path_encoding
po = [@dir1]
cache = Cache.new(NullCache, po)

path = cache.find('a')

assert_equal("#{@dir1}/a.rb", path)
require path
internal_path = $LOADED_FEATURES.last
assert_equal(OS_ASCII_PATH_ENCODING, internal_path.encoding)
assert_equal(OS_ASCII_PATH_ENCODING, path.encoding)
File.write(path, '')
assert_same path, internal_path

utf8_path = cache.find('béé')
require utf8_path
internal_utf8_path = $LOADED_FEATURES.last
assert_equal("#{@dir1}/béé.rb", utf8_path)
assert_equal(Encoding::UTF_8, internal_utf8_path.encoding)
assert_equal(Encoding::UTF_8, utf8_path.encoding)
File.write(utf8_path, '')
assert_same utf8_path, internal_utf8_path
end
end
end
end
end

0 comments on commit 47df251

Please sign in to comment.