# shadow-rs bakes build and Git facts into your Rust binary

> shadow-rs is a Rust crate that captures Cargo, Git and toolchain information at build time and exposes it to the running program as constants or function calls. It suits CLI tools and services that need to report their own version, commit and build environment, and it has a build-pattern switch for controlling when the build script re-runs.

**baoyachi/shadow-rs** — A build-time information stored in your rust project.(binary,lib,cdylib,dylib,wasm)

- Repository: https://github.com/baoyachi/shadow-rs
- Website: https://docs.rs/shadow-rs
- Stars: 701 · Forks: 54
- Language: Rust
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/baoyachi-shadow-rs

## Build-time facts that a running program can read

The idea is narrow and useful. shadow-rs lets you access properties of the build process and environment at runtime, and the list in the README includes Cargo.toml information such as the project version, dependency information, Git information such as the commit that produced the build artifact, the version of the Rust toolchain used in compilation, and the build variant such as debug or release.

The framing in the README is that you can use the crate to programmatically check where a binary came from and how it was built. That is the use case the author cares about: a language server, for instance, can report the commit it was built from without a separate file being shipped alongside the executable. The README goes further and strongly recommends using shadow-rs on an LSP service developed in Rust, which tells you the intended user is someone who ships a binary and needs to identify it later.

The supported target types are listed in the crate description: binary, lib, cdylib, dylib and wasm. Integration into wasm and no_std is also supported, with separate example directories for each case in the repository.

## Two ways in: a constant module or a function call

The crate offers both a macro that produces constants and a set of builtin functions, and the difference matters for how the values are computed.

The macro form creates a module with the name you give it, and the constants in it are read from that module:

```rust
use shadow_rs::shadow;

shadow!(build);
```

The builtin function form is called at runtime instead, and the README's example prints several of them side by side:

```rust
    println!("debug:{}", shadow_rs::is_debug()); // check if this is a debug build. e.g 'true/false'
    println!("branch:{}", shadow_rs::branch()); // get current project branch. e.g 'master/develop'
    println!("tag:{}", shadow_rs::tag()); // get current project tag. e.g 'v1.3.5'
    println!("git_clean:{}", shadow_rs::git_clean()); // get current project clean. e.g 'true/false'
```

The constant form is what you want for values that never change at runtime, such as the commit hash or the package version, since they become plain compile-time data. The builtin function form is more flexible but does the work when called, which matters for a program that wants to check the build variant at runtime rather than bake in a constant.

There is also a hook mechanism. The example_shadow_hook directory demonstrates how custom hooks can be used to add extra information to the output, and the example_shadow directory shows a simple demonstration of supplying build-time information at runtime, while examples/builtin_fn.rs demonstrates the builtin functions on their own.

## The three-step setup in Cargo.toml, build.rs and the macro

Setting the crate up takes three edits, and the README lays them out in order. First the manifest, where the crate is added twice, once as a normal dependency and once as a build dependency, and where the build script is declared:

```toml
[package]
build = "build.rs"

[dependencies]
shadow-rs = { version = "{latest version}", default-features = false }

[build-dependencies]
shadow-rs = "{latest version}"
```

The `build = "build.rs"` line is described as an optional addition, because Cargo points at build.rs by default, and it is recommended to leave it that way. If your build script has another name, such as gen.rs, you set it explicitly.

Second, the build script itself, in the root of the project next to Cargo.toml:

```rust
fn main() {
  ShadowBuilder::builder().build().unwrap();
}
```

Third, the macro invocation in your main file, usually main.rs or lib.rs, which is the snippet shown earlier. If you want to exclude some build constants, the README says you can use new_deny instead of new.

One dependency detail is worth noting. The normal dependency uses default-features = false, and the crate's own feature list explains why: the default set is git2 plus build, while std and no_std are separate features, and build pulls in jiff, is_debug/std and std. Turning off default features for the runtime side is what keeps a no_std build possible.

## BuildPattern decides when the build script runs again

The BuildPattern enum defines strategies for triggering package rebuilding, and the default mode is Lazy.

In Lazy mode, if the current Rust environment is set to debug, the rebuild package will not run every time the build script is triggered. If the environment is set to release, it behaves the same as the RealTime mode. RealTime always triggers rebuilding a package upon any change, regardless of whether the environment is debug or release. Custom is described as an enhanced version of RealTime, allowing user-defined conditions to trigger a rebuild.

This is the setting that will bite you first if you are not careful. The default means that in a debug build, a stale set of constants can survive a git commit, because the build script was not re-run to refresh them. In release builds the behaviour changes and the values do get refreshed. For a tool that reports its own commit hash, a stale hash in a debug binary is exactly the kind of small lie that costs an afternoon, so setting RealTime while developing is the safer default for most people.

The Cargo.toml also lists the document-features crate as an optional dependency, which is how the feature set is turned into documentation for the docs.rs build, and the manifest sets all-features = true for the docs.rs metadata.

## Build time breaks reproducibility unless SOURCE_DATE_EPOCH is set

One caveat is stated directly. The tool includes the current time in the binary, which would normally make it non-reproducible. However, it respects the SOURCE_DATE_EPOCH variable, and if that is set to a Unix timestamp it overrides the value of build time.

That is the standard escape hatch for reproducible builds, and it is worth wiring up early if you care, because changing the timestamp behaviour after the fact changes the bytes of every artifact. The crate exposes a build::BUILD_TIME constant in the example output, so the value is visible in what you ship and can be checked.

The other reproducibility angle is the dependency tree, since the crate exposes a CARGO_TREE constant, described in the example as the output of the cargo tree command, and a CARGO_LOCK-style picture of what went into the build. The repository topics list reproducible-build and reproducible-builds, and the constant set includes CARGO_MANIFEST_DIR, the manifest directory path.

## Optional backends, MIT licensing and a maintained-but-quiet release line

The dependency set is small and the heavier pieces are optional. The manifest lists is_debug, const_format and an optional jiff for time handling, then four optional crates: git2, described in a comment as the backend to use libgit2 for git operations, document-features, cargo_metadata and serde_json. The metadata feature turns on cargo_metadata and serde_json, and the default feature set is git2 plus build.

That structure lets you choose how the Git information is obtained. With git2 in the default features, the crate links libgit2 and reads the repository itself. With git2 disabled, the metadata and cargo_metadata route is available instead, which is the path for environments where shelling out or linking libgit2 is awkward, and it is the combination the no_std example needs.

On licensing, the two sources disagree slightly and you should read the file. The manifest declares license = "MIT AND Apache-2.0", while the licence field GitHub shows for the repository is MIT, and the tree contains a LICENSE file. The releases are frequent: v1.7.0 on 2026-01-27, v1.7.1 on 2026-03-09 and v2.0.0 on 2026-04-23, with the last push to master on 2026-05-19. The crate is published on crates.io with documentation on docs.rs, and the manifest declares edition 2021.

## Conclusion

Adopt shadow-rs when your Rust program should be able to report where its own artifact came from, since the crate turns Cargo metadata, the commit hash and the toolchain version into runtime constants with a build script and one macro. Skip it if you already generate version constants by hand with an env! based build script, because the overlap is real, and do not enable it in a build where the git directory is absent without checking what it records. Before you depend on it, read the BuildPattern setting, because the default Lazy mode skips the rebuild in debug builds, set SOURCE_DATE_EPOCH if you need reproducible output since the crate embeds the current time, and note that the crate manifest declares the licence as MIT AND Apache-2.0 while the licence field GitHub shows for the repository is MIT alone, so check the LICENSE file before redistributing.

## FAQ

### What does shadow-rs add to a Rust binary?

It exposes build-time information at runtime, including Cargo.toml details such as the project version, dependency information, Git information such as the commit that produced the artifact, the Rust toolchain version and the build variant such as debug or release.

### How do I set up shadow-rs in a Rust project?

Add shadow-rs to both dependencies and build-dependencies in Cargo.toml, add a build.rs file in the project root that calls ShadowBuilder::builder().build().unwrap(), and invoke the shadow! macro in your main file. The crate also documents a no_std variant that adds the no_std feature to the build dependency.

### What is the difference between Lazy and RealTime in shadow-rs BuildPattern?

Lazy is the default and, in a debug environment, does not re-run the rebuild package every time the build script is triggered, while in release it behaves like RealTime. RealTime always triggers a rebuild on any change, and Custom adds user-defined conditions on top of that.

### How do I keep shadow-rs builds reproducible?

The crate embeds the current build time, which would normally make the binary non-reproducible. Setting the SOURCE_DATE_EPOCH environment variable to a Unix timestamp overrides the build time value.

### Which licence applies to shadow-rs?

The Cargo.toml manifest declares the licence as MIT AND Apache-2.0, while the repository shows MIT in the licence field and includes a LICENSE file. Read the LICENSE file in the checkout to confirm which terms apply.

## Sources

- [baoyachi/shadow-rs on GitHub](https://github.com/baoyachi/shadow-rs)
- [License: MIT](https://github.com/baoyachi/shadow-rs/blob/master/LICENSE)
- [Project website](https://docs.rs/shadow-rs)
- [README](https://github.com/baoyachi/shadow-rs/blob/master/README.md)
- [Releases](https://github.com/baoyachi/shadow-rs/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/baoyachi-shadow-rs
