cargo/ops/
cargo_generate_lockfile.rs1use std::collections::{BTreeMap, HashSet};
2
3use log::debug;
4use termcolor::Color::{self, Cyan, Green, Red};
5
6use crate::core::registry::PackageRegistry;
7use crate::core::resolver::ResolveOpts;
8use crate::core::PackageId;
9use crate::core::{Resolve, SourceId, Workspace};
10use crate::ops;
11use crate::util::config::Config;
12use crate::util::CargoResult;
13
14pub 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}
21
22pub fn generate_lockfile(ws: &Workspace<'_>) -> CargoResult<()> {
23 let mut registry = PackageRegistry::new(ws.config())?;
24 let resolve = ops::resolve_with_previous(
25 &mut registry,
26 ws,
27 &ResolveOpts::everything(),
28 None,
29 None,
30 &[],
31 true,
32 )?;
33 ops::write_pkg_lockfile(ws, &resolve)?;
34 Ok(())
35}
36
37pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoResult<()> {
38 if opts.aggressive && opts.precise.is_some() {
39 anyhow::bail!("cannot specify both aggressive and precise simultaneously")
40 }
41
42 if ws.members().count() == 0 {
43 anyhow::bail!("you can't generate a lockfile for an empty workspace.")
44 }
45
46 if opts.config.offline() {
47 anyhow::bail!("you can't update in the offline mode");
48 }
49
50 let _lock = ws.config().acquire_package_cache_lock()?;
53
54 let previous_resolve = match ops::load_pkg_lockfile(ws)? {
55 Some(resolve) => resolve,
56 None => {
57 match opts.precise {
58 None => return generate_lockfile(ws),
59
60 Some(_) => {
63 let mut registry = PackageRegistry::new(opts.config)?;
64 ops::resolve_with_previous(
65 &mut registry,
66 ws,
67 &ResolveOpts::everything(),
68 None,
69 None,
70 &[],
71 true,
72 )?
73 }
74 }
75 }
76 };
77 let mut registry = PackageRegistry::new(opts.config)?;
78 let mut to_avoid = HashSet::new();
79
80 if opts.to_update.is_empty() {
81 to_avoid.extend(previous_resolve.iter());
82 } else {
83 let mut sources = Vec::new();
84 for name in opts.to_update.iter() {
85 let dep = previous_resolve.query(name)?;
86 if opts.aggressive {
87 fill_with_deps(&previous_resolve, dep, &mut to_avoid, &mut HashSet::new());
88 } else {
89 to_avoid.insert(dep);
90 sources.push(match opts.precise {
91 Some(precise) => {
92 let precise = if dep.source_id().is_registry() {
96 format!("{}={}->{}", dep.name(), dep.version(), precise)
97 } else {
98 precise.to_string()
99 };
100 dep.source_id().with_precise(Some(precise))
101 }
102 None => dep.source_id().with_precise(None),
103 });
104 }
105 }
106
107 registry.add_sources(sources)?;
108 }
109
110 let resolve = ops::resolve_with_previous(
111 &mut registry,
112 ws,
113 &ResolveOpts::everything(),
114 Some(&previous_resolve),
115 Some(&to_avoid),
116 &[],
117 true,
118 )?;
119
120 let print_change = |status: &str, msg: String, color: Color| {
122 opts.config.shell().status_with_color(status, msg, color)
123 };
124 for (removed, added) in compare_dependency_graphs(&previous_resolve, &resolve) {
125 if removed.len() == 1 && added.len() == 1 {
126 let msg = if removed[0].source_id().is_git() {
127 format!(
128 "{} -> #{}",
129 removed[0],
130 &added[0].source_id().precise().unwrap()[..8]
131 )
132 } else {
133 format!("{} -> v{}", removed[0], added[0].version())
134 };
135 print_change("Updating", msg, Green)?;
136 } else {
137 for package in removed.iter() {
138 print_change("Removing", format!("{}", package), Red)?;
139 }
140 for package in added.iter() {
141 print_change("Adding", format!("{}", package), Cyan)?;
142 }
143 }
144 }
145 if opts.dry_run {
146 opts.config
147 .shell()
148 .warn("not updating lockfile due to dry run")?;
149 } else {
150 ops::write_pkg_lockfile(ws, &resolve)?;
151 }
152 return Ok(());
153
154 fn fill_with_deps<'a>(
155 resolve: &'a Resolve,
156 dep: PackageId,
157 set: &mut HashSet<PackageId>,
158 visited: &mut HashSet<PackageId>,
159 ) {
160 if !visited.insert(dep) {
161 return;
162 }
163 set.insert(dep);
164 for (dep, _) in resolve.deps_not_replaced(dep) {
165 fill_with_deps(resolve, dep, set, visited);
166 }
167 }
168
169 fn compare_dependency_graphs(
170 previous_resolve: &Resolve,
171 resolve: &Resolve,
172 ) -> Vec<(Vec<PackageId>, Vec<PackageId>)> {
173 fn key(dep: PackageId) -> (&'static str, SourceId) {
174 (dep.name().as_str(), dep.source_id())
175 }
176
177 fn vec_subtract(a: &[PackageId], b: &[PackageId]) -> Vec<PackageId> {
182 a.iter()
183 .filter(|a| {
184 let i = match b.binary_search(a) {
187 Ok(i) => i,
188 Err(..) => return true,
189 };
190
191 if a.source_id().is_registry() {
200 return false;
201 }
202 b[i..]
203 .iter()
204 .take_while(|b| a == b)
205 .all(|b| a.source_id().precise() != b.source_id().precise())
206 })
207 .cloned()
208 .collect()
209 }
210
211 let mut changes = BTreeMap::new();
213 let empty = (Vec::new(), Vec::new());
214 for dep in previous_resolve.iter() {
215 changes
216 .entry(key(dep))
217 .or_insert_with(|| empty.clone())
218 .0
219 .push(dep);
220 }
221 for dep in resolve.iter() {
222 changes
223 .entry(key(dep))
224 .or_insert_with(|| empty.clone())
225 .1
226 .push(dep);
227 }
228
229 for v in changes.values_mut() {
230 let (ref mut old, ref mut new) = *v;
231 old.sort();
232 new.sort();
233 let removed = vec_subtract(old, new);
234 let added = vec_subtract(new, old);
235 *old = removed;
236 *new = added;
237 }
238 debug!("{:#?}", changes);
239
240 changes.into_iter().map(|(_, v)| v).collect()
241 }
242}