Chapter 18

 §2. Fetcher

Fetcherのしくみ

Fetcherは再現性を損わずにインターネットからリソースを取得する仕組みです。FetcherはNix言語の関数として提供されており、用途ごとに様々な種類がありますが、その全てがハッシュ値を引数にとります。Fetcher実行時、Nixは事前に指定されたハッシュと取得したリソースから算出したハッシュが一致するか検証します。もし、一致しなかった場合は例外を投げてNix式の評価を終了します。シンプルですが確実に冪等性を保証できます。

様々なFetcher

ビルトインのFetcher

Nix言語の組み込み関数としていくつかのfetcherが搭載されています。ここではfetchGitfetchTarballを紹介します。

fetchGit

その名の通りGitリポジトリを取得するfetcherです。試しにNixOS/nixを取得してみましょう。執筆時点で最新のコミットハッシュを指定しておきます。

fetchGitDemo.nix
builtins.fetchGit {
  url = "https://github.com/NixOS/nix";
  ref = "master"; # ブランチ
  rev = "59def6c23b6d5173cc07990cf4d17d5a3ee1bddc"; # コミットハッシュ
}
fetchGitDemo.nixの評価結果
$ nix eval --file ./fetchGitDemo.nix
# ↓実際は1行だが見やすさのため表示を改変
{
  lastModified = 1723483316;
  lastModifiedDate = "20240812172156";
  narHash = "sha256-3D7e6g4doWtOXHleD1n555nw411bAu0rSSpsUWp8ti4=";
  outPath = "/nix/store/dpinwg1p2kynwji4hlvk7jqyv9zhyi8s-source";
  rev = "59def6c23b6d5173cc07990cf4d17d5a3ee1bddc";
  revCount = 18115;
  shortRev = "59def6c";
  submodules = false;
}

リポジトリを取得できました。
fetchGitはAttrSetを返します。outPathが取得したリポジトリが配置されたストアパスで、narHashはNix独自のアーカイブフォーマットであるNARのハッシュです。その他はGitリポジトリのメタデータです。
このAttrSetをmkDerivationのsrcに渡せばそのままビルドに使用できます。

fetchTarball

Tarball(.tar.gzなど)を取得します。先程はGitリポジトリとして取得しましたが、今回はGitHubのreleaseからtarballを取得してみましょう。

fetchTarballはtarballのハッシュを指定する必要があるのですが、ハッシュがまだ分からないので空文字列を入れておきます。

fetchTarballDemo.nix
builtins.fetchTarball {
  url = "https://github.com/NixOS/nix/archive/refs/tags/2.24.2.tar.gz";
  sha256 = "";
}
fetchTarballDemo.nixの評価とエラー
$ nix eval --file ./fetchTarballDemo.nix
error:
       … while evaluating the file '/home/asahi/Anything/nix-src/fetchTarballDemo.nix':while calling the 'fetchTarball' builtin

         at /home/asahi/Anything/nix-src/fetchTarballDemo.nix:1:1:

            1| builtins.fetchTarball {
             | ^
            2|   url = "https://github.com/NixOS/nix/archive/refs/tags/2.24.2.tar.gz";

       error: hash mismatch in file downloaded from 'https://github.com/NixOS/nix/archive/refs/tags/2.24.2.tar.gz':
         specified: sha256:0000000000000000000000000000000000000000000000000000
         got:       sha256:063yg69fx8s27q1zjihss0zci4744scj0cnf460yg11nn7kkzvlx

sha256に空文字列を指定すると代わりにsha256:0000000000000000000000000000000000000000000000000000というハッシュで検証が行われます。もちろん一致しないので失敗しますが、そのとき正しいハッシュも表示されます。正しいハッシュを入れてもう一度評価しましょう。

fetchTarballDemo.nix
builtins.fetchTarball {
  url = "https://github.com/NixOS/nix/archive/refs/tags/2.24.2.tar.gz";
- sha256 = "";
+ sha256 = "sha256:063yg69fx8s27q1zjihss0zci4744scj0cnf460yg11nn7kkzvlx";
}
もう一度fetchTarballDemo.nixを評価
$ nix eval --file ./fetchTarballDemo.nix
"/nix/store/11ny982v57jfyrm4q4iv2y3i4i76s2zk-source"

ストアパスが表示されました。fetchTarballは取得したtarballを展開するので、ストアパスの中身はディレクトリになっています。

Nixpkgsが提供するFetcher

色々なfetcherが提供されているので、それぞれの用途に合ったものを使いましょう。

fetchFromGitHub

最も広く使われているfetcherです。GitHubからソースコードを取得します。

revにはreleaseのtag名を指定します。hashはコミットハッシュではなく、取得したアーカイブのハッシュです。

fetchGitDemo.nix
fetchFromGitHub {
  owner = "NixOS";
  repo = "nix";
  rev = "2.24.2";
  hash = "sha256-ne4/57E2hOeBIc4yIJkm5JDIPtAaRvkDPkKj7pJ5fhg=";
};

これと似たfetcherとしてfetchFromGitLabがあります。

プログラミング言語固有のFetcher

現代的なプログラミング言語の多くは、独自の固有のパッケージマネージャを持っているので、fetchurlfetchFromGitHubのように簡単にはいきません。この問題に対するアプローチとして主に以下の方法があります。

1. ロックファイルを解析してfetcherに変換

主流の手法です。現代的なパッケージマネージャは、依存関係を記述したファイルとロックファイルを持っているため、これらを解析することでfetcherに変換できます。

Rustのパッケージマネージャ・Cargoの場合、Cargo.lockにパッケージの場所とチェックサムが記述されているので、これを読み取ってfetcherに変換することができます。

Rustだけでなく、様々なプログラミング言語に対応した専用のfetcherがNixpkgsから提供されています。

2. コード生成

ツールを利用してfetcherが記述されたNixファイルを生成し、それを評価するというタイプです。1の手法がNix言語内部で完結するのに対し、この手法はコード生成を行う外部ツールが必要です。

このタイプのツールとしてzon2nixがあります。これはZigの依存関係を定義するbuild.zig.zonを解析し、対応するfetcherが記述されたNixファイルを生成します。

3. 手動でfetcherを記述

Node.jsのnpmやPythonのpipではよくある話ですが、一度パッケージをインストールした後、そのパッケージ独自のインストール処理を行う場合があります。これはfetcherの自動生成では対応できないので、手動でfetcherを記述する必要があります。

Flakes as Fetcher

実は、Flakesをfetcherとして利用することができます。Flakeのinputsにflake = falseというオプションをつけることで、Flake以外のものを取得することができます。

flake.nix
{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
    flake-utils.url = "github:numtide/flake-utils";
    gnu-hello-src = {
      url = "https://ftp.gnu.org/gnu/hello/hello-2.12.tar.gz";
      flake = false;
    };
  };

  outputs =
    {
      nixpkgs,
      flake-utils,
      gnu-hello-src,
      ...
    }:
    flake-utils.lib.eachDefaultSystem (
      system:
      let
        pkgs = nixpkgs.legacyPackages.${system};
      in
      {
        packages = {
          hello = pkgs.stdenv.mkDerivation {
            pname = "hello";
            version = "2.12";
            src = gnu-hello-src;
          };
        };
      }
    );
}

GNU Helloのソースコードのtarballをinputsに指定し、それをそのままmkDerivationのsrcに渡しています。

問題なくビルドすることができます。

ビルド & 実行
$ nix run .#hello
Hello, world!

今回はアーカイブのURLを指定しましたが、github:owner/repoのようにGitHubリポジトリを指定することも可能で、その場合はfetchFromGitHubと同じようにソースコードが取得されます。

通常のfetcherとの大きな違いは、自分でハッシュを記述することなくflake.lockで自動で管理できることです。初回取得時はflake.lockが自動生成され、更新するときもnix flake updateだけで済みます。

デメリットとして、Flakeを評価すると必ず全てのinputsが取得されてしまうことが挙げられます。Fetcherは通常のNix式として評価されるので、遅延評価により必要になるまで実行されませんが、Flakeは一度評価されるとバージョンロックを行うために全てのinputsを取得してしまうので、不要なリソースを取得してしまう可能性があります。

Fetcherの自動生成

nvfetcherは設定ファイルからfetcherをコード生成するツールです。Gitリポジトリやpypiパッケージなどに対応しています。

https://github.com/berberman/nvfetcher

大量のfetcherを利用する場合に便利でしょう。GitHub ActionsなどのCIと組み合わせて、定期的にfetcherを更新するという使い方もできます。