GNU GuixでQuicklispから卒業したい
処理系固有のツールを避けたい&Guixインストールからの続きです。
これまでのあらすじ
先日GNU Guixと言うNix派生の次世代パッケージ管理システム(と言ってもNixはそれなりに古いですが)を導入しました。GNU Guileのコードでパッケージの定義を行うのでNixと比較し個人的にはわかりやすい感じがします。ということでQuicklispが担う役割をプライベートチャネルを作って、自分が使うパッケージのみをひとまず管理する感じで代替しつつ、ECL処理系を複数バージョン同時に導入し、切り替えも簡単にできるように、突き進んでみます。
Guixの特徴を簡単にまとめてみます。
- 個別のユーザー単位で導入パッケージの管理ができる
- パッケージ操作の履歴が保持され、ロールバックが可能
- 同一パッケージの別バージョンを両方入れておくことができる。LibraryAバージョン1とLibraryAバージョン1.1を別名で管理するのではなく、どちらもLibraryAという名前で同時に存在する
- パッケージ定義ファイルが書きやすい(少なくともDebパッケージより簡単だと思う)
- GNU/Linux、GNU/Hurdで動作する
- OSに付随するパッケージ管理システムで導入したパッケージとは競合しない
このツールの有用性は、ディストリビューションに依存しない、言語処理系に依存しないパッケージ管理ツールであり、それら全てのツールを代替できる可能性がある点だと思っています。
他の言語処理系であっても、DockerとGuixがあれば中間に存在するOSや言語処理系に依存するパッケージ管理ツールは不要になるのでは?不要にしたい!という願いを実現するべく、使いものになるのかちょっとずつ試していきます。
環境
- Debian GNU/Linux 10.2
- Guix 1.0.1
Guixのコマンドについて
以下のように、--help
でコマンドの説明を確認できます。guix package --help
とすると、package操作の詳細なヘルプを確認できます。
$ guix --help
$ guix package --help
パッケージ操作
$ guix package -I # インストール済みパッケージリスト
$ guix package -r package-name # パッケージ削除
$ guix package -i package-name # パッケージインストール
$ guix package -s package-name # パッケージ検索
インストールや削除は1回の操作で1つのトランザクションになり、次にように複数のパッケージのインストールと削除を1回の操作で実行できます。
$ guix package -r pn1 pn2 -i pn3 pn4 pn5
世代操作
一般的なパッケージ管理システムにない世代操作について。世代一覧は、-l
オプションを与えることで確認できます。
$ guix package -l
Generation 25 2月 14 2020 12:53:36
+ cl-alexandria 1.0.0-1.3b849bc out /gnu/store/f85dbyyhfyanqly48nrmsvs048qy7hxa-cl-alexandria-1.0.0-1.3b849bc
Generation 26 2月 14 2020 13:26:05
+ cl-sdl2 1.0.0-1.1588954 out /gnu/store/r4pb0zby2aq2fn043byykvn559bbdvb5-cl-sdl2-1.0.0-1.1588954
Generation 27 2月 14 2020 14:06:30
- cl-sdl2 1.0.0-1.1588954 out /gnu/store/r4pb0zby2aq2fn043byykvn559bbdvb5-cl-sdl2-1.0.0-1.1588954
Generation 28 2月 15 2020 03:16:23 (current)
+ cl-sdl2 1.0.0-1.1588954 out /gnu/store/r4pb0zby2aq2fn043byykvn559bbdvb5-cl-sdl2-1.0.0-1.1588954
+
は導入、-
は削除を意味します。実験のためにcl-sdl2を導入したり削除したりしてたんですが、その世代の履歴が上の内容から確認できます。
nano
を入れて、もう一度-l
を付けて実行します。Generation 29
が作られ、(current)
という指定が付与される状態になります。
$ guix package -i nano
$ guix package -l
Generation 28 2月 15 2020 03:16:23
+ cl-sdl2 1.0.0-1.1588954 out /gnu/store/r4pb0zby2aq2fn043byykvn559bbdvb5-cl-sdl2-1.0.0-1.1588954
Generation 29 2月 15 2020 04:08:01 (current)
+ nano 4.8 out /gnu/store/l5ds0mx103lrir23l302ax148wy2pd7q-nano-4.8
世代をひとつ前に戻します。-S
オプションに-1
を指定します。すると、以下のように(current)
がGeneration 28
に付与された状態になります。
$ guix package -S -1
switched from generation 29 to 28
$ guix package -l
Generation 28 2月 15 2020 03:16:23 (current)
+ cl-sdl2 1.0.0-1.1588954 out /gnu/store/r4pb0zby2aq2fn043byykvn559bbdvb5-cl-sdl2-1.0.0-1.1588954
Generation 29 2月 15 2020 04:08:01
+ nano 4.8 out /gnu/store/l5ds0mx103lrir23l302ax148wy2pd7q-nano-4.8
これにより、インストール前の状態に戻すことができます。
プライベートチャネルを作る
初期状態で、Guixにはguixという名前のデフォルトチャネルが存在します。このチャネルにあるパッケージは、https://guix.gnu.org/packages/で確認することができ、現在は1万以上のパッケージが登録されています。
このようなパッケージのチャネルは自分で作成することができます。cl-sdl2はGNUのパッケージチャネルに存在しないため、次のようなディレクトリ構造で、cl-sdl2を配信するチャネルを作成します。
チャネルのディレクトリ・ファイル
$ tree
.
└── cl-guix
└── packages
└── cl-package.scm
cl-package.scmの中身は以下のような形になります。
(define-module (cl-guix packages cl-package)
#:use-module (guix packages)
#:use-module (guix utils)
#:use-module (guix download)
#:use-module (guix git-download)
#:use-module (guix build-system gnu)
#:use-module (guix build-system asdf)
#:use-module ((guix licenses) #:prefix license:)
#:use-module (ice-9 match))
(define-public cl-sdl2
(let ((revision "1")
(commit "1588954ee4abc37b01a7e2e17a76e84fd4da8c77"))
(package
(name "cl-sdl2")
(version (git-version "1.0.0" revision commit))
(source
(origin
(method git-fetch)
(uri (git-reference
(url "https://github.com/lispgames/cl-sdl2.git")
(commit commit)))
(file-name (git-file-name name version))
(sha256
(base32 "15x5d139qj3zba2qx3r43s1sh2bqgm4zcwd07kx2l23q2j89v509"))))
(build-system asdf-build-system/source)
(synopsis "Common Lisp SDL2 Package")
(description "")
(home-page "https://github.com/lispgames/cl-sdl2")
(license license:expat))))
define-module
に渡している(cl-guix packages cl-package)
は、このscmファイルのあるディレクトリまでの構造を記述します。cl-guix以下のpackages以下のcl-package.scmファイルのため、このように記述しています。
#:use-module
は、このパッケージ内で使用するモジュールの指定を行います。
define-public
で、公開するパッケージの定義を開始します。実際のパッケージ定義は、package
から始まる箇所です。
Gitリポジトリからファイルを取得するための記述
今回、cl-sdl2はhttps://github.com/lispgames/cl-sdl2の最新のコミットを取得する形にしています。以下の部分がGitリポジトリからファイルを取得するための記述になります。
(origin
(method git-fetch)
(uri (git-reference
(url "https://github.com/lispgames/cl-sdl2.git")
(commit commit)))
(file-name (git-file-name name version))
(sha256
(base32 "15x5d139qj3zba2qx3r43s1sh2bqgm4zcwd07kx2l23q2j89v509"))))
commitという変数には、GitのコミットIDを入れておき、どのコミットを取得するのかを指定します。sha256
、base32
の値は、以下のコマンドで確認できるため、それをそのまま記述しておきます。
$ git clone https://github.com/lispgames/cl-sdl2.git
$ guix hash -rx ./cl-sdl2
15x5d139qj3zba2qx3r43s1sh2bqgm4zcwd07kx2l23q2j89v509
ASDFシステムのためのビルドシステム
Guixには様々な環境に向けたビルドシステムが用意されており、ここでは、ASDFシステム用のビルドシステムを用います。
(build-system asdf-build-system/source)
は、特別なことをせずASDFのシステム定義をソースのままインストールする形になります。asdf-build-system/ecl
、asdf-build-system/sbcl
と言ったオプションもあるのですが、実行時にコンパイルすれば良い感じがするのでsourceを選択しています。
チャネルの公開
作成したディレクトリとファイルをGitバージョン管理下に置き、そのまま公開します。今回は、https://gitlab.com/hu.moonstone/cl-guix.gitにpushしました。
チャネルの登録
手元のGuixで、新しく公開したチャネルを登録する必要があります。$HOME/.config/guix/channels.scm
というファイルを新しく作成し、次の内容を記述します。
(cons (channel
(name 'cl-guix)
(url "https://gitlab.com/hu.moonstone/cl-guix.git")
(branch "master"))
%default-channels)
その後、以下のコマンドを実行します。
$ guix pull
登録に問題がなければ、guix pull -l
によって以下のような形でcl-guixが新しく登録されていることを確認できます。
Generation 2 2月 15 2020 03:14:44 (current)
cl-guix 83c669a
repository URL: https://gitlab.com/hu.moonstone/cl-guix.git
branch: master
commit: 83c669af1bba037ea09ad6886eb540f3cb10e9bf
guix 64fc4f3
repository URL: https://git.savannah.gnu.org/git/guix.git
branch: master
commit: 64fc4f3705423c83c680a95d8dea81a39fce9a70
あとは、通常通りの操作でcl-guixに登録されているcl-sdl2パッケージを導入することができます。
$ guix package -i cl-sdl2
インストールされたファイルの場所
$HOME/.guix-profile
以下に展開されます。asdf-build-system/sourceの場合、.guix-profile/share/common-lisp/source
以下にファイルが設置されるので、次のようにしてASDFにパスを教えると読み込みにいきます。以下の設定ファイルはECLのもので、Guixインストール時に設定したGUIX_PROFILE環境変数の値を使ってASDFのsource-registry-parameterの設定を変更しています。
(defparameter *deepspace-home* (si:getenv "DEEPSPACE_HOME"))
(defparameter *guix-profile* (si:getenv "GUIX_PROFILE"))
(load (concatenate 'string *deepspace-home* "/lib/asdf.lisp"))
(require 'asdf)
(setf asdf:*asdf-verbose* nil)
(setf *load-verbose* nil)
(asdf:initialize-source-registry
`(:source-registry
(:tree ,(concatenate 'string *guix-profile* "/share/common-lisp/source"))
(:tree ,(concatenate 'string *deepspace-home* "/share/deepspace/quicklisp/dists/quicklisp/software"))
(:tree ,(concatenate 'string *deepspace-home* "/lib/"))
(:tree ,(namestring *default-pathname-defaults*))
(:tree ,(concatenate 'string (namestring *default-pathname-defaults*) "lib"))
:inherit-configuration))
(asdf:initialize-output-translations
'(:output-translations
:enable-user-cache
:ignore-inherited-configuration))
多少手間はかかりますが、Quicklispが月に1度の更新で、最新の修正を取り込みたい場合や、以前のバージョンを使いたい場合など、簡単にパッケージを作成できるので、自分のプロジェクトが依存するパッケージのためのプライベートチャネルの作成を進めるのも良いかもしれません。