cargo-rbmt
A cargo orchestrator built with xshell that codifies rust-bitcoin build standards and patterns. cargo-rbmt manages toolchain and lockfile dependency versions for thorough and reproducible tests.
Table of Contents
Environment Variables
RBMT_LOG_LEVELverbose: [DEFAULT] Print out underlying cargo commands and all their output, good for CI.quiet: Suppress both cargo and rbmt stderr to reduce all noise.progress: Show rbmt stderr on a single line with a visual indicator, for interactive use.
Configuration
Configuration for cargo-rbmt is stored in [package.metadata.rbmt] in a package's Cargo.toml manifest. Some configuration lives under [workspace.metadata.rbmt] in the root manifest of a workspace, but can fallback to [package.metadata.rbmt] if there is only one package in the repository.
NOTE: Cargo reserves
[package.metadata]and[workspace.metadata]as explicitly supported extension points for third-party tooling. Cargo itself ignores any keys nested under these tables, so they will never clash with a future built-in Cargo field.[workspace.metadata]was stabilized in Cargo 1.46 and[package.metadata]has been around much longer. Therbmtsub-key further namespaces the configuration to avoid collisions with other tools. If a repository only has one package and is not using any workspace features, use thepackagenamespace because simply adding theworkspace.metadatasettings enables workspace features in cargo.
Version Pinning
Pin a specific version of cargo-rbmt by setting rbmt.version in the [workspace.metadata] or [package.metadata] section. When a version requirement is specified, either with a semantic version or git hash, cargo-rbmt will check its own version on startup. Pass --ignore-version (-i) to skip the check and run anyway (e.g. when testing a local cargo-rbmt build against a pinned workspace).
[]
= "0.4.1"
The TOML metadata is grep-able for a simple bootstrap script for cargo-rbmt.
VERSION=
For git hashes, you'll need to install from the repository instead.
HASH=
Commands
All of cargo-rbmt's subcommands use one of the existing lockfiles (minimal, maximum, recent, or existing) through the --lockfile flag. The lockfiles are generated and managed with the lock command.
api
The api command helps maintain API stability by generating and diff'ing a package's public API. It uses the public-api crate under the hood.
NOTE:
apihas an implicit dependency on the version of the nightly toolchain since it relies on an unstable docsrs interface. Currently, it requires nightly-2025-08-02 or later.
[]
= true
= false
= [ ["alloc"], ["alloc", "serde"] ]
= ["bitcoin-internals"]
A package must opt-in to api checking through the api.enabled configuration since it can be quite noisy during heavy development. Since a package's API depends on what features are enabled, no features enabled and all features enabled are always generated. Specific feature sets can be added through the api.features configuration.
If the --snapshot flag is passed in, API files are generated under <package-dir>/api/<feature-set>/. If the --baseline flag is set, the current APIs are compared to their versions at the given git ref. An error is thrown on any changes so that they can be further analyzed.
If there are dependencies which should not be a part of the public API, they can be listed in the private config. The command will fail if they are detected. This is a hack until RFC #3516 lands in cargo.
#[doc(hidden)] policy
Items marked with #[doc(hidden)] are excluded from API snapshots and breaking change detection. #[doc(hidden)] is an escape hatch to allow API changes without triggering breaking change warnings in CI. While hiding documentation doesn't change the actual types or signatures, it signals that the item is not part of the public API contract and may be modified or removed without warning.
docs
The docs command builds documentation following the convention in the rust-bitcoin ecosystem.
fmt
The fmt command formats all files in the workspace using rustfmt with the nightly toolchain, which is the convention in the rust-bitcoin ecosystem.
generate
The generate command detects changes to generated files by running the file generation script, which is assumed to be at <package-root>/generate-files.sh, and running a diff. The command fails if the file generation script is not present or a diff is present after file generation.
integration
The integration command runs tests in isolated integration sub-packages designed to work with the corepc testing framework, which provides Bitcoin Core binaries and testing infrastructure. Integration sub-packages should define its own [package.metadata.rbmt.toolchains.stable] to lock a stable version.
Integration sub-packages should be standalone, not members of a workspace. This ensures test infrastructure dependencies don't influence workspace dependency resolution which could change the minimum versions for tests.
[]
# Integration tests package name, defaults to "bitcoind-tests".
= "bitcoind-tests"
# Versions to test. If omitted, tests all discovered versions from Cargo.toml.
= ["29_0", "28_2", "27_2"]
lint
The lint command detects duplicate dependencies, but some may be unavoidable (e.g., during dependency updates where transitive dependencies haven't caught up). Configure [package.metadata.rbmt.lint] to whitelist specific duplicates.
[]
= [
"syn",
"bitcoin_hashes",
]
NOTE: Linting is only enforced (through command failure) on the given nightly toolchain. It is possible for different versions of rust to have different lint rules and behaviour, so to keep things simple just the newest is considered fail worthy.
lock
To ensure your package works with the full range of declared dependency versions, cargo-rbmt can generate lockfiles for different version scenarios.
Cargo-minimal.lock- Minimum versions that satisfy your dependency constraints. Verifies that direct dependency versions aren't being bumped by transitive dependencies.Cargo-maximum.lock- Maximum versions that satisfy your dependency constraints. Verifies new updates do not break.Cargo-recent.lock- Recent versions start out the same as maximum, but are conservatively updated. Versions are only increased if needed due to new dependency constraints.
The lock command generates and maintains these files for you. You can then use --lockfile with any command to test against any version set.
# Generate minimal and recent (default for backward compatibility).
# Generate all three lockfiles.
The lock command also keeps the single Cargo.lock of any nested workspaces (e.g. embedded test crates excluded from the main workspace) in sync using the same conservative strategy as Cargo-recent.lock. Only git-tracked lockfiles are managed. A nested workspace can opt-out via its configuration.
[]
= false
When you specify --lockfile, the tool copies that lockfile to Cargo.lock before running the command. This allows you to test your code against different dependency version constraints.
# Test with minimal versions.
# Test with maximum versions.
prerelease
The prerelease command performs readiness checks before releasing a package. Checks only run for packages that have a version bump in Cargo.toml since the baseline ref.
[]
= true
= "master"
Use --force to run checks regardless of whether a version bump is detected.
run
The run command executes arbitrary cargo commands with the specified toolchain and lockfile.
The -- separator tells cargo-rbmt to stop parsing its own flags and pass everything after it to cargo. For example, here is how to run benchmarks with the nightly toolchain.
The --baseline <ref> flag checks that every commit between <ref> and HEAD can run the command.
test
The test command runs feature matrix testing for your package. Every run unconditionally tests all features enabled, no features enabled, and each feature by itself. A package's features are auto-discovered. Randomly sampled feature subsets (number of sets grows with the number of package features) are tested per commit ID to try and catch interaction bugs without running massive matrices on every run.
The --baseline <ref> flag checks that every commit between <ref> and HEAD passes the test suite, ensuring the branch remains bisectable.
Arguments after -- are passed to both build and test commands.
For test-specific options use environment variables. There are many different layers under the hood, so might take some searching. For example, setting thread counts in tests uses the RUST_TEST_THREADS environment variable.
RUST_TEST_THREADS=4
NOTE: The separate build step detects implicit test code dependencies. Example runs use default settings and ignore all arguments.
[]
# Examples to run with different feature configurations.
#
# Supported formats:
# * "name" - runs with no default features.
# * "name:feature1 feature2" - runs with specific features.
= [
"bip32", # No default features
"bip32:serde rand", # Specific features
]
# Features to exclude from auto-discovery.
# Use for internal or alias features that should not be tested in isolation.
= ["_internal", "default-features"]
# Always test specific feature combinations.
= [
["serde", "rand"], # Test serde and rand interaction.
["serde", "std"], # Assuming serde has a weak dependency on std, test interaction when enabled.
["rand", "std"], # Assuming rand has a weak dependency on std, test interaction when enabled.
["serde", "rand", "std"], # Test both with weak dependency interaction.
]
# Run tests on all possible feature subsets instead of the default handful per-commit.
= "all"
# Feature-specific MSRV overrides.
= { = "1.75.0" }
no_std
When a package declares #![no_std] in its library source, cargo-rbmt test automatically performs an additional verification step on the thumbv7m-none-eabi target to try and detect unintentional std library usage.
toolchains
The toolchains command installs the three required toolchains for cargo-rbmt commands, nightly, stable, and MSRV. nightly and stable Toolchain versions are read from the root manifest Cargo.toml of a repository. The MSRV is read from all the package manifests in a workspace. Workspaces must declare a single consistent MSRV across all packages. Workspaces with conflicting rust-version fields are not supported.
NOTE: This command requires
rustupon the system, which is not the case for all othercargo-rbmtcommands.
Workspace enabled repositories should set the versions under the workspace.metadata.rbmt.toolchains namespace in the root Cargo.toml. If a repository is a single package without a workspace, use the package.metadata.rbmt.toolchains namespace instead.
[]
= "nightly-2026-03-13"
= "1.93.1"
The current versions can be queried with the --msrv, --stable, or --nightly flags.
The --update-nightly and --update-stable flags each install the corresponding floating toolchain, query its resolved version from rustc, and write the result to the appropriate version file before proceeding with the normal install and export.
tools
The tools command installs external cargo tools whose versions are pinned in the root Cargo.toml manifest. The preferred location is [workspace.metadata.rbmt.tools].
[]
= "0.46.0"
= "0.50.1"
For single-package repos with no explicit [workspace] table, [package.metadata.rbmt.tools] is supported as a fallback.
# Install all tools at their pinned versions.
# Install only a specific tool.
# Install each tool at its latest version and update the pins in Cargo.toml.
# Update only a specific tool.
The --update flag installs each tool without a version constraint, then reads the resolved version back from cargo install --list and writes it into Cargo.toml. The resulting diff can be reviewed and committed as a deliberate version bump.
Note: Tools are installed via
cargo install. Installing or updating a tool overwrites any previously installed version of that binary system-wide. If you rely on a specific version of a tool outside of this workflow, be aware that runningcargo rbmt toolswill replace it with the pinned version.
tree
The tree command shows the internal workspace dependency tree flattened into release order. It exists to help plan "waves" of releases: a package can only be published once all of its internal (workspace member) dependencies are published.
# Release order for all publishable packages in the workspace.
# Release order restricted to the internal dependencies of one package root.
# Only show packages with changes since a git ref.
Workspace Integration
cargo-rbmt can simply be installed globally on a system or added as a dev-dependency to a package.
1. Install on system
Install the tool globally on your system with cargo install.
Then run from anywhere in your repository as a cargo subcommand. It can also be called directly as cargo-rbmt.
2. Add as a dev-dependency
Add as a dev-dependency to a workspace member. This pins the tool version in your lockfile for reproducible builds. But this also means that cargo-rbmt dependencies could influence version resolution for the workspace.
[]
= "0.1.0"
Then run via cargo.
It might be worth wrapping in an xtask package for a clean interface.
CI Actions
A composite action is provided to make it easy to use cargo-rbmt in Github/Forgejo Actions CI. Although it might be easier to write a custom action per-repository.
For faster CI runs, consider adding cargo build caching to your workflow with something like Swatinem/rust-cache.
steps:
- uses: actions/checkout@v6
- uses: Swatinem/rust-cache@v2
- uses: rust-bitcoin/rust-bitcoin-maintainer-tools/.github/actions/setup-rbmt@master
- run: cargo rbmt test
See the action for more details.