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