前回の記事で予告した通り Flakes についての入門記事を書く。 なお、Flakes は experimental な機能なためここに記載している内容は将来変更される可能性がある。 特に各コマンドの名称やオプションなどは issue 等で議論されているのをチラホラ見かけたりするので、変更される可能性は割と高い。
Note: この記事に記載の内容はすべてあくまで個人的な見解である。
Note: この記事内では Attribute Set 型のことは
AttrSetと省略して記載する。
Flakes とは何か
(Flakes でできることはいろいろあるため一言で表現するのは難しいが強いていうとすると..)
Nix Flakes とは Nix ベースのプロジェクトのための標準化されたインターフェース(を提供するもの)。そしてその Flakes の標準に従って作られたプロジェクト 1 つ 1 つのことを "flake" と呼ぶ。("A flake" と言ったほうが良いか)
具体的に "A flake" とはルートディレクトリに flake.nix ファイルを含んだ git リポジトリや tarball 等のソースツリーのことで、その flake に属するファイルはすべて git であれば git リポジトリに、tarball であればその tarball の中に入っている必要がある。
まあ基本的に git リポジトリとして作ると思う。(それ以外のケースはあまり見たことがない)
flake.nix というファイルは、NodeJS の package.json のような、いわゆるマニフェスト的なもの。
flake をより雑に表現するなら、npm のパッケージや cargo のクレートの Nix 版的なもの。(が、Nix の場合はどんなプログラミング言語のプロジェクトでも使えるという意味ではそれらよりもメタな感じはある。)
flake を使って作業する際によくありがちなのが、
そんなファイル知りません的なエラー。 flake に属するファイルは git であればすべて git の管理下に存在しなければならない。 ここで言う git 管理下というのはつまり任意の staging あるいは commit にそのファイルが存在するということ。
Flakes で何ができるかは flake.nix のスキーマと、flake に関連する nix のコマンドを見ていくのが早い。 (imo)
flake.nix のスキーマ
flake.nix 自体はとてもシンプルで、description, inputs, outputs, という 3 つの attribute を持ったただの AttrSet である。
TypeScript の型的に書くとこんな感じ。
type FlakeAttrSet = {
description: string;
inputs: InputsAttrSet;
outputs: (self: FlakeAttrSet、...inputs: InputsValues) => OutputsAttrSet;
}
以下にそれぞれの attribute の説明を書く。
description
文字通り、その flake が何であるかの説明。
inputs
flake が依存する inputs を定義する AttrSet.
nodejs の package.json で言うところの dependencies に近い。
inputs AttrSet の attribute 名は完全に任意で、値には flake references を指定する。(flake でないものも指定できる。)
いろいろな種類のものを指定でき、また書き方は複数ある。 ローカルのファイルだけでなくインターネット上の他の flake、 flake でないもの、git リポジトリ、tarball、GitHub や sourcehut のリポジトリなど。
ここで指定したものは fetch/eval された後に outputs の引数に渡される。(後述)
flake references の詳細はマニュアルにまとまっている。
inputs で特筆すべきは fetchgit や fetchzip などの fetcher とは異なり、特定の revision の hash を明示的に指定しなくてもよいという点だ。
特定の revision を決める hash は flake 関連のコマンド群によって flake.lock というロックファイルに自動で書き込まれる。 (初回のコマンド実行時ににその時点の最新の revision が指定される。lock ファイルはコマンドを通じてアップデートしない限りはかわらない。)
inputs ついてはわりとマニュアルにまとまっているので詳細についてはマニュアルを見るのが良い。
Note: ちなみに、private な GitHub リポジトリも inputs として使える。 see: https://github.com/NixOS/nix/issues/3991
outputs
outputs を定義する AttrSet を return する関数。(inputs とは異なりこちらは関数。)
引数には flake 自身のソースツリーや outputs 等を表す self、そしてあとは inputs で定義したものが fetch/eval され、それぞれ定義した名前で Nix value として引数に渡ってくる。(重要)
inputs metadata
outputs の引数に渡ってくるそれぞれの inputs には以下の metadata が生えている。(すべて存在するとは限らない)
- outPath
- rev
- revCount
- lastModifiedDate
- lastModified
- narHash
それぞれの詳細はマニュアルのここに記載されている。
outputs AttrSet
return する AttrSet (以降 outputs AttrSet と呼ぶ)は基本的には任意の AttrSet だが、nix コマンドで利用される attribute が存在する。 また nix コマンド以外にも他のツールやライブラリによっては特定の attribute を利用する場合がある。例えば home-manager は homeConfigurations.<hostname> という attribute を利用する。
以降の説明を簡略化するため、nix コマンドで利用されるものを予約 attribute と呼ぶことにする。予約という言葉使っているが、なにか言語上で物理的に予約されていたりするわけではない。
基本的に何をどう書いてどう使うかは結構自由で、その flake がどういうことをする flake なのかはこの outputs で決まる。
予約 attribute のリスト
自分の知る限りマニュアルにはまとまったリストは存在しない。公式マニュアルの各コマンドのページを一つ一つ見ていくのも一つだが、
ユーザー Wiki が一番よくまとまっているのでここを見るのがよいと思う。(執筆時点)
上記 wiki に記載されているものがすべてではないことに注意。nix fmt で利用される formatter.<system> = derivation など。
それぞれ optional であり、自分が必要なものだけ設定すればよい。
たとえば nix develop (flake 版の nix-shell 的なコマンド) だけを使いたいければ devShells だけを指定すればよい。
なお、すでにいくつかは名前が変わりそうな気配がしているので無理に名前を覚えたりする必要はない。 例: https://github.com/NixOS/nix/issues/6257
self について
self は flake のソースツリーと outputs を表す。
self を使えば outputs AttrSet の任意の attribute を参照できる。
outputs だけではなく、上記に記載した inputs metadata 系の attribute も生えている。(ここでは自身の flake に関する metadata)
また、self は flake のソースツリーを表すので、mkDerivation.src に self をそのまま渡したりという使い方もできる。
公式のマニュアルでこの辺のことがまとまって記載されている箇所はここ。
(flake inputs の項だが、その中でまとめて outputs についても説明されていて、そこで self に関する説明がでてくる。)
公式の flake.nix のテンプレ
公式に flake.nix のテンプレートリポジトリがある。
このリポジトリもまたそれ自体が flake として作られており、outputs に各テンプレートが定義されている。
nix flake show templates でテンプレのリストを見ることができ、nix flake init -t templates#テンプレ名 でその中のテンプレを使って新しい flake を作れる。
補足: flake-url については後で記載するが上記コマンドで templates がこの GitHub リポジトリの flake を指すようになっているのは、flake registry でそう定義されているため。 他によく使う可能性のありそうなのでいうと nixpkgs や home-manager などもある。
flake 関連の nix コマンド
新しい nix で始まるコマンドはだいたいのものが何かしらの形で flake に関連する。(nix flake だけではない)
関連するというよりは、flake に対応していると言ったほうが良いかもしれない。
まだまだ experimental な感じなため、いつ何が変わってもおかしくない感じはある。
以下にいくつかのコマンドを簡略化して紹介する。
こんなコマンドがある、という紹介が目的なため詳細はマニュアルを(以下略)
<flake-url>
コマンド紹介にあたって、inputs で紹介した flake references のマニュアルにある flake references in their URL-like representation のことを <flake-url> と呼ぶことにする。
任意の flake を指定できる nix コマンドでは基本的に <flake-url> を用いて指定する。
また <flake-url> を使うコマンドでは <flake-url>#<attribute-name> のような感じでその flake の outputs の特定の attribute 名(具体的には後述)を指定できる。(# 以降もコメントではないことに注意)
<flake-url> で自身の flake を指したいときは flake.nix のあるディレクトリを相対パスで指定すればよい。例: flake.nix のあるディレクトリにいるなら .
nix build
derivation をビルドする。(ビルドとは instantiate して realisation すること。(US 英語では realization だがマニュアルでは realisation のほうが多く使われている。))
ビルド後にカレントディレクトリに result という Nix Store の成果物への symlink を生成する。
<flake-url>#<package-name> を渡すとその指定したパッケージの derivation をビルドする。ここで <package-name> とは outputs AttrSet の package.<system>.<name> または legacyPackages.<system>.<name> の <name> のこと。
もし何もパッケージ名を指定しなかった場合は packages.<system>.default または defaultPackage.<system> がビルドされる。
例:
nix build nixpkgs#hello
./result/bin/hello
# Hello, world!
nix run
Nix application を実行する。ここで Nix application とは、outputs AttrSet の apps attribute で指定されたプログラムのこと。
<flake-url>#<app-name> を渡すとその指定した app を run する。ここで <app-name> とは outputs AttrSet の apps.<system>.<name> の <name> こと。
(apps だけでなく、packages や legacyPackages のほうが使われる可能性もあるがここでは省略する。詳細はマニュアルを。)
apps.<system>.<name> の値は derivation ではなく app definition という型。
app definition は以下のような形
{
type = "app"; # type は必ず app
program = "${self.packages.x86_64-linux.blender_2_79}/bin/blender"; # 実行するファイルのフルパス(に評価されるもの)
}
なお、program にはフルパスのみで、引数を渡すことはできない。引数は nix run コマンドのあとに続けて -- 引数 とすることで渡せる。
何も app name を指定しなかった場合は apps.<system>.default が実行される。
例:
# flake.nix
{
description = "hoge";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
};
outputs = { self, nixpkgs, ... }: {
apps.x86_64-linux = {
"show-msg" = {
type = "app";
program = "${nixpkgs.legacyPackages.x86_64-linux.figlet}/bin/figlet";
};
};
};
}
nix run '.#show-msg' -- "ok"
_
___ | | __
/ _ \| |/ /
| (_) | <
\___/|_|\_\
nix develop
nix develop は derivation の build environment 環境で bash インタラクティブシェルを立ち上げる。
使われる derivation は devShells.<system>.<name> または packages.<system>.<name> or legacyPackages.<system>.<name> に書いたもの。
devShells が shell.nix の flake 版のような立ち位置で、nix develop は nix-shell の flake 版のようなもの。
nix develop の使いみちの例としては、packages の derivation の build environment に入ってデバッグしたり、あるいはその flake を使う・開発する上での development shell 環境を devShells で明示、提供したりなど。
ちなみに
nix-direnvはdevShellsに対応している。
また devShells を定義する際、mkShell で作る以外に、便利なライブラリとして devshell というものがある。numtide/devshell
例:
# flake.nix
{
description = "hoge";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
};
outputs = { self, nixpkgs, ... }:
let
pkgs = nixpkgs.legacyPackages.x86_64-linux;
in
{
formatter.x86_64-linux = pkgs.nixpkgs-fmt;
devShells.x86_64-linux.hello-shell = pkgs.mkShell {
buildInputs = [ pkgs.hello ];
shellHook = ''
echo "dev shell started"
'';
};
};
}
# dev shell を起動
nix develop '.#hello-shell'
# dev shell started
hello
# Hello, world!
nix shell
nix develop とは別に nix shell というコマンドもある。(nix-shell ではない)
名前も役割も似ていて紛らわしいが、nix develop はほぼ全ての環境変数等も含めた build environment の全体の再現が目的に対して、nix shell は単純に任意の application を PATH に通した状態の shell を立ち上げることだけに特化したもの。shellHook 等は適用されない。
基本は nix develop で、PATH だけでよければ nix shell という使い分けになる。というのが自分の理解。
例
nix shell nixpkgs#hello
hello
# Hello, world!
nix-shell にしかできないこと
flake からちょっと脱線するが、nix develop と nix shell を紹介したが依然として nix-shell にしかできないことがある。
それはインタープリター機能を使った shebang での利用である。
任意のプログラムにおける shebang としての利用は現在も nix-shell にしかできない。
以下は TypeScript に nix-shell で bun 環境で実行するようにした shebang を付ける例。
シェルスクリプトのように実行権限さえつければ TS ファイルをコンパイルせずに"ドットスラッシュ" (./) で nix-shell と bun によって直接実行できる。(bun が PC に入ってなくても nix-shell によって bun が入った環境で実行される)
bun は新しい JS/TS のランタイム。TS が直接実行できるという点で deno に似ているが、bun は nodejs の API を 9 割程度実装してあるため既存のコードもそのまま実行しやすい。個人的には bun が来てから TS を./で実行したいときは bun を使うようになり、 ts-node はほぼ使わなくなった。
hoge.ts
#! /usr/bin/env nix-shell
/*
#! nix-shell -i bun -p bun
*/
function greet(name: string) {
console.log(`hello ${name}`)
}
greet('hoge')
./hoge.ts
# nix-shell により bun が入ったシェルから bun で hoge.ts が実行される
# hello hoge
その他のコマンド
ここまで主に開発系のコマンドを紹介してきたがそうではないコマンドもいくつか書いておく。
nixos-rebuild
NixOS ユーザーならおなじみのコマンド、nixos-rebuild も flake に対応している。なので flake を NixOS の config 用に使うこともできる。
任意の flake の outputs AttrSet の nixosConfigurations.<hostname> に書かれた設定を --flake <flake-url>#<hostname> で指定できる。
詳細はユーザー Wikiがわかりやすい。
nix profile
nix-env の flake 版相当のコマンド。
自分はこのコマンドは 使ったことがない & 今のところ使う予定はない & 使いたい場面がない が一応書いておく。
nix flake show <flake-url>
任意の flake の outputs AttrSet のツリー構造をみることができる。
nix flake metadata <flake-url>
任意の flake の description を含んだ metadata を表示する。
TIPS
flake.nix を書く上でのちょっとした tips を紹介する。
デバッグ
nix repl で :lf <flake-url> すると repl 内に flake をロードできる。
# repl 起動
nix repl
# カレントディレクトリの flake をロード
nix-repl> :lf .
# 例1 (tab補完も効く)
nix-repl> outputs.packages
# 例2
nix-repl> inputs.hoge
forAllSystem, nixpkgsFor
複数の system をターゲットにした flake の場合、<attr>.<system>.<name> や nixpkgs について、いちいち同じものを手動で system 毎に書いていくのはかなりめんどくさい。
そんなときに便利な utility 関数の例を紹介する。
{
description = "hoge";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
};
outputs = { self, nixpkgs, ... }:
let
supportedSystems = [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ];
# Helper function to generate an attrset '{ x86_64-linux = f "x86_64-linux"; ... }'.
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
# Nixpkgs instantiated for supported system types.
nixpkgsFor = forAllSystems (system: import nixpkgs { inherit system; });
in
{
devShells = forAllSystems (system:
let
pkgs = nixpkgsFor.${system};
in
{
hello-shell = pkgs.mkShell {
buildInputs = [ pkgs.hello ];
shellHook = ''
echo "dev shell started"
'';
};
});
};
}
flake-utils
なお、上記のような utility 関数は需要がとても高いため、flake-utils という flake.nix を補助することに特化したライブラリが存在する。
このライブラリもまた flake であり、 lib として util 関数群が outputs に定義されているため
inputs で inputs.flake-utils.url = "github:numtide/flake-utils"; として、outputs で引数 flake-utils を使って flake-utils.lib.<関数名> のようにして使える。
オワリ
この辺で終わりにする。
書き始めたら結構長くなってしまったが書きたかったことは大体書けたと思う。
Nix Flakes の勘どころが伝わったら幸いです。