15
15
"Python toolchain module extensions for use with bzlmod."
16
16
17
17
load ("@bazel_features//:features.bzl" , "bazel_features" )
18
- load ("//python:versions.bzl" , "DEFAULT_RELEASE_BASE_URL" , "PLATFORMS" , "TOOL_VERSIONS" )
18
+ load ("//python:versions.bzl" , "DEFAULT_RELEASE_BASE_URL" , "PLATFORMS" , "TOOL_VERSIONS" , "platform_info" )
19
19
load (":auth.bzl" , "AUTH_ATTRS" )
20
20
load (":full_version.bzl" , "full_version" )
21
21
load (":python_register_toolchains.bzl" , "python_register_toolchains" )
22
22
load (":pythons_hub.bzl" , "hub_repo" )
23
23
load (":repo_utils.bzl" , "repo_utils" )
24
- load (":toolchains_repo.bzl" , "host_compatible_python_repo" , "multi_toolchain_aliases" , "sorted_host_platforms" )
24
+ load (
25
+ ":toolchains_repo.bzl" ,
26
+ "host_compatible_python_repo" ,
27
+ "multi_toolchain_aliases" ,
28
+ "sorted_host_platform_names" ,
29
+ "sorted_host_platforms" ,
30
+ )
25
31
load (":util.bzl" , "IS_BAZEL_6_4_OR_HIGHER" )
26
32
load (":version.bzl" , "version" )
27
33
@@ -267,6 +273,24 @@ def parse_modules(*, module_ctx, _fail = fail):
267
273
def _python_impl (module_ctx ):
268
274
py = parse_modules (module_ctx = module_ctx )
269
275
276
+ # Host compatible runtime repos
277
+ # dict[str version, struct] where struct has:
278
+ # * full_python_version: str
279
+ # * platform: platform_info struct
280
+ # * platform_name: str platform name
281
+ # * impl_repo_name: str repo name of the runtime's python_repository() repo
282
+ all_host_compatible_impls = {}
283
+
284
+ # Host compatible repos that still need to be created because, when
285
+ # creating the actual runtime repo, there wasn't a host-compatible
286
+ # variant defined for it.
287
+ # dict[str reponame, struct] where struct has:
288
+ # * compatible_version: str, e.g. 3.10 or 3.10.1. The version the host
289
+ # repo should be compatible with
290
+ # * full_python_version: str, e.g. 3.10.1, the full python version of
291
+ # the toolchain that still needs a host repo created.
292
+ needed_host_repos = {}
293
+
270
294
# list of structs; see inline struct call within the loop below.
271
295
toolchain_impls = []
272
296
@@ -293,11 +317,24 @@ def _python_impl(module_ctx):
293
317
kwargs .update (py .config .kwargs .get (toolchain_info .python_version , {}))
294
318
kwargs .update (py .config .kwargs .get (full_python_version , {}))
295
319
kwargs .update (py .config .default )
320
+ if "3.13" in full_python_version :
321
+ print ("bzlmod register:" , toolchain_info .name , full_python_version )
322
+ print ("kwargs.platforms:" , kwargs ["platforms" ].keys ())
323
+ ##print("kwargs.tool_versions:", kwargs["tool_versions"])
324
+
325
+ # todo: this part is failing. python_register_toolchains doesn't have
326
+ # the new platform in its platform map, so never tries
296
327
register_result = python_register_toolchains (
297
328
name = toolchain_info .name ,
298
329
_internal_bzlmod_toolchain_call = True ,
299
330
** kwargs
300
331
)
332
+ if not register_result .impl_repos :
333
+ # If nothing was registered, something has gone wrong. This probably
334
+ # means the `platforms` map and `tool_versions[version]["shas"]`
335
+ # aren't in sync.
336
+ # todo: ignore instead of fail?
337
+ fail ("No impls registered for" , toolchain_info )
301
338
302
339
host_platforms = {}
303
340
for repo_name , (platform_name , platform_info ) in register_result .impl_repos .items ():
@@ -318,23 +355,111 @@ def _python_impl(module_ctx):
318
355
set_python_version_constraint = is_last ,
319
356
))
320
357
if _is_compatible_with_host (module_ctx , platform_info ):
321
- host_platforms [platform_name ] = platform_info
322
-
323
- host_platforms = sorted_host_platforms (host_platforms )
324
- host_compatible_python_repo (
325
- name = toolchain_info .name + "_host" ,
326
- # NOTE: Order matters. The first found to be compatible is (usually) used.
327
- platforms = host_platforms .keys (),
328
- os_names = {
329
- str (i ): platform_info .os_name
330
- for i , platform_info in enumerate (host_platforms .values ())
331
- },
332
- arch_names = {
333
- str (i ): platform_info .arch
334
- for i , platform_info in enumerate (host_platforms .values ())
335
- },
336
- python_version = full_python_version ,
337
- )
358
+ host_compat_entry = struct (
359
+ full_python_version = full_python_version ,
360
+ platform = platform_info ,
361
+ platform_name = platform_name ,
362
+ impl_repo_name = repo_name ,
363
+ )
364
+ host_platforms [platform_name ] = host_compat_entry
365
+ all_host_compatible_impls .setdefault (full_python_version , []).append (
366
+ host_compat_entry ,
367
+ )
368
+ all_host_compatible_impls .setdefault (
369
+ full_python_version .rpartition ("." )[0 ],
370
+ [],
371
+ ).append (host_compat_entry )
372
+
373
+ host_repo_name = toolchain_info .name + "_host"
374
+ if not host_platforms :
375
+ print ("need:" , host_repo_name )
376
+ needed_host_repos [host_repo_name ] = struct (
377
+ compatible_version = toolchain_info .python_version ,
378
+ full_python_version = full_python_version ,
379
+ )
380
+ else :
381
+ print ("create:" , host_repo_name )
382
+ host_platforms = sorted_host_platforms (host_platforms )
383
+ entries = host_platforms .values ()
384
+ host_compatible_python_repo (
385
+ name = host_repo_name ,
386
+ base_name = host_repo_name ,
387
+ # NOTE: Order matters. The first found to be compatible is (usually) used.
388
+ platforms = host_platforms .keys (),
389
+ os_names = {
390
+ str (i ): entry .platform .os_name
391
+ for i , entry in enumerate (entries )
392
+ },
393
+ arch_names = {
394
+ str (i ): entry .platform .arch
395
+ for i , entry in enumerate (entries )
396
+ },
397
+ python_versions = {
398
+ str (i ): entry .full_python_version
399
+ for i , entry in enumerate (entries )
400
+ },
401
+ impl_repo_names = {
402
+ str (i ): entry .impl_repo_name
403
+ for i , entry in enumerate (entries )
404
+ },
405
+ )
406
+
407
+ def vt (s ):
408
+ return tuple ([int (x ) for x in s .split ("." )])
409
+
410
+ if needed_host_repos :
411
+ print ("host repos still needed:" , needed_host_repos )
412
+ for key , entries in all_host_compatible_impls .items ():
413
+ all_host_compatible_impls [key ] = sorted (
414
+ entries ,
415
+ reverse = True ,
416
+ key = lambda e : vt (e .full_python_version ),
417
+ )
418
+
419
+ for host_repo_name , info in needed_host_repos .items ():
420
+ choices = []
421
+ if info .compatible_version not in all_host_compatible_impls :
422
+ print (
423
+ "No host compatible for:" ,
424
+ info .compatible_version ,
425
+ "available:" ,
426
+ all_host_compatible_impls .keys (),
427
+ )
428
+ continue
429
+ ##fail(" version missing", info.compatible_version)
430
+
431
+ for entry in all_host_compatible_impls [info .compatible_version ]:
432
+ # todo: numeric version comparison
433
+ # todo: should we restrict at all? Maybe just take the highest?
434
+ if vt (entry .full_python_version ) <= vt (info .full_python_version ):
435
+ choices .append (entry )
436
+ if choices :
437
+ platform_keys = [
438
+ # We have to prepend the offset because the same platform
439
+ # name might occur across different versions
440
+ "{}_{}" .format (i , entry .platform_name )
441
+ for i , entry in enumerate (choices )
442
+ ]
443
+ platform_keys = sorted_host_platform_names (platform_keys )
444
+
445
+ print ("create alt: {} for {}" .format (host_repo_name , info .compatible_version ))
446
+ print ("platforms=" , platform_keys )
447
+
448
+ host_compatible_python_repo (
449
+ name = host_repo_name ,
450
+ base_name = host_repo_name ,
451
+ platforms = platform_keys ,
452
+ impl_repo_names = {
453
+ str (i ): entry .impl_repo_name
454
+ for i , entry in enumerate (choices )
455
+ },
456
+ os_names = {str (i ): entry .platform .os_name for i , entry in enumerate (choices )},
457
+ arch_names = {str (i ): entry .platform .arch for i , entry in enumerate (choices )},
458
+ python_versions = {str (i ): entry .full_python_version for i , entry in enumerate (choices )},
459
+ )
460
+ else :
461
+ # todo: figure out what to do. Define nothing, if we can.
462
+ fail ("No host-compatible found" )
338
463
339
464
# List of the base names ("python_3_10") for the toolchain repos
340
465
base_toolchain_repo_names = []
@@ -586,6 +711,49 @@ def _process_single_version_platform_overrides(*, tag, _fail = fail, default):
586
711
if tag .urls :
587
712
available_versions [tag .python_version ].setdefault ("url" , {})[tag .platform ] = tag .urls
588
713
714
+ if tag .platform not in default ["platforms" ]:
715
+ os_name = tag .os_name
716
+ arch = tag .arch
717
+ if not os_name or not arch :
718
+ for v in tag .target_compatible_with :
719
+ if os_name and arch :
720
+ break
721
+ if not os_name :
722
+ if v .startswith ("@platforms//os:" ):
723
+ if v .endswith (":linux" ):
724
+ os_name = "linux"
725
+
726
+ if not arch :
727
+ if v .startswith ("@platforms//cpu:" ):
728
+ if v .endswith (":x86_64" ):
729
+ arch = "x86_64"
730
+
731
+ if not os_name :
732
+ os_name = "UNKNOWN_CUSTOM"
733
+ if not arch :
734
+ arch = "UNKNOWN_CUSTOM"
735
+
736
+ # todo: figure out why these can't be none
737
+ os_name = None
738
+ arch = None
739
+
740
+ default ["platforms" ][tag .platform ] = platform_info (
741
+ compatible_with = tag .target_compatible_with ,
742
+ target_settings = tag .target_settings ,
743
+ os_name = os_name ,
744
+ arch = arch ,
745
+ )
746
+ elif (
747
+ tag .target_compatible_with or tag .target_settings or
748
+ tag .os_name or tag .arch
749
+ ):
750
+ # todo: fail, or ignore?
751
+ fail ((
752
+ "Cannot override platform {} with custom platform settings"
753
+ ).format (
754
+ tag .platform ,
755
+ ))
756
+
589
757
def _process_global_overrides (* , tag , default , _fail = fail ):
590
758
if tag .available_python_versions :
591
759
available_versions = default ["tool_versions" ]
@@ -1084,12 +1252,14 @@ configuration, please use {obj}`single_version_override`.
1084
1252
:::
1085
1253
""" ,
1086
1254
attrs = {
1255
+ "arch" : attr .string (),
1087
1256
"coverage_tool" : attr .label (
1088
1257
doc = """\
1089
1258
The coverage tool to be used for a particular Python interpreter. This can override
1090
1259
`rules_python` defaults.
1091
1260
""" ,
1092
1261
),
1262
+ "os_name" : attr .string (),
1093
1263
"patch_strip" : attr .int (
1094
1264
mandatory = False ,
1095
1265
doc = "Same as the --strip argument of Unix patch." ,
@@ -1101,7 +1271,6 @@ The coverage tool to be used for a particular Python interpreter. This can overr
1101
1271
),
1102
1272
"platform" : attr .string (
1103
1273
mandatory = True ,
1104
- values = PLATFORMS .keys (),
1105
1274
doc = "The platform to override the values for, must be one of:\n {}." .format ("\n " .join (sorted (["* `{}`" .format (p ) for p in PLATFORMS ]))),
1106
1275
),
1107
1276
"python_version" : attr .string (
@@ -1117,6 +1286,8 @@ The coverage tool to be used for a particular Python interpreter. This can overr
1117
1286
doc = "The 'strip_prefix' for the archive, defaults to 'python'." ,
1118
1287
default = "python" ,
1119
1288
),
1289
+ "target_compatible_with" : attr .string_list (),
1290
+ "target_settings" : attr .string_list (),
1120
1291
"urls" : attr .string_list (
1121
1292
mandatory = False ,
1122
1293
doc = "The URL template to fetch releases for this Python version. If the URL template results in a relative fragment, default base URL is going to be used. Occurrences of `{python_version}`, `{platform}` and `{build}` will be interpolated based on the contents in the override and the known {attr}`platform` values." ,
0 commit comments