From 598cfab7e9c4d05d44d178a50a72ae69c7abd894 Mon Sep 17 00:00:00 2001 From: Vito Secona Date: Sat, 13 Sep 2025 14:09:17 +0700 Subject: [PATCH 01/10] refactor: move out into_resolve from EncodableResolve --- src/cargo/core/resolver/encode.rs | 522 +++++++++++++++--------------- src/cargo/ops/lockfile.rs | 5 +- 2 files changed, 265 insertions(+), 262 deletions(-) diff --git a/src/cargo/core/resolver/encode.rs b/src/cargo/core/resolver/encode.rs index 0c629f93529..ef0147b83c5 100644 --- a/src/cargo/core/resolver/encode.rs +++ b/src/cargo/core/resolver/encode.rs @@ -144,297 +144,299 @@ struct Patch { pub type Metadata = BTreeMap; -impl EncodableResolve { - /// Convert a `Cargo.lock` to a Resolve. - /// - /// Note that this `Resolve` is not "complete". For example, the - /// dependencies do not know the difference between regular/dev/build - /// dependencies, so they are not filled in. It also does not include - /// `features`. Care should be taken when using this Resolve. One of the - /// primary uses is to be used with `resolve_with_previous` to guide the - /// resolver to create a complete Resolve. - pub fn into_resolve(self, original: &str, ws: &Workspace<'_>) -> CargoResult { - let path_deps: HashMap> = build_path_deps(ws)?; - let mut checksums = HashMap::new(); - - let mut version = match self.version { - Some(n @ 5) if ws.gctx().nightly_features_allowed => { - if ws.gctx().cli_unstable().next_lockfile_bump { - ResolveVersion::V5 - } else { - anyhow::bail!("lock file version `{n}` requires `-Znext-lockfile-bump`"); - } - } - Some(4) => ResolveVersion::V4, - Some(3) => ResolveVersion::V3, - Some(n) => bail!( - "lock file version `{}` was found, but this version of Cargo \ - does not understand this lock file, perhaps Cargo needs \ - to be updated?", - n, - ), - // Historically Cargo did not have a version indicator in lock - // files, so this could either be the V1 or V2 encoding. We assume - // an older format is being parsed until we see so otherwise. - None => ResolveVersion::V1, - }; - - let packages = { - let mut packages = self.package.unwrap_or_default(); - if let Some(root) = self.root { - packages.insert(0, root); +/// Convert a `Cargo.lock` to a Resolve. +/// +/// Note that this `Resolve` is not "complete". For example, the +/// dependencies do not know the difference between regular/dev/build +/// dependencies, so they are not filled in. It also does not include +/// `features`. Care should be taken when using this Resolve. One of the +/// primary uses is to be used with `resolve_with_previous` to guide the +/// resolver to create a complete Resolve. +pub fn into_resolve( + resolve: EncodableResolve, + original: &str, + ws: &Workspace<'_>, +) -> CargoResult { + let path_deps: HashMap> = build_path_deps(ws)?; + let mut checksums = HashMap::new(); + + let mut version = match resolve.version { + Some(n @ 5) if ws.gctx().nightly_features_allowed => { + if ws.gctx().cli_unstable().next_lockfile_bump { + ResolveVersion::V5 + } else { + anyhow::bail!("lock file version `{n}` requires `-Znext-lockfile-bump`"); } - packages - }; + } + Some(4) => ResolveVersion::V4, + Some(3) => ResolveVersion::V3, + Some(n) => bail!( + "lock file version `{}` was found, but this version of Cargo \ + does not understand this lock file, perhaps Cargo needs \ + to be updated?", + n, + ), + // Historically Cargo did not have a version indicator in lock + // files, so this could either be the V1 or V2 encoding. We assume + // an older format is being parsed until we see so otherwise. + None => ResolveVersion::V1, + }; - // `PackageId`s in the lock file don't include the `source` part - // for workspace members, so we reconstruct proper IDs. - let live_pkgs = { - let mut live_pkgs = HashMap::new(); - let mut all_pkgs = HashSet::new(); - for pkg in packages.iter() { - let enc_id = EncodablePackageId { - name: pkg.name.clone(), - version: Some(pkg.version.clone()), - source: pkg.source.clone(), - }; + let packages = { + let mut packages = resolve.package.unwrap_or_default(); + if let Some(root) = resolve.root { + packages.insert(0, root); + } + packages + }; - if !all_pkgs.insert(enc_id.clone()) { - anyhow::bail!("package `{}` is specified twice in the lockfile", pkg.name); - } - let id = match pkg - .source - .as_deref() - .or_else(|| get_source_id(&path_deps, pkg)) - { - // We failed to find a local package in the workspace. - // It must have been removed and should be ignored. - None => { - debug!("path dependency now missing {} v{}", pkg.name, pkg.version); - continue; - } - Some(&source) => PackageId::try_new(&pkg.name, &pkg.version, source)?, - }; + // `PackageId`s in the lock file don't include the `source` part + // for workspace members, so we reconstruct proper IDs. + let live_pkgs = { + let mut live_pkgs = HashMap::new(); + let mut all_pkgs = HashSet::new(); + for pkg in packages.iter() { + let enc_id = EncodablePackageId { + name: pkg.name.clone(), + version: Some(pkg.version.clone()), + source: pkg.source.clone(), + }; - // If a package has a checksum listed directly on it then record - // that here, and we also bump our version up to 2 since V1 - // didn't ever encode this field. - if let Some(cksum) = &pkg.checksum { - version = version.max(ResolveVersion::V2); - checksums.insert(id, Some(cksum.clone())); + if !all_pkgs.insert(enc_id.clone()) { + anyhow::bail!("package `{}` is specified twice in the lockfile", pkg.name); + } + let id = match pkg + .source + .as_deref() + .or_else(|| get_source_id(&path_deps, pkg)) + { + // We failed to find a local package in the workspace. + // It must have been removed and should be ignored. + None => { + debug!("path dependency now missing {} v{}", pkg.name, pkg.version); + continue; } + Some(&source) => PackageId::try_new(&pkg.name, &pkg.version, source)?, + }; - assert!(live_pkgs.insert(enc_id, (id, pkg)).is_none()) + // If a package has a checksum listed directly on it then record + // that here, and we also bump our version up to 2 since V1 + // didn't ever encode this field. + if let Some(cksum) = &pkg.checksum { + version = version.max(ResolveVersion::V2); + checksums.insert(id, Some(cksum.clone())); } - live_pkgs - }; - // When decoding a V2 version the edges in `dependencies` aren't - // guaranteed to have either version or source information. This `map` - // is used to find package ids even if dependencies have missing - // information. This map is from name to version to source to actual - // package ID. (various levels to drill down step by step) - let mut map = HashMap::new(); - for (id, _) in live_pkgs.values() { - map.entry(id.name().as_str()) - .or_insert_with(HashMap::new) - .entry(id.version().to_string()) - .or_insert_with(HashMap::new) - .insert(id.source_id(), *id); + assert!(live_pkgs.insert(enc_id, (id, pkg)).is_none()) } + live_pkgs + }; - let mut lookup_id = |enc_id: &EncodablePackageId| -> Option { - // The name of this package should always be in the larger list of - // all packages. - let by_version = map.get(enc_id.name.as_str())?; - - // If the version is provided, look that up. Otherwise if the - // version isn't provided this is a V2 manifest and we should only - // have one version for this name. If we have more than one version - // for the name then it's ambiguous which one we'd use. That - // shouldn't ever actually happen but in theory bad git merges could - // produce invalid lock files, so silently ignore these cases. - let by_source = match &enc_id.version { - Some(version) => by_version.get(version)?, - None => { - version = version.max(ResolveVersion::V2); - if by_version.len() == 1 { - by_version.values().next().unwrap() - } else { - return None; - } - } - }; + // When decoding a V2 version the edges in `dependencies` aren't + // guaranteed to have either version or source information. This `map` + // is used to find package ids even if dependencies have missing + // information. This map is from name to version to source to actual + // package ID. (various levels to drill down step by step) + let mut map = HashMap::new(); + for (id, _) in live_pkgs.values() { + map.entry(id.name().as_str()) + .or_insert_with(HashMap::new) + .entry(id.version().to_string()) + .or_insert_with(HashMap::new) + .insert(id.source_id(), *id); + } - // This is basically the same as above. Note though that `source` is - // always missing for path dependencies regardless of serialization - // format. That means we have to handle the `None` case a bit more - // carefully. - match &enc_id.source { - Some(source) => by_source.get(source).cloned(), - None => { - // Look through all possible packages ids for this - // name/version. If there's only one `path` dependency then - // we are hardcoded to use that since `path` dependencies - // can't have a source listed. - let mut path_packages = by_source.values().filter(|p| p.source_id().is_path()); - if let Some(path) = path_packages.next() { - if path_packages.next().is_some() { - return None; - } - Some(*path) - - // ... otherwise if there's only one then we must be - // implicitly using that one due to a V2 serialization of - // the lock file - } else if by_source.len() == 1 { - let id = by_source.values().next().unwrap(); - version = version.max(ResolveVersion::V2); - Some(*id) - - // ... and failing that we probably had a bad git merge of - // `Cargo.lock` or something like that, so just ignore this. - } else { - None - } + let mut lookup_id = |enc_id: &EncodablePackageId| -> Option { + // The name of this package should always be in the larger list of + // all packages. + let by_version = map.get(enc_id.name.as_str())?; + + // If the version is provided, look that up. Otherwise if the + // version isn't provided this is a V2 manifest and we should only + // have one version for this name. If we have more than one version + // for the name then it's ambiguous which one we'd use. That + // shouldn't ever actually happen but in theory bad git merges could + // produce invalid lock files, so silently ignore these cases. + let by_source = match &enc_id.version { + Some(version) => by_version.get(version)?, + None => { + version = version.max(ResolveVersion::V2); + if by_version.len() == 1 { + by_version.values().next().unwrap() + } else { + return None; } } }; - let mut g = Graph::new(); - - for (id, _) in live_pkgs.values() { - g.add(*id); - } + // This is basically the same as above. Note though that `source` is + // always missing for path dependencies regardless of serialization + // format. That means we have to handle the `None` case a bit more + // carefully. + match &enc_id.source { + Some(source) => by_source.get(source).cloned(), + None => { + // Look through all possible packages ids for this + // name/version. If there's only one `path` dependency then + // we are hardcoded to use that since `path` dependencies + // can't have a source listed. + let mut path_packages = by_source.values().filter(|p| p.source_id().is_path()); + if let Some(path) = path_packages.next() { + if path_packages.next().is_some() { + return None; + } + Some(*path) - for &(ref id, pkg) in live_pkgs.values() { - let Some(ref deps) = pkg.dependencies else { - continue; - }; + // ... otherwise if there's only one then we must be + // implicitly using that one due to a V2 serialization of + // the lock file + } else if by_source.len() == 1 { + let id = by_source.values().next().unwrap(); + version = version.max(ResolveVersion::V2); + Some(*id) - for edge in deps.iter() { - if let Some(to_depend_on) = lookup_id(edge) { - g.link(*id, to_depend_on); + // ... and failing that we probably had a bad git merge of + // `Cargo.lock` or something like that, so just ignore this. + } else { + None } } } + }; - let replacements = { - let mut replacements = HashMap::new(); - for &(ref id, pkg) in live_pkgs.values() { - if let Some(ref replace) = pkg.replace { - assert!(pkg.dependencies.is_none()); - if let Some(replace_id) = lookup_id(replace) { - replacements.insert(*id, replace_id); - } - } - } - replacements - }; + let mut g = Graph::new(); - let mut metadata = self.metadata.unwrap_or_default(); - - // In the V1 serialization formats all checksums were listed in the lock - // file in the `[metadata]` section, so if we're still V1 then look for - // that here. - let prefix = "checksum "; - let mut to_remove = Vec::new(); - for (k, v) in metadata.iter().filter(|p| p.0.starts_with(prefix)) { - to_remove.push(k.to_string()); - let k = k.strip_prefix(prefix).unwrap(); - let enc_id: EncodablePackageId = k - .parse() - .with_context(|| internal("invalid encoding of checksum in lockfile"))?; - let Some(id) = lookup_id(&enc_id) else { - continue; - }; + for (id, _) in live_pkgs.values() { + g.add(*id); + } - let v = if v == "" { - None - } else { - Some(v.to_string()) - }; - checksums.insert(id, v); - } - // If `checksum` was listed in `[metadata]` but we were previously - // listed as `V2` then assume some sort of bad git merge happened, so - // discard all checksums and let's regenerate them later. - if !to_remove.is_empty() && version >= ResolveVersion::V2 { - checksums.drain(); - } - for k in to_remove { - metadata.remove(&k); + for &(ref id, pkg) in live_pkgs.values() { + let Some(ref deps) = pkg.dependencies else { + continue; + }; + + for edge in deps.iter() { + if let Some(to_depend_on) = lookup_id(edge) { + g.link(*id, to_depend_on); + } } + } - let mut unused_patches = Vec::new(); - for pkg in self.patch.unused { - let id = match pkg - .source - .as_deref() - .or_else(|| get_source_id(&path_deps, &pkg)) - { - Some(&src) => PackageId::try_new(&pkg.name, &pkg.version, src)?, - None => continue, - }; - unused_patches.push(id); + let replacements = { + let mut replacements = HashMap::new(); + for &(ref id, pkg) in live_pkgs.values() { + if let Some(ref replace) = pkg.replace { + assert!(pkg.dependencies.is_none()); + if let Some(replace_id) = lookup_id(replace) { + replacements.insert(*id, replace_id); + } + } } + replacements + }; + + let mut metadata = resolve.metadata.unwrap_or_default(); + + // In the V1 serialization formats all checksums were listed in the lock + // file in the `[metadata]` section, so if we're still V1 then look for + // that here. + let prefix = "checksum "; + let mut to_remove = Vec::new(); + for (k, v) in metadata.iter().filter(|p| p.0.starts_with(prefix)) { + to_remove.push(k.to_string()); + let k = k.strip_prefix(prefix).unwrap(); + let enc_id: EncodablePackageId = k + .parse() + .with_context(|| internal("invalid encoding of checksum in lockfile"))?; + let Some(id) = lookup_id(&enc_id) else { + continue; + }; - // We have a curious issue where in the "v1 format" we buggily had a - // trailing blank line at the end of lock files under some specific - // conditions. - // - // Cargo is trying to write new lockfies in the "v2 format" but if you - // have no dependencies, for example, then the lockfile encoded won't - // really have any indicator that it's in the new format (no - // dependencies or checksums listed). This means that if you type `cargo - // new` followed by `cargo build` it will generate a "v2 format" lock - // file since none previously existed. When reading this on the next - // `cargo build`, however, it generates a new lock file because when - // reading in that lockfile we think it's the v1 format. - // - // To help fix this issue we special case here. If our lockfile only has - // one trailing newline, not two, *and* it only has one package, then - // this is actually the v2 format. - if original.ends_with('\n') - && !original.ends_with("\n\n") - && version == ResolveVersion::V1 - && g.iter().count() == 1 + let v = if v == "" { + None + } else { + Some(v.to_string()) + }; + checksums.insert(id, v); + } + // If `checksum` was listed in `[metadata]` but we were previously + // listed as `V2` then assume some sort of bad git merge happened, so + // discard all checksums and let's regenerate them later. + if !to_remove.is_empty() && version >= ResolveVersion::V2 { + checksums.drain(); + } + for k in to_remove { + metadata.remove(&k); + } + + let mut unused_patches = Vec::new(); + for pkg in resolve.patch.unused { + let id = match pkg + .source + .as_deref() + .or_else(|| get_source_id(&path_deps, &pkg)) { - version = ResolveVersion::V2; - } + Some(&src) => PackageId::try_new(&pkg.name, &pkg.version, src)?, + None => continue, + }; + unused_patches.push(id); + } - return Ok(Resolve::new( - g, - replacements, - HashMap::new(), - checksums, - metadata, - unused_patches, - version, - HashMap::new(), - )); - - fn get_source_id<'a>( - path_deps: &'a HashMap>, - pkg: &'a EncodableDependency, - ) -> Option<&'a SourceId> { - path_deps.iter().find_map(|(name, version_source)| { - if name != &pkg.name || version_source.len() == 0 { - return None; - } - if version_source.len() == 1 { - return Some(version_source.values().next().unwrap()); - } - // If there are multiple candidates for the same name, it needs to be determined by combining versions (See #13405). - if let Ok(pkg_version) = pkg.version.parse::() { - if let Some(source_id) = version_source.get(&pkg_version) { - return Some(source_id); - } + // We have a curious issue where in the "v1 format" we buggily had a + // trailing blank line at the end of lock files under some specific + // conditions. + // + // Cargo is trying to write new lockfies in the "v2 format" but if you + // have no dependencies, for example, then the lockfile encoded won't + // really have any indicator that it's in the new format (no + // dependencies or checksums listed). This means that if you type `cargo + // new` followed by `cargo build` it will generate a "v2 format" lock + // file since none previously existed. When reading this on the next + // `cargo build`, however, it generates a new lock file because when + // reading in that lockfile we think it's the v1 format. + // + // To help fix this issue we special case here. If our lockfile only has + // one trailing newline, not two, *and* it only has one package, then + // this is actually the v2 format. + if original.ends_with('\n') + && !original.ends_with("\n\n") + && version == ResolveVersion::V1 + && g.iter().count() == 1 + { + version = ResolveVersion::V2; + } + + return Ok(Resolve::new( + g, + replacements, + HashMap::new(), + checksums, + metadata, + unused_patches, + version, + HashMap::new(), + )); + + fn get_source_id<'a>( + path_deps: &'a HashMap>, + pkg: &'a EncodableDependency, + ) -> Option<&'a SourceId> { + path_deps.iter().find_map(|(name, version_source)| { + if name != &pkg.name || version_source.len() == 0 { + return None; + } + if version_source.len() == 1 { + return Some(version_source.values().next().unwrap()); + } + // If there are multiple candidates for the same name, it needs to be determined by combining versions (See #13405). + if let Ok(pkg_version) = pkg.version.parse::() { + if let Some(source_id) = version_source.get(&pkg_version) { + return Some(source_id); } + } - None - }) - } + None + }) } } diff --git a/src/cargo/ops/lockfile.rs b/src/cargo/ops/lockfile.rs index 279c9a100dc..04c2a834185 100644 --- a/src/cargo/ops/lockfile.rs +++ b/src/cargo/ops/lockfile.rs @@ -1,5 +1,6 @@ use std::io::prelude::*; +use crate::core::resolver::encode::into_resolve; use crate::core::{Resolve, ResolveVersion, Workspace, resolver}; use crate::util::Filesystem; use crate::util::errors::CargoResult; @@ -23,7 +24,7 @@ pub fn load_pkg_lockfile(ws: &Workspace<'_>) -> CargoResult> { let resolve = (|| -> CargoResult> { let v: resolver::EncodableResolve = toml::from_str(&s)?; - Ok(Some(v.into_resolve(&s, ws)?)) + Ok(Some(into_resolve(v, &s, ws)?)) })() .with_context(|| format!("failed to parse lock file at: {}", f.path().display()))?; Ok(resolve) @@ -208,7 +209,7 @@ fn are_equal_lockfiles(orig: &str, current: &str, ws: &Workspace<'_>) -> bool { let res: CargoResult = (|| { let old: resolver::EncodableResolve = toml::from_str(orig)?; let new: resolver::EncodableResolve = toml::from_str(current)?; - Ok(old.into_resolve(orig, ws)? == new.into_resolve(current, ws)?) + Ok(into_resolve(old, orig, ws)? == into_resolve(new, current, ws)?) })(); if let Ok(true) = res { return true; From 6a97f6469a5867dd73784cf950af515a963815bd Mon Sep 17 00:00:00 2001 From: Vito Secona Date: Sun, 14 Sep 2025 08:37:52 +0700 Subject: [PATCH 02/10] refactor: inline `EncodableSourceId` deserialization --- src/cargo/core/resolver/encode.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/cargo/core/resolver/encode.rs b/src/cargo/core/resolver/encode.rs index ef0147b83c5..675c568b082 100644 --- a/src/cargo/core/resolver/encode.rs +++ b/src/cargo/core/resolver/encode.rs @@ -535,15 +535,13 @@ pub struct EncodableDependency { /// The serialization for `SourceId` doesn't do URL encode for parameters. /// In contrast, this type is aware of that whenever [`ResolveVersion`] allows /// us to do so (v4 or later). -#[derive(Deserialize, Debug, PartialOrd, Ord, Clone)] -#[serde(transparent)] +#[derive(Debug, PartialOrd, Ord, Clone)] pub struct EncodableSourceId { inner: SourceId, /// We don't care about the deserialization of this, as the `url` crate /// will always decode as the URL was encoded. Only when a [`Resolve`] /// turns into a [`EncodableResolve`] will it set the value accordingly /// via [`encodable_source_id`]. - #[serde(skip)] encoded: bool, } @@ -592,6 +590,20 @@ impl ser::Serialize for EncodableSourceId { } } +impl<'de> de::Deserialize<'de> for EncodableSourceId { + fn deserialize(d: D) -> Result + where + D: de::Deserializer<'de>, + { + let s = String::deserialize(d)?; + let sid = SourceId::from_url(&s).map_err(de::Error::custom)?; + Ok(EncodableSourceId { + inner: sid, + encoded: false, + }) + } +} + impl std::hash::Hash for EncodableSourceId { fn hash(&self, state: &mut H) { self.inner.hash(state) From ef7814f399906642432eaeeb7415b2b6fff77587 Mon Sep 17 00:00:00 2001 From: Vito Secona Date: Sun, 14 Sep 2025 09:08:51 +0700 Subject: [PATCH 03/10] refactor: replace `SourceId` in `EncodableSourceId` --- src/cargo/core/resolver/encode.rs | 144 ++++++++++++++++++------------ 1 file changed, 85 insertions(+), 59 deletions(-) diff --git a/src/cargo/core/resolver/encode.rs b/src/cargo/core/resolver/encode.rs index 675c568b082..0f0b37c522b 100644 --- a/src/cargo/core/resolver/encode.rs +++ b/src/cargo/core/resolver/encode.rs @@ -117,13 +117,16 @@ use crate::util::errors::CargoResult; use crate::util::interning::InternedString; use crate::util::{Graph, internal}; use anyhow::{Context as _, bail}; +use cargo_util_schemas::core::SourceKind; use serde::de; use serde::ser; use serde::{Deserialize, Serialize}; +use std::cmp::Ordering; use std::collections::{BTreeMap, HashMap, HashSet}; use std::fmt; use std::str::FromStr; use tracing::debug; +use url::Url; /// The `Cargo.lock` structure. #[derive(Serialize, Deserialize, Debug)] @@ -207,8 +210,10 @@ pub fn into_resolve( } let id = match pkg .source - .as_deref() - .or_else(|| get_source_id(&path_deps, pkg)) + .as_ref() + .map(|source| SourceId::from_url(&source.source_str())) + .transpose()? + .or_else(|| get_source_id(&path_deps, &pkg).copied()) { // We failed to find a local package in the workspace. // It must have been removed and should be ignored. @@ -216,7 +221,7 @@ pub fn into_resolve( debug!("path dependency now missing {} v{}", pkg.name, pkg.version); continue; } - Some(&source) => PackageId::try_new(&pkg.name, &pkg.version, source)?, + Some(source) => PackageId::try_new(&pkg.name, &pkg.version, source)?, }; // If a package has a checksum listed directly on it then record @@ -274,7 +279,9 @@ pub fn into_resolve( // format. That means we have to handle the `None` case a bit more // carefully. match &enc_id.source { - Some(source) => by_source.get(source).cloned(), + Some(source) => by_source + .get(&SourceId::from_url(&source.source_str()).unwrap()) + .cloned(), None => { // Look through all possible packages ids for this // name/version. If there's only one `path` dependency then @@ -373,10 +380,12 @@ pub fn into_resolve( for pkg in resolve.patch.unused { let id = match pkg .source - .as_deref() - .or_else(|| get_source_id(&path_deps, &pkg)) + .as_ref() + .map(|source| SourceId::from_url(&source.source_str())) + .transpose()? + .or_else(|| get_source_id(&path_deps, &pkg).copied()) { - Some(&src) => PackageId::try_new(&pkg.name, &pkg.version, src)?, + Some(src) => PackageId::try_new(&pkg.name, &pkg.version, src)?, None => continue, }; unused_patches.push(id); @@ -530,54 +539,58 @@ pub struct EncodableDependency { replace: Option, } -/// Pretty much equivalent to [`SourceId`] with a different serialization method. -/// -/// The serialization for `SourceId` doesn't do URL encode for parameters. -/// In contrast, this type is aware of that whenever [`ResolveVersion`] allows -/// us to do so (v4 or later). -#[derive(Debug, PartialOrd, Ord, Clone)] +#[derive(Debug, Clone)] pub struct EncodableSourceId { - inner: SourceId, - /// We don't care about the deserialization of this, as the `url` crate - /// will always decode as the URL was encoded. Only when a [`Resolve`] - /// turns into a [`EncodableResolve`] will it set the value accordingly - /// via [`encodable_source_id`]. - encoded: bool, + /// Full string of the source + source_str: String, + /// Used for sources ordering + kind: SourceKind, + /// Used for sources ordering + url: Url, } impl EncodableSourceId { - /// Creates a `EncodableSourceId` that always encodes URL params. - fn new(inner: SourceId) -> Self { - Self { - inner, - encoded: true, - } + fn new(source: String) -> CargoResult { + let source_str = source.clone(); + let (kind, url) = source + .split_once('+') + .ok_or_else(|| anyhow::format_err!("invalid source `{}`", source_str))?; + + let url = + Url::parse(url).map_err(|s| anyhow::format_err!("invalid url `{}`: {}", url, s))?; + + let kind = match kind { + "git" => { + let reference = GitReference::from_query(url.query_pairs()); + SourceKind::Git(reference) + } + "registry" => SourceKind::Registry, + "sparse" => SourceKind::SparseRegistry, + "path" => SourceKind::Path, + kind => anyhow::bail!("unsupported source protocol: {}", kind), + }; + + Ok(Self { + source_str, + kind, + url, + }) } - /// Creates a `EncodableSourceId` that doesn't encode URL params. This is - /// for backward compatibility for order lockfile version. - fn without_url_encoded(inner: SourceId) -> Self { - Self { - inner, - encoded: false, - } + pub fn kind(&self) -> &SourceKind { + &self.kind } - /// Encodes the inner [`SourceId`] as a URL. - fn as_url(&self) -> impl fmt::Display + '_ { - if self.encoded { - self.inner.as_encoded_url() - } else { - self.inner.as_url() - } + pub fn url(&self) -> &Url { + &self.url } -} -impl std::ops::Deref for EncodableSourceId { - type Target = SourceId; + pub fn source_str(&self) -> &String { + &self.source_str + } - fn deref(&self) -> &Self::Target { - &self.inner + fn as_url(&self) -> impl fmt::Display + '_ { + self.source_str.clone() } } @@ -596,28 +609,39 @@ impl<'de> de::Deserialize<'de> for EncodableSourceId { D: de::Deserializer<'de>, { let s = String::deserialize(d)?; - let sid = SourceId::from_url(&s).map_err(de::Error::custom)?; - Ok(EncodableSourceId { - inner: sid, - encoded: false, - }) + Ok(EncodableSourceId::new(s).map_err(de::Error::custom)?) } } impl std::hash::Hash for EncodableSourceId { fn hash(&self, state: &mut H) { - self.inner.hash(state) + self.kind.hash(state); + self.url.hash(state); } } impl std::cmp::PartialEq for EncodableSourceId { fn eq(&self, other: &Self) -> bool { - self.inner == other.inner + self.kind == other.kind && self.url == other.url } } impl std::cmp::Eq for EncodableSourceId {} +impl PartialOrd for EncodableSourceId { + fn partial_cmp(&self, other: &EncodableSourceId) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for EncodableSourceId { + fn cmp(&self, other: &EncodableSourceId) -> Ordering { + self.kind + .cmp(&other.kind) + .then_with(|| self.url.cmp(&other.url)) + } +} + #[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Clone)] pub struct EncodablePackageId { name: String, @@ -648,7 +672,7 @@ impl FromStr for EncodablePackageId { let source_id = match s.next() { Some(s) => { if let Some(s) = s.strip_prefix('(').and_then(|s| s.strip_suffix(')')) { - Some(SourceId::from_url(s)?) + Some(EncodableSourceId::new(s.to_string())?) } else { anyhow::bail!("invalid serialized PackageId") } @@ -659,8 +683,7 @@ impl FromStr for EncodablePackageId { Ok(EncodablePackageId { name: name.to_string(), version: version.map(|v| v.to_string()), - // Default to url encoded. - source: source_id.map(EncodableSourceId::new), + source: source_id, }) } } @@ -850,10 +873,13 @@ fn encodable_source_id(id: SourceId, version: ResolveVersion) -> Option= ResolveVersion::V4 { - EncodableSourceId::new(id) - } else { - EncodableSourceId::without_url_encoded(id) - }) + Some( + if version >= ResolveVersion::V4 { + EncodableSourceId::new(id.as_encoded_url().to_string()) + } else { + EncodableSourceId::new(id.as_url().to_string()) + } + .expect("source ID should have valid URLs"), + ) } } From 982ee85b2eb524b1d67787ad98795736511d02f0 Mon Sep 17 00:00:00 2001 From: Vito Secona Date: Fri, 19 Sep 2025 00:06:43 +0700 Subject: [PATCH 04/10] refactor: make certain fields and methods public --- src/cargo/core/resolver/encode.rs | 36 +++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/cargo/core/resolver/encode.rs b/src/cargo/core/resolver/encode.rs index 0f0b37c522b..bf41df35615 100644 --- a/src/cargo/core/resolver/encode.rs +++ b/src/cargo/core/resolver/encode.rs @@ -131,18 +131,18 @@ use url::Url; /// The `Cargo.lock` structure. #[derive(Serialize, Deserialize, Debug)] pub struct EncodableResolve { - version: Option, - package: Option>, + pub version: Option, + pub package: Option>, /// `root` is optional to allow backward compatibility. - root: Option, - metadata: Option, + pub root: Option, + pub metadata: Option, #[serde(default, skip_serializing_if = "Patch::is_empty")] - patch: Patch, + pub patch: Patch, } #[derive(Serialize, Deserialize, Debug, Default)] -struct Patch { - unused: Vec, +pub struct Patch { + pub unused: Vec, } pub type Metadata = BTreeMap; @@ -531,12 +531,12 @@ impl Patch { #[derive(Serialize, Deserialize, Debug, PartialOrd, Ord, PartialEq, Eq)] pub struct EncodableDependency { - name: String, - version: String, - source: Option, - checksum: Option, - dependencies: Option>, - replace: Option, + pub name: String, + pub version: String, + pub source: Option, + pub checksum: Option, + pub dependencies: Option>, + pub replace: Option, } #[derive(Debug, Clone)] @@ -550,7 +550,7 @@ pub struct EncodableSourceId { } impl EncodableSourceId { - fn new(source: String) -> CargoResult { + pub fn new(source: String) -> CargoResult { let source_str = source.clone(); let (kind, url) = source .split_once('+') @@ -589,7 +589,7 @@ impl EncodableSourceId { &self.source_str } - fn as_url(&self) -> impl fmt::Display + '_ { + pub fn as_url(&self) -> impl fmt::Display + '_ { self.source_str.clone() } } @@ -644,9 +644,9 @@ impl Ord for EncodableSourceId { #[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Clone)] pub struct EncodablePackageId { - name: String, - version: Option, - source: Option, + pub name: String, + pub version: Option, + pub source: Option, } impl fmt::Display for EncodablePackageId { From 535f7730fab9d3bffbe52f5c7f1ebbcb11ec5db5 Mon Sep 17 00:00:00 2001 From: Vito Secona Date: Fri, 19 Sep 2025 00:38:56 +0700 Subject: [PATCH 05/10] refactor: define lockfile errors in `cargo-util-schemas` Some of the fields are made public to make the moving phase easier. They will be reverted back to private when its done moving over. --- crates/cargo-util-schemas/src/lib.rs | 1 + crates/cargo-util-schemas/src/lockfile.rs | 36 +++++++++++++++++++++++ src/cargo/core/resolver/encode.rs | 28 +++++++++++------- 3 files changed, 55 insertions(+), 10 deletions(-) create mode 100644 crates/cargo-util-schemas/src/lockfile.rs diff --git a/crates/cargo-util-schemas/src/lib.rs b/crates/cargo-util-schemas/src/lib.rs index 9324480f14d..f03be81ce0c 100644 --- a/crates/cargo-util-schemas/src/lib.rs +++ b/crates/cargo-util-schemas/src/lib.rs @@ -10,6 +10,7 @@ pub mod core; pub mod index; +pub mod lockfile; pub mod manifest; pub mod messages; #[cfg(feature = "unstable-schema")] diff --git a/crates/cargo-util-schemas/src/lockfile.rs b/crates/cargo-util-schemas/src/lockfile.rs new file mode 100644 index 00000000000..23323c93b07 --- /dev/null +++ b/crates/cargo-util-schemas/src/lockfile.rs @@ -0,0 +1,36 @@ +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub struct EncodableSourceIdError(#[from] pub EncodableSourceIdErrorKind); + +#[non_exhaustive] +#[derive(Debug, thiserror::Error)] +pub enum EncodableSourceIdErrorKind { + #[error("invalid source `{0}`")] + InvalidSource(String), + + #[error("invalid url `{url}`: {msg}")] + InvalidUrl { url: String, msg: String }, + + #[error("unsupported source protocol: {0}")] + UnsupportedSource(String), +} + +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub struct EncodablePackageIdError(#[from] EncodablePackageIdErrorKind); + +impl From for EncodablePackageIdError { + fn from(value: EncodableSourceIdError) -> Self { + EncodablePackageIdErrorKind::Source(value).into() + } +} + +#[non_exhaustive] +#[derive(Debug, thiserror::Error)] +pub enum EncodablePackageIdErrorKind { + #[error("invalid serialied PackageId")] + InvalidSerializedPackageId, + + #[error(transparent)] + Source(#[from] EncodableSourceIdError), +} diff --git a/src/cargo/core/resolver/encode.rs b/src/cargo/core/resolver/encode.rs index bf41df35615..3569bc9a138 100644 --- a/src/cargo/core/resolver/encode.rs +++ b/src/cargo/core/resolver/encode.rs @@ -118,6 +118,10 @@ use crate::util::interning::InternedString; use crate::util::{Graph, internal}; use anyhow::{Context as _, bail}; use cargo_util_schemas::core::SourceKind; +use cargo_util_schemas::lockfile::{ + EncodablePackageIdError, EncodablePackageIdErrorKind, EncodableSourceIdError, + EncodableSourceIdErrorKind, +}; use serde::de; use serde::ser; use serde::{Deserialize, Serialize}; @@ -550,14 +554,16 @@ pub struct EncodableSourceId { } impl EncodableSourceId { - pub fn new(source: String) -> CargoResult { + pub fn new(source: String) -> Result { let source_str = source.clone(); - let (kind, url) = source - .split_once('+') - .ok_or_else(|| anyhow::format_err!("invalid source `{}`", source_str))?; + let (kind, url) = source.split_once('+').ok_or_else(|| { + EncodableSourceIdError(EncodableSourceIdErrorKind::InvalidSource(source.clone()).into()) + })?; - let url = - Url::parse(url).map_err(|s| anyhow::format_err!("invalid url `{}`: {}", url, s))?; + let url = Url::parse(url).map_err(|msg| EncodableSourceIdErrorKind::InvalidUrl { + url: url.to_string(), + msg: msg.to_string(), + })?; let kind = match kind { "git" => { @@ -567,7 +573,9 @@ impl EncodableSourceId { "registry" => SourceKind::Registry, "sparse" => SourceKind::SparseRegistry, "path" => SourceKind::Path, - kind => anyhow::bail!("unsupported source protocol: {}", kind), + kind => { + return Err(EncodableSourceIdErrorKind::UnsupportedSource(kind.to_string()).into()); + } }; Ok(Self { @@ -663,9 +671,9 @@ impl fmt::Display for EncodablePackageId { } impl FromStr for EncodablePackageId { - type Err = anyhow::Error; + type Err = EncodablePackageIdError; - fn from_str(s: &str) -> CargoResult { + fn from_str(s: &str) -> Result { let mut s = s.splitn(3, ' '); let name = s.next().unwrap(); let version = s.next(); @@ -674,7 +682,7 @@ impl FromStr for EncodablePackageId { if let Some(s) = s.strip_prefix('(').and_then(|s| s.strip_suffix(')')) { Some(EncodableSourceId::new(s.to_string())?) } else { - anyhow::bail!("invalid serialized PackageId") + return Err(EncodablePackageIdErrorKind::InvalidSerializedPackageId.into()); } } None => None, From efc42e1342e81f3df638f8673eb19f2224c532d2 Mon Sep 17 00:00:00 2001 From: Vito Secona Date: Fri, 19 Sep 2025 01:06:09 +0700 Subject: [PATCH 06/10] refactor: change imports of `encode` simplifies the moving over of the schemas --- src/cargo/ops/lockfile.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/cargo/ops/lockfile.rs b/src/cargo/ops/lockfile.rs index 04c2a834185..5102df9d0c1 100644 --- a/src/cargo/ops/lockfile.rs +++ b/src/cargo/ops/lockfile.rs @@ -1,7 +1,8 @@ use std::io::prelude::*; +use crate::core::resolver::encode::EncodableResolve; use crate::core::resolver::encode::into_resolve; -use crate::core::{Resolve, ResolveVersion, Workspace, resolver}; +use crate::core::{Resolve, ResolveVersion, Workspace}; use crate::util::Filesystem; use crate::util::errors::CargoResult; @@ -23,7 +24,7 @@ pub fn load_pkg_lockfile(ws: &Workspace<'_>) -> CargoResult> { .with_context(|| format!("failed to read file: {}", f.path().display()))?; let resolve = (|| -> CargoResult> { - let v: resolver::EncodableResolve = toml::from_str(&s)?; + let v: EncodableResolve = toml::from_str(&s)?; Ok(Some(into_resolve(v, &s, ws)?)) })() .with_context(|| format!("failed to parse lock file at: {}", f.path().display()))?; @@ -207,8 +208,8 @@ fn are_equal_lockfiles(orig: &str, current: &str, ws: &Workspace<'_>) -> bool { // common case where we can update lock files. if !ws.gctx().lock_update_allowed() { let res: CargoResult = (|| { - let old: resolver::EncodableResolve = toml::from_str(orig)?; - let new: resolver::EncodableResolve = toml::from_str(current)?; + let old: EncodableResolve = toml::from_str(orig)?; + let new: EncodableResolve = toml::from_str(current)?; Ok(into_resolve(old, orig, ws)? == into_resolve(new, current, ws)?) })(); if let Ok(true) = res { From 949e863cb908ca7b4d45e2f5ec9437b9f695ce76 Mon Sep 17 00:00:00 2001 From: Vito Secona Date: Fri, 19 Sep 2025 00:58:58 +0700 Subject: [PATCH 07/10] refactor: move lockfile schemas to `cargo-util-schemas` --- crates/cargo-util-schemas/src/lockfile.rs | 225 +++++++++++++++++++++- src/cargo/core/resolver/encode.rs | 222 +-------------------- src/cargo/core/resolver/mod.rs | 2 - src/cargo/core/resolver/resolve.rs | 2 +- src/cargo/ops/lockfile.rs | 2 +- 5 files changed, 226 insertions(+), 227 deletions(-) diff --git a/crates/cargo-util-schemas/src/lockfile.rs b/crates/cargo-util-schemas/src/lockfile.rs index 23323c93b07..84bf4b0ac40 100644 --- a/crates/cargo-util-schemas/src/lockfile.rs +++ b/crates/cargo-util-schemas/src/lockfile.rs @@ -1,10 +1,229 @@ +use std::collections::BTreeMap; +use std::fmt; +use std::{cmp::Ordering, str::FromStr}; + +use serde::{Deserialize, Serialize, de, ser}; +use url::Url; + +use crate::core::{GitReference, SourceKind}; + +/// The `Cargo.lock` structure. +#[derive(Serialize, Deserialize, Debug)] +pub struct EncodableResolve { + pub version: Option, + pub package: Option>, + /// `root` is optional to allow backward compatibility. + pub root: Option, + pub metadata: Option, + #[serde(default, skip_serializing_if = "Patch::is_empty")] + pub patch: Patch, +} + +#[derive(Serialize, Deserialize, Debug, Default)] +pub struct Patch { + pub unused: Vec, +} + +pub type Metadata = BTreeMap; + +impl Patch { + fn is_empty(&self) -> bool { + self.unused.is_empty() + } +} + +#[derive(Serialize, Deserialize, Debug, PartialOrd, Ord, PartialEq, Eq)] +pub struct EncodableDependency { + pub name: String, + pub version: String, + pub source: Option, + pub checksum: Option, + pub dependencies: Option>, + pub replace: Option, +} + +#[derive(Debug, Clone)] +pub struct EncodableSourceId { + /// Full string of the source + source_str: String, + /// Used for sources ordering + kind: SourceKind, + /// Used for sources ordering + url: Url, +} + +impl EncodableSourceId { + pub fn new(source: String) -> Result { + let source_str = source.clone(); + let (kind, url) = source.split_once('+').ok_or_else(|| { + EncodableSourceIdError(EncodableSourceIdErrorKind::InvalidSource(source.clone()).into()) + })?; + + let url = Url::parse(url).map_err(|msg| EncodableSourceIdErrorKind::InvalidUrl { + url: url.to_string(), + msg: msg.to_string(), + })?; + + let kind = match kind { + "git" => { + let reference = GitReference::from_query(url.query_pairs()); + SourceKind::Git(reference) + } + "registry" => SourceKind::Registry, + "sparse" => SourceKind::SparseRegistry, + "path" => SourceKind::Path, + kind => { + return Err(EncodableSourceIdErrorKind::UnsupportedSource(kind.to_string()).into()); + } + }; + + Ok(Self { + source_str, + kind, + url, + }) + } + + pub fn kind(&self) -> &SourceKind { + &self.kind + } + + pub fn url(&self) -> &Url { + &self.url + } + + pub fn source_str(&self) -> &String { + &self.source_str + } + + pub fn as_url(&self) -> impl fmt::Display + '_ { + self.source_str.clone() + } +} + +impl ser::Serialize for EncodableSourceId { + fn serialize(&self, s: S) -> Result + where + S: ser::Serializer, + { + s.collect_str(&self.as_url()) + } +} + +impl<'de> de::Deserialize<'de> for EncodableSourceId { + fn deserialize(d: D) -> Result + where + D: de::Deserializer<'de>, + { + let s = String::deserialize(d)?; + Ok(EncodableSourceId::new(s).map_err(de::Error::custom)?) + } +} + +impl std::hash::Hash for EncodableSourceId { + fn hash(&self, state: &mut H) { + self.kind.hash(state); + self.url.hash(state); + } +} + +impl std::cmp::PartialEq for EncodableSourceId { + fn eq(&self, other: &Self) -> bool { + self.kind == other.kind && self.url == other.url + } +} + +impl std::cmp::Eq for EncodableSourceId {} + +impl PartialOrd for EncodableSourceId { + fn partial_cmp(&self, other: &EncodableSourceId) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for EncodableSourceId { + fn cmp(&self, other: &EncodableSourceId) -> Ordering { + self.kind + .cmp(&other.kind) + .then_with(|| self.url.cmp(&other.url)) + } +} + +#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Clone)] +pub struct EncodablePackageId { + pub name: String, + pub version: Option, + pub source: Option, +} + +impl fmt::Display for EncodablePackageId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.name)?; + if let Some(s) = &self.version { + write!(f, " {}", s)?; + } + if let Some(s) = &self.source { + write!(f, " ({})", s.as_url())?; + } + Ok(()) + } +} + +impl FromStr for EncodablePackageId { + type Err = EncodablePackageIdError; + + fn from_str(s: &str) -> Result { + let mut s = s.splitn(3, ' '); + let name = s.next().unwrap(); + let version = s.next(); + let source_id = match s.next() { + Some(s) => { + if let Some(s) = s.strip_prefix('(').and_then(|s| s.strip_suffix(')')) { + Some(EncodableSourceId::new(s.to_string())?) + } else { + return Err(EncodablePackageIdErrorKind::InvalidSerializedPackageId.into()); + } + } + None => None, + }; + + Ok(EncodablePackageId { + name: name.to_string(), + version: version.map(|v| v.to_string()), + source: source_id, + }) + } +} + +impl ser::Serialize for EncodablePackageId { + fn serialize(&self, s: S) -> Result + where + S: ser::Serializer, + { + s.collect_str(self) + } +} + +impl<'de> de::Deserialize<'de> for EncodablePackageId { + fn deserialize(d: D) -> Result + where + D: de::Deserializer<'de>, + { + String::deserialize(d).and_then(|string| { + string + .parse::() + .map_err(de::Error::custom) + }) + } +} + #[derive(Debug, thiserror::Error)] #[error(transparent)] -pub struct EncodableSourceIdError(#[from] pub EncodableSourceIdErrorKind); +pub struct EncodableSourceIdError(#[from] EncodableSourceIdErrorKind); #[non_exhaustive] #[derive(Debug, thiserror::Error)] -pub enum EncodableSourceIdErrorKind { +enum EncodableSourceIdErrorKind { #[error("invalid source `{0}`")] InvalidSource(String), @@ -27,7 +246,7 @@ impl From for EncodablePackageIdError { #[non_exhaustive] #[derive(Debug, thiserror::Error)] -pub enum EncodablePackageIdErrorKind { +enum EncodablePackageIdErrorKind { #[error("invalid serialied PackageId")] InvalidSerializedPackageId, diff --git a/src/cargo/core/resolver/encode.rs b/src/cargo/core/resolver/encode.rs index 3569bc9a138..793c311c124 100644 --- a/src/cargo/core/resolver/encode.rs +++ b/src/cargo/core/resolver/encode.rs @@ -117,39 +117,12 @@ use crate::util::errors::CargoResult; use crate::util::interning::InternedString; use crate::util::{Graph, internal}; use anyhow::{Context as _, bail}; -use cargo_util_schemas::core::SourceKind; use cargo_util_schemas::lockfile::{ - EncodablePackageIdError, EncodablePackageIdErrorKind, EncodableSourceIdError, - EncodableSourceIdErrorKind, + EncodableDependency, EncodablePackageId, EncodableResolve, EncodableSourceId, Patch, }; -use serde::de; use serde::ser; -use serde::{Deserialize, Serialize}; -use std::cmp::Ordering; -use std::collections::{BTreeMap, HashMap, HashSet}; -use std::fmt; -use std::str::FromStr; +use std::collections::{HashMap, HashSet}; use tracing::debug; -use url::Url; - -/// The `Cargo.lock` structure. -#[derive(Serialize, Deserialize, Debug)] -pub struct EncodableResolve { - pub version: Option, - pub package: Option>, - /// `root` is optional to allow backward compatibility. - pub root: Option, - pub metadata: Option, - #[serde(default, skip_serializing_if = "Patch::is_empty")] - pub patch: Patch, -} - -#[derive(Serialize, Deserialize, Debug, Default)] -pub struct Patch { - pub unused: Vec, -} - -pub type Metadata = BTreeMap; /// Convert a `Cargo.lock` to a Resolve. /// @@ -527,197 +500,6 @@ fn build_path_deps( } } -impl Patch { - fn is_empty(&self) -> bool { - self.unused.is_empty() - } -} - -#[derive(Serialize, Deserialize, Debug, PartialOrd, Ord, PartialEq, Eq)] -pub struct EncodableDependency { - pub name: String, - pub version: String, - pub source: Option, - pub checksum: Option, - pub dependencies: Option>, - pub replace: Option, -} - -#[derive(Debug, Clone)] -pub struct EncodableSourceId { - /// Full string of the source - source_str: String, - /// Used for sources ordering - kind: SourceKind, - /// Used for sources ordering - url: Url, -} - -impl EncodableSourceId { - pub fn new(source: String) -> Result { - let source_str = source.clone(); - let (kind, url) = source.split_once('+').ok_or_else(|| { - EncodableSourceIdError(EncodableSourceIdErrorKind::InvalidSource(source.clone()).into()) - })?; - - let url = Url::parse(url).map_err(|msg| EncodableSourceIdErrorKind::InvalidUrl { - url: url.to_string(), - msg: msg.to_string(), - })?; - - let kind = match kind { - "git" => { - let reference = GitReference::from_query(url.query_pairs()); - SourceKind::Git(reference) - } - "registry" => SourceKind::Registry, - "sparse" => SourceKind::SparseRegistry, - "path" => SourceKind::Path, - kind => { - return Err(EncodableSourceIdErrorKind::UnsupportedSource(kind.to_string()).into()); - } - }; - - Ok(Self { - source_str, - kind, - url, - }) - } - - pub fn kind(&self) -> &SourceKind { - &self.kind - } - - pub fn url(&self) -> &Url { - &self.url - } - - pub fn source_str(&self) -> &String { - &self.source_str - } - - pub fn as_url(&self) -> impl fmt::Display + '_ { - self.source_str.clone() - } -} - -impl ser::Serialize for EncodableSourceId { - fn serialize(&self, s: S) -> Result - where - S: ser::Serializer, - { - s.collect_str(&self.as_url()) - } -} - -impl<'de> de::Deserialize<'de> for EncodableSourceId { - fn deserialize(d: D) -> Result - where - D: de::Deserializer<'de>, - { - let s = String::deserialize(d)?; - Ok(EncodableSourceId::new(s).map_err(de::Error::custom)?) - } -} - -impl std::hash::Hash for EncodableSourceId { - fn hash(&self, state: &mut H) { - self.kind.hash(state); - self.url.hash(state); - } -} - -impl std::cmp::PartialEq for EncodableSourceId { - fn eq(&self, other: &Self) -> bool { - self.kind == other.kind && self.url == other.url - } -} - -impl std::cmp::Eq for EncodableSourceId {} - -impl PartialOrd for EncodableSourceId { - fn partial_cmp(&self, other: &EncodableSourceId) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for EncodableSourceId { - fn cmp(&self, other: &EncodableSourceId) -> Ordering { - self.kind - .cmp(&other.kind) - .then_with(|| self.url.cmp(&other.url)) - } -} - -#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Clone)] -pub struct EncodablePackageId { - pub name: String, - pub version: Option, - pub source: Option, -} - -impl fmt::Display for EncodablePackageId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.name)?; - if let Some(s) = &self.version { - write!(f, " {}", s)?; - } - if let Some(s) = &self.source { - write!(f, " ({})", s.as_url())?; - } - Ok(()) - } -} - -impl FromStr for EncodablePackageId { - type Err = EncodablePackageIdError; - - fn from_str(s: &str) -> Result { - let mut s = s.splitn(3, ' '); - let name = s.next().unwrap(); - let version = s.next(); - let source_id = match s.next() { - Some(s) => { - if let Some(s) = s.strip_prefix('(').and_then(|s| s.strip_suffix(')')) { - Some(EncodableSourceId::new(s.to_string())?) - } else { - return Err(EncodablePackageIdErrorKind::InvalidSerializedPackageId.into()); - } - } - None => None, - }; - - Ok(EncodablePackageId { - name: name.to_string(), - version: version.map(|v| v.to_string()), - source: source_id, - }) - } -} - -impl ser::Serialize for EncodablePackageId { - fn serialize(&self, s: S) -> Result - where - S: ser::Serializer, - { - s.collect_str(self) - } -} - -impl<'de> de::Deserialize<'de> for EncodablePackageId { - fn deserialize(d: D) -> Result - where - D: de::Deserializer<'de>, - { - String::deserialize(d).and_then(|string| { - string - .parse::() - .map_err(de::Error::custom) - }) - } -} - impl ser::Serialize for Resolve { #[tracing::instrument(skip_all)] fn serialize(&self, s: S) -> Result diff --git a/src/cargo/core/resolver/mod.rs b/src/cargo/core/resolver/mod.rs index 1e82c9e72af..dd65e17d3f1 100644 --- a/src/cargo/core/resolver/mod.rs +++ b/src/cargo/core/resolver/mod.rs @@ -76,8 +76,6 @@ use self::features::RequestedFeatures; use self::types::{ConflictMap, ConflictReason, DepsFrame}; use self::types::{FeaturesSet, RcVecIter, RemainingDeps, ResolverProgress}; -pub use self::encode::Metadata; -pub use self::encode::{EncodableDependency, EncodablePackageId, EncodableResolve}; pub use self::errors::{ActivateError, ActivateResult, ResolveError}; pub use self::features::{CliFeatures, ForceAllTargets, HasDevUnits}; pub use self::resolve::{Resolve, ResolveVersion}; diff --git a/src/cargo/core/resolver/resolve.rs b/src/cargo/core/resolver/resolve.rs index ef5b7f22171..536a22631cc 100644 --- a/src/cargo/core/resolver/resolve.rs +++ b/src/cargo/core/resolver/resolve.rs @@ -1,12 +1,12 @@ use cargo_util_schemas::core::PartialVersion; use cargo_util_schemas::manifest::RustVersion; -use super::encode::Metadata; use crate::core::dependency::DepKind; use crate::core::{Dependency, PackageId, PackageIdSpec, PackageIdSpecQuery, Summary, Target}; use crate::util::Graph; use crate::util::errors::CargoResult; use crate::util::interning::InternedString; +use cargo_util_schemas::lockfile::Metadata; use std::borrow::Borrow; use std::collections::{HashMap, HashSet}; use std::fmt; diff --git a/src/cargo/ops/lockfile.rs b/src/cargo/ops/lockfile.rs index 5102df9d0c1..9f441283ccf 100644 --- a/src/cargo/ops/lockfile.rs +++ b/src/cargo/ops/lockfile.rs @@ -1,12 +1,12 @@ use std::io::prelude::*; -use crate::core::resolver::encode::EncodableResolve; use crate::core::resolver::encode::into_resolve; use crate::core::{Resolve, ResolveVersion, Workspace}; use crate::util::Filesystem; use crate::util::errors::CargoResult; use anyhow::Context as _; +use cargo_util_schemas::lockfile::EncodableResolve; pub const LOCKFILE_NAME: &str = "Cargo.lock"; From 3738481c5b184b4c3686b70872c8710a836d8b0b Mon Sep 17 00:00:00 2001 From: Vito Secona Date: Fri, 19 Sep 2025 05:36:02 +0700 Subject: [PATCH 08/10] feat: add lockfile schema generation --- .../cargo-util-schemas/lockfile.schema.json | 133 ++++++++++++++++++ crates/cargo-util-schemas/src/lockfile.rs | 17 +++ 2 files changed, 150 insertions(+) create mode 100644 crates/cargo-util-schemas/lockfile.schema.json diff --git a/crates/cargo-util-schemas/lockfile.schema.json b/crates/cargo-util-schemas/lockfile.schema.json new file mode 100644 index 00000000000..fe82ef6f379 --- /dev/null +++ b/crates/cargo-util-schemas/lockfile.schema.json @@ -0,0 +1,133 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "EncodableResolve", + "description": "The `Cargo.lock` structure.", + "type": "object", + "properties": { + "version": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0 + }, + "package": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/$defs/EncodableDependency" + } + }, + "root": { + "description": "`root` is optional to allow backward compatibility.", + "anyOf": [ + { + "$ref": "#/$defs/EncodableDependency" + }, + { + "type": "null" + } + ] + }, + "metadata": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string" + } + }, + "patch": { + "$ref": "#/$defs/Patch" + } + }, + "$defs": { + "EncodableDependency": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "type": [ + "string", + "null" + ] + }, + "checksum": { + "type": [ + "string", + "null" + ] + }, + "dependencies": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/$defs/EncodablePackageId" + } + }, + "replace": { + "anyOf": [ + { + "$ref": "#/$defs/EncodablePackageId" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "name", + "version" + ] + }, + "EncodablePackageId": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": [ + "string", + "null" + ] + }, + "source": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "name" + ] + }, + "Patch": { + "type": "object", + "properties": { + "unused": { + "type": "array", + "items": { + "$ref": "#/$defs/EncodableDependency" + } + } + }, + "required": [ + "unused" + ] + } + } +} \ No newline at end of file diff --git a/crates/cargo-util-schemas/src/lockfile.rs b/crates/cargo-util-schemas/src/lockfile.rs index 84bf4b0ac40..ddcca099802 100644 --- a/crates/cargo-util-schemas/src/lockfile.rs +++ b/crates/cargo-util-schemas/src/lockfile.rs @@ -9,6 +9,7 @@ use crate::core::{GitReference, SourceKind}; /// The `Cargo.lock` structure. #[derive(Serialize, Deserialize, Debug)] +#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct EncodableResolve { pub version: Option, pub package: Option>, @@ -20,6 +21,7 @@ pub struct EncodableResolve { } #[derive(Serialize, Deserialize, Debug, Default)] +#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct Patch { pub unused: Vec, } @@ -33,6 +35,7 @@ impl Patch { } #[derive(Serialize, Deserialize, Debug, PartialOrd, Ord, PartialEq, Eq)] +#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct EncodableDependency { pub name: String, pub version: String, @@ -43,6 +46,11 @@ pub struct EncodableDependency { } #[derive(Debug, Clone)] +#[cfg_attr( + feature = "unstable-schema", + derive(schemars::JsonSchema), + schemars(with = "String") +)] pub struct EncodableSourceId { /// Full string of the source source_str: String, @@ -150,6 +158,7 @@ impl Ord for EncodableSourceId { } #[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Clone)] +#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct EncodablePackageId { pub name: String, pub version: Option, @@ -253,3 +262,11 @@ enum EncodablePackageIdErrorKind { #[error(transparent)] Source(#[from] EncodableSourceIdError), } + +#[cfg(feature = "unstable-schema")] +#[test] +fn dump_lockfile_schema() { + let schema = schemars::schema_for!(crate::lockfile::EncodableResolve); + let dump = serde_json::to_string_pretty(&schema).unwrap(); + snapbox::assert_data_eq!(dump, snapbox::file!("../lockfile.schema.json").raw()); +} From d51d7f6cfc5d93e665c0d866a5f61cad63489ec6 Mon Sep 17 00:00:00 2001 From: Vito Secona Date: Fri, 19 Sep 2025 05:38:40 +0700 Subject: [PATCH 09/10] chore: bump version --- Cargo.lock | 6 +++--- crates/cargo-util-schemas/Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3da45c0bbc7..30dbfcf9661 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -334,7 +334,7 @@ dependencies = [ "cargo-platform 0.3.1", "cargo-test-support", "cargo-util", - "cargo-util-schemas 0.10.1", + "cargo-util-schemas 0.10.2", "clap", "clap_complete", "color-print", @@ -537,7 +537,7 @@ dependencies = [ [[package]] name = "cargo-util-schemas" -version = "0.10.1" +version = "0.10.2" dependencies = [ "schemars", "semver", @@ -3712,7 +3712,7 @@ dependencies = [ "cargo", "cargo-platform 0.3.1", "cargo-util", - "cargo-util-schemas 0.10.1", + "cargo-util-schemas 0.10.2", "proptest", "varisat", ] diff --git a/crates/cargo-util-schemas/Cargo.toml b/crates/cargo-util-schemas/Cargo.toml index 4c393e2684c..f33f270008e 100644 --- a/crates/cargo-util-schemas/Cargo.toml +++ b/crates/cargo-util-schemas/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo-util-schemas" -version = "0.10.1" +version = "0.10.2" rust-version = "1.89" # MSRV:1 edition.workspace = true license.workspace = true From 703988f0c26926573c9a486d82a884f87a32b2fc Mon Sep 17 00:00:00 2001 From: Vito Secona Date: Fri, 19 Sep 2025 06:05:02 +0700 Subject: [PATCH 10/10] refactor: rename lockfile schemas scheme Manifests schemas use `TomlManifest`. To match, we rename the lockfile schemas to `TomlLockfile` --- .../cargo-util-schemas/lockfile.schema.json | 20 ++--- crates/cargo-util-schemas/src/lockfile.rs | 76 +++++++++---------- src/cargo/core/resolver/encode.rs | 33 ++++---- src/cargo/core/resolver/resolve.rs | 8 +- src/cargo/ops/lockfile.rs | 8 +- 5 files changed, 73 insertions(+), 72 deletions(-) diff --git a/crates/cargo-util-schemas/lockfile.schema.json b/crates/cargo-util-schemas/lockfile.schema.json index fe82ef6f379..5d7149d55d4 100644 --- a/crates/cargo-util-schemas/lockfile.schema.json +++ b/crates/cargo-util-schemas/lockfile.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "EncodableResolve", + "title": "TomlLockfile", "description": "The `Cargo.lock` structure.", "type": "object", "properties": { @@ -18,14 +18,14 @@ "null" ], "items": { - "$ref": "#/$defs/EncodableDependency" + "$ref": "#/$defs/TomlLockfileDependency" } }, "root": { "description": "`root` is optional to allow backward compatibility.", "anyOf": [ { - "$ref": "#/$defs/EncodableDependency" + "$ref": "#/$defs/TomlLockfileDependency" }, { "type": "null" @@ -42,11 +42,11 @@ } }, "patch": { - "$ref": "#/$defs/Patch" + "$ref": "#/$defs/TomlLockfilePatch" } }, "$defs": { - "EncodableDependency": { + "TomlLockfileDependency": { "type": "object", "properties": { "name": { @@ -73,13 +73,13 @@ "null" ], "items": { - "$ref": "#/$defs/EncodablePackageId" + "$ref": "#/$defs/TomlLockfilePackageId" } }, "replace": { "anyOf": [ { - "$ref": "#/$defs/EncodablePackageId" + "$ref": "#/$defs/TomlLockfilePackageId" }, { "type": "null" @@ -92,7 +92,7 @@ "version" ] }, - "EncodablePackageId": { + "TomlLockfilePackageId": { "type": "object", "properties": { "name": { @@ -115,13 +115,13 @@ "name" ] }, - "Patch": { + "TomlLockfilePatch": { "type": "object", "properties": { "unused": { "type": "array", "items": { - "$ref": "#/$defs/EncodableDependency" + "$ref": "#/$defs/TomlLockfileDependency" } } }, diff --git a/crates/cargo-util-schemas/src/lockfile.rs b/crates/cargo-util-schemas/src/lockfile.rs index ddcca099802..8e949d02735 100644 --- a/crates/cargo-util-schemas/src/lockfile.rs +++ b/crates/cargo-util-schemas/src/lockfile.rs @@ -10,25 +10,25 @@ use crate::core::{GitReference, SourceKind}; /// The `Cargo.lock` structure. #[derive(Serialize, Deserialize, Debug)] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] -pub struct EncodableResolve { +pub struct TomlLockfile { pub version: Option, - pub package: Option>, + pub package: Option>, /// `root` is optional to allow backward compatibility. - pub root: Option, - pub metadata: Option, - #[serde(default, skip_serializing_if = "Patch::is_empty")] - pub patch: Patch, + pub root: Option, + pub metadata: Option, + #[serde(default, skip_serializing_if = "TomlLockfilePatch::is_empty")] + pub patch: TomlLockfilePatch, } #[derive(Serialize, Deserialize, Debug, Default)] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] -pub struct Patch { - pub unused: Vec, +pub struct TomlLockfilePatch { + pub unused: Vec, } -pub type Metadata = BTreeMap; +pub type TomlLockfileMetadata = BTreeMap; -impl Patch { +impl TomlLockfilePatch { fn is_empty(&self) -> bool { self.unused.is_empty() } @@ -36,13 +36,13 @@ impl Patch { #[derive(Serialize, Deserialize, Debug, PartialOrd, Ord, PartialEq, Eq)] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] -pub struct EncodableDependency { +pub struct TomlLockfileDependency { pub name: String, pub version: String, - pub source: Option, + pub source: Option, pub checksum: Option, - pub dependencies: Option>, - pub replace: Option, + pub dependencies: Option>, + pub replace: Option, } #[derive(Debug, Clone)] @@ -51,7 +51,7 @@ pub struct EncodableDependency { derive(schemars::JsonSchema), schemars(with = "String") )] -pub struct EncodableSourceId { +pub struct TomlLockfileSourceId { /// Full string of the source source_str: String, /// Used for sources ordering @@ -60,7 +60,7 @@ pub struct EncodableSourceId { url: Url, } -impl EncodableSourceId { +impl TomlLockfileSourceId { pub fn new(source: String) -> Result { let source_str = source.clone(); let (kind, url) = source.split_once('+').ok_or_else(|| { @@ -109,7 +109,7 @@ impl EncodableSourceId { } } -impl ser::Serialize for EncodableSourceId { +impl ser::Serialize for TomlLockfileSourceId { fn serialize(&self, s: S) -> Result where S: ser::Serializer, @@ -118,39 +118,39 @@ impl ser::Serialize for EncodableSourceId { } } -impl<'de> de::Deserialize<'de> for EncodableSourceId { +impl<'de> de::Deserialize<'de> for TomlLockfileSourceId { fn deserialize(d: D) -> Result where D: de::Deserializer<'de>, { let s = String::deserialize(d)?; - Ok(EncodableSourceId::new(s).map_err(de::Error::custom)?) + Ok(TomlLockfileSourceId::new(s).map_err(de::Error::custom)?) } } -impl std::hash::Hash for EncodableSourceId { +impl std::hash::Hash for TomlLockfileSourceId { fn hash(&self, state: &mut H) { self.kind.hash(state); self.url.hash(state); } } -impl std::cmp::PartialEq for EncodableSourceId { +impl std::cmp::PartialEq for TomlLockfileSourceId { fn eq(&self, other: &Self) -> bool { self.kind == other.kind && self.url == other.url } } -impl std::cmp::Eq for EncodableSourceId {} +impl std::cmp::Eq for TomlLockfileSourceId {} -impl PartialOrd for EncodableSourceId { - fn partial_cmp(&self, other: &EncodableSourceId) -> Option { +impl PartialOrd for TomlLockfileSourceId { + fn partial_cmp(&self, other: &TomlLockfileSourceId) -> Option { Some(self.cmp(other)) } } -impl Ord for EncodableSourceId { - fn cmp(&self, other: &EncodableSourceId) -> Ordering { +impl Ord for TomlLockfileSourceId { + fn cmp(&self, other: &TomlLockfileSourceId) -> Ordering { self.kind .cmp(&other.kind) .then_with(|| self.url.cmp(&other.url)) @@ -159,13 +159,13 @@ impl Ord for EncodableSourceId { #[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Clone)] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] -pub struct EncodablePackageId { +pub struct TomlLockfilePackageId { pub name: String, pub version: Option, - pub source: Option, + pub source: Option, } -impl fmt::Display for EncodablePackageId { +impl fmt::Display for TomlLockfilePackageId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.name)?; if let Some(s) = &self.version { @@ -178,17 +178,17 @@ impl fmt::Display for EncodablePackageId { } } -impl FromStr for EncodablePackageId { +impl FromStr for TomlLockfilePackageId { type Err = EncodablePackageIdError; - fn from_str(s: &str) -> Result { + fn from_str(s: &str) -> Result { let mut s = s.splitn(3, ' '); let name = s.next().unwrap(); let version = s.next(); let source_id = match s.next() { Some(s) => { if let Some(s) = s.strip_prefix('(').and_then(|s| s.strip_suffix(')')) { - Some(EncodableSourceId::new(s.to_string())?) + Some(TomlLockfileSourceId::new(s.to_string())?) } else { return Err(EncodablePackageIdErrorKind::InvalidSerializedPackageId.into()); } @@ -196,7 +196,7 @@ impl FromStr for EncodablePackageId { None => None, }; - Ok(EncodablePackageId { + Ok(TomlLockfilePackageId { name: name.to_string(), version: version.map(|v| v.to_string()), source: source_id, @@ -204,7 +204,7 @@ impl FromStr for EncodablePackageId { } } -impl ser::Serialize for EncodablePackageId { +impl ser::Serialize for TomlLockfilePackageId { fn serialize(&self, s: S) -> Result where S: ser::Serializer, @@ -213,14 +213,14 @@ impl ser::Serialize for EncodablePackageId { } } -impl<'de> de::Deserialize<'de> for EncodablePackageId { - fn deserialize(d: D) -> Result +impl<'de> de::Deserialize<'de> for TomlLockfilePackageId { + fn deserialize(d: D) -> Result where D: de::Deserializer<'de>, { String::deserialize(d).and_then(|string| { string - .parse::() + .parse::() .map_err(de::Error::custom) }) } @@ -266,7 +266,7 @@ enum EncodablePackageIdErrorKind { #[cfg(feature = "unstable-schema")] #[test] fn dump_lockfile_schema() { - let schema = schemars::schema_for!(crate::lockfile::EncodableResolve); + let schema = schemars::schema_for!(crate::lockfile::TomlLockfile); let dump = serde_json::to_string_pretty(&schema).unwrap(); snapbox::assert_data_eq!(dump, snapbox::file!("../lockfile.schema.json").raw()); } diff --git a/src/cargo/core/resolver/encode.rs b/src/cargo/core/resolver/encode.rs index 793c311c124..d7c44de3da0 100644 --- a/src/cargo/core/resolver/encode.rs +++ b/src/cargo/core/resolver/encode.rs @@ -118,7 +118,8 @@ use crate::util::interning::InternedString; use crate::util::{Graph, internal}; use anyhow::{Context as _, bail}; use cargo_util_schemas::lockfile::{ - EncodableDependency, EncodablePackageId, EncodableResolve, EncodableSourceId, Patch, + TomlLockfile, TomlLockfileDependency, TomlLockfilePackageId, TomlLockfilePatch, + TomlLockfileSourceId, }; use serde::ser; use std::collections::{HashMap, HashSet}; @@ -133,7 +134,7 @@ use tracing::debug; /// primary uses is to be used with `resolve_with_previous` to guide the /// resolver to create a complete Resolve. pub fn into_resolve( - resolve: EncodableResolve, + resolve: TomlLockfile, original: &str, ws: &Workspace<'_>, ) -> CargoResult { @@ -176,7 +177,7 @@ pub fn into_resolve( let mut live_pkgs = HashMap::new(); let mut all_pkgs = HashSet::new(); for pkg in packages.iter() { - let enc_id = EncodablePackageId { + let enc_id = TomlLockfilePackageId { name: pkg.name.clone(), version: Some(pkg.version.clone()), source: pkg.source.clone(), @@ -228,7 +229,7 @@ pub fn into_resolve( .insert(id.source_id(), *id); } - let mut lookup_id = |enc_id: &EncodablePackageId| -> Option { + let mut lookup_id = |enc_id: &TomlLockfilePackageId| -> Option { // The name of this package should always be in the larger list of // all packages. let by_version = map.get(enc_id.name.as_str())?; @@ -329,7 +330,7 @@ pub fn into_resolve( for (k, v) in metadata.iter().filter(|p| p.0.starts_with(prefix)) { to_remove.push(k.to_string()); let k = k.strip_prefix(prefix).unwrap(); - let enc_id: EncodablePackageId = k + let enc_id: TomlLockfilePackageId = k .parse() .with_context(|| internal("invalid encoding of checksum in lockfile"))?; let Some(id) = lookup_id(&enc_id) else { @@ -405,7 +406,7 @@ pub fn into_resolve( fn get_source_id<'a>( path_deps: &'a HashMap>, - pkg: &'a EncodableDependency, + pkg: &'a TomlLockfileDependency, ) -> Option<&'a SourceId> { path_deps.iter().find_map(|(name, version_source)| { if name != &pkg.name || version_source.len() == 0 { @@ -535,11 +536,11 @@ impl ser::Serialize for Resolve { Some(metadata) }; - let patch = Patch { + let patch = TomlLockfilePatch { unused: self .unused_patches() .iter() - .map(|id| EncodableDependency { + .map(|id| TomlLockfileDependency { name: id.name().to_string(), version: id.version().to_string(), source: encodable_source_id(id.source_id(), self.version()), @@ -553,7 +554,7 @@ impl ser::Serialize for Resolve { }) .collect(), }; - EncodableResolve { + TomlLockfile { package: Some(encodable), root: None, metadata, @@ -597,7 +598,7 @@ fn encodable_resolve_node( id: PackageId, resolve: &Resolve, state: &EncodeState<'_>, -) -> EncodableDependency { +) -> TomlLockfileDependency { let (replace, deps) = match resolve.replacement(id) { Some(id) => ( Some(encodable_package_id(id, state, resolve.version())), @@ -613,7 +614,7 @@ fn encodable_resolve_node( } }; - EncodableDependency { + TomlLockfileDependency { name: id.name().to_string(), version: id.version().to_string(), source: encodable_source_id(id.source_id(), resolve.version()), @@ -631,7 +632,7 @@ pub fn encodable_package_id( id: PackageId, state: &EncodeState<'_>, resolve_version: ResolveVersion, -) -> EncodablePackageId { +) -> TomlLockfilePackageId { let mut version = Some(id.version().to_string()); let mut id_to_encode = id.source_id(); if resolve_version <= ResolveVersion::V2 { @@ -652,22 +653,22 @@ pub fn encodable_package_id( } } } - EncodablePackageId { + TomlLockfilePackageId { name: id.name().to_string(), version, source, } } -fn encodable_source_id(id: SourceId, version: ResolveVersion) -> Option { +fn encodable_source_id(id: SourceId, version: ResolveVersion) -> Option { if id.is_path() { None } else { Some( if version >= ResolveVersion::V4 { - EncodableSourceId::new(id.as_encoded_url().to_string()) + TomlLockfileSourceId::new(id.as_encoded_url().to_string()) } else { - EncodableSourceId::new(id.as_url().to_string()) + TomlLockfileSourceId::new(id.as_url().to_string()) } .expect("source ID should have valid URLs"), ) diff --git a/src/cargo/core/resolver/resolve.rs b/src/cargo/core/resolver/resolve.rs index 536a22631cc..fb472d99d2b 100644 --- a/src/cargo/core/resolver/resolve.rs +++ b/src/cargo/core/resolver/resolve.rs @@ -6,7 +6,7 @@ use crate::core::{Dependency, PackageId, PackageIdSpec, PackageIdSpecQuery, Summ use crate::util::Graph; use crate::util::errors::CargoResult; use crate::util::interning::InternedString; -use cargo_util_schemas::lockfile::Metadata; +use cargo_util_schemas::lockfile::TomlLockfileMetadata; use std::borrow::Borrow; use std::collections::{HashMap, HashSet}; use std::fmt; @@ -34,7 +34,7 @@ pub struct Resolve { /// "Unknown" metadata. This is a collection of extra, unrecognized data /// found in the `[metadata]` section of `Cargo.lock`, preserved for /// forwards compatibility. - metadata: Metadata, + metadata: TomlLockfileMetadata, /// `[patch]` entries that did not match anything, preserved in /// `Cargo.lock` as the `[[patch.unused]]` table array. Tracking unused /// patches helps prevent Cargo from being forced to re-update the @@ -156,7 +156,7 @@ impl Resolve { replacements: HashMap, features: HashMap>, checksums: HashMap>, - metadata: Metadata, + metadata: TomlLockfileMetadata, unused_patches: Vec, version: ResolveVersion, summaries: HashMap, @@ -394,7 +394,7 @@ unable to verify that `{0}` is the same as when the lockfile was generated self.checksums.insert(pkg_id, Some(checksum)); } - pub fn metadata(&self) -> &Metadata { + pub fn metadata(&self) -> &TomlLockfileMetadata { &self.metadata } diff --git a/src/cargo/ops/lockfile.rs b/src/cargo/ops/lockfile.rs index 9f441283ccf..9066f1decb2 100644 --- a/src/cargo/ops/lockfile.rs +++ b/src/cargo/ops/lockfile.rs @@ -6,7 +6,7 @@ use crate::util::Filesystem; use crate::util::errors::CargoResult; use anyhow::Context as _; -use cargo_util_schemas::lockfile::EncodableResolve; +use cargo_util_schemas::lockfile::TomlLockfile; pub const LOCKFILE_NAME: &str = "Cargo.lock"; @@ -24,7 +24,7 @@ pub fn load_pkg_lockfile(ws: &Workspace<'_>) -> CargoResult> { .with_context(|| format!("failed to read file: {}", f.path().display()))?; let resolve = (|| -> CargoResult> { - let v: EncodableResolve = toml::from_str(&s)?; + let v: TomlLockfile = toml::from_str(&s)?; Ok(Some(into_resolve(v, &s, ws)?)) })() .with_context(|| format!("failed to parse lock file at: {}", f.path().display()))?; @@ -208,8 +208,8 @@ fn are_equal_lockfiles(orig: &str, current: &str, ws: &Workspace<'_>) -> bool { // common case where we can update lock files. if !ws.gctx().lock_update_allowed() { let res: CargoResult = (|| { - let old: EncodableResolve = toml::from_str(orig)?; - let new: EncodableResolve = toml::from_str(current)?; + let old: TomlLockfile = toml::from_str(orig)?; + let new: TomlLockfile = toml::from_str(current)?; Ok(into_resolve(old, orig, ws)? == into_resolve(new, current, ws)?) })(); if let Ok(true) = res {