mason.nvim icon indicating copy to clipboard operation
mason.nvim copied to clipboard

Ruby LSP does not work when using different versions of Ruby

Open klaustopher opened this issue 1 year ago • 14 comments

I've searched open issues for similar requests

  • [X] Yes

I've manually reviewed logs to find potential errors

  • [X] Yes

I've recently downloaded the latest plugin version of mason.nvim

  • [X] Yes

Problem description

First, I use rbenv to manage my ruby versions.

My system ruby is set to 3.3.4 (most current), and then in my project specific folders I am using different ruby versions using the .ruby-version file. When I install the ruby-lsp package using mason, it is installed with the system ruby version, that includes compiling the prism gem that is a natively built C extension that is being linked against the specific ruby version it was built with (so in this case 3.3.4)

When I now start neovim in one of my project folders that has a .ruby-version file, nvim tries to run ruby-lsp with the project specific ruby (i.e. 3.3.1), but since the installed version in ~/.local/share/nvim/mason/ruby-lsp was built against the system ruby (3.3.4 here). Log file shows:

~/.rbenv/versions/3.3.1/lib/ruby/3.3.0/bundled_gems.rb:74:in `require': linked to incompatible ~/.rbenv/versions/3.3.4/lib/libruby.3.3.dylib - ~/.local/share/nvim/mason/packages/ruby-lsp/gems/prism-0.30.0/lib/prism/prism.bundle (LoadError)
	from ~/.rbenv/versions/3.3.1/lib/ruby/3.3.0/bundled_gems.rb:74:in `block (2 levels) in replace_require'
	from ~/.local/share/nvim/mason/packages/ruby-lsp/gems/prism-0.30.0/lib/prism.rb:78:in `<top (required)>'
	from ~/.rbenv/versions/3.3.1/lib/ruby/3.3.0/bundled_gems.rb:74:in `require'
	from ~/.rbenv/versions/3.3.1/lib/ruby/3.3.0/bundled_gems.rb:74:in `block (2 levels) in replace_require'
	from ~/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/ruby-lsp-0.17.15/lib/ruby_lsp/internal.rb:19:in `<top (required)>'
	from ~/.rbenv/versions/3.3.1/lib/ruby/3.3.0/bundled_gems.rb:74:in `require'
	from ~/.rbenv/versions/3.3.1/lib/ruby/3.3.0/bundled_gems.rb:74:in `block (2 levels) in replace_require'
	from ~/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/ruby-lsp-0.17.15/exe/ruby-lsp:82:in `<top (required)>'
	from ~/.rbenv/versions/3.3.1/bin/ruby-lsp:25:in `load'
	from ~/.rbenv/versions/3.3.1/bin/ruby-lsp:25:in `<main>'
	from ~/.rbenv/versions/3.3.1/bin/ruby_executable_hooks:22:in `eval'
	from ~/.rbenv/versions/3.3.1/bin/ruby_executable_hooks:22:in `<main>'

I have currently worked around this in my neovim config, by hardcoding the paths for ruby-lsp and rubocop in my neovim-lspconfig.lua file, and skipping mason:

return {
    "neovim/nvim-lspconfig",
    opts = {
        servers = {
            ruby_lsp = {
                mason = false,
                cmd = { vim.fn.expand("~/.rbenv/shims/ruby-lsp") },
            },
            rubocop = {
                mason = false,
                cmd = { vim.fn.expand("~/.rbenv/shims/rubocop"), "--lsp" },
            }
        },
    },
}

Expected behavior

Personally, I think that mason should support installing the ruby-lsp gem for multiple ruby versions, one way how this could be done, is that instead of installing the gem into ~/.local/share/nvim/mason/packages/ruby-lsp, the current ruby version could be included in the path i.e. ~/.local/share/nvim/mason/packages/ruby-3.3.4/ruby-lsp or similar. This way, the package would be reinstalled with a freshly compiled version of prism for the currently used ruby version for each project.

Steps to reproduce

  1. Switch to a specific ruby version (i.e. 3.3.4)
  2. Open Neovim and install the ruby-lsp package using mason
  3. Close Neovim
  4. Switch to a different ruby version (i.e. 3.2.5, but every version works)
  5. Open Neovim and open a Ruby file
  6. See that LSP cannot work because the error message above is shown

Affected packages

ruby-lsp

Neovim version (>= 0.7)

NVIM v0.10.1 Build type: Release LuaJIT 2.1.1724512491

Operating system/version

Darwin mymachine.internal 23.6.0 Darwin Kernel Version 23.6.0: Mon Jul 29 21:14:30 PDT 2024; root:xnu-10063.141.2~1/RELEASE_ARM64_T6030 arm64

Healthcheck

==============================================================================
mason: require("mason.health").check()

mason.nvim ~
- OK mason.nvim version v1.10.0
- OK PATH: prepend
- OK Providers: 
    mason.providers.registry-api
    mason.providers.client
- OK neovim version >= 0.7.0

mason.nvim [Registries] ~
- OK Registry `github.com/mason-org/mason-registry version: 2024-08-26-cute-join` is installed.

mason.nvim [Core utils] ~
- OK unzip: `UnZip 6.00 of 20 April 2009, by Info-ZIP.  Maintained by C. Spieler.  Send`
- OK wget: `GNU Wget 1.24.5 built on darwin23.2.0.`
- OK curl: `curl 8.7.1 (x86_64-apple-darwin23.0) libcurl/8.7.1 (SecureTransport) LibreSSL/3.3.6 zlib/1.2.12 nghttp2/1.61.0`
- OK gzip: `Apple gzip 430.140.2`
- OK tar: `bsdtar 3.5.3 - libarchive 3.5.3 zlib/1.2.12 liblzma/5.4.3 bz2lib/1.0.8 `
- OK bash: `GNU bash, version 5.2.32(1)-release (aarch64-apple-darwin23.4.0)`
- OK sh: `Ok`

mason.nvim [Languages] ~
- WARNING Go: not available
  - ADVICE:
    - spawn: go failed with exit code - and signal -. go is not executable
- WARNING Composer: not available
  - ADVICE:
    - spawn: composer failed with exit code - and signal -. composer is not executable
- WARNING PHP: not available
  - ADVICE:
    - spawn: php failed with exit code - and signal -. php is not executable
- WARNING cargo: not available
  - ADVICE:
    - spawn: cargo failed with exit code - and signal -. cargo is not executable
- WARNING luarocks: not available
  - ADVICE:
    - spawn: luarocks failed with exit code - and signal -. luarocks is not executable
- WARNING javac: not available
  - ADVICE:
    - spawn: javac failed with exit code 1 and signal 0. The operation couldn’t be completed. Unable to locate a Java Runtime.
      Please visit http://www.java.com for information on installing Java.
      
      
- OK node: `v18.20.1`
- WARNING julia: not available
  - ADVICE:
    - spawn: julia failed with exit code - and signal -. julia is not executable
- OK python: `Python 3.12.5`
- WARNING java: not available
  - ADVICE:
    - spawn: java failed with exit code 1 and signal 0. The operation couldn’t be completed. Unable to locate a Java Runtime.
      Please visit http://www.java.com for information on installing Java.
      
      
- OK Ruby: `ruby 3.3.1 (2024-04-23 revision c56cd86388) [arm64-darwin23]`
- OK RubyGem: `3.5.17`
- OK pip: `pip 24.2 from /opt/homebrew/lib/python3.12/site-packages/pip (python 3.12)`
- OK npm: `10.5.0`
- OK python venv: `Ok`

mason.nvim [GitHub] ~
- OK GitHub API rate limit. Used: 5. Remaining: 55. Limit: 60. Reset: Mon Aug 26 13:34:52 2024.
  Install and authenticate via gh-cli to increase rate limit.


### Screenshots or recordings

_No response_

klaustopher avatar Aug 26 '24 11:08 klaustopher

I'm struggling with this issue too

alexis avatar Aug 31 '24 18:08 alexis

I guess you need to update mason to solve this issue. :MasonUpdate solved it in my case

fidalgo avatar Oct 25 '24 08:10 fidalgo

Was there an update to Mason that includes a fix for this?

klaustopher avatar Oct 25 '24 09:10 klaustopher

@klaustopher I guess the update triggered a new installation of the dependencies, so in my case because I was already using a different ruby version it worked.

fidalgo avatar Oct 25 '24 12:10 fidalgo

Ok, but manually running this every time is not really an option for me as I am often switching projects with different versions. So my fix mentioned above and manually installing ruby-lsp (or automating it with rbenv-default-gems plugin)

klaustopher avatar Oct 25 '24 12:10 klaustopher

I override the BUNDLE_GEMFILE env to a global gemfile to prevent this issue.

ruby_lsp = {
          cmd_env = { BUNDLE_GEMFILE = vim.fn.getenv('GLOBAL_GEMFILE')},
          cmd = { 'ruby-lsp' },
          filetypes = { 'ruby', 'eruby' },
          root_dir = function()
            return vim.loop.cwd()
          end,
        },

Chrispycode avatar Oct 27 '24 12:10 Chrispycode

@Chrispycode's workaround worked for me with Lazy and mason-lspconfig.nvim. The config looks something like this:

{
	'williamboman/mason-lspconfig.nvim',
	lazy = true,
	dependencies = { 'neovim/nvim-lspconfig' },
	config = function()
		require('mason').setup {}
		require("mason-lspconfig").setup {}
		require("mason-lspconfig").setup_handlers {
			function(server_name)
				require("lspconfig")[server_name].setup {}
			end,

			ruby_lsp = function()
				require('lspconfig').ruby_lsp.setup {
					cmd_env = { BUNDLE_GEMFILE = vim.fn.getenv('GLOBAL_GEMFILE') },
				}
			end,
	end,
},

DeedleFake avatar Jan 09 '25 02:01 DeedleFake

I have the same problem, trying to fix it. What do you put in your GLOBAL_GEMFILE ?

jonathanpa avatar Apr 07 '25 15:04 jonathanpa

Encountered the same issue.

Not a long term fix, but I had a global ~/.config/mise/config.toml file where my global ruby version was set. I updated that value to the ruby version I had in my active project, then reinstalled ruby-lsp via Mason in the project, which worked.

johncardiologs avatar Apr 09 '25 13:04 johncardiologs

For anyone who might be using AstroNvim, I was also able to solve this by changing the path to the ruby-lsp executable to point to my version manager shim:

-- astronvim/lua/plugins/astrolsp.lua

---@type LazySpec
return {
  "AstroNvim/astrolsp",
  ---@type AstroLSPOpts
  opts = {
    config = {
      ruby_lsp = {
        cmd = {
          "/Users/cbeach/.asdf/shims/ruby-lsp",
        }
      }
    },
  },
}

I'm still installing ruby-lsp the default AstroNvim way with Mason, and ruby-lsp operates as expected across different projects with different Ruby versions.

I'm using AstroNvim 5.1.3.

conradbeach avatar Apr 11 '25 20:04 conradbeach

According to the ruby-lsp author (https://github.com/Shopify/ruby-lsp/issues/1248#issuecomment-1969404796), the LSP should be lauched from the ruby env of the project you work in, not from a 'global' ruby. So similarly to conradbeach above, pointing to the shims does the work. With rbenv and Lazyvim, it translated into the following config

  {
    "neovim/nvim-lspconfig",
    opts = {
      servers = {
        ...
        ruby_lsp = {
          cmd = { os.getenv("HOME") .. "/.rbenv/shims/ruby-lsp" },
        },
        ...
      }
    }
  }

Mate2xo avatar May 21 '25 07:05 Mate2xo

Hi, I'm using Omakub, which defaults to Mise, NeoVim, and LazyVim with the LazyExtras Ruby language. This setup uses Mason under the hood to install Ruby-LSP and RuboCop, but fails when switching between projects with different Ruby versions. I needed to uninstall/reinstall Ruby-LSP and RuboCop every time I switched from project with different Ruby versions, which was annoying.

Based on @klaustopher's workaround, here is mine that seems to work fine in projects with different Ruby versions. Hopefully, it would help someone else:

~/.config/nvim/lua/plugins/lsp.lua

return {
  "neovim/nvim-lspconfig",
  opts = {
    servers = {
      ruby_lsp = {
        mason = false,
        cmd = { vim.fn.expand("~/.local/share/mise/shims/ruby-lsp") },
      },
      rubocop = {
        mason = false,
        cmd = { vim.fn.expand("~/.local/share/mise/shims/rubocop"), "--lsp" },
      },
    },
  },
}

As this way skips Ruby-LSP and Rubocop installation via Mason we need to include ruby-lsp and rubocop gems in your project's Gemfile. Alternatively, you can install them by running gem install ruby-lsp rubocop. Ensure mise detects correctly your project ruby version before installing the gems.

This is my mise global config:

~/.config/mise/config.toml

[tools]
ruby = "3.2.2"
node = "lts"

[settings]
idiomatic_version_file_enable_tools = ["ruby"]
legacy_version_file = true

Senen avatar Jul 19 '25 15:07 Senen

As this way skips Ruby-LSP and Rubocop installation via Mason we need to include ruby-lsp and rubocop gems in your project's Gemfile. Alternatively, you can install them by running gem install ruby-lsp rubocop. Ensure mise detects correctly your project ruby version before installing the gems.

Thank you ❤️
This also works for asdf! When manually installing the gems don't forget to reinstall the gems again 💎

PrinceUdoka avatar Aug 04 '25 15:08 PrinceUdoka

While working on different projects that may include or not LSP dependencies in the Gemfile, I had to make some tweaks on my ruby language servers setup as follows.

Note: I'm using ASDF, but should with in a similar way with rbenv, rvm, etc.

  1. create a lsp_utils module with a function to check where the LSP server lives
-- Utility functions for LSP
local M = {}

---Finds the first executable command from a list of commands
---@param commands table[] List of command objects, each with 'cmd' and 'check' properties
---@return string First working command or fallback command
---
---Example:
---```lua
---local cmd = M.first_executable({
---  { cmd = "prettier", check = "command -v prettier" },
---  { cmd = "prettierd", check = "command -v prettierd" }
---})
---```
function M.check_executable(commands)
  local cmd = 'exit 1'

  for _, item in ipairs(commands or  {}) do
    cmd = item["cmd"]
    local check = item["check"]

    if os.execute(check .. ' &> /dev/null') == 0 then
      return cmd
    end
  end

  return cmd -- fallback to last command
end

return M
  1. When configuring ruby language servers, set the cmd using this function:
    -- Ruby LSP
    vim.lsp.config("ruby_lsp", {
      cmd = lsp_utils.check_executable({
        { cmd = { "bundle", "exec", "ruby-lsp" }, check = "bundle exec ruby-lsp --version" },
        { cmd = { "asdf", "exec", "ruby-lsp" },   check = "asdf exec ruby-lsp --version" },
        { cmd = { "ruby-lsp" },                   check = "ruby-lsp --version" },
      }),
    })

    -- Rubocop
    vim.lsp.config("rubocop", {
      cmd = lsp_utils.check_executable({
        { cmd = { "bundle", "exec", "rubocop", "--lsp" }, check = "bundle exec rubocop --version" },
        { cmd = { "asdf", "exec", "rubocop", "--lsp" },   check = "asdf exec rubocop --version" },
        { cmd = { "rubocop", "--lsp" },                   check = "rubocop --version" },
      }),
    })

    -- Solargraph
    vim.lsp.config("solargraph", {
      cmd = lsp_utils.check_executable({
        { cmd = { "bundle", "exec", "solargraph", "stdio" }, check = "bundle exec solargraph --version" },
        { cmd = { "asdf", "exec", "solargraph", "stdio" },   check = "asdf exec solargraph --version" },
        { cmd = { "solargraph", "stdio" },                   check = "solargraph --version" },
      }),
    })

    -- Sorbet
    vim.lsp.config("sorbet", {
      cmd = lsp_utils.check_executable({
        { cmd = { "bundle", "exec", "srb", "tc", "--lsp", "--disable-watchman" }, check = "bundle exec srb --version" },
        { cmd = { "asdf", "exec", "srb", "tc", "--lsp", "--disable-watchman" },   check = "asdf exec srb --version" },
        { cmd = { "srb", "tc", "--lsp", "--disable-watchman" },                   check = "srb --version" },
      }),
    })

This allows for conflicting setups, using the first available command among bundler installed, asdf shim, or the default mason installation.

Usage:

  1. Make sure you have a global ruby installation. It can be the system, or a default ruby set with asdf set -u
  2. Install the language server with MasonInstall as normal. It will install using your global/user ruby
  3. Configure LSPs with the settings above
  4. When available, Nvim will use the bundled version, asdf shim for current directory, or fallback to mason installed version.

Result of :checkhealth vim.lsp

vim.lsp: Active Clients ~
- rubocop (id: 1)
  - Version: ? (no serverInfo.version response)
  - Root directory: ~/Development/some-ruby-project
  - Command: { "bundle", "exec", "rubocop", "--lsp" }
  - Settings: {}
  - Attached buffers: 4
- ruby_lsp (id: 2)
  - Version: 0.26.1
  - Root directory: ~/Development/some-ruby-project
  - Command: { "asdf", "exec", "ruby-lsp" }
  - Settings: {}
  - Attached buffers: 4
- solargraph (id: 3)
  - Version: ? (no serverInfo.version response)
  - Root directory: ~/Development/some-ruby-project
  - Command: { "asdf", "exec", "solargraph", "stdio" }
  - Settings: {
      solargraph = {
        diagnostics = true
      }
    }
  - Attached buffers: 4
- sorbet (id: 4)
  - Version: ? (no serverInfo.version response)
  - Root directory: ~/Development/some-ruby-project
  - Command: { "bundle", "exec", "srb", "tc", "--lsp", "--disable-watchman" }
  - Settings: {}
  - Attached buffers: 4

paulodiovani avatar Aug 14 '25 15:08 paulodiovani