Given that I’m making reasonably frequent changes to a Rust library at work, I wanted a way to check the build version without having to keep track of the Cargo.toml crate version and in a way that was reliably accessible to any caller of my library. Conceptually, it would be something like:

const BUILT_ON : &str = compile_time!(); // value gets baked into the application
fn main() {
    println!("This binary was compiled on {}", BUILT_ON);
}

As I discovered, it’s actually quite easy to do with build scripts.

First, we’ll start with the build script itself. This is a Rust package (a binary with a main function) that gets compiled and executed before our primary crate/package. We’ll use the OUT_DIR environment variable to give us the directory in which our build is taking place – the build script will create a small text file, and the main project will read this at compile time. To get the system time, use chrono::Local::now():

use std::{env, io::Write, fs};

fn main() {
    let outdir = env::var("OUT_DIR").unwrap();
    let outfile = format!("{}/timestamp.txt", outdir);

    let mut fh = fs::File::create(&outfile).unwrap();
    write!(fh, r#""{}""#, chrono::Local::now()).ok();
}

This script must use env::var and not env!. We construct the path into which we’ll write the value, arbitrarily choosing timestamp.txt as the filename. The main project will include! that file, so we enquote the timestamp.

build.rs is the default filename for build scripts; if you choose to use something else, you have to specify the build value in your Cargo.toml in the [package] section. Because the build script depends on an external crate, we also specify the required build dependency:

[package]
build = "my-build.rs"
# ^ or just use 'build.rs' with or without specifying it here ^

[build-dependencies]
chrono = "0.4.19"

After building out project, we’ll find timestamp.txt created for us:

adam@wsl:~/rust/build-script$ find . | grep timestamp.txt
./target/debug/build/build-script-fa7510033e271b43/out/timestamp.txt
adam@wsl:~/rust/build-script$ cat ./target/debug/build/build-script-fa7510033e271b43/out/timestamp.txt
"2021-02-12 23:22:20.778846100 -08:00"adam@wsl:~/rust/build-script$

(This file lacks a trailing newline.)

Now all that remains is to read this file within the main project. Here, we can use env! to get the OUT_DIR. Concatening that with the desired filename and including those contents into some place that expects a &str will work fine:

const BUILD_TIME : &str = include!(concat!(env!("OUT_DIR"), "/timestamp.txt"));

fn main() {
    println!("This package was compiled at {}", BUILD_TIME);
}

Then run it:

adam@wsl:~/rust/build-script$ cargo run
   Compiling build-script v0.1.0 (/home/adam/rust/build-script)
    Finished dev [unoptimized + debuginfo] target(s) in 1.03s
     Running `target/debug/build-script`
This package was compiled at 2021-02-12 23:22:20.778846100 -08:00

adam@wsl:~/rust/build-script$ touch src/main.rs

adam@wsl:~/rust/build-script$ cargo run
   Compiling build-script v0.1.0 (/home/adam/rust/build-script)
    Finished dev [unoptimized + debuginfo] target(s) in 0.44s
     Running `target/debug/build-script`
This package was compiled at 2021-02-12 23:25:38.701110700 -08:00