]> git.proxmox.com Git - cargo.git/blob - src/cargo/ops/cargo_generate_lockfile.rs
Auto merge of #9467 - ehuss:install-metadata, r=alexcrichton
[cargo.git] / src / cargo / ops / cargo_generate_lockfile.rs
1 use std::collections::{BTreeMap, HashSet};
2
3 use log::debug;
4 use termcolor::Color::{self, Cyan, Green, Red};
5
6 use crate::core::registry::PackageRegistry;
7 use crate::core::resolver::features::{CliFeatures, HasDevUnits};
8 use crate::core::{PackageId, PackageIdSpec};
9 use crate::core::{Resolve, SourceId, Workspace};
10 use crate::ops;
11 use crate::util::config::Config;
12 use crate::util::CargoResult;
13
14 pub struct UpdateOptions<'a> {
15 pub config: &'a Config,
16 pub to_update: Vec<String>,
17 pub precise: Option<&'a str>,
18 pub aggressive: bool,
19 pub dry_run: bool,
20 pub workspace: bool,
21 }
22
23 pub fn generate_lockfile(ws: &Workspace<'_>) -> CargoResult<()> {
24 let mut registry = PackageRegistry::new(ws.config())?;
25 let mut resolve = ops::resolve_with_previous(
26 &mut registry,
27 ws,
28 &CliFeatures::new_all(true),
29 HasDevUnits::Yes,
30 None,
31 None,
32 &[],
33 true,
34 )?;
35 ops::write_pkg_lockfile(ws, &mut resolve)?;
36 Ok(())
37 }
38
39 pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoResult<()> {
40 if opts.aggressive && opts.precise.is_some() {
41 anyhow::bail!("cannot specify both aggressive and precise simultaneously")
42 }
43
44 if ws.members().count() == 0 {
45 anyhow::bail!("you can't generate a lockfile for an empty workspace.")
46 }
47
48 // Updates often require a lot of modifications to the registry, so ensure
49 // that we're synchronized against other Cargos.
50 let _lock = ws.config().acquire_package_cache_lock()?;
51
52 let previous_resolve = match ops::load_pkg_lockfile(ws)? {
53 Some(resolve) => resolve,
54 None => {
55 match opts.precise {
56 None => return generate_lockfile(ws),
57
58 // Precise option specified, so calculate a previous_resolve required
59 // by precise package update later.
60 Some(_) => {
61 let mut registry = PackageRegistry::new(opts.config)?;
62 ops::resolve_with_previous(
63 &mut registry,
64 ws,
65 &CliFeatures::new_all(true),
66 HasDevUnits::Yes,
67 None,
68 None,
69 &[],
70 true,
71 )?
72 }
73 }
74 }
75 };
76 let mut registry = PackageRegistry::new(opts.config)?;
77 let mut to_avoid = HashSet::new();
78
79 if opts.to_update.is_empty() {
80 if !opts.workspace {
81 to_avoid.extend(previous_resolve.iter());
82 to_avoid.extend(previous_resolve.unused_patches());
83 }
84 } else {
85 let mut sources = Vec::new();
86 for name in opts.to_update.iter() {
87 let dep = previous_resolve.query(name)?;
88 if opts.aggressive {
89 fill_with_deps(&previous_resolve, dep, &mut to_avoid, &mut HashSet::new());
90 } else {
91 to_avoid.insert(dep);
92 sources.push(match opts.precise {
93 Some(precise) => {
94 // TODO: see comment in `resolve.rs` as well, but this
95 // seems like a pretty hokey reason to single out
96 // the registry as well.
97 let precise = if dep.source_id().is_registry() {
98 format!("{}={}->{}", dep.name(), dep.version(), precise)
99 } else {
100 precise.to_string()
101 };
102 dep.source_id().with_precise(Some(precise))
103 }
104 None => dep.source_id().with_precise(None),
105 });
106 }
107 if let Ok(unused_id) =
108 PackageIdSpec::query_str(name, previous_resolve.unused_patches().iter().cloned())
109 {
110 to_avoid.insert(unused_id);
111 }
112 }
113
114 registry.add_sources(sources)?;
115 }
116
117 let mut resolve = ops::resolve_with_previous(
118 &mut registry,
119 ws,
120 &CliFeatures::new_all(true),
121 HasDevUnits::Yes,
122 Some(&previous_resolve),
123 Some(&to_avoid),
124 &[],
125 true,
126 )?;
127
128 // Summarize what is changing for the user.
129 let print_change = |status: &str, msg: String, color: Color| {
130 opts.config.shell().status_with_color(status, msg, color)
131 };
132 for (removed, added) in compare_dependency_graphs(&previous_resolve, &resolve) {
133 if removed.len() == 1 && added.len() == 1 {
134 let msg = if removed[0].source_id().is_git() {
135 format!(
136 "{} -> #{}",
137 removed[0],
138 &added[0].source_id().precise().unwrap()[..8]
139 )
140 } else {
141 format!("{} -> v{}", removed[0], added[0].version())
142 };
143 print_change("Updating", msg, Green)?;
144 } else {
145 for package in removed.iter() {
146 print_change("Removing", format!("{}", package), Red)?;
147 }
148 for package in added.iter() {
149 print_change("Adding", format!("{}", package), Cyan)?;
150 }
151 }
152 }
153 if opts.dry_run {
154 opts.config
155 .shell()
156 .warn("not updating lockfile due to dry run")?;
157 } else {
158 ops::write_pkg_lockfile(ws, &mut resolve)?;
159 }
160 return Ok(());
161
162 fn fill_with_deps<'a>(
163 resolve: &'a Resolve,
164 dep: PackageId,
165 set: &mut HashSet<PackageId>,
166 visited: &mut HashSet<PackageId>,
167 ) {
168 if !visited.insert(dep) {
169 return;
170 }
171 set.insert(dep);
172 for (dep, _) in resolve.deps_not_replaced(dep) {
173 fill_with_deps(resolve, dep, set, visited);
174 }
175 }
176
177 fn compare_dependency_graphs(
178 previous_resolve: &Resolve,
179 resolve: &Resolve,
180 ) -> Vec<(Vec<PackageId>, Vec<PackageId>)> {
181 fn key(dep: PackageId) -> (&'static str, SourceId) {
182 (dep.name().as_str(), dep.source_id())
183 }
184
185 // Removes all package IDs in `b` from `a`. Note that this is somewhat
186 // more complicated because the equality for source IDs does not take
187 // precise versions into account (e.g., git shas), but we want to take
188 // that into account here.
189 fn vec_subtract(a: &[PackageId], b: &[PackageId]) -> Vec<PackageId> {
190 a.iter()
191 .filter(|a| {
192 // If this package ID is not found in `b`, then it's definitely
193 // in the subtracted set.
194 let i = match b.binary_search(a) {
195 Ok(i) => i,
196 Err(..) => return true,
197 };
198
199 // If we've found `a` in `b`, then we iterate over all instances
200 // (we know `b` is sorted) and see if they all have different
201 // precise versions. If so, then `a` isn't actually in `b` so
202 // we'll let it through.
203 //
204 // Note that we only check this for non-registry sources,
205 // however, as registries contain enough version information in
206 // the package ID to disambiguate.
207 if a.source_id().is_registry() {
208 return false;
209 }
210 b[i..]
211 .iter()
212 .take_while(|b| a == b)
213 .all(|b| a.source_id().precise() != b.source_id().precise())
214 })
215 .cloned()
216 .collect()
217 }
218
219 // Map `(package name, package source)` to `(removed versions, added versions)`.
220 let mut changes = BTreeMap::new();
221 let empty = (Vec::new(), Vec::new());
222 for dep in previous_resolve.iter() {
223 changes
224 .entry(key(dep))
225 .or_insert_with(|| empty.clone())
226 .0
227 .push(dep);
228 }
229 for dep in resolve.iter() {
230 changes
231 .entry(key(dep))
232 .or_insert_with(|| empty.clone())
233 .1
234 .push(dep);
235 }
236
237 for v in changes.values_mut() {
238 let (ref mut old, ref mut new) = *v;
239 old.sort();
240 new.sort();
241 let removed = vec_subtract(old, new);
242 let added = vec_subtract(new, old);
243 *old = removed;
244 *new = added;
245 }
246 debug!("{:#?}", changes);
247
248 changes.into_iter().map(|(_, v)| v).collect()
249 }
250 }