Iced を使った GUI が NixOS で起動しない

NixOS 上で Iced を使った GUI が WaylandError(Connection(NoWaylandLib)) となって起動しない問題と解決策

公開日時
更新日時

問題

Iced という Rust の GUI ライブラリがある。 これを使って書いたアプリケーションを NixOS で動かそうとするとエラーになって立ち上がらない。

thread 'main' panicked at /build/cargo-vendor-dir/iced_winit-0.14.0/src/lib.rs:81:10:
Create event loop: Os(OsError { line: 89, file: "/build/cargo-vendor-dir/winit-0.30.12/src/platform_impl/linux/wayland/event_loop/mod.rs", error: WaylandError(Connection(NoWaylandLib)) })
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Iced はウィンドウ管理に winit というライブラリを使っており、これが dlopen というものを使って Wayland にアクセスしている。 しかし NixOS ではライブラリを探すためのパスが設定されていないため、 dlopen がライブラリを見つけられずにエラーを返している状態。

winit は wayland-dlopen というデフォルトで ON の機能フラグを持っており、これを OFF にして (なんか諸々色々ごちゃごちゃ設定をして) ビルドをすれば正常にリンクできるはずだ。 しかし Iced がそのフラグをハードコードしているためライブラリ利用者やエンドユーザ側から変えることができない。

解決法

Derivation (パッケージ) には autoPatchelfHook (もしくは手動で patchelf) を使い、 nix-shellnix develop などの mkShell には LD_LIBRARY_PATH という環境変数を指定する。

Derivation

nativeBuildInputsautoPatchelfHook を追加し、 runtimeDependencies にリンクするライブラリ (エラーが出て怒られるライブラリ) を指定するだけ。

# my-derivation.nix
{
  rustPlatform,
  autoPatchelfHook,
  libgcc,
  libxkbcommon,
  vulkan-loader,
  wayland,
}
rustPlatform.buildRustPackage rec {
  # ...

  nativeBuildInputs = [
    # ... 他の nativeBuildInputs ...

    autoPatchelfHook
  ];

  # autoPatchelfHook がこのフィールドを見て勝手にビルドされたバイナリをパッチしてくれる
  runtimeDependencies = [
    # 明確に Iced が利用しているライブラリ
    libxkbcommon
    vulkan-loader
    wayland

    # なんか知らないけどこれも呼んでたので必要だった
    libgcc
  ];

  # ランタイムでも必要なのでこちらにも追加する
  buildInputs = [
    # ... 他の buildInputs ...
  ] ++ runtimeDependencies;

  # ...
}

mkShell

上記をやっている前提。

pkgs.mkShell {
  packages = [
    # ... 他のパッケージ ...
  ];

  # mkShell (mkDerivation) は未知のフィールドを環境変数として設定する。
  LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath your-derivation.runtimeDependencies;
}

Flakes を使っている場合はこんな感じになるはず。

{
  # flake.nix
  # ...

  outputs = { nixpkgs, ... }:
    let
      # 人によってやり方が違うので説明用に簡潔化
      system = "...";
      pkgs = nixpkgs.legacyPackages.${system};
    in rec {
      packages.${system}.default = pkgs.callPackage ./path/to/derivation.nix { };

      devShells.${system}.default = pkgs.mkShell {
        packages = [
          # ... 他のパッケージ ...
        ];

        LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath packages.${system}.default.runtimeDependencies;
      };
    };
}