Skip to main content

cargo/ops/
cargo_generate_lockfile.rs

1use 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    // Updates often require a lot of modifications to the registry, so ensure
51    // that we're synchronized against other Cargos.
52    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                // Precise option specified, so calculate a previous_resolve required
61                // by precise package update later.
62                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                        // TODO: see comment in `resolve.rs` as well, but this
93                        //       seems like a pretty hokey reason to single out
94                        //       the registry as well.
95                        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    // Summarize what is changing for the user.
121    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        // Removes all package IDs in `b` from `a`. Note that this is somewhat
178        // more complicated because the equality for source IDs does not take
179        // precise versions into account (e.g., git shas), but we want to take
180        // that into account here.
181        fn vec_subtract(a: &[PackageId], b: &[PackageId]) -> Vec<PackageId> {
182            a.iter()
183                .filter(|a| {
184                    // If this package ID is not found in `b`, then it's definitely
185                    // in the subtracted set.
186                    let i = match b.binary_search(a) {
187                        Ok(i) => i,
188                        Err(..) => return true,
189                    };
190
191                    // If we've found `a` in `b`, then we iterate over all instances
192                    // (we know `b` is sorted) and see if they all have different
193                    // precise versions. If so, then `a` isn't actually in `b` so
194                    // we'll let it through.
195                    //
196                    // Note that we only check this for non-registry sources,
197                    // however, as registries contain enough version information in
198                    // the package ID to disambiguate.
199                    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        // Map `(package name, package source)` to `(removed versions, added versions)`.
212        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}