1
1
#!/usr/bin/env python3
2
2
3
+ import copy
3
4
import logging
4
- from itertools import groupby
5
5
from pathlib import Path
6
- from typing import Dict , List , Optional
6
+ from typing import Dict , Optional , Union
7
7
8
8
import click
9
9
import requests
10
10
import rich
11
- import tomlkit
12
- from packaging .specifiers import SpecifierSet
11
+ import toml
12
+ from packaging .specifiers import Specifier , SpecifierSet
13
13
from packaging .version import Version
14
14
from rich .logging import RichHandler
15
15
from rich .syntax import Syntax
16
16
17
17
from cibuildwheel .extra import InlineArrayDictEncoder
18
- from cibuildwheel .typing import Final , Literal , PlatformName , TypedDict
18
+ from cibuildwheel .typing import Final , Literal , TypedDict
19
19
20
20
log = logging .getLogger ("cibw" )
21
21
32
32
33
33
class ConfigWinCP (TypedDict ):
34
34
identifier : str
35
- version : Version
35
+ version : str
36
36
arch : str
37
37
38
38
39
39
class ConfigWinPP (TypedDict ):
40
40
identifier : str
41
- version : Version
41
+ version : str
42
42
arch : str
43
43
url : str
44
44
45
45
46
46
class ConfigMacOS (TypedDict ):
47
47
identifier : str
48
- version : Version
48
+ version : str
49
49
url : str
50
50
51
51
@@ -66,7 +66,8 @@ def __init__(self, arch_str: ArchStr) -> None:
66
66
ARCH_DICT = {"32" : "win32" , "64" : "win_amd64" }
67
67
PACKAGE_DICT = {"32" : "pythonx86" , "64" : "python" }
68
68
69
- arch = ARCH_DICT [arch_str ]
69
+ self .arch_str = arch_str
70
+ self .arch = ARCH_DICT [arch_str ]
70
71
package = PACKAGE_DICT [arch_str ]
71
72
72
73
response = requests .get (f"{ endpoint } { package } /index.json" )
@@ -76,21 +77,21 @@ def __init__(self, arch_str: ArchStr) -> None:
76
77
versions = (Version (v ) for v in cp_info ["versions" ])
77
78
self .versions = sorted (v for v in versions if not v .is_devrelease )
78
79
79
- def update_version (self , spec : Specifier ) -> Optional [ConfigWinCP ]:
80
+ def update_version_windows (self , spec : Specifier ) -> Optional [ConfigWinCP ]:
80
81
versions = sorted (v for v in self .versions if spec .contains (v ))
81
82
if not all (v .is_prerelease for v in versions ):
82
83
versions = [v for v in versions if not v .is_prerelease ]
83
- log .debug (versions )
84
+ log .debug (f"Windows { self . arch } { spec } has { ', ' . join ( str ( v ) for v in versions ) } " )
84
85
85
86
if not versions :
86
87
return None
87
88
88
89
version = versions [- 1 ]
89
- identifier = f"cp{ version .major } { version .minor } -{ ARCH_DICT [ arch ] } "
90
+ identifier = f"cp{ version .major } { version .minor } -{ self . arch } "
90
91
result = ConfigWinCP (
91
92
identifier = identifier ,
92
- version = version ,
93
- arch = arch ,
93
+ version = str ( version ) ,
94
+ arch = self . arch_str ,
94
95
)
95
96
return result
96
97
@@ -106,47 +107,56 @@ def __init__(self, arch_str: ArchStr):
106
107
release ["pypy_version" ] = Version (release ["pypy_version" ])
107
108
release ["python_version" ] = Version (release ["python_version" ])
108
109
109
- self .releases = [r for r in releases if not r ["pypy_version" ].is_prerelease and r ["pypy_version" ].is_devrelease ]
110
- self .arch == arch_str
110
+ self .releases = [
111
+ r for r in releases if not r ["pypy_version" ].is_prerelease and not r ["pypy_version" ].is_devrelease
112
+ ]
113
+ self .arch = arch_str
111
114
112
- def update_version_windows (self , spec : Specifier ) -> Optional [ ConfigWinCP ] :
115
+ def update_version_windows (self , spec : Specifier ) -> ConfigWinCP :
113
116
if self .arch != "32" :
114
117
raise RuntimeError ("64 bit releases not supported yet on Windows" )
115
118
116
- releases = [r for r in releases if spec .contains (r ["python_verison " ])]
119
+ releases = [r for r in self . releases if spec .contains (r ["python_version " ])]
117
120
releases = sorted (releases , key = lambda r : r ["pypy_version" ])
118
121
119
122
if not releases :
120
- return None
123
+ raise RuntimeError ( f"PyPy Win { self . arch } not found for { spec } ! { self . releases } " )
121
124
122
125
release = releases [- 1 ]
123
126
version = release ["python_version" ]
124
127
identifier = f"pp{ version .major } { version .minor } -win32"
128
+
129
+ (url ,) = [rf ["download_url" ] for rf in release ["files" ] if "" in rf ["platform" ] == "win32" ]
130
+
125
131
return ConfigWinPP (
126
132
identifier = identifier ,
127
- version = Version ( f"{ version .major } .{ version .minor } " ) ,
133
+ version = f"{ version .major } .{ version .minor } " ,
128
134
arch = "32" ,
129
- url = r [ "download_url" ] ,
135
+ url = url ,
130
136
)
131
137
132
- def update_version_macos (self , spec : Specifier ) -> Optional [ ConfigMacOS ] :
138
+ def update_version_macos (self , spec : Specifier ) -> ConfigMacOS :
133
139
if self .arch != "64" :
134
140
raise RuntimeError ("Other archs not supported yet on macOS" )
135
141
136
- releases = [r for r in releases if spec .contains (r ["python_verison " ])]
142
+ releases = [r for r in self . releases if spec .contains (r ["python_version " ])]
137
143
releases = sorted (releases , key = lambda r : r ["pypy_version" ])
138
144
139
145
if not releases :
140
- return None
146
+ raise RuntimeError ( f"PyPy macOS { self . arch } not found for { spec } !" )
141
147
142
148
release = releases [- 1 ]
143
149
version = release ["python_version" ]
144
- identifier = f"pp{ version .major } { version .minor } -win32"
150
+ identifier = f"pp{ version .major } { version .minor } -macosx_x86_64"
151
+
152
+ (url ,) = [
153
+ rf ["download_url" ] for rf in release ["files" ] if "" in rf ["platform" ] == "darwin" and rf ["arch" ] == "x64"
154
+ ]
145
155
146
156
return ConfigMacOS (
147
157
identifier = identifier ,
148
- version = Version ( f"{ version .major } .{ version .minor } " ) ,
149
- url = rf [ "download_url" ] ,
158
+ version = f"{ version .major } .{ version .minor } " ,
159
+ url = url ,
150
160
)
151
161
152
162
@@ -162,36 +172,89 @@ def __init__(self, plat_arch: str, file_ident: str) -> None:
162
172
163
173
releases_info = response .json ()
164
174
165
- # Removing the prefix, Python 3.9 would use: release["name"].removeprefix("Python ")
166
- known_versions = {Version (release ["name" ][7 :]): _get_id (release ["resource_uri" ]) for release in releases_info }
167
- self .versions = sorted (v for v in known_versions if not (v .is_prerelease or v .is_devrelease ))
175
+ self .versions_dict : Dict [Version , int ] = {}
176
+ for release in releases_info :
177
+ # Removing the prefix, Python 3.9 would use: release["name"].removeprefix("Python ")
178
+ version = Version (release ["name" ][7 :])
179
+
180
+ if not version .is_prerelease and not version .is_devrelease :
181
+ uri = int (release ["resource_uri" ].rstrip ("/" ).split ("/" )[- 1 ])
182
+ self .versions_dict [version ] = uri
168
183
169
- def update_python_macos (self , spec : Specifier ) -> Optional [ConfigMacOS ]:
184
+ self .file_ident = file_ident
185
+ self .plat_arch = plat_arch
186
+
187
+ def update_version_macos (self , spec : Specifier ) -> Optional [ConfigMacOS ]:
170
188
171
- sorted_versions = [ v for v in self .versions if spec .contains (v )]
189
+ sorted_versions = sorted ( v for v in self .versions_dict if spec .contains (v ))
172
190
173
- for version in reversed (versions ):
191
+ for version in reversed (sorted_versions ):
174
192
# Find the first patch version that contains the requested file
175
- uri = self .versions [version ]
193
+ uri = self .versions_dict [version ]
176
194
response = requests .get (f"https://www.python.org/api/v2/downloads/release_file/?release={ uri } " )
177
195
response .raise_for_status ()
178
196
file_info = response .json ()
179
197
180
- canidate_files = [rf ["url" ] for rf in file_info if file_ident in rf ["url" ]]
181
- if canidate_files :
198
+ urls = [rf ["url" ] for rf in file_info if self . file_ident in rf ["url" ]]
199
+ if urls :
182
200
return ConfigMacOS (
183
- identifier = f"cp{ version .major } { version .minor } -{ plat_arch } " ,
184
- version = version ,
185
- url = canidate_files [0 ],
201
+ identifier = f"cp{ version .major } { version .minor } -{ self . plat_arch } " ,
202
+ version = f" { version . major } . { version . minor } " ,
203
+ url = urls [0 ],
186
204
)
187
205
188
206
return None
189
207
190
208
209
+ class AllVersions :
210
+ def __init__ (self ) -> None :
211
+ self .windows_32 = WindowsVersions ("32" )
212
+ self .windows_64 = WindowsVersions ("64" )
213
+ self .windows_pypy = PyPyVersions ("32" )
214
+
215
+ self .macos_6 = CPythonVersions (plat_arch = "macosx_x86_64" , file_ident = "macosx10.6.pkg" )
216
+ self .macos_9 = CPythonVersions (plat_arch = "macosx_x86_64" , file_ident = "macosx10.9.pkg" )
217
+ self .macos_u2 = CPythonVersions (
218
+ plat_arch = "macosx_universal2" ,
219
+ file_ident = "macos11.0.pkg" ,
220
+ )
221
+ self .macos_pypy = PyPyVersions ("64" )
222
+
223
+ def update_config (self , config : Dict [str , str ]) -> None :
224
+ identifier = config ["identifier" ]
225
+ version = Version (config ["version" ])
226
+ spec = Specifier (f"=={ version .major } .{ version .minor } .*" )
227
+ log .info (f"Reading in '{ identifier } ' -> { spec } @ { version } " )
228
+ orig_config = copy .copy (config )
229
+ config_update : Optional [AnyConfig ]
230
+
231
+ if "macosx_x86_64" in identifier :
232
+ if identifier .startswith ("pp" ):
233
+ config_update = self .macos_pypy .update_version_macos (spec )
234
+ else :
235
+ config_update = self .macos_9 .update_version_macos (spec ) or self .macos_6 .update_version_macos (spec )
236
+ assert config_update is not None , f"MacOS { spec } not found!"
237
+ config .update (** config_update )
238
+ elif "win32" in identifier :
239
+ if identifier .startswith ("pp" ):
240
+ config .update (** self .windows_pypy .update_version_windows (spec ))
241
+ else :
242
+ config_update = self .windows_32 .update_version_windows (spec )
243
+ if config_update :
244
+ config .update (** config_update )
245
+ elif "win_amd64" in identifier :
246
+ config_update = self .windows_64 .update_version_windows (spec )
247
+ if config_update :
248
+ config .update (** config_update )
249
+
250
+ if config != orig_config :
251
+ log .info (f" Updated { orig_config } to { config } " )
252
+
253
+
191
254
@click .command ()
192
255
@click .option ("--inplace" , is_flag = True )
193
256
@click .option ("--level" , default = "INFO" , type = click .Choice (["INFO" , "DEBUG" , "TRACE" ], case_sensitive = False ))
194
- def update_pythons (inplace : bool , prereleases : bool , level : str ) -> None :
257
+ def update_pythons (inplace : bool , level : str ) -> None :
195
258
196
259
logging .basicConfig (
197
260
level = "INFO" ,
@@ -201,34 +264,14 @@ def update_pythons(inplace: bool, prereleases: bool, level: str) -> None:
201
264
)
202
265
log .setLevel (level )
203
266
204
- windows_32 = WindowsVersions ("32" )
205
- windows_64 = WindowsVersions ("64" )
206
- windows_pypy = PyPyVersions ("32" )
207
-
208
- macos_6 = CPythonVersions (plat_arch = "macosx_x86_64" , file_ident = "macosx10.6.pkg" )
209
-
210
- macos_9 = CPythonVersions (plat_arch = "macosx_x86_64" , file_ident = "macosx10.9.pkg" )
211
-
212
- macos_u2 = CPythonVersions (
213
- plat_arch = "macosx_universal2" ,
214
- file_ident = "macos11.0.pkg" ,
215
- )
216
-
217
- macos_pypy = PyPyVersions ("64" )
218
-
267
+ all_versions = AllVersions ()
219
268
configs = toml .load (RESOURCES_DIR / "build-platforms.toml" )
220
269
221
270
for config in configs ["windows" ]["python_configurations" ]:
222
- version = Version (config ["version" ])
223
- spec = Specifier (f"=={ version .major } .{ version .minor } .*" )
224
- arch = config ["arch" ]
225
- cpython = config ["identifier" ].startswith ("cp" )
271
+ all_versions .update_config (config )
226
272
227
273
for config in configs ["macos" ]["python_configurations" ]:
228
- version = Version (config ["version" ])
229
- spec = Specifier (f"=={ version .major } .{ version .minor } .*" )
230
- arch = "64"
231
- pypy = config ["identifier" ].startswith ("pp" )
274
+ all_versions .update_config (config )
232
275
233
276
if inplace :
234
277
with open (RESOURCES_DIR / "build-platforms.toml" , "w" ) as f :
0 commit comments