Nix Flakes の勘どころ

#Nix

#Nix Flakes

#NixOS

#Linux

#プログラミング

投稿日: 2022-08-14

前回の記事で予告した通り 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 AttrSetattribute 名は完全に任意で、値には flake references を指定する。(flake でないものも指定できる。)

いろいろな種類のものを指定でき、また書き方は複数ある。 ローカルのファイルだけでなくインターネット上の他の flake、 flake でないもの、git リポジトリ、tarball、GitHub や sourcehut のリポジトリなど。

ここで指定したものは fetch/eval された後に outputs の引数に渡される。(後述)

flake references の詳細はマニュアルにまとまっている。

flake references のドキュメント

inputs で特筆すべきは fetchgitfetchzip などの fetcher とは異なり、特定の revision の hash を明示的に指定しなくてもよいという点だ。

特定の revision を決める hash は flake 関連のコマンド群によって flake.lock というロックファイルに自動で書き込まれる。 (初回のコマンド実行時ににその時点の最新の revision が指定される。lock ファイルはコマンドを通じてアップデートしない限りはかわらない。)

inputs ついてはわりとマニュアルにまとまっているので詳細についてはマニュアルを見るのが良い。

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-managerhomeConfigurations.<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 のテンプレートリポジトリがある。

NixOS/templates

このリポジトリもまたそれ自体が flake として作られており、outputs に各テンプレートが定義されている。

nix flake show templates でテンプレのリストを見ることができ、nix flake init -t templates#テンプレ名 でその中のテンプレを使って新しい flake を作れる。

補足: flake-url については後で記載するが上記コマンドで templates がこの GitHub リポジトリの flake を指すようになっているのは、flake registry でそう定義されているため。 他によく使う可能性のありそうなのでいうと nixpkgshome-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 AttrSetpackage.<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 AttrSetapps attribute で指定されたプログラムのこと。

<flake-url>#<app-name> を渡すとその指定した app を run する。ここで <app-name> とは outputs AttrSetapps.<system>.<name><name> こと。

(apps だけでなく、packageslegacyPackages のほうが使われる可能性もあるがここでは省略する。詳細はマニュアルを。)

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> に書いたもの。

devShellsshell.nix の flake 版のような立ち位置で、nix developnix-shell の flake 版のようなもの。

nix develop の使いみちの例としては、packages の derivation の build environment に入ってデバッグしたり、あるいはその flake を使う・開発する上での development shell 環境を devShells で明示、提供したりなど。

ちなみに nix-direnvdevShells に対応している。

また 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 developnix 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 AttrSetnixosConfigurations.<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-utils

このライブラリもまた flake であり、 lib として util 関数群が outputs に定義されているため

inputs で inputs.flake-utils.url = "github:numtide/flake-utils"; として、outputs で引数 flake-utils を使って flake-utils.lib.<関数名> のようにして使える。


オワリ

この辺で終わりにする。

書き始めたら結構長くなってしまったが書きたかったことは大体書けたと思う。

Nix Flakes の勘どころが伝わったら幸いです。

Author

profile icon

тars (たーず)

tars0x9752

Japan, Tokyo

TypeScript

プログラミング, 音楽, Turntablism, Video Game, スノーボード, 味噌汁(特に赤味噌), 味噌煮込みうどん, 川魚(特に鮎)

Tags

#Contentful

#Domain

#Gandi

#Git

#GitHub

#KaTeX

#Linux

#Markdown

#Next.js

#Nix

#Nix Flakes

#NixOS

#OCaml

#PEG

#Phenyl

#React

#SVG

#Terminal

#Turntablism

#TypeScript

#VSCode

#Vercel

#WSL

#Windows

#Yarn

#Yoyo

#lsp

#npm

#sitemap

#アルゴリズム

#データ構造

#プログラミング

#携帯

#数学

#映画

#競技プログラミング

#雑記

#音楽