diff --git a/src/poetry/core/json/schemas/poetry-schema.json b/src/poetry/core/json/schemas/poetry-schema.json index 01d912ce4..59ba40e13 100644 --- a/src/poetry/core/json/schemas/poetry-schema.json +++ b/src/poetry/core/json/schemas/poetry-schema.json @@ -94,6 +94,10 @@ }, "format": { "$ref": "#/definitions/package-formats" + }, + "to": { + "type": "string", + "description": "Where the package should be installed in the final distribution." } } } diff --git a/src/poetry/core/masonry/builders/builder.py b/src/poetry/core/masonry/builders/builder.py index 118c2ecc8..1492d21cb 100644 --- a/src/poetry/core/masonry/builders/builder.py +++ b/src/poetry/core/masonry/builders/builder.py @@ -180,6 +180,15 @@ def find_files_to_add(self, exclude_build: bool = True) -> set[BuildIncludeFile] else: source_root = self._path + if ( + isinstance(include, PackageInclude) + and include.target + and self.format == "wheel" + ): + target_dir = include.target + else: + target_dir = None + if file.is_dir(): if self.format in formats: for current_file in file.glob("**/*"): @@ -187,6 +196,7 @@ def find_files_to_add(self, exclude_build: bool = True) -> set[BuildIncludeFile] path=current_file, project_root=self._path, source_root=source_root, + target_dir=target_dir, ) if not ( @@ -199,7 +209,10 @@ def find_files_to_add(self, exclude_build: bool = True) -> set[BuildIncludeFile] continue include_file = BuildIncludeFile( - path=file, project_root=self._path, source_root=source_root + path=file, + project_root=self._path, + source_root=source_root, + target_dir=target_dir, ) if self.is_excluded( @@ -367,17 +380,20 @@ def __init__( self, path: Path | str, project_root: Path | str, - source_root: Path | str | None = None, + source_root: Path | str, + target_dir: Path | str | None = None, ) -> None: """ :param project_root: the full path of the project's root :param path: a full path to the file to be included - :param source_root: the root path to resolve to + :param source_root: the full root path to resolve to + :param target_dir: the relative target root to resolve to """ self.path = Path(path) self.project_root = Path(project_root).resolve() - self.source_root = None if not source_root else Path(source_root).resolve() - if not self.path.is_absolute() and self.source_root: + self.source_root = Path(source_root).resolve() + self.target_dir = None if not target_dir else Path(target_dir) + if not self.path.is_absolute(): self.path = self.source_root / self.path else: self.path = self.path @@ -400,7 +416,10 @@ def relative_to_project_root(self) -> Path: return self.path.relative_to(self.project_root) def relative_to_source_root(self) -> Path: - if self.source_root is not None: - return self.path.relative_to(self.source_root) + return self.path.relative_to(self.source_root) - return self.path + def relative_to_target_root(self) -> Path: + path = self.relative_to_source_root() + if self.target_dir is not None: + return self.target_dir / path + return path diff --git a/src/poetry/core/masonry/builders/wheel.py b/src/poetry/core/masonry/builders/wheel.py index ab8bd5f92..441edd467 100644 --- a/src/poetry/core/masonry/builders/wheel.py +++ b/src/poetry/core/masonry/builders/wheel.py @@ -267,7 +267,7 @@ def _copy_module(self, wheel: zipfile.ZipFile) -> None: # Walk the files and compress them, # sorting everything so the order is stable. for file in sorted(to_add, key=lambda x: x.path): - self._add_file(wheel, file.path, file.relative_to_source_root()) + self._add_file(wheel, file.path, file.relative_to_target_root()) def prepare_metadata(self, metadata_directory: Path) -> Path: dist_info = metadata_directory / self.dist_info diff --git a/src/poetry/core/masonry/utils/module.py b/src/poetry/core/masonry/utils/module.py index c97aefda6..f58a95a4b 100644 --- a/src/poetry/core/masonry/utils/module.py +++ b/src/poetry/core/masonry/utils/module.py @@ -81,6 +81,7 @@ def __init__( package["include"], formats=formats, source=package.get("from"), + target=package.get("to"), ) ) diff --git a/src/poetry/core/masonry/utils/package_include.py b/src/poetry/core/masonry/utils/package_include.py index 283c0d519..5fa659aee 100644 --- a/src/poetry/core/masonry/utils/package_include.py +++ b/src/poetry/core/masonry/utils/package_include.py @@ -16,11 +16,13 @@ def __init__( include: str, formats: list[str] | None = None, source: str | None = None, + target: str | None = None, ) -> None: self._package: str self._is_package = False self._is_module = False self._source = source + self._target = target if source is not None: base = base / source @@ -36,6 +38,10 @@ def package(self) -> str: def source(self) -> str | None: return self._source + @property + def target(self) -> str | None: + return self._target + def is_package(self) -> bool: return self._is_package diff --git a/tests/masonry/builders/fixtures/with-include/etc/from_to/__init__.py b/tests/masonry/builders/fixtures/with-include/etc/from_to/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/masonry/builders/fixtures/with-include/my_module_to.py b/tests/masonry/builders/fixtures/with-include/my_module_to.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/masonry/builders/fixtures/with-include/pyproject.toml b/tests/masonry/builders/fixtures/with-include/pyproject.toml index 07f5f4cf2..ea2a19430 100644 --- a/tests/masonry/builders/fixtures/with-include/pyproject.toml +++ b/tests/masonry/builders/fixtures/with-include/pyproject.toml @@ -28,6 +28,8 @@ packages = [ { include = "tests", format = "sdist" }, { include = "for_wheel_only", format = ["wheel"] }, { include = "src_package", from = "src"}, + { include = "from_to", from = "etc", "to" = "target_from_to"}, + { include = "my_module_to.py", "to" = "target_module"}, ] include = [ diff --git a/tests/masonry/builders/test_complete.py b/tests/masonry/builders/test_complete.py index 44cbfbd72..6581dcbb6 100644 --- a/tests/masonry/builders/test_complete.py +++ b/tests/masonry/builders/test_complete.py @@ -321,6 +321,7 @@ def test_package_with_include(mocker: MockerFixture) -> None: assert "with_include-1.2.3/PKG-INFO" in names assert "with_include-1.2.3/for_wheel_only/__init__.py" not in names assert "with_include-1.2.3/src/src_package/__init__.py" in names + assert "with_include-1.2.3/etc/from_to/__init__.py" in names whl = module_path / "dist" / "with_include-1.2.3-py3-none-any.whl" @@ -340,6 +341,8 @@ def test_package_with_include(mocker: MockerFixture) -> None: assert "package_with_include/__init__.py" in names assert "tests/__init__.py" not in names assert "src_package/__init__.py" in names + assert "target_from_to/from_to/__init__.py" in names + assert "target_module/my_module_to.py" in names def test_respect_format_for_explicit_included_files() -> None: diff --git a/tests/test_factory.py b/tests/test_factory.py index 77f10f858..e3303dc3c 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -217,6 +217,8 @@ def test_create_poetry_with_packages_and_includes() -> None: {"include": "tests", "format": "sdist"}, {"include": "for_wheel_only", "format": ["wheel"]}, {"include": "src_package", "from": "src"}, + {"include": "from_to", "from": "etc", "to": "target_from_to"}, + {"include": "my_module_to.py", "to": "target_module"}, ] assert package.include == [