diff --git a/src/poetry/core/json/schemas/poetry-schema.json b/src/poetry/core/json/schemas/poetry-schema.json index 564559070..8b7a9c257 100644 --- a/src/poetry/core/json/schemas/poetry-schema.json +++ b/src/poetry/core/json/schemas/poetry-schema.json @@ -647,6 +647,11 @@ }, "script": { "$ref": "#/definitions/build-script" + }, + "wheel-tag-regex": { + "type": "string", + "description": "Regex pattern to use to select a wheel tag from the list of tags supplied by the python interpreter.", + "default": ".*" } } }, diff --git a/src/poetry/core/masonry/builders/wheel.py b/src/poetry/core/masonry/builders/wheel.py index 14fc71dd0..79896f6d0 100644 --- a/src/poetry/core/masonry/builders/wheel.py +++ b/src/poetry/core/masonry/builders/wheel.py @@ -5,6 +5,7 @@ import hashlib import logging import os +import re import shutil import stat import subprocess @@ -17,7 +18,7 @@ from typing import TYPE_CHECKING from typing import TextIO -from packaging.tags import sys_tags +import packaging.tags from poetry.core import __version__ from poetry.core.constraints.version import parse_constraint @@ -327,16 +328,69 @@ def dist_info_name(self, name: NormalizedName, version: str) -> str: escaped_name = distribution_name(name) return f"{escaped_name}-{version}.dist-info" + def get_tags(self) -> list[str]: + env = os.environ + try: + tags = ( + subprocess.check_output( + [ + self.executable.as_posix(), + "-c", + f""" +import importlib.util +import sys + +from pathlib import Path + +spec = importlib.util.spec_from_file_location( + "packaging", Path(r"{packaging.__file__}") +) + +packaging = importlib.util.module_from_spec(spec) +sys.modules[spec.name] = packaging + +spec = importlib.util.spec_from_file_location( + "packaging.tags", Path(r"{packaging.tags.__file__}") +) +packaging_tags = importlib.util.module_from_spec(spec) +spec.loader.exec_module(packaging_tags) +for t in packaging_tags.sys_tags(): + print(t.interpreter, t.abi, t.platform, sep="-") +""", + ], + env=env, + stderr=subprocess.STDOUT, + ) + .decode("utf-8") + .strip() + .splitlines() + ) + except subprocess.CalledProcessError as cpe: + pythonpath = env.get("PYTHONPATH", "").strip() + raise RuntimeError( + "get_tags failed to get sys_tags for python interpreter" + f" '{self.executable.as_posix()}' using PYTHONPATH='{pythonpath}'" + " to supply packaging.tags package: script exited" + f" {cpe.returncode} with output '{cpe.output}'" + ) + return tags + @property def tag(self) -> str: if self._package.build_script: - sys_tag = next(sys_tags()) - tag = (sys_tag.interpreter, sys_tag.abi, sys_tag.platform) + tags = self.get_tags() + wheel_tag_pattern = re.compile(self._poetry.package.build_wheel_tag_regex()) + for tag in tags: + if wheel_tag_pattern.search(tag) is not None: + return tag + raise RuntimeError( + "no sys tags matched regex" + f" '{self._poetry.package.build_wheel_tag_regex()}': {','.join(tags)}" + ) else: platform = "any" impl = "py2.py3" if self.supports_python2() else "py3" tag = (impl, "none", platform) - return "-".join(tag) def _add_file( diff --git a/src/poetry/core/packages/project_package.py b/src/poetry/core/packages/project_package.py index 7cf82fd24..42f8055b4 100644 --- a/src/poetry/core/packages/project_package.py +++ b/src/poetry/core/packages/project_package.py @@ -100,3 +100,6 @@ def __hash__(self) -> int: def build_should_generate_setup(self) -> bool: return self.build_config.get("generate-setup-file", False) + + def build_wheel_tag_regex(self) -> str: + return self.build_config.get("wheel-tag-regex", ".*") diff --git a/tests/masonry/builders/test_complete.py b/tests/masonry/builders/test_complete.py index 4e4dff019..682cbd9d3 100644 --- a/tests/masonry/builders/test_complete.py +++ b/tests/masonry/builders/test_complete.py @@ -46,14 +46,14 @@ def clear_samples_dist() -> None: platform.python_implementation().lower() == "pypy", reason="Disable test for PyPy" ) @pytest.mark.parametrize( - ["project", "exptected_c_dir"], + ["project", "expected_c_dir"], [ ("extended", "extended"), ("extended_with_no_setup", "extended"), ("src_extended", "src/extended"), ], ) -def test_wheel_c_extension(project: str, exptected_c_dir: str) -> None: +def test_wheel_c_extension(project: str, expected_c_dir: str) -> None: module_path = fixtures_dir / project builder = Builder(Factory().create_poetry(module_path)) builder.build(fmt="all") @@ -63,7 +63,7 @@ def test_wheel_c_extension(project: str, exptected_c_dir: str) -> None: with tarfile.open(sdist, "r") as tar: assert "extended-0.1/build.py" in tar.getnames() - assert f"extended-0.1/{exptected_c_dir}/extended.c" in tar.getnames() + assert f"extended-0.1/{expected_c_dir}/extended.c" in tar.getnames() whl = list((module_path / "dist").glob("extended-0.1-cp*-cp*-*.whl"))[0] assert whl.exists() diff --git a/tests/masonry/builders/test_wheel.py b/tests/masonry/builders/test_wheel.py index 2a9ee05a0..5c08bd4c0 100644 --- a/tests/masonry/builders/test_wheel.py +++ b/tests/masonry/builders/test_wheel.py @@ -147,6 +147,28 @@ def test_wheel_localversionlabel() -> None: assert local_version_string + ".dist-info/METADATA" in z.namelist() +def test_wheel_get_tags(mocker: MockerFixture) -> None: + class MockGetTags: + def get_tags(self) -> list[str]: + return [ + "cp311-cp311-manylinux_2_37_x86_64" + "cp311-abi3-manylinux_2_36_x86_64" + "cp311-none-manylinux_2_35_x86_64" + "cp311-abi3-linux_x86_64" + ] + + p = mocker.patch("packaging.tags") + p.return_value = MockGetTags() + + module_path = fixtures_dir / "get_tags" + WheelBuilder.make(Factory().create_poetry(module_path)) + + tag = "cp311-cp311-manylinux_2_37_x86_64" + whl = module_path / "dist" / (tag + ".whl") + + assert whl.exists() + + def test_wheel_package_src() -> None: module_path = fixtures_dir / "source_package" WheelBuilder.make(Factory().create_poetry(module_path))