またまたRustについてです。
今回はworkspace機能についてよくわからなかったので勉強がてら書いていこうと思います。
Rustのワークスペースとは
大元であるCargoパッケージ内に複数パッケージがある場合に、相互にパッケージを扱うための機能です。
大きくなってきたプロジェクトで使用します。
Cargoのワークスペース - The Rust Programming Language 日本語版
公式のチュートリアルをなぞる形になりますが、やってみます。
まずはワークスペース機能のチュートリアルを行うCargoパッケージを作成します。
cargo new rust-workspace-playground
作成したパッケージに移動し、更にadderというパッケージを作成します。
cargo new adder
この時点でcargo build
しても、adderの依存性をrust-workspace-playgroundには記述していないため、targetに出力されるのはrust-workspace-playgroundにあるhello worldプログラムのみです。
# cargo build実行後のtargetディレクトリの中身
target
├── CACHEDIR.TAG
└── debug
├── build
├── deps
│ ├── librust_workspace_playground-25fa51ac7f690b9a.rmeta
│ ├── librust_workspace_playground-5cbc2718815bd853.rmeta
│ ├── librust_workspace_playground-9f7880f2efd546b2.rmeta
│ ├── rust_workspace_playground-01238b030f757f04
│ ├── rust_workspace_playground-01238b030f757f04.d
│ ├── rust_workspace_playground-25fa51ac7f690b9a.d
│ ├── rust_workspace_playground-5cbc2718815bd853.d
│ └── rust_workspace_playground-9f7880f2efd546b2.d
├── examples
├── incremental
│ ├── rust_workspace_playground-1xmwk187vqtbq
│ ├── rust_workspace_playground-3oehp425tljca
│ ├── rust_workspace_playground-3tb4u1zdv4nwu
├── rust-workspace-playground
└── rust-workspace-playground.d
ここで、rust-workspace-playgroundのCargo.toml
に以下のworkspaceセクションを追加します。
[workspace]
members = [
"adder",
]
この状態で再度cargo build
してみます。すると…?
target
├── CACHEDIR.TAG
└── debug
├── build
├── deps
│ ├── adder-cbecbe60265a0fa6.d
│ ├── adder-fe72b53491ff983c.d
│ ├── libadder-cbecbe60265a0fa6.rmeta
│ ├── libadder-fe72b53491ff983c.rmeta
│ ├── librust_workspace_playground-25fa51ac7f690b9a.rmeta
│ ├── librust_workspace_playground-5cbc2718815bd853.rmeta
│ ├── librust_workspace_playground-9f7880f2efd546b2.rmeta
│ ├── rust_workspace_playground-01238b030f757f04
│ ├── rust_workspace_playground-01238b030f757f04.d
│ ├── rust_workspace_playground-25fa51ac7f690b9a.d
│ ├── rust_workspace_playground-5cbc2718815bd853.d
│ └── rust_workspace_playground-9f7880f2efd546b2.d
├── examples
├── incremental
│ ├── adder-3mb5u48tdpopp
│ ├── adder-vwno9594lzgk
│ ├── rust_workspace_playground-1xmwk187vqtbq
│ ├── rust_workspace_playground-3oehp425tljca
│ ├── rust_workspace_playground-3tb4u1zdv4nwu
│ └── rust_workspace_playground-8wlz61uwis6j
├── rust-workspace-playground
└── rust-workspace-playground.d
targetにadderの実装が増えています!
このように、ワークスペースに依存性のあるパッケージを記述すると、一緒にコンパイルしていくれるので、わざわざadderをビルドした後に親パッケージを再ビルドする必要性がなくなります。
ワークスペース内で実装が依存する場合
続けて、add-oneというライブラリを作成します。
cargo new --lib add-one
add-oneのlib.rs
には以下の実装を追加します。
pub fn add_one(x: i32) -> i32 {
x + 1
}
そしてこの実装をadderから使用してみます。
adderのCargo.toml
のdependenciesセクションに、add_oneへの依存を追加します。
[dependencies]
add-one = { path = "../add-one" }
依存追加したらadderからadd_oneを使用する実装を追加します。
extern crate add_one;
fn main() {
let num = 10;
println!("Hello, world! {} plus one is {}!", num, add_one::add_one(num));
}
adderディレクトリに移動してこれを実行してみます。
% cargo run
Compiling add-one v0.1.0 (/home/username/Project/study/rust-workspace-playground/add-one)
Compiling adder v0.1.0 (/home/username/Project/study/rust-workspace-playground/adder)
Finished dev [unoptimized + debuginfo] target(s) in 0.32s
Running `/home/username/Project/study/rust-workspace-playground/target/debug/adder`
Hello, world! 10 plus one is 11!
依存関係の追加によって、それぞれのCargoパッケージを相互に実行することができました。
これをトップディレクトリのCargoパッケージから実行するためには、workspace
セクションにadd-oneを追加してあげます。
[workspace]
members = [
"adder",
"add-one", # これを追加
]
トップディレクトリに移動し、adderを指定して実行します。
% cargo run -p adder
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
Running `target/debug/adder`
Hello, world! 10 plus one is 11!
実行できました!
単にadder
を実行するだけなら、workspace
セクションにadd-oneは書いていなくても実行は可能です。
[workspace]
members = [
"adder",
# "add-one", # コメントアウトする
]
% cargo run -p adder
Finished dev [unoptimized + debuginfo] target(s) in 0.07s
Running `target/debug/adder`
Hello, world! 10 plus one is 11!
このときにtarget
ディレクトリを見てみると、adderの依存関係をトップディレクトリのCargoパッケージが内包してくれていることが分かります。
target
├── CACHEDIR.TAG
└── debug
├── adder
├── adder.d
├── build
├── deps
│ ├── add_one-5168b2c83d0536d9.d
│ ├── adder-6d0954c78f9b2a9c
│ ├── adder-6d0954c78f9b2a9c.d
│ ├── libadd_one-5168b2c83d0536d9.rlib
│ └── libadd_one-5168b2c83d0536d9.rmeta
├── examples
└── incremental
├── add_one-q1oi50ut71e4
└── adder-3qznkde7cai8g
頭いい〜
この動きは外部モジュールをインポートしている場合でも同じです。
add-oneの依存関係にrandを追加してtarget
ディレクトリを確認してみます。
# add-oneのCargo.toml
[dependencies]
rand = "0.8.5"
追加したら、再度トップディレクトリでcargo build
を実行してtarget
ディレクトリを確認してみます。
target
├── CACHEDIR.TAG
└── debug
├── adder
├── adder.d
├── build
│ ├── libc-71bfd4a20ae19e3a
│ └── libc-a761acc43f1934dd
├── deps # randの依存関係も全部入ってる
│ ├── add_one-4b8dfa575ad7380d.d
│ ├── add_one-5168b2c83d0536d9.d
│ ├── add_one-581f1a9188327ca9.d
│ ├── add_one-c81e23f3aee9a915.d
│ ├── add_one-c8a2936fd4a8f7eb.d
│ ├── adder-09bb83f0a4e3e4bf.d
│ ├── adder-4e39b8f5b576d33e.d
│ ├── adder-6d0954c78f9b2a9c
│ ├── adder-6d0954c78f9b2a9c.d
│ ├── adder-9e293660ddbcfd52.d
│ ├── adder-c92e197a5751b803.d
│ ├── cfg_if-14458ed2700f0b6c.d
│ ├── getrandom-4afcf66a92bc0894.d
│ ├── libadd_one-4b8dfa575ad7380d.rmeta
│ ├── libadd_one-5168b2c83d0536d9.rlib
│ ├── libadd_one-5168b2c83d0536d9.rmeta
│ ├── libadd_one-581f1a9188327ca9.rmeta
│ ├── libadd_one-c81e23f3aee9a915.rmeta
│ ├── libadd_one-c8a2936fd4a8f7eb.rmeta
│ ├── libadder-09bb83f0a4e3e4bf.rmeta
│ ├── libadder-4e39b8f5b576d33e.rmeta
│ ├── libadder-9e293660ddbcfd52.rmeta
│ ├── libadder-c92e197a5751b803.rmeta
│ ├── libc-be18f47547602408.d
│ ├── libcfg_if-14458ed2700f0b6c.rmeta
│ ├── libgetrandom-4afcf66a92bc0894.rmeta
│ ├── liblibc-be18f47547602408.rmeta
│ ├── libppv_lite86-04333437dd28a938.rmeta
│ ├── librand-8a6d3b1609e3b558.rmeta
│ ├── librand_chacha-f6d33a0b3d88ccc9.rmeta
│ ├── librand_core-ba7e5bf0385b471a.rmeta
│ ├── librust_workspace_playground-5cbc2718815bd853.rmeta
│ ├── librust_workspace_playground-9f7880f2efd546b2.rmeta
│ ├── ppv_lite86-04333437dd28a938.d
│ ├── rand-8a6d3b1609e3b558.d
│ ├── rand_chacha-f6d33a0b3d88ccc9.d
│ ├── rand_core-ba7e5bf0385b471a.d
│ ├── rust_workspace_playground-01238b030f757f04
│ ├── rust_workspace_playground-01238b030f757f04.d
│ ├── rust_workspace_playground-5cbc2718815bd853.d
│ └── rust_workspace_playground-9f7880f2efd546b2.d
├── examples
├── incremental
│ ├── add_one-21eecct0kd129
│ ├── add_one-2ccc3ytqkzrjq
│ ├── add_one-3jik0adxp4co5
│ ├── add_one-q1oi50ut71e4
│ ├── add_one-yp14fxs0zi82
│ ├── adder-1cydw5mbduoj4
│ ├── adder-3qznkde7cai8g
│ ├── adder-5q6n4hi11en3
│ ├── adder-afird3wdl4ox
│ ├── adder-e598hbdk7zh7
│ ├── rust_workspace_playground-1xmwk187vqtbq
│ ├── rust_workspace_playground-3oehp425tljca
│ └── rust_workspace_playground-3tb4u1zdv4nwu
├── rust-workspace-playground
└── rust-workspace-playground.d
randの依存関係が入っていることが確認できました。
このケースではplayground -> adder -> add-one -> randという依存関係になっているため、workspaceにadderを指定しただけでキレイにすべての依存関係が入ります。
テストの一括実行
ワークスペースで管理するメリットは他にもあります。
トップディレクトリでcargo test
をするだけで、ワークスペースに登録されているCargoパッケージのテストが一斉に実行できます。
試しにadd-oneにテストコードを実装します。
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));
}
}
これをトップディレクトリから実行できるか試してみます。
# rust-workspace-playgroundから実行
% cargo test
Compiling rust-workspace-playground v0.1.0 (/home/username/Project/study/rust-workspace-playground)
Finished test [unoptimized + debuginfo] target(s) in 0.43s
Running unittests src/main.rs (target/debug/deps/rust_workspace_playground-8d966a1f0e1420d6)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
あら、実行できませんでした。
先程workspace
からadd-one
の設定を抜いていたためでした。
このように、テストコードは依存関係とは関係がないので、workspace
で定義されていないと実行されません。
(まぁそりゃ依存先のテストコードが全部実行されたら面倒ですもんね…)
add-one
のコメントアウトを復活させます。
[workspace]
members = [
"adder",
"add-one", # コメントアウトを外す
]
再度実行します。
% cargo test
Finished test [unoptimized + debuginfo] target(s) in 0.01s
Running unittests src/main.rs (target/debug/deps/rust_workspace_playground-8d966a1f0e1420d6)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
あれ、思ったとおりの出力にならない…
どうやらworkspace
のすべてのテストを実行するには、cargo test
で--workspace
オプションをつける必要があるみたいです。
アップデートの影響かな?
% cargo test --workspace
Compiling adder v0.1.0 (/home/username/Project/study/rust-workspace-playground/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.37s
Running unittests src/lib.rs (target/debug/deps/add_one-9254bbc45c7edb6a)
running 1 test
test tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/main.rs (target/debug/deps/adder-6b8696df6a92a81f)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/main.rs (target/debug/deps/rust_workspace_playground-8d966a1f0e1420d6)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests add-one
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
これでトップディレクトリからワークスペースのすべてのテストが実行できました。
ちなみに、add-one
をコメントアウトした状態でcargo test --workspace
を実行しても、テストが認識されませんでした。
% cargo test --workspace
Finished test [unoptimized + debuginfo] target(s) in 0.01s
Running unittests src/lib.rs (target/debug/deps/add_one-9254bbc45c7edb6a)
running 1 test
test tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/main.rs (target/debug/deps/adder-6b8696df6a92a81f)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/main.rs (target/debug/deps/rust_workspace_playground-8d966a1f0e1420d6)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests add-one
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
2023-08-07 追記
workspace
セクションに書けるすべてのプロパティはここに書いてありました。
- members
- ワークスペースに含めるパッケージ。
- resolver
- 使用する依存性リゾルバを設定します。
- exclude
- ワークスペースから除外するパッケージ。
- default-members
- 特定のパッケージが選択されていない場合に操作するパッケージ。
cargo run --workspace
で指定しない場合に実行されるデフォルトのパッケージのこと- 複数選択できるのはなぜ…?
- package
- パッケージを継承するためのキー。後述するサンプルを参考。
- dependencies
- パッケージの依存関係を継承するためのキー。後述するサンプルを参考。
- metadata
- 外部ツール用の追加設定。通常は使用しない。
packageは面白い機能で、子パッケージに親パッケージのプロパティを継承するときに使用するプロパティのようです。
# 親パッケージ
# [PROJECT_DIR]/Cargo.toml
[workspace]
members = ["bar"]
[workspace.package] # 子のパッケージに継承する情報
version = "1.2.3"
authors = ["Nice Folks"]
description = "A short description of my package"
documentation = "https://example.com/bar"
# 子パッケージ
# [PROJECT_DIR]/bar/Cargo.toml
[package]
name = "bar"
version.workspace = true
authors.workspace = true
description.workspace = true
documentation.workspace = true
dependenciesも子パッケージに依存関係を継承するための機能になっています。
[依存パッケージ].workspace = true
とするだけで継承できます。
# 親パッケージ
# [PROJECT_DIR]/Cargo.toml
[workspace]
members = ["bar"]
[workspace.dependencies]
cc = "1.0.73"
rand = "0.8.5"
regex = { version = "1.6.0", default-features = false, features = ["std"] }
# 子パッケージ
# [PROJECT_DIR]/bar/Cargo.toml
[package]
name = "bar"
version = "0.2.0"
[dependencies]
regex = { workspace = true, features = ["unicode"] }
[build-dependencies]
cc.workspace = true
[dev-dependencies]
rand.workspace = true
ちなみに、このドキュメント読んでて気づいたんですが、[package]
セクションを実装しているCargo.tomlを配置したパッケージをルートパッケージといい、その子として配置した[package]
セクションのないCargo.tomlを配置したパッケージをバーチャルワークスペースと呼ぶようです。
まとめ
プロジェクトの規模によって使い分けると便利そうですね。
個人的には、モノリポ運用では役に立ちそうだなと感じています。