// This file is part of Substrate. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use std::cmp::Ordering; /// The version of rustc/cargo. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Version { pub major: u32, pub minor: u32, pub patch: u32, pub is_nightly: bool, pub year: Option, pub month: Option, pub day: Option, } impl Version { /// Returns if `self` is a stable version. pub fn is_stable(&self) -> bool { !self.is_nightly } /// Return if `self` is a nightly version. pub fn is_nightly(&self) -> bool { self.is_nightly } /// Extract from the given `version` string. pub fn extract(version: &str) -> Option { let mut is_nightly = false; let version_parts = version .trim() .split(" ") .nth(1)? .split(".") .filter_map(|v| { if let Some(rest) = v.strip_suffix("-nightly") { is_nightly = true; rest.parse().ok() } else { v.parse().ok() } }) .collect::>(); if version_parts.len() != 3 { return None } let date_parts = version .split(" ") .nth(3) .map(|date| { date.split("-") .filter_map(|v| v.trim().strip_suffix(")").unwrap_or(v).parse().ok()) .collect::>() }) .unwrap_or_default(); Some(Version { major: version_parts[0], minor: version_parts[1], patch: version_parts[2], is_nightly, year: date_parts.get(0).copied(), month: date_parts.get(1).copied(), day: date_parts.get(2).copied(), }) } } /// Ordering is done in the following way: /// /// 1. `stable` > `nightly` /// 2. Then compare major, minor and patch. /// 3. Last compare the date. impl Ord for Version { fn cmp(&self, other: &Self) -> Ordering { if self == other { return Ordering::Equal } // Ensure that `stable > nightly` if self.is_stable() && other.is_nightly() { return Ordering::Greater } else if self.is_nightly() && other.is_stable() { return Ordering::Less } let to_compare = [ (Some(self.major), Some(other.major)), (Some(self.minor), Some(other.minor)), (Some(self.patch), Some(other.patch)), (self.year, other.year), (self.month, other.month), (self.day, other.day), ]; to_compare .iter() .find_map(|(l, r)| if l != r { l.partial_cmp(&r) } else { None }) // We already checked this right at the beginning, so we should never return here // `Equal`. .unwrap_or(Ordering::Equal) } } impl PartialOrd for Version { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } #[cfg(test)] mod tests { use super::*; #[test] fn version_compare_and_extract_works() { let version_1_66_0 = Version::extract("cargo 1.66.0 (d65d197ad 2022-11-15)").unwrap(); let version_1_66_1 = Version::extract("cargo 1.66.1 (d65d197ad 2022-11-15)").unwrap(); let version_1_66_0_nightly = Version::extract("cargo 1.66.0-nightly (d65d197ad 2022-10-15)").unwrap(); let version_1_66_0_nightly_older_date = Version::extract("cargo 1.66.0-nightly (d65d197ad 2022-10-14)").unwrap(); let version_1_65_0 = Version::extract("cargo 1.65.0 (d65d197ad 2022-10-15)").unwrap(); let version_1_65_0_older_date = Version::extract("cargo 1.65.0 (d65d197ad 2022-10-14)").unwrap(); assert!(version_1_66_1 > version_1_66_0); assert!(version_1_66_1 > version_1_65_0); assert!(version_1_66_1 > version_1_66_0_nightly); assert!(version_1_66_1 > version_1_66_0_nightly_older_date); assert!(version_1_66_1 > version_1_65_0_older_date); assert!(version_1_66_0 > version_1_65_0); assert!(version_1_66_0 > version_1_66_0_nightly); assert!(version_1_66_0 > version_1_66_0_nightly_older_date); assert!(version_1_66_0 > version_1_65_0_older_date); assert!(version_1_65_0 > version_1_66_0_nightly); assert!(version_1_65_0 > version_1_66_0_nightly_older_date); assert!(version_1_65_0 > version_1_65_0_older_date); let mut versions = vec![ version_1_66_0, version_1_66_0_nightly, version_1_66_0_nightly_older_date, version_1_65_0_older_date, version_1_65_0, version_1_66_1, ]; versions.sort_by(|a, b| b.cmp(a)); let expected_versions_order = vec![ version_1_66_1, version_1_66_0, version_1_65_0, version_1_65_0_older_date, version_1_66_0_nightly, version_1_66_0_nightly_older_date, ]; assert_eq!(expected_versions_order, versions); } #[test] fn parse_with_newline() { let version_1_66_0 = Version::extract("cargo 1.66.0 (d65d197ad 2022-11-15)\n").unwrap(); assert_eq!( Version { major: 1, minor: 66, patch: 0, is_nightly: false, year: Some(2022), month: Some(11), day: Some(15), }, version_1_66_0, ); } #[test] fn version_without_hash_and_date() { // Apparently there are installations that print without the hash and date. let version_1_69_0 = Version::extract("cargo 1.69.0-nightly").unwrap(); assert_eq!( Version { major: 1, minor: 69, patch: 0, is_nightly: true, year: None, month: None, day: None, }, version_1_69_0, ); } #[test] fn parse_rustc_version() { let version = Version::extract("rustc 1.73.0 (cc66ad468 2023-10-03)").unwrap(); assert_eq!( version, Version { major: 1, minor: 73, patch: 0, is_nightly: false, year: Some(2023), month: Some(10), day: Some(03), } ); } }