1
1
#!/usr/bin/env python3
2
2
3
3
4
- import copy
5
4
import difflib
6
5
import logging
7
6
import operator
34
33
ArchStr = Literal ["32" , "64" , "ARM64" ]
35
34
36
35
37
- class ConfigWinCP (TypedDict ):
36
+ class Config (TypedDict ):
38
37
identifier : str
39
38
version : str
40
- arch : str
41
39
42
40
43
- class ConfigWinPP (TypedDict ):
44
- identifier : str
45
- version : str
46
- arch : str
47
- url : str
48
-
49
-
50
- class ConfigWinGP (TypedDict ):
51
- identifier : str
52
- version : str
53
- url : str
54
-
55
-
56
- class ConfigApple (TypedDict ):
57
- identifier : str
58
- version : str
59
- url : str
60
-
61
-
62
- class ConfigAndroid (TypedDict ):
63
- identifier : str
64
- version : str
41
+ class ConfigUrl (Config ):
65
42
url : str
66
43
67
44
68
- class ConfigPyodide (TypedDict ):
69
- identifier : str
70
- version : str
45
+ class ConfigPyodide (Config ):
71
46
default_pyodide_version : str
72
47
node_version : str
73
48
74
49
75
- AnyConfig = ConfigWinCP | ConfigWinPP | ConfigWinGP | ConfigApple | ConfigAndroid | ConfigPyodide
76
-
77
-
78
50
# The following set of "Versions" classes allow the initial call to the APIs to
79
51
# be cached and reused in the `update_version_*` methods.
80
52
@@ -106,7 +78,7 @@ def __init__(self, arch_str: ArchStr, free_threaded: bool) -> None:
106
78
107
79
self .version_dict = {Version (v ): v for v in cp_info ["versions" ]}
108
80
109
- def update_version_windows (self , spec : Specifier ) -> ConfigWinCP | None :
81
+ def update_version_windows (self , spec : Specifier ) -> Config | None :
110
82
# Specifier.filter selects all non pre-releases that match the spec,
111
83
# unless there are only pre-releases, then it selects pre-releases
112
84
# instead (like pip)
@@ -121,10 +93,9 @@ def update_version_windows(self, spec: Specifier) -> ConfigWinCP | None:
121
93
flags = "t" if self .free_threaded else ""
122
94
version = versions [0 ]
123
95
identifier = f"cp{ version .major } { version .minor } { flags } -{ self .arch } "
124
- return ConfigWinCP (
96
+ return Config (
125
97
identifier = identifier ,
126
98
version = self .version_dict [version ],
127
- arch = self .arch_str ,
128
99
)
129
100
130
101
@@ -146,7 +117,7 @@ def __init__(self) -> None:
146
117
147
118
self .releases = [r for r in releases if "graalpy_version" in r and "python_version" in r ]
148
119
149
- def update_version (self , identifier : str , spec : Specifier ) -> AnyConfig :
120
+ def update_version (self , identifier : str , spec : Specifier ) -> ConfigUrl :
150
121
if "x86_64" in identifier or "amd64" in identifier :
151
122
arch = "x86_64"
152
123
elif "arm64" in identifier or "aarch64" in identifier :
@@ -172,11 +143,9 @@ def update_version(self, identifier: str, spec: Specifier) -> AnyConfig:
172
143
173
144
if "macosx" in identifier :
174
145
arch = "x86_64" if "x86_64" in identifier else "arm64"
175
- config = ConfigApple
176
146
platform = "macos"
177
147
elif "win" in identifier :
178
148
arch = "aarch64" if "arm64" in identifier else "x86_64"
179
- config = ConfigWinGP
180
149
platform = "windows"
181
150
else :
182
151
msg = "GraalPy provides downloads for macOS and Windows and is included for manylinux"
@@ -191,7 +160,7 @@ def update_version(self, identifier: str, spec: Specifier) -> AnyConfig:
191
160
and rf ["name" ].startswith (f"graalpy-{ gpversion .major } " )
192
161
)
193
162
194
- return config (
163
+ return ConfigUrl (
195
164
identifier = identifier ,
196
165
version = f"{ version .major } .{ version .minor } " ,
197
166
url = url ,
@@ -223,7 +192,7 @@ def get_arch_file(self, release: Mapping[str, Any]) -> str:
223
192
]
224
193
return urls [0 ] if urls else ""
225
194
226
- def update_version_windows (self , spec : Specifier ) -> ConfigWinCP :
195
+ def update_version_windows (self , spec : Specifier ) -> ConfigUrl :
227
196
releases = [r for r in self .releases if spec .contains (r ["python_version" ])]
228
197
releases = sorted (releases , key = operator .itemgetter ("pypy_version" ))
229
198
releases = [r for r in releases if self .get_arch_file (r )]
@@ -239,14 +208,13 @@ def update_version_windows(self, spec: Specifier) -> ConfigWinCP:
239
208
identifier = f"pp{ version .major } { version .minor } -{ version_arch } "
240
209
url = self .get_arch_file (release )
241
210
242
- return ConfigWinPP (
211
+ return ConfigUrl (
243
212
identifier = identifier ,
244
213
version = f"{ version .major } .{ version .minor } " ,
245
- arch = self .arch ,
246
214
url = url ,
247
215
)
248
216
249
- def update_version_macos (self , spec : Specifier ) -> ConfigApple :
217
+ def update_version_macos (self , spec : Specifier ) -> ConfigUrl :
250
218
if self .arch not in {"64" , "ARM64" }:
251
219
msg = f"'{ self .arch } ' arch not supported yet on macOS"
252
220
raise RuntimeError (msg )
@@ -270,7 +238,7 @@ def update_version_macos(self, spec: Specifier) -> ConfigApple:
270
238
if "" in rf ["platform" ] == "darwin" and rf ["arch" ] == arch
271
239
)
272
240
273
- return ConfigApple (
241
+ return ConfigUrl (
274
242
identifier = identifier ,
275
243
version = f"{ version .major } .{ version .minor } " ,
276
244
url = url ,
@@ -298,16 +266,11 @@ def __init__(self) -> None:
298
266
uri = int (release ["resource_uri" ].rstrip ("/" ).split ("/" )[- 1 ])
299
267
self .versions_dict [version ] = uri
300
268
301
- def update_version_macos (
302
- self , identifier : str , version : Version , spec : Specifier
303
- ) -> ConfigApple | None :
269
+ def update_version (self , identifier : str , spec : Specifier , file_ident : str ) -> ConfigUrl | None :
304
270
# see note above on Specifier.filter
305
271
unsorted_versions = spec .filter (self .versions_dict )
306
272
sorted_versions = sorted (unsorted_versions , reverse = True )
307
273
308
- macver = "x10.9" if version <= Version ("3.8.9999" ) else "11"
309
- file_ident = f"macos{ macver } .pkg"
310
-
311
274
for new_version in sorted_versions :
312
275
# Find the first patch version that contains the requested file
313
276
uri = self .versions_dict [new_version ]
@@ -319,17 +282,25 @@ def update_version_macos(
319
282
320
283
urls = [rf ["url" ] for rf in file_info if file_ident in rf ["url" ]]
321
284
if urls :
322
- return ConfigApple (
285
+ return ConfigUrl (
323
286
identifier = identifier ,
324
287
version = f"{ new_version .major } .{ new_version .minor } " ,
325
288
url = urls [0 ],
326
289
)
327
290
328
291
return None
329
292
293
+ def update_version_macos (
294
+ self , identifier : str , version : Version , spec : Specifier
295
+ ) -> ConfigUrl | None :
296
+ macver = "x10.9" if version <= Version ("3.8.9999" ) else "11"
297
+ return self .update_version (identifier , spec , f"macos{ macver } .pkg" )
298
+
299
+ def update_version_android (self , identifier : str , spec : Specifier ) -> ConfigUrl | None :
300
+ return self .update_version (identifier , spec , android_triplet (identifier ))
330
301
331
- class AndroidVersions :
332
- # This should be replaced with official python.org downloads once they're available.
302
+
303
+ class MavenVersions :
333
304
MAVEN_URL = "https://repo.maven.apache.org/maven2/com/chaquo/python/python"
334
305
335
306
def __init__ (self ) -> None :
@@ -343,18 +314,16 @@ def __init__(self) -> None:
343
314
assert isinstance (version_str , str ), version_str
344
315
self .versions .append (Version (version_str ))
345
316
346
- def update_version_android (
347
- self , identifier : str , version : Version , spec : Specifier
348
- ) -> ConfigAndroid | None :
317
+ def update_version_android (self , identifier : str , spec : Specifier ) -> ConfigUrl | None :
349
318
sorted_versions = sorted (spec .filter (self .versions ), reverse = True )
350
319
351
320
# Return a config using the highest version for the given specifier.
352
321
if sorted_versions :
353
322
max_version = sorted_versions [0 ]
354
323
triplet = android_triplet (identifier )
355
- return ConfigAndroid (
324
+ return ConfigUrl (
356
325
identifier = identifier ,
357
- version = str ( version ) ,
326
+ version = f" { max_version . major } . { max_version . minor } " ,
358
327
url = f"{ self .MAVEN_URL } /{ max_version } /python-{ max_version } -{ triplet } .tar.gz" ,
359
328
)
360
329
else :
@@ -390,11 +359,11 @@ def __init__(self) -> None:
390
359
if filename .endswith ("-iOS-support" ):
391
360
self .versions_dict [version ][int (build [1 :])] = asset ["browser_download_url" ]
392
361
393
- def update_version_ios (self , identifier : str , version : Version ) -> ConfigApple | None :
362
+ def update_version_ios (self , identifier : str , version : Version ) -> ConfigUrl | None :
394
363
# Return a config using the highest build number for the given version.
395
364
urls = [url for _ , url in sorted (self .versions_dict .get (version , {}).items ())]
396
365
if urls :
397
- return ConfigApple (
366
+ return ConfigUrl (
398
367
identifier = identifier ,
399
368
version = str (version ),
400
369
url = urls [- 1 ],
@@ -450,11 +419,11 @@ def __init__(self) -> None:
450
419
self .windows_t_arm64 = WindowsVersions ("ARM64" , True )
451
420
self .windows_pypy_64 = PyPyVersions ("64" )
452
421
453
- self .macos_cpython = CPythonVersions ()
422
+ self .cpython = CPythonVersions ()
454
423
self .macos_pypy = PyPyVersions ("64" )
455
424
self .macos_pypy_arm64 = PyPyVersions ("ARM64" )
456
425
457
- self .android = AndroidVersions ()
426
+ self .maven = MavenVersions ()
458
427
self .ios_cpython = CPythonIOSVersions ()
459
428
460
429
self .graalpy = GraalPyVersions ()
@@ -466,13 +435,12 @@ def update_config(self, config: MutableMapping[str, str]) -> None:
466
435
version = Version (config ["version" ])
467
436
spec = Specifier (f"=={ version .major } .{ version .minor } .*" )
468
437
log .info ("Reading in %r -> %s @ %s" , str (identifier ), spec , version )
469
- orig_config = copy .copy (config )
470
- config_update : AnyConfig | None = None
438
+ config_update : Config | None = None
471
439
472
440
# We need to use ** in update due to MyPy (probably a bug)
473
441
if "macosx" in identifier :
474
442
if identifier .startswith ("cp" ):
475
- config_update = self .macos_cpython .update_version_macos (identifier , version , spec )
443
+ config_update = self .cpython .update_version_macos (identifier , version , spec )
476
444
elif identifier .startswith ("pp" ):
477
445
if "macosx_x86_64" in identifier :
478
446
config_update = self .macos_pypy .update_version_macos (spec )
@@ -498,7 +466,10 @@ def update_config(self, config: MutableMapping[str, str]) -> None:
498
466
elif "win_arm64" in identifier and identifier .startswith ("cp" ):
499
467
config_update = self .windows_arm64 .update_version_windows (spec )
500
468
elif "android" in identifier :
501
- config_update = self .android .update_version_android (identifier , version , spec )
469
+ # Python 3.13 is released by Chaquopy on Maven Central.
470
+ # Python 3.14 and newer have official releases on python.org.
471
+ versions = self .maven if identifier .startswith ("cp313" ) else self .cpython
472
+ config_update = versions .update_version_android (identifier , spec )
502
473
elif "ios" in identifier :
503
474
config_update = self .ios_cpython .update_version_ios (identifier , version )
504
475
elif "pyodide" in identifier :
@@ -507,10 +478,10 @@ def update_config(self, config: MutableMapping[str, str]) -> None:
507
478
)
508
479
509
480
assert config_update is not None , f"{ identifier } not found!"
510
- config . update ( ** config_update )
511
-
512
- if config != orig_config :
513
- log . info ( " Updated %s to %s" , orig_config , config )
481
+ if config_update != config :
482
+ log . info ( " Updated %s to %s" , config , config_update )
483
+ config . clear ()
484
+ config . update ( ** config_update )
514
485
515
486
516
487
@click .command ()
0 commit comments