mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-02 12:59:45 +00:00
Remove PubGrub dependency from uv (#4116)
## Summary Encapsulates more of the details are `Requires-Python` and PubGrub. Closes https://github.com/astral-sh/uv/issues/4110.
This commit is contained in:
parent
52bdee2e85
commit
cc7c780523
14 changed files with 209 additions and 135 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -4396,7 +4396,6 @@ dependencies = [
|
|||
"pep508_rs",
|
||||
"platform-tags",
|
||||
"predicates",
|
||||
"pubgrub",
|
||||
"pypi-types",
|
||||
"rayon",
|
||||
"regex",
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ pub use preferences::{Preference, PreferenceError};
|
|||
pub use prerelease_mode::PreReleaseMode;
|
||||
pub use pubgrub::{PubGrubSpecifier, PubGrubSpecifierError};
|
||||
pub use python_requirement::PythonRequirement;
|
||||
pub use requires_python::{RequiresPython, RequiresPythonError};
|
||||
pub use resolution::{AnnotationStyle, DisplayResolutionGraph, ResolutionGraph};
|
||||
pub use resolution_mode::ResolutionMode;
|
||||
pub use resolver::{
|
||||
|
|
@ -39,6 +40,7 @@ mod prerelease_mode;
|
|||
mod pubgrub;
|
||||
mod python_requirement;
|
||||
mod redirect;
|
||||
mod requires_python;
|
||||
mod resolution;
|
||||
mod resolution_mode;
|
||||
mod resolver;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ use distribution_types::{
|
|||
GitSourceDist, IndexUrl, PathBuiltDist, PathSourceDist, RegistryBuiltDist, RegistryBuiltWheel,
|
||||
RegistrySourceDist, RemoteSource, Resolution, ResolvedDist, ToUrlError,
|
||||
};
|
||||
use pep440_rs::{Version, VersionSpecifiers};
|
||||
use pep440_rs::Version;
|
||||
use pep508_rs::{MarkerEnvironment, MarkerTree, VerbatimUrl};
|
||||
use platform_tags::{TagCompatibility, TagPriority, Tags};
|
||||
use pypi_types::{HashDigest, ParsedArchiveUrl, ParsedGitUrl};
|
||||
|
|
@ -29,7 +29,7 @@ use uv_git::{GitReference, GitSha, RepositoryReference, ResolvedRepositoryRefere
|
|||
use uv_normalize::{ExtraName, GroupName, PackageName};
|
||||
|
||||
use crate::resolution::AnnotatedDist;
|
||||
use crate::ResolutionGraph;
|
||||
use crate::{RequiresPython, ResolutionGraph};
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize)]
|
||||
#[serde(try_from = "LockWire")]
|
||||
|
|
@ -37,7 +37,7 @@ pub struct Lock {
|
|||
version: u32,
|
||||
distributions: Vec<Distribution>,
|
||||
/// The range of supported Python versions.
|
||||
requires_python: Option<VersionSpecifiers>,
|
||||
requires_python: Option<RequiresPython>,
|
||||
/// A map from distribution ID to index in `distributions`.
|
||||
///
|
||||
/// This can be used to quickly lookup the full distribution for any ID
|
||||
|
|
@ -107,7 +107,7 @@ impl Lock {
|
|||
/// Initialize a [`Lock`] from a list of [`Distribution`] entries.
|
||||
fn new(
|
||||
distributions: Vec<Distribution>,
|
||||
requires_python: Option<VersionSpecifiers>,
|
||||
requires_python: Option<RequiresPython>,
|
||||
) -> Result<Self, LockError> {
|
||||
let wire = LockWire {
|
||||
version: 1,
|
||||
|
|
@ -123,7 +123,7 @@ impl Lock {
|
|||
}
|
||||
|
||||
/// Returns the supported Python version range for the lockfile, if present.
|
||||
pub fn requires_python(&self) -> Option<&VersionSpecifiers> {
|
||||
pub fn requires_python(&self) -> Option<&RequiresPython> {
|
||||
self.requires_python.as_ref()
|
||||
}
|
||||
|
||||
|
|
@ -226,7 +226,7 @@ struct LockWire {
|
|||
#[serde(rename = "distribution")]
|
||||
distributions: Vec<Distribution>,
|
||||
#[serde(rename = "requires-python")]
|
||||
requires_python: Option<VersionSpecifiers>,
|
||||
requires_python: Option<RequiresPython>,
|
||||
}
|
||||
|
||||
impl From<Lock> for LockWire {
|
||||
|
|
|
|||
|
|
@ -13,12 +13,13 @@ use pubgrub::type_aliases::Map;
|
|||
use rustc_hash::FxHashMap;
|
||||
|
||||
use distribution_types::IndexLocations;
|
||||
use pep440_rs::{Version, VersionSpecifiers};
|
||||
use pep440_rs::Version;
|
||||
use uv_normalize::PackageName;
|
||||
|
||||
use crate::candidate_selector::CandidateSelector;
|
||||
use crate::python_requirement::{PythonRequirement, RequiresPython};
|
||||
use crate::python_requirement::{PythonRequirement, PythonTarget};
|
||||
use crate::resolver::{IncompletePackage, UnavailablePackage, UnavailableReason};
|
||||
use crate::RequiresPython;
|
||||
|
||||
use super::{PubGrubPackage, PubGrubPackageInner, PubGrubPython};
|
||||
|
||||
|
|
@ -534,9 +535,11 @@ impl PubGrubReportFormatter<'_> {
|
|||
PubGrubPackageInner::Python(PubGrubPython::Target)
|
||||
) {
|
||||
if let Some(python) = self.python_requirement {
|
||||
if let Some(RequiresPython::Specifiers(specifiers)) = python.target() {
|
||||
if let Some(PythonTarget::RequiresPython(requires_python)) =
|
||||
python.target()
|
||||
{
|
||||
hints.insert(PubGrubHint::RequiresPython {
|
||||
requires_python: specifiers.clone(),
|
||||
requires_python: requires_python.clone(),
|
||||
package: package.clone(),
|
||||
package_set: self
|
||||
.simplify_set(package_set, package)
|
||||
|
|
@ -632,7 +635,7 @@ pub(crate) enum PubGrubHint {
|
|||
},
|
||||
/// The `Requires-Python` requirement was not satisfied.
|
||||
RequiresPython {
|
||||
requires_python: VersionSpecifiers,
|
||||
requires_python: RequiresPython,
|
||||
#[derivative(PartialEq = "ignore", Hash = "ignore")]
|
||||
package: PubGrubPackage,
|
||||
#[derivative(PartialEq = "ignore", Hash = "ignore")]
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use std::collections::Bound;
|
||||
|
||||
use pep440_rs::VersionSpecifiers;
|
||||
use pep508_rs::StringVersion;
|
||||
use uv_interpreter::{Interpreter, PythonVersion};
|
||||
|
||||
use crate::RequiresPython;
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct PythonRequirement {
|
||||
/// The installed version of Python.
|
||||
|
|
@ -13,7 +13,7 @@ pub struct PythonRequirement {
|
|||
/// when specifying an alternate Python version for the resolution.
|
||||
///
|
||||
/// If `None`, the target version is the same as the installed version.
|
||||
target: Option<RequiresPython>,
|
||||
target: Option<PythonTarget>,
|
||||
}
|
||||
|
||||
impl PythonRequirement {
|
||||
|
|
@ -22,7 +22,7 @@ impl PythonRequirement {
|
|||
pub fn from_python_version(interpreter: &Interpreter, python_version: &PythonVersion) -> Self {
|
||||
Self {
|
||||
installed: interpreter.python_full_version().clone(),
|
||||
target: Some(RequiresPython::Specifier(StringVersion {
|
||||
target: Some(PythonTarget::Version(StringVersion {
|
||||
string: python_version.to_string(),
|
||||
version: python_version.python_full_version(),
|
||||
})),
|
||||
|
|
@ -33,11 +33,11 @@ impl PythonRequirement {
|
|||
/// [`MarkerEnvironment`].
|
||||
pub fn from_requires_python(
|
||||
interpreter: &Interpreter,
|
||||
requires_python: &VersionSpecifiers,
|
||||
requires_python: &RequiresPython,
|
||||
) -> Self {
|
||||
Self {
|
||||
installed: interpreter.python_full_version().clone(),
|
||||
target: Some(RequiresPython::Specifiers(requires_python.clone())),
|
||||
target: Some(PythonTarget::RequiresPython(requires_python.clone())),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -55,103 +55,49 @@ impl PythonRequirement {
|
|||
}
|
||||
|
||||
/// Return the target version of Python.
|
||||
pub fn target(&self) -> Option<&RequiresPython> {
|
||||
pub fn target(&self) -> Option<&PythonTarget> {
|
||||
self.target.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum RequiresPython {
|
||||
/// The [`RequiresPython`] specifier is a single version specifier, as provided via
|
||||
pub enum PythonTarget {
|
||||
/// The [`PythonTarget`] specifier is a single version specifier, as provided via
|
||||
/// `--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),
|
||||
/// The [`RequiresPython`] specifier is a set of version specifiers, as extracted from the
|
||||
Version(StringVersion),
|
||||
/// The [`PythonTarget`] specifier is a set of version specifiers, as extracted from the
|
||||
/// `Requires-Python` field in a `pyproject.toml` or `METADATA` file.
|
||||
Specifiers(VersionSpecifiers),
|
||||
RequiresPython(RequiresPython),
|
||||
}
|
||||
|
||||
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.
|
||||
///
|
||||
/// 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 {
|
||||
impl PythonTarget {
|
||||
/// Returns `true` if the target Python is compatible with the [`VersionSpecifiers`].
|
||||
pub fn is_compatible_with(&self, target: &VersionSpecifiers) -> bool {
|
||||
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;
|
||||
};
|
||||
|
||||
// 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,
|
||||
}
|
||||
PythonTarget::Version(version) => target.contains(version),
|
||||
PythonTarget::RequiresPython(requires_python) => {
|
||||
requires_python.is_contained_by(target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`VersionSpecifiers`] for the [`RequiresPython`] specifier.
|
||||
pub fn as_specifiers(&self) -> Option<&VersionSpecifiers> {
|
||||
/// Returns the [`RequiresPython`] for the [`PythonTarget`] specifier.
|
||||
pub fn as_requires_python(&self) -> Option<&RequiresPython> {
|
||||
match self {
|
||||
RequiresPython::Specifier(_) => None,
|
||||
RequiresPython::Specifiers(specifiers) => Some(specifiers),
|
||||
PythonTarget::Version(_) => None,
|
||||
PythonTarget::RequiresPython(requires_python) => Some(requires_python),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for RequiresPython {
|
||||
impl std::fmt::Display for PythonTarget {
|
||||
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),
|
||||
PythonTarget::Version(specifier) => std::fmt::Display::fmt(specifier, f),
|
||||
PythonTarget::RequiresPython(specifiers) => std::fmt::Display::fmt(specifiers, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
146
crates/uv-resolver/src/requires_python.rs
Normal file
146
crates/uv-resolver/src/requires_python.rs
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
use std::collections::Bound;
|
||||
|
||||
use itertools::Itertools;
|
||||
use pubgrub::range::Range;
|
||||
|
||||
use pep440_rs::{Version, VersionSpecifier, VersionSpecifiers};
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum RequiresPythonError {
|
||||
#[error(transparent)]
|
||||
PubGrub(#[from] crate::pubgrub::PubGrubSpecifierError),
|
||||
}
|
||||
|
||||
/// The `Requires-Python` requirement specifier.
|
||||
///
|
||||
/// 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/>
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct RequiresPython(VersionSpecifiers);
|
||||
|
||||
impl RequiresPython {
|
||||
/// Returns a [`RequiresPython`] to express `>=` equality with the given version.
|
||||
pub fn greater_than_equal_version(version: Version) -> Self {
|
||||
Self(VersionSpecifiers::from(
|
||||
VersionSpecifier::greater_than_equal_version(version),
|
||||
))
|
||||
}
|
||||
|
||||
/// Returns a [`RequiresPython`] to express the union of the given version specifiers.
|
||||
///
|
||||
/// For example, given `>=3.8` and `>=3.9`, this would return `>=3.8`.
|
||||
pub fn union<'a>(
|
||||
specifiers: impl Iterator<Item = &'a VersionSpecifiers>,
|
||||
) -> Result<Option<Self>, RequiresPythonError> {
|
||||
// Convert to PubGrub range and perform a union.
|
||||
let range = specifiers
|
||||
.into_iter()
|
||||
.map(crate::pubgrub::PubGrubSpecifier::try_from)
|
||||
.fold_ok(None, |range: Option<Range<Version>>, requires_python| {
|
||||
if let Some(range) = range {
|
||||
Some(range.union(&requires_python.into()))
|
||||
} else {
|
||||
Some(requires_python.into())
|
||||
}
|
||||
})?;
|
||||
|
||||
let Some(range) = range else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
// Convert back to PEP 440 specifiers.
|
||||
let requires_python = Self(
|
||||
range
|
||||
.iter()
|
||||
.flat_map(VersionSpecifier::from_bounds)
|
||||
.collect(),
|
||||
);
|
||||
|
||||
Ok(Some(requires_python))
|
||||
}
|
||||
|
||||
/// Returns `true` if the `Requires-Python` is compatible with the given version.
|
||||
pub fn contains(&self, version: &Version) -> bool {
|
||||
self.0.contains(version)
|
||||
}
|
||||
|
||||
/// Returns `true` if the `Requires-Python` is compatible with the given version specifiers.
|
||||
///
|
||||
/// For example, if the `Requires-Python` is `>=3.8`, then `>=3.7` would be considered
|
||||
/// compatible, since all versions in the `Requires-Python` range are also covered by the
|
||||
/// provided range. However, `>=3.9` would not be considered compatible, as the
|
||||
/// `Requires-Python` includes Python 3.8, but `>=3.9` does not.
|
||||
pub fn is_contained_by(&self, target: &VersionSpecifiers) -> bool {
|
||||
let Ok(requires_python) = crate::pubgrub::PubGrubSpecifier::try_from(&self.0) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let Ok(target) = crate::pubgrub::PubGrubSpecifier::try_from(target) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
// If the dependency has no lower bound, then it supports all versions.
|
||||
let Some((target_lower, _)) = target.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((requires_python_lower, _)) = requires_python.iter().next() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
// We want, e.g., `requires_python_lower` to be `>=3.8` and `version_lower` to be
|
||||
// `>=3.7`.
|
||||
//
|
||||
// That is: `version_lower` should be less than or equal to `requires_python_lower`.
|
||||
match (target_lower, requires_python_lower) {
|
||||
(Bound::Included(target_lower), Bound::Included(requires_python_lower)) => {
|
||||
target_lower <= requires_python_lower
|
||||
}
|
||||
(Bound::Excluded(target_lower), Bound::Included(requires_python_lower)) => {
|
||||
target_lower < requires_python_lower
|
||||
}
|
||||
(Bound::Included(target_lower), Bound::Excluded(requires_python_lower)) => {
|
||||
target_lower <= requires_python_lower
|
||||
}
|
||||
(Bound::Excluded(target_lower), Bound::Excluded(requires_python_lower)) => {
|
||||
target_lower < requires_python_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,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`VersionSpecifiers`] for the `Requires-Python` specifier.
|
||||
pub fn specifiers(&self) -> &VersionSpecifiers {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for RequiresPython {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for RequiresPython {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
self.0.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for RequiresPython {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
let specifiers = VersionSpecifiers::deserialize(deserializer)?;
|
||||
Ok(Self(specifiers))
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
|
|||
use distribution_types::{
|
||||
Dist, DistributionMetadata, Name, ResolutionDiagnostic, VersionId, VersionOrUrlRef,
|
||||
};
|
||||
use pep440_rs::{Version, VersionSpecifier, VersionSpecifiers};
|
||||
use pep440_rs::{Version, VersionSpecifier};
|
||||
use pep508_rs::{MarkerEnvironment, MarkerTree};
|
||||
use pypi_types::{ParsedUrlError, Yanked};
|
||||
use uv_git::GitResolver;
|
||||
|
|
@ -17,12 +17,13 @@ use uv_normalize::{ExtraName, GroupName, PackageName};
|
|||
|
||||
use crate::preferences::Preferences;
|
||||
use crate::pubgrub::{PubGrubDistribution, PubGrubPackageInner};
|
||||
use crate::python_requirement::RequiresPython;
|
||||
use crate::python_requirement::PythonTarget;
|
||||
use crate::redirect::url_to_precise;
|
||||
use crate::resolution::AnnotatedDist;
|
||||
use crate::resolver::Resolution;
|
||||
use crate::{
|
||||
InMemoryIndex, Manifest, MetadataResponse, PythonRequirement, ResolveError, VersionsResponse,
|
||||
InMemoryIndex, Manifest, MetadataResponse, PythonRequirement, RequiresPython, ResolveError,
|
||||
VersionsResponse,
|
||||
};
|
||||
|
||||
/// A complete resolution graph in which every node represents a pinned package and every edge
|
||||
|
|
@ -32,7 +33,7 @@ pub struct ResolutionGraph {
|
|||
/// The underlying graph.
|
||||
pub(crate) petgraph: Graph<AnnotatedDist, Version, Directed>,
|
||||
/// The range of supported Python versions.
|
||||
pub(crate) requires_python: Option<VersionSpecifiers>,
|
||||
pub(crate) requires_python: Option<RequiresPython>,
|
||||
/// Any diagnostics that were encountered while building the graph.
|
||||
pub(crate) diagnostics: Vec<ResolutionDiagnostic>,
|
||||
}
|
||||
|
|
@ -319,7 +320,7 @@ impl ResolutionGraph {
|
|||
// included packages.
|
||||
let requires_python = python
|
||||
.target()
|
||||
.and_then(RequiresPython::as_specifiers)
|
||||
.and_then(PythonTarget::as_requires_python)
|
||||
.cloned();
|
||||
|
||||
Ok(Self {
|
||||
|
|
|
|||
|
|
@ -730,7 +730,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
// The version is incompatible due to its Python requirement.
|
||||
if let Some(requires_python) = metadata.requires_python.as_ref() {
|
||||
if let Some(target) = self.python_requirement.target() {
|
||||
if !target.contains(requires_python) {
|
||||
if !target.is_compatible_with(requires_python) {
|
||||
return Ok(Some(ResolverVersion::Unavailable(
|
||||
version.clone(),
|
||||
UnavailableVersion::IncompatibleDist(IncompatibleDist::Source(
|
||||
|
|
|
|||
|
|
@ -467,7 +467,7 @@ impl VersionMapLazy {
|
|||
// _installed_ Python version (to build successfully)
|
||||
if let Some(requires_python) = requires_python {
|
||||
if let Some(target) = self.python_requirement.target() {
|
||||
if !target.contains(&requires_python) {
|
||||
if !target.is_compatible_with(&requires_python) {
|
||||
return SourceDistCompatibility::Incompatible(
|
||||
IncompatibleSource::RequiresPython(
|
||||
requires_python,
|
||||
|
|
@ -534,7 +534,7 @@ impl VersionMapLazy {
|
|||
// Check for a Python version incompatibility
|
||||
if let Some(requires_python) = requires_python {
|
||||
if let Some(target) = self.python_requirement.target() {
|
||||
if !target.contains(&requires_python) {
|
||||
if !target.is_compatible_with(&requires_python) {
|
||||
return WheelCompatibility::Incompatible(IncompatibleWheel::RequiresPython(
|
||||
requires_python,
|
||||
PythonRequirementKind::Target,
|
||||
|
|
|
|||
|
|
@ -50,7 +50,6 @@ indicatif = { workspace = true }
|
|||
itertools = { workspace = true }
|
||||
miette = { workspace = true, features = ["fancy"] }
|
||||
owo-colors = { workspace = true }
|
||||
pubgrub = { workspace = true }
|
||||
rayon = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ use distribution_types::{
|
|||
DistributionMetadata, IndexLocations, InstalledMetadata, LocalDist, Name, Resolution,
|
||||
};
|
||||
use install_wheel_rs::linker::LinkMode;
|
||||
use pep440_rs::VersionSpecifiers;
|
||||
use pep508_rs::MarkerEnvironment;
|
||||
use platform_tags::Tags;
|
||||
use pypi_types::Requirement;
|
||||
|
|
@ -37,7 +36,7 @@ use uv_requirements::{
|
|||
};
|
||||
use uv_resolver::{
|
||||
DependencyMode, Exclusions, FlatIndex, InMemoryIndex, Manifest, Options, Preference,
|
||||
PythonRequirement, ResolutionGraph, Resolver,
|
||||
PythonRequirement, RequiresPython, ResolutionGraph, Resolver,
|
||||
};
|
||||
use uv_types::{HashStrategy, InFlight, InstalledPackagesProvider};
|
||||
use uv_warnings::warn_user;
|
||||
|
|
@ -91,7 +90,7 @@ pub(crate) async fn resolve<InstalledPackages: InstalledPackagesProvider>(
|
|||
interpreter: &Interpreter,
|
||||
tags: &Tags,
|
||||
markers: Option<&MarkerEnvironment>,
|
||||
requires_python: Option<&VersionSpecifiers>,
|
||||
requires_python: Option<&RequiresPython>,
|
||||
client: &RegistryClient,
|
||||
flat_index: &FlatIndex,
|
||||
index: &InMemoryIndex,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
use anstream::eprint;
|
||||
use itertools::Itertools;
|
||||
use pubgrub::range::Range;
|
||||
|
||||
use distribution_types::{IndexLocations, UnresolvedRequirementSpecification};
|
||||
use install_wheel_rs::linker::LinkMode;
|
||||
use pep440_rs::{Version, VersionSpecifier, VersionSpecifiers};
|
||||
use uv_cache::Cache;
|
||||
use uv_client::RegistryClientBuilder;
|
||||
use uv_configuration::{
|
||||
|
|
@ -17,7 +14,7 @@ use uv_git::GitResolver;
|
|||
use uv_interpreter::PythonEnvironment;
|
||||
use uv_normalize::PackageName;
|
||||
use uv_requirements::upgrade::{read_lockfile, LockedRequirements};
|
||||
use uv_resolver::{ExcludeNewer, FlatIndex, InMemoryIndex, Lock, OptionsBuilder, PubGrubSpecifier};
|
||||
use uv_resolver::{ExcludeNewer, FlatIndex, InMemoryIndex, Lock, OptionsBuilder, RequiresPython};
|
||||
use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight};
|
||||
use uv_warnings::warn_user;
|
||||
|
||||
|
|
@ -109,38 +106,20 @@ pub(super) async fn do_lock(
|
|||
//
|
||||
// For a workspace, we compute the union of all workspace requires-python values, ensuring we
|
||||
// keep track of `None` vs. a full range.
|
||||
let requires_python_workspace = workspace
|
||||
.packages()
|
||||
.values()
|
||||
.filter_map(|member| {
|
||||
let requires_python_workspace =
|
||||
RequiresPython::union(workspace.packages().values().filter_map(|member| {
|
||||
member
|
||||
.pyproject_toml()
|
||||
.project
|
||||
.as_ref()
|
||||
.and_then(|project| project.requires_python.as_ref())
|
||||
})
|
||||
// Convert to pubgrub range, perform the union, convert back to pep440_rs.
|
||||
.map(PubGrubSpecifier::try_from)
|
||||
.fold_ok(None, |range: Option<Range<Version>>, requires_python| {
|
||||
if let Some(range) = range {
|
||||
Some(range.union(&requires_python.into()))
|
||||
} else {
|
||||
Some(requires_python.into())
|
||||
}
|
||||
})?
|
||||
.map(|range| {
|
||||
range
|
||||
.iter()
|
||||
.flat_map(VersionSpecifier::from_bounds)
|
||||
.collect()
|
||||
});
|
||||
}))?;
|
||||
|
||||
let requires_python = if let Some(requires_python) = requires_python_workspace {
|
||||
requires_python
|
||||
} else {
|
||||
let requires_python = VersionSpecifiers::from(
|
||||
VersionSpecifier::greater_than_equal_version(venv.interpreter().python_minor_version()),
|
||||
);
|
||||
let requires_python =
|
||||
RequiresPython::greater_than_equal_version(venv.interpreter().python_minor_version());
|
||||
if let Some(root_project_name) = root_project_name.as_ref() {
|
||||
warn_user!(
|
||||
"No `requires-python` field found in `{root_project_name}`. Defaulting to `{requires_python}`.",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use tracing::debug;
|
|||
|
||||
use distribution_types::{IndexLocations, Resolution};
|
||||
use install_wheel_rs::linker::LinkMode;
|
||||
use pep440_rs::{Version, VersionSpecifiers};
|
||||
use pep440_rs::Version;
|
||||
use uv_cache::Cache;
|
||||
use uv_client::{BaseClientBuilder, Connectivity, RegistryClientBuilder};
|
||||
use uv_configuration::{
|
||||
|
|
@ -21,7 +21,7 @@ use uv_git::GitResolver;
|
|||
use uv_installer::{SatisfiesResult, SitePackages};
|
||||
use uv_interpreter::{find_default_interpreter, PythonEnvironment};
|
||||
use uv_requirements::{RequirementsSource, RequirementsSpecification};
|
||||
use uv_resolver::{FlatIndex, InMemoryIndex, Options};
|
||||
use uv_resolver::{FlatIndex, InMemoryIndex, Options, RequiresPython};
|
||||
use uv_types::{BuildIsolation, HashStrategy, InFlight};
|
||||
|
||||
use crate::commands::pip;
|
||||
|
|
@ -34,7 +34,7 @@ pub(crate) mod sync;
|
|||
#[derive(thiserror::Error, Debug)]
|
||||
pub(crate) enum ProjectError {
|
||||
#[error("The current Python version ({0}) is not compatible with the locked Python requirement ({1})")]
|
||||
RequiresPython(Version, VersionSpecifiers),
|
||||
PythonIncompatibility(Version, RequiresPython),
|
||||
|
||||
#[error(transparent)]
|
||||
Interpreter(#[from] uv_interpreter::Error),
|
||||
|
|
@ -64,7 +64,7 @@ pub(crate) enum ProjectError {
|
|||
Operation(#[from] pip::operations::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
PubGrubSpecifier(#[from] uv_resolver::PubGrubSpecifierError),
|
||||
RequiresPython(#[from] uv_resolver::RequiresPythonError),
|
||||
}
|
||||
|
||||
/// Initialize a virtual environment for the current project.
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ pub(super) async fn do_sync(
|
|||
// Validate that the Python version is supported by the lockfile.
|
||||
if let Some(requires_python) = lock.requires_python() {
|
||||
if !requires_python.contains(venv.interpreter().python_version()) {
|
||||
return Err(ProjectError::RequiresPython(
|
||||
return Err(ProjectError::PythonIncompatibility(
|
||||
venv.interpreter().python_version().clone(),
|
||||
requires_python.clone(),
|
||||
));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue