Skip to content

Commit

Permalink
editable: use writable script dir for system env
Browse files Browse the repository at this point in the history
This change ensures that, When using system environment, poetry falls
back to `userbase` if default location is not writable.
  • Loading branch information
abn committed Oct 23, 2020
1 parent 7f49842 commit d660b51
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 15 deletions.
19 changes: 14 additions & 5 deletions poetry/masonry/builders/editable.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from poetry.utils._compat import WINDOWS
from poetry.utils._compat import Path
from poetry.utils._compat import decode
from poetry.utils.helpers import is_dir_writable


SCRIPT_TEMPLATE = """\
Expand Down Expand Up @@ -128,7 +129,17 @@ def _add_pth(self):
def _add_scripts(self):
added = []
entry_points = self.convert_entry_points()
scripts_path = Path(self._env.paths["scripts"])

for scripts_path in self._env.script_dirs:
if is_dir_writable(scripts_path):
break
else:
self._io.error_line(
" - Failed to find a suitable script installation directory for {}".format(
self._poetry.file.parent
)
)
return []

scripts = entry_points.get("console_scripts", [])
for script in scripts:
Expand All @@ -146,7 +157,7 @@ def _add_scripts(self):
f.write(
decode(
SCRIPT_TEMPLATE.format(
python=self._env._bin("python"),
python=self._env.python,
module=module,
callable_holder=callable_holder,
callable_=callable_,
Expand All @@ -160,9 +171,7 @@ def _add_scripts(self):

if WINDOWS:
cmd_script = script_file.with_suffix(".cmd")
cmd = WINDOWS_CMD_TEMPLATE.format(
python=self._env._bin("python"), script=name
)
cmd = WINDOWS_CMD_TEMPLATE.format(python=self._env.python, script=name)
self._debug(
" - Adding the <c2>{}</c2> script wrapper to <b>{}</b>".format(
cmd_script.name, scripts_path
Expand Down
38 changes: 28 additions & 10 deletions poetry/utils/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import shutil
import sys
import sysconfig
import tempfile
import textwrap

from contextlib import contextmanager
Expand Down Expand Up @@ -40,6 +39,7 @@
from poetry.utils._compat import encode
from poetry.utils._compat import list_to_shell_command
from poetry.utils._compat import subprocess
from poetry.utils.helpers import is_dir_writable
from poetry.utils.helpers import paths_csv


Expand Down Expand Up @@ -170,14 +170,9 @@ def writable_candidates(self): # type: () -> List[Path]

self._writable_candidates = []
for candidate in self._candidates:
try:
if not candidate.exists():
continue

with tempfile.TemporaryFile(dir=str(candidate)):
self._writable_candidates.append(candidate)
except (IOError, OSError):
pass
if not is_dir_writable(candidate):
continue
self._writable_candidates.append(candidate)

return self._writable_candidates

Expand Down Expand Up @@ -877,6 +872,7 @@ def __init__(self, path, base=None): # type: (Path, Optional[Path]) -> None
self._supported_tags = None
self._purelib = None
self._platlib = None
self._script_dirs = None

@property
def path(self): # type: () -> Path
Expand Down Expand Up @@ -945,6 +941,11 @@ def usersite(self): # type: () -> Optional[Path]
if "usersite" in self.paths:
return Path(self.paths["usersite"])

@property
def userbase(self): # type: () -> Optional[Path]
if "userbase" in self.paths:
return Path(self.paths["userbase"])

@property
def purelib(self): # type: () -> Path
if self._purelib is None:
Expand Down Expand Up @@ -1091,6 +1092,18 @@ def execute(self, bin, *args, **kwargs):
def is_venv(self): # type: () -> bool
raise NotImplementedError()

@property
def script_dirs(self): # type: () -> List[Path]
if self._script_dirs is None:
self._script_dirs = (
[Path(self.paths["scripts"])]
if "scripts" in self.paths
else self._bin_dir
)
if self.userbase:
self._script_dirs.append(self.userbase / self._script_dirs[0].name)
return self._script_dirs

def _bin(self, bin): # type: (str) -> str
"""
Return path to the given executable.
Expand Down Expand Up @@ -1126,6 +1139,10 @@ class SystemEnv(Env):
A system (i.e. not a virtualenv) Python environment.
"""

@property
def python(self): # type: () -> str
return sys.executable

@property
def sys_path(self): # type: () -> List[str]
return sys.path
Expand Down Expand Up @@ -1166,6 +1183,7 @@ def get_paths(self): # type: () -> Dict[str, str]

if site.check_enableusersite() and hasattr(obj, "install_usersite"):
paths["usersite"] = getattr(obj, "install_usersite")
paths["userbase"] = getattr(obj, "install_userbase")

return paths

Expand Down Expand Up @@ -1301,7 +1319,7 @@ def is_venv(self): # type: () -> bool

def is_sane(self):
# A virtualenv is considered sane if both "python" and "pip" exist.
return os.path.exists(self._bin("python")) and os.path.exists(self._bin("pip"))
return os.path.exists(self.python) and os.path.exists(self._bin("pip"))

def _run(self, cmd, **kwargs):
with self.temp_environ():
Expand Down
15 changes: 15 additions & 0 deletions poetry/utils/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,18 @@ def get_package_version_display_string(

def paths_csv(paths): # type: (List[Path]) -> str
return ", ".join('"{}"'.format(str(c)) for c in paths)


def is_dir_writable(path, create=False): # type: (Path, bool) -> bool
try:
if not path.exists():
if not create:
return False
path.mkdir(parents=True, exist_ok=True)

with tempfile.TemporaryFile(dir=str(path)):
pass
except (IOError, OSError):
return False
else:
return True

0 comments on commit d660b51

Please sign in to comment.