Skip to content

Commit a9e59ed

Browse files
authored
Merge pull request #4507 from python-poetry/1.1-fix-system-env-detection
[1.1] Fix system environment detection
2 parents 634bb23 + 459c8c9 commit a9e59ed

File tree

3 files changed

+310
-23
lines changed

3 files changed

+310
-23
lines changed

poetry/inspection/info.py

+3-5
Original file line numberDiff line numberDiff line change
@@ -464,17 +464,15 @@ def _pep517_metadata(cls, path): # type (Path) -> PackageInfo
464464
dest_dir.mkdir()
465465

466466
try:
467-
venv.run(
468-
"python",
467+
venv.run_python(
469468
"-m",
470469
"pip",
471470
"install",
472471
"--disable-pip-version-check",
473472
"--ignore-installed",
474473
*PEP517_META_BUILD_DEPS
475474
)
476-
venv.run(
477-
"python",
475+
venv.run_python(
478476
"-",
479477
input_=PEP517_META_BUILD.format(
480478
source=path.as_posix(), dest=dest_dir.as_posix()
@@ -496,7 +494,7 @@ def _pep517_metadata(cls, path): # type (Path) -> PackageInfo
496494
cwd = Path.cwd()
497495
os.chdir(path.as_posix())
498496
try:
499-
venv.run("python", "setup.py", "egg_info")
497+
venv.run_python("setup.py", "egg_info")
500498
return cls.from_metadata(path)
501499
except EnvCommandError as fbe:
502500
raise PackageInfoError(

poetry/utils/env.py

+182-18
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,38 @@ def _version_nodot(version):
145145
print(json.dumps(sysconfig.get_paths()))
146146
"""
147147

148+
GET_PATHS_FOR_GENERIC_ENVS = """\
149+
# We can't use sysconfig.get_paths() because
150+
# on some distributions it does not return the proper paths
151+
# (those used by pip for instance). We go through distutils
152+
# to get the proper ones.
153+
import json
154+
import site
155+
import sysconfig
156+
157+
from distutils.command.install import SCHEME_KEYS # noqa
158+
from distutils.core import Distribution
159+
160+
d = Distribution()
161+
d.parse_config_files()
162+
obj = d.get_command_obj("install", create=True)
163+
obj.finalize_options()
164+
165+
paths = sysconfig.get_paths().copy()
166+
for key in SCHEME_KEYS:
167+
if key == "headers":
168+
# headers is not a path returned by sysconfig.get_paths()
169+
continue
170+
171+
paths[key] = getattr(obj, f"install_{key}")
172+
173+
if site.check_enableusersite() and hasattr(obj, "install_usersite"):
174+
paths["usersite"] = getattr(obj, "install_usersite")
175+
paths["userbase"] = getattr(obj, "install_userbase")
176+
177+
print(json.dumps(paths))
178+
"""
179+
148180

149181
class SitePackages:
150182
def __init__(
@@ -615,7 +647,7 @@ def remove(self, python): # type: (str) -> Env
615647

616648
self.remove_venv(venv)
617649

618-
return VirtualEnv(venv)
650+
return VirtualEnv(venv, venv)
619651

620652
def create_venv(
621653
self, io, name=None, executable=None, force=False
@@ -848,15 +880,21 @@ def get_system_env(
848880
(e.g. plugin installation or self update).
849881
"""
850882
prefix, base_prefix = Path(sys.prefix), Path(cls.get_base_prefix())
883+
env = SystemEnv(prefix)
851884
if not naive:
852-
try:
853-
Path(__file__).relative_to(prefix)
854-
except ValueError:
855-
pass
885+
if prefix.joinpath("poetry_env").exists():
886+
env = GenericEnv(base_prefix, child_env=env)
856887
else:
857-
return GenericEnv(base_prefix)
888+
from poetry.locations import data_dir
858889

859-
return SystemEnv(prefix)
890+
try:
891+
prefix.relative_to(data_dir())
892+
except ValueError:
893+
pass
894+
else:
895+
env = GenericEnv(base_prefix, child_env=env)
896+
897+
return env
860898

861899
@classmethod
862900
def get_base_prefix(cls): # type: () -> str
@@ -892,6 +930,11 @@ def __init__(self, path, base=None): # type: (Path, Optional[Path]) -> None
892930

893931
self._base = base or path
894932

933+
self._executable = "python"
934+
self._pip_executable = "pip"
935+
936+
self.find_executables()
937+
895938
self._marker_env = None
896939
self._pip_version = None
897940
self._site_packages = None
@@ -922,7 +965,7 @@ def python(self): # type: () -> str
922965
"""
923966
Path to current python executable
924967
"""
925-
return self._bin("python")
968+
return self._bin(self._executable)
926969

927970
@property
928971
def marker_env(self):
@@ -931,12 +974,16 @@ def marker_env(self):
931974

932975
return self._marker_env
933976

977+
@property
978+
def parent_env(self): # type: () -> GenericEnv
979+
return GenericEnv(self.base, child_env=self)
980+
934981
@property
935982
def pip(self): # type: () -> str
936983
"""
937984
Path to current pip executable
938985
"""
939-
return self._bin("pip")
986+
return self._bin(self._pip_executable)
940987

941988
@property
942989
def platform(self): # type: () -> str
@@ -1028,6 +1075,35 @@ def get_base_prefix(cls): # type: () -> str
10281075

10291076
return sys.prefix
10301077

1078+
def find_executables(self): # type: () -> None
1079+
python_executables = sorted(
1080+
[
1081+
p.name
1082+
for p in self._bin_dir.glob("python*")
1083+
if re.match(r"python(?:\d+(?:\.\d+)?)?(?:\.exe)?$", p.name)
1084+
]
1085+
)
1086+
if python_executables:
1087+
executable = python_executables[0]
1088+
if executable.endswith(".exe"):
1089+
executable = executable[:-4]
1090+
1091+
self._executable = executable
1092+
1093+
pip_executables = sorted(
1094+
[
1095+
p.name
1096+
for p in self._bin_dir.glob("pip*")
1097+
if re.match(r"pip(?:\d+(?:\.\d+)?)?(?:\.exe)?$", p.name)
1098+
]
1099+
)
1100+
if pip_executables:
1101+
pip_executable = pip_executables[0]
1102+
if pip_executable.endswith(".exe"):
1103+
pip_executable = pip_executable[:-4]
1104+
1105+
self._pip_executable = pip_executable
1106+
10311107
def get_version_info(self): # type: () -> Tuple[int]
10321108
raise NotImplementedError()
10331109

@@ -1063,6 +1139,9 @@ def run(self, bin, *args, **kwargs):
10631139
cmd = [bin] + list(args)
10641140
return self._run(cmd, **kwargs)
10651141

1142+
def run_python(self, *args, **kwargs):
1143+
return self.run(self._executable, *args, **kwargs)
1144+
10661145
def run_pip(self, *args, **kwargs):
10671146
pip = self.get_pip_command()
10681147
cmd = pip + list(args)
@@ -1133,7 +1212,11 @@ def _bin(self, bin): # type: (str) -> str
11331212
"""
11341213
Return path to the given executable.
11351214
"""
1136-
bin_path = (self._bin_dir / bin).with_suffix(".exe" if self._is_windows else "")
1215+
if self._is_windows and not bin.endswith(".exe"):
1216+
bin_path = self._bin_dir / (bin + ".exe")
1217+
else:
1218+
bin_path = self._bin_dir / bin
1219+
11371220
if not bin_path.exists():
11381221
# On Windows, some executables can be in the base path
11391222
# This is especially true when installing Python with
@@ -1144,7 +1227,11 @@ def _bin(self, bin): # type: (str) -> str
11441227
# that creates a fake virtual environment pointing to
11451228
# a base Python install.
11461229
if self._is_windows:
1147-
bin_path = (self._path / bin).with_suffix(".exe")
1230+
if not bin.endswith(".exe"):
1231+
bin_path = self._bin_dir / (bin + ".exe")
1232+
else:
1233+
bin_path = self._path / bin
1234+
11481235
if bin_path.exists():
11491236
return str(bin_path)
11501237

@@ -1270,16 +1357,18 @@ def __init__(self, path, base=None): # type: (Path, Optional[Path]) -> None
12701357
# In this case we need to get sys.base_prefix
12711358
# from inside the virtualenv.
12721359
if base is None:
1273-
self._base = Path(self.run("python", "-", input_=GET_BASE_PREFIX).strip())
1360+
self._base = Path(
1361+
self.run(self._executable, "-", input_=GET_BASE_PREFIX).strip()
1362+
)
12741363

12751364
@property
12761365
def sys_path(self): # type: () -> List[str]
1277-
output = self.run("python", "-", input_=GET_SYS_PATH)
1366+
output = self.run(self._executable, "-", input_=GET_SYS_PATH)
12781367

12791368
return json.loads(output)
12801369

12811370
def get_version_info(self): # type: () -> Tuple[int]
1282-
output = self.run("python", "-", input_=GET_PYTHON_VERSION)
1371+
output = self.run(self._executable, "-", input_=GET_PYTHON_VERSION)
12831372

12841373
return tuple([int(s) for s in output.strip().split(".")])
12851374

@@ -1289,7 +1378,7 @@ def get_python_implementation(self): # type: () -> str
12891378
def get_pip_command(self): # type: () -> List[str]
12901379
# We're in a virtualenv that is known to be sane,
12911380
# so assume that we have a functional pip
1292-
return [self._bin("pip")]
1381+
return [self._bin(self._pip_executable)]
12931382

12941383
def get_supported_tags(self): # type: () -> List[Tag]
12951384
file_path = Path(packaging.tags.__file__)
@@ -1317,12 +1406,12 @@ def get_supported_tags(self): # type: () -> List[Tag]
13171406
"""
13181407
)
13191408

1320-
output = self.run("python", "-", input_=script)
1409+
output = self.run(self._executable, "-", input_=script)
13211410

13221411
return [Tag(*t) for t in json.loads(output)]
13231412

13241413
def get_marker_env(self): # type: () -> Dict[str, Any]
1325-
output = self.run("python", "-", input_=GET_ENVIRONMENT_INFO)
1414+
output = self.run(self._executable, "-", input_=GET_ENVIRONMENT_INFO)
13261415

13271416
return json.loads(output)
13281417

@@ -1335,7 +1424,7 @@ def get_pip_version(self): # type: () -> Version
13351424
return Version.parse(m.group(1))
13361425

13371426
def get_paths(self): # type: () -> Dict[str, str]
1338-
output = self.run("python", "-", input_=GET_PATHS)
1427+
output = self.run(self._executable, "-", input_=GET_PATHS)
13391428

13401429
return json.loads(output)
13411430

@@ -1388,6 +1477,81 @@ def _updated_path(self):
13881477

13891478

13901479
class GenericEnv(VirtualEnv):
1480+
def __init__(
1481+
self, path, base=None, child_env=None
1482+
): # type: (Path, Optional[Path], Optional[Env]) -> None
1483+
self._child_env = child_env
1484+
1485+
super(GenericEnv, self).__init__(path, base=base)
1486+
1487+
def find_executables(self): # type: () -> None
1488+
patterns = [("python*", "pip*")]
1489+
1490+
if self._child_env:
1491+
minor_version = "{}.{}".format(
1492+
self._child_env.version_info[0], self._child_env.version_info[1]
1493+
)
1494+
major_version = "{}".format(self._child_env.version_info[0])
1495+
patterns = [
1496+
("python{}".format(minor_version), "pip{}".format(minor_version)),
1497+
("python{}".format(major_version), "pip{}".format(major_version)),
1498+
]
1499+
1500+
python_executable = None
1501+
pip_executable = None
1502+
1503+
for python_pattern, pip_pattern in patterns:
1504+
if python_executable and pip_executable:
1505+
break
1506+
1507+
if not python_executable:
1508+
python_executables = sorted(
1509+
[
1510+
p.name
1511+
for p in self._bin_dir.glob(python_pattern)
1512+
if re.match(r"python(?:\d+(?:\.\d+)?)?(?:\.exe)?$", p.name)
1513+
]
1514+
)
1515+
1516+
if python_executables:
1517+
executable = python_executables[0]
1518+
if executable.endswith(".exe"):
1519+
executable = executable[:-4]
1520+
1521+
python_executable = executable
1522+
1523+
if not pip_executable:
1524+
pip_executables = sorted(
1525+
[
1526+
p.name
1527+
for p in self._bin_dir.glob(pip_pattern)
1528+
if re.match(r"pip(?:\d+(?:\.\d+)?)?(?:\.exe)?$", p.name)
1529+
]
1530+
)
1531+
if pip_executables:
1532+
pip_executable = pip_executables[0]
1533+
if pip_executable.endswith(".exe"):
1534+
pip_executable = pip_executable[:-4]
1535+
1536+
pip_executable = pip_executable
1537+
1538+
if python_executable:
1539+
self._executable = python_executable
1540+
1541+
if pip_executable:
1542+
self._pip_executable = pip_executable
1543+
1544+
def get_paths(self): # type: () -> Dict[str, str]
1545+
output = self.run(self._executable, "-", input_=GET_PATHS_FOR_GENERIC_ENVS)
1546+
1547+
return json.loads(output)
1548+
1549+
def execute(self, bin, *args, **kwargs): # type: (str, str, Any) -> Optional[int]
1550+
return super(VirtualEnv, self).execute(bin, *args, **kwargs)
1551+
1552+
def _run(self, cmd, **kwargs): # type: (List[str], Any) -> Optional[int]
1553+
return super(VirtualEnv, self)._run(cmd, **kwargs)
1554+
13911555
def is_venv(self): # type: () -> bool
13921556
return self._path != self._base
13931557

0 commit comments

Comments
 (0)