Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Retry composing bundle if it's modified during setup #2890

Merged
Merged
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
28 changes: 23 additions & 5 deletions lib/ruby_lsp/setup_bundler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ def initialize(project_path, **options)
)
@lockfile = T.let(@gemfile ? Bundler.default_lockfile : nil, T.nilable(Pathname))

@gemfile_hash = T.let(@gemfile ? Digest::SHA256.hexdigest(@gemfile.read) : nil, T.nilable(String))
@lockfile_hash = T.let(@lockfile&.exist? ? Digest::SHA256.hexdigest(@lockfile.read) : nil, T.nilable(String))

@gemfile_name = T.let(@gemfile&.basename&.to_s || "Gemfile", String)

# Custom bundle paths
Expand Down Expand Up @@ -91,10 +94,8 @@ def setup!
return run_bundle_install(@custom_gemfile)
end

lockfile_contents = @lockfile.read
current_lockfile_hash = Digest::SHA256.hexdigest(lockfile_contents)

if @custom_lockfile.exist? && @lockfile_hash_path.exist? && @lockfile_hash_path.read == current_lockfile_hash
if @lockfile_hash && @custom_lockfile.exist? && @lockfile_hash_path.exist? &&
@lockfile_hash_path.read == @lockfile_hash
$stderr.puts(
"Ruby LSP> Skipping composed bundle setup since #{@custom_lockfile} already exists and is up to date",
)
Expand All @@ -103,7 +104,7 @@ def setup!

FileUtils.cp(@lockfile.to_s, @custom_lockfile.to_s)
correct_relative_remote_paths
@lockfile_hash_path.write(current_lockfile_hash)
@lockfile_hash_path.write(@lockfile_hash)
run_bundle_install(@custom_gemfile)
end

Expand Down Expand Up @@ -214,6 +215,23 @@ def run_bundle_install(bundle_gemfile = @gemfile)
@error_path.write(Marshal.dump(e))
end

# If either the Gemfile or the lockfile have been modified during the process of setting up the bundle, retry
# composing the bundle from scratch

if @gemfile && @lockfile
current_gemfile_hash = Digest::SHA256.hexdigest(@gemfile.read)
current_lockfile_hash = Digest::SHA256.hexdigest(@lockfile.read)

if !@retry && (current_gemfile_hash != @gemfile_hash || current_lockfile_hash != @lockfile_hash)
@gemfile_hash = current_gemfile_hash
@lockfile_hash = current_lockfile_hash
@retry = true
@custom_dir.rmtree
$stderr.puts("Ruby LSP> Bundle was modified during setup. Retrying from scratch...")
return setup!
end
end

env
end

Expand Down
37 changes: 37 additions & 0 deletions test/setup_bundler_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,43 @@ def test_succeeds_when_using_ssh_git_sources_instead_of_https
end
end

def test_is_resilient_to_gemfile_changes_in_the_middle_of_setup
Dir.mktmpdir do |dir|
Dir.chdir(dir) do
File.write(File.join(dir, "Gemfile"), <<~GEMFILE)
source "https://rubygems.org"
gem "rdoc"
GEMFILE

Bundler.with_unbundled_env do
capture_subprocess_io do
# Run bundle install to generate the lockfile
system("bundle install")

# This section simulates the bundle being modified during the composed bundle setup. We initialize the
# composed bundle first to eagerly calculate the gemfile and lockfile hashes. Then we modify the Gemfile
# afterwards and trigger the setup.
#
# This type of scenario may happen if someone switches branches in the middle of running bundle install. By
# the time we finish, the bundle may be in a different state and we need to recover from that
composed_bundle = RubyLsp::SetupBundler.new(dir, launcher: true)

File.write(File.join(dir, "Gemfile"), <<~GEMFILE)
source "https://rubygems.org"
gem "rdoc"
gem "irb"
GEMFILE
system("bundle install")

composed_bundle.setup!
end

assert_match("irb", File.read(".ruby-lsp/Gemfile.lock"))
end
end
end
end

private

def with_default_external_encoding(encoding, &block)
Expand Down
Loading