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 .map(|s| s.as_summary())
379 .map(|s| s.version())
380 .filter(|v| !v.is_prerelease())
381 .max()
382 } else {
383 None
384 };
385
386 let Some(latest) = latest else {
387 trace!("skipping dependency `{name}` without any published versions");
388 return Ok(dependency);
389 };
390
391 if current.matches(&latest) {
392 trace!("skipping dependency `{name}` without a breaking update available");
393 return Ok(dependency);
394 }
395
396 let Some((new_req_string, _)) = upgrade_requirement(¤t.to_string(), latest)? else {
397 trace!("skipping dependency `{name}` because the version requirement didn't change");
398 return Ok(dependency);
399 };
400
401 let upgrade_message = format!("{name} {current} -> {new_req_string}");
402 trace!(upgrade_message);
403
404 if upgrade_messages.insert(upgrade_message.clone()) {
405 gctx.shell()
406 .status_with_color("Upgrading", &upgrade_message, &style::GOOD)?;
407 }
408
409 upgrades.insert((name.to_string(), dependency.source_id()), latest.clone());
410
411 remaining_specs
413 .retain(|spec| !(spec.name() == name.as_str() && dependency.source_id().is_registry()));
414
415 let req = OptVersionReq::Req(VersionReq::parse(&latest.to_string())?);
416 let mut dep = dependency.clone();
417 dep.set_version_req(req);
418 Ok(dep)
419}
420
421pub fn write_manifest_upgrades(
433 ws: &Workspace<'_>,
434 upgrades: &UpgradeMap,
435 dry_run: bool,
436) -> CargoResult<bool> {
437 if upgrades.is_empty() {
438 return Ok(false);
439 }
440
441 let mut any_file_has_changed = false;
442
443 let items = std::iter::once((ws.root_manifest(), ws.unstable_features()))
444 .chain(ws.members().map(|member| {
445 (
446 member.manifest_path(),
447 member.manifest().unstable_features(),
448 )
449 }))
450 .collect::<Vec<_>>();
451
452 for (manifest_path, unstable_features) in items {
453 trace!("updating TOML manifest at `{manifest_path:?}` with upgraded dependencies");
454
455 let crate_root = manifest_path
456 .parent()
457 .expect("manifest path is absolute")
458 .to_owned();
459
460 let mut local_manifest = LocalManifest::try_new(&manifest_path)?;
461 let mut manifest_has_changed = false;
462
463 for dep_table in local_manifest.get_dependency_tables_mut() {
464 for (mut dep_key, dep_item) in dep_table.iter_mut() {
465 let dep_key_str = dep_key.get();
466 let dependency = crate::util::toml_mut::dependency::Dependency::from_toml(
467 ws.gctx(),
468 ws.root(),
469 &manifest_path,
470 unstable_features,
471 dep_key_str,
472 dep_item,
473 )?;
474 let name = &dependency.name;
475
476 if let Some(renamed_to) = dependency.rename {
477 trace!("skipping dependency renamed from `{name}` to `{renamed_to}`");
478 continue;
479 }
480
481 let Some(current) = dependency.version() else {
482 trace!("skipping dependency without a version: {name}");
483 continue;
484 };
485
486 let (MaybeWorkspace::Other(source_id), Some(Source::Registry(source))) =
487 (dependency.source_id(ws.gctx())?, dependency.source())
488 else {
489 trace!("skipping non-registry dependency: {name}");
490 continue;
491 };
492
493 let Some(latest) = upgrades.get(&(name.to_owned(), source_id)) else {
494 trace!("skipping dependency without an upgrade: {name}");
495 continue;
496 };
497
498 let Some((new_req_string, new_req)) = upgrade_requirement(current, latest)? else {
499 trace!(
500 "skipping dependency `{name}` because the version requirement didn't change"
501 );
502 continue;
503 };
504
505 let [comparator] = &new_req.comparators[..] else {
506 trace!(
507 "skipping dependency `{}` with multiple version comparators: {:?}",
508 name, new_req.comparators
509 );
510 continue;
511 };
512
513 if comparator.op != Op::Caret {
514 trace!("skipping non-caret dependency `{}`: {}", name, comparator);
515 continue;
516 }
517
518 let mut dep = dependency.clone();
519 let mut source = source.clone();
520 source.version = new_req_string;
521 dep.source = Some(Source::Registry(source));
522
523 trace!("upgrading dependency {name}");
524 dep.update_toml(
525 ws.gctx(),
526 ws.root(),
527 &crate_root,
528 unstable_features,
529 &mut dep_key,
530 dep_item,
531 )?;
532 manifest_has_changed = true;
533 any_file_has_changed = true;
534 }
535 }
536
537 if manifest_has_changed && !dry_run {
538 debug!("writing upgraded manifest to {}", manifest_path.display());
539 local_manifest.write()?;
540 }
541 }
542
543 Ok(any_file_has_changed)
544}
545
546fn print_lockfile_generation(
547 ws: &Workspace<'_>,
548 resolve: &Resolve,
549 registry: &mut PackageRegistry<'_>,
550) -> CargoResult<()> {
551 let mut changes = PackageChange::new(ws, resolve);
552 let num_pkgs: usize = changes
553 .values()
554 .filter(|change| change.kind.is_new() && !change.is_member.unwrap_or(false))
555 .count();
556 if num_pkgs == 0 {
557 return Ok(());
559 }
560 annotate_required_rust_version(ws, resolve, &mut changes);
561
562 status_locking(ws, num_pkgs)?;
563 for change in changes.values() {
564 if change.is_member.unwrap_or(false) {
565 continue;
566 };
567 match change.kind {
568 PackageChangeKind::Added => {
569 let possibilities = if let Some(query) = change.alternatives_query() {
570 crate::util::block_on(registry.query_vec(&query, QueryKind::Exact))?
571 } else {
572 vec![]
573 };
574
575 let required_rust_version = report_required_rust_version(resolve, change);
576 let latest = report_latest(&possibilities, change);
577 let note = required_rust_version.or(latest);
578
579 if let Some(note) = note {
580 ws.gctx().shell().status_with_color(
581 change.kind.status(),
582 format!("{change}{note}"),
583 &change.kind.style(),
584 )?;
585 }
586 }
587 PackageChangeKind::Upgraded
588 | PackageChangeKind::Downgraded
589 | PackageChangeKind::Removed
590 | PackageChangeKind::Unchanged => {
591 unreachable!("without a previous resolve, everything should be added")
592 }
593 }
594 }
595
596 Ok(())
597}
598
599fn print_lockfile_sync(
600 ws: &Workspace<'_>,
601 previous_resolve: &Resolve,
602 resolve: &Resolve,
603 registry: &mut PackageRegistry<'_>,
604) -> CargoResult<()> {
605 let mut changes = PackageChange::diff(ws, previous_resolve, resolve);
606 let num_pkgs: usize = changes
607 .values()
608 .filter(|change| change.kind.is_new() && !change.is_member.unwrap_or(false))
609 .count();
610 if num_pkgs == 0 {
611 return Ok(());
613 }
614 annotate_required_rust_version(ws, resolve, &mut changes);
615
616 status_locking(ws, num_pkgs)?;
617 for change in changes.values() {
618 if change.is_member.unwrap_or(false) {
619 continue;
620 };
621 match change.kind {
622 PackageChangeKind::Added
623 | PackageChangeKind::Upgraded
624 | PackageChangeKind::Downgraded => {
625 let possibilities = if let Some(query) = change.alternatives_query() {
626 crate::util::block_on(registry.query_vec(&query, QueryKind::Exact))?
627 } else {
628 vec![]
629 };
630
631 let required_rust_version = report_required_rust_version(resolve, change);
632 let latest = report_latest(&possibilities, change);
633 let note = required_rust_version.or(latest).unwrap_or_default();
634
635 ws.gctx().shell().status_with_color(
636 change.kind.status(),
637 format!("{change}{note}"),
638 &change.kind.style(),
639 )?;
640 }
641 PackageChangeKind::Removed | PackageChangeKind::Unchanged => {}
642 }
643 }
644
645 Ok(())
646}
647
648fn print_lockfile_updates(
649 ws: &Workspace<'_>,
650 previous_resolve: &Resolve,
651 resolve: &Resolve,
652 precise: bool,
653 registry: &mut PackageRegistry<'_>,
654) -> CargoResult<()> {
655 let mut changes = PackageChange::diff(ws, previous_resolve, resolve);
656 let num_pkgs: usize = changes
657 .values()
658 .filter(|change| change.kind.is_new())
659 .count();
660 annotate_required_rust_version(ws, resolve, &mut changes);
661
662 if !precise {
663 status_locking(ws, num_pkgs)?;
664 }
665 let mut unchanged_behind = 0;
666 for change in changes.values() {
667 let possibilities = if let Some(query) = change.alternatives_query() {
668 crate::util::block_on(registry.query_vec(&query, QueryKind::Exact))?
669 } else {
670 vec![]
671 };
672
673 match change.kind {
674 PackageChangeKind::Added
675 | PackageChangeKind::Upgraded
676 | PackageChangeKind::Downgraded => {
677 let required_rust_version = report_required_rust_version(resolve, change);
678 let latest = report_latest(&possibilities, change);
679 let note = required_rust_version.or(latest).unwrap_or_default();
680
681 ws.gctx().shell().status_with_color(
682 change.kind.status(),
683 format!("{change}{note}"),
684 &change.kind.style(),
685 )?;
686 }
687 PackageChangeKind::Removed => {
688 ws.gctx().shell().status_with_color(
689 change.kind.status(),
690 format!("{change}"),
691 &change.kind.style(),
692 )?;
693 }
694 PackageChangeKind::Unchanged => {
695 let required_rust_version = report_required_rust_version(resolve, change);
696 let latest = report_latest(&possibilities, change);
697 let note = required_rust_version.as_deref().or(latest.as_deref());
698
699 if let Some(note) = note {
700 if latest.is_some() {
701 unchanged_behind += 1;
702 }
703 if ws.gctx().shell().verbosity() == Verbosity::Verbose {
704 ws.gctx().shell().status_with_color(
705 change.kind.status(),
706 format!("{change}{note}"),
707 &change.kind.style(),
708 )?;
709 }
710 }
711 }
712 }
713 }
714
715 if ws.gctx().shell().verbosity() == Verbosity::Verbose {
716 ws.gctx()
717 .shell()
718 .note("to see how you depend on a package, run `cargo tree --invert <dep>@<ver>`")?;
719 } else {
720 if 0 < unchanged_behind {
721 ws.gctx().shell().note(format!(
722 "pass `--verbose` to see {unchanged_behind} unchanged dependencies behind latest"
723 ))?;
724 }
725 }
726
727 Ok(())
728}
729
730fn status_locking(ws: &Workspace<'_>, num_pkgs: usize) -> CargoResult<()> {
731 use std::fmt::Write as _;
732
733 let plural = if num_pkgs == 1 { "" } else { "s" };
734
735 let mut cfg = String::new();
736 if !ws.gctx().cli_unstable().direct_minimal_versions {
738 write!(&mut cfg, " to")?;
739 if ws.gctx().cli_unstable().minimal_versions {
740 write!(&mut cfg, " earliest")?;
741 } else {
742 write!(&mut cfg, " latest")?;
743 }
744
745 if let Some(rust_version) = required_rust_version(ws) {
746 write!(&mut cfg, " Rust {rust_version}")?;
747 }
748 write!(&mut cfg, " compatible version{plural}")?;
749 if let Some(publish_time) = ws.resolve_publish_time() {
750 write!(&mut cfg, " as of {publish_time}")?;
751 }
752 }
753
754 ws.gctx()
755 .shell()
756 .status("Locking", format!("{num_pkgs} package{plural}{cfg}"))?;
757 Ok(())
758}
759
760fn required_rust_version(ws: &Workspace<'_>) -> Option<PartialVersion> {
761 if !ws.resolve_honors_rust_version() {
762 return None;
763 }
764
765 if let Some(ver) = ws.lowest_rust_version() {
766 Some(ver.to_partial())
767 } else {
768 let rustc = ws.gctx().load_global_rustc(Some(ws)).ok()?;
769 let rustc_version = rustc.version.clone().into();
770 Some(rustc_version)
771 }
772}
773
774fn report_required_rust_version(resolve: &Resolve, change: &PackageChange) -> Option<String> {
775 if change.package_id.source_id().is_path() {
776 return None;
777 }
778 let summary = resolve.summary(change.package_id);
779 let package_rust_version = summary.rust_version()?;
780 let required_rust_version = change.required_rust_version.as_ref()?;
781 if package_rust_version.is_compatible_with(required_rust_version) {
782 return None;
783 }
784
785 let error = style::ERROR;
786 Some(format!(
787 " {error}(requires Rust {package_rust_version}){error:#}"
788 ))
789}
790
791fn report_latest(possibilities: &[IndexSummary], change: &PackageChange) -> Option<String> {
792 let package_id = change.package_id;
793 if !package_id.source_id().is_registry() {
794 return None;
795 }
796
797 let version_req = package_id.version().to_caret_req();
798 let required_rust_version = change.required_rust_version.as_ref();
799
800 let compat_ver_compat_msrv_summary = possibilities
801 .iter()
802 .map(|s| s.as_summary())
803 .filter(|s| {
804 if let (Some(summary_rust_version), Some(required_rust_version)) =
805 (s.rust_version(), required_rust_version)
806 {
807 summary_rust_version.is_compatible_with(required_rust_version)
808 } else {
809 true
810 }
811 })
812 .filter(|s| package_id.version() != s.version() && version_req.matches(s.version()))
813 .max_by_key(|s| s.version());
814 if let Some(summary) = compat_ver_compat_msrv_summary {
815 let warn = style::WARN;
816 let version = summary.version();
817 let report = format!(" {warn}(available: v{version}){warn:#}");
818 return Some(report);
819 }
820
821 if !change.is_transitive.unwrap_or(true) {
822 let incompat_ver_compat_msrv_summary = possibilities
823 .iter()
824 .map(|s| s.as_summary())
825 .filter(|s| {
826 if let (Some(summary_rust_version), Some(required_rust_version)) =
827 (s.rust_version(), required_rust_version)
828 {
829 summary_rust_version.is_compatible_with(required_rust_version)
830 } else {
831 true
832 }
833 })
834 .filter(|s| is_latest(s.version(), package_id.version()))
835 .max_by_key(|s| s.version());
836 if let Some(summary) = incompat_ver_compat_msrv_summary {
837 let warn = style::WARN;
838 let version = summary.version();
839 let report = format!(" {warn}(available: v{version}){warn:#}");
840 return Some(report);
841 }
842 }
843
844 let compat_ver_summary = possibilities
845 .iter()
846 .map(|s| s.as_summary())
847 .filter(|s| package_id.version() != s.version() && version_req.matches(s.version()))
848 .max_by_key(|s| s.version());
849 if let Some(summary) = compat_ver_summary {
850 let msrv_note = summary
851 .rust_version()
852 .map(|rv| format!(", requires Rust {rv}"))
853 .unwrap_or_default();
854 let warn = style::NOP;
855 let version = summary.version();
856 let report = format!(" {warn}(available: v{version}{msrv_note}){warn:#}");
857 return Some(report);
858 }
859
860 if !change.is_transitive.unwrap_or(true) {
861 let incompat_ver_summary = possibilities
862 .iter()
863 .map(|s| s.as_summary())
864 .filter(|s| is_latest(s.version(), package_id.version()))
865 .max_by_key(|s| s.version());
866 if let Some(summary) = incompat_ver_summary {
867 let msrv_note = summary
868 .rust_version()
869 .map(|rv| format!(", requires Rust {rv}"))
870 .unwrap_or_default();
871 let warn = style::NOP;
872 let version = summary.version();
873 let report = format!(" {warn}(available: v{version}{msrv_note}){warn:#}");
874 return Some(report);
875 }
876 }
877
878 None
879}
880
881fn is_latest(candidate: &semver::Version, current: &semver::Version) -> bool {
882 current < candidate
883 && (candidate.pre.is_empty()
885 || (candidate.major == current.major
886 && candidate.minor == current.minor
887 && candidate.patch == current.patch))
888}
889
890fn fill_with_deps<'a>(
891 resolve: &'a Resolve,
892 dep: PackageId,
893 set: &mut HashSet<PackageId>,
894 visited: &mut HashSet<PackageId>,
895) {
896 if !visited.insert(dep) {
897 return;
898 }
899 set.insert(dep);
900 for (dep, _) in resolve.deps_not_replaced(dep) {
901 fill_with_deps(resolve, dep, set, visited);
902 }
903}
904
905#[derive(Clone, Debug)]
906struct PackageChange {
907 package_id: PackageId,
908 previous_id: Option<PackageId>,
909 kind: PackageChangeKind,
910 is_member: Option<bool>,
911 is_transitive: Option<bool>,
912 required_rust_version: Option<PartialVersion>,
913}
914
915impl PackageChange {
916 pub fn new(ws: &Workspace<'_>, resolve: &Resolve) -> IndexMap<PackageId, Self> {
917 let diff = PackageDiff::new(resolve);
918 Self::with_diff(diff, ws, resolve)
919 }
920
921 pub fn diff(
922 ws: &Workspace<'_>,
923 previous_resolve: &Resolve,
924 resolve: &Resolve,
925 ) -> IndexMap<PackageId, Self> {
926 let diff = PackageDiff::diff(previous_resolve, resolve);
927 Self::with_diff(diff, ws, resolve)
928 }
929
930 fn with_diff(
931 diff: impl Iterator<Item = PackageDiff>,
932 ws: &Workspace<'_>,
933 resolve: &Resolve,
934 ) -> IndexMap<PackageId, Self> {
935 let member_ids: HashSet<_> = ws.members().map(|p| p.package_id()).collect();
936
937 let mut changes = IndexMap::new();
938 for diff in diff {
939 if let Some((previous_id, package_id)) = diff.change() {
940 let kind = if previous_id.version().cmp_precedence(package_id.version())
945 == Ordering::Greater
946 {
947 PackageChangeKind::Downgraded
948 } else {
949 PackageChangeKind::Upgraded
950 };
951 let is_member = Some(member_ids.contains(&package_id));
952 let is_transitive = Some(true);
953 let change = Self {
954 package_id,
955 previous_id: Some(previous_id),
956 kind,
957 is_member,
958 is_transitive,
959 required_rust_version: None,
960 };
961 changes.insert(change.package_id, change);
962 } else {
963 for package_id in diff.removed {
964 let kind = PackageChangeKind::Removed;
965 let is_member = None;
966 let is_transitive = None;
967 let change = Self {
968 package_id,
969 previous_id: None,
970 kind,
971 is_member,
972 is_transitive,
973 required_rust_version: None,
974 };
975 changes.insert(change.package_id, change);
976 }
977 for package_id in diff.added {
978 let kind = PackageChangeKind::Added;
979 let is_member = Some(member_ids.contains(&package_id));
980 let is_transitive = Some(true);
981 let change = Self {
982 package_id,
983 previous_id: None,
984 kind,
985 is_member,
986 is_transitive,
987 required_rust_version: None,
988 };
989 changes.insert(change.package_id, change);
990 }
991 }
992 for package_id in diff.unchanged {
993 let kind = PackageChangeKind::Unchanged;
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
1008 for member_id in &member_ids {
1009 let Some(change) = changes.get_mut(member_id) else {
1010 continue;
1011 };
1012 change.is_transitive = Some(false);
1013 for (direct_dep_id, _) in resolve.deps(*member_id) {
1014 let Some(change) = changes.get_mut(&direct_dep_id) else {
1015 continue;
1016 };
1017 change.is_transitive = Some(false);
1018 }
1019 }
1020
1021 changes
1022 }
1023
1024 fn alternatives_query(&self) -> Option<crate::core::dependency::Dependency> {
1026 if !self.package_id.source_id().is_registry() {
1027 return None;
1028 }
1029
1030 let query = crate::core::dependency::Dependency::parse(
1031 self.package_id.name(),
1032 None,
1033 self.package_id.source_id(),
1034 )
1035 .expect("already a valid dependency");
1036 Some(query)
1037 }
1038}
1039
1040impl std::fmt::Display for PackageChange {
1041 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1042 let package_id = self.package_id;
1043 if let Some(previous_id) = self.previous_id {
1044 if package_id.source_id().is_git() {
1045 write!(
1046 f,
1047 "{previous_id} -> #{}",
1048 &package_id.source_id().precise_git_fragment().unwrap()[..8],
1049 )
1050 } else {
1051 write!(f, "{previous_id} -> v{}", package_id.version())
1052 }
1053 } else {
1054 write!(f, "{package_id}")
1055 }
1056 }
1057}
1058
1059#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1060enum PackageChangeKind {
1061 Added,
1062 Removed,
1063 Upgraded,
1064 Downgraded,
1065 Unchanged,
1066}
1067
1068impl PackageChangeKind {
1069 pub fn is_new(&self) -> bool {
1070 match self {
1071 Self::Added | Self::Upgraded | Self::Downgraded => true,
1072 Self::Removed | Self::Unchanged => false,
1073 }
1074 }
1075
1076 pub fn status(&self) -> &'static str {
1077 match self {
1078 Self::Added => "Adding",
1079 Self::Removed => "Removing",
1080 Self::Upgraded => "Updating",
1081 Self::Downgraded => "Downgrading",
1082 Self::Unchanged => "Unchanged",
1083 }
1084 }
1085
1086 pub fn style(&self) -> anstyle::Style {
1087 match self {
1088 Self::Added => style::UPDATE_ADDED,
1089 Self::Removed => style::UPDATE_REMOVED,
1090 Self::Upgraded => style::UPDATE_UPGRADED,
1091 Self::Downgraded => style::UPDATE_DOWNGRADED,
1092 Self::Unchanged => style::UPDATE_UNCHANGED,
1093 }
1094 }
1095}
1096
1097#[derive(Default, Clone, Debug)]
1099pub struct PackageDiff {
1100 removed: Vec<PackageId>,
1101 added: Vec<PackageId>,
1102 unchanged: Vec<PackageId>,
1103}
1104
1105impl PackageDiff {
1106 pub fn new(resolve: &Resolve) -> impl Iterator<Item = Self> {
1107 let mut changes = BTreeMap::new();
1108 let empty = Self::default();
1109 for dep in resolve.iter() {
1110 changes
1111 .entry(Self::key(dep))
1112 .or_insert_with(|| empty.clone())
1113 .added
1114 .push(dep);
1115 }
1116
1117 changes.into_iter().map(|(_, v)| v)
1118 }
1119
1120 pub fn diff(previous_resolve: &Resolve, resolve: &Resolve) -> impl Iterator<Item = Self> {
1121 fn vec_subset(a: &[PackageId], b: &[PackageId]) -> Vec<PackageId> {
1122 a.iter().filter(|a| !contains_id(b, a)).cloned().collect()
1123 }
1124
1125 fn vec_intersection(a: &[PackageId], b: &[PackageId]) -> Vec<PackageId> {
1126 a.iter().filter(|a| contains_id(b, a)).cloned().collect()
1127 }
1128
1129 fn contains_id(haystack: &[PackageId], needle: &PackageId) -> bool {
1135 let Ok(i) = haystack.binary_search(needle) else {
1136 return false;
1137 };
1138
1139 if needle.source_id().is_registry() {
1148 return true;
1149 }
1150 haystack[i..]
1151 .iter()
1152 .take_while(|b| &needle == b)
1153 .any(|b| needle.source_id().has_same_precise_as(b.source_id()))
1154 }
1155
1156 let mut changes = BTreeMap::new();
1158 let empty = Self::default();
1159 for dep in previous_resolve.iter() {
1160 changes
1161 .entry(Self::key(dep))
1162 .or_insert_with(|| empty.clone())
1163 .removed
1164 .push(dep);
1165 }
1166 for dep in resolve.iter() {
1167 changes
1168 .entry(Self::key(dep))
1169 .or_insert_with(|| empty.clone())
1170 .added
1171 .push(dep);
1172 }
1173
1174 for v in changes.values_mut() {
1175 let Self {
1176 removed: ref mut old,
1177 added: ref mut new,
1178 unchanged: ref mut other,
1179 } = *v;
1180 old.sort();
1181 new.sort();
1182 let removed = vec_subset(old, new);
1183 let added = vec_subset(new, old);
1184 let unchanged = vec_intersection(new, old);
1185 *old = removed;
1186 *new = added;
1187 *other = unchanged;
1188 }
1189 debug!("{:#?}", changes);
1190
1191 changes.into_iter().map(|(_, v)| v)
1192 }
1193
1194 fn key(dep: PackageId) -> (&'static str, SourceId) {
1195 (dep.name().as_str(), dep.source_id())
1196 }
1197
1198 pub fn change(&self) -> Option<(PackageId, PackageId)> {
1204 if self.removed.len() == 1 && self.added.len() == 1 {
1205 Some((self.removed[0], self.added[0]))
1206 } else {
1207 None
1208 }
1209 }
1210}
1211
1212fn annotate_required_rust_version(
1213 ws: &Workspace<'_>,
1214 resolve: &Resolve,
1215 changes: &mut IndexMap<PackageId, PackageChange>,
1216) {
1217 let rustc = ws.gctx().load_global_rustc(Some(ws)).ok();
1218 let rustc_version: Option<PartialVersion> =
1219 rustc.as_ref().map(|rustc| rustc.version.clone().into());
1220
1221 if ws.resolve_honors_rust_version() {
1222 let mut queue: std::collections::VecDeque<_> = ws
1223 .members()
1224 .map(|p| {
1225 (
1226 p.rust_version()
1227 .map(|r| r.to_partial())
1228 .or_else(|| rustc_version.clone()),
1229 p.package_id(),
1230 )
1231 })
1232 .collect();
1233 while let Some((required_rust_version, current_id)) = queue.pop_front() {
1234 let Some(required_rust_version) = required_rust_version else {
1235 continue;
1236 };
1237 if let Some(change) = changes.get_mut(¤t_id) {
1238 if let Some(existing) = change.required_rust_version.as_ref() {
1239 if *existing <= required_rust_version {
1240 continue;
1242 }
1243 }
1244 change.required_rust_version = Some(required_rust_version.clone());
1245 }
1246 queue.extend(
1247 resolve
1248 .deps(current_id)
1249 .map(|(dep, _)| (Some(required_rust_version.clone()), dep)),
1250 );
1251 }
1252 } else {
1253 for change in changes.values_mut() {
1254 change.required_rust_version = rustc_version.clone();
1255 }
1256 }
1257}