Featured image of post NixpkgsでCommonLisp開発環境をつくる

NixpkgsでCommonLisp開発環境をつくる

CommonLispの開発環境、というほど普段Lispを使う機会は少ないのだけど、学習のために触りたいときに触れるようになっているのは大事だと思います。 過去に勉強のために用意していた環境はroswellを使っていました。

roswellはとてもいいのだけど、せっかく nixpkgs を使っているのでこちらでどこまでできるものか興味がありました。


先日CommonLispに関するコミュニティサーベイというものをHackerNewsで見かけました。

Common Lisp Community Survey 2024 Results — Dan’s Musings

中でも気になったのは、Dependency ManagementにGuixやNixを利用する人々がマイナーながら存在することです。

Guixについては日本語でも記事を見かけることはあったし、GuileはLisp方言なので学習コストもある程度低いし、かなり興味はありました。 ただ自分の環境はすでに nixpkgs で動作しているし、(Nixはとっ突き難いことはわかっているものの)、Guixに乗り替えるには決定打が足りませんでした。

nixpkgs では他システムのパッケージマネジメントを統合して利用しようという活動はよく見かけることがあります。例えば npmelisp など。 そしてCommonLispでもQuickLispに代わる方法として、 nixpkgs はある程度実用的なのかもしれない。。。

というわけで、 nixpkgs でCommonLispのDependency Managementを代替してみようと思います。

どうやら2024年9月現在の最新では nix-cl の手法がマージされているようです。

Hi! I made a small nix library (nix-cl), for working with ASDF and Quicklisp, which has been working great for me recently. The code is still a mess but I want to show it to you and see if it spark...
Proposing another implementation of lisp-modules · Issue #155851 · NixOS/nixpkgs

公式のlisp-modulesマニュアルにも、この方法をベースにした解説が掲載されていました。

Nixpkgs Reference Manual

ライブラリをダウンロードして、ロードする

CommonLispのライブラリ管理は基本的にはASDFがデファクトスタンダードと考えていいと思います。以下のまとめがとてもわかりやすいです。

κeenです。最近のCommon Lispのパッケージ管理はql:quickloadしか知らないという方も多いのではないでしょうか。しかしそれだけでは機能が足りないこともあります。Common Lispには様々な管理システムがあるので整理しましょう。
require, ASDF, quicklispを正しく使う | κeenのHappy Hacκing Blog

QuickLispは一部ASDFをラップしながら、QuickLispリポジトリに登録されているライブラリを依存解決しつつダウンロードしてきてくれる機能もありますが、 nixpkgs はここを置き替えてくれます。。

つまり、 nixpkgs で依存ライブラリの管理とダウンロードを、生のASDFでロードを分業するイメージです。

nixpkgs では、単に sbcl を入れるだけだとASDFが入っていません。 ここでは、 sbcl.withPackages をすることで /nix/store 以下のASDFが環境変数にセットされ、sbclランタイムからシステムをロードすることができるようになります。

公式マニュアルにある通りですが、 shell.nix を書くとこのような感じになります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let pkgs = import <nixpkgs> {};
    sbcl' = sbcl.withPackages (ps: [
      ps.alexandria
    ]);
in pkgs.mkShell rec {
  name = "cl";
  packages = [
    sbcl'
  ];
}

nix-shell でこれを実行すると、sbclが利用でき、ここでは alexandria が使えるようになっています。

1
2
(load (sb-ext:posix-getenv "ASDF"))
(asdf:load-system 'alexandria)

ASDFでローカルのシステムをロードする

上記の方法でASDFは利用でき、 nixpkgs で解決したライブラリはロードできるのですが、そのままだとローカルに配置したシステムをロードすることができません。 例えばASDFのdefault search pathには ~/common-lisp があるのですが、ここに配置されたシステムは参照されません。

ASDF Manual | Configuring ASDF to find your systems

あまり詳しくないので推察ですが、恐らくASDFをビルドするときに /nix/store を見るようにした結果デフォルトが上書きされているような状態なのかもしれません。

とりあえず回避する方法として、以下のようにレジストリを追加します。

1
2
3
4
5
(load (sb-ext:posix-getenv "ASDF"))
(asdf:initialize-source-registry
 '(:source-registry
   (:tree "/home/my/common-lisp")
   :inherit-configuration))

native依存を解決する

lisp-modulesマニュアルの"Adding native dependencies"にあるように、 nixpkgs 内では ql.nix にCFFI依存が記述されているようです。

しかし試しに cl-portaudio を入れてみると、 libporaudio2.so が参照できないためビルドエラーをします。 QuickLisp由来ではCFFIの依存解決ができないようなので、恐らくマニュアルでメンテされているのだと思われます。

本来はコントリビュートされて nixpkgs 内で ql.nix が網羅された状態になるのが理想なのかもしれませんが、 ここでは一旦ローカルで解決してみます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
let pkgs = import <nixpkgs> {};
    cl-portaudio = pkgs.sbcl.pkgs.cl-portaudio.overrideLispAttrs (oldAttrs: {
      nativeLibs = [
        pkgs.portaudio
      ];
    });
    sbcl = pkgs.sbcl.withOverrides (self: super: {
      inherit cl-portaudio;
    });
    sbcl' = sbcl.withPackages (ps: [
      ps.cl-portaudio
    ]);
in pkgs.mkShell rec {
  name = "cl";
  packages = [
    sbcl'
  ];
}

Nixの書き方としてこれがベストなのかわかりませんが、まず cl-portaudionativeLibs アトリビュートをCFFI依存のパッケージを指定してオーバーライドします。 そのパッケージでsbclPackagesの cl-portaudio をオーバーライドしています。

まとめ

Nixを使ってCommonLispの依存管理を試してみました。

一番のネックはCFFI依存の解決が難しいところでした。 実際他にも試していたのですが、 Qt に関するライブラリのビルドには成功できませんでした。

これはroswell環境であれば比較的簡単に動作できているので、やはりNixをメイン使いするのはまだ難しい気がしました。単純に私の力不足かもしれませんが。。。

本来は nixpkgs に全てのパッケージ環境が記述されて、CommonLispでも利用できるのが理想だと思うのですが、まだまだroswellとaptには並行してお世話になる必要がありそうです。

Built with Hugo
テーマ StackJimmy によって設計されています。