From 5587ffdbeff04f9b18f6ceb22139610a20c5837f Mon Sep 17 00:00:00 2001 From: finswimmer Date: Fri, 9 Oct 2020 06:43:56 +0200 Subject: [PATCH] add config option to force `--always-copy` argument for virtualenv fix issue for showing all configs when options has more then two level --- docs/docs/configuration.md | 7 +++++ poetry/config/config.py | 19 ++++++++++-- poetry/console/commands/config.py | 5 ++++ poetry/utils/env.py | 41 ++++++++++++++++++-------- tests/console/commands/env/helpers.py | 4 ++- tests/console/commands/env/test_use.py | 4 ++- tests/console/commands/test_config.py | 3 ++ tests/utils/test_env.py | 32 +++++++++++++++----- 8 files changed, 90 insertions(+), 25 deletions(-) diff --git a/docs/docs/configuration.md b/docs/docs/configuration.md index 0378f2fca9e..782c2f9ac87 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 = null +virtualenvs.options.always-copy = true virtualenvs.path = "{cache-dir}/virtualenvs" # /path/to/cache/directory/virtualenvs ``` @@ -128,6 +129,12 @@ If not set explicitly (default), `poetry` will use the virtualenv from the `.ven Directory where virtual environments will be created. Defaults to `{cache-dir}/virtualenvs` (`{cache-dir}\virtualenvs` on Windows). +### `virtualenvs.options.always-copy`: boolean + +If set to `true` the `--always-copy` parameter is passed to `virtualenv` on creation of the venv. Thus all needed files are copied into the venv instead of symlinked. +Defaults to `false`. + + ### `repositories.`: string Set a new alternative repository. See [Repositories](/docs/repositories/) for more information. diff --git a/poetry/config/config.py b/poetry/config/config.py index be585575c05..d1fb421e53c 100644 --- a/poetry/config/config.py +++ b/poetry/config/config.py @@ -36,6 +36,7 @@ class Config(object): "create": True, "in-project": None, "path": os.path.join("{cache-dir}", "virtualenvs"), + "options": {"always-copy": False}, }, "experimental": {"new-installer": True}, } @@ -87,7 +88,11 @@ def _all(config, parent_key=""): for key in config: value = self.get(parent_key + key) if isinstance(value, dict): - all_[key] = _all(config[key], parent_key=key + ".") + if parent_key != "": + current_parent = parent_key + key + "." + else: + current_parent = key + "." + all_[key] = _all(config[key], parent_key=current_parent) continue all_[key] = value @@ -131,14 +136,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.options.always-copy", + }: 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.options.always-copy", + }: return boolean_normalizer if name == "virtualenvs.path": diff --git a/poetry/console/commands/config.py b/poetry/console/commands/config.py index 551876912c2..6f19bc241fe 100644 --- a/poetry/console/commands/config.py +++ b/poetry/console/commands/config.py @@ -63,6 +63,11 @@ def unique_config_values(self): boolean_normalizer, True, ), + "virtualenvs.options.always-copy": ( + boolean_validator, + boolean_normalizer, + False, + ), } return unique_config_values diff --git a/poetry/utils/env.py b/poetry/utils/env.py index ccd855b5c60..0e3df320d6a 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -642,7 +642,11 @@ def create_venv( "Creating virtualenv {} in {}".format(name, str(venv_path)) ) - self.build_venv(venv, executable=executable) + self.build_venv( + venv, + executable=executable, + flags=self._poetry.config.get("virtualenvs.options"), + ) else: if force: if not env.is_sane(): @@ -655,7 +659,11 @@ def create_venv( "Recreating virtualenv {} in {}".format(name, str(venv)) ) self.remove_venv(venv) - self.build_venv(venv, executable=executable) + self.build_venv( + venv, + executable=executable, + flags=self._poetry.config.get("virtualenvs.options"), + ) elif io.is_very_verbose(): io.write_line("Virtualenv {} already exists.".format(name)) @@ -679,19 +687,26 @@ def create_venv( @classmethod def build_venv( - cls, path, executable=None - ): # type: (Union[Path,str], Optional[Union[str, Path]]) -> virtualenv.run.session.Session + cls, path, executable=None, flags=None + ): # type: (Union[Path,str], Optional[Union[str, Path]], Dict[str, bool]) -> virtualenv.run.session.Session + flags = flags or {} + if isinstance(executable, Path): executable = executable.resolve().as_posix() - return virtualenv.cli_run( - [ - "--no-download", - "--no-periodic-update", - "--python", - executable or sys.executable, - str(path), - ] - ) + + args = [ + "--no-download", + "--no-periodic-update", + "--python", + executable or sys.executable, + str(path), + ] + + for flag, value in flags.items(): + if value is True: + args.insert(0, "--{}".format(flag)) + + return virtualenv.cli_run(args) @classmethod def remove_venv(cls, path): # type: (Union[Path,str]) -> None diff --git a/tests/console/commands/env/helpers.py b/tests/console/commands/env/helpers.py index 17d4c2ac274..1c7e64dc17e 100644 --- a/tests/console/commands/env/helpers.py +++ b/tests/console/commands/env/helpers.py @@ -5,7 +5,9 @@ from poetry.utils._compat import Path -def build_venv(path, executable=None): # type: (Union[Path,str], Optional[str]) -> () +def build_venv( + path, executable=None, flags=None +): # type: (Union[Path,str], Optional[str], bool) -> () Path(path).mkdir(parents=True, exist_ok=True) diff --git a/tests/console/commands/env/test_use.py b/tests/console/commands/env/test_use.py index 563e0ac7ffe..b3ea3458d06 100644 --- a/tests/console/commands/env/test_use.py +++ b/tests/console/commands/env/test_use.py @@ -50,7 +50,9 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file( tester.execute("3.7") venv_py37 = venv_cache / "{}-py3.7".format(venv_name) - mock_build_env.assert_called_with(venv_py37, executable="python3.7") + mock_build_env.assert_called_with( + venv_py37, executable="python3.7", flags={"always-copy": False} + ) envs_file = TOMLFile(venv_cache / "envs.toml") assert envs_file.exists() diff --git a/tests/console/commands/test_config.py b/tests/console/commands/test_config.py index 4bf102073d5..1164938f0bd 100644 --- a/tests/console/commands/test_config.py +++ b/tests/console/commands/test_config.py @@ -19,6 +19,7 @@ def test_list_displays_default_value_if_not_set(tester, config): experimental.new-installer = true virtualenvs.create = true virtualenvs.in-project = null +virtualenvs.options.always-copy = false virtualenvs.path = {path} # /foo{sep}virtualenvs """.format( path=json.dumps(os.path.join("{cache-dir}", "virtualenvs")), sep=os.path.sep @@ -36,6 +37,7 @@ def test_list_displays_set_get_setting(tester, config): experimental.new-installer = true virtualenvs.create = false virtualenvs.in-project = null +virtualenvs.options.always-copy = false virtualenvs.path = {path} # /foo{sep}virtualenvs """.format( path=json.dumps(os.path.join("{cache-dir}", "virtualenvs")), sep=os.path.sep @@ -75,6 +77,7 @@ def test_list_displays_set_get_local_setting(tester, config): experimental.new-installer = true virtualenvs.create = false virtualenvs.in-project = null +virtualenvs.options.always-copy = false virtualenvs.path = {path} # /foo{sep}virtualenvs """.format( path=json.dumps(os.path.join("{cache-dir}", "virtualenvs")), sep=os.path.sep diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index 3779623839f..8c816a2d32a 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -118,7 +118,9 @@ def test_env_get_venv_with_venv_folder_present( assert venv.path == in_project_venv_dir -def build_venv(path, executable=None): # type: (Union[Path,str], Optional[str]) -> () +def build_venv( + path, executable=None, flags=None +): # type: (Union[Path,str], Optional[str], bool) -> () os.mkdir(str(path)) @@ -156,7 +158,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( - Path(tmp_dir) / "{}-py3.7".format(venv_name), executable="python3.7" + Path(tmp_dir) / "{}-py3.7".format(venv_name), + executable="python3.7", + flags={"always-copy": False}, ) envs_file = TOMLFile(Path(tmp_dir) / "envs.toml") @@ -274,7 +278,9 @@ def test_activate_activates_different_virtualenv_with_envs_file( env = manager.activate("python3.6", NullIO()) m.assert_called_with( - Path(tmp_dir) / "{}-py3.6".format(venv_name), executable="python3.6" + Path(tmp_dir) / "{}-py3.6".format(venv_name), + executable="python3.6", + flags={"always-copy": False}, ) assert envs_file.exists() @@ -326,7 +332,9 @@ def test_activate_activates_recreates_for_different_patch( env = manager.activate("python3.7", NullIO()) build_venv_m.assert_called_with( - Path(tmp_dir) / "{}-py3.7".format(venv_name), executable="python3.7" + Path(tmp_dir) / "{}-py3.7".format(venv_name), + executable="python3.7", + flags={"always-copy": False}, ) remove_venv_m.assert_called_with(Path(tmp_dir) / "{}-py3.7".format(venv_name)) @@ -654,7 +662,9 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_generic_ manager.create_venv(NullIO()) m.assert_called_with( - Path("/foo/virtualenvs/{}-py3.7".format(venv_name)), executable="python3" + Path("/foo/virtualenvs/{}-py3.7".format(venv_name)), + executable="python3", + flags={"always-copy": False}, ) @@ -678,7 +688,9 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_specific manager.create_venv(NullIO()) m.assert_called_with( - Path("/foo/virtualenvs/{}-py3.9".format(venv_name)), executable="python3.9" + Path("/foo/virtualenvs/{}-py3.9".format(venv_name)), + executable="python3.9", + flags={"always-copy": False}, ) @@ -767,6 +779,7 @@ def test_create_venv_uses_patch_version_to_detect_compatibility( ) ), executable=None, + flags={"always-copy": False}, ) @@ -804,6 +817,7 @@ def test_create_venv_uses_patch_version_to_detect_compatibility_with_executable( ) ), executable="python{}.{}".format(version.major, version.minor - 1), + flags={"always-copy": False}, ) @@ -834,7 +848,11 @@ def test_activate_with_in_project_setting_does_not_fail_if_no_venvs_dir( manager.activate("python3.7", NullIO()) - m.assert_called_with(poetry.file.parent / ".venv", executable="python3.7") + m.assert_called_with( + poetry.file.parent / ".venv", + executable="python3.7", + flags={"always-copy": False}, + ) envs_file = TOMLFile(Path(tmp_dir) / "virtualenvs" / "envs.toml") assert not envs_file.exists()