Skip to content

Commit

Permalink
refactor(util): Pull out mod util_semver
Browse files Browse the repository at this point in the history
This `mod` is a proposal for what a new package would look like.
This needs to be split out so a future `util_manifest_schema`
package can depend on it (rust-lang#12801).

This doesn't address where `RustVersion` should live (along with
`PackageIdSpec`).

This builds on the work from rust-lang#12924 and rust-lang#12926
  • Loading branch information
epage committed Nov 8, 2023
1 parent 8cf7143 commit 2b2502f
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 197 deletions.
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

0 comments on commit 2b2502f

Please sign in to comment.