codekingpro/portable-devtools
114k
1"""PEP 656 support.2 3This module implements logic to detect if the currently running Python is4linked against musl, and what musl version is used.5"""6 7import functools8import re9import subprocess10import sys11from typing import Iterator, NamedTuple, Optional, Sequence12 13from ._elffile import ELFFile14 15 16class _MuslVersion(NamedTuple):17 major: int18 minor: int19 20 21def _parse_musl_version(output: str) -> Optional[_MuslVersion]:22 lines = [n for n in (n.strip() for n in output.splitlines()) if n]23 if len(lines) < 2 or lines[0][:4] != "musl":24 return None25 m = re.match(r"Version (\d+)\.(\d+)", lines[1])26 if not m:27 return None28 return _MuslVersion(major=int(m.group(1)), minor=int(m.group(2)))29 30 31@functools.lru_cache()32def _get_musl_version(executable: str) -> Optional[_MuslVersion]:33 """Detect currently-running musl runtime version.34 35 This is done by checking the specified executable's dynamic linking36 information, and invoking the loader to parse its output for a version37 string. If the loader is musl, the output would be something like::38 39 musl libc (x86_64)40 Version 1.2.241 Dynamic Program Loader42 """43 try:44 with open(executable, "rb") as f:45 ld = ELFFile(f).interpreter46 except (OSError, TypeError, ValueError):47 return None48 if ld is None or "musl" not in ld:49 return None50 proc = subprocess.run([ld], stderr=subprocess.PIPE, text=True)51 return _parse_musl_version(proc.stderr)52 53 54def platform_tags(archs: Sequence[str]) -> Iterator[str]:55 """Generate musllinux tags compatible to the current platform.56 57 :param archs: Sequence of compatible architectures.58 The first one shall be the closest to the actual architecture and be the part of59 platform tag after the ``linux_`` prefix, e.g. ``x86_64``.60 The ``linux_`` prefix is assumed as a prerequisite for the current platform to61 be musllinux-compatible.62 63 :returns: An iterator of compatible musllinux tags.64 """65 sys_musl = _get_musl_version(sys.executable)66 if sys_musl is None: # Python not dynamically linked against musl.67 return68 for arch in archs:69 for minor in range(sys_musl.minor, -1, -1):70 yield f"musllinux_{sys_musl.major}_{minor}_{arch}"71 72 73if __name__ == "__main__": # pragma: no cover74 import sysconfig75 76 plat = sysconfig.get_platform()77 assert plat.startswith("linux-"), "not linux"78 79 print("plat:", plat)80 print("musl:", _get_musl_version(sys.executable))81 print("tags:", end=" ")82 for t in platform_tags(re.sub(r"[.-]", "_", plat.split("-", 1)[-1])):83 print(t, end="\n ")84 