Commit c7342e45 by Sébastien Eustace

Fix executables discovery for generic environments

parent d526348c
...@@ -1042,10 +1042,11 @@ class EnvManager: ...@@ -1042,10 +1042,11 @@ class EnvManager:
(e.g. plugin installation or self update). (e.g. plugin installation or self update).
""" """
prefix, base_prefix = Path(sys.prefix), Path(cls.get_base_prefix()) prefix, base_prefix = Path(sys.prefix), Path(cls.get_base_prefix())
env = SystemEnv(prefix)
if not naive: if not naive:
if prefix.joinpath("poetry_env").exists(): if prefix.joinpath("poetry_env").exists():
return GenericEnv(base_prefix) env = GenericEnv(base_prefix, child_env=env)
else:
from poetry.locations import data_dir from poetry.locations import data_dir
try: try:
...@@ -1053,9 +1054,9 @@ class EnvManager: ...@@ -1053,9 +1054,9 @@ class EnvManager:
except ValueError: except ValueError:
pass pass
else: else:
return GenericEnv(base_prefix) env = GenericEnv(base_prefix, child_env=env)
return SystemEnv(prefix) return env
@classmethod @classmethod
def get_base_prefix(cls) -> Path: def get_base_prefix(cls) -> Path:
...@@ -1093,29 +1094,10 @@ class Env: ...@@ -1093,29 +1094,10 @@ class Env:
self._path = path self._path = path
self._bin_dir = self._path / bin_dir self._bin_dir = self._path / bin_dir
try: self._executable = "python"
python_executables = sorted( self._pip_executable = "pip"
[
p.name
for p in self._bin_dir.glob("python*")
if re.match(r"python(?:\d+(?:\.\d+)?)?(?:\.exe)?$", p.name)
]
)
self._executable = python_executables[0].rstrip(".exe")
except IndexError:
self._executable = "python" + (".exe" if self._is_windows else "")
try: self.find_executables()
pip_executables = sorted(
[
p.name
for p in self._bin_dir.glob("pip*")
if re.match(r"pip(?:\d+(?:\.\d+)?)?(?:\.exe)?$", p.name)
]
)
self._pip_executable = pip_executables[0].rstrip(".exe")
except IndexError:
self._pip_executable = "pip" + (".exe" if self._is_windows else "")
self._base = base or path self._base = base or path
...@@ -1160,6 +1142,39 @@ class Env: ...@@ -1160,6 +1142,39 @@ class Env:
return self._marker_env return self._marker_env
@property
def parent_env(self) -> "GenericEnv":
return GenericEnv(self.base, child_env=self)
def find_executables(self) -> None:
python_executables = sorted(
[
p.name
for p in self._bin_dir.glob("python*")
if re.match(r"python(?:\d+(?:\.\d+)?)?(?:\.exe)?$", p.name)
]
)
if python_executables:
executable = python_executables[0]
if executable.endswith(".exe"):
executable = executable[:-4]
self._executable = executable
pip_executables = sorted(
[
p.name
for p in self._bin_dir.glob("pip*")
if re.match(r"pip(?:\d+(?:\.\d+)?)?(?:\.exe)?$", p.name)
]
)
if pip_executables:
pip_executable = pip_executables[0]
if pip_executable.endswith(".exe"):
pip_executable = pip_executable[:-4]
self._pip_executable = pip_executable
def get_embedded_wheel(self, distribution): def get_embedded_wheel(self, distribution):
return get_embed_wheel( return get_embed_wheel(
distribution, "{}.{}".format(self.version_info[0], self.version_info[1]) distribution, "{}.{}".format(self.version_info[0], self.version_info[1])
...@@ -1390,7 +1405,11 @@ class Env: ...@@ -1390,7 +1405,11 @@ class Env:
""" """
Return path to the given executable. Return path to the given executable.
""" """
bin_path = (self._bin_dir / bin).with_suffix(".exe" if self._is_windows else "") if self._is_windows and not bin.endswith(".exe"):
bin_path = self._bin_dir / (bin + ".exe")
else:
bin_path = self._bin_dir / bin
if not bin_path.exists(): if not bin_path.exists():
# On Windows, some executables can be in the base path # On Windows, some executables can be in the base path
# This is especially true when installing Python with # This is especially true when installing Python with
...@@ -1401,7 +1420,11 @@ class Env: ...@@ -1401,7 +1420,11 @@ class Env:
# that creates a fake virtual environment pointing to # that creates a fake virtual environment pointing to
# a base Python install. # a base Python install.
if self._is_windows: if self._is_windows:
bin_path = (self._path / bin).with_suffix(".exe") if not bin.endswith(".exe"):
bin_path = self._bin_dir / (bin + ".exe")
else:
bin_path = self._path / bin
if bin_path.exists(): if bin_path.exists():
return str(bin_path) return str(bin_path)
...@@ -1649,6 +1672,70 @@ class VirtualEnv(Env): ...@@ -1649,6 +1672,70 @@ class VirtualEnv(Env):
class GenericEnv(VirtualEnv): class GenericEnv(VirtualEnv):
def __init__(
self, path: Path, base: Optional[Path] = None, child_env: Optional["Env"] = None
) -> None:
self._child_env = child_env
super().__init__(path, base=base)
def find_executables(self) -> None:
patterns = [("python*", "pip*")]
if self._child_env:
minor_version = "{}.{}".format(
self._child_env.version_info[0], self._child_env.version_info[1]
)
major_version = "{}".format(self._child_env.version_info[0])
patterns = [
("python{}".format(minor_version), "pip{}".format(minor_version)),
("python{}".format(major_version), "pip{}".format(major_version)),
]
python_executable = None
pip_executable = None
for python_pattern, pip_pattern in patterns:
if python_executable and pip_executable:
break
if not python_executable:
python_executables = sorted(
[
p.name
for p in self._bin_dir.glob(python_pattern)
if re.match(r"python(?:\d+(?:\.\d+)?)?(?:\.exe)?$", p.name)
]
)
if python_executables:
executable = python_executables[0]
if executable.endswith(".exe"):
executable = executable[:-4]
python_executable = executable
if not pip_executable:
pip_executables = sorted(
[
p.name
for p in self._bin_dir.glob(pip_pattern)
if re.match(r"pip(?:\d+(?:\.\d+)?)?(?:\.exe)?$", p.name)
]
)
if pip_executables:
pip_executable = pip_executables[0]
if pip_executable.endswith(".exe"):
pip_executable = pip_executable[:-4]
pip_executable = pip_executable
if python_executable:
self._executable = python_executable
if pip_executable:
self._pip_executable = pip_executable
def get_paths(self) -> Dict[str, str]: def get_paths(self) -> Dict[str, str]:
output = self.run_python_script(GET_PATHS_FOR_GENERIC_ENVS) output = self.run_python_script(GET_PATHS_FOR_GENERIC_ENVS)
......
...@@ -19,6 +19,7 @@ from poetry.utils._compat import WINDOWS ...@@ -19,6 +19,7 @@ from poetry.utils._compat import WINDOWS
from poetry.utils.env import GET_BASE_PREFIX from poetry.utils.env import GET_BASE_PREFIX
from poetry.utils.env import EnvCommandError from poetry.utils.env import EnvCommandError
from poetry.utils.env import EnvManager from poetry.utils.env import EnvManager
from poetry.utils.env import GenericEnv
from poetry.utils.env import NoCompatiblePythonVersionFound from poetry.utils.env import NoCompatiblePythonVersionFound
from poetry.utils.env import SystemEnv from poetry.utils.env import SystemEnv
from poetry.utils.env import VirtualEnv from poetry.utils.env import VirtualEnv
...@@ -1001,3 +1002,84 @@ def test_env_finds_the_correct_executables(tmp_dir, manager): ...@@ -1001,3 +1002,84 @@ def test_env_finds_the_correct_executables(tmp_dir, manager):
assert Path(venv.python).name == expected_executable assert Path(venv.python).name == expected_executable
assert Path(venv.pip).name == expected_pip_executable assert Path(venv.pip).name == expected_pip_executable
def test_env_finds_the_correct_executables_for_generic_env(tmp_dir, manager):
venv_path = Path(tmp_dir) / "Virtual Env"
child_venv_path = Path(tmp_dir) / "Child Virtual Env"
manager.build_venv(str(venv_path), with_pip=True)
parent_venv = VirtualEnv(venv_path)
manager.build_venv(
str(child_venv_path), executable=parent_venv.python, with_pip=True
)
venv = GenericEnv(parent_venv.path, child_env=VirtualEnv(child_venv_path))
expected_executable = "python{}.{}{}".format(
sys.version_info[0], sys.version_info[1], ".exe" if WINDOWS else ""
)
expected_pip_executable = "pip{}.{}{}".format(
sys.version_info[0], sys.version_info[1], ".exe" if WINDOWS else ""
)
assert Path(venv.python).name == expected_executable
assert Path(venv.pip).name == expected_pip_executable
def test_env_finds_fallback_executables_for_generic_env(tmp_dir, manager):
venv_path = Path(tmp_dir) / "Virtual Env"
child_venv_path = Path(tmp_dir) / "Child Virtual Env"
manager.build_venv(str(venv_path), with_pip=True)
parent_venv = VirtualEnv(venv_path)
manager.build_venv(
str(child_venv_path), executable=parent_venv.python, with_pip=True
)
venv = GenericEnv(parent_venv.path, child_env=VirtualEnv(child_venv_path))
default_executable = "python" + (".exe" if WINDOWS else "")
major_executable = "python{}{}".format(
sys.version_info[0], ".exe" if WINDOWS else ""
)
minor_executable = "python{}.{}{}".format(
sys.version_info[0], sys.version_info[1], ".exe" if WINDOWS else ""
)
expected_executable = minor_executable
if (
venv._bin_dir.joinpath(expected_executable).exists()
and venv._bin_dir.joinpath(major_executable).exists()
):
venv._bin_dir.joinpath(expected_executable).unlink()
expected_executable = major_executable
if (
venv._bin_dir.joinpath(expected_executable).exists()
and venv._bin_dir.joinpath(default_executable).exists()
):
venv._bin_dir.joinpath(expected_executable).unlink()
expected_executable = default_executable
default_pip_executable = "pip" + (".exe" if WINDOWS else "")
major_pip_executable = "pip{}{}".format(
sys.version_info[0], ".exe" if WINDOWS else ""
)
minor_pip_executable = "pip{}.{}{}".format(
sys.version_info[0], sys.version_info[1], ".exe" if WINDOWS else ""
)
expected_pip_executable = minor_pip_executable
if (
venv._bin_dir.joinpath(expected_pip_executable).exists()
and venv._bin_dir.joinpath(major_pip_executable).exists()
):
venv._bin_dir.joinpath(expected_pip_executable).unlink()
expected_pip_executable = major_pip_executable
if (
venv._bin_dir.joinpath(expected_pip_executable).exists()
and venv._bin_dir.joinpath(default_pip_executable).exists()
):
venv._bin_dir.joinpath(expected_pip_executable).unlink()
expected_pip_executable = default_pip_executable
venv = GenericEnv(parent_venv.path, child_env=VirtualEnv(child_venv_path))
assert Path(venv.python).name == expected_executable
assert Path(venv.pip).name == expected_pip_executable
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment