finished ch14.4
Some checks failed
Test Gitea Actions / first (push) Successful in 15s
Test Gitea Actions / check-code (push) Failing after 13s
Test Gitea Actions / test (push) Has been skipped
Test Gitea Actions / documentation-check (push) Has been skipped

This commit is contained in:
darkicewolf50 2025-02-26 16:41:39 -07:00
parent 43816205f3
commit 629cbea79d
3 changed files with 321 additions and 2 deletions

View File

@ -1 +1,319 @@
# Cargo Workspaces
As our project developsm you might find that the library crate continues to get bigger and you want to split your package further into multiple library crates.
Cargo offers a feature called *workspaces* that can help manage related packages that are developed at the same time
## Creating a Workspace
A *workspace* is a set of packages that share the same *Cargo.lock* and output directory.
Lets explore this by a making a project using a workspace
Here we will use trivial code so that the focus is on the structure of the workspace.
There are mulitple ways to structure a workspace, here we will just show one common way.
Here we will have a workspace containing a binary and two libraries.
The binary will provide the main functionality, will depend on the two libraries.
One lib will provide an `add_one` function
Another lib will provide an `add_two` function
These three crates will be part of the same workspace.
We will start by creating a new directory for the workspace
```
$ mkdir add
$ cd add
```
Now in the *add* directory, we create the *Cargo.toml* file that will config the entire workspace.
This file wont have a `[package]` section, instead it will start with a `[workspace]` section.
This allows us to add members to the workspace.
We also make it a point to use the latest and greatest version of Cargo's resolver algorithm in our workspace by setting the `resolver` to `"2"`
Here is the written out version of the toml file until this point
```toml
[workspace]
resolver = "2"
```
Next we will create the `adder` binary crate
We do this by running `cargo new` within the *add* directory
```
$ cargo new adder
Creating binary (application) `adder` package
Adding `adder` as member of workspace at `file:///projects/add`
```
By running the `cargo new` command inside a workspace it automatically adds the newly created package to the `members` key in the `[workspace]` definition in the workspace `Cargo.toml`
It will look like this
```toml
[workspace]
resolver = "2"
members = ["adder"]
```
Now at this point we can build the workspace by rnning `cargo build`
The files in the *add* directory should now look like this
```
├── Cargo.lock
├── Cargo.toml
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
```
The workspace has one *target* directory at the top level that compled artifacts will be placed into
The `adder` package doesn't have its own *target* directory.
Even if we were to run `cargo build` from inside the *addre* dir, the compiled artifacts would still end up in *add/target* rather than *add/adder/target*
Cargo structs the *target* directory in a workspace like this because the crates in a workspace are meant to depend on each other.
Without this we would have to repeat compiled artifacts and this would cause unnecessary rebuilding.
So we share one *target* directory to avoid this.
## Creating the Second Package in the Wrokspace
Now lets add another package to the workspace, we will call it
First we will change the top level *Cargo.toml* to specify the *add_one* path in the `members` list
Here is the updated toml
```toml
[workspace]
resolver = "2"
members = ["adder", "add_one"]
```
Now lets generate a new library crate named `add_one`
```
$ cargo new add_one --lib
Creating library `add_one` package
Adding `add_one` as member of workspace at `file:///projects/add`
```
Now the *add* directory should look like this
```
├── Cargo.lock
├── Cargo.toml
├── add_one
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
```
In *add_one/src/lib.rs* lets add the `add_one` function
```rust
pub fn add_one(x: i32) -> i32 {
x + 1
}
```
Now we need to have the `adder` package with our binary depend on the `add_one` package that has our library.
We need to add a path dependency on `add_one` to *adder/Cargo.toml*
```toml
[dependencies]
add_one = { path = "../add_one" }
```
Cargo doesnt assume that crates in a workspace will depend on each other.
We must explicit about the dependency relationships.
Now lets use the `add_one` function in the `adder` crate.
You modify the *adder/src/main.rs* file and change the `main` function to call the `add_one` function
Here is the updated `main` function in the binary crate
```rust
fn main() {
let num = 10;
println!("Hello, world! {num} plus one is {}!", add_one::add_one(num));
}
```
Now we can build the workspace from the top-level *add* directory
```
$ 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
```
In order to run the binary crate from the *add* directory have have to specify which package in the workspace we want to run.
We do this by using the `-p` argument and the package name with `cargo run`
```
$ 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!
```
This runs the `main` function in *adder/src/main.rs*, which depends on the `add_one` crate
### Depending on an External Package in a Wrokspace
Notice that the workspace only has one *Cargo.lock* file at the top level.
This ensure that all crates are using the same version of all dependencies.
If we add the `rand` package to the *adder/Cargo.toml* and *add_one/Cargo.toml* files
Cargo will resolve both of thos to one version of `rand` and record that in the one *Cargo.lock*.
This ensures that all the crates in the workspace use the same dependencies, this also ensures that all crates will always be compatible with each other.
Lets demonstrate how to add the `rand` crate to the `[dependencies]` section in the *add_one/Cargo.toml* file so we can use the `rand` crate in the `add_one` crate
```toml
[dependencies]
rand = "0.8.5"
```
Now we can add `use rand;` to the *add_one/src/lib.rs* file.
Then build the whole workspace in the *add* directory will bring in and compile the `rand` crate.
We will get a warning about a unused `rand` becasue we dont refer to it after we bring it into scope
```
$ 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
```
Now the top level *Cargo.lock* now contains info about the dependency of `add_one` on `rand`
However, even though `rand` is used somewhere in the workspace, we can't use it in other crates in the workspace unless we add `rand` to thier *Cargo.toml* files as well
For example if we add `use rand;` to the *adder/src/main.rs* file for the `adder` package we would get an error
```
$ 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`
```
In order to fix this we need to edit the *Cargo.toml* file for the `adder` package and indicate that `rand` is a dependency for it as well.
Rebuilding the `adder` package with the eidt will now add the `rand` crate to the list of dependencies for `adder` in *Cargo.lock*.
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 log as they specify compatible versions of `rand`.
This saves us space and ensures that the crates in the workspace will be compatible with each other.
If crates in the workspace specify incompatible verions of the same dependency, Cargo will attempt to resolve each of them but it will still try to resolve it with as few versions as possible
### Adding a Test to a Workspace
For another enchancement lets add a test of the `add)one::add_one` function within the `add_one` crate
Here is the updated *add_one/src/lib.rs*
```rust
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));
}
}
```
Now if we run `cargo test` in the top level *add* directory in a workspace structured like thisone will run the tests for all the crates in the workspace.
Here is what the output will look like this
```
$ 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-f0253159197f7841)
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-49979ff40686fa8e)
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
```
The first section shows that the `it_works` test in the `add_one` crate pased
The next section shows that zero tests were found in the `adder` crate.
Thhe last section shows zero documentation tests were found in the `add_one` crate.
We can also run tests for a particular crate in a workspace from the top-level directory.
We do this by adding the `-p` flag and specifying th name of the crate we want to test.
Here is an example of this
```
$ 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-b3235fea9a156f74)
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
```
This shows that `cargo test` only ran tests for the `add_one` crate and didn't run the `adder` crate tests.
If you publish the crates in the workspace to [crates.io](https://crates.io), each crate in the workspace will need to be published separately.
Just like `cargo test`, you can publish a particular crate in the workspace by using the `-p` flag and specifying the name of the crate we want to publish.
As your project grows, consider using a workspace.
It makes it easier to understand smaller, individual components than one big blog of code.
Also by keeping the crates in a workspace can make coordination between crates easier if they are often changed at the same time.

View File

@ -8,7 +8,7 @@ This chapter will go over some more advanced features like:
- [Customize your build](./Custome%20Build%20Profiles.md) through release profiles
- [Publish libraries](./Publishing%20libraries.md) on [crates.io](https://crates.io/)
- Organize large projects with [workspaces](./Cargo%20Workspaces.md)
- Install binaries from [crates.io](https://crates.io/)
- [Install binaries](./Install%20Binaries.md) from [crates.io](https://crates.io/)
- Extend Cargo using custom commands
Cargo can do even more than what will be covered.

1
Install Binaries.md Normal file
View File

@ -0,0 +1 @@
# Installing Binaries with `cargo install`