From 870f3992771fc0d64d082cf84978fa21f310a821 Mon Sep 17 00:00:00 2001 From: Igor Ovsyannikov Date: Wed, 5 Feb 2020 23:51:12 +0300 Subject: [PATCH 1/2] Add system-packages setting closes #1393 --- docs/docs/configuration.md | 7 +++++ poetry/config/config.py | 13 +++++++-- poetry/console/commands/config.py | 5 ++++ poetry/utils/env.py | 20 +++++++++---- tests/console/commands/env/test_use.py | 6 ++-- tests/console/commands/test_config.py | 3 ++ tests/test_factory.py | 1 + tests/utils/test_env.py | 40 +++++++++++++++++++++----- 8 files changed, 79 insertions(+), 16 deletions(-) diff --git a/docs/docs/configuration.md b/docs/docs/configuration.md index 88d38bd03ee..0928c5de5a7 100644 --- a/docs/docs/configuration.md +++ b/docs/docs/configuration.md @@ -34,6 +34,7 @@ which will give you something similar to this: cache-dir = "/path/to/cache/directory" virtualenvs.create = true virtualenvs.in-project = false +virtualenvs.include-system-packages = false virtualenvs.path = "{cache-dir}/virtualenvs" # /path/to/cache/directory/virtualenvs ``` @@ -112,6 +113,12 @@ Defaults to `true`. Create the virtualenv inside the project's root directory. Defaults to `false`. +### `virtualenvs.system-packages`: boolean + +Give the virtual environment access to the system site-packages directory. +Applies on virtualenv creation. +Defaults to `false`. + ### `virtualenvs.path`: string Directory where virtual environments will be created. diff --git a/poetry/config/config.py b/poetry/config/config.py index d7fc62d35f9..4635e01b70e 100644 --- a/poetry/config/config.py +++ b/poetry/config/config.py @@ -35,6 +35,7 @@ class Config(object): "virtualenvs": { "create": True, "in-project": False, + "system-packages": False, "path": os.path.join("{cache-dir}", "virtualenvs"), }, } @@ -130,14 +131,22 @@ def process(self, value): # type: (Any) -> Any return re.sub(r"{(.+?)}", lambda m: self.get(m.group(1)), value) def _get_validator(self, name): # type: (str) -> Callable - if name in {"virtualenvs.create", "virtualenvs.in-project"}: + if name in { + "virtualenvs.create", + "virtualenvs.in-project", + "virtualenvs.system-packages", + }: return boolean_validator if name == "virtualenvs.path": return str def _get_normalizer(self, name): # type: (str) -> Callable - if name in {"virtualenvs.create", "virtualenvs.in-project"}: + if name in { + "virtualenvs.create", + "virtualenvs.in-project", + "virtualenvs.system-packages", + }: return boolean_normalizer if name == "virtualenvs.path": diff --git a/poetry/console/commands/config.py b/poetry/console/commands/config.py index 68154296ee9..c0c5002847e 100644 --- a/poetry/console/commands/config.py +++ b/poetry/console/commands/config.py @@ -52,6 +52,11 @@ def unique_config_values(self): ), "virtualenvs.create": (boolean_validator, boolean_normalizer, True), "virtualenvs.in-project": (boolean_validator, boolean_normalizer, False), + "virtualenvs.system-packages": ( + boolean_validator, + boolean_normalizer, + False, + ), "virtualenvs.path": ( str, lambda val: str(Path(val)), diff --git a/poetry/utils/env.py b/poetry/utils/env.py index dcdec0363bf..3b7d71ff948 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -1,4 +1,5 @@ import base64 +import functools import hashlib import json import os @@ -483,6 +484,7 @@ def create_venv( create_venv = self._poetry.config.get("virtualenvs.create") root_venv = self._poetry.config.get("virtualenvs.in-project") + system_packages = self._poetry.config.get("virtualenvs.system-packages") venv_path = self._poetry.config.get("virtualenvs.path") if root_venv: @@ -607,7 +609,9 @@ def create_venv( "Creating virtualenv {} in {}".format(name, str(venv_path)) ) - self.build_venv(str(venv), executable=executable) + self.build_venv( + str(venv), executable=executable, system_packages=system_packages + ) else: if force: if not env.is_sane(): @@ -620,7 +624,9 @@ def create_venv( "Recreating virtualenv {} in {}".format(name, str(venv)) ) self.remove_venv(str(venv)) - self.build_venv(str(venv), executable=executable) + self.build_venv( + str(venv), executable=executable, system_packages=system_packages + ) elif io.is_very_verbose(): io.write_line("Virtualenv {} already exists.".format(name)) @@ -643,7 +649,7 @@ def create_venv( return VirtualEnv(venv) @classmethod - def build_venv(cls, path, executable=None): + def build_venv(cls, path, executable=None, system_packages=False): if executable is not None: # Create virtualenv by using an external executable try: @@ -667,13 +673,17 @@ def build_venv(cls, path, executable=None): else: use_symlinks = True - builder = EnvBuilder(with_pip=True, symlinks=use_symlinks) + builder = EnvBuilder( + system_site_packages=system_packages, + with_pip=True, + symlinks=use_symlinks, + ) build = builder.create except ImportError: # We fallback on virtualenv for Python 2.7 from virtualenv import create_environment - build = create_environment + build = functools.partial(create_environment, site_packages=system_packages) build(path) diff --git a/tests/console/commands/env/test_use.py b/tests/console/commands/env/test_use.py index 7f848494755..89f2d2481bc 100644 --- a/tests/console/commands/env/test_use.py +++ b/tests/console/commands/env/test_use.py @@ -16,7 +16,7 @@ CWD = Path(__file__).parent.parent / "fixtures" / "simple_project" -def build_venv(path, executable=None): +def build_venv(path, executable=None, system_packages=False): os.mkdir(path) @@ -61,7 +61,9 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file(app, tmp_dir, m ) m.assert_called_with( - os.path.join(tmp_dir, "{}-py3.7".format(venv_name)), executable="python3.7" + os.path.join(tmp_dir, "{}-py3.7".format(venv_name)), + executable="python3.7", + system_packages=False, ) envs_file = TomlFile(Path(tmp_dir) / "envs.toml") diff --git a/tests/console/commands/test_config.py b/tests/console/commands/test_config.py index fe331d777fc..528c86ffa22 100644 --- a/tests/console/commands/test_config.py +++ b/tests/console/commands/test_config.py @@ -16,6 +16,7 @@ def test_list_displays_default_value_if_not_set(app, config): virtualenvs.create = true virtualenvs.in-project = false virtualenvs.path = {path} # /foo{sep}virtualenvs +virtualenvs.system-packages = false """.format( path=json.dumps(os.path.join("{cache-dir}", "virtualenvs")), sep=os.path.sep ) @@ -35,6 +36,7 @@ def test_list_displays_set_get_setting(app, config): virtualenvs.create = false virtualenvs.in-project = false virtualenvs.path = {path} # /foo{sep}virtualenvs +virtualenvs.system-packages = false """.format( path=json.dumps(os.path.join("{cache-dir}", "virtualenvs")), sep=os.path.sep ) @@ -82,6 +84,7 @@ def test_list_displays_set_get_local_setting(app, config): virtualenvs.create = false virtualenvs.in-project = false virtualenvs.path = {path} # /foo{sep}virtualenvs +virtualenvs.system-packages = false """.format( path=json.dumps(os.path.join("{cache-dir}", "virtualenvs")), sep=os.path.sep ) diff --git a/tests/test_factory.py b/tests/test_factory.py index bf3493ae5fa..38f6a99b913 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -205,3 +205,4 @@ def test_create_poetry_with_local_config(fixture_dir): assert not poetry.config.get("virtualenvs.in-project") assert not poetry.config.get("virtualenvs.create") + assert not poetry.config.get("virtualenvs.system-packages") diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index e5926e68862..1098637f925 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -95,7 +95,7 @@ def test_env_get_in_project_venv(manager, poetry): shutil.rmtree(str(venv.path)) -def build_venv(path, executable=None): +def build_venv(path, executable=None, system_packages=False): os.mkdir(path) @@ -137,7 +137,9 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file( venv_name = EnvManager.generate_env_name("simple-project", str(poetry.file.parent)) m.assert_called_with( - os.path.join(tmp_dir, "{}-py3.7".format(venv_name)), executable="python3.7" + os.path.join(tmp_dir, "{}-py3.7".format(venv_name)), + executable="python3.7", + system_packages=False, ) envs_file = TomlFile(Path(tmp_dir) / "envs.toml") @@ -255,7 +257,9 @@ def test_activate_activates_different_virtualenv_with_envs_file( env = manager.activate("python3.6", NullIO()) m.assert_called_with( - os.path.join(tmp_dir, "{}-py3.6".format(venv_name)), executable="python3.6" + os.path.join(tmp_dir, "{}-py3.6".format(venv_name)), + executable="python3.6", + system_packages=False, ) assert envs_file.exists() @@ -307,7 +311,9 @@ def test_activate_activates_recreates_for_different_patch( env = manager.activate("python3.7", NullIO()) build_venv_m.assert_called_with( - os.path.join(tmp_dir, "{}-py3.7".format(venv_name)), executable="python3.7" + os.path.join(tmp_dir, "{}-py3.7".format(venv_name)), + executable="python3.7", + system_packages=False, ) remove_venv_m.assert_called_with( os.path.join(tmp_dir, "{}-py3.7".format(venv_name)) @@ -596,7 +602,9 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_generic_ manager.create_venv(NullIO()) m.assert_called_with( - str(Path("/foo/virtualenvs/{}-py3.7".format(venv_name))), executable="python3" + str(Path("/foo/virtualenvs/{}-py3.7".format(venv_name))), + executable="python3", + system_packages=False, ) @@ -620,7 +628,9 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_specific manager.create_venv(NullIO()) m.assert_called_with( - str(Path("/foo/virtualenvs/{}-py3.8".format(venv_name))), executable="python3.8" + str(Path("/foo/virtualenvs/{}-py3.8".format(venv_name))), + executable="python3.8", + system_packages=False, ) @@ -711,6 +721,7 @@ def test_create_venv_uses_patch_version_to_detect_compatibility( ) ), executable=None, + system_packages=False, ) @@ -750,6 +761,7 @@ def test_create_venv_uses_patch_version_to_detect_compatibility_with_executable( ) ), executable="python{}.{}".format(version.major, version.minor - 1), + system_packages=False, ) @@ -781,7 +793,9 @@ def test_activate_with_in_project_setting_does_not_fail_if_no_venvs_dir( manager.activate("python3.7", NullIO()) m.assert_called_with( - os.path.join(str(poetry.file.parent), ".venv"), executable="python3.7" + os.path.join(str(poetry.file.parent), ".venv"), + executable="python3.7", + system_packages=False, ) envs_file = TomlFile(Path(tmp_dir) / "virtualenvs" / "envs.toml") @@ -843,3 +857,15 @@ def test_env_site_packages_should_raise_an_error_if_no_site_packages(tmp_dir): with pytest.raises(RuntimeError): env.site_packages + + +def test_env_system_packages(tmp_path, config): + venv_path = tmp_path / "venv" + EnvManager(config).build_venv(str(venv_path), system_packages=True) + pyvenv_cfg = venv_path / "pyvenv.cfg" + if sys.version_info >= (3, 3): + assert "include-system-site-packages = true" in pyvenv_cfg.read_text() + elif (2, 6) < sys.version_info < (3, 0): + assert not venv_path.joinpath( + "lib", "python2.7", "no-global-site-packages.txt" + ).exists() From 11c20790f881f23c3a7b0962c2cdf298da9dcb40 Mon Sep 17 00:00:00 2001 From: Igor Ovsyannikov Date: Thu, 6 Feb 2020 07:09:52 +0300 Subject: [PATCH 2/2] Fix config example in docs; add debug log for system-packages setting --- docs/docs/configuration.md | 2 +- poetry/utils/env.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/docs/configuration.md b/docs/docs/configuration.md index 0928c5de5a7..7b436ab8795 100644 --- a/docs/docs/configuration.md +++ b/docs/docs/configuration.md @@ -34,7 +34,7 @@ which will give you something similar to this: cache-dir = "/path/to/cache/directory" virtualenvs.create = true virtualenvs.in-project = false -virtualenvs.include-system-packages = false +virtualenvs.system-packages = false virtualenvs.path = "{cache-dir}/virtualenvs" # /path/to/cache/directory/virtualenvs ``` diff --git a/poetry/utils/env.py b/poetry/utils/env.py index 3b7d71ff948..7062aa9b0f5 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -486,6 +486,9 @@ def create_venv( root_venv = self._poetry.config.get("virtualenvs.in-project") system_packages = self._poetry.config.get("virtualenvs.system-packages") + if system_packages and io.is_debug(): + io.write_line("Including system site-packages") + venv_path = self._poetry.config.get("virtualenvs.path") if root_venv: venv_path = cwd / ".venv"