Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 16 additions & 4 deletions src/poetry_plugin_bundle/utils/platforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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("_")
Expand Down
66 changes: 59 additions & 7 deletions tests/bundlers/test_venv_bundler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 [
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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"
)
Empty file.
28 changes: 28 additions & 0 deletions tests/fixtures/project_with_legacy_manylinux_wheel/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions tests/fixtures/project_with_legacy_manylinux_wheel/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[tool.poetry]
name = "project-with-legacy-manylinux-wheel"
version = "0.1.0"
description = ""
authors = ["BrandonLWhite <brandonlwhite@gmail.com>"]
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"
37 changes: 37 additions & 0 deletions tests/utils/test_platforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
},
)

Expand All @@ -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",
},
)

Expand All @@ -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={
Expand All @@ -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",
Expand Down
Loading