// 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<u32>,
pub month: Option<u32>,
pub day: Option<u32>,
}
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<Self> {
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::<Vec<u32>>();
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::<Vec<u32>>()
})
.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<Ordering> {
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),
}
);
}
}