mason.nvim
mason.nvim copied to clipboard
Package does not run on NixOS
I believe this to be an issue that should be addressed by maintainers of mason.nvim.
- [X] Yes
Why do you think this is an issue with mason.nvim?
This is very much a NixOS problem and I would like to find a way of addressing it. Whether that is through mason or NixOS is tbd. But I haven't found any mention of it anywhere here so I thought it would be worth having the discussion and maybe we can find a way of making it work.
Neovim version (>= 0.7)
NVIM v0.7.2 Build type: Release LuaJIT 2.1.0-beta3 Compiled by nixbld
Features: +acl +iconv +tui See ":help feature-compile"
system vimrc file: "$VIM/sysinit.vim" fall-back for $VIM: " /nix/store/jnfqzl7lzqshbqfm9mkl27024nsbcgvz-neovim-unwrapped-0.7.2/share/nvim "
Run :checkhealth for more info
Operating system/version
Linux xray 5.19.5 #1-NixOS SMP PREEMPT_DYNAMIC Mon Aug 29 09:18:05 UTC 2022 x86_64 GNU/Linux
I've manually reviewed logs to find potential errors
- [X] Yes
I've recently downloaded the latest plugin version of mason.nvim
- [x] Yes
Affected packages
All
Problem description
When installing packages through mason.vim it installs binaries that are not executable on NixOS because shared libraries not working the same on NixOS.
Steps to reproduce
Run NixOS :)
Actual behavior
[ERROR][2022-09-15 00:17:55] .../vim/lsp/rpc.lua:420 "rpc" "lua-language-server" "stderr" "/home/leex/.local/share/nvim/mason/bin/lua-language-server: line 3: /home/leex/.local/share/nvim/mason/packages/lua-language-server/extension/server/bin/lua-language-server: No such file or directory\n"
Expected behavior
Maybe we can have a post install hook where we patch the elf or we maybe that is just the wrong way and the right way would be to bring mason into home-manager. :shrug: That's why I am asking :slightly_smiling_face:
Healthcheck
mason: require("mason.health").check()
========================================================================
## mason.nvim report
- OK: neovim version >= 0.7.0
- WARNING: **Go**: not available
- OK: **cargo**: `cargo 1.65.0-nightly (646e9a0b9 2022-09-02)`
- WARNING: **luarocks**: not available
- OK: **Ruby**: `ruby 3.0.4p208 (2022-04-12 revision 3fa771dded) [x86_64-linux]`
- OK: **RubyGem**: `3.3.20`
- WARNING: **Composer**: not available
- WARNING: **PHP**: not available
- OK: **npm**: `8.19.1`
- OK: **node**: `v18.9.0`
- OK: **python3**: `Python 3.10.6`
- OK: **pip3**: `pip 22.2.2 from /home/leex/.local/lib/python3.10/site-packages/pip (python 3.10)`
- OK: **javac**: `javac 17.0.4`
- OK: **java**: `Picked up _JAVA_OPTIONS: -Dsun.java2d.uiScale=2`
- WARNING: **julia**: not available
- OK: **wget**: `GNU Wget 1.21.3 built on linux-gnu.`
- OK: **curl**: `curl 7.84.0 (x86_64-pc-linux-gnu) libcurl/7.84.0 OpenSSL/1.1.1q zlib/1.2.12 brotli/1.0.9 zstd/1.5.2 libidn2/2.3.2 libssh2/1.10.0 nghttp2/1.47.0`
- OK: **gzip**: `gzip 1.12`
- OK: **tar**: `tar (GNU tar) 1.34`
- OK: **bash**: `GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)`
- OK: **sh**: `Ok`
- OK: **python3_host_prog**: `Python 3.10.6`
- OK: GitHub API rate limit. Used: 1. Remaining: 59. Limit: 60. Reset: Thu 15 Sep 2022 12:46:29 PM PDT.
Screenshots or recordings
No response
Hope I picked the right issue type here. :slightly_smiling_face:
@klautcomputing It is because the dynamic loader of the binary is pointing to a path that does not exist on NixOS. You can run something like this to fix it:
#! /usr/bin/env nix-shell
#! nix-shell -i bash -p patchelf
for binary in ${@}
do
patchelf \
--set-interpreter "$(cat ${NIX_CC}/nix-support/dynamic-linker)" \
"${binary}"
done
I put this as fx_loader.sh. You can then call something like this:
./fix_linker.sh ~/.local/share/nvim/mason/packages/lua-language-server/extension/server/bin/lua-language-server
After that, it will work on NixOS.
@archite Thank you for documenting this! I should have maybe done that myself instead of just mentioning that I patched the elf without actually posting any solution for anyone who stumbles upon this issue. :sweat_smile:
I would like for the issue to remain open as my question wasn't how to get it to work on an individual level, but instead what the best way would be to automate this in mason. Or whether mason-like functionality could be integrated in something like home-manager?
I would like to explore what the best way could be to address this for all NixOS users. Patching the elf by hand all the time isn't ergonomic and definitely does not allow for other plugins like mason-tool-installer to be used.
@klautcomputing I was curious if I could make a more automated solution for fun. I highly don't suggest this but one could add something ing like this to configuration.nix:
environment = {
extraSetup = ''
mkdir -p $out/lib64
ln -sf ${pkgs.glibc}/lib64/ld-linux-x86-64.so.2 $out/lib64/ld-linux-x86-64.so.2
'';
};
systemd.tmpfiles.rules = [
"L+ /lib64 - - - - /run/current-system/sw/lib64"
];
Once again, I did this for fun and some quick testing. I'd probably recycle only for nixos containers.
Hello! Currently the platform detection for Linux systems is a bit too relaxed, causing packages to be installed on systems where they're not supported. I plan on revising these eventually (which effectively will cause these packages to instead fail installation).
I've never used NixOS personally, but isn't the notion of centrally and reproducibly (is that a word?) managing ones system dependencies a key value proposition of it? The way Mason handles packages arguably pales in comparison to the Nix way. Just out of pure curiosity - is there anything specific that Mason does that makes it particularly appealing, even on NixOS?
As for auto-patching installed binaries, you could probably hack something together like:
local mason_registry = require("mason-registry")
local function is_binary(file)
-- ...
end
local function patch_elf(binary_file)
-- ...
end
mason_registry:on("package:install:success", function (pkg)
pkg:get_receipt():if_present(function (receipt)
for bin, rel_path in pairs(receipt.links.bin) do
local bin_abs_path = pkg:get_install_path() .. "/" .. rel_path
if is_binary(bin_abs_path) then
patch_elf(bin_abs_path)
end
end
end)
end)
I just got this same issue and I don't know how and why it's failing. I can't even run the binary manually:
~/.local/share/nvim/mason/packages/lua-language-server/extension/server/bin/lua-language-server
exec: Failed to execute process '/home/budiman/.local/share/nvim/mason/packages/lua-language-server/extension/server/bin/lua-language-server': The file exists and is executable. Check the interpreter or linker?
~/.local/share/nvim/mason/bin/lua-language-server
/home/budiman/.local/share/nvim/mason/bin/lua-language-server: line 3: /home/budiman/.local/share/nvim/mason/pack
ages/lua-language-server/extension/server/bin/lua-language-server: No such file or directory
@budimanjojo it's because the binary needs to be patched for the changed ld location. I do this on my arm system because I'm too lazy to patch every binary each time I update nixes:
environment.etc = {
"tmpfiles.d/ld-so.conf" = {
text = ''
D /lib 0755 root root - -
L+ /lib/ld-linux-aarch64.so.1 - - - - ${pkgs.glibc}/lib/ld-linux-aarch64.so.1
'';
};
};
Probably a bad idea but I don't run production dev systems.
@archite thanks! But I can't get it to work by changing ld-linux-aarch64 to ld-linux-x86-64.
I also noticed that it's only affecting lua-language-server. All the others (most of them are nodejs applications and I also have mason-tool-installer to manage linting tools) are working fine in NixOS. Wondering if this is specific issue with sumneko_lua or the way mason handle it.
I think mason should declare where the ld location just like how it "force" neovim to use its own PATH? Or am I completely wrong?
@budimanjojo on x86_64 it is ld-linux-x86-64.so.2. Many LSP servers are written in Node but a good number are not such as clangd, rnix, and many more. The problem I run into is that patching must be done anytime glibc changes which can be frustrating.
@archite unfortunately it still doesn't work. I tried ld-linux-x86-64.so.2, ld-linux-x86_64.so.2 and ld-linux.so.2 (because it's in this path on my Ubuntu machine). Maybe I got another problem?
@budimanjojo yeah, that was nixos specific. Sorry. If I get time I'll try and reproduce for you.
I've never used NixOS personally, but isn't the notion of centrally and reproducibly (is that a word?) managing ones system dependencies a key value proposition of it? The way Mason handles packages arguably pales in comparison to the Nix way. Just out of pure curiosity - is there anything specific that Mason does that makes it particularly appealing, even on NixOS?
I use mason instead of nixos to handle the binaries because I was already using mason before moving to nixos. And I still have some machines not using nix yet and I share the same nvimrc for all of them.
I actually managed to make it work with nix-ld but I feel like that's too much hack just for this.
I decided to set the PATH settings in mason to append instead of prepend so I can just default to nixos provided package for stuffs that doesn't work in nixos (currently only sumneko-lua in my case).
Hey! I am having the same problem with mason and NixOS, and also want to use mason because I use ubuntu in other computers. @budimanjojo can you explain how you made it work please?
I got this working by adding the following to my init.lua:
local mason_registry = require("mason-registry")
mason_registry:on("package:install:success", function(pkg)
pkg:get_receipt():if_present(function(receipt)
for _, rel_path in pairs(receipt.links.bin) do
local bin_abs_path = pkg:get_install_path() ..
"/extension/server/bin/" .. rel_path
os.execute(
'patchelf --set-interpreter "$(patchelf --print-interpreter $(grep -oE \\/nix\\/store\\/[a-z0-9]+-neovim-unwrapped-[0-9]+\\.[0-9]+\\.[0-9]+\\/bin\\/nvim $(which nvim)))" ' ..
bin_abs_path)
end
end)
end)
This uses patchelf to both figure out what the interpreter should be (by inspecting nvim itself) and then to set the interpreter on the target binary. Non-ELF files will be ignored.
@SergioQuijanoRey The commit to what I did is linked in this issue above. Basically I just set mason to prefer using system installed program if it's available with:
require('mason').setup {
PATH = "append",
}
@s1341 awesome!
Oh, thanks a lot @budimanjojo and @s1341 for the responses! I am trying to make @s1341 solution work (is more attractive for me) but it is not yet working.
I think the problem, for me, is bin_abs_path. My file structure is the following:
/home/sergio/.local/share/nvim/mason
├── bin
│ └── marksman -> ../packages/marksman/marksman
└── packages
└── marksman
├── marksman
└── mason-receipt.json
Also, executing the regex in my terminal does not work, I have to quote the regex to get results.
The regex is escaped for inclusion in a lua string. What we actually have to do is figure out where all the executables are in the package and patch them all.
I've modified your function this way:
local function osExecute(cmd)
local fileHandle = assert(io.popen(cmd, 'r'))
local commandOutput = assert(fileHandle:read('*a'))
local returnTable = {fileHandle:close()}
return commandOutput,returnTable[3] -- rc[3] contains returnCode
end
-- Make mason packages work with nixos
-- We're using patchelf to mathe that work
-- Thanks to: https://github.com/williamboman/mason.nvim/issues/428#issuecomment-1357192515
local mason_registry = require("mason-registry")
mason_registry:on("package:install:success", function(pkg)
pkg:get_receipt():if_present(function(receipt)
-- Figure out the interpreter inspecting nvim itself
-- This is the same for all packages, so compute only once
local interpreter = osExecute(
"patchelf --print-interpreter $(grep -oE '\\/nix\\/store\\/[a-z0-9]+-neovim-unwrapped-[0-9]+\\.[0-9]+\\.[0-9]+\\/bin\\/nvim' $(which nvim))"
)
for _, rel_path in pairs(receipt.links.bin) do
local bin_abs_path = pkg:get_install_path() .. "/extension/server/bin/" .. rel_path
print("TODO -- bin_abs_path is " .. bin_abs_path)
print("TODO -- interpreter is " .. interpreter)
-- Set the interpreter on the binary
os.execute(
'patchelf --set-interpreter ' .. interpreter .. " " .. bin_abs_path
)
end
end)
end)
This gives me the following messages after installing a package:
TODO -- bin_abs_path is /home/sergio/.local/share/nvim/mason/packages/marksman/extension/server/bin/marksman
TODO -- interpreter is /nix/store/hsk71z8admvgykn7vzjy11dfnar9f4r1-glibc-2.35-163/lib/ld-linux-x86-64.so.2
Mason fails with following message:
/home/sergio/.local/share/nvim/mason/packages/marksman/extension/server/bin/marksman: No such file or directory
When changing local bin_abs_path = pkg:get_install_path() .. "/" .. rel_path same Mason error shows (with different path of course). Any idea of what I am doing wrong?
Thanks you in advance for your patience.
What is the actual path to the file you want to patch?
So to generalize this you'd need to exclude the "/extension/server/bin/" component, as this is specific to the lua-language- server. This is needed because Mason has to write a wrapper bash script for lua-language-server because of reasons (I believe lua-language-server is currently the only package that installs a wrapper bash script, so it can be managed as an edge case).
I'd do something like this:
local mason_registry = require("mason-registry")
mason_registry:on("package:install:success", function(pkg)
pkg:get_receipt():if_present(function(receipt)
-- Figure out the interpreter inspecting nvim itself
-- This is the same for all packages, so compute only once
local interpreter = os.execute(
("patchelf --print-interpreter %q"):format(
"$(grep -oE '\\/nix\\/store\\/[a-z0-9]+-neovim-unwrapped-[0-9]+\\.[0-9]+\\.[0-9]+\\/bin\\/nvim' $(which nvim))"
)
)
for _, rel_path in pairs(receipt.links.bin) do
local bin_abs_path = pkg:get_install_path() .. "/" .. rel_path
if pkg.name == "lua-language-server" then
bin_abs_path = pkg:get_install_path() .. "/extension/server/bin/lua-language-server"
end
-- Set the interpreter on the binary
os.execute(
("patchelf --set-interpreter %q %q"):format(interpreter, bin_abs_path)
)
end
end)
end)
Note: I use string templates with "%q" placeholders to avoid word splitting when calling os.execute.
Btw I'd probably be open to exposing APIs to hook into the "linking" process during installation, allowing people to register a middleware that patches binaries within the installation lifecycle itself, instead of after the fact. This would also allow it to fail the installation should it be unable to patch the binary, resulting in better feedback. I'm thinking something like this (but more thought through):
require("mason-core.installer").register_link_middleware(function (target)
if is_binary(target) then
patch_elf(target)
end
end)
Let me think some more about it.
I am trying to install marksman through mason. NixOS has no package for it so its a real use case for Mason in NixOS. After doing normal Mason install for that LSP I've got:
/home/sergio/.local/share/nvim/mason
├── bin
│ └── marksman -> ../packages/marksman/marksman
└── packages
└── marksman
├── marksman
└── mason-receipt.json
So I guess that I want to patch file /home/sergio/.local/share/nvim/mason/marksman/marksman.
Doing so with prev lua code does nothing, as Client 1 quit with exit code 127 and signal 0 error keeps showing up.
Check :LspInfo. Could be it is working.
:LspLog is helpful for debugging too. You can also change log level :h vim.lsp.set_log_level()
LspInfo gives me:
Detected filetype: markdown
0 client(s) attached to this buffer:
Other clients that match the filetype: markdown
Config: marksman
Refer to :h lspconfig-root-detection for help.
filetypes: markdown
root directory: Not found.
cmd: marksman server
cmd is executable: true
autostart: true
custom handlers:
Configured servers list: /texlab, r_language_server, sumneko_lua, marksman, rust_analyzer, pylsp, clangd, rnix
LspLog gives me:
[START][2022-12-19 20:33:49] LSP logging initiated
[ERROR][2022-12-19 20:33:49] .../vim/lsp/rpc.lua:733 "rpc" "marksman" "stderr" "marksman: error while loading shared libraries: libz.so.1: cannot open shared object file: No such file or directory\n"
Any idea what is happening?
Again, thank you both for your immense patience
It looks like marksman need zlib. You need to make that available in the shell you are running neovim from with e.g. nix-shell -p zlib
@williamboman did you give any more thought to the middleware api?
How about including nixos support directly in mason?
@s1341 if having direct support for NixOS is not an option maybe writing a plugin that adds on top of mason.nvim for having such support could be an option. It should not be a huge plugin and I will be willing to collaborate.
I'm happy to help out too. Let's wait to see what @williamboman decides.
Just to clarify, this would be very far down the backlog and more of an escape hatch for NixOS people who insist on using Mason. I feel like it's fighting the fundamental design and philosophy of the distro and not something I'd like to encourage (although I understand the use case of having a portable Neovim config).