99import struct
1010import sys
1111import sysconfig
12+ import typing
13+
14+
15+ if typing .TYPE_CHECKING : # pragma: no cover
16+ from typing import TypedDict
17+
18+ class _Abi (TypedDict ):
19+ extension_suffix : str
20+
21+ class _ImplementationVersion (TypedDict ):
22+ major : int
23+ minor : int
24+
25+ class _Implementation (TypedDict ):
26+ name : str
27+ version : _ImplementationVersion
28+
29+ class BuildDetails (TypedDict ):
30+ abi : _Abi
31+ implementation : _Implementation
32+ platform : str
1233
1334
1435# https://peps.python.org/pep-0425/#python-tag
2445_32_BIT_INTERPRETER = struct .calcsize ('P' ) == 4
2546
2647
27- def get_interpreter_tag () -> str :
28- name = sys .implementation .name
48+ def _get_macosx_platform () -> str :
49+ ver , _ , arch = platform .mac_ver ()
50+ major , minor = map (int , ver .split ('.' )[:2 ])
51+
52+ # Python built with older macOS SDK on macOS 11, reports an
53+ # nonexistent macOS 10.16 version instead of the real version.
54+ #
55+ # The packaging module introduced a workaround
56+ # https://github.com/pypa/packaging/commit/67c4a2820c549070bbfc4bfbf5e2a250075048da
57+ #
58+ # This results in packaging versions up to 21.3 generating
59+ # platform tags like "macosx_10_16_x86_64" and later versions
60+ # generating "macosx_11_0_x86_64". Using the latter would be more
61+ # correct but prevents the resulting wheel from being installed on
62+ # systems using packaging 21.3 or earlier (pip 22.3 or earlier).
63+ #
64+ # Fortunately packaging versions carrying the workaround still
65+ # accepts "macosx_10_16_x86_64" as a compatible platform tag. We
66+ # can therefore ignore the issue and generate the slightly
67+ # incorrect tag.
68+
69+ if _32_BIT_INTERPRETER :
70+ # 32-bit Python running on a 64-bit kernel.
71+ if arch == 'ppc64' :
72+ arch = 'ppc'
73+ if arch == 'x86_64' :
74+ arch = 'i386'
75+
76+ return f'macosx-{ major } .{ minor } -{ arch } '
77+
78+
79+ def _get_ios_platform () -> str :
80+ ver = platform .ios_ver ().release
81+ major , minor = map (int , ver .split ('.' )[:2 ])
82+
83+ # Although _multiarch is an internal implementation detail, it's a core part
84+ # of how CPython is implemented on iOS; this attribute is also relied upon
85+ # by `packaging` as part of tag determination.
86+ multiarch = sys .implementation ._multiarch .replace ('-' , '_' )
87+
88+ return f'ios-{ major } .{ minor } -{ multiarch } '
89+
90+
91+ def introspect_build_details () -> BuildDetails :
92+ platform = sysconfig .get_platform ()
93+ if platform .startswith ('macosx' ):
94+ platform = _get_macosx_platform ()
95+ elif platform .startswith ('ios' ):
96+ platform = _get_ios_platform ()
97+ elif _32_BIT_INTERPRETER :
98+ # 32-bit Python running on a 64-bit kernel.
99+ if platform == 'linux-x86_64' :
100+ platform = 'linux_i686'
101+ if platform == 'linux-aarch64' :
102+ platform = 'linux_armv7l'
103+
104+ return {
105+ 'abi' : {
106+ # PyPy reports a $SOABI that does not agree with $EXT_SUFFIX.
107+ # Using $EXT_SUFFIX will not break when PyPy will fix this.
108+ # See https://foss.heptapod.net/pypy/pypy/-/issues/3816 and
109+ # https://github.com/pypa/packaging/pull/607.
110+ 'extension_suffix' : str (sysconfig .get_config_var ('EXT_SUFFIX' )),
111+ },
112+ 'implementation' : {
113+ 'name' : sys .implementation .name ,
114+ 'version' : {
115+ 'major' : sys .version_info .major ,
116+ 'minor' : sys .version_info .minor ,
117+ },
118+ },
119+ 'platform' : platform ,
120+ }
121+
122+
123+ def get_interpreter_tag (build_details : BuildDetails ) -> str :
124+ name = build_details ['implementation' ]['name' ]
125+ _v = build_details ['implementation' ]['version' ]
126+ major = _v ['major' ]
127+ minor = _v ['minor' ]
29128 name = INTERPRETERS .get (name , name )
30- version = sys .version_info
31- return f'{ name } { version [0 ]} { version [1 ]} '
129+ return f'{ name } { major } { minor } '
32130
33131
34- def get_abi_tag () -> str :
132+ def get_abi_tag (build_details : BuildDetails ) -> str :
35133 # The best solution to obtain the Python ABI is to parse the
36134 # $SOABI or $EXT_SUFFIX sysconfig variables as defined in PEP-314.
37135
38136 # PyPy reports a $SOABI that does not agree with $EXT_SUFFIX.
39137 # Using $EXT_SUFFIX will not break when PyPy will fix this.
40138 # See https://foss.heptapod.net/pypy/pypy/-/issues/3816 and
41139 # https://github.com/pypa/packaging/pull/607.
42- empty , abi , ext = str (sysconfig .get_config_var ('EXT_SUFFIX' )).split ('.' )
140+ ext_suffix = build_details ['abi' ]['extension_suffix' ]
141+ empty , abi , ext = ext_suffix .split ('.' )
43142
44143 # The packaging module initially based his understanding of the
45144 # $SOABI variable on the inconsistent value reported by PyPy, and
@@ -60,8 +159,9 @@ def get_abi_tag() -> str:
60159 return abi .replace ('.' , '_' ).replace ('-' , '_' )
61160
62161
63- def _get_macosx_platform_tag () -> str :
64- ver , _ , arch = platform .mac_ver ()
162+ def _get_macosx_platform_tag (platform : str ) -> str :
163+ name , ver , arch = platform .split ('-' , 2 )
164+ assert name == 'macosx'
65165
66166 # Override the architecture with the one provided in the
67167 # _PYTHON_HOST_PLATFORM environment variable. This environment
@@ -81,24 +181,7 @@ def _get_macosx_platform_tag() -> str:
81181 parts = os .environ .get ('MACOSX_DEPLOYMENT_TARGET' , '' ).split ('.' )[:2 ]
82182 version = tuple (map (int , parts + ['0' ] * (2 - len (parts ))))
83183 except ValueError :
84- version = tuple (map (int , ver .split ('.' )))[:2 ]
85-
86- # Python built with older macOS SDK on macOS 11, reports an
87- # nonexistent macOS 10.16 version instead of the real version.
88- #
89- # The packaging module introduced a workaround
90- # https://github.com/pypa/packaging/commit/67c4a2820c549070bbfc4bfbf5e2a250075048da
91- #
92- # This results in packaging versions up to 21.3 generating
93- # platform tags like "macosx_10_16_x86_64" and later versions
94- # generating "macosx_11_0_x86_64". Using the latter would be more
95- # correct but prevents the resulting wheel from being installed on
96- # systems using packaging 21.3 or earlier (pip 22.3 or earlier).
97- #
98- # Fortunately packaging versions carrying the workaround still
99- # accepts "macosx_10_16_x86_64" as a compatible platform tag. We
100- # can therefore ignore the issue and generate the slightly
101- # incorrect tag.
184+ version = tuple (map (int , ver .split ('.' )[:2 ]))
102185
103186 # The minimum macOS ABI version on arm64 is 11.0. The macOS SDK
104187 # on arm64 silently bumps any compatibility version specified via
@@ -120,53 +203,39 @@ def _get_macosx_platform_tag() -> str:
120203 # the patch level. Reset the patch level to zero.
121204 minor = 0
122205
123- if _32_BIT_INTERPRETER :
124- # 32-bit Python running on a 64-bit kernel.
125- if arch == 'ppc64' :
126- arch = 'ppc'
127- if arch == 'x86_64' :
128- arch = 'i386'
129-
130206 return f'macosx_{ major } _{ minor } _{ arch } '
131207
132208
133- def _get_ios_platform_tag () -> str :
209+ def _get_ios_platform_tag (platform : str ) -> str :
210+ name , version , multiarch = platform .split ('-' , 2 )
211+ assert name == 'ios'
212+
134213 # Override the iOS version if one is provided via the
135214 # IPHONEOS_DEPLOYMENT_TARGET environment variable.
136215 try :
137216 parts = os .environ .get ('IPHONEOS_DEPLOYMENT_TARGET' , '' ).split ('.' )[:2 ]
138- version = tuple ( map ( int , parts + ['0' ] * (2 - len (parts ) )))
217+ version = '.' . join ( parts + ['0' ] * (2 - len (parts )))
139218 except ValueError :
140- version = tuple (map (int , platform .ios_ver ().release .split ('.' )))[:2 ] # type: ignore[attr-defined]
141-
142- # Although _multiarch is an internal implementation detail, it's a core part
143- # of how CPython is implemented on iOS; this attribute is also relied upon
144- # by `packaging` as part of tag determination.
145- multiarch = sys .implementation ._multiarch .replace ('-' , '_' )
219+ pass
146220
147- return f'ios_{ version [ 0 ] } _ { version [ 1 ] } _{ multiarch } '
221+ return f'ios_{ version . replace ( "." , "_" ) } _{ multiarch . replace ( "-" , "_" ) } '
148222
149223
150- def get_platform_tag () -> str :
151- platform = sysconfig . get_platform ()
224+ def get_platform_tag (build_details : BuildDetails ) -> str :
225+ platform = build_details [ 'platform' ]
152226 if platform .startswith ('macosx' ):
153- return _get_macosx_platform_tag ()
227+ return _get_macosx_platform_tag (platform )
154228 if platform .startswith ('ios' ):
155- return _get_ios_platform_tag ()
156- if _32_BIT_INTERPRETER :
157- # 32-bit Python running on a 64-bit kernel.
158- if platform == 'linux-x86_64' :
159- return 'linux_i686'
160- if platform == 'linux-aarch64' :
161- return 'linux_armv7l'
229+ return _get_ios_platform_tag (platform )
162230 return platform .replace ('-' , '_' ).replace ('.' , '_' ).lower ()
163231
164232
165233class Tag :
166- def __init__ (self , interpreter : str | None = None , abi : str | None = None , platform : str | None = None ):
167- self .interpreter = interpreter or get_interpreter_tag ()
168- self .abi = abi or get_abi_tag ()
169- self .platform = platform or get_platform_tag ()
234+ def __init__ (self , interpreter : str | None = None , abi : str | None = None , platform : str | None = None ,
235+ * , build_details : BuildDetails ):
236+ self .interpreter = interpreter or get_interpreter_tag (build_details )
237+ self .abi = abi or get_abi_tag (build_details )
238+ self .platform = platform or get_platform_tag (build_details )
170239
171240 def __str__ (self ) -> str :
172241 return f'{ self .interpreter } -{ self .abi } -{ self .platform } '
0 commit comments