From d5a99f004cc3f6b2e82f9b40949f4d24ca4eb3e3 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 14 Jul 2020 14:17:35 +0200 Subject: [PATCH] Standardise on using virtualenv package (#2666) Until now poetry used the built-in venv module available in Python 3. This has presented a few concerns. Chief of which has been inconsistent environment setup. This includes, but is not limited to, pip version used by the `ensurepip` module which is in turn used by the built-in `venv.EnvBuilder`. Additionally, the `virtualenv` package allows for faster environment creation. This can also allow us to, going forward, drop ad-hoc code required to inspect and manage environments. --- poetry.lock | 7 +++- poetry/utils/env.py | 74 +++++++++-------------------------------- pyproject.toml | 3 +- tests/utils/test_env.py | 15 +++------ 4 files changed, 28 insertions(+), 71 deletions(-) diff --git a/poetry.lock b/poetry.lock index cd1293b708b..f159ce8d5bc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1150,7 +1150,7 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["pathlib2", "unittest2", "jaraco.itertools", "func-timeout"] [metadata] -content-hash = "363cbb8480f9bbda72b318c6edfcaa1de130ceecba7c7d0e774005a3bca91df5" +content-hash = "31e245abc965de18a070191e854be0f3e7161386fb22439b0a297f187d94b3d7" python-versions = "~2.7 || ^3.5" [metadata.files] @@ -1426,6 +1426,11 @@ markupsafe = [ {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] mkdocs = [ diff --git a/poetry/utils/env.py b/poetry/utils/env.py index 7c1136a8792..42ae0e5d70a 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -15,12 +15,14 @@ from typing import List from typing import Optional from typing import Tuple +from typing import Union import tomlkit from clikit.api.io import IO import packaging.tags +import virtualenv from packaging.tags import Tag from packaging.tags import interpreter_name @@ -141,27 +143,6 @@ def _version_nodot(version): print(json.dumps(sysconfig.get_paths())) """ -CREATE_VENV_COMMAND = """\ -path = {!r} - -try: - from venv import EnvBuilder - - builder = EnvBuilder(with_pip=True) - builder.create(path) -except ImportError: - try: - # We fallback on virtualenv for Python 2.7 - from virtualenv import create_environment - - create_environment(path) - except ImportError: - # since virtualenv>20 we have to use cli_run - from virtualenv import cli_run - - cli_run([path]) -""" - class EnvError(Exception): @@ -697,43 +678,20 @@ def create_venv( return VirtualEnv(venv) @classmethod - def build_venv(cls, path, executable=None): - if executable is not None: - # Create virtualenv by using an external executable - try: - p = subprocess.Popen( - list_to_shell_command([executable, "-"]), - stdin=subprocess.PIPE, - shell=True, - ) - p.communicate(encode(CREATE_VENV_COMMAND.format(path))) - except CalledProcessError as e: - raise EnvCommandError(e) - - return - - try: - from venv import EnvBuilder - - # use the same defaults as python -m venv - if os.name == "nt": - use_symlinks = False - else: - use_symlinks = True - - builder = EnvBuilder(with_pip=True, symlinks=use_symlinks) - builder.create(path) - except ImportError: - try: - # We fallback on virtualenv for Python 2.7 - from virtualenv import create_environment - - create_environment(path) - except ImportError: - # since virtualenv>20 we have to use cli_run - from virtualenv import cli_run - - cli_run([path]) + def build_venv( + cls, path, executable=None + ): # type: (str, Optional[Union[str, Path]]) -> virtualenv.run.session.Session + if isinstance(executable, Path): + executable = executable.resolve() + return virtualenv.cli_run( + [ + "--no-download", + "--no-periodic-update", + "--python", + executable or sys.executable, + path, + ] + ) def remove_venv(self, path): # type: (str) -> None shutil.rmtree(path) diff --git a/pyproject.toml b/pyproject.toml index 4fc92753817..ea42cddf180 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,6 +38,7 @@ shellingham = "^1.1" tomlkit = "^0.5.11" pexpect = "^4.7.0" packaging = "^20.4" +virtualenv = { version = "^20.0.26" } # The typing module is not in the stdlib in Python 2.7 typing = { version = "^3.6", python = "~2.7" } @@ -48,8 +49,6 @@ pathlib2 = { version = "^2.3", python = "~2.7" } futures = { version = "^3.3.0", python = "~2.7" } # Use glob2 for Python 2.7 and 3.4 glob2 = { version = "^0.6", python = "~2.7" } -# Use virtualenv for Python 2.7 since venv does not exist -virtualenv = { version = "^20.0.18", python = "~2.7" } # functools32 is needed for Python 2.7 functools32 = { version = "^3.2.3", python = "~2.7" } keyring = [ diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index 800fc9b60f4..ef6500c5ac2 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -9,6 +9,7 @@ from poetry.core.semver import Version from poetry.factory import Factory +from poetry.utils._compat import PY2 from poetry.utils._compat import Path from poetry.utils.env import EnvCommandError from poetry.utils.env import EnvManager @@ -535,17 +536,11 @@ def test_remove_also_deactivates(tmp_dir, manager, poetry, config, mocker): assert venv_name not in envs +@pytest.mark.skipif( + os.name == "nt" or PY2, reason="Symlinks are not support for Windows" +) def test_env_has_symlinks_on_nix(tmp_dir, tmp_venv): - venv_available = False - try: - from venv import EnvBuilder # noqa - - venv_available = True - except ImportError: - pass - - if os.name != "nt" and venv_available: - assert os.path.islink(tmp_venv.python) + assert os.path.islink(tmp_venv.python) def test_run_with_input(tmp_dir, tmp_venv):