近年プライベートで遊んでいる技術はNixです。「ふつうのソフトウェアエンジニア」の ひむら (id:eiel)です。こんにちは。
kubell Advent Calendar 2024 シリーズ2になにか書こうと思って、この記事を書き始めました。
ということで(?)、最近関心のある技術であるNixについて、業務上でどのように さりげなく 使っているかを書き残しておこうとおもいます。 本記事はゆるふわな気分で、エッセイ感覚でかいています。読み手に何かを学んでもらおうという意図はないです。TextLintも設定せずに書いています。誰かがなにか思うことがあれば嬉しい、というモチベーションで書いています。
- Nixと私
- Homebrewの代わりとしてのNix
- macOSの宣言的な設定を行うための nix-darwin
- 環境構築としての home-manager
- vagrantやdockerを用いた開発環境
- オチ
Nixと私
さて、社内でNixの流行状況ですか、たぶん流行っていません。 業務中で使っているのは私だけなのではないかというくらいのレベルです。
ところで、そもそもNixとはなんでしょうか。
OSのパッケージマネージャと答えるのがおそらく正しいでしょう。Homebrewとかaptとかyumとかそういったものです。 ただし、Nixという言葉は文脈次第ではプログラミング言語だったり、Linuxのディストリビューションの一つだったりします。 要するに自分のコンピュータにソフトウェアをインストールするのに使うものなんですが、広い意味だと、その周辺のエコシステムやツールそのものなどまで指す感じです。
もうちょっと具体的にいうと、Nixで提供するパッケージ(Derivation)は Nix言語というプログラミング言語を用いて記述します。また、このNixパッケージマネージャをデフォルトのパッケージマネージャとして使い、かつ、設定もNix言語で行うNixOSというLinuxのディストリビューションがあります。これらのプログラミング言語やOSも端にNixと呼ぶことがあります。
そんなNixですが、私が業務中でひっそり使っているNix関連のツールは以下の4つです。
- Homebrewの代わりとしての Nix
- macOSの設定を宣言的にするための nix-darwin
- 自分自身の開発環境を設定するための home-manager
- vagrantやdockerを用いた開発環境の代わりとしての devenv
macOSにNixを導入して、上記のツールを使っています。
さて、Nix自体の説明は世の中に溢れている(?)ので、ここでは割愛しましょう。 私の活用方法とその理由でも淡々と書いていきます。
さて、なぜNixを使うのでしょうか。Nixのウェブサイトを開いてみましょう。大きく書かれているのが 「Declarative builds and deployments」です。そうです。宣言的なのです。みんな宣言的UIは大好きですよね。みんな宣言型プログラミング言語が好きですよね。言うまでもないですが、関数プログラミングは宣言的です。ReactiveXは宣言的です。SQLは宣言的です。Reactは宣言的です。
すみません。みんなはちょっと主語が大きかったです。反省します。とはいえ、関数界隈の人たちの多くの人は宣言的なものが好きなんじゃないかと思います。
話をわざと逸らしましたが、なぜ使いたいかというと、手段を目的にしています。 私はソフトウェアエンジニアなので、手段で扱う技術をたまに目的にして遊びたいのです。遊びたいのです。 興味が湧いたものを深堀りして遊びたいのです。私の生きる様にとって「とーっても」大切なことなのです。たぶん。
Homebrewの代わりとしてのNix
まずは、Homebrewの代わりにNixを使う話をしましょう。
私はNixを使うという手段が目的なので、Nixが使えればいいです。使うことで学びが得られれば問題ありません。 そこで、NixでインストールできるものはなるべくNixを使い、Nixに存在しないパッケージもあるので、そこは自分でパッケージを書きたいと考えています。とはいえ、実際に書ける余裕はなく、結局Homebrewでインストールしている状態ではあります。そういった場合はたいてい今すぐ必要なときなので、時間を割けないのです。負けていますが仕方ないのです。別途、自由な時間で遊ぶ必要があります。
nix-envというコマンドを使ってパッケージをインストールできますが、あまり使いません。nix-shellを使うと一時的にそのパッケージがインストールされた環境にすることができます。ここで確認をして、今後も使うようであれば、後述のnix-darwinやhome-managerを使いインストールしていくことが多いです。使い方自体は人それぞれでしょう。
そんな感じで必要なパッケージをインストールして使っていて、業務上で困ることはいまのところありません。あえて困ったことをあげるなら、バイナリの対象とする最小OS macOS 11でビルドされていそうなので最高のパフォーマンスを発揮するバイナリにはならなさそうなところでしょうか。別に困ってないですね。業務上で起きた話ではないですが、自分の環境では動くけど、他の人の環境で動かない現象と戦う場合はNixでインストールしたものを参照しないように調整しないといけない場合があって、ちょっと手間でした。とはいえ、大した話ではないです。
macOSの宣言的な設定を行うための nix-darwin
続いて、nix-darwinです。
NixOSのようにmacOSを設定できるツールです。 このツールをmacOSの設定に使っています。Nix言語を用いて用意されているルールに従って値を設定する感じです。 どんな設定ができるかは公式サイトにドキュメントがあるのでみてみてください。
例えば、homebrewでインストールするbrewやcaskを設定ファイルに書けます。
具体例を抜粋するとこんな感じ。
homebrew = { enable = true; taps = ["homebrew/cask-versions"]; brews = [ # nixのlimaでは VM types: 'vz'を利用できないためbrew版を利用する(v0.21.0のとき調べ) "lima" "colima" ]; casks = [ "karabiner-elements" "firefox-beta" "google-chrome-beta" "google-japanese-ime" ]; masApps = {}; };
(ふえーん。シンタクスハイライトが効かないよぉ)
なお、masApps
は mas-cli/mas
でインストールするMac App Storeのアプリの一覧を書く感じです。そういえば、なんでhomebrew配下になるんだろう。一旦masがHomebrewでインストールされるからだろうか。 参考
macのシステム設定しているところも抜粋します。
system.defaults = { NSGlobalDomain = { AppleShowAllFiles = true; AppleKeyboardUIMode = 3; InitialKeyRepeat = 12; KeyRepeat = 1; "com.apple.trackpad.scaling" = 3.0; }; # 押すよりもタッチで済む方が楽なため trackpad.Clicking = true; # 画面共有時など止まっていないか確認しやすいように menuExtraClock.ShowSeconds = true; # 画面を広く使いたいため dock.autohide = true; };
設定としてファイルに書けるとその設定の理由もコメントに残せるのも嬉しいですね。丁寧にコミットを作ってコミットメッセージに書ければいいんですが、プロダクトでもなく試行錯誤することも多いのでコミットしそこねることが多くて、まとめてコミットしがちな状態です。なので、コメントに書くのはwhy not だけでなく why を書いておくことが多いです。
こうやってみると、JSONを書くのと大差ない感じがしますね。 そんなnix-darwinを使う強みは新しいマシンに移行するときに設定を維持しやすいところです。
環境構築としての home-manager
さてhome-managerです。
nix-darwin同様、nixを使って設定ができるツールです。 違う点はマルチユーザ環境において、自ユーザの環境だけを設定するのに使います。 nix-darwinでインストールすれば、同じコンピュータを使う誰でも基本的に使えますが、home-managerは自分だけ使うといったことができるわけです。 私の使い方としては、nix-darwinでなくても設定できるものはhome-managerを使う形にしています。 home-managerは管理者権限を必要としないので、反映にroot権限が必要になるnix-darwinではなるべく使わず済ませるという使い方です。
ちなみにhome-managerはnix-darwinと連携してまとめて動かしたりできます。一括アップデートができて便利だったりします。個人的にはhome-managerだけ実行することでオーバーヘッドを減らしたいので、単体利用しています。home-managerを使うモチベーションは人それぞれあると思うので、人によっては連携して使っているのではないかと思います。
home-managerで設定しているパッケージは開発で使うツールでかつプロジェクトに関係なく使うものに関連したもの多いです。具体的にはemacsとか、gitの設定です。
例としてgitの設定の例をおいておきます。
programs.git = { enable = true; ignores = let direnv = [".envrc" ".direnv" "devenv.local.nix" ".pre-commit-config.yaml"]; emacs = ["*~"]; mac = [".DS_Store"]; in direnv ++ emacs ++ mac; extraConfig = { init.defaultBranch = "main"; }; };
これまでの例とは違い、演算している部分があります。 let inなるものがありますが、なにを目的にignoreしているかわかるように名前をつけてあげて、最後にひとつの配列にまとめ上げている感じです。単純なjsonより便利ですね。もちろん計算してあると現在の状態がなんなのかわからなくなる欠点はあります。別途結果を知りたいなら全体の評価すればいいだけです。(実は関数を全体で書くので、引数を渡す必要はありますが…)
vagrantやdockerを用いた開発環境
さて、最後はdevenvです。
Vagrantはもはや古典となってしまっているかもしれませんが、開発環境でdockerを使う目的と同様の理由でnixを使う場合にdevenvを使うことがあります。データベース、サーバ、開発言語の準備、linterの実行環境などなどの用意の自動化が目的です。 特に、特定のリポジトリの固有のものを用意し、リポジトリごとに独立した環境を提供する目的で使っています。 ランゲージサーバーもこれで入れているのですが、プロジェクトディレクトリ外にジャンプすることがあるので、前述のhome-managerにも入れています。 他とは違うのはPythonプロジェクトならPythonを使う設定をしておくと便利なものを一緒にいれてくれたり、思想の違いを感じられます。
私はdevenvのdirenv連携をしていて、そのディレクトリに移動すれば有効になるようにして使っています。これはおそらく一般的な使い方です。リポジトリをcloneして、ディレクトリに移動すると開発環境が整う感じです。
devenvを使わず default.nixなどを書いて、Nix付属のnix-shellを使って開発するような手段で近いことができたりしますが、devenvが登場してからはお手軽なので、そのまま試し続けています。 default.nixを使っていないかというとこちらはリポジトリをパッケージとして扱うために使う目的と割り切るようにしました。
話はそれますが、このdefault.nix といったファイルはRustやC++なんかのリポジトリをみていると最近見かけることがあります。 これはビルドするための環境に必要なもが書かているため、すぐにbuildができる状態になります。共有ライブラリが必要になるものは別途brewで libXXX のようなものをインストールしておく必要がありますがそういった依存を書いておくのに適しています。
とはいえ、社内でNixを使っている人はあまりいないので、同様の課題に対してはBrewfileで必要なものを別途かいておくことに個人的にはしています。
なお、devenvに関する設定は基本的にはコミットしておらず、私のローカルに眠らせたままにしていることが多いです。 自分に主導権があるリポジトリではコミットしてしまいますが、他の人は使われないため、ノイズになり認知負荷が無駄に高くならないようにしています。コミットしておくと宣伝になる可能性はありますがトレードオフです。 私が主導権を持っているリポジトリにはきっとコミットされています。
最近試していないですが、JetBrains IDEやVSCodeで連携するのがあまりうまくいかなかったです。コマンドが見つけられるようにならなかったりして、困ることがあります。そのため他の人に強要する気は全くなく、実験台となり触っていこうと思います。 とはいえ、それらのエディタでもdirenv連携ができたら使う事自体はできるかと思います。
たまに困る点では、frontendチームのリポジトリでは、積極的にnodejsのバージョンを最新してくれているので、まだnix側に新しいnodeがなくて npm intsall
や npm ci
ができなくなり --force
をつけるか即席で最新パッケージをつくるかどうか悩んでしまう場合があります。
オチ
という感じで、特別 Nixのメリットをアピールする気はなく、私がさりげなく業務中にNixを使っているという話を具体例を交えながらしました。 言いたいことは、手段を目的にする行為はとても楽しいです。メリットは大切ではないのです。アウトカムは大切ではないのです。楽しいことが大切なのです。
そうはいっても、プロダクトを開発する上では、ユーザにアウトカムを生み出すことが「とーっても」大切です。手段を目的化している場合ではありません。そんなことをしていたら成果がでません。 なので、こういった遊びは業務外で、普段の生活の中に取り入れ、遊び心をもって手段を目的化して、遊んでいきたいと思います。