A recent problem was to include the build version of a rust binary, so that $ program --version
answers with the appropriate version number.
We’ll look at various solutions.
Here is a snippet of such a program:
// bin/empty.rs
extern crate clap;
use clap::App;
fn main() {
App::new("")
.version("1.0")
.get_matches();
}
in Cargo.toml
, you’ll need to add clap = "*"
as a dependency. It’s a popular crate that does Command Line Argument Parsing, you’ll encounter it often if you write command line programs.
If you run this example, it’ll output our hardcoded version:
$ cargo run --bin empty -- --version
1.0
It’s not very useful for now since it’s static, but that’s a start.
A note about the command syntax:
bin
directory) called empty : cargo run --bin empty
--
--version
It’s very similar as running cargo build && ./target/debug/empty --version
.
Let’s improve this program. A first way to do is it to use cargo’s built-in variables:
// bin/cargopkg.rs
fn main() {
App::new("")
.version(env!("CARGO_PKG_VERSION"))
.get_matches();
}
If we run this program, we have a version:
$ cargo run --bin cargopkg -- --version
0.1.0
Where does this 0.1.0
come from ? std::env!
is a macro that fetches the value of an environment variable during compile time. CARGO_PKG_VERSION
gives us the version number set in Cargo.toml
.
A few other environment variables are available.
If you go in this direction, you’ll need to update your version every time you make a release. There are pros and cons to this approach.
Another approach is to use a build script. There are many parts so it is a bit hard to grasp at first:
build.rs
, and specify it’s a build script in Cargo.toml
One of the things we can tell Cargo is “let’s add a new environment variable, with the hash of the latest commit”. Let’s do that.
# Cargo.toml
[package]
# …
build = "build.rs"
Cargo only supports one build script per project at the moment, so everything we do will be available to all the binary (should they care to use it). Here is our build.rs
script:
// build.rs
use std::process::Command;
fn main() {
// taken from https://stackoverflow.com/questions/43753491/include-git-commit-hash-as-string-into-rust-program
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);
}
So we write a program that fetches the last commit hash via git rev-parse HEAD
. git rev-parse provides the SHA1 of a revision.
Now we can make use of the GIT_HASH
environment variable in our main program:
// bin/gitcommit.rs
extern crate clap;
use clap::App;
fn main() {
App::new("")
.version(env!("GIT_HASH"))
.get_matches();
}
Now, lets run this program:
$ cargo run --bin gitcommit -- --version
909d20fe5bf4cfc3ae784c78401455b42bdf02d2
This is the approach we went with for ds_proxy
Well, parsing the git output ourselves in order to feed it to a special cargo string can be a bit tedious. As often in the rust ecosystem, there is a crate for that. vergen exposes a bunch of environment variables, like the build date, the sha1 of the commit or the target architecture.