1use crate::core::Registry as _;
2use crate::core::dependency::Dependency;
3use crate::core::registry::PackageRegistry;
4use crate::core::resolver::features::{CliFeatures, HasDevUnits};
5use crate::core::{PackageId, PackageIdSpec, PackageIdSpecQuery};
6use crate::core::{Resolve, SourceId, Workspace};
7use crate::ops;
8use crate::sources::IndexSummary;
9use crate::sources::source::QueryKind;
10use crate::util::cache_lock::CacheLockMode;
11use crate::util::context::GlobalContext;
12use crate::util::toml_mut::dependency::{MaybeWorkspace, Source};
13use crate::util::toml_mut::manifest::LocalManifest;
14use crate::util::toml_mut::upgrade::upgrade_requirement;
15use crate::util::{CargoResult, VersionExt};
16use crate::util::{OptVersionReq, style};
17use anyhow::Context as _;
18use cargo_util_schemas::core::PartialVersion;
19use cargo_util_terminal::Verbosity;
20use indexmap::{IndexMap, IndexSet};
21use itertools::Itertools;
22use semver::{Op, Version, VersionReq};
23use std::cmp::Ordering;
24use std::collections::{BTreeMap, HashMap, HashSet};
25use tracing::{debug, trace};
26
27pub type UpgradeMap = HashMap<(String, SourceId), Version>;
28
29pub struct UpdateOptions<'a> {
30 pub gctx: &'a GlobalContext,
31 pub to_update: Vec<String>,
32 pub precise: Option<&'a str>,
33 pub recursive: bool,
34 pub dry_run: bool,
35 pub workspace: bool,
36}
37
38pub fn generate_lockfile(ws: &Workspace<'_>) -> CargoResult<()> {
39 let mut registry = ws.package_registry()?;
40 let previous_resolve = None;
41 let mut resolve = ops::resolve_with_previous(
42 &mut registry,
43 ws,
44 &CliFeatures::new_all(true),
45 HasDevUnits::Yes,
46 previous_resolve,
47 None,
48 &[],
49 true,
50 )?;
51 ops::write_pkg_lockfile(ws, &mut resolve)?;
52 print_lockfile_changes(ws, previous_resolve, &resolve, &mut registry)?;
53 Ok(())
54}
55
56pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoResult<()> {
57 if opts.recursive && opts.precise.is_some() {
58 anyhow::bail!("cannot specify both recursive and precise simultaneously")
59 }
60
61 if ws.members().count() == 0 {
62 anyhow::bail!("you can't generate a lockfile for an empty workspace.")
63 }
64
65 let _lock = ws
68 .gctx()
69 .acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?;
70
71 let previous_resolve = match ops::load_pkg_lockfile(ws)? {
72 Some(resolve) => resolve,
73 None => {
74 match opts.precise {
75 None => return generate_lockfile(ws),
76
77 Some(_) => {
80 let mut registry = ws.package_registry()?;
81 ops::resolve_with_previous(
82 &mut registry,
83 ws,
84 &CliFeatures::new_all(true),
85 HasDevUnits::Yes,
86 None,
87 None,
88 &[],
89 true,
90 )?
91 }
92 }
93 }
94 };
95 let mut registry = ws.package_registry()?;
96 let mut to_avoid = HashSet::new();
97
98 if opts.to_update.is_empty() {
99 if !opts.workspace {
100 to_avoid.extend(previous_resolve.iter());
101 to_avoid.extend(previous_resolve.unused_patches());
102 }
103 } else {
104 let mut sources = Vec::new();
105 for name in opts.to_update.iter() {
106 let pid = previous_resolve.query(name)?;
107 if opts.recursive {
108 fill_with_deps(&previous_resolve, pid, &mut to_avoid, &mut HashSet::new());
109 } else {
110 to_avoid.insert(pid);
111 sources.push(match opts.precise {
112 Some(precise) => {
113 if pid.source_id().is_registry() {
117 pid.source_id().with_precise_registry_version(
118 pid.name(),
119 pid.version().clone(),
120 precise,
121 )?
122 } else {
123 pid.source_id().with_git_precise(Some(precise.to_string()))
124 }
125 }
126 None => pid.source_id().without_precise(),
127 });
128 }
129 if let Ok(unused_id) =
130 PackageIdSpec::query_str(name, previous_resolve.unused_patches().iter().cloned())
131 {
132 to_avoid.insert(unused_id);
133 }
134 }
135
136 to_avoid.retain(|id| {
140 for package in ws.members() {
141 let member_id = package.package_id();
142 if id.name() == member_id.name() && id.source_id() == member_id.source_id() {
147 return false;
148 }
149 }
150 true
151 });
152
153 registry.add_sources(sources)?;
154 }
155
156 let to_avoid_sources: HashSet<_> = to_avoid
164 .iter()
165 .map(|p| p.source_id())
166 .filter(|s| !s.is_registry())
167 .collect();
168
169 let keep = |p: &PackageId| !to_avoid_sources.contains(&p.source_id()) && !to_avoid.contains(p);
170
171 let mut resolve = ops::resolve_with_previous(
172 &mut registry,
173 ws,
174 &CliFeatures::new_all(true),
175 HasDevUnits::Yes,
176 Some(&previous_resolve),
177 Some(&keep),
178 &[],
179 true,
180 )?;
181
182 print_lockfile_updates(
183 ws,
184 &previous_resolve,
185 &resolve,
186 opts.precise.is_some(),
187 &mut registry,
188 )?;
189 if opts.dry_run {
190 opts.gctx
191 .shell()
192 .warn("not updating lockfile due to dry run")?;
193 } else {
194 ops::write_pkg_lockfile(ws, &mut resolve)?;
195 }
196 Ok(())
197}
198
199pub fn print_lockfile_changes(
204 ws: &Workspace<'_>,
205 previous_resolve: Option<&Resolve>,
206 resolve: &Resolve,
207 registry: &mut PackageRegistry<'_>,
208) -> CargoResult<()> {
209 let _lock = ws
210 .gctx()
211 .acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?;
212 if let Some(previous_resolve) = previous_resolve {
213 print_lockfile_sync(ws, previous_resolve, resolve, registry)
214 } else {
215 print_lockfile_generation(ws, resolve, registry)
216 }
217}
218pub fn upgrade_manifests(
219 ws: &mut Workspace<'_>,
220 to_update: &Vec<String>,
221) -> CargoResult<UpgradeMap> {
222 let gctx = ws.gctx();
223 let mut upgrades = HashMap::new();
224 let mut upgrade_messages = HashSet::new();
225
226 let to_update = to_update
227 .iter()
228 .map(|spec| {
229 PackageIdSpec::parse(spec)
230 .with_context(|| format!("invalid package ID specification: `{spec}`"))
231 })
232 .collect::<Result<Vec<_>, _>>()?;
233
234 let _lock = gctx.acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?;
237
238 let mut registry = ws.package_registry()?;
239 registry.lock_patches();
240
241 let mut remaining_specs: IndexSet<_> = to_update.iter().cloned().collect();
242
243 for member in ws.members_mut().sorted() {
244 debug!("upgrading manifest for `{}`", member.name());
245
246 *member.manifest_mut().summary_mut() = member
247 .manifest()
248 .summary()
249 .clone()
250 .try_map_dependencies(|d| {
251 upgrade_dependency(
252 &gctx,
253 &to_update,
254 &mut registry,
255 &mut upgrades,
256 &mut upgrade_messages,
257 &mut remaining_specs,
258 d,
259 )
260 })?;
261 }
262
263 if !remaining_specs.is_empty() {
264 let previous_resolve = ops::load_pkg_lockfile(ws)?;
265 let plural = if remaining_specs.len() == 1 { "" } else { "s" };
266
267 let mut error_msg = format!(
268 "package ID specification{plural} did not match any direct dependencies that could be upgraded"
269 );
270
271 let mut transitive_specs = Vec::new();
272 for spec in &remaining_specs {
273 error_msg.push_str(&format!("\n {spec}"));
274
275 let in_lockfile = if let Some(ref resolve) = previous_resolve {
277 spec.query(resolve.iter()).is_ok()
278 } else {
279 false
280 };
281
282 let matches_direct_dep = ws.members().any(|member| {
284 member.dependencies().iter().any(|dep| {
285 spec.name() == dep.package_name().as_str()
286 && dep.source_id().is_registry()
287 && spec.url().map_or(true, |url| url == dep.source_id().url())
288 && spec
289 .version()
290 .map_or(true, |v| dep.version_req().matches(&v))
291 })
292 });
293
294 if in_lockfile && !matches_direct_dep {
296 transitive_specs.push(spec);
297 }
298 }
299
300 for spec in transitive_specs {
301 error_msg.push_str(&format!(
302 "\nnote: `{spec}` exists as a transitive dependency but those are not available for upgrading through `--breaking`"
303 ));
304 }
305
306 anyhow::bail!("{error_msg}");
307 }
308
309 Ok(upgrades)
310}
311
312fn upgrade_dependency(
313 gctx: &GlobalContext,
314 to_update: &Vec<PackageIdSpec>,
315 registry: &mut PackageRegistry<'_>,
316 upgrades: &mut UpgradeMap,
317 upgrade_messages: &mut HashSet<String>,
318 remaining_specs: &mut IndexSet<PackageIdSpec>,
319 dependency: Dependency,
320) -> CargoResult<Dependency> {
321 let name = dependency.package_name();
322 let renamed_to = dependency.name_in_toml();
323
324 if name != renamed_to {
325 trace!("skipping dependency renamed from `{name}` to `{renamed_to}`");
326 return Ok(dependency);
327 }
328
329 if !to_update.is_empty()
330 && !to_update.iter().any(|spec| {
331 spec.name() == name.as_str()
332 && dependency.source_id().is_registry()
333 && spec
334 .url()
335 .map_or(true, |url| url == dependency.source_id().url())
336 && spec
337 .version()
338 .map_or(true, |v| dependency.version_req().matches(&v))
339 })
340 {
341 trace!("skipping dependency `{name}` not selected for upgrading");
342 return Ok(dependency);
343 }
344
345 if !dependency.source_id().is_registry() {
346 trace!("skipping non-registry dependency: {name}");
347 return Ok(dependency);
348 }
349
350 let version_req = dependency.version_req();
351
352 let OptVersionReq::Req(current) = version_req else {
353 trace!("skipping dependency `{name}` without a simple version requirement: {version_req}");
354 return Ok(dependency);
355 };
356
357 let [comparator] = ¤t.comparators[..] else {
358 trace!(
359 "skipping dependency `{name}` with multiple version comparators: {:?}",
360 ¤t.comparators
361 );
362 return Ok(dependency);
363 };
364
365 if comparator.op != Op::Caret {
366 trace!("skipping non-caret dependency `{name}`: {comparator}");
367 return Ok(dependency);
368 }
369
370 let query =
371 crate::core::dependency::Dependency::parse(name, None, dependency.source_id().clone())?;
372
373 let possibilities = crate::util::block_on(registry.query_vec(&query, QueryKind::Exact))?;
374
375 let latest = if !possibilities.is_empty() {
376 possibilities
377 .iter()
378 .filter_map(|s| match s {
379 IndexSummary::Candidate(s) => Some(s),
380 _ => None,
381 })
382 .map(|s| s.version())
383 .filter(|v| !v.is_prerelease())
384 .max()
385 } else {
386 None
387 };
388
389 let Some(latest) = latest else {
390 trace!("skipping dependency `{name}` without any published versions");
391 return Ok(dependency);
392 };
393
394 if current.matches(&latest) {
395 trace!("skipping dependency `{name}` without a breaking update available");
396 return Ok(dependency);
397 }
398
399 let Some((new_req_string, _)) = upgrade_requirement(¤t.to_string(), latest)? else {
400 trace!("skipping dependency `{name}` because the version requirement didn't change");
401 return Ok(dependency);
402 };
403
404 let upgrade_message = format!("{name} {current} -> {new_req_string}");
405 trace!(upgrade_message);
406
407 if upgrade_messages.insert(upgrade_message.clone()) {
408 gctx.shell()
409 .status_with_color("Upgrading", &upgrade_message, &style::GOOD)?;
410 }
411
412 upgrades.insert((name.to_string(), dependency.source_id()), latest.clone());
413
414 remaining_specs
416 .retain(|spec| !(spec.name() == name.as_str() && dependency.source_id().is_registry()));
417
418 let req = OptVersionReq::Req(VersionReq::parse(&latest.to_string())?);
419 let mut dep = dependency.clone();
420 dep.set_version_req(req);
421 Ok(dep)
422}
423
424pub fn write_manifest_upgrades(
436 ws: &Workspace<'_>,
437 upgrades: &UpgradeMap,
438 dry_run: bool,
439) -> CargoResult<bool> {
440 if upgrades.is_empty() {
441 return Ok(false);
442 }
443
444 let mut any_file_has_changed = false;
445
446 let items = std::iter::once((ws.root_manifest(), ws.unstable_features()))
447 .chain(ws.members().map(|member| {
448 (
449 member.manifest_path(),
450 member.manifest().unstable_features(),
451 )
452 }))
453 .collect::<Vec<_>>();
454
455 for (manifest_path, unstable_features) in items {
456 trace!("updating TOML manifest at `{manifest_path:?}` with upgraded dependencies");
457
458 let crate_root = manifest_path
459 .parent()
460 .expect("manifest path is absolute")
461 .to_owned();
462
463 let mut local_manifest = LocalManifest::try_new(&manifest_path)?;
464 let mut manifest_has_changed = false;
465
466 for dep_table in local_manifest.get_dependency_tables_mut() {
467 for (mut dep_key, dep_item) in dep_table.iter_mut() {
468 let dep_key_str = dep_key.get();
469 let dependency = crate::util::toml_mut::dependency::Dependency::from_toml(
470 ws.gctx(),
471 ws.root(),
472 &manifest_path,
473 unstable_features,
474 dep_key_str,
475 dep_item,
476 )?;
477 let name = &dependency.name;
478
479 if let Some(renamed_to) = dependency.rename {
480 trace!("skipping dependency renamed from `{name}` to `{renamed_to}`");
481 continue;
482 }
483
484 let Some(current) = dependency.version() else {
485 trace!("skipping dependency without a version: {name}");
486 continue;
487 };
488
489 let (MaybeWorkspace::Other(source_id), Some(Source::Registry(source))) =
490 (dependency.source_id(ws.gctx())?, dependency.source())
491 else {
492 trace!("skipping non-registry dependency: {name}");
493 continue;
494 };
495
496 let Some(latest) = upgrades.get(&(name.to_owned(), source_id)) else {
497 trace!("skipping dependency without an upgrade: {name}");
498 continue;
499 };
500
501 let Some((new_req_string, new_req)) = upgrade_requirement(current, latest)? else {
502 trace!(
503 "skipping dependency `{name}` because the version requirement didn't change"
504 );
505 continue;
506 };
507
508 let [comparator] = &new_req.comparators[..] else {
509 trace!(
510 "skipping dependency `{}` with multiple version comparators: {:?}",
511 name, new_req.comparators
512 );
513 continue;
514 };
515
516 if comparator.op != Op::Caret {
517 trace!("skipping non-caret dependency `{}`: {}", name, comparator);
518 continue;
519 }
520
521 let mut dep = dependency.clone();
522 let mut source = source.clone();
523 source.version = new_req_string;
524 dep.source = Some(Source::Registry(source));
525
526 trace!("upgrading dependency {name}");
527 dep.update_toml(
528 ws.gctx(),
529 ws.root(),
530 &crate_root,
531 unstable_features,
532 &mut dep_key,
533 dep_item,
534 )?;
535 manifest_has_changed = true;
536 any_file_has_changed = true;
537 }
538 }
539
540 if manifest_has_changed && !dry_run {
541 debug!("writing upgraded manifest to {}", manifest_path.display());
542 local_manifest.write()?;
543 }
544 }
545
546 Ok(any_file_has_changed)
547}
548
549fn print_lockfile_generation(
550 ws: &Workspace<'_>,
551 resolve: &Resolve,
552 registry: &mut PackageRegistry<'_>,
553) -> CargoResult<()> {
554 let mut changes = PackageChange::new(ws, resolve);
555 let num_pkgs: usize = changes
556 .values()
557 .filter(|change| change.kind.is_new() && !change.is_member.unwrap_or(false))
558 .count();
559 if num_pkgs == 0 {
560 return Ok(());
562 }
563 annotate_required_rust_version(ws, resolve, &mut changes);
564
565 status_locking(ws, num_pkgs)?;
566 for change in changes.values() {
567 if change.is_member.unwrap_or(false) {
568 continue;
569 };
570 match change.kind {
571 PackageChangeKind::Added => {
572 let possibilities = if let Some(query) = change.alternatives_query() {
573 crate::util::block_on(registry.query_vec(&query, QueryKind::Exact))?
574 } else {
575 vec![]
576 };
577
578 let required_rust_version = report_required_rust_version(resolve, change);
579 let latest = report_latest(&possibilities, change);
580 let note = required_rust_version.or(latest);
581
582 if let Some(note) = note {
583 ws.gctx().shell().status_with_color(
584 change.kind.status(),
585 format!("{change}{note}"),
586 &change.kind.style(),
587 )?;
588 }
589 }
590 PackageChangeKind::Upgraded
591 | PackageChangeKind::Downgraded
592 | PackageChangeKind::Removed
593 | PackageChangeKind::Unchanged => {
594 unreachable!("without a previous resolve, everything should be added")
595 }
596 }
597 }
598
599 Ok(())
600}
601
602fn print_lockfile_sync(
603 ws: &Workspace<'_>,
604 previous_resolve: &Resolve,
605 resolve: &Resolve,
606 registry: &mut PackageRegistry<'_>,
607) -> CargoResult<()> {
608 let mut changes = PackageChange::diff(ws, previous_resolve, resolve);
609 let num_pkgs: usize = changes
610 .values()
611 .filter(|change| change.kind.is_new() && !change.is_member.unwrap_or(false))
612 .count();
613 if num_pkgs == 0 {
614 return Ok(());
616 }
617 annotate_required_rust_version(ws, resolve, &mut changes);
618
619 status_locking(ws, num_pkgs)?;
620 for change in changes.values() {
621 if change.is_member.unwrap_or(false) {
622 continue;
623 };
624 match change.kind {
625 PackageChangeKind::Added
626 | PackageChangeKind::Upgraded
627 | PackageChangeKind::Downgraded => {
628 let possibilities = if let Some(query) = change.alternatives_query() {
629 crate::util::block_on(registry.query_vec(&query, QueryKind::Exact))?
630 } else {
631 vec![]
632 };
633
634 let required_rust_version = report_required_rust_version(resolve, change);
635 let latest = report_latest(&possibilities, change);
636 let note = required_rust_version.or(latest).unwrap_or_default();
637
638 ws.gctx().shell().status_with_color(
639 change.kind.status(),
640 format!("{change}{note}"),
641 &change.kind.style(),
642 )?;
643 }
644 PackageChangeKind::Removed | PackageChangeKind::Unchanged => {}
645 }
646 }
647
648 Ok(())
649}
650
651fn print_lockfile_updates(
652 ws: &Workspace<'_>,
653 previous_resolve: &Resolve,
654 resolve: &Resolve,
655 precise: bool,
656 registry: &mut PackageRegistry<'_>,
657) -> CargoResult<()> {
658 let mut changes = PackageChange::diff(ws, previous_resolve, resolve);
659 let num_pkgs: usize = changes
660 .values()
661 .filter(|change| change.kind.is_new())
662 .count();
663 annotate_required_rust_version(ws, resolve, &mut changes);
664
665 if !precise {
666 status_locking(ws, num_pkgs)?;
667 }
668 let mut unchanged_behind = 0;
669 for change in changes.values() {
670 let possibilities = if let Some(query) = change.alternatives_query() {
671 crate::util::block_on(registry.query_vec(&query, QueryKind::Exact))?
672 } else {
673 vec![]
674 };
675
676 match change.kind {
677 PackageChangeKind::Added
678 | PackageChangeKind::Upgraded
679 | PackageChangeKind::Downgraded => {
680 let required_rust_version = report_required_rust_version(resolve, change);
681 let latest = report_latest(&possibilities, change);
682 let note = required_rust_version.or(latest).unwrap_or_default();
683
684 ws.gctx().shell().status_with_color(
685 change.kind.status(),
686 format!("{change}{note}"),
687 &change.kind.style(),
688 )?;
689 }
690 PackageChangeKind::Removed => {
691 ws.gctx().shell().status_with_color(
692 change.kind.status(),
693 format!("{change}"),
694 &change.kind.style(),
695 )?;
696 }
697 PackageChangeKind::Unchanged => {
698 let required_rust_version = report_required_rust_version(resolve, change);
699 let latest = report_latest(&possibilities, change);
700 let note = required_rust_version.as_deref().or(latest.as_deref());
701
702 if let Some(note) = note {
703 if latest.is_some() {
704 unchanged_behind += 1;
705 }
706 if ws.gctx().shell().verbosity() == Verbosity::Verbose {
707 ws.gctx().shell().status_with_color(
708 change.kind.status(),
709 format!("{change}{note}"),
710 &change.kind.style(),
711 )?;
712 }
713 }
714 }
715 }
716 }
717
718 if ws.gctx().shell().verbosity() == Verbosity::Verbose {
719 ws.gctx()
720 .shell()
721 .note("to see how you depend on a package, run `cargo tree --invert <dep>@<ver>`")?;
722 } else {
723 if 0 < unchanged_behind {
724 ws.gctx().shell().note(format!(
725 "pass `--verbose` to see {unchanged_behind} unchanged dependencies behind latest"
726 ))?;
727 }
728 }
729
730 Ok(())
731}
732
733fn status_locking(ws: &Workspace<'_>, num_pkgs: usize) -> CargoResult<()> {
734 use std::fmt::Write as _;
735
736 let plural = if num_pkgs == 1 { "" } else { "s" };
737
738 let mut cfg = String::new();
739 if !ws.gctx().cli_unstable().direct_minimal_versions {
741 write!(&mut cfg, " to")?;
742 if ws.gctx().cli_unstable().minimal_versions {
743 write!(&mut cfg, " earliest")?;
744 } else {
745 write!(&mut cfg, " latest")?;
746 }
747
748 if let Some(rust_version) = required_rust_version(ws) {
749 write!(&mut cfg, " Rust {rust_version}")?;
750 }
751 write!(&mut cfg, " compatible version{plural}")?;
752 if let Some(publish_time) = ws.resolve_publish_time() {
753 write!(&mut cfg, " as of {publish_time}")?;
754 }
755 }
756
757 ws.gctx()
758 .shell()
759 .status("Locking", format!("{num_pkgs} package{plural}{cfg}"))?;
760 Ok(())
761}
762
763fn required_rust_version(ws: &Workspace<'_>) -> Option<PartialVersion> {
764 if !ws.resolve_honors_rust_version() {
765 return None;
766 }
767
768 if let Some(ver) = ws.lowest_rust_version() {
769 Some(ver.to_partial())
770 } else {
771 let rustc = ws.gctx().load_global_rustc(Some(ws)).ok()?;
772 let rustc_version = rustc.version.clone().into();
773 Some(rustc_version)
774 }
775}
776
777fn report_required_rust_version(resolve: &Resolve, change: &PackageChange) -> Option<String> {
778 if change.package_id.source_id().is_path() {
779 return None;
780 }
781 let summary = resolve.summary(change.package_id);
782 let package_rust_version = summary.rust_version()?;
783 let required_rust_version = change.required_rust_version.as_ref()?;
784 if package_rust_version.is_compatible_with(required_rust_version) {
785 return None;
786 }
787
788 let error = style::ERROR;
789 Some(format!(
790 " {error}(requires Rust {package_rust_version}){error:#}"
791 ))
792}
793
794fn report_latest(possibilities: &[IndexSummary], change: &PackageChange) -> Option<String> {
795 let package_id = change.package_id;
796 if !package_id.source_id().is_registry() {
797 return None;
798 }
799
800 let version_req = package_id.version().to_caret_req();
801 let required_rust_version = change.required_rust_version.as_ref();
802
803 let compat_ver_compat_msrv_summary = possibilities
804 .iter()
805 .filter_map(|s| match s {
806 IndexSummary::Candidate(s) => Some(s),
807 _ => None,
808 })
809 .filter(|s| {
810 if let (Some(summary_rust_version), Some(required_rust_version)) =
811 (s.rust_version(), required_rust_version)
812 {
813 summary_rust_version.is_compatible_with(required_rust_version)
814 } else {
815 true
816 }
817 })
818 .filter(|s| package_id.version() != s.version() && version_req.matches(s.version()))
819 .max_by_key(|s| s.version());
820 if let Some(summary) = compat_ver_compat_msrv_summary {
821 let warn = style::WARN;
822 let version = summary.version();
823 let report = format!(" {warn}(available: v{version}){warn:#}");
824 return Some(report);
825 }
826
827 if !change.is_transitive.unwrap_or(true) {
828 let incompat_ver_compat_msrv_summary = possibilities
829 .iter()
830 .filter_map(|s| match s {
831 IndexSummary::Candidate(s) => Some(s),
832 _ => None,
833 })
834 .filter(|s| {
835 if let (Some(summary_rust_version), Some(required_rust_version)) =
836 (s.rust_version(), required_rust_version)
837 {
838 summary_rust_version.is_compatible_with(required_rust_version)
839 } else {
840 true
841 }
842 })
843 .filter(|s| is_latest(s.version(), package_id.version()))
844 .max_by_key(|s| s.version());
845 if let Some(summary) = incompat_ver_compat_msrv_summary {
846 let warn = style::WARN;
847 let version = summary.version();
848 let report = format!(" {warn}(available: v{version}){warn:#}");
849 return Some(report);
850 }
851 }
852
853 let compat_ver_summary = possibilities
854 .iter()
855 .filter_map(|s| match s {
856 IndexSummary::Candidate(s) => Some(s),
857 _ => None,
858 })
859 .filter(|s| package_id.version() != s.version() && version_req.matches(s.version()))
860 .max_by_key(|s| s.version());
861 if let Some(summary) = compat_ver_summary {
862 let msrv_note = summary
863 .rust_version()
864 .map(|rv| format!(", requires Rust {rv}"))
865 .unwrap_or_default();
866 let warn = style::NOP;
867 let version = summary.version();
868 let report = format!(" {warn}(available: v{version}{msrv_note}){warn:#}");
869 return Some(report);
870 }
871
872 if !change.is_transitive.unwrap_or(true) {
873 let incompat_ver_summary = possibilities
874 .iter()
875 .filter_map(|s| match s {
876 IndexSummary::Candidate(s) => Some(s),
877 _ => None,
878 })
879 .filter(|s| is_latest(s.version(), package_id.version()))
880 .max_by_key(|s| s.version());
881 if let Some(summary) = incompat_ver_summary {
882 let msrv_note = summary
883 .rust_version()
884 .map(|rv| format!(", requires Rust {rv}"))
885 .unwrap_or_default();
886 let warn = style::NOP;
887 let version = summary.version();
888 let report = format!(" {warn}(available: v{version}{msrv_note}){warn:#}");
889 return Some(report);
890 }
891 }
892
893 None
894}
895
896fn is_latest(candidate: &semver::Version, current: &semver::Version) -> bool {
897 current < candidate
898 && (candidate.pre.is_empty()
900 || (candidate.major == current.major
901 && candidate.minor == current.minor
902 && candidate.patch == current.patch))
903}
904
905fn fill_with_deps<'a>(
906 resolve: &'a Resolve,
907 dep: PackageId,
908 set: &mut HashSet<PackageId>,
909 visited: &mut HashSet<PackageId>,
910) {
911 if !visited.insert(dep) {
912 return;
913 }
914 set.insert(dep);
915 for (dep, _) in resolve.deps_not_replaced(dep) {
916 fill_with_deps(resolve, dep, set, visited);
917 }
918}
919
920#[derive(Clone, Debug)]
921struct PackageChange {
922 package_id: PackageId,
923 previous_id: Option<PackageId>,
924 kind: PackageChangeKind,
925 is_member: Option<bool>,
926 is_transitive: Option<bool>,
927 required_rust_version: Option<PartialVersion>,
928}
929
930impl PackageChange {
931 pub fn new(ws: &Workspace<'_>, resolve: &Resolve) -> IndexMap<PackageId, Self> {
932 let diff = PackageDiff::new(resolve);
933 Self::with_diff(diff, ws, resolve)
934 }
935
936 pub fn diff(
937 ws: &Workspace<'_>,
938 previous_resolve: &Resolve,
939 resolve: &Resolve,
940 ) -> IndexMap<PackageId, Self> {
941 let diff = PackageDiff::diff(previous_resolve, resolve);
942 Self::with_diff(diff, ws, resolve)
943 }
944
945 fn with_diff(
946 diff: impl Iterator<Item = PackageDiff>,
947 ws: &Workspace<'_>,
948 resolve: &Resolve,
949 ) -> IndexMap<PackageId, Self> {
950 let member_ids: HashSet<_> = ws.members().map(|p| p.package_id()).collect();
951
952 let mut changes = IndexMap::new();
953 for diff in diff {
954 if let Some((previous_id, package_id)) = diff.change() {
955 let kind = if previous_id.version().cmp_precedence(package_id.version())
960 == Ordering::Greater
961 {
962 PackageChangeKind::Downgraded
963 } else {
964 PackageChangeKind::Upgraded
965 };
966 let is_member = Some(member_ids.contains(&package_id));
967 let is_transitive = Some(true);
968 let change = Self {
969 package_id,
970 previous_id: Some(previous_id),
971 kind,
972 is_member,
973 is_transitive,
974 required_rust_version: None,
975 };
976 changes.insert(change.package_id, change);
977 } else {
978 for package_id in diff.removed {
979 let kind = PackageChangeKind::Removed;
980 let is_member = None;
981 let is_transitive = None;
982 let change = Self {
983 package_id,
984 previous_id: None,
985 kind,
986 is_member,
987 is_transitive,
988 required_rust_version: None,
989 };
990 changes.insert(change.package_id, change);
991 }
992 for package_id in diff.added {
993 let kind = PackageChangeKind::Added;
994 let is_member = Some(member_ids.contains(&package_id));
995 let is_transitive = Some(true);
996 let change = Self {
997 package_id,
998 previous_id: None,
999 kind,
1000 is_member,
1001 is_transitive,
1002 required_rust_version: None,
1003 };
1004 changes.insert(change.package_id, change);
1005 }
1006 }
1007 for package_id in diff.unchanged {
1008 let kind = PackageChangeKind::Unchanged;
1009 let is_member = Some(member_ids.contains(&package_id));
1010 let is_transitive = Some(true);
1011 let change = Self {
1012 package_id,
1013 previous_id: None,
1014 kind,
1015 is_member,
1016 is_transitive,
1017 required_rust_version: None,
1018 };
1019 changes.insert(change.package_id, change);
1020 }
1021 }
1022
1023 for member_id in &member_ids {
1024 let Some(change) = changes.get_mut(member_id) else {
1025 continue;
1026 };
1027 change.is_transitive = Some(false);
1028 for (direct_dep_id, _) in resolve.deps(*member_id) {
1029 let Some(change) = changes.get_mut(&direct_dep_id) else {
1030 continue;
1031 };
1032 change.is_transitive = Some(false);
1033 }
1034 }
1035
1036 changes
1037 }
1038
1039 fn alternatives_query(&self) -> Option<crate::core::dependency::Dependency> {
1041 if !self.package_id.source_id().is_registry() {
1042 return None;
1043 }
1044
1045 let query = crate::core::dependency::Dependency::parse(
1046 self.package_id.name(),
1047 None,
1048 self.package_id.source_id(),
1049 )
1050 .expect("already a valid dependency");
1051 Some(query)
1052 }
1053}
1054
1055impl std::fmt::Display for PackageChange {
1056 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1057 let package_id = self.package_id;
1058 if let Some(previous_id) = self.previous_id {
1059 if package_id.source_id().is_git() {
1060 write!(
1061 f,
1062 "{previous_id} -> #{}",
1063 &package_id.source_id().precise_git_fragment().unwrap()[..8],
1064 )
1065 } else {
1066 write!(f, "{previous_id} -> v{}", package_id.version())
1067 }
1068 } else {
1069 write!(f, "{package_id}")
1070 }
1071 }
1072}
1073
1074#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1075enum PackageChangeKind {
1076 Added,
1077 Removed,
1078 Upgraded,
1079 Downgraded,
1080 Unchanged,
1081}
1082
1083impl PackageChangeKind {
1084 pub fn is_new(&self) -> bool {
1085 match self {
1086 Self::Added | Self::Upgraded | Self::Downgraded => true,
1087 Self::Removed | Self::Unchanged => false,
1088 }
1089 }
1090
1091 pub fn status(&self) -> &'static str {
1092 match self {
1093 Self::Added => "Adding",
1094 Self::Removed => "Removing",
1095 Self::Upgraded => "Updating",
1096 Self::Downgraded => "Downgrading",
1097 Self::Unchanged => "Unchanged",
1098 }
1099 }
1100
1101 pub fn style(&self) -> anstyle::Style {
1102 match self {
1103 Self::Added => style::UPDATE_ADDED,
1104 Self::Removed => style::UPDATE_REMOVED,
1105 Self::Upgraded => style::UPDATE_UPGRADED,
1106 Self::Downgraded => style::UPDATE_DOWNGRADED,
1107 Self::Unchanged => style::UPDATE_UNCHANGED,
1108 }
1109 }
1110}
1111
1112#[derive(Default, Clone, Debug)]
1114pub struct PackageDiff {
1115 removed: Vec<PackageId>,
1116 added: Vec<PackageId>,
1117 unchanged: Vec<PackageId>,
1118}
1119
1120impl PackageDiff {
1121 pub fn new(resolve: &Resolve) -> impl Iterator<Item = Self> {
1122 let mut changes = BTreeMap::new();
1123 let empty = Self::default();
1124 for dep in resolve.iter() {
1125 changes
1126 .entry(Self::key(dep))
1127 .or_insert_with(|| empty.clone())
1128 .added
1129 .push(dep);
1130 }
1131
1132 changes.into_iter().map(|(_, v)| v)
1133 }
1134
1135 pub fn diff(previous_resolve: &Resolve, resolve: &Resolve) -> impl Iterator<Item = Self> {
1136 fn vec_subset(a: &[PackageId], b: &[PackageId]) -> Vec<PackageId> {
1137 a.iter().filter(|a| !contains_id(b, a)).cloned().collect()
1138 }
1139
1140 fn vec_intersection(a: &[PackageId], b: &[PackageId]) -> Vec<PackageId> {
1141 a.iter().filter(|a| contains_id(b, a)).cloned().collect()
1142 }
1143
1144 fn contains_id(haystack: &[PackageId], needle: &PackageId) -> bool {
1150 let Ok(i) = haystack.binary_search(needle) else {
1151 return false;
1152 };
1153
1154 if needle.source_id().is_registry() {
1163 return true;
1164 }
1165 haystack[i..]
1166 .iter()
1167 .take_while(|b| &needle == b)
1168 .any(|b| needle.source_id().has_same_precise_as(b.source_id()))
1169 }
1170
1171 let mut changes = BTreeMap::new();
1173 let empty = Self::default();
1174 for dep in previous_resolve.iter() {
1175 changes
1176 .entry(Self::key(dep))
1177 .or_insert_with(|| empty.clone())
1178 .removed
1179 .push(dep);
1180 }
1181 for dep in resolve.iter() {
1182 changes
1183 .entry(Self::key(dep))
1184 .or_insert_with(|| empty.clone())
1185 .added
1186 .push(dep);
1187 }
1188
1189 for v in changes.values_mut() {
1190 let Self {
1191 removed: ref mut old,
1192 added: ref mut new,
1193 unchanged: ref mut other,
1194 } = *v;
1195 old.sort();
1196 new.sort();
1197 let removed = vec_subset(old, new);
1198 let added = vec_subset(new, old);
1199 let unchanged = vec_intersection(new, old);
1200 *old = removed;
1201 *new = added;
1202 *other = unchanged;
1203 }
1204 debug!("{:#?}", changes);
1205
1206 changes.into_iter().map(|(_, v)| v)
1207 }
1208
1209 fn key(dep: PackageId) -> (&'static str, SourceId) {
1210 (dep.name().as_str(), dep.source_id())
1211 }
1212
1213 pub fn change(&self) -> Option<(PackageId, PackageId)> {
1219 if self.removed.len() == 1 && self.added.len() == 1 {
1220 Some((self.removed[0], self.added[0]))
1221 } else {
1222 None
1223 }
1224 }
1225}
1226
1227fn annotate_required_rust_version(
1228 ws: &Workspace<'_>,
1229 resolve: &Resolve,
1230 changes: &mut IndexMap<PackageId, PackageChange>,
1231) {
1232 let rustc = ws.gctx().load_global_rustc(Some(ws)).ok();
1233 let rustc_version: Option<PartialVersion> =
1234 rustc.as_ref().map(|rustc| rustc.version.clone().into());
1235
1236 if ws.resolve_honors_rust_version() {
1237 let mut queue: std::collections::VecDeque<_> = ws
1238 .members()
1239 .map(|p| {
1240 (
1241 p.rust_version()
1242 .map(|r| r.to_partial())
1243 .or_else(|| rustc_version.clone()),
1244 p.package_id(),
1245 )
1246 })
1247 .collect();
1248 while let Some((required_rust_version, current_id)) = queue.pop_front() {
1249 let Some(required_rust_version) = required_rust_version else {
1250 continue;
1251 };
1252 if let Some(change) = changes.get_mut(¤t_id) {
1253 if let Some(existing) = change.required_rust_version.as_ref() {
1254 if *existing <= required_rust_version {
1255 continue;
1257 }
1258 }
1259 change.required_rust_version = Some(required_rust_version.clone());
1260 }
1261 queue.extend(
1262 resolve
1263 .deps(current_id)
1264 .map(|(dep, _)| (Some(required_rust_version.clone()), dep)),
1265 );
1266 }
1267 } else {
1268 for change in changes.values_mut() {
1269 change.required_rust_version = rustc_version.clone();
1270 }
1271 }
1272}