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