]> git.proxmox.com Git - rustc.git/blame - src/doc/book/src/ch14-03-cargo-workspaces.md
New upstream version 1.36.0+dfsg1
[rustc.git] / src / doc / book / src / ch14-03-cargo-workspaces.md
CommitLineData
13cf67c4
XL
1## Cargo Workspaces
2
3In Chapter 12, we built a package that included a binary crate and a library
4crate. As your project develops, you might find that the library crate
5continues to get bigger and you want to split up your package further into
6multiple library crates. In this situation, Cargo offers a feature called
7*workspaces* that can help manage multiple related packages that are developed
8in tandem.
9
10### Creating a Workspace
11
12A *workspace* is a set of packages that share the same *Cargo.lock* and output
13directory. Let’s make a project using a workspace—we’ll use trivial code so we
14can concentrate on the structure of the workspace. There are multiple ways to
15structure a workspace; we’re going to show one common way. We’ll have a
16workspace containing a binary and two libraries. The binary, which will provide
17the main functionality, will depend on the two libraries. One library will
18provide an `add_one` function, and a second library an `add_two` function.
19These three crates will be part of the same workspace. We’ll start by creating
20a new directory for the workspace:
21
22```text
23$ mkdir add
24$ cd add
25```
26
27Next, in the *add* directory, we create the *Cargo.toml* file that will
28configure the entire workspace. This file won’t have a `[package]` section or
29the metadata we’ve seen in other *Cargo.toml* files. Instead, it will start
30with a `[workspace]` section that will allow us to add members to the workspace
31by specifying the path to our binary crate; in this case, that path is *adder*:
32
33<span class="filename">Filename: Cargo.toml</span>
34
35```toml
36[workspace]
37
38members = [
39 "adder",
40]
41```
42
43Next, we’ll create the `adder` binary crate by running `cargo new` within the
44*add* directory:
45
46```text
47$ cargo new adder
48 Created binary (application) `adder` project
49```
50
51At this point, we can build the workspace by running `cargo build`. The files
52in your *add* directory should look like this:
53
54```text
55├── Cargo.lock
56├── Cargo.toml
57├── adder
58│ ├── Cargo.toml
59│ └── src
60│ └── main.rs
61└── target
62```
63
64The workspace has one *target* directory at the top level for the compiled
65artifacts to be placed into; the `adder` crate doesn’t have its own *target*
66directory. Even if we were to run `cargo build` from inside the *adder*
67directory, the compiled artifacts would still end up in *add/target* rather
68than *add/adder/target*. Cargo structures the *target* directory in a workspace
69like this because the crates in a workspace are meant to depend on each other.
70If each crate had its own *target* directory, each crate would have to
71recompile each of the other crates in the workspace to have the artifacts in
72its own *target* directory. By sharing one *target* directory, the crates can
73avoid unnecessary rebuilding.
74
75### Creating the Second Crate in the Workspace
76
77Next, let’s create another member crate in the workspace and call it `add-one`.
78Change the top-level *Cargo.toml* to specify the *add-one* path in the
79`members` list:
80
81<span class="filename">Filename: Cargo.toml</span>
82
83```toml
84[workspace]
85
86members = [
87 "adder",
88 "add-one",
89]
90```
91
92Then generate a new library crate named `add-one`:
93
94```text
95$ cargo new add-one --lib
96 Created library `add-one` project
97```
98
99Your *add* directory should now have these directories and files:
100
101```text
102├── Cargo.lock
103├── Cargo.toml
104├── add-one
105│ ├── Cargo.toml
106│ └── src
107│ └── lib.rs
108├── adder
109│ ├── Cargo.toml
110│ └── src
111│ └── main.rs
112└── target
113```
114
115In the *add-one/src/lib.rs* file, let’s add an `add_one` function:
116
117<span class="filename">Filename: add-one/src/lib.rs</span>
118
119```rust
120pub fn add_one(x: i32) -> i32 {
121 x + 1
122}
123```
124
125Now that we have a library crate in the workspace, we can have the binary crate
126`adder` depend on the library crate `add-one`. First, we’ll need to add a path
127dependency on `add-one` to *adder/Cargo.toml*.
128
129<span class="filename">Filename: adder/Cargo.toml</span>
130
131```toml
132[dependencies]
133
134add-one = { path = "../add-one" }
135```
136
137Cargo doesn’t assume that crates in a workspace will depend on each other, so
138we need to be explicit about the dependency relationships between the crates.
139
140Next, let’s use the `add_one` function from the `add-one` crate in the `adder`
9fa01778 141crate. Open the *adder/src/main.rs* file and add a `use` line at the top to
13cf67c4 142bring the new `add-one` library crate into scope. Then change the `main`
9fa01778 143function to call the `add_one` function, as in Listing 14-7.
13cf67c4
XL
144
145<span class="filename">Filename: adder/src/main.rs</span>
146
147```rust,ignore
148use add_one;
149
150fn main() {
151 let num = 10;
152 println!("Hello, world! {} plus one is {}!", num, add_one::add_one(num));
153}
154```
155
156<span class="caption">Listing 14-7: Using the `add-one` library crate from the
157`adder` crate</span>
158
159Let’s build the workspace by running `cargo build` in the top-level *add*
160directory!
161
162```text
163$ cargo build
164 Compiling add-one v0.1.0 (file:///projects/add/add-one)
165 Compiling adder v0.1.0 (file:///projects/add/adder)
166 Finished dev [unoptimized + debuginfo] target(s) in 0.68 secs
167```
168
169To run the binary crate from the *add* directory, we need to specify which
170package in the workspace we want to use by using the `-p` argument and the
171package name with `cargo run`:
172
173```text
174$ cargo run -p adder
175 Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
176 Running `target/debug/adder`
177Hello, world! 10 plus one is 11!
178```
179
180This runs the code in *adder/src/main.rs*, which depends on the `add-one` crate.
181
182#### Depending on an External Crate in a Workspace
183
184Notice that the workspace has only one *Cargo.lock* file at the top level of
185the workspace rather than having a *Cargo.lock* in each crate’s directory. This
186ensures that all crates are using the same version of all dependencies. If we
187add the `rand` crate to the *adder/Cargo.toml* and *add-one/Cargo.toml*
188files, Cargo will resolve both of those to one version of `rand` and record
189that in the one *Cargo.lock*. Making all crates in the workspace use the same
190dependencies means the crates in the workspace will always be compatible with
191each other. Let’s add the `rand` crate to the `[dependencies]` section in the
192*add-one/Cargo.toml* file to be able to use the `rand` crate in the `add-one`
193crate:
194
195<span class="filename">Filename: add-one/Cargo.toml</span>
196
197```toml
198[dependencies]
199
200rand = "0.3.14"
201```
202
203We can now add `use rand;` to the *add-one/src/lib.rs* file, and building the
204whole workspace by running `cargo build` in the *add* directory will bring in
205and compile the `rand` crate:
206
207```text
208$ cargo build
209 Updating registry `https://github.com/rust-lang/crates.io-index`
210 Downloading rand v0.3.14
211 --snip--
212 Compiling rand v0.3.14
213 Compiling add-one v0.1.0 (file:///projects/add/add-one)
214 Compiling adder v0.1.0 (file:///projects/add/adder)
215 Finished dev [unoptimized + debuginfo] target(s) in 10.18 secs
216```
217
218The top-level *Cargo.lock* now contains information about the dependency of
219`add-one` on `rand`. However, even though `rand` is used somewhere in the
220workspace, we can’t use it in other crates in the workspace unless we add
532ac7d7
XL
221`rand` to their *Cargo.toml* files as well. For example, if we add `use rand;`
222to the *adder/src/main.rs* file for the `adder` crate, we’ll get an error:
13cf67c4
XL
223
224```text
225$ cargo build
226 Compiling adder v0.1.0 (file:///projects/add/adder)
227error: use of unstable library feature 'rand': use `rand` from crates.io (see
228issue #27703)
229 --> adder/src/main.rs:1:1
230 |
2311 | use rand;
232```
233
234To fix this, edit the *Cargo.toml* file for the `adder` crate and indicate that
235`rand` is a dependency for that crate as well. Building the `adder` crate will
236add `rand` to the list of dependencies for `adder` in *Cargo.lock*, but no
237additional copies of `rand` will be downloaded. Cargo has ensured that every
238crate in the workspace using the `rand` crate will be using the same version.
239Using the same version of `rand` across the workspace saves space because we
240won’t have multiple copies and ensures that the crates in the workspace will be
241compatible with each other.
242
243#### Adding a Test to a Workspace
244
245For another enhancement, let’s add a test of the `add_one::add_one` function
246within the `add_one` crate:
247
248<span class="filename">Filename: add-one/src/lib.rs</span>
249
250```rust
251pub fn add_one(x: i32) -> i32 {
252 x + 1
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258
259 #[test]
260 fn it_works() {
261 assert_eq!(3, add_one(2));
262 }
263}
264```
265
266Now run `cargo test` in the top-level *add* directory:
267
268```text
269$ cargo test
270 Compiling add-one v0.1.0 (file:///projects/add/add-one)
271 Compiling adder v0.1.0 (file:///projects/add/adder)
272 Finished dev [unoptimized + debuginfo] target(s) in 0.27 secs
273 Running target/debug/deps/add_one-f0253159197f7841
274
275running 1 test
276test tests::it_works ... ok
277
278test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
279
280 Running target/debug/deps/adder-f88af9d2cc175a5e
281
282running 0 tests
283
284test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
285
286 Doc-tests add-one
287
288running 0 tests
289
290test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
291```
292
293The first section of the output shows that the `it_works` test in the `add-one`
294crate passed. The next section shows that zero tests were found in the `adder`
295crate, and then the last section shows zero documentation tests were found in
296the `add-one` crate. Running `cargo test` in a workspace structured like this
297one will run the tests for all the crates in the workspace.
298
299We can also run tests for one particular crate in a workspace from the
300top-level directory by using the `-p` flag and specifying the name of the crate
301we want to test:
302
303```text
304$ cargo test -p add-one
305 Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
306 Running target/debug/deps/add_one-b3235fea9a156f74
307
308running 1 test
309test tests::it_works ... ok
310
311test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
312
313 Doc-tests add-one
314
315running 0 tests
316
317test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
318```
319
320This output shows `cargo test` only ran the tests for the `add-one` crate and
321didn’t run the `adder` crate tests.
322
323If you publish the crates in the workspace to *https://crates.io/*, each crate
324in the workspace will need to be published separately. The `cargo publish`
325command does not have an `--all` flag or a `-p` flag, so you must change to
326each crate’s directory and run `cargo publish` on each crate in the workspace
327to publish the crates.
328
329For additional practice, add an `add-two` crate to this workspace in a similar
330way as the `add-one` crate!
331
332As your project grows, consider using a workspace: it’s easier to understand
333smaller, individual components than one big blob of code. Furthermore, keeping
334the crates in a workspace can make coordination between them easier if they are
335often changed at the same time.