更新履歴

序文

前回の記事では、ビルドを伴うパッケージの作成を行いました。しかし、どうしてもビルドがうまくいかないこともあります。特に前回触れたインポーターがまだない他の既存パッケージマネージャで管理されたソフトウェアや、そもそもインポーターが介在する余地のないような独自のビルド手順を持つソフトウェアなどで、まだGNU Guixの公式チャンネルに実装されていない依存が大量にある場合には、多大な労力が必要になります。さらに言うと、我々が利用しているソフトウェアはソースが公開されているとは限りません。そのようなプロプライエタリなソフトウェアをソースコードからビルドすることは当然不可能です。このような場合、GNU Guixでソフトウェアの管理を行うことは不可能なのでしょうか。

上記の話への回答は二つあります。 GNU Guix(というよりGNUプロジェクトやFSFかも)の哲学に基づいて話すならば、「プロプライエタリなソフトウェアは使うべきではないし、ビルドしないという選択肢はない」です。彼等は自由ソフトウェア原理主義です。ソースコードが公開されておらず、中でなにをしているかわからないような自由でないソフトウェアは利用するべきではないと考えています。同様な理由で、自分でビルドしていないバイナリは本当に公開されているソースコードからビルドされたものかわからないから信用するべきではない、ということになります。

私も自由ソフトウェアはよいものだと思っていますが、現実的にプロプライエタリなソフトウェアをただちに排除することは不可能ですし、たとえばインターネットを介して他人と競うようなゲームではコードの公開は不正に直結するでしょう。

実際自由ソフトウェアしか利用できないのは不便なので、Nonguixというコミュニティ及びチャンネルが存在します。このチャンネルでは不自由なソフトウェアやドライバが配信されています。これは非公式のものであり、GNUの哲学に反するものです。そのため、Nonguixはこのチャンネルは不自由なソフトウェアを推奨するものではないこと、 IRCやメーリングリストなどの公式のGNU Guixコミュニケーションでは宣伝しないこと、とREADMEに書いています。言及自体を避けるものではないですが、GNUの哲学を尊重しましょうねという形です。このあたりのポリシーはコミュニティによって異なりますが、 GNU直属のコミュニティではかなり厳格なので注意して発言する必要があります。

このような経緯で、不自由なソフトウェアやドライバGNU Guixを扱うノウハウはすべてNonguixに集まっています。しかし、Nonguixは使い始めるための最低限のドキュメント(READMEにあるものが全て)しかありません。パッケージを利用するだけならチャンネルを登録するだけなので簡単ですが、不自由なソフトウェアのパッケージ(つまりバイナリを直接インストールするパッケージ)を作る知識を得るには Nonguixの他のパッケージ定義を真似るしかありません。

そこで、ここでは、バイナリを直接インストールするパッケージの作り方を解説していきます。前回や前々回の記事とは違ってきちんとしたドキュメントに基づいているわけではなく、私も見様見真似でやっているところがあるので、説明が間違っている可能性が前の記事たちよりも少し高くなっていることにご留意ください。

Nonguixチャンネルを登録する(Nonguixチャンネルの提供するパッケージを利用したい場合のみ)

先に、Nonguixのパッケージを利用する方法を述べておきます。この章の操作はNonguixのパッケージを利用するのには必要ですが、 Nonguixの提供するビルドシステムを利用したパッケージを自分で作りたい場合には不要です (そのような場合は後述するようにチャンネルに依存を書く必要があるが、ユーザー側は明にチャンネルを登録する必要はない)。

NonguixのREADMEにあるのと似たように、以下を ~/.config/guix/channels.scm に書きます。

(cons* (channel
        (name 'nonguix)
        (url "https://gitlab.com/nonguix/nonguix")
        ;; Enable signature verification:
        (introduction
         (make-channel-introduction
          "897c1a470da759236cc11798f4e0a5f7d4d59fbc"
          (openpgp-fingerprint
           "2A39 3FFF 68F4 EF7A 3D29  12AF 6F51 20A0 22FB B2D5"))))
       ;; 前回つくったチャンネルはここに
       (channel
        (name 'your-channel-name)
        (url "<your-channel-URL>"))
       ;; その他のチャンネルを登録したい場合も続けて書ける
       %default-channels)

今まで書いてきたものと違い、 introduction フィールドがあります。この introduction フィールドには署名が書かれています。 前の記事で言及したハッシュ値の検証と意味合いとしては近く、偽物のチャンネルを掴まされても偽物だと気付けるようになっています。詳しくは公式のマニュアル(英語)をご覧ください。

バイナリからインストールするパッケージを作成する

この章では、バイナリから直接インストールするパッケージを作成していきます。例として、texlabという言語サーバーについてのパッケージを作成します。

自分のチャンネルに依存チャンネルを追加する

チャンネルの宣言は、チャンネル直下の .guix-channel に以下のように書いていました。

(channel
 (version 0)
 (url "<your-channel-URL>"))

今回はNonguixの提供するビルドシステムを利用したいので、チャンネルにNonguixへの依存を追加します。

(channel
 (version 0)
 (url "<your-channel-URL>")
 (dependencies
  (channel
   (name nonguix)
   (url "https://gitlab.com/nonguix/nonguix")
   ;; Enable signature verification:
   (introduction
    (channel-introduction
     (version 0)
     (commit "897c1a470da759236cc11798f4e0a5f7d4d59fbc")
     (signer
      "2A39 3FFF 68F4 EF7A 3D29  12AF 6F51 20A0 22FB B2D5"))))))

dependencies フィールドが追加されています。ここに依存するチャンネルを書いていきます。基本的には前節のチャンネル追加と同じものですが、1点だけ注意事項として、 name フィールドにクオート ' がありません。前節のチャンネル追加では (name 'nonguix) としましたが、依存の追加では (name nonguix) となっています。このようになっている理由は不明ですが、ハマりすいので注意してください。

2023-06-09: チャンネルの依存のintroduction節の書き方の変更

依存先の introduction の書き方がいつのまにか変更されていたため、コードを書き換えました。変更点は以下の通りです。

  • make-channel-introduction の代わりに channel-introduction を利用する。
  • 通常のコンストラクタ(呼び方が合っているかは怪しい)を呼び出すようになったため、全ての値にフィールド名を付けることになった。元々フィールド名が付いていなかったコミットハッシュは commit 、元々 openpgp-fingerprint をフィールド名のようにしていたフィンガープリントは signer をフィールドとする。
  • 依存するチャンネルのバージョン情報として version フィールドを与えることになった(例えばNonguixでは (version 0))。

変更前のコードを以下に示しておきます。

(channel
 (version 0)
 (url "<your-channel-URL>")
 (dependencies
  (channel
   (name nonguix)
   (url "https://gitlab.com/nonguix/nonguix")
   ;; Enable signature verification:
   (introduction
    (make-channel-introduction
     "897c1a470da759236cc11798f4e0a5f7d4d59fbc"
     (openpgp-fingerprint
      "2A39 3FFF 68F4 EF7A 3D29  12AF 6F51 20A0 22FB B2D5"))))))

パッケージを定義する

前回同様、モジュールやパッケージ定義の雛形を最初に書きます。あらかじめ埋めておいたものを以下に示しますが、以下のフィールドは前回前々回の記事ですべて解説しているので、余力があれば自分で書いてみてください。ファイルは your-channel-name/packages/texlab.scm としましょう。

(define-module (your-channel-name packages texlab)
  #:use-module (guix packages)
  #:use-module ((guix licenses) #:prefix license:)
  #:use-module (guix download))

(define-public my-texlab
  (package
   (name "my-texlab")
   (version "4.3.0")
   (source
    (origin
     (method url-fetch)
     (uri (string-append
           "https://github.com/latex-lsp/texlab/releases/download/v"
           version
           "/texlab-x86_64-linux.tar.gz"))
     (sha256
      (base32
       "0xrn9392yw7vy6f7yhbb37xm60igy5fv0mip4qgyzq5kfa3ni182"))))
   (synopsis "An implementation of the Language Server Protocol for LaTeX")
   (description
    "Language Server providing rich cross-editing support for the LaTeX typesetting system.
The server may be used with any editor that implements the Language Server Protocol.")
   (home-page "https://github.com/latex-lsp/texlab")
   (license license:gpl3)))

名前は将来衝突した場合に混乱の元にならないよう my-texlab にしました。この記事がチュートリアル的な構成をとっているためにこのような変な命名を行っていますが、自分のチャンネルで実際にパッケージを定義をする場合は一般的な名前を付けても構いません。

さて、今回ダウンロードしてくるファイルを展開したものはバイナリです。実際に展開して見てみましょう。

wget https://github.com/latex-lsp/texlab/releases/download/v4.3.0/texlab-x86_64-linux.tar.gz
tar xvf texlab-x86_64-linux.tar.gz

すると、現在いるディレクトリに texlab というファイルが生成されています。つまりディレクトリではなく、ファイルが直に圧縮されていることがわかります。このバイナリをそのままインストールするには、 binary-build-system を用います。

(define-public my-texlab
  (package
   ...
   (build-system binary-build-system)
   ...))

このビルドシステムは (nonguix build-system binary) に定義されているので、 #:use-module しておきましょう。

バイナリを直接コピーしてインストールするためには、当然「どのファイルをどこに配置するか」という情報を与える必要があります。そのような情報を binary-build-system に与えるには、 #:install-plan 引数を用います。この引数は '(("source/filename" "/output/filename") ...) のような値を取ります。 "source/filename" でダウンロードしたファイルを指定し、出力ディレクトリの "/output/filename" にコピーします。ここで指定するファイルは、ディレクトリでも構いません。それぞれの末尾にスラッシュが付いているかによって、コピーをよしなにしてくれます。末尾にスラッシュを付けると中身をそれぞれ個別に指定しているような意味になります。どのような組み合わせでどのようなコピーの仕方を得られるのかについての詳細は公式マニュアル(英語)copy-build-system を参照ください(binary-build-systemcopy-build-system を継承しています)。

たとえば今回の場合、単ファイル texlab/bin/ の中へとコピーすればよいので、以下のようになります。末尾のスラッシュを忘れた場合(("texlab" "/bin"))、 texlab というファイルを bin という名前にした上でルートディレクトリ / に置く、という指定になってしまいます。

(define-public my-texlab
  (package
   ...
   (build-system binary-build-system)
   (arguments '(#:install-plan
                '(("texlab" "/bin/"))))
   ...))

さて、このパッケージをビルドしてみましょう。

guix build -L /path/to/your/channel/ my-texlab

すると、ログの末尾で以下のようなエラーが出ると思います。

starting phase `validate-runpath'
validating RUNPATH of 1 binaries in "/gnu/store/wsacmf19wa4xdvniwza1vlrbhl3fk6jq-my-texlab-4.3.0/bin"...
/gnu/store/wsacmf19wa4xdvniwza1vlrbhl3fk6jq-my-texlab-4.3.0/bin/texlab: error: depends on 'libgcc_s.so.1', which cannot be found in RUNPATH ()
/gnu/store/wsacmf19wa4xdvniwza1vlrbhl3fk6jq-my-texlab-4.3.0/bin/texlab: error: depends on 'ld-linux-x86-64.so.2', which cannot be found in RUNPATH ()
error: in phase 'validate-runpath': uncaught exception:
misc-error #f "RUNPATH validation failed" () #f
phase `validate-runpath' failed after 0.0 seconds
Backtrace:
           8 (primitive-load "/gnu/store/z4gvf8acdds19bphq0sq4bd768l…")
In guix/build/gnu-build-system.scm:
    906:2  7 (gnu-build #:source _ #:outputs _ #:inputs _ #:phases . #)
In ice-9/boot-9.scm:
  1752:10  6 (with-exception-handler _ _ #:unwind? _ # _)
In srfi/srfi-1.scm:
    634:9  5 (for-each #<procedure 7ffff6043080 at guix/build/gnu-b…> …)
In ice-9/boot-9.scm:
  1752:10  4 (with-exception-handler _ _ #:unwind? _ # _)
In guix/build/gnu-build-system.scm:
   927:23  3 (_)
   568:10  2 (validate-runpath #:validate-runpath? _ # _ #:outputs _)
In ice-9/boot-9.scm:
  1685:16  1 (raise-exception _ #:continuable? _)
  1685:16  0 (raise-exception _ #:continuable? _)

ice-9/boot-9.scm:1685:16: In procedure raise-exception:
RUNPATH validation failed
builder for `/gnu/store/db0x0apr340mjxc6z85dx09ra62k92ld-my-texlab-4.3.0.drv' failed with exit code 1
build of /gnu/store/db0x0apr340mjxc6z85dx09ra62k92ld-my-texlab-4.3.0.drv failed
View build log at '/var/log/guix/drvs/db/0x0apr340mjxc6z85dx09ra62k92ld-my-texlab-4.3.0.drv.gz'.
guix build: error: build of `/gnu/store/db0x0apr340mjxc6z85dx09ra62k92ld-my-texlab-4.3.0.drv' failed

見ての通りですが、かなり複雑で長いエラーメッセージが出ます。これはGNU Guix自体がGNU Guileという処理系で動いており、Guixの吐くエラーとGuileの吐くエラーとが両方表示されているためです。前半はGuixの吐くエラー、後半はGuileの吐くエラーです。 Guileの出すエラーはScheme処理系自体のバックトレースでしかなく、パッケージのビルド過程そのものの情報はあまりありません。そのため、基本的には前半のエラーを読んでいくことになります。

前半の部分を抜き出すと、以下のようになります。

/gnu/store/wsacmf19wa4xdvniwza1vlrbhl3fk6jq-my-texlab-4.3.0/bin/texlab: error: depends on 'libgcc_s.so.1', which cannot be found in RUNPATH ()
/gnu/store/wsacmf19wa4xdvniwza1vlrbhl3fk6jq-my-texlab-4.3.0/bin/texlab: error: depends on 'ld-linux-x86-64.so.2', which cannot be found in RUNPATH ()

RUNPATH というのはバイナリに含まれている情報で、依存する共有ライブラリを探すときにどのディレクトリの中を探すか、保持しています。 GNU GuixはFHSという標準的なディレクトリ構成から逸脱しているため、この RUNPATH をあらかじめ書き換えておかないと、依存する共有ライブラリを見つけることができません。このエラーは、「 texlab バイナリは libgcc_s.so.1 及び ld-linux-x86-64.so.2 という二つの共有ライブラリに依存しているけど、 RUNPATH の中を探してもそんなファイルないよ?」ということを表しています。行末の () には本来パスが入るのですが、 RUNPATH を一切設定していないので空になっています。

RUNPATH を書き換えるには、 #:patchelf-plan という引数を用います。例を見せたほうがわかりやすいと思うので、以下に示します。

(define-public my-texlab
  (package
   ...
   (arguments '(#:install-plan
                '(("texlab" "/bin/"))
                #:patchelf-plan
                '(("texlab" ("glibc")))))
   (inputs
    (list glibc))
   ...))

#:patchelf-plan には '(("binary-filename" ("input1" "input2" ...))) のような値を与えます。 "binary-filename"RUNPATH を変更したいバイナリファイル、 "input1""input2" には RUNPATH に追加したいディレクトリを保持するパッケージの名前を書きます。すなわち、共有ライブラリを提供しているパッケージを指定すればよいです。もちろん、 #:patchelf-plan に指定するパッケージは inputs に指定する必要があります。

上記の例では、 glibcinputs に指定し、 #:patchelf-plan によって texlabRUNPATH に追加しました。 glibcld-linux-x86-64.so.2 を提供します。さて、 glibc を提供するモジュール (gnu packages base)#:use-module した上で、ビルドしてみましょう。

guix build -L /path/to/your/channel/ my-texlab

すると、以下のようなエラーが見られます。

/gnu/store/2ga0imdyrgq5miqysvry8fbphjh4w4ky-my-texlab-4.3.0/bin/texlab: error: depends on 'libgcc_s.so.1', which cannot be found in RUNPATH ("/gnu/store/ayc9r7162rphy4zjw8ch01pmyh214h82-glibc-2.33/lib")

まず、行末の RUNPATH () の中に glibc のパスが追加されています。さらに、 RUNPATH を追加したことで、さきほど出ていた ld-linux-x86-64.so.2 についてのエラーが解消されています。

次に、 libgcc についてのエラーを解消します。 libgccgcc のうち "lib" 出力で提供されています。この言い回しははじめてかもしれません。通常のパッケージは out 出力に書き込まれますが、ドキュメント部分やライブラリ部分がそれぞれ大きすぎて1つのパッケージにするのは不便な場合があります。そのような場合のために、 doc 出力や lib 出力など、複数の出力を持てるようになっています。なにも指定せずにインストールすれば out 出力だけがインストールされ、必要な人だけが別途 libdoc などの別の出力をインストールできるようになっています。コマンドラインからインストールする場合、 gcc:lib のようにコロン区切りで出力の種類を指定できます。

inputs で依存として指定する場合は、以下のように出力を指定できます。

(inputs
 (list
  glibc
  (list gcc "lib")
  ;; `(,gcc "lib") でも同義
  ))

つまり、 inputs に与えるリストの各要素は、パッケージ定義そのものだけでなく「第一要素がパッケージ定義、第二要素が出力を表す文字列」のリストも用いることができます。

#:patchelf-plan に与えるパッケージ名は、単にパッケージ名で構いません。すなわち、以下のようになります。

(define-public my-texlab
  (package
   ...
   (arguments '(#:install-plan
                '(("texlab" "/bin/"))
                #:patchelf-plan
                '(("texlab" ("glibc"
                             "gcc")))))
   (inputs
    (list
     glibc
     (list gcc "lib")))
   ...))

さて、 gcc を提供するモジュール (gnu packages gcc)#:use-module した上で、もう一度ビルドしてみましょう。

guix build -L /path/to/your/channel/ my-texlab

ここまで正しくできていれば、ビルドが成功しているはずです。 my-texlab がインストールされた環境に入って確かめてみましょう。 --pure でも --container でも構いません。

guix shell -L /path/to/your/channel/ my-texlab --container -- texlab --version

-- による環境での即時実行を使ってみました。該当環境で texlab --version を実行しています。ここでバージョンが出力されたら成功です!!

トラブルシューティング

この章では、 binary-build-system を用いた場合に経験した諸トラブルを扱います。今後追記するかもしれません。

validate-runpath フェイズでGuileがエラーを吐いてしまう

validate-runpath において、以下のようなエラーが出ることがあります。

wrong-type-arg "struct-vtable" "Wrong type argument in position ~A (expecting ~A): ~S" (1 "struct" #f) (#f)

このエラーは、 #:strip-binaries? 引数に #:f を与えると解消します。この引数によって strip フェイズが無効化されますが、これは主にデバッグ情報を取り除くものらしく、少なくとも私の手元ではこれによって動作に影響が出たことはありません。

最後に

今回はバイナリを直接インストールするパッケージの定義方法を説明しました。 binary-build-system において、今回出てきた ld-linux-x86-64.so.2 (glibc)と libgcc (gcc:lib)の解消はかなりの頻度で求められるので、覚えておくとよいです。

重ね重ねになりますが、 binary-build-system は少なくとも GNU哲学において推奨されるものではないため、 GNU直属のコミュニティで喧伝するのは控えたほうがよいです。しかし、一方で一般的なユーザーにとって便利であることも事実です。バイナリから直接インストールする危険性は念頭に置きつつも、上手に使っていきましょう。

また、AppImageなどの一部のバイナリについては、この方法ではインストールできず、どうやってインストールするのか私にはわかりません。成功した方やなにか情報があるかたは気軽にご連絡ください。どうしてもパッケージの作成が成功しない場合のサブとして、Flatpakというパッケージマネージャを利用する人が多いようです。

またまた前回と同じ締めですが、もしなにか質問などあれば、コメントなりメールなりで受け付けます。また、Guixの日本語コミュニティであるGuix-jpもあります。そちらには私以上のエキスパートの人がいますので、是非そちらもご興味あれば覗いてみてください。もちろんそちらでも質問などをお受けいたします。