mirror of
https://github.com/rust-lang/cargo
synced 2024-10-13 11:12:25 +00:00
Auto merge of #13128 - epage:schema, r=weihanglo
refactor: Pull PackageIdSpec into schema ### What does this PR try to resolve? This removes one of the remaining ties `util_schemas` has on the rest of cargo as part of #12801. ### How should we test and review this PR? This is broken up into small commits ### Additional information
This commit is contained in:
commit
a1449eeb3e
|
@ -1,5 +1,6 @@
|
|||
use cargo::core::dependency::DepKind;
|
||||
use cargo::core::PackageIdSpec;
|
||||
use cargo::core::PackageIdSpecQuery;
|
||||
use cargo::core::Resolve;
|
||||
use cargo::core::Workspace;
|
||||
use cargo::ops::cargo_remove::remove;
|
||||
|
|
|
@ -4,16 +4,17 @@ pub use self::manifest::{EitherManifest, VirtualManifest};
|
|||
pub use self::manifest::{Manifest, Target, TargetKind};
|
||||
pub use self::package::{Package, PackageSet};
|
||||
pub use self::package_id::PackageId;
|
||||
pub use self::package_id_spec::PackageIdSpec;
|
||||
pub use self::package_id_spec::PackageIdSpecQuery;
|
||||
pub use self::registry::Registry;
|
||||
pub use self::resolver::{Resolve, ResolveVersion};
|
||||
pub use self::shell::{Shell, Verbosity};
|
||||
pub use self::source_id::{GitReference, SourceId, SourceKind};
|
||||
pub use self::source_id::SourceId;
|
||||
pub use self::summary::{FeatureMap, FeatureValue, Summary};
|
||||
pub use self::workspace::{
|
||||
find_workspace_root, resolve_relative_path, MaybePackage, Workspace, WorkspaceConfig,
|
||||
WorkspaceRootConfig,
|
||||
};
|
||||
pub use crate::util_schemas::core::{GitReference, PackageIdSpec, SourceKind};
|
||||
|
||||
pub mod compiler;
|
||||
pub mod dependency;
|
||||
|
|
|
@ -10,6 +10,7 @@ use std::sync::OnceLock;
|
|||
use serde::de;
|
||||
use serde::ser;
|
||||
|
||||
use crate::core::PackageIdSpec;
|
||||
use crate::core::SourceId;
|
||||
use crate::util::interning::InternedString;
|
||||
use crate::util::CargoResult;
|
||||
|
@ -186,6 +187,15 @@ impl PackageId {
|
|||
pub fn tarball_name(&self) -> String {
|
||||
format!("{}-{}.crate", self.name(), self.version())
|
||||
}
|
||||
|
||||
/// Convert a `PackageId` to a `PackageIdSpec`, which will have both the `PartialVersion` and `Url`
|
||||
/// fields filled in.
|
||||
pub fn to_spec(&self) -> PackageIdSpec {
|
||||
PackageIdSpec::new(String::from(self.name().as_str()))
|
||||
.with_version(self.version().clone().into())
|
||||
.with_url(self.source_id().url().clone())
|
||||
.with_kind(self.source_id().kind().clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PackageIdStableHash<'a>(PackageId, &'a Path);
|
||||
|
|
|
@ -1,92 +1,30 @@
|
|||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
|
||||
use anyhow::{bail, Context as _};
|
||||
use semver::Version;
|
||||
use serde::{de, ser};
|
||||
use url::Url;
|
||||
|
||||
use crate::core::GitReference;
|
||||
use crate::core::PackageId;
|
||||
use crate::core::SourceKind;
|
||||
use crate::core::PackageIdSpec;
|
||||
use crate::util::edit_distance;
|
||||
use crate::util::errors::CargoResult;
|
||||
use crate::util::{validate_package_name, IntoUrl};
|
||||
use crate::util_semver::PartialVersion;
|
||||
|
||||
/// Some or all of the data required to identify a package:
|
||||
///
|
||||
/// 1. the package name (a `String`, required)
|
||||
/// 2. the package version (a `Version`, optional)
|
||||
/// 3. the package source (a `Url`, optional)
|
||||
///
|
||||
/// If any of the optional fields are omitted, then the package ID may be ambiguous, there may be
|
||||
/// more than one package/version/url combo that will match. However, often just the name is
|
||||
/// sufficient to uniquely define a package ID.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)]
|
||||
pub struct PackageIdSpec {
|
||||
name: String,
|
||||
version: Option<PartialVersion>,
|
||||
url: Option<Url>,
|
||||
kind: Option<SourceKind>,
|
||||
pub trait PackageIdSpecQuery {
|
||||
/// Roughly equivalent to `PackageIdSpec::parse(spec)?.query(i)`
|
||||
fn query_str<I>(spec: &str, i: I) -> CargoResult<PackageId>
|
||||
where
|
||||
I: IntoIterator<Item = PackageId>;
|
||||
|
||||
/// Checks whether the given `PackageId` matches the `PackageIdSpec`.
|
||||
fn matches(&self, package_id: PackageId) -> bool;
|
||||
|
||||
/// Checks a list of `PackageId`s to find 1 that matches this `PackageIdSpec`. If 0, 2, or
|
||||
/// more are found, then this returns an error.
|
||||
fn query<I>(&self, i: I) -> CargoResult<PackageId>
|
||||
where
|
||||
I: IntoIterator<Item = PackageId>;
|
||||
}
|
||||
|
||||
impl PackageIdSpec {
|
||||
/// Parses a spec string and returns a `PackageIdSpec` if the string was valid.
|
||||
///
|
||||
/// # Examples
|
||||
/// Some examples of valid strings
|
||||
///
|
||||
/// ```
|
||||
/// use cargo::core::PackageIdSpec;
|
||||
///
|
||||
/// let specs = vec![
|
||||
/// "https://crates.io/foo",
|
||||
/// "https://crates.io/foo#1.2.3",
|
||||
/// "https://crates.io/foo#bar:1.2.3",
|
||||
/// "https://crates.io/foo#bar@1.2.3",
|
||||
/// "foo",
|
||||
/// "foo:1.2.3",
|
||||
/// "foo@1.2.3",
|
||||
/// ];
|
||||
/// for spec in specs {
|
||||
/// assert!(PackageIdSpec::parse(spec).is_ok());
|
||||
/// }
|
||||
pub fn parse(spec: &str) -> CargoResult<PackageIdSpec> {
|
||||
if spec.contains("://") {
|
||||
if let Ok(url) = spec.into_url() {
|
||||
return PackageIdSpec::from_url(url);
|
||||
}
|
||||
} else if spec.contains('/') || spec.contains('\\') {
|
||||
let abs = std::env::current_dir().unwrap_or_default().join(spec);
|
||||
if abs.exists() {
|
||||
let maybe_url = Url::from_file_path(abs)
|
||||
.map_or_else(|_| "a file:// URL".to_string(), |url| url.to_string());
|
||||
bail!(
|
||||
"package ID specification `{}` looks like a file path, \
|
||||
maybe try {}",
|
||||
spec,
|
||||
maybe_url
|
||||
);
|
||||
}
|
||||
}
|
||||
let mut parts = spec.splitn(2, [':', '@']);
|
||||
let name = parts.next().unwrap();
|
||||
let version = match parts.next() {
|
||||
Some(version) => Some(version.parse::<PartialVersion>()?),
|
||||
None => None,
|
||||
};
|
||||
validate_package_name(name, "pkgid", "")?;
|
||||
Ok(PackageIdSpec {
|
||||
name: String::from(name),
|
||||
version,
|
||||
url: None,
|
||||
kind: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Roughly equivalent to `PackageIdSpec::parse(spec)?.query(i)`
|
||||
pub fn query_str<I>(spec: &str, i: I) -> CargoResult<PackageId>
|
||||
impl PackageIdSpecQuery for PackageIdSpec {
|
||||
fn query_str<I>(spec: &str, i: I) -> CargoResult<PackageId>
|
||||
where
|
||||
I: IntoIterator<Item = PackageId>,
|
||||
{
|
||||
|
@ -98,150 +36,25 @@ impl PackageIdSpec {
|
|||
spec.query(i)
|
||||
}
|
||||
|
||||
/// Convert a `PackageId` to a `PackageIdSpec`, which will have both the `PartialVersion` and `Url`
|
||||
/// fields filled in.
|
||||
pub fn from_package_id(package_id: PackageId) -> PackageIdSpec {
|
||||
PackageIdSpec {
|
||||
name: String::from(package_id.name().as_str()),
|
||||
version: Some(package_id.version().clone().into()),
|
||||
url: Some(package_id.source_id().url().clone()),
|
||||
kind: Some(package_id.source_id().kind().clone()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to convert a valid `Url` to a `PackageIdSpec`.
|
||||
fn from_url(mut url: Url) -> CargoResult<PackageIdSpec> {
|
||||
let mut kind = None;
|
||||
if let Some((kind_str, scheme)) = url.scheme().split_once('+') {
|
||||
match kind_str {
|
||||
"git" => {
|
||||
let git_ref = GitReference::from_query(url.query_pairs());
|
||||
url.set_query(None);
|
||||
kind = Some(SourceKind::Git(git_ref));
|
||||
url = strip_url_protocol(&url);
|
||||
}
|
||||
"registry" => {
|
||||
if url.query().is_some() {
|
||||
bail!("cannot have a query string in a pkgid: {url}")
|
||||
}
|
||||
kind = Some(SourceKind::Registry);
|
||||
url = strip_url_protocol(&url);
|
||||
}
|
||||
"sparse" => {
|
||||
if url.query().is_some() {
|
||||
bail!("cannot have a query string in a pkgid: {url}")
|
||||
}
|
||||
kind = Some(SourceKind::SparseRegistry);
|
||||
// Leave `sparse` as part of URL, see `SourceId::new`
|
||||
// url = strip_url_protocol(&url);
|
||||
}
|
||||
"path" => {
|
||||
if url.query().is_some() {
|
||||
bail!("cannot have a query string in a pkgid: {url}")
|
||||
}
|
||||
if scheme != "file" {
|
||||
anyhow::bail!("`path+{scheme}` is unsupported; `path+file` and `file` schemes are supported");
|
||||
}
|
||||
kind = Some(SourceKind::Path);
|
||||
url = strip_url_protocol(&url);
|
||||
}
|
||||
kind => anyhow::bail!("unsupported source protocol: {kind}"),
|
||||
}
|
||||
} else {
|
||||
if url.query().is_some() {
|
||||
bail!("cannot have a query string in a pkgid: {url}")
|
||||
}
|
||||
}
|
||||
|
||||
let frag = url.fragment().map(|s| s.to_owned());
|
||||
url.set_fragment(None);
|
||||
|
||||
let (name, version) = {
|
||||
let mut path = url
|
||||
.path_segments()
|
||||
.ok_or_else(|| anyhow::format_err!("pkgid urls must have a path: {}", url))?;
|
||||
let path_name = path.next_back().ok_or_else(|| {
|
||||
anyhow::format_err!(
|
||||
"pkgid urls must have at least one path \
|
||||
component: {}",
|
||||
url
|
||||
)
|
||||
})?;
|
||||
match frag {
|
||||
Some(fragment) => match fragment.split_once([':', '@']) {
|
||||
Some((name, part)) => {
|
||||
let version = part.parse::<PartialVersion>()?;
|
||||
(String::from(name), Some(version))
|
||||
}
|
||||
None => {
|
||||
if fragment.chars().next().unwrap().is_alphabetic() {
|
||||
(String::from(fragment.as_str()), None)
|
||||
} else {
|
||||
let version = fragment.parse::<PartialVersion>()?;
|
||||
(String::from(path_name), Some(version))
|
||||
}
|
||||
}
|
||||
},
|
||||
None => (String::from(path_name), None),
|
||||
}
|
||||
};
|
||||
Ok(PackageIdSpec {
|
||||
name,
|
||||
version,
|
||||
url: Some(url),
|
||||
kind,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
self.name.as_str()
|
||||
}
|
||||
|
||||
/// Full `semver::Version`, if present
|
||||
pub fn version(&self) -> Option<Version> {
|
||||
self.version.as_ref().and_then(|v| v.to_version())
|
||||
}
|
||||
|
||||
pub fn partial_version(&self) -> Option<&PartialVersion> {
|
||||
self.version.as_ref()
|
||||
}
|
||||
|
||||
pub fn url(&self) -> Option<&Url> {
|
||||
self.url.as_ref()
|
||||
}
|
||||
|
||||
pub fn set_url(&mut self, url: Url) {
|
||||
self.url = Some(url);
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> Option<&SourceKind> {
|
||||
self.kind.as_ref()
|
||||
}
|
||||
|
||||
pub fn set_kind(&mut self, kind: SourceKind) {
|
||||
self.kind = Some(kind);
|
||||
}
|
||||
|
||||
/// Checks whether the given `PackageId` matches the `PackageIdSpec`.
|
||||
pub fn matches(&self, package_id: PackageId) -> bool {
|
||||
fn matches(&self, package_id: PackageId) -> bool {
|
||||
if self.name() != package_id.name().as_str() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(ref v) = self.version {
|
||||
if let Some(ref v) = self.partial_version() {
|
||||
if !v.matches(package_id.version()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(u) = &self.url {
|
||||
if u != package_id.source_id().url() {
|
||||
if let Some(u) = &self.url() {
|
||||
if *u != package_id.source_id().url() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(k) = &self.kind {
|
||||
if k != package_id.source_id().kind() {
|
||||
if let Some(k) = &self.kind() {
|
||||
if *k != package_id.source_id().kind() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -249,9 +62,7 @@ impl PackageIdSpec {
|
|||
true
|
||||
}
|
||||
|
||||
/// Checks a list of `PackageId`s to find 1 that matches this `PackageIdSpec`. If 0, 2, or
|
||||
/// more are found, then this returns an error.
|
||||
pub fn query<I>(&self, i: I) -> CargoResult<PackageId>
|
||||
fn query<I>(&self, i: I) -> CargoResult<PackageId>
|
||||
where
|
||||
I: IntoIterator<Item = PackageId>,
|
||||
{
|
||||
|
@ -270,31 +81,21 @@ impl PackageIdSpec {
|
|||
minimize(suggestion, &try_matches, self);
|
||||
}
|
||||
};
|
||||
if self.url.is_some() {
|
||||
try_spec(
|
||||
PackageIdSpec {
|
||||
name: self.name.clone(),
|
||||
version: self.version.clone(),
|
||||
url: None,
|
||||
kind: None,
|
||||
},
|
||||
&mut suggestion,
|
||||
);
|
||||
if self.url().is_some() {
|
||||
let spec = PackageIdSpec::new(self.name().to_owned());
|
||||
let spec = if let Some(version) = self.partial_version().cloned() {
|
||||
spec.with_version(version)
|
||||
} else {
|
||||
spec
|
||||
};
|
||||
try_spec(spec, &mut suggestion);
|
||||
}
|
||||
if suggestion.is_empty() && self.version.is_some() {
|
||||
try_spec(
|
||||
PackageIdSpec {
|
||||
name: self.name.clone(),
|
||||
version: None,
|
||||
url: None,
|
||||
kind: None,
|
||||
},
|
||||
&mut suggestion,
|
||||
);
|
||||
if suggestion.is_empty() && self.version().is_some() {
|
||||
try_spec(PackageIdSpec::new(self.name().to_owned()), &mut suggestion);
|
||||
}
|
||||
if suggestion.is_empty() {
|
||||
suggestion.push_str(&edit_distance::closest_msg(
|
||||
&self.name,
|
||||
self.name(),
|
||||
all_ids.iter(),
|
||||
|id| id.name().as_str(),
|
||||
));
|
||||
|
@ -335,380 +136,20 @@ impl PackageIdSpec {
|
|||
if version_cnt[id.version()] == 1 {
|
||||
msg.push_str(&format!("\n {}@{}", spec.name(), id.version()));
|
||||
} else {
|
||||
msg.push_str(&format!("\n {}", PackageIdSpec::from_package_id(*id)));
|
||||
msg.push_str(&format!("\n {}", id.to_spec()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn strip_url_protocol(url: &Url) -> Url {
|
||||
// Ridiculous hoop because `Url::set_scheme` errors when changing to http/https
|
||||
let raw = url.to_string();
|
||||
raw.split_once('+').unwrap().1.parse().unwrap()
|
||||
}
|
||||
|
||||
impl fmt::Display for PackageIdSpec {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut printed_name = false;
|
||||
match self.url {
|
||||
Some(ref url) => {
|
||||
if let Some(protocol) = self.kind.as_ref().and_then(|k| k.protocol()) {
|
||||
write!(f, "{protocol}+")?;
|
||||
}
|
||||
write!(f, "{}", url)?;
|
||||
if let Some(SourceKind::Git(git_ref)) = self.kind.as_ref() {
|
||||
if let Some(pretty) = git_ref.pretty_ref(true) {
|
||||
write!(f, "?{}", pretty)?;
|
||||
}
|
||||
}
|
||||
if url.path_segments().unwrap().next_back().unwrap() != &*self.name {
|
||||
printed_name = true;
|
||||
write!(f, "#{}", self.name)?;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
printed_name = true;
|
||||
write!(f, "{}", self.name)?;
|
||||
}
|
||||
}
|
||||
if let Some(ref v) = self.version {
|
||||
write!(f, "{}{}", if printed_name { "@" } else { "#" }, v)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ser::Serialize for PackageIdSpec {
|
||||
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: ser::Serializer,
|
||||
{
|
||||
self.to_string().serialize(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> de::Deserialize<'de> for PackageIdSpec {
|
||||
fn deserialize<D>(d: D) -> Result<PackageIdSpec, D::Error>
|
||||
where
|
||||
D: de::Deserializer<'de>,
|
||||
{
|
||||
let string = String::deserialize(d)?;
|
||||
PackageIdSpec::parse(&string).map_err(de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::PackageIdSpec;
|
||||
use crate::core::{GitReference, PackageId, SourceId, SourceKind};
|
||||
use super::PackageIdSpecQuery;
|
||||
use crate::core::{PackageId, SourceId};
|
||||
use url::Url;
|
||||
|
||||
#[test]
|
||||
fn good_parsing() {
|
||||
#[track_caller]
|
||||
fn ok(spec: &str, expected: PackageIdSpec, expected_rendered: &str) {
|
||||
let parsed = PackageIdSpec::parse(spec).unwrap();
|
||||
assert_eq!(parsed, expected);
|
||||
let rendered = parsed.to_string();
|
||||
assert_eq!(rendered, expected_rendered);
|
||||
let reparsed = PackageIdSpec::parse(&rendered).unwrap();
|
||||
assert_eq!(reparsed, expected);
|
||||
}
|
||||
|
||||
ok(
|
||||
"https://crates.io/foo",
|
||||
PackageIdSpec {
|
||||
name: String::from("foo"),
|
||||
version: None,
|
||||
url: Some(Url::parse("https://crates.io/foo").unwrap()),
|
||||
kind: None,
|
||||
},
|
||||
"https://crates.io/foo",
|
||||
);
|
||||
ok(
|
||||
"https://crates.io/foo#1.2.3",
|
||||
PackageIdSpec {
|
||||
name: String::from("foo"),
|
||||
version: Some("1.2.3".parse().unwrap()),
|
||||
url: Some(Url::parse("https://crates.io/foo").unwrap()),
|
||||
kind: None,
|
||||
},
|
||||
"https://crates.io/foo#1.2.3",
|
||||
);
|
||||
ok(
|
||||
"https://crates.io/foo#1.2",
|
||||
PackageIdSpec {
|
||||
name: String::from("foo"),
|
||||
version: Some("1.2".parse().unwrap()),
|
||||
url: Some(Url::parse("https://crates.io/foo").unwrap()),
|
||||
kind: None,
|
||||
},
|
||||
"https://crates.io/foo#1.2",
|
||||
);
|
||||
ok(
|
||||
"https://crates.io/foo#bar:1.2.3",
|
||||
PackageIdSpec {
|
||||
name: String::from("bar"),
|
||||
version: Some("1.2.3".parse().unwrap()),
|
||||
url: Some(Url::parse("https://crates.io/foo").unwrap()),
|
||||
kind: None,
|
||||
},
|
||||
"https://crates.io/foo#bar@1.2.3",
|
||||
);
|
||||
ok(
|
||||
"https://crates.io/foo#bar@1.2.3",
|
||||
PackageIdSpec {
|
||||
name: String::from("bar"),
|
||||
version: Some("1.2.3".parse().unwrap()),
|
||||
url: Some(Url::parse("https://crates.io/foo").unwrap()),
|
||||
kind: None,
|
||||
},
|
||||
"https://crates.io/foo#bar@1.2.3",
|
||||
);
|
||||
ok(
|
||||
"https://crates.io/foo#bar@1.2",
|
||||
PackageIdSpec {
|
||||
name: String::from("bar"),
|
||||
version: Some("1.2".parse().unwrap()),
|
||||
url: Some(Url::parse("https://crates.io/foo").unwrap()),
|
||||
kind: None,
|
||||
},
|
||||
"https://crates.io/foo#bar@1.2",
|
||||
);
|
||||
ok(
|
||||
"registry+https://crates.io/foo#bar@1.2",
|
||||
PackageIdSpec {
|
||||
name: String::from("bar"),
|
||||
version: Some("1.2".parse().unwrap()),
|
||||
url: Some(Url::parse("https://crates.io/foo").unwrap()),
|
||||
kind: Some(SourceKind::Registry),
|
||||
},
|
||||
"registry+https://crates.io/foo#bar@1.2",
|
||||
);
|
||||
ok(
|
||||
"sparse+https://crates.io/foo#bar@1.2",
|
||||
PackageIdSpec {
|
||||
name: String::from("bar"),
|
||||
version: Some("1.2".parse().unwrap()),
|
||||
url: Some(Url::parse("sparse+https://crates.io/foo").unwrap()),
|
||||
kind: Some(SourceKind::SparseRegistry),
|
||||
},
|
||||
"sparse+https://crates.io/foo#bar@1.2",
|
||||
);
|
||||
ok(
|
||||
"foo",
|
||||
PackageIdSpec {
|
||||
name: String::from("foo"),
|
||||
version: None,
|
||||
url: None,
|
||||
kind: None,
|
||||
},
|
||||
"foo",
|
||||
);
|
||||
ok(
|
||||
"foo:1.2.3",
|
||||
PackageIdSpec {
|
||||
name: String::from("foo"),
|
||||
version: Some("1.2.3".parse().unwrap()),
|
||||
url: None,
|
||||
kind: None,
|
||||
},
|
||||
"foo@1.2.3",
|
||||
);
|
||||
ok(
|
||||
"foo@1.2.3",
|
||||
PackageIdSpec {
|
||||
name: String::from("foo"),
|
||||
version: Some("1.2.3".parse().unwrap()),
|
||||
url: None,
|
||||
kind: None,
|
||||
},
|
||||
"foo@1.2.3",
|
||||
);
|
||||
ok(
|
||||
"foo@1.2",
|
||||
PackageIdSpec {
|
||||
name: String::from("foo"),
|
||||
version: Some("1.2".parse().unwrap()),
|
||||
url: None,
|
||||
kind: None,
|
||||
},
|
||||
"foo@1.2",
|
||||
);
|
||||
|
||||
// pkgid-spec.md
|
||||
ok(
|
||||
"regex",
|
||||
PackageIdSpec {
|
||||
name: String::from("regex"),
|
||||
version: None,
|
||||
url: None,
|
||||
kind: None,
|
||||
},
|
||||
"regex",
|
||||
);
|
||||
ok(
|
||||
"regex@1.4",
|
||||
PackageIdSpec {
|
||||
name: String::from("regex"),
|
||||
version: Some("1.4".parse().unwrap()),
|
||||
url: None,
|
||||
kind: None,
|
||||
},
|
||||
"regex@1.4",
|
||||
);
|
||||
ok(
|
||||
"regex@1.4.3",
|
||||
PackageIdSpec {
|
||||
name: String::from("regex"),
|
||||
version: Some("1.4.3".parse().unwrap()),
|
||||
url: None,
|
||||
kind: None,
|
||||
},
|
||||
"regex@1.4.3",
|
||||
);
|
||||
ok(
|
||||
"https://github.com/rust-lang/crates.io-index#regex",
|
||||
PackageIdSpec {
|
||||
name: String::from("regex"),
|
||||
version: None,
|
||||
url: Some(Url::parse("https://github.com/rust-lang/crates.io-index").unwrap()),
|
||||
kind: None,
|
||||
},
|
||||
"https://github.com/rust-lang/crates.io-index#regex",
|
||||
);
|
||||
ok(
|
||||
"https://github.com/rust-lang/crates.io-index#regex@1.4.3",
|
||||
PackageIdSpec {
|
||||
name: String::from("regex"),
|
||||
version: Some("1.4.3".parse().unwrap()),
|
||||
url: Some(Url::parse("https://github.com/rust-lang/crates.io-index").unwrap()),
|
||||
kind: None,
|
||||
},
|
||||
"https://github.com/rust-lang/crates.io-index#regex@1.4.3",
|
||||
);
|
||||
ok(
|
||||
"sparse+https://github.com/rust-lang/crates.io-index#regex@1.4.3",
|
||||
PackageIdSpec {
|
||||
name: String::from("regex"),
|
||||
version: Some("1.4.3".parse().unwrap()),
|
||||
url: Some(
|
||||
Url::parse("sparse+https://github.com/rust-lang/crates.io-index").unwrap(),
|
||||
),
|
||||
kind: Some(SourceKind::SparseRegistry),
|
||||
},
|
||||
"sparse+https://github.com/rust-lang/crates.io-index#regex@1.4.3",
|
||||
);
|
||||
ok(
|
||||
"https://github.com/rust-lang/cargo#0.52.0",
|
||||
PackageIdSpec {
|
||||
name: String::from("cargo"),
|
||||
version: Some("0.52.0".parse().unwrap()),
|
||||
url: Some(Url::parse("https://github.com/rust-lang/cargo").unwrap()),
|
||||
kind: None,
|
||||
},
|
||||
"https://github.com/rust-lang/cargo#0.52.0",
|
||||
);
|
||||
ok(
|
||||
"https://github.com/rust-lang/cargo#cargo-platform@0.1.2",
|
||||
PackageIdSpec {
|
||||
name: String::from("cargo-platform"),
|
||||
version: Some("0.1.2".parse().unwrap()),
|
||||
url: Some(Url::parse("https://github.com/rust-lang/cargo").unwrap()),
|
||||
kind: None,
|
||||
},
|
||||
"https://github.com/rust-lang/cargo#cargo-platform@0.1.2",
|
||||
);
|
||||
ok(
|
||||
"ssh://git@github.com/rust-lang/regex.git#regex@1.4.3",
|
||||
PackageIdSpec {
|
||||
name: String::from("regex"),
|
||||
version: Some("1.4.3".parse().unwrap()),
|
||||
url: Some(Url::parse("ssh://git@github.com/rust-lang/regex.git").unwrap()),
|
||||
kind: None,
|
||||
},
|
||||
"ssh://git@github.com/rust-lang/regex.git#regex@1.4.3",
|
||||
);
|
||||
ok(
|
||||
"git+ssh://git@github.com/rust-lang/regex.git#regex@1.4.3",
|
||||
PackageIdSpec {
|
||||
name: String::from("regex"),
|
||||
version: Some("1.4.3".parse().unwrap()),
|
||||
url: Some(Url::parse("ssh://git@github.com/rust-lang/regex.git").unwrap()),
|
||||
kind: Some(SourceKind::Git(GitReference::DefaultBranch)),
|
||||
},
|
||||
"git+ssh://git@github.com/rust-lang/regex.git#regex@1.4.3",
|
||||
);
|
||||
ok(
|
||||
"git+ssh://git@github.com/rust-lang/regex.git?branch=dev#regex@1.4.3",
|
||||
PackageIdSpec {
|
||||
name: String::from("regex"),
|
||||
version: Some("1.4.3".parse().unwrap()),
|
||||
url: Some(Url::parse("ssh://git@github.com/rust-lang/regex.git").unwrap()),
|
||||
kind: Some(SourceKind::Git(GitReference::Branch("dev".to_owned()))),
|
||||
},
|
||||
"git+ssh://git@github.com/rust-lang/regex.git?branch=dev#regex@1.4.3",
|
||||
);
|
||||
ok(
|
||||
"file:///path/to/my/project/foo",
|
||||
PackageIdSpec {
|
||||
name: String::from("foo"),
|
||||
version: None,
|
||||
url: Some(Url::parse("file:///path/to/my/project/foo").unwrap()),
|
||||
kind: None,
|
||||
},
|
||||
"file:///path/to/my/project/foo",
|
||||
);
|
||||
ok(
|
||||
"file:///path/to/my/project/foo#1.1.8",
|
||||
PackageIdSpec {
|
||||
name: String::from("foo"),
|
||||
version: Some("1.1.8".parse().unwrap()),
|
||||
url: Some(Url::parse("file:///path/to/my/project/foo").unwrap()),
|
||||
kind: None,
|
||||
},
|
||||
"file:///path/to/my/project/foo#1.1.8",
|
||||
);
|
||||
ok(
|
||||
"path+file:///path/to/my/project/foo#1.1.8",
|
||||
PackageIdSpec {
|
||||
name: String::from("foo"),
|
||||
version: Some("1.1.8".parse().unwrap()),
|
||||
url: Some(Url::parse("file:///path/to/my/project/foo").unwrap()),
|
||||
kind: Some(SourceKind::Path),
|
||||
},
|
||||
"path+file:///path/to/my/project/foo#1.1.8",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_parsing() {
|
||||
assert!(PackageIdSpec::parse("baz:").is_err());
|
||||
assert!(PackageIdSpec::parse("baz:*").is_err());
|
||||
assert!(PackageIdSpec::parse("baz@").is_err());
|
||||
assert!(PackageIdSpec::parse("baz@*").is_err());
|
||||
assert!(PackageIdSpec::parse("baz@^1.0").is_err());
|
||||
assert!(PackageIdSpec::parse("https://baz:1.0").is_err());
|
||||
assert!(PackageIdSpec::parse("https://#baz:1.0").is_err());
|
||||
assert!(
|
||||
PackageIdSpec::parse("foobar+https://github.com/rust-lang/crates.io-index").is_err()
|
||||
);
|
||||
assert!(PackageIdSpec::parse("path+https://github.com/rust-lang/crates.io-index").is_err());
|
||||
|
||||
// Only `git+` can use `?`
|
||||
assert!(PackageIdSpec::parse("file:///path/to/my/project/foo?branch=dev").is_err());
|
||||
assert!(PackageIdSpec::parse("path+file:///path/to/my/project/foo?branch=dev").is_err());
|
||||
assert!(PackageIdSpec::parse(
|
||||
"registry+https://github.com/rust-lang/cargo#0.52.0?branch=dev"
|
||||
)
|
||||
.is_err());
|
||||
assert!(PackageIdSpec::parse(
|
||||
"sparse+https://github.com/rust-lang/cargo#0.52.0?branch=dev"
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matching() {
|
||||
let url = Url::parse("https://example.com").unwrap();
|
||||
|
|
|
@ -25,7 +25,9 @@ use crate::core::compiler::{CompileKind, CompileTarget, Unit};
|
|||
use crate::core::dependency::Artifact;
|
||||
use crate::core::resolver::features::FeaturesFor;
|
||||
use crate::core::Feature;
|
||||
use crate::core::{PackageId, PackageIdSpec, Resolve, Shell, Target, Workspace};
|
||||
use crate::core::{
|
||||
PackageId, PackageIdSpec, PackageIdSpecQuery, Resolve, Shell, Target, Workspace,
|
||||
};
|
||||
use crate::util::interning::InternedString;
|
||||
use crate::util::toml::validate_profile;
|
||||
use crate::util::{closest_msg, config, CargoResult, Config};
|
||||
|
|
|
@ -16,7 +16,9 @@ use crate::core::resolver::{
|
|||
ActivateError, ActivateResult, CliFeatures, RequestedFeatures, ResolveOpts, VersionOrdering,
|
||||
VersionPreferences,
|
||||
};
|
||||
use crate::core::{Dependency, FeatureValue, PackageId, PackageIdSpec, Registry, Summary};
|
||||
use crate::core::{
|
||||
Dependency, FeatureValue, PackageId, PackageIdSpec, PackageIdSpecQuery, Registry, Summary,
|
||||
};
|
||||
use crate::sources::source::QueryKind;
|
||||
use crate::util::errors::CargoResult;
|
||||
use crate::util::interning::InternedString;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use super::encode::Metadata;
|
||||
use crate::core::dependency::DepKind;
|
||||
use crate::core::{Dependency, PackageId, PackageIdSpec, Summary, Target};
|
||||
use crate::core::{Dependency, PackageId, PackageIdSpec, PackageIdSpecQuery, Summary, Target};
|
||||
use crate::util::errors::CargoResult;
|
||||
use crate::util::interning::InternedString;
|
||||
use crate::util::Graph;
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use crate::core::GitReference;
|
||||
use crate::core::PackageId;
|
||||
use crate::core::SourceKind;
|
||||
use crate::sources::registry::CRATES_IO_HTTP_INDEX;
|
||||
use crate::sources::source::Source;
|
||||
use crate::sources::{DirectorySource, CRATES_IO_DOMAIN, CRATES_IO_INDEX, CRATES_IO_REGISTRY};
|
||||
|
@ -82,38 +84,6 @@ impl fmt::Display for Precise {
|
|||
}
|
||||
}
|
||||
|
||||
/// The possible kinds of code source.
|
||||
/// Along with [`SourceIdInner`], this fully defines the source.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum SourceKind {
|
||||
/// A git repository.
|
||||
Git(GitReference),
|
||||
/// A local path.
|
||||
Path,
|
||||
/// A remote registry.
|
||||
Registry,
|
||||
/// A sparse registry.
|
||||
SparseRegistry,
|
||||
/// A local filesystem-based registry.
|
||||
LocalRegistry,
|
||||
/// A directory-based registry.
|
||||
Directory,
|
||||
}
|
||||
|
||||
/// Information to find a specific commit in a Git repository.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum GitReference {
|
||||
/// From a tag.
|
||||
Tag(String),
|
||||
/// From a branch.
|
||||
Branch(String),
|
||||
/// From a specific revision. Can be a commit hash (either short or full),
|
||||
/// or a named reference like `refs/pull/493/head`.
|
||||
Rev(String),
|
||||
/// The default branch of the repository, the reference named `HEAD`.
|
||||
DefaultBranch,
|
||||
}
|
||||
|
||||
/// Where the remote source key is defined.
|
||||
///
|
||||
/// The purpose of this is to provide better diagnostics for different sources of keys.
|
||||
|
@ -746,108 +716,6 @@ impl PartialEq for SourceIdInner {
|
|||
}
|
||||
}
|
||||
|
||||
impl SourceKind {
|
||||
pub(crate) fn protocol(&self) -> Option<&str> {
|
||||
match self {
|
||||
SourceKind::Path => Some("path"),
|
||||
SourceKind::Git(_) => Some("git"),
|
||||
SourceKind::Registry => Some("registry"),
|
||||
// Sparse registry URL already includes the `sparse+` prefix, see `SourceId::new`
|
||||
SourceKind::SparseRegistry => None,
|
||||
SourceKind::LocalRegistry => Some("local-registry"),
|
||||
SourceKind::Directory => Some("directory"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Forwards to `Ord`
|
||||
impl PartialOrd for SourceKind {
|
||||
fn partial_cmp(&self, other: &SourceKind) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
/// Note that this is specifically not derived on `SourceKind` although the
|
||||
/// implementation here is very similar to what it might look like if it were
|
||||
/// otherwise derived.
|
||||
///
|
||||
/// The reason for this is somewhat obtuse. First of all the hash value of
|
||||
/// `SourceKind` makes its way into `~/.cargo/registry/index/github.com-XXXX`
|
||||
/// which means that changes to the hash means that all Rust users need to
|
||||
/// redownload the crates.io index and all their crates. If possible we strive
|
||||
/// to not change this to make this redownloading behavior happen as little as
|
||||
/// possible. How is this connected to `Ord` you might ask? That's a good
|
||||
/// question!
|
||||
///
|
||||
/// Since the beginning of time `SourceKind` has had `#[derive(Hash)]`. It for
|
||||
/// the longest time *also* derived the `Ord` and `PartialOrd` traits. In #8522,
|
||||
/// however, the implementation of `Ord` changed. This handwritten implementation
|
||||
/// forgot to sync itself with the originally derived implementation, namely
|
||||
/// placing git dependencies as sorted after all other dependencies instead of
|
||||
/// first as before.
|
||||
///
|
||||
/// This regression in #8522 (Rust 1.47) went unnoticed. When we switched back
|
||||
/// to a derived implementation in #9133 (Rust 1.52 beta) we only then ironically
|
||||
/// saw an issue (#9334). In #9334 it was observed that stable Rust at the time
|
||||
/// (1.51) was sorting git dependencies last, whereas Rust 1.52 beta would sort
|
||||
/// git dependencies first. This is because the `PartialOrd` implementation in
|
||||
/// 1.51 used #8522, the buggy implementation, which put git deps last. In 1.52
|
||||
/// it was (unknowingly) restored to the pre-1.47 behavior with git dependencies
|
||||
/// first.
|
||||
///
|
||||
/// Because the breakage was only witnessed after the original breakage, this
|
||||
/// trait implementation is preserving the "broken" behavior. Put a different way:
|
||||
///
|
||||
/// * Rust pre-1.47 sorted git deps first.
|
||||
/// * Rust 1.47 to Rust 1.51 sorted git deps last, a breaking change (#8522) that
|
||||
/// was never noticed.
|
||||
/// * Rust 1.52 restored the pre-1.47 behavior (#9133, without knowing it did
|
||||
/// so), and breakage was witnessed by actual users due to difference with
|
||||
/// 1.51.
|
||||
/// * Rust 1.52 (the source as it lives now) was fixed to match the 1.47-1.51
|
||||
/// behavior (#9383), which is now considered intentionally breaking from the
|
||||
/// pre-1.47 behavior.
|
||||
///
|
||||
/// Note that this was all discovered when Rust 1.53 was in nightly and 1.52 was
|
||||
/// in beta. #9133 was in both beta and nightly at the time of discovery. For
|
||||
/// 1.52 #9383 reverted #9133, meaning 1.52 is the same as 1.51. On nightly
|
||||
/// (1.53) #9397 was created to fix the regression introduced by #9133 relative
|
||||
/// to the current stable (1.51).
|
||||
///
|
||||
/// That's all a long winded way of saying "it's weird that git deps hash first
|
||||
/// and are sorted last, but it's the way it is right now". The author of this
|
||||
/// comment chose to handwrite the `Ord` implementation instead of the `Hash`
|
||||
/// implementation, but it's only required that at most one of them is
|
||||
/// hand-written because the other can be derived. Perhaps one day in
|
||||
/// the future someone can figure out how to remove this behavior.
|
||||
impl Ord for SourceKind {
|
||||
fn cmp(&self, other: &SourceKind) -> Ordering {
|
||||
match (self, other) {
|
||||
(SourceKind::Path, SourceKind::Path) => Ordering::Equal,
|
||||
(SourceKind::Path, _) => Ordering::Less,
|
||||
(_, SourceKind::Path) => Ordering::Greater,
|
||||
|
||||
(SourceKind::Registry, SourceKind::Registry) => Ordering::Equal,
|
||||
(SourceKind::Registry, _) => Ordering::Less,
|
||||
(_, SourceKind::Registry) => Ordering::Greater,
|
||||
|
||||
(SourceKind::SparseRegistry, SourceKind::SparseRegistry) => Ordering::Equal,
|
||||
(SourceKind::SparseRegistry, _) => Ordering::Less,
|
||||
(_, SourceKind::SparseRegistry) => Ordering::Greater,
|
||||
|
||||
(SourceKind::LocalRegistry, SourceKind::LocalRegistry) => Ordering::Equal,
|
||||
(SourceKind::LocalRegistry, _) => Ordering::Less,
|
||||
(_, SourceKind::LocalRegistry) => Ordering::Greater,
|
||||
|
||||
(SourceKind::Directory, SourceKind::Directory) => Ordering::Equal,
|
||||
(SourceKind::Directory, _) => Ordering::Less,
|
||||
(_, SourceKind::Directory) => Ordering::Greater,
|
||||
|
||||
(SourceKind::Git(a), SourceKind::Git(b)) => a.cmp(b),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A `Display`able view into a `SourceId` that will write it as a url
|
||||
pub struct SourceIdAsUrl<'a> {
|
||||
inner: &'a SourceIdInner,
|
||||
|
@ -877,73 +745,6 @@ impl<'a> fmt::Display for SourceIdAsUrl<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl GitReference {
|
||||
pub fn from_query(
|
||||
query_pairs: impl Iterator<Item = (impl AsRef<str>, impl AsRef<str>)>,
|
||||
) -> Self {
|
||||
let mut reference = GitReference::DefaultBranch;
|
||||
for (k, v) in query_pairs {
|
||||
let v = v.as_ref();
|
||||
match k.as_ref() {
|
||||
// Map older 'ref' to branch.
|
||||
"branch" | "ref" => reference = GitReference::Branch(v.to_owned()),
|
||||
|
||||
"rev" => reference = GitReference::Rev(v.to_owned()),
|
||||
"tag" => reference = GitReference::Tag(v.to_owned()),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
reference
|
||||
}
|
||||
|
||||
/// Returns a `Display`able view of this git reference, or None if using
|
||||
/// the head of the default branch
|
||||
pub fn pretty_ref(&self, url_encoded: bool) -> Option<PrettyRef<'_>> {
|
||||
match self {
|
||||
GitReference::DefaultBranch => None,
|
||||
_ => Some(PrettyRef {
|
||||
inner: self,
|
||||
url_encoded,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A git reference that can be `Display`ed
|
||||
pub struct PrettyRef<'a> {
|
||||
inner: &'a GitReference,
|
||||
url_encoded: bool,
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for PrettyRef<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let value: &str;
|
||||
match self.inner {
|
||||
GitReference::Branch(s) => {
|
||||
write!(f, "branch=")?;
|
||||
value = s;
|
||||
}
|
||||
GitReference::Tag(s) => {
|
||||
write!(f, "tag=")?;
|
||||
value = s;
|
||||
}
|
||||
GitReference::Rev(s) => {
|
||||
write!(f, "rev=")?;
|
||||
value = s;
|
||||
}
|
||||
GitReference::DefaultBranch => unreachable!(),
|
||||
}
|
||||
if self.url_encoded {
|
||||
for value in url::form_urlencoded::byte_serialize(value.as_bytes()) {
|
||||
write!(f, "{value}")?;
|
||||
}
|
||||
} else {
|
||||
write!(f, "{value}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyOf {
|
||||
/// Gets the underlying key.
|
||||
fn key(&self) -> &str {
|
||||
|
|
|
@ -15,7 +15,9 @@ use crate::core::features::Features;
|
|||
use crate::core::registry::PackageRegistry;
|
||||
use crate::core::resolver::features::CliFeatures;
|
||||
use crate::core::resolver::ResolveBehavior;
|
||||
use crate::core::{Dependency, Edition, FeatureValue, PackageId, PackageIdSpec};
|
||||
use crate::core::{
|
||||
Dependency, Edition, FeatureValue, PackageId, PackageIdSpec, PackageIdSpecQuery,
|
||||
};
|
||||
use crate::core::{EitherManifest, Package, SourceId, VirtualManifest};
|
||||
use crate::ops;
|
||||
use crate::sources::{PathSource, CRATES_IO_INDEX, CRATES_IO_REGISTRY};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::core::compiler::{CompileKind, CompileMode, Layout, RustcTargetData};
|
||||
use crate::core::profiles::Profiles;
|
||||
use crate::core::{PackageIdSpec, TargetKind, Workspace};
|
||||
use crate::core::{PackageIdSpec, PackageIdSpecQuery, TargetKind, Workspace};
|
||||
use crate::ops;
|
||||
use crate::util::edit_distance;
|
||||
use crate::util::errors::CargoResult;
|
||||
|
|
|
@ -47,7 +47,7 @@ impl Packages {
|
|||
Packages::All => ws
|
||||
.members()
|
||||
.map(Package::package_id)
|
||||
.map(PackageIdSpec::from_package_id)
|
||||
.map(|id| id.to_spec())
|
||||
.collect(),
|
||||
Packages::OptOut(opt_out) => {
|
||||
let (mut patterns, mut names) = opt_patterns_and_names(opt_out)?;
|
||||
|
@ -57,7 +57,7 @@ impl Packages {
|
|||
!names.remove(pkg.name().as_str()) && !match_patterns(pkg, &mut patterns)
|
||||
})
|
||||
.map(Package::package_id)
|
||||
.map(PackageIdSpec::from_package_id)
|
||||
.map(|id| id.to_spec())
|
||||
.collect();
|
||||
let warn = |e| ws.config().shell().warn(e);
|
||||
emit_package_not_found(ws, names, true).or_else(warn)?;
|
||||
|
@ -65,7 +65,7 @@ impl Packages {
|
|||
specs
|
||||
}
|
||||
Packages::Packages(packages) if packages.is_empty() => {
|
||||
vec![PackageIdSpec::from_package_id(ws.current()?.package_id())]
|
||||
vec![ws.current()?.package_id().to_spec()]
|
||||
}
|
||||
Packages::Packages(opt_in) => {
|
||||
let (mut patterns, packages) = opt_patterns_and_names(opt_in)?;
|
||||
|
@ -78,7 +78,7 @@ impl Packages {
|
|||
.members()
|
||||
.filter(|pkg| match_patterns(pkg, &mut patterns))
|
||||
.map(Package::package_id)
|
||||
.map(PackageIdSpec::from_package_id);
|
||||
.map(|id| id.to_spec());
|
||||
specs.extend(matched_pkgs);
|
||||
}
|
||||
emit_pattern_not_found(ws, patterns, false)?;
|
||||
|
@ -87,7 +87,7 @@ impl Packages {
|
|||
Packages::Default => ws
|
||||
.default_members()
|
||||
.map(Package::package_id)
|
||||
.map(PackageIdSpec::from_package_id)
|
||||
.map(|id| id.to_spec())
|
||||
.collect(),
|
||||
};
|
||||
if specs.is_empty() {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::core::registry::PackageRegistry;
|
||||
use crate::core::resolver::features::{CliFeatures, HasDevUnits};
|
||||
use crate::core::{PackageId, PackageIdSpec};
|
||||
use crate::core::{PackageId, PackageIdSpec, PackageIdSpecQuery};
|
||||
use crate::core::{Resolve, SourceId, Workspace};
|
||||
use crate::ops;
|
||||
use crate::util::cache_lock::CacheLockMode;
|
||||
|
|
|
@ -4,9 +4,7 @@ use std::sync::Arc;
|
|||
use std::{env, fs};
|
||||
|
||||
use crate::core::compiler::{CompileKind, DefaultExecutor, Executor, UnitOutput};
|
||||
use crate::core::{
|
||||
Dependency, Edition, Package, PackageId, PackageIdSpec, SourceId, Target, Workspace,
|
||||
};
|
||||
use crate::core::{Dependency, Edition, Package, PackageId, SourceId, Target, Workspace};
|
||||
use crate::ops::{common_for_install_and_uninstall::*, FilterRule};
|
||||
use crate::ops::{CompileFilter, Packages};
|
||||
use crate::sources::source::Source;
|
||||
|
@ -206,7 +204,7 @@ impl<'cfg> InstallablePackage<'cfg> {
|
|||
// For cargo install tracking, we retain the source git url in `pkg`, but for the build spec
|
||||
// we need to unconditionally use `ws.current()` to correctly address the path where we
|
||||
// locally cloned that repo.
|
||||
let pkgidspec = PackageIdSpec::from_package_id(ws.current()?.package_id());
|
||||
let pkgidspec = ws.current()?.package_id().to_spec();
|
||||
opts.spec = Packages::Packages(vec![pkgidspec.to_string()]);
|
||||
|
||||
if from_cwd {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::{PackageIdSpec, Workspace};
|
||||
use crate::core::{PackageIdSpec, PackageIdSpecQuery, Workspace};
|
||||
use crate::ops;
|
||||
use crate::util::CargoResult;
|
||||
|
||||
|
@ -11,5 +11,5 @@ pub fn pkgid(ws: &Workspace<'_>, spec: Option<&str>) -> CargoResult<PackageIdSpe
|
|||
Some(spec) => PackageIdSpec::query_str(spec, resolve.iter())?,
|
||||
None => ws.current()?.package_id(),
|
||||
};
|
||||
Ok(PackageIdSpec::from_package_id(pkgid))
|
||||
Ok(pkgid.to_spec())
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::core::PackageId;
|
||||
use crate::core::{PackageIdSpec, SourceId};
|
||||
use crate::core::{PackageIdSpec, PackageIdSpecQuery, SourceId};
|
||||
use crate::ops::common_for_install_and_uninstall::*;
|
||||
use crate::sources::PathSource;
|
||||
use crate::util::errors::CargoResult;
|
||||
|
|
|
@ -21,6 +21,7 @@ use crate::core::manifest::ManifestMetadata;
|
|||
use crate::core::resolver::CliFeatures;
|
||||
use crate::core::Dependency;
|
||||
use crate::core::Package;
|
||||
use crate::core::PackageIdSpecQuery;
|
||||
use crate::core::SourceId;
|
||||
use crate::core::Workspace;
|
||||
use crate::ops;
|
||||
|
|
|
@ -64,7 +64,9 @@ use crate::core::resolver::{
|
|||
self, HasDevUnits, Resolve, ResolveOpts, ResolveVersion, VersionOrdering, VersionPreferences,
|
||||
};
|
||||
use crate::core::summary::Summary;
|
||||
use crate::core::{GitReference, PackageId, PackageIdSpec, PackageSet, SourceId, Workspace};
|
||||
use crate::core::{
|
||||
GitReference, PackageId, PackageIdSpec, PackageIdSpecQuery, PackageSet, SourceId, Workspace,
|
||||
};
|
||||
use crate::ops;
|
||||
use crate::sources::PathSource;
|
||||
use crate::util::cache_lock::CacheLockMode;
|
||||
|
|
|
@ -4,7 +4,7 @@ use self::format::Pattern;
|
|||
use crate::core::compiler::{CompileKind, RustcTargetData};
|
||||
use crate::core::dependency::DepKind;
|
||||
use crate::core::resolver::{features::CliFeatures, ForceAllTargets, HasDevUnits};
|
||||
use crate::core::{Package, PackageId, PackageIdSpec, Workspace};
|
||||
use crate::core::{Package, PackageId, PackageIdSpec, PackageIdSpecQuery, Workspace};
|
||||
use crate::ops::{self, Packages};
|
||||
use crate::util::{CargoResult, Config};
|
||||
use crate::{drop_print, drop_println};
|
||||
|
|
6
src/cargo/util_schemas/core/mod.rs
Normal file
6
src/cargo/util_schemas/core/mod.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
mod package_id_spec;
|
||||
mod source_kind;
|
||||
|
||||
pub use package_id_spec::PackageIdSpec;
|
||||
pub use source_kind::GitReference;
|
||||
pub use source_kind::SourceKind;
|
601
src/cargo/util_schemas/core/package_id_spec.rs
Normal file
601
src/cargo/util_schemas/core/package_id_spec.rs
Normal file
|
@ -0,0 +1,601 @@
|
|||
use std::fmt;
|
||||
|
||||
use anyhow::bail;
|
||||
use semver::Version;
|
||||
use serde::{de, ser};
|
||||
use url::Url;
|
||||
|
||||
use crate::core::GitReference;
|
||||
use crate::core::PackageId;
|
||||
use crate::core::SourceKind;
|
||||
use crate::util::errors::CargoResult;
|
||||
use crate::util::{validate_package_name, IntoUrl};
|
||||
use crate::util_semver::PartialVersion;
|
||||
|
||||
/// Some or all of the data required to identify a package:
|
||||
///
|
||||
/// 1. the package name (a `String`, required)
|
||||
/// 2. the package version (a `Version`, optional)
|
||||
/// 3. the package source (a `Url`, optional)
|
||||
///
|
||||
/// If any of the optional fields are omitted, then the package ID may be ambiguous, there may be
|
||||
/// more than one package/version/url combo that will match. However, often just the name is
|
||||
/// sufficient to uniquely define a package ID.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)]
|
||||
pub struct PackageIdSpec {
|
||||
name: String,
|
||||
version: Option<PartialVersion>,
|
||||
url: Option<Url>,
|
||||
kind: Option<SourceKind>,
|
||||
}
|
||||
|
||||
impl PackageIdSpec {
|
||||
pub fn new(name: String) -> Self {
|
||||
Self {
|
||||
name,
|
||||
version: None,
|
||||
url: None,
|
||||
kind: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_version(mut self, version: PartialVersion) -> Self {
|
||||
self.version = Some(version);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_url(mut self, url: Url) -> Self {
|
||||
self.url = Some(url);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_kind(mut self, kind: SourceKind) -> Self {
|
||||
self.kind = Some(kind);
|
||||
self
|
||||
}
|
||||
|
||||
/// Parses a spec string and returns a `PackageIdSpec` if the string was valid.
|
||||
///
|
||||
/// # Examples
|
||||
/// Some examples of valid strings
|
||||
///
|
||||
/// ```
|
||||
/// use cargo::core::PackageIdSpec;
|
||||
///
|
||||
/// let specs = vec![
|
||||
/// "https://crates.io/foo",
|
||||
/// "https://crates.io/foo#1.2.3",
|
||||
/// "https://crates.io/foo#bar:1.2.3",
|
||||
/// "https://crates.io/foo#bar@1.2.3",
|
||||
/// "foo",
|
||||
/// "foo:1.2.3",
|
||||
/// "foo@1.2.3",
|
||||
/// ];
|
||||
/// for spec in specs {
|
||||
/// assert!(PackageIdSpec::parse(spec).is_ok());
|
||||
/// }
|
||||
pub fn parse(spec: &str) -> CargoResult<PackageIdSpec> {
|
||||
if spec.contains("://") {
|
||||
if let Ok(url) = spec.into_url() {
|
||||
return PackageIdSpec::from_url(url);
|
||||
}
|
||||
} else if spec.contains('/') || spec.contains('\\') {
|
||||
let abs = std::env::current_dir().unwrap_or_default().join(spec);
|
||||
if abs.exists() {
|
||||
let maybe_url = Url::from_file_path(abs)
|
||||
.map_or_else(|_| "a file:// URL".to_string(), |url| url.to_string());
|
||||
bail!(
|
||||
"package ID specification `{}` looks like a file path, \
|
||||
maybe try {}",
|
||||
spec,
|
||||
maybe_url
|
||||
);
|
||||
}
|
||||
}
|
||||
let mut parts = spec.splitn(2, [':', '@']);
|
||||
let name = parts.next().unwrap();
|
||||
let version = match parts.next() {
|
||||
Some(version) => Some(version.parse::<PartialVersion>()?),
|
||||
None => None,
|
||||
};
|
||||
validate_package_name(name, "pkgid", "")?;
|
||||
Ok(PackageIdSpec {
|
||||
name: String::from(name),
|
||||
version,
|
||||
url: None,
|
||||
kind: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Convert a `PackageId` to a `PackageIdSpec`, which will have both the `PartialVersion` and `Url`
|
||||
/// fields filled in.
|
||||
pub fn from_package_id(package_id: PackageId) -> PackageIdSpec {
|
||||
PackageIdSpec {
|
||||
name: String::from(package_id.name().as_str()),
|
||||
version: Some(package_id.version().clone().into()),
|
||||
url: Some(package_id.source_id().url().clone()),
|
||||
kind: Some(package_id.source_id().kind().clone()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to convert a valid `Url` to a `PackageIdSpec`.
|
||||
fn from_url(mut url: Url) -> CargoResult<PackageIdSpec> {
|
||||
let mut kind = None;
|
||||
if let Some((kind_str, scheme)) = url.scheme().split_once('+') {
|
||||
match kind_str {
|
||||
"git" => {
|
||||
let git_ref = GitReference::from_query(url.query_pairs());
|
||||
url.set_query(None);
|
||||
kind = Some(SourceKind::Git(git_ref));
|
||||
url = strip_url_protocol(&url);
|
||||
}
|
||||
"registry" => {
|
||||
if url.query().is_some() {
|
||||
bail!("cannot have a query string in a pkgid: {url}")
|
||||
}
|
||||
kind = Some(SourceKind::Registry);
|
||||
url = strip_url_protocol(&url);
|
||||
}
|
||||
"sparse" => {
|
||||
if url.query().is_some() {
|
||||
bail!("cannot have a query string in a pkgid: {url}")
|
||||
}
|
||||
kind = Some(SourceKind::SparseRegistry);
|
||||
// Leave `sparse` as part of URL, see `SourceId::new`
|
||||
// url = strip_url_protocol(&url);
|
||||
}
|
||||
"path" => {
|
||||
if url.query().is_some() {
|
||||
bail!("cannot have a query string in a pkgid: {url}")
|
||||
}
|
||||
if scheme != "file" {
|
||||
anyhow::bail!("`path+{scheme}` is unsupported; `path+file` and `file` schemes are supported");
|
||||
}
|
||||
kind = Some(SourceKind::Path);
|
||||
url = strip_url_protocol(&url);
|
||||
}
|
||||
kind => anyhow::bail!("unsupported source protocol: {kind}"),
|
||||
}
|
||||
} else {
|
||||
if url.query().is_some() {
|
||||
bail!("cannot have a query string in a pkgid: {url}")
|
||||
}
|
||||
}
|
||||
|
||||
let frag = url.fragment().map(|s| s.to_owned());
|
||||
url.set_fragment(None);
|
||||
|
||||
let (name, version) = {
|
||||
let mut path = url
|
||||
.path_segments()
|
||||
.ok_or_else(|| anyhow::format_err!("pkgid urls must have a path: {}", url))?;
|
||||
let path_name = path.next_back().ok_or_else(|| {
|
||||
anyhow::format_err!(
|
||||
"pkgid urls must have at least one path \
|
||||
component: {}",
|
||||
url
|
||||
)
|
||||
})?;
|
||||
match frag {
|
||||
Some(fragment) => match fragment.split_once([':', '@']) {
|
||||
Some((name, part)) => {
|
||||
let version = part.parse::<PartialVersion>()?;
|
||||
(String::from(name), Some(version))
|
||||
}
|
||||
None => {
|
||||
if fragment.chars().next().unwrap().is_alphabetic() {
|
||||
(String::from(fragment.as_str()), None)
|
||||
} else {
|
||||
let version = fragment.parse::<PartialVersion>()?;
|
||||
(String::from(path_name), Some(version))
|
||||
}
|
||||
}
|
||||
},
|
||||
None => (String::from(path_name), None),
|
||||
}
|
||||
};
|
||||
Ok(PackageIdSpec {
|
||||
name,
|
||||
version,
|
||||
url: Some(url),
|
||||
kind,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
self.name.as_str()
|
||||
}
|
||||
|
||||
/// Full `semver::Version`, if present
|
||||
pub fn version(&self) -> Option<Version> {
|
||||
self.version.as_ref().and_then(|v| v.to_version())
|
||||
}
|
||||
|
||||
pub fn partial_version(&self) -> Option<&PartialVersion> {
|
||||
self.version.as_ref()
|
||||
}
|
||||
|
||||
pub fn url(&self) -> Option<&Url> {
|
||||
self.url.as_ref()
|
||||
}
|
||||
|
||||
pub fn set_url(&mut self, url: Url) {
|
||||
self.url = Some(url);
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> Option<&SourceKind> {
|
||||
self.kind.as_ref()
|
||||
}
|
||||
|
||||
pub fn set_kind(&mut self, kind: SourceKind) {
|
||||
self.kind = Some(kind);
|
||||
}
|
||||
}
|
||||
|
||||
fn strip_url_protocol(url: &Url) -> Url {
|
||||
// Ridiculous hoop because `Url::set_scheme` errors when changing to http/https
|
||||
let raw = url.to_string();
|
||||
raw.split_once('+').unwrap().1.parse().unwrap()
|
||||
}
|
||||
|
||||
impl fmt::Display for PackageIdSpec {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut printed_name = false;
|
||||
match self.url {
|
||||
Some(ref url) => {
|
||||
if let Some(protocol) = self.kind.as_ref().and_then(|k| k.protocol()) {
|
||||
write!(f, "{protocol}+")?;
|
||||
}
|
||||
write!(f, "{}", url)?;
|
||||
if let Some(SourceKind::Git(git_ref)) = self.kind.as_ref() {
|
||||
if let Some(pretty) = git_ref.pretty_ref(true) {
|
||||
write!(f, "?{}", pretty)?;
|
||||
}
|
||||
}
|
||||
if url.path_segments().unwrap().next_back().unwrap() != &*self.name {
|
||||
printed_name = true;
|
||||
write!(f, "#{}", self.name)?;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
printed_name = true;
|
||||
write!(f, "{}", self.name)?;
|
||||
}
|
||||
}
|
||||
if let Some(ref v) = self.version {
|
||||
write!(f, "{}{}", if printed_name { "@" } else { "#" }, v)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ser::Serialize for PackageIdSpec {
|
||||
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: ser::Serializer,
|
||||
{
|
||||
self.to_string().serialize(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> de::Deserialize<'de> for PackageIdSpec {
|
||||
fn deserialize<D>(d: D) -> Result<PackageIdSpec, D::Error>
|
||||
where
|
||||
D: de::Deserializer<'de>,
|
||||
{
|
||||
let string = String::deserialize(d)?;
|
||||
PackageIdSpec::parse(&string).map_err(de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::PackageIdSpec;
|
||||
use crate::util_schemas::core::{GitReference, SourceKind};
|
||||
use url::Url;
|
||||
|
||||
#[test]
|
||||
fn good_parsing() {
|
||||
#[track_caller]
|
||||
fn ok(spec: &str, expected: PackageIdSpec, expected_rendered: &str) {
|
||||
let parsed = PackageIdSpec::parse(spec).unwrap();
|
||||
assert_eq!(parsed, expected);
|
||||
let rendered = parsed.to_string();
|
||||
assert_eq!(rendered, expected_rendered);
|
||||
let reparsed = PackageIdSpec::parse(&rendered).unwrap();
|
||||
assert_eq!(reparsed, expected);
|
||||
}
|
||||
|
||||
ok(
|
||||
"https://crates.io/foo",
|
||||
PackageIdSpec {
|
||||
name: String::from("foo"),
|
||||
version: None,
|
||||
url: Some(Url::parse("https://crates.io/foo").unwrap()),
|
||||
kind: None,
|
||||
},
|
||||
"https://crates.io/foo",
|
||||
);
|
||||
ok(
|
||||
"https://crates.io/foo#1.2.3",
|
||||
PackageIdSpec {
|
||||
name: String::from("foo"),
|
||||
version: Some("1.2.3".parse().unwrap()),
|
||||
url: Some(Url::parse("https://crates.io/foo").unwrap()),
|
||||
kind: None,
|
||||
},
|
||||
"https://crates.io/foo#1.2.3",
|
||||
);
|
||||
ok(
|
||||
"https://crates.io/foo#1.2",
|
||||
PackageIdSpec {
|
||||
name: String::from("foo"),
|
||||
version: Some("1.2".parse().unwrap()),
|
||||
url: Some(Url::parse("https://crates.io/foo").unwrap()),
|
||||
kind: None,
|
||||
},
|
||||
"https://crates.io/foo#1.2",
|
||||
);
|
||||
ok(
|
||||
"https://crates.io/foo#bar:1.2.3",
|
||||
PackageIdSpec {
|
||||
name: String::from("bar"),
|
||||
version: Some("1.2.3".parse().unwrap()),
|
||||
url: Some(Url::parse("https://crates.io/foo").unwrap()),
|
||||
kind: None,
|
||||
},
|
||||
"https://crates.io/foo#bar@1.2.3",
|
||||
);
|
||||
ok(
|
||||
"https://crates.io/foo#bar@1.2.3",
|
||||
PackageIdSpec {
|
||||
name: String::from("bar"),
|
||||
version: Some("1.2.3".parse().unwrap()),
|
||||
url: Some(Url::parse("https://crates.io/foo").unwrap()),
|
||||
kind: None,
|
||||
},
|
||||
"https://crates.io/foo#bar@1.2.3",
|
||||
);
|
||||
ok(
|
||||
"https://crates.io/foo#bar@1.2",
|
||||
PackageIdSpec {
|
||||
name: String::from("bar"),
|
||||
version: Some("1.2".parse().unwrap()),
|
||||
url: Some(Url::parse("https://crates.io/foo").unwrap()),
|
||||
kind: None,
|
||||
},
|
||||
"https://crates.io/foo#bar@1.2",
|
||||
);
|
||||
ok(
|
||||
"registry+https://crates.io/foo#bar@1.2",
|
||||
PackageIdSpec {
|
||||
name: String::from("bar"),
|
||||
version: Some("1.2".parse().unwrap()),
|
||||
url: Some(Url::parse("https://crates.io/foo").unwrap()),
|
||||
kind: Some(SourceKind::Registry),
|
||||
},
|
||||
"registry+https://crates.io/foo#bar@1.2",
|
||||
);
|
||||
ok(
|
||||
"sparse+https://crates.io/foo#bar@1.2",
|
||||
PackageIdSpec {
|
||||
name: String::from("bar"),
|
||||
version: Some("1.2".parse().unwrap()),
|
||||
url: Some(Url::parse("sparse+https://crates.io/foo").unwrap()),
|
||||
kind: Some(SourceKind::SparseRegistry),
|
||||
},
|
||||
"sparse+https://crates.io/foo#bar@1.2",
|
||||
);
|
||||
ok(
|
||||
"foo",
|
||||
PackageIdSpec {
|
||||
name: String::from("foo"),
|
||||
version: None,
|
||||
url: None,
|
||||
kind: None,
|
||||
},
|
||||
"foo",
|
||||
);
|
||||
ok(
|
||||
"foo:1.2.3",
|
||||
PackageIdSpec {
|
||||
name: String::from("foo"),
|
||||
version: Some("1.2.3".parse().unwrap()),
|
||||
url: None,
|
||||
kind: None,
|
||||
},
|
||||
"foo@1.2.3",
|
||||
);
|
||||
ok(
|
||||
"foo@1.2.3",
|
||||
PackageIdSpec {
|
||||
name: String::from("foo"),
|
||||
version: Some("1.2.3".parse().unwrap()),
|
||||
url: None,
|
||||
kind: None,
|
||||
},
|
||||
"foo@1.2.3",
|
||||
);
|
||||
ok(
|
||||
"foo@1.2",
|
||||
PackageIdSpec {
|
||||
name: String::from("foo"),
|
||||
version: Some("1.2".parse().unwrap()),
|
||||
url: None,
|
||||
kind: None,
|
||||
},
|
||||
"foo@1.2",
|
||||
);
|
||||
|
||||
// pkgid-spec.md
|
||||
ok(
|
||||
"regex",
|
||||
PackageIdSpec {
|
||||
name: String::from("regex"),
|
||||
version: None,
|
||||
url: None,
|
||||
kind: None,
|
||||
},
|
||||
"regex",
|
||||
);
|
||||
ok(
|
||||
"regex@1.4",
|
||||
PackageIdSpec {
|
||||
name: String::from("regex"),
|
||||
version: Some("1.4".parse().unwrap()),
|
||||
url: None,
|
||||
kind: None,
|
||||
},
|
||||
"regex@1.4",
|
||||
);
|
||||
ok(
|
||||
"regex@1.4.3",
|
||||
PackageIdSpec {
|
||||
name: String::from("regex"),
|
||||
version: Some("1.4.3".parse().unwrap()),
|
||||
url: None,
|
||||
kind: None,
|
||||
},
|
||||
"regex@1.4.3",
|
||||
);
|
||||
ok(
|
||||
"https://github.com/rust-lang/crates.io-index#regex",
|
||||
PackageIdSpec {
|
||||
name: String::from("regex"),
|
||||
version: None,
|
||||
url: Some(Url::parse("https://github.com/rust-lang/crates.io-index").unwrap()),
|
||||
kind: None,
|
||||
},
|
||||
"https://github.com/rust-lang/crates.io-index#regex",
|
||||
);
|
||||
ok(
|
||||
"https://github.com/rust-lang/crates.io-index#regex@1.4.3",
|
||||
PackageIdSpec {
|
||||
name: String::from("regex"),
|
||||
version: Some("1.4.3".parse().unwrap()),
|
||||
url: Some(Url::parse("https://github.com/rust-lang/crates.io-index").unwrap()),
|
||||
kind: None,
|
||||
},
|
||||
"https://github.com/rust-lang/crates.io-index#regex@1.4.3",
|
||||
);
|
||||
ok(
|
||||
"sparse+https://github.com/rust-lang/crates.io-index#regex@1.4.3",
|
||||
PackageIdSpec {
|
||||
name: String::from("regex"),
|
||||
version: Some("1.4.3".parse().unwrap()),
|
||||
url: Some(
|
||||
Url::parse("sparse+https://github.com/rust-lang/crates.io-index").unwrap(),
|
||||
),
|
||||
kind: Some(SourceKind::SparseRegistry),
|
||||
},
|
||||
"sparse+https://github.com/rust-lang/crates.io-index#regex@1.4.3",
|
||||
);
|
||||
ok(
|
||||
"https://github.com/rust-lang/cargo#0.52.0",
|
||||
PackageIdSpec {
|
||||
name: String::from("cargo"),
|
||||
version: Some("0.52.0".parse().unwrap()),
|
||||
url: Some(Url::parse("https://github.com/rust-lang/cargo").unwrap()),
|
||||
kind: None,
|
||||
},
|
||||
"https://github.com/rust-lang/cargo#0.52.0",
|
||||
);
|
||||
ok(
|
||||
"https://github.com/rust-lang/cargo#cargo-platform@0.1.2",
|
||||
PackageIdSpec {
|
||||
name: String::from("cargo-platform"),
|
||||
version: Some("0.1.2".parse().unwrap()),
|
||||
url: Some(Url::parse("https://github.com/rust-lang/cargo").unwrap()),
|
||||
kind: None,
|
||||
},
|
||||
"https://github.com/rust-lang/cargo#cargo-platform@0.1.2",
|
||||
);
|
||||
ok(
|
||||
"ssh://git@github.com/rust-lang/regex.git#regex@1.4.3",
|
||||
PackageIdSpec {
|
||||
name: String::from("regex"),
|
||||
version: Some("1.4.3".parse().unwrap()),
|
||||
url: Some(Url::parse("ssh://git@github.com/rust-lang/regex.git").unwrap()),
|
||||
kind: None,
|
||||
},
|
||||
"ssh://git@github.com/rust-lang/regex.git#regex@1.4.3",
|
||||
);
|
||||
ok(
|
||||
"git+ssh://git@github.com/rust-lang/regex.git#regex@1.4.3",
|
||||
PackageIdSpec {
|
||||
name: String::from("regex"),
|
||||
version: Some("1.4.3".parse().unwrap()),
|
||||
url: Some(Url::parse("ssh://git@github.com/rust-lang/regex.git").unwrap()),
|
||||
kind: Some(SourceKind::Git(GitReference::DefaultBranch)),
|
||||
},
|
||||
"git+ssh://git@github.com/rust-lang/regex.git#regex@1.4.3",
|
||||
);
|
||||
ok(
|
||||
"git+ssh://git@github.com/rust-lang/regex.git?branch=dev#regex@1.4.3",
|
||||
PackageIdSpec {
|
||||
name: String::from("regex"),
|
||||
version: Some("1.4.3".parse().unwrap()),
|
||||
url: Some(Url::parse("ssh://git@github.com/rust-lang/regex.git").unwrap()),
|
||||
kind: Some(SourceKind::Git(GitReference::Branch("dev".to_owned()))),
|
||||
},
|
||||
"git+ssh://git@github.com/rust-lang/regex.git?branch=dev#regex@1.4.3",
|
||||
);
|
||||
ok(
|
||||
"file:///path/to/my/project/foo",
|
||||
PackageIdSpec {
|
||||
name: String::from("foo"),
|
||||
version: None,
|
||||
url: Some(Url::parse("file:///path/to/my/project/foo").unwrap()),
|
||||
kind: None,
|
||||
},
|
||||
"file:///path/to/my/project/foo",
|
||||
);
|
||||
ok(
|
||||
"file:///path/to/my/project/foo#1.1.8",
|
||||
PackageIdSpec {
|
||||
name: String::from("foo"),
|
||||
version: Some("1.1.8".parse().unwrap()),
|
||||
url: Some(Url::parse("file:///path/to/my/project/foo").unwrap()),
|
||||
kind: None,
|
||||
},
|
||||
"file:///path/to/my/project/foo#1.1.8",
|
||||
);
|
||||
ok(
|
||||
"path+file:///path/to/my/project/foo#1.1.8",
|
||||
PackageIdSpec {
|
||||
name: String::from("foo"),
|
||||
version: Some("1.1.8".parse().unwrap()),
|
||||
url: Some(Url::parse("file:///path/to/my/project/foo").unwrap()),
|
||||
kind: Some(SourceKind::Path),
|
||||
},
|
||||
"path+file:///path/to/my/project/foo#1.1.8",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_parsing() {
|
||||
assert!(PackageIdSpec::parse("baz:").is_err());
|
||||
assert!(PackageIdSpec::parse("baz:*").is_err());
|
||||
assert!(PackageIdSpec::parse("baz@").is_err());
|
||||
assert!(PackageIdSpec::parse("baz@*").is_err());
|
||||
assert!(PackageIdSpec::parse("baz@^1.0").is_err());
|
||||
assert!(PackageIdSpec::parse("https://baz:1.0").is_err());
|
||||
assert!(PackageIdSpec::parse("https://#baz:1.0").is_err());
|
||||
assert!(
|
||||
PackageIdSpec::parse("foobar+https://github.com/rust-lang/crates.io-index").is_err()
|
||||
);
|
||||
assert!(PackageIdSpec::parse("path+https://github.com/rust-lang/crates.io-index").is_err());
|
||||
|
||||
// Only `git+` can use `?`
|
||||
assert!(PackageIdSpec::parse("file:///path/to/my/project/foo?branch=dev").is_err());
|
||||
assert!(PackageIdSpec::parse("path+file:///path/to/my/project/foo?branch=dev").is_err());
|
||||
assert!(PackageIdSpec::parse(
|
||||
"registry+https://github.com/rust-lang/cargo#0.52.0?branch=dev"
|
||||
)
|
||||
.is_err());
|
||||
assert!(PackageIdSpec::parse(
|
||||
"sparse+https://github.com/rust-lang/cargo#0.52.0?branch=dev"
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
}
|
201
src/cargo/util_schemas/core/source_kind.rs
Normal file
201
src/cargo/util_schemas/core/source_kind.rs
Normal file
|
@ -0,0 +1,201 @@
|
|||
use std::cmp::Ordering;
|
||||
|
||||
/// The possible kinds of code source.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum SourceKind {
|
||||
/// A git repository.
|
||||
Git(GitReference),
|
||||
/// A local path.
|
||||
Path,
|
||||
/// A remote registry.
|
||||
Registry,
|
||||
/// A sparse registry.
|
||||
SparseRegistry,
|
||||
/// A local filesystem-based registry.
|
||||
LocalRegistry,
|
||||
/// A directory-based registry.
|
||||
Directory,
|
||||
}
|
||||
|
||||
impl SourceKind {
|
||||
pub fn protocol(&self) -> Option<&str> {
|
||||
match self {
|
||||
SourceKind::Path => Some("path"),
|
||||
SourceKind::Git(_) => Some("git"),
|
||||
SourceKind::Registry => Some("registry"),
|
||||
// Sparse registry URL already includes the `sparse+` prefix, see `SourceId::new`
|
||||
SourceKind::SparseRegistry => None,
|
||||
SourceKind::LocalRegistry => Some("local-registry"),
|
||||
SourceKind::Directory => Some("directory"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Note that this is specifically not derived on `SourceKind` although the
|
||||
/// implementation here is very similar to what it might look like if it were
|
||||
/// otherwise derived.
|
||||
///
|
||||
/// The reason for this is somewhat obtuse. First of all the hash value of
|
||||
/// `SourceKind` makes its way into `~/.cargo/registry/index/github.com-XXXX`
|
||||
/// which means that changes to the hash means that all Rust users need to
|
||||
/// redownload the crates.io index and all their crates. If possible we strive
|
||||
/// to not change this to make this redownloading behavior happen as little as
|
||||
/// possible. How is this connected to `Ord` you might ask? That's a good
|
||||
/// question!
|
||||
///
|
||||
/// Since the beginning of time `SourceKind` has had `#[derive(Hash)]`. It for
|
||||
/// the longest time *also* derived the `Ord` and `PartialOrd` traits. In #8522,
|
||||
/// however, the implementation of `Ord` changed. This handwritten implementation
|
||||
/// forgot to sync itself with the originally derived implementation, namely
|
||||
/// placing git dependencies as sorted after all other dependencies instead of
|
||||
/// first as before.
|
||||
///
|
||||
/// This regression in #8522 (Rust 1.47) went unnoticed. When we switched back
|
||||
/// to a derived implementation in #9133 (Rust 1.52 beta) we only then ironically
|
||||
/// saw an issue (#9334). In #9334 it was observed that stable Rust at the time
|
||||
/// (1.51) was sorting git dependencies last, whereas Rust 1.52 beta would sort
|
||||
/// git dependencies first. This is because the `PartialOrd` implementation in
|
||||
/// 1.51 used #8522, the buggy implementation, which put git deps last. In 1.52
|
||||
/// it was (unknowingly) restored to the pre-1.47 behavior with git dependencies
|
||||
/// first.
|
||||
///
|
||||
/// Because the breakage was only witnessed after the original breakage, this
|
||||
/// trait implementation is preserving the "broken" behavior. Put a different way:
|
||||
///
|
||||
/// * Rust pre-1.47 sorted git deps first.
|
||||
/// * Rust 1.47 to Rust 1.51 sorted git deps last, a breaking change (#8522) that
|
||||
/// was never noticed.
|
||||
/// * Rust 1.52 restored the pre-1.47 behavior (#9133, without knowing it did
|
||||
/// so), and breakage was witnessed by actual users due to difference with
|
||||
/// 1.51.
|
||||
/// * Rust 1.52 (the source as it lives now) was fixed to match the 1.47-1.51
|
||||
/// behavior (#9383), which is now considered intentionally breaking from the
|
||||
/// pre-1.47 behavior.
|
||||
///
|
||||
/// Note that this was all discovered when Rust 1.53 was in nightly and 1.52 was
|
||||
/// in beta. #9133 was in both beta and nightly at the time of discovery. For
|
||||
/// 1.52 #9383 reverted #9133, meaning 1.52 is the same as 1.51. On nightly
|
||||
/// (1.53) #9397 was created to fix the regression introduced by #9133 relative
|
||||
/// to the current stable (1.51).
|
||||
///
|
||||
/// That's all a long winded way of saying "it's weird that git deps hash first
|
||||
/// and are sorted last, but it's the way it is right now". The author of this
|
||||
/// comment chose to handwrite the `Ord` implementation instead of the `Hash`
|
||||
/// implementation, but it's only required that at most one of them is
|
||||
/// hand-written because the other can be derived. Perhaps one day in
|
||||
/// the future someone can figure out how to remove this behavior.
|
||||
impl Ord for SourceKind {
|
||||
fn cmp(&self, other: &SourceKind) -> Ordering {
|
||||
match (self, other) {
|
||||
(SourceKind::Path, SourceKind::Path) => Ordering::Equal,
|
||||
(SourceKind::Path, _) => Ordering::Less,
|
||||
(_, SourceKind::Path) => Ordering::Greater,
|
||||
|
||||
(SourceKind::Registry, SourceKind::Registry) => Ordering::Equal,
|
||||
(SourceKind::Registry, _) => Ordering::Less,
|
||||
(_, SourceKind::Registry) => Ordering::Greater,
|
||||
|
||||
(SourceKind::SparseRegistry, SourceKind::SparseRegistry) => Ordering::Equal,
|
||||
(SourceKind::SparseRegistry, _) => Ordering::Less,
|
||||
(_, SourceKind::SparseRegistry) => Ordering::Greater,
|
||||
|
||||
(SourceKind::LocalRegistry, SourceKind::LocalRegistry) => Ordering::Equal,
|
||||
(SourceKind::LocalRegistry, _) => Ordering::Less,
|
||||
(_, SourceKind::LocalRegistry) => Ordering::Greater,
|
||||
|
||||
(SourceKind::Directory, SourceKind::Directory) => Ordering::Equal,
|
||||
(SourceKind::Directory, _) => Ordering::Less,
|
||||
(_, SourceKind::Directory) => Ordering::Greater,
|
||||
|
||||
(SourceKind::Git(a), SourceKind::Git(b)) => a.cmp(b),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Forwards to `Ord`
|
||||
impl PartialOrd for SourceKind {
|
||||
fn partial_cmp(&self, other: &SourceKind) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
/// Information to find a specific commit in a Git repository.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum GitReference {
|
||||
/// From a tag.
|
||||
Tag(String),
|
||||
/// From a branch.
|
||||
Branch(String),
|
||||
/// From a specific revision. Can be a commit hash (either short or full),
|
||||
/// or a named reference like `refs/pull/493/head`.
|
||||
Rev(String),
|
||||
/// The default branch of the repository, the reference named `HEAD`.
|
||||
DefaultBranch,
|
||||
}
|
||||
|
||||
impl GitReference {
|
||||
pub fn from_query(
|
||||
query_pairs: impl Iterator<Item = (impl AsRef<str>, impl AsRef<str>)>,
|
||||
) -> Self {
|
||||
let mut reference = GitReference::DefaultBranch;
|
||||
for (k, v) in query_pairs {
|
||||
let v = v.as_ref();
|
||||
match k.as_ref() {
|
||||
// Map older 'ref' to branch.
|
||||
"branch" | "ref" => reference = GitReference::Branch(v.to_owned()),
|
||||
|
||||
"rev" => reference = GitReference::Rev(v.to_owned()),
|
||||
"tag" => reference = GitReference::Tag(v.to_owned()),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
reference
|
||||
}
|
||||
|
||||
/// Returns a `Display`able view of this git reference, or None if using
|
||||
/// the head of the default branch
|
||||
pub fn pretty_ref(&self, url_encoded: bool) -> Option<PrettyRef<'_>> {
|
||||
match self {
|
||||
GitReference::DefaultBranch => None,
|
||||
_ => Some(PrettyRef {
|
||||
inner: self,
|
||||
url_encoded,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A git reference that can be `Display`ed
|
||||
pub struct PrettyRef<'a> {
|
||||
inner: &'a GitReference,
|
||||
url_encoded: bool,
|
||||
}
|
||||
|
||||
impl<'a> std::fmt::Display for PrettyRef<'a> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let value: &str;
|
||||
match self.inner {
|
||||
GitReference::Branch(s) => {
|
||||
write!(f, "branch=")?;
|
||||
value = s;
|
||||
}
|
||||
GitReference::Tag(s) => {
|
||||
write!(f, "tag=")?;
|
||||
value = s;
|
||||
}
|
||||
GitReference::Rev(s) => {
|
||||
write!(f, "rev=")?;
|
||||
value = s;
|
||||
}
|
||||
GitReference::DefaultBranch => unreachable!(),
|
||||
}
|
||||
if self.url_encoded {
|
||||
for value in url::form_urlencoded::byte_serialize(value.as_bytes()) {
|
||||
write!(f, "{value}")?;
|
||||
}
|
||||
} else {
|
||||
write!(f, "{value}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -15,8 +15,8 @@ use serde::ser;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use serde_untagged::UntaggedEnumVisitor;
|
||||
|
||||
use crate::core::PackageIdSpec;
|
||||
use crate::util::RustVersion;
|
||||
use crate::util_schemas::core::PackageIdSpec;
|
||||
|
||||
/// This type is used to deserialize `Cargo.toml` files.
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
|
|
|
@ -5,4 +5,5 @@
|
|||
//! Any logic for getting final semantics from these will likely need other tools to process, like
|
||||
//! `cargo metadata`.
|
||||
|
||||
pub mod core;
|
||||
pub mod manifest;
|
||||
|
|
Loading…
Reference in a new issue