|
|
|
@@ -36,12 +36,14 @@ use crate::{Interpreter, PythonVersion};
|
|
|
|
|
/// See [`PythonRequest::from_str`].
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
|
|
|
|
pub enum PythonRequest {
|
|
|
|
|
/// Use an appropriate default Python installation
|
|
|
|
|
/// An appropriate default Python installation
|
|
|
|
|
///
|
|
|
|
|
/// This may skip some Python installations, such as pre-release versions or alternative
|
|
|
|
|
/// implementations.
|
|
|
|
|
#[default]
|
|
|
|
|
Default,
|
|
|
|
|
/// Any Python installation
|
|
|
|
|
Any,
|
|
|
|
|
/// A Python version without an implementation name e.g. `3.10` or `>=3.12,<3.13`
|
|
|
|
|
Version(VersionRequest),
|
|
|
|
|
/// A path to a directory containing a Python installation, e.g. `.venv`
|
|
|
|
@@ -137,6 +139,8 @@ pub enum VersionRequest {
|
|
|
|
|
/// Allow an appropriate default Python version.
|
|
|
|
|
#[default]
|
|
|
|
|
Default,
|
|
|
|
|
/// Allow any Python version.
|
|
|
|
|
Any,
|
|
|
|
|
Major(u8),
|
|
|
|
|
MajorMinor(u8, u8),
|
|
|
|
|
MajorMinorPatch(u8, u8, u8),
|
|
|
|
@@ -488,7 +492,10 @@ fn find_all_minor(
|
|
|
|
|
dir: &Path,
|
|
|
|
|
) -> impl Iterator<Item = PathBuf> {
|
|
|
|
|
match version_request {
|
|
|
|
|
VersionRequest::Default | VersionRequest::Major(_) | VersionRequest::Range(_) => {
|
|
|
|
|
&VersionRequest::Any
|
|
|
|
|
| VersionRequest::Default
|
|
|
|
|
| VersionRequest::Major(_)
|
|
|
|
|
| VersionRequest::Range(_) => {
|
|
|
|
|
let regex = if let Some(implementation) = implementation {
|
|
|
|
|
Regex::new(&format!(
|
|
|
|
|
r"^({}|python3)\.(?<minor>\d\d?){}$",
|
|
|
|
@@ -780,9 +787,31 @@ pub fn find_python_installations<'a>(
|
|
|
|
|
))))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
PythonRequest::Any => Box::new({
|
|
|
|
|
debug!("Searching for any Python interpreter in {preference}");
|
|
|
|
|
python_interpreters(
|
|
|
|
|
Some(&VersionRequest::Any),
|
|
|
|
|
None,
|
|
|
|
|
environments,
|
|
|
|
|
preference,
|
|
|
|
|
cache,
|
|
|
|
|
)
|
|
|
|
|
.map(|result| {
|
|
|
|
|
result
|
|
|
|
|
.map(PythonInstallation::from_tuple)
|
|
|
|
|
.map(FindPythonResult::Ok)
|
|
|
|
|
})
|
|
|
|
|
}),
|
|
|
|
|
PythonRequest::Default => Box::new({
|
|
|
|
|
debug!("Searching for Python interpreter in {preference}");
|
|
|
|
|
python_interpreters(None, None, environments, preference, cache).map(|result| {
|
|
|
|
|
debug!("Searching for default Python interpreter in {preference}");
|
|
|
|
|
python_interpreters(
|
|
|
|
|
Some(&VersionRequest::Default),
|
|
|
|
|
None,
|
|
|
|
|
environments,
|
|
|
|
|
preference,
|
|
|
|
|
cache,
|
|
|
|
|
)
|
|
|
|
|
.map(|result| {
|
|
|
|
|
result
|
|
|
|
|
.map(PythonInstallation::from_tuple)
|
|
|
|
|
.map(FindPythonResult::Ok)
|
|
|
|
@@ -979,7 +1008,7 @@ pub fn find_best_python_installation(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If a Python version was requested but cannot be fulfilled, just take any version
|
|
|
|
|
debug!("Looking for Python installation with any version");
|
|
|
|
|
debug!("Looking for a default Python installation");
|
|
|
|
|
let request = PythonRequest::Default;
|
|
|
|
|
Ok(
|
|
|
|
|
find_python_installation(&request, environments, preference, cache)?.map_err(|err| {
|
|
|
|
@@ -1144,8 +1173,11 @@ impl PythonRequest {
|
|
|
|
|
///
|
|
|
|
|
/// This cannot fail, which means weird inputs will be parsed as [`PythonRequest::File`] or [`PythonRequest::ExecutableName`].
|
|
|
|
|
pub fn parse(value: &str) -> Self {
|
|
|
|
|
// e.g. `any`
|
|
|
|
|
// Literals, e.g. `any` or `default`
|
|
|
|
|
if value.eq_ignore_ascii_case("any") {
|
|
|
|
|
return Self::Any;
|
|
|
|
|
}
|
|
|
|
|
if value.eq_ignore_ascii_case("default") {
|
|
|
|
|
return Self::Default;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -1250,7 +1282,7 @@ impl PythonRequest {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match self {
|
|
|
|
|
PythonRequest::Default => true,
|
|
|
|
|
PythonRequest::Default | PythonRequest::Any => true,
|
|
|
|
|
PythonRequest::Version(version_request) => {
|
|
|
|
|
version_request.matches_interpreter(interpreter)
|
|
|
|
|
}
|
|
|
|
@@ -1335,6 +1367,7 @@ impl PythonRequest {
|
|
|
|
|
pub(crate) fn allows_prereleases(&self) -> bool {
|
|
|
|
|
match self {
|
|
|
|
|
Self::Default => false,
|
|
|
|
|
Self::Any => true,
|
|
|
|
|
Self::Version(version) => version.allows_prereleases(),
|
|
|
|
|
Self::Directory(_) | Self::File(_) | Self::ExecutableName(_) => true,
|
|
|
|
|
Self::Implementation(_) => false,
|
|
|
|
@@ -1352,7 +1385,8 @@ impl PythonRequest {
|
|
|
|
|
/// [`Self::parse`] should always return the same request when given the output of this method.
|
|
|
|
|
pub fn to_canonical_string(&self) -> String {
|
|
|
|
|
match self {
|
|
|
|
|
Self::Default => "any".to_string(),
|
|
|
|
|
Self::Any => "any".to_string(),
|
|
|
|
|
Self::Default => "default".to_string(),
|
|
|
|
|
Self::Version(version) => version.to_string(),
|
|
|
|
|
Self::Directory(path) => path.display().to_string(),
|
|
|
|
|
Self::File(path) => path.display().to_string(),
|
|
|
|
@@ -1453,7 +1487,7 @@ impl VersionRequest {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match self {
|
|
|
|
|
Self::Default | Self::Range(_) => [Some(python3), Some(python), None, None],
|
|
|
|
|
Self::Any | Self::Default | Self::Range(_) => [Some(python3), Some(python), None, None],
|
|
|
|
|
Self::Major(major) => [
|
|
|
|
|
Some(Cow::Owned(format!("python{major}{extension}"))),
|
|
|
|
|
Some(python),
|
|
|
|
@@ -1504,7 +1538,9 @@ impl VersionRequest {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match self {
|
|
|
|
|
Self::Default | Self::Range(_) => [Some(python3), Some(python), None, None],
|
|
|
|
|
Self::Any | Self::Default | Self::Range(_) => {
|
|
|
|
|
[Some(python3), Some(python), None, None]
|
|
|
|
|
}
|
|
|
|
|
Self::Major(major) => [
|
|
|
|
|
Some(Cow::Owned(format!("{name}{major}{extension}"))),
|
|
|
|
|
Some(python),
|
|
|
|
@@ -1541,7 +1577,7 @@ impl VersionRequest {
|
|
|
|
|
|
|
|
|
|
pub(crate) fn check_supported(&self) -> Result<(), String> {
|
|
|
|
|
match self {
|
|
|
|
|
Self::Default => (),
|
|
|
|
|
Self::Any | Self::Default => (),
|
|
|
|
|
Self::Major(major) => {
|
|
|
|
|
if *major < 3 {
|
|
|
|
|
return Err(format!(
|
|
|
|
@@ -1580,7 +1616,7 @@ impl VersionRequest {
|
|
|
|
|
/// Check if a interpreter matches the requested Python version.
|
|
|
|
|
pub(crate) fn matches_interpreter(&self, interpreter: &Interpreter) -> bool {
|
|
|
|
|
match self {
|
|
|
|
|
Self::Default => true,
|
|
|
|
|
Self::Any | Self::Default => true,
|
|
|
|
|
Self::Major(major) => interpreter.python_major() == *major,
|
|
|
|
|
Self::MajorMinor(major, minor) => {
|
|
|
|
|
(interpreter.python_major(), interpreter.python_minor()) == (*major, *minor)
|
|
|
|
@@ -1612,7 +1648,7 @@ impl VersionRequest {
|
|
|
|
|
|
|
|
|
|
pub(crate) fn matches_version(&self, version: &PythonVersion) -> bool {
|
|
|
|
|
match self {
|
|
|
|
|
Self::Default => true,
|
|
|
|
|
Self::Any | Self::Default => true,
|
|
|
|
|
Self::Major(major) => version.major() == *major,
|
|
|
|
|
Self::MajorMinor(major, minor) => {
|
|
|
|
|
(version.major(), version.minor()) == (*major, *minor)
|
|
|
|
@@ -1631,7 +1667,7 @@ impl VersionRequest {
|
|
|
|
|
|
|
|
|
|
fn matches_major_minor(&self, major: u8, minor: u8) -> bool {
|
|
|
|
|
match self {
|
|
|
|
|
Self::Default => true,
|
|
|
|
|
Self::Any | Self::Default => true,
|
|
|
|
|
Self::Major(self_major) => *self_major == major,
|
|
|
|
|
Self::MajorMinor(self_major, self_minor) => {
|
|
|
|
|
(*self_major, *self_minor) == (major, minor)
|
|
|
|
@@ -1650,7 +1686,7 @@ impl VersionRequest {
|
|
|
|
|
|
|
|
|
|
pub(crate) fn matches_major_minor_patch(&self, major: u8, minor: u8, patch: u8) -> bool {
|
|
|
|
|
match self {
|
|
|
|
|
Self::Default => true,
|
|
|
|
|
Self::Any | Self::Default => true,
|
|
|
|
|
Self::Major(self_major) => *self_major == major,
|
|
|
|
|
Self::MajorMinor(self_major, self_minor) => {
|
|
|
|
|
(*self_major, *self_minor) == (major, minor)
|
|
|
|
@@ -1673,7 +1709,7 @@ impl VersionRequest {
|
|
|
|
|
/// Return true if a patch version is present in the request.
|
|
|
|
|
fn has_patch(&self) -> bool {
|
|
|
|
|
match self {
|
|
|
|
|
Self::Default => false,
|
|
|
|
|
Self::Any | Self::Default => false,
|
|
|
|
|
Self::Major(..) => false,
|
|
|
|
|
Self::MajorMinor(..) => false,
|
|
|
|
|
Self::MajorMinorPatch(..) => true,
|
|
|
|
@@ -1689,6 +1725,7 @@ impl VersionRequest {
|
|
|
|
|
fn without_patch(self) -> Self {
|
|
|
|
|
match self {
|
|
|
|
|
Self::Default => Self::Default,
|
|
|
|
|
Self::Any => Self::Any,
|
|
|
|
|
Self::Major(major) => Self::Major(major),
|
|
|
|
|
Self::MajorMinor(major, minor) => Self::MajorMinor(major, minor),
|
|
|
|
|
Self::MajorMinorPatch(major, minor, _) => Self::MajorMinor(major, minor),
|
|
|
|
@@ -1703,6 +1740,7 @@ impl VersionRequest {
|
|
|
|
|
pub(crate) fn allows_prereleases(&self) -> bool {
|
|
|
|
|
match self {
|
|
|
|
|
Self::Default => false,
|
|
|
|
|
Self::Any => true,
|
|
|
|
|
Self::Major(_) => true,
|
|
|
|
|
Self::MajorMinor(..) => true,
|
|
|
|
|
Self::MajorMinorPatch(..) => true,
|
|
|
|
@@ -1788,6 +1826,7 @@ impl From<&PythonVersion> for VersionRequest {
|
|
|
|
|
impl fmt::Display for VersionRequest {
|
|
|
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
|
|
|
match self {
|
|
|
|
|
Self::Any => f.write_str("any"),
|
|
|
|
|
Self::Default => f.write_str("default"),
|
|
|
|
|
Self::Major(major) => write!(f, "{major}"),
|
|
|
|
|
Self::MajorMinor(major, minor) => write!(f, "{major}.{minor}"),
|
|
|
|
@@ -1805,7 +1844,8 @@ impl fmt::Display for VersionRequest {
|
|
|
|
|
impl fmt::Display for PythonRequest {
|
|
|
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
|
|
|
match self {
|
|
|
|
|
Self::Default => write!(f, "any Python"),
|
|
|
|
|
Self::Default => write!(f, "a default Python"),
|
|
|
|
|
Self::Any => write!(f, "any Python"),
|
|
|
|
|
Self::Version(version) => write!(f, "Python {version}"),
|
|
|
|
|
Self::Directory(path) => write!(f, "directory `{}`", path.user_display()),
|
|
|
|
|
Self::File(path) => write!(f, "path `{}`", path.user_display()),
|
|
|
|
@@ -1887,7 +1927,7 @@ impl fmt::Display for PythonNotFound {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match self.request {
|
|
|
|
|
PythonRequest::Default => {
|
|
|
|
|
PythonRequest::Default | PythonRequest::Any => {
|
|
|
|
|
write!(f, "No interpreter found in {sources}")
|
|
|
|
|
}
|
|
|
|
|
_ => {
|
|
|
|
@@ -1965,7 +2005,8 @@ mod tests {
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn interpreter_request_from_str() {
|
|
|
|
|
assert_eq!(PythonRequest::parse("any"), PythonRequest::Default);
|
|
|
|
|
assert_eq!(PythonRequest::parse("any"), PythonRequest::Any);
|
|
|
|
|
assert_eq!(PythonRequest::parse("default"), PythonRequest::Default);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
PythonRequest::parse("3.12"),
|
|
|
|
|
PythonRequest::Version(VersionRequest::from_str("3.12").unwrap())
|
|
|
|
@@ -2127,7 +2168,8 @@ mod tests {
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn interpreter_request_to_canonical_string() {
|
|
|
|
|
assert_eq!(PythonRequest::Default.to_canonical_string(), "any");
|
|
|
|
|
assert_eq!(PythonRequest::Default.to_canonical_string(), "default");
|
|
|
|
|
assert_eq!(PythonRequest::Any.to_canonical_string(), "any");
|
|
|
|
|
assert_eq!(
|
|
|
|
|
PythonRequest::Version(VersionRequest::from_str("3.12").unwrap()).to_canonical_string(),
|
|
|
|
|
"3.12"
|
|
|
|
|