Crystalをインストールしたかった (Guix)

GNU GuixにCrystalをインストールしようとして、まだ納得の行く形でできていない、という内容です。
あまり整理された内容ではないため、こういうことをする人もいる、程度に読み流してください。

設定にしくじっていなければ[Crystal Advent Calendar 2023][advent]に登録できているはずです。
昨日の記事はkojix2さんの[CrystalでDeepLを使うコマンドラインツールを書いた][yesterday]でした。バイナリが配布されているのはありがたいですね。また、`doc`サブコマンドを指定すると`--input`オプションで指定されたファイルの内容が翻訳対象になるようです。
一昨日の記事は[Crystalでクリップボードを扱う安直なShardを書いた話][prev]でした。クリップボードはOSや環境により仕様が様々なので、統一的に扱えるレイヤーには需要があるでしょう。ここから更に、例えばHTMLで持っている表現や画像のデータを扱えるようにする方法を考えると面白そうです。

[advent]: https://qiita.com/advent-calendar/2023/crystal
[prev]: https://qiita.com/kojix2/items/4c03ab7f25601548124a
[yesterday]: https://qiita.com/kojix2/items/fbc27b2d354cd4ffba45

## Guixとは

[GNU Guix][guix]はLinux系のOSの1つです。似た雰囲気のディストリビューションとしては[NixOS][nix]があります。GNUとあるように純粋な自由ソフトウェアで構成されている点も特徴です。なおそうした背景もあり、本記事でも原則として[The GNU General Public Licenseのバージョン3以降][gpl]及び[GNU Free Documentation Licenseのバージョン1.3以降][fdl]の元に使用が許諾されます。

Guixは[Filesystem Hierarchy Standard][fhs] (FHS) に従っていません[^1]。つまり、通常はルートパス以下にあるディレクトリやファイルが、Guixには無いことがよくあります。Rubyで読み込む共有オブジェクトファイルは`lib`ディレクトリにあるのがお決まりで、C言語で利用するヘッダファイルは`include`ディレクトリにあるのが定番ですが、Guixでは簡単に見付からない訳です。

それでどうやって生きているのかというと、こうです。まず、ファイルが悉皆`/gnu/store`ディレクトリ以下に配置されます。BashやGCC、Apache HTTPdであればその設定ファイルもあり得ます。そうして保管されている資材のうち、必要なものへのリンクの集合を環境とするのです。例えば私の手元では環境変数`C_INCLUDE_PATH`が以下のようになっています。

```shell-mode
$ echo  $C_INCLUDE_PATH
/home/gemmaro/.guix-home/profile/include:/home/gemmaro/.guix-profile/include
```

前者のディレクトリは以下のようになっています。

```shell-mode
$ ls -l /home/gemmaro/.guix-home/profile/include
total 860
lrwxrwxrwx 1 root root  78 Jan  1  1970 aio.h -> /gnu/store/dpfxpfyghkc19wz8jwaw31llhnvn8ngx-gcc-toolchain-13.2.0/include/aio.h
lrwxrwxrwx 1 root root  82 Jan  1  1970 aliases.h -> /gnu/store/dpfxpfyghkc19wz8jwaw31llhnvn8ngx-gcc-toolchain-13.2.0/include/aliases.h
lrwxrwxrwx 1 root root  81 Jan  1  1970 alloca.h -> /gnu/store/dpfxpfyghkc19wz8jwaw31llhnvn8ngx-gcc-toolchain-13.2.0/include/alloca.h
...
```

そういう訳で、世の中のあらゆるソフトウェアは簡単にはコンパイルできませんし、いわゆるパッケージ管理ツールは期待通りに動きません。Node.jsのNPM然り、JavaのMaven然りです。こうしたパッケージについてはGuix用のパッケージを作成して使うことになります。Rubyだとgemそれぞれを必要なバージョン毎に作るのです[^2]。パッケージを定義する際は、メタデータの記録はもちろんのこと、例えばシェバングは`/gnu/store`以下のファイルへの絶対パスに展開されますし、止むを得ず未収録のソフトウェアへの参照部分はパッチで取り除かれます。

追い討ちを掛けるようですが、いわゆるバイナリからのインストールはしにくくなっています。よしもし実行を試みたとて、大抵は必要なライブラリが見付からない旨のエラーで失敗するのがオチです。Guixの思想としては多分、ソフトウェアはすべからくソースからコンパイルされるべし、と。それもまた一興のように思われます。

以上の様相を踏まえて先に進みます。

[hotoke]: https://dl.ndl.go.jp/pid/947652/1/186
[fhs]: https://refspecs.linuxfoundation.org/fhs.shtml
[container]: https://guix.gnu.org/en/blog/2023/the-filesystem-hierarchy-standard-comes-to-guix-containers/
[gpl]: https://www.gnu.org/licenses/gpl-3.0.html
[fdl]: https://www.gnu.org/licenses/fdl-1.3.html

## きっかけ

GuixでふとCrystalを触ってみたいと思ったときがありました。公式の情報を見てみましょう。

Crystalの公式サイトは[Crystal - A language for humans and computers][crystal]で、ここから[Install][install]ページに遷移すると様々なOS向けのインストールの手順が載っています。残念ながらGuixは載っていません。

それでGuixでパッケージを検索[^3]したところ、まだ無いようでした。無いものは作るしかないので手を動かし始めました。

先のインストールのページには「from sources」と「from `.tar.gz`」とがありました。前者のページの説明には以下とあります。

> 1. [Install the latest Crystal release.][install] To compile Crystal, you need Crystal :).

ところが今は、この初めの一歩が踏み出せません。前述した通り、宗教上の都合があるからです。

> 八つになりし年、父に問うて曰く、「佛は、いかなるものにか候ふらむ」といふ。
> 父が曰く、「佛には、人のなりたるなり」と。
> 又問ふ、「人は、何として佛にはなり候ふやらむ」と。
> 父又、「佛の教によりてなるなり」と答ふ。
> 又問ふ、「教へ候ひける佛をば、何が教へ候ひける」と。
> 又答ふ、「それも又、さきの佛の教によりて、なり給ふなり」と。
> 又問ふ、「その教へ始め候ひける、第一の佛は、いかなる佛にか候ひける」といふ時、父「空よりやふりけむ。土よりやわきけむ」といひて笑ふ。
> 「問ひつめられて、え答へずなり侍りつ」と。
> 諸人に語りて興じき。
>
>[『つれつれ草：新註対訳』][hotoke]池辺義象著、第二百四十三段。

[crystal]: https://crystal-lang.org/
[install]: https://crystal-lang.org/install/

## 模索

Crystalのリポジトリは[crystal-lang/crystal][repo]です。ここを漁ってみたところ、どうも`master`や更新が新しそうなブランチはCrystal自体を使っているようでしたが、唯一`ruby`ブランチはRubyがあれば良さそう、ということに気付きました。
以下はコミット`04964442a`に向けた`manifest.scm`です。このファイルは何かというと、そのディレクトリで使用するパッケージを書いたもので、`guix shell`とすると環境構築がなされます。Rubyで言うと、gemのパスが通ったりする訳です。

```scheme
(specifications->manifest (list "ruby@3.1" "ruby-llvm" "ruby-levenshtein-ffi"
                                "ruby-rspec" "ruby-graphviz" "ruby-diff-lcs"))
```

当然これだけでは不充分であり、その他のファイルも勘で埋めたり削ったりといった具合で、`manifest.scm`に挙げたパッケージもこれが全部ではありません。なお、`ruby-llvm`、`ruby-levenshtein-ffi`、`ruby-graphviz`はGuix公式チャンネルに登録されていなかったため、自作する必要がありました。これらのgemが必要とする依存関係にも無いものがあり、そのまた依存関係にも無いものがあり、といった具合で、再帰的に作っていったように記憶しています。

`ruby-llvm`の`manifest.scm`は以下の通り。

```scheme
(specifications->manifest (list "ruby@3.1"
                                "ruby-debug"
                                "ruby-ffi-gen"
                                "ruby-minitest"
                                "ruby-minitest-reporters"
                                "ruby-rubocop-performance"
                                "ruby-simplecov"
                                "llvm"
                                "glibc"))
```

`ruby-levenshtein-ffi`の`manifest.scm`は以下の通り。

```scheme
(specifications->manifest (list "ruby@3.1" "ruby-ffi" "ruby-rspec"))
```

それぞれパッチなどもありましたが、当座の供養はここまでに、折を見てGuix公式のチャンネル（Rubyで言うところのRubyGems.orgのようなもの）に投稿できる程度に仕立てたいと思っています。

ともあれこうして闇雲に作業をしていると、これでは埒が開かないとようやく気が付き始めます。それで既にこうした試みがなされているのではないかと探してみると、ありました。[Guix issue tracker / Add ruby-for-crystal.][ruby-for-crystal]がそれです。そこではCrystalをブートストラップするための古いRubyのパッケージが追加されているのですが、驚いたのは[crystal-lang/bootstrap-script][script]というリポジトリの存在でした。先達のあらまほしきことなり。

やや脱力気味でそちらのスクリプトをいじり始めましたが、あちらこちらをコメントアウトしてみたり、`pcl`なるパッケージが要るのかどうか、といったところで中座しているところです。

## 背信

先に触れたように、Guixは純粋に自由ソフトウェアで成り立っているOSです。故に所収されているパッケージもまた自由ソフトウェアとして適う使用許諾のものが選定されています。Crystalも原理的にはソースからのコンパイルが可能ではあるので、将来パッケージが追加される可能性はあるでしょう。

ただ、ちょっとCrystalを動かしてみたいという当初の思い付きからすると、時間を掛け過ぎてしまった嫌いがあるのは認めざるを得ません。実はGuixにはいくつかの裏道があり、不自由なソフトウェアを使うことはできなくはないのです。

その1つとしてバイナリをそのまま保管してパスを通してしまう作戦があります。後ろめたさを感じなくはないパッケージがあるチャンネルとして[nonguix][nonguix]があり、そこからは`binary-build-system`という構築方法が提供されています。日本語の記事としては[ROCKTAKEY’s site／Guixのパッケージをつくってみよう-その3: どうしてもビルドできないときはバイナリを直接使ったパッケージを作る][bin]に丁寧に書かれています。

この構築システムを使った例は以下のようになります。先に掲げた公式のインストールのページに「from `.tar.gz`」のリンクがありましたが、これはGitHubのリリースページからバイナリをダウンロードできるということでした。

```scheme
(define-module (gemmaro packages crystal)
  #:use-module ((guix licenses)
                #:prefix license:)
  #:use-module (guix download)
  #:use-module (guix packages)
  #:use-module (nonguix build-system binary))

(define-public crystal
  (let ((revision "1"))
    (package
      (name "crystal")
      (version "1.10.1")
      (source
       (origin
        (method url-fetch)
        (uri (string-append
              "https://github.com/crystal-lang/crystal/releases/download/"
              version
              "/crystal-"
              version
              "-"
              revision
              "-linux-x86_64.tar.gz"))
        (sha256
         (base32 "12rl28ndinby96jdnc3iz0m3kyhpll81yaf00yxd2lrnbmsy6hhp"))))
      (build-system binary-build-system)
      (home-page "https://crystal-lang.org/")
      (synopsis "Crystal programming language")
      (description
       "The Crystall language is a programming language for humans and
computers.  It's syntax is heavily inspired by Ruby's.  Crystal is
statically type checked and has built-in type inference.")
      (license license:asl2.0))))
```

インストールして動かしてみましょう。一応うまくいっていそうですね。

```shell
$ crystal --version
Crystal 1.10.1 [c6f3552f5] (2023-10-13)

LLVM: 15.0.7
Default target: x86_64-unknown-linux-gnu
```

[repo]: https://github.com/crystal-lang/crystal
[ruby-for-crystal]: https://issues.guix.gnu.org/49158
[script]: https://github.com/crystal-lang/bootstrap-script
[bin]: https://blog.rocktakey.com/information-science/2022-6353e393-4e0b-6ad4-754b-0d84ebd31c97/
[nonguix]: https://gitlab.com/nonguix/nonguix

## おわりに

Crystalコンパイラはセルフホストされる方式を採る言語処理系であり、使えるようにするまでのハードルは低くないことが分かりました。実はRustにも同じことが言えて、[Guix blog / Bootstrapping Rust][rust]という記事があります。セルフホストはメタフィクション的な魅力こそあれ、[Yorick Peterse / A decade of developing a programming language][inko]にあるように現実的な課題はあるようです。

話変わって`shards`という便利そうなプログラムも同梱されていました。ですがきっと、ライブラリ1つ1つを手ずからGuixのパッケージとして仕立てるのが筋というものでしょう。Guixの姿勢としては恐らく、出所不明のパッケージが一切合切降ってくるのは看過できない、と。そう言われるとそうかもしれません。

Crystalをインストールする冒険はまだまだ続きます。

[^1]: [Guix Blog / The Filesystem Hierarchy Standard Comes to Guix Containers][container]にあるように、このFHSを模擬する仕組みは一応あります。
[^2]: Rustだとフィーチャーの組み合わせ毎になるので大変です。ただ、引数を取るパッケージが出てくるかもしれないという話がどこかにあった気がします。
[^3]: Emacsの構成が済んでいれば`M-x guix p n`として検索できます。web上でも[GNU Guix / Packages][packages]というページから簡単に検索できます。

[guix]: https://guix.gnu.org/
[nix]: https://nixos.org/
[packages]: https://packages.guix.gnu.org/
[rust]: https://guix.gnu.org/blog/2018/bootstrapping-rust/
[inko]: https://yorickpeterse.com/articles/a-decade-of-developing-a-programming-language/