Commit 6400f423 by Arun Babu Neelicattu

Use isolated ephemeral envs for editable installs

parent 9a68da31
...@@ -22,8 +22,7 @@ from poetry.core.utils.helpers import parse_requires ...@@ -22,8 +22,7 @@ from poetry.core.utils.helpers import parse_requires
from poetry.core.utils.helpers import temporary_directory from poetry.core.utils.helpers import temporary_directory
from poetry.core.version.markers import InvalidMarker from poetry.core.version.markers import InvalidMarker
from poetry.utils.env import EnvCommandError from poetry.utils.env import EnvCommandError
from poetry.utils.env import EnvManager from poetry.utils.env import ephemeral_environment
from poetry.utils.env import VirtualEnv
from poetry.utils.setup_reader import SetupReader from poetry.utils.setup_reader import SetupReader
...@@ -451,13 +450,9 @@ class PackageInfo: ...@@ -451,13 +450,9 @@ class PackageInfo:
except PackageInfoError: except PackageInfoError:
pass 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 # TODO: cache PEP 517 build environment corresponding to each project venv
venv_dir = Path(tmp_dir) / ".venv" dest_dir = venv.path.parent / "dist"
EnvManager.build_venv(venv_dir.as_posix(), with_pip=True)
venv = VirtualEnv(venv_dir, venv_dir)
dest_dir = Path(tmp_dir) / "dist"
dest_dir.mkdir() dest_dir.mkdir()
try: try:
......
...@@ -22,6 +22,7 @@ from poetry.core.pyproject.toml import PyProjectTOML ...@@ -22,6 +22,7 @@ from poetry.core.pyproject.toml import PyProjectTOML
from poetry.utils._compat import decode from poetry.utils._compat import decode
from poetry.utils.env import EnvCommandError from poetry.utils.env import EnvCommandError
from poetry.utils.helpers import safe_rmtree from poetry.utils.helpers import safe_rmtree
from poetry.utils.pip import pip_editable_install
from .authenticator import Authenticator from .authenticator import Authenticator
from .chef import Chef from .chef import Chef
...@@ -572,14 +573,14 @@ class Executor(object): ...@@ -572,14 +573,14 @@ class Executor(object):
with builder.setup_py(): with builder.setup_py():
if package.develop: if package.develop:
args.append("-e") return pip_editable_install(req, self._env)
args.append(req) args.append(req)
return self.run_pip(*args) return self.run_pip(*args)
if package.develop: if package.develop:
args.append("-e") return pip_editable_install(req, self._env)
args.append(req) args.append(req)
......
...@@ -10,15 +10,15 @@ from typing import Union ...@@ -10,15 +10,15 @@ from typing import Union
from cleo.io.io import IO from cleo.io.io import IO
from poetry.installation.base_installer import BaseInstaller
from poetry.core.pyproject.toml import PyProjectTOML from poetry.core.pyproject.toml import PyProjectTOML
from poetry.installation.base_installer import BaseInstaller
from poetry.repositories.pool import Pool from poetry.repositories.pool import Pool
from poetry.utils._compat import encode from poetry.utils._compat import encode
from poetry.utils.env import Env from poetry.utils.env import Env
from poetry.utils.env import EnvManager
from poetry.utils.env import VirtualEnv
from poetry.utils.helpers import safe_rmtree from poetry.utils.helpers import safe_rmtree
from poetry.utils.helpers import temporary_directory from poetry.utils.pip import pip_editable_install
from poetry.utils.pip import pip_install
if TYPE_CHECKING: if TYPE_CHECKING:
from poetry.core.packages.package import Package from poetry.core.packages.package import Package
...@@ -190,12 +190,12 @@ class PipInstaller(BaseInstaller): ...@@ -190,12 +190,12 @@ class PipInstaller(BaseInstaller):
from poetry.factory import Factory from poetry.factory import Factory
req: Path
if package.root_dir: if package.root_dir:
req = (package.root_dir / package.source_url).as_posix() req = (package.root_dir / package.source_url).as_posix()
else: else:
req = os.path.realpath(package.source_url) req = Path(package.source_url).resolve(strict=False)
args = ["install", "--no-deps", "-U", "--root", str(self._env.path)]
pyproject = PyProjectTOML(os.path.join(req, "pyproject.toml")) pyproject = PyProjectTOML(os.path.join(req, "pyproject.toml"))
...@@ -230,22 +230,16 @@ class PipInstaller(BaseInstaller): ...@@ -230,22 +230,16 @@ class PipInstaller(BaseInstaller):
with builder.setup_py(): with builder.setup_py():
if package.develop: if package.develop:
args.append("-e") return pip_editable_install(
directory=req, environment=self._env
args.append(req) )
return pip_install(
return self.run(*args) path=req, environment=self._env, deps=False, upgrade=True
)
if package.develop: if package.develop:
args.append("-e") return pip_editable_install(directory=req, environment=self._env)
return pip_install(path=req, environment=self._env, deps=False, upgrade=True)
args.append(req)
with temporary_directory() as tmp_dir:
venv_dir = Path(tmp_dir) / ".venv"
EnvManager.build_venv(venv_dir.as_posix(), with_pip=True)
venv = VirtualEnv(venv_dir, venv_dir)
return venv.run("pip", *args)
def install_git(self, package: "Package") -> None: def install_git(self, package: "Package") -> None:
from poetry.core.packages.package import Package from poetry.core.packages.package import Package
......
...@@ -16,6 +16,7 @@ from poetry.core.semver.version import Version ...@@ -16,6 +16,7 @@ from poetry.core.semver.version import Version
from poetry.utils._compat import WINDOWS from poetry.utils._compat import WINDOWS
from poetry.utils._compat import decode from poetry.utils._compat import decode
from poetry.utils.helpers import is_dir_writable from poetry.utils.helpers import is_dir_writable
from poetry.utils.pip import pip_editable_install
if TYPE_CHECKING: if TYPE_CHECKING:
...@@ -56,7 +57,6 @@ class EditableBuilder(Builder): ...@@ -56,7 +57,6 @@ class EditableBuilder(Builder):
self._debug( self._debug(
" - <warning>Falling back on using a <b>setup.py</b></warning>" " - <warning>Falling back on using a <b>setup.py</b></warning>"
) )
return self._setup_build() return self._setup_build()
self._run_build_script(self._package.build_script) self._run_build_script(self._package.build_script)
...@@ -85,14 +85,14 @@ class EditableBuilder(Builder): ...@@ -85,14 +85,14 @@ class EditableBuilder(Builder):
try: try:
if self._env.pip_version < Version(19, 0): 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: else:
# Temporarily rename pyproject.toml # Temporarily rename pyproject.toml
shutil.move( shutil.move(
str(self._poetry.file), str(self._poetry.file.with_suffix(".tmp")) str(self._poetry.file), str(self._poetry.file.with_suffix(".tmp"))
) )
try: try:
self._env.run_pip("install", "-e", str(self._path), "--no-deps") pip_editable_install(self._path, self._env)
finally: finally:
shutil.move( shutil.move(
str(self._poetry.file.with_suffix(".tmp")), str(self._poetry.file.with_suffix(".tmp")),
......
...@@ -43,6 +43,7 @@ from poetry.utils._compat import encode ...@@ -43,6 +43,7 @@ from poetry.utils._compat import encode
from poetry.utils._compat import list_to_shell_command from poetry.utils._compat import list_to_shell_command
from poetry.utils.helpers import is_dir_writable from poetry.utils.helpers import is_dir_writable
from poetry.utils.helpers import paths_csv from poetry.utils.helpers import paths_csv
from poetry.utils.helpers import temporary_directory
GET_ENVIRONMENT_INFO = """\ GET_ENVIRONMENT_INFO = """\
...@@ -812,6 +813,8 @@ class EnvManager(object): ...@@ -812,6 +813,8 @@ class EnvManager(object):
executable: Optional[Union[str, Path]] = None, executable: Optional[Union[str, Path]] = None,
flags: Dict[str, bool] = None, flags: Dict[str, bool] = None,
with_pip: bool = False, with_pip: bool = False,
with_wheel: bool = False,
with_setuptools: bool = False,
) -> virtualenv.run.session.Session: ) -> virtualenv.run.session.Session:
flags = flags or {} flags = flags or {}
...@@ -826,7 +829,16 @@ class EnvManager(object): ...@@ -826,7 +829,16 @@ class EnvManager(object):
] ]
if not with_pip: if not with_pip:
args.extend(["--no-pip", "--no-wheel", "--no-setuptools"]) args.append("--no-pip")
else:
if with_wheel is None:
with_wheel = True
if with_wheel is None or not with_wheel:
args.append("--no-wheel")
if with_setuptools is None or not with_setuptools:
args.append("--no-setuptools")
for flag, value in flags.items(): for flag, value in flags.items():
if value is True: if value is True:
...@@ -1429,6 +1441,21 @@ class NullEnv(SystemEnv): ...@@ -1429,6 +1441,21 @@ class NullEnv(SystemEnv):
return 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): class MockEnv(NullEnv):
def __init__( def __init__(
self, self,
......
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
)
...@@ -66,8 +66,12 @@ def mock_file_downloads(http): ...@@ -66,8 +66,12 @@ def mock_file_downloads(http):
def test_execute_executes_a_batch_of_operations( def test_execute_executes_a_batch_of_operations(
config, pool, io, tmp_dir, mock_file_downloads, env mocker, config, pool, io, tmp_dir, mock_file_downloads, env
): ):
pip_editable_install = mocker.patch(
"poetry.installation.executor.pip_editable_install"
)
config = Config() config = Config()
config.merge({"cache-dir": tmp_dir}) config.merge({"cache-dir": tmp_dir})
...@@ -101,6 +105,7 @@ def test_execute_executes_a_batch_of_operations( ...@@ -101,6 +105,7 @@ def test_execute_executes_a_batch_of_operations(
source_type="git", source_type="git",
source_reference="master", source_reference="master",
source_url="https://github.com/demo/demo.git", source_url="https://github.com/demo/demo.git",
develop=True,
) )
return_code = executor.execute( return_code = executor.execute(
...@@ -131,8 +136,9 @@ Package operations: 4 installs, 1 update, 1 removal ...@@ -131,8 +136,9 @@ Package operations: 4 installs, 1 update, 1 removal
expected = set(expected.splitlines()) expected = set(expected.splitlines())
output = set(io.fetch_output().splitlines()) output = set(io.fetch_output().splitlines())
assert expected == output assert expected == output
assert 6 == len(env.executed) assert 4 == len(env.executed)
assert 0 == return_code assert 0 == return_code
pip_editable_install.assert_called_once()
def test_execute_shows_skipped_operations_if_verbose( def test_execute_shows_skipped_operations_if_verbose(
......
...@@ -12,8 +12,8 @@ from cleo.io.io import IO ...@@ -12,8 +12,8 @@ from cleo.io.io import IO
from cleo.io.null_io import NullIO from cleo.io.null_io import NullIO
from cleo.io.outputs.buffered_output import BufferedOutput from cleo.io.outputs.buffered_output import BufferedOutput
from cleo.io.outputs.output import Verbosity from cleo.io.outputs.output import Verbosity
from deepdiff import DeepDiff from deepdiff import DeepDiff
from poetry.core.packages.project_package import ProjectPackage from poetry.core.packages.project_package import ProjectPackage
from poetry.core.toml.file import TOMLFile from poetry.core.toml.file import TOMLFile
from poetry.factory import Factory from poetry.factory import Factory
...@@ -833,8 +833,7 @@ def test_installer_with_pypi_repository(package, locker, installed, config): ...@@ -833,8 +833,7 @@ def test_installer_with_pypi_repository(package, locker, installed, config):
installer.run() installer.run()
expected = fixture("with-pypi-repository") expected = fixture("with-pypi-repository")
assert not DeepDiff(expected, locker.written_data, ignore_order=True)
assert not DeepDiff(locker.written_data, expected, ignore_order=True)
def test_run_installs_with_local_file(installer, locker, repo, package, fixture_dir): def test_run_installs_with_local_file(installer, locker, repo, package, fixture_dir):
......
...@@ -7,8 +7,8 @@ from pathlib import Path ...@@ -7,8 +7,8 @@ from pathlib import Path
import pytest import pytest
from cleo.io.null_io import NullIO from cleo.io.null_io import NullIO
from deepdiff import DeepDiff from deepdiff import DeepDiff
from poetry.core.packages.project_package import ProjectPackage from poetry.core.packages.project_package import ProjectPackage
from poetry.core.toml.file import TOMLFile from poetry.core.toml.file import TOMLFile
from poetry.factory import Factory from poetry.factory import Factory
...@@ -738,8 +738,7 @@ def test_installer_with_pypi_repository(package, locker, installed, config): ...@@ -738,8 +738,7 @@ def test_installer_with_pypi_repository(package, locker, installed, config):
installer.run() installer.run()
expected = fixture("with-pypi-repository") expected = fixture("with-pypi-repository")
assert not DeepDiff(expected, locker.written_data, ignore_order=True)
assert not DeepDiff(locker.written_data, expected, ignore_order=True)
def test_run_installs_with_local_file(installer, locker, repo, package, fixture_dir): def test_run_installs_with_local_file(installer, locker, repo, package, fixture_dir):
......
...@@ -179,16 +179,19 @@ if __name__ == '__main__': ...@@ -179,16 +179,19 @@ if __name__ == '__main__':
def test_builder_falls_back_on_setup_and_pip_for_packages_with_build_scripts( def test_builder_falls_back_on_setup_and_pip_for_packages_with_build_scripts(
extended_poetry, tmp_dir mocker, extended_poetry, tmp_dir
): ):
pip_editable_install = mocker.patch(
"poetry.masonry.builders.editable.pip_editable_install"
)
env = MockEnv(path=Path(tmp_dir) / "foo") env = MockEnv(path=Path(tmp_dir) / "foo")
builder = EditableBuilder(extended_poetry, env, NullIO()) builder = EditableBuilder(extended_poetry, env, NullIO())
builder.build() builder.build()
assert [ pip_editable_install.assert_called_once_with(
env.get_pip_command() extended_poetry.pyproject.file.path.parent, env
+ ["install", "-e", str(extended_poetry.file.parent), "--no-deps"] )
] == env.executed assert [] == env.executed
def test_builder_installs_proper_files_when_packages_configured( def test_builder_installs_proper_files_when_packages_configured(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment