1 | #![cfg_attr (feature = "deny-warnings" , deny(warnings))] |
2 | |
3 | /// This macro creates the version string during compilation from the |
4 | /// current environment |
5 | #[macro_export ] |
6 | macro_rules! get_version_info { |
7 | () => {{ |
8 | let major = std::env!("CARGO_PKG_VERSION_MAJOR" ).parse::<u8>().unwrap(); |
9 | let minor = std::env!("CARGO_PKG_VERSION_MINOR" ).parse::<u8>().unwrap(); |
10 | let patch = std::env!("CARGO_PKG_VERSION_PATCH" ).parse::<u16>().unwrap(); |
11 | let crate_name = String::from(std::env!("CARGO_PKG_NAME" )); |
12 | |
13 | let host_compiler = std::option_env!("RUSTC_RELEASE_CHANNEL" ).map(str::to_string); |
14 | let commit_hash = std::option_env!("GIT_HASH" ).map(str::to_string); |
15 | let commit_date = std::option_env!("COMMIT_DATE" ).map(str::to_string); |
16 | |
17 | $crate::VersionInfo { |
18 | major, |
19 | minor, |
20 | patch, |
21 | host_compiler, |
22 | commit_hash, |
23 | commit_date, |
24 | crate_name, |
25 | } |
26 | }}; |
27 | } |
28 | |
29 | /// This macro can be used in `build.rs` to automatically set the needed |
30 | /// environment values, namely `GIT_HASH`, `COMMIT_DATE` and |
31 | /// `RUSTC_RELEASE_CHANNEL` |
32 | #[macro_export ] |
33 | macro_rules! setup_version_info { |
34 | () => {{ |
35 | println!( |
36 | "cargo:rustc-env=GIT_HASH={}" , |
37 | $crate::get_commit_hash().unwrap_or_default() |
38 | ); |
39 | println!( |
40 | "cargo:rustc-env=COMMIT_DATE={}" , |
41 | $crate::get_commit_date().unwrap_or_default() |
42 | ); |
43 | println!("cargo:rustc-env=RUSTC_RELEASE_CHANNEL={}" , $crate::get_channel()); |
44 | }}; |
45 | } |
46 | |
47 | // some code taken and adapted from RLS and cargo |
48 | pub struct VersionInfo { |
49 | pub major: u8, |
50 | pub minor: u8, |
51 | pub patch: u16, |
52 | pub host_compiler: Option<String>, |
53 | pub commit_hash: Option<String>, |
54 | pub commit_date: Option<String>, |
55 | pub crate_name: String, |
56 | } |
57 | |
58 | impl std::fmt::Display for VersionInfo { |
59 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
60 | let hash: String = self.commit_hash.clone().unwrap_or_default(); |
61 | let hash_trimmed: &str = hash.trim(); |
62 | |
63 | let date: String = self.commit_date.clone().unwrap_or_default(); |
64 | let date_trimmed: &str = date.trim(); |
65 | |
66 | if (hash_trimmed.len() + date_trimmed.len()) > 0 { |
67 | write!( |
68 | f, |
69 | " {} {}. {}. {} ( {hash_trimmed} {date_trimmed})" , |
70 | self.crate_name, self.major, self.minor, self.patch, |
71 | )?; |
72 | } else { |
73 | write!(f, " {} {}. {}. {}" , self.crate_name, self.major, self.minor, self.patch)?; |
74 | } |
75 | |
76 | Ok(()) |
77 | } |
78 | } |
79 | |
80 | impl std::fmt::Debug for VersionInfo { |
81 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
82 | write!( |
83 | f, |
84 | "VersionInfo {{ crate_name: \"{}\", major: {}, minor: {}, patch: {}" , |
85 | self.crate_name, self.major, self.minor, self.patch, |
86 | )?; |
87 | if self.commit_hash.is_some() { |
88 | write!( |
89 | f, |
90 | ", commit_hash: \"{}\", commit_date: \"{}\" }}" , |
91 | self.commit_hash.clone().unwrap_or_default().trim(), |
92 | self.commit_date.clone().unwrap_or_default().trim() |
93 | )?; |
94 | } else { |
95 | write!(f, " }}" )?; |
96 | } |
97 | |
98 | Ok(()) |
99 | } |
100 | } |
101 | |
102 | #[must_use ] |
103 | pub fn get_commit_hash() -> Option<String> { |
104 | stdOption::process::Command::new(program:"git" ) |
105 | .args(["rev-parse" , "--short" , "HEAD" ]) |
106 | .output() |
107 | .ok() |
108 | .and_then(|r: Output| String::from_utf8(vec:r.stdout).ok()) |
109 | } |
110 | |
111 | #[must_use ] |
112 | pub fn get_commit_date() -> Option<String> { |
113 | stdOption::process::Command::new(program:"git" ) |
114 | .args(["log" , "-1" , "--date=short" , "--pretty=format:%cd" ]) |
115 | .output() |
116 | .ok() |
117 | .and_then(|r: Output| String::from_utf8(vec:r.stdout).ok()) |
118 | } |
119 | |
120 | #[must_use ] |
121 | pub fn get_channel() -> String { |
122 | match std::env::var("CFG_RELEASE_CHANNEL" ) { |
123 | Ok(channel) => channel, |
124 | Err(_) => { |
125 | // if that failed, try to ask rustc -V, do some parsing and find out |
126 | match std::process::Command::new("rustc" ) |
127 | .arg("-V" ) |
128 | .output() |
129 | .ok() |
130 | .and_then(|r| String::from_utf8(r.stdout).ok()) |
131 | { |
132 | Some(rustc_output) => { |
133 | if rustc_output.contains("beta" ) { |
134 | String::from("beta" ) |
135 | } else if rustc_output.contains("stable" ) { |
136 | String::from("stable" ) |
137 | } else { |
138 | // default to nightly if we fail to parse |
139 | String::from("nightly" ) |
140 | } |
141 | }, |
142 | // default to nightly |
143 | None => String::from("nightly" ), |
144 | } |
145 | }, |
146 | } |
147 | } |
148 | |
149 | #[cfg (test)] |
150 | mod test { |
151 | use super::*; |
152 | |
153 | #[test ] |
154 | fn test_struct_local() { |
155 | let vi = get_version_info!(); |
156 | assert_eq!(vi.major, 0); |
157 | assert_eq!(vi.minor, 2); |
158 | assert_eq!(vi.patch, 1); |
159 | assert_eq!(vi.crate_name, "rustc_tools_util" ); |
160 | // hard to make positive tests for these since they will always change |
161 | assert!(vi.commit_hash.is_none()); |
162 | assert!(vi.commit_date.is_none()); |
163 | } |
164 | |
165 | #[test ] |
166 | fn test_display_local() { |
167 | let vi = get_version_info!(); |
168 | assert_eq!(vi.to_string(), "rustc_tools_util 0.3.0" ); |
169 | } |
170 | |
171 | #[test ] |
172 | fn test_debug_local() { |
173 | let vi = get_version_info!(); |
174 | let s = format!(" {vi:?}" ); |
175 | assert_eq!( |
176 | s, |
177 | "VersionInfo { crate_name: \"rustc_tools_util \", major: 0, minor: 2, patch: 1 }" |
178 | ); |
179 | } |
180 | } |
181 | |