From 5da9a2ed898d9438a8235169f3a4833a1380acdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Marquis?= Date: Mon, 22 May 2023 17:20:04 +0200 Subject: [PATCH] Fix wheel tag determination (#591) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Joshua C. Randall Co-authored-by: Randy Döring <30527984+radoering@users.noreply.github.com> --- src/poetry/core/masonry/builders/wheel.py | 51 +++++++++++++++++++++-- src/poetry/core/utils/helpers.py | 14 +++++++ tests/masonry/builders/test_wheel.py | 20 +++++++++ 3 files changed, 82 insertions(+), 3 deletions(-) diff --git a/src/poetry/core/masonry/builders/wheel.py b/src/poetry/core/masonry/builders/wheel.py index 14fc71dd0..e29213243 100644 --- a/src/poetry/core/masonry/builders/wheel.py +++ b/src/poetry/core/masonry/builders/wheel.py @@ -8,6 +8,7 @@ import shutil import stat import subprocess +import sys import tempfile import zipfile @@ -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 @@ -26,6 +27,7 @@ from poetry.core.masonry.utils.helpers import distribution_name from poetry.core.masonry.utils.helpers import normalize_file_permissions from poetry.core.masonry.utils.package_include import PackageInclude +from poetry.core.utils.helpers import decode from poetry.core.utils.helpers import temporary_directory @@ -327,16 +329,59 @@ def dist_info_name(self, name: NormalizedName, version: str) -> str: escaped_name = distribution_name(name) return f"{escaped_name}-{version}.dist-info" + def _get_sys_tags(self) -> list[str]: + """Get sys_tags via subprocess. + Required if poetry-core is not run inside the build environment. + """ + try: + output = 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="-") +""", + ], + stderr=subprocess.STDOUT, + ) + except subprocess.CalledProcessError as e: + raise RuntimeError( + "Failed to get sys_tags for python interpreter" + f" '{self.executable.as_posix()}':\n{decode(e.output)}" + ) + return decode(output).strip().splitlines() + @property def tag(self) -> str: if self._package.build_script: - sys_tag = next(sys_tags()) + if self.executable != Path(sys.executable): + # poetry-core is not run in the build environment + # -> this is probably not a PEP 517 build but a poetry build + return self._get_sys_tags()[0] + sys_tag = next(packaging.tags.sys_tags()) tag = (sys_tag.interpreter, sys_tag.abi, sys_tag.platform) 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/utils/helpers.py b/src/poetry/core/utils/helpers.py index 887bb2532..83ff3436b 100644 --- a/src/poetry/core/utils/helpers.py +++ b/src/poetry/core/utils/helpers.py @@ -10,6 +10,7 @@ import warnings from contextlib import contextmanager +from contextlib import suppress from pathlib import Path from typing import TYPE_CHECKING from typing import Any @@ -27,6 +28,19 @@ def combine_unicode(string: str) -> str: return unicodedata.normalize("NFC", string) +def decode(string: bytes | str, encodings: list[str] | None = None) -> str: + if not isinstance(string, bytes): + return string + + encodings = encodings or ["utf-8", "latin1", "ascii"] + + for encoding in encodings: + with suppress(UnicodeEncodeError, UnicodeDecodeError): + return string.decode(encoding) + + return string.decode(encodings[0], errors="ignore") + + def module_name(name: str) -> str: return canonicalize_name(name).replace("-", "_") diff --git a/tests/masonry/builders/test_wheel.py b/tests/masonry/builders/test_wheel.py index 2a9ee05a0..d25d64248 100644 --- a/tests/masonry/builders/test_wheel.py +++ b/tests/masonry/builders/test_wheel.py @@ -1,6 +1,7 @@ from __future__ import annotations import os +import re import shutil import zipfile @@ -350,3 +351,22 @@ def capturing_fdopen(*args: Any, **kwargs: Any) -> TextIO | None: assert fd_file[0] is not None assert fd_file[0].closed + + +@pytest.mark.parametrize("in_venv_build", [True, False]) +def test_tag(in_venv_build: bool, mocker: MockerFixture) -> None: + """Tests that tag returns a valid tag if a build script is used, + no matter if poetry-core lives inside the build environment or not. + """ + root = fixtures_dir / "extended" + builder = WheelBuilder(Factory().create_poetry(root)) + + get_sys_tags_spy = mocker.spy(builder, "_get_sys_tags") + if not in_venv_build: + mocker.patch("sys.executable", "other/python") + + assert re.match("^cp[23]_?\\d+-cp[23]_?\\d+m?u?-.+$", builder.tag) + if in_venv_build: + get_sys_tags_spy.assert_not_called() + else: + get_sys_tags_spy.assert_called()