Skip to main content

lyquor_cli/
script.rs

1use std::env;
2use std::path::{Path, PathBuf};
3use std::process::Command;
4
5/// Emit Cargo build-script metadata for the Lyquor version and git revision.
6pub fn lyquor_version_build_script() {
7    println!("cargo:rerun-if-changed=build.rs");
8
9    let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is not set"));
10    emit_git_rerun_hints(&manifest_dir);
11
12    let version = env::var("CARGO_PKG_VERSION").expect("CARGO_PKG_VERSION is not set");
13    let git_sha = git_output(&manifest_dir, ["rev-parse", "HEAD"]).unwrap_or_else(|| "unknown".to_string());
14    let git_sha_short =
15        git_output(&manifest_dir, ["rev-parse", "--short=7", "HEAD"]).unwrap_or_else(|| "unknown".to_string());
16    let git_dirty = git_is_dirty(&manifest_dir);
17    let build_version = if git_sha_short == "unknown" {
18        version
19    } else if git_dirty {
20        format!("{version}+{git_sha_short}.dirty")
21    } else {
22        format!("{version}+{git_sha_short}")
23    };
24
25    println!("cargo:rustc-env=LYQUOR_BUILD_VERSION={build_version}");
26    println!("cargo:rustc-env=LYQUOR_GIT_COMMIT_SHA={git_sha}");
27    println!("cargo:rustc-env=LYQUOR_GIT_COMMIT_SHA_SHORT={git_sha_short}");
28    println!("cargo:rustc-env=LYQUOR_GIT_DIRTY={}", if git_dirty { "1" } else { "0" });
29}
30
31fn emit_git_rerun_hints(manifest_dir: &Path) {
32    let Some(repo_root) = git_path(manifest_dir, ["rev-parse", "--show-toplevel"]) else {
33        return;
34    };
35    emit_tracked_file_rerun_hints(&repo_root);
36
37    let Some(git_dir) = git_path(manifest_dir, ["rev-parse", "--git-dir"]) else {
38        return;
39    };
40    emit_rerun_if_exists(&git_dir.join("HEAD"));
41    emit_rerun_if_exists(&git_dir.join("index"));
42
43    let Some(common_dir) = git_path(manifest_dir, ["rev-parse", "--git-common-dir"]) else {
44        return;
45    };
46    emit_rerun_if_exists(&common_dir.join("packed-refs"));
47
48    if let Some(head_ref) = git_output(manifest_dir, ["symbolic-ref", "-q", "HEAD"]) {
49        emit_rerun_if_exists(&common_dir.join(head_ref));
50    }
51}
52
53fn emit_tracked_file_rerun_hints(repo_root: &Path) {
54    let Some(paths) = git_outputs(repo_root, ["ls-files", "-z"], b'\0') else {
55        return;
56    };
57
58    for path in paths {
59        emit_rerun_if_exists(&repo_root.join(path));
60    }
61}
62
63fn emit_rerun_if_exists(path: &Path) {
64    if path.exists() {
65        println!("cargo:rerun-if-changed={}", path.display());
66    }
67}
68
69fn git_path<const N: usize>(manifest_dir: &Path, args: [&str; N]) -> Option<PathBuf> {
70    let path = git_output(manifest_dir, args)?;
71    let path = PathBuf::from(path);
72    if path.is_absolute() {
73        Some(path)
74    } else {
75        Some(manifest_dir.join(path))
76    }
77}
78
79fn git_output<const N: usize>(manifest_dir: &Path, args: [&str; N]) -> Option<String> {
80    let value = git_stdout(manifest_dir, args)?;
81    let value = value.trim();
82    if value.is_empty() {
83        return None;
84    }
85
86    Some(value.to_string())
87}
88
89fn git_outputs<const N: usize>(manifest_dir: &Path, args: [&str; N], delimiter: u8) -> Option<Vec<String>> {
90    let stdout = git_stdout(manifest_dir, args)?;
91    Some(
92        stdout
93            .split(char::from(delimiter))
94            .filter(|value| !value.is_empty())
95            .map(ToOwned::to_owned)
96            .collect(),
97    )
98}
99
100fn git_is_dirty(manifest_dir: &Path) -> bool {
101    git_output(manifest_dir, ["status", "--porcelain", "--untracked-files=no"]).is_some()
102}
103
104fn git_stdout<const N: usize>(manifest_dir: &Path, args: [&str; N]) -> Option<String> {
105    let output = Command::new("git").current_dir(manifest_dir).args(args).output().ok()?;
106    if !output.status.success() {
107        return None;
108    }
109
110    String::from_utf8(output.stdout).ok()
111}