Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pip/_vendor/packaging/_musllinux.py: 25%
72 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:48 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:48 +0000
1"""PEP 656 support.
3This module implements logic to detect if the currently running Python is
4linked against musl, and what musl version is used.
5"""
7import contextlib
8import functools
9import operator
10import os
11import re
12import struct
13import subprocess
14import sys
15from typing import IO, Iterator, NamedTuple, Optional, Tuple
18def _read_unpacked(f: IO[bytes], fmt: str) -> Tuple[int, ...]:
19 return struct.unpack(fmt, f.read(struct.calcsize(fmt)))
22def _parse_ld_musl_from_elf(f: IO[bytes]) -> Optional[str]:
23 """Detect musl libc location by parsing the Python executable.
25 Based on: https://gist.github.com/lyssdod/f51579ae8d93c8657a5564aefc2ffbca
26 ELF header: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html
27 """
28 f.seek(0)
29 try:
30 ident = _read_unpacked(f, "16B")
31 except struct.error:
32 return None
33 if ident[:4] != tuple(b"\x7fELF"): # Invalid magic, not ELF.
34 return None
35 f.seek(struct.calcsize("HHI"), 1) # Skip file type, machine, and version.
37 try:
38 # e_fmt: Format for program header.
39 # p_fmt: Format for section header.
40 # p_idx: Indexes to find p_type, p_offset, and p_filesz.
41 e_fmt, p_fmt, p_idx = {
42 1: ("IIIIHHH", "IIIIIIII", (0, 1, 4)), # 32-bit.
43 2: ("QQQIHHH", "IIQQQQQQ", (0, 2, 5)), # 64-bit.
44 }[ident[4]]
45 except KeyError:
46 return None
47 else:
48 p_get = operator.itemgetter(*p_idx)
50 # Find the interpreter section and return its content.
51 try:
52 _, e_phoff, _, _, _, e_phentsize, e_phnum = _read_unpacked(f, e_fmt)
53 except struct.error:
54 return None
55 for i in range(e_phnum + 1):
56 f.seek(e_phoff + e_phentsize * i)
57 try:
58 p_type, p_offset, p_filesz = p_get(_read_unpacked(f, p_fmt))
59 except struct.error:
60 return None
61 if p_type != 3: # Not PT_INTERP.
62 continue
63 f.seek(p_offset)
64 interpreter = os.fsdecode(f.read(p_filesz)).strip("\0")
65 if "musl" not in interpreter:
66 return None
67 return interpreter
68 return None
71class _MuslVersion(NamedTuple):
72 major: int
73 minor: int
76def _parse_musl_version(output: str) -> Optional[_MuslVersion]:
77 lines = [n for n in (n.strip() for n in output.splitlines()) if n]
78 if len(lines) < 2 or lines[0][:4] != "musl":
79 return None
80 m = re.match(r"Version (\d+)\.(\d+)", lines[1])
81 if not m:
82 return None
83 return _MuslVersion(major=int(m.group(1)), minor=int(m.group(2)))
86@functools.lru_cache()
87def _get_musl_version(executable: str) -> Optional[_MuslVersion]:
88 """Detect currently-running musl runtime version.
90 This is done by checking the specified executable's dynamic linking
91 information, and invoking the loader to parse its output for a version
92 string. If the loader is musl, the output would be something like::
94 musl libc (x86_64)
95 Version 1.2.2
96 Dynamic Program Loader
97 """
98 with contextlib.ExitStack() as stack:
99 try:
100 f = stack.enter_context(open(executable, "rb"))
101 except OSError:
102 return None
103 ld = _parse_ld_musl_from_elf(f)
104 if not ld:
105 return None
106 proc = subprocess.run([ld], stderr=subprocess.PIPE, universal_newlines=True)
107 return _parse_musl_version(proc.stderr)
110def platform_tags(arch: str) -> Iterator[str]:
111 """Generate musllinux tags compatible to the current platform.
113 :param arch: Should be the part of platform tag after the ``linux_``
114 prefix, e.g. ``x86_64``. The ``linux_`` prefix is assumed as a
115 prerequisite for the current platform to be musllinux-compatible.
117 :returns: An iterator of compatible musllinux tags.
118 """
119 sys_musl = _get_musl_version(sys.executable)
120 if sys_musl is None: # Python not dynamically linked against musl.
121 return
122 for minor in range(sys_musl.minor, -1, -1):
123 yield f"musllinux_{sys_musl.major}_{minor}_{arch}"
126if __name__ == "__main__": # pragma: no cover
127 import sysconfig
129 plat = sysconfig.get_platform()
130 assert plat.startswith("linux-"), "not linux"
132 print("plat:", plat)
133 print("musl:", _get_musl_version(sys.executable))
134 print("tags:", end=" ")
135 for t in platform_tags(re.sub(r"[.-]", "_", plat.split("-", 1)[-1])):
136 print(t, end="\n ")