Respect requires-python when installing tools (#10401)
## Summary This PR revives https://github.com/astral-sh/uv/pull/7827 to improve tool resolutions such that, if the resolution fails, and the selected interpreter doesn't match the required Python version from the solve, we attempt to re-solve with a newly-discovered interpreter that _does_ match the required Python version. For now, we attempt to choose a Python interpreter that's greater than the inferred `requires-python`, but compatible with the same Python minor. This helps avoid successive failures for cases like Posting, where choosing Python 3.13 fails because it has a dependency that lacks source distributions and doesn't publish any Python 3.13 wheels. We should further improve the strategy to solve _that_ case too, but this is at least the more conservative option... In short, if you do `uv tool instal posting`, and we find Python 3.8 on your machine, we'll detect that `requires-python: >=3.11`, then search for the latest Python 3.11 interpreter and re-resolve. Closes https://github.com/astral-sh/uv/issues/6381. Closes https://github.com/astral-sh/uv/issues/10282. ## Test Plan The following should succeed: ``` cargo run python uninstall --all cargo run python install 3.8 cargo run tool install posting ``` In the logs, we see: ``` ... DEBUG No compatible version found for: posting DEBUG Refining interpreter with: Python >=3.11, <3.12 DEBUG Searching for Python >=3.11, <3.12 in managed installations or search path DEBUG Searching for managed installations at `/Users/crmarsh/.local/share/uv/python` DEBUG Skipping incompatible managed installation `cpython-3.8.20-macos-aarch64-none` DEBUG Found `cpython-3.13.1-macos-aarch64-none` at `/opt/homebrew/bin/python3` (search path) DEBUG Skipping interpreter at `/opt/homebrew/opt/python@3.13/bin/python3.13` from search path: does not satisfy request `>=3.11, <3.12` DEBUG Found `cpython-3.11.7-macos-aarch64-none` at `/opt/homebrew/bin/python3.11` (search path) DEBUG Re-resolving with Python 3.11.7 DEBUG Using request timeout of 30s DEBUG Solving with installed Python version: 3.11.7 DEBUG Solving with target Python version: >=3.11.7 DEBUG Adding direct dependency: posting* DEBUG Searching for a compatible version of posting (*) ... ```
This commit is contained in:
@@ -586,6 +586,17 @@ impl SimplifiedMarkerTree {
|
||||
pub struct LowerBound(Bound<Version>);
|
||||
|
||||
impl LowerBound {
|
||||
/// Initialize a [`LowerBound`] with the given bound.
|
||||
///
|
||||
/// These bounds use release-only semantics when comparing versions.
|
||||
pub fn new(bound: Bound<Version>) -> Self {
|
||||
Self(match bound {
|
||||
Bound::Included(version) => Bound::Included(version.only_release()),
|
||||
Bound::Excluded(version) => Bound::Excluded(version.only_release()),
|
||||
Bound::Unbounded => Bound::Unbounded,
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the [`LowerBound`] truncated to the major and minor version.
|
||||
fn major_minor(&self) -> Self {
|
||||
match &self.0 {
|
||||
@@ -600,6 +611,15 @@ impl LowerBound {
|
||||
Bound::Unbounded => Self(Bound::Unbounded),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the lower bound contains the given version.
|
||||
pub fn contains(&self, version: &Version) -> bool {
|
||||
match self.0 {
|
||||
Bound::Included(ref bound) => bound <= version,
|
||||
Bound::Excluded(ref bound) => bound < version,
|
||||
Bound::Unbounded => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for LowerBound {
|
||||
@@ -668,19 +688,6 @@ impl Default for LowerBound {
|
||||
}
|
||||
}
|
||||
|
||||
impl LowerBound {
|
||||
/// Initialize a [`LowerBound`] with the given bound.
|
||||
///
|
||||
/// These bounds use release-only semantics when comparing versions.
|
||||
pub fn new(bound: Bound<Version>) -> Self {
|
||||
Self(match bound {
|
||||
Bound::Included(version) => Bound::Included(version.only_release()),
|
||||
Bound::Excluded(version) => Bound::Excluded(version.only_release()),
|
||||
Bound::Unbounded => Bound::Unbounded,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for LowerBound {
|
||||
type Target = Bound<Version>;
|
||||
|
||||
@@ -699,6 +706,17 @@ impl From<LowerBound> for Bound<Version> {
|
||||
pub struct UpperBound(Bound<Version>);
|
||||
|
||||
impl UpperBound {
|
||||
/// Initialize a [`UpperBound`] with the given bound.
|
||||
///
|
||||
/// These bounds use release-only semantics when comparing versions.
|
||||
pub fn new(bound: Bound<Version>) -> Self {
|
||||
Self(match bound {
|
||||
Bound::Included(version) => Bound::Included(version.only_release()),
|
||||
Bound::Excluded(version) => Bound::Excluded(version.only_release()),
|
||||
Bound::Unbounded => Bound::Unbounded,
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the [`UpperBound`] truncated to the major and minor version.
|
||||
fn major_minor(&self) -> Self {
|
||||
match &self.0 {
|
||||
@@ -721,6 +739,15 @@ impl UpperBound {
|
||||
Bound::Unbounded => Self(Bound::Unbounded),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the upper bound contains the given version.
|
||||
pub fn contains(&self, version: &Version) -> bool {
|
||||
match self.0 {
|
||||
Bound::Included(ref bound) => bound >= version,
|
||||
Bound::Excluded(ref bound) => bound > version,
|
||||
Bound::Unbounded => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for UpperBound {
|
||||
@@ -787,19 +814,6 @@ impl Default for UpperBound {
|
||||
}
|
||||
}
|
||||
|
||||
impl UpperBound {
|
||||
/// Initialize a [`UpperBound`] with the given bound.
|
||||
///
|
||||
/// These bounds use release-only semantics when comparing versions.
|
||||
pub fn new(bound: Bound<Version>) -> Self {
|
||||
Self(match bound {
|
||||
Bound::Included(version) => Bound::Included(version.only_release()),
|
||||
Bound::Excluded(version) => Bound::Excluded(version.only_release()),
|
||||
Bound::Unbounded => Bound::Unbounded,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for UpperBound {
|
||||
type Target = Bound<Version>;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user