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_variables)] #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));
}
最上位の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.tomlとadd-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
クレートをビルドすると、rand
をCargo.lockのadder
の依存一覧に追加しますが、
rand
のファイルが追加でダウンロードされることはありません。Cargoが、ワークスペースのrand
を使用するどのクレートも、
同じバージョンを使っていることを確かめてくれるのです。ワークスペース全体でrand
の同じバージョンを使用することにより、
複数のコピーが存在しないのでスペースを節約し、ワークスペースのクレートが相互に互換性を維持することを確かめます。
ワークスペースにテストを追加する
さらなる改善として、add_one
クレート内にadd_one::add_one
関数のテストを追加しましょう:
ファイル名: add-one/src/lib.rs
# #![allow(unused_variables)] #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 test
がadd-one
クレートのテストのみを実行し、adder
クレートのテストは実行しなかったことを示しています。
ワークスペースのクレートを https://crates.io/ に公開したら、ワークスペースのクレートは個別に公開される必要があります。
cargo publish
コマンドには--all
フラグや-p
フラグはないので、各クレートのディレクトリに移動して、
ワークスペースの各クレートをcargo publish
して、公開しなければなりません。
鍛錬を積むために、add-one
クレートと同様の方法でワークスペースにadd-two
クレートを追加してください!
プロジェクトが肥大化してきたら、ワークスペースの使用を考えてみてください: 大きな一つのコードの塊よりも、 微細で個別のコンポーネントの方が理解しやすいです。またワークスペースにクレートを保持することは、 同時に変更されることが多いのなら、協調しやすくなることにも繋がります。