Add launcher mode for gracefully handling missing dependencies #2751
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Motivation
One of the biggest pain points of using the Ruby LSP is that we're tightly tied to the project's bundle. There are several reasons for why this is the case:
Not being able to hook into the project's bundle would mean significant losses in terms of DX. However, this also means that if even a single gem failed to install, we are unable to launch the server - and that is also bad DX.
Even worse,
bundle exec
immediately exits the process if any gems are missing, which violates the LSP life cycle (the server needs to inform the editor about an initialization failure by printing a JSON back and the process should never exit prematurely).This PR proposes a new way of launching the Ruby LSP, which should allow us to have a bit more control over missing dependencies.
Implementation
I recommend reviewing per commit.
Bundler.setup
back to the user. It also allows us to read what is the workspace URI before invokingBundler.setup
, which allows us to fix the longstanding bug of not setting up the bundle in the right directory for editors that launch the server process outside of the workspace directoryruby-lsp-launcher
executable. I will explain below the reason why we need thisWhy do we need a new executable
We need to be able to:
Bundler.setup
is invoked, so that we can fail gracefully and report errors back to the userThe problem is that when a gem's executable is invoked, you don't actually invoke the executable. You invoke Rubygems binstubs, which automatically activate all of the gem's dependencies.
That means that when
ruby-lsp
is executed, Rubygems will eagerly activate whatever latest versions of our dependencies are present. If the project's bundle is locked to any other version other than the ones eagerly activated, thenBundler.setup
is guaranteed to fail complaining that another version of that gem has already been activated.To work around this, we replace the current process with
exec
, invoking theruby-lsp-launcher
executable directly (without going through the Rubygems binstub) to avoid having our own dependencies eagerly activated and polluting the process before we had a chance to invokeBundler.setup
.This approach means that no gems are activated until we finished setting up the composed bundle and manually invoke
Bundler.setup
ourselves, which ensures that no conflicts can occur and let's us handle the errors in an editor friendly way.Automated Tests
I will follow up with some integration tests.