Nix学習備忘録Part 1: Nixに入門する
1 はじめに
技術記事をまともに書くのは初めてなので、 記述が冗長(既にこの章が冗長かもしれない)であると感じることもあるかもしれませんが、 温かい目で読んで頂ければ幸いです。 また、読者にNixの入門をさせることが目的ではなく、 あくまで個人的にNixを勉強していて、 そろそろどこかにまとめないと忘れそうだと感じたので、 本シリーズはタイトル通り``備忘録"という位置付けです。 もし、情報に誤り等御座いましたら、 Twitter、GitHub、またはEmailがフッターにありますので、 そちらからご報告頂ければ有難いです。
2 Nixとは
``Nix"は純粋関数型パッケージマネージャの1つです。 パッケージを純粋関数型言語の値として扱います。 つまり、各パッケージは副作用を持たない関数によって構築され、 ビルド後に変更されることはありません。
…と、あまり抽象的な話をし過ぎてもHaskell等の関数型プログラミング言語を使ったことがある人であれば、 何となくは、こういうことかと理解できるかもしれませんが、 そうでない人にとっては何を言ってるかさっぱりだと思うので、 (というより、私自身も完全には理解できていないので、) 実際にPythonを例に具体的に環境構築を行なってみます。
Nixは学習コストが高いことでも有名なので、 全部理解して発信するより、機能に少しずつ触れながら、 こういうことできました~って発信する方が、 この記事を読む方も置いてけぼりにならなくて良いかなという 淡い期待と甘えで筆を進めています。
3 Nixのインストール方法
日本語でも既に記事があり、何番煎じかわかりませんが、 一応載せておきます。
※以下のインストール方法は2022/01/19現在のものです。
4 NixでPythonの環境を構築する
4.1 何ができるようになるか
今まで私はpyenvでPythonのバージョンを管理し、 virtualenvを使って各種パッケージの管理を行なっていました。
Nixを用いると、pyenvもvirtualenvも使わず、 Pythonのバージョン、パッケージをまとめて管理できます1。 また、この環境はプロジェクトごとに作ることができ、 グローバル環境を汚すことなく、 クリーンにインストールすることができます。
4.2 今回作るもの
何を作るのかはあまり重要じゃないのですが、 PNG形式の画像ファイルをPDF形式に変換するプログラムを作成します。 理由については、本筋と関係ないので、読みたい方だけ読んでください。
なぜPDF変換のプロジェクト?
論文を書いたりするときに、LaTeXを使うのですが、 pngファイルの出力が上手く行かないことがあり、 eps形式のファイルが推奨はされているのですが、 pdfで十分出力される上、使い勝手も良いので、 変換する作業が必要になることがちょくちょくあります。
論文に使うような画像の変換に、 オンラインの変換サービスを利用するのは流石に気が引けるので、 Pythonで一気に変換できた方が良いなと考えた次第です。4.3 Pythonを使えるようにする
まず、現在の状況を確認します。 ~/work
下にpng2pdf
ディレクトリを作成します。
ホームディレクトリ
~
は/home/username
にあるとします。
$ cd ~
$ mkdir work && cd $_
$ mkdir png2pdf && cd $_
$ pwd
/home/usename/work/png2pdf
$ ls
$
既存のPythonの環境はすべて消し去ったので、 今はpython
コマンドは使えない状況です:
$ python
python: command not found
ここで、お好みのエディタで現在のディレクトリ(~/work/png2pdf
)に、 以下のようなshell.nix
という名前のファイルを作成します。
{ pkgs ? import <nixpkgs> {} , ...}:
with pkgs;
mkShell {
buildInputs = [ python3 ];
}
以下のような状態であればOKです。
$ pwd
/home/username/work/png2pdf
$ ls
shell.nix
$ cat shell.nix
{ pkgs ? import <nixpkgs> {} , ...}:
with pkgs;
mkShell {
buildInputs = [ python3 ];
}
ここで、nix-shell
というコマンドを入力すると、 Pythonがインストールされたシェルが起動します。
$ nix-shell
# 初回は若干時間が掛かるかもしれません。
[nix-shell:~/work/png2pdf]$ python
Python 3.9.9 (main, Nov 15 2021, 18:05:17)
[GCC 10.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 1 + 1
2
>>> exit()
[nix-shell:~/work/png2pdf]$
~/work/png2pdf
下でPythonが使えるようになりました!
すでにグローバルにPythonが入っていた人のために、 どのPythonが使われているのかも確認してみましょう。
[nix-shell:~/work/png2pdf]$ which python
/nix/store/dn4fwp0yx6nsa85cr20cwvdmg64xwmcy-python3-3.9.9/bin/python
頭に/nix/store/
と付いています。 各種パッケージ等がインストールされる先はこのNix Storeと呼ばれる場所で、自分のPCで利用可能なパッケージがすべて含まれています。
また、python3-3.9.9
というバージョンの前にハッシュのようなものが付いています。 このおかげで、バージョンは同じだけど、依存関係は違うようなPythonも衝突(conflict)しないことが保証されています。
このシェル環境から出るにはexit
とタイプします。 この環境から抜けると、最初の状態と同様、python
は使えなくなります。 グローバルの環境は一切汚れていないことがわかります。
[nix-shell:~/work/png2pdf]$ exit
$ python
python: command not found
今回作るPNGをPDFに変換するプログラムはimg2pdf
というパッケージを必要とします。 しかし、現在そのようなパッケージはインストールしていないため、使うことができません。 実際にnix-shell
コマンドで再度シェルに入ってみます。 2回目なので、すぐにシェルが起動します。
$ nix-shell
[nix-shell:~/work/png2pdf]$ python
Python 3.9.9 (main, Nov 15 2021, 18:05:17)
[GCC 10.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import img2pdf
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'img2pdf'
>>> exit()
[nix-shell:~/work/png2pdf]$ exit
$
4.4 Pythonパッケージを利用可能にする
img2pdf
を利用可能にするためには、shell.nix
のbuildInputs
に python3Packages.img2pdf
という記述を追加します:
{ pkgs ? import <nixpkgs> {} , ...}:
with pkgs;
mkShell {
buildInputs = [
python3
python3Packages.img2pdf # <- この行を追加
];
}
再度nix-shell
コマンドにより、再度シェルを起動してみます。
これでimg2pdf
が使えるようになってるはず…
$ nix-shell
[nix-shell:~/work/png2pdf]$ python
(省略)
>>> import img2pdf
Traceback (most recent call last):
(中略)
ModuleNotFoundError: No module named 'PIL'
>>>
PIL
という名前のモジュールが見つかりませんってエラーが出ました。 img2pdf
を使用するには、pillow
ってパッケージをインストールする必要があるみたいです。
この問題どうにかならないの?(Pythonユーザー向け)
ということで、前置きが長くなりましたが、依存関係のpillow
をbuildInput
に加えます。
{ pkgs ? import <nixpkgs> {} , ...}:
with pkgs;
mkShell {
buildInputs = [
python3
python3Packages.pillow # <- この行を追加
python3Packages.img2pdf
];
}
再度nix-shell
を実行し、img2pdf
が使えるか確認してみます。
$ nix-shell
[nix-shell:~/work/png2pdf]$ python
Python 3.9.9 (main, Nov 15 2021, 18:05:17)
[GCC 10.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import img2pdf
>>> img2pdf.__version__
'0.4.3'
>>> exit()
[nix-shell:~/work/png2pdf]$
無事img2pdf
が使えるようになりました!
4.5 プログラムを書く(詳細は省略)
Pythonのプログラムは主目的から外れるので、 プログラムとファイル構造だけ載せ、プログラムに関する詳細な説明は省略します。
メインはNixの方なので、Pythonのこの書き方おかしいだろとか、 ファイル構造どうにかならんのかとか、 そういうツッコミは心の中でお願いします。笑
[nix-shell:~/work/png2pdf]$ mkdir png pdf
[nix-shell:~/work/png2pdf]$ ls
pdf png shell.nix
このpng
フォルダにPDFに変換したいPNG形式の画像ファイルを入れます。 pdf
フォルダは空のままです。
[nix-shell:~/work/png2pdf]$ ls png
fuga.png hoge.png
[nix-shell:~/work/png2pdf]$ ls pdf
[nix-shell:~/work/png2pdf]$
お好みのエディタで、~/work/img2pdf/
内に、 以下のようなmain.py
ファイルを作成します:
import os
import img2pdf
from PIL import Image
if __name__ == '__main__':
= os.getcwd()
cwd = f"{cwd}/pdf/"
pdf_dir_path = f"{cwd}/png/"
png_dir_path
for png_filename in os.listdir(png_dir_path):
= png_filename.replace('.png', '.pdf')
pdf_filename
# 既に変換済みファイルがあったらスキップ
if pdf_filename in os.listdir(pdf_dir_path):
continue
with open(pdf_dir_path + pdf_filename, "wb") as f:
f.write(img2pdf.convert(open(png_dir_path + png_filename).filename
Image. ))
ファイル構造が以下のようになっていればOKです。
[nix-shell:~/work/png2pdf]$ tree .
.
├── main.py
├── pdf
├── png
│ ├── fuga.png
│ └── hoge.png
└── shell.nix
2 directories, 4 files
では、main.py
を実行してみます:
[nix-shell:~/work/png2pdf]$ python main.py
Image contains an alpha channel which will be stored as a separate soft mask (/SMask) image in PDF.
Image contains an alpha channel which will be stored as a separate soft mask (/SMask) image in PDF.
[nix-shell:~/work/png2pdf]$ tree .
.
├── main.py
├── pdf
│ ├── fuga.pdf
│ └── hoge.pdf
├── png
│ ├── fuga.png
│ └── hoge.png
└── shell.nix
2 directories, 6 files
[nix-shell:~/work/png2pdf]$ exit
$
新しくhoge.pdf
とfuga.pdf
がpdf
ディレクトリの中に追加されました。
5 まとめ
Nixを用いてPythonと、Pythonのパッケージを利用可能にする方法を紹介しました。
次回は、毎回nix-shell
で環境に入って、 exit
で出るの面倒臭くない?って問題を解決します。
本当にNixだけで良いかというと、実はそうでもなく、 PoetryというPythonのパッケージマネージャ―を使った方が良くなったりもするのですが、 小規模のプロジェクトではそれほど問題にはならないので、 今後、必要に応じて記事を書きます。↩︎