Skip to content

Commit de307a9

Browse files
authored
feat: allow manylinux 2.28 and 2.34 on python 3.12+ when compiled on a different architecture (#762)
* feat: allow manylinux 2.28 and 2.34 on python 3.12+ when compiled on a different architecture * fix: docstring formatting
1 parent 9cc29a8 commit de307a9

File tree

4 files changed

+202
-16
lines changed

4 files changed

+202
-16
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ lint:
2121
ruff check aws_lambda_builders
2222

2323
lint-fix:
24-
ruff aws_lambda_builders --fix
24+
ruff check aws_lambda_builders --fix
2525

2626
# Command to run everytime you make changes to verify everything works
2727
dev: lint test

aws_lambda_builders/workflows/python_pip/packager.py

Lines changed: 77 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
Installs packages using PIP
33
"""
44

5+
import itertools
56
import logging
67
import re
78
import subprocess
89
from email.parser import FeedParser
9-
from typing import Tuple
10+
from typing import List, Tuple
1011

1112
from aws_lambda_builders.architecture import ARM64, X86_64
1213
from aws_lambda_builders.utils import extract_tarfile
@@ -171,19 +172,25 @@ class DependencyBuilder(object):
171172
packager.
172173
"""
173174

174-
_COMPATIBLE_PLATFORM_ARM64 = {
175+
_COMPATIBLE_PLATFORM_ARM64 = [
175176
"any",
176177
"linux_aarch64",
177178
"manylinux2014_aarch64",
178-
}
179+
"manylinux_2_17_aarch64",
180+
"manylinux_2_28_aarch64",
181+
"manylinux_2_34_aarch64",
182+
]
179183

180-
_COMPATIBLE_PLATFORM_X86_64 = {
184+
_COMPATIBLE_PLATFORM_X86_64 = [
181185
"any",
182186
"linux_x86_64",
183187
"manylinux1_x86_64",
184188
"manylinux2010_x86_64",
185189
"manylinux2014_x86_64",
186-
}
190+
"manylinux_2_17_x86_64",
191+
"manylinux_2_28_x86_64",
192+
"manylinux_2_34_x86_64",
193+
]
187194

188195
_COMPATIBLE_PLATFORMS = {
189196
ARM64: _COMPATIBLE_PLATFORM_ARM64,
@@ -214,6 +221,14 @@ class DependencyBuilder(object):
214221
# Unlikely to hit this case.
215222
_DEFAULT_GLIBC = (2, 17)
216223

224+
# Mapping of glibc version to the most recent manylinux version compatible.
225+
# The offically supported manylinux versions are 2_17, 2_28 and 2_34 as per https://github.com/pypa/manylinux
226+
_GLIBC_TO_LATEST_MANYLINUX = {
227+
(2, 17): "manylinux_2_17",
228+
(2, 26): "manylinux_2_17",
229+
(2, 34): "manylinux_2_34",
230+
}
231+
217232
def __init__(self, osutils, runtime, python_exe, pip_runner=None, architecture=X86_64):
218233
"""Initialize a DependencyBuilder.
219234
@@ -379,8 +394,60 @@ def _download_binary_wheels(self, packages, directory):
379394
# Try to get binary wheels for each package that isn't compatible.
380395
LOG.debug("Downloading missing wheels: %s", packages)
381396
lambda_abi = get_lambda_abi(self.runtime)
382-
platform = "manylinux2014_aarch64" if self.architecture == ARM64 else "manylinux2014_x86_64"
383-
self._pip.download_manylinux_wheels([pkg.identifier for pkg in packages], directory, lambda_abi, platform)
397+
self._pip.download_manylinux_wheels(
398+
[pkg.identifier for pkg in packages], directory, lambda_abi, self.compatible_platforms
399+
)
400+
401+
@property
402+
def compatible_platforms(self) -> List[str]:
403+
"""Get the list of all compatible platforms for the current architecture.
404+
405+
Examples:
406+
```python
407+
# Return value with python 3.11 on x86_64
408+
[
409+
'any',
410+
'linux_x86_64',
411+
'manylinux1_x86_64',
412+
'manylinux2010_x86_64',
413+
'manylinux2014_x86_64',
414+
'manylinux_2_17_x86_64'
415+
]
416+
417+
# Return value with python 3.12 on x86_64
418+
[
419+
'any',
420+
'linux_x86_64',
421+
'manylinux1_x86_64',
422+
'manylinux2010_x86_64',
423+
'manylinux2014_x86_64',
424+
'manylinux_2_17_x86_64',
425+
'manylinux_2_28_x86_64',
426+
'manylinux_2_34_x86_64'
427+
]
428+
429+
# Return value with python 3.13 on ARM64
430+
[
431+
'any',
432+
'linux_aarch64',
433+
'manylinux2014_aarch64',
434+
'manylinux_2_17_aarch64',
435+
'manylinux_2_28_aarch64',
436+
'manylinux_2_34_aarch64'
437+
]
438+
```
439+
"""
440+
lambda_abi = get_lambda_abi(self.runtime)
441+
manylinux_prefix = self._GLIBC_TO_LATEST_MANYLINUX.get(self._RUNTIME_GLIBC.get(lambda_abi, self._DEFAULT_GLIBC))
442+
architecture = "aarch64" if self.architecture == ARM64 else "x86_64"
443+
444+
# Get the latest compatible platform tag for the current architecture,
445+
# all the previous ones are also compatible.
446+
latest_compatible_platform = f"{manylinux_prefix}_{architecture}"
447+
448+
all_platforms = self._COMPATIBLE_PLATFORMS[self.architecture]
449+
max_index = all_platforms.index(latest_compatible_platform)
450+
return all_platforms[: max_index + 1]
384451

385452
def _build_sdists(self, sdists, directory, compile_c=True):
386453
LOG.debug("Build missing wheels from sdists " "(C compiling %s): %s", compile_c, sdists)
@@ -432,7 +499,7 @@ def _is_compatible_platform_tag(self, expected_abi, platform):
432499
433500
In addition to checking the tag pattern, we also need to verify the glibc version
434501
"""
435-
if platform in self._COMPATIBLE_PLATFORMS[self.architecture]:
502+
if platform in self.compatible_platforms:
436503
return True
437504

438505
arch = "aarch64" if self.architecture == ARM64 else "x86_64"
@@ -832,7 +899,7 @@ def download_all_dependencies(self, requirements_filename, directory):
832899
# complain at deployment time.
833900
self.build_wheel(wheel_package_path, directory)
834901

835-
def download_manylinux_wheels(self, packages, directory, lambda_abi, platform="manylinux2014_x86_64"):
902+
def download_manylinux_wheels(self, packages, directory, lambda_abi, platforms):
836903
"""Download wheel files for manylinux for all the given packages."""
837904
# If any one of these dependencies fails pip will bail out. Since we
838905
# are only interested in all the ones we can download, we need to feed
@@ -846,8 +913,7 @@ def download_manylinux_wheels(self, packages, directory, lambda_abi, platform="m
846913
arguments = [
847914
"--only-binary=:all:",
848915
"--no-deps",
849-
"--platform",
850-
platform,
916+
*list(itertools.chain.from_iterable(["--platform", element] for element in platforms)),
851917
"--implementation",
852918
"cp",
853919
"--abi",

tests/functional/workflows/python_pip/test_packager.py

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,10 +202,10 @@ def _write_requirements_txt(self, packages, directory):
202202
with open(filepath, "w") as f:
203203
f.write(contents)
204204

205-
def _make_appdir_and_dependency_builder(self, reqs, tmpdir, runner, **kwargs):
205+
def _make_appdir_and_dependency_builder(self, reqs, tmpdir, runner, runtime="python3.9", **kwargs):
206206
appdir = str(_create_app_structure(tmpdir))
207207
self._write_requirements_txt(reqs, appdir)
208-
builder = DependencyBuilder(OSUtils(), "python3.9", sys.executable, runner, **kwargs)
208+
builder = DependencyBuilder(OSUtils(), runtime, sys.executable, runner, **kwargs)
209209
return appdir, builder
210210

211211
def test_can_build_local_dir_as_whl(self, tmpdir, pip_runner, osutils):
@@ -516,6 +516,74 @@ def test_can_get_arm64_whls(self, tmpdir, osutils, pip_runner):
516516
for req in reqs:
517517
assert req in installed_packages
518518

519+
def test_can_get_newer_platforms(self, tmpdir, osutils, pip_runner):
520+
reqs = ["foo", "bar"]
521+
pip, runner = pip_runner
522+
appdir, builder = self._make_appdir_and_dependency_builder(reqs, tmpdir, runner, runtime="python3.12")
523+
requirements_file = os.path.join(appdir, "requirements.txt")
524+
pip.packages_to_download(
525+
expected_args=["-r", requirements_file, "--dest", mock.ANY, "--exists-action", "i"],
526+
packages=["foo-1.0-cp312-none-any.whl", "bar-1.2-cp312-cp312-manylinux_2_28_x86_64.whl"],
527+
)
528+
site_packages = os.path.join(appdir, ".chalice.", "site-packages")
529+
with osutils.tempdir() as scratch_dir:
530+
builder.build_site_packages(requirements_file, site_packages, scratch_dir)
531+
installed_packages = os.listdir(site_packages)
532+
533+
pip.validate()
534+
for req in reqs:
535+
assert req in installed_packages
536+
537+
def test_can_get_newer_platforms_cross_compile(self, tmpdir, osutils, pip_runner):
538+
reqs = ["foo", "bar"]
539+
pip, runner = pip_runner
540+
appdir, builder = self._make_appdir_and_dependency_builder(
541+
reqs, tmpdir, runner, runtime="python3.12", architecture=ARM64
542+
)
543+
requirements_file = os.path.join(appdir, "requirements.txt")
544+
pip.packages_to_download(
545+
expected_args=["-r", requirements_file, "--dest", mock.ANY, "--exists-action", "i"],
546+
packages=["foo-1.0-cp312-none-any.whl", "bar-1.2-cp312-cp312-manylinux_2_28_x86_64.whl"],
547+
)
548+
549+
# First call returned x86_64 wheels, fallback to the second call
550+
pip.packages_to_download(
551+
expected_args=[
552+
"--only-binary=:all:",
553+
"--no-deps",
554+
"--platform",
555+
"any",
556+
"--platform",
557+
"linux_aarch64",
558+
"--platform",
559+
"manylinux2014_aarch64",
560+
"--platform",
561+
"manylinux_2_17_aarch64",
562+
# It's python 3.12, so we can use newer platforms.
563+
"--platform",
564+
"manylinux_2_28_aarch64",
565+
"--platform",
566+
"manylinux_2_34_aarch64",
567+
"--implementation",
568+
"cp",
569+
"--abi",
570+
get_lambda_abi(builder.runtime),
571+
"--dest",
572+
mock.ANY,
573+
"bar==1.2",
574+
],
575+
packages=["bar-1.2-cp312-cp312-manylinux_2_28_aarch64.whl"],
576+
)
577+
578+
site_packages = os.path.join(appdir, ".chalice.", "site-packages")
579+
with osutils.tempdir() as scratch_dir:
580+
builder.build_site_packages(requirements_file, site_packages, scratch_dir)
581+
installed_packages = os.listdir(site_packages)
582+
583+
pip.validate()
584+
for req in reqs:
585+
assert req in installed_packages
586+
519587
def test_does_fail_on_invalid_local_package(self, tmpdir, osutils, pip_runner):
520588
reqs = ["../foo"]
521589
pip, runner = pip_runner
@@ -629,7 +697,17 @@ def test_can_replace_incompat_whl(self, tmpdir, osutils, pip_runner):
629697
"--only-binary=:all:",
630698
"--no-deps",
631699
"--platform",
700+
"any",
701+
"--platform",
702+
"linux_x86_64",
703+
"--platform",
704+
"manylinux1_x86_64",
705+
"--platform",
706+
"manylinux2010_x86_64",
707+
"--platform",
632708
"manylinux2014_x86_64",
709+
"--platform",
710+
"manylinux_2_17_x86_64",
633711
"--implementation",
634712
"cp",
635713
"--abi",
@@ -663,7 +741,17 @@ def test_allowlist_sqlalchemy(self, tmpdir, osutils, pip_runner):
663741
"--only-binary=:all:",
664742
"--no-deps",
665743
"--platform",
744+
"any",
745+
"--platform",
746+
"linux_x86_64",
747+
"--platform",
748+
"manylinux1_x86_64",
749+
"--platform",
750+
"manylinux2010_x86_64",
751+
"--platform",
666752
"manylinux2014_x86_64",
753+
"--platform",
754+
"manylinux_2_17_x86_64",
667755
"--implementation",
668756
"cp",
669757
"--abi",
@@ -798,7 +886,17 @@ def test_build_into_existing_dir_with_preinstalled_packages(self, tmpdir, osutil
798886
"--only-binary=:all:",
799887
"--no-deps",
800888
"--platform",
889+
"any",
890+
"--platform",
891+
"linux_x86_64",
892+
"--platform",
893+
"manylinux1_x86_64",
894+
"--platform",
895+
"manylinux2010_x86_64",
896+
"--platform",
801897
"manylinux2014_x86_64",
898+
"--platform",
899+
"manylinux_2_17_x86_64",
802900
"--implementation",
803901
"cp",
804902
"--abi",

tests/unit/workflows/python_pip/test_packager.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,13 +249,35 @@ def test_download_wheels(self, pip_factory):
249249
# for getting lambda compatible wheels.
250250
pip, runner = pip_factory()
251251
packages = ["foo", "bar", "baz"]
252-
runner.download_manylinux_wheels(packages, "directory", "abi")
252+
runner.download_manylinux_wheels(
253+
packages,
254+
"directory",
255+
"abi",
256+
[
257+
"any",
258+
"linux_x86_64",
259+
"manylinux1_x86_64",
260+
"manylinux2010_x86_64",
261+
"manylinux2014_x86_64",
262+
"manylinux_2_17_x86_64",
263+
],
264+
)
253265
expected_prefix = [
254266
"download",
255267
"--only-binary=:all:",
256268
"--no-deps",
257269
"--platform",
270+
"any",
271+
"--platform",
272+
"linux_x86_64",
273+
"--platform",
274+
"manylinux1_x86_64",
275+
"--platform",
276+
"manylinux2010_x86_64",
277+
"--platform",
258278
"manylinux2014_x86_64",
279+
"--platform",
280+
"manylinux_2_17_x86_64",
259281
"--implementation",
260282
"cp",
261283
"--abi",
@@ -270,7 +292,7 @@ def test_download_wheels(self, pip_factory):
270292

271293
def test_download_wheels_no_wheels(self, pip_factory):
272294
pip, runner = pip_factory()
273-
runner.download_manylinux_wheels([], "directory", "abi")
295+
runner.download_manylinux_wheels([], "directory", "abi", [])
274296
assert len(pip.calls) == 0
275297

276298
def test_does_find_local_directory(self, pip_factory):

0 commit comments

Comments
 (0)