1
1
#!/usr/bin/env python3
2
2
3
- import sys
4
- from collections import defaultdict
5
3
from itertools import groupby
6
4
from pathlib import Path
7
- from typing import Dict , List , Tuple
5
+ from typing import List
8
6
7
+ import click
9
8
import requests
10
9
import toml
11
10
from packaging .version import Version
12
11
13
- if sys .version_info < (3 , 8 ):
14
- from typing_extensions import TypedDict
15
- else :
16
- from typing import TypedDict
17
-
18
- # Use pretty printing for debugging
19
- # from rich import print
20
-
21
-
22
- allow_prerelease = False
23
-
24
-
25
- class InlineArrayDictEncoder (toml .encoder .TomlEncoder ):
26
- def dump_sections (self , o : dict , sup : str ):
27
- if all (isinstance (a , list ) for a in o .values ()):
28
- val = ""
29
- for k , v in o .items ():
30
- inner = ",\n " .join (self .dump_inline_table (d_i ).strip () for d_i in v )
31
- val += f"{ k } = [\n { inner } ,\n ]\n "
32
- return val , self ._dict ()
33
- else :
34
- return super ().dump_sections (o , sup )
35
-
12
+ from cibuildwheel .extra import InlineArrayDictEncoder
13
+ from cibuildwheel .typing import PlatformName , TypedDict
36
14
15
+ # Looking up the dir instead of using utils.resources_dir
16
+ # since we want to write to it.
37
17
DIR = Path (__file__ ).parent .parent .resolve ()
38
18
RESOURCES_DIR = DIR / "cibuildwheel/resources"
39
19
40
20
41
- class ConfigWinCP (TypedDict ):
21
+ class AnyConfig (TypedDict ):
42
22
identifier : str
43
- version : str
23
+ version : Version
24
+
25
+
26
+ class ConfigWinCP (AnyConfig ):
44
27
arch : str
45
28
46
29
47
- class ConfigWinPP (TypedDict ):
48
- identifier : str
49
- version : str
30
+ class ConfigWinPP (AnyConfig ):
50
31
arch : str
51
32
url : str
52
33
53
34
54
- def get_cpython_windows () -> Dict [str , List [Version ]]:
35
+ def get_cpython_windows () -> List [ConfigWinCP ]:
36
+ ARCH_DICT = {"32" : "win32" , "64" : "win_amd64" }
37
+
55
38
response = requests .get ("https://api.nuget.org/v3/index.json" )
56
39
response .raise_for_status ()
57
40
api_info = response .json ()
@@ -60,120 +43,140 @@ def get_cpython_windows() -> Dict[str, List[Version]]:
60
43
if resource ["@type" ] == "PackageBaseAddress/3.0.0" :
61
44
endpoint = resource ["@id" ]
62
45
63
- cp_versions : Dict [str , List [Version ]] = {"64" : [], "32" : []}
64
- for id , package in [("64" , "python" ), ("32" , "pythonx86" )]:
46
+ items : List [ConfigWinCP ] = []
47
+
48
+ for arch , package in [("64" , "python" ), ("32" , "pythonx86" )]:
65
49
response = requests .get (f"{ endpoint } { package } /index.json" )
66
50
response .raise_for_status ()
67
51
cp_info = response .json ()
68
52
69
53
for version_str in cp_info ["versions" ]:
70
54
version = Version (version_str )
55
+
71
56
if version .is_devrelease :
72
57
continue
73
- if not allow_prerelease and version .is_prerelease :
74
- continue
75
- cp_versions [id ].append (version )
76
- cp_versions [id ].sort ()
77
58
78
- return cp_versions
59
+ identifier = f"cp{ version .major } { version .minor } -{ ARCH_DICT [arch ]} "
60
+
61
+ items .append (
62
+ ConfigWinCP (
63
+ identifier = identifier ,
64
+ version = version ,
65
+ arch = arch ,
66
+ )
67
+ )
68
+ return items
79
69
80
70
81
- def get_pypy_windows (
82
- plat_arch : str = "win32-x86" ,
83
- ) -> Dict [str , List [Tuple [Version , str ]]]:
71
+ def get_pypy (platform : PlatformName ) -> List [AnyConfig ]:
84
72
85
73
response = requests .get ("https://downloads.python.org/pypy/versions.json" )
86
74
response .raise_for_status ()
87
- pp_realeases = response .json ()
88
- pp_versions = defaultdict (list )
75
+ pp_releases = response .json ()
89
76
90
- for pp_realease in pp_realeases :
91
- if pp_realease ["pypy_version" ] == "nightly" :
92
- continue
93
- version = Version (pp_realease ["pypy_version" ])
94
- python_version = Version (pp_realease ["python_version" ])
95
- python_version_str = f"{ python_version .major } .{ python_version .minor } "
96
- url = None
97
- for file in pp_realease ["files" ]:
98
- if f"{ file ['platform' ]} -{ file ['arch' ]} " == plat_arch :
99
- url = file ["download_url" ]
100
- break
101
- if url :
102
- pp_versions [python_version_str ].append ((version , url ))
77
+ items : List [AnyConfig ] = []
103
78
104
- return pp_versions
79
+ for pp_release in pp_releases :
105
80
81
+ if pp_release ["pypy_version" ] == "nightly" :
82
+ continue
83
+ pypy_version = Version (pp_release ["pypy_version" ])
84
+ if pypy_version .is_prerelease or pypy_version .is_devrelease :
85
+ continue
106
86
107
- # Debugging printout:
108
- # print(get_cpython_windows())
109
- # print()
110
- # print("[bold]Getting PyPy")
111
- # print(get_pypy_windows())
87
+ version = Version (pp_release ["python_version" ])
88
+
89
+ for rf in pp_release ["files" ]:
90
+ if platform == "windows" :
91
+ if rf ["platform" ] == "win32" and rf ["arch" ] == "x86" :
92
+ identifier = f"pp{ version .major } { version .minor } -win32"
93
+ items .append (
94
+ ConfigWinPP (
95
+ identifier = identifier ,
96
+ version = Version (f"{ version .major } .{ version .minor } " ),
97
+ arch = "32" ,
98
+ url = rf ["download_url" ],
99
+ )
100
+ )
101
+ break
112
102
113
- ARCH_DICT = { "32" : "win32" , "64" : "win_amd64" }
103
+ return items
114
104
115
105
116
- def build_ids_cp (in_dict : Dict [str , List [Version ]]) -> List [ConfigWinCP ]:
117
- items : List [ConfigWinCP ] = []
118
- for arch in in_dict :
119
- for minor , grp in groupby (in_dict [arch ], lambda v : v .minor ):
120
- # Filter pre-releases, unless it's all pre-releases
121
- if not all (v .is_devrelease for v in grp ):
122
- grp = filter (lambda v : not v .is_devrelease , grp )
106
+ def sort_and_filter_configs (
107
+ orig_items : List [AnyConfig ],
108
+ * ,
109
+ prereleases : bool = False ,
110
+ ) -> List [AnyConfig ]:
123
111
124
- version = sorted (grp )[- 1 ]
125
- identifier = f"cp3{ minor } -{ ARCH_DICT [arch ]} "
112
+ items : List [AnyConfig ] = []
126
113
127
- items .append (
128
- ConfigWinCP (
129
- identifier = identifier ,
130
- version = str (version ),
131
- arch = arch ,
132
- )
133
- )
114
+ # Groupby requires pre-grouped input
115
+ orig_items = sorted (orig_items , key = lambda x : x ["identifier" ])
134
116
135
- return items
117
+ for _ , grp in groupby (orig_items , lambda x : x ["identifier" ]):
118
+ # Never select dev releases
119
+ choices = list (filter (lambda x : not x ["version" ].is_devrelease , grp ))
136
120
121
+ # Filter pre-releases, unless it's all pre-releases
122
+ if not all (x ["version" ].is_prerelease for x in grp ):
123
+ choices = list (filter (lambda x : not x ["version" ].is_prerelease , choices ))
137
124
138
- def build_ids_pp ( in_dict : Dict [ str , List [ Tuple [ Version , str ]]]) -> List [ ConfigWinPP ]:
139
- items : List [ ConfigWinPP ] = []
140
- for vers , matches in in_dict . items ():
141
- vers_id = vers . replace ( "." , "" )
142
- if not all ( v [ 0 ]. is_devrelease for v in matches ):
143
- matches = list ( filter ( lambda v : not v [ 0 ]. is_devrelease , matches ))
125
+ # Select the highest choice unless there are none
126
+ _url = "url" # Needed for MyPy, see https://github.com/python/mypy/issues/9902
127
+ choices = sorted ( choices , key = lambda x : ( x [ "version" ], x . get ( _url )))
128
+ if not choices :
129
+ continue
130
+ best_choice = choices [ - 1 ]
144
131
145
- version , url = sorted (matches , key = lambda v : v [0 ])[- 1 ]
146
- identifier = f"pp{ vers_id } -win32"
132
+ # Only allow a pre-release if they are all prereleases, and we've asked for them
133
+ if best_choice ["version" ].is_prerelease and not prereleases :
134
+ continue
147
135
148
- items .append (
149
- ConfigWinPP (
150
- identifier = identifier ,
151
- version = vers ,
152
- arch = "32" ,
153
- url = url ,
154
- )
155
- )
136
+ items .append (best_choice )
156
137
157
- return items
138
+ return sorted (
139
+ items ,
140
+ key = lambda x : (
141
+ x ["identifier" ][:3 ],
142
+ x ["version" ].minor ,
143
+ x ["identifier" ].split ("-" )[- 1 ],
144
+ ),
145
+ )
158
146
159
147
160
- windows_configs = [
161
- * build_ids_cp ( get_cpython_windows ()) ,
162
- * build_ids_pp ( get_pypy_windows ()) ,
148
+ CLASSIC_WINDOWS : List [ ConfigWinCP ] = [
149
+ { "identifier" : "cp27-win32" , "version" : Version ( "2.7.18" ), "arch" : "32" } ,
150
+ { "identifier" : "cp27-win_amd64" , "version" : Version ( "2.7.18" ), "arch" : "64" } ,
163
151
]
164
152
165
- configs = toml .load (RESOURCES_DIR / "build-platforms.toml" )
166
- origpy2 = list (
167
- filter (
168
- lambda c : c ["identifier" ].startswith ("cp27" ),
169
- configs ["windows" ]["python_configurations" ],
170
- )
171
- )
172
153
173
- items = origpy2 + windows_configs
174
- new_configs = sorted (items , key = lambda x : (x ["identifier" ][:3 ], x ["identifier" ][3 :]))
154
+ @click .command ()
155
+ @click .option ("--inplace" , is_flag = True )
156
+ @click .option ("--prereleases" , is_flag = True )
157
+ @click .option ("--all" , is_flag = True )
158
+ def update_pythons (inplace : bool , prereleases : bool , all : bool ) -> None :
159
+ windows_configs : List [AnyConfig ] = [
160
+ * CLASSIC_WINDOWS ,
161
+ * get_cpython_windows (),
162
+ * get_pypy ("windows" ),
163
+ ]
164
+
165
+ if not all :
166
+ windows_configs = sort_and_filter_configs (
167
+ windows_configs ,
168
+ prereleases = prereleases ,
169
+ )
170
+
171
+ configs = toml .load (RESOURCES_DIR / "build-platforms.toml" )
172
+ configs ["windows" ]["python_configurations" ] = windows_configs
173
+
174
+ if inplace :
175
+ with open (RESOURCES_DIR / "build-platforms.toml" , "w" ) as f :
176
+ toml .dump (configs , f , encoder = InlineArrayDictEncoder ()) # type: ignore
177
+ else :
178
+ print (toml .dumps (configs , encoder = InlineArrayDictEncoder ())) # type: ignore
175
179
176
- configs ["windows" ]["python_configurations" ] = new_configs
177
180
178
- with open ( RESOURCES_DIR / "build-platforms.toml" , "w" ) as f :
179
- toml . dump ( configs , f , encoder = InlineArrayDictEncoder ()) # type: ignore
181
+ if __name__ == "__main__" :
182
+ update_pythons ()
0 commit comments