Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(util): Pull out mod util_semver #12940

Merged
merged 1 commit into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/bin/cargo/commands/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use anyhow::format_err;
use cargo::core::{GitReference, SourceId, Workspace};
use cargo::ops;
use cargo::util::IntoUrl;
use cargo::util::VersionExt;
use cargo::util_semver::VersionExt;
use cargo::CargoResult;
use itertools::Itertools;
use semver::VersionReq;
Expand Down
2 changes: 1 addition & 1 deletion src/cargo/core/package_id_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ use url::Url;
use crate::core::PackageId;
use crate::util::edit_distance;
use crate::util::errors::CargoResult;
use crate::util::PartialVersion;
use crate::util::{validate_package_name, IntoUrl};
use crate::util_semver::PartialVersion;

/// Some or all of the data required to identify a package:
///
Expand Down
3 changes: 2 additions & 1 deletion src/cargo/core/resolver/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ use std::task::Poll;
use crate::core::{Dependency, PackageId, Registry, Summary};
use crate::sources::source::QueryKind;
use crate::util::edit_distance::edit_distance;
use crate::util::{Config, OptVersionReq, VersionExt};
use crate::util::{Config, OptVersionReq};
use crate::util_semver::VersionExt;
use anyhow::Error;

use super::context::Context;
Expand Down
1 change: 1 addition & 0 deletions src/cargo/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ pub mod core;
pub mod ops;
pub mod sources;
pub mod util;
pub mod util_semver;
mod version;

pub fn exit_with_error(err: CliError, shell: &mut Shell) -> ! {
Expand Down
2 changes: 1 addition & 1 deletion src/cargo/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ pub use self::progress::{Progress, ProgressStyle};
pub use self::queue::Queue;
pub use self::restricted_names::validate_package_name;
pub use self::rustc::Rustc;
pub use self::semver_ext::{OptVersionReq, PartialVersion, RustVersion, VersionExt};
pub use self::semver_ext::{OptVersionReq, RustVersion};
pub use self::vcs::{existing_vcs_repo, FossilRepo, GitRepo, HgRepo, PijulRepo};
pub use self::workspace::{
add_path_args, path_args, print_available_benches, print_available_binaries,
Expand Down
199 changes: 6 additions & 193 deletions src/cargo/util/semver_ext.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
use semver::{Comparator, Op, Version, VersionReq};
use serde_untagged::UntaggedEnumVisitor;
use std::fmt::{self, Display};

use semver::{Op, Version, VersionReq};
use serde_untagged::UntaggedEnumVisitor;

use crate::util_semver::PartialVersion;
use crate::util_semver::VersionExt as _;

#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub enum OptVersionReq {
Any,
Expand All @@ -12,30 +16,6 @@ pub enum OptVersionReq {
UpdatePrecise(Version, VersionReq),
}

pub trait VersionExt {
fn is_prerelease(&self) -> bool;

fn to_exact_req(&self) -> VersionReq;
}

impl VersionExt for Version {
fn is_prerelease(&self) -> bool {
!self.pre.is_empty()
}

fn to_exact_req(&self) -> VersionReq {
VersionReq {
comparators: vec![Comparator {
op: Op::Exact,
major: self.major,
minor: Some(self.minor),
patch: Some(self.patch),
pre: self.pre.clone(),
}],
}
}
}

impl OptVersionReq {
pub fn exact(version: &Version) -> Self {
OptVersionReq::Req(version.to_exact_req())
Expand Down Expand Up @@ -190,170 +170,3 @@ impl Display for RustVersion {
self.0.fmt(f)
}
}

#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Debug)]
pub struct PartialVersion {
pub major: u64,
pub minor: Option<u64>,
pub patch: Option<u64>,
pub pre: Option<semver::Prerelease>,
pub build: Option<semver::BuildMetadata>,
}

impl PartialVersion {
pub fn to_version(&self) -> Option<Version> {
Some(Version {
major: self.major,
minor: self.minor?,
patch: self.patch?,
pre: self.pre.clone().unwrap_or_default(),
build: self.build.clone().unwrap_or_default(),
})
}

pub fn to_caret_req(&self) -> VersionReq {
VersionReq {
comparators: vec![Comparator {
op: semver::Op::Caret,
major: self.major,
minor: self.minor,
patch: self.patch,
pre: self.pre.as_ref().cloned().unwrap_or_default(),
}],
}
}

/// Check if this matches a version, including build metadata
///
/// Build metadata does not affect version precedence but may be necessary for uniquely
/// identifying a package.
pub fn matches(&self, version: &Version) -> bool {
if !version.pre.is_empty() && self.pre.is_none() {
// Pre-release versions must be explicitly opted into, if for no other reason than to
// give us room to figure out and define the semantics
return false;
}
self.major == version.major
&& self.minor.map(|f| f == version.minor).unwrap_or(true)
&& self.patch.map(|f| f == version.patch).unwrap_or(true)
&& self.pre.as_ref().map(|f| f == &version.pre).unwrap_or(true)
&& self
.build
.as_ref()
.map(|f| f == &version.build)
.unwrap_or(true)
}
}

impl From<semver::Version> for PartialVersion {
fn from(ver: semver::Version) -> Self {
let pre = if ver.pre.is_empty() {
None
} else {
Some(ver.pre)
};
let build = if ver.build.is_empty() {
None
} else {
Some(ver.build)
};
Self {
major: ver.major,
minor: Some(ver.minor),
patch: Some(ver.patch),
pre,
build,
}
}
}

impl std::str::FromStr for PartialVersion {
type Err = anyhow::Error;

fn from_str(value: &str) -> Result<Self, Self::Err> {
if is_req(value) {
anyhow::bail!("unexpected version requirement, expected a version like \"1.32\"")
}
match semver::Version::parse(value) {
Ok(ver) => Ok(ver.into()),
Err(_) => {
// HACK: Leverage `VersionReq` for partial version parsing
let mut version_req = match semver::VersionReq::parse(value) {
Ok(req) => req,
Err(_) if value.contains('-') => {
anyhow::bail!(
"unexpected prerelease field, expected a version like \"1.32\""
)
}
Err(_) if value.contains('+') => {
anyhow::bail!("unexpected build field, expected a version like \"1.32\"")
}
Err(_) => anyhow::bail!("expected a version like \"1.32\""),
};
assert_eq!(version_req.comparators.len(), 1, "guaranteed by is_req");
let comp = version_req.comparators.pop().unwrap();
assert_eq!(comp.op, semver::Op::Caret, "guaranteed by is_req");
let pre = if comp.pre.is_empty() {
None
} else {
Some(comp.pre)
};
Ok(Self {
major: comp.major,
minor: comp.minor,
patch: comp.patch,
pre,
build: None,
})
}
}
}
}

impl Display for PartialVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let major = self.major;
write!(f, "{major}")?;
if let Some(minor) = self.minor {
write!(f, ".{minor}")?;
}
if let Some(patch) = self.patch {
write!(f, ".{patch}")?;
}
if let Some(pre) = self.pre.as_ref() {
write!(f, "-{pre}")?;
}
if let Some(build) = self.build.as_ref() {
write!(f, "+{build}")?;
}
Ok(())
}
}

impl serde::Serialize for PartialVersion {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.collect_str(self)
}
}

impl<'de> serde::Deserialize<'de> for PartialVersion {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
UntaggedEnumVisitor::new()
.expecting("SemVer version")
.string(|value| value.parse().map_err(serde::de::Error::custom))
.deserialize(deserializer)
}
}

fn is_req(value: &str) -> bool {
let Some(first) = value.chars().next() else {
return false;
};
"<>=^~".contains(first) || value.contains('*') || value.contains(',')
}
Loading