nixで自前パッケージをビルドしてNixOSで使う

## 概要
[nixpkgs](https://github.com/NixOS/nixpkgs)には現在80,000を超えるパッケージが登録されていますが、ArchLinuxと比べて特定の分野でかつ利用者が少ないソフトやAsusやRazerなどベンダー固有の設定をするユーティリティなどはまだ十分とは言い難い状況です。
このとき自分でbuildしてpathを通すことになりますが、nixではbuild手順の提供とnixpkgs、NixOSを拡張できるテンプレートが用意されているため比較的簡単に自前パッケージを取り組むことができます。
ここでは、ビルド手順の自動化から[Flakes](https://nixos.wiki/wiki/Flakes)を用いるかつパッケージバージョンの自動追尾までを順に解説します。

## ToDO
- [ ] GitHub Action+nvfetcherでのパッケージ更新の自動追尾 (2022/10/29)
- [ ] pythonやrustのパッケージの拡張の仕方 (2022/10/29)
    - `buildPythonPackages`を使ってパッケージとして拡張するのでいい？
    - `pythonPackages`のoverlaysはできる？（[このnixpkgs(awscli2)](https://github.com/NixOS/nixpkgs/blob/c8204403c51604739dfb4a35059d4a43906ffce9/pkgs/tools/admin/awscli2/default.nix#L8)とか参考になる???)

## パッケージ作製前に
- `nix 2.7`で廃止されたSchemeが多くある。（2022/03/07以前の書き方だと警告が出る）
    - ここのコードは新しいSchemeで書いているため`nix 2.6`以下では動かない
    - [NixOS/Templates](https://github.com/NixOS/templates/tree/master)も更新されていなくて新旧の書き方が混在している
- Flakeを用いると
    - プロジェクトのテンプレートを提供する
    - 実行可能なプログラムであることを定義する
    - 外部のgitリポジトリから依存関係を取り組むことができる
    - パッケージのビルドと環境構築を同じリポジトリで管理できる
    - nixpkgs、NixOSの拡張を簡単にできる
        - [overlays](https://nixos.wiki/wiki/Overlays)
- nixは複数言語のためのビルドツール及び環境作製ツールである（[nixos.org](https://nixos.org/)でも例はどう環境をつくるかが多い）

- nixでパッケージを作るということは、1. どこかしらのリポジトリやダウンロードページからソースを取ってきて 2. 手順にそってビルドして 3. そのパッケージが入った環境に入る みたいな認識
    - NixOS（nixパッケージマネージャー）はFilesystem Hierarchy Standard(FHS)に対応していない（`/lib`, `/usr/bin`がない）
    - ビルド時に依存関係のパスを解決する必要がある→基本的に自動でやってくれる
        - まれにパスを自分で明示してやる必要がある（Tipsにて簡単に説明）

- パッケージが作れればdockerコンテナのように自前の環境を作ることに応用できる（逆も然り）

- `nix build`(Flake)を用いる方法だと自前のパッケージをモジュールとして組み込みやすい

- コードは[Sumi-Sumi/nix-making-package-tutorial](https://github.com/Sumi-Sumi/making-nix-package-tutorial)

## nixを用いたbuildおよびbuild環境の提供
### `nix-build`のための記法
- 書き方はnixpkgsのパッケージを見たほうがいい
    - （先頭を`{pkgs ? (import <nixpkgs>) {} }: with pkgs; stdenv.mkDerivation{...}`に置き換える）
-  リファレンスは[Nixpkgs Manual](https://nixos.org/manual/nixpkgs/stable/#sec-stdenv-phases)や[Nixpkgs Users and Contributors Guide](https://jtojnar.github.io/dumpling/)
-  [nix-tutorial](https://nix-tutorial.gitlabpages.inria.fr/nix-tutorial/index.html)もわかりやすい
-  AURにパッケージがある場合、そのPKGBUILDをnixで読み替えれば大体書ける。（patchはいるかもしれないしいらないかもしれない）
-  sha256はtarball→`nix-prefetch-url`、git→`nix-prefetch-git`で得られる

<details><summary>gnugrepのビルド</summary><div>

```nix
{ pkgs ? (import <nixpkgs> {}) }:
let 
  version = "3.7";
in
with pkgs; stdenv.mkDerivation {
  pname = "gnugrep";                      # パッケージ情報
  inherit version;

  src = fetchurl {                        # ソースコードの取得
    url = "mirror://gnu/grep/grep-${version}.tar.xz";
    sha256 = "0g42svbc1nq5bamxfj6x7320wli4dlj86padk0hwgbk04hqxl42w";
  };

  nativeBuildInputs = [ perl ];           # ビルド時に必要なもの（コンパイラとか）
  outputs = [ "out" "info" ];             # 分割ビルド、 $outはstore-path, $infoはmanとか
                                          # pkgs/build-support/setup-hooks/multiple-outputs.shや
                                          # Multiple-output packages: https://jtojnar.github.io/dumpling/#chap-multiple-outputを参照

  buildInputs = [ pcre libiconv ];        # コンパイラ以外で実行時に必要なもの（システムパッケージとしてインストールされる）

  doCheck = false;

  preConfigure = ''                       # ビルドは各Phasesがありその中でPrePhase, Phase, PostPhaseなどがある
    export MKDIR_P="mkdir -p"             # https://nixos.org/manual/nixpkgs/stable/#sec-stdenv-phases参照
  '';

  postInstall =
    ''
      rm $out/bin/egrep $out/bin/fgrep
      echo "#! /bin/sh" > $out/bin/egrep
      echo "exec $out/bin/grep -E \"\$@\"" >> $out/bin/egrep
      echo "#! /bin/sh" > $out/bin/fgrep
      echo "exec $out/bin/grep -F \"\$@\"" >> $out/bin/fgrep
      chmod +x $out/bin/egrep $out/bin/fgrep
    '';

  meta = with lib; {                      # meta情報、自前パッケージならhomepageとdescriptionとlicenseとplatformsがあればいい？
    homepage = "https://www.gnu.org/software/grep/";
    description = "GNU implementation of the Unix grep command";

    longDescription = ''
      The grep command searches one or more input files for lines
      containing a match to a specified pattern.  By default, grep
      prints the matching lines.
    '';

    license = licenses.gpl3Plus;

    platforms = platforms.all;
    mainProgram = "grep";
  };

  passthru = {inherit pcre;};
}
```
</div></details>


### Flakeを用いた書き方

- [flake-utils](https://github.com/numtide/flake-utils)を使うとアーキテクチャ管理がしやすくなる
- 基本的にビルド部分は[nix-buildで使える書き方](https://qiita.com/drafts/18038127ef54fe0bb8d1/edit)と同じ

#### [flake-utils](https://github.com/numtide/flake-utils)を使わない書き方
- `nix flake new --template templates/#c-hello ./c-hello`でプロジェクトを作る
- [NixOS/Templetes:c-hello](https://github.com/NixOS/templates/tree/master/go-hello)を`nix >= 2.7`の書き方に直した。

<details><summary>コードと結果</summary><div>

```nix
{
  description = "Sample no using flake-utils";

  # Nixpkgs / NixOS version to use.
  inputs.nixpkgs.url = "nixpkgs/nixos-22.05";

  outputs = { self, nixpkgs }:
    let

      # to work with older version of flakes
      lastModifiedDate = self.lastModifiedDate or self.lastModified or "19700101";

      # Generate a user-friendly version number.
      version = builtins.substring 0 8 lastModifiedDate;

      # System types to support.
      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
    {
      packages = forAllSystems (system:
      let
        pkgs = nixpkgsFor.${system};
      in
      with pkgs; {
        hello = stdenv.mkDerivation rec {
          name = "hello-${version}";
          src = ./.;

          nativeBuildInputs = [ autoreconfHook ];
        };
      });
    };
}
```

```shell
$ nix flake show
path:/home/<name/Templates/samplepkgs/c-hello?lastModified=1666067866&narHash=sha256-2x+v9Wl9PjeeraiBChPiSdfD89cIkbLHsSYJv4Udgpw=
└───packages
    ├───aarch64-darwin
    │   └───hello: package 'hello-20221018'
    ├───aarch64-linux
    │   └───hello: package 'hello-20221018'
    ├───x86_64-darwin
    │   └───hello: package 'hello-20221018'
    └───x86_64-linux
        └───hello: package 'hello-20221018'
```
</div></details>

#### 複数パッケージある場合にデフォルトのパッケージを設定
- 指定なしで`nix build`や`nix shell`を実行した際にビルドされる

<details><summary>コードと結果</summary><div>

- **`rec`をつけて再帰的に変数を呼び出す**
    - 自前パッケージは`<nixpkgs>`にないため、変数定義がされてないとなってエラーとなる

```nix
{
  description = "Multi-package sample";

  # Nixpkgs / NixOS version to use.
  inputs.nixpkgs.url = "nixpkgs/nixos-22.05";

  outputs = { self, nixpkgs }:
    let

      # to work with older version of flakes
      lastModifiedDate = self.lastModifiedDate or self.lastModified or "19700101";

      # Generate a user-friendly version number.
      version = builtins.substring 0 8 lastModifiedDate;

      # System types to support.
      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
    {
      packages = forAllSystems (system:
        let
          pkgs = nixpkgsFor.${system};
        in
        with pkgs; rec {        # 再帰的に変数を呼び出す
          c-hello = stdenv.mkDerivation {
            name = "c-hello-${version}";
            src = ./c-hello;

            nativeBuildInputs = [ autoreconfHook ];
          };
          go-hello = buildGoModule {
            pname = "go-hello";
            inherit version;
            src = ./go-hello;
            vendorSha256 = "sha256-pQpattmS9VmO3ZIQUFn66az8GSmB4IvYhTTCFn6SUmo=";
          };
          default = c-hello;   # デフォルトパッケージの定義
        }
      );
    };
}jj
```

```shell
$ nix flake show
# 各アーキテクチャーでdefaultが定義されている
path:/home/<name>/Templates/samplepkgs/sample-flakes?lastModified=1666072389&narHash=sha256-WcPrywIo3N596bZ1A0ZpoIcKnGIaogAsnP%2fxcuHzdhY=
└───packages
    ├───aarch64-darwin
    │   ├───c-hello: package 'c-hello-20221018'
    │   ├───default: package 'c-hello-20221018'
    │   └───go-hello: package 'go-hello-20221018'
    ├───aarch64-linux
    │   ├───c-hello: package 'c-hello-20221018'
    │   ├───default: package 'c-hello-20221018'
    │   └───go-hello: package 'go-hello-20221018'
    ├───x86_64-darwin
    │   ├───c-hello: package 'c-hello-20221018'
    │   ├───default: package 'c-hello-20221018'
    │   └───go-hello: package 'go-hello-20221018'
    └───x86_64-linux
        ├───c-hello: package 'c-hello-20221018'
        ├───default: package 'c-hello-20221018'
        └───go-hello: package 'go-hello-20221018'
```
</div></details>

### [flake-utils](https://github.com/numtide/flake-utils)を使った方法
- apps周りなどちょっとスッキリ書ける
- アーキテクチャ・システムを指定しなくても主要なものは勝手に入れてくれる
    - `flake-utils.lib.eachDefaultPackages ["x86_64-linux" "aarch64-linux"]`のように指定することも可能

<details><summary>コードと結果</summary><div>

```nix
{
  description = "A sample with flake-utils";

  # Nixpkgs / NixOS version to use.
  inputs.nixpkgs.url = "nixpkgs/nixos-22.05";
  # Using flake-utils
  inputs.flake-utils.url = "github:numtide/flake-utils";

  outputs = { self, nixpkgs, flake-utils }:
  let
    # to work with older version of flakes
    lastModifiedDate = self.lastModifiedDate or self.lastModified or "19700101";

    # Generate a user-friendly version number.
    version = builtins.substring 0 8 lastModifiedDate;
  in
    flake-utils.lib.eachDefaultSystem (system:
    # 特定のシステムのみをサポートすることも可能
    # flake-utils.lib.eachSystem ["x86_64-linux" "aarch64-linux"] (system: 
      let pkgs = nixpkgs.legacyPackages.${system}; in
        with pkgs; {
          packages = flake-utils.lib.flattenTree rec{  # Flakesはネストされたパッケージは見えないのでそれの解消
            c-hello = stdenv.mkDerivation {
              name = "c-hello-${version}";
              src = ./c-hello;
              nativeBuildInputs = [ autoreconfHook ];
            };
            go-hello = buildGoModule {
              pname = "go-hello";
              inherit version;
              src = ./go-hello;
              vendorSha256 = "sha256-pQpattmS9VmO3ZIQUFn66az8GSmB4IvYhTTCFn6SUmo=";
            };
            default = c-hello;
          };
          apps = rec {
            c-hello = flake-utils.lib.mkApp { drv = packages.c-hello; };
            go-hello = flake-utils.lib.mkApp { drv = packages.go-hello; };
            default = c-hello;
          };
        }
  );
}
```


```shell
$ nix flake show
path:/home/<name>/Templates/samplepkgs/flake-templates/sample-with-flake-utils?lastModified=1666083014&narHash=sha256-dVeym+Lp4EgaVMzlXtmXqX61zSKoNbAkCvzX1Avwwdw=
├───apps
│   ├───aarch64-darwin
│   │   ├───c-hello: app
│   │   ├───default: app
│   │   └───go-hello: app
│   ├───aarch64-linux
│   │   ├───c-hello: app
│   │   ├───default: app
│   │   └───go-hello: app
│   ├───i686-linux
│   │   ├───c-hello: app
│   │   ├───default: app
│   │   └───go-hello: app
│   ├───x86_64-darwin
│   │   ├───c-hello: app
│   │   ├───default: app
│   │   └───go-hello: app
│   └───x86_64-linux
│       ├───c-hello: app
│       ├───default: app
│       └───go-hello: app
└───packages
    ├───aarch64-darwin
    │   ├───c-hello: package 'c-hello-20221018'
    │   ├───default: package 'c-hello-20221018'
    │   └───go-hello: package 'go-hello-20221018'
    ├───aarch64-linux
    │   ├───c-hello: package 'c-hello-20221018'
    │   ├───default: package 'c-hello-20221018'
    │   └───go-hello: package 'go-hello-20221018'
    ├───i686-linux
    │   ├───c-hello: package 'c-hello-20221018'
    │   ├───default: package 'c-hello-20221018'
    │   └───go-hello: package 'go-hello-20221018'
    ├───x86_64-darwin
    │   ├───c-hello: package 'c-hello-20221018'
    │   ├───default: package 'c-hello-20221018'
    │   └───go-hello: package 'go-hello-20221018'
    └───x86_64-linux
        ├───c-hello: package 'c-hello-20221018'
        ├───default: package 'c-hello-20221018'
        └───go-hello: package 'go-hello-20221018'
```
</div></details>

## Overlays可能なパッケージの作製
- ビルドを定義したファイルに`passthru = {runnable = (true or false);}`を追記することで`nix run .#<package>`で実行できるように追加してくれる
    - `passthru`はビルド時には無視される任意の属性を定義できる。（meta情報の付与、ラベル付といった用途でつける）
- ディレクトリ構造は以下を想定
    - packages/\<package\>/default.nixは各パッケージのビルド手順を定義
    - sources/sources.nixはパッケージバージョンやハッシュ、提供元を定義

```shell
.
├── flake.lock
├── flake.nix
├── packages
│   ├── c-hello
│   │   └── default.nix
│   └── go-hello
│       └── default.nix
└── sources
    └── sources.nix
```

<details><summary>コードと結果</summary><div>

- flake.nix

```nix
{
  description = "A sample with flake-utils";

  # Nixpkgs / NixOS version to use.
  inputs.nixpkgs.url = "nixpkgs/nixos-unstable";
  # Using flake-utils
  inputs.flake-utils.url = "github:numtide/flake-utils";

  outputs = { self, nixpkgs, flake-utils } @ inputs:
    let
      lib = nixpkgs.lib;
      genPkg = func: name: {      # 別ファイルに記されたビルド定義とバッケージ情報などをまとめる
        inherit name;
        value = func name;
      };
      genApp = pkgs: name: {
        inherit name;
        value = flake-utils.lib.mkApp { drv = pkgs.${name}; };
        };
      # default.nixの属性にpassthru = { runnable = true }を追記するとnix runで実行できるように自動でappsに追加する
      isRunableApp = pkgs: name: if pkgs.${name}.passthru.runnable or false then name else null;
      runnableApps = pkgs: lib.remove null (map (isRunableApp pkgs) names);

      pkgDir = ./packages;        # ビルと手順が記されたパッケージディレクトリ
      sources = import ./sources/sources.nix;  # パッケージ情報が記されたディレクトリ

      ls = builtins.readDir pkgDir;
      names = with builtins; lib.remove null (map ((ts: t: if ts.${t} == "directory" then t else null) ls) (attrNames ls));  # ディレクトリのみを列挙
      withContents = func: with builtins; listToAttrs (map (genPkg func) names);

      mkApps = pkgs: appNames: with builtins; listToAttrs (map (genApp pkgs) appNames);
    in
      { # Overlaysの定義
        overlays.default = final: prev:      # final: prev:はpythonでいうself, super
        let
          sources' = sources {};
        in withContents (name:
          let
            pkgs = import (pkgDir + "/${name}");
            override = builtins.intersectAttrs (builtins.functionArgs pkgs) ({
              pythonPackages = final.python3.pkgs;
              mySource = sources'.${name};
            });
          in final.callPackage pkgs override
          ) // { sources = sources'; };
      } # ここから先はnix buildやnix runで使うための設定（このリポジトリ単体でも使えるようにする）
      // flake-utils.lib.eachSystem ["x86_64-linux" "aarch64-linux"] (system:
        let
          pkgs = import nixpkgs {
            system = "${system}";
            overlays = [ self.overlays.default ];
            config.allowUnfree = true;
          };
        in with pkgs.legacyPackages.${system}; rec {
          packages =  withContents (name: pkgs.${name});
          apps = mkApps pkgs (runnableApps pkgs);
        }
      );
}
```

```shell
├───apps
│   ├───aarch64-linux
│   └───x86_64-linux
├───overlays
│   └───default: Nixpkgs overlay
└───packages
    ├───aarch64-linux
    │   ├───c-hello: package 'c-hello-20221020'
    │   └───go-hello: package 'go-hello-20221020'
    └───x86_64-linux
        ├───c-hello: package 'c-hello-20221020'
        └───go-hello: package 'go-hello-20221020'
```
</div></details>

## [nvfetcher](https://github.com/berberman/nvfetcher)を用いて独自パッケージの自動追跡をする
- nvfetcherは独自パッケージの更新を自動追跡してくれるツール
    - 自動追跡は[GitHub Actions](https://docs.github.com/ja/actions)を使うといい
    - GitHubのタグやブランチだけでなく、[AUR](https://aur.archlinux.org/)やweb・http header+正規表現を用いてバージョンを自動で取得してくれる
- TOMLでパッケージのソース元、`nix run`で実行可能かなどを定義することができる
    - `nvfetcher -c "/path/to/toml-file"`で`_sources/`以下に生成される
    
- これまでの解説はこれをするための布石だった。

<details><summary>コードと結果</summary><div>

- flake.nix

```nix
{
  description = "A sample with flake-utils";

  inputs = {
    nixpkgs.url = "nixpkgs/nixos-unstable";
    flake-utils.url = "github:numtide/flake-utils";
    nvfetcher = {
      url = "github:berberman/nvfetcher";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = { self, nixpkgs, flake-utils, nvfetcher } @ inputs:
    let
      lib = nixpkgs.lib;
      genPkg = func: name: {      # 別ファイルに記されたビルド定義とバッケージ情報などをまとめる
        inherit name;
        value = func name;
      };
      genApp = pkgs: name: {
        inherit name;
        value = flake-utils.lib.mkApp { drv = pkgs.${name}; };
        };
      # default.nixの属性にpassthru = { runnable = true }を追記するとnix runで実行できるように自動でappsに追加する
      isRunableApp = pkgs: name: if pkgs.${name}.passthru.runnable or false then name else null;
      runnableApps = pkgs: lib.remove null (map (isRunableApp pkgs) names);

      pkgDir = ./packages;        # ビルと手順が記されたパッケージディレクトリ
      sources = import ./_sources/generated.nix;  # パッケージ情報が記されたディレクトリ
      broken = import ./packages/broken.nix;  # ビルドできないパッケージを列挙

      ls = builtins.readDir pkgDir;
      isDir = ts: t: if ts.${t} == "directory" then t else null; # ディレクトリのみを列挙
      names = with builtins; lib.subtractLists broken (lib.remove null (map (isDir ls) (attrNames ls)));
      withContents = func: with builtins; listToAttrs (map (genPkg func) names);

      mkApps = pkgs: appNames: with builtins; listToAttrs (map (genApp pkgs) appNames);
    in
      { # Overlaysの定義
        overlays.default = final: prev:      # final: prev:はpythonでいうself, super
        let
          sources' = sources { inherit (final) fetchgit fetchurl fetchFromGitHub ; };
        in withContents (name:
          let
            pkgs = import (pkgDir + "/${name}");
            override = builtins.intersectAttrs (builtins.functionArgs pkgs) ({
              pythonPackages = final.python3.pkgs;
              mySource = sources'.${name};
            });
          in final.callPackage pkgs override
          ) // { sources = sources'; };
      } # ここから先はnix buildやnix runで使うための設定（このリポジトリ単体でも使えるようにする）
      // flake-utils.lib.eachSystem ["x86_64-linux" "aarch64-linux"] (system:
        let
          pkgs = import nixpkgs {
            system = "${system}";
            overlays = [ self.overlays.default nvfetcher.overlay];  # nvfetcherもoverlayする
            config.allowUnfree = true;
          };
        in with pkgs.legacyPackages.${system}; rec {
          packages =  withContents (name: pkgs.${name});
          apps = mkApps pkgs (runnableApps pkgs);
          checks = packages;  # For `nix flake check`
          devShells.default = nvfetcher.packages.${system}.ghcWithNvfetcher;  # For `nix develop`
        }
      );
}
```

- packages.toml

```toml
[c-hello]
src.manual = "2.12.1"
fetch.url = "mirror://gnu/hello/hello-2.12.1.tar.gz"
```

</div></details>

## Tips

- `stdenv.mkDerivation`はネットワーク接続のない環境でビルドを行う
    - あくまでソースのみを用いてビルドする場合に使える
    - `cargo`や`pip`が使えない
- `buildInputs`はビルド時に必要なコマンドを提供する依存関係を示す（gccやcargoなど）
- `nativeBuildInputs`はビルド時にコマンドとしては必要ないが、libなどにパスを通さなければいけない依存関係を示す。
    - 一般的な依存関係
- nixpkgsは各々のパッケージが独立している
    - そのパッケージを実行するのに必要なPATH（環境）しか持たない
    - ホストのターミナルではPATHが通ってコマンドも実行できるのに、あるプログラムを実行するとPATHが通っていないと怒られるというのが多々ある
- ビルド時に依存関係のパスは勝手に解釈してくれるが、**シェルスクリプト**や**プログラムの中からshellを叩く**（pythonのsubprocessなど）場合は解釈してくれない
    - この場合、実行スクリプトに`wrapProgram`を適用してPATHを通さなければならない
    - 例
    ```nix
    ...
    postFixup = ''
        wrapProgram $out/bin/hoge \  # プログラムはシェルスクリプトでもバイナリでもいい
            --prefix PATH : ${lib.makeBinPATH [ foo ]} \  # バイナリへのパスを通す
            --prefix LD_LIBRARY_PATH : ${lib.makeLibraryPATH [ bar ]  # ライブラリへのパスを通す
    ''
    ...
    ```

- `cargo`や`pip`を用いて依存関係を得る場合はそれぞれ、`rustPlatform.buildRustPackage`、`pythonPackages.buildPythonPackage`を使用する
    - [nixpkgs](https://nixos.org/learn.html)のマニュアルに各言語のビルドの仕方が載っている

## 参考
- https://github.com/NixOS/templates
- https://github.com/numtide/flake-utils
- https://github.com/berberman/nvfetcher
- https://nixos.org/manual/nix/stable/language/builtins.html