Workspaces در Cargo
در فصل 12، ما یک پکیج ساختیم که شامل یک crate باینری و یک crate کتابخانهای بود. همانطور که پروژه شما توسعه مییابد، ممکن است متوجه شوید که crate کتابخانهای همچنان بزرگتر میشود و بخواهید پکیج خود را بیشتر به crateهای کتابخانهای چندگانه تقسیم کنید. Cargo یک ویژگی به نام workspaces ارائه میدهد که میتواند به مدیریت پکیجهای مرتبط که به صورت همزمان توسعه داده میشوند کمک کند.
Creating a Workspace
A workspace is a set of packages that share the same Cargo.lock and output
directory. Let’s make a project using a workspace—we’ll use trivial code so we
can concentrate on the structure of the workspace. There are multiple ways to
structure a workspace, so we’ll just show one common way. We’ll have a
workspace containing a binary and two libraries. The binary, which will provide
the main functionality, will depend on the two libraries. One library will
provide an add_one
function, and a second library an add_two
function.
These three crates will be part of the same workspace. We’ll start by creating
a new directory for the workspace:
$ mkdir add
$ cd add
Next, in the add directory, we create the Cargo.toml file that will
configure the entire workspace. This file won’t have a [package]
section.
Instead, it will start with a [workspace]
section that will allow us to add
members to the workspace. We also make a point to use the latest and greatest
version of Cargo’s resolver algorithm in our workspace by setting the
resolver
to "2"
.
Filename: Cargo.toml
[workspace]
resolver = "3"
Next, we’ll create the adder
binary crate by running cargo new
within the
add directory:
$ cargo new adder
Created binary (application) `adder` package
Adding `adder` as member of workspace at `file:///projects/add`
Running cargo new
inside a workspace also automatically adds the newly created
package to the members
key in the [workspace]
definition in the workspace
Cargo.toml
, like this:
[workspace]
resolver = "3"
members = ["adder"]
در این مرحله، میتوانیم workspace را با اجرای دستور cargo build
بسازیم. فایلهای موجود در دایرکتوری add شما باید به این صورت باشند:
├── Cargo.lock
├── Cargo.toml
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
Workspace یک دایرکتوری target در سطح بالا دارد که فایلهای کامپایلشده در آن قرار خواهند گرفت. پکیج adder
دایرکتوری target اختصاصی خود را ندارد. حتی اگر دستور cargo build
را از داخل دایرکتوری adder اجرا کنیم، فایلهای کامپایلشده همچنان در add/target قرار میگیرند نه در add/adder/target. Cargo دایرکتوری target را در یک workspace به این صورت ساختاردهی میکند زیرا crateهای موجود در یک workspace برای وابستگی به یکدیگر طراحی شدهاند. اگر هر crate دایرکتوری target اختصاصی خود را داشت، هر crate مجبور بود هر کدام از crateهای دیگر را در workspace دوباره کامپایل کند تا فایلهای کامپایلشده را در دایرکتوری target خود قرار دهد. با به اشتراکگذاری یک دایرکتوری target، crateها میتوانند از ساخت مجدد غیرضروری جلوگیری کنند.
ایجاد پکیج دوم در Workspace
Next, let’s create another member package in the workspace and call it
add_one
. Change the top-level Cargo.toml to specify the add_one path in
the members
list:
Filename: Cargo.toml
[workspace]
resolver = "3"
members = ["adder", "add_one"]
Then generate a new library crate named add_one
:
$ cargo new add_one --lib
Created library `add_one` package
Adding `add_one` as member of workspace at `file:///projects/add`
The top-level Cargo.toml will now include the add_one path in the members
list:
Filename: Cargo.toml
[workspace]
resolver = "3"
members = ["adder", "add_one"]
دایرکتوری 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
اضافه کنیم:
Filename: add_one/src/lib.rs
pub fn add_one(x: i32) -> i32 {
x + 1
}
حالا میتوانیم پکیج adder
که حاوی باینری ما است را وابسته به پکیج add_one
که حاوی کتابخانه ما است کنیم. ابتدا باید یک وابستگی مسیر (path dependency) به add_one
در فایل adder/Cargo.toml اضافه کنیم.
Filename: adder/Cargo.toml
[dependencies]
add_one = { path = "../add_one" }
Cargo فرض نمیکند که crateهای موجود در یک workspace به یکدیگر وابسته هستند، بنابراین ما باید به صراحت روابط وابستگی را مشخص کنیم.
در ادامه، بیایید از تابع add_one
(از crate به نام add_one
) در crate به نام adder
استفاده کنیم. فایل adder/src/main.rs را باز کنید و تابع main
را تغییر دهید تا تابع add_one
را فراخوانی کند، همانطور که در لیست ۱۴-۷ نشان داده شده است.
fn main() {
let num = 10;
println!("Hello, world! {num} plus one is {}!", add_one::add_one(num));
}
add_one
library crate in the adder
crateبیایید workspace را با اجرای دستور cargo build
در دایرکتوری سطح بالای add بسازیم!
$ cargo build
Compiling add_one v0.1.0 (file:///projects/add/add_one)
Compiling adder v0.1.0 (file:///projects/add/adder)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.22s
برای اجرای crate باینری از دایرکتوری add، میتوانیم با استفاده از آرگومان -p
و نام پکیج همراه با دستور cargo run
مشخص کنیم کدام پکیج در workspace اجرا شود:
$ cargo run -p adder
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/adder`
Hello, world! 10 plus one is 11!
این کد در فایل adder/src/main.rs را اجرا میکند که به crate add_one
وابسته است.
وابستگی به یک پکیج خارجی در یک Workspace
توجه کنید که workspace فقط یک فایل Cargo.lock در سطح بالا دارد، به جای اینکه هر crate دایرکتوری خود فایل Cargo.lock داشته باشد. این اطمینان حاصل میکند که تمام crateها از همان نسخه تمام وابستگیها استفاده میکنند. اگر پکیج rand
را به فایلهای adder/Cargo.toml و add_one/Cargo.toml اضافه کنیم، Cargo هر دو را به یک نسخه از rand
تبدیل میکند و آن را در فایل Cargo.lock ثبت میکند. اطمینان از اینکه همه crateهای موجود در workspace از همان وابستگیها استفاده میکنند، به این معناست که crateها همیشه با یکدیگر سازگار خواهند بود. بیایید پکیج rand
را به بخش [dependencies]
در فایل add_one/Cargo.toml اضافه کنیم تا بتوانیم از crate rand
در crate add_one
استفاده کنیم:
Filename: add_one/Cargo.toml
[dependencies]
rand = "0.8.5"
حالا میتوانیم use rand;
را به فایل add_one/src/lib.rs اضافه کنیم و با اجرای دستور cargo build
در دایرکتوری add کل workspace را بسازیم، که crate rand
را وارد کرده و کامپایل خواهد کرد. یک هشدار دریافت خواهیم کرد زیرا به rand
که به محدوده وارد شده است اشارهای نمیکنیم:
$ cargo build
Updating crates.io index
Downloaded rand v0.8.5
--snip--
Compiling rand v0.8.5
Compiling add_one v0.1.0 (file:///projects/add/add_one)
warning: unused import: `rand`
--> add_one/src/lib.rs:1:5
|
1 | use rand;
| ^^^^
|
= note: `#[warn(unused_imports)]` on by default
warning: `add_one` (lib) generated 1 warning (run `cargo fix --lib -p add_one` to apply 1 suggestion)
Compiling adder v0.1.0 (file:///projects/add/adder)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.95s
فایل Cargo.lock در سطح بالا اکنون اطلاعاتی درباره وابستگی add_one
به rand
دارد. با این حال، حتی اگر rand
در جایی از workspace استفاده شود، نمیتوانیم از آن در crateهای دیگر workspace استفاده کنیم مگر اینکه rand
را به فایلهای Cargo.toml آنها نیز اضافه کنیم. برای مثال، اگر use rand;
را به فایل adder/src/main.rs برای پکیج adder
اضافه کنیم، با خطا مواجه خواهیم شد:
$ cargo build
--snip--
Compiling adder v0.1.0 (file:///projects/add/adder)
error[E0432]: unresolved import `rand`
--> adder/src/main.rs:2:5
|
2 | use rand;
| ^^^^ no external crate `rand`
To fix this, edit the Cargo.toml file for the adder
package and indicate
that rand
is a dependency for it as well. Building the adder
package will
add rand
to the list of dependencies for adder
in Cargo.lock, but no
additional copies of rand
will be downloaded. Cargo will ensure that every
crate in every package in the workspace using the rand
package will be using
the same version as long as they specify compatible versions of rand
, saving
us space and ensuring that the crates in the workspace will be compatible with
each other.
If crates in the workspace specify incompatible versions of the same dependency, Cargo will resolve each of them, but will still try to resolve as few versions as possible.
افزودن یک تست به یک Workspace
برای یک بهبود دیگر، بیایید یک تست برای تابع add_one::add_one
در crate add_one
اضافه کنیم:
Filename: add_one/src/lib.rs
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));
}
}
حالا دستور cargo test
را در دایرکتوری سطح بالای add اجرا کنید. اجرای دستور cargo test
در یک workspace با ساختاری مانند این، تستهای تمام crateهای موجود در workspace را اجرا خواهد کرد:
$ cargo test
Compiling add_one v0.1.0 (file:///projects/add/add_one)
Compiling adder v0.1.0 (file:///projects/add/adder)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.20s
Running unittests src/lib.rs (target/debug/deps/add_one-93c49ee75dc46543)
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-3a47283c568d2b6a)
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
بخش اول خروجی نشان میدهد که تست it_works
در crate add_one
پاس شده است. بخش بعدی نشان میدهد که هیچ تستی در crate adder
پیدا نشده است، و سپس بخش آخر نشان میدهد که هیچ تست مستنداتی در crate add_one
پیدا نشده است.
ما همچنین میتوانیم تستهای یک crate خاص در workspace را از دایرکتوری سطح بالا با استفاده از گزینه -p
و مشخص کردن نام crateای که میخواهیم تست کنیم، اجرا کنیم:
$ cargo test -p add_one
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.00s
Running unittests src/lib.rs (target/debug/deps/add_one-93c49ee75dc46543)
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
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
این خروجی نشان میدهد که cargo test
فقط تستهای crate add_one
را اجرا کرده و تستهای crate adder
را اجرا نکرده است.
If you publish the crates in the workspace to crates.io,
each crate in the workspace will need to be published separately. Like cargo test
, we can publish a particular crate in our workspace by using the -p
flag and specifying the name of the crate we want to publish.
For additional practice, add an add_two
crate to this workspace in a similar
way as the add_one
crate!
As your project grows, consider using a workspace: it’s easier to understand smaller, individual components than one big blob of code. Furthermore, keeping the crates in a workspace can make coordination between crates easier if they are often changed at the same time.