diff --git a/README.md b/README.md index 14a7a38..4c8b11e 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,13 @@ This is not a comprehensive list, but illustrates typical examples. `macosx_10_9_x86_64`, `macosx_10_9_intel`, `macosx_11_1_universal2`, `macosx_11_0_arm64` +#### Compatible wheels +The specified platform is treated as the newest one the target system supports, so wheels built for older versions of +it are selected as well. For example, `manylinux_2_34_aarch64` also accepts wheels built for any older glibc version, +including wheels tagged only with one of the legacy aliases `manylinux2014`, `manylinux2010` or `manylinux1` (see +[PEP 600](https://peps.python.org/pep-0600/#legacy-manylinux-tags)). Among otherwise equivalent wheels, the one built +for the newest platform version is preferred. + #### Example use case for AWS Lambda As an example of one motivating use case for this option, consider the AWS Lambda "serverless" execution environment. Depending upon which Python version you configure for your runtime, you may get different versions of the Linux system diff --git a/src/poetry_plugin_bundle/utils/platforms.py b/src/poetry_plugin_bundle/utils/platforms.py index 0ee69ae..51d153c 100644 --- a/src/poetry_plugin_bundle/utils/platforms.py +++ b/src/poetry_plugin_bundle/utils/platforms.py @@ -97,16 +97,24 @@ def create_supported_manylinux_platforms(platform: str) -> list[str]: For now, only GLIBCMAJOR "2" is supported. It is unclear if there will be a need to support a future major version like "3" and if specified, how generate the compatible 2.x version tags. + + The legacy aliases (manylinux1, manylinux2010, manylinux2014) are included as well, since wheels may be + tagged with only the alias. See https://peps.python.org/pep-0600/#legacy-manylinux-tags """ # Implementation based on https://peps.python.org/pep-0600/#package-installers tag = normalize_legacy_manylinux_alias(platform) parsed = PlatformTagParseResult.parse(tag) - return [ - f"{parsed.platform}_{parsed.version_major}_{tag_minor}_{parsed.arch}" - for tag_minor in range(parsed.version_minor, -1, -1) - ] + platforms: list[str] = [] + for tag_minor in range(parsed.version_minor, -1, -1): + glibc_tag = f"{parsed.platform}_{parsed.version_major}_{tag_minor}" + platforms.append(f"{glibc_tag}_{parsed.arch}") + # A legacy alias ranks just below the tag for the same glibc version. + legacy_alias = LEGACY_MANYLINUX_ALIASES_BY_GLIBC_TAG.get(glibc_tag) + if legacy_alias: + platforms.append(f"{legacy_alias}_{parsed.arch}") + return platforms LEGACY_MANYLINUX_ALIASES = { @@ -115,6 +123,10 @@ def create_supported_manylinux_platforms(platform: str) -> list[str]: "manylinux2014": "manylinux_2_17", } +LEGACY_MANYLINUX_ALIASES_BY_GLIBC_TAG = { + glibc_tag: alias for alias, glibc_tag in LEGACY_MANYLINUX_ALIASES.items() +} + def normalize_legacy_manylinux_alias(tag: str) -> str: tag_os_index_end = tag.index("_") diff --git a/tests/bundlers/test_venv_bundler.py b/tests/bundlers/test_venv_bundler.py index 4a0a992..2140afe 100644 --- a/tests/bundlers/test_venv_bundler.py +++ b/tests/bundlers/test_venv_bundler.py @@ -26,6 +26,8 @@ if TYPE_CHECKING: + from collections.abc import Callable + from poetry.config.config import Config from poetry.poetry import Poetry from pytest_mock import MockerFixture @@ -433,13 +435,12 @@ def test_bundler_non_package_mode( assert expected == io.fetch_output() -def test_bundler_platform_override( - io: BufferedIO, tmpdir: str, mocker: MockerFixture, config: Config -) -> None: - poetry = Factory().create_poetry( - Path(__file__).parent.parent / "fixtures" / "project_with_binary_wheel" - ) - poetry.set_config(config) +def _mock_platform_installs(mocker: MockerFixture) -> Callable[[], dict[str, str]]: + """ + Mock the installer's downloads and installs, serving each locked package's + files as its links. Returns a function mapping each package name to the + filename of the link most recently chosen for it. + """ def find_links_for_package_fake(package: Package) -> list[Link]: return [ @@ -467,6 +468,18 @@ def get_installed_links() -> dict[str, str]: for call in mock_download_link.call_args_list } + return get_installed_links + + +def test_bundler_platform_override( + io: BufferedIO, tmpdir: str, mocker: MockerFixture, config: Config +) -> None: + poetry = Factory().create_poetry( + Path(__file__).parent.parent / "fixtures" / "project_with_binary_wheel" + ) + poetry.set_config(config) + get_installed_links = _mock_platform_installs(mocker) + bundler = VenvBundler() bundler.set_path(Path(tmpdir)) bundler.set_remove(True) @@ -505,3 +518,42 @@ def get_installed_links() -> dict[str, str]: assert "musllinux_1_2_aarch64" in installed_link_by_package["cryptography"] assert "musllinux_1_2_aarch64" in installed_link_by_package["cffi"] assert "py3-none-any.whl" in installed_link_by_package["pycparser"] + + +def test_bundler_platform_override_selects_legacy_manylinux_wheel( + io: BufferedIO, tmpdir: str, mocker: MockerFixture, config: Config +) -> None: + # cmake 3.18.4.post1 tags its Linux wheels only with legacy manylinux aliases, + # alongside an sdist that would otherwise be chosen and built on the host. + poetry = Factory().create_poetry( + Path(__file__).parent.parent + / "fixtures" + / "project_with_legacy_manylinux_wheel" + ) + poetry.set_config(config) + get_installed_links = _mock_platform_installs(mocker) + + bundler = VenvBundler() + bundler.set_path(Path(tmpdir)) + bundler.set_remove(True) + + bundler.set_platform("manylinux_2_34_aarch64") + bundler.bundle(poetry, io) + assert ( + get_installed_links()["cmake"] + == "cmake-3.18.4.post1-py3-none-manylinux2014_aarch64.whl" + ) + + bundler.set_platform("manylinux2014_aarch64") + bundler.bundle(poetry, io) + assert ( + get_installed_links()["cmake"] + == "cmake-3.18.4.post1-py3-none-manylinux2014_aarch64.whl" + ) + + bundler.set_platform("manylinux_2_28_x86_64") + bundler.bundle(poetry, io) + assert ( + get_installed_links()["cmake"] + == "cmake-3.18.4.post1-py3-none-manylinux1_x86_64.whl" + ) diff --git a/tests/fixtures/project_with_legacy_manylinux_wheel/README.md b/tests/fixtures/project_with_legacy_manylinux_wheel/README.md new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/project_with_legacy_manylinux_wheel/poetry.lock b/tests/fixtures/project_with_legacy_manylinux_wheel/poetry.lock new file mode 100644 index 0000000..c87183c --- /dev/null +++ b/tests/fixtures/project_with_legacy_manylinux_wheel/poetry.lock @@ -0,0 +1,28 @@ +# This file is automatically @generated by Poetry 2.5.1 and should not be changed by hand. + +[[package]] +name = "cmake" +version = "3.18.4.post1" +description = "CMake is an open-source, cross-platform family of tools designed to build, test and package software" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "cmake-3.18.4.post1-py2-none-macosx_10_6_x86_64.whl", hash = "sha256:10c46b0fd2c087b0cae611d1e734f065a1a8169d0b54ec834a9dff005c1857ca"}, + {file = "cmake-3.18.4.post1-py2-none-manylinux1_i686.whl", hash = "sha256:65cd763dd232973a0deedf1f349e229fa3bf1357e0e2576da65ad118ff53b070"}, + {file = "cmake-3.18.4.post1-py2-none-manylinux1_x86_64.whl", hash = "sha256:1c900642859c5970d81ae8821ae05a2af93d2630cd1c0f2bffc80e7abdbc087d"}, + {file = "cmake-3.18.4.post1-py2-none-win32.whl", hash = "sha256:605c2a07c9ebf332319106bffb11941463d18e586902e3659c315cae9f0caaeb"}, + {file = "cmake-3.18.4.post1-py2-none-win_amd64.whl", hash = "sha256:c1b14b302d3def2672968cd675031793e193382d0e4a00e2121af4b333d62ece"}, + {file = "cmake-3.18.4.post1-py3-none-macosx_10_6_x86_64.whl", hash = "sha256:6dd3abb1afdd9a986a55977ef85a0d245ebf289cc704b687f061294c48c126ec"}, + {file = "cmake-3.18.4.post1-py3-none-manylinux1_i686.whl", hash = "sha256:1c86369700f74363ee46de64e4167ac2d292a7c7f1606e372b8dcaf3108d0cc7"}, + {file = "cmake-3.18.4.post1-py3-none-manylinux1_x86_64.whl", hash = "sha256:34f7ee67cef21b178a793fe760c979608d4ac66a1697cae6b382dbcc5d1ec485"}, + {file = "cmake-3.18.4.post1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:e8ef8dab578e8ca85724b8506f230a5a5017ead67cb9da60fe1240fc9ab24135"}, + {file = "cmake-3.18.4.post1-py3-none-win32.whl", hash = "sha256:5096f5d4541b5d0040bae9dbc364bb1c8cd9211e273c481baf9a1a3635be1d00"}, + {file = "cmake-3.18.4.post1-py3-none-win_amd64.whl", hash = "sha256:ac062ac13591e4acbb6e919e5b1196a3b04f8d1022eb3ab4dbd20779ade9d5ab"}, + {file = "cmake-3.18.4.post1.tar.gz", hash = "sha256:d7981ac85f1abb75c24eb14936d56dafbd327e7ba371d91007e38704af7b52b5"}, +] + +[metadata] +lock-version = "2.1" +python-versions = "^3.10" +content-hash = "da774ecf1351268cbdbb858033c53c2a38a579301558e1c015c2f2dd52198cae" diff --git a/tests/fixtures/project_with_legacy_manylinux_wheel/project_with_legacy_manylinux_wheel/__init__.py b/tests/fixtures/project_with_legacy_manylinux_wheel/project_with_legacy_manylinux_wheel/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/project_with_legacy_manylinux_wheel/pyproject.toml b/tests/fixtures/project_with_legacy_manylinux_wheel/pyproject.toml new file mode 100644 index 0000000..bb5101e --- /dev/null +++ b/tests/fixtures/project_with_legacy_manylinux_wheel/pyproject.toml @@ -0,0 +1,15 @@ +[tool.poetry] +name = "project-with-legacy-manylinux-wheel" +version = "0.1.0" +description = "" +authors = ["BrandonLWhite "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.10" +cmake = "==3.18.4.post1" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/tests/utils/test_platforms.py b/tests/utils/test_platforms.py index bd2b67e..1b7b547 100644 --- a/tests/utils/test_platforms.py +++ b/tests/utils/test_platforms.py @@ -54,10 +54,12 @@ def test_create_supported_tags_legacy_manylinux_aliases() -> None: python_version_info=(3, 10, 2), expected_tags={ "cp310-cp310-manylinux_2_5_x86_64", + "cp310-cp310-manylinux1_x86_64", "cp310-cp310-manylinux_2_1_x86_64", }, unexpected_tags={ "cp310-cp310-manylinux_2_6_x86_64", + "cp310-cp310-manylinux2010_x86_64", }, ) @@ -66,10 +68,13 @@ def test_create_supported_tags_legacy_manylinux_aliases() -> None: python_version_info=(3, 10, 2), expected_tags={ "cp310-cp310-manylinux_2_12_x86_64", + "cp310-cp310-manylinux2010_x86_64", + "cp310-cp310-manylinux1_x86_64", "cp310-cp310-manylinux_2_1_x86_64", }, unexpected_tags={ "cp310-cp310-manylinux_2_13_x86_64", + "cp310-cp310-manylinux2014_x86_64", }, ) @@ -78,6 +83,9 @@ def test_create_supported_tags_legacy_manylinux_aliases() -> None: python_version_info=(3, 11, 9), expected_tags={ "cp311-cp311-manylinux_2_17_x86_64", + "cp311-cp311-manylinux2014_x86_64", + "cp311-cp311-manylinux2010_x86_64", + "cp311-cp311-manylinux1_x86_64", "cp311-cp311-manylinux_2_1_x86_64", }, unexpected_tags={ @@ -86,6 +94,35 @@ def test_create_supported_tags_legacy_manylinux_aliases() -> None: ) +def test_create_supported_tags_manylinux_includes_legacy_aliases() -> None: + _test_create_supported_tags( + platform="manylinux_2_34_aarch64", + python_version_info=(3, 14, 0), + expected_tags={ + "cp314-cp314-manylinux_2_34_aarch64", + "cp314-cp314-manylinux_2_17_aarch64", + "cp314-cp314-manylinux2014_aarch64", + "cp314-abi3-manylinux2014_aarch64", + "py3-none-manylinux2014_aarch64", + }, + unexpected_tags={ + "cp314-cp314-manylinux_2_35_aarch64", + }, + ) + + +def test_create_supported_tags_ranks_legacy_alias_with_its_glibc_version() -> None: + env = MockEnv(version_info=(3, 14, 0)) + tags = [ + str(tag) + for tag in platforms.create_supported_tags("manylinux_2_34_aarch64", env) + ] + + index = tags.index("cp314-cp314-manylinux2014_aarch64") + assert tags[index - 1] == "cp314-cp314-manylinux_2_17_aarch64" + assert tags[index + 1] == "cp314-cp314-manylinux_2_16_aarch64" + + def test_create_supported_tags_macosx() -> None: _test_create_supported_tags( platform="macosx_11_0_arm64",