Skip to content

Commit

Permalink
Merge pull request #4433 from python-poetry/revert-system-env-changes
Browse files Browse the repository at this point in the history
Fix the detection of the system environment with custom installer
  • Loading branch information
sdispater authored Sep 13, 2021
2 parents c320955 + 96bc4ee commit 61301a1
Show file tree
Hide file tree
Showing 5 changed files with 345 additions and 21 deletions.
4 changes: 4 additions & 0 deletions install-poetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,10 @@ def make_env(self, version: str) -> Path:

virtualenv.cli_run([str(env_path), "--clear"])

# We add a special file so that Poetry can detect
# its own virtual environment
env_path.joinpath("poetry_env").touch()

return env_path

def make_bin(self, version: str) -> None:
Expand Down
14 changes: 11 additions & 3 deletions poetry/console/commands/env/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ def _display_complete_info(self, env: "Env") -> None:
"<info>Path</info>: <comment>{}</>".format(
env.path if env.is_venv() else "NA"
),
"<info>Executable</info>: <comment>{}</>".format(
env.python if env.is_venv() else "NA"
),
]
if env.is_venv():
listing.append(
Expand All @@ -55,13 +58,18 @@ def _display_complete_info(self, env: "Env") -> None:

self.line("")

system_env = env.parent_env
self.line("<b>System</b>")
self.line(
"\n".join(
[
"<info>Platform</info>: <comment>{}</>".format(env.platform),
"<info>OS</info>: <comment>{}</>".format(env.os),
"<info>Python</info>: <comment>{}</>".format(env.base),
"<info>Platform</info>: <comment>{}</>".format(env.platform),
"<info>OS</info>: <comment>{}</>".format(env.os),
"<info>Python</info>: <comment>{}</>".format(
".".join(str(v) for v in system_env.version_info[:3])
),
"<info>Path</info>: <comment>{}</>".format(system_env.path),
"<info>Executable</info>: <comment>{}</>".format(system_env.python),
]
)
)
202 changes: 188 additions & 14 deletions poetry/utils/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,38 @@ def _version_nodot(version):
print(json.dumps(sysconfig.get_paths()))
"""

GET_PATHS_FOR_GENERIC_ENVS = """\
# We can't use sysconfig.get_paths() because
# on some distributions it does not return the proper paths
# (those used by pip for instance). We go through distutils
# to get the proper ones.
import json
import site
import sysconfig
from distutils.command.install import SCHEME_KEYS # noqa
from distutils.core import Distribution
d = Distribution()
d.parse_config_files()
obj = d.get_command_obj("install", create=True)
obj.finalize_options()
paths = sysconfig.get_paths().copy()
for key in SCHEME_KEYS:
if key == "headers":
# headers is not a path returned by sysconfig.get_paths()
continue
paths[key] = getattr(obj, f"install_{key}")
if site.check_enableusersite() and hasattr(obj, "install_usersite"):
paths["usersite"] = getattr(obj, "install_usersite")
paths["userbase"] = getattr(obj, "install_userbase")
print(json.dumps(paths))
"""


class SitePackages:
def __init__(
Expand Down Expand Up @@ -730,7 +762,7 @@ def remove(self, python: str) -> "Env":

self.remove_venv(venv)

return VirtualEnv(venv)
return VirtualEnv(venv, venv)

def create_venv(
self,
Expand Down Expand Up @@ -1010,15 +1042,21 @@ def get_system_env(cls, naive: bool = False) -> Union["SystemEnv", "GenericEnv"]
(e.g. plugin installation or self update).
"""
prefix, base_prefix = Path(sys.prefix), Path(cls.get_base_prefix())
env = SystemEnv(prefix)
if not naive:
try:
Path(__file__).relative_to(prefix)
except ValueError:
pass
if prefix.joinpath("poetry_env").exists():
env = GenericEnv(base_prefix, child_env=env)
else:
return GenericEnv(base_prefix)
from poetry.locations import data_dir

return SystemEnv(prefix)
try:
prefix.relative_to(data_dir())
except ValueError:
pass
else:
env = GenericEnv(base_prefix, child_env=env)

return env

@classmethod
def get_base_prefix(cls) -> Path:
Expand Down Expand Up @@ -1056,6 +1094,11 @@ def __init__(self, path: Path, base: Optional[Path] = None) -> None:
self._path = path
self._bin_dir = self._path / bin_dir

self._executable = "python"
self._pip_executable = "pip"

self.find_executables()

self._base = base or path

self._marker_env = None
Expand Down Expand Up @@ -1090,7 +1133,7 @@ def python(self) -> str:
"""
Path to current python executable
"""
return self._bin("python")
return self._bin(self._executable)

@property
def marker_env(self) -> Dict[str, Any]:
Expand All @@ -1099,6 +1142,39 @@ def marker_env(self) -> Dict[str, Any]:

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):
return get_embed_wheel(
distribution, "{}.{}".format(self.version_info[0], self.version_info[1])
Expand All @@ -1116,7 +1192,7 @@ def pip(self) -> str:
Path to current pip executable
"""
# we do not use as_posix() here due to issues with windows pathlib2 implementation
path = self._bin("pip")
path = self._bin(self._pip_executable)
if not Path(path).exists():
return str(self.pip_embedded)
return path
Expand Down Expand Up @@ -1262,7 +1338,7 @@ def run_pip(self, *args: str, **kwargs: Any) -> Union[int, str]:
return self._run(cmd, **kwargs)

def run_python_script(self, content: str, **kwargs: Any) -> str:
return self.run("python", "-W", "ignore", "-", input_=content, **kwargs)
return self.run(self._executable, "-W", "ignore", "-", input_=content, **kwargs)

def _run(self, cmd: List[str], **kwargs: Any) -> Union[int, str]:
"""
Expand Down Expand Up @@ -1329,7 +1405,11 @@ def _bin(self, bin: str) -> str:
"""
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():
# On Windows, some executables can be in the base path
# This is especially true when installing Python with
Expand All @@ -1340,7 +1420,11 @@ def _bin(self, bin: str) -> str:
# that creates a fake virtual environment pointing to
# a base Python install.
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():
return str(bin_path)

Expand Down Expand Up @@ -1484,7 +1568,10 @@ def get_python_implementation(self) -> str:
def get_pip_command(self, embedded: bool = False) -> List[str]:
# We're in a virtualenv that is known to be sane,
# so assume that we have a functional pip
return [self._bin("python"), self.pip_embedded if embedded else self.pip]
return [
self._bin(self._executable),
self.pip_embedded if embedded else self.pip,
]

def get_supported_tags(self) -> List[Tag]:
file_path = Path(packaging.tags.__file__)
Expand Down Expand Up @@ -1585,6 +1672,90 @@ def _updated_path(self) -> str:


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]:
output = self.run_python_script(GET_PATHS_FOR_GENERIC_ENVS)

return json.loads(output)

def execute(self, bin: str, *args: str, **kwargs: Any) -> Optional[int]:
command = self.get_command_from_bin(bin) + list(args)
env = kwargs.pop("env", {k: v for k, v in os.environ.items()})

if not self._is_windows:
return os.execvpe(command[0], command, env=env)
else:
exe = subprocess.Popen([command[0]] + command[1:], env=env, **kwargs)
exe.communicate()

return exe.returncode

def _run(self, cmd: List[str], **kwargs: Any) -> Optional[int]:
return super(VirtualEnv, self)._run(cmd, **kwargs)

def is_venv(self) -> bool:
return self._path != self._base

Expand All @@ -1602,7 +1773,10 @@ def __init__(
self.executed = []

def get_pip_command(self, embedded: bool = False) -> List[str]:
return [self._bin("python"), self.pip_embedded if embedded else self.pip]
return [
self._bin(self._executable),
self.pip_embedded if embedded else self.pip,
]

def _run(self, cmd: List[str], **kwargs: Any) -> int:
self.executed.append(cmd)
Expand Down
17 changes: 13 additions & 4 deletions tests/console/commands/env/test_info.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import sys

from pathlib import Path

import pytest
Expand Down Expand Up @@ -28,14 +30,21 @@ def test_env_info_displays_complete_info(tester):
Python: 3.7.0
Implementation: CPython
Path: {prefix}
Executable: {executable}
Valid: True
System
Platform: darwin
OS: posix
Python: {base_prefix}
Platform: darwin
OS: posix
Python: {base_version}
Path: {base_prefix}
Executable: {base_executable}
""".format(
prefix=str(Path("/prefix")), base_prefix=str(Path("/base/prefix"))
prefix=str(Path("/prefix")),
base_prefix=str(Path("/base/prefix")),
base_version=".".join(str(v) for v in sys.version_info[:3]),
executable=sys.executable,
base_executable="python",
)

assert expected == tester.io.fetch_output()
Expand Down
Loading

0 comments on commit 61301a1

Please sign in to comment.