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