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
Cargo | class | Cargo over one source tree and one toolchain. |
cargoRender | variable | bldr-cargo-render, the log renderer every Cargo command runs |
CargoToolchain | class | A Rust toolchain: a container image that knows where cargo and rustc live. |
CROSS_TARGETS | variable | The cross targets a toolchain can be given, by Rust target triple. |
crossEnvironment | function | |
parseCargoMetadata | function |
Types
Each has its own page.
CargoBinariesOptions | interface | Which binaries to build together in one cargo invocation. |
CargoBinaryOptions | interface | Which binary to build, and how. |
CargoBinaryResult | interface | The result of building one binary. |
CargoBuildOptions | type | What a plain cargo build compiles. |
CargoBuildResult | interface | The result of a build. |
CargoCheckOptions | type | What a check target compiles. |
CargoCheckResult | interface | The result of a check. Type-checks without producing artifacts. |
CargoClippyOptions | interface | What a clippy gate lints. |
CargoClippyResult | interface | The result of a clippy run. |
CargoCommandResult | interface | What every cargo command hands back. |
CargoConfig | interface | How a Cargo is configured. image and source are required, so |
CargoCoverageFormat | type | A machine-parseable coverage format — the two llvm-cov export speaks. |
CargoCoverageOptions | interface | Extra knobs for Cargo.coverage. |
CargoCoverageResult | interface | The result of a coverage run. |
CargoCrate | interface | One workspace member crate, as cargo reports it. |
CargoCrateTarget | interface | A crate's buildable target as cargo reports it: a lib, a bin, an example, |
CargoDocOptions | interface | Extra knobs for Cargo.doc. |
CargoDocResult | interface | The result of a documentation build. |
CargoExampleOptions | interface | Which example to build (--example <name>). |
CargoFmtOptions | type | What a fmt gate checks. |
CargoFmtResult | type | The result of a fmt --check. |
CargoInventory | interface | The workspace's layout, scraped from cargo metadata --no-deps: the crates |
CargoOptions | interface | Knobs shared by every cargo command this builder runs. |
CargoPackageOptions | type | What cargo package produces — the crates of this workspace as the tarballs |
CargoPackageResult | interface | The result of a package run. |
CargoRunOptions | interface | Knobs for a command that runs code, and can therefore hang. |
CargoTestOptions | interface | Extra knobs for Cargo.test. |
CargoTestResult | interface | The result of a test run. |
CargoToolchainOptions | interface | How to build a CargoToolchain. |
CrossTarget | interface | How a Debian toolchain image cross-compiles one Rust target. |
OnTheFly | type | Options for a command you are asking for on the fly, rather than one the |
RustAnalyzerOptions | type | How an editor's rust-analyzer should see the crate. |
WithId | type | One 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?: { … }): CargoSee 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>): CargoBuildResultSee CargoBinariesOptions, CargoBuildResult, OnTheFly.
.binary(arg?: string | OnTheFly<CargoBinaryOptions>)
method
Build one of the binaries the config declares.
binary(arg?: string | OnTheFly<CargoBinaryOptions>): CargoBinaryResultSee 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>): CargoBuildResultReturns 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>): CargoCheckResultSee 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>): CargoClippyResultSee 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>): CargoCoverageResultSee CargoCoverageOptions, CargoCoverageResult, OnTheFly.
.doc(arg?: string | OnTheFly<CargoDocOptions>)
method
cargo doc — render the crate's documentation.
doc(arg?: string | OnTheFly<CargoDocOptions>): CargoDocResultSee 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>): CargoBinaryResultSee CargoBinaryResult, CargoExampleOptions, OnTheFly.
.examples(opts?: OnTheFly<CargoOptions>)
method
Build every example the crate declares (--examples).
examples(opts?: OnTheFly<CargoOptions>): CargoBuildResultSee 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>): CargoCommandResultSee 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(): DirectorySee 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): LanguageServerSee 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.crateThis 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>): CargoPackageResultSee 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(): DirectorySee 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>): CargoTestResultSee 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): CargoToolchainimage: 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): CargoToolchainSee 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): CargoToolchainSee 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): CargoToolchainSee 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): CargoToolchainCROSS_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): CargoInventorySee CargoInventory.