Use requires-python semantics for --universal (#4701)

## Summary

This doesn't actually change any behaviors, but it does make it a bit
easier to solve #4669, because we don't have to support "version
narrowing" for the non-`RequiresPython` variants in here. Right now, the
semantics are kind of muddied, because the `target` variant is
_sometimes_ interpreted as an exact version and sometimes as a lower
bound.
This commit is contained in:
Charlie Marsh 2024-07-01 15:16:40 -04:00 committed by GitHub
parent 348efa26ba
commit f3d1e52e65
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 27 additions and 38 deletions

View file

@ -831,7 +831,7 @@ impl std::fmt::Display for PubGrubHint {
} => { } => {
write!( write!(
f, f,
"{}{} The `Requires-Python` requirement ({}) defined in your `pyproject.toml` includes Python versions that are not supported by your dependencies (e.g., {} only supports {}). Consider using a more restrictive `Requires-Python` requirement (like {}).", "{}{} The `Requires-Python` requirement ({}) includes Python versions that are not supported by your dependencies (e.g., {} only supports {}). Consider using a more restrictive `Requires-Python` requirement (like {}).",
"hint".bold().cyan(), "hint".bold().cyan(),
":".bold(), ":".bold(),
requires_python.bold(), requires_python.bold(),

View file

@ -1,5 +1,5 @@
use pep440_rs::{Operator, Version, VersionSpecifier, VersionSpecifiers}; use pep440_rs::VersionSpecifiers;
use pep508_rs::{MarkerExpression, MarkerTree, MarkerValueVersion, StringVersion}; use pep508_rs::{MarkerTree, StringVersion};
use uv_toolchain::{Interpreter, PythonVersion}; use uv_toolchain::{Interpreter, PythonVersion};
use crate::RequiresPython; use crate::RequiresPython;
@ -62,32 +62,12 @@ impl PythonRequirement {
/// Return a [`MarkerTree`] representing the Python requirement. /// Return a [`MarkerTree`] representing the Python requirement.
/// ///
/// See: [`RequiresPython::to_marker_tree`] /// See: [`RequiresPython::to_marker_tree`]
pub fn to_marker_tree(&self) -> MarkerTree { pub fn to_marker_tree(&self) -> Option<MarkerTree> {
let version = match &self.target { if let Some(PythonTarget::RequiresPython(requires_python)) = self.target.as_ref() {
None => self.installed.version.clone(), Some(requires_python.to_marker_tree())
Some(PythonTarget::Version(version)) => version.version.clone(), } else {
Some(PythonTarget::RequiresPython(requires_python)) => { None
return requires_python.to_marker_tree()
} }
};
let version_major_minor_only = Version::new(version.release().iter().take(2));
let expr_python_version = MarkerExpression::Version {
key: MarkerValueVersion::PythonVersion,
specifier: VersionSpecifier::from_version(
Operator::GreaterThanEqual,
version_major_minor_only,
)
.unwrap(),
};
let expr_python_full_version = MarkerExpression::Version {
key: MarkerValueVersion::PythonFullVersion,
specifier: VersionSpecifier::from_version(Operator::GreaterThanEqual, version).unwrap(),
};
MarkerTree::And(vec![
MarkerTree::Expression(expr_python_version),
MarkerTree::Expression(expr_python_full_version),
])
} }
} }

View file

@ -191,11 +191,6 @@ impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
provider: Provider, provider: Provider,
installed_packages: InstalledPackages, installed_packages: InstalledPackages,
) -> Result<Self, ResolveError> { ) -> Result<Self, ResolveError> {
let requires_python = if markers.is_some() {
None
} else {
Some(python_requirement.to_marker_tree())
};
let state = ResolverState { let state = ResolverState {
index: index.clone(), index: index.clone(),
git: git.clone(), git: git.clone(),
@ -214,8 +209,12 @@ impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
exclusions: manifest.exclusions, exclusions: manifest.exclusions,
hasher: hasher.clone(), hasher: hasher.clone(),
markers: markers.cloned(), markers: markers.cloned(),
requires_python: if markers.is_some() {
None
} else {
python_requirement.to_marker_tree()
},
python_requirement: python_requirement.clone(), python_requirement: python_requirement.clone(),
requires_python,
reporter: None, reporter: None,
installed_packages, installed_packages,
}; };

View file

@ -28,7 +28,8 @@ use uv_requirements::{
}; };
use uv_resolver::{ use uv_resolver::{
AnnotationStyle, DependencyMode, DisplayResolutionGraph, ExcludeNewer, FlatIndex, AnnotationStyle, DependencyMode, DisplayResolutionGraph, ExcludeNewer, FlatIndex,
InMemoryIndex, OptionsBuilder, PreReleaseMode, PythonRequirement, ResolutionMode, InMemoryIndex, OptionsBuilder, PreReleaseMode, PythonRequirement, RequiresPython,
ResolutionMode,
}; };
use uv_toolchain::{ use uv_toolchain::{
EnvironmentPreference, PythonEnvironment, PythonVersion, Toolchain, ToolchainPreference, EnvironmentPreference, PythonEnvironment, PythonVersion, Toolchain, ToolchainPreference,
@ -212,7 +213,16 @@ pub(crate) async fn pip_compile(
}; };
// Determine the Python requirement, if the user requested a specific version. // Determine the Python requirement, if the user requested a specific version.
let python_requirement = if let Some(python_version) = python_version.as_ref() { let python_requirement = if universal {
let requires_python = RequiresPython::greater_than_equal_version(
if let Some(python_version) = python_version.as_ref() {
python_version.version.clone()
} else {
interpreter.python_version().clone()
},
);
PythonRequirement::from_requires_python(&interpreter, &requires_python)
} else if let Some(python_version) = python_version.as_ref() {
PythonRequirement::from_python_version(&interpreter, python_version) PythonRequirement::from_python_version(&interpreter, python_version)
} else { } else {
PythonRequirement::from_interpreter(&interpreter) PythonRequirement::from_interpreter(&interpreter)

View file

@ -1650,7 +1650,7 @@ fn lock_requires_python() -> Result<()> {
And because project==0.1.0 depends on pygls>=1.1.0, we can conclude that project==0.1.0 cannot be used. And because project==0.1.0 depends on pygls>=1.1.0, we can conclude that project==0.1.0 cannot be used.
And because only project==0.1.0 is available and you require project, we can conclude that the requirements are unsatisfiable. And because only project==0.1.0 is available and you require project, we can conclude that the requirements are unsatisfiable.
hint: The `Requires-Python` requirement (>=3.7) defined in your `pyproject.toml` includes Python versions that are not supported by your dependencies (e.g., pygls>=1.1.0,<=1.2.1 only supports >=3.7.9, <4). Consider using a more restrictive `Requires-Python` requirement (like >=3.7.9, <4). hint: The `Requires-Python` requirement (>=3.7) includes Python versions that are not supported by your dependencies (e.g., pygls>=1.1.0,<=1.2.1 only supports >=3.7.9, <4). Consider using a more restrictive `Requires-Python` requirement (like >=3.7.9, <4).
"###); "###);
// Require >=3.7, and allow locking to a version of `pygls` that is compatible (==1.0.1). // Require >=3.7, and allow locking to a version of `pygls` that is compatible (==1.0.1).