1use std::path::{Path, PathBuf};
2use std::process::{Command, Stdio};
3
4use anyhow::{Context, Result, anyhow};
5
6pub enum DescribeFormat {
8 Reach,
10 CommitOnly,
12}
13
14pub fn git_describe(describe_type: DescribeFormat) -> Result<String> {
20 let manifest_dir = manifest_dir()?;
21 let git_dir = git_dir(&manifest_dir)?;
22 register_git_paths(&git_dir);
23
24 match describe_type {
25 DescribeFormat::Reach => run_git(&manifest_dir, [
26 "describe",
27 "--always",
28 "--dirty=-modified",
29 "--tags",
30 "--match=v[0-9]*",
31 ]),
32 DescribeFormat::CommitOnly => run_git(&manifest_dir, ["rev-parse", "HEAD"]),
33 }
34}
35
36fn manifest_dir() -> Result<PathBuf> {
37 std::env::var_os("CARGO_MANIFEST_DIR")
38 .map(PathBuf::from)
39 .ok_or_else(|| anyhow!("missing 'CARGO_MANIFEST_DIR' environment variable"))
40}
41
42fn git_dir(manifest_dir: &Path) -> Result<PathBuf> {
43 let raw = run_git(manifest_dir, ["rev-parse", "--git-dir"])?;
44 let path = PathBuf::from(raw.trim());
45 Ok(if path.is_absolute() {
46 path
47 } else {
48 manifest_dir.join(path)
49 })
50}
51
52fn register_git_paths(git_dir: &Path) {
53 for subpath in ["HEAD", "logs/HEAD", "index"] {
54 if let Ok(path) = git_dir.join(subpath).canonicalize() {
55 println!("cargo:rerun-if-changed={}", path.display());
56 }
57 }
58}
59
60fn run_git(cwd: &Path, args: impl IntoIterator<Item = &'static str>) -> Result<String> {
61 let args: Vec<&'static str> = args.into_iter().collect();
62 run_command(cwd, "git", &args)
63}
64
65pub fn run_command(cwd: &Path, program: &str, args: &[&str]) -> Result<String> {
66 println!("cargo:rerun-if-env-changed=PATH");
67 let output = Command::new(program)
68 .args(args)
69 .current_dir(cwd)
70 .stderr(Stdio::inherit())
71 .output()
72 .with_context(|| format!("{program} {:?} failed", args))?;
73
74 if output.status.success() {
75 let mut text = String::from_utf8(output.stdout)
76 .with_context(|| format!("{program} {:?} printed invalid utf-8", args))?;
77 while matches!(text.as_bytes().last(), Some(b'\n' | b'\r')) {
78 text.pop();
79 }
80 Ok(text)
81 } else if let Some(code) = output.status.code() {
82 Err(anyhow!("{program} {:?}: exited with {code}", args))
83 } else {
84 Err(anyhow!("{program} {:?}: terminated by signal", args))
85 }
86}