2024-06-06 13:52:57 -04:00
|
|
|
use std::collections::Bound;
|
|
|
|
|
|
2024-06-04 09:56:08 -04:00
|
|
|
use pep440_rs::VersionSpecifiers;
|
2024-06-03 18:37:15 -04:00
|
|
|
use pep508_rs::StringVersion;
|
|
|
|
|
use uv_interpreter::{Interpreter, PythonVersion};
|
2024-01-03 11:20:45 -04:00
|
|
|
|
2024-03-12 17:11:50 -07:00
|
|
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
2024-01-22 00:32:02 -06:00
|
|
|
pub struct PythonRequirement {
|
2024-01-03 11:20:45 -04:00
|
|
|
/// The installed version of Python.
|
2024-03-12 17:11:50 -07:00
|
|
|
installed: StringVersion,
|
2024-01-03 11:20:45 -04:00
|
|
|
/// The target version of Python; that is, the version of Python for which we are resolving
|
|
|
|
|
/// dependencies. This is typically the same as the installed version, but may be different
|
|
|
|
|
/// when specifying an alternate Python version for the resolution.
|
2024-06-03 18:37:15 -04:00
|
|
|
///
|
|
|
|
|
/// If `None`, the target version is the same as the installed version.
|
2024-06-04 09:56:08 -04:00
|
|
|
target: Option<RequiresPython>,
|
2024-01-03 11:20:45 -04:00
|
|
|
}
|
|
|
|
|
|
2024-01-22 00:32:02 -06:00
|
|
|
impl PythonRequirement {
|
2024-06-03 18:37:15 -04:00
|
|
|
/// Create a [`PythonRequirement`] to resolve against both an [`Interpreter`] and a
|
|
|
|
|
/// [`PythonVersion`].
|
|
|
|
|
pub fn from_python_version(interpreter: &Interpreter, python_version: &PythonVersion) -> Self {
|
2024-01-03 11:20:45 -04:00
|
|
|
Self {
|
2024-03-12 17:11:50 -07:00
|
|
|
installed: interpreter.python_full_version().clone(),
|
2024-06-04 09:56:08 -04:00
|
|
|
target: Some(RequiresPython::Specifier(StringVersion {
|
2024-06-03 18:37:15 -04:00
|
|
|
string: python_version.to_string(),
|
|
|
|
|
version: python_version.python_full_version(),
|
2024-06-04 09:56:08 -04:00
|
|
|
})),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Create a [`PythonRequirement`] to resolve against both an [`Interpreter`] and a
|
|
|
|
|
/// [`MarkerEnvironment`].
|
|
|
|
|
pub fn from_requires_python(
|
|
|
|
|
interpreter: &Interpreter,
|
|
|
|
|
requires_python: &VersionSpecifiers,
|
|
|
|
|
) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
installed: interpreter.python_full_version().clone(),
|
|
|
|
|
target: Some(RequiresPython::Specifiers(requires_python.clone())),
|
2024-01-03 11:20:45 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-03 18:37:15 -04:00
|
|
|
/// Create a [`PythonRequirement`] to resolve against an [`Interpreter`].
|
2024-06-02 21:33:18 -04:00
|
|
|
pub fn from_interpreter(interpreter: &Interpreter) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
installed: interpreter.python_full_version().clone(),
|
2024-06-03 18:37:15 -04:00
|
|
|
target: None,
|
2024-06-02 21:33:18 -04:00
|
|
|
}
|
2024-05-08 11:21:11 -04:00
|
|
|
}
|
|
|
|
|
|
2024-01-03 11:20:45 -04:00
|
|
|
/// Return the installed version of Python.
|
2024-03-12 17:11:50 -07:00
|
|
|
pub fn installed(&self) -> &StringVersion {
|
2024-01-22 00:32:02 -06:00
|
|
|
&self.installed
|
2024-01-03 11:20:45 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Return the target version of Python.
|
2024-06-04 09:56:08 -04:00
|
|
|
pub fn target(&self) -> Option<&RequiresPython> {
|
2024-06-03 18:37:15 -04:00
|
|
|
self.target.as_ref()
|
2024-01-03 11:20:45 -04:00
|
|
|
}
|
|
|
|
|
}
|
2024-06-04 09:56:08 -04:00
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
|
|
|
|
pub enum RequiresPython {
|
2024-06-05 16:21:59 -04:00
|
|
|
/// The [`RequiresPython`] specifier is a single version specifier, as provided via
|
2024-06-04 09:56:08 -04:00
|
|
|
/// `--python-version` on the command line.
|
|
|
|
|
///
|
|
|
|
|
/// The use of a separate enum variant allows us to use a verbatim representation when reporting
|
|
|
|
|
/// back to the user.
|
|
|
|
|
Specifier(StringVersion),
|
2024-06-05 16:21:59 -04:00
|
|
|
/// The [`RequiresPython`] specifier is a set of version specifiers, as extracted from the
|
|
|
|
|
/// `Requires-Python` field in a `pyproject.toml` or `METADATA` file.
|
2024-06-04 09:56:08 -04:00
|
|
|
Specifiers(VersionSpecifiers),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl RequiresPython {
|
|
|
|
|
/// Returns `true` if the target Python is covered by the [`VersionSpecifiers`].
|
|
|
|
|
///
|
|
|
|
|
/// For example, if the target Python is `>=3.8`, then `>=3.7` would cover it. However, `>=3.9`
|
|
|
|
|
/// would not.
|
2024-06-06 13:52:57 -04:00
|
|
|
///
|
|
|
|
|
/// We treat `Requires-Python` as a lower bound. For example, if the requirement expresses
|
|
|
|
|
/// `>=3.8, <4`, we treat it as `>=3.8`. `Requires-Python` itself was intended to enable
|
|
|
|
|
/// packages to drop support for older versions of Python without breaking installations on
|
|
|
|
|
/// those versions, and packages cannot know whether they are compatible with future, unreleased
|
|
|
|
|
/// versions of Python.
|
|
|
|
|
///
|
|
|
|
|
/// See: <https://packaging.python.org/en/latest/guides/dropping-older-python-versions/>
|
|
|
|
|
pub fn contains(&self, requires_python: &VersionSpecifiers) -> bool {
|
2024-06-04 09:56:08 -04:00
|
|
|
match self {
|
|
|
|
|
RequiresPython::Specifier(specifier) => requires_python.contains(specifier),
|
|
|
|
|
RequiresPython::Specifiers(specifiers) => {
|
|
|
|
|
let Ok(target) = crate::pubgrub::PubGrubSpecifier::try_from(specifiers) else {
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let Ok(requires_python) =
|
|
|
|
|
crate::pubgrub::PubGrubSpecifier::try_from(requires_python)
|
|
|
|
|
else {
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
|
2024-06-06 13:52:57 -04:00
|
|
|
// If the dependency has no lower bound, then it supports all versions.
|
|
|
|
|
let Some((requires_python_lower, _)) = requires_python.iter().next() else {
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// If we have no lower bound, then there must be versions we support that the
|
|
|
|
|
// dependency does not.
|
|
|
|
|
let Some((target_lower, _)) = target.iter().next() else {
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// We want, e.g., `target_lower` to be `>=3.8` and `requires_python_lower` to be
|
|
|
|
|
// `>=3.7`.
|
|
|
|
|
//
|
|
|
|
|
// That is: `requires_python_lower` should be less than or equal to `target_lower`.
|
|
|
|
|
match (requires_python_lower, target_lower) {
|
|
|
|
|
(Bound::Included(requires_python_lower), Bound::Included(target_lower)) => {
|
|
|
|
|
requires_python_lower <= target_lower
|
|
|
|
|
}
|
|
|
|
|
(Bound::Excluded(requires_python_lower), Bound::Included(target_lower)) => {
|
|
|
|
|
requires_python_lower < target_lower
|
|
|
|
|
}
|
|
|
|
|
(Bound::Included(requires_python_lower), Bound::Excluded(target_lower)) => {
|
|
|
|
|
requires_python_lower <= target_lower
|
|
|
|
|
}
|
|
|
|
|
(Bound::Excluded(requires_python_lower), Bound::Excluded(target_lower)) => {
|
|
|
|
|
requires_python_lower < target_lower
|
|
|
|
|
}
|
|
|
|
|
// If the dependency has no lower bound, then it supports all versions.
|
|
|
|
|
(Bound::Unbounded, _) => true,
|
|
|
|
|
// If we have no lower bound, then there must be versions we support that the
|
|
|
|
|
// dependency does not.
|
|
|
|
|
(_, Bound::Unbounded) => false,
|
|
|
|
|
}
|
2024-06-04 09:56:08 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-06-05 16:21:59 -04:00
|
|
|
|
|
|
|
|
/// Returns the [`VersionSpecifiers`] for the [`RequiresPython`] specifier.
|
|
|
|
|
pub fn as_specifiers(&self) -> Option<&VersionSpecifiers> {
|
|
|
|
|
match self {
|
|
|
|
|
RequiresPython::Specifier(_) => None,
|
|
|
|
|
RequiresPython::Specifiers(specifiers) => Some(specifiers),
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-06-04 09:56:08 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl std::fmt::Display for RequiresPython {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
match self {
|
|
|
|
|
RequiresPython::Specifier(specifier) => std::fmt::Display::fmt(specifier, f),
|
|
|
|
|
RequiresPython::Specifiers(specifiers) => std::fmt::Display::fmt(specifiers, f),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|