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

GoalIntegrationModule
imp buildCargo//rules/rust
imp testCargo//rules/rust
imp packageCargo//rules/rust
imp fmtrustfmt//rules/rust
imp lintClippy//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

OptionTypeDefaultRequired
buildGenerateboolfalseno
doctestbooltrueno

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.

ParameterTypeDescription
optsobject
[opts.path]stringWorkspace-relative directory containing Cargo.toml. Defaults to the calling BUILD.js's own directory.
[opts.bin]stringstring[]
[opts.release=false]booleanAlways build with cargo build --release.
[opts.toolchain]objectstring
[opts.cargoArgs=[]]string[]Extra arguments appended to cargo build.
[opts.testArgs=[]]string[]Extra arguments appended to cargo test.
[opts.testTools=[]]ArraynativeTool() specifications exposed on PATH while running tests (including doc-tests in a real workspace's shared run).
[opts.deps=[]]ArrayExtra graph-native input handles the build needs (e.g. a resourcePackage()'s .files).
[opts.testDeps=[]]ArrayExtra graph-native input handles the test run needs but the build doesn't.
[opts.generatedSrcs=[]]Arraycodegen() results or { artifact, path } entries staged at their declared paths during compilation.
[opts.workspaceMember=false]booleanThis package is a member of a workspace rooted at "." (see module docstring's limitation on non-root workspace roots).
[opts.builtin=false]booleanRead the package from the installed rules bundle instead of the consumer workspace.

Returns: object Frozen object with lazy [BUILD]/[TEST]/[LINT]/[FMT]/[PACKAGE] getters.