Skip to content

Commit

Permalink
Use the wheel builder to build editable wheels
Browse files Browse the repository at this point in the history
  • Loading branch information
sdispater committed Aug 23, 2022
1 parent ca99889 commit 62fb172
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 40 deletions.
12 changes: 8 additions & 4 deletions src/poetry/installation/chef.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,18 +94,22 @@ def __init__(self, config: Config, env: Env) -> None:
)
self._lock = threading.Lock()

def prepare(self, archive: Path, output_dir: Path | None = None) -> Path:
def prepare(
self, archive: Path, output_dir: Path | None = None, *, editable: bool = False
) -> Path:
if not self.should_prepare(archive):
return archive

if archive.is_dir():
tmp_dir = tempfile.mkdtemp(prefix="poetry-chef-")

return self._prepare(archive, Path(tmp_dir))
return self._prepare(archive, Path(tmp_dir), editable=editable)

return self._prepare_sdist(archive, destination=output_dir)

def _prepare(self, directory: Path, destination: Path) -> Path:
def _prepare(
self, directory: Path, destination: Path, *, editable: bool = False
) -> Path:
from poetry.utils.env import EnvManager
from poetry.utils.env import VirtualEnv

Expand All @@ -129,7 +133,7 @@ def _prepare(self, directory: Path, destination: Path) -> Path:
try:
return Path(
builder.build(
"wheel",
"wheel" if not editable else "editable",
destination.as_posix(),
)
)
Expand Down
8 changes: 1 addition & 7 deletions src/poetry/installation/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,7 @@ def _prepare_archive(self, operation: Install | Update) -> Path:
if not Path(package.source_url).is_absolute() and package.root_dir:
archive = package.root_dir / archive

return self._chef.prepare(archive)
return self._chef.prepare(archive, editable=package.develop)

def _install_directory(self, operation: Install | Update) -> int:
package = operation.package
Expand All @@ -554,12 +554,6 @@ def _install_directory(self, operation: Install | Update) -> int:
if package.source_subdirectory:
req /= package.source_subdirectory

if package.develop:
# Editable installations are currently not supported
# for PEP-517 build systems so we defer to pip.
# TODO: Remove this workaround once either PEP-660 or PEP-662 is accepted
return self.pip_install(req, editable=True)

archive = self._prepare_archive(operation)

try:
Expand Down
14 changes: 14 additions & 0 deletions tests/installation/test_chef.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from pathlib import Path
from typing import TYPE_CHECKING
from zipfile import ZipFile

import pytest

Expand Down Expand Up @@ -165,3 +166,16 @@ def test_prepare_directory_with_extensions(
wheel = chef.prepare(archive)

assert wheel.name == f"extended-0.1-{env.supported_tags[0]}.whl"


def test_prepare_directory_editable(config: Config, config_cache_dir: Path):
chef = Chef(config, EnvManager.get_system_env())

archive = Path(__file__).parent.parent.joinpath("fixtures/simple_project").resolve()

wheel = chef.prepare(archive, editable=True)

assert wheel.name == "simple_project-1.2.3-py2.py3-none-any.whl"

with ZipFile(wheel) as z:
assert "simple_project.pth" in z.namelist()
105 changes: 76 additions & 29 deletions tests/installation/test_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,19 @@
import json
import re
import shutil
import tempfile

from pathlib import Path
from typing import TYPE_CHECKING
from typing import Any
from typing import Callable
from urllib.parse import urlparse

import pytest

from cleo.formatters.style import Style
from cleo.io.buffered_io import BufferedIO
from cleo.io.outputs.output import Verbosity
from poetry.core.packages.package import Package
from poetry.core.packages.utils.link import Link

Expand Down Expand Up @@ -41,26 +44,40 @@


class Chef(BaseChef):
_directory_wheel = None
_sdist_wheel = None
_directory_wheels: list[Path] | None = None
_sdist_wheels: list[Path] | None = None

def set_directory_wheel(self, wheel: Path) -> None:
self._directory_wheel = wheel
def set_directory_wheel(self, wheels: Path | list[Path]) -> None:
if not isinstance(wheels, list):
wheels = [wheels]

def set_sdist_wheel(self, wheel: Path) -> None:
self._sdist_wheel = wheel
self._directory_wheels = wheels

def set_sdist_wheel(self, wheels: Path | list[Path]) -> None:
if not isinstance(wheels, list):
wheels = [wheels]

self._sdist_wheels = wheels

def _prepare_sdist(self, archive: Path, destination: Path | None = None) -> Path:
if self._sdist_wheel is not None:
return self._sdist_wheel
if self._sdist_wheels is not None:
wheel = self._sdist_wheels.pop(0)
self._sdist_wheels.append(wheel)

return wheel

return super()._prepare_sdist(archive)

def _prepare(self, directory: Path, destination: Path) -> Path:
if self._directory_wheel is not None:
return self._directory_wheel
def _prepare(
self, directory: Path, destination: Path, *, editable: bool = False
) -> Path:
if self._directory_wheels is not None:
wheel = self._directory_wheels.pop(0)
self._directory_wheels.append(wheel)

return super()._prepare(directory, destination)
return wheel

return super()._prepare(directory, destination, editable=editable)


@pytest.fixture
Expand Down Expand Up @@ -132,17 +149,38 @@ def callback(


@pytest.fixture()
def wheel(tmp_dir: Path) -> Path:
shutil.copyfile(
Path(__file__)
.parent.parent.joinpath(
"fixtures/distributions/demo-0.1.2-py2.py3-none-any.whl"
def copy_wheel(tmp_dir: Path) -> Callable[[], Path]:
def _copy_wheel() -> Path:
tmp_name = tempfile.mktemp()
Path(tmp_dir).joinpath(tmp_name).mkdir()

shutil.copyfile(
Path(__file__)
.parent.parent.joinpath(
"fixtures/distributions/demo-0.1.2-py2.py3-none-any.whl"
)
.as_posix(),
Path(tmp_dir)
.joinpath(tmp_name)
.joinpath("demo-0.1.2-py2.py3-none-any.whl")
.as_posix(),
)
.as_posix(),
Path(tmp_dir).joinpath("demo-0.1.2-py2.py3-none-any.whl").as_posix(),
)

return Path(tmp_dir).joinpath("demo-0.1.2-py2.py3-none-any.whl")
return (
Path(tmp_dir).joinpath(tmp_name).joinpath("demo-0.1.2-py2.py3-none-any.whl")
)

return _copy_wheel


@pytest.fixture()
def wheel(copy_wheel: Callable[[], Path]) -> Path:
archive = copy_wheel()

yield archive

if archive.exists():
archive.unlink()


def test_execute_executes_a_batch_of_operations(
Expand All @@ -153,15 +191,18 @@ def test_execute_executes_a_batch_of_operations(
tmp_dir: str,
mock_file_downloads: None,
env: MockEnv,
wheel: Path,
copy_wheel: Callable[[], Path],
):
wheel_install = mocker.patch.object(WheelInstaller, "install")

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

prepare_spy = mocker.spy(Chef, "_prepare")
chef = Chef(config, env)
chef.set_directory_wheel(wheel)
chef.set_sdist_wheel(wheel)
chef.set_directory_wheel([copy_wheel(), copy_wheel()])
chef.set_sdist_wheel(copy_wheel())

io.set_verbosity(Verbosity.VERY_VERBOSE)

executor = Executor(env, pool, config, io)
executor.set_chef(chef)
Expand Down Expand Up @@ -223,11 +264,17 @@ def test_execute_executes_a_batch_of_operations(
expected = set(expected.splitlines())
output = set(io.fetch_output().splitlines())
assert output == expected
assert wheel_install.call_count == 4
# One pip uninstall and one pip editable install
assert len(env.executed) == 2
assert wheel_install.call_count == 5
# One pip uninstall
assert len(env.executed) == 1
assert return_code == 0

assert prepare_spy.call_count == 2
assert prepare_spy.call_args_list == [
mocker.call(chef, mocker.ANY, mocker.ANY, editable=False),
mocker.call(chef, mocker.ANY, mocker.ANY, editable=True),
]


@pytest.mark.parametrize(
"operations, has_warning",
Expand Down Expand Up @@ -586,7 +633,7 @@ def test_executor_should_write_pep610_url_references_for_directories(


def test_executor_should_write_pep610_url_references_for_editable_directories(
tmp_venv: VirtualEnv, pool: Pool, config: Config, io: BufferedIO
tmp_venv: VirtualEnv, pool: Pool, config: Config, io: BufferedIO, wheel: Path
):
url = (
Path(__file__)
Expand All @@ -605,7 +652,7 @@ def test_executor_should_write_pep610_url_references_for_editable_directories(
chef.set_directory_wheel(wheel)

executor = Executor(tmp_venv, pool, config, io)
executor.set_chef(wheel)
executor.set_chef(chef)
executor.execute([Install(package)])
verify_installed_distribution(
tmp_venv, package, {"dir_info": {"editable": True}, "url": url.as_uri()}
Expand Down

0 comments on commit 62fb172

Please sign in to comment.