# Nix でHello, Worldをビルドする方法

Nix を使ってプログラムをビルドする方法のメモ書きです。

## 基本の流れ

ビルドの基本の流れを概観します。
まず、プロジェクトディレクトリの中に、`default.nix`ファイルを作成します。

```shell
> mkdir myproject && cd myproject

myproject> touch default.nix
```

次に、default.nix の中に`stdenv.mkDerivation`を書きます。
`mkDerivation`は Nix でパッケージをつくる時に使う関数です。

```nix:default.nix
with import <nixpkgs> {};
stdenv.mkDerivation { ... }
```

`default.nix`を書き終えたら`nix-build`コマンドを実行して、パッケージをビルドします。

```shell
myproject> nix-build
```

ビルドが終わると、プロジェクトディレクトリの中に`result`というフォルダができます。
この中にビルドしたバイナリなどが入ります。

## 最小サンプル

以下の C ソースコードをビルドします。

```c:hello.c
#include <stdio.h>

int main(int argc, char *argv[]) {
  printf("Hello, world\n"); 
}
```

`default.nix`はこのように書きます。

```nix:default.nix
with import <nixpkgs> {};
stdenv.mkDerivation {
  name = "hello";
  src = ./hello.c;
  buildCommand = ''
    gcc -Wall -O2 -o hello $src
    install -m555 -Dt $out/bin hello
  '';
}
```

この時点でプロジェクトの構成はこんな感じです

```shell
myproject> tree
.
├── default.nix
└── hello.c
```

れっつびるど

```shell
myproject> nix-build
these derivations will be built:
  /nix/store/l46b92csn5d9ics7zfs9yb51gb5n6y4r-hello.drv
building '/nix/store/l46b92csn5d9ics7zfs9yb51gb5n6y4r-hello.drv'...
/nix/store/0a3f6ddksy17jmfzasj2np999cgkrs8a-hello
```

ビルドすると`result`ディレクトリができました。

```shell
myproject> tree -l
.
├── default.nix
├── hello.c
└── result -> /nix/store/0a3f6ddksy17jmfzasj2np999cgkrs8a-hello
    └── bin
        └── hello
```

実際のところ、`result`は `nix/store/0a3f...-hello` へのシンボリックです。
バイナリを実行してみます。

```shell
myproject> result/bin/hello
Hello, world
```

うまく動きました。

## `default.nix`のに書かれていることを読む

`default.nix`でやっていることは、`stdenv.mkDerivation`に必要な引数を渡しているだけです。

`mkDerivation`の引数には、

- `name`
- `src`
- `buildCommand`

が渡されています。

`name`はパッケージの名前、`src`はビルドするソースコードです。
`buildCommand`では、ビルドに必要なシェルスクリプトを書きます。

`buildCommand`の中では、`$src`として引数のキー`src`に渡したパスを参照できます。
また、`$out`には、ビルドしたあとのパス(`/nix/store/0a3f...-hello`)が入ってます。

## `default.nix`のリファクタリング

`mkDerivation`の他の機能を見るために、さっきの`default.nix`を書き換えていきます。

まず、`name`の代わりに`pname`と`version`を使います。
こうすると、パッケージの名前が自動で`<pname>-<version>`という形式になります。

```nix:deafult.nix
with import <nixpkgs> {};
stdenv.mkDerivation {
  pname = "hello";
  version = "0.1.0";  
  src = ./hello.c;
  buildCommand = ...;
}
```

それから、`buildCommand`を`default.nix`に直書きするのをやめて、別ファイルにビルドスクリプトを書きます。

```bash:builder.sh
source $stdenv/setup

gcc -Wall -O2 -o hello $src
install -m555 -Dt $out/bin hello
```

1 行目の`source $stdenv/setup`は、Nix でのビルド環境をセットアップするために必要です。
ビルドスクリプトを書いたら、スクリプトのパスを`builder`に渡します。

```nix:default.nix
with import <nixpkgs> {};
stdenv.mkDerivation {
  pname = "hello";
  version = "0.1.0";  
  src = ./hello.c;
  builder = ./builder.sh;
}
```

れっつびるど

```shell
myproject> nix-build
these derivations will be built:
  /nix/store/j2vxh4xm6jarf0jnm9x3sywmr1nvxdhg-hello-0.1.0.drv
building '/nix/store/j2vxh4xm6jarf0jnm9x3sywmr1nvxdhg-hello-0.1.0.drv'...
/nix/store/i8l2rmiy708sz6n486yasgzshp044kpd-hello-0.1.0
```

default.nix を書き換えるとハッシュが変ったのがわかります。
さっきビルドしたものが上書きされるわけではなく、別ファイルに新しいビルドが作られます。
シンボリックを張りなおせば、いつでもさっきのビルドに戻せそうですね。

## ウェブからソースをとってきてビルドする

ローカルのソースコードを使うのはやめて、ウェブ上のソースコードをダウンロードしてビルドしてみます。
GNU/hello をビルドしてみましょう。プロジェクトのページは[こちら](https://www.gnu.org/software/hello/)です。

ソースをダウンロードするために`fetchzip`を使います。

```nix:default.nix
stdenv.mkDerivation {
　...
  src = fetchzip {
    url = "https://ftp.gnu.org/gnu/hello/hello-2.10.tar.gz";
    sha256 = "1im1gglfm4k10bh4mdaqzmx3lm3kivnsmxrvl6vyvmfqqzljq75l";  
  };
  ...
}
```

`fetchzip`は、ウェブから圧縮されたソースをダウンロードしてローカルに展開します。
引数には`url`だけでなく`sha256`などのハッシュが必要です。

ハッシュを計算するために、`nix-prefetch-url`コマンドを使います。
圧縮ファイルの場合は `--unpack`オプションが必要です。

```shell
myproject> nix-prefetch-url --unpack 'https://ftp.gnu.org/gnu/hello/hello-2.10.tar.gz'
unpacking...
[0.7 MiB DL]
path is '/nix/store/xsh1xgry2vsf6674jhc26q8k1bv4p76s-hello-2.10.tar.gz'
1im1gglfm4k10bh4mdaqzmx3lm3kivnsmxrvl6vyvmfqqzljq75l
```

GNU/hello は`./configure; make; make install`でビルドできます。

このような典型的なビルドのときは、`mkDerivation`がよしなにビルドしてくれるので、手で`builder.sh`を書く必要はありません。
`default.nix`の全体は以下のようになります。

```nix:default.nix
stdenv.mkDerivation rec {
  pname = "hello";
  version = "2.10";  
  src = fetchzip {
    url = "https://ftp.gnu.org/gnu/hello/${pname}-${version}.tar.gz";
    sha256 = "1im1gglfm4k10bh4mdaqzmx3lm3kivnsmxrvl6vyvmfqqzljq75l";  
  };
}
```

細かいことですが、Nix での文字列補完には`${}`を使います。`rec`も忘れないでください。

れっつびるど

```shell
myproject> ls
default.nix

myproject> nix-build
these derivations will be built:
  /nix/store/q64l9f6d29zivah3sqrxlhpw8wmz3jrk-source.drv
  /nix/store/936zpfpmzxliinc69a49yn4dh67pgj6x-hello-2.10.drv
...
```

以上、基本的なビルド方法でした。

Nix のビルドは特定の環境に依存しないので、`default.nix`一枚あればどの PC でも同じようにビルドできます。
ぶっちゃけこの安心感のためだけに Nix を使う価値があるんじゃないかと思います。

## より詳しい解説へのリンク

mkDerivation についてもっと詳しく知りたければ、マニュアルと Nix Pills が参考になります。

- [NixOS-Nixpkgs 20.03 manual - Part II. Standard environment](https://nixos.org/manual/nixpkgs/stable/#idm140737320651584)
- [Nix Pills](https://nixos.org/guides/nix-pills/)

`fetchzip`のほかにも、GitHub や GitLab からソースをダウンロードする関数も用意されています。
それらもマニュアルで解説されています。

- [NixOS-Nixpkgs 20.03 manual - Chapter 11. Fetchers](https://nixos.org/manual/nixpkgs/stable/#chap-pkgs-fetchers)

しかし、残念ながら、Nixのマニュアルはまだ発展途上です。従って、マニュアルには数多くの内容がまだ欠けています。
実際にプログラムを Nix でビルドするときは、Nixpkgs のリポジトリのソースがとても参考になります。

- https://github.com/NixOS/nixpkgs/tree/master/pkgs

また、`mkDerivation`を自在に扱うためには、`stdenv/setup`のソースにも目を通しておくといいです。

- https://github.com/NixOS/nixpkgs/blob/master/pkgs/stdenv/generic/setup.sh