1 use std
::collections
::{BTreeMap, HashSet}
;
4 use termcolor
::Color
::{self, Cyan, Green, Red}
;
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}
;
11 use crate::util
::config
::Config
;
12 use crate::util
::CargoResult
;
14 pub struct UpdateOptions
<'a
> {
15 pub config
: &'a Config
,
16 pub to_update
: Vec
<String
>,
17 pub precise
: Option
<&'a
str>,
23 pub fn generate_lockfile(ws
: &Workspace
<'_
>) -> CargoResult
<()> {
24 let mut registry
= PackageRegistry
::new(ws
.config())?
;
25 let mut resolve
= ops
::resolve_with_previous(
28 &CliFeatures
::new_all(true),
35 ops
::write_pkg_lockfile(ws
, &mut resolve
)?
;
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")
44 if ws
.members().count() == 0 {
45 anyhow
::bail
!("you can't generate a lockfile for an empty workspace.")
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()?
;
52 let previous_resolve
= match ops
::load_pkg_lockfile(ws
)?
{
53 Some(resolve
) => resolve
,
56 None
=> return generate_lockfile(ws
),
58 // Precise option specified, so calculate a previous_resolve required
59 // by precise package update later.
61 let mut registry
= PackageRegistry
::new(opts
.config
)?
;
62 ops
::resolve_with_previous(
65 &CliFeatures
::new_all(true),
76 let mut registry
= PackageRegistry
::new(opts
.config
)?
;
77 let mut to_avoid
= HashSet
::new();
79 if opts
.to_update
.is_empty() {
81 to_avoid
.extend(previous_resolve
.iter());
82 to_avoid
.extend(previous_resolve
.unused_patches());
85 let mut sources
= Vec
::new();
86 for name
in opts
.to_update
.iter() {
87 let dep
= previous_resolve
.query(name
)?
;
89 fill_with_deps(&previous_resolve
, dep
, &mut to_avoid
, &mut HashSet
::new());
92 sources
.push(match opts
.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
)
102 dep
.source_id().with_precise(Some(precise
))
104 None
=> dep
.source_id().with_precise(None
),
107 if let Ok(unused_id
) =
108 PackageIdSpec
::query_str(name
, previous_resolve
.unused_patches().iter().cloned())
110 to_avoid
.insert(unused_id
);
114 registry
.add_sources(sources
)?
;
117 let mut resolve
= ops
::resolve_with_previous(
120 &CliFeatures
::new_all(true),
122 Some(&previous_resolve
),
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
)
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() {
138 &added
[0].source_id().precise().unwrap()[..8]
141 format
!("{} -> v{}", removed
[0], added
[0].version())
143 print_change("Updating", msg
, Green
)?
;
145 for package
in removed
.iter() {
146 print_change("Removing", format
!("{}", package
), Red
)?
;
148 for package
in added
.iter() {
149 print_change("Adding", format
!("{}", package
), Cyan
)?
;
156 .warn("not updating lockfile due to dry run")?
;
158 ops
::write_pkg_lockfile(ws
, &mut resolve
)?
;
162 fn fill_with_deps
<'a
>(
163 resolve
: &'a Resolve
,
165 set
: &mut HashSet
<PackageId
>,
166 visited
: &mut HashSet
<PackageId
>,
168 if !visited
.insert(dep
) {
172 for (dep
, _
) in resolve
.deps_not_replaced(dep
) {
173 fill_with_deps(resolve
, dep
, set
, visited
);
177 fn compare_dependency_graphs(
178 previous_resolve
: &Resolve
,
180 ) -> Vec
<(Vec
<PackageId
>, Vec
<PackageId
>)> {
181 fn key(dep
: PackageId
) -> (&'
static str, SourceId
) {
182 (dep
.name().as_str(), dep
.source_id())
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
> {
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
) {
196 Err(..) => return true,
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.
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() {
212 .take_while(|b
| a
== b
)
213 .all(|b
| a
.source_id().precise() != b
.source_id().precise())
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() {
225 .or_insert_with(|| empty
.clone())
229 for dep
in resolve
.iter() {
232 .or_insert_with(|| empty
.clone())
237 for v
in changes
.values_mut() {
238 let (ref mut old
, ref mut new
) = *v
;
241 let removed
= vec_subtract(old
, new
);
242 let added
= vec_subtract(new
, old
);
246 debug
!("{:#?}", changes
);
248 changes
.into_iter().map(|(_
, v
)| v
).collect()