
Cargo is an excellent tool that makes it easy to publish your code and share it with others. However, it can also lead to publishing packages that are much larger than necessary if you're not careful. We can make fun of JavaScript for including Guy Fieri in its packages, but Cargo also makes it easy to do similar things. Default behavior of Rust package manager for packaging boils down to including all files that are not part of .gitignore
and this can lead to unnecessary bloat. Some projects may include a fancy gif that is used only in a README file, some include binary assets that are used by some of the examples. Reasons differ, but the result is the same: a package that is much larger than necessary.
I don't need or want to shout at any specific crate, but I will show you how you can inspect a bit the bottomless hole, or how the nerds call it, the ~/.cargo/registry/cache
directory. To do so you need a steady hand and a terminal with Unix tools. Below is the command to list locally cached packages, get their sizes, and sort them:
find ~/.cargo/registry/cache -iname "*.crate" -print0 | xargs -0 du -h | sort -h
I will not call out the size of any of the crates, because it is not the point of this article. The point is to show you how to trim your packages so they are small and efficient.
Specifying package contents
Cargo publish by default has a rather opt-out approach for deciding what to include in the package (which is IMHO wrong approach, but it is what it is). It uses the .gitignore
file if present to exclude files or just exclude anything that starts with .
. One more rule would be to exclude other packages (if you have a subdirectory with Cargo.toml
in it it will be excluded).
But even with not so ideal default configuration it can be changed to opt-in approach by specifying the include
field in the Cargo.toml
package
section. It should be a list of patterns that match the files and directories you want to include in the package. Example:
[package]
# name of the package, etc.
# ...
include = ["/src", "README.md"]
There is also exclude
field which can be used to exclude files or directories from the package. You need to specify only one of them. I would suggest using the include
approach, since I preffer opt-in approach. You can learn more about both of the fields by reading Rust documentation.
Inspecting the package contents
Cargo also gives you a way to get information what will be included in the package:
cargo package --list --allow-dirty
You can, and should, call these commands to package the project without deploying it to the registry (extra command for getting the size of the package):
cargo publish --dry-run --allow-dirty
du -h target/package/*.crate
Argument
--allow-dirty
is used to allow to use the commands in repositories that have uncommitted changes.
It should be used to verify that the package will include only the files and directories you want.
Example
I have an old package for Bevy that I have not updated for a long time, but it can serve as an example of how not to do it (and how to fix it). You can clone the repo and follow the steps, or just read along - your choice!
git clone --branch withoutInclude git@github.com:Leinnan/bevy_tiled_blueprints.git
If we use our command to check what will be included in the package:
cd bevy_tiled_blueprints
cargo package --list --allow-dirty
Some of the files that would be included don't exist at this moment, but that doesn't matter for our demonstration. We want to have a broad understanding of what will be included in the package.
It should output like this:
.cargo_vcs_info.json
.github/workflows/rust.yml
.gitignore
CHANGELOG.md
Cargo.toml
LICENSE-APACHE
LICENSE-MIT
README.md
assets/map.tmx
assets/textures/colony-grounds-ready.png
examples/simple.rs
index.html
simple_example.png
simple_example_tiled.png
src/debug.rs
src/lib.rs
To get more information lets do a dry run of publish and check the package size:
cargo publish --dry-run --allow-dirty
du -h target/package/*.crate
Output:
264K target/package/bevy_tiled_blueprints-0.1.0.crate
The package isn't enormous, but it's larger than it needs to be. There's no reason to include image and TMX files in the package. We can reduce the package size by removing unnecessary files.
First, let's check the exclusion approach. Edit Cargo.toml
and add an exclude
field to the package
section:
exclude = ["*tmx", "*png"]
Let's check what will be included in the package now by calling the same command, output now should be:
.cargo_vcs_info.json
.gitignore
CHANGELOG.md
Cargo.lock
Cargo.toml
Cargo.toml.orig
LICENSE-APACHE
LICENSE-MIT
README.md
examples/simple.rs
index.html
src/debug.rs
src/lib.rs
48K target/package/bevy_tiled_blueprints-0.1.0.crate
One fifth of the package size!
We can do a bit better by taking the opposite approach. Instead of excluding files, we can include only the necessary ones.
To do so, edit Cargo.toml
, make sure there is no exclude
field, and add an include
field to the package
section:
include = ["*rs"]
Let's check what will be included in the package now by calling the same command, output now should be:
.cargo_vcs_info.json
Cargo.lock
Cargo.toml
Cargo.toml.orig
src/debug.rs
src/lib.rs
44K target/package/bevy_tiled_blueprints-0.1.0.crate
Even less! We've reduced the package size significantly, and now it contains only the necessary files.
This work is licensed under CC BY-NC-SA 4.0.
Piotr Siuszko
GameDev tool engineer, C#/C++ at day, Rust at night.