NixOS上のVS CodeでMicrosoftのC/C++ Extensionを使用する

NixOS上のVS CodeでMicrosoftのC/C++ Extensionを使用する

VS CodeにはMicrosoft謹製のC/C++ Extensionがある.このExtensionにはバイナリが含まれているため,NixOSではそのまま使用することができず,cpptools client: couldn't create connection to serverというエラーが出る.これはNixOSの構成に起因するもので,通常であればpatchelfを使用して実行時リンカー(ELFインタープリター)を置き換えるなどの対応が必要なのだが,幸いnixpkgsに当該Extensionが登録されているため,自分で修正することなくこれを使うことができる.この記事では,home-managerを使用してこれを導入した.

Tags: vscode microsoft nixos
Takafumi Asano · 10 minute read

導入

私は,これまでC/C++を読み書きする際,簡単なものであればemacs,込み入ったものであればJetBrainsCLionを使ってきた.

どちらも十分に便利なのだが,最近ではemacsよりVS Codeを使用していることのほうが多く,いくつかC++で実装されているOSS製品の実装を把握する必要がありそうなので,VS CodeにもC/C++の環境を整えることにした.

VS CodeでC++のExtensionを探そうとしたところ,RECOMENDEDにMicrosoft謹製のExtensionがあったので,何も考えずにこれをインストールした.

さて,使っていこう.と.ccをファイルを開いた所,以下のようなエラーが出たのである.

cpptools client: couldn't create connection to server.

cpptools client: couldn't create connection to server.

さて,表示のとおり,Language Serverに接続が出来ていないようだ.VS CodeのOUTPUTには以下のようなログがある.

[Error - 12:12:04 AM] cpptools client: couldn't create connection to server.
Launching server using command /home/<user>/.vscode/extensions/ms-vscode.cpptools-1.13.8-linux-x64/bin/cpptools failed. Error: spawn /home/<user>/.vscode/extensions/ms-vscode.cpptools-1.13.8-linux-x64/bin/cpptools ENOENT

バイナリを起動しようとしたが,ENOENTが返されている.

勘の良いNixOSユーザならこの時点でpatchelfの存在を思い出すだろう.

原因

全く同じ問題がNixOSのDiscourseに上がっており,完璧な回答も示されていることから,__これを読めば原因も対応もわかる__のだが,調査中に記事を書くつもりでメモをここまで書いてしまったので,この記事も最後まで書き切ろう.

このVS Code Extensionには,事前ビルド済みのバイナリが含まれている.

一般的な大多数のLinux Distributionでは問題なく動作するのだろうが,NixOSはglibcですら複数のバージョンの同居を許す柔軟なLinux Distributionのため,他のDistributionでビルドされたバイナリが期待する場所に共有ライブラリが設置されていないのだ.

実際にlddで,エラーを出力しているバイナリが使用する共有ライブラリを見てみよう.

$ ldd ~/.vscode/extensions/ms-vscode.cpptools-1.13.8-linux-x64/bin/cpptools                              
	linux-vdso.so.1 (0x00007ffc2d709000)
	libm.so.6 => /nix/store/<hash>-glibc-2.35-163/lib/libm.so.6 (0x00007f25a36e8000)
	libpthread.so.0 => /nix/store/<hash>-glibc-2.35-163/lib/libpthread.so.0 (0x00007f25a36e3000)
	libc.so.6 => /nix/store/<hash>-glibc-2.35-163/lib/libc.so.6 (0x00007f25a3400000)
	libdl.so.2 => /nix/store/<hash>-glibc-2.35-163/lib/libdl.so.2 (0x00007f25a36de000)
	librt.so.1 => /nix/store/<hash>-glibc-2.35-163/lib/librt.so.1 (0x00007f25a36d9000)
	libgcc_s.so.1 => /nix/store/<hash>-glibc-2.35-163/lib/libgcc_s.so.1 (0x00007f25a36bd000)
	/lib64/ld-linux-x86-64.so.2 => /nix/store/<hash>-glibc-2.35-163/lib64/ld-linux-x86-64.so.2 (0x00007f25a37ca000)

実行時リンカーとしてldを期待しているが,我らがNixOSには/lib64ディレクトリが存在しない.実行時にENOENTで落ちるわけである.

対応

前述の完璧な回答の通り,対応は2つ考えられる.

  1. VS Code Extension内に含まれるバイナリに対して手動でpatchelfをあてる
  2. ExtensionをNixで入れる

正直どっちでもいいのだが,Extensionのアップデート毎にpatchelfを毎回あてることを考えると流石に面倒であり,自動化するとなると結局nix式を自分で書いていそうなので,それならば最初から先人が書いたnix式を有効活用させてもらおう.

私はhome-managerで作業環境の構成を管理しているので,回答にあげられていたoverrideではなく,home-managerに以下のスニペットを追加することで対応することにした.

let
  unstable = import <unstable> { config.allowUnfree = true; };
in
{
  programs.vscode = {
    enable = true;
    package = unstable.vscode;
    extensions = with unstable.vscode-extensions; [
      ms-vscode.cpptools
    ];
  };
}
}

unstable channelを参照しているのは,この記事を書いている12月26日現在,stableにあるVS Codeのバージョンが1.73系と少し古く,一部のExtensionが動かなかっためである.

nixは配備先に既にファイルがある場合,シンボリックリンクの作成を行わないので,このソリューションを機能させるためには,事前にms-vscode.cpptoolsをVS Codeから削除する必要があることに注意する.

~/.vscode/extensions/ms-vscode.cpptools-<version>-<arch>が存在しないことを確認したら,上記スニペットをimportして,home-manager switchをする.

プロファイルの切換が完了したら,nixで導入したextensionが存在することを確認してみよう.

$ ls -la ~/.vscode/extensions/ms-vscode.cpptools
lrwxrwxrwx 1 <user> users 100 Dec 27 09:56 /home/<user>/.vscode/extensions/ms-vscode.cpptools -> /nix/store/<hash>-home-manager-files/.vscode/extensions/ms-vscode.cpptools

実行時リンカーのパスが適切なものに替わっていることも確認してみる.

$ ldd ~/.vscode/extensions/ms-vscode.cpptools/bin/cpptools
        linux-vdso.so.1 (0x00007ffc601db000)
        libm.so.6 => /nix/store/<hash>-glibc-2.35-163/lib/libm.so.6 (0x00007f8c47cae000)
        libpthread.so.0 => /nix/store/<hash>-glibc-2.35-163/lib/libpthread.so.0 (0x00007f8c47ca9000)
        libc.so.6 => /nix/store/<hash>-glibc-2.35-163/lib/libc.so.6 (0x00007f8c47a00000)
        libdl.so.2 => /nix/store/<hash>-glibc-2.35-163/lib/libdl.so.2 (0x00007f8c47ca4000)
        librt.so.1 => /nix/store/<hash>-glibc-2.35-163/lib/librt.so.1 (0x00007f8c47c9f000)
        libgcc_s.so.1 => /nix/store/<hash>-glibc-2.35-163/lib/libgcc_s.so.1 (0x00007f8c47c83000)
        /nix/store/<hash>-glibc-2.35-163/lib/ld-linux-x86-64.so.2 => /nix/store/<hash>-glibc-2.35-163/lib64/ld-linux-x86-64.so.2 (0x00007f8c47d90000)

問題ないようだ.

この状態でVS CodeでC++のコードを開くと,冒頭のエラーは表示されず,IntelliSenseやJumpなどが機能することを確認することができた.

最後に

私は普段Linuxを使うようになったが,macOSやWindowsでも作業することがあるので,複数環境でも同一の環境を構成出来るようVS Code Setting Syncを使用している.

これは設定だけではなくExtensionも同期するため,Nix(home-manager)との二重管理状態がどういう影響を及ぼすのかについては確認できていない.

なにか不都合が出たら,また記事にするかもしれない.