| Viewing file:  _musllinux.py (4.28 KB)      -rw-r--r-- Select action/file-type:
 
  (+) |  (+) |  (+) | Code (+) | Session (+) |  (+) | SDB (+) |  (+) |  (+) |  (+) |  (+) |  (+) | 
 
"""PEP 656 support.
 This module implements logic to detect if the currently running Python is
 linked against musl, and what musl version is used.
 """
 
 import contextlib
 import functools
 import operator
 import os
 import re
 import struct
 import subprocess
 import sys
 from typing import IO, Iterator, NamedTuple, Optional, Tuple
 
 
 def _read_unpacked(f: IO[bytes], fmt: str) -> Tuple[int, ...]:
 return struct.unpack(fmt, f.read(struct.calcsize(fmt)))
 
 
 def _parse_ld_musl_from_elf(f: IO[bytes]) -> Optional[str]:
 """Detect musl libc location by parsing the Python executable.
 
 Based on: https://gist.github.com/lyssdod/f51579ae8d93c8657a5564aefc2ffbca
 ELF header: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html
 """
 f.seek(0)
 try:
 ident = _read_unpacked(f, "16B")
 except struct.error:
 return None
 if ident[:4] != tuple(b"\x7fELF"):  # Invalid magic, not ELF.
 return None
 f.seek(struct.calcsize("HHI"), 1)  # Skip file type, machine, and version.
 
 try:
 # e_fmt: Format for program header.
 # p_fmt: Format for section header.
 # p_idx: Indexes to find p_type, p_offset, and p_filesz.
 e_fmt, p_fmt, p_idx = {
 1: ("IIIIHHH", "IIIIIIII", (0, 1, 4)),  # 32-bit.
 2: ("QQQIHHH", "IIQQQQQQ", (0, 2, 5)),  # 64-bit.
 }[ident[4]]
 except KeyError:
 return None
 else:
 p_get = operator.itemgetter(*p_idx)
 
 # Find the interpreter section and return its content.
 try:
 _, e_phoff, _, _, _, e_phentsize, e_phnum = _read_unpacked(f, e_fmt)
 except struct.error:
 return None
 for i in range(e_phnum + 1):
 f.seek(e_phoff + e_phentsize * i)
 try:
 p_type, p_offset, p_filesz = p_get(_read_unpacked(f, p_fmt))
 except struct.error:
 return None
 if p_type != 3:  # Not PT_INTERP.
 continue
 f.seek(p_offset)
 interpreter = os.fsdecode(f.read(p_filesz)).strip("\0")
 if "musl" not in interpreter:
 return None
 return interpreter
 return None
 
 
 class _MuslVersion(NamedTuple):
 major: int
 minor: int
 
 
 def _parse_musl_version(output: str) -> Optional[_MuslVersion]:
 lines = [n for n in (n.strip() for n in output.splitlines()) if n]
 if len(lines) < 2 or lines[0][:4] != "musl":
 return None
 m = re.match(r"Version (\d+)\.(\d+)", lines[1])
 if not m:
 return None
 return _MuslVersion(major=int(m.group(1)), minor=int(m.group(2)))
 
 
 @functools.lru_cache()
 def _get_musl_version(executable: str) -> Optional[_MuslVersion]:
 """Detect currently-running musl runtime version.
 
 This is done by checking the specified executable's dynamic linking
 information, and invoking the loader to parse its output for a version
 string. If the loader is musl, the output would be something like::
 
 musl libc (x86_64)
 Version 1.2.2
 Dynamic Program Loader
 """
 with contextlib.ExitStack() as stack:
 try:
 f = stack.enter_context(open(executable, "rb"))
 except IOError:
 return None
 ld = _parse_ld_musl_from_elf(f)
 if not ld:
 return None
 proc = subprocess.run([ld], stderr=subprocess.PIPE, universal_newlines=True)
 return _parse_musl_version(proc.stderr)
 
 
 def platform_tags(arch: str) -> Iterator[str]:
 """Generate musllinux tags compatible to the current platform.
 
 :param arch: Should be the part of platform tag after the ``linux_``
 prefix, e.g. ``x86_64``. The ``linux_`` prefix is assumed as a
 prerequisite for the current platform to be musllinux-compatible.
 
 :returns: An iterator of compatible musllinux tags.
 """
 sys_musl = _get_musl_version(sys.executable)
 if sys_musl is None:  # Python not dynamically linked against musl.
 return
 for minor in range(sys_musl.minor, -1, -1):
 yield f"musllinux_{sys_musl.major}_{minor}_{arch}"
 
 
 if __name__ == "__main__":  # pragma: no cover
 import sysconfig
 
 plat = sysconfig.get_platform()
 assert plat.startswith("linux-"), "not linux"
 
 print("plat:", plat)
 print("musl:", _get_musl_version(sys.executable))
 print("tags:", end=" ")
 for t in platform_tags(re.sub(r"[.-]", "_", plat.split("-", 1)[-1])):
 print(t, end="\n      ")
 
 |