1use std::path::PathBuf;
2use std::process::Command;
3use std::str;
4
5#[macro_export]
8macro_rules! get_version_info {
9 () => {{
10 let major = std::env!("CARGO_PKG_VERSION_MAJOR").parse::<u8>().unwrap();
11 let minor = std::env!("CARGO_PKG_VERSION_MINOR").parse::<u8>().unwrap();
12 let patch = std::env!("CARGO_PKG_VERSION_PATCH").parse::<u16>().unwrap();
13 let crate_name = String::from(std::env!("CARGO_PKG_NAME"));
14
15 let host_compiler = std::option_env!("RUSTC_RELEASE_CHANNEL").map(str::to_string);
16 let commit_hash = std::option_env!("GIT_HASH").map(str::to_string);
17 let commit_date = std::option_env!("COMMIT_DATE").map(str::to_string);
18
19 $crate::VersionInfo {
20 major,
21 minor,
22 patch,
23 host_compiler,
24 commit_hash,
25 commit_date,
26 crate_name,
27 }
28 }};
29}
30
31#[macro_export]
34macro_rules! setup_version_info {
35 () => {{
36 let _ = $crate::rerun_if_git_changes();
37 println!(
38 "cargo:rustc-env=GIT_HASH={}",
39 $crate::get_commit_hash().unwrap_or_default()
40 );
41 println!(
42 "cargo:rustc-env=COMMIT_DATE={}",
43 $crate::get_commit_date().unwrap_or_default()
44 );
45 let compiler_version = $crate::get_compiler_version();
46 println!(
47 "cargo:rustc-env=RUSTC_RELEASE_CHANNEL={}",
48 $crate::get_channel(compiler_version)
49 );
50 }};
51}
52
53pub struct VersionInfo {
55 pub major: u8,
56 pub minor: u8,
57 pub patch: u16,
58 pub host_compiler: Option<String>,
59 pub commit_hash: Option<String>,
60 pub commit_date: Option<String>,
61 pub crate_name: String,
62}
63
64impl std::fmt::Display for VersionInfo {
65 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66 let hash = self.commit_hash.clone().unwrap_or_default();
67 let hash_trimmed = hash.trim();
68
69 let date = self.commit_date.clone().unwrap_or_default();
70 let date_trimmed = date.trim();
71
72 if (hash_trimmed.len() + date_trimmed.len()) > 0 {
73 write!(
74 f,
75 "{} {}.{}.{} ({hash_trimmed} {date_trimmed})",
76 self.crate_name, self.major, self.minor, self.patch,
77 )?;
78 } else {
79 write!(f, "{} {}.{}.{}", self.crate_name, self.major, self.minor, self.patch)?;
80 }
81
82 Ok(())
83 }
84}
85
86impl std::fmt::Debug for VersionInfo {
87 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88 write!(
89 f,
90 "VersionInfo {{ crate_name: \"{}\", major: {}, minor: {}, patch: {}",
91 self.crate_name, self.major, self.minor, self.patch,
92 )?;
93 if let Some(ref commit_hash) = self.commit_hash {
94 write!(f, ", commit_hash: \"{}\"", commit_hash.trim(),)?;
95 }
96 if let Some(ref commit_date) = self.commit_date {
97 write!(f, ", commit_date: \"{}\"", commit_date.trim())?;
98 }
99 if let Some(ref host_compiler) = self.host_compiler {
100 write!(f, ", host_compiler: \"{}\"", host_compiler.trim())?;
101 }
102
103 write!(f, " }}")?;
104
105 Ok(())
106 }
107}
108
109#[must_use]
110fn get_output(cmd: &str, args: &[&str]) -> Option<String> {
111 let output = Command::new(cmd).args(args).output().ok()?;
112 let mut stdout = output.status.success().then_some(output.stdout)?;
113 while stdout.last().copied() == Some(b'\n') {
115 stdout.pop();
116 }
117 String::from_utf8(stdout).ok()
118}
119
120#[must_use]
121pub fn rerun_if_git_changes() -> Option<()> {
122 let git_head_file = PathBuf::from(get_output("git", &["rev-parse", "--git-path", "HEAD"])?);
128 if git_head_file.exists() {
129 println!("cargo::rerun-if-changed={}", git_head_file.display());
130 }
131
132 let git_head_ref = get_output("git", &["symbolic-ref", "-q", "HEAD"])?;
135 let git_head_ref_file = PathBuf::from(get_output("git", &["rev-parse", "--git-path", &git_head_ref])?);
137 if git_head_ref_file.exists() {
140 println!("cargo::rerun-if-changed={}", git_head_ref_file.display());
141 }
142
143 Some(())
144}
145
146#[must_use]
147pub fn get_commit_hash() -> Option<String> {
148 let mut stdout = get_output("git", &["rev-parse", "HEAD"])?;
149 stdout.truncate(10);
150 Some(stdout)
151}
152
153#[must_use]
154pub fn get_commit_date() -> Option<String> {
155 get_output("git", &["log", "-1", "--date=short", "--pretty=format:%cd"])
156}
157
158#[must_use]
159pub fn get_compiler_version() -> Option<String> {
160 get_output("rustc", &["-V"])
161}
162
163#[must_use]
164pub fn get_channel(compiler_version: Option<String>) -> String {
165 if let Ok(channel) = std::env::var("CFG_RELEASE_CHANNEL") {
166 return channel;
167 }
168
169 if let Some(rustc_output) = compiler_version {
171 if rustc_output.contains("beta") {
172 return String::from("beta");
173 } else if rustc_output.contains("nightly") {
174 return String::from("nightly");
175 }
176 }
177
178 String::from("stable")
180}
181
182#[cfg(test)]
183mod test {
184 use super::*;
185
186 #[test]
187 fn test_struct_local() {
188 let vi = get_version_info!();
189 assert_eq!(vi.major, 0);
190 assert_eq!(vi.minor, 4);
191 assert_eq!(vi.patch, 2);
192 assert_eq!(vi.crate_name, "rustc_tools_util");
193 assert!(vi.commit_hash.is_none());
195 assert!(vi.commit_date.is_none());
196
197 assert!(vi.host_compiler.is_none());
198 }
199
200 #[test]
201 fn test_display_local() {
202 let vi = get_version_info!();
203 assert_eq!(vi.to_string(), "rustc_tools_util 0.4.2");
204 }
205
206 #[test]
207 fn test_debug_local() {
208 let vi = get_version_info!();
209 let s = format!("{vi:?}");
210 assert_eq!(
211 s,
212 "VersionInfo { crate_name: \"rustc_tools_util\", major: 0, minor: 4, patch: 2 }"
213 );
214 }
215}