1515"Python toolchain module extensions for use with bzlmod."
1616
1717load ("@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" )
1919load (":auth.bzl" , "AUTH_ATTRS" )
2020load (":full_version.bzl" , "full_version" )
2121load (":python_register_toolchains.bzl" , "python_register_toolchains" )
2222load (":pythons_hub.bzl" , "hub_repo" )
2323load (":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+ )
2531load (":util.bzl" , "IS_BAZEL_6_4_OR_HIGHER" )
2632load (":version.bzl" , "version" )
2733
@@ -267,6 +273,24 @@ def parse_modules(*, module_ctx, _fail = fail):
267273def _python_impl (module_ctx ):
268274 py = parse_modules (module_ctx = module_ctx )
269275
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+
270294 # list of structs; see inline struct call within the loop below.
271295 toolchain_impls = []
272296
@@ -293,11 +317,24 @@ def _python_impl(module_ctx):
293317 kwargs .update (py .config .kwargs .get (toolchain_info .python_version , {}))
294318 kwargs .update (py .config .kwargs .get (full_python_version , {}))
295319 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
296327 register_result = python_register_toolchains (
297328 name = toolchain_info .name ,
298329 _internal_bzlmod_toolchain_call = True ,
299330 ** kwargs
300331 )
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 )
301338
302339 host_platforms = {}
303340 for repo_name , (platform_name , platform_info ) in register_result .impl_repos .items ():
@@ -318,23 +355,111 @@ def _python_impl(module_ctx):
318355 set_python_version_constraint = is_last ,
319356 ))
320357 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" )
338463
339464 # List of the base names ("python_3_10") for the toolchain repos
340465 base_toolchain_repo_names = []
@@ -586,6 +711,49 @@ def _process_single_version_platform_overrides(*, tag, _fail = fail, default):
586711 if tag .urls :
587712 available_versions [tag .python_version ].setdefault ("url" , {})[tag .platform ] = tag .urls
588713
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+
589757def _process_global_overrides (* , tag , default , _fail = fail ):
590758 if tag .available_python_versions :
591759 available_versions = default ["tool_versions" ]
@@ -1084,12 +1252,14 @@ configuration, please use {obj}`single_version_override`.
10841252:::
10851253""" ,
10861254 attrs = {
1255+ "arch" : attr .string (),
10871256 "coverage_tool" : attr .label (
10881257 doc = """\
10891258 The coverage tool to be used for a particular Python interpreter. This can override
10901259`rules_python` defaults.
10911260""" ,
10921261 ),
1262+ "os_name" : attr .string (),
10931263 "patch_strip" : attr .int (
10941264 mandatory = False ,
10951265 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
11011271 ),
11021272 "platform" : attr .string (
11031273 mandatory = True ,
1104- values = PLATFORMS .keys (),
11051274 doc = "The platform to override the values for, must be one of:\n {}." .format ("\n " .join (sorted (["* `{}`" .format (p ) for p in PLATFORMS ]))),
11061275 ),
11071276 "python_version" : attr .string (
@@ -1117,6 +1286,8 @@ The coverage tool to be used for a particular Python interpreter. This can overr
11171286 doc = "The 'strip_prefix' for the archive, defaults to 'python'." ,
11181287 default = "python" ,
11191288 ),
1289+ "target_compatible_with" : attr .string_list (),
1290+ "target_settings" : attr .string_list (),
11201291 "urls" : attr .string_list (
11211292 mandatory = False ,
11221293 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