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