1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368
//! This tiny crate checks that the running or installed `rustc` meets some //! version requirements. The version is queried by calling the Rust compiler //! with `--version`. The path to the compiler is determined first via the //! `RUSTC` environment variable. If it is not set, then `rustc` is used. If //! that fails, no determination is made, and calls return `None`. //! //! # Examples //! //! Set a `cfg` flag in `build.rs` if the running compiler was determined to be //! at least version `1.13.0`: //! //! ```rust //! extern crate version_check as rustc; //! //! if rustc::is_min_version("1.13.0").unwrap_or(false) { //! println!("cargo:rustc-cfg=question_mark_operator"); //! } //! ``` //! //! See [`is_max_version`] or [`is_exact_version`] to check if the compiler //! is _at most_ or _exactly_ a certain version. //! //! Check that the running compiler was released on or after `2018-12-18`: //! //! ```rust //! extern crate version_check as rustc; //! //! match rustc::is_min_date("2018-12-18") { //! Some(true) => "Yep! It's recent!", //! Some(false) => "No, it's older.", //! None => "Couldn't determine the rustc version." //! }; //! ``` //! //! See [`is_max_date`] or [`is_exact_date`] to check if the compiler was //! released _prior to_ or _exactly on_ a certain date. //! //! Check that the running compiler supports feature flags: //! //! ```rust //! extern crate version_check as rustc; //! //! match rustc::is_feature_flaggable() { //! Some(true) => "Yes! It's a dev or nightly release!", //! Some(false) => "No, it's stable or beta.", //! None => "Couldn't determine the rustc version." //! }; //! ``` //! //! Check that the running compiler is on the stable channel: //! //! ```rust //! extern crate version_check as rustc; //! //! match rustc::Channel::read() { //! Some(c) if c.is_stable() => format!("Yes! It's stable."), //! Some(c) => format!("No, the channel {} is not stable.", c), //! None => format!("Couldn't determine the rustc version.") //! }; //! ``` //! //! To interact with the version, release date, and release channel as structs, //! use [`Version`], [`Date`], and [`Channel`], respectively. The [`triple()`] //! function returns all three values efficiently. //! //! # Alternatives //! //! This crate is dead simple with no dependencies. If you need something more //! and don't care about panicking if the version cannot be obtained, or if you //! don't mind adding dependencies, see //! [rustc_version](https://crates.io/crates/rustc_version). #![allow(deprecated)] mod version; mod channel; mod date; use std::env; use std::process::Command; #[doc(inline)] pub use version::*; #[doc(inline)] pub use channel::*; #[doc(inline)] pub use date::*; /// Parses (version, date) as available from rustc version string. fn version_and_date_from_rustc_version(s: &str) -> (Option<String>, Option<String>) { let last_line = s.lines().last().unwrap_or(s); let mut components = last_line.trim().split(" "); let version = components.nth(1); let date = components.filter(|c| c.ends_with(')')).next() .map(|s| s.trim_right().trim_right_matches(")").trim_left().trim_left_matches('(')); (version.map(|s| s.to_string()), date.map(|s| s.to_string())) } /// Parses (version, date) as available from rustc verbose version output. fn version_and_date_from_rustc_verbose_version(s: &str) -> (Option<String>, Option<String>) { let (mut version, mut date) = (None, None); for line in s.lines() { let split = |s: &str| s.splitn(2, ":").nth(1).map(|s| s.trim().to_string()); match line.trim().split(" ").nth(0) { Some("rustc") => { let (v, d) = version_and_date_from_rustc_version(line); version = version.or(v); date = date.or(d); }, Some("release:") => version = split(line), Some("commit-date:") if line.ends_with("unknown") => date = None, Some("commit-date:") => date = split(line), _ => continue } } (version, date) } /// Returns (version, date) as available from `rustc --version`. fn get_version_and_date() -> Option<(Option<String>, Option<String>)> { let rustc = env::var("RUSTC").unwrap_or_else(|_| "rustc".to_string()); Command::new(rustc).arg("--verbose").arg("--version").output().ok() .and_then(|output| String::from_utf8(output.stdout).ok()) .map(|s| version_and_date_from_rustc_verbose_version(&s)) } /// Reads the triple of [`Version`], [`Channel`], and [`Date`] of the installed /// or running `rustc`. /// /// If any attribute cannot be determined (see the [top-level /// documentation](crate)), returns `None`. /// /// To obtain only one of three attributes, use [`Version::read()`], /// [`Channel::read()`], or [`Date::read()`]. pub fn triple() -> Option<(Version, Channel, Date)> { let (version_str, date_str) = match get_version_and_date() { Some((Some(version), Some(date))) => (version, date), _ => return None }; // Can't use `?` or `try!` for `Option` in 1.0.0. match Version::parse(&version_str) { Some(version) => match Channel::parse(&version_str) { Some(channel) => match Date::parse(&date_str) { Some(date) => Some((version, channel, date)), _ => None, }, _ => None, }, _ => None } } /// Checks that the running or installed `rustc` was released **on or after** /// some date. /// /// The format of `min_date` must be YYYY-MM-DD. For instance: `2016-12-20` or /// `2017-01-09`. /// /// If the date cannot be retrieved or parsed, or if `min_date` could not be /// parsed, returns `None`. Otherwise returns `true` if the installed `rustc` /// was release on or after `min_date` and `false` otherwise. pub fn is_min_date(min_date: &str) -> Option<bool> { match (Date::read(), Date::parse(min_date)) { (Some(rustc_date), Some(min_date)) => Some(rustc_date >= min_date), _ => None } } /// Checks that the running or installed `rustc` was released **on or before** /// some date. /// /// The format of `max_date` must be YYYY-MM-DD. For instance: `2016-12-20` or /// `2017-01-09`. /// /// If the date cannot be retrieved or parsed, or if `max_date` could not be /// parsed, returns `None`. Otherwise returns `true` if the installed `rustc` /// was release on or before `max_date` and `false` otherwise. pub fn is_max_date(max_date: &str) -> Option<bool> { match (Date::read(), Date::parse(max_date)) { (Some(rustc_date), Some(max_date)) => Some(rustc_date <= max_date), _ => None } } /// Checks that the running or installed `rustc` was released **exactly** on /// some date. /// /// The format of `date` must be YYYY-MM-DD. For instance: `2016-12-20` or /// `2017-01-09`. /// /// If the date cannot be retrieved or parsed, or if `date` could not be parsed, /// returns `None`. Otherwise returns `true` if the installed `rustc` was /// release on `date` and `false` otherwise. pub fn is_exact_date(date: &str) -> Option<bool> { match (Date::read(), Date::parse(date)) { (Some(rustc_date), Some(date)) => Some(rustc_date == date), _ => None } } /// Checks that the running or installed `rustc` is **at least** some minimum /// version. /// /// The format of `min_version` is a semantic version: `1.3.0`, `1.15.0-beta`, /// `1.14.0`, `1.16.0-nightly`, etc. /// /// If the version cannot be retrieved or parsed, or if `min_version` could not /// be parsed, returns `None`. Otherwise returns `true` if the installed `rustc` /// is at least `min_version` and `false` otherwise. pub fn is_min_version(min_version: &str) -> Option<bool> { match (Version::read(), Version::parse(min_version)) { (Some(rustc_ver), Some(min_ver)) => Some(rustc_ver >= min_ver), _ => None } } /// Checks that the running or installed `rustc` is **at most** some maximum /// version. /// /// The format of `max_version` is a semantic version: `1.3.0`, `1.15.0-beta`, /// `1.14.0`, `1.16.0-nightly`, etc. /// /// If the version cannot be retrieved or parsed, or if `max_version` could not /// be parsed, returns `None`. Otherwise returns `true` if the installed `rustc` /// is at most `max_version` and `false` otherwise. pub fn is_max_version(max_version: &str) -> Option<bool> { match (Version::read(), Version::parse(max_version)) { (Some(rustc_ver), Some(max_ver)) => Some(rustc_ver <= max_ver), _ => None } } /// Checks that the running or installed `rustc` is **exactly** some version. /// /// The format of `version` is a semantic version: `1.3.0`, `1.15.0-beta`, /// `1.14.0`, `1.16.0-nightly`, etc. /// /// If the version cannot be retrieved or parsed, or if `version` could not be /// parsed, returns `None`. Otherwise returns `true` if the installed `rustc` is /// exactly `version` and `false` otherwise. pub fn is_exact_version(version: &str) -> Option<bool> { match (Version::read(), Version::parse(version)) { (Some(rustc_ver), Some(version)) => Some(rustc_ver == version), _ => None } } /// Checks whether the running or installed `rustc` supports feature flags. /// /// In other words, if the channel is either "nightly" or "dev". /// /// If the version could not be determined, returns `None`. Otherwise returns /// `true` if the running version supports feature flags and `false` otherwise. pub fn is_feature_flaggable() -> Option<bool> { Channel::read().map(|c| c.supports_features()) } #[cfg(test)] mod tests { use super::version_and_date_from_rustc_version; use super::version_and_date_from_rustc_verbose_version; macro_rules! check_parse { (@ $f:expr, $s:expr => $v:expr, $d:expr) => ({ if let (Some(v), d) = $f($s) { let e_d: Option<&str> = $d.into(); assert_eq!((v, d), ($v.into(), e_d.map(|s| s.into()))); } else { panic!("{:?} didn't parse for version testing.", $s); } }); ($f:expr, $s:expr => $v:expr, $d:expr) => ({ let warn = "warning: invalid logging spec 'warning', ignoring it"; let warn2 = "warning: sorry, something went wrong :(sad)"; check_parse!(@ $f, $s => $v, $d); check_parse!(@ $f, &format!("{}\n{}", warn, $s) => $v, $d); check_parse!(@ $f, &format!("{}\n{}", warn2, $s) => $v, $d); check_parse!(@ $f, &format!("{}\n{}\n{}", warn, warn2, $s) => $v, $d); check_parse!(@ $f, &format!("{}\n{}\n{}", warn2, warn, $s) => $v, $d); }) } macro_rules! check_terse_parse { ($($s:expr => $v:expr, $d:expr,)+) => {$( check_parse!(version_and_date_from_rustc_version, $s => $v, $d); )+} } macro_rules! check_verbose_parse { ($($s:expr => $v:expr, $d:expr,)+) => {$( check_parse!(version_and_date_from_rustc_verbose_version, $s => $v, $d); )+} } #[test] fn test_version_parse() { check_terse_parse! { "rustc 1.18.0" => "1.18.0", None, "rustc 1.8.0" => "1.8.0", None, "rustc 1.20.0-nightly" => "1.20.0-nightly", None, "rustc 1.20" => "1.20", None, "rustc 1.3" => "1.3", None, "rustc 1" => "1", None, "rustc 1.5.1-beta" => "1.5.1-beta", None, "rustc 1.20.0 (2017-07-09)" => "1.20.0", Some("2017-07-09"), "rustc 1.20.0-dev (2017-07-09)" => "1.20.0-dev", Some("2017-07-09"), "rustc 1.20.0-nightly (d84693b93 2017-07-09)" => "1.20.0-nightly", Some("2017-07-09"), "rustc 1.20.0 (d84693b93 2017-07-09)" => "1.20.0", Some("2017-07-09"), "rustc 1.30.0-nightly (3bc2ca7e4 2018-09-20)" => "1.30.0-nightly", Some("2018-09-20"), }; } #[test] fn test_verbose_version_parse() { check_verbose_parse! { "rustc 1.0.0 (a59de37e9 2015-05-13) (built 2015-05-14)\n\ binary: rustc\n\ commit-hash: a59de37e99060162a2674e3ff45409ac73595c0e\n\ commit-date: 2015-05-13\n\ build-date: 2015-05-14\n\ host: x86_64-unknown-linux-gnu\n\ release: 1.0.0" => "1.0.0", Some("2015-05-13"), "rustc 1.0.0 (a59de37e9 2015-05-13) (built 2015-05-14)\n\ commit-hash: a59de37e99060162a2674e3ff45409ac73595c0e\n\ commit-date: 2015-05-13\n\ build-date: 2015-05-14\n\ host: x86_64-unknown-linux-gnu\n\ release: 1.0.0" => "1.0.0", Some("2015-05-13"), "rustc 1.50.0 (cb75ad5db 2021-02-10)\n\ binary: rustc\n\ commit-hash: cb75ad5db02783e8b0222fee363c5f63f7e2cf5b\n\ commit-date: 2021-02-10\n\ host: x86_64-unknown-linux-gnu\n\ release: 1.50.0" => "1.50.0", Some("2021-02-10"), "rustc 1.52.0-nightly (234781afe 2021-03-07)\n\ binary: rustc\n\ commit-hash: 234781afe33d3f339b002f85f948046d8476cfc9\n\ commit-date: 2021-03-07\n\ host: x86_64-unknown-linux-gnu\n\ release: 1.52.0-nightly\n\ LLVM version: 12.0.0" => "1.52.0-nightly", Some("2021-03-07"), "rustc 1.41.1\n\ binary: rustc\n\ commit-hash: unknown\n\ commit-date: unknown\n\ host: x86_64-unknown-linux-gnu\n\ release: 1.41.1\n\ LLVM version: 7.0" => "1.41.1", None, "rustc 1.49.0\n\ binary: rustc\n\ commit-hash: unknown\n\ commit-date: unknown\n\ host: x86_64-unknown-linux-gnu\n\ release: 1.49.0" => "1.49.0", None, "rustc 1.50.0 (Fedora 1.50.0-1.fc33)\n\ binary: rustc\n\ commit-hash: unknown\n\ commit-date: unknown\n\ host: x86_64-unknown-linux-gnu\n\ release: 1.50.0" => "1.50.0", None, }; } }