Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
_manylinux.py253 linesDownload Raw Back to packaging
1import collections
2import contextlib
3import functools
4import os
5import re
6import sys
7import warnings
8from typing import Dict, Generator, Iterator, NamedTuple, Optional, Sequence, Tuple
9
10from ._elffile import EIClass, EIData, ELFFile, EMachine
11
12EF_ARM_ABIMASK = 0xFF000000
13EF_ARM_ABI_VER5 = 0x05000000
14EF_ARM_ABI_FLOAT_HARD = 0x00000400
15
16
17# `os.PathLike` not a generic type until Python 3.9, so sticking with `str`
18# as the type for `path` until then.
19@contextlib.contextmanager
20def _parse_elf(path: str) -> Generator[Optional[ELFFile], None, None]:
21    try:
22        with open(path, "rb") as f:
23            yield ELFFile(f)
24    except (OSError, TypeError, ValueError):
25        yield None
26
27
28def _is_linux_armhf(executable: str) -> bool:
29    # hard-float ABI can be detected from the ELF header of the running
30    # process
31    # https://static.docs.arm.com/ihi0044/g/aaelf32.pdf
32    with _parse_elf(executable) as f:
33        return (
34            f is not None
35            and f.capacity == EIClass.C32
36            and f.encoding == EIData.Lsb
37            and f.machine == EMachine.Arm
38            and f.flags & EF_ARM_ABIMASK == EF_ARM_ABI_VER5
39            and f.flags & EF_ARM_ABI_FLOAT_HARD == EF_ARM_ABI_FLOAT_HARD
40        )
41
42
43def _is_linux_i686(executable: str) -> bool:
44    with _parse_elf(executable) as f:
45        return (
46            f is not None
47            and f.capacity == EIClass.C32
48            and f.encoding == EIData.Lsb
49            and f.machine == EMachine.I386
50        )
51
52
53def _have_compatible_abi(executable: str, archs: Sequence[str]) -> bool:
54    if "armv7l" in archs:
55        return _is_linux_armhf(executable)
56    if "i686" in archs:
57        return _is_linux_i686(executable)
58    allowed_archs = {"x86_64", "aarch64", "ppc64", "ppc64le", "s390x", "loongarch64"}
59    return any(arch in allowed_archs for arch in archs)
60
61
62# If glibc ever changes its major version, we need to know what the last
63# minor version was, so we can build the complete list of all versions.
64# For now, guess what the highest minor version might be, assume it will
65# be 50 for testing. Once this actually happens, update the dictionary
66# with the actual value.
67_LAST_GLIBC_MINOR: Dict[int, int] = collections.defaultdict(lambda: 50)
68
69
70class _GLibCVersion(NamedTuple):
71    major: int
72    minor: int
73
74
75def _glibc_version_string_confstr() -> Optional[str]:
76    """
77    Primary implementation of glibc_version_string using os.confstr.
78    """
79    # os.confstr is quite a bit faster than ctypes.DLL. It's also less likely
80    # to be broken or missing. This strategy is used in the standard library
81    # platform module.
82    # https://github.com/python/cpython/blob/fcf1d003bf4f0100c/Lib/platform.py#L175-L183
83    try:
84        # Should be a string like "glibc 2.17".
85        version_string: str = getattr(os, "confstr")("CS_GNU_LIBC_VERSION")
86        assert version_string is not None
87        _, version = version_string.rsplit()
88    except (AssertionError, AttributeError, OSError, ValueError):
89        # os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)...
90        return None
91    return version
92
93
94def _glibc_version_string_ctypes() -> Optional[str]:
95    """
96    Fallback implementation of glibc_version_string using ctypes.
97    """
98    try:
99        import ctypes
100    except ImportError:
101        return None
102
103    # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen
104    # manpage says, "If filename is NULL, then the returned handle is for the
105    # main program". This way we can let the linker do the work to figure out
106    # which libc our process is actually using.
107    #
108    # We must also handle the special case where the executable is not a
109    # dynamically linked executable. This can occur when using musl libc,
110    # for example. In this situation, dlopen() will error, leading to an
111    # OSError. Interestingly, at least in the case of musl, there is no
112    # errno set on the OSError. The single string argument used to construct
113    # OSError comes from libc itself and is therefore not portable to
114    # hard code here. In any case, failure to call dlopen() means we
115    # can proceed, so we bail on our attempt.
116    try:
117        process_namespace = ctypes.CDLL(None)
118    except OSError:
119        return None
120
121    try:
122        gnu_get_libc_version = process_namespace.gnu_get_libc_version
123    except AttributeError:
124        # Symbol doesn't exist -> therefore, we are not linked to
125        # glibc.
126        return None
127
128    # Call gnu_get_libc_version, which returns a string like "2.5"
129    gnu_get_libc_version.restype = ctypes.c_char_p
130    version_str: str = gnu_get_libc_version()
131    # py2 / py3 compatibility:
132    if not isinstance(version_str, str):
133        version_str = version_str.decode("ascii")
134
135    return version_str
136
137
138def _glibc_version_string() -> Optional[str]:
139    """Returns glibc version string, or None if not using glibc."""
140    return _glibc_version_string_confstr() or _glibc_version_string_ctypes()
141
142
143def _parse_glibc_version(version_str: str) -> Tuple[int, int]:
144    """Parse glibc version.
145
146    We use a regexp instead of str.split because we want to discard any
147    random junk that might come after the minor version -- this might happen
148    in patched/forked versions of glibc (e.g. Linaro's version of glibc
149    uses version strings like "2.20-2014.11"). See gh-3588.
150    """
151    m = re.match(r"(?P<major>[0-9]+)\.(?P<minor>[0-9]+)", version_str)
152    if not m:
153        warnings.warn(
154            f"Expected glibc version with 2 components major.minor,"
155            f" got: {version_str}",
156            RuntimeWarning,
157        )
158        return -1, -1
159    return int(m.group("major")), int(m.group("minor"))
160
161
162@functools.lru_cache()
163def _get_glibc_version() -> Tuple[int, int]:
164    version_str = _glibc_version_string()
165    if version_str is None:
166        return (-1, -1)
167    return _parse_glibc_version(version_str)
168
169
170# From PEP 513, PEP 600
171def _is_compatible(arch: str, version: _GLibCVersion) -> bool:
172    sys_glibc = _get_glibc_version()
173    if sys_glibc < version:
174        return False
175    # Check for presence of _manylinux module.
176    try:
177        import _manylinux  # noqa
178    except ImportError:
179        return True
180    if hasattr(_manylinux, "manylinux_compatible"):
181        result = _manylinux.manylinux_compatible(version[0], version[1], arch)
182        if result is not None:
183            return bool(result)
184        return True
185    if version == _GLibCVersion(2, 5):
186        if hasattr(_manylinux, "manylinux1_compatible"):
187            return bool(_manylinux.manylinux1_compatible)
188    if version == _GLibCVersion(2, 12):
189        if hasattr(_manylinux, "manylinux2010_compatible"):
190            return bool(_manylinux.manylinux2010_compatible)
191    if version == _GLibCVersion(2, 17):
192        if hasattr(_manylinux, "manylinux2014_compatible"):
193            return bool(_manylinux.manylinux2014_compatible)
194    return True
195
196
197_LEGACY_MANYLINUX_MAP = {
198    # CentOS 7 w/ glibc 2.17 (PEP 599)
199    (2, 17): "manylinux2014",
200    # CentOS 6 w/ glibc 2.12 (PEP 571)
201    (2, 12): "manylinux2010",
202    # CentOS 5 w/ glibc 2.5 (PEP 513)
203    (2, 5): "manylinux1",
204}
205
206
207def platform_tags(archs: Sequence[str]) -> Iterator[str]:
208    """Generate manylinux tags compatible to the current platform.
209
210    :param archs: Sequence of compatible architectures.
211        The first one shall be the closest to the actual architecture and be the part of
212        platform tag after the ``linux_`` prefix, e.g. ``x86_64``.
213        The ``linux_`` prefix is assumed as a prerequisite for the current platform to
214        be manylinux-compatible.
215
216    :returns: An iterator of compatible manylinux tags.
217    """
218    if not _have_compatible_abi(sys.executable, archs):
219        return
220    # Oldest glibc to be supported regardless of architecture is (2, 17).
221    too_old_glibc2 = _GLibCVersion(2, 16)
222    if set(archs) & {"x86_64", "i686"}:
223        # On x86/i686 also oldest glibc to be supported is (2, 5).
224        too_old_glibc2 = _GLibCVersion(2, 4)
225    current_glibc = _GLibCVersion(*_get_glibc_version())
226    glibc_max_list = [current_glibc]
227    # We can assume compatibility across glibc major versions.
228    # https://sourceware.org/bugzilla/show_bug.cgi?id=24636
229    #
230    # Build a list of maximum glibc versions so that we can
231    # output the canonical list of all glibc from current_glibc
232    # down to too_old_glibc2, including all intermediary versions.
233    for glibc_major in range(current_glibc.major - 1, 1, -1):
234        glibc_minor = _LAST_GLIBC_MINOR[glibc_major]
235        glibc_max_list.append(_GLibCVersion(glibc_major, glibc_minor))
236    for arch in archs:
237        for glibc_max in glibc_max_list:
238            if glibc_max.major == too_old_glibc2.major:
239                min_minor = too_old_glibc2.minor
240            else:
241                # For other glibc major versions oldest supported is (x, 0).
242                min_minor = -1
243            for glibc_minor in range(glibc_max.minor, min_minor, -1):
244                glibc_version = _GLibCVersion(glibc_max.major, glibc_minor)
245                tag = "manylinux_{}_{}".format(*glibc_version)
246                if _is_compatible(arch, glibc_version):
247                    yield f"{tag}_{arch}"
248                # Handle the legacy manylinux1, manylinux2010, manylinux2014 tags.
249                if glibc_version in _LEGACY_MANYLINUX_MAP:
250                    legacy_tag = _LEGACY_MANYLINUX_MAP[glibc_version]
251                    if _is_compatible(arch, glibc_version):
252                        yield f"{legacy_tag}_{arch}"
253 
codekingpro/portable-devtools · Team Ai