diff --git a/src/poetry/console/commands/build.py b/src/poetry/console/commands/build.py index 5339a865200..0d1bd15af45 100644 --- a/src/poetry/console/commands/build.py +++ b/src/poetry/console/commands/build.py @@ -1,11 +1,17 @@ from __future__ import annotations +from typing import TYPE_CHECKING + from cleo.helpers import option from poetry.console.commands.env_command import EnvCommand from poetry.utils.env import build_environment +if TYPE_CHECKING: + from pathlib import Path + + class BuildCommand(EnvCommand): name = "build" description = "Builds a package, as a tarball and a wheel by default." @@ -20,9 +26,26 @@ class BuildCommand(EnvCommand): "poetry.core.masonry.builders.wheel", ] - def handle(self) -> int: - from poetry.core.masonry.builder import Builder + def _build( + self, + fmt: str, + executable: str | Path | None = None, + *, + target_dir: Path | None = None, + ) -> None: + from poetry.masonry.builders import BUILD_FORMATS + + if fmt in BUILD_FORMATS: + builders = [BUILD_FORMATS[fmt]] + elif fmt == "all": + builders = list(BUILD_FORMATS.values()) + else: + raise ValueError(f"Invalid format: {fmt}") + + for builder in builders: + builder(self.poetry, executable=executable).build(target_dir) + def handle(self) -> int: with build_environment(poetry=self.poetry, env=self.env, io=self.io) as env: fmt = self.option("format") or "all" package = self.poetry.package @@ -30,7 +53,6 @@ def handle(self) -> int: f"Building {package.pretty_name} ({package.version})" ) - builder = Builder(self.poetry) - builder.build(fmt, executable=env.python) + self._build(fmt, executable=env.python) return 0 diff --git a/src/poetry/masonry/builders/__init__.py b/src/poetry/masonry/builders/__init__.py index 61662422c39..4fdd87d96e0 100644 --- a/src/poetry/masonry/builders/__init__.py +++ b/src/poetry/masonry/builders/__init__.py @@ -1,6 +1,16 @@ from __future__ import annotations +from poetry.core.masonry.builders.sdist import SdistBuilder +from poetry.core.masonry.builders.wheel import WheelBuilder + from poetry.masonry.builders.editable import EditableBuilder -__all__ = ["EditableBuilder"] +__all__ = ["BUILD_FORMATS", "EditableBuilder"] + + +# might be extended by plugins +BUILD_FORMATS = { + "sdist": SdistBuilder, + "wheel": WheelBuilder, +} diff --git a/tests/console/commands/test_build.py b/tests/console/commands/test_build.py index ed9f3a3c5fa..3b378d1e4e5 100644 --- a/tests/console/commands/test_build.py +++ b/tests/console/commands/test_build.py @@ -5,17 +5,63 @@ from typing import TYPE_CHECKING +import pytest + from poetry.factory import Factory if TYPE_CHECKING: from pathlib import Path + from cleo.testers.command_tester import CommandTester + + from poetry.poetry import Poetry from poetry.utils.env import VirtualEnv from tests.types import CommandTesterFactory from tests.types import FixtureDirGetter +@pytest.fixture +def tmp_project_path(tmp_path: Path) -> Path: + return tmp_path / "project" + + +@pytest.fixture +def tmp_poetry(tmp_project_path: Path, fixture_dir: FixtureDirGetter) -> Poetry: + # copy project so that we start with a clean directory + shutil.copytree(fixture_dir("simple_project"), tmp_project_path) + poetry = Factory().create_poetry(tmp_project_path) + return poetry + + +@pytest.fixture +def tmp_tester( + tmp_poetry: Poetry, command_tester_factory: CommandTesterFactory +) -> CommandTester: + return command_tester_factory("build", tmp_poetry) + + +def get_package_glob(poetry: Poetry) -> str: + return f"{poetry.package.name.replace('-', '_')}-{poetry.package.version}*" + + +def test_build_format_is_not_valid(tmp_tester: CommandTester) -> None: + with pytest.raises(ValueError, match=r"Invalid format.*"): + tmp_tester.execute("--format not_valid") + + +@pytest.mark.parametrize("format", ["sdist", "wheel", "all"]) +def test_build_creates_packages_in_dist_directory_if_no_output_is_specified( + tmp_tester: CommandTester, tmp_project_path: Path, tmp_poetry: Poetry, format: str +) -> None: + tmp_tester.execute(f"--format {format}") + build_artifacts = tuple( + (tmp_project_path / "dist").glob(get_package_glob(tmp_poetry)) + ) + assert len(build_artifacts) > 0 + assert all(archive.exists() for archive in build_artifacts) + + def test_build_with_multiple_readme_files( fixture_dir: FixtureDirGetter, tmp_path: Path,