前回の記事で予告した通り 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 の勘どころが伝わったら幸いです。