uv_resolver/upgrade.rs
1use rustc_hash::FxHashSet;
2
3use uv_configuration::Upgrade;
4use uv_normalize::PackageName;
5
6use crate::Lock;
7
8/// The resolved set of packages that should be upgraded.
9///
10/// This combines explicitly named packages (from `--upgrade-package`) with packages belonging to
11/// upgraded dependency groups (from `--upgrade-group`), providing a single `contains` check that
12/// accounts for both.
13#[derive(Debug, Default, Clone)]
14pub struct UpgradePackages {
15 /// Whether all packages should be upgraded.
16 all: bool,
17 /// The specific packages to upgrade.
18 packages: FxHashSet<PackageName>,
19}
20
21impl UpgradePackages {
22 /// Create an [`UpgradePackages`] for non-project commands (e.g., `pip compile`, `pip install`)
23 /// where dependency groups are not supported.
24 pub fn for_non_project(upgrade: &Upgrade) -> Self {
25 match (upgrade.is_all(), upgrade.packages()) {
26 (true, _) => Self {
27 all: true,
28 packages: FxHashSet::default(),
29 },
30 (false, Some(packages)) => Self {
31 all: false,
32 packages: packages.clone(),
33 },
34 (false, None) => Self::default(),
35 }
36 }
37
38 /// Create an [`UpgradePackages`] for workspace/project commands, combining explicitly named
39 /// packages with packages resolved from dependency groups in the lockfile.
40 pub fn for_workspace(lock: &Lock, upgrade: &Upgrade) -> Self {
41 match (upgrade.is_all(), upgrade.packages()) {
42 (true, _) => Self {
43 all: true,
44 packages: FxHashSet::default(),
45 },
46 (false, Some(packages)) => {
47 let mut combined = packages.clone();
48
49 if let Some(groups) = upgrade.groups() {
50 // Check package-level dependency groups (the standard case for projects with
51 // a `[project]` table).
52 for package in lock.packages() {
53 for (group_name, dependencies) in package.resolved_dependency_groups() {
54 if groups.contains(group_name) {
55 for dependency in dependencies {
56 combined.insert(dependency.package_name().clone());
57 }
58 }
59 }
60 }
61
62 // Check manifest-level dependency groups, which cover projects without a
63 // `[project]` table (e.g., virtual workspace roots or PEP 723 scripts).
64 for (group_name, requirements) in lock.dependency_groups() {
65 if groups.contains(group_name) {
66 for requirement in requirements {
67 combined.insert(requirement.name.clone());
68 }
69 }
70 }
71 }
72
73 Self {
74 all: false,
75 packages: combined,
76 }
77 }
78 (false, None) => Self::default(),
79 }
80 }
81
82 /// Returns `true` if the given package should be upgraded.
83 pub fn contains(&self, package_name: &PackageName) -> bool {
84 self.all || self.packages.contains(package_name)
85 }
86}