diff --git a/src/poetry/core/masonry/api.py b/src/poetry/core/masonry/api.py index a712baf91..fbb7a01f7 100644 --- a/src/poetry/core/masonry/api.py +++ b/src/poetry/core/masonry/api.py @@ -40,7 +40,7 @@ def prepare_metadata_for_build_wheel( metadata_directory: str, config_settings: dict[str, Any] | None = None ) -> str: poetry = Factory().create_poetry(Path().resolve(), with_groups=False) - builder = WheelBuilder(poetry) + builder = WheelBuilder(poetry, config_settings=config_settings) metadata_path = Path(metadata_directory) dist_info = builder.prepare_metadata(metadata_path) return dist_info.name @@ -56,7 +56,10 @@ def build_wheel( metadata_path = None if metadata_directory is None else Path(metadata_directory) return WheelBuilder.make_in( - poetry, Path(wheel_directory), metadata_directory=metadata_path + poetry, + Path(wheel_directory), + metadata_directory=metadata_path, + config_settings=config_settings, ) @@ -66,7 +69,9 @@ def build_sdist( """Builds an sdist, places it in sdist_directory""" poetry = Factory().create_poetry(Path().resolve(), with_groups=False) - path = SdistBuilder(poetry).build(Path(sdist_directory)) + path = SdistBuilder(poetry, config_settings=config_settings).build( + Path(sdist_directory) + ) return path.name @@ -80,7 +85,11 @@ def build_editable( metadata_path = None if metadata_directory is None else Path(metadata_directory) return WheelBuilder.make_in( - poetry, Path(wheel_directory), metadata_directory=metadata_path, editable=True + poetry, + Path(wheel_directory), + metadata_directory=metadata_path, + editable=True, + config_settings=config_settings, ) diff --git a/src/poetry/core/masonry/builders/builder.py b/src/poetry/core/masonry/builders/builder.py index 890eca44b..2c40df442 100644 --- a/src/poetry/core/masonry/builders/builder.py +++ b/src/poetry/core/masonry/builders/builder.py @@ -7,6 +7,7 @@ from functools import cached_property from pathlib import Path from typing import TYPE_CHECKING +from typing import Any if TYPE_CHECKING: @@ -27,7 +28,12 @@ class Builder: format: str | None = None - def __init__(self, poetry: Poetry, executable: Path | None = None) -> None: + def __init__( + self, + poetry: Poetry, + executable: Path | None = None, + config_settings: dict[str, Any] | None = None, + ) -> None: from poetry.core.masonry.metadata import Metadata if not poetry.is_package_mode: @@ -35,7 +41,11 @@ def __init__(self, poetry: Poetry, executable: Path | None = None) -> None: "Building a package is not possible in non-package mode." ) + self._config_settings = config_settings or {} + self._poetry = poetry + self._apply_local_version_label() + self._package = poetry.package self._path: Path = poetry.pyproject_path.parent self._excluded_files: set[str] | None = None @@ -72,6 +82,13 @@ def executable(self) -> Path: def default_target_dir(self) -> Path: return self._path / "dist" + def _apply_local_version_label(self) -> None: + """Apply local version label from config settings to the poetry package version if present.""" + if local_version_label := self._config_settings.get("local-version"): + self._poetry.package.version = self._poetry.package.version.replace( + local=local_version_label + ) + def build(self, target_dir: Path | None) -> Path: raise NotImplementedError diff --git a/src/poetry/core/masonry/builders/wheel.py b/src/poetry/core/masonry/builders/wheel.py index 17d5f34a8..e9825e4ac 100644 --- a/src/poetry/core/masonry/builders/wheel.py +++ b/src/poetry/core/masonry/builders/wheel.py @@ -18,6 +18,7 @@ from io import StringIO from pathlib import Path from typing import TYPE_CHECKING +from typing import Any from typing import TextIO import packaging.tags @@ -61,8 +62,9 @@ def __init__( executable: Path | None = None, editable: bool = False, metadata_directory: Path | None = None, + config_settings: dict[str, Any] | None = None, ) -> None: - super().__init__(poetry, executable=executable) + super().__init__(poetry, executable=executable, config_settings=config_settings) self._records: list[tuple[str, str, int]] = [] self._original_path = self._path @@ -80,6 +82,7 @@ def make_in( executable: Path | None = None, editable: bool = False, metadata_directory: Path | None = None, + config_settings: dict[str, Any] | None = None, ) -> str: wb = WheelBuilder( poetry, @@ -87,6 +90,7 @@ def make_in( executable=executable, editable=editable, metadata_directory=metadata_directory, + config_settings=config_settings, ) wb.build(target_dir=directory) diff --git a/tests/masonry/builders/test_builder.py b/tests/masonry/builders/test_builder.py index 25c124325..7be7fce37 100644 --- a/tests/masonry/builders/test_builder.py +++ b/tests/masonry/builders/test_builder.py @@ -333,3 +333,21 @@ def test_metadata_with_wildcard_dependency_constraint() -> None: requires = metadata.get_all("Requires-Dist") assert requires == ["google-api-python-client (>=1.8,!=2.0.*)"] + + +@pytest.mark.parametrize( + ["local_version", "expected_version"], + [ + ("", "1.2.3"), + ("some-label", "1.2.3+some-label"), + ], +) +def test_builder_apply_local_version_label( + local_version: str, expected_version: str +) -> None: + builder = Builder( + Factory().create_poetry(Path(__file__).parent / "fixtures" / "complete"), + config_settings={"local-version": local_version}, + ) + + assert builder._poetry.package.version.text == expected_version diff --git a/tests/masonry/test_api.py b/tests/masonry/test_api.py index 2d9c8350c..eb5b7b174 100644 --- a/tests/masonry/test_api.py +++ b/tests/masonry/test_api.py @@ -61,6 +61,19 @@ def test_build_wheel(project: str) -> None: ) +def test_build_wheel_with_local_version() -> None: + with temporary_directory() as tmp_dir, cwd(fixtures / "complete"): + filename = api.build_wheel( + str(tmp_dir), config_settings={"local-version": "some-label"} + ) + validate_wheel_contents( + name="my_package", + version="1.2.3+some-label", + path=tmp_dir / filename, + files=["entry_points.txt"], + ) + + def test_build_wheel_with_include() -> None: with temporary_directory() as tmp_dir, cwd(fixtures / "with-include"): filename = api.build_wheel(str(tmp_dir)) @@ -106,6 +119,19 @@ def test_build_sdist(project: str) -> None: ) +def test_build_sdist_with_local_version() -> None: + with temporary_directory() as tmp_dir, cwd(fixtures / "complete"): + filename = api.build_sdist( + str(tmp_dir), config_settings={"local-version": "some-label"} + ) + validate_sdist_contents( + name="my-package", + version="1.2.3+some-label", + path=tmp_dir / filename, + files=["LICENSE"], + ) + + def test_build_sdist_with_include() -> None: with temporary_directory() as tmp_dir, cwd(fixtures / "with-include"): filename = api.build_sdist(str(tmp_dir)) @@ -208,6 +234,85 @@ def test_prepare_metadata_for_build_wheel(project: str) -> None: assert f.read() == metadata +def test_prepare_metadata_for_build_wheel_with_local_version() -> None: + local_version = "some-label" + entry_points = """\ +[console_scripts] +extra-script=my_package.extra:main +my-2nd-script=my_package:main2 +my-script=my_package:main + +[poetry.application.plugin] +my-command=my_package.plugins:MyApplicationPlugin + +""" + wheel_data = f"""\ +Wheel-Version: 1.0 +Generator: poetry-core {__version__} +Root-Is-Purelib: true +Tag: py3-none-any +""" + metadata = f"""\ +Metadata-Version: 2.3 +Name: my-package +Version: 1.2.3+{local_version} +Summary: Some description. +License: MIT +Keywords: packaging,dependency,poetry +Author: Sébastien Eustace +Author-email: sebastien@eustace.io +Maintainer: People Everywhere +Maintainer-email: people@everywhere.com +Requires-Python: >=3.6,<4.0 +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Topic :: Software Development :: Build Tools +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Provides-Extra: time +Requires-Dist: cachy[msgpack] (>=0.2.0,<0.3.0) +Requires-Dist: cleo (>=0.6,<0.7) +Requires-Dist: pendulum (>=1.4,<2.0) ; (python_version ~= "2.7" and sys_platform == "win32" or python_version in "3.4 3.5") and (extra == "time") +Project-URL: Documentation, https://python-poetry.org/docs +Project-URL: Homepage, https://python-poetry.org/ +Project-URL: Issue Tracker, /~https://github.com/python-poetry/poetry/issues +Project-URL: Repository, /~https://github.com/python-poetry/poetry +Description-Content-Type: text/x-rst + +My Package +========== + +""" + with temporary_directory() as tmp_dir, cwd(fixtures / "complete"): + dirname = api.prepare_metadata_for_build_wheel( + str(tmp_dir), config_settings={"local-version": local_version} + ) + + assert dirname == f"my_package-1.2.3+{local_version}.dist-info" + + dist_info = Path(tmp_dir, dirname) + + assert (dist_info / "entry_points.txt").exists() + assert (dist_info / "WHEEL").exists() + assert (dist_info / "METADATA").exists() + + with (dist_info / "entry_points.txt").open(encoding="utf-8") as f: + assert f.read() == entry_points + + with (dist_info / "WHEEL").open(encoding="utf-8") as f: + assert f.read() == wheel_data + + with (dist_info / "METADATA").open(encoding="utf-8") as f: + assert f.read() == metadata + + def test_prepare_metadata_for_build_wheel_with_bad_path_dev_dep_succeeds() -> None: with temporary_directory() as tmp_dir, cwd(fixtures / "with_bad_path_dev_dep"): api.prepare_metadata_for_build_wheel(str(tmp_dir)) @@ -244,6 +349,21 @@ def test_build_editable_wheel(project: str) -> None: assert z.read("my_package.pth").decode().strip() == pkg_dir.as_posix() +def test_build_editable_wheel_with_local_version() -> None: + pkg_dir = fixtures / "complete" + with temporary_directory() as tmp_dir, cwd(pkg_dir): + filename = api.build_editable( + str(tmp_dir), config_settings={"local-version": "some-label"} + ) + wheel_pth = Path(tmp_dir) / filename + + validate_wheel_contents( + name="my_package", + version="1.2.3+some-label", + path=wheel_pth, + ) + + @pytest.mark.parametrize("project", ["complete", "complete_new", "complete_dynamic"]) def test_build_wheel_with_metadata_directory(project: str) -> None: pkg_dir = fixtures / project