前回の記事では、feature を用いてバイナリクレートとライブラリクレートの dependencies を分けました。

Rustでバイナリとライブラリでdependenciesを分ける | 為せばnull

今回はもう一つの手法として、dev-dependencies を用いる方法を書き残したいと思います。

なお、前回はバイナリクレートでしたが、今回の方法は、アプリケーション(ライブラリではない方)をバイナリクレートとしてではなく example として扱わないと利用できない点に注意してください。便宜上、以降はアプリケーションと呼ぶことにします。

正直言うと筆者はバイナリクレートと example との違いはよく分かっていないのですが、バイナリはクレートのメインアプリケーションのことを、example はライブラリクレートの使用例としてのデモプログラムのことを示すようです。

ですので、アプリケーションが中心で、あくまでもライブラリはおまけ程度であるクレートならばバイナリクレート、ライブラリが中心でアプリケーションがおまけ程度であるクレートならば examples として扱うのが良いのかな?と思っていますが、このあたりの位置づけは正直よくわかりません。

結局どちらもアプリケーションとしてのビルドが可能で、cargo install でコマンドとしてインストールすることが可能ですので、動作上はほとんど変化はないと思います。ただ、アプリケーションを example として扱った場合、クレートのメインはアプリケーションではなくライブラリの方になります。ユーザは cargo install 実行時に --example オプションで example をインストールすることを明記する必要があります。

結論

アプリケーション(バイナリ)側だけが使用する依存関係を [dev-dependencies] として Cargo.toml に定義します。

[dev-dependencies] はテスト実行、examples のビルド、ベンチマーク実行時のみ依存関係に含まれることを示すもので、ライブラリのビルド時には依存関係には含まれません。よって、ライブラリから不要な外部クレートを除外することができます。

手順は以下の通りです。

  • アプリケーションを examples ディレクトリに移動する

  • アプリケーションを Cargo.toml に example として定義する

  • アプリケーションだけが使用する依存関係を [dependencies] ではなく [dev-dependencies] に定義する

1. アプリケーションを examples ディレクトリに移動する

アプリケーションのソースコードである main.rsexamples/[example名] に移動することで、アプリケーションは examples として扱われます。example 名はアプリケーションの名前でも付けておきましょう。

すなわち、

rust-app
├ src
│ ├ main.rs
│ └ lib.rs
└ Cargo.toml  

これを

rust-app
├ examples
│ └ rust-app
│   └ main.rs
├ src
│ └ lib.rs
└ Cargo.toml    

こうします。

2. アプリケーションを Cargo.toml に example として定義する

[[examples]]
name = "rust-app"

Cargo.toml に [[examples]] を追記しておき、name に先ほどのアプリケーション名を設定しておきます。

3. アプリケーションだけが使用する依存関係を [dev-dependencies] に定義する

もともと [dependencies] に定義されていた、アプリケーションだけが依存してライブラリが依存しない外部クレートを、[dev-dependencies] に移動します。

例えば、[dependencies]

[dependencies]
image = "0.24.5"
mozjpeg = { version = "0.9.4", optional = true }
oxipng = { version = "8.0.0", optional = true }
dep_webp = { version = "0.2.2", optional = true, package = "webp" }
clap = { version = "4.1.8", features = ["derive"] }
regex = { version ="1.7.2" }
viuer = { version ="0.6.2" }
glob = { version = "0.3.1" }
colored = { version = "2.0.4" }

このようになっていて、下5つ、すなわち clap、regex、viuer、glob、colored がアプリケーションでしか使用しない外部クレートであった場合、

[dependencies]
image = "0.24.5"
mozjpeg = { version = "0.9.4", optional = true }
oxipng = { version = "8.0.0", optional = true }
dep_webp = { version = "0.2.2", optional = true, package = "webp" }

[dev-dependencies]
clap = { version = "4.1.8", features = ["derive"] }
regex = { version ="1.7.2" }
viuer = { version ="0.6.2" }
glob = { version = "0.3.1" }
colored = { version = "2.0.4" }

このようにします。

これで完了です。

動作確認

ここからは、自分が現在開発しているアプリケーション「rusimg」を例に動作確認してみます。

まずは cargo install --git https://github.com/yotiosoft/rusimg でインストールしようとした場合の結果を見てみます。すると、

$ cargo install --git https://github.com/yotiosoft/rusimg
    Updating git repository `https://github.com/yotiosoft/rusimg`
error: there is nothing to install in `rusimg v0.1.0 (https://github.com/yotiosoft/rusimg#18a34951)`, because it has no binaries
`cargo install` is only for installing programs, and can't be used with libraries.
To use a library crate, add it as a dependency to a Cargo project with `cargo add`.

このようなエラーが現れました。cargo install はライブラリインストール用のコマンドではないから、ライブラリを追加したいなら cargo add を使え、という趣旨のエラーです。おそらく、アプリケーションを examples として扱った場合、アプリケーションではなくライブラリがクレートのメインという扱いになるからだと思います。

それでは、空の Rust のクレートディレクトリに移動して、rusimg を外部クレートとして追加してみます。すると、

$ cargo add --git https://github.com/yotiosoft/rusimg
    Updating git repository `https://github.com/yotiosoft/rusimg`
    Updating git repository `https://github.com/yotiosoft/rusimg`
      Adding rusimg (git) to dependencies.
             Features:
             + bmp
             + dep_webp
             + jpeg
             + mozjpeg
             + oxipng
             + png
             + webp
    Updating git repository `https://github.com/yotiosoft/rusimg`
    Updating crates.io index

こちらでは、clap などといった、[dev-dependencies] に登録したクレートを除き、ライブラリが必要とする外部クレートだけがインストールされたようするが確認できました。

続いて、examples をインストールしてみます。cargo install --git https://github.com/yotiosoft/rusimg --examples rusimg でインストールできます。

$ cargo install --git https://github.com/yotiosoft/rusimg --examples rusimg
    Updating git repository `https://github.com/yotiosoft/rusimg`
  Installing rusimg v0.1.0 (https://github.com/yotiosoft/rusimg#18a34951)
    Updating crates.io index
  Downloaded mio v0.8.11
  Downloaded tempfile v3.10.1
  Downloaded half v2.4.0
  Downloaded regex-automata v0.4.6
  Downloaded rayon v1.9.0
  Downloaded 5 crates (982.6 KB) in 1.21s
   Compiling libc v0.2.153
 ...

今度は examples がビルドされている様子が確認できました。インストール完了後、コマンドとして rusimg を実行できました。

$ rusimg
 2 images are detected.
[1/2] Processing: test.bmp
[2/2] Processing: test_save.bmp

✅ All images are processed.

前回の features で dependencies を分ける方法との違い

クレート開発者側の手数で言えば、別途 features を定義する必要がない分、今回の examples を利用する方法のほうが手軽だと思います。

ただ、examples に指定するとライブラリが中心のクレートとして扱われますので、cargo install 時に --example [アプリケーション名] の指定が必要になります。

一方、アプリケーションをバイナリクレートして扱い、features で dependencies を分けた場合も --features=app といった、アプリケーション用の外部クレートの feature 名を指定する必要があります。

利用者側の視点で言えばどちらも利便性に差異はないでしょう。

参考文献