]> git.proxmox.com Git - rustc.git/blame - src/tools/cargo/src/cargo/ops/cargo_generate_lockfile.rs
New upstream version 1.76.0+dfsg1
[rustc.git] / src / tools / cargo / src / cargo / ops / cargo_generate_lockfile.rs
CommitLineData
0a29b90c
FG
1use crate::core::registry::PackageRegistry;
2use crate::core::resolver::features::{CliFeatures, HasDevUnits};
4b012472 3use crate::core::{PackageId, PackageIdSpec, PackageIdSpecQuery};
0a29b90c
FG
4use crate::core::{Resolve, SourceId, Workspace};
5use crate::ops;
ed00b5ec 6use crate::util::cache_lock::CacheLockMode;
0a29b90c 7use crate::util::config::Config;
781aab86 8use crate::util::style;
0a29b90c 9use crate::util::CargoResult;
781aab86 10use anstyle::Style;
ed00b5ec 11use std::cmp::Ordering;
0a29b90c 12use std::collections::{BTreeMap, HashSet};
add651ee 13use tracing::debug;
0a29b90c
FG
14
15pub 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
24pub 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
42pub 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}