Repository navigation
RFC 0002: Build Rust crates hermetically from Cargo metadata #14
Windsor Nguyễn (windsornguyen)
started this conversation in
Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
RFC: Build Rust crates hermetically from Cargo metadata
Status: Proposed
Summary
BSMR should integrate with Cargo rather than replace it.
Cargo should remain responsible for Rust ecosystem semantics: interpreting
Cargo.toml, resolving package versions, applying workspace inheritance andpatches, selecting features under Cargo's resolver rules, and writing
Cargo.lock. BSMR should consume the committed Cargo inputs as an immutabledependency graph, acquire the exact Rust toolchain and crate sources, and
execute crate compilation, procedural macros, build scripts, linking, and tests
as explicit BSMR actions.
This design is successful only if it improves the outcomes developers actually
care about:
dependency kinds, host/target split, build-script behavior, and platform
predicates without inventing competing Rust semantics.
toolchains are shared through the CAS, and local or remote caches reuse work
across binaries, tests, workspaces, and machines.
procedural macros execute with declared inputs and no public network access,
and native toolchains are explicit.
workspaces, lockfiles, features, profiles, and tools. BSMR must not require a
second dependency manifest or a BSMR-specific Rust resolver.
The intended boundary follows the strongest public precedents in
rules_rustCrate Universe and Meta's Reindeer: Cargo owns dependencyresolution; the build system imports the result and owns frozen execution.
Decision
The proposed architecture has three explicit phases:
BSMR should describe this publicly as hermetic frozen Rust builds, not
"hermetic Cargo resolution." Updating
Cargo.lockmay consult registry and Gitstate. Once the lockfile and toolchain declaration are committed, resolution and
public network access do not belong in the build.
Cargo has two related but distinct resolutions:
Cargo.lock.host platform, target platform, and command.
Cargo resolver version 2 deliberately avoids unifying build-dependency and
procedural-macro features with ordinary target dependencies, and it avoids
unifying inactive target-specific dependencies. The relevant BSMR identity is
therefore:
It is not merely
(crate name, version).User experience
The normal workflow should remain familiar:
Cargo.tomlor uses Cargo to add or update a dependency.Cargo.lock.relevant Cargo configuration together.
A stale lockfile, unsupported source, missing native toolchain, or ambiguous
feature graph must produce one actionable error naming the affected package and
the explicit update or configuration required. BSMR must never repair
Cargo.lockduring an ordinary build.Requirements
Each requirement below is intended to become an independently reviewable
implementation issue. The "not desired" sections are part of the contract.
1. Pin the Rust toolchain as a verified BSMR toolchain
Desired
declaration.
execution platform.
rustc,rustdoc, Cargo, the standard library, LLVM tools,components, target libraries, and declared linker toolchains in affected
action identities.
CAS.
components, or targets.
Not desired
stable,beta, or an unpinned nightly during a frozenbuild.
rustcor Cargo appears first onPATH.unavailable.
Acceptance
Rust installation.
target, and observed artifact identity.
linker invalidates every affected action and no unrelated action.
2. Separate dependency updates, acquisition, and frozen builds
Desired
Cargo.lockcreation or update as an explicit operation outside thefrozen build.
phase.
are available.
configuration byte-for-byte unchanged.
Not desired
cargo update.inside a frozen action.
Acceptance
acquisition completes.
new resolution.
Cargo.lock,Cargo.toml,.cargo/config.toml, or the toolchain declaration.3. Validate native Cargo inputs fail-closed
Desired
Cargo.tomlfiles,Cargo.lock, the toolchaindeclaration, relevant
.cargo/config.tomlfiles, patches, and sourcereplacements as semantic inputs.
Cargo.lockwhenever the selected Cargo workflow requires it.Cargo version.
workspace root, independent of the client process's current directory.
Not desired
[patch],[replace], alternate registry, sourcereplacement, or workspace inheritance behavior.
Acceptance
patches, alternate registries, Git sources, path sources, target-specific
dependencies, optional dependencies, and stale lockfiles.
macro executes.
without weakening fail-closed behavior.
4. Import Cargo's configured dependency graph
Desired
cargo metadata --format-version=1output.edges, resolved feature sets, workspace membership, source identity, and
target predicates.
triple, and Cargo resolver semantics.
affected-target analysis, provenance, and policy.
Not desired
configuration.
Acceptance
dependency-kind edges as the pinned Cargo toolchain.
dependency kind, or platform decision selected it.
identity; changing a semantic Cargo input does.
5. Fetch and verify crate sources into the CAS
Desired
Cargo.lock.checkout because the commit identifies source history but is not itself a
content checksum.
without recording credentials.
Not desired
Acceptance
analysis or compilation.
access.
6. Represent host and target crate variants explicitly
Desired
execution platform.
dependency kinds.
compiler, and relevant configuration in each variant's identity.
in one build graph.
Not desired
the execution host.
Acceptance
cross-compilation, multiple versions, and procedural-macro host execution.
caused a distinct variant.
7. Model each Cargo build script as explicit actions
Desired
build.rsprogram as a host action.environment, and platform configuration as inputs.
cargo:/cargo::directives asstructured outputs.
linksmetadata propagation and Cargo's required ordering.inputs only after correctness is demonstrated.
Not desired
environment.
Acceptance
rerun-if-changed,rerun-if-env-changed,emitted cfg values, linker directives, environment values, warnings,
metadata, native compilation, undeclared writes, and denied network access.
capability.
8. Treat procedural macros and native code as first-class toolchain actions
Desired
declared dependents.
rustcaction keys.pkg-config, and system-library inputsexplicitly when a crate builds or links native code.
environment, or system-library mappings.
during cross-compilation.
Not desired
PATH,/usr/lib, ormutable package-manager state.
build.rsnative behavior can always be inferred fromCargo.toml.Acceptance
pkg-config, static and dynamic linking, bindgen-like tools, and missingcross-toolchains.
visible in action provenance.
conformance tests.
9. Compile crates and link artifacts as fine-grained BSMR actions
Desired
rustcdirectly for libraries, binaries, tests, examples,benchmarks, and procedural macros after graph import.
features, profile, lint flags, codegen flags, target specification, linker,
and toolchain as inputs.
declared outputs as CAS objects.
binaries and tests.
Not desired
cargo buildas the permanent opaque compilation primitive.Acceptance
Cargo baseline.
10. Expose Rust targets and tools through ordinary BSMR interfaces
Desired
procedural macros, and documentation operations as distinct target kinds.
versions or dependency edges in BUCK files.
rustfmt, Clippy, and ordinary Rust testsusing components from the pinned toolchain.
ordinary actions.
it.
Not desired
Acceptance
dependency versions into BSMR configuration.
11. Derive cache identity, provenance, policy, and diagnostics from one graph
Desired
build-script outputs, toolchains, target and execution platforms, declared
environment, profiles, and BSMR rule implementation in action identity.
generated source, compilation, and linking.
the same graph BSMR executes.
did this rebuild?" in developer-facing terms.
Not desired
Acceptance
checksum, build script, native compiler, crate variants, and consuming target.
12. Prove Cargo parity and performance before cutover
Desired
and Rust toolchain.
compiler invocations, test behavior, diagnostics, and final artifacts.
rebuilds, offline replay, and remote-cache restoration.
old frozen compilation path at cutover.
Not desired
cargo build.Acceptance
dependencies, target-specific dependencies, build scripts,
links,procedural macros, native code, workspace inheritance, dev dependencies,
tests, examples, and cross-compilation.
size, affected closure, and correctness result.
Delivery milestones
Milestone 0: establish the pinned Cargo baseline
Run the exact, verified Rust toolchain in a sandbox with locked dependency
semantics. Capture package graph, features, outputs, failures, and performance.
Exit criterion: a tested baseline with no ambient compiler, mutable lockfile, or
floating toolchain.
Milestone 1: import and validate Cargo metadata
Add a read-only Cargo graph provider and canonical Rust variants. Use the graph
for queries, policy, provenance, and target analysis while Cargo still performs
compilation.
Exit criterion: differential graph tests pass for the initial supported Cargo
surface and importing the graph changes no build behavior.
Milestone 2: own source and toolchain acquisition
Fetch and verify Rust distributions, registry crates, and Git sources as
individual CAS objects.
Exit criterion: the baseline build runs fully offline and every external input
has immutable provenance.
Milestone 3: compile hook-free Rust targets natively
Invoke
rustcdirectly for crate graphs that do not require build scripts ornative fixups.
Exit criterion: supported libraries, binaries, and tests match Cargo across
clean machines and remote execution.
Milestone 4: actionize build scripts, procedural macros, and native code
Add structured build-script execution, host procedural macros, native
toolchains, and package fixups.
Exit criterion: representative generated and native crates reproduce with
network access denied and sound remote-cache hits.
Milestone 5: cut over
Make BSMR-native Rust execution the only frozen-build path for the supported
surface. Keep Cargo solely for explicit dependency updates and graph analysis.
Exit criterion: compatibility, security, performance, documentation, and
migration gates pass; no automatic fallback remains.
Initial scope
The first production-quality release should support:
Cargo.lockversion used by the pinned Cargo toolchainSupport must be defined by conformance fixtures, not by optimistically
accepting arbitrary Cargo behavior.
Non-goals
network during frozen builds.
paths, randomness, or undeclared state. Hermeticity is necessary but not
sufficient for reproducibility.
Alternatives considered
Shell out to Cargo forever
This is the correct baseline and may remain sufficient for small repositories.
It leaves compilation, build scripts, target closure, and many cache boundaries
inside one comparatively opaque process and limits BSMR's remote execution,
provenance, and affected-target analysis.
Reimplement Cargo resolution
This would make BSMR responsible for registry selection, semantic versions,
features, resolver evolution, workspaces, patches, target predicates, source
replacement, and years of compatibility behavior. It does not improve the
initial developer workflow and should occur only if a measured requirement
cannot be satisfied through Cargo's native contract.
Adopt
rules_rustdirectlyrules_rustis the strongest Bazel precedent and should inform BSMR's variantmodel, toolchain acquisition, build-script handling, and conformance suite. Its
Bazel repository, module-extension, provider, and Starlark APIs are not BSMR's
native architecture.
Adopt Reindeer as the permanent importer
Reindeer provides excellent prior art for translating Cargo metadata and
package fixups into a build graph. BSMR should learn from its contracts and may
use it in early experiments, but should not make its public graph dependent on
another build system's generated-file model.
Risks
fixtures against each supported toolchain.
identity proves variants equivalent.
and use exact package fixups.
and linkers.
failure behavior with Cargo.
and remote scheduling at large workspace scale.
Success criteria
This RFC is complete when a developer can:
it, and why it rebuilt.
behavior.
The project should optimize for those outcomes, not for ownership of Cargo
internals.
Prior art and references
resolver documentation
defines package, feature, dependency-kind, and target resolution.
cargo metadatais Cargo's structured graph interface for external tooling.
rules_rustCrate Universeimports Cargo dependencies into hermetic Bazel targets.
metadata into Buck targets and maintains package-specific fixups.
build-script reference
defines the directives and ordering that BSMR must preserve.
platform support table
defines target tiers; BSMR support remains narrower and fixture-defined.
Questions for design review
These questions should be settled before their respective implementation
milestones; they do not reopen the core Cargo/BSMR boundary.
toolchain matrix?
rust-toolchain.tomldirectly, or expose a generateddigest lock adjacent to it for official distribution artifacts?
cargo metadatafeature configurations are imported eagerly versus ondemand?
milestone?
All reactions