I host a Rust project in git repository and I want to make it print the version on some command. How can I include the version into the program? I thought that the build script
There is an easy way to do this without the need for any build.rs logic or custom crates. You simply pass the current git hash directly to the build command as an environment variable, and read it in your program with option_env!("PROJECT_VERSION")
, with a env!("CARGO_PKG_VERSION")
fallback. These macros read environment variables during build time.
Examples follow that builds this minimal src/main.rs:
fn main() {
let version = option_env!("PROJECT_VERSION").unwrap_or(env!("CARGO_PKG_VERSION"));
println!("This binary was built from {}", version);
}
When you build the program and want an accurate git hash, e.g. in your CI/CD configuration, you prefix the cargo command with PROJECT_VERSION=$(git rev-parse --short HEAD)
. Like this for cargo run
(but also works for cargo build
and others):
% PROJECT_VERSION=$(git rev-parse --short HEAD) cargo run
This binary was built from 6ca63b2
Personally I prefer $(git describe)
over $(git rev-parse)
since the former is more descriptive (using cargo build
as example now just for variation):
% PROJECT_VERSION=$(git describe) cargo build
% ./target/debug/your-program
This binary was built from v0.3.0-15-g6ca63b2 # or just 'v0.3.0' if current commit is tagged with that
Since you have a CARGO_PKG_VERSION
fallback, your IDE can still build the files on-the-fly for you. Likewise, for development, you can skip passing PROJECT_VERSION
. In that case, the version from your Cargo.toml
will be used:
% cargo run
This binary was built from 0.3.0
I can only think about writing data to some file, but I think this is overkill for this case.
That's unfortunate, because that is the only way of doing it. Environment variables can't work because changes to the environment can't "leak" into other, non-child processes.
For simpler things, you can instruct Cargo to define conditional compilation flags, but those aren't powerful enough to communicate a string [1].
The details of generating code from a build script is detailed in the code generation section of the Cargo documentation.
[1]: I mean, unless you feel like breaking the hash into 160 config flags and then re-assembling them in the source being compiled, but that's even more overkill.
Since Rust 1.19 (cargo 0.20.0), thanks to https://github.com/rust-lang/cargo/pull/3929, you can now define a compile-time environment variable (env!(…)
) for rustc
and rustdoc
via:
println!("cargo:rustc-env=KEY=value");
So OP's program can be written as:
// build.rs
use std::process::Command;
fn main() {
// note: add error checking yourself.
let output = Command::new("git").args(&["rev-parse", "HEAD"]).output().unwrap();
let git_hash = String::from_utf8(output.stdout).unwrap();
println!("cargo:rustc-env=GIT_HASH={}", git_hash);
}
// main.rs
fn main() {
println!("{}", env!("GIT_HASH"));
// output something like:
// 7480b50f3c75eeed88323ec6a718d7baac76290d
}
Note that you still cannot use this if you still want to support 1.18 or below.
There is already an existing crate vergen that can calculate the git commit in the build script. As @DK's answer described, the build script cannot modify environment variable before Rust 1.19, so vergen
still works by writing the result into OUT_DIR (i.e. vergen
still won't solve OP's question, but it should be easier to use).
Usage:
# Cargo.toml
...
[build-dependencies]
vergen = "0.1"
// build.rs
extern crate vergen;
use vergen::*;
fn main() {
vergen(SHORT_SHA | COMMIT_DATE).unwrap();
}
mod version {
include!(concat!(env!("OUT_DIR"), "/version.rs"));
}
fn main() {
println!("commit: {} {}", version::commit_date(), version::short_sha());
// output something like:
// commit: 2017-05-03 a29c7e5
}
https://github.com/baoyachi/shadow-rs
shadow-rs
allows you to recall properties of the build process and environment at runtime, including:
Cargo.toml
project versiondebug
or release
You can use this tool to check in production exactly where a binary came from and how it was built.
shadow-rs
might be used to provide build-time information at run-time.Uh. (I do not recommend this in production or in testing or in public code or even in private code but I mean, it kinda does the job?)
const REF: &str = include_str!("../.git/HEAD");
const REF_MASTER: &str = include_str!("../.git/refs/heads/master");
// (elsewhere)
if REF == "ref: refs/heads/master" { REF_MASTER } else { REF }
(do not use this unless you're making some sort of codegolf. note that this is 100% untested.)