]> git.proxmox.com Git - rustc.git/blame - src/doc/book/second-edition/src/ch14-03-cargo-workspaces.md
New upstream version 1.21.0+dfsg1
[rustc.git] / src / doc / book / second-edition / src / ch14-03-cargo-workspaces.md
CommitLineData
cc61c64b
XL
1## Cargo Workspaces
2
3In Chapter 12, we built a package that included both a binary crate and a
3b2f2976
XL
4library crate. You may find, as your project develops, that the library crate
5continues to get bigger and you want to split your package up further into
6multiple library crates. In this situation, Cargo has a feature called
7*workspaces* that can help manage multiple related packages that are developed
8in tandem.
cc61c64b
XL
9
10A *workspace* is a set of packages that will all share the same *Cargo.lock*
3b2f2976
XL
11and output directory. Let’s make a project using a workspace, using trivial
12code so we can concentrate on the structure of a workspace. We’ll have a binary
13that uses two libraries: one library that will provide an `add_one` function
14and a second library that will provide an `add_two` function. These three
15crates will all be part of the same workspace. We’ll start by creating a new
16crate for the binary:
cc61c64b
XL
17
18```text
19$ cargo new --bin adder
20 Created binary (application) `adder` project
21$ cd adder
22```
23
3b2f2976
XL
24We need to modify the binary package’s *Cargo.toml* and add a `[workspace]`
25section to tell Cargo the `adder` package is a workspace. Add this at the
26bottom of the file:
cc61c64b
XL
27
28```toml
29[workspace]
30```
31
32Like many Cargo features, workspaces support convention over configuration: we
3b2f2976
XL
33don’t need to add anything more than this to *Cargo.toml* to define our
34workspace as long as we follow the convention.
35
36<!-- Below -- any crates what depends on, specifically? The program? -->
37<!-- They're all programs. We mean the top-level crate in the workspace here,
38I've tried to clarify. /Carol -->
39
40### Specifying Workspace Dependencies
41
42The workspace convention says any crates in any subdirectories that the
43top-level crate depends on are part of the workspace. Any crate, whether in a
44workspace or not, can specify that it has a dependency on a crate in a local
45directory by using the `path` attribute on the dependency specification in
46*Cargo.toml*. If a crate has the `[workspace]` key and we specify path
47dependencies where the paths are subdirectories of the crate’s directory, those
48dependent crates will be considered part of the workspace. Let’s specify in the
49*Cargo.toml* for the top-level `adder` crate that it will have a dependency on
50an `add-one` crate that will be in the `add-one` subdirectory, by changing
51*Cargo.toml* to look like this:
52
53<!-- Above, what is the path dependency actually doing here, can you fill out
54the paragraph above? -->
55<!-- done /Carol -->
cc61c64b
XL
56
57```toml
58[dependencies]
59add-one = { path = "add-one" }
60```
61
3b2f2976
XL
62If we add dependencies to *Cargo.toml* that don’t have a `path` specified,
63those dependencies will be normal dependencies that aren’t in this workspace
64and are assumed to come from Crates.io.
cc61c64b 65
3b2f2976
XL
66### Creating the Second Crate in the Workspace
67
68<!-- You can see I'm adding headings, here, trying to add some more navigable
69structure -- can you improve these? I'm not sure mine are accurate -->
70<!-- Yep! /Carol -->
71
72Next, while in the `adder` directory, generate an `add-one` crate:
cc61c64b
XL
73
74```text
75$ cargo new add-one
76 Created library `add-one` project
77```
78
79Your `adder` directory should now have these directories and files:
80
81```text
82├── Cargo.toml
83├── add-one
84│   ├── Cargo.toml
85│   └── src
86│   └── lib.rs
87└── src
88 └── main.rs
89```
90
3b2f2976 91In *add-one/src/lib.rs*, let’s add an `add_one` function:
cc61c64b
XL
92
93<span class="filename">Filename: add-one/src/lib.rs</span>
94
95```rust
96pub fn add_one(x: i32) -> i32 {
97 x + 1
98}
99```
100
3b2f2976
XL
101<!-- below -- Where are we adding the extern crate line? -->
102<!-- at the top, where all the extern crate lines go and as illustrated by the listing /Carol -->
103
104Open up *src/main.rs* for `adder` and add an `extern crate` line at the top of
105the file to bring the new `add-one` library crate into scope. Then change the
106`main` function to call the `add_one` function, as in Listing 14-12:
cc61c64b
XL
107
108```rust,ignore
109extern crate add_one;
110
111fn main() {
112 let num = 10;
113 println!("Hello, world! {} plus one is {}!", num, add_one::add_one(num));
114}
115```
116
3b2f2976
XL
117<span class="caption">Listing 14-12: Using the `add-one` library crate from the
118`adder` crate</span>
119
120Let’s build the `adder` crate by running `cargo build` in the *adder* directory!
cc61c64b
XL
121
122```text
123$ cargo build
124 Compiling add-one v0.1.0 (file:///projects/adder/add-one)
125 Compiling adder v0.1.0 (file:///projects/adder)
7cac9316 126 Finished dev [unoptimized + debuginfo] target(s) in 0.68 secs
cc61c64b
XL
127```
128
3b2f2976
XL
129Note that this builds both the `adder` crate and the `add-one` crate in
130*adder/add-one*. Now your *adder* directory should have these files:
131
132```text
133├── Cargo.lock
134├── Cargo.toml
135├── add-one
136│   ├── Cargo.toml
137│   └── src
138│   └── lib.rs
139├── src
140│   └── main.rs
141└── target
142```
cc61c64b 143
3b2f2976
XL
144The workspace has one *target* directory at the top level; *add-one* doesn’t
145have its own *target* directory. Even if we go into the `add-one` directory and
146run `cargo build`, the compiled artifacts end up in *adder/target* rather than
147*adder/add-one/target*. The crates in a workspace depend on each other. If each
148crate had its own *target* directory, each crate in the workspace would have to
149recompile each other crate in the workspace in order to have the artifacts in
150its own *target* directory. By sharing one *target* directory, the crates in
151the workspace can avoid rebuilding the other crates in the workspace more than
152necessary.
153
154<!-- Above -- I have no idea what this means for our project here, can you put
155it in more practical terms, or otherwise maybe just explain what this means for
156the user? -->
157<!-- I added more explanation for the target directory in this section and
158added more explanation for the Cargo.lock in the next section, since the
159Cargo.lock advantages aren't as visible until you start adding dependencies on
160external crates. What do you think? /Carol -->
161
162#### Depending on an External Crate in a Workspace
163
164Also notice the workspace only has one *Cargo.lock*, rather than having a
165top-level *Cargo.lock* and *add-one/Cargo.lock*. This ensures that all crates
166are using the same version of all dependencies. If we add the `rand` crate to
167both *Cargo.toml* and *add-one/Cargo.toml*, Cargo will resolve both of those to
168one version of `rand` and record that in the one *Cargo.lock*. Making all
169crates in the workspace use the same dependencies means the crates in the
170workspace will always be compatible with each other. Let’s try this out now.
171
172Let’s add the `rand` crate to the `[dependencies]` section in
173*add-one/Cargo.toml* in order to be able to use the `rand` crate in the
174`add-one` crate:
cc61c64b
XL
175
176<span class="filename">Filename: add-one/Cargo.toml</span>
177
178```toml
179[dependencies]
180
181rand = "0.3.14"
182```
183
3b2f2976
XL
184We can now add `extern crate rand;` to *add-one/src/lib.rs*, and building the
185whole workspace by running `cargo build` in the *adder* directory will bring in
186and compile the `rand` crate:
cc61c64b
XL
187
188```text
189$ cargo build
190 Updating registry `https://github.com/rust-lang/crates.io-index`
191 Downloading rand v0.3.14
192 ...snip...
193 Compiling rand v0.3.14
194 Compiling add-one v0.1.0 (file:///projects/adder/add-one)
195 Compiling adder v0.1.0 (file:///projects/adder)
7cac9316 196 Finished dev [unoptimized + debuginfo] target(s) in 10.18 secs
cc61c64b
XL
197```
198
3b2f2976
XL
199The top level *Cargo.lock* now contains information about `add-one`’s
200dependency on `rand`. However, even though `rand` is used somewhere in the
201workspace, we can’t use it in other crates in the workspace unless we add
cc61c64b 202`rand` to their *Cargo.toml* as well. If we add `extern crate rand;` to
3b2f2976 203*src/main.rs* for the top level `adder` crate, for example, we’ll get an error:
cc61c64b
XL
204
205```text
206$ cargo build
207 Compiling adder v0.1.0 (file:///projects/adder)
208error[E0463]: can't find crate for `rand`
209 --> src/main.rs:1:1
210 |
2111 | extern crate rand;
212 | ^^^^^^^^^^^^^^^^^^^ can't find crate
213```
214
3b2f2976
XL
215To fix this, edit *Cargo.toml* for the top level `adder` crate and indicate
216that `rand` is a dependency for that crate as well. Building the `adder` crate
217will add `rand` to the list of dependencies for `adder` in *Cargo.lock*, but no
218additional copies of `rand` will be downloaded. Cargo has ensured for us that
219any crate in the workspace using the `rand` crate will be using the same
220version. Using the same version of `rand` across the workspace saves space
221since we won’t have multiple copies and ensures that the crates in the
222workspace will be compatible with each other.
cc61c64b 223
3b2f2976
XL
224#### Adding a Test to a Workspace
225
226For another enhancement, let’s add a test of the `add_one::add_one` function
227within the `add_one` crate:
cc61c64b
XL
228
229<span class="filename">Filename: add-one/src/lib.rs</span>
230
231```rust
232pub fn add_one(x: i32) -> i32 {
233 x + 1
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239
240 #[test]
241 fn it_works() {
242 assert_eq!(3, add_one(2));
243 }
244}
245```
246
247Now run `cargo test` in the top-level *adder* directory:
248
249```text
250$ cargo test
251 Compiling adder v0.1.0 (file:///projects/adder)
7cac9316 252 Finished dev [unoptimized + debuginfo] target(s) in 0.27 secs
cc61c64b
XL
253 Running target/debug/adder-f0253159197f7841
254
255running 0 tests
256
257test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured
258```
259
260Wait a second, zero tests? We just added one! If we look at the output, we can
3b2f2976
XL
261see that `cargo test` in a workspace only runs tests for the top level crate.
262To run tests for all of the crates in the workspace, we need to pass the
263`--all` flag:
264
265```text
266$ cargo test --all
267 Finished dev [unoptimized + debuginfo] target(s) in 0.37 secs
268 Running target/debug/deps/add_one-abcabcabc
269
270running 1 test
271test tests::it_works ... ok
272
273test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
274
275 Running target/debug/deps/adder-abcabcabc
276
277running 0 tests
278
279test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
280
281 Doc-tests add-one
282
283running 0 tests
284
285test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
286```
287
288When passing `--all`, `cargo test` will run the tests for all of the crates in
289the workspace. We can also choose to run tests for one particular crate in a
290workspace from the top level directory by using the `-p` flag and specifying
291the name of the crate we want to test:
cc61c64b
XL
292
293```text
294$ cargo test -p add-one
7cac9316 295 Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
3b2f2976 296 Running target/debug/deps/add_one-b3235fea9a156f74
cc61c64b
XL
297
298running 1 test
299test tests::it_works ... ok
300
3b2f2976 301test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
cc61c64b
XL
302
303 Doc-tests add-one
304
305running 0 tests
306
3b2f2976 307test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
cc61c64b
XL
308```
309
3b2f2976
XL
310This output shows `cargo test` only ran the tests for the `add-one` crate and
311didn’t run the `adder` crate tests.
312
313If you choose to publish the crates in the workspace to crates.io, each crate
314in the workspace will get published separately. The `cargo publish` command
315does not have an `--all` flag or a `-p` flag, so it is necessary to change to
316each crate’s directory and run `cargo publish` on each crate in the workspace
317in order to publish them.
318
319<!-- What does that mean, we have to publish them all one at a time?-->
320<!-- Yep, we've tried to clarify /Carol -->
321
322Now try adding an `add-two` crate to this workspace in a similar way as the
323`add-one` crate for some more practice!
cc61c64b 324
3b2f2976
XL
325As your project grows, consider using a workspace: smaller components are
326easier to understand individually than one big blob of code. Keeping the crates
327in a workspace can make coordination among them easier if they work together
328and are often changed at the same time.