From 051e8f241e0675b514d9676ad4eb036c3b6f2799 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cristi=C3=A1n=20Maureira-Fredes?= Date: Mon, 24 Apr 2023 20:21:18 +0200 Subject: [PATCH 01/10] bpo-43248: add musl support to platform.libc_ver This includes an implementation from the packaging module to detect the musl version on the system, allowing libc_ver to properly return the version rather than an empty tuple. Co-authored-by: Manuel Kaufmann --- Lib/_elffile.py | 111 ++++++++++++++++++++++++++++++++++++++++++++++++ Lib/platform.py | 49 +++++++++++++++++++++ 2 files changed, 160 insertions(+) create mode 100644 Lib/_elffile.py diff --git a/Lib/_elffile.py b/Lib/_elffile.py new file mode 100644 index 00000000000000..d2f608a0513ef9 --- /dev/null +++ b/Lib/_elffile.py @@ -0,0 +1,111 @@ +""" +ELF file parser. + +This provides a class ``ELFFile`` that parses an ELF executable in a similar +interface to ``ZipFile``. Only the read interface is implemented. + +Taken from the packaging module without modification. + +Based on: https://gist.github.com/lyssdod/f51579ae8d93c8657a5564aefc2ffbca +ELF header: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html + +""" + +import enum +import os +import struct +from typing import IO, Optional, Tuple + + +class ELFInvalid(ValueError): + pass + + +class EIClass(enum.IntEnum): + C32 = 1 + C64 = 2 + + +class EIData(enum.IntEnum): + Lsb = 1 + Msb = 2 + + +class EMachine(enum.IntEnum): + I386 = 3 + S390 = 22 + Arm = 40 + X8664 = 62 + AArc64 = 183 + + +class ELFFile: + """ + Representation of an ELF executable. + """ + + def __init__(self, f: IO[bytes]) -> None: + self._f = f + + try: + ident = self._read("16B") + except struct.error: + raise ELFInvalid("unable to parse identification") + magic = bytes(ident[:4]) + if magic != b"\x7fELF": + raise ELFInvalid(f"invalid magic: {magic!r}") + + self.capacity = ident[4] # Format for program header (bitness). + self.encoding = ident[5] # Data structure encoding (endianness). + + 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, self._p_fmt, self._p_idx = { + (1, 1): ("HHIIIIIHHH", ">IIIIIIII", (0, 1, 4)), # 32-bit MSB. + (2, 1): ("HHIQQQIHHH", ">IIQQQQQQ", (0, 2, 5)), # 64-bit MSB. + }[(self.capacity, self.encoding)] + except KeyError: + raise ELFInvalid( + f"unrecognized capacity ({self.capacity}) or " + f"encoding ({self.encoding})" + ) + + try: + ( + _, + self.machine, # Architecture type. + _, + _, + self._e_phoff, # Offset of program header. + _, + self.flags, # Processor-specific flags. + _, + self._e_phentsize, # Size of section. + self._e_phnum, # Number of sections. + ) = self._read(e_fmt) + except struct.error as e: + raise ELFInvalid("unable to parse machine and section information") from e + + def _read(self, fmt: str) -> Tuple[int, ...]: + return struct.unpack(fmt, self._f.read(struct.calcsize(fmt))) + + @property + def interpreter(self) -> Optional[str]: + """ + The path recorded in the ``PT_INTERP`` section header. + """ + for index in range(self._e_phnum): + self._f.seek(self._e_phoff + self._e_phentsize * index) + try: + data = self._read(self._p_fmt) + except struct.error: + continue + if data[self._p_idx[0]] != 3: # Not PT_INTERP. + continue + self._f.seek(data[self._p_idx[1]]) + return os.fsdecode(self._f.read(data[self._p_idx[2]])).strip("\0") + return None diff --git a/Lib/platform.py b/Lib/platform.py index b018046f5268d1..327480bd25eb37 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -118,6 +118,7 @@ import sys import functools import itertools +from typing import NamedTuple, Optional ### Globals & Constants @@ -199,6 +200,10 @@ def libc_ver(executable=None, lib='', version='', chunksize=16384): binary = f.read(chunksize) pos = 0 while pos < len(binary): + if b'musl' in binary: + mv = _get_musl_version(executable) + return "musl", f"{mv.major}.{mv.minor}" + if b'libc' in binary or b'GLIBC' in binary: m = _libc_search.search(binary, pos) else: @@ -1375,6 +1380,50 @@ def freedesktop_os_release(): return _os_release_cache.copy() +### musl libc version support +# These functions were copied from the packaging module: +# https://github.com/pypa/packaging/blob/main/src/packaging/_musllinux.py + +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]: + from ._elffile import ELFFile + import subprocess + """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 + """ + try: + with open(executable, "rb") as f: + ld = ELFFile(f).interpreter + except (OSError, TypeError, ValueError): + return None + if ld is None or "musl" not in ld: + return None + proc = subprocess.run([ld], stderr=subprocess.PIPE, universal_newlines=True) + return _parse_musl_version(proc.stderr) + + ### Command line interface if __name__ == '__main__': From 625330aac427258d260cb84be65c90bdaa29f29e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cristi=C3=A1n=20Maureira-Fredes?= Date: Mon, 24 Apr 2023 23:53:00 +0200 Subject: [PATCH 02/10] Remove typehints and the MuslVersion structure --- Lib/_elffile.py | 3 +++ Lib/platform.py | 19 ++++++++----------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Lib/_elffile.py b/Lib/_elffile.py index d2f608a0513ef9..cd0be99505f440 100644 --- a/Lib/_elffile.py +++ b/Lib/_elffile.py @@ -9,6 +9,9 @@ Based on: https://gist.github.com/lyssdod/f51579ae8d93c8657a5564aefc2ffbca ELF header: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html +This module is used to get the proper musl libc information for +platform.libc_ver. +It has no documented public API and should not be used directly. """ import enum diff --git a/Lib/platform.py b/Lib/platform.py index 327480bd25eb37..24aef7aa15c428 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -118,7 +118,6 @@ import sys import functools import itertools -from typing import NamedTuple, Optional ### Globals & Constants @@ -202,7 +201,7 @@ def libc_ver(executable=None, lib='', version='', chunksize=16384): while pos < len(binary): if b'musl' in binary: mv = _get_musl_version(executable) - return "musl", f"{mv.major}.{mv.minor}" + return "musl", mv if b'libc' in binary or b'GLIBC' in binary: m = _libc_search.search(binary, pos) @@ -1384,25 +1383,19 @@ def freedesktop_os_release(): # These functions were copied from the packaging module: # https://github.com/pypa/packaging/blob/main/src/packaging/_musllinux.py -class _MuslVersion(NamedTuple): - major: int - minor: int - -def _parse_musl_version(output: str) -> Optional[_MuslVersion]: +def _parse_musl_version(output): 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))) + return f"{m.group(1)}.{m.group(2)}" @functools.lru_cache() -def _get_musl_version(executable: str) -> Optional[_MuslVersion]: - from ._elffile import ELFFile - import subprocess +def _get_musl_version(executable): """Detect currently-running musl runtime version. This is done by checking the specified executable's dynamic linking @@ -1413,6 +1406,10 @@ def _get_musl_version(executable: str) -> Optional[_MuslVersion]: Version 1.2.2 Dynamic Program Loader """ + + from ._elffile import ELFFile + import subprocess + try: with open(executable, "rb") as f: ld = ELFFile(f).interpreter From c6306332b40155c6ae88e40da1a7eb6342be45fd Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 25 Apr 2023 11:05:55 -0600 Subject: [PATCH 03/10] The relative import is not valid We get the following error otherwise: ``` ImportError: attempted relative import with no known parent package ``` --- Lib/platform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/platform.py b/Lib/platform.py index c5e9e12c62149e..1a7b63cbe612ad 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -1376,7 +1376,7 @@ def _get_musl_version(executable): Dynamic Program Loader """ - from ._elffile import ELFFile + from _elffile import ELFFile import subprocess try: From 1a1096604973d6b1d98b0d914958f060d8f7425b Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 25 Apr 2023 11:40:13 -0600 Subject: [PATCH 04/10] Remove typing annotations from `_elffile.py` --- Lib/_elffile.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Lib/_elffile.py b/Lib/_elffile.py index cd0be99505f440..0486f4112b2d79 100644 --- a/Lib/_elffile.py +++ b/Lib/_elffile.py @@ -17,7 +17,6 @@ import enum import os import struct -from typing import IO, Optional, Tuple class ELFInvalid(ValueError): @@ -47,7 +46,7 @@ class ELFFile: Representation of an ELF executable. """ - def __init__(self, f: IO[bytes]) -> None: + def __init__(self, f): self._f = f try: @@ -93,11 +92,11 @@ def __init__(self, f: IO[bytes]) -> None: except struct.error as e: raise ELFInvalid("unable to parse machine and section information") from e - def _read(self, fmt: str) -> Tuple[int, ...]: + def _read(self, fmt): return struct.unpack(fmt, self._f.read(struct.calcsize(fmt))) @property - def interpreter(self) -> Optional[str]: + def interpreter(self): """ The path recorded in the ``PT_INTERP`` section header. """ From ee37ed7f93b9f4aa1464ed720c47b42e1e67ea2b Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 25 Apr 2023 11:40:32 -0600 Subject: [PATCH 05/10] Remove unused classes from `_elffile.py` --- Lib/_elffile.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/Lib/_elffile.py b/Lib/_elffile.py index 0486f4112b2d79..13405989ff2e28 100644 --- a/Lib/_elffile.py +++ b/Lib/_elffile.py @@ -23,24 +23,6 @@ class ELFInvalid(ValueError): pass -class EIClass(enum.IntEnum): - C32 = 1 - C64 = 2 - - -class EIData(enum.IntEnum): - Lsb = 1 - Msb = 2 - - -class EMachine(enum.IntEnum): - I386 = 3 - S390 = 22 - Arm = 40 - X8664 = 62 - AArc64 = 183 - - class ELFFile: """ Representation of an ELF executable. From e945a5d92930f9ff19fa358a221177da35f566d2 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 25 Apr 2023 11:50:00 -0600 Subject: [PATCH 06/10] Move ELFFile class from its own module to `platform.py` --- Lib/_elffile.py | 95 ------------------------------------------------- Lib/platform.py | 80 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 77 insertions(+), 98 deletions(-) delete mode 100644 Lib/_elffile.py diff --git a/Lib/_elffile.py b/Lib/_elffile.py deleted file mode 100644 index 13405989ff2e28..00000000000000 --- a/Lib/_elffile.py +++ /dev/null @@ -1,95 +0,0 @@ -""" -ELF file parser. - -This provides a class ``ELFFile`` that parses an ELF executable in a similar -interface to ``ZipFile``. Only the read interface is implemented. - -Taken from the packaging module without modification. - -Based on: https://gist.github.com/lyssdod/f51579ae8d93c8657a5564aefc2ffbca -ELF header: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html - -This module is used to get the proper musl libc information for -platform.libc_ver. -It has no documented public API and should not be used directly. -""" - -import enum -import os -import struct - - -class ELFInvalid(ValueError): - pass - - -class ELFFile: - """ - Representation of an ELF executable. - """ - - def __init__(self, f): - self._f = f - - try: - ident = self._read("16B") - except struct.error: - raise ELFInvalid("unable to parse identification") - magic = bytes(ident[:4]) - if magic != b"\x7fELF": - raise ELFInvalid(f"invalid magic: {magic!r}") - - self.capacity = ident[4] # Format for program header (bitness). - self.encoding = ident[5] # Data structure encoding (endianness). - - 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, self._p_fmt, self._p_idx = { - (1, 1): ("HHIIIIIHHH", ">IIIIIIII", (0, 1, 4)), # 32-bit MSB. - (2, 1): ("HHIQQQIHHH", ">IIQQQQQQ", (0, 2, 5)), # 64-bit MSB. - }[(self.capacity, self.encoding)] - except KeyError: - raise ELFInvalid( - f"unrecognized capacity ({self.capacity}) or " - f"encoding ({self.encoding})" - ) - - try: - ( - _, - self.machine, # Architecture type. - _, - _, - self._e_phoff, # Offset of program header. - _, - self.flags, # Processor-specific flags. - _, - self._e_phentsize, # Size of section. - self._e_phnum, # Number of sections. - ) = self._read(e_fmt) - except struct.error as e: - raise ELFInvalid("unable to parse machine and section information") from e - - def _read(self, fmt): - return struct.unpack(fmt, self._f.read(struct.calcsize(fmt))) - - @property - def interpreter(self): - """ - The path recorded in the ``PT_INTERP`` section header. - """ - for index in range(self._e_phnum): - self._f.seek(self._e_phoff + self._e_phentsize * index) - try: - data = self._read(self._p_fmt) - except struct.error: - continue - if data[self._p_idx[0]] != 3: # Not PT_INTERP. - continue - self._f.seek(data[self._p_idx[1]]) - return os.fsdecode(self._f.read(data[self._p_idx[2]])).strip("\0") - return None diff --git a/Lib/platform.py b/Lib/platform.py index 1a7b63cbe612ad..a1f23a98f4ffbd 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -118,6 +118,7 @@ import sys import functools import itertools +import struct ### Globals & Constants @@ -1349,9 +1350,84 @@ def freedesktop_os_release(): ### musl libc version support -# These functions were copied from the packaging module: +# These functions were copied and adapted from the packaging module: # https://github.com/pypa/packaging/blob/main/src/packaging/_musllinux.py +# https://github.com/pypa/packaging/blob/main/src/packaging/_elffile.py +class ELFInvalid(ValueError): + pass + + +class ELFFile: + """ + Representation of an ELF executable. + """ + + def __init__(self, f): + self._f = f + + try: + ident = self._read("16B") + except struct.error: + raise ELFInvalid("unable to parse identification") + magic = bytes(ident[:4]) + if magic != b"\x7fELF": + raise ELFInvalid(f"invalid magic: {magic!r}") + + self.capacity = ident[4] # Format for program header (bitness). + self.encoding = ident[5] # Data structure encoding (endianness). + + 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, self._p_fmt, self._p_idx = { + (1, 1): ("HHIIIIIHHH", ">IIIIIIII", (0, 1, 4)), # 32-bit MSB. + (2, 1): ("HHIQQQIHHH", ">IIQQQQQQ", (0, 2, 5)), # 64-bit MSB. + }[(self.capacity, self.encoding)] + except KeyError: + raise ELFInvalid( + f"unrecognized capacity ({self.capacity}) or " + f"encoding ({self.encoding})" + ) + + try: + ( + _, + self.machine, # Architecture type. + _, + _, + self._e_phoff, # Offset of program header. + _, + self.flags, # Processor-specific flags. + _, + self._e_phentsize, # Size of section. + self._e_phnum, # Number of sections. + ) = self._read(e_fmt) + except struct.error as e: + raise ELFInvalid("unable to parse machine and section information") from e + + def _read(self, fmt): + return struct.unpack(fmt, self._f.read(struct.calcsize(fmt))) + + @property + def interpreter(self): + """ + The path recorded in the ``PT_INTERP`` section header. + """ + for index in range(self._e_phnum): + self._f.seek(self._e_phoff + self._e_phentsize * index) + try: + data = self._read(self._p_fmt) + except struct.error: + continue + if data[self._p_idx[0]] != 3: # Not PT_INTERP. + continue + self._f.seek(data[self._p_idx[1]]) + return os.fsdecode(self._f.read(data[self._p_idx[2]])).strip("\0") + return None def _parse_musl_version(output): lines = [n for n in (n.strip() for n in output.splitlines()) if n] @@ -1375,8 +1451,6 @@ def _get_musl_version(executable): Version 1.2.2 Dynamic Program Loader """ - - from _elffile import ELFFile import subprocess try: From 8a3f5153f19113b936cdfe0408080c9686e56fb4 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 25 Apr 2023 12:55:12 -0600 Subject: [PATCH 07/10] Add some tests for ELFFile class and musl functions --- Lib/test/test_platform.py | 48 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index 216973350319fe..a77c2c497c7f19 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -1,3 +1,4 @@ +import io import os import copy import pickle @@ -68,6 +69,9 @@ """ +ELFFILE_HEADER = b"\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + class PlatformTest(unittest.TestCase): def clear_caches(self): platform._platform_cache.clear() @@ -538,6 +542,50 @@ def test_parse_os_release(self): self.assertEqual(info, expected) self.assertEqual(len(info["SPECIALS"]), 5) + def test_parse_musl_version(self): + output = """\ +musl libc (x86_64) +Version 1.2.3 +Dynamic Program Loader +Usage: /lib/ld-musl-x86_64.so.1 [options] [--] pathname [args] + """ + self.assertEqual(platform._parse_musl_version(output), "1.2") + + @support.requires_subprocess() + # TODO: how should we write this test to only execute on systems with musl? + # @support.requires_musl() + def test_libc_ver_musl(self): + self.assertEqual(platform.libc_ver(), ("musl", "1.2")) + + +class ELFFileTest(unittest.TestCase): + + # FIXME: make this test correct and passing + # def test_get_interpreter(self): + # f = io.BytesIO(ELFFILE_HEADER) + # elffile = platform.ELFFile(f) + # self.assertEqual(elffile.interpreter, "") + + def test_init_invalid_magic(self): + BAD_ELFFILE_HEADER = b"\x7fBAD\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00" + f = io.BytesIO(BAD_ELFFILE_HEADER) + self.assertRaisesRegex( + platform.ELFInvalid, + "invalid magic:", + platform.ELFFile, + f, + ) + + def test_init_parse_error(self): + EMPTY_ELF_HEADER = b"\x00" + f = io.BytesIO(EMPTY_ELF_HEADER) + self.assertRaisesRegex( + platform.ELFInvalid, + "unable to parse identification", + platform.ELFFile, + f, + ) + if __name__ == '__main__': unittest.main() From 18420f99acafe287e6ae7c9ab97cc914883b4c31 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 25 Apr 2023 14:20:18 -0600 Subject: [PATCH 08/10] Add `@support.requires_musl` decorator to skip tests if needed Put the musl tests behind `requires_musl` decorator so they are not run in systems without its support. --- Lib/test/support/__init__.py | 13 +++++++++++++ Lib/test/test_platform.py | 13 ++++++------- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index d063837baee2de..30c6fb0952531e 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -387,6 +387,19 @@ def wrapper(*args, **kw): return wrapper return decorator +def requires_musl(): + """Decorator raising SkipTest if the musl is not available.""" + import subprocess + proc = subprocess.run(["ldd"], stderr=subprocess.PIPE, universal_newlines=True) + if "musl" in proc.stderr: + skip = False + else: + skip = True + + return unittest.skipIf( + skip, + f"musl is not available in this platform", + ) def skip_if_buildbot(reason=None): """Decorator raising SkipTest if running on a buildbot.""" diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index a77c2c497c7f19..a42c732ce7ac4f 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -552,19 +552,18 @@ def test_parse_musl_version(self): self.assertEqual(platform._parse_musl_version(output), "1.2") @support.requires_subprocess() - # TODO: how should we write this test to only execute on systems with musl? - # @support.requires_musl() + @support.requires_musl() def test_libc_ver_musl(self): self.assertEqual(platform.libc_ver(), ("musl", "1.2")) +@support.requires_musl() class ELFFileTest(unittest.TestCase): - # FIXME: make this test correct and passing - # def test_get_interpreter(self): - # f = io.BytesIO(ELFFILE_HEADER) - # elffile = platform.ELFFile(f) - # self.assertEqual(elffile.interpreter, "") + def test_get_interpreter(self): + with open(sys.executable, "rb") as f: + elffile = platform.ELFFile(f) + self.assertEqual(elffile.interpreter, "/lib/ld-musl-x86_64.so.1") def test_init_invalid_magic(self): BAD_ELFFILE_HEADER = b"\x7fBAD\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00" From 76550e74975bf50fa64cb3573c4e6f131562188a Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Fri, 28 Apr 2023 15:01:28 -0700 Subject: [PATCH 09/10] Add `NEWS.d` entry --- .../next/Library/2023-04-28-15-01-07.gh-issue-87414.r6fYP1.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2023-04-28-15-01-07.gh-issue-87414.r6fYP1.rst diff --git a/Misc/NEWS.d/next/Library/2023-04-28-15-01-07.gh-issue-87414.r6fYP1.rst b/Misc/NEWS.d/next/Library/2023-04-28-15-01-07.gh-issue-87414.r6fYP1.rst new file mode 100644 index 00000000000000..af6d7e2d43b3c3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-04-28-15-01-07.gh-issue-87414.r6fYP1.rst @@ -0,0 +1 @@ +Add musl support to ``platform.libc_ver`` function. From 421685867d8201d3aaf9d1c61eec245de5c19f5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cristi=C3=A1n=20Maureira-Fredes?= Date: Sun, 28 Jan 2024 18:56:06 +0100 Subject: [PATCH 10/10] Adapt latest comments from review - Making requires_musl compatible with macOS - Add permalinks on the packaging references - Add comment for the b'musl' check before b'libc' --- Lib/platform.py | 7 +++++-- Lib/test/support/__init__.py | 10 +++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Lib/platform.py b/Lib/platform.py index 9f70d2046f2030..9340bd20d09270 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -205,6 +205,9 @@ def libc_ver(executable=None, lib='', version='', chunksize=16384): binary = f.read(chunksize) pos = 0 while pos < len(binary): + # We first check 'musl' in the binary, because the next + # condition of looking for 'libc' in binary will be + # true for the case of musl. if b'musl' in binary: mv = _get_musl_version(executable) return "musl", mv @@ -1362,8 +1365,8 @@ def freedesktop_os_release(): ### musl libc version support # These functions were copied and adapted from the packaging module: -# https://github.com/pypa/packaging/blob/main/src/packaging/_musllinux.py -# https://github.com/pypa/packaging/blob/main/src/packaging/_elffile.py +# https://github.com/pypa/packaging/blob/4d8534061364e3cbfee582192ab81a095ec2db51/src/packaging/_musllinux.py +# https://github.com/pypa/packaging/blob/4d8534061364e3cbfee582192ab81a095ec2db51/src/packaging/_elffile.py class ELFInvalid(ValueError): pass diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 1f8e1252022715..ad1dd16bb14910 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -382,7 +382,15 @@ def wrapper(*args, **kw): def requires_musl(): """Decorator raising SkipTest if the musl is not available.""" import subprocess - proc = subprocess.run(["ldd"], stderr=subprocess.PIPE, universal_newlines=True) + if sys.platform == "darwin": + _cmd = "otool -L" + elif sys.platform == "linux": + _cmd = "ldd" + else: + return False + + proc = subprocess.run(_cmd.split(), stderr=subprocess.PIPE, universal_newlines=True) + if "musl" in proc.stderr: skip = False else: