要約

この記事では、Pythonでいうvirtualenv的なことをする方法について述べる。つまり、ツールを一時的に導入したり、ツールが実行可能な開発環境を整備したりする目的として、以下の話題を扱う。

  • 各種コマンド
    • nix shell
    • nix run
    • nix develop
  • direnv + nix-direnv
  • nix flake init にtemplateを指定する方法

なお本記事ではNixOS固有の話ではなく、(パッケージマネージャとしての)Nixを使う場合の話をする。使用するNixとhome-managerのバージョンは以下の通り。

1
2
3
4
~ $ nix --version
nix (Nix) 2.18.1
~ $ home-manager --version
24.05-pre

はじめに

Nixではユーザ環境にパッケージを入れるために、以下の2つのどちらかを使うはず(宣言的に管理できる後者がよりデファクトになっている気がする)。

これらは永続的にパッケージを導入する仕組みであるが、そうではなく一時的にパッケージを導入したいという場合があるだろう。具体的には、以下の2つの場合がありえる。

  • あるツールを使いたいが、別に永続的にそれを使う必要はない。試しに使ってみたい場合は、ある瞬間にそれが使えれば十分
  • virtualenvみたいに、開発時のみに特定のバージョンの開発ツールが導入されている状態であってほしい

前者の場合、nix shellnix runコマンドを用いる。後者の場合、nix developコマンドを用いる。

nix shellコマンド

nix shell コマンドを用いると、一時的にパッケージを導入して新しいshellに入ることができる。

以下のように使うと、パッケージを導入した状態で新しいshellに入る。パッケージは複数指定が可能。

1
nix shell (package name) (package name) ...

以下は、nix shell コマンドを用いてgccを使える状態にする例。

1
2
3
4
5
6
~ $ nix shell nixpkgs#gcc
~ $ gcc --version
gcc (GCC) 13.2.0
Copyright (C) 2023 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

ちなみに、1パッケージ内の1コマンドだけ実行したい場合で、わざわざ新しいshellを作る必要もない場合は、以下のように --command 引数を指定する。

1
2
3
4
5
~ $ nix shell nixpkgs#gcc --command gcc --version
gcc (GCC) 13.2.0
Copyright (C) 2023 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

nix runコマンド

nix runは上記の --command 付きの nix-shell の簡略版。

外部パッケージを手軽に実行する

1
2
3
4
5
~ $ nix run nixpkgs#gcc -- --version
gcc (GCC) 13.2.0
Copyright (C) 2023 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

上記では横棒 ----version の前に挟まれているが、これは nix run コマンドの引数と gcc の引数を区別するためのもの。

内部動作はDescriptionを見るとわかるが、パッケージの元となるderivationに特に何も設定されていなければ、そのパッケージ名と同じファイルを実行する。例えばパッケージ名が gcc の場合、 $out/bin/gcc を実行する。そのため、パッケージ名と実行ファイルが異なる場合はこのコマンドは使えない場合がある。

自分のflake.nixから使う

もちろん自分で書いた flake.nix に書いたパッケージについて nix run で実行することができるので、簡易的なシェルスクリプトを書いてコマンドとして定義したり、ある実行ファイルのエイリアスを作ったりできる。いわゆる npm run 的にコマンドが作れる。

やり方は、自分が作った flake.nix のoutputsの packages に指定する。以下のwriteShellScriptBinは、shellスクリプトを作成しそれを $out/bin/(第一引数で指定した名前) として出力するderivation(を返す関数)である。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
{
  description = "A very basic flake";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
  };

  outputs = { self, nixpkgs }: 
  let
    pkgs = nixpkgs.legacyPackages.x86_64-linux;
  in
  {
    packages.x86_64-linux.my-hello = pkgs.writeShellScriptBin "my-hello" ''
      echo "Hello!"
    '';
  };
}

実行例。

1
2
~/t/nix-develop-test $ nix run .#my-hello
Hello!

ちなみに実行ファイル名がパッケージ名と異なる場合は、以下のように apps に指定する。以下は、 my-hello を別名 hello-alias として実行したい場合の例。 program に実行ファイルへのパスを設定する。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
{
  description = "A very basic flake";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
  };

  outputs = { self, nixpkgs }: 
  let
    pkgs = nixpkgs.legacyPackages.x86_64-linux;
  in
  {
    packages.x86_64-linux.my-hello = pkgs.writeShellScriptBin "my-hello" ''
      echo "Hello!"
    '';
    apps.x86_64-linux.hello-alias = {
      type = "app";
      program = "${self.packages.x86_64-linux.my-hello}/bin/my-hello";
    };
  };
}

実行例。

1
2
~/t/nix-develop-test $ nix run .#hello-alias
Hello!

nix developコマンド

nix developは、ドキュメントを引用すると

nix develop - run a bash shell that provides the build environment of a derivation

である。つまりderivationをビルドするためのbashを提供するコマンドである。そのため、自分が開発したいものがderivationとして扱われることが前提にあるが、単に開発ツールや依存関係ををひとまとめにした環境を作る使い方もできる。

使い方

Flake output attributesに書いてある通り、 devShells というoutputにderivationを記述する。derivationにはnixpkgsのmkShellNoCCを使おう。以下では、

  • packages = ... でpythonを指定している
  • 環境変数 FOO を設定している
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
{
  description = "A very basic flake";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
  };

  outputs = { self, nixpkgs }: 
  let
    pkgs = nixpkgs.legacyPackages.x86_64-linux;
  in
  {
    devShells.x86_64-linux.default = pkgs.mkShellNoCC {
      packages = with pkgs; [
        python312
      ];
      FOO = "BAR";
    };
  };
}

nix develop コマンドを実行すると、bashに入り、

  • flake.nix に設定した python が実行できる
  • flake.nix に設定した環境変数が設定されている
1
2
3
4
5
6
7
~/t/nix-develop-test $ nix develop

[bombrary@nixos:~/tmp/nix-develop-test]$ python --version
Python 3.12.3

[bombrary@nixos:~/tmp/nix-develop-test]$ echo $FOO
BAR

余談

mkShellないしmkShellNoCCの動作について。

  • inputsFrom にderivationやパッケージを指定すると、それをstdenv.mkDerivationの各種buildInputsやnativeBuildInputsに波及させられる。詳細はmkShellのソースコードを読めばよい。例えばあるパッケージをビルドしたくて、その依存関係を全部備えた上でshellを作りたい場合に役立つかも
  • stdenv.mkDerivationないしそれをラップした関数( mkShell も含む)が前提で作られている模様。これは nix develop -vvvvnix derivation show で探ってみるとわかるが、以下のように stdenv に依存する記述があるからである
    • $name-env をビルドするときに、 get-env.sh がbuilderの引数に指定される
    • get-env.shを見ると、 $stdenv がもしあったら $stdenv/setup を実行する記述がある

bash起動までの内部動作

内部動作としてはNix 2.22.0のdevelop.ccを読むと、

  1. $name-env というderivationを作成、ビルドする
    • $name はderivation名で、mkShell の場合特に指定がなければ name=nix-shell となる(該当ソース)。
    • $name-env のoutputはJSON形式で、環境変数やパッケージなどの情報が詰まっている。これがおそらく、Exampleで言及されている profile のこと。
  2. $name-env のoutputからbashrcを生成する
  3. 2を引数にしてbashを起動する

となっていることが分かる。

(おまけ) nix-shell コマンドについて

nix-shellコマンドもまた、シェルを起動するが、こちらはnix developとほぼ同機能のコマンドである。目的についても、Descriptionに、

This is useful for reproducing the environment of a derivation for development.

と記載されている。

違いとしては、こちらはNix Flakeが開発される前に作成されたコマンドのため、flake.nix ではなく shell.nix にderivation定義を記述する点である。

bash以外のshellを使った開発環境が作りたい場合

nix developコマンドがbashしか対応していないのはなぜ?(考察)

nix develop コマンドのデフォルトのshellはbashである。ほかのshellをオプションで切り替えられるということはできない。理由として考えられるのは以下の2点。

  • shellはたくさんあるので、nix develop に全部対応させるのは現実的でない
  • ほとんどの場合bashがderivationをビルドするために使われている

まずNixは、あらゆるパッケージをderivaitonからビルドすることを前提として作られている。そして nix develop は、ビルドのインタラクティブなshellを提供するコマンドであった。

derivationを作るといった場合、builtinのderivationを使うことはあまりなく、多くの場合nixpkgsのstdenv.mkDerivationないしそれをラップしたものを用いることになる。そしてこの mkDerivation は、内部的にはbashスクリプトを実行してパッケージをビルドする。

derivationのほとんどがbashを用いる以上、 nix develop が用いるshellはbashが望ましい。実際、nix developのExampleにある configurePhasebuildPhaseinstallPhase などのコマンドは mkDerivation に含まれているbash関数であり、bashでないと動作しない。

ビルドの動作確認として nix develop を使うならbashが良いことが分かった。しかしそれ以外の場合、例えば、別にderivationをビルドする目的で使わず、bashの関数を使う必要もない場合は、別のshellを使いたいときもある。その場合、以下のやり方が考えられる。

(その1)nix developを用いる方法

nix develop コマンドは shellHook を、shell起動時に実行するように作られている(ドキュメントに記載はないものの、ほぼ同機能のnix-shellのドキュメントにはshellHookについて記載されている)。そのため、 shellHookexec (shell名) を指定すれば、好きなshellに入った状態にできる。例えば exec ${pkgs.fish}/bin/fish などとすればfishが使えるし、 exec $SHELL などとすればユーザが現在利用中のshellが使える。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
  description = "A very basic flake";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
  };

  outputs = { self, nixpkgs }: 
  let
    pkgs = nixpkgs.legacyPackages.x86_64-linux;
  in
  {
    devShells.x86_64-linux.default = pkgs.mkShellNoCC {
      packages = with pkgs; [
        python312
      ];
      shellHook = ''
        exec $SHELL
      '';
    };
  };
}

実行例。

1
2
3
4
~/t/nix-develop-test $ nix develop

[bombrary@nixos:~/tmp/nix-develop-test]$ python --version
Python 3.12.3

(その2)nix runを用いる方法

nix runをflake.nixから使うの応用。お好みのshellの実行ファイルをラップしたパッケージを作成する。

nixpkgsのrunCommandは、stdenv.mkDerivation よりもシンプルなderivaiton作成関数で、最後の引数に指定されたシェルスクリプトを実行することでoutputを作成するだけである。これを用いて、outputにfishへのsymlinkを張る。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
  description = "A very basic flake";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
  };

  outputs = { self, nixpkgs }: 
  let
    pkgs = nixpkgs.legacyPackages.x86_64-linux;
  in
  {
    packages.x86_64-linux.dev-shell = pkgs.runCommand "dev-shell" {
      packages = with pkgs; [
        python312
      ];
    } ''
     mkdir -p $out/bin/
     ln -s ${pkgs.fish}/bin/fish $out/bin/dev-shell
    '';
  };
}

ところがこの状態だと、素のfishのsymlinkを張っただけで、環境変数 PATH が何も設定されていない。

1
2
3
4
5
6
7
~/t/nix-develop-test $ nix run .#dev-shell
Welcome to fish, the friendly interactive shell
Type help for instructions on how to use fish

~/t/nix-develop-test $ python --version
DBI connect('dbname=/nix/var/nix/profiles/per-user/root/channels/nixos/programs.sqlite','',...) failed: unable to open database file at /run/current-system/sw/bin/command-not-found line 13.
cannot open database `/nix/var/nix/profiles/per-user/root/channels/nixos/programs.sqlite' at /run/current-system/sw/bin/command-not-found line 13.

そこで、以下のように、環境変数 PATH を付加したfishを作成する。

  • makeWrapperパッケージの wrapProgram 関数を使い、環境変数を設定した実行ファイルを新たに作成できる。引数についてはmake-wrapper.shのソースコードのが詳しいのでそちらを参照
  • LD_LIBRARY_PATH など、他に付けたい環境変数があれば、wrapProgram の引数に追加する
  • PATH:で実行ファイルのあるパスを文字列指定するが、そのような文字列を作成するユーティリティにlib.strings.makeBinPathがあるので、それを使っている
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
  description = "A very basic flake";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
  };

  outputs = { self, nixpkgs }: 
  let
    pkgs = nixpkgs.legacyPackages.x86_64-linux;
  in
  {
    packages.x86_64-linux.dev-shell =
    let
      packages = with pkgs; [
        python312
      ];
    in
    pkgs.runCommand "dev-shell" {
      packages = packages;
      nativeBuildInputs = [ pkgs.makeWrapper ];
    } ''
     mkdir -p $out/bin/
     ln -s ${pkgs.fish}/bin/fish $out/bin/dev-shell
     wrapProgram $out/bin/dev-shell --prefix PATH : ${pkgs.lib.strings.makeBinPath packages}
    '';
  };
}

実行例。

1
2
3
4
5
6
~/t/nix-develop-test $ nix run .#dev-shell
Welcome to fish, the friendly interactive shell
Type help for instructions on how to use fish

~/t/nix-develop-test $ python --version
Python 3.12.3

nix developを用いた方法とは違って、良くも悪くも環境変数の設定がまっさらな状態でスタートする。例えば ln -s ${pkgs.fish}/bin/fish $out/bin/dev-shellln -s $SHELL $out/bin/dev-shell のようにしても、shellのプロンプトなどの設定が引き継がれず思い通りにならない。その分、一からカスタマイズして開発環境を用意したい場合には有効かもしれない。

(その3)direnvとnix-direnvの利用

direnvnix-community/nix-direnvと組み合わせると、flake.nixdevShells が設定されたディレクトリに入ったときに、nix develop で実行された時と同じ環境変数が自動で設定される。別に nix develop を実行しているわけではないので、shellが切り替わることはない。

まずnix-direnvのinstallationにはいくつか方法が書いてあるが、

  • 今後継続的に使ってみるならhome-managerに指定する方法
  • お試しで使ってみるなら .envrc にソースを書き込む方法

を試してみるのがよいかもしれない。もちろん、nix-direnvだけでなくその大元であるdirenvの導入も必要なので注意。

ここではhome-managerの導入を試す。以下は home-manager の設定ファイルの追記例。

1
2
3
4
5
6
7
8
9
{pkgs, ...}: {
  # ...
  programs.direnv = {
    enable = true;
    enableBashIntegration = true; # see note on other shells below
    nix-direnv.enable = true;
  };
  # ...
}

flake.nixnix developコマンドのときと同じにする。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
{
  description = "A very basic flake";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
  };

  outputs = { self, nixpkgs }: 
  let
    pkgs = nixpkgs.legacyPackages.x86_64-linux;
  in
  {
    devShells.x86_64-linux.default = pkgs.mkShellNoCC {
      packages = with pkgs; [
        python312
      ];
      FOO = "BAR";
    };
  };
}

use flake と書かれた行を .envrc に追記して、 direnv allow コマンドを実行する。

1
2
3
4
5
6
7
8
~/t/nix-develop-test $ echo "use flake" >> .envrc
direnv: error /home/bombrary/tmp/nix-develop-test/.envrc is blocked. Run `direnv allow` to approve its content

~/t/nix-develop-test $ direnv allow
direnv: loading ~/tmp/nix-develop-test/.envrc
direnv: using flake
direnv: nix-direnv: renewed cache
direnv: export +CONFIG_SHELL +DETERMINISTIC_BUILD +FOO +HOST_PATH +IN_NIX_SHELL +NIX_BUILD_CORES +NIX_CFLAGS_COMPILE +NIX_ENFORCE_NO_NATIVE +NIX_LDFLAGS +NIX_STORE +PYTHONHASHSEED +PYTHONNOUSERSITE +PYTHONPATH +SOURCE_DATE_EPOCH +_PYTHON_HOST_PLATFORM +_PYTHON_SYSCONFIGDATA_NAME +__structuredAttrs +buildInputs +buildPhase +builder +cmakeFlags +configureFlags +depsBuildBuild +depsBuildBuildPropagated +depsBuildTarget +depsBuildTargetPropagated +depsHostHost +depsHostHostPropagated +depsTargetTarget +depsTargetTargetPropagated +doCheck +doInstallCheck +dontAddDisableDepTrack +mesonFlags +name +nativeBuildInputs +out +outputs +patches +phases +preferLocalBuild +propagatedBuildInputs +propagatedNativeBuildInputs +shell +shellHook +stdenv +strictDeps +system ~PATH ~XDG_DATA_DIRS

環境変数が効いていることが分かる。

1
2
3
4
5
~/t/nix-develop-test $ python --version
Python 3.12.3

~/t/nix-develop-test $ echo $FOO
BAR

ディレクトリを抜けると、環境変数が効かなくなったことが分かる。

1
2
3
4
~/tmp $ python --version
DBI connect('dbname=/nix/var/nix/profiles/per-user/root/channels/nixos/programs.sqlite','',...) failed: unable to open database file at /run/current-system/sw/bin/command-not-found line 13.
cannot open database `/nix/var/nix/profiles/per-user/root/channels/nixos/programs.sqlite' at /run/current-system/sw/bin/command-not-found line 13.
~/tmp $ echo $FOO

開発環境のtemplate化

いままで開発環境を flake.nix に書いてきたが、毎度毎度これを書くのは面倒である。そこで、flake.nix に限らずファイルやディレクトリのひな形をtemplateとして残しておいて、nix flake initの時にそこからコピーしてくるようにする仕組みがある。

自分で作る方法

具体的には、flakeのoutputsに templates 属性があるのでそれを指定する。

試しにtemplateを作って、それを用いて開発環境を用意しよう。~/flake-templates/ を作成し、以下のようにする。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
~/flake-templates $ nix run nixpkgs#tree
.
├── flake.nix
└── templates
    └── foo
        ├── flake.nix
        └── src
            └── main.py

4 directories, 4 files

./flake-templates/flake.nix を次のようにする。

  • templates.<name><name> のところは適当なものにする。
  • 他に、templateが展開されたときに時に文章を出力できる引数 welcomeText がある。詳細は nix flake initを参照。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
  description = "A very basic flake";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
  };

  outputs = { self, nixpkgs }: {
    templates.foo = {
      path = ./templates/foo;
      description = "bar";
    };
  };
}

この状態で、適当なディレクトリから nix flake init を実行する。このとき、 -t でtemplateを指定する。 <flake.nixへのパス>#<name> の形式。

1
2
3
4
5
~/t/foo $ nix flake init -t ~/flake-templates#foo .
warning: Git tree '/home/bombrary/flake-templates' is dirty
wrote: /home/bombrary/tmp/foo/flake.nix
wrote: /home/bombrary/tmp/foo/src/main.py
wrote: /home/bombrary/tmp/foo/src

ファイルやディレクトリが作成されていることがわかる。

1
2
3
4
5
6
7
~/t/foo $ nix run nixpkgs#tree
.
├── flake.nix
└── src
    └── main.py

2 directories, 2 files

外部のtemplateを利用する方法

nix flake init コマンドの -t 引数にはflakeへのリポジトリも指定可能なので、誰かが作った外部のtemplateをとってくることもできる。実はNixOS/templatesにはある程度の言語が揃っているので、ここからtemplateを引っ張ってきた後に、自分なりに整形すればよい。また、ほかのリポジトリを探したり、また自分でリポジトリを作って管理しても良し。

例えば、以下はNixOS:templates#pythonの例。どうやらpoetry向けのプロジェクトのようで、pythonディレクトリにあるものが展開されている。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
~/t/foo $ nix flake init -t github:NixOS/templates#python
wrote: /home/bombrary/tmp/foo/flake.nix
wrote: /home/bombrary/tmp/foo/poetry.lock
wrote: /home/bombrary/tmp/foo/pyproject.toml
wrote: /home/bombrary/tmp/foo/README.md
wrote: /home/bombrary/tmp/foo/sample_package/__init__.py
wrote: /home/bombrary/tmp/foo/sample_package/__main__.py
wrote: /home/bombrary/tmp/foo/sample_package


Getting started

      · Run nix develop
      · Run poetry run python -m sample_package

~/t/foo $ nix run nixpkgs#tree
.
├── flake.lock
├── flake.nix
├── poetry.lock
├── pyproject.toml
├── README.md
└── sample_package
    ├── __init__.py
    └── __main__.py

2 directories, 7 files

(おまけ)その他トピック

numtide/devshellを使ってみる

numtide/devshellは、nix develop での設定ファイルをNixではなくtomlで書くようにしたものっぽい。READMEによると、シンプルに開発環境を記述できることが目的のようだ。

READMEによるとまだunstable状態のようだが、試しにdocsのGetting startedをやってみる。flakesの場合、templateがあるようなのでそれを引っ張ってくる。

1
2
3
4
5
6
7
~/t/foo $ nix flake new -t "github:numtide/devshell" .
wrote: /home/bombrary/tmp/foo/flake.nix
wrote: /home/bombrary/tmp/foo/flake.lock
wrote: /home/bombrary/tmp/foo/devshell.toml
wrote: /home/bombrary/tmp/foo/shell.nix
wrote: /home/bombrary/tmp/foo/.gitignore
wrote: /home/bombrary/tmp/foo/.envrc

devshell.toml を次のようにする。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# https://numtide.github.io/devshell
[[commands]]
package = "python312"

[[env]]
name = "FOO"
value = "bar"

[[env]]
name = "BAZ"
value = "qux"

nix develop を起動すると、

  • 親切なメニューが出力され、python3が使えることを教えてくれる
  • 環境変数が設定されている
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
~/t/foo $ nix develop
🔨 Welcome to devshell

[general commands]

  menu    - prints this menu
  python3 - A high-level dynamically-typed programming language

[devshell]$ echo $FOO $BAZ
bar qux

続いて、新しいコマンドを追加してみる。 flake.nix を編集する。

  • my-hello を追加
  • nixpkgsをimportするところで、my-hello パッケージを追加するようにoverlayを指定
    • overlayをちゃんと説明しようとすると別記事が作れそうなので、ここでは省略する
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
  # ...
  outputs = { self, flake-utils, devshell, nixpkgs, ... }:
    flake-utils.lib.eachDefaultSystem (system: {
      devShells.default =
        let
          my-hello = nixpkgs.legacyPackages.${system}.writeShellScriptBin "my-hello" ''
            echo Hello
          '';
          pkgs = import nixpkgs {
            inherit system;

            overlays = [
              devshell.overlays.default
              (final: prev: { my-hello = my-hello; })
            ];
          };
        in
        pkgs.devshell.mkShellNoCC {
          imports = [ (pkgs.devshell.importTOML ./devshell.toml) ];
        };
    });
}

これで、nixpkgsにあたかも my-hello が入っているようにできた。そのため、以下のように devshells.toml に指定できる。

1
2
3
[[commands]]
package = "my-hello"
help = "print hello"

実行例。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
~/t/foo $ nix develop
🔨 Welcome to devshell

[general commands]

  menu     - prints this menu
  my-hello - print hello
  python3  - A high-level dynamically-typed programming language

[devshell]$ my-hello
Hello

参考