GNU GuixでQuicklispから卒業したい

# GNU GuixでQuicklispから卒業したい

[処理系固有のツールを避けたい＆Guixインストール](https://qiita.com/NOEU/items/f7958daad2edfc851a78)からの続きです。

## これまでのあらすじ

先日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操作の詳細なヘルプを確認できます。

```bash
$ guix --help
$ guix package --help
```

### パッケージ操作

```bash
$ guix package -I # インストール済みパッケージリスト
$ guix package -r package-name # パッケージ削除
$ guix package -i package-name # パッケージインストール
$ guix package -s package-name # パッケージ検索
```

インストールや削除は1回の操作で1つのトランザクションになり、次にように複数のパッケージのインストールと削除を1回の操作で実行できます。

```bash
$ guix package -r pn1 pn2 -i pn3 pn4 pn5
```


### 世代操作
一般的なパッケージ管理システムにない世代操作について。世代一覧は、`-l`オプションを与えることで確認できます。

```bash
$ 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)`という指定が付与される状態になります。

```bash
$ 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`に付与された状態になります。

```bash
$ 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/](https://guix.gnu.org/packages/)で確認することができ、現在は1万以上のパッケージが登録されています。

このようなパッケージのチャネルは自分で作成することができます。cl-sdl2はGNUのパッケージチャネルに存在しないため、次のようなディレクトリ構造で、cl-sdl2を配信するチャネルを作成します。

### チャネルのディレクトリ・ファイル

```bash
$ tree 
.
└── cl-guix
    └── packages
        └── cl-package.scm
```

cl-package.scmの中身は以下のような形になります。

```scheme: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](https://github.com/lispgames/cl-sdl2)の最新のコミットを取得する形にしています。以下の部分がGitリポジトリからファイルを取得するための記述になります。

```scheme
(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`の値は、以下のコマンドで確認できるため、それをそのまま記述しておきます。

```bash
$ git clone https://github.com/lispgames/cl-sdl2.git
$ guix hash -rx ./cl-sdl2
15x5d139qj3zba2qx3r43s1sh2bqgm4zcwd07kx2l23q2j89v509
```

### ASDFシステムのためのビルドシステム

Guixには様々な環境に向けた[ビルドシステム](https://guix.gnu.org/manual/en/html_node/Build-Systems.html)が用意されており、ここでは、ASDFシステム用のビルドシステムを用います。

```scheme
(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](https://gitlab.com/hu.moonstone/cl-guix.git)にpushしました。


### チャネルの登録

手元のGuixで、新しく公開したチャネルを登録する必要があります。`$HOME/.config/guix/channels.scm`というファイルを新しく作成し、次の内容を記述します。

```scheme:channels.scm
(cons (channel
       (name 'cl-guix)
       (url "https://gitlab.com/hu.moonstone/cl-guix.git")
       (branch "master"))
      %default-channels)
```

その後、以下のコマンドを実行します。

```bash
$ guix pull
```

登録に問題がなければ、`guix pull -l`によって以下のような形でcl-guixが新しく登録されていることを確認できます。

```bash
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パッケージを導入することができます。

```bash
$ 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の設定を変更しています。

```common-lisp:.eclrc
(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度の更新で、最新の修正を取り込みたい場合や、以前のバージョンを使いたい場合など、簡単にパッケージを作成できるので、自分のプロジェクトが依存するパッケージのためのプライベートチャネルの作成を進めるのも良いかもしれません。


## 参考

- [GNU Guix Reference Manual](https://guix.gnu.org/manual/en/html_node/)