Repository information in version

This commit is contained in:
John Doty 2024-08-07 12:14:04 -07:00
parent 8a60f89110
commit b8fe678ff0
2 changed files with 134 additions and 6 deletions

108
build.rs Normal file
View file

@ -0,0 +1,108 @@
use std::io::Write;
use std::path::{absolute, Path, PathBuf};
/// Fetch the contents of the given file, and also tell cargo that we looked
/// in there.
fn file_contents<P: AsRef<Path>>(path: P) -> String {
let path =
absolute(path.as_ref()).expect("Unable to make the path absolute");
let mut stdout = std::io::stdout();
stdout
.write_all(b"cargo::rerun-if-changed=")
.expect("Unable to write stdout");
stdout
.write_all(path.as_os_str().as_encoded_bytes())
.expect("Unable to write path to stdout");
stdout
.write_all(b"\n")
.expect("Unable to write newline to stdout");
std::fs::read_to_string(path).expect("Unable to read file")
}
/// Emit the current git commit.
fn emit_git_commit() {
// Fetch the current commit from the head. We do it this way instead of
// asking `git rev-parse` to do it for us because we want to reliably
// tell cargo which files it should monitor for changes.
let head = file_contents("./.git/HEAD");
let rev = if let Some(r) = head.strip_prefix("ref: ") {
let mut ref_path = PathBuf::from("./.git/");
ref_path.push(r.trim());
file_contents(ref_path)
} else {
head
};
// But *now* we ask git rev-parse to make this into a short hash (a) to
// make sure we got it right and (b) because git knows how to quickly
// determine how much of a commit is required to be unique. We don't need
// to tell cargo anything here, no file that git consults will be
// mutable.
let output = std::process::Command::new("git")
.arg("rev-parse")
.arg("--short")
.arg(rev.trim())
.output()
.expect("could not spawn `git` to get the hash");
if !output.status.success() {
let stderr = std::str::from_utf8(&output.stderr)
.expect("git failed and stderr was not utf8");
eprintln!("`git rev-parse --short HEAD` failed, stderr: {stderr}");
panic!("`git rev-parse --short HEAD` failed");
}
let rev =
std::str::from_utf8(&output.stdout).expect("git did not output utf8");
println!("cargo::rustc-env=REPO_REV={rev}");
}
fn emit_git_dirty() {
// Here is the way to see if anything is up with the repository: run `git
// status --porcelain=v1`. The status output in the v1 porcelain format
// has one line for every file that's modified in some way: staged,
// changed but unstaged, untracked, you name it. Files in the working
// tree that are up to date with the repository are not emitted. This is
// exactly what we want.
//
// (Yes, I want to track untracked files, because they can mess with the
// build too. The only good build is a clean build!)
let output = std::process::Command::new("git")
.arg("status")
.arg("-z")
.arg("--porcelain=v1")
.output()
.expect("could not spawn `git` to get repository status");
if !output.status.success() {
let stderr = std::str::from_utf8(&output.stderr)
.expect("git failed and stderr was not utf8");
eprintln!("`git status` failed, stderr: {stderr}");
panic!("`git status` failed");
}
let output =
std::str::from_utf8(&output.stdout).expect("git did not output utf8");
// If there *was* any output, parse it and tell cargo to re-run if any of
// these files changed. (Maybe they get reverted! Then the repo status
// will change.)
for line in output.lines() {
let fields: Vec<_> = line.split('\x00').collect();
let parts: Vec<_> = fields[0].split(' ').collect();
let path = parts[1];
println!("cargo::rerun-if-changed={path}");
}
// Emit the repository status.
let dirty = if output.trim().is_empty() {
""
} else {
" *dirty*"
};
println!("cargo::rustc-env=REPO_DIRTY={dirty}");
}
fn main() {
emit_git_commit();
emit_git_dirty();
}