[bldr]docs
Guides

Rust projects

Checks, tests and binaries with Cargo, and crates shared between members.

Cargo builds Rust crates. Constructing one declares the targets, so a member gets check, test, clippy and doc without asking.

import { Cargo, CargoToolchain } from "bldr/rust-tools";
import rust from "rust";

const c = new Cargo({
    scope,
    toolchain: new CargoToolchain({ baseImage: rust }),
    source: member.rootDirectory,
    binary: { binaryName: "my-tool", profile: "release" },
});

export const binary = c.binary().dir;
bldr build my-member/check      # does it compile
bldr test my-member:test        # per-case results
bldr build my-member/binary     # the executable

Crates are downloaded by one step, Cargo > fetch, which runs cargo fetch over your Cargo.toml and Cargo.lock files alone: its cache key is your manifests, and it survives every source edit. It is the only cargo step with internet access. Every other command compiles against what it fetched, offline — so a test that calls out to the internet fails, and a build.rs that downloads has to get its input some other way (a mount, a file in the toolchain image).

The toolchain image

A CargoToolchain is the image cargo runs in. Add what your crate's build needs:

const toolchain = new CargoToolchain({ baseImage: rust })
    .aptInstall(["clang", "libclang-dev"]);

A build step has no network unless it is approved for one. .aptInstall() approves itself, as do the rustup steps behind channel, components and targets; any other step that downloads says so: .run("cargo install cargo-nextest --locked", { internet: true }).

You can also bake files in with .copy(), which is how a crate whose build.rs reads a .proto finds it without a path relative to the repository.

Depending on another member's crate

Two members, one crate, compiled from one copy. The consumer depends on it by version, not by path:

Cargo.toml
my-shared-crate = "0"
.bldr.ts
import { lib as shared } from "my/shared-crate";

new Cargo({ scope, toolchain, source: member.rootDirectory, dependencies: [shared] });

The version dependency keeps the manifest ordinary, so the crate still builds outside bldr, and dependencies patches it to the member's source at build time.

Cross-compiling

const toolchain = new CargoToolchain({
    baseImage: rust,
    targets: ["x86_64-unknown-linux-musl"],
    env: { RUSTFLAGS: "-C target-feature=+crt-static" },
}).aptInstall("musl-tools");

new Cargo({
    scope,
    toolchain,
    source: member.rootDirectory,
    defaults: { target: "x86_64-unknown-linux-musl" },
    binary: { binaryName: "my-tool", profile: "release" },
});

Set the target on defaults rather than per command. A toolchain that sets +crt-static without a target applies it to host artifacts too, and proc-macros are host artifacts that cannot link statically.

Bounding parallelism

Cargo defaults to one job per core, and peak memory scales with it. A large workspace on a many-core machine can exceed the memory a build is allowed and be killed with no diagnostic:

check: { jobs: 12 },
test: [{ id: "unit", jobs: 12, cargoArgs: ["--lib", "--bins", "--no-fail-fast"] }],

--no-fail-fast matters for a test target. Without it, cargo test stops at the first crate that fails, and the report looks complete while missing every later crate.

On this page