Run Miri (Rust's undefined behavior detector) as Bazel test targets in a Cargo workspace.
Please note this is a POC and I would not use this anywhere. Theres alot of cleanup that needs to happen across the board.
- Zero config —
bazel test //crates/core:core_mirijust works - Automatic nightly toolchain — Miri targets use nightly via Bazel transitions, no
--configflags needed - Hermetic — Miri binary, rust-src, and sysroot are all downloaded/built by Bazel
- Cached sysroot — The Miri sysroot (slow to build) is cached as a Bazel action output
- Multi-platform — Supports macOS ARM64, Linux x86_64, and Linux ARM64
bazel_dep(name = "rules_rust", version = "0.54.1")
bazel_dep(name = "bazel_skylib", version = "1.7.1")
bazel_dep(name = "platforms", version = "0.0.10")
# Rust toolchain — must include nightly with the same date as Miri
rust = use_extension("@rules_rust//rust:extensions.bzl", "rust")
rust.toolchain(
edition = "2021",
versions = ["nightly/2025-12-01", "1.88.0"],
)
use_repo(rust, "rust_toolchains")
register_toolchains("@rust_toolchains//:all")
# Miri
miri = use_extension("//miri:extensions.bzl", "miri")
miri.toolchain(nightly_date = "2025-12-01")
use_repo(
miri,
"miri",
"miri_rust_src",
"miri_aarch64-apple-darwin",
"miri_x86_64-unknown-linux-gnu",
"miri_aarch64-unknown-linux-gnu",
"miri_cargo_workspace",
)Important: The
nightly_dateinmiri.toolchain()must exactly match the nightly version inrust.toolchain(). Even one day difference will causelibrustc_driverhash mismatches. We will need to fix this long term
load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")
load("//miri:defs.bzl", "rust_miri_test")
rust_library(
name = "my_lib",
srcs = ["src/lib.rs"],
edition = "2021",
)
rust_test(
name = "my_lib_test",
crate = ":my_lib",
)
# Miri test — runs #[test] functions under Miri
rust_miri_test(
name = "my_lib_miri",
target = ":my_lib",
)# Run Miri tests — no flags needed
bazel test //crates/core:core_miri
# Run all tests (including Miri)
bazel test //...
# See failure output
bazel test //crates/core:core_miri --test_output=streamed# Show test output on failures
test --test_output=errors
┌─────────────────────────────────────────────────────────┐
│ MODULE.bazel │
│ miri.toolchain(nightly_date = "2025-12-01") │
└─────────────────────┬───────────────────────────────────┘
│
┌───────────┼───────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────────────┐
│ @miri// │ │ rust-src │ │ cargo_workspace │
│ miri bin │ │ download │ │ repo (symlinks │
│ per-plat │ │ │ │ Cargo.toml + .rs)│
└────┬─────┘ └────┬─────┘ └────────┬─────────┘
│ │ │
▼ ▼ │
┌─────────────────────────┐ │
│ //miri:miri_sysroot │ │
│ (cargo miri setup) │ │
│ Builds .rlib with MIR │ │
│ [nightly transition] │ │
└────────────┬────────────┘ │
│ │
▼ ▼
┌─────────────────────────────────────────┐
│ rust_miri_test / rust_miri_run │
│ │
│ macro creates: │
│ *_internal (nightly transition) │
│ * (sh_test wrapper) │
│ │
│ Runs: cargo miri test -p <crate> --lib │
└─────────────────────────────────────────┘
- Module extension downloads Miri binary (per-platform),
rust-src, and creates a repo with all Cargo workspace files miri_sysrootrule runscargo miri setupto compilelibstd/libcorewith-Zalways-encode-mir, producing.rlibfiles Miri can interpret. This is cached after the first build.rust_miri_testmacro creates a shell script that:- Sets up
DYLD_LIBRARY_PATH/LD_LIBRARY_PATHforlibrustc_driver - Sets
MIRI_SYSROOTto the pre-built sysroot - Puts
rustc,cargo,miri,cargo-mirionPATH - Fakes
rustupto prevent network calls - Runs
cargo miri test -p <crate_name> --lib
- Sets up
- Nightly transition on both the sysroot and test internal target ensures the nightly Rust toolchain is used regardless of the user's default config
The standalone miri binary cannot be invoked directly — it requires cargo miri to:
- Properly set up the
MIRI_BE_RUSTCenvironment for compilation - Resolve crate dependencies and
--externflags - Handle the sysroot validation
- Manage the two-phase compilation (compile with MIR, then interpret)
Newer versions of Miri explicitly error with: "Note that directly invoking the miri binary is not supported; please use cargo miri instead."
cargo miri test is a Cargo subcommand that needs:
- The workspace root
Cargo.tomlandCargo.lock - All workspace member
Cargo.tomlfiles - All
.rssource files
Bazel's sandbox doesn't include files from other packages by default. The cargo_workspace_repo repository rule symlinks all Cargo manifests and Rust source files into a single repo that cargo can navigate as a normal workspace.
Runs #[test] functions under Miri (cargo miri test --lib).
rust_miri_test(
name = "my_lib_miri",
target = ":my_lib",
# Optional:
miri_flags = ["-Zmiri-disable-isolation", "-Zmiri-symbolic-alignment-check"],
env = {"MY_VAR": "value"},
crate_name = "my_lib", # Auto-detected from CrateInfo
)| Attribute | Type | Default | Description |
|---|---|---|---|
target |
label | required | The rust_library or rust_binary target to test |
miri_flags |
string_list | ["-Zmiri-disable-isolation", "-Zmiri-symbolic-alignment-check", "-Zmiri-retag-fields"] |
Flags passed via MIRIFLAGS |
env |
string_dict | {} |
Extra environment variables |
crate_name |
string | auto | Cargo crate name (auto-detected from CrateInfo) |
Runs a binary's fn main() under Miri (cargo miri run).
rust_miri_run(
name = "my_app_miri",
target = ":my_app",
)Same attributes as rust_miri_test.
miri/
├── BUILD.bazel # miri_sysroot target
├── defs.bzl # Public API: rust_miri_test, rust_miri_run
├── miri.bzl # Core rule implementation
├── miri_sysroot.bzl # Sysroot build rule (cargo miri setup)
├── transition.bzl # Nightly toolchain transition
├── extensions.bzl # Bzlmod module extension
├── repositories.bzl # Repository rules (download Miri, rust-src, etc.)
└── setup_cargo_workspace.sh # Shell script to symlink workspace files
The Miri nightly date must exactly match the rules_rust nightly toolchain date. Check both:
grep "nightly" MODULE.bazelThe sysroot is keyed on its inputs. If the toolchain config changes (e.g., switching between stable and nightly), Bazel rebuilds it. This is expected on the first run after a config change. Subsequent runs are cached.
Ensure all workspace member Cargo.toml files and source files are being symlinked. Check:
ls bazel-bin/crates/core/core_miri.runfiles/+miri+miri_cargo_workspace/If bazel test //... skips Miri targets, check .bazelrc for --test_tag_filters=-miri. Remove it or run Miri targets explicitly.
-
Reuse
rules_rustcrate_universedata — Currently we symlink the entire Cargo workspace into a separate repo viasetup_cargo_workspace.sh. Ideally we would reuse the workspace metadata thatcrate_universealready parsed, avoiding the need to duplicate Cargo manifests and source files. This would also eliminate the shell script for Cargo.toml parsing. -
Explore standalone
miriinvocation — The current approach usescargo miribecause standalonemirirequires manual--externflag construction and sysroot wiring that newer Miri versions reject. If future Miri versions re-support direct invocation, we could bypass Cargo entirely and make the rule more Bazel-native (no Cargo workspace needed, deps fromCrateInfo). -
Pre-built Miri sysroot caching — The sysroot build (
cargo miri setup) takes ~10-15 seconds and downloads crates from crates.io. Consider distributing pre-built sysroots per nightly date or caching them in a remote cache. -
Windows support — Add
x86_64-pc-windows-msvcplatform support. TheDYLD_LIBRARY_PATH/LD_LIBRARY_PATHapproach would need to be replaced withPATHon Windows. -
rust-srcfromrules_rust— Currentlyrust-srcis downloaded separately viahttp_archive. Ifrules_rustadds support forextra_rustup_components(e.g.,rust-src), we should use that instead. -
Reduce
use_repoboilerplate — The user currently needs to list all per-platform repos inuse_repo(). Explore hub-repo patterns or Bzlmod improvements to reduce this touse_repo(miri, "miri"). -
Integration test targets — Support
--testsand--binsflags forcargo miri testin addition to--lib, to run integration tests and binary targets under Miri. -
Remote execution compatibility — The current
DYLD_LIBRARY_PATHapproach is macOS-specific. Verify and fix for remote execution environments where the execution platform may differ from the host. -
Doc-test support — Currently doc-tests are skipped (
--libflag). The--test-runtoolerror with doc-tests needs investigation. -
Configurable sysroot
CARGO_HOME— The sysroot build downloads crates to a tempCARGO_HOME. For air-gapped environments, support pointing to a pre-populated registry.