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