この記事はNix Advent Calendar 2024の6日目の記事です。
概要
NixでRustアプリケーションのパッケージ化時に遭遇したエラーとその解決方法を紹介します。そのために、NixにおけるRustのビルドのフローを確認し、エラーの発生箇所を明らかにし、修正方法を説明します。
今回の問題を解決するために、パッチを作成しました。
導入
Rustでは、同じパッケージ名で異なるバージョンや、同一のバージョンで異なるGitリポジトリを持つことが可能です。たとえば、次のようにCargo.toml
に記述できますが、Nixのビルドではエラーが発生します。
[dependencies]
rand = "0.8"
rand_07 = { package = "rand", version = "0.7" }
rand_08 = { package = "rand", version = "0.8", git = "https://github.com/rand/xxx", rev="xx" }
Nixでのビルドでは、RustのCargoはインターネットに直接アクセスできません。これは、Nixのビルド環境がインターネットアクセスを制限しているためです。そのため、cargo vendor
というコマンドを使用してローカルに関連パッケージをダウンロードし、それをもとにビルドを行います。しかし、上記のような重複したパッケージを使うプロジェクトにおいて、cargo vendorを使用してビルドすると、通常のcargo build
では成功するプロジェクトが失敗することがあります。
そのような場合、Nixでのパッケージ化は困難を伴います。具体的には、重複したパッケージの除去や、cargo vendor
が生成したファイルを手動で修正する必要が出てきます。
次に、なぜNixのビルドフローが通常のビルドフローと異なるのかを説明し、NixにおけるRustのビルドフローとその問題点を紹介したうえで、修正方法について解説します。
なぜNixにおけるRustのビルドで問題が起こるのか?
通常のビルドフロー(cargo build
)では、Cargoが必要なパッケージをその都度ダウンロードします。一方、Nixではcargo vendor
コマンドを使用してローカルにパッケージをダウンロードします。この際、パッケージはパッケージ名+バージョン
のディレクトリに配置されます。しかし、Gitのハッシュが異なるパッケージであっても、同じパッケージ名+バージョン
のディレクトリが使用されるため、衝突が発生し、パッケージの配置に失敗します。
cargo vendor
がCargoに組み込まれる前のcargo-vendor
ツールには、Gitハッシュによる衝突を回避するオプション(--no-merge-sources)がありましたが、Cargoに組み込まれた際にその機能は削除されました。そのため、Nixでのビルドが成功したり失敗したりする状況が続いていました。
この問題は現在のcargo vendor
に起因しており、NixのRustビルドフレームワークが変更されても、本質的な解決には至っていないと考えられます。
NixにおけるRustのビルドフロー
nixpkgsのpkgs/build-support/rust/build-rust-packageには、buildRustPackage
が定義されており、Rustプロジェクトのビルドが管理されています。
NixにおけるRustのビルドフローは、以下のような手順で行われます。フローの簡略化のため、Cargo.lockを使う場合で説明します。
-
cargo-vendor-dirの作成:
importCargoLock
を使ってcargo-vendor-dirを作成します。 -
importCargoLock
の中でCargoのパッケージ(Crate)をcrates.ioから取り出します。そこにないものはoutputHashes
から検索し、Gitからダウンロードしようとしますが、Gitのハッシュが見つからないパッケージがあればエラーで失敗します。 -
importCargoLock
の中でCargoのパッケージをダウンロードしたあと、ワークスペースの設定のための親のCargo.tomlと子のCargo.tomlの融合をreplace-workspace-values.py
で行います。この融合では、ワークスペース全体で共通の依存関係やバージョンを管理し、各サブプロジェクトで整合性のあるビルドができるようにする目的があります。融合したパッケージをcargo-vendor-dirの中に配置します。 -
importCargoLock
の中でオフラインでビルドするための.cargo/config.toml
ファイルを生成します。 -
ビルドの実行:
buildRustPackage
関数のnativeBuildInputs
に設定されているcargoBuildHook
を使ってcargo build
を実行し、ビルドします。 - 成果物の生成: ビルドが成功すると、バイナリやその他の成果物がNixストアに配置され、再利用可能な形で保存されます。
問題の修正方法
cargo-vendor
のGitハッシュによる衝突を回避するオプション(--no-merge-sources)の振る舞いを再現するスクリプトを用意することで、この問題を解決しました。Gitハッシュによる衝突を回避するように、Gitハッシュを含めたディレクトリ名にパッケージ名+バージョンを配置し、重複による衝突を避けることで、すべての依存パッケージを正しく配置できるようにしました。
outputHashes
も従来はパッケージ名+バージョンに対するハッシュ値しか設定できませんでしたが、パッケージ名+バージョン+Gitハッシュに対するハッシュ値を設定できるようにしました。
outputHashes = {
"quantization-0.1.0" = "sha256-ggVqJiftu0nvyEM0dzsH0JqIc/Z1XILyUSKiJHeuuZs=";
"tonic-0.9.2" = "sha256-ZlcDUZy/FhxcgZE7DtYhAubOq8DMSO17T+TCmXar1jE=";
"wal-0.1.2-acaf1b2ebd5de3a871f4d2c48e13fc8788ffa43b" = "sha256-CeHQWHUVsHZvIy/7ftDWzbJ7BTARjsKvWHinEjhgL10=";
"wal-0.1.2-fad0e7c48be58d8e7db4cc739acd9b1cf6735de0" = "sha256-nBGwpphtj+WBwL9TmWk7qXiEqlIWkgh/2V9uProqhMk=";
};
suiやqdrantといったパッケージで修正を確認しました。
まとめ
NixにおけるRustのビルドで遭遇するパッケージの衝突問題について、その原因と解決方法を紹介しました。今回のパッチにより、より安定したビルド環境を提供することが可能になります。この知見が、NixでRustのアプリケーションをパッケージ化する際の助けになれば幸いです。