mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-24 13:43:45 +00:00
Prefer Python executable names that match the request over default names (#9066)
This restores behavior previously removed in https://github.com/astral-sh/uv/pull/7649. I thought it'd be clearer (and simpler) to have a consistent Python executable name ordering. However, we've seen some cases where this can be surprising and, in combination with #8481, can result in incorrect behavior. For example, see https://github.com/astral-sh/uv/issues/9046 where we prefer `python3` over `python3.12` in the same directory even though `python3.12` was requested. While `python3` and `python3.12` both point to valid Python 3.12 environments there, the expectation is that when `python3.12` is requested that the `python3.12` executable is preferred. This expectation may be less obvious if the user requests `python@3.12`, but uv does not distinguish between these request forms. Similarly, this may be surprising as by default uv prefers `python` over `python3` but when requesting `python3.12` the preference will be swapped.
This commit is contained in:
parent
15ef807c80
commit
2966471db2
5 changed files with 302 additions and 175 deletions
|
@ -1600,9 +1600,9 @@ impl EnvironmentPreference {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Default, Copy, PartialEq, Eq)]
|
||||
pub(crate) struct ExecutableName {
|
||||
name: &'static str,
|
||||
implementation: Option<ImplementationName>,
|
||||
major: Option<u8>,
|
||||
minor: Option<u8>,
|
||||
patch: Option<u8>,
|
||||
|
@ -1610,10 +1610,92 @@ pub(crate) struct ExecutableName {
|
|||
variant: PythonVariant,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct ExecutableNameComparator<'a> {
|
||||
name: ExecutableName,
|
||||
request: &'a VersionRequest,
|
||||
implementation: Option<&'a ImplementationName>,
|
||||
}
|
||||
|
||||
impl Ord for ExecutableNameComparator<'_> {
|
||||
/// Note the comparison returns a reverse priority ordering.
|
||||
///
|
||||
/// Higher priority items are "Greater" than lower priority items.
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
// Prefer the default name over a specific implementation, unless an implementation was
|
||||
// requested
|
||||
let name_ordering = if self.implementation.is_some() {
|
||||
std::cmp::Ordering::Greater
|
||||
} else {
|
||||
std::cmp::Ordering::Less
|
||||
};
|
||||
if self.name.implementation.is_none() && other.name.implementation.is_some() {
|
||||
return name_ordering.reverse();
|
||||
}
|
||||
if self.name.implementation.is_some() && other.name.implementation.is_none() {
|
||||
return name_ordering;
|
||||
}
|
||||
// Otherwise, use the names in supported order
|
||||
let ordering = self.name.implementation.cmp(&other.name.implementation);
|
||||
if ordering != std::cmp::Ordering::Equal {
|
||||
return ordering;
|
||||
}
|
||||
let ordering = self.name.major.cmp(&other.name.major);
|
||||
let is_default_request =
|
||||
matches!(self.request, VersionRequest::Any | VersionRequest::Default);
|
||||
if ordering != std::cmp::Ordering::Equal {
|
||||
return if is_default_request {
|
||||
ordering.reverse()
|
||||
} else {
|
||||
ordering
|
||||
};
|
||||
}
|
||||
let ordering = self.name.minor.cmp(&other.name.minor);
|
||||
if ordering != std::cmp::Ordering::Equal {
|
||||
return if is_default_request {
|
||||
ordering.reverse()
|
||||
} else {
|
||||
ordering
|
||||
};
|
||||
}
|
||||
let ordering = self.name.patch.cmp(&other.name.patch);
|
||||
if ordering != std::cmp::Ordering::Equal {
|
||||
return if is_default_request {
|
||||
ordering.reverse()
|
||||
} else {
|
||||
ordering
|
||||
};
|
||||
}
|
||||
let ordering = self.name.prerelease.cmp(&other.name.prerelease);
|
||||
if ordering != std::cmp::Ordering::Equal {
|
||||
return if is_default_request {
|
||||
ordering.reverse()
|
||||
} else {
|
||||
ordering
|
||||
};
|
||||
}
|
||||
let ordering = self.name.variant.cmp(&other.name.variant);
|
||||
if ordering != std::cmp::Ordering::Equal {
|
||||
return if is_default_request {
|
||||
ordering.reverse()
|
||||
} else {
|
||||
ordering
|
||||
};
|
||||
}
|
||||
ordering
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for ExecutableNameComparator<'_> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecutableName {
|
||||
#[must_use]
|
||||
fn with_name(mut self, name: &'static str) -> Self {
|
||||
self.name = name;
|
||||
fn with_implementation(mut self, implementation: ImplementationName) -> Self {
|
||||
self.implementation = Some(implementation);
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -1646,24 +1728,27 @@ impl ExecutableName {
|
|||
self.variant = variant;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ExecutableName {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: "python",
|
||||
major: None,
|
||||
minor: None,
|
||||
patch: None,
|
||||
prerelease: None,
|
||||
variant: PythonVariant::Default,
|
||||
fn into_comparator<'a>(
|
||||
self,
|
||||
request: &'a VersionRequest,
|
||||
implementation: Option<&'a ImplementationName>,
|
||||
) -> ExecutableNameComparator<'a> {
|
||||
ExecutableNameComparator {
|
||||
name: self,
|
||||
request,
|
||||
implementation,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ExecutableName {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.name)?;
|
||||
if let Some(implementation) = self.implementation {
|
||||
write!(f, "{implementation}")?;
|
||||
} else {
|
||||
f.write_str("python")?;
|
||||
}
|
||||
if let Some(major) = self.major {
|
||||
write!(f, "{major}")?;
|
||||
if let Some(minor) = self.minor {
|
||||
|
@ -1741,15 +1826,15 @@ impl VersionRequest {
|
|||
// Add all the implementation-specific names
|
||||
if let Some(implementation) = implementation {
|
||||
for i in 0..names.len() {
|
||||
let name = names[i].with_name(implementation.into());
|
||||
let name = names[i].with_implementation(*implementation);
|
||||
names.push(name);
|
||||
}
|
||||
} else {
|
||||
// When looking for all implementations, include all possible names
|
||||
if matches!(self, Self::Any) {
|
||||
for i in 0..names.len() {
|
||||
for implementation in ImplementationName::long_names() {
|
||||
let name = names[i].with_name(implementation);
|
||||
for implementation in ImplementationName::iter_all() {
|
||||
let name = names[i].with_implementation(implementation);
|
||||
names.push(name);
|
||||
}
|
||||
}
|
||||
|
@ -1764,6 +1849,9 @@ impl VersionRequest {
|
|||
}
|
||||
}
|
||||
|
||||
names.sort_unstable_by_key(|name| name.into_comparator(self, implementation));
|
||||
names.reverse();
|
||||
|
||||
names
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue