TL;DR
- 現在のstableチャンネルのCargo1では、ルートクレート2の依存パッケージを決定する際に
dev-dependencies
とbuild-dependencies
も考慮してしまう。 - nightlyのCargoでは修正されていて、
cargo +nightly -Z features=dev_dep [Command]
やcargo +nightly -Z features=host_dep [Command]
とすることで回避できる。3 4
バグの紹介
Cargo.toml
にはdev-dependencies
という項目を追加でき、tests
やexamples
などをビルドする際に必要な依存クレートを記すために使われる。この目的から、この項目に記載されているものはライブラリや実行バイナリ本体のビルドには不必要なものである事が容易に分かる(そもそも本体のビルドに必要であればdependencies
に記すだけで十分である)。
しかし、現在のstableチャンネルから得られるCargoはプロダクト本体をビルドする際にこのdev-dependencies
のビルドに記されている依存クレートも考慮に入れて依存関係の解決をしてしまう。このバグはbuild.rs
で必要になる依存クレートを記載するためのbuild-dependencies
についても同様に発生する。
問題が起きるケース
このバグは致命的なエラーを引き起こすことは少ないが、特定の条件ではライブラリのバグを見逃してしまう。例えば開発しているライブラリがnum-traits
に依存していて以下のような項目を含むCargo.toml
であるとしよう。
[dependencies]
num-traits = { version = "0.2.12", default-features = false }
[dev-dependencies]
num-traits = { version = "0.2.12", features = ["std"] }
num-traits
はデフォルトでstd
フィーチャーが有効になっているが、ここではdefault-features = false
とすることによって無効化している。一方、dev-dependencies
ではstd
フィーチャーが必要な場合を想定している。
ここで、num-traits
のFloat
トレイトをインポートしてみる。
#[allow(unused_imports)]
use num_traits::float::Float;
実はFloat
トレイトはnum-traits
のstd
フィーチャーが有効化されている時のみ定義されているので、このコードはビルドに失敗して然るべきである。しかしCargoのバグによりこのライブラリをビルドする際にはdev-dependencies
が考慮されるので、num-traits
はstd
フィーチャーが有効化された状態でビルドされ、このライブラリはビルドが成功してしまう。しかし、このライブラリに依存するクレートをビルドしようとした時には問題が起こる。
このライブラリの名前をdev_dep_bug
として、このクレートと同じディレクトリに、dev_dep_bug
に依存するバイナリクレートを新たに作ろう。
[dependencies]
dev_dep_bug = { version = "*", path = "../dev_dep_bug"}
依存クレートをビルドする際は、依存クレートのdev-dependencies
は考慮されないのでnum-traits
をstd
フィーチャーが無効化された状態でビルドする。これによってdev_dep_bug
をビルドする際にFloat
トレイトが見つからず、以下のようなエラーが吐き出される。
$ cargo check
Updating crates.io index
Compiling autocfg v1.0.1
Compiling num-traits v0.2.12
Checking dev_dep_bug v0.1.0 (/path/to/dev_dep_bug)
error[E0432]: unresolved import `num_traits::float::Float`
--> /path/to/dev_dep_bug/src/lib.rs:2:5
|
2 | use num_traits::float::Float;
| ^^^^^^^^^^^^^^^^^^^^^^^^ no `Float` in `float`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0432`.
error: could not compile `dev_dep_bug`.
To learn more, run the command again with --verbose.
この問題はライブラリ使用者が手の付けられない範囲に原因が潜んでおり、IssueやPRを投げる他に対策方法が無いことにある(forkしたり手元にライブラリのコードを持ってきて修正したものを使用するという手があるが、そこまでやるならPRを投げてContributeした方が良いと思う)。
しかもライブラリ開発者は手元であれCIであれ、単純にcargo check
やcargo build
をするだけでは気づき得ないという点が凶悪である。
解決方法
このバグはnightlyチャンネルでは既に修正されており、-Z
フラグを使ってfeatures=dev_dep
3やfeatures=host_dep
4を指定することによってそれぞれdev-dependencies
、build-dependencies
のバグを回避でき、ライブラリがこれに起因する依存関係のバグを仕込んでいないか確認が取れる。
$ cargo +nightly -Zfeatures=dev_dep check # or build
$ cargo +nightly -Zfeatures=host_dep check # or build
最後に先程の例をnightlyのCargoでビルドしてみると、以下の通り正しくビルドエラーになる。
$ cargo +nightly -Zfeatures=dev_dep check
Checking dev_dep_bug v0.1.0 (/path/to/dev_dep_bug)
error[E0432]: unresolved import `num_traits::float::Float`
--> src/lib.rs:2:5
|
2 | use num_traits::float::Float;
| ^^^^^^^^^^^^^^^^^^^^^^^^ no `Float` in `float`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0432`.
error: could not compile `dev_dep_bug`.
To learn more, run the command again with --verbose.