Rust
The Rust rules expose Cargo packages as selectable imp labels without
replacing Cargo's package model. A label can build one or more binaries, run the package's test
suite, publish binaries under dist/, and participate in workspace-wide
formatting and linting. Rust, Cargo, the C link driver, linker, and optional
compiler cache are all declared tool dependencies rather than ambient host
requirements.
Available goals
| Goal | Integration | Module |
|---|---|---|
imp build | Cargo | //rules/rust |
imp test | Cargo | //rules/rust |
imp package | Cargo | //rules/rust |
imp fmt | rustfmt | //rules/rust |
imp lint | Clippy | //rules/rust |
Set up the workspace
Import the Rust rules in imp.workspace.js. They provide the default Rust
toolchain and C link driver; workflow modules enable their high-level commands
and load the formatter and linter integrations:
import "//rules/rust";
import "//rules/workflows/fmt";
import "//rules/workflows/lint";
import "//rules/workflows/package";
import "//rules/workflows/test";
Toolchains may also select an explicit C link driver, linker, and kache tool handle. Pinning them in the workspace file makes the same tool graph available to build, test, and Clippy instead of letting those commands drift apart.
When a kache target is set, KACHE_BASE_DIR and rustc's
--remap-path-prefix are wired up automatically so cache hits survive
imp's per-run sandbox paths, and KACHE_MAX_SIZE caps the on-disk object
cache at 4GiB by default — override it via
kacheToolchain(version, { cacheSize: "8GiB" }). Caching stays strictly
local: KACHE_LOCAL_ONLY=1 is always set, so no S3/remote cache config kache
supports is ever reached. Once kache has been used at least once, imp cache stats --details also prints its own kache stats output (hit rate,
compile counts, …) alongside the on-disk size for the kache-data cache —
this starts kache's background daemon if it isn't already running, since
kache (unlike sccache) needs the daemon up to report stats at all.
Kache does not cache user-facing executable links by default. Enable that workspace-wide when those links dominate builds:
export const kacheConfig = { cacheExecutables: true };
This repository's workspace also imports //rules/imp/mode, which declares
the default (opt=debug) and release (opt=release) profiles. Cargo
builds follow the selected profile:
imp build --profile release //path/to/package:server
release: true on an individual cargoPackage() remains an unconditional
opt-in to Cargo's release profile.
Select a workspace lockfile
The Rust toolchain and its kache sidecar each ship a lockfile pinning the download URL, size, and SHA-256 of every release artifact they know. To pin a version the shipped lockfile does not know, give the toolchain the address of a lockfile this workspace owns; the shipped lockfile stays the default.
import { rustToolchain } from "//rules/rust/toolchain";
export const rust = rustToolchain("1.90.0", {
default: true,
lockfile: "//locks/rust.lock",
});
The toolchain handle is also the lockfile generation root:
imp goal gen-lockfiles //:rust
The generation root writes to the address the toolchain declares, so the
address is given one time only. Downloads stay verified: an address with no
file, or a lockfile with no entry for the selected version and platform, makes
the acquire fail and points at imp goal gen-lockfiles.
Declare a Cargo package
In the directory containing Cargo.toml, add a BUILD.js:
import { cargoPackage } from "//rules/rust";
export const server = cargoPackage({
bin: "server",
release: true,
});
The export name forms the label address, so this declaration is selected as
//path/to/package:server. Set path when the manifest is below the declaring
BUILD.js. bin accepts a string or a list and explicitly names the binaries
Cargo produces — it is not derived from Cargo.toml. Omit it for a
library-only crate: it can still be formatted, linted, and tested, but has no
binary artifact for build or package.
For a package declared inside an enclosing Cargo workspace, set
workspaceMember: true. That stages the workspace root and sibling path
dependencies so Cargo can resolve the outer [workspace]. Leave it false for
a standalone crate or for the target representing the workspace root itself.
Generated sources
generatedSrcs accepts a codegen() result from //rules/imp/codegen and
stages its outputs at their declared workspace paths for Cargo build, lint,
test-build, and doctest actions. The explicit form,
{ artifact, path }, uses a path relative to the Cargo package:
import { cargoPackage } from "//rules/rust";
import { codegen } from "//rules/imp/codegen";
import { nativeTool } from "//rules/imp/native-tool";
const generated = codegen({
tools: { sh: nativeTool("sh") },
outputPaths: ["app/src/generated.rs"],
argv: (exec, { sh }) => [
exec.tool(sh, "sh"),
"-c",
'printf "pub const VALUE: u32 = 42;\\n" > "$1"',
"generate",
"app/src/generated.rs",
],
});
export const app = cargoPackage({
path: "app",
bin: "app",
generatedSrcs: [generated],
});
The artifact must land at the path declared by the entry. Two different artifacts cannot claim the same path. Generated files stay in the graph and do not get written into the workspace.
Run goals
imp build //path/to/package:server
imp test //path/to/package:server
imp fmt --check //path/to/package:server
imp lint //path/to/package:server
imp package //path/to/package:server
build captures Cargo's selected binaries in the task result. package
publishes the build output to dist/path/to/package/server. Tests are always
executed rather than replaying a cached successful run; compilation work below
the test invocation can still use the normal task and compiler caches.
cargoArgs and testArgs append arguments to the corresponding Cargo command.
Use testTools for host programs that tests invoke: they are resolved
imperatively and placed on the sandbox's PATH. Use deps/testDeps for
additional graph-native input handles the build/test run needs (e.g. a
resourcePackage()'s .files, for files referenced by include_str! or
include_bytes!). Set rustConfig.doctest: false to disable Cargo doc-tests
workspace-wide; there is no per-package override yet.
Generate missing BUILD files
The Rust build generator can declare packages for otherwise unowned
Cargo.toml files. Enable it explicitly in imp.workspace.js:
import "//rules/rust/generate_build";
export const rustConfig = {
buildGenerate: true,
// Defaults to true; turn it off for a workspace with no Rust doc-tests.
doctest: false,
};
Then run imp goal generate-build. The generator uses cargo metadata to
identify package names, binaries, workspace roots, and workspace members. It is
off by default and does not rewrite declarations that already own a manifest.
Configuration
| Option | Type | Default | Required |
|---|---|---|---|
| buildGenerate | bool | false | no |
| doctest | bool | true | no |
Example
export const rustConfig = {
buildGenerate: false,
doctest: true,
};Targets
cargoPackage()
cargoPackage(opts = {})
Declare a Cargo package target: a self-contained crate, a cargo workspace root (member manifests are globbed via **\/Cargo.toml), or one member of an outer workspace declared elsewhere (see workspaceMember). bin is optional — a lib-only package is a fully valid target for fmt/test, just not for build/package.
| Parameter | Type | Description |
|---|---|---|
| opts | object | |
| [opts.path] | string | Workspace-relative directory containing Cargo.toml. Defaults to the calling BUILD.js's own directory. |
| [opts.bin] | string | string[] |
| [opts.release=false] | boolean | Always build with cargo build --release. |
| [opts.toolchain] | object | string |
| [opts.cargoArgs=[]] | string[] | Extra arguments appended to cargo build. |
| [opts.testArgs=[]] | string[] | Extra arguments appended to cargo test. |
| [opts.testTools=[]] | Array | nativeTool() specifications exposed on PATH while running tests (including doc-tests in a real workspace's shared run). |
| [opts.deps=[]] | Array | Extra graph-native input handles the build needs (e.g. a resourcePackage()'s .files). |
| [opts.testDeps=[]] | Array | Extra graph-native input handles the test run needs but the build doesn't. |
| [opts.generatedSrcs=[]] | Array | codegen() results or { artifact, path } entries staged at their declared paths during compilation. |
| [opts.workspaceMember=false] | boolean | This package is a member of a workspace rooted at "." (see module docstring's limitation on non-root workspace roots). |
| [opts.builtin=false] | boolean | Read the package from the installed rules bundle instead of the consumer workspace. |
Returns: object Frozen object with lazy [BUILD]/[TEST]/[LINT]/[FMT]/[PACKAGE] getters.