Skip to content

Commit 7401836

Browse files
committed
Use isolated ephemeral envs for editable installs
1 parent 7035130 commit 7401836

File tree

10 files changed

+100
-28
lines changed

10 files changed

+100
-28
lines changed

poetry/inspection/info.py

+3-8
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@
2222
from poetry.core.utils.helpers import temporary_directory
2323
from poetry.core.version.markers import InvalidMarker
2424
from poetry.utils.env import EnvCommandError
25-
from poetry.utils.env import EnvManager
26-
from poetry.utils.env import VirtualEnv
25+
from poetry.utils.env import ephemeral_environment
2726
from poetry.utils.setup_reader import SetupReader
2827
from poetry.utils.toml_file import TomlFile
2928

@@ -441,13 +440,9 @@ def _pep517_metadata(cls, path): # type (Path) -> PackageInfo
441440
except PackageInfoError:
442441
pass
443442

444-
with temporary_directory() as tmp_dir:
443+
with ephemeral_environment(pip=True, wheel=True, setuptools=True) as venv:
445444
# TODO: cache PEP 517 build environment corresponding to each project venv
446-
venv_dir = Path(tmp_dir) / ".venv"
447-
EnvManager.build_venv(venv_dir.as_posix(), with_pip=True)
448-
venv = VirtualEnv(venv_dir, venv_dir)
449-
450-
dest_dir = Path(tmp_dir) / "dist"
445+
dest_dir = venv.path.parent / "dist"
451446
dest_dir.mkdir()
452447

453448
try:

poetry/installation/executor.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from poetry.utils._compat import decode
2121
from poetry.utils.env import EnvCommandError
2222
from poetry.utils.helpers import safe_rmtree
23+
from poetry.utils.pip import pip_editable_install
2324

2425
from .authenticator import Authenticator
2526
from .chef import Chef
@@ -523,14 +524,14 @@ def _install_directory(self, operation):
523524

524525
with builder.setup_py():
525526
if package.develop:
526-
args.append("-e")
527+
return pip_editable_install(req, self._env)
527528

528529
args.append(req)
529530

530531
return self.run_pip(*args)
531532

532533
if package.develop:
533-
args.append("-e")
534+
return pip_editable_install(req, self._env)
534535

535536
args.append(req)
536537

poetry/installation/pip_installer.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from poetry.utils.env import VirtualEnv
1515
from poetry.utils.helpers import safe_rmtree
1616
from poetry.utils.helpers import temporary_directory
17+
from poetry.utils.pip import pip_editable_install
1718

1819

1920
try:
@@ -235,14 +236,13 @@ def install_directory(self, package):
235236

236237
with builder.setup_py():
237238
if package.develop:
238-
args.append("-e")
239-
239+
return pip_editable_install(req, self._env)
240240
args.append(req)
241241

242242
return self.run(*args)
243243

244244
if package.develop:
245-
args.append("-e")
245+
return pip_editable_install(req, self._env)
246246

247247
args.append(req)
248248

poetry/masonry/builders/editable.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from poetry.utils._compat import WINDOWS
1414
from poetry.utils._compat import Path
1515
from poetry.utils._compat import decode
16+
from poetry.utils.pip import pip_editable_install
1617

1718

1819
SCRIPT_TEMPLATE = """\
@@ -47,7 +48,6 @@ def build(self):
4748
self._debug(
4849
" - <warning>Falling back on using a <b>setup.py</b></warning>"
4950
)
50-
5151
return self._setup_build()
5252

5353
self._run_build_script(self._package.build_script)
@@ -76,14 +76,14 @@ def _setup_build(self):
7676

7777
try:
7878
if self._env.pip_version < Version(19, 0):
79-
self._env.run_pip("install", "-e", str(self._path), "--no-deps")
79+
pip_editable_install(self._path, self._env)
8080
else:
8181
# Temporarily rename pyproject.toml
8282
shutil.move(
8383
str(self._poetry.file), str(self._poetry.file.with_suffix(".tmp"))
8484
)
8585
try:
86-
self._env.run_pip("install", "-e", str(self._path), "--no-deps")
86+
pip_editable_install(self._path, self._env)
8787
finally:
8888
shutil.move(
8989
str(self._poetry.file.with_suffix(".tmp")),

poetry/utils/env.py

+33-3
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
from poetry.utils._compat import encode
4040
from poetry.utils._compat import list_to_shell_command
4141
from poetry.utils._compat import subprocess
42+
from poetry.utils.helpers import temporary_directory
4243
from poetry.utils.toml_file import TomlFile
4344

4445

@@ -680,8 +681,13 @@ def create_venv(
680681

681682
@classmethod
682683
def build_venv(
683-
cls, path, executable=None, with_pip=False
684-
): # type: (Union[Path,str], Optional[Union[str, Path]], bool) -> virtualenv.run.session.Session
684+
cls,
685+
path,
686+
executable=None,
687+
with_pip=False,
688+
with_wheel=None,
689+
with_setuptools=None,
690+
): # type: (Union[Path,str], Optional[Union[str, Path]], bool, Optional[bool], Optional[bool]) -> virtualenv.run.session.Session
685691
if isinstance(executable, Path):
686692
executable = executable.resolve().as_posix()
687693

@@ -693,7 +699,16 @@ def build_venv(
693699
]
694700

695701
if not with_pip:
696-
opts.extend(["--no-pip", "--no-wheel", "--no-setuptools"])
702+
opts.append("--no-pip")
703+
else:
704+
if with_wheel is None:
705+
with_wheel = True
706+
707+
if with_wheel is None or not with_wheel:
708+
opts.append("--no-wheel")
709+
710+
if with_setuptools is None or not with_setuptools:
711+
opts.append("--no-setuptools")
697712

698713
opts.append(str(path))
699714

@@ -1250,6 +1265,21 @@ def _bin(self, bin):
12501265
return bin
12511266

12521267

1268+
@contextmanager
1269+
def ephemeral_environment(executable=None, pip=False, wheel=None, setuptools=None):
1270+
with temporary_directory() as tmp_dir:
1271+
# TODO: cache PEP 517 build environment corresponding to each project venv
1272+
venv_dir = Path(tmp_dir) / ".venv"
1273+
EnvManager.build_venv(
1274+
path=venv_dir.as_posix(),
1275+
executable=executable,
1276+
with_pip=pip,
1277+
with_wheel=wheel,
1278+
with_setuptools=setuptools,
1279+
)
1280+
yield VirtualEnv(venv_dir, venv_dir)
1281+
1282+
12531283
class MockEnv(NullEnv):
12541284
def __init__(
12551285
self,

poetry/utils/pip.py

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from poetry.exceptions import PoetryException
2+
from poetry.utils._compat import Path
3+
from poetry.utils.env import Env
4+
from poetry.utils.env import ephemeral_environment
5+
6+
7+
def pip_install(
8+
path, environment, editable=False, deps=False, upgrade=False
9+
): # type: (Path, Env, bool, bool, bool) -> None
10+
path = Path(path) if isinstance(path, str) else path
11+
12+
args = ["pip", "install", "--prefix", str(environment.path)]
13+
14+
if upgrade:
15+
args.append("--upgrade")
16+
17+
if not deps:
18+
args.append("--no-deps")
19+
20+
if editable:
21+
if not path.is_dir():
22+
raise PoetryException(
23+
"Cannot install non directory dependencies in editable mode"
24+
)
25+
args.append("-e")
26+
27+
args.append(str(path))
28+
29+
with ephemeral_environment(
30+
executable=environment.python, pip=True, setuptools=True
31+
) as env:
32+
return env.run(*args)
33+
34+
35+
def pip_editable_install(directory, environment): # type: (Path, Env) -> None
36+
return pip_install(
37+
path=directory, environment=environment, editable=True, deps=False, upgrade=True
38+
)

tests/installation/test_executor.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,12 @@ def callback(request, uri, headers):
5757

5858

5959
def test_execute_executes_a_batch_of_operations(
60-
config, pool, io, tmp_dir, mock_file_downloads
60+
mocker, config, pool, io, tmp_dir, mock_file_downloads
6161
):
62+
pip_editable_install = mocker.patch(
63+
"poetry.installation.executor.pip_editable_install"
64+
)
65+
6266
config = Config()
6367
config.merge({"cache-dir": tmp_dir})
6468

@@ -123,7 +127,8 @@ def test_execute_executes_a_batch_of_operations(
123127
expected = set(expected.splitlines())
124128
output = set(io.fetch_output().splitlines())
125129
assert expected == output
126-
assert 5 == len(env.executed)
130+
assert 4 == len(env.executed)
131+
pip_editable_install.assert_called_once()
127132

128133

129134
def test_execute_shows_skipped_operations_if_verbose(config, pool, io):

tests/installation/test_installer.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
import pytest
77

88
from clikit.io import NullIO
9-
109
from deepdiff import DeepDiff
10+
1111
from poetry.core.packages import ProjectPackage
1212
from poetry.factory import Factory
1313
from poetry.installation import Installer as BaseInstaller

tests/installation/test_installer_old.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
import pytest
66

77
from clikit.io import NullIO
8-
98
from deepdiff import DeepDiff
9+
1010
from poetry.core.packages import ProjectPackage
1111
from poetry.factory import Factory
1212
from poetry.installation import Installer as BaseInstaller

tests/masonry/builders/test_editable_builder.py

+8-5
Original file line numberDiff line numberDiff line change
@@ -164,16 +164,19 @@ def test_builder_installs_proper_files_for_standard_packages(simple_poetry, tmp_
164164

165165

166166
def test_builder_falls_back_on_setup_and_pip_for_packages_with_build_scripts(
167-
extended_poetry,
167+
mocker, extended_poetry,
168168
):
169+
pip_editable_install = mocker.patch(
170+
"poetry.masonry.builders.editable.pip_editable_install"
171+
)
169172
env = MockEnv(path=Path("/foo"))
170173
builder = EditableBuilder(extended_poetry, env, NullIO())
171174

172175
builder.build()
173-
assert [
174-
env.get_pip_command()
175-
+ ["install", "-e", str(extended_poetry.file.parent), "--no-deps"]
176-
] == env.executed
176+
pip_editable_install.assert_called_once_with(
177+
extended_poetry.pyproject.file.path.parent, env
178+
)
179+
assert [] == env.executed
177180

178181

179182
def test_builder_installs_proper_files_when_packages_configured(

0 commit comments

Comments
 (0)