]>
Commit | Line | Data |
---|---|---|
0a29b90c FG |
1 | use crate::core::registry::PackageRegistry; |
2 | use crate::core::resolver::features::{CliFeatures, HasDevUnits}; | |
4b012472 | 3 | use crate::core::{PackageId, PackageIdSpec, PackageIdSpecQuery}; |
0a29b90c FG |
4 | use crate::core::{Resolve, SourceId, Workspace}; |
5 | use crate::ops; | |
ed00b5ec | 6 | use crate::util::cache_lock::CacheLockMode; |
0a29b90c | 7 | use crate::util::config::Config; |
781aab86 | 8 | use crate::util::style; |
0a29b90c | 9 | use crate::util::CargoResult; |
781aab86 | 10 | use anstyle::Style; |
ed00b5ec | 11 | use std::cmp::Ordering; |
0a29b90c | 12 | use std::collections::{BTreeMap, HashSet}; |
add651ee | 13 | use tracing::debug; |
0a29b90c FG |
14 | |
15 | pub struct UpdateOptions<'a> { | |
16 | pub config: &'a Config, | |
17 | pub to_update: Vec<String>, | |
18 | pub precise: Option<&'a str>, | |
781aab86 | 19 | pub recursive: bool, |
0a29b90c FG |
20 | pub dry_run: bool, |
21 | pub workspace: bool, | |
22 | } | |
23 | ||
24 | pub fn generate_lockfile(ws: &Workspace<'_>) -> CargoResult<()> { | |
25 | let mut registry = PackageRegistry::new(ws.config())?; | |
781aab86 | 26 | let max_rust_version = ws.rust_version(); |
0a29b90c FG |
27 | let mut resolve = ops::resolve_with_previous( |
28 | &mut registry, | |
29 | ws, | |
30 | &CliFeatures::new_all(true), | |
31 | HasDevUnits::Yes, | |
32 | None, | |
33 | None, | |
34 | &[], | |
35 | true, | |
781aab86 | 36 | max_rust_version, |
0a29b90c FG |
37 | )?; |
38 | ops::write_pkg_lockfile(ws, &mut resolve)?; | |
39 | Ok(()) | |
40 | } | |
41 | ||
42 | pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoResult<()> { | |
781aab86 FG |
43 | if opts.recursive && opts.precise.is_some() { |
44 | anyhow::bail!("cannot specify both recursive and precise simultaneously") | |
0a29b90c FG |
45 | } |
46 | ||
47 | if ws.members().count() == 0 { | |
48 | anyhow::bail!("you can't generate a lockfile for an empty workspace.") | |
49 | } | |
50 | ||
51 | // Updates often require a lot of modifications to the registry, so ensure | |
52 | // that we're synchronized against other Cargos. | |
ed00b5ec FG |
53 | let _lock = ws |
54 | .config() | |
55 | .acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?; | |
0a29b90c | 56 | |
781aab86 FG |
57 | let max_rust_version = ws.rust_version(); |
58 | ||
0a29b90c FG |
59 | let previous_resolve = match ops::load_pkg_lockfile(ws)? { |
60 | Some(resolve) => resolve, | |
61 | None => { | |
62 | match opts.precise { | |
63 | None => return generate_lockfile(ws), | |
64 | ||
65 | // Precise option specified, so calculate a previous_resolve required | |
66 | // by precise package update later. | |
67 | Some(_) => { | |
68 | let mut registry = PackageRegistry::new(opts.config)?; | |
69 | ops::resolve_with_previous( | |
70 | &mut registry, | |
71 | ws, | |
72 | &CliFeatures::new_all(true), | |
73 | HasDevUnits::Yes, | |
74 | None, | |
75 | None, | |
76 | &[], | |
77 | true, | |
781aab86 | 78 | max_rust_version, |
0a29b90c FG |
79 | )? |
80 | } | |
81 | } | |
82 | } | |
83 | }; | |
84 | let mut registry = PackageRegistry::new(opts.config)?; | |
85 | let mut to_avoid = HashSet::new(); | |
86 | ||
87 | if opts.to_update.is_empty() { | |
88 | if !opts.workspace { | |
89 | to_avoid.extend(previous_resolve.iter()); | |
90 | to_avoid.extend(previous_resolve.unused_patches()); | |
91 | } | |
92 | } else { | |
93 | let mut sources = Vec::new(); | |
94 | for name in opts.to_update.iter() { | |
781aab86 FG |
95 | let pid = previous_resolve.query(name)?; |
96 | if opts.recursive { | |
97 | fill_with_deps(&previous_resolve, pid, &mut to_avoid, &mut HashSet::new()); | |
0a29b90c | 98 | } else { |
781aab86 | 99 | to_avoid.insert(pid); |
0a29b90c FG |
100 | sources.push(match opts.precise { |
101 | Some(precise) => { | |
102 | // TODO: see comment in `resolve.rs` as well, but this | |
103 | // seems like a pretty hokey reason to single out | |
104 | // the registry as well. | |
781aab86 FG |
105 | if pid.source_id().is_registry() { |
106 | pid.source_id().with_precise_registry_version( | |
107 | pid.name(), | |
ed00b5ec | 108 | pid.version().clone(), |
781aab86 FG |
109 | precise, |
110 | )? | |
0a29b90c | 111 | } else { |
ed00b5ec | 112 | pid.source_id().with_git_precise(Some(precise.to_string())) |
781aab86 | 113 | } |
0a29b90c | 114 | } |
ed00b5ec | 115 | None => pid.source_id().without_precise(), |
0a29b90c FG |
116 | }); |
117 | } | |
118 | if let Ok(unused_id) = | |
119 | PackageIdSpec::query_str(name, previous_resolve.unused_patches().iter().cloned()) | |
120 | { | |
121 | to_avoid.insert(unused_id); | |
122 | } | |
123 | } | |
124 | ||
4b012472 FG |
125 | // Mirror `--workspace` and never avoid workspace members. |
126 | // Filtering them out here so the above processes them normally | |
127 | // so their dependencies can be updated as requested | |
128 | to_avoid = to_avoid | |
129 | .into_iter() | |
130 | .filter(|id| { | |
131 | for package in ws.members() { | |
132 | let member_id = package.package_id(); | |
133 | // Skip checking the `version` because `previous_resolve` might have a stale | |
134 | // value. | |
135 | // When dealing with workspace members, the other fields should be a | |
136 | // sufficiently unique match. | |
137 | if id.name() == member_id.name() && id.source_id() == member_id.source_id() { | |
138 | return false; | |
139 | } | |
140 | } | |
141 | true | |
142 | }) | |
143 | .collect(); | |
144 | ||
0a29b90c FG |
145 | registry.add_sources(sources)?; |
146 | } | |
147 | ||
148 | let mut resolve = ops::resolve_with_previous( | |
149 | &mut registry, | |
150 | ws, | |
151 | &CliFeatures::new_all(true), | |
152 | HasDevUnits::Yes, | |
153 | Some(&previous_resolve), | |
154 | Some(&to_avoid), | |
155 | &[], | |
156 | true, | |
781aab86 | 157 | max_rust_version, |
0a29b90c FG |
158 | )?; |
159 | ||
160 | // Summarize what is changing for the user. | |
781aab86 | 161 | let print_change = |status: &str, msg: String, color: &Style| { |
0a29b90c FG |
162 | opts.config.shell().status_with_color(status, msg, color) |
163 | }; | |
164 | for (removed, added) in compare_dependency_graphs(&previous_resolve, &resolve) { | |
165 | if removed.len() == 1 && added.len() == 1 { | |
166 | let msg = if removed[0].source_id().is_git() { | |
167 | format!( | |
168 | "{} -> #{}", | |
169 | removed[0], | |
ed00b5ec | 170 | &added[0].source_id().precise_git_fragment().unwrap() |
0a29b90c FG |
171 | ) |
172 | } else { | |
173 | format!("{} -> v{}", removed[0], added[0].version()) | |
174 | }; | |
175 | ||
ed00b5ec FG |
176 | // If versions differ only in build metadata, we call it an "update" |
177 | // regardless of whether the build metadata has gone up or down. | |
178 | // This metadata is often stuff like git commit hashes, which are | |
179 | // not meaningfully ordered. | |
180 | if removed[0].version().cmp_precedence(added[0].version()) == Ordering::Greater { | |
781aab86 | 181 | print_change("Downgrading", msg, &style::WARN)?; |
0a29b90c | 182 | } else { |
781aab86 | 183 | print_change("Updating", msg, &style::GOOD)?; |
0a29b90c FG |
184 | } |
185 | } else { | |
186 | for package in removed.iter() { | |
781aab86 | 187 | print_change("Removing", format!("{}", package), &style::ERROR)?; |
0a29b90c FG |
188 | } |
189 | for package in added.iter() { | |
781aab86 | 190 | print_change("Adding", format!("{}", package), &style::NOTE)?; |
0a29b90c FG |
191 | } |
192 | } | |
193 | } | |
194 | if opts.dry_run { | |
195 | opts.config | |
196 | .shell() | |
197 | .warn("not updating lockfile due to dry run")?; | |
198 | } else { | |
199 | ops::write_pkg_lockfile(ws, &mut resolve)?; | |
200 | } | |
201 | return Ok(()); | |
202 | ||
203 | fn fill_with_deps<'a>( | |
204 | resolve: &'a Resolve, | |
205 | dep: PackageId, | |
206 | set: &mut HashSet<PackageId>, | |
207 | visited: &mut HashSet<PackageId>, | |
208 | ) { | |
209 | if !visited.insert(dep) { | |
210 | return; | |
211 | } | |
212 | set.insert(dep); | |
213 | for (dep, _) in resolve.deps_not_replaced(dep) { | |
214 | fill_with_deps(resolve, dep, set, visited); | |
215 | } | |
216 | } | |
217 | ||
218 | fn compare_dependency_graphs( | |
219 | previous_resolve: &Resolve, | |
220 | resolve: &Resolve, | |
221 | ) -> Vec<(Vec<PackageId>, Vec<PackageId>)> { | |
222 | fn key(dep: PackageId) -> (&'static str, SourceId) { | |
223 | (dep.name().as_str(), dep.source_id()) | |
224 | } | |
225 | ||
226 | // Removes all package IDs in `b` from `a`. Note that this is somewhat | |
227 | // more complicated because the equality for source IDs does not take | |
228 | // precise versions into account (e.g., git shas), but we want to take | |
229 | // that into account here. | |
230 | fn vec_subtract(a: &[PackageId], b: &[PackageId]) -> Vec<PackageId> { | |
231 | a.iter() | |
232 | .filter(|a| { | |
233 | // If this package ID is not found in `b`, then it's definitely | |
234 | // in the subtracted set. | |
781aab86 FG |
235 | let Ok(i) = b.binary_search(a) else { |
236 | return true; | |
0a29b90c FG |
237 | }; |
238 | ||
239 | // If we've found `a` in `b`, then we iterate over all instances | |
240 | // (we know `b` is sorted) and see if they all have different | |
241 | // precise versions. If so, then `a` isn't actually in `b` so | |
242 | // we'll let it through. | |
243 | // | |
244 | // Note that we only check this for non-registry sources, | |
245 | // however, as registries contain enough version information in | |
246 | // the package ID to disambiguate. | |
247 | if a.source_id().is_registry() { | |
248 | return false; | |
249 | } | |
250 | b[i..] | |
251 | .iter() | |
252 | .take_while(|b| a == b) | |
ed00b5ec | 253 | .all(|b| !a.source_id().has_same_precise_as(b.source_id())) |
0a29b90c FG |
254 | }) |
255 | .cloned() | |
256 | .collect() | |
257 | } | |
258 | ||
259 | // Map `(package name, package source)` to `(removed versions, added versions)`. | |
260 | let mut changes = BTreeMap::new(); | |
261 | let empty = (Vec::new(), Vec::new()); | |
262 | for dep in previous_resolve.iter() { | |
263 | changes | |
264 | .entry(key(dep)) | |
265 | .or_insert_with(|| empty.clone()) | |
266 | .0 | |
267 | .push(dep); | |
268 | } | |
269 | for dep in resolve.iter() { | |
270 | changes | |
271 | .entry(key(dep)) | |
272 | .or_insert_with(|| empty.clone()) | |
273 | .1 | |
274 | .push(dep); | |
275 | } | |
276 | ||
277 | for v in changes.values_mut() { | |
278 | let (ref mut old, ref mut new) = *v; | |
279 | old.sort(); | |
280 | new.sort(); | |
281 | let removed = vec_subtract(old, new); | |
282 | let added = vec_subtract(new, old); | |
283 | *old = removed; | |
284 | *new = added; | |
285 | } | |
286 | debug!("{:#?}", changes); | |
287 | ||
288 | changes.into_iter().map(|(_, v)| v).collect() | |
289 | } | |
290 | } |