概要
mason.nvimはNeovim向けのLSP serverマネージャーで、OSに応じてビルド済みバイナリ取ってきたり、コンパイルを勝手にやってくれます。
ビルド済みバイナリはライブラリへのリンクがハードリンクでありFHS(Filesystem Hierarchy Standard)に準拠していないNixOSでは基本的にそのまま使うことができません。
この問題は2023/01/28現在、issue#428で議論されています。
ここではNixOSでmason.nvimで取ってきたLSP serverを使う方法を記します。
なお、mason.nvimを使わなくてもnixpkgsでLSP serverを管理する方法もあります。
長所と短所は以下の通りです。
- mason.nvim
- 長所
- linux, mac, windowsでコードを共用できる
- 常に最新版のパッケージが使える
- グラフィカルにインストール状況がNeovimから参照できる
- 短所
- 最新のパッケージのbreaking changeを踏む可能性が高くなる
- NixOSではpatchを当てないと使えないパッケージがある
- mason.nvim上ではインストール完了扱いになっているためLSP起動時まで使えるか分からない
- 長所
- nixpkgs
- 長所
- インストールすれば即使える状態になっている
- linuxとmacではnixpkgs周りのコードを共用できる
- 短所
- channelによっては更新が遅い
- パッケージが長くメンテナンスされていないことがある
- ArchLinuxと違ってNixOSでは更新が止まっているpkgsが散見される
- windowsでは使えない
- 長所
パッチの当て方
パッチはpatchelfを使って当てます。(事前にインストールが必要)
patchelfの使い方は下に書いた。
mason-registry
でインストール後にhookが起動するようにし、そこでハードリンクを修正します。
local mason = require("mason")
local mason_lsp = require("mason-lspconfig")
-- 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 function return_exe_value(cmd)
local handle = io.popen(cmd)
local result = handle:read("*a")
handle:close()
return result
end
local mason_registry = require("mason-registry")
if is_linux and vim.api.nvim_exec("!cat /etc/os-release | grep '^NAME'", true):find("NixOS") ~= nil then
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
-- Set the interpreter on the binary
local nvim = return_exe_value("nix path-info -r /run/current-system | grep neovim-unwrapped"):sub(1, -2)
local interpreter = return_exe_value(("patchelf --print-interpreter %q" .. "/bin/nvim"):format(nvim)):sub(1, -2)
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"
os.execute(("patchelf --set-interpreter %s %s"):format(interpreter, bin_abs_path))
elseif pkg.name == "marksman" then
bin_abs_path = pkg:get_install_path() .. "/marksman"
local libstdcpp = return_exe_value("nix path-info -r /run/current-system | grep gcc | grep lib | head -n1"):sub(1, -2) .. "/lib"
local zlib = return_exe_value("nix path-info -r /run/current-system | grep zlib | head -n1"):sub(1, -2) .. "/lib"
local icu4c = return_exe_value("nix path-info -r /run/current-system | grep icu4c | head -n1"):sub(1, -2) .. "/lib"
os.execute(("patchelf --set-interpreter %s --set-rpath %s:%s:%s %s"):format(interpreter, libstdcpp, zlib, icu4c, bin_abs_path))
elseif pkg.name == "stylua" then
bin_abs_path = pkg:get_install_path() .. "/stylua"
os.execute(("patchelf --set-interpreter %s %s"):format(interpreter, bin_abs_path))
elseif pkg.name == "texlab" then
bin_abs_path = pkg:get_install_path() .. "/texlab"
os.execute(("patchelf --set-interpreter %s %s"):format(interpreter, bin_abs_path))
end
end
end)
end)
end
patchelfの使い方
pathelfの基本的な使い方は以下の通り(詳しい説明はNixOS wiki: Packaging/Binaries
patchelf --print-interpreter <nixpkgsでインストールした適当なバイナリ>
-
patchelf --set-interpreter <1.で出力されたinterpreterのパス> <バイナリ>
2.で動作しない場合 -
patchelf --print-needed <バイナリ>
で依存ライブラリを列挙 -
fd <ライブラリ> /run/current-system
などで依存ライブラリを探す patchelf --set-interpreter <1.で出力されたinterpreterのパス> --set-rpath <lib path 1>:<lib path 2>:... <バイナリ>
まとめ
patchelfを使ってmason.nvimでインストールしたLSP serverをNixOSで動作させる方法を記しました。
LSPを動作させるまでNixOS上で実行可能かどうか分からず、都度パッチを当てる必要があることからnixpkgsで管理した方が幸せになれる気もします。
(そうすると今度はwindowsためにコードを書かないといけないという...)