Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion lib/solargraph/pin_cache.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'yard-activesupport-concern'
require 'digest'
require 'fileutils'
require 'pathname' # @todo Required by RBS but not loaded in some use cases
require 'rbs'
Expand All @@ -19,13 +20,36 @@ def base_dir
File.join(Dir.home, '.cache', 'solargraph')
end

# A digest of solargraph's own lib/ contents. Marshal silently
# tolerates loading pins serialized by a class definition with
# fewer ivars than the one currently loaded (missing ivars just
# read back as nil), so a `Solargraph::VERSION`-only cache key
# isn't enough to protect against a gem source swap that changes
# Pin behavior without bumping VERSION -- e.g. switching a
# bundler `path:`/`git:` install to a different commit, common
# in fork-based development. Folding this digest into work_dir
# busts the cache whenever the library code actually changes.
#
# @sg-ignore flow sensitive typing doesn't narrow @lib_digest past the nil guard below
# @return [String]
def lib_digest
return @lib_digest unless @lib_digest.nil?
lib_dir = File.expand_path('..', __dir__)
digest = Digest::SHA256.new
Dir.glob(File.join(lib_dir, '**', '*.rb')).each do |file|
stat = File.stat(file)
digest << file << stat.mtime.to_i.to_s << stat.size.to_s
end
@lib_digest = digest.hexdigest[0, 12]
end

# The working directory for the current Ruby, RBS, and Solargraph versions.
#
# @return [String]
def work_dir
# The directory is not stored in a variable so it can be overridden
# in specs.
File.join(base_dir, "ruby-#{RUBY_VERSION}", "rbs-#{RBS::VERSION}", "solargraph-#{Solargraph::VERSION}")
File.join(base_dir, "ruby-#{RUBY_VERSION}", "rbs-#{RBS::VERSION}", "solargraph-#{Solargraph::VERSION}-#{lib_digest}")
end

# @param gemspec [Gem::Specification]
Expand Down
28 changes: 28 additions & 0 deletions spec/pin_cache_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

describe Solargraph::PinCache do
describe '.lib_digest' do
it 'changes when a lib file is edited, even though Solargraph::VERSION does not' do
original_digest = described_class.lib_digest

target = File.expand_path('../lib/solargraph/pin_cache.rb', __dir__)
original_mtime = File.mtime(target)
begin
# bump mtime without touching the file's actual content, the
# same signal a real code change (or a fresh git checkout of a
# different commit) would produce
File.utime(Time.now + 1, Time.now + 1, target)
described_class.instance_variable_set(:@lib_digest, nil)

expect(described_class.lib_digest).not_to eq(original_digest)
ensure
File.utime(original_mtime, original_mtime, target)
described_class.instance_variable_set(:@lib_digest, nil)
end
end

it 'is folded into work_dir so a code-only change (no VERSION bump) still busts the pin cache' do
expect(described_class.work_dir).to include(described_class.lib_digest)
end
end
end
Loading