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