Skip to content

Commit cb34c3d

Browse files
authored
config: support virtualenv --always-copy option
Mitigates: #3134
1 parent 9c8155c commit cb34c3d

File tree

8 files changed

+90
-25
lines changed

8 files changed

+90
-25
lines changed

docs/docs/configuration.md

+7
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ which will give you something similar to this:
3434
cache-dir = "/path/to/cache/directory"
3535
virtualenvs.create = true
3636
virtualenvs.in-project = null
37+
virtualenvs.options.always-copy = true
3738
virtualenvs.path = "{cache-dir}/virtualenvs" # /path/to/cache/directory/virtualenvs
3839
```
3940

@@ -128,6 +129,12 @@ If not set explicitly (default), `poetry` will use the virtualenv from the `.ven
128129
Directory where virtual environments will be created.
129130
Defaults to `{cache-dir}/virtualenvs` (`{cache-dir}\virtualenvs` on Windows).
130131

132+
### `virtualenvs.options.always-copy`: boolean
133+
134+
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.
135+
Defaults to `false`.
136+
137+
131138
### `repositories.<name>`: string
132139

133140
Set a new alternative repository. See [Repositories](/docs/repositories/) for more information.

poetry/config/config.py

+16-3
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class Config(object):
3636
"create": True,
3737
"in-project": None,
3838
"path": os.path.join("{cache-dir}", "virtualenvs"),
39+
"options": {"always-copy": False},
3940
},
4041
"experimental": {"new-installer": True},
4142
}
@@ -87,7 +88,11 @@ def _all(config, parent_key=""):
8788
for key in config:
8889
value = self.get(parent_key + key)
8990
if isinstance(value, dict):
90-
all_[key] = _all(config[key], parent_key=key + ".")
91+
if parent_key != "":
92+
current_parent = parent_key + key + "."
93+
else:
94+
current_parent = key + "."
95+
all_[key] = _all(config[key], parent_key=current_parent)
9196
continue
9297

9398
all_[key] = value
@@ -131,14 +136,22 @@ def process(self, value): # type: (Any) -> Any
131136
return re.sub(r"{(.+?)}", lambda m: self.get(m.group(1)), value)
132137

133138
def _get_validator(self, name): # type: (str) -> Callable
134-
if name in {"virtualenvs.create", "virtualenvs.in-project"}:
139+
if name in {
140+
"virtualenvs.create",
141+
"virtualenvs.in-project",
142+
"virtualenvs.options.always-copy",
143+
}:
135144
return boolean_validator
136145

137146
if name == "virtualenvs.path":
138147
return str
139148

140149
def _get_normalizer(self, name): # type: (str) -> Callable
141-
if name in {"virtualenvs.create", "virtualenvs.in-project"}:
150+
if name in {
151+
"virtualenvs.create",
152+
"virtualenvs.in-project",
153+
"virtualenvs.options.always-copy",
154+
}:
142155
return boolean_normalizer
143156

144157
if name == "virtualenvs.path":

poetry/console/commands/config.py

+5
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ def unique_config_values(self):
6464
boolean_normalizer,
6565
True,
6666
),
67+
"virtualenvs.options.always-copy": (
68+
boolean_validator,
69+
boolean_normalizer,
70+
False,
71+
),
6772
}
6873

6974
return unique_config_values

poetry/utils/env.py

+28-13
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,11 @@ def create_venv(
642642
"Creating virtualenv <c1>{}</> in {}".format(name, str(venv_path))
643643
)
644644

645-
self.build_venv(venv, executable=executable)
645+
self.build_venv(
646+
venv,
647+
executable=executable,
648+
flags=self._poetry.config.get("virtualenvs.options"),
649+
)
646650
else:
647651
if force:
648652
if not env.is_sane():
@@ -655,7 +659,11 @@ def create_venv(
655659
"Recreating virtualenv <c1>{}</> in {}".format(name, str(venv))
656660
)
657661
self.remove_venv(venv)
658-
self.build_venv(venv, executable=executable)
662+
self.build_venv(
663+
venv,
664+
executable=executable,
665+
flags=self._poetry.config.get("virtualenvs.options"),
666+
)
659667
elif io.is_very_verbose():
660668
io.write_line("Virtualenv <c1>{}</> already exists.".format(name))
661669

@@ -679,19 +687,26 @@ def create_venv(
679687

680688
@classmethod
681689
def build_venv(
682-
cls, path, executable=None
683-
): # type: (Union[Path,str], Optional[Union[str, Path]]) -> virtualenv.run.session.Session
690+
cls, path, executable=None, flags=None
691+
): # type: (Union[Path,str], Optional[Union[str, Path]], Dict[str, bool]) -> virtualenv.run.session.Session
692+
flags = flags or {}
693+
684694
if isinstance(executable, Path):
685695
executable = executable.resolve().as_posix()
686-
return virtualenv.cli_run(
687-
[
688-
"--no-download",
689-
"--no-periodic-update",
690-
"--python",
691-
executable or sys.executable,
692-
str(path),
693-
]
694-
)
696+
697+
args = [
698+
"--no-download",
699+
"--no-periodic-update",
700+
"--python",
701+
executable or sys.executable,
702+
str(path),
703+
]
704+
705+
for flag, value in flags.items():
706+
if value is True:
707+
args.insert(0, "--{}".format(flag))
708+
709+
return virtualenv.cli_run(args)
695710

696711
@classmethod
697712
def remove_venv(cls, path): # type: (Union[Path,str]) -> None

tests/console/commands/env/helpers.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
from poetry.utils._compat import Path
66

77

8-
def build_venv(path, executable=None): # type: (Union[Path,str], Optional[str]) -> ()
8+
def build_venv(
9+
path, executable=None, flags=None
10+
): # type: (Union[Path,str], Optional[str], bool) -> ()
911
Path(path).mkdir(parents=True, exist_ok=True)
1012

1113

tests/console/commands/env/test_use.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file(
5050
tester.execute("3.7")
5151

5252
venv_py37 = venv_cache / "{}-py3.7".format(venv_name)
53-
mock_build_env.assert_called_with(venv_py37, executable="python3.7")
53+
mock_build_env.assert_called_with(
54+
venv_py37, executable="python3.7", flags={"always-copy": False}
55+
)
5456

5557
envs_file = TOMLFile(venv_cache / "envs.toml")
5658
assert envs_file.exists()

tests/console/commands/test_config.py

+3
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ def test_list_displays_default_value_if_not_set(tester, config):
3030
experimental.new-installer = true
3131
virtualenvs.create = true
3232
virtualenvs.in-project = null
33+
virtualenvs.options.always-copy = false
3334
virtualenvs.path = {path} # /foo{sep}virtualenvs
3435
""".format(
3536
path=json.dumps(os.path.join("{cache-dir}", "virtualenvs")), sep=os.path.sep
@@ -47,6 +48,7 @@ def test_list_displays_set_get_setting(tester, config):
4748
experimental.new-installer = true
4849
virtualenvs.create = false
4950
virtualenvs.in-project = null
51+
virtualenvs.options.always-copy = false
5052
virtualenvs.path = {path} # /foo{sep}virtualenvs
5153
""".format(
5254
path=json.dumps(os.path.join("{cache-dir}", "virtualenvs")), sep=os.path.sep
@@ -86,6 +88,7 @@ def test_list_displays_set_get_local_setting(tester, config):
8688
experimental.new-installer = true
8789
virtualenvs.create = false
8890
virtualenvs.in-project = null
91+
virtualenvs.options.always-copy = false
8992
virtualenvs.path = {path} # /foo{sep}virtualenvs
9093
""".format(
9194
path=json.dumps(os.path.join("{cache-dir}", "virtualenvs")), sep=os.path.sep

tests/utils/test_env.py

+25-7
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,9 @@ def test_env_get_venv_with_venv_folder_present(
118118
assert venv.path == in_project_venv_dir
119119

120120

121-
def build_venv(path, executable=None): # type: (Union[Path,str], Optional[str]) -> ()
121+
def build_venv(
122+
path, executable=None, flags=None
123+
): # type: (Union[Path,str], Optional[str], bool) -> ()
122124
os.mkdir(str(path))
123125

124126

@@ -156,7 +158,9 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file(
156158
venv_name = EnvManager.generate_env_name("simple-project", str(poetry.file.parent))
157159

158160
m.assert_called_with(
159-
Path(tmp_dir) / "{}-py3.7".format(venv_name), executable="python3.7"
161+
Path(tmp_dir) / "{}-py3.7".format(venv_name),
162+
executable="python3.7",
163+
flags={"always-copy": False},
160164
)
161165

162166
envs_file = TOMLFile(Path(tmp_dir) / "envs.toml")
@@ -274,7 +278,9 @@ def test_activate_activates_different_virtualenv_with_envs_file(
274278
env = manager.activate("python3.6", NullIO())
275279

276280
m.assert_called_with(
277-
Path(tmp_dir) / "{}-py3.6".format(venv_name), executable="python3.6"
281+
Path(tmp_dir) / "{}-py3.6".format(venv_name),
282+
executable="python3.6",
283+
flags={"always-copy": False},
278284
)
279285

280286
assert envs_file.exists()
@@ -326,7 +332,9 @@ def test_activate_activates_recreates_for_different_patch(
326332
env = manager.activate("python3.7", NullIO())
327333

328334
build_venv_m.assert_called_with(
329-
Path(tmp_dir) / "{}-py3.7".format(venv_name), executable="python3.7"
335+
Path(tmp_dir) / "{}-py3.7".format(venv_name),
336+
executable="python3.7",
337+
flags={"always-copy": False},
330338
)
331339
remove_venv_m.assert_called_with(Path(tmp_dir) / "{}-py3.7".format(venv_name))
332340

@@ -654,7 +662,9 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_generic_
654662
manager.create_venv(NullIO())
655663

656664
m.assert_called_with(
657-
Path("/foo/virtualenvs/{}-py3.7".format(venv_name)), executable="python3"
665+
Path("/foo/virtualenvs/{}-py3.7".format(venv_name)),
666+
executable="python3",
667+
flags={"always-copy": False},
658668
)
659669

660670

@@ -678,7 +688,9 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_specific
678688
manager.create_venv(NullIO())
679689

680690
m.assert_called_with(
681-
Path("/foo/virtualenvs/{}-py3.9".format(venv_name)), executable="python3.9"
691+
Path("/foo/virtualenvs/{}-py3.9".format(venv_name)),
692+
executable="python3.9",
693+
flags={"always-copy": False},
682694
)
683695

684696

@@ -767,6 +779,7 @@ def test_create_venv_uses_patch_version_to_detect_compatibility(
767779
)
768780
),
769781
executable=None,
782+
flags={"always-copy": False},
770783
)
771784

772785

@@ -804,6 +817,7 @@ def test_create_venv_uses_patch_version_to_detect_compatibility_with_executable(
804817
)
805818
),
806819
executable="python{}.{}".format(version.major, version.minor - 1),
820+
flags={"always-copy": False},
807821
)
808822

809823

@@ -834,7 +848,11 @@ def test_activate_with_in_project_setting_does_not_fail_if_no_venvs_dir(
834848

835849
manager.activate("python3.7", NullIO())
836850

837-
m.assert_called_with(poetry.file.parent / ".venv", executable="python3.7")
851+
m.assert_called_with(
852+
poetry.file.parent / ".venv",
853+
executable="python3.7",
854+
flags={"always-copy": False},
855+
)
838856

839857
envs_file = TOMLFile(Path(tmp_dir) / "virtualenvs" / "envs.toml")
840858
assert not envs_file.exists()

0 commit comments

Comments
 (0)