Skip to content

Commit

Permalink
Use isolated ephemeral envs for editable installs
Browse files Browse the repository at this point in the history
  • Loading branch information
abn committed Sep 29, 2020
1 parent 7035130 commit 7401836
Show file tree
Hide file tree
Showing 10 changed files with 100 additions and 28 deletions.
11 changes: 3 additions & 8 deletions poetry/inspection/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@
from poetry.core.utils.helpers import temporary_directory
from poetry.core.version.markers import InvalidMarker
from poetry.utils.env import EnvCommandError
from poetry.utils.env import EnvManager
from poetry.utils.env import VirtualEnv
from poetry.utils.env import ephemeral_environment
from poetry.utils.setup_reader import SetupReader
from poetry.utils.toml_file import TomlFile

Expand Down Expand Up @@ -441,13 +440,9 @@ def _pep517_metadata(cls, path): # type (Path) -> PackageInfo
except PackageInfoError:
pass

with temporary_directory() as tmp_dir:
with ephemeral_environment(pip=True, wheel=True, setuptools=True) as venv:
# TODO: cache PEP 517 build environment corresponding to each project venv
venv_dir = Path(tmp_dir) / ".venv"
EnvManager.build_venv(venv_dir.as_posix(), with_pip=True)
venv = VirtualEnv(venv_dir, venv_dir)

dest_dir = Path(tmp_dir) / "dist"
dest_dir = venv.path.parent / "dist"
dest_dir.mkdir()

try:
Expand Down
5 changes: 3 additions & 2 deletions poetry/installation/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from poetry.utils._compat import decode
from poetry.utils.env import EnvCommandError
from poetry.utils.helpers import safe_rmtree
from poetry.utils.pip import pip_editable_install

from .authenticator import Authenticator
from .chef import Chef
Expand Down Expand Up @@ -523,14 +524,14 @@ def _install_directory(self, operation):

with builder.setup_py():
if package.develop:
args.append("-e")
return pip_editable_install(req, self._env)

args.append(req)

return self.run_pip(*args)

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

args.append(req)

Expand Down
6 changes: 3 additions & 3 deletions poetry/installation/pip_installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from poetry.utils.env import VirtualEnv
from poetry.utils.helpers import safe_rmtree
from poetry.utils.helpers import temporary_directory
from poetry.utils.pip import pip_editable_install


try:
Expand Down Expand Up @@ -235,14 +236,13 @@ def install_directory(self, package):

with builder.setup_py():
if package.develop:
args.append("-e")

return pip_editable_install(req, self._env)
args.append(req)

return self.run(*args)

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

args.append(req)

Expand Down
6 changes: 3 additions & 3 deletions poetry/masonry/builders/editable.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from poetry.utils._compat import WINDOWS
from poetry.utils._compat import Path
from poetry.utils._compat import decode
from poetry.utils.pip import pip_editable_install


SCRIPT_TEMPLATE = """\
Expand Down Expand Up @@ -47,7 +48,6 @@ def build(self):
self._debug(
" - <warning>Falling back on using a <b>setup.py</b></warning>"
)

return self._setup_build()

self._run_build_script(self._package.build_script)
Expand Down Expand Up @@ -76,14 +76,14 @@ def _setup_build(self):

try:
if self._env.pip_version < Version(19, 0):
self._env.run_pip("install", "-e", str(self._path), "--no-deps")
pip_editable_install(self._path, self._env)
else:
# Temporarily rename pyproject.toml
shutil.move(
str(self._poetry.file), str(self._poetry.file.with_suffix(".tmp"))
)
try:
self._env.run_pip("install", "-e", str(self._path), "--no-deps")
pip_editable_install(self._path, self._env)
finally:
shutil.move(
str(self._poetry.file.with_suffix(".tmp")),
Expand Down
36 changes: 33 additions & 3 deletions poetry/utils/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from poetry.utils._compat import encode
from poetry.utils._compat import list_to_shell_command
from poetry.utils._compat import subprocess
from poetry.utils.helpers import temporary_directory
from poetry.utils.toml_file import TomlFile


Expand Down Expand Up @@ -680,8 +681,13 @@ def create_venv(

@classmethod
def build_venv(
cls, path, executable=None, with_pip=False
): # type: (Union[Path,str], Optional[Union[str, Path]], bool) -> virtualenv.run.session.Session
cls,
path,
executable=None,
with_pip=False,
with_wheel=None,
with_setuptools=None,
): # type: (Union[Path,str], Optional[Union[str, Path]], bool, Optional[bool], Optional[bool]) -> virtualenv.run.session.Session
if isinstance(executable, Path):
executable = executable.resolve().as_posix()

Expand All @@ -693,7 +699,16 @@ def build_venv(
]

if not with_pip:
opts.extend(["--no-pip", "--no-wheel", "--no-setuptools"])
opts.append("--no-pip")
else:
if with_wheel is None:
with_wheel = True

if with_wheel is None or not with_wheel:
opts.append("--no-wheel")

if with_setuptools is None or not with_setuptools:
opts.append("--no-setuptools")

opts.append(str(path))

Expand Down Expand Up @@ -1250,6 +1265,21 @@ def _bin(self, bin):
return bin


@contextmanager
def ephemeral_environment(executable=None, pip=False, wheel=None, setuptools=None):
with temporary_directory() as tmp_dir:
# TODO: cache PEP 517 build environment corresponding to each project venv
venv_dir = Path(tmp_dir) / ".venv"
EnvManager.build_venv(
path=venv_dir.as_posix(),
executable=executable,
with_pip=pip,
with_wheel=wheel,
with_setuptools=setuptools,
)
yield VirtualEnv(venv_dir, venv_dir)


class MockEnv(NullEnv):
def __init__(
self,
Expand Down
38 changes: 38 additions & 0 deletions poetry/utils/pip.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from poetry.exceptions import PoetryException
from poetry.utils._compat import Path
from poetry.utils.env import Env
from poetry.utils.env import ephemeral_environment


def pip_install(
path, environment, editable=False, deps=False, upgrade=False
): # type: (Path, Env, bool, bool, bool) -> None
path = Path(path) if isinstance(path, str) else path

args = ["pip", "install", "--prefix", str(environment.path)]

if upgrade:
args.append("--upgrade")

if not deps:
args.append("--no-deps")

if editable:
if not path.is_dir():
raise PoetryException(
"Cannot install non directory dependencies in editable mode"
)
args.append("-e")

args.append(str(path))

with ephemeral_environment(
executable=environment.python, pip=True, setuptools=True
) as env:
return env.run(*args)


def pip_editable_install(directory, environment): # type: (Path, Env) -> None
return pip_install(
path=directory, environment=environment, editable=True, deps=False, upgrade=True
)
9 changes: 7 additions & 2 deletions tests/installation/test_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,12 @@ def callback(request, uri, headers):


def test_execute_executes_a_batch_of_operations(
config, pool, io, tmp_dir, mock_file_downloads
mocker, config, pool, io, tmp_dir, mock_file_downloads
):
pip_editable_install = mocker.patch(
"poetry.installation.executor.pip_editable_install"
)

config = Config()
config.merge({"cache-dir": tmp_dir})

Expand Down Expand Up @@ -123,7 +127,8 @@ def test_execute_executes_a_batch_of_operations(
expected = set(expected.splitlines())
output = set(io.fetch_output().splitlines())
assert expected == output
assert 5 == len(env.executed)
assert 4 == len(env.executed)
pip_editable_install.assert_called_once()


def test_execute_shows_skipped_operations_if_verbose(config, pool, io):
Expand Down
2 changes: 1 addition & 1 deletion tests/installation/test_installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
import pytest

from clikit.io import NullIO

from deepdiff import DeepDiff

from poetry.core.packages import ProjectPackage
from poetry.factory import Factory
from poetry.installation import Installer as BaseInstaller
Expand Down
2 changes: 1 addition & 1 deletion tests/installation/test_installer_old.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import pytest

from clikit.io import NullIO

from deepdiff import DeepDiff

from poetry.core.packages import ProjectPackage
from poetry.factory import Factory
from poetry.installation import Installer as BaseInstaller
Expand Down
13 changes: 8 additions & 5 deletions tests/masonry/builders/test_editable_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,16 +164,19 @@ def test_builder_installs_proper_files_for_standard_packages(simple_poetry, tmp_


def test_builder_falls_back_on_setup_and_pip_for_packages_with_build_scripts(
extended_poetry,
mocker, extended_poetry,
):
pip_editable_install = mocker.patch(
"poetry.masonry.builders.editable.pip_editable_install"
)
env = MockEnv(path=Path("/foo"))
builder = EditableBuilder(extended_poetry, env, NullIO())

builder.build()
assert [
env.get_pip_command()
+ ["install", "-e", str(extended_poetry.file.parent), "--no-deps"]
] == env.executed
pip_editable_install.assert_called_once_with(
extended_poetry.pyproject.file.path.parent, env
)
assert [] == env.executed


def test_builder_installs_proper_files_when_packages_configured(
Expand Down

0 comments on commit 7401836

Please sign in to comment.