|
| 1 | +//! This tiny crate checks that the running or installed `rustc` meets some |
| 2 | +//! version requirements. The version is queried by calling the Rust compiler |
| 3 | +//! with `--version`. The path to the compiler is determined first via the |
| 4 | +//! `RUSTC` environment variable. If it is not set, then `rustc` is used. If |
| 5 | +//! that fails, no determination is made, and calls return `None`. |
| 6 | +//! |
| 7 | +//! # Example |
| 8 | +//! |
| 9 | +//! Check that the running compiler is a nightly release: |
| 10 | +//! |
| 11 | +//! ```rust |
| 12 | +//! extern crate version_check; |
| 13 | +//! |
| 14 | +//! match version_check::is_nightly() { |
| 15 | +//! Some(true) => "running a nightly", |
| 16 | +//! Some(false) => "not nightly", |
| 17 | +//! None => "couldn't figure it out" |
| 18 | +//! }; |
| 19 | +//! ``` |
| 20 | +//! |
| 21 | +//! Check that the running compiler is at least version `1.13.0`: |
| 22 | +//! |
| 23 | +//! ```rust |
| 24 | +//! extern crate version_check; |
| 25 | +//! |
| 26 | +//! match version_check::is_min_version("1.13.0") { |
| 27 | +//! Some((true, version)) => format!("Yes! It's: {}", version), |
| 28 | +//! Some((false, version)) => format!("No! {} is too old!", version), |
| 29 | +//! None => "couldn't figure it out".into() |
| 30 | +//! }; |
| 31 | +//! ``` |
| 32 | +//! |
| 33 | +//! Check that the running compiler was released on or after `2016-12-18`: |
| 34 | +//! |
| 35 | +//! ```rust |
| 36 | +//! extern crate version_check; |
| 37 | +//! |
| 38 | +//! match version_check::is_min_date("2016-12-18") { |
| 39 | +//! Some((true, date)) => format!("Yes! It's: {}", date), |
| 40 | +//! Some((false, date)) => format!("No! {} is too long ago!", date), |
| 41 | +//! None => "couldn't figure it out".into() |
| 42 | +//! }; |
| 43 | +//! ``` |
| 44 | +//! |
| 45 | +//! # Alternatives |
| 46 | +//! |
| 47 | +//! This crate is dead simple with no dependencies. If you need something more |
| 48 | +//! and don't care about panicking if the version cannot be obtained or adding |
| 49 | +//! dependencies, see [rustc_version](https://crates.io/crates/rustc_version). |
| 50 | +
|
| 51 | +use std::env; |
| 52 | +use std::process::Command; |
| 53 | + |
| 54 | +// Convert a string of %Y-%m-%d to a single u32 maintaining ordering. |
| 55 | +fn str_to_ymd(ymd: &str) -> Option<u32> { |
| 56 | + let ymd: Vec<u32> = ymd.split("-").filter_map(|s| s.parse::<u32>().ok()).collect(); |
| 57 | + if ymd.len() != 3 { |
| 58 | + return None |
| 59 | + } |
| 60 | + |
| 61 | + let (y, m, d) = (ymd[0], ymd[1], ymd[2]); |
| 62 | + Some((y << 9) | (m << 5) | d) |
| 63 | +} |
| 64 | + |
| 65 | +// Convert a string with prefix major-minor-patch to a single u64 maintaining |
| 66 | +// ordering. Assumes none of the components are > 1048576. |
| 67 | +fn str_to_mmp(mmp: &str) -> Option<u64> { |
| 68 | + let mmp: Vec<u16> = mmp.split('-') |
| 69 | + .nth(0) |
| 70 | + .unwrap_or("") |
| 71 | + .split('.') |
| 72 | + .filter_map(|s| s.parse::<u16>().ok()) |
| 73 | + .collect(); |
| 74 | + |
| 75 | + if mmp.len() != 3 { |
| 76 | + return None |
| 77 | + } |
| 78 | + |
| 79 | + let (maj, min, patch) = (mmp[0] as u64, mmp[1] as u64, mmp[2] as u64); |
| 80 | + Some((maj << 32) | (min << 16) | patch) |
| 81 | +} |
| 82 | + |
| 83 | +fn get_version_and_date() -> Option<(String, String)> { |
| 84 | + let output = env::var("RUSTC").ok() |
| 85 | + .and_then(|rustc| Command::new(rustc).arg("--version").output().ok()) |
| 86 | + .or_else(|| Command::new("rustc").arg("--version").output().ok()) |
| 87 | + .and_then(|output| String::from_utf8(output.stdout).ok()) |
| 88 | + .map(|s| { |
| 89 | + let mut components = s.split(" "); |
| 90 | + let version = components.nth(1); |
| 91 | + let date = components.nth(1).map(|s| s.trim_right().trim_right_matches(")")); |
| 92 | + (version.map(|s| s.to_string()), date.map(|s| s.to_string())) |
| 93 | + }); |
| 94 | + |
| 95 | + match output { |
| 96 | + Some((Some(version), Some(date))) => Some((version, date)), |
| 97 | + _ => None |
| 98 | + } |
| 99 | +} |
| 100 | + |
| 101 | +/// Checks that the running or installed `rustc` was released no earlier than |
| 102 | +/// some date. |
| 103 | +/// |
| 104 | +/// The format of `min_date` must be YYYY-MM-DD. For instance: `2016-12-20` or |
| 105 | +/// `2017-01-09`. |
| 106 | +/// |
| 107 | +/// If the date cannot be retrieved or parsed, or if `min_date` could not be |
| 108 | +/// parsed, returns `None`. Otherwise returns a tuple where the first value is |
| 109 | +/// `true` if the installed `rustc` is at least from `min_data` and the second |
| 110 | +/// value is the date (in YYYY-MM-DD) of the installed `rustc`. |
| 111 | +pub fn is_min_date(min_date: &str) -> Option<(bool, String)> { |
| 112 | + if let Some((_, actual_date_str)) = get_version_and_date() { |
| 113 | + str_to_ymd(&actual_date_str) |
| 114 | + .and_then(|actual| str_to_ymd(min_date).map(|min| (min, actual))) |
| 115 | + .map(|(min, actual)| (actual >= min, actual_date_str)) |
| 116 | + } else { |
| 117 | + None |
| 118 | + } |
| 119 | +} |
| 120 | + |
| 121 | +/// Checks that the running or installed `rustc` is at least some minimum |
| 122 | +/// version. |
| 123 | +/// |
| 124 | +/// The format of `min_version` is a semantic version: `1.15.0-beta`, `1.14.0`, |
| 125 | +/// `1.16.0-nightly`, etc. |
| 126 | +/// |
| 127 | +/// If the version cannot be retrieved or parsed, or if `min_version` could not |
| 128 | +/// be parsed, returns `None`. Otherwise returns a tuple where the first value |
| 129 | +/// is `true` if the installed `rustc` is at least `min_version` and the second |
| 130 | +/// value is the version (semantic) of the installed `rustc`. |
| 131 | +pub fn is_min_version(min_version: &str) -> Option<(bool, String)> { |
| 132 | + if let Some((actual_version_str, _)) = get_version_and_date() { |
| 133 | + str_to_mmp(&actual_version_str) |
| 134 | + .and_then(|actual| str_to_mmp(min_version).map(|min| (min, actual))) |
| 135 | + .map(|(min, actual)| (actual >= min, actual_version_str)) |
| 136 | + } else { |
| 137 | + None |
| 138 | + } |
| 139 | +} |
| 140 | + |
| 141 | +/// Determines whether the running or installed `rustc` is on the nightly |
| 142 | +/// channel. |
| 143 | +/// |
| 144 | +/// If the version could not be determined, returns `None`. Otherwise returns |
| 145 | +/// `Some(true)` if the running version is a nightly release, and `Some(false)` |
| 146 | +/// otherwise. |
| 147 | +pub fn is_nightly() -> Option<bool> { |
| 148 | + get_version_and_date() |
| 149 | + .map(|(actual_version_str, _)| actual_version_str.contains("nightly")) |
| 150 | +} |
| 151 | + |
| 152 | +/// Determines whether the running or installed `rustc` is on the beta channel. |
| 153 | +/// |
| 154 | +/// If the version could not be determined, returns `None`. Otherwise returns |
| 155 | +/// `Some(true)` if the running version is a beta release, and `Some(false)` |
| 156 | +/// otherwise. |
| 157 | +pub fn is_beta() -> Option<bool> { |
| 158 | + get_version_and_date() |
| 159 | + .map(|(actual_version_str, _)| actual_version_str.contains("beta")) |
| 160 | +} |
0 commit comments