1use std::env;
2use std::path::{Path, PathBuf};
3use std::process::Command;
4
5pub 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}