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}