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::shell::Verbosity;
6use crate::core::{PackageId, PackageIdSpec, PackageIdSpecQuery};
7use crate::core::{Resolve, SourceId, Workspace};
8use crate::ops;
9use crate::sources::IndexSummary;
10use crate::sources::source::QueryKind;
11use crate::util::cache_lock::CacheLockMode;
12use crate::util::context::GlobalContext;
13use crate::util::toml_mut::dependency::{MaybeWorkspace, Source};
14use crate::util::toml_mut::manifest::LocalManifest;
15use crate::util::toml_mut::upgrade::upgrade_requirement;
16use crate::util::{CargoResult, VersionExt};
17use crate::util::{OptVersionReq, style};
18use anyhow::Context as _;
19use cargo_util_schemas::core::PartialVersion;
20use indexmap::IndexMap;
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 for member in ws.members_mut().sorted() {
242 debug!("upgrading manifest for `{}`", member.name());
243
244 *member.manifest_mut().summary_mut() = member
245 .manifest()
246 .summary()
247 .clone()
248 .try_map_dependencies(|d| {
249 upgrade_dependency(
250 &gctx,
251 &to_update,
252 &mut registry,
253 &mut upgrades,
254 &mut upgrade_messages,
255 d,
256 )
257 })?;
258 }
259
260 Ok(upgrades)
261}
262
263fn upgrade_dependency(
264 gctx: &GlobalContext,
265 to_update: &Vec<PackageIdSpec>,
266 registry: &mut PackageRegistry<'_>,
267 upgrades: &mut UpgradeMap,
268 upgrade_messages: &mut HashSet<String>,
269 dependency: Dependency,
270) -> CargoResult<Dependency> {
271 let name = dependency.package_name();
272 let renamed_to = dependency.name_in_toml();
273
274 if name != renamed_to {
275 trace!("skipping dependency renamed from `{name}` to `{renamed_to}`");
276 return Ok(dependency);
277 }
278
279 if !to_update.is_empty()
280 && !to_update.iter().any(|spec| {
281 spec.name() == name.as_str()
282 && dependency.source_id().is_registry()
283 && spec
284 .url()
285 .map_or(true, |url| url == dependency.source_id().url())
286 && spec
287 .version()
288 .map_or(true, |v| dependency.version_req().matches(&v))
289 })
290 {
291 trace!("skipping dependency `{name}` not selected for upgrading");
292 return Ok(dependency);
293 }
294
295 if !dependency.source_id().is_registry() {
296 trace!("skipping non-registry dependency: {name}");
297 return Ok(dependency);
298 }
299
300 let version_req = dependency.version_req();
301
302 let OptVersionReq::Req(current) = version_req else {
303 trace!("skipping dependency `{name}` without a simple version requirement: {version_req}");
304 return Ok(dependency);
305 };
306
307 let [comparator] = ¤t.comparators[..] else {
308 trace!(
309 "skipping dependency `{name}` with multiple version comparators: {:?}",
310 ¤t.comparators
311 );
312 return Ok(dependency);
313 };
314
315 if comparator.op != Op::Caret {
316 trace!("skipping non-caret dependency `{name}`: {comparator}");
317 return Ok(dependency);
318 }
319
320 let query =
321 crate::core::dependency::Dependency::parse(name, None, dependency.source_id().clone())?;
322
323 let possibilities = {
324 loop {
325 match registry.query_vec(&query, QueryKind::Exact) {
326 std::task::Poll::Ready(res) => {
327 break res?;
328 }
329 std::task::Poll::Pending => registry.block_until_ready()?,
330 }
331 }
332 };
333
334 let latest = if !possibilities.is_empty() {
335 possibilities
336 .iter()
337 .map(|s| s.as_summary())
338 .map(|s| s.version())
339 .filter(|v| !v.is_prerelease())
340 .max()
341 } else {
342 None
343 };
344
345 let Some(latest) = latest else {
346 trace!("skipping dependency `{name}` without any published versions");
347 return Ok(dependency);
348 };
349
350 if current.matches(&latest) {
351 trace!("skipping dependency `{name}` without a breaking update available");
352 return Ok(dependency);
353 }
354
355 let Some((new_req_string, _)) = upgrade_requirement(¤t.to_string(), latest)? else {
356 trace!("skipping dependency `{name}` because the version requirement didn't change");
357 return Ok(dependency);
358 };
359
360 let upgrade_message = format!("{name} {current} -> {new_req_string}");
361 trace!(upgrade_message);
362
363 if upgrade_messages.insert(upgrade_message.clone()) {
364 gctx.shell()
365 .status_with_color("Upgrading", &upgrade_message, &style::GOOD)?;
366 }
367
368 upgrades.insert((name.to_string(), dependency.source_id()), latest.clone());
369
370 let req = OptVersionReq::Req(VersionReq::parse(&latest.to_string())?);
371 let mut dep = dependency.clone();
372 dep.set_version_req(req);
373 Ok(dep)
374}
375
376pub fn write_manifest_upgrades(
388 ws: &Workspace<'_>,
389 upgrades: &UpgradeMap,
390 dry_run: bool,
391) -> CargoResult<bool> {
392 if upgrades.is_empty() {
393 return Ok(false);
394 }
395
396 let mut any_file_has_changed = false;
397
398 let items = std::iter::once((ws.root_manifest(), ws.unstable_features()))
399 .chain(ws.members().map(|member| {
400 (
401 member.manifest_path(),
402 member.manifest().unstable_features(),
403 )
404 }))
405 .collect::<Vec<_>>();
406
407 for (manifest_path, unstable_features) in items {
408 trace!("updating TOML manifest at `{manifest_path:?}` with upgraded dependencies");
409
410 let crate_root = manifest_path
411 .parent()
412 .expect("manifest path is absolute")
413 .to_owned();
414
415 let mut local_manifest = LocalManifest::try_new(&manifest_path)?;
416 let mut manifest_has_changed = false;
417
418 for dep_table in local_manifest.get_dependency_tables_mut() {
419 for (mut dep_key, dep_item) in dep_table.iter_mut() {
420 let dep_key_str = dep_key.get();
421 let dependency = crate::util::toml_mut::dependency::Dependency::from_toml(
422 ws.gctx(),
423 ws.root(),
424 &manifest_path,
425 unstable_features,
426 dep_key_str,
427 dep_item,
428 )?;
429 let name = &dependency.name;
430
431 if let Some(renamed_to) = dependency.rename {
432 trace!("skipping dependency renamed from `{name}` to `{renamed_to}`");
433 continue;
434 }
435
436 let Some(current) = dependency.version() else {
437 trace!("skipping dependency without a version: {name}");
438 continue;
439 };
440
441 let (MaybeWorkspace::Other(source_id), Some(Source::Registry(source))) =
442 (dependency.source_id(ws.gctx())?, dependency.source())
443 else {
444 trace!("skipping non-registry dependency: {name}");
445 continue;
446 };
447
448 let Some(latest) = upgrades.get(&(name.to_owned(), source_id)) else {
449 trace!("skipping dependency without an upgrade: {name}");
450 continue;
451 };
452
453 let Some((new_req_string, new_req)) = upgrade_requirement(current, latest)? else {
454 trace!(
455 "skipping dependency `{name}` because the version requirement didn't change"
456 );
457 continue;
458 };
459
460 let [comparator] = &new_req.comparators[..] else {
461 trace!(
462 "skipping dependency `{}` with multiple version comparators: {:?}",
463 name, new_req.comparators
464 );
465 continue;
466 };
467
468 if comparator.op != Op::Caret {
469 trace!("skipping non-caret dependency `{}`: {}", name, comparator);
470 continue;
471 }
472
473 let mut dep = dependency.clone();
474 let mut source = source.clone();
475 source.version = new_req_string;
476 dep.source = Some(Source::Registry(source));
477
478 trace!("upgrading dependency {name}");
479 dep.update_toml(
480 ws.gctx(),
481 ws.root(),
482 &crate_root,
483 unstable_features,
484 &mut dep_key,
485 dep_item,
486 )?;
487 manifest_has_changed = true;
488 any_file_has_changed = true;
489 }
490 }
491
492 if manifest_has_changed && !dry_run {
493 debug!("writing upgraded manifest to {}", manifest_path.display());
494 local_manifest.write()?;
495 }
496 }
497
498 Ok(any_file_has_changed)
499}
500
501fn print_lockfile_generation(
502 ws: &Workspace<'_>,
503 resolve: &Resolve,
504 registry: &mut PackageRegistry<'_>,
505) -> CargoResult<()> {
506 let mut changes = PackageChange::new(ws, resolve);
507 let num_pkgs: usize = changes
508 .values()
509 .filter(|change| change.kind.is_new() && !change.is_member.unwrap_or(false))
510 .count();
511 if num_pkgs == 0 {
512 return Ok(());
514 }
515 annotate_required_rust_version(ws, resolve, &mut changes);
516
517 status_locking(ws, num_pkgs)?;
518 for change in changes.values() {
519 if change.is_member.unwrap_or(false) {
520 continue;
521 };
522 match change.kind {
523 PackageChangeKind::Added => {
524 let possibilities = if let Some(query) = change.alternatives_query() {
525 loop {
526 match registry.query_vec(&query, QueryKind::Exact) {
527 std::task::Poll::Ready(res) => {
528 break res?;
529 }
530 std::task::Poll::Pending => registry.block_until_ready()?,
531 }
532 }
533 } else {
534 vec![]
535 };
536
537 let required_rust_version = report_required_rust_version(resolve, change);
538 let latest = report_latest(&possibilities, change);
539 let note = required_rust_version.or(latest);
540
541 if let Some(note) = note {
542 ws.gctx().shell().status_with_color(
543 change.kind.status(),
544 format!("{change}{note}"),
545 &change.kind.style(),
546 )?;
547 }
548 }
549 PackageChangeKind::Upgraded
550 | PackageChangeKind::Downgraded
551 | PackageChangeKind::Removed
552 | PackageChangeKind::Unchanged => {
553 unreachable!("without a previous resolve, everything should be added")
554 }
555 }
556 }
557
558 Ok(())
559}
560
561fn print_lockfile_sync(
562 ws: &Workspace<'_>,
563 previous_resolve: &Resolve,
564 resolve: &Resolve,
565 registry: &mut PackageRegistry<'_>,
566) -> CargoResult<()> {
567 let mut changes = PackageChange::diff(ws, previous_resolve, resolve);
568 let num_pkgs: usize = changes
569 .values()
570 .filter(|change| change.kind.is_new() && !change.is_member.unwrap_or(false))
571 .count();
572 if num_pkgs == 0 {
573 return Ok(());
575 }
576 annotate_required_rust_version(ws, resolve, &mut changes);
577
578 status_locking(ws, num_pkgs)?;
579 for change in changes.values() {
580 if change.is_member.unwrap_or(false) {
581 continue;
582 };
583 match change.kind {
584 PackageChangeKind::Added
585 | PackageChangeKind::Upgraded
586 | PackageChangeKind::Downgraded => {
587 let possibilities = if let Some(query) = change.alternatives_query() {
588 loop {
589 match registry.query_vec(&query, QueryKind::Exact) {
590 std::task::Poll::Ready(res) => {
591 break res?;
592 }
593 std::task::Poll::Pending => registry.block_until_ready()?,
594 }
595 }
596 } else {
597 vec![]
598 };
599
600 let required_rust_version = report_required_rust_version(resolve, change);
601 let latest = report_latest(&possibilities, change);
602 let note = required_rust_version.or(latest).unwrap_or_default();
603
604 ws.gctx().shell().status_with_color(
605 change.kind.status(),
606 format!("{change}{note}"),
607 &change.kind.style(),
608 )?;
609 }
610 PackageChangeKind::Removed | PackageChangeKind::Unchanged => {}
611 }
612 }
613
614 Ok(())
615}
616
617fn print_lockfile_updates(
618 ws: &Workspace<'_>,
619 previous_resolve: &Resolve,
620 resolve: &Resolve,
621 precise: bool,
622 registry: &mut PackageRegistry<'_>,
623) -> CargoResult<()> {
624 let mut changes = PackageChange::diff(ws, previous_resolve, resolve);
625 let num_pkgs: usize = changes
626 .values()
627 .filter(|change| change.kind.is_new())
628 .count();
629 annotate_required_rust_version(ws, resolve, &mut changes);
630
631 if !precise {
632 status_locking(ws, num_pkgs)?;
633 }
634 let mut unchanged_behind = 0;
635 for change in changes.values() {
636 let possibilities = if let Some(query) = change.alternatives_query() {
637 loop {
638 match registry.query_vec(&query, QueryKind::Exact) {
639 std::task::Poll::Ready(res) => {
640 break res?;
641 }
642 std::task::Poll::Pending => registry.block_until_ready()?,
643 }
644 }
645 } else {
646 vec![]
647 };
648
649 match change.kind {
650 PackageChangeKind::Added
651 | PackageChangeKind::Upgraded
652 | PackageChangeKind::Downgraded => {
653 let required_rust_version = report_required_rust_version(resolve, change);
654 let latest = report_latest(&possibilities, change);
655 let note = required_rust_version.or(latest).unwrap_or_default();
656
657 ws.gctx().shell().status_with_color(
658 change.kind.status(),
659 format!("{change}{note}"),
660 &change.kind.style(),
661 )?;
662 }
663 PackageChangeKind::Removed => {
664 ws.gctx().shell().status_with_color(
665 change.kind.status(),
666 format!("{change}"),
667 &change.kind.style(),
668 )?;
669 }
670 PackageChangeKind::Unchanged => {
671 let required_rust_version = report_required_rust_version(resolve, change);
672 let latest = report_latest(&possibilities, change);
673 let note = required_rust_version.as_deref().or(latest.as_deref());
674
675 if let Some(note) = note {
676 if latest.is_some() {
677 unchanged_behind += 1;
678 }
679 if ws.gctx().shell().verbosity() == Verbosity::Verbose {
680 ws.gctx().shell().status_with_color(
681 change.kind.status(),
682 format!("{change}{note}"),
683 &change.kind.style(),
684 )?;
685 }
686 }
687 }
688 }
689 }
690
691 if ws.gctx().shell().verbosity() == Verbosity::Verbose {
692 ws.gctx()
693 .shell()
694 .note("to see how you depend on a package, run `cargo tree --invert <dep>@<ver>`")?;
695 } else {
696 if 0 < unchanged_behind {
697 ws.gctx().shell().note(format!(
698 "pass `--verbose` to see {unchanged_behind} unchanged dependencies behind latest"
699 ))?;
700 }
701 }
702
703 Ok(())
704}
705
706fn status_locking(ws: &Workspace<'_>, num_pkgs: usize) -> CargoResult<()> {
707 use std::fmt::Write as _;
708
709 let plural = if num_pkgs == 1 { "" } else { "s" };
710
711 let mut cfg = String::new();
712 if !ws.gctx().cli_unstable().direct_minimal_versions {
714 write!(&mut cfg, " to")?;
715 if ws.gctx().cli_unstable().minimal_versions {
716 write!(&mut cfg, " earliest")?;
717 } else {
718 write!(&mut cfg, " latest")?;
719 }
720
721 if let Some(rust_version) = required_rust_version(ws) {
722 write!(&mut cfg, " Rust {rust_version}")?;
723 }
724 write!(&mut cfg, " compatible version{plural}")?;
725 if let Some(publish_time) = ws.resolve_publish_time() {
726 write!(&mut cfg, " as of {publish_time}")?;
727 }
728 }
729
730 ws.gctx()
731 .shell()
732 .status("Locking", format!("{num_pkgs} package{plural}{cfg}"))?;
733 Ok(())
734}
735
736fn required_rust_version(ws: &Workspace<'_>) -> Option<PartialVersion> {
737 if !ws.resolve_honors_rust_version() {
738 return None;
739 }
740
741 if let Some(ver) = ws.lowest_rust_version() {
742 Some(ver.clone().into_partial())
743 } else {
744 let rustc = ws.gctx().load_global_rustc(Some(ws)).ok()?;
745 let rustc_version = rustc.version.clone().into();
746 Some(rustc_version)
747 }
748}
749
750fn report_required_rust_version(resolve: &Resolve, change: &PackageChange) -> Option<String> {
751 if change.package_id.source_id().is_path() {
752 return None;
753 }
754 let summary = resolve.summary(change.package_id);
755 let package_rust_version = summary.rust_version()?;
756 let required_rust_version = change.required_rust_version.as_ref()?;
757 if package_rust_version.is_compatible_with(required_rust_version) {
758 return None;
759 }
760
761 let error = style::ERROR;
762 Some(format!(
763 " {error}(requires Rust {package_rust_version}){error:#}"
764 ))
765}
766
767fn report_latest(possibilities: &[IndexSummary], change: &PackageChange) -> Option<String> {
768 let package_id = change.package_id;
769 if !package_id.source_id().is_registry() {
770 return None;
771 }
772
773 let version_req = package_id.version().to_caret_req();
774 let required_rust_version = change.required_rust_version.as_ref();
775
776 let compat_ver_compat_msrv_summary = possibilities
777 .iter()
778 .map(|s| s.as_summary())
779 .filter(|s| {
780 if let (Some(summary_rust_version), Some(required_rust_version)) =
781 (s.rust_version(), required_rust_version)
782 {
783 summary_rust_version.is_compatible_with(required_rust_version)
784 } else {
785 true
786 }
787 })
788 .filter(|s| package_id.version() != s.version() && version_req.matches(s.version()))
789 .max_by_key(|s| s.version());
790 if let Some(summary) = compat_ver_compat_msrv_summary {
791 let warn = style::WARN;
792 let version = summary.version();
793 let report = format!(" {warn}(available: v{version}){warn:#}");
794 return Some(report);
795 }
796
797 if !change.is_transitive.unwrap_or(true) {
798 let incompat_ver_compat_msrv_summary = possibilities
799 .iter()
800 .map(|s| s.as_summary())
801 .filter(|s| {
802 if let (Some(summary_rust_version), Some(required_rust_version)) =
803 (s.rust_version(), required_rust_version)
804 {
805 summary_rust_version.is_compatible_with(required_rust_version)
806 } else {
807 true
808 }
809 })
810 .filter(|s| is_latest(s.version(), package_id.version()))
811 .max_by_key(|s| s.version());
812 if let Some(summary) = incompat_ver_compat_msrv_summary {
813 let warn = style::WARN;
814 let version = summary.version();
815 let report = format!(" {warn}(available: v{version}){warn:#}");
816 return Some(report);
817 }
818 }
819
820 let compat_ver_summary = possibilities
821 .iter()
822 .map(|s| s.as_summary())
823 .filter(|s| package_id.version() != s.version() && version_req.matches(s.version()))
824 .max_by_key(|s| s.version());
825 if let Some(summary) = compat_ver_summary {
826 let msrv_note = summary
827 .rust_version()
828 .map(|rv| format!(", requires Rust {rv}"))
829 .unwrap_or_default();
830 let warn = style::NOP;
831 let version = summary.version();
832 let report = format!(" {warn}(available: v{version}{msrv_note}){warn:#}");
833 return Some(report);
834 }
835
836 if !change.is_transitive.unwrap_or(true) {
837 let incompat_ver_summary = possibilities
838 .iter()
839 .map(|s| s.as_summary())
840 .filter(|s| is_latest(s.version(), package_id.version()))
841 .max_by_key(|s| s.version());
842 if let Some(summary) = incompat_ver_summary {
843 let msrv_note = summary
844 .rust_version()
845 .map(|rv| format!(", requires Rust {rv}"))
846 .unwrap_or_default();
847 let warn = style::NOP;
848 let version = summary.version();
849 let report = format!(" {warn}(available: v{version}{msrv_note}){warn:#}");
850 return Some(report);
851 }
852 }
853
854 None
855}
856
857fn is_latest(candidate: &semver::Version, current: &semver::Version) -> bool {
858 current < candidate
859 && (candidate.pre.is_empty()
861 || (candidate.major == current.major
862 && candidate.minor == current.minor
863 && candidate.patch == current.patch))
864}
865
866fn fill_with_deps<'a>(
867 resolve: &'a Resolve,
868 dep: PackageId,
869 set: &mut HashSet<PackageId>,
870 visited: &mut HashSet<PackageId>,
871) {
872 if !visited.insert(dep) {
873 return;
874 }
875 set.insert(dep);
876 for (dep, _) in resolve.deps_not_replaced(dep) {
877 fill_with_deps(resolve, dep, set, visited);
878 }
879}
880
881#[derive(Clone, Debug)]
882struct PackageChange {
883 package_id: PackageId,
884 previous_id: Option<PackageId>,
885 kind: PackageChangeKind,
886 is_member: Option<bool>,
887 is_transitive: Option<bool>,
888 required_rust_version: Option<PartialVersion>,
889}
890
891impl PackageChange {
892 pub fn new(ws: &Workspace<'_>, resolve: &Resolve) -> IndexMap<PackageId, Self> {
893 let diff = PackageDiff::new(resolve);
894 Self::with_diff(diff, ws, resolve)
895 }
896
897 pub fn diff(
898 ws: &Workspace<'_>,
899 previous_resolve: &Resolve,
900 resolve: &Resolve,
901 ) -> IndexMap<PackageId, Self> {
902 let diff = PackageDiff::diff(previous_resolve, resolve);
903 Self::with_diff(diff, ws, resolve)
904 }
905
906 fn with_diff(
907 diff: impl Iterator<Item = PackageDiff>,
908 ws: &Workspace<'_>,
909 resolve: &Resolve,
910 ) -> IndexMap<PackageId, Self> {
911 let member_ids: HashSet<_> = ws.members().map(|p| p.package_id()).collect();
912
913 let mut changes = IndexMap::new();
914 for diff in diff {
915 if let Some((previous_id, package_id)) = diff.change() {
916 let kind = if previous_id.version().cmp_precedence(package_id.version())
921 == Ordering::Greater
922 {
923 PackageChangeKind::Downgraded
924 } else {
925 PackageChangeKind::Upgraded
926 };
927 let is_member = Some(member_ids.contains(&package_id));
928 let is_transitive = Some(true);
929 let change = Self {
930 package_id,
931 previous_id: Some(previous_id),
932 kind,
933 is_member,
934 is_transitive,
935 required_rust_version: None,
936 };
937 changes.insert(change.package_id, change);
938 } else {
939 for package_id in diff.removed {
940 let kind = PackageChangeKind::Removed;
941 let is_member = None;
942 let is_transitive = None;
943 let change = Self {
944 package_id,
945 previous_id: None,
946 kind,
947 is_member,
948 is_transitive,
949 required_rust_version: None,
950 };
951 changes.insert(change.package_id, change);
952 }
953 for package_id in diff.added {
954 let kind = PackageChangeKind::Added;
955 let is_member = Some(member_ids.contains(&package_id));
956 let is_transitive = Some(true);
957 let change = Self {
958 package_id,
959 previous_id: None,
960 kind,
961 is_member,
962 is_transitive,
963 required_rust_version: None,
964 };
965 changes.insert(change.package_id, change);
966 }
967 }
968 for package_id in diff.unchanged {
969 let kind = PackageChangeKind::Unchanged;
970 let is_member = Some(member_ids.contains(&package_id));
971 let is_transitive = Some(true);
972 let change = Self {
973 package_id,
974 previous_id: None,
975 kind,
976 is_member,
977 is_transitive,
978 required_rust_version: None,
979 };
980 changes.insert(change.package_id, change);
981 }
982 }
983
984 for member_id in &member_ids {
985 let Some(change) = changes.get_mut(member_id) else {
986 continue;
987 };
988 change.is_transitive = Some(false);
989 for (direct_dep_id, _) in resolve.deps(*member_id) {
990 let Some(change) = changes.get_mut(&direct_dep_id) else {
991 continue;
992 };
993 change.is_transitive = Some(false);
994 }
995 }
996
997 changes
998 }
999
1000 fn alternatives_query(&self) -> Option<crate::core::dependency::Dependency> {
1002 if !self.package_id.source_id().is_registry() {
1003 return None;
1004 }
1005
1006 let query = crate::core::dependency::Dependency::parse(
1007 self.package_id.name(),
1008 None,
1009 self.package_id.source_id(),
1010 )
1011 .expect("already a valid dependency");
1012 Some(query)
1013 }
1014}
1015
1016impl std::fmt::Display for PackageChange {
1017 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1018 let package_id = self.package_id;
1019 if let Some(previous_id) = self.previous_id {
1020 if package_id.source_id().is_git() {
1021 write!(
1022 f,
1023 "{previous_id} -> #{}",
1024 &package_id.source_id().precise_git_fragment().unwrap()[..8],
1025 )
1026 } else {
1027 write!(f, "{previous_id} -> v{}", package_id.version())
1028 }
1029 } else {
1030 write!(f, "{package_id}")
1031 }
1032 }
1033}
1034
1035#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1036enum PackageChangeKind {
1037 Added,
1038 Removed,
1039 Upgraded,
1040 Downgraded,
1041 Unchanged,
1042}
1043
1044impl PackageChangeKind {
1045 pub fn is_new(&self) -> bool {
1046 match self {
1047 Self::Added | Self::Upgraded | Self::Downgraded => true,
1048 Self::Removed | Self::Unchanged => false,
1049 }
1050 }
1051
1052 pub fn status(&self) -> &'static str {
1053 match self {
1054 Self::Added => "Adding",
1055 Self::Removed => "Removing",
1056 Self::Upgraded => "Updating",
1057 Self::Downgraded => "Downgrading",
1058 Self::Unchanged => "Unchanged",
1059 }
1060 }
1061
1062 pub fn style(&self) -> anstyle::Style {
1063 match self {
1064 Self::Added => style::UPDATE_ADDED,
1065 Self::Removed => style::UPDATE_REMOVED,
1066 Self::Upgraded => style::UPDATE_UPGRADED,
1067 Self::Downgraded => style::UPDATE_DOWNGRADED,
1068 Self::Unchanged => style::UPDATE_UNCHANGED,
1069 }
1070 }
1071}
1072
1073#[derive(Default, Clone, Debug)]
1075pub struct PackageDiff {
1076 removed: Vec<PackageId>,
1077 added: Vec<PackageId>,
1078 unchanged: Vec<PackageId>,
1079}
1080
1081impl PackageDiff {
1082 pub fn new(resolve: &Resolve) -> impl Iterator<Item = Self> {
1083 let mut changes = BTreeMap::new();
1084 let empty = Self::default();
1085 for dep in resolve.iter() {
1086 changes
1087 .entry(Self::key(dep))
1088 .or_insert_with(|| empty.clone())
1089 .added
1090 .push(dep);
1091 }
1092
1093 changes.into_iter().map(|(_, v)| v)
1094 }
1095
1096 pub fn diff(previous_resolve: &Resolve, resolve: &Resolve) -> impl Iterator<Item = Self> {
1097 fn vec_subset(a: &[PackageId], b: &[PackageId]) -> Vec<PackageId> {
1098 a.iter().filter(|a| !contains_id(b, a)).cloned().collect()
1099 }
1100
1101 fn vec_intersection(a: &[PackageId], b: &[PackageId]) -> Vec<PackageId> {
1102 a.iter().filter(|a| contains_id(b, a)).cloned().collect()
1103 }
1104
1105 fn contains_id(haystack: &[PackageId], needle: &PackageId) -> bool {
1111 let Ok(i) = haystack.binary_search(needle) else {
1112 return false;
1113 };
1114
1115 if needle.source_id().is_registry() {
1124 return true;
1125 }
1126 haystack[i..]
1127 .iter()
1128 .take_while(|b| &needle == b)
1129 .any(|b| needle.source_id().has_same_precise_as(b.source_id()))
1130 }
1131
1132 let mut changes = BTreeMap::new();
1134 let empty = Self::default();
1135 for dep in previous_resolve.iter() {
1136 changes
1137 .entry(Self::key(dep))
1138 .or_insert_with(|| empty.clone())
1139 .removed
1140 .push(dep);
1141 }
1142 for dep in resolve.iter() {
1143 changes
1144 .entry(Self::key(dep))
1145 .or_insert_with(|| empty.clone())
1146 .added
1147 .push(dep);
1148 }
1149
1150 for v in changes.values_mut() {
1151 let Self {
1152 removed: ref mut old,
1153 added: ref mut new,
1154 unchanged: ref mut other,
1155 } = *v;
1156 old.sort();
1157 new.sort();
1158 let removed = vec_subset(old, new);
1159 let added = vec_subset(new, old);
1160 let unchanged = vec_intersection(new, old);
1161 *old = removed;
1162 *new = added;
1163 *other = unchanged;
1164 }
1165 debug!("{:#?}", changes);
1166
1167 changes.into_iter().map(|(_, v)| v)
1168 }
1169
1170 fn key(dep: PackageId) -> (&'static str, SourceId) {
1171 (dep.name().as_str(), dep.source_id())
1172 }
1173
1174 pub fn change(&self) -> Option<(PackageId, PackageId)> {
1180 if self.removed.len() == 1 && self.added.len() == 1 {
1181 Some((self.removed[0], self.added[0]))
1182 } else {
1183 None
1184 }
1185 }
1186}
1187
1188fn annotate_required_rust_version(
1189 ws: &Workspace<'_>,
1190 resolve: &Resolve,
1191 changes: &mut IndexMap<PackageId, PackageChange>,
1192) {
1193 let rustc = ws.gctx().load_global_rustc(Some(ws)).ok();
1194 let rustc_version: Option<PartialVersion> =
1195 rustc.as_ref().map(|rustc| rustc.version.clone().into());
1196
1197 if ws.resolve_honors_rust_version() {
1198 let mut queue: std::collections::VecDeque<_> = ws
1199 .members()
1200 .map(|p| {
1201 (
1202 p.rust_version()
1203 .map(|r| r.clone().into_partial())
1204 .or_else(|| rustc_version.clone()),
1205 p.package_id(),
1206 )
1207 })
1208 .collect();
1209 while let Some((required_rust_version, current_id)) = queue.pop_front() {
1210 let Some(required_rust_version) = required_rust_version else {
1211 continue;
1212 };
1213 if let Some(change) = changes.get_mut(¤t_id) {
1214 if let Some(existing) = change.required_rust_version.as_ref() {
1215 if *existing <= required_rust_version {
1216 continue;
1218 }
1219 }
1220 change.required_rust_version = Some(required_rust_version.clone());
1221 }
1222 queue.extend(
1223 resolve
1224 .deps(current_id)
1225 .map(|(dep, _)| (Some(required_rust_version.clone()), dep)),
1226 );
1227 }
1228 } else {
1229 for change in changes.values_mut() {
1230 change.required_rust_version = rustc_version.clone();
1231 }
1232 }
1233}