| 1 | use std::path::PathBuf; |
| 2 | use std::process::Command; |
| 3 | use std::str; |
| 4 | |
| 5 | /// This macro creates the version string during compilation from the |
| 6 | /// current environment |
| 7 | #[macro_export ] |
| 8 | macro_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 | /// This macro can be used in `build.rs` to automatically set the needed |
| 32 | /// environment values, namely `GIT_HASH`, `COMMIT_DATE` and |
| 33 | /// `RUSTC_RELEASE_CHANNEL` |
| 34 | #[macro_export ] |
| 35 | macro_rules! setup_version_info { |
| 36 | () => {{ |
| 37 | let _ = $crate::rerun_if_git_changes(); |
| 38 | println!( |
| 39 | "cargo:rustc-env=GIT_HASH={}" , |
| 40 | $crate::get_commit_hash().unwrap_or_default() |
| 41 | ); |
| 42 | println!( |
| 43 | "cargo:rustc-env=COMMIT_DATE={}" , |
| 44 | $crate::get_commit_date().unwrap_or_default() |
| 45 | ); |
| 46 | println!("cargo:rustc-env=RUSTC_RELEASE_CHANNEL={}" , $crate::get_channel()); |
| 47 | }}; |
| 48 | } |
| 49 | |
| 50 | // some code taken and adapted from RLS and cargo |
| 51 | pub struct VersionInfo { |
| 52 | pub major: u8, |
| 53 | pub minor: u8, |
| 54 | pub patch: u16, |
| 55 | pub host_compiler: Option<String>, |
| 56 | pub commit_hash: Option<String>, |
| 57 | pub commit_date: Option<String>, |
| 58 | pub crate_name: String, |
| 59 | } |
| 60 | |
| 61 | impl std::fmt::Display for VersionInfo { |
| 62 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 63 | let hash: String = self.commit_hash.clone().unwrap_or_default(); |
| 64 | let hash_trimmed: &str = hash.trim(); |
| 65 | |
| 66 | let date: String = self.commit_date.clone().unwrap_or_default(); |
| 67 | let date_trimmed: &str = date.trim(); |
| 68 | |
| 69 | if (hash_trimmed.len() + date_trimmed.len()) > 0 { |
| 70 | write!( |
| 71 | f, |
| 72 | " {} {}. {}. {} ( {hash_trimmed} {date_trimmed})" , |
| 73 | self.crate_name, self.major, self.minor, self.patch, |
| 74 | )?; |
| 75 | } else { |
| 76 | write!(f, " {} {}. {}. {}" , self.crate_name, self.major, self.minor, self.patch)?; |
| 77 | } |
| 78 | |
| 79 | Ok(()) |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | impl std::fmt::Debug for VersionInfo { |
| 84 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 85 | write!( |
| 86 | f, |
| 87 | "VersionInfo {{ crate_name: \"{}\", major: {}, minor: {}, patch: {}" , |
| 88 | self.crate_name, self.major, self.minor, self.patch, |
| 89 | )?; |
| 90 | if self.commit_hash.is_some() { |
| 91 | write!( |
| 92 | f, |
| 93 | ", commit_hash: \"{}\", commit_date: \"{}\" }}" , |
| 94 | self.commit_hash.clone().unwrap_or_default().trim(), |
| 95 | self.commit_date.clone().unwrap_or_default().trim() |
| 96 | )?; |
| 97 | } else { |
| 98 | write!(f, " }}" )?; |
| 99 | } |
| 100 | |
| 101 | Ok(()) |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | #[must_use ] |
| 106 | fn get_output(cmd: &str, args: &[&str]) -> Option<String> { |
| 107 | let output: Output = Command::new(program:cmd).args(args).output().ok()?; |
| 108 | let mut stdout: Vec = output.status.success().then_some(output.stdout)?; |
| 109 | // Remove trailing newlines. |
| 110 | while stdout.last().copied() == Some(b' \n' ) { |
| 111 | stdout.pop(); |
| 112 | } |
| 113 | String::from_utf8(vec:stdout).ok() |
| 114 | } |
| 115 | |
| 116 | #[must_use ] |
| 117 | pub fn rerun_if_git_changes() -> Option<()> { |
| 118 | // Make sure we get rerun when the git commit changes. |
| 119 | // We want to watch two files: HEAD, which tracks which branch we are on, |
| 120 | // and the file for that branch that tracks which commit is is on. |
| 121 | |
| 122 | // First, find the `HEAD` file. This should work even with worktrees. |
| 123 | let git_head_file: PathBuf = PathBuf::from(get_output(cmd:"git" , &["rev-parse" , "--git-path" , "HEAD" ])?); |
| 124 | if git_head_file.exists() { |
| 125 | println!("cargo::rerun-if-changed= {}" , git_head_file.display()); |
| 126 | } |
| 127 | |
| 128 | // Determine the name of the current ref. |
| 129 | // This will quit if HEAD is detached. |
| 130 | let git_head_ref: String = get_output(cmd:"git" , &["symbolic-ref" , "-q" , "HEAD" ])?; |
| 131 | // Ask git where this ref is stored. |
| 132 | let git_head_ref_file: PathBuf = PathBuf::from(get_output(cmd:"git" , &["rev-parse" , "--git-path" , &git_head_ref])?); |
| 133 | // If this ref is packed, the file does not exist. However, the checked-out branch is never (?) |
| 134 | // packed, so we should always be able to find this file. |
| 135 | if git_head_ref_file.exists() { |
| 136 | println!("cargo::rerun-if-changed= {}" , git_head_ref_file.display()); |
| 137 | } |
| 138 | |
| 139 | Some(()) |
| 140 | } |
| 141 | |
| 142 | #[must_use ] |
| 143 | pub fn get_commit_hash() -> Option<String> { |
| 144 | let mut stdout: String = get_output(cmd:"git" , &["rev-parse" , "HEAD" ])?; |
| 145 | stdout.truncate(new_len:10); |
| 146 | Some(stdout) |
| 147 | } |
| 148 | |
| 149 | #[must_use ] |
| 150 | pub fn get_commit_date() -> Option<String> { |
| 151 | get_output(cmd:"git" , &["log" , "-1" , "--date=short" , "--pretty=format:%cd" ]) |
| 152 | } |
| 153 | |
| 154 | #[must_use ] |
| 155 | pub fn get_channel() -> String { |
| 156 | if let Ok(channel: String) = std::env::var(key:"CFG_RELEASE_CHANNEL" ) { |
| 157 | return channel; |
| 158 | } |
| 159 | |
| 160 | // if that failed, try to ask rustc -V, do some parsing and find out |
| 161 | if let Some(rustc_output: String) = get_output(cmd:"rustc" , &["-V" ]) { |
| 162 | if rustc_output.contains("beta" ) { |
| 163 | return String::from("beta" ); |
| 164 | } else if rustc_output.contains("stable" ) { |
| 165 | return String::from("stable" ); |
| 166 | } |
| 167 | } |
| 168 | |
| 169 | // default to nightly |
| 170 | String::from("nightly" ) |
| 171 | } |
| 172 | |
| 173 | #[cfg (test)] |
| 174 | mod test { |
| 175 | use super::*; |
| 176 | |
| 177 | #[test ] |
| 178 | fn test_struct_local() { |
| 179 | let vi = get_version_info!(); |
| 180 | assert_eq!(vi.major, 0); |
| 181 | assert_eq!(vi.minor, 4); |
| 182 | assert_eq!(vi.patch, 0); |
| 183 | assert_eq!(vi.crate_name, "rustc_tools_util" ); |
| 184 | // hard to make positive tests for these since they will always change |
| 185 | assert!(vi.commit_hash.is_none()); |
| 186 | assert!(vi.commit_date.is_none()); |
| 187 | } |
| 188 | |
| 189 | #[test ] |
| 190 | fn test_display_local() { |
| 191 | let vi = get_version_info!(); |
| 192 | assert_eq!(vi.to_string(), "rustc_tools_util 0.4.0" ); |
| 193 | } |
| 194 | |
| 195 | #[test ] |
| 196 | fn test_debug_local() { |
| 197 | let vi = get_version_info!(); |
| 198 | let s = format!("{vi:?}" ); |
| 199 | assert_eq!( |
| 200 | s, |
| 201 | "VersionInfo { crate_name: \"rustc_tools_util \", major: 0, minor: 4, patch: 0 }" |
| 202 | ); |
| 203 | } |
| 204 | } |
| 205 | |