11import collections
2+ import contextlib
23import functools
34import os
45import re
5- import struct
66import sys
77import warnings
8- from typing import IO , Dict , Iterator , NamedTuple , Optional , Tuple
9-
10-
11- # Python does not provide platform information at sufficient granularity to
12- # identify the architecture of the running executable in some cases, so we
13- # determine it dynamically by reading the information from the running
14- # process. This only applies on Linux, which uses the ELF format.
15- class _ELFFileHeader :
16- # https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header
17- class _InvalidELFFileHeader (ValueError ):
18- """
19- An invalid ELF file header was found.
20- """
21-
22- ELF_MAGIC_NUMBER = 0x7F454C46
23- ELFCLASS32 = 1
24- ELFCLASS64 = 2
25- ELFDATA2LSB = 1
26- ELFDATA2MSB = 2
27- EM_386 = 3
28- EM_S390 = 22
29- EM_ARM = 40
30- EM_X86_64 = 62
31- EF_ARM_ABIMASK = 0xFF000000
32- EF_ARM_ABI_VER5 = 0x05000000
33- EF_ARM_ABI_FLOAT_HARD = 0x00000400
34-
35- def __init__ (self , file : IO [bytes ]) -> None :
36- def unpack (fmt : str ) -> int :
37- try :
38- data = file .read (struct .calcsize (fmt ))
39- result : Tuple [int , ...] = struct .unpack (fmt , data )
40- except struct .error :
41- raise _ELFFileHeader ._InvalidELFFileHeader ()
42- return result [0 ]
43-
44- self .e_ident_magic = unpack (">I" )
45- if self .e_ident_magic != self .ELF_MAGIC_NUMBER :
46- raise _ELFFileHeader ._InvalidELFFileHeader ()
47- self .e_ident_class = unpack ("B" )
48- if self .e_ident_class not in {self .ELFCLASS32 , self .ELFCLASS64 }:
49- raise _ELFFileHeader ._InvalidELFFileHeader ()
50- self .e_ident_data = unpack ("B" )
51- if self .e_ident_data not in {self .ELFDATA2LSB , self .ELFDATA2MSB }:
52- raise _ELFFileHeader ._InvalidELFFileHeader ()
53- self .e_ident_version = unpack ("B" )
54- self .e_ident_osabi = unpack ("B" )
55- self .e_ident_abiversion = unpack ("B" )
56- self .e_ident_pad = file .read (7 )
57- format_h = "<H" if self .e_ident_data == self .ELFDATA2LSB else ">H"
58- format_i = "<I" if self .e_ident_data == self .ELFDATA2LSB else ">I"
59- format_q = "<Q" if self .e_ident_data == self .ELFDATA2LSB else ">Q"
60- format_p = format_i if self .e_ident_class == self .ELFCLASS32 else format_q
61- self .e_type = unpack (format_h )
62- self .e_machine = unpack (format_h )
63- self .e_version = unpack (format_i )
64- self .e_entry = unpack (format_p )
65- self .e_phoff = unpack (format_p )
66- self .e_shoff = unpack (format_p )
67- self .e_flags = unpack (format_i )
68- self .e_ehsize = unpack (format_h )
69- self .e_phentsize = unpack (format_h )
70- self .e_phnum = unpack (format_h )
71- self .e_shentsize = unpack (format_h )
72- self .e_shnum = unpack (format_h )
73- self .e_shstrndx = unpack (format_h )
74-
75-
76- def _get_elf_header () -> Optional [_ELFFileHeader ]:
8+ from typing import Dict , Generator , Iterator , NamedTuple , Optional , Tuple
9+
10+ from ._elffile import EIClass , EIData , ELFFile , EMachine
11+
12+ EF_ARM_ABIMASK = 0xFF000000
13+ EF_ARM_ABI_VER5 = 0x05000000
14+ EF_ARM_ABI_FLOAT_HARD = 0x00000400
15+
16+
17+ @contextlib .contextmanager
18+ def _parse_elf (path : str ) -> Generator [Optional [ELFFile ], None , None ]:
7719 try :
78- with open (sys .executable , "rb" ) as f :
79- elf_header = _ELFFileHeader (f )
80- except (OSError , TypeError , _ELFFileHeader ._InvalidELFFileHeader ):
81- return None
82- return elf_header
20+ with open (path , "rb" ) as f :
21+ yield ELFFile (f )
22+ except (OSError , TypeError , ValueError ):
23+ yield None
8324
8425
85- def _is_linux_armhf () -> bool :
26+ def _is_linux_armhf (executable : str ) -> bool :
8627 # hard-float ABI can be detected from the ELF header of the running
8728 # process
8829 # https://static.docs.arm.com/ihi0044/g/aaelf32.pdf
89- elf_header = _get_elf_header ()
90- if elf_header is None :
91- return False
92- result = elf_header .e_ident_class == elf_header .ELFCLASS32
93- result &= elf_header .e_ident_data == elf_header .ELFDATA2LSB
94- result &= elf_header .e_machine == elf_header .EM_ARM
95- result &= (
96- elf_header .e_flags & elf_header .EF_ARM_ABIMASK
97- ) == elf_header .EF_ARM_ABI_VER5
98- result &= (
99- elf_header .e_flags & elf_header .EF_ARM_ABI_FLOAT_HARD
100- ) == elf_header .EF_ARM_ABI_FLOAT_HARD
101- return result
102-
103-
104- def _is_linux_i686 () -> bool :
105- elf_header = _get_elf_header ()
106- if elf_header is None :
107- return False
108- result = elf_header .e_ident_class == elf_header .ELFCLASS32
109- result &= elf_header .e_ident_data == elf_header .ELFDATA2LSB
110- result &= elf_header .e_machine == elf_header .EM_386
111- return result
30+ with _parse_elf (executable ) as f :
31+ return (
32+ f is not None
33+ and f .capacity == EIClass .C32
34+ and f .encoding == EIData .Lsb
35+ and f .machine == EMachine .Arm
36+ and f .flags & EF_ARM_ABIMASK == EF_ARM_ABI_VER5
37+ and f .flags & EF_ARM_ABI_FLOAT_HARD == EF_ARM_ABI_FLOAT_HARD
38+ )
39+
40+
41+ def _is_linux_i686 (executable : str ) -> bool :
42+ with _parse_elf (executable ) as f :
43+ return (
44+ f is not None
45+ and f .capacity == EIClass .C32
46+ and f .encoding == EIData .Lsb
47+ and f .machine == EMachine .I386
48+ )
11249
11350
114- def _have_compatible_abi (arch : str ) -> bool :
51+ def _have_compatible_abi (executable : str , arch : str ) -> bool :
11552 if arch == "armv7l" :
116- return _is_linux_armhf ()
53+ return _is_linux_armhf (executable )
11754 if arch == "i686" :
118- return _is_linux_i686 ()
55+ return _is_linux_i686 (executable )
11956 return arch in {"x86_64" , "aarch64" , "ppc64" , "ppc64le" , "s390x" }
12057
12158
@@ -141,10 +78,10 @@ def _glibc_version_string_confstr() -> Optional[str]:
14178 # platform module.
14279 # https://github.com/python/cpython/blob/fcf1d003bf4f0100c/Lib/platform.py#L175-L183
14380 try :
144- # os.confstr("CS_GNU_LIBC_VERSION") returns a string like "glibc 2.17".
145- version_string = os . confstr ("CS_GNU_LIBC_VERSION" )
81+ # Should be a string like "glibc 2.17".
82+ version_string : str = getattr ( os , " confstr" ) ("CS_GNU_LIBC_VERSION" )
14683 assert version_string is not None
147- _ , version = version_string .split ()
84+ _ , version = version_string .rsplit ()
14885 except (AssertionError , AttributeError , OSError , ValueError ):
14986 # os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)...
15087 return None
@@ -211,8 +148,8 @@ def _parse_glibc_version(version_str: str) -> Tuple[int, int]:
211148 m = re .match (r"(?P<major>[0-9]+)\.(?P<minor>[0-9]+)" , version_str )
212149 if not m :
213150 warnings .warn (
214- "Expected glibc version with 2 components major.minor,"
215- " got: %s" % version_str ,
151+ f "Expected glibc version with 2 components major.minor,"
152+ f " got: { version_str } " ,
216153 RuntimeWarning ,
217154 )
218155 return - 1 , - 1
@@ -265,7 +202,7 @@ def _is_compatible(name: str, arch: str, version: _GLibCVersion) -> bool:
265202
266203
267204def platform_tags (linux : str , arch : str ) -> Iterator [str ]:
268- if not _have_compatible_abi (arch ):
205+ if not _have_compatible_abi (sys . executable , arch ):
269206 return
270207 # Oldest glibc to be supported regardless of architecture is (2, 17).
271208 too_old_glibc2 = _GLibCVersion (2 , 16 )
0 commit comments