Looking around, it seems that everyone uses Cargo for new Rust projects, then teams will switch over to Bazel if they need to. For me, I have an existing Bazel project that I'm adding Rust to, so I don't want to start with Cargo, so in this post I'll document how to use Bazel to build a new Rust project. Since this is an existing project, and I haven't upgraded to using bzlmod yet, I'll be using the WORKSPACE
file.
Quickly make sure your Bazel project works
You don't want to be caught debugging your Rust rules when it actually turns out to be some other change you (or a teammate) made.
Add Rust toolchain
The first thing to actually add is a toolchain. In my case, since I don't want to rely on binaries installed into the host machine (or dev docker), I'll use rules_rust
's rust_register_toolchain()
function:
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
# ...snip...
RULES_RUST_VERSION = "0.51.0"
RUST_EDITION = "2021"
RUSTC_VERSION = "1.81.0"
# ...snip...
# To find additional information on this release or newer ones visit:
# https://github.com/bazelbuild/rules_rust/releases
http_archive(
name = "rules_rust",
integrity = "sha256-BCrPtzRpstGEj+FI2Bw0IsYepHqeGQDxyew29R6OcZM=",
urls = ["https://github.com/bazelbuild/rules_rust/releases/download/{version}/rules_rust-v{version}.tar.gz".format(version = RULES_RUST_VERSION)],
)
# Pull rules_rust's dependencies, then register the toolchain version(s) we want
load("@rules_rust//rust:repositories.bzl", "rules_rust_dependencies", "rust_register_toolchains")
rules_rust_dependencies()
rust_register_toolchains(
edition = RUST_EDITION,
versions = [RUSTC_VERSION],
)
Note that I'm using the very latest (stable) versions, since this is a new project and I don't have a reason to use older stable versions. The edition isn't stable yet, but by the time I'm done it will be so I might as well start with it.
Add the ability to have dependencies
It's possible to have a Rust project that has no dependencies (beyond the toolchain), but I don't think I'll be able to do that for my project. We're going to use crate_universe
, which is newer than cargo-raze
and according to rules_rust
the
# crate_universe is the newer option, cargo-raze is older.
load("@rules_rust//crate_universe:repositories.bzl", "crate_universe_dependencies")
crate_universe_dependencies()
# Now actually load the cargo files and pull in our own dependencies
load("@rules_rust//crate_universe:defs.bzl", "crates_repository")
crates_repository(
name = "crate_index",
cargo_lockfile = "//rust-src:Cargo.lock",
lockfile = "//rust-src:Cargo.bazel-lock.json",
manifests = ["//rust-src:Cargo.toml"],
)
load("@crate_index//:defs.bzl", "crate_repositories")
crate_repositories()
Then create all those files:
touch rust-src/Cargo.{lock,toml,bazel-lock.json}
However, that won't work as-is. If you try to simply sync your dependencies, it won't work because your Cargo.toml
is too empty.
Add a main.rs or lib.rs
Let's create a simple main.rs
for now. You will likely want a lib.rs
, as well as a test.rs
so your main.rs
is a simple entry-point and not the only source file. To keep things shorter here, we'll stick to main.rs
:
fn main() {
println!("Hello, world!");
}
Then we need to update the Cargo.toml
:
[package]
name = "hello_world"
version = "0.1.0"
[dependencies]
[[bin]]
name = "hello_world"
path = "main.rs"
Note that path
isn't the default (src/main.rs
) because in Bazel we tend to create project- or service-specific folders, rather than a whole series of folders like src/
, test/
, etc.
Sync your lockfile
Now that we have our files ready, you might be tempted to run bazel build
, but we're not there yet. We want to re-pin our dependencies (even if you have none!) by running bazel sync
with the right environment variables.
CARGO_BAZEL_REPIN=true bazel sync --only=crate_index
That will update both lock files to go from empty to valid. Now you can build your dependency-free file, or add dependencies (and re-pin again) and build your full project.
Add a BUILD.bazel
file to get a binary out
So far, we have a main.rs
and a Cargo.toml
that points to it, but Bazel doesn't actually know about our main.rs
yet. Let's create a BUILD.bazel
file with a rust_binary
rule in it to build it:
# rust-src/BUILD.bazel
load("@rules_rust//rust:defs.bzl", "rust_binary")
rust_binary(
name = "hello_world",
srcs = ["main.rs"],
)
Note, at this moment, rules_rust
has a dependency on a C++ toolchain, even though for pure-Rust projects you don't need it. Until that support is added, you need a C++ toolchain installed or made available to Bazel somehow. For testing, I verified that apt install g++
is sufficient.
bazel build rust-src:hello_world
./bazel-bin/rust-src/hello_world
>>> Hello, world!
Conclusion
While there's a few minor hiccups to deal with, such as Cargo.toml requiring a [lib]
or [[bin]]
section, and having a valid C++ toolchain, overall it's pretty easy to get started with using Bazel to build Rust.
Good luck to you!