331 lines
16 KiB
HTML
331 lines
16 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>Cargo Workspaces</title>
|
||
</head>
|
||
<body>
|
||
<h2 id="cargo-workspaces"><a class="header" href="#cargo-workspaces">Cargo Workspaces</a></h2>
|
||
<p>In Chapter 12, we built a package that included a binary crate and a library
|
||
crate. As your project develops, 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 <em>workspaces</em> that can
|
||
help manage multiple related packages that are developed in tandem.</p>
|
||
<h3 id="creating-a-workspace"><a class="header" href="#creating-a-workspace">Creating a Workspace</a></h3>
|
||
<p>A <em>workspace</em> is a set of packages that share the same <em>Cargo.lock</em> and output
|
||
directory. Let’s make a project using a workspace—we’ll use trivial code so
|
||
that 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 <code>add_one</code> function and the other library an <code>add_two</code> function.
|
||
These three crates will be part of the same workspace. We’ll start by creating
|
||
a new directory for the workspace:</p>
|
||
<pre><code class="language-console">$ mkdir add
|
||
$ cd add
|
||
</code></pre>
|
||
<p>Next, in the <em>add</em> directory, we create the <em>Cargo.toml</em> file that will
|
||
configure the entire workspace. This file won’t have a <code>[package]</code> section.
|
||
Instead, it will start with a <code>[workspace]</code> 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
|
||
<code>resolver</code> value to <code>"3"</code>:</p>
|
||
<p><span class="filename">Filename: Cargo.toml</span></p>
|
||
<pre><code class="language-toml">[workspace]
|
||
resolver = "3"
|
||
</code></pre>
|
||
<p>Next, we’ll create the <code>adder</code> binary crate by running <code>cargo new</code> within the
|
||
<em>add</em> directory:</p>
|
||
<!-- manual-regeneration
|
||
cd listings/ch14-more-about-cargo/output-only-01-adder-crate/add
|
||
remove `members = ["adder"]` from Cargo.toml
|
||
rm -rf adder
|
||
cargo new adder
|
||
copy output below
|
||
-->
|
||
<pre><code class="language-console">$ cargo new adder
|
||
Created binary (application) `adder` package
|
||
Adding `adder` as member of workspace at `file:///projects/add`
|
||
</code></pre>
|
||
<p>Running <code>cargo new</code> inside a workspace also automatically adds the newly created
|
||
package to the <code>members</code> key in the <code>[workspace]</code> definition in the workspace
|
||
<em>Cargo.toml</em>, like this:</p>
|
||
<pre><code class="language-toml">[workspace]
|
||
resolver = "3"
|
||
members = ["adder"]
|
||
</code></pre>
|
||
<p>At this point, we can build the workspace by running <code>cargo build</code>. The files
|
||
in your <em>add</em> directory should look like this:</p>
|
||
<pre><code class="language-text">├── Cargo.lock
|
||
├── Cargo.toml
|
||
├── adder
|
||
│ ├── Cargo.toml
|
||
│ └── src
|
||
│ └── main.rs
|
||
└── target
|
||
</code></pre>
|
||
<p>The workspace has one <em>target</em> directory at the top level that the compiled
|
||
artifacts will be placed into; the <code>adder</code> package doesn’t have its own
|
||
<em>target</em> directory. Even if we were to run <code>cargo build</code> from inside the
|
||
<em>adder</em> directory, the compiled artifacts would still end up in <em>add/target</em>
|
||
rather than <em>add/adder/target</em>. Cargo structures the <em>target</em> directory in a
|
||
workspace like this because the crates in a workspace are meant to depend on
|
||
each other. If each crate had its own <em>target</em> directory, each crate would have
|
||
to recompile each of the other crates in the workspace to place the artifacts
|
||
in its own <em>target</em> directory. By sharing one <em>target</em> directory, the crates
|
||
can avoid unnecessary rebuilding.</p>
|
||
<h3 id="creating-the-second-package-in-the-workspace"><a class="header" href="#creating-the-second-package-in-the-workspace">Creating the Second Package in the Workspace</a></h3>
|
||
<p>Next, let’s create another member package in the workspace and call it
|
||
<code>add_one</code>. Generate a new library crate named <code>add_one</code>:</p>
|
||
<!-- manual-regeneration
|
||
cd listings/ch14-more-about-cargo/output-only-02-add-one/add
|
||
remove `"add_one"` from `members` list in Cargo.toml
|
||
rm -rf add_one
|
||
cargo new add_one --lib
|
||
copy output below
|
||
-->
|
||
<pre><code class="language-console">$ cargo new add_one --lib
|
||
Created library `add_one` package
|
||
Adding `add_one` as member of workspace at `file:///projects/add`
|
||
</code></pre>
|
||
<p>The top-level <em>Cargo.toml</em> will now include the <em>add_one</em> path in the <code>members</code>
|
||
list:</p>
|
||
<p><span class="filename">Filename: Cargo.toml</span></p>
|
||
<pre><code class="language-toml">[workspace]
|
||
resolver = "3"
|
||
members = ["adder", "add_one"]
|
||
</code></pre>
|
||
<p>Your <em>add</em> directory should now have these directories and files:</p>
|
||
<pre><code class="language-text">├── Cargo.lock
|
||
├── Cargo.toml
|
||
├── add_one
|
||
│ ├── Cargo.toml
|
||
│ └── src
|
||
│ └── lib.rs
|
||
├── adder
|
||
│ ├── Cargo.toml
|
||
│ └── src
|
||
│ └── main.rs
|
||
└── target
|
||
</code></pre>
|
||
<p>In the <em>add_one/src/lib.rs</em> file, let’s add an <code>add_one</code> function:</p>
|
||
<p><span class="filename">Filename: add_one/src/lib.rs</span></p>
|
||
<pre><code class="language-rust noplayground">pub fn add_one(x: i32) -> i32 {
|
||
x + 1
|
||
}</code></pre>
|
||
<p>Now we can have the <code>adder</code> package with our binary depend on the <code>add_one</code>
|
||
package that has our library. First, we’ll need to add a path dependency on
|
||
<code>add_one</code> to <em>adder/Cargo.toml</em>.</p>
|
||
<p><span class="filename">Filename: adder/Cargo.toml</span></p>
|
||
<pre><code class="language-toml">[dependencies]
|
||
add_one = { path = "../add_one" }
|
||
</code></pre>
|
||
<p>Cargo doesn’t assume that crates in a workspace will depend on each other, so
|
||
we need to be explicit about the dependency relationships.</p>
|
||
<p>Next, let’s use the <code>add_one</code> function (from the <code>add_one</code> crate) in the
|
||
<code>adder</code> crate. Open the <em>adder/src/main.rs</em> file and change the <code>main</code>
|
||
function to call the <code>add_one</code> function, as in Listing 14-7.</p>
|
||
<figure class="listing" id="listing-14-7">
|
||
<span class="file-name">Filename: adder/src/main.rs</span>
|
||
<pre><code class="language-rust ignore">fn main() {
|
||
let num = 10;
|
||
println!("Hello, world! {num} plus one is {}!", add_one::add_one(num));
|
||
}</code></pre>
|
||
<figcaption><a href="#listing-14-7">Listing 14-7</a>: Using the <code>add_one</code> library crate from the <code>adder</code> crate</figcaption>
|
||
</figure>
|
||
<p>Let’s build the workspace by running <code>cargo build</code> in the top-level <em>add</em>
|
||
directory!</p>
|
||
<!-- manual-regeneration
|
||
cd listings/ch14-more-about-cargo/listing-14-07/add
|
||
cargo build
|
||
copy output below; the output updating script doesn't handle subdirectories in paths properly
|
||
-->
|
||
<pre><code class="language-console">$ 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
|
||
</code></pre>
|
||
<p>To run the binary crate from the <em>add</em> directory, we can specify which package
|
||
in the workspace we want to run by using the <code>-p</code> argument and the package name
|
||
with <code>cargo run</code>:</p>
|
||
<!-- manual-regeneration
|
||
cd listings/ch14-more-about-cargo/listing-14-07/add
|
||
cargo run -p adder
|
||
copy output below; the output updating script doesn't handle subdirectories in paths properly
|
||
-->
|
||
<pre><code class="language-console">$ 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!
|
||
</code></pre>
|
||
<p>This runs the code in <em>adder/src/main.rs</em>, which depends on the <code>add_one</code> crate.</p>
|
||
<!-- Old headings. Do not remove or links may break. -->
|
||
<p><a id="depending-on-an-external-package-in-a-workspace"></a></p>
|
||
<h3 id="depending-on-an-external-package"><a class="header" href="#depending-on-an-external-package">Depending on an External Package</a></h3>
|
||
<p>Notice that the workspace has only one <em>Cargo.lock</em> file at the top level,
|
||
rather than having a <em>Cargo.lock</em> in each crate’s directory. This ensures that
|
||
all crates are using the same version of all dependencies. If we add the <code>rand</code>
|
||
package to the <em>adder/Cargo.toml</em> and <em>add_one/Cargo.toml</em> files, Cargo will
|
||
resolve both of those to one version of <code>rand</code> and record that in the one
|
||
<em>Cargo.lock</em>. Making all crates in the workspace use the same dependencies
|
||
means the crates will always be compatible with each other. Let’s add the
|
||
<code>rand</code> crate to the <code>[dependencies]</code> section in the <em>add_one/Cargo.toml</em> file
|
||
so that we can use the <code>rand</code> crate in the <code>add_one</code> crate:</p>
|
||
<!-- When updating the version of `rand` used, also update the version of
|
||
`rand` used in these files so they all match:
|
||
* ch02-00-guessing-game-tutorial.md
|
||
* ch07-04-bringing-paths-into-scope-with-the-use-keyword.md
|
||
-->
|
||
<p><span class="filename">Filename: add_one/Cargo.toml</span></p>
|
||
<pre><code class="language-toml">[dependencies]
|
||
rand = "0.8.5"
|
||
</code></pre>
|
||
<p>We can now add <code>use rand;</code> to the <em>add_one/src/lib.rs</em> file, and building the
|
||
whole workspace by running <code>cargo build</code> in the <em>add</em> directory will bring in
|
||
and compile the <code>rand</code> crate. We will get one warning because we aren’t
|
||
referring to the <code>rand</code> we brought into scope:</p>
|
||
<!-- manual-regeneration
|
||
cd listings/ch14-more-about-cargo/no-listing-03-workspace-with-external-dependency/add
|
||
cargo build
|
||
copy output below; the output updating script doesn't handle subdirectories in paths properly
|
||
-->
|
||
<pre><code class="language-console">$ 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
|
||
</code></pre>
|
||
<p>The top-level <em>Cargo.lock</em> now contains information about the dependency of
|
||
<code>add_one</code> on <code>rand</code>. However, even though <code>rand</code> is used somewhere in the
|
||
workspace, we can’t use it in other crates in the workspace unless we add
|
||
<code>rand</code> to their <em>Cargo.toml</em> files as well. For example, if we add <code>use rand;</code>
|
||
to the <em>adder/src/main.rs</em> file for the <code>adder</code> package, we’ll get an error:</p>
|
||
<!-- manual-regeneration
|
||
cd listings/ch14-more-about-cargo/output-only-03-use-rand/add
|
||
cargo build
|
||
copy output below; the output updating script doesn't handle subdirectories in paths properly
|
||
-->
|
||
<pre><code class="language-console">$ 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`
|
||
</code></pre>
|
||
<p>To fix this, edit the <em>Cargo.toml</em> file for the <code>adder</code> package and indicate
|
||
that <code>rand</code> is a dependency for it as well. Building the <code>adder</code> package will
|
||
add <code>rand</code> to the list of dependencies for <code>adder</code> in <em>Cargo.lock</em>, but no
|
||
additional copies of <code>rand</code> will be downloaded. Cargo will ensure that every
|
||
crate in every package in the workspace using the <code>rand</code> package will use the
|
||
same version as long as they specify compatible versions of <code>rand</code>, saving us
|
||
space and ensuring that the crates in the workspace will be compatible with
|
||
each other.</p>
|
||
<p>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.</p>
|
||
<h3 id="adding-a-test-to-a-workspace"><a class="header" href="#adding-a-test-to-a-workspace">Adding a Test to a Workspace</a></h3>
|
||
<p>For another enhancement, let’s add a test of the <code>add_one::add_one</code> function
|
||
within the <code>add_one</code> crate:</p>
|
||
<p><span class="filename">Filename: add_one/src/lib.rs</span></p>
|
||
<pre><code class="language-rust noplayground">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));
|
||
}
|
||
}</code></pre>
|
||
<p>Now run <code>cargo test</code> in the top-level <em>add</em> directory. Running <code>cargo test</code> in
|
||
a workspace structured like this one will run the tests for all the crates in
|
||
the workspace:</p>
|
||
<!-- manual-regeneration
|
||
cd listings/ch14-more-about-cargo/no-listing-04-workspace-with-tests/add
|
||
cargo test
|
||
copy output below; the output updating script doesn't handle subdirectories in
|
||
paths properly
|
||
-->
|
||
<pre><code class="language-console">$ 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
|
||
</code></pre>
|
||
<p>The first section of the output shows that the <code>it_works</code> test in the <code>add_one</code>
|
||
crate passed. The next section shows that zero tests were found in the <code>adder</code>
|
||
crate, and then the last section shows that zero documentation tests were found
|
||
in the <code>add_one</code> crate.</p>
|
||
<p>We can also run tests for one particular crate in a workspace from the
|
||
top-level directory by using the <code>-p</code> flag and specifying the name of the crate
|
||
we want to test:</p>
|
||
<!-- manual-regeneration
|
||
cd listings/ch14-more-about-cargo/no-listing-04-workspace-with-tests/add
|
||
cargo test -p add_one
|
||
copy output below; the output updating script doesn't handle subdirectories in paths properly
|
||
-->
|
||
<pre><code class="language-console">$ 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
|
||
</code></pre>
|
||
<p>This output shows <code>cargo test</code> only ran the tests for the <code>add_one</code> crate and
|
||
didn’t run the <code>adder</code> crate tests.</p>
|
||
<p>If you publish the crates in the workspace to
|
||
<a href="https://crates.io/">crates.io</a><!-- ignore -->, each crate in the workspace
|
||
will need to be published separately. Like <code>cargo test</code>, we can publish a
|
||
particular crate in our workspace by using the <code>-p</code> flag and specifying the
|
||
name of the crate we want to publish.</p>
|
||
<p>For additional practice, add an <code>add_two</code> crate to this workspace in a similar
|
||
way as the <code>add_one</code> crate!</p>
|
||
<p>As your project grows, consider using a workspace: It enables you to work with
|
||
smaller, easier-to-understand 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.</p>
|
||
</body>
|
||
</html>
|