mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-27 18:36:44 +00:00
Add support for requesting pypy interpreters by implementation name (#3706)
Updates our executable name searches to support implementation names i.e. `cpython` and `pypy` and adds support for PyPy. We might want to _not_ support searching for `cpython` because that's non-standard?
This commit is contained in:
parent
84afca2696
commit
1379fb7dcd
3 changed files with 513 additions and 78 deletions
|
|
@ -174,6 +174,7 @@ pub enum Error {
|
||||||
/// [`find_interpreter`] instead.
|
/// [`find_interpreter`] instead.
|
||||||
fn python_executables<'a>(
|
fn python_executables<'a>(
|
||||||
version: Option<&'a VersionRequest>,
|
version: Option<&'a VersionRequest>,
|
||||||
|
implementation: Option<&'a ImplementationName>,
|
||||||
sources: &SourceSelector,
|
sources: &SourceSelector,
|
||||||
) -> impl Iterator<Item = Result<(InterpreterSource, PathBuf), Error>> + 'a {
|
) -> impl Iterator<Item = Result<(InterpreterSource, PathBuf), Error>> + 'a {
|
||||||
// Note we are careful to ensure the iterator chain is lazy to avoid unnecessary work
|
// Note we are careful to ensure the iterator chain is lazy to avoid unnecessary work
|
||||||
|
|
@ -221,7 +222,7 @@ fn python_executables<'a>(
|
||||||
// (4) The search path
|
// (4) The search path
|
||||||
.chain(
|
.chain(
|
||||||
sources.contains(InterpreterSource::SearchPath).then(move ||
|
sources.contains(InterpreterSource::SearchPath).then(move ||
|
||||||
python_executables_from_search_path(version)
|
python_executables_from_search_path(version, implementation)
|
||||||
.map(|path| Ok((InterpreterSource::SearchPath, path))),
|
.map(|path| Ok((InterpreterSource::SearchPath, path))),
|
||||||
).into_iter().flatten()
|
).into_iter().flatten()
|
||||||
)
|
)
|
||||||
|
|
@ -248,21 +249,32 @@ fn python_executables<'a>(
|
||||||
|
|
||||||
/// Lazily iterate over Python executables in the `PATH`.
|
/// Lazily iterate over Python executables in the `PATH`.
|
||||||
///
|
///
|
||||||
/// The [`VersionRequest`] is used to determine the possible Python interpreter names, e.g.
|
/// The [`VersionRequest`] and [`ImplementationName`] are used to determine the possible
|
||||||
/// if looking for Python 3.9 we will look for `python3.9` in addition to the default names.
|
/// Python interpreter names, e.g. if looking for Python 3.9 we will look for `python3.9`
|
||||||
|
/// or if looking for `PyPy` we will look for `pypy` in addition to the default names.
|
||||||
///
|
///
|
||||||
/// Executables are returned in the search path order, then by specificity of the name, e.g.
|
/// Executables are returned in the search path order, then by specificity of the name, e.g.
|
||||||
/// `python3.9` is preferred over `python3`.
|
/// `python3.9` is preferred over `python3` and `pypy3.9` is preferred over `python3.9`.
|
||||||
///
|
///
|
||||||
/// If a `version` is not provided, we will only look for default executable names e.g.
|
/// If a `version` is not provided, we will only look for default executable names e.g.
|
||||||
/// `python3` and `python` — `python3.9` and similar will not be included.
|
/// `python3` and `python` — `python3.9` and similar will not be included.
|
||||||
fn python_executables_from_search_path(
|
fn python_executables_from_search_path<'a>(
|
||||||
version: Option<&VersionRequest>,
|
version: Option<&'a VersionRequest>,
|
||||||
) -> impl Iterator<Item = PathBuf> + '_ {
|
implementation: Option<&'a ImplementationName>,
|
||||||
|
) -> impl Iterator<Item = PathBuf> + 'a {
|
||||||
// `UV_TEST_PYTHON_PATH` can be used to override `PATH` to limit Python executable availability in the test suite
|
// `UV_TEST_PYTHON_PATH` can be used to override `PATH` to limit Python executable availability in the test suite
|
||||||
let search_path =
|
let search_path =
|
||||||
env::var_os("UV_TEST_PYTHON_PATH").unwrap_or(env::var_os("PATH").unwrap_or_default());
|
env::var_os("UV_TEST_PYTHON_PATH").unwrap_or(env::var_os("PATH").unwrap_or_default());
|
||||||
let possible_names = version.unwrap_or(&VersionRequest::Default).possible_names();
|
|
||||||
|
let possible_names: Vec<_> = version
|
||||||
|
.unwrap_or(&VersionRequest::Default)
|
||||||
|
.possible_names(implementation)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
trace!(
|
||||||
|
"Searching PATH for executables: {}",
|
||||||
|
possible_names.join(", ")
|
||||||
|
);
|
||||||
|
|
||||||
// Split and iterate over the paths instead of using `which_all` so we can
|
// Split and iterate over the paths instead of using `which_all` so we can
|
||||||
// check multiple names per directory while respecting the search path order
|
// check multiple names per directory while respecting the search path order
|
||||||
|
|
@ -280,7 +292,6 @@ fn python_executables_from_search_path(
|
||||||
possible_names
|
possible_names
|
||||||
.clone()
|
.clone()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten()
|
|
||||||
.flat_map(move |name| {
|
.flat_map(move |name| {
|
||||||
// Since we're just working with a single directory at a time, we collect to simplify ownership
|
// Since we're just working with a single directory at a time, we collect to simplify ownership
|
||||||
which::which_in_global(&*name, Some(&dir))
|
which::which_in_global(&*name, Some(&dir))
|
||||||
|
|
@ -310,10 +321,11 @@ fn python_executables_from_search_path(
|
||||||
///See [`python_executables`] for more information on discovery.
|
///See [`python_executables`] for more information on discovery.
|
||||||
fn python_interpreters<'a>(
|
fn python_interpreters<'a>(
|
||||||
version: Option<&'a VersionRequest>,
|
version: Option<&'a VersionRequest>,
|
||||||
|
implementation: Option<&'a ImplementationName>,
|
||||||
sources: &SourceSelector,
|
sources: &SourceSelector,
|
||||||
cache: &'a Cache,
|
cache: &'a Cache,
|
||||||
) -> impl Iterator<Item = Result<(InterpreterSource, Interpreter), Error>> + 'a {
|
) -> impl Iterator<Item = Result<(InterpreterSource, Interpreter), Error>> + 'a {
|
||||||
python_executables(version, sources).map(|result| match result {
|
python_executables(version, implementation, sources).map(|result| match result {
|
||||||
Ok((source, path)) => Interpreter::query(&path, cache)
|
Ok((source, path)) => Interpreter::query(&path, cache)
|
||||||
.map(|interpreter| (source, interpreter))
|
.map(|interpreter| (source, interpreter))
|
||||||
.inspect(|(source, interpreter)| {
|
.inspect(|(source, interpreter)| {
|
||||||
|
|
@ -420,17 +432,18 @@ pub fn find_interpreter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InterpreterRequest::Implementation(implementation) => {
|
InterpreterRequest::Implementation(implementation) => {
|
||||||
let Some((source, interpreter)) = python_interpreters(None, sources, cache)
|
let Some((source, interpreter)) =
|
||||||
.find(|result| {
|
python_interpreters(None, Some(implementation), sources, cache)
|
||||||
match result {
|
.find(|result| {
|
||||||
// Return the first critical error or matching interpreter
|
match result {
|
||||||
Err(err) => should_stop_discovery(err),
|
// Return the first critical error or matching interpreter
|
||||||
Ok((_source, interpreter)) => {
|
Err(err) => should_stop_discovery(err),
|
||||||
interpreter.implementation_name() == implementation.as_str()
|
Ok((_source, interpreter)) => {
|
||||||
|
interpreter.implementation_name() == implementation.as_str()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
})
|
.transpose()?
|
||||||
.transpose()?
|
|
||||||
else {
|
else {
|
||||||
return Ok(InterpreterResult::Err(
|
return Ok(InterpreterResult::Err(
|
||||||
InterpreterNotFound::NoMatchingImplementation(sources.clone(), *implementation),
|
InterpreterNotFound::NoMatchingImplementation(sources.clone(), *implementation),
|
||||||
|
|
@ -442,18 +455,19 @@ pub fn find_interpreter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InterpreterRequest::ImplementationVersion(implementation, version) => {
|
InterpreterRequest::ImplementationVersion(implementation, version) => {
|
||||||
let Some((source, interpreter)) = python_interpreters(Some(version), sources, cache)
|
let Some((source, interpreter)) =
|
||||||
.find(|result| {
|
python_interpreters(Some(version), Some(implementation), sources, cache)
|
||||||
match result {
|
.find(|result| {
|
||||||
// Return the first critical error or matching interpreter
|
match result {
|
||||||
Err(err) => should_stop_discovery(err),
|
// Return the first critical error or matching interpreter
|
||||||
Ok((_source, interpreter)) => {
|
Err(err) => should_stop_discovery(err),
|
||||||
version.matches_interpreter(interpreter)
|
Ok((_source, interpreter)) => {
|
||||||
&& interpreter.implementation_name() == implementation.as_str()
|
version.matches_interpreter(interpreter)
|
||||||
|
&& interpreter.implementation_name() == implementation.as_str()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
})
|
.transpose()?
|
||||||
.transpose()?
|
|
||||||
else {
|
else {
|
||||||
// TODO(zanieb): Peek if there are any interpreters with the requested implementation
|
// TODO(zanieb): Peek if there are any interpreters with the requested implementation
|
||||||
// to improve the error message e.g. using `NoMatchingImplementation` instead
|
// to improve the error message e.g. using `NoMatchingImplementation` instead
|
||||||
|
|
@ -471,15 +485,16 @@ pub fn find_interpreter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InterpreterRequest::Version(version) => {
|
InterpreterRequest::Version(version) => {
|
||||||
let Some((source, interpreter)) = python_interpreters(Some(version), sources, cache)
|
let Some((source, interpreter)) =
|
||||||
.find(|result| {
|
python_interpreters(Some(version), None, sources, cache)
|
||||||
match result {
|
.find(|result| {
|
||||||
// Return the first critical error or matching interpreter
|
match result {
|
||||||
Err(err) => should_stop_discovery(err),
|
// Return the first critical error or matching interpreter
|
||||||
Ok((_source, interpreter)) => version.matches_interpreter(interpreter),
|
Err(err) => should_stop_discovery(err),
|
||||||
}
|
Ok((_source, interpreter)) => version.matches_interpreter(interpreter),
|
||||||
})
|
}
|
||||||
.transpose()?
|
})
|
||||||
|
.transpose()?
|
||||||
else {
|
else {
|
||||||
let err = if matches!(version, VersionRequest::Default) {
|
let err = if matches!(version, VersionRequest::Default) {
|
||||||
InterpreterNotFound::NoPythonInstallation(sources.clone(), Some(*version))
|
InterpreterNotFound::NoPythonInstallation(sources.clone(), Some(*version))
|
||||||
|
|
@ -812,7 +827,7 @@ impl InterpreterRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VersionRequest {
|
impl VersionRequest {
|
||||||
pub(crate) fn possible_names(self) -> [Option<Cow<'static, str>>; 4] {
|
pub(crate) fn default_names(self) -> [Option<Cow<'static, str>>; 4] {
|
||||||
let (python, python3, extension) = if cfg!(windows) {
|
let (python, python3, extension) = if cfg!(windows) {
|
||||||
(
|
(
|
||||||
Cow::Borrowed("python.exe"),
|
Cow::Borrowed("python.exe"),
|
||||||
|
|
@ -848,6 +863,52 @@ impl VersionRequest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn possible_names<'a>(
|
||||||
|
&'a self,
|
||||||
|
implementation: Option<&'a ImplementationName>,
|
||||||
|
) -> impl Iterator<Item = Cow<'static, str>> + 'a {
|
||||||
|
implementation
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(move |implementation| {
|
||||||
|
let extension = std::env::consts::EXE_SUFFIX;
|
||||||
|
let name = implementation.as_str();
|
||||||
|
let (python, python3) = if extension.is_empty() {
|
||||||
|
(Cow::Borrowed(name), Cow::Owned(format!("{name}3")))
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
Cow::Owned(format!("{name}{extension}")),
|
||||||
|
Cow::Owned(format!("{name}3{extension}")),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Self::Default => [Some(python3), Some(python), None, None],
|
||||||
|
Self::Major(major) => [
|
||||||
|
Some(Cow::Owned(format!("{name}{major}{extension}"))),
|
||||||
|
Some(python),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
],
|
||||||
|
Self::MajorMinor(major, minor) => [
|
||||||
|
Some(Cow::Owned(format!("{name}{major}.{minor}{extension}"))),
|
||||||
|
Some(Cow::Owned(format!("{name}{major}{extension}"))),
|
||||||
|
Some(python),
|
||||||
|
None,
|
||||||
|
],
|
||||||
|
Self::MajorMinorPatch(major, minor, patch) => [
|
||||||
|
Some(Cow::Owned(format!(
|
||||||
|
"{name}{major}.{minor}.{patch}{extension}",
|
||||||
|
))),
|
||||||
|
Some(Cow::Owned(format!("{name}{major}.{minor}{extension}"))),
|
||||||
|
Some(Cow::Owned(format!("{name}{major}{extension}"))),
|
||||||
|
Some(python),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.chain(self.default_names())
|
||||||
|
.flatten()
|
||||||
|
}
|
||||||
|
|
||||||
/// Check if a interpreter matches the requested Python version.
|
/// Check if a interpreter matches the requested Python version.
|
||||||
fn matches_interpreter(self, interpreter: &Interpreter) -> bool {
|
fn matches_interpreter(self, interpreter: &Interpreter) -> bool {
|
||||||
match self {
|
match self {
|
||||||
|
|
@ -1185,6 +1246,28 @@ mod tests {
|
||||||
VersionRequest::from_str("3.12.2").unwrap()
|
VersionRequest::from_str("3.12.2").unwrap()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
InterpreterRequest::parse("pypy"),
|
||||||
|
InterpreterRequest::Implementation(ImplementationName::PyPy)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
InterpreterRequest::parse("pypy3.10"),
|
||||||
|
InterpreterRequest::ImplementationVersion(
|
||||||
|
ImplementationName::PyPy,
|
||||||
|
VersionRequest::from_str("3.10").unwrap()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
InterpreterRequest::parse("pypy@3.10"),
|
||||||
|
InterpreterRequest::ImplementationVersion(
|
||||||
|
ImplementationName::PyPy,
|
||||||
|
VersionRequest::from_str("3.10").unwrap()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
InterpreterRequest::parse("pypy310"),
|
||||||
|
InterpreterRequest::ExecutableName("pypy310".to_string())
|
||||||
|
);
|
||||||
|
|
||||||
let tempdir = TempDir::new().unwrap();
|
let tempdir = TempDir::new().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
||||||
|
|
@ -10,20 +10,24 @@ pub enum Error {
|
||||||
UnknownImplementation(String),
|
UnknownImplementation(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
#[derive(Debug, Eq, PartialEq, Clone, Copy, Default)]
|
||||||
pub enum ImplementationName {
|
pub enum ImplementationName {
|
||||||
|
#[default]
|
||||||
CPython,
|
CPython,
|
||||||
|
PyPy,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImplementationName {
|
impl ImplementationName {
|
||||||
pub(crate) fn iter() -> impl Iterator<Item = &'static ImplementationName> {
|
pub(crate) fn iter() -> impl Iterator<Item = &'static ImplementationName> {
|
||||||
static NAMES: &[ImplementationName] = &[ImplementationName::CPython];
|
static NAMES: &[ImplementationName] =
|
||||||
|
&[ImplementationName::CPython, ImplementationName::PyPy];
|
||||||
NAMES.iter()
|
NAMES.iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::CPython => "cpython",
|
Self::CPython => "cpython",
|
||||||
|
Self::PyPy => "pypy",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -34,6 +38,7 @@ impl FromStr for ImplementationName {
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
match s.to_ascii_lowercase().as_str() {
|
match s.to_ascii_lowercase().as_str() {
|
||||||
"cpython" => Ok(Self::CPython),
|
"cpython" => Ok(Self::CPython),
|
||||||
|
"pypy" => Ok(Self::PyPy),
|
||||||
_ => Err(Error::UnknownImplementation(s.to_string())),
|
_ => Err(Error::UnknownImplementation(s.to_string())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,7 @@ mod tests {
|
||||||
use crate::{
|
use crate::{
|
||||||
discovery::{self, DiscoveredInterpreter, InterpreterRequest, VersionRequest},
|
discovery::{self, DiscoveredInterpreter, InterpreterRequest, VersionRequest},
|
||||||
find_best_interpreter, find_default_interpreter, find_interpreter,
|
find_best_interpreter, find_default_interpreter, find_interpreter,
|
||||||
|
implementation::ImplementationName,
|
||||||
virtualenv::virtualenv_python_executable,
|
virtualenv::virtualenv_python_executable,
|
||||||
Error, InterpreterNotFound, InterpreterSource, PythonEnvironment, PythonVersion,
|
Error, InterpreterNotFound, InterpreterSource, PythonEnvironment, PythonVersion,
|
||||||
SourceSelector,
|
SourceSelector,
|
||||||
|
|
@ -84,7 +85,11 @@ mod tests {
|
||||||
|
|
||||||
/// Create a fake Python interpreter executable which returns fixed metadata mocking our interpreter
|
/// Create a fake Python interpreter executable which returns fixed metadata mocking our interpreter
|
||||||
/// query script output.
|
/// query script output.
|
||||||
fn create_mock_interpreter(path: &Path, version: &PythonVersion) -> Result<()> {
|
fn create_mock_interpreter(
|
||||||
|
path: &Path,
|
||||||
|
version: &PythonVersion,
|
||||||
|
implementation: ImplementationName,
|
||||||
|
) -> Result<()> {
|
||||||
let json = indoc! {r##"
|
let json = indoc! {r##"
|
||||||
{
|
{
|
||||||
"result": "success",
|
"result": "success",
|
||||||
|
|
@ -97,11 +102,11 @@ mod tests {
|
||||||
"arch": "x86_64"
|
"arch": "x86_64"
|
||||||
},
|
},
|
||||||
"markers": {
|
"markers": {
|
||||||
"implementation_name": "cpython",
|
"implementation_name": "{IMPLEMENTATION}",
|
||||||
"implementation_version": "{FULL_VERSION}",
|
"implementation_version": "{FULL_VERSION}",
|
||||||
"os_name": "posix",
|
"os_name": "posix",
|
||||||
"platform_machine": "x86_64",
|
"platform_machine": "x86_64",
|
||||||
"platform_python_implementation": "CPython",
|
"platform_python_implementation": "{IMPLEMENTATION}",
|
||||||
"platform_release": "6.5.0-13-generic",
|
"platform_release": "6.5.0-13-generic",
|
||||||
"platform_system": "Linux",
|
"platform_system": "Linux",
|
||||||
"platform_version": "#13-Ubuntu SMP PREEMPT_DYNAMIC Fri Nov 3 12:16:05 UTC 2023",
|
"platform_version": "#13-Ubuntu SMP PREEMPT_DYNAMIC Fri Nov 3 12:16:05 UTC 2023",
|
||||||
|
|
@ -138,7 +143,8 @@ mod tests {
|
||||||
"##}
|
"##}
|
||||||
.replace("{PATH}", path.to_str().expect("Path can be represented as string"))
|
.replace("{PATH}", path.to_str().expect("Path can be represented as string"))
|
||||||
.replace("{FULL_VERSION}", &version.to_string())
|
.replace("{FULL_VERSION}", &version.to_string())
|
||||||
.replace("{VERSION}", &version.without_patch().to_string());
|
.replace("{VERSION}", &version.without_patch().to_string())
|
||||||
|
.replace("{IMPLEMENTATION}", implementation.as_str());
|
||||||
|
|
||||||
fs_err::write(
|
fs_err::write(
|
||||||
path,
|
path,
|
||||||
|
|
@ -190,16 +196,31 @@ mod tests {
|
||||||
/// Create fake Python interpreters the given Python versions.
|
/// Create fake Python interpreters the given Python versions.
|
||||||
///
|
///
|
||||||
/// Returns a search path for the mock interpreters.
|
/// Returns a search path for the mock interpreters.
|
||||||
fn mock_interpreters(tempdir: &TempDir, versions: &[&'static str]) -> Result<OsString> {
|
fn simple_mock_interpreters(tempdir: &TempDir, versions: &[&'static str]) -> Result<OsString> {
|
||||||
let names: Vec<OsString> = (0..versions.len())
|
let kinds: Vec<_> = versions
|
||||||
|
.iter()
|
||||||
|
.map(|version| (ImplementationName::default(), "python", *version))
|
||||||
|
.collect();
|
||||||
|
mock_interpreters(tempdir, kinds.as_slice())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create fake Python interpreters the given Python implementations and versions.
|
||||||
|
///
|
||||||
|
/// Returns a search path for the mock interpreters.
|
||||||
|
fn mock_interpreters(
|
||||||
|
tempdir: &TempDir,
|
||||||
|
kinds: &[(ImplementationName, &'static str, &'static str)],
|
||||||
|
) -> Result<OsString> {
|
||||||
|
let names: Vec<OsString> = (0..kinds.len())
|
||||||
.map(|i| OsString::from(i.to_string()))
|
.map(|i| OsString::from(i.to_string()))
|
||||||
.collect();
|
.collect();
|
||||||
let paths = create_children(tempdir, names.as_slice())?;
|
let paths = create_children(tempdir, names.as_slice())?;
|
||||||
for (path, version) in itertools::zip_eq(&paths, versions) {
|
for (path, (implementation, executable, version)) in itertools::zip_eq(&paths, kinds) {
|
||||||
let python = format!("python{}", std::env::consts::EXE_SUFFIX);
|
let python = format!("{executable}{}", std::env::consts::EXE_SUFFIX);
|
||||||
create_mock_interpreter(
|
create_mock_interpreter(
|
||||||
&path.join(python),
|
&path.join(python),
|
||||||
&PythonVersion::from_str(version).unwrap(),
|
&PythonVersion::from_str(version).unwrap(),
|
||||||
|
*implementation,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
Ok(env::join_paths(paths)?)
|
Ok(env::join_paths(paths)?)
|
||||||
|
|
@ -219,6 +240,7 @@ mod tests {
|
||||||
create_mock_interpreter(
|
create_mock_interpreter(
|
||||||
&executable,
|
&executable,
|
||||||
&PythonVersion::from_str(version).expect("A valid Python version is used for tests"),
|
&PythonVersion::from_str(version).expect("A valid Python version is used for tests"),
|
||||||
|
ImplementationName::default(),
|
||||||
)?;
|
)?;
|
||||||
venv.child("pyvenv.cfg").touch()?;
|
venv.child("pyvenv.cfg").touch()?;
|
||||||
Ok(venv.to_path_buf())
|
Ok(venv.to_path_buf())
|
||||||
|
|
@ -300,7 +322,11 @@ mod tests {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
let python = tempdir.child(format!("python{}", std::env::consts::EXE_SUFFIX));
|
let python = tempdir.child(format!("python{}", std::env::consts::EXE_SUFFIX));
|
||||||
create_mock_interpreter(&python, &PythonVersion::from_str("3.12.1").unwrap())?;
|
create_mock_interpreter(
|
||||||
|
&python,
|
||||||
|
&PythonVersion::from_str("3.12.1").unwrap(),
|
||||||
|
ImplementationName::default(),
|
||||||
|
)?;
|
||||||
|
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
|
|
@ -364,7 +390,11 @@ mod tests {
|
||||||
let python = tempdir
|
let python = tempdir
|
||||||
.child("good")
|
.child("good")
|
||||||
.child(format!("python{}", std::env::consts::EXE_SUFFIX));
|
.child(format!("python{}", std::env::consts::EXE_SUFFIX));
|
||||||
create_mock_interpreter(&python, &PythonVersion::from_str("3.12.1").unwrap())?;
|
create_mock_interpreter(
|
||||||
|
&python,
|
||||||
|
&PythonVersion::from_str("3.12.1").unwrap(),
|
||||||
|
ImplementationName::default(),
|
||||||
|
)?;
|
||||||
|
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
|
|
@ -450,7 +480,11 @@ mod tests {
|
||||||
let python3 = tempdir
|
let python3 = tempdir
|
||||||
.child("good")
|
.child("good")
|
||||||
.child(format!("python{}", std::env::consts::EXE_SUFFIX));
|
.child(format!("python{}", std::env::consts::EXE_SUFFIX));
|
||||||
create_mock_interpreter(&python3, &PythonVersion::from_str("3.12.1").unwrap())?;
|
create_mock_interpreter(
|
||||||
|
&python3,
|
||||||
|
&PythonVersion::from_str("3.12.1").unwrap(),
|
||||||
|
ImplementationName::default(),
|
||||||
|
)?;
|
||||||
|
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
|
|
@ -504,7 +538,7 @@ mod tests {
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_BOOTSTRAP_DIR", None),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(mock_interpreters(
|
Some(simple_mock_interpreters(
|
||||||
&tempdir,
|
&tempdir,
|
||||||
&["3.10.1", "3.11.2", "3.12.3"],
|
&["3.10.1", "3.11.2", "3.12.3"],
|
||||||
)?),
|
)?),
|
||||||
|
|
@ -556,7 +590,7 @@ mod tests {
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_BOOTSTRAP_DIR", None),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(mock_interpreters(
|
Some(simple_mock_interpreters(
|
||||||
&tempdir,
|
&tempdir,
|
||||||
&["3.10.1", "3.11.2", "3.12.3"],
|
&["3.10.1", "3.11.2", "3.12.3"],
|
||||||
)?),
|
)?),
|
||||||
|
|
@ -609,7 +643,7 @@ mod tests {
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_BOOTSTRAP_DIR", None),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(mock_interpreters(
|
Some(simple_mock_interpreters(
|
||||||
&tempdir,
|
&tempdir,
|
||||||
&["3.10.1", "3.11.2", "3.12.3"],
|
&["3.10.1", "3.11.2", "3.12.3"],
|
||||||
)?),
|
)?),
|
||||||
|
|
@ -651,7 +685,7 @@ mod tests {
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_BOOTSTRAP_DIR", None),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(mock_interpreters(
|
Some(simple_mock_interpreters(
|
||||||
&tempdir,
|
&tempdir,
|
||||||
&["3.10.1", "3.11.2", "3.12.3"],
|
&["3.10.1", "3.11.2", "3.12.3"],
|
||||||
)?),
|
)?),
|
||||||
|
|
@ -688,7 +722,7 @@ mod tests {
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_BOOTSTRAP_DIR", None),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(mock_interpreters(
|
Some(simple_mock_interpreters(
|
||||||
&tempdir,
|
&tempdir,
|
||||||
&["3.10.1", "3.11.2", "3.11.9"],
|
&["3.10.1", "3.11.2", "3.11.9"],
|
||||||
)?),
|
)?),
|
||||||
|
|
@ -738,7 +772,7 @@ mod tests {
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_BOOTSTRAP_DIR", None),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(mock_interpreters(
|
Some(simple_mock_interpreters(
|
||||||
&tempdir,
|
&tempdir,
|
||||||
&["3.10.1", "3.11.2", "3.12.3"],
|
&["3.10.1", "3.11.2", "3.12.3"],
|
||||||
)?),
|
)?),
|
||||||
|
|
@ -780,7 +814,7 @@ mod tests {
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_BOOTSTRAP_DIR", None),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(mock_interpreters(
|
Some(simple_mock_interpreters(
|
||||||
&tempdir,
|
&tempdir,
|
||||||
&["3.10.1", "3.11.2", "3.11.8", "3.12.3"],
|
&["3.10.1", "3.11.2", "3.11.8", "3.12.3"],
|
||||||
)?),
|
)?),
|
||||||
|
|
@ -834,7 +868,7 @@ mod tests {
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_BOOTSTRAP_DIR", None),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(mock_interpreters(&tempdir, &["3.11.1", "3.12.3"])?),
|
Some(simple_mock_interpreters(&tempdir, &["3.11.1", "3.12.3"])?),
|
||||||
),
|
),
|
||||||
("VIRTUAL_ENV", Some(venv.into())),
|
("VIRTUAL_ENV", Some(venv.into())),
|
||||||
("PWD", Some(tempdir.path().into())),
|
("PWD", Some(tempdir.path().into())),
|
||||||
|
|
@ -883,7 +917,10 @@ mod tests {
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_BOOTSTRAP_DIR", None),
|
||||||
("PATH", Some(mock_interpreters(&tempdir, &["3.10.1"])?)),
|
(
|
||||||
|
"PATH",
|
||||||
|
Some(simple_mock_interpreters(&tempdir, &["3.10.1"])?),
|
||||||
|
),
|
||||||
("VIRTUAL_ENV", Some(venv.into())),
|
("VIRTUAL_ENV", Some(venv.into())),
|
||||||
("PWD", Some(tempdir.path().into())),
|
("PWD", Some(tempdir.path().into())),
|
||||||
],
|
],
|
||||||
|
|
@ -931,7 +968,10 @@ mod tests {
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_BOOTSTRAP_DIR", None),
|
||||||
("PATH", Some(mock_interpreters(&tempdir, &["3.10.3"])?)),
|
(
|
||||||
|
"PATH",
|
||||||
|
Some(simple_mock_interpreters(&tempdir, &["3.10.3"])?),
|
||||||
|
),
|
||||||
("VIRTUAL_ENV", Some(venv.into())),
|
("VIRTUAL_ENV", Some(venv.into())),
|
||||||
("PWD", Some(tempdir.path().into())),
|
("PWD", Some(tempdir.path().into())),
|
||||||
],
|
],
|
||||||
|
|
@ -980,7 +1020,10 @@ mod tests {
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_BOOTSTRAP_DIR", None),
|
||||||
("PATH", Some(mock_interpreters(&tempdir, &["3.11.2"])?)),
|
(
|
||||||
|
"PATH",
|
||||||
|
Some(simple_mock_interpreters(&tempdir, &["3.11.2"])?),
|
||||||
|
),
|
||||||
("VIRTUAL_ENV", Some(venv.clone().into())),
|
("VIRTUAL_ENV", Some(venv.clone().into())),
|
||||||
("PWD", Some(tempdir.path().into())),
|
("PWD", Some(tempdir.path().into())),
|
||||||
],
|
],
|
||||||
|
|
@ -1021,7 +1064,7 @@ mod tests {
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_BOOTSTRAP_DIR", None),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(mock_interpreters(&tempdir, &["3.11.2", "3.10.0"])?),
|
Some(simple_mock_interpreters(&tempdir, &["3.11.2", "3.10.0"])?),
|
||||||
),
|
),
|
||||||
("VIRTUAL_ENV", Some(venv.into())),
|
("VIRTUAL_ENV", Some(venv.into())),
|
||||||
("PWD", Some(tempdir.path().into())),
|
("PWD", Some(tempdir.path().into())),
|
||||||
|
|
@ -1072,7 +1115,7 @@ mod tests {
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_BOOTSTRAP_DIR", None),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(mock_interpreters(&tempdir, &["3.10.1", "3.11.2"])?),
|
Some(simple_mock_interpreters(&tempdir, &["3.10.1", "3.11.2"])?),
|
||||||
),
|
),
|
||||||
("VIRTUAL_ENV", Some(venv.into())),
|
("VIRTUAL_ENV", Some(venv.into())),
|
||||||
("PWD", Some(tempdir.path().into())),
|
("PWD", Some(tempdir.path().into())),
|
||||||
|
|
@ -1105,7 +1148,7 @@ mod tests {
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_BOOTSTRAP_DIR", None),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(mock_interpreters(&tempdir, &["3.10.1", "3.11.2"])?),
|
Some(simple_mock_interpreters(&tempdir, &["3.10.1", "3.11.2"])?),
|
||||||
),
|
),
|
||||||
("PWD", Some(tempdir.path().into())),
|
("PWD", Some(tempdir.path().into())),
|
||||||
],
|
],
|
||||||
|
|
@ -1138,7 +1181,7 @@ mod tests {
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_BOOTSTRAP_DIR", None),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(mock_interpreters(&tempdir, &["3.10.1", "3.11.2"])?),
|
Some(simple_mock_interpreters(&tempdir, &["3.10.1", "3.11.2"])?),
|
||||||
),
|
),
|
||||||
("VIRTUAL_ENV", Some(venv.clone().into())),
|
("VIRTUAL_ENV", Some(venv.clone().into())),
|
||||||
],
|
],
|
||||||
|
|
@ -1161,7 +1204,7 @@ mod tests {
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_BOOTSTRAP_DIR", None),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(mock_interpreters(&tempdir, &["3.10.1", "3.12.2"])?),
|
Some(simple_mock_interpreters(&tempdir, &["3.10.1", "3.12.2"])?),
|
||||||
),
|
),
|
||||||
("VIRTUAL_ENV", Some(venv.clone().into())),
|
("VIRTUAL_ENV", Some(venv.clone().into())),
|
||||||
],
|
],
|
||||||
|
|
@ -1184,7 +1227,7 @@ mod tests {
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_BOOTSTRAP_DIR", None),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(mock_interpreters(&tempdir, &["3.10.1", "3.12.2"])?),
|
Some(simple_mock_interpreters(&tempdir, &["3.10.1", "3.12.2"])?),
|
||||||
),
|
),
|
||||||
("VIRTUAL_ENV", Some(venv.clone().into())),
|
("VIRTUAL_ENV", Some(venv.clone().into())),
|
||||||
("PWD", Some(tempdir.path().into())),
|
("PWD", Some(tempdir.path().into())),
|
||||||
|
|
@ -1213,7 +1256,7 @@ mod tests {
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_BOOTSTRAP_DIR", None),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(mock_interpreters(&tempdir, &["3.10.1", "3.11.2"])?),
|
Some(simple_mock_interpreters(&tempdir, &["3.10.1", "3.11.2"])?),
|
||||||
),
|
),
|
||||||
("PWD", Some(tempdir.path().into())),
|
("PWD", Some(tempdir.path().into())),
|
||||||
],
|
],
|
||||||
|
|
@ -1234,7 +1277,11 @@ mod tests {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
let python = tempdir.join("foobar");
|
let python = tempdir.join("foobar");
|
||||||
create_mock_interpreter(&python, &PythonVersion::from_str("3.10.0").unwrap())?;
|
create_mock_interpreter(
|
||||||
|
&python,
|
||||||
|
&PythonVersion::from_str("3.10.0").unwrap(),
|
||||||
|
ImplementationName::default(),
|
||||||
|
)?;
|
||||||
|
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
|
|
@ -1263,7 +1310,11 @@ mod tests {
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
tempdir.child("foo").create_dir_all()?;
|
tempdir.child("foo").create_dir_all()?;
|
||||||
let python = tempdir.child("foo").join("bar");
|
let python = tempdir.child("foo").join("bar");
|
||||||
create_mock_interpreter(&python, &PythonVersion::from_str("3.10.0").unwrap())?;
|
create_mock_interpreter(
|
||||||
|
&python,
|
||||||
|
&PythonVersion::from_str("3.10.0").unwrap(),
|
||||||
|
ImplementationName::default(),
|
||||||
|
)?;
|
||||||
|
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
|
|
@ -1295,7 +1346,11 @@ mod tests {
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
tempdir.child("foo").create_dir_all()?;
|
tempdir.child("foo").create_dir_all()?;
|
||||||
let python = tempdir.child("foo").join("bar");
|
let python = tempdir.child("foo").join("bar");
|
||||||
create_mock_interpreter(&python, &PythonVersion::from_str("3.10.0").unwrap())?;
|
create_mock_interpreter(
|
||||||
|
&python,
|
||||||
|
&PythonVersion::from_str("3.10.0").unwrap(),
|
||||||
|
ImplementationName::default(),
|
||||||
|
)?;
|
||||||
|
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
|
|
@ -1359,7 +1414,11 @@ mod tests {
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
tempdir.child("foo").create_dir_all()?;
|
tempdir.child("foo").create_dir_all()?;
|
||||||
let python = tempdir.child("foo").join("bar");
|
let python = tempdir.child("foo").join("bar");
|
||||||
create_mock_interpreter(&python, &PythonVersion::from_str("3.10.0").unwrap())?;
|
create_mock_interpreter(
|
||||||
|
&python,
|
||||||
|
&PythonVersion::from_str("3.10.0").unwrap(),
|
||||||
|
ImplementationName::default(),
|
||||||
|
)?;
|
||||||
|
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
|
|
@ -1428,7 +1487,11 @@ mod tests {
|
||||||
pwd.create_dir_all()?;
|
pwd.create_dir_all()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
let python = tempdir.join("foobar");
|
let python = tempdir.join("foobar");
|
||||||
create_mock_interpreter(&python, &PythonVersion::from_str("3.10.0").unwrap())?;
|
create_mock_interpreter(
|
||||||
|
&python,
|
||||||
|
&PythonVersion::from_str("3.10.0").unwrap(),
|
||||||
|
ImplementationName::default(),
|
||||||
|
)?;
|
||||||
|
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
|
|
@ -1450,4 +1513,288 @@ mod tests {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn find_environment_pypy() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let cache = Cache::temp()?;
|
||||||
|
|
||||||
|
with_vars(
|
||||||
|
[
|
||||||
|
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
||||||
|
(
|
||||||
|
"PATH",
|
||||||
|
Some(mock_interpreters(
|
||||||
|
&tempdir,
|
||||||
|
&[(ImplementationName::PyPy, "pypy", "3.10.1")],
|
||||||
|
)?),
|
||||||
|
),
|
||||||
|
("PWD", Some(tempdir.path().into())),
|
||||||
|
],
|
||||||
|
|| {
|
||||||
|
let environment =
|
||||||
|
PythonEnvironment::find(Some("pypy"), crate::SystemPython::Allowed, &cache)
|
||||||
|
.expect("Environment should be found");
|
||||||
|
assert_eq!(
|
||||||
|
environment.interpreter().python_full_version().to_string(),
|
||||||
|
"3.10.1",
|
||||||
|
"We should find the pypy interpreter"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn find_environment_pypy_request_ignores_cpython() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let cache = Cache::temp()?;
|
||||||
|
|
||||||
|
with_vars(
|
||||||
|
[
|
||||||
|
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
||||||
|
(
|
||||||
|
"PATH",
|
||||||
|
Some(mock_interpreters(
|
||||||
|
&tempdir,
|
||||||
|
&[
|
||||||
|
(ImplementationName::CPython, "python", "3.10.0"),
|
||||||
|
(ImplementationName::PyPy, "pypy", "3.10.1"),
|
||||||
|
],
|
||||||
|
)?),
|
||||||
|
),
|
||||||
|
("PWD", Some(tempdir.path().into())),
|
||||||
|
],
|
||||||
|
|| {
|
||||||
|
let environment =
|
||||||
|
PythonEnvironment::find(Some("pypy"), crate::SystemPython::Allowed, &cache)
|
||||||
|
.expect("Environment should be found");
|
||||||
|
assert_eq!(
|
||||||
|
environment.interpreter().python_full_version().to_string(),
|
||||||
|
"3.10.1",
|
||||||
|
"We should skip the CPython interpreter"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn find_environment_pypy_request_skips_wrong_versions() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let cache = Cache::temp()?;
|
||||||
|
|
||||||
|
// We should prefer the `pypy` executable with the requested version
|
||||||
|
with_vars(
|
||||||
|
[
|
||||||
|
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
||||||
|
(
|
||||||
|
"PATH",
|
||||||
|
Some(mock_interpreters(
|
||||||
|
&tempdir,
|
||||||
|
&[
|
||||||
|
(ImplementationName::PyPy, "pypy", "3.9"),
|
||||||
|
(ImplementationName::PyPy, "pypy", "3.10.1"),
|
||||||
|
],
|
||||||
|
)?),
|
||||||
|
),
|
||||||
|
("PWD", Some(tempdir.path().into())),
|
||||||
|
],
|
||||||
|
|| {
|
||||||
|
let environment =
|
||||||
|
PythonEnvironment::find(Some("pypy3.10"), crate::SystemPython::Allowed, &cache)
|
||||||
|
.expect("Environment should be found");
|
||||||
|
assert_eq!(
|
||||||
|
environment.interpreter().python_full_version().to_string(),
|
||||||
|
"3.10.1",
|
||||||
|
"We should skip the first interpreter"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn find_environment_pypy_finds_executable_with_version_name() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let cache = Cache::temp()?;
|
||||||
|
|
||||||
|
// We should find executables that include the version number
|
||||||
|
with_vars(
|
||||||
|
[
|
||||||
|
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
||||||
|
(
|
||||||
|
"PATH",
|
||||||
|
Some(mock_interpreters(
|
||||||
|
&tempdir,
|
||||||
|
&[
|
||||||
|
(ImplementationName::PyPy, "pypy3.9", "3.10.0"), // We don't consider this one because of the executable name
|
||||||
|
(ImplementationName::PyPy, "pypy3.10", "3.10.1"),
|
||||||
|
(ImplementationName::PyPy, "pypy", "3.10.2"),
|
||||||
|
],
|
||||||
|
)?),
|
||||||
|
),
|
||||||
|
("PWD", Some(tempdir.path().into())),
|
||||||
|
],
|
||||||
|
|| {
|
||||||
|
let environment = PythonEnvironment::find(
|
||||||
|
Some("pypy@3.10"),
|
||||||
|
crate::SystemPython::Allowed,
|
||||||
|
&cache,
|
||||||
|
)
|
||||||
|
.expect("Environment should be found");
|
||||||
|
assert_eq!(
|
||||||
|
environment.interpreter().python_full_version().to_string(),
|
||||||
|
"3.10.1",
|
||||||
|
"We should find the one with the requested version"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn find_environment_pypy_prefers_executable_with_implementation_name() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let cache = Cache::temp()?;
|
||||||
|
|
||||||
|
// We should prefer `pypy` executables over `python` executables even if they are both pypy
|
||||||
|
create_mock_interpreter(
|
||||||
|
&tempdir.path().join("python"),
|
||||||
|
&PythonVersion::from_str("3.10.0").unwrap(),
|
||||||
|
ImplementationName::PyPy,
|
||||||
|
)?;
|
||||||
|
create_mock_interpreter(
|
||||||
|
&tempdir.path().join("pypy"),
|
||||||
|
&PythonVersion::from_str("3.10.1").unwrap(),
|
||||||
|
ImplementationName::PyPy,
|
||||||
|
)?;
|
||||||
|
with_vars(
|
||||||
|
[
|
||||||
|
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
||||||
|
("PATH", Some(tempdir.path().into())),
|
||||||
|
("PWD", Some(tempdir.path().into())),
|
||||||
|
],
|
||||||
|
|| {
|
||||||
|
let environment = PythonEnvironment::find(
|
||||||
|
Some("pypy@3.10"),
|
||||||
|
crate::SystemPython::Allowed,
|
||||||
|
&cache,
|
||||||
|
)
|
||||||
|
.expect("Environment should be found");
|
||||||
|
assert_eq!(
|
||||||
|
environment.interpreter().python_full_version().to_string(),
|
||||||
|
"3.10.1",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// But we should not prefer `pypy` executables over `python` executables that
|
||||||
|
// appear earlier in the search path
|
||||||
|
with_vars(
|
||||||
|
[
|
||||||
|
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
||||||
|
(
|
||||||
|
"PATH",
|
||||||
|
Some(mock_interpreters(
|
||||||
|
&tempdir,
|
||||||
|
&[
|
||||||
|
(ImplementationName::PyPy, "python", "3.10.0"),
|
||||||
|
(ImplementationName::PyPy, "pypy", "3.10.1"),
|
||||||
|
],
|
||||||
|
)?),
|
||||||
|
),
|
||||||
|
("PWD", Some(tempdir.path().into())),
|
||||||
|
],
|
||||||
|
|| {
|
||||||
|
let environment = PythonEnvironment::find(
|
||||||
|
Some("pypy@3.10"),
|
||||||
|
crate::SystemPython::Allowed,
|
||||||
|
&cache,
|
||||||
|
)
|
||||||
|
.expect("Environment should be found");
|
||||||
|
assert_eq!(
|
||||||
|
environment.interpreter().python_full_version().to_string(),
|
||||||
|
"3.10.0",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn find_environment_pypy_prefers_executable_with_version() -> Result<()> {
|
||||||
|
let cache = Cache::temp()?;
|
||||||
|
|
||||||
|
// We should prefer executables with the version number over those with implementation names
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
create_mock_interpreter(
|
||||||
|
&tempdir.path().join("pypy3.10"),
|
||||||
|
&PythonVersion::from_str("3.10.0").unwrap(),
|
||||||
|
ImplementationName::PyPy,
|
||||||
|
)?;
|
||||||
|
create_mock_interpreter(
|
||||||
|
&tempdir.path().join("pypy"),
|
||||||
|
&PythonVersion::from_str("3.10.1").unwrap(),
|
||||||
|
ImplementationName::PyPy,
|
||||||
|
)?;
|
||||||
|
with_vars(
|
||||||
|
[
|
||||||
|
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
||||||
|
("PATH", Some(tempdir.path().into())),
|
||||||
|
("PWD", Some(tempdir.path().into())),
|
||||||
|
],
|
||||||
|
|| {
|
||||||
|
let environment = PythonEnvironment::find(
|
||||||
|
Some("pypy@3.10"),
|
||||||
|
crate::SystemPython::Allowed,
|
||||||
|
&cache,
|
||||||
|
)
|
||||||
|
.expect("Environment should be found");
|
||||||
|
assert_eq!(
|
||||||
|
environment.interpreter().python_full_version().to_string(),
|
||||||
|
"3.10.0",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// But we'll prefer an implementation name executable over a generic name with a version
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
create_mock_interpreter(
|
||||||
|
&tempdir.path().join("python3.10"),
|
||||||
|
&PythonVersion::from_str("3.10.0").unwrap(),
|
||||||
|
ImplementationName::PyPy,
|
||||||
|
)?;
|
||||||
|
create_mock_interpreter(
|
||||||
|
&tempdir.path().join("pypy"),
|
||||||
|
&PythonVersion::from_str("3.10.1").unwrap(),
|
||||||
|
ImplementationName::PyPy,
|
||||||
|
)?;
|
||||||
|
with_vars(
|
||||||
|
[
|
||||||
|
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
||||||
|
("PATH", Some(tempdir.path().into())),
|
||||||
|
("PWD", Some(tempdir.path().into())),
|
||||||
|
],
|
||||||
|
|| {
|
||||||
|
let environment = PythonEnvironment::find(
|
||||||
|
Some("pypy@3.10"),
|
||||||
|
crate::SystemPython::Allowed,
|
||||||
|
&cache,
|
||||||
|
)
|
||||||
|
.expect("Environment should be found");
|
||||||
|
assert_eq!(
|
||||||
|
environment.interpreter().python_full_version().to_string(),
|
||||||
|
"3.10.1",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue