Index ¦ Archives  ¦ Atom  ¦ RSS

Using Bazel to build a new Rust project

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!

© Fahrzin Hemmati. Built using Pelican. Theme by Giulio Fidente on github.