mirror of
https://github.com/darkicewolf50/RustBrock.git
synced 2025-06-15 04:54:17 -06:00
finished ch14.4
This commit is contained in:
parent
43816205f3
commit
629cbea79d
@ -1 +1,319 @@
|
||||
# Cargo Workspaces
|
||||
# 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.
|
@ -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
1
Install Binaries.md
Normal file
@ -0,0 +1 @@
|
||||
# Installing Binaries with `cargo install`
|
Loading…
x
Reference in New Issue
Block a user