🍷

Nix初心者がZero to Nixをやってみた

2024/12/18に公開

この記事は Nix Advent Calendar 2024 17日目の記事になります

この記事は何?

最近Nixをはじめたばかりの筆者がZero to NixでNixに入門した記事です!

チュートリアルを一通りやったので、学習したことの整理という目的で記事を書きました。

Nixってなに?

Nixは純粋関数型パッケージマネージャといわれるパッケージマネージャの一つです。宣言的な記述で再現性の高い開発環境を提供することができます。

NixOSで使われているパッケージマネージャですが、WindowsやmacOS, 他のlinuxでも使うことができます。

また、一度実行するとキャッシュされて2回目以降は高速化するという特徴もあります。

より詳しい話はnix.orgNix入門をご参照ください!

なんでNixを触ろうと思ったのか

一言で言うと「純粋関数型パッケージマネージャ」という言葉に惹かれたからです。
あとはNixOSを触る過程で、これって普段の開発マシン(僕の場合はmacOS)でも使えるのでは?と思いました。

『再現可能な開発環境を提供する』という目的ならDockerがありますが、Dockerは仮想環境を立てる以上、どうしても重たくなってしまう印象があります。また、HDD容量も逼迫する印象です。

パッケージマネージャだけでこの問題が解決すれば、もっと手軽に再現可能な環境を提供できるのでは、というのがNixを触るモチベーションでした。

で、どうやって学べば良いの?

NixOSはNixOSで最強のLinuxデスクトップを作ろうを読みながら構築していたのですが、パッケージマネージャはどうやって勉強するのか悩んでました

そこで、Asahiさんにこんなアドバイスをいただきました。(ありがとうございます!!)
https://x.com/asa_high_ost/status/1860523309865353405

Zero to Nixが Nix初心者にはちょうどよさそうです!ということで、実際にZero to Nixでパッケージマネージャの世界に入門してみることにしました。

Zero to Nixをはじめてみる

Zero to Nixはさきほど説明した通り、Nix初心者向けのチュートリアルです。

Zero to Nixは以下のコンテンツで作られています。
Nixをインストールして実際に環境構築したり、flake化(後述します)したりといった流れになっています。

  1. Get Nix running on your system
  2. Run a program with Nix
  3. Explore Nix development environments
  4. Build a package using Nix
  5. Search for Nix packages
  6. Turn your project into a flake
  7. Uninstall Nix (if necessary)
  8. Learn more

それでは、一つ一つ見ていきましょう。

1. Get Nix running on your system

Nixのインストール方法です。
コマンド一発でインストールすることができます。

$ curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install

2. Run a program with Nix

実際にnixのコマンドを実行してみます。
サンプルだとこんな感じです(実際はカラーのもっとかわいい画像が出てきます)。

$ echo "Hello Nix" | nix run "https://flakehub.com/f/NixOS/nixpkgs/*#ponysay"
 ___________ 
< Hello Nix >
 ----------- 
   \                               
    \                              
     \                             
      \                        █   
    ▄▄▄▄▄  ▄▄▄                ███  
   ▀▄▀█▄█▄█▄▄▄▄▄              ████ 
    ▄▄██▄▄█▄▄█▄█            █▄██▄▄█
   ▄▄▄▄█▄█▄████▄▄          ██▄███▄▀
 ▄▄▄▄██▄▄▄▄████▄▄         ▄▄██▄▄▄▀ 
  ▀▄▄▄▄▄█▄██▄▄▄▀ ▄▄▄▄▄▄▄▄▄█▄▄▄▀▀   
    ▄█▄█▄▄▄▄▄▄█▄▄████████▄█        
    ▀  ██▄▄██▄███████▄█████        
       ████████████████████        
       ██▄██▄▄███▄██▄▄▄███▄▄▄      
        ▄▄▄▄▀██▄██▀▀▀▄▄▄█▄▄▄█▄▄    
      ▄▄▄▀▀ ▄▄██▀    ███    ▀▄▄▄   
   ▄▄▄▄▀   ▄▄▄▀     █▄▄▀      ███  
   ▀▀▀  ▄▄▄▄▄▀              ▄▄▄██  
       ▀▄▄▄▀                 ▀▀▀

このコマンドの実行内容は

  1. flakehubからnixpkgsのNixコードを取得して、ponysayのflake outputをターゲットにした
  2. ponysay をビルドして、Nix Storeに格納した
  3. ponysayパッケージからbin/ponysayを実行

という流れになっています。
(flakeについてはNix flakesを参照してください。)

このように、nix runコマンドを使うだけで、installしなくてもコマンドを実行することができます。

3. Explore Nix development environments

この節では、nix developを使って開発環境を構築する方法や、このコマンドが何をしているかについて解説しています。

ためしに、以下のコマンドを実行してみます。

$ nix develop "https://flakehub.com/f/DeterminateSystems/zero-to-nix/*#example"

シェルが切り替わるので、typeコマンドを使ってコマンドのパスを確認してみます。

bash-5.2$ type curl
curl is /nix/store/740pphyabn1dmrl58p26xdqfnfckbcir-curl-8.11.0-bin/bin/curl
bash-5.2$ type git
git is /nix/store/w7a374fz8ai3swihnynx4r7hgqsw9xvd-git-2.47.0/bin/git

かなり独特なパスが表示されますね。これは一体何なのでしょうか?

Nixは、再現性を高めるため、インストール先のパスにHashを使用するという特徴があります。

例えば上記のパスは、以下の様に分離することができます。

/nix/store/740pphyabn1dmrl58p26xdqfnfckbcir-curl-8.11.0-bin/bin/curl
---------- -------------------------------- --------------- --------
↑                          ↑                       ↑            ↑
[Nix store prefix]    [Hash path]           [package path]  [program path]

このHash pathが存在することによって、新しくパッケージをinstallしても独自のパスにinsallされ、他のpackageと衝突することが無くなるのです。

また、nix developは起動時に発火するshell hooksを設定することができます。

$ nix develop "github:DeterminateSystems/zero-to-nix#hook"
Congrats! You just triggered a shell hook for a Nix development environment.
Run "exit" to exit this environment.
Then run "nix develop github:DeterminateSystems/zero-to-nix#hook" again to re-trigger this hook.

環境変数も使えます

$ nix develop "https://flakehub.com/f/DeterminateSystems/zero-to-nix/*#example"
bash-5.2$ echo $FUNNY_JOKE
What kind of phone does a turtle use? A shell phone!

--commandを使えば、外部から直接コマンドを実行することもできます!

$ nix develop "https://flakehub.com/f/DeterminateSystems/zero-to-nix/*#example" --command curl https://example.com

<!doctype html>
<html>
<head>
    <title>Example Domain</title>
以下省略

一般的には、nix flake initでflakeファイルを作ってnix developを実行するのがセオリーだそうです

$ nix flake init --template "github:DeterminateSystems/zero-to-nix#haskell-dev"
wrote: /home/nix-haskell-dev/flake.lock
wrote: /home/nix_sandbox/nix-haskell-dev/flake.nix
$ ls
flake.lock   flake.nix
$ nix develop
bash-5.2$ type ghc
ghc is /nix/store/92cjsh3z5f8kn16djfi395fch0jm84gz-ghc-9.6.5/bin/ghc
bash-5.2$ ghci
GHCi, version 9.6.5: https://www.haskell.org/ghc/  :? for help
ghci> 

4. Build a package using Nix

この節では、Nixのビルド方法について解説しています。
ビルドには、リモートのNixpkgsからビルドする方法と、ローカルにflakeファイルを持ってきてビルドする方法の2つがあります。

リモートからビルドするにはnix buildを使います。

$ mkdir build-nix-package && cd build-nix-package
$ nix build "https://flakehub.com/f/NixOS/nixpkgs/*#bat"

buildしたファイルはresultとして配置されます。これはシンボリックリンクとして配置されます。

$ ls
 result

リンク元は先ほどと同じように、Nix特有のパスに配置されていることがわかりますね。

$ readlink result
/nix/store/xpvx99kslai1054085ys1fbmvpz5zv7y-bat-0.24.0

ちゃんと実行できることがわかりますね。

./result/bin/bat hoge.txt 
───────┬───────────────────────────────────────────────────────────────────────────────────────────────────────────────────
       │ File: hoge.txt
───────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   1   │ hoge
───────┴───────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Haskellのプロダクトもビルドできます。pandocはこんな感じです。

nix build "nixpkgs#pandoc"
./result/bin/pandoc --version
pandoc 3.1.11.1
Features: +server +lua
Scripting engine: Lua 5.4
User data directory: /home/.local/share/pandoc
Copyright (C) 2006-2023 John MacFarlane. Web: https://pandoc.org
This is free software; see the source for copying conditions. There is no
warranty, not even for merchantability or fitness for a particular purpose.

ローカルにflakeファイルを持ってきてビルドすることもできます。

$ mkdir nix-haskell-pkg && cd nix-haskell-pkg
$ nix flake init --template "github:DeterminateSystems/zero-to-nix#haskell-pkg"
wrote: /home/build-nix-package/nix-haskell-pkg/flake.lock
wrote: /home/build-nix-package/nix-haskell-pkg/stack.yaml
wrote: /home/build-nix-package/nix-haskell-pkg/zero-to-nix-haskell.cabal
wrote: /home/build-nix-package/nix-haskell-pkg/.gitignore
wrote: /home/build-nix-package/nix-haskell-pkg/flake.nix
wrote: /home/build-nix-package/nix-haskell-pkg/package.yaml
wrote: /home/build-nix-package/nix-haskell-pkg/src/Main.hs
wrote: /home/build-nix-package/nix-haskell-pkg/src

$ nix build

ローカルに持ってきたflakeファイルの中身はこんな感じです。

{
  description = "Haskell example flake for Zero to Nix";

  inputs = {
    nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/0.2405.*.tar.gz";
  };

  outputs = { self, nixpkgs }:
    let
      # Systems supported
      allSystems = [
        "x86_64-linux" # 64-bit Intel/AMD Linux
        "aarch64-linux" # 64-bit ARM Linux
        "x86_64-darwin" # 64-bit Intel macOS
        "aarch64-darwin" # 64-bit ARM macOS
      ];

      # Helper to provide system-specific attributes
      forAllSystems = f: nixpkgs.lib.genAttrs allSystems (system: f {
        pkgs = import nixpkgs { inherit system; };
      });
    in
    {
      packages = forAllSystems ({ pkgs }: {
        default = pkgs.haskellPackages.mkDerivation {
          pname = "zero-to-nix-haskell";
          version = "0.1.0";
          src = self;
          license = pkgs.lib.licenses.cc-by-sa-40;
          executableHaskellDepends = with pkgs.haskellPackages; [
            base
          ];
        };
      });
    };

}

ビルドしたパッケージを実行するとこんな感じです。

$ ./result/bin/zero-to-nix-haskell
Hello from inside a Haskell program built with Nix!

5. Search for Nix packages

nix searchを使うと、パッケージから語句を指定して検索することができます。
例えば、cargoという単語が含まれるパッケージは以下のように検索できます。

$ nix search "https://flakehub.com/f/NixOS/nixpkgs/*" cargo
* legacyPackages.x86_64-darwin.cargo (1.82.0)
  Downloads your Rust project's dependencies and builds your project

* legacyPackages.x86_64-darwin.cargo-about (0.6.4)
  Cargo plugin to generate list of all licenses for a crate

...

legacyPackagesっていう単語がでてきますが、これは古いという意味ではありません

Nixではnix flake showコマンドの高速化のために、legacyPackagesをつかってpackageを出力しています。
詳しくはこちらを参照してください。

nix flake showを使う方法もあります。

nix flake show "github:nix-community/nixpkgs-wayland"

github:nix-community/nixpkgs-wayland/31b60284243f8b077894371f1ab3753dc26653c7?narHash=sha256-EUiA7xmVzzc8BpGNltmtimACsK9%2BrM6w12ts3lPemII%3D
├───bundle: unknown
├───devShells
│   ├───aarch64-linux
│   │   └───default omitted (use '--all-systems' to show)
...

Nixpkgsではnix searchを、それ以外ではnix flake showを使うのが良い様です。

6. Turn your project into a flake

既存プロジェクトからflakeを生成する方法について解説しています。
flake化にはfhが便利です。

早速使ってみましょう。

$ nix run "https://flakehub.com/f/DeterminateSystems/fh/*" -- init

ビルドしたものを見てみましょう。

$ nix flake show
git+file:///home/fh-init-example-project
├───devShells
│   └───x86_64-darwin
│       └───default: development environment 'nix-shell'
└───schemas: unknown

ちなみに、fh initはプロジェクトルートのファイルをみて何のプロジェクトかを推論してくれます。

例えば、cargo.tomlがあればRustだと推測してプロンプトで質問を投げかけてくれます。

$ nix run "https://flakehub.com/f/DeterminateSystems/fh/*" -- init
Let's build a Nix flake!
> An optional description for your flake: 
> Which systems would you like to support? You selected 1 system: x86_64-darwin
> Which Nixpkgs version would you like to include? latest stable (currently 24.11)
> This seems to be a Rust project. Would you like to initialize your flake with some standard dependencies for Rust? Yes
...

ただし、この機能は今のところ環境構築のみしか使えません。(devShellしか対応していないため)つまりパッケージには使うことができないということです。

fh shellを使えばシェルを起動して使用することもできます。

$ nix shell "https://api.flakehub.com/f/DeterminateSystems/fh/*.tar.gz"
❯ fh --version
fh 0.1.21

7. Uninstall Nix (if necessary)

uninstallはコマンド一発でできます。

$ /nix/nix-installer uninstall

8. Learn more

もっと学習したい場合は、こちらに学習コンテンツがまとまっています。
個人的にはNixOSを触るのがいちばん良いと思います!

https://zero-to-nix.com/start/learn-more/

さいごに

駆け足ですが、Zero to Nixの内容をざっとまとめてみました!
これからもっと使いこなして業務でも活かせたら、と思います!

最後になりますが、Nixについて教えてくれたAsahiさん、記事を書く後押しをしてくれたtoybootさん、ありがとうございます!また色々教えてください!

参考

Discussion