Rust Monorepo Setup (Cargo Workspaces, Mac)
In this article, I'm going to show you how to create a monorepo with Rust using Cargo Workspaces.
These instructions were written and tested on a Mac.
Step 1. Project Setup
-
If you don't already have Rust installed, see my article: Getting Started with Rust (Hello World, Mac)
-
Create a new project folder and change to it:
mkdir -p ~/projects/rust/rust-monorepo-demo
cd ~/projects/rust/rust-monorepo-demo
Step 2. Add a virtual manifest file
- Create a Cargo.toml file in the root of the project.
touch Cargo.toml
If there is no root src folder or package info, then the root Cargo.toml is referred to as a "virtual manifest" file.
- Open the file in VS Code (
code .
) - Replace the contents (if any), with the contents below and save it:
[workspace]
members = [
"tools_lib",
"alpha_app",
"beta_app"
]
The monorepo is going to contain two apps and a shared library (tools_lib
).
Step 3. Create workspace packages (members)
In this step you will create the three members of the workspace:
- tools_lib - a library used by the other members
- alpha_app - a binary that uses the library
- beta_app - another binary that also uses the library
Create tools_lib
- Create the tools_lib package
- Use the
--vcs none
flag so that a git repo isn't created just for the child package - Because it's a library we need to use the
--lib
flag - Don't worry about errors referring to other packages that we haven't created yet
cargo new --vcs none --lib tools_lib
Create alpha_app
- Create the alpha_app binary package
- In this case there is no lib flag because we're building a binary executable
cargo new --vcs none alpha_app
Create beta_app
- Create the beta_app binary package
cargo new --vcs none beta_app
Tree view
If you have tree installed, run it and you should see a file layout like this:
% tree
.
├── Cargo.toml
├── LICENSE
├── Makefile
├── README.md
├── alpha_app
│ ├── Cargo.toml
│ └── src
│ └── main.rs
├── beta_app
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── tools_lib
├── Cargo.toml
└── src
└── lib.rs
Step 4. Run cargo build
- To build everything in the monorepo run this command:
cargo build
You should see that everything built, but the only target folder is in the root.
If you run tree again you should see this snippet in the middle of the results. It shows that alpha_app and beta_app were created in the root target:
├── target
│ ├── CACHEDIR.TAG
│ └── debug
│ ├── alpha_app
│ ├── alpha_app.d
│ ├── beta_app
│ ├── beta_app.d
│ ├── build
Step 5. Fix the workspace.resolver warning
When you ran cargo build
you may have seen a warning about a workspace.resolver
parameter.
To fix that, do the following:
- In the root Cargo.toml file add the resolver line in the
[workspace]
section and save the file:
[workspace]
resolver = "2"
- Before rebuilding, clear out the old targets with this command:
cargo clean
- Rebuild and this time the warning should hopefully be gone:
cargo build
Step 6. Run a package
- Try running this command:
cargo run
You should see an error like this:
error: `cargo run` could not determine which binary to run.
Use the `--bin` option to specify a binary, or the `default-run` manifest key.
available binaries: alpha_app, beta_app
You could use the --bin
flag as the error suggests. But you can also use the -p
flag.
- Run this command to run just alpha_app:
cargo run -p alpha_app
The -p
stands for package, which you can see if you look at the help for the cargo run
command:
cargo run --help
Using the --bin
flag will do the same:
cargo run --bin alpha_app
Step 7. Add workspace dependencies
So far everything has been running in isolation. Now we can add some dependencies:
- In the alpha_app folder edit the Cargo.toml file
- Add this line in the dependencies section and save it:
tools_lib = { path = "../tools_lib"}
- Do the same for the Cargo.toml file in the beta_app folder
Step 8. Use a workspace function
When you created tools_lib, a default public function that adds numbers together should have been added.
- Check tools_lib/src/lib.rs just to be sure
Update alpha_app
- Edit alpha_app/src/main.rs to use the tools library
- Change it to look like this and save the file
use tools_lib::add;
fn main() {
let x = 10;
let y = 20;
let result = add(x, y);
println!("Alpha: {x} + {y} = {result}");
}
- Run the app to verify the results:
cargo run -p alpha_app
Update beta_app
- Repeat the last set of steps for the beta_app package
- Change the numbers (x and y) to get a different result
- Change the print statement to say Beta instead of Alpha
- Verify that you can run it and get the expected output
cargo run -p beta_app
If you just want to see the output, you can add the --quiet
flag:
cargo run --quiet -p beta_app
Example Repo
You can find an example of the repo created for this article here:
Makefile
I've added to the example project a Makefile with some instructions for using that to run the project.
Conclusion
Congratulations! In this article you learned how to:
- Create a rust monorepo using cargo workspaces
- How to setup Cargo.toml as a virtual manifest for a monorepo
- How to add binary and lib packages to a rust monorepo
- How to create dependencies to local libraries in a monorepo
- How to run individual packages within a monorepo
References
- Getting Started with Rust (Hello World, Mac)
- Cargo Workspaces
- rust-workspace-102
- rust-workspace-101
- Structuring larger Rust projects with Cargo Workspaces - Let's Get Rusty (YouTube)
- Even Better TOML - VS Code extension for syntax highlighting