Cargoのワークスペース

第12章で、バイナリクレートとライブラリクレートを含むパッケージを構築しました。プロジェクトの開発が進むにつれて、 ライブラリクレートの肥大化が続き、その上で複数のライブラリクレートにパッケージを分割したくなることでしょう。 この場面において、Cargoはワークスペースという協調して開発された関連のある複数のパッケージを管理するのに役立つ機能を提供しています。

ワークスペースを生成する

ワークスペースは、同じCargo.lockと出力ディレクトリを共有する一連のパッケージです。 ワークスペースを使用したプロジェクトを作成し、ワークスペースの構造に集中できるよう、瑣末なコードを使用しましょう。 ワークスペースを構築する方法は複数ありますが、一般的な方法を提示しましょう。バイナリ1つとライブラリ2つを含むワークスペースを作ります。 バイナリは、主要な機能を提供しますが、2つのライブラリに依存しています。 一方のライブラリは、add_one関数を提供し、2番目のライブラリは、add_two関数を提供します。 これら3つのクレートが同じワークスペースの一部になります。ワークスペース用の新しいディレクトリを作ることから始めましょう:

$ mkdir add $ cd add

次にaddディレクトリにワークスペース全体を設定するCargo.tomlファイルを作成します。 このファイルには、他のCargo.tomlファイルで見かけるような[package]セクションやメタデータはありません。 代わりにバイナリクレートへのパスを指定することでワークスペースにメンバを追加させてくれる[workspace]セクションから開始します; 今回の場合、そのパスはadderです:

ファイル名: Cargo.toml

[workspace] members = [ "adder", ]

次に、addディレクトリ内でcargo newを実行することでadderバイナリクレートを作成しましょう:

$ cargo new --bin adder Created binary (application) `adder` project

この時点で、cargo buildを走らせるとワークスペースを構築できます。addディレクトリに存在するファイルは、 以下のようになるはずです:

├── Cargo.lock ├── Cargo.toml ├── adder │ ├── Cargo.toml │ └── src │ └── main.rs └── target

ワークスペースには、コンパイルした生成物を置けるように最上位にtargetのディレクトリがあります; adderクレートにはtargetディレクトリはありません。 adderディレクトリ内部からcargo buildを走らせることになっていたとしても、コンパイルされる生成物は、 add/adder/targetではなく、add/targetに落ち着くでしょう。ワークスペースのクレートは、 お互いに依存しあうことを意味するので、Cargoはワークスペースのtargetディレクトリをこのように構成します。 各クレートがtargetディレクトリを持っていたら、各クレートがワークスペースの他のクレートを再コンパイルし、 targetディレクトリに生成物がある状態にしなければならないでしょう。一つのtargetディレクトリを共有することで、 クレートは不必要な再ビルドを回避できるのです。

ワークスペース内に2番目のクレートを作成する

次に、ワークスペースに別のメンバクレートを作成し、add-oneと呼びましょう。 最上位のCargo.tomlを変更してmembersリストでadd-oneパスを指定するようにしてください:

ファイル名: Cargo.toml

[workspace] members = [ "adder", "add-one", ]

それから、add-oneという名前のライブラリクレートを生成してください:

$ cargo new add-one --lib Created library `add-one` project

これでaddディレクトリには、以下のディレクトリやファイルが存在するはずです:

├── Cargo.lock ├── Cargo.toml ├── add-one │ ├── Cargo.toml │ └── src │ └── lib.rs ├── adder │ ├── Cargo.toml │ └── src │ └── main.rs └── target

add-one/src/lib.rsファイルにadd_one関数を追加しましょう:

ファイル名: add-one/src/lib.rs

#![allow(unused)] fn main() { pub fn add_one(x: i32) -> i32 { x + 1 } }

ワークスペースにライブラリクレートが存在するようになったので、バイナリクレートadderをライブラリクレートのadd-oneに依存させられます。 まず、add-oneへのパス依存をadder/Cargo.tomlに追加する必要があります:

ファイル名: adder/Cargo.toml

[dependencies] add-one = { path = "../add-one" }

Cargoはワークスペースのクレートが、お互いに依存しているとは想定していないので、 クレート間の依存関係について明示する必要があります。

次に、adderクレートのadd-oneクレートからadd_one関数を使用しましょう。adder/src/main.rsファイルを開き、 冒頭にextern crate行を追加して新しいadd-oneライブラリクレートをスコープに導入してください。 それからmain関数を変更し、add_one関数を呼び出します。リスト14-7のようにですね:

ファイル名: adder/src/main.rs

extern crate add_one; fn main() { let num = 10; // こんにちは世界!{}+1は{}! println!("Hello, world! {} plus one is {}!", num, add_one::add_one(num)); }

リスト14-7: adderクレートからadd-oneライブラリクレートを使用する

最上位のaddディレクトリでcargo buildを実行することでワークスペースをビルドしましょう!

$ cargo build Compiling add-one v0.1.0 (file:///projects/add/add-one) Compiling adder v0.1.0 (file:///projects/add/adder) Finished dev [unoptimized + debuginfo] target(s) in 0.68 secs

addディレクトリからバイナリクレートを実行するには、-p引数とパッケージ名をcargo runと共に使用して、 使用したいワークスペースのパッケージを指定する必要があります:

$ cargo run -p adder Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs Running `target/debug/adder` Hello, world! 10 plus one is 11!

これにより、adder/src/main.rsのコードが実行され、これはadd_oneクレートに依存しています。

ワークスペースの外部クレートに依存する

ワークスペースには、各クレートのディレクトリそれぞれにCargo.lockが存在するのではなく、 ワークスペースの最上位階層にただ一つのCargo.lockが存在するだけのことに注目してください。 これにより、全クレートが全依存の同じバージョンを使用していることが確認されます。 randクレートをadder/Cargo.tomladd-one/Cargo.tomlファイルに追加すると、 Cargoは両者をあるバージョンのrandに解決し、それを一つのCargo.lockに記録します。 ワークスペースの全クレートに同じ依存を使用させるということは、 ワークスペースのクレートが相互に互換性を常に維持するということになります。 add-one/Cargo.tomlファイルの[dependencies]セクションにrandクレートを追加して、 add-oneクレートでrandクレートを使用できます:

ファイル名: add-one/Cargo.toml

[dependencies] rand = "0.3.14"

これで、add-one/src/lib.rsファイルにextern crate rand;を追加でき、 addディレクトリでcargo buildを実行することでワークスペース全体をビルドすると、 randクレートを持ってきてコンパイルするでしょう:

$ cargo build Updating registry `https://github.com/rust-lang/crates.io-index` Downloading rand v0.3.14 --snip-- Compiling rand v0.3.14 Compiling add-one v0.1.0 (file:///projects/add/add-one) Compiling adder v0.1.0 (file:///projects/add/adder) Finished dev [unoptimized + debuginfo] target(s) in 10.18 secs

さて、最上位のCargo.lockは、randに対するadd-oneの依存の情報を含むようになりました。 ですが、randはワークスペースのどこかで使用されているにも関わらず、それぞれのCargo.tomlファイルにも、 randを追加しない限り、ワークスペースの他のクレートでそれを使用することはできません。 例えば、adderクレートのadder/src/main.rsファイルにextern crate rand;を追加すると、 エラーが出ます:

$ cargo build Compiling adder v0.1.0 (file:///projects/add/adder) error: use of unstable library feature 'rand': use `rand` from crates.io (see issue #27703) (エラー: 不安定なライブラリの機能'rand'を使用しています: crates.ioの`rand`を使用してください) --> adder/src/main.rs:1:1 | 1 | extern crate rand;

これを修正するには、adderクレートのCargo.tomlファイルを編集し、同様にそのクレートがrandに依存していることを示してください。 adderクレートをビルドすると、randCargo.lockadderの依存一覧に追加しますが、 randのファイルが追加でダウンロードされることはありません。Cargoが、ワークスペースのrandを使用するどのクレートも、 同じバージョンを使っていることを確かめてくれるのです。ワークスペース全体でrandの同じバージョンを使用することにより、 複数のコピーが存在しないのでスペースを節約し、ワークスペースのクレートが相互に互換性を維持することを確かめます。

ワークスペースにテストを追加する

さらなる改善として、add_oneクレート内にadd_one::add_one関数のテストを追加しましょう:

ファイル名: add-one/src/lib.rs

#![allow(unused)] fn main() { pub fn add_one(x: i32) -> i32 { x + 1 } #[cfg(test)] mod tests { use super::*; #[test] fn it_works() { assert_eq!(3, add_one(2)); } } }

では、最上位のaddディレクトリでcargo testを実行してください:

$ cargo test Compiling add-one v0.1.0 (file:///projects/add/add-one) Compiling adder v0.1.0 (file:///projects/add/adder) Finished dev [unoptimized + debuginfo] target(s) in 0.27 secs Running target/debug/deps/add_one-f0253159197f7841 running 1 test test tests::it_works ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out Running target/debug/deps/adder-f88af9d2cc175a5e running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out Doc-tests add-one running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

出力の最初の区域が、add-oneクレートのit_worksテストが通ったことを示しています。 次の区域には、adderクレートにはテストが見つからなかったことが示され、 さらに最後の区域には、add-oneクレートにドキュメンテーションテストは見つからなかったと表示されています。 このような構造をしたワークスペースでcargo testを走らせると、ワークスペースの全クレートのテストを実行します。

-pフラグを使用し、テストしたいクレートの名前を指定することで最上位ディレクトリから、 ワークスペースのある特定のクレート用のテストを実行することもできます:

$ cargo test -p add-one Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs Running target/debug/deps/add_one-b3235fea9a156f74 running 1 test test tests::it_works ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out Doc-tests add-one running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

この出力は、cargo testadd-oneクレートのテストのみを実行し、adderクレートのテストは実行しなかったことを示しています。

ワークスペースのクレートを https://crates.io/ に公開したら、ワークスペースのクレートは個別に公開される必要があります。 cargo publishコマンドには--allフラグや-pフラグはないので、各クレートのディレクトリに移動して、 ワークスペースの各クレートをcargo publishして、公開しなければなりません。

鍛錬を積むために、add-oneクレートと同様の方法でワークスペースにadd-twoクレートを追加してください!

プロジェクトが肥大化してきたら、ワークスペースの使用を考えてみてください: 大きな一つのコードの塊よりも、 微細で個別のコンポーネントの方が理解しやすいです。またワークスペースにクレートを保持することは、 同時に変更されることが多いのなら、協調しやすくなることにも繋がります。