Skip to content

Commit

Permalink
Fix errors for virtualenvs with spaces in their path
Browse files Browse the repository at this point in the history
  • Loading branch information
sdispater committed Nov 16, 2018
1 parent aca69a1 commit 58dbca4
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 67 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -7,6 +7,7 @@
- Fixed executables from outside the virtualenv not being accessible.
- Fixed a possible error when building distributions with the `exclude` option.
- Fixed the `run` command for namespaced packages.
- Fixed errors for virtualenvs with spaces in their path.


## [0.12.8] - 2018-11-13
Expand Down
19 changes: 19 additions & 0 deletions poetry/utils/_compat.py
@@ -1,3 +1,4 @@
import subprocess
import sys

try:
Expand All @@ -19,6 +20,17 @@
PY35 = sys.version_info >= (3, 5)
PY36 = sys.version_info >= (3, 6)

WINDOWS = sys.platform == "win32"

if PY2:
import pipes

shell_quote = pipes.quote
else:
import shlex

shell_quote = shlex.quote


if PY35:
from pathlib import Path
Expand Down Expand Up @@ -80,3 +92,10 @@ def to_str(string):
pass

return getattr(string, method)(encodings[0], errors="ignore")


def list_to_shell_command(cmd):
if not WINDOWS:
cmd = [cmd[0]] + [shell_quote(a) for a in cmd[1:]]

return subprocess.list2cmdline(cmd)
137 changes: 83 additions & 54 deletions poetry/utils/env.py
Expand Up @@ -17,9 +17,72 @@
from poetry.locations import CACHE_DIR
from poetry.utils._compat import Path
from poetry.utils._compat import decode
from poetry.utils._compat import encode
from poetry.utils._compat import list_to_shell_command
from poetry.version.markers import BaseMarker


GET_ENVIRONMENT_INFO = """\
import json
import os
import platform
import sys
if hasattr(sys, "implementation"):
info = sys.implementation.version
iver = "{0.major}.{0.minor}.{0.micro}".format(info)
kind = info.releaselevel
if kind != "final":
iver += kind[0] + str(info.serial)
implementation_name = sys.implementation.name
else:
iver = "0"
implementation_name = ""
env = {
"implementation_name": implementation_name,
"implementation_version": iver,
"os_name": os.name,
"platform_machine": platform.machine(),
"platform_release": platform.release(),
"platform_system": platform.system(),
"platform_version": platform.version(),
"python_full_version": platform.python_version(),
"platform_python_implementation": platform.python_implementation(),
"python_version": platform.python_version()[:3],
"sys_platform": sys.platform,
"version_info": tuple(sys.version_info),
}
print(json.dumps(env))
"""


GET_BASE_PREFIX = """\
import sys
if hasattr(sys, "real_prefix"):
print(sys.real_prefix)
elif hasattr(sys, "base_prefix"):
print(sys.base_prefix)
else:
print(sys.prefix)
"""

GET_CONFIG_VAR = """\
import sysconfig
print(sysconfig.get_config_var("{config_var}")),
"""

GET_PYTHON_VERSION = """\
import sys
print('.'.join([str(s) for s in sys.version_info[:3]]))
"""


class EnvError(Exception):

pass
Expand Down Expand Up @@ -272,18 +335,30 @@ def run(self, bin, *args, **kwargs):
cmd = [bin] + list(args)
shell = kwargs.get("shell", False)
call = kwargs.pop("call", False)
input_ = kwargs.pop("input_", None)

if shell:
cmd = " ".join(cmd)
cmd = list_to_shell_command(cmd)

try:
if self._is_windows:
kwargs["shell"] = True

if call:
if input_:
p = subprocess.Popen(
cmd,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
**kwargs
)
output = p.communicate(encode(input_))[0]
elif call:
return subprocess.call(cmd, stderr=subprocess.STDOUT, **kwargs)

output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, **kwargs)
else:
output = subprocess.check_output(
cmd, stderr=subprocess.STDOUT, **kwargs
)
except CalledProcessError as e:
raise EnvCommandError(e)

Expand Down Expand Up @@ -375,71 +450,25 @@ def __init__(self, path, base=None): # type: (Path, Optional[Path]) -> None
# In this case we need to get sys.base_prefix
# from inside the virtualenv.
if base is None:
self._base = Path(
self.run(
"python",
"-c",
'"import sys; '
"print("
" getattr("
" sys,"
" 'real_prefix', "
" getattr(sys, 'base_prefix', sys.prefix)"
" )"
')"',
shell=True,
).strip()
)
self._base = Path(self.run("python", "-", input_=GET_BASE_PREFIX).strip())

def get_version_info(self): # type: () -> Tuple[int]
output = self.run(
"python",
"-c",
"\"import sys; print('.'.join([str(s) for s in sys.version_info[:3]]))\"",
shell=True,
)
output = self.run("python", "-", input_=GET_PYTHON_VERSION)

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

def get_python_implementation(self): # type: () -> str
return self.marker_env["platform_python_implementation"]

def get_marker_env(self): # type: () -> Dict[str, Any]
output = self.run(
"python",
"-c",
'"import json; import os; import platform; import sys; '
"implementation = getattr(sys, 'implementation', None); "
"iver = '{0.major}.{0.minor}.{0.micro}'.format(implementation.version) if implementation else '0'; "
"implementation_name = implementation.name if implementation else ''; "
"env = {"
"'implementation_name': implementation_name,"
"'implementation_version': iver,"
"'os_name': os.name,"
"'platform_machine': platform.machine(),"
"'platform_release': platform.release(),"
"'platform_system': platform.system(),"
"'platform_version': platform.version(),"
"'python_full_version': platform.python_version(),"
"'platform_python_implementation': platform.python_implementation(),"
"'python_version': platform.python_version()[:3],"
"'sys_platform': sys.platform,"
"'version_info': sys.version_info[:3],"
"};"
'print(json.dumps(env))"',
shell=True,
)
output = self.run("python", "-", input_=GET_ENVIRONMENT_INFO)

return json.loads(output)

def config_var(self, var): # type: (str) -> Any
try:
value = self.run(
"python",
"-c",
'"import sysconfig; '
"print(sysconfig.get_config_var('{}'))\"".format(var),
shell=True,
"python", "-", input_=GET_CONFIG_VAR.format(config_var=var)
).strip()
except EnvCommandError as e:
warnings.warn("{0}".format(e), RuntimeWarning)
Expand Down
9 changes: 9 additions & 0 deletions tests/conftest.py
Expand Up @@ -12,6 +12,15 @@
from poetry.utils.toml_file import TomlFile


@pytest.fixture
def tmp_dir():
dir_ = tempfile.mkdtemp(prefix="poetry_")

yield dir_

shutil.rmtree(dir_)


@pytest.fixture
def config(): # type: () -> Config
with tempfile.NamedTemporaryFile() as f:
Expand Down
13 changes: 0 additions & 13 deletions tests/console/commands/test_init.py
@@ -1,23 +1,10 @@
import pytest
import shutil
import tempfile

from cleo.testers import CommandTester

from poetry.utils._compat import Path

from tests.helpers import get_package


@pytest.fixture
def tmp_dir():
dir_ = tempfile.mkdtemp(prefix="poetry_")

yield dir_

shutil.rmtree(dir_)


def test_basic_interactive(app, mocker, poetry):
command = app.find("init")
command._pool = poetry.pool
Expand Down
13 changes: 13 additions & 0 deletions tests/utils/test_env.py
@@ -0,0 +1,13 @@
from poetry.utils._compat import Path
from poetry.utils.env import Env
from poetry.utils.env import VirtualEnv


def test_virtualenvs_with_spaces_in_their_path_work_as_expected(tmp_dir):
venv_path = Path(tmp_dir) / "Virtual Env"

Env.build_venv(str(venv_path))

venv = VirtualEnv(venv_path)

assert venv.run("python", "-V", shell=True).startswith("Python")

0 comments on commit 58dbca4

Please sign in to comment.