はじめに
(この記事では、プロジェクトの依存関係(Cargo.tomlやCargo.lock)でcmake
クレートが直接または間接的に使用されていることを前提とします。)
WindowsでRustの環境構築を進めていると、cargo build
を実行した際に error: failed to run custom build command for <crate-name>
のようなエラーと共に、「Missing dependency: cmake
」というメッセージに遭遇することがあります。
これは、プロジェクトが依存しているクレートが内部でC/C++のコードを利用しており、そのビルドに CMake
というツールが必要にもかかわらず、開発環境に cmake
がインストールされていないか、cmake
が内部で利用する C/C++ コンパイラ (gcc や MSVC など) が適切に設定されていない場合に発生します。
以下の記事でも、WindowsではCMakeがデフォルトでインストールされていないことが指摘されています。
そこで、この記事では、このエラーの原因と、特に VS Code の Dev Containers を活用した解決策について紹介します。
想定読者
- Rust 開発初心者
- WindowsでのRustの環境構築がうまくいかない人
- C/C++ライブラリに依存するクレート(例:
*-sys
クレート[1])を利用していてcargo build
時に "Missing dependency: cmake" エラーに遭遇した方
(VS Code と Docker が使えることは前提にしています)
TL;DR
- Windows環境で
cargo build
を実行した際に "Missing dependency: cmake" と表示される場合、ビルドプロセスにCMakeを必要とするRustの依存クレートが存在し、C/C++のコンパイラなどの必要なツールがインストールされていないことが原因であると考えられる - ホストOSに直接
cmake
をインストールするのはプロジェクト間の依存関係の衝突のリスクがあるため、VS Code の Dev Containers を使うのが個人的におすすめ
なぜ "Missing dependency: cmake" エラーが発生するのか?
Rustプロジェクトの中には、既存のC言語やC++言語で書かれたライブラリ(例: OpenSSL, SQLiteなど)の機能を利用するために、それらをRustから呼び出すための「バインディング」を提供しているクレートがあります(openssl-sys
や sqlite3-sys
などが典型例)。
より詳細には、「FFI (Foreign Function Interface)」や「FFIバインディング」で調べてみることをお勧めします。
これらの -sys
クレートの多くは、元のC/C++ライブラリをソースコードからビルドする機能を持っています。C/C++では、プロジェクトのビルド手順を管理するツールとして CMake
が広く使われています。
cargo build
を実行すると、Cargoは依存クレートのビルドスクリプトを実行します。このスクリプトが、依存するC/C++ライブラリのビルドを試みる過程で、以下のような問題が起きていると考えられます。
- 開発環境に
cmake
がインストールされていない - CMakeが内部的に必要なC/C++コンパイラ(gcc, clangなど)がない
結果として、「Missing dependency: cmake」エラーが発生し、ビルドプロセス全体が失敗してしまいます。
(補足)Rustが cmake クレートを使用して、C言語で書かれたコードをコンパイルするための手順
(以下は、RustによるC言語のコンパイルの手順を、自分なりにまとめてみました。厳密には多少違うかもしれません。)
- C言語で記述されたパッケージをビルドする際に、CMakeを使用してビルドするよう明示的に指定される
- cmake クレートを使用することで、CMakeを利用するためのConfig が作成される
- CMake が内部的に、gccなどのCコンパイラ(ここはtarget-tripleごとに異なる)を用いて、コンパイルを試みる
- この際に、実行環境のtarget-triple によって、C言語に対するABI(Application Binary Interface)が決定され、C言語がバイナリへとコンパイルされる[2]
- Rust上で上記が行われているため、全体として、Rustのビルド時にCのコードもコンパイルできているように見える
解決策: Dev Containersの活用
この問題を解決する最も確実な方法は、VSCodeの Dev Containers 機能を利用することです。
Dev Containers とは
Dev Containersは Visual Studio Code の拡張で、コンテナ内のファイル操作やコマンド実行を、ローカル環境と同じ使い心地で利用できるようにする拡張です。実行する Dockerfile や docker-compose.yml などをリポジトリの設定ファイルとして含めることで、同じ開発環境を再現・利用することがより容易になります。
(今回のケースにおける)Dev Containers のメリット
-
Rust用のDocker公式イメージが使用できる
- 公式イメージ(例えば、rust:1.85.1-bookworm)は、linux 上でRustやC/C++ コンパイラであるgcc がプリインストールされている環境を構築できます
- コンパイラがプリインストールされているのは、Windowsでの環境構築と比較して、最大のメリットといえます
-
Dockerに関する操作を必要としない
- 仮想環境の立ち上げであれば、Dockerコンテナにボリュームをマウントすることで、実現できます。しかし、Dockerコンテナの起動などのコマンドに関する知識が必要になります
- 一方で、Dev Containers であれば、VS Code上での操作で仮想環境を完結させることができます。これにより、開発環境を再現性高く構築することができます
Devcontainerの設定
ディレクトリ構成
root/
└ .devcontainer/ ← DevContainer 設定ディレクトリ
| ├ docker-compose.yml
| └ devcontainer.json
└ src/ ← 作業ディレクトリ
docker-compose.yml
(src部分はPathに応じて変更してください)
services:
rust_devcontainer:
image: rust:1.85.1-bookworm
restart: always
tty: true
volumes:
- ../src:/workspaces/src
working_dir: /workspaces/src
devcontainer.json
{
"dockerComposeFile": [
"docker-compose.yml"
],
"service": "rust_devcontainer",
"workspaceFolder": "/workspaces/src",
}
(Dev Containers の詳しい使い方は本題ではないので、以下の記事に譲ります)
まとめ
VS Code の Dev Containers を利用して、プロジェクトごとに隔離された環境内にCMakeをインストールする ことで、再利用性の高い形でのRustの環境構築ができます。また、C/C++コンパイラ などのOS依存性の高い部分を回避できるのも魅力的な部分だと感じています。
補足
[1] Rustのクレートには、C言語で記述されたインターフェースを利用する必要のクレートがあり、それの接尾辞は-sys
にする慣習があります
[2] Rustコンパイラ(rustc)やCargoは、target-triple(例:x86_64-unknown-linux-gnu
)を使用して、対象とするプラットフォームのABIやバイナリ形式に合わせたコードを生成することにより、クロスコンパイル時も適切なABIに従った関数呼び出しが可能にしています