Rust
Rust is a general-purpose programming language with emphasis on performance, type safety, concurrency, and memory safety. It references build dependencies through cryptographic hashes recorded in dependency lockfiles. Programs written in Rust are often already reproducible by default, given the original build toolchain is version-matched and build paths are normalized, but this page documents some common issues you may encounter.
Dependency Lockfiles
Some distributions (like Arch Linux, Alpine and Homebrew) rely on the
upstream project to commit a dependency lockfile into the repository. In the
Rust ecosystem, this file is called Cargo.lock.
Usually this file is automatically respected by cargo build and cargo
build --release, but if it can’t satisfy the dependencies in the
corresponding Cargo.toml, cargo may re-resolve the dependency tree using
the latest semver-compatible versions available, which are going to be
different in the future.
To disable this behavior and instead insist on an error, use:
cargo build --locked
Not all distributions use the dependency lockfile, the Debian project is using their own dependency resolver and .buildinfo files.
Diffing the Build Directory
If you encounter an unreproducible binary, you can usually track down the
problem to a specific binary by running diffoscope on the target/
directory1.
cargo build --release && mv target target.1
cargo build --release && mv target target.2
diffoscope --html diff.html --exclude-directory-metadata=yes target.1/ target.2/
You can ignore the file .rustc_info.json as well as the files in
.fingerprint/2.
If you see a difference in e.g. target.1/release/libfoo.rlib that is a
strong indicator the problem is in the foo crate and you can focus your
investigation there.
Build Scripts (build.rs)
See stable outputs3.
Embedded Build Time
It is very uncommon for Rust programs to record the date and time the binary
has been compiled. If you still need the current time for some reason during
the compile, it is recommended to check if the SOURCE_DATE_EPOCH
environment variable is set, and only read the current system time if it is
not set.
The SOURCE_DATE_EPOCH environment variable has it’s own documentation page that also contains
Rust example code.
rust-embed
The popular crate rust-embed
contains macros to embed additional files into the compiled binary. This is
often used to bundle web assets. By default, it also records the filesystem
metadata of those files, including the modification time.
This can be turned off using the deterministic-timestamps feature.
[dependencies]
rust-embed = { version = "8.5.0", features = ["deterministic-timestamps"] }
Footnotes
Introduction
- Which problems do Reproducible Builds Solve?
- Definitions
- History
- Why reproducible builds?
- Making plans
- Academic publications
Achieve deterministic builds
Managing variance
- Variations in the build environment
- SOURCE_DATE_EPOCH
- Deterministic build systems
- Volatile inputs can disappear
- Stable order for inputs
- Stripping of unreproducible information
- Value initialization
- Version information
- Timestamps
- Timezones
- Locales
- Archive metadata
- Stable order for outputs
- Randomness
- Build path
- System images
- Rust
- JVM
- Helm
Define a build environment
- What's in a build environment?
- Recording the build environment
- Definition strategies
- Proprietary operating systems