-
Notifications
You must be signed in to change notification settings - Fork 735
/
package.rs
118 lines (112 loc) · 5.86 KB
/
package.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
use derivative::Derivative;
use pep508_rs::VerbatimUrl;
use uv_normalize::{ExtraName, PackageName};
use crate::resolver::Urls;
/// A PubGrub-compatible wrapper around a "Python package", with two notable characteristics:
///
/// 1. Includes a [`PubGrubPackage::Root`] variant, to satisfy `PubGrub`'s requirement that a
/// resolution starts from a single root.
/// 2. Uses the same strategy as pip and posy to handle extras: for each extra, we create a virtual
/// package (e.g., `black[colorama]`), and mark it as a dependency of the real package (e.g.,
/// `black`). We then discard the virtual packages at the end of the resolution process.
#[derive(Debug, Clone, Eq, Derivative)]
#[derivative(PartialEq, Hash)]
pub enum PubGrubPackage {
/// The root package, which is used to start the resolution process.
Root(Option<PackageName>),
/// A Python version.
Python(PubGrubPython),
/// A Python package.
Package(
PackageName,
Option<ExtraName>,
/// The URL of the package, if it was specified in the requirement.
///
/// There are a few challenges that come with URL-based packages, and how they map to
/// PubGrub.
///
/// If the user declares a direct URL dependency, and then a transitive dependency
/// appears for the same package, we need to ensure that the direct URL dependency can
/// "satisfy" that requirement. So if the user declares a URL dependency on Werkzeug, and a
/// registry dependency on Flask, we need to ensure that Flask's dependency on Werkzeug
/// is resolved by the URL dependency. This means: (1) we need to avoid adding a second
/// Werkzeug variant from PyPI; and (2) we need to error if the Werkzeug version requested
/// by Flask doesn't match that of the URL dependency.
///
/// Additionally, we need to ensure that we disallow multiple versions of the same package,
/// even if requested from different URLs.
///
/// To enforce this requirement, we require that all possible URL dependencies are
/// defined upfront, as `requirements.txt` or `constraints.txt` or similar. Otherwise,
/// solving these graphs becomes far more complicated -- and the "right" behavior isn't
/// even clear. For example, imagine that you define a direct dependency on Werkzeug, and
/// then one of your other direct dependencies declares a dependency on Werkzeug at some
/// URL. Which is correct? By requiring direct dependencies, the semantics are at least
/// clear.
///
/// With the list of known URLs available upfront, we then only need to do two things:
///
/// 1. When iterating over the dependencies for a single package, ensure that we respect
/// URL variants over registry variants, if the package declares a dependency on both
/// `Werkzeug==2.0.0` _and_ `Werkzeug @ https://...` , which is strange but possible.
/// This is enforced by [`crate::pubgrub::dependencies::PubGrubDependencies`].
/// 2. Reject any URL dependencies that aren't known ahead-of-time.
///
/// Eventually, we could relax this constraint, in favor of something more lenient, e.g., if
/// we're going to have a dependency that's provided as a URL, we _need_ to visit the URL
/// version before the registry version. So we could just error if we visit a URL variant
/// _after_ a registry variant.
Option<VerbatimUrl>,
),
/// A proxy package to represent a dependency with an extra (e.g., `black[colorama]`).
///
/// For a given package `black`, and an extra `colorama`, we create a virtual package
/// with exactly two dependencies: `PubGrubPackage::Package("black", None)` and
/// `PubGrubPackage::Package("black", Some("colorama")`. Both dependencies are pinned to the
/// same version, and the virtual package is discarded at the end of the resolution process.
///
/// The benefit of the proxy package (versus `PubGrubPackage::Package("black", Some("colorama")`
/// on its own) is that it enables us to avoid attempting to retrieve metadata for irrelevant
/// versions the extra variants by making it clear to PubGrub that the extra variant must match
/// the exact same version of the base variant. Without the proxy package, then when provided
/// requirements like `black==23.0.1` and `black[colorama]`, PubGrub may attempt to retrieve
/// metadata for `black[colorama]` versions other than `23.0.1`.
Extra(PackageName, ExtraName, Option<VerbatimUrl>),
}
impl PubGrubPackage {
/// Create a [`PubGrubPackage`] from a package name and optional extra name.
pub(crate) fn from_package(name: PackageName, extra: Option<ExtraName>, urls: &Urls) -> Self {
let url = urls.get(&name).cloned();
if let Some(extra) = extra {
Self::Extra(name, extra, url)
} else {
Self::Package(name, extra, url)
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum PubGrubPython {
/// The Python version installed in the current environment.
Installed,
/// The Python version for which dependencies are being resolved.
Target,
}
impl std::fmt::Display for PubGrubPackage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Root(name) => {
if let Some(name) = name {
write!(f, "{}", name.as_ref())
} else {
write!(f, "root")
}
}
Self::Python(_) => write!(f, "Python"),
Self::Package(name, None, ..) => write!(f, "{name}"),
Self::Package(name, Some(extra), ..) => {
write!(f, "{name}[{extra}]")
}
Self::Extra(name, extra, ..) => write!(f, "{name}[{extra}]"),
}
}
}