[bldr]docs
Build APIbldr/rust-tools

bldr/rust-tools

Building Rust crates with Cargo: checks, tests, binaries, clippy, docs.

Building Rust crates with Cargo: checks, tests, binaries, clippy, docs.

Import from bldr/rust-tools in a .bldr.ts. Generated from the module's TypeScript.

Contents

CargoclassCargo over one source tree and one toolchain.
cargoRendervariablebldr-cargo-render, the log renderer every Cargo command runs
CargoToolchainclassA Rust toolchain: a container image that knows where cargo and rustc live.
CROSS_TARGETSvariableThe cross targets a toolchain can be given, by Rust target triple.
crossEnvironmentfunction
parseCargoMetadatafunction

Types

Each has its own page.

CargoBinariesOptionsinterfaceWhich binaries to build together in one cargo invocation.
CargoBinaryOptionsinterfaceWhich binary to build, and how.
CargoBinaryResultinterfaceThe result of building one binary.
CargoBuildOptionstypeWhat a plain cargo build compiles.
CargoBuildResultinterfaceThe result of a build.
CargoCheckOptionstypeWhat a check target compiles.
CargoCheckResultinterfaceThe result of a check. Type-checks without producing artifacts.
CargoClippyOptionsinterfaceWhat a clippy gate lints.
CargoClippyResultinterfaceThe result of a clippy run.
CargoCommandResultinterfaceWhat every cargo command hands back.
CargoConfiginterfaceHow a Cargo is configured. image and source are required, so
CargoCoverageFormattypeA machine-parseable coverage format — the two llvm-cov export speaks.
CargoCoverageOptionsinterfaceExtra knobs for Cargo.coverage.
CargoCoverageResultinterfaceThe result of a coverage run.
CargoCrateinterfaceOne workspace member crate, as cargo reports it.
CargoCrateTargetinterfaceA crate's buildable target as cargo reports it: a lib, a bin, an example,
CargoDocOptionsinterfaceExtra knobs for Cargo.doc.
CargoDocResultinterfaceThe result of a documentation build.
CargoExampleOptionsinterfaceWhich example to build (--example <name>).
CargoFmtOptionstypeWhat a fmt gate checks.
CargoFmtResulttypeThe result of a fmt --check.
CargoInventoryinterfaceThe workspace's layout, scraped from cargo metadata --no-deps: the crates
CargoOptionsinterfaceKnobs shared by every cargo command this builder runs.
CargoPackageOptionstypeWhat cargo package produces — the crates of this workspace as the tarballs
CargoPackageResultinterfaceThe result of a package run.
CargoRunOptionsinterfaceKnobs for a command that runs code, and can therefore hang.
CargoTestOptionsinterfaceExtra knobs for Cargo.test.
CargoTestResultinterfaceThe result of a test run.
CargoToolchainOptionsinterfaceHow to build a CargoToolchain.
CrossTargetinterfaceHow a Debian toolchain image cross-compiles one Rust target.
OnTheFlytypeOptions for a command you are asking for on the fly, rather than one the
RustAnalyzerOptionstypeHow an editor's rust-analyzer should see the crate.
WithIdtypeOne entry in a list of same-kind targets.

Cargo

class

Cargo over one source tree and one toolchain.

Constructing declares the crate's targets — see the constructor — and the methods then build things. Every command runs in its own pod, and everything a command returns is lazy, so describing work is free and only asking for a result runs it.

.constructor(config: CargoConfig, discovered?: { … })

constructor

Build a Cargo over a source tree, and declare the targets its config asks for.

Constructing is declaring: by the time this returns, check, test, doc, clippy and an editor language server exist as targets on the scope you passed (whichever of them the config left switched on). That is why there is exactly one Cargo per scope — a second one would try to declare the same target names, and the framework rejects a duplicate rather than quietly building the same thing twice.

constructor(config: CargoConfig, discovered?: { … }): Cargo

See CargoConfig, CargoInventory, Scope.

const c = new Cargo({
    scope,
    toolchain,
    source: member.rootDirectory,
    binary: { binaryName: "myapp", profile: "release" },
});

inventory: CargoInventory

property

The workspace layout this builder was discovered from, when it was constructed with Cargo.discover: the crates and the targets each declares, as cargo reported them. undefined on a manually configured builder.

See CargoInventory.

.binaries(opts: OnTheFly<CargoBinariesOptions>)

method

Build several binaries in one cargo invocation.

binaries(opts: OnTheFly<CargoBinariesOptions>): CargoBuildResult

See CargoBinariesOptions, CargoBuildResult, OnTheFly.

.binary(arg?: string | OnTheFly<CargoBinaryOptions>)

method

Build one of the binaries the config declares.

binary(arg?: string | OnTheFly<CargoBinaryOptions>): CargoBinaryResult

See CargoBinaryOptions, CargoBinaryResult, OnTheFly.

c.binary();            // the one declared without an id
c.binary("musl");      // a declared variant
c.binary({ binaryName: "helper", profile: "release" });   // a one-off

.build(arg?: string | OnTheFly<CargoOptions>)

method

cargo build — compile, keeping what it produced.

build(arg?: string | OnTheFly<CargoOptions>): CargoBuildResult

Returns CargoBuildResult — The result, whose artifacts is the profile's output directory. For a single named executable, prefer Cargo.binary, which knows where the file lands.

See CargoBuildResult, CargoOptions, OnTheFly.

.check(arg?: string | OnTheFly<CargoOptions>)

method

cargo check — type-check without producing artifacts.

The fastest answer to "does this still compile", which is why a check target is registered by default.

check(arg?: string | OnTheFly<CargoOptions>): CargoCheckResult

See CargoCheckResult, CargoOptions, OnTheFly.

c.check();                                  // the registered one
c.check("musl");                            // a registered variant
c.check({ allFeatures: true });             // a one-off

// ask rather than insist:
const probe = c.check({ allFeatures: true, ignoreFailures: true });
if (await probe.failed.value()) console.warn("all-features is broken");

.clippy(arg?: string | OnTheFly<CargoClippyOptions>)

method

cargo clippy — lint.

report holds the machine-readable diagnostics (clippy.json) beside what clippy printed, so a tool can read the findings without parsing a log. Clippy's lint-failure exit is treated as "there are findings", so the report exists because there were some rather than being lost with a failed pod; anything else — clippy crashing, a compile error — fails.

clippy(arg?: string | OnTheFly<CargoClippyOptions>): CargoClippyResult

See CargoClippyOptions, CargoClippyResult, OnTheFly.

c.clippy({ denyWarnings: true });

.coverage(arg?: string | OnTheFly<CargoCoverageOptions>)

method

Run the tests instrumented, and report what they executed.

data is the converted report — indexed by member, then by path within it, with a percentage at every level — and is what a coverage target registers. report is the tool's own lcov.info / coverage.json, for anything outside this build system that needs to read them.

Coverage is data, not a gate: it never fails a build for being low, and failing tests do not suppress it, because coverage of a red run is still the measurement that was asked for.

coverage(arg?: string | OnTheFly<CargoCoverageOptions>): CargoCoverageResult

See CargoCoverageOptions, CargoCoverageResult, OnTheFly.

.doc(arg?: string | OnTheFly<CargoDocOptions>)

method

cargo doc — render the crate's documentation.

doc(arg?: string | OnTheFly<CargoDocOptions>): CargoDocResult

See CargoDocOptions, CargoDocResult, OnTheFly.

c.doc();                              // the registered one
c.doc({ privateItems: true });        // including internals

.example(opts: OnTheFly<CargoExampleOptions>)

method

Build one example (--example <name>).

example(opts: OnTheFly<CargoExampleOptions>): CargoBinaryResult

See CargoBinaryResult, CargoExampleOptions, OnTheFly.

.examples(opts?: OnTheFly<CargoOptions>)

method

Build every example the crate declares (--examples).

examples(opts?: OnTheFly<CargoOptions>): CargoBuildResult

See CargoBuildResult, CargoOptions, OnTheFly.

.fmt(arg?: string | OnTheFly<CargoOptions>)

method

cargo fmt --all --check — verify the sources are formatted.

Never rewrites anything. A build runs against a content-addressed copy of the sources, so reformatting them here would produce a tree nobody sees; the answer worth having is whether what is committed is formatted. To get a formatted tree back, use Cargo.formatted.

fmt(arg?: string | OnTheFly<CargoOptions>): CargoCommandResult

See CargoCommandResult, CargoOptions, OnTheFly.

.formatted()

method

cargo fmt — a reformatted copy of the sources.

The source mount is never written to; a copy is formatted and captured. To check formatting instead of rewriting it, use Cargo.fmt.

Takes no options, unlike every other command here: rustfmt reads the crate's rustfmt.toml, and no feature set, target triple or profile changes what it prints. A parameter would only be somewhere to put a setting that has no effect.

formatted(): Directory

See Directory.

.lsp(arg?: string | CargoOptions)

method

A rust-analyzer language server for this crate, configured to agree with the build about toolchain, source layout and manifest location.

Registered automatically, so an editor finds one without the build file mentioning it.

lsp(arg?: string | CargoOptions): LanguageServer

See CargoOptions, LanguageServer.

.package(arg?: string | OnTheFly<CargoOptions>)

method

cargo package — the crates as the tarballs cargo publish would upload, extracted and keyed by crate name:

mylib/          # the contents of mylib-0.1.0.crate
mylib-macros/   # the contents of mylib-macros-0.1.0.crate

This is what another crate should depend on rather than a raw source tree. A source tree leaks the producer's layout — how many crates there are, where they sit, how to patch each one. A packaged artifact is self-describing: the directory names are the crate names.

It is also honest about what ships, since cargo package applies the manifest's include/exclude, so a consumer builds against what the crate actually publishes rather than whatever happens to be lying in the directory.

package(arg?: string | OnTheFly<CargoOptions>): CargoPackageResult

See CargoOptions, CargoPackageResult, OnTheFly.

.packagedCrates()

method

This builder's crates, packaged — see CargoDependency.

Every crate in the source tree, keyed by name, because cargo packages a whole workspace in one command and knows its members without being told.

packagedCrates(): Directory

See Directory.

.test(arg?: string | OnTheFly<CargoTestOptions>)

method

cargo test — compile and run the tests, keeping each case.

A test failure is data: it lands in report as a failing case, and bldr test shows it. A compile failure is not, and fails the command with the compiler's output as the failing task's log. Telling those two apart is the whole job here — a suite that did not build must never read as a suite with no failures.

test(arg?: string | OnTheFly<CargoTestOptions>): CargoTestResult

See CargoTestOptions, CargoTestResult, OnTheFly.

c.test();                                        // the registered one
c.test({ lib: true, timeoutSecs: 600 });

.discover(config: CargoConfig)

static method

Build a Cargo over a source tree, with the crate's targets discovered from the manifests rather than enumerated by hand — the preferred way to construct one. cargo metadata --no-deps runs once in a small pod over just the layout-bearing files (manifests and autodiscovery entry points), so the discovery is content-addressed: it re-runs when the layout changes and is a cache hit otherwise.

What discovery adds on top of Cargo's constructor: every [[bin]] (declared or autodiscovered) becomes a binary/<name> target without being named in build code, and Cargo.inventory carries the whole layout for consumers. Everything the constructor declares — check, test, doc, lsp — is declared the same way here.

Explicit config still wins: a configured binary entry overrides the discovered one of the same name (that is where a profile or a musl target goes), and is validated against the inventory so a typo names the binaries that do exist. binary: false opts out of binary derivation; constructing with new Cargo(...) opts out of discovery entirely.

awaited at module evaluation: declaring targets needs their names, so the discovery cannot be lazy. The pod it forces is cached like any other invocation.

discover(config: CargoConfig): Promise<Cargo>

See CargoConfig.

cargoRender: Cargo

variable

bldr-cargo-render, the log renderer every Cargo command runs through — checked and tested here, under this member, because the crate is this member's tool and its source member has no build file of its own: one there would import this module, which imports the source, and the two would evaluate in a cycle. Its tests run the real cargo both ways and require the rendered log to match cargo's own, byte for byte.

Configured, not discovered: every build evaluates this module, and a build declared as a graph runs no pod while it is being declared, so the binary is named here rather than read from the manifest by a metadata pod.

This is the conformance build. The copies that actually run are compiled per toolchain image, by that image, inside Cargo — see withRenderer in cargo.ts.

See Cargo.

CargoToolchain

class

A Rust toolchain: a container image that knows where cargo and rustc live.

Immutable, like every other builder here. Each method returns a new toolchain, so one base can be specialised several ways without the variants interfering:

const base  = new CargoToolchain({ baseImage: rust });
const lint  = base.withComponents("clippy", "rustfmt");

Pass one as a CargoConfig.image. Anything the toolchain does not cover — a system package, a cargo install — is an ordinary image step away through CargoToolchain.image.

.constructor(opts: any)

constructor

constructor(opts: any): CargoToolchain

image: ContainerImage

property

The underlying image, for anything this class does not model.

See ContainerImage.

.aptInstall(packageNames: string | string[], opts?: AptInstallOptions)

method

apt-get install in the toolchain image, as one layer — see DebianContainerImage.aptInstall.

A Rust toolchain that cross-compiles almost always needs a package or two from the distro (musl-tools, protobuf-compiler), and every one of those callsites was spelling the same update/install/clean incantation.

aptInstall(packageNames: string | string[], opts?: AptInstallOptions): CargoToolchain

See AptInstallOptions.

.copy(source: Directory, dest: string)

method

A toolchain with a directory copied into it.

Useful for anything a crate's build.rs reads at compile time — a schema it generates code from, a vendored header — since that has to be present in the image rather than mounted per command.

copy(source: Directory, dest: string): CargoToolchain

See Directory.

.run(command: string, opts?: RunOptions)

method

A toolchain with an extra build step — a build tool, a generated file.

The step has no network unless { internet: true } approves it. For a system package, aptInstall is the approved form already.

run(command: string, opts?: RunOptions): CargoToolchain

See RunOptions.

toolchain.run("cargo install cargo-nextest --locked", { internet: true })

.withComponents(...components: string[])

method

A toolchain with extra rustup components on it.

You rarely need this: a Cargo adds clippy, rustfmt and rust-analyzer on demand, once each, when a command actually calls for one. Reach for it when something else needs the component, or when you would rather pay for it up front than inside the first command that wants it.

withComponents(...components: string[]): CargoToolchain

.withCrossTarget(target: string)

method

A toolchain that cross-compiles target from a Debian image, C dependencies included: the distro packages for it installed, and the variables cargo, the cc crate, cmake-rs and bindgen read for a target that differs from the host set.

What a target needs is data — CROSS_TARGETS — so supporting another one is a row there, not a method here. The target's standard library (targets) and its RUSTFLAGS (+crt-static for a static musl binary) are the caller's, as for any other target.

withCrossTarget(target: string): CargoToolchain

CROSS_TARGETS: Readonly<Record<string, CrossTarget>>

variable

The cross targets a toolchain can be given, by Rust target triple.

aarch64-unknown-linux-musl: the C compiler is aarch64-linux-musl-gcc from Debian's multiarch musl-dev:arm64 — a real musl sysroot for arm64 (headers in /usr/include/aarch64-linux-musl, libc.a and the crt objects in /usr/lib/aarch64-linux-musl) and a wrapper that runs the GNU cross gcc with a specs file putting those in place of glibc's. A build script's cc then compiles against the headers of the libc the binary links. The GNU cross gcc alone has no arm64 libc headers, falls through to the host's x86_64 glibc headers in /usr/include, and fails on bits/wordsize.h at the first #include — which is how a crate with C in its graph (blake3, zstd-sys) fails to cross-build. The link stays with the GNU cross gcc: with +crt-static rustc links a musl target self-contained, from the crt objects and libc its own target ships, and the driver only runs ld.

aarch64-unknown-linux-gnu: the GNU cross compilers throughout, C++ included, and the cross sysroot for bindgen.

See CrossTarget.

crossEnvironment(target: string, cross: CrossTarget)

function

The environment that points cargo and the build-script conventions at a cross toolchain: CARGO_TARGET_<TRIPLE>_LINKER for rustc's link, and the per-target CC_/CXX_/AR_ the cc crate, cmake-rs and autoconf-driven build scripts read, plus bindgen's sysroot.

crossEnvironment(target: string, cross: CrossTarget): Record<string, string>

See CrossTarget.

parseCargoMetadata(json: string)

function

Parse cargo metadata --format-version 1 output into the inventory.

parseCargoMetadata(json: string): CargoInventory

See CargoInventory.

On this page