uv_resolver/pubgrub/
package.rs

1use std::ops::Deref;
2use std::sync::Arc;
3
4use uv_normalize::{ExtraName, GroupName, PackageName};
5use uv_pep508::MarkerTree;
6use uv_pypi_types::ConflictItemRef;
7
8use crate::python_requirement::PythonRequirement;
9
10/// [`Arc`] wrapper around [`PubGrubPackageInner`] to make cloning (inside PubGrub) cheap.
11#[derive(Debug, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
12pub struct PubGrubPackage(Arc<PubGrubPackageInner>);
13
14impl Deref for PubGrubPackage {
15    type Target = PubGrubPackageInner;
16
17    fn deref(&self) -> &Self::Target {
18        &self.0
19    }
20}
21
22impl std::fmt::Display for PubGrubPackage {
23    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24        std::fmt::Display::fmt(&self.0, f)
25    }
26}
27
28impl From<PubGrubPackageInner> for PubGrubPackage {
29    fn from(package: PubGrubPackageInner) -> Self {
30        Self(Arc::new(package))
31    }
32}
33
34/// A PubGrub-compatible wrapper around a "Python package", with two notable characteristics:
35///
36/// 1. Includes a [`PubGrubPackage::Root`] variant, to satisfy PubGrub's requirement that a
37///    resolution starts from a single root.
38/// 2. Uses the same strategy as pip and posy to handle extras: for each extra, we create a virtual
39///    package (e.g., `black[colorama]`), and mark it as a dependency of the real package (e.g.,
40///    `black`). We then discard the virtual packages at the end of the resolution process.
41#[derive(Debug, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
42pub enum PubGrubPackageInner {
43    /// The root package, which is used to start the resolution process.
44    Root(Option<PackageName>),
45    /// A Python version.
46    Python(PubGrubPython),
47    /// A system package, which is used to represent a non-Python package.
48    System(PackageName),
49    /// A Python package.
50    ///
51    /// Note that it is guaranteed that `extra` and `dev` are never both
52    /// `Some`. That is, if one is `Some` then the other must be `None`.
53    Package {
54        name: PackageName,
55        extra: Option<ExtraName>,
56        group: Option<GroupName>,
57        marker: MarkerTree,
58    },
59    /// A proxy package to represent a dependency with an extra (e.g., `black[colorama]`).
60    ///
61    /// For a given package `black`, and an extra `colorama`, we create a virtual package
62    /// with exactly two dependencies: `PubGrubPackage::Package("black", None)` and
63    /// `PubGrubPackage::Package("black", Some("colorama")`. Both dependencies are pinned to the
64    /// same version, and the virtual package is discarded at the end of the resolution process.
65    ///
66    /// The benefit of the proxy package (versus `PubGrubPackage::Package("black", Some("colorama")`
67    /// on its own) is that it enables us to avoid attempting to retrieve metadata for irrelevant
68    /// versions the extra variants by making it clear to PubGrub that the extra variant must match
69    /// the exact same version of the base variant. Without the proxy package, then when provided
70    /// requirements like `black==23.0.1` and `black[colorama]`, PubGrub may attempt to retrieve
71    /// metadata for `black[colorama]` versions other than `23.0.1`.
72    Extra {
73        name: PackageName,
74        extra: ExtraName,
75        marker: MarkerTree,
76    },
77    /// A proxy package to represent an enabled dependency group.
78    ///
79    /// This is similar in spirit to [PEP 735](https://peps.python.org/pep-0735/) and similar in
80    /// implementation to the `Extra` variant. The main difference is that we treat groups as
81    /// enabled globally, rather than on a per-requirement basis.
82    Group {
83        name: PackageName,
84        group: GroupName,
85        marker: MarkerTree,
86    },
87    /// A proxy package for a base package with a marker (e.g., `black; python_version >= "3.6"`).
88    ///
89    /// If a requirement has an extra _and_ a marker, it will be represented via the `Extra` variant
90    /// rather than the `Marker` variant.
91    Marker {
92        name: PackageName,
93        /// The marker associated with this proxy package.
94        marker: MarkerTree,
95    },
96}
97
98impl PubGrubPackage {
99    /// Create a [`PubGrubPackage`] from a package name and extra.
100    pub(crate) fn from_package(
101        name: PackageName,
102        extra: Option<ExtraName>,
103        group: Option<GroupName>,
104        marker: MarkerTree,
105    ) -> Self {
106        // Remove all extra expressions from the marker, since we track extras
107        // separately. This also avoids an issue where packages added via
108        // extras end up having two distinct marker expressions, which in turn
109        // makes them two distinct packages. This results in PubGrub being
110        // unable to unify version constraints across such packages.
111        let marker = marker.simplify_extras_with(|_| true);
112        if let Some(extra) = extra {
113            Self(Arc::new(PubGrubPackageInner::Extra {
114                name,
115                extra,
116                marker,
117            }))
118        } else if let Some(group) = group {
119            Self(Arc::new(PubGrubPackageInner::Group {
120                name,
121                group,
122                marker,
123            }))
124        } else if !marker.is_true() {
125            Self(Arc::new(PubGrubPackageInner::Marker { name, marker }))
126        } else {
127            Self(Arc::new(PubGrubPackageInner::Package {
128                name,
129                extra,
130                group: None,
131                marker,
132            }))
133        }
134    }
135
136    /// If this package is a proxy package, return the base package it depends on.
137    ///
138    /// While dependency groups may be attached to a package, we don't consider them here as
139    /// there is no (mandatory) dependency from a dependency group to the package.
140    pub(crate) fn base_package(&self) -> Option<Self> {
141        match &**self {
142            PubGrubPackageInner::Root(_)
143            | PubGrubPackageInner::Python(_)
144            | PubGrubPackageInner::System(_)
145            | PubGrubPackageInner::Package { .. } => None,
146            PubGrubPackageInner::Group { .. } => {
147                // The dependency groups of a package do not by themselves require the package
148                // itself.
149                None
150            }
151            PubGrubPackageInner::Extra { name, .. } | PubGrubPackageInner::Marker { name, .. } => {
152                Some(Self::from_package(
153                    name.clone(),
154                    None,
155                    None,
156                    MarkerTree::TRUE,
157                ))
158            }
159        }
160    }
161
162    /// Returns the name of this PubGrub package, if it has one.
163    pub(crate) fn name(&self) -> Option<&PackageName> {
164        match &**self {
165            // A root can never be a dependency of another package, and a `Python` pubgrub
166            // package is never returned by `get_dependencies`. So these cases never occur.
167            PubGrubPackageInner::Root(None) | PubGrubPackageInner::Python(_) => None,
168            PubGrubPackageInner::Root(Some(name))
169            | PubGrubPackageInner::System(name)
170            | PubGrubPackageInner::Package { name, .. }
171            | PubGrubPackageInner::Extra { name, .. }
172            | PubGrubPackageInner::Group { name, .. }
173            | PubGrubPackageInner::Marker { name, .. } => Some(name),
174        }
175    }
176
177    /// Returns the name of this PubGrub package, if it is not the root package, a Python version
178    /// constraint, or a system package.
179    pub(crate) fn name_no_root(&self) -> Option<&PackageName> {
180        match &**self {
181            PubGrubPackageInner::Root(_)
182            | PubGrubPackageInner::Python(_)
183            | PubGrubPackageInner::System(_) => None,
184            PubGrubPackageInner::Package { name, .. }
185            | PubGrubPackageInner::Extra { name, .. }
186            | PubGrubPackageInner::Group { name, .. }
187            | PubGrubPackageInner::Marker { name, .. } => Some(name),
188        }
189    }
190
191    /// Returns the marker expression associated with this PubGrub package, if
192    /// it has one.
193    pub(crate) fn marker(&self) -> MarkerTree {
194        match &**self {
195            // A root can never be a dependency of another package, and a `Python` pubgrub
196            // package is never returned by `get_dependencies`. So these cases never occur.
197            PubGrubPackageInner::Root(_)
198            | PubGrubPackageInner::Python(_)
199            | PubGrubPackageInner::System(_) => MarkerTree::TRUE,
200            PubGrubPackageInner::Package { marker, .. }
201            | PubGrubPackageInner::Extra { marker, .. }
202            | PubGrubPackageInner::Group { marker, .. } => *marker,
203            PubGrubPackageInner::Marker { marker, .. } => *marker,
204        }
205    }
206
207    /// Returns the extra name associated with this PubGrub package, if it has
208    /// one.
209    ///
210    /// Note that if this returns `Some`, then `dev` must return `None`.
211    pub(crate) fn extra(&self) -> Option<&ExtraName> {
212        match &**self {
213            // A root can never be a dependency of another package, and a `Python` pubgrub
214            // package is never returned by `get_dependencies`. So these cases never occur.
215            PubGrubPackageInner::Root(_)
216            | PubGrubPackageInner::Python(_)
217            | PubGrubPackageInner::System(_)
218            | PubGrubPackageInner::Package { extra: None, .. }
219            | PubGrubPackageInner::Group { .. }
220            | PubGrubPackageInner::Marker { .. } => None,
221            PubGrubPackageInner::Package {
222                extra: Some(extra), ..
223            }
224            | PubGrubPackageInner::Extra { extra, .. } => Some(extra),
225        }
226    }
227
228    /// Returns the dependency group name associated with this PubGrub
229    /// package, if it has one.
230    ///
231    /// Note that if this returns `Some`, then `extra` must return `None`.
232    pub(crate) fn group(&self) -> Option<&GroupName> {
233        match &**self {
234            // A root can never be a dependency of another package, and a `Python` pubgrub
235            // package is never returned by `get_dependencies`. So these cases never occur.
236            PubGrubPackageInner::Root(_)
237            | PubGrubPackageInner::Python(_)
238            | PubGrubPackageInner::System(_)
239            | PubGrubPackageInner::Package { group: None, .. }
240            | PubGrubPackageInner::Extra { .. }
241            | PubGrubPackageInner::Marker { .. } => None,
242            PubGrubPackageInner::Package {
243                group: Some(group), ..
244            }
245            | PubGrubPackageInner::Group { group, .. } => Some(group),
246        }
247    }
248
249    /// Extracts a possible conflicting item from this package.
250    ///
251    /// If this package can't possibly be classified as conflicting, then
252    /// this returns `None`.
253    pub(crate) fn conflicting_item(&self) -> Option<ConflictItemRef<'_>> {
254        let package = self.name_no_root()?;
255        match (self.extra(), self.group()) {
256            (None, None) => Some(ConflictItemRef::from(package)),
257            (Some(extra), None) => Some(ConflictItemRef::from((package, extra))),
258            (None, Some(group)) => Some(ConflictItemRef::from((package, group))),
259            (Some(extra), Some(group)) => {
260                unreachable!(
261                    "PubGrub package cannot have both an extra and a group, \
262                     but found extra=`{extra}` and group=`{group}` for \
263                     package `{package}`",
264                )
265            }
266        }
267    }
268
269    /// Returns `true` if this PubGrub package is the root package.
270    pub(crate) fn is_root(&self) -> bool {
271        matches!(&**self, PubGrubPackageInner::Root(_))
272    }
273
274    /// Returns `true` if this PubGrub package is a proxy package.
275    pub(crate) fn is_proxy(&self) -> bool {
276        matches!(
277            &**self,
278            PubGrubPackageInner::Extra { .. }
279                | PubGrubPackageInner::Group { .. }
280                | PubGrubPackageInner::Marker { .. }
281        )
282    }
283
284    /// This simplifies the markers on this package (if any exist) using the
285    /// given Python requirement as assumed context.
286    ///
287    /// See `RequiresPython::simplify_markers` for more details.
288    ///
289    /// NOTE: This routine is kind of weird, because this should only really be
290    /// applied in contexts where the `PubGrubPackage` is printed as output.
291    /// So in theory, this should be a transformation into a new type with a
292    /// "printable" `PubGrubPackage` coupled with a `Requires-Python`. But at
293    /// time of writing, this was a larger refactor, particularly in the error
294    /// reporting where this routine is used.
295    pub(crate) fn simplify_markers(&mut self, python_requirement: &PythonRequirement) {
296        match *Arc::make_mut(&mut self.0) {
297            PubGrubPackageInner::Root(_)
298            | PubGrubPackageInner::Python(_)
299            | PubGrubPackageInner::System(_) => {}
300            PubGrubPackageInner::Package { ref mut marker, .. }
301            | PubGrubPackageInner::Extra { ref mut marker, .. }
302            | PubGrubPackageInner::Group { ref mut marker, .. }
303            | PubGrubPackageInner::Marker { ref mut marker, .. } => {
304                *marker = python_requirement.simplify_markers(*marker);
305            }
306        }
307    }
308
309    /// This isn't actually used anywhere, but can be useful for printf-debugging.
310    #[allow(dead_code)]
311    pub(crate) fn kind(&self) -> &'static str {
312        match &**self {
313            PubGrubPackageInner::Root(_) => "root",
314            PubGrubPackageInner::Python(_) => "python",
315            PubGrubPackageInner::System(_) => "system",
316            PubGrubPackageInner::Package { .. } => "package",
317            PubGrubPackageInner::Extra { .. } => "extra",
318            PubGrubPackageInner::Group { .. } => "group",
319            PubGrubPackageInner::Marker { .. } => "marker",
320        }
321    }
322
323    /// Returns a new [`PubGrubPackage`] representing the base package with the given name.
324    pub(crate) fn base(name: &PackageName) -> Self {
325        Self::from_package(name.clone(), None, None, MarkerTree::TRUE)
326    }
327}
328
329#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Hash, Ord)]
330pub enum PubGrubPython {
331    /// The Python version installed in the current environment.
332    Installed,
333    /// The Python version for which dependencies are being resolved.
334    Target,
335}
336
337impl std::fmt::Display for PubGrubPackageInner {
338    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
339        match self {
340            Self::Root(name) => {
341                if let Some(name) = name {
342                    write!(f, "{}", name.as_ref())
343                } else {
344                    write!(f, "root")
345                }
346            }
347            Self::Python(_) => write!(f, "Python"),
348            Self::System(name) => write!(f, "system:{name}"),
349            Self::Package {
350                name,
351                extra: None,
352                marker,
353                group: None,
354            } => {
355                if let Some(marker) = marker.contents() {
356                    write!(f, "{name}{{{marker}}}")
357                } else {
358                    write!(f, "{name}")
359                }
360            }
361            Self::Package {
362                name,
363                extra: Some(extra),
364                marker,
365                group: None,
366            } => {
367                if let Some(marker) = marker.contents() {
368                    write!(f, "{name}[{extra}]{{{marker}}}")
369                } else {
370                    write!(f, "{name}[{extra}]")
371                }
372            }
373            Self::Package {
374                name,
375                extra: None,
376                marker,
377                group: Some(dev),
378            } => {
379                if let Some(marker) = marker.contents() {
380                    write!(f, "{name}:{dev}{{{marker}}}")
381                } else {
382                    write!(f, "{name}:{dev}")
383                }
384            }
385            Self::Marker { name, marker, .. } => {
386                if let Some(marker) = marker.contents() {
387                    write!(f, "{name}{{{marker}}}")
388                } else {
389                    write!(f, "{name}")
390                }
391            }
392            Self::Extra { name, extra, .. } => write!(f, "{name}[{extra}]"),
393            Self::Group {
394                name, group: dev, ..
395            } => write!(f, "{name}:{dev}"),
396            // It is guaranteed that `extra` and `dev` are never set at the same time.
397            Self::Package {
398                name: _,
399                extra: Some(_),
400                marker: _,
401                group: Some(_),
402            } => unreachable!(),
403        }
404    }
405}
406
407impl From<&Self> for PubGrubPackage {
408    fn from(package: &Self) -> Self {
409        package.clone()
410    }
411}