cargo_plumbing/ops/
resolve.rs1use std::collections::{BTreeMap, HashMap, HashSet};
2
3use cargo::core::{
4 PackageId, PackageIdSpec, Resolve, ResolveVersion, SourceId, SourceKind, Workspace,
5};
6use cargo::util::Graph;
7use cargo::CargoResult;
8use cargo_plumbing_schemas::lockfile::{NormalizedDependency, NormalizedPatch};
9
10use crate::cargo::core::resolver::encode::build_path_deps;
11
12pub fn into_resolve(
16 ws: &Workspace<'_>,
17 version: Option<u32>,
18 packages: Vec<NormalizedDependency>,
19 patch: NormalizedPatch,
20) -> CargoResult<Resolve> {
21 let path_deps = build_path_deps(ws)?;
22
23 let mut checksums = HashMap::new();
24
25 let live_pkgs = {
26 let mut all_pkgs = HashSet::new();
27 let mut live_pkgs = HashMap::new();
28 for pkg in packages.iter() {
29 if !all_pkgs.insert(pkg.id.clone()) {
30 anyhow::bail!("package `{}` is specified twice", pkg.id.name());
31 }
32
33 let source_id = get_path_deps_source_id(&path_deps, pkg.id.name(), pkg.id.version());
34 let Ok(Some(id)) = spec_to_id(pkg.id.clone(), source_id) else {
35 continue;
36 };
37
38 if let Some(cksum) = &pkg.checksum {
39 checksums.insert(id, Some(cksum.clone()));
40 }
41
42 live_pkgs.insert(pkg.id.clone(), (id, pkg));
43 }
44 live_pkgs
45 };
46
47 let mut map = HashMap::new();
53 for (id, _) in live_pkgs.values() {
54 map.entry(id.name().as_str())
55 .or_insert_with(HashMap::new)
56 .entry(id.version())
57 .or_insert_with(HashMap::new)
58 .insert(id.source_id(), *id);
59 }
60
61 let lookup_id = |pkg_id: &PackageIdSpec, source_id: Option<&SourceId>| -> Option<PackageId> {
62 let by_version = map.get(pkg_id.name())?;
63
64 let by_source = match &pkg_id.version() {
65 Some(version) => by_version.get(version)?,
66 None => {
67 if by_version.len() == 1 {
68 by_version.values().next().unwrap()
69 } else {
70 return None;
71 }
72 }
73 };
74
75 match &source_id {
76 Some(source) => by_source.get(source).cloned(),
77 None => {
78 let mut path_packages = by_source.values().filter(|p| p.source_id().is_path());
79 if let Some(path) = path_packages.next() {
80 if path_packages.next().is_some() {
81 None
82 } else {
83 Some(*path)
84 }
85 } else if by_source.len() == 1 {
86 Some(*by_source.values().next().unwrap())
87 } else {
88 None
89 }
90 }
91 }
92 };
93
94 let graph = {
95 let mut g = Graph::new();
96
97 for (id, _) in live_pkgs.values() {
98 g.add(*id);
99 }
100
101 for &(ref id, pkg) in live_pkgs.values() {
102 let Some(ref deps) = pkg.dependencies else {
103 continue;
104 };
105
106 for edge in deps.iter() {
107 let package_id = spec_to_id(edge.clone(), None)?;
108 let source_id = package_id.map(|p| p.source_id());
109 if let Some(to_depend_on) = lookup_id(edge, source_id.as_ref()) {
110 g.link(*id, to_depend_on);
111 }
112 }
113 }
114 g
115 };
116
117 let replacements = {
118 let mut replacements = HashMap::new();
119 for &(ref id, pkg) in live_pkgs.values() {
120 if let Some(ref replace) = pkg.replace {
121 assert!(pkg.dependencies.is_none());
122 let source_id = id.source_id();
123 if let Some(replace_id) = lookup_id(replace, Some(&source_id)) {
124 replacements.insert(*id, replace_id);
125 }
126 }
127 }
128 replacements
129 };
130
131 let unused_patches = {
132 let mut unused_patches = Vec::new();
133 for pkg in patch.unused {
134 let source_id = get_path_deps_source_id(&path_deps, pkg.id.name(), pkg.id.version());
135 let Ok(Some(id)) = spec_to_id(pkg.id.clone(), source_id) else {
136 continue;
137 };
138 unused_patches.push(id);
139 }
140 unused_patches
141 };
142
143 let metadata = BTreeMap::new();
144 let features = HashMap::new();
145 let summaries = HashMap::new();
146
147 let version = match version {
148 Some(4) => ResolveVersion::V4,
149 Some(3) => ResolveVersion::V3,
150 Some(2) => ResolveVersion::V2,
151 Some(1) => ResolveVersion::V1,
152 None => ResolveVersion::V2,
153 Some(_) => anyhow::bail!("invalid lockfile version"),
154 };
155
156 Ok(Resolve::new(
157 graph,
158 replacements,
159 features,
160 checksums,
161 metadata,
162 unused_patches,
163 version,
164 summaries,
165 ))
166}
167
168pub fn get_path_deps_source_id<'a>(
169 path_deps: &'a HashMap<String, HashMap<semver::Version, SourceId>>,
170 package_name: &str,
171 package_version: Option<semver::Version>,
172) -> Option<&'a SourceId> {
173 path_deps.iter().find_map(|(name, version_source)| {
174 if name != package_name || version_source.is_empty() {
175 return None;
176 }
177
178 if version_source.len() == 1 {
179 return Some(version_source.values().next().unwrap());
180 }
181
182 if let Some(pkg_version) = &package_version {
183 if let Some(source_id) = version_source.get(pkg_version) {
184 return Some(source_id);
185 }
186 }
187
188 None
189 })
190}
191
192pub fn spec_to_id(
193 spec: PackageIdSpec,
194 source_id: Option<&SourceId>,
195) -> CargoResult<Option<PackageId>> {
196 if let Some(kind) = spec.kind() {
197 if let Some(url) = spec.url() {
198 if let Some(version) = spec.version() {
199 let name = spec.name();
200 let source_id = match kind {
201 SourceKind::Git(git_ref) => SourceId::for_git(url, git_ref.clone()),
202 SourceKind::Registry | SourceKind::SparseRegistry => {
203 SourceId::for_registry(url)
204 }
205 _ => anyhow::bail!("unsupported source"),
206 }?;
207
208 return Ok(Some(PackageId::new(name.into(), version, source_id)));
209 }
210 }
211 }
212
213 if let Some(source_id) = source_id {
214 if let Some(version) = spec.version() {
215 let name = spec.name();
216 return Ok(Some(PackageId::new(name.into(), version, *source_id)));
217 }
218 }
219
220 Ok(None)
221}