Skip to main content

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}