Rust and Cargo¶
BSMR compiles Cargo packages through native Rust actions. Configured Cargo planning extends the native frontend released in version 0.0.6.
Build a Cargo package without writing or synchronizing build rules:
With no target, build and test select the current Cargo directory. At the
workspace root, Cargo's default-members controls the selection. A package
directory selects its libraries and binaries. bsmr build . makes the current
directory explicit. Named targets and configured aliases retain their meaning.
Only workspace members expose build targets. Excluded path dependencies remain
available to their consumers, but direct directory builds report a membership error.
An explicit build file owns its directory and requires an explicit target.
Directory selections skip binaries and tests with disabled required-features,
using Cargo's resolved features across all selected packages. Naming a disabled
target explicitly is an error. A skipped target has no artifact or compiler
action. JSON output represents that target with an empty output path.
Cargo workspaces do not need bsmr init. BSMR uses the pinned Cargo to locate
the owning workspace and loads its bundled build rules without writing .bsmr.
An enclosing explicit BSMR project keeps ownership. Use bsmr init when you
want to write and customize that configuration. Existing .bsmr.local settings
can configure the native workspace without changing its Cargo files.
Run bsmr build --help or bsmr test --help for command options.
Select Cargo profiles and features through the existing configuration interface:
bsmr build app -c rust.profile=release
bsmr test app -c rust.features=app/test-hooks
bsmr build app -c rust.default_features=false -c rust.all_features=true
The rust configuration section accepts profile, features, default_features,
and all_features. Profiles default to dev for builds and test for tests.
Features use Cargo's comma- or space-separated syntax. Default features are enabled
and all-features selection is disabled unless requested. Cargo validates names
and resolves profile inheritance and dependency features. Changes invalidate the
selected plan.
Select several packages or named targets in one command to resolve their shared
features together, as Cargo does. For example, bsmr build app worker compiles
one shared dependency with the union of features requested by both packages.
Building worker alone resolves its own feature set. Changing the requested
roots invalidates the cached plan, even in a warm daemon. Reordering the same
roots does not duplicate compiler work.
The selected roots share one native graph per workspace and build/test mode.
Targets retain their owning package, so equally named binaries remain distinct.
Run python3 test/rust/roots.py /path/to/bsmr to verify these transitions against
Cargo and inspect the actual number of compiler actions.
Cargo profiles can select lto = "thin", "fat", or true for optimization
across crates. Bessemer retains LLVM bitcode in dependency libraries and applies
the selected mode when linking binaries, C libraries, or inline unit tests. Rust library artifacts
also retain object code so they remain usable by consumers that do not use LTO.
false permits optimization within one crate. "off" disables it entirely.
See Cargo's LTO settings.
Commit Cargo.toml, Cargo.lock, and an exact rust-toolchain.toml at the
project root. The current catalog supports 1.97.1, 1.98.0, and nightly-2026-04-11 on
Linux and macOS, for ARM64 and x86-64. Install the matching Cargo resolver with
rustup toolchain install <channel> --profile minimal before building.
BSMR reads manifests, project Cargo configuration, and target file names into an isolated snapshot. It discovers public targets without resolving their dependencies, then asks the bundled Cargo 0.98.0 planner for the selected build or test. The snapshot retains existing manifest-declared entrypoint paths, including custom tests outside conventional directories. Missing files remain missing so Cargo reports them. Source contents still belong to native compiler inputs. The compiler pin is independent of this resolver version.
Planning may acquire locked dependencies into BSMR's own Cargo home. It never
compiles a build script or loads a procedural macro. BSMR keeps generated build
rules in memory and writes no BUILD.bsmr files into the checkout. Resolver input
changes invalidate planning. Ordinary source edits invalidate compiler actions.
Cargo files -> locked resolution -> private targets -> native rustc actions
-> custom recipe actions
Each crate uses the existing native Rust compilation, linking, and test machinery.
A package containing libraries or binaries can be selected by its directory,
even when its Cargo name differs. Library builds preserve lib, rlib, cdylib,
and staticlib declarations. All declared formats materialize even when the
library is only a dependency of the selected executable. :lib[cdylib] and
:lib[staticlib] select individual C-compatible artifacts. :lib[check] requests
metadata only. Shared libraries use .dylib on macOS and .so on Linux.
Libraries expose :lib, binaries expose
their Cargo target name, and dependency
renames preserve the name used in source. Package metadata enters the compiler
as literal environment values, so text such as $(location ...) cannot become
a build dependency. Inline unit tests and integration tests are associated with
their build targets. Changing an unrelated crate does not recompile the selected
binary. Changing a dependency invalidates its consumers.
Add another build step¶
A custom recipe can consume an inferred Rust executable without restating its crate dependencies. Keep the recipe in its own package so the Rust package retains its inferred definition.
Supported boundary¶
First-party scripts can read captured checkout identity inside the verified Linux runtime. The captured Git inputs participate in cache identity.
The frontend supports local path, public registry, and pinned Git libraries,
binaries, inline unit tests, and integration tests. Registry archives are verified against
Cargo.lock. Git packages are read from the locked commit without ambient
Git filters or hooks. Compilation reads native source artifacts, not Cargo's
mutable checkout cache. The planner exports pinned Git objects to checksum-verified
archives, so compiler actions need no Git fetch or network access. Git submodules
and authenticated registries remain unsupported.
Excluded path dependencies provide source inputs without becoming public workspace targets.
Cargo resolves requested features, conditional dependencies, dev dependencies, profile
settings, and workspace lints before native compilation. Each build or test has a
separate configured graph, so building a library does not activate its test-only
dependencies.
The native compiler checks Cargo's docsrs and test cfg names without enabling
them. Other undeclared cfg names still follow the package's lint policy.
Every compiler invocation receives absolute CARGO_MANIFEST_DIR and
CARGO_MANIFEST_PATH values for its declared package. Procedural macros can
read package files, such as grammar definitions, and edits invalidate expansion.
Build scripts and procedural macros require the verified Linux namespace runtime. Unisolated execution rejects package code before compilation. This does not establish macOS package-code isolation or complete C/C++ toolchain support.
The daemon retains one verified namespace runtime between commands. Each command rechecks both pinned files before reuse. Matching bytes avoid another copy and extraction. A replacement does not change an active action's runtime.
Within a command, actions reuse private input files verified against the same digest and executable bit. Their input mounts stay read-only. Each action keeps its own writable outputs. A new command verifies inputs again, so this storage does not accumulate historical source versions between commands.
Cargo's links metadata flows only to direct dependents' build scripts.
Generated directory paths remain attached to their producing artifacts and are
rebound when cached outputs are restored. Metadata emission order is preserved,
including keys that become identical after Cargo's environment-name conversion.
Declaring links does not itself require a C compilation or linker invocation.
Generic rustc-link-arg directives reach the package's compiler actions through
the existing argument file. Rustc applies them when linking and ignores them for
rlib compilation. Target-specific linker directives remain unsupported.
Cargo's target.<triple>.linker and target.'cfg(...)'.linker select the driver.
-C link-arg=... passes arguments unchanged. For example:
[target.'cfg(target_os = "linux")']
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=wild"]
Configured linking requires the verified namespace runtime. Include the driver,
linker and their libraries in that pinned runtime. Its content digest enters
action identity. BSMR preserves RUSTC_LINKER for build scripts and uses the
existing native C++ toolchain for linking. A missing driver or linker fails.
There is no linker-name allowlist or automatic replacement.
Experimental linkers use the same explicit configuration and do not change the
default. The mold Mach-O port accepts
Clang's --ld-path selection. Native macOS configured-linker execution remains
unqualified, as does macOS package-code isolation.
Cross compilation remains unsupported. Project compiler flags otherwise support cfg values, lints, and scalar optimization settings. Response files, compiler extensions, file-based overrides, and Cargo CLI option parity are not implemented. Shared files outside a crate require explicitly declared action inputs.
Repeated builds of the same entrypoint reuse unchanged actions, including across checkouts. Distinct entrypoints currently own separate configured graphs and may compile an otherwise identical shared library again.
Stable toolchain pins reject nightly-only language features. Stable builds reuse compiled libraries for the metadata needed by dependent crates. Nightly builds retain separate metadata actions so dependent compilation can start sooner.
Native actions validate rustc's reported source and environment reads before accepting a successful compiler result. Undeclared reported inputs fail instead of publishing an incomplete cache entry. Declared directories cover their resolved contents. Symlink targets outside those directories must be declared separately. This check is not a filesystem sandbox.
Rust compiler, Clippy, and standard-library archives have pinned SHA-256 digests. Their pinned sizes let the download rule use cached compiler archives without querying their HTTP headers. Their content contributes to compilation action identity. Target discovery uses the selected local rustup installation, while configured planning uses the bundled resolver. C/C++ linking and Python bootstrap tools still come from the execution host. This is not a fully hermetic or remotely qualified toolchain. Native actions honor the existing cache-upload policy.
Ambient Rust flags and user Cargo configuration do not configure native actions.
Project configuration supplies the planner's flags and profiles. Project [env]
values are not supported yet.
Unsupported projects can use Cargo directly. BSMR never silently switches to a
whole-workspace Cargo build after inference or compilation fails.
RFC 0002 describes the broader Rust design.
Verification¶
test/native-rust-build.ts exercises real compiler actions, unit-test success and
failure, and a custom recipe consuming an inferred executable. It checks
manifest invalidation, source invalidation, unrelated edits, cache restoration
in a second checkout, environment isolation, and build-script rejection. It also
checks that inference leaves the checkout free of generated build files.
python3 test/rust/linker.py /path/to/bsmr /path/to/runtime.json clang -- -fuse-ld=wild
compares the selected driver with Cargo. It checks build-script environment,
warm reuse, changed linker arguments and missing tools without substitution.
test/rust/metadata.py compares metadata visibility and ordering with Cargo. It
checks generated paths, cache restoration across checkouts, and source edits.
bsmr test app includes the package's integration tests. Cargo supplies their
dev dependencies and the binaries exposed through CARGO_BIN_EXE_<name>.
Tests run from their declared package source directory. Artifact-backed
environment paths remain absolute when a test changes its working directory.
Local packages in the selected dependency graph retain their workspace layout
in a declared test resource. A test can read a sibling package's fixtures through
the same relative path used by Cargo. The primary package retains source changes
produced by its build script. The view remains read-only and tracks fixture edits.
When that directory is a mapped source root, file!() paths are relative to it.
Snapshot readers can locate the checked-in files without exposing the generated
target directory or making source inputs writable.
Custom harnesses use their own main with Cargo's cfg(test) setting.
python3 test/rust/tests.py /path/to/bsmr compares these contracts with Cargo.
Supply /path/to/runtime.json as a second argument to select the Linux namespace
runtime explicitly. The fixture checks test-only
features, binary execution, fixture reads and failing custom harnesses. It
does not qualify macOS package-code isolation or arbitrary host dependencies.
test/rust/lto.ts compares release LTO with Cargo across a three-crate chain.
It checks compiler flags, dependency edits, cached output restoration, and unit tests.
test/rust/libraries.ts compares Cargo's multiple-output behavior with native
builds. It runs a Rust consumer and loads the generated C library before and after
a source edit, then checks warm reuse and restoration after cleaning outputs.
The native toolchain reports the release channel selected by the verified compiler
pin. Nightly features require the nightly channel. For local rule development,
node test/native-rust-build.ts /path/to/bsmr prelude tests the source prelude
without rebuilding the CLI. Omitting the third argument tests its bundled rules.