@@ -313,7 +313,7 @@ def _host_toolchain_impl(rctx):
313313 rctx .file ("BUILD.bazel" , _HOST_TOOLCHAIN_BUILD_CONTENT )
314314
315315 os_name = repo_utils .get_platforms_os_name (rctx )
316- host_platform = _get_host_platform (
316+ host_platform = _get_host_impl_repo_suffix (
317317 rctx = rctx ,
318318 logger = repo_utils .logger (rctx ),
319319 python_version = rctx .attr .python_version ,
@@ -386,7 +386,27 @@ Creates a repository with a shorter name meant to be used in the repository_ctx,
386386which needs to have `symlinks` for the interpreter. This is separate from the
387387toolchain_aliases repo because referencing the `python` interpreter target from
388388this repo causes an eager fetch of the toolchain for the host platform.
389- """ ,
389+
390+ This repo has three different ways in which is it called:
391+
392+ 1. Workspace. The `platforms` attribute is set, which are keys into the
393+ PLATFORMS global. It assumes `name` + <matching platform name> is a
394+ valid repo name which it can use as the backing repo.
395+
396+ 2. Bzlmod, created along side when python_register_toolchains is called
397+ and expected to use one of repos created as part of that
398+ python_register_toolchains call.
399+ Because the bzlmod extension decides the platform mapping, it is given
400+ the `platform`, `os_names`, and `archs` attributes to figure out which
401+ to use.
402+
403+ 3. Bzlmod, created when the initial python_register_toolchains didn't
404+ have a host-compatible runtime, so a different host-compatible
405+ implementation was used.
406+ This is like the normal bzlmod creation, except the python_versions
407+ and suffixes may vary between choices, so the `impl_repo_suffixes` and
408+ `python_version` attributes are specified.
409+ """ ,
390410 attrs = {
391411 "archs" : attr .string_dict (
392412 doc = """
@@ -398,8 +418,43 @@ If set, overrides the platform metadata. Keyed by index in `platforms`
398418If set, overrides the platform metadata. Keyed by index in `platforms`
399419""" ,
400420 ),
401- "platforms" : attr .string_list (mandatory = True ),
402- "python_version" : attr .string (mandatory = True ),
421+ "platforms" : attr .string_list (
422+ mandatory = True ,
423+ doc = """
424+ Platform names and backing repo-suffix.
425+
426+ NOTE: The order of this list matters. The first platform that is compatible
427+ with the host will be selected; this can be customized by using the
428+ `RULES_PYTHON_REPO_TOOLCHAIN_*` env vars.
429+
430+ When os_names aren't set, they act the key into the PLATFORMS
431+ dict to determine if a platform is compatible with the host. When
432+ os_names is set, then it is a (mostly) arbitrary platform name string
433+ (and platform metadata comes from the os_names/archs args).
434+
435+ The string is used as a suffix to create the name of the repo that
436+ should be pointed to. i.e. `name` + <selected platform string> should
437+ result in a valid repo (e.g. created by python_register_toolchains()).
438+ Under bzlmod, this also means the same extension must create the
439+ repo named `name+suffix` and the host_toolchain repo.
440+ """ ,
441+ ),
442+ "python_version" : attr .string (
443+ mandatory = True ,
444+ doc = "Full python version, Major.Minor.Micro" ,
445+ ),
446+ "python_versions" : attr .string_dict (
447+ doc = """
448+ If set, the Python version for the corresponding selected platform.
449+ Keyed by index in `platforms`. Values Major.Minor.Patch
450+ """ ,
451+ ),
452+ "impl_repo_suffixes" : attr .string_dict (
453+ doc = """
454+ If set, the suffix to append to `name` to identify the backing repo that is used.
455+ Keyed by index in `platforms`.
456+ """ ,
457+ ),
403458 "_rule_name" : attr .string (default = "host_toolchain" ),
404459 "_rules_python_workspace" : attr .label (default = Label ("//:WORKSPACE" )),
405460 },
@@ -435,8 +490,8 @@ multi_toolchain_aliases = repository_rule(
435490 },
436491)
437492
438- def sorted_host_platforms ( platform_map ):
439- """Sort the keys in the platform map to give correct precedence.
493+ def sorted_host_platform_names ( platform_names ):
494+ """Sort platform names to give correct precedence.
440495
441496 The order of keys in the platform mapping matters for the host toolchain
442497 selection. When multiple runtimes are compatible with the host, we take the
@@ -452,6 +507,29 @@ def sorted_host_platforms(platform_map):
452507 in a dict that autoformatters like to clobber and whose only documentation
453508 is an innocous looking formatter disable directive.
454509
510+ Args:
511+ platform_names: a list of platform names
512+
513+ Returns:
514+ list[str] the same values, but in the desired order.
515+ """
516+
517+ def platform_keyer (name ):
518+ # Ascending sort: lower is higher precedence
519+ pref = 0
520+ if name .endswith ("-" + FREETHREADED ):
521+ pref = 1
522+ elif name .endswith ("-" + MUSL ):
523+ pref = 2
524+ return (pref , name )
525+
526+ return sorted (platform_map .keys (), key = platform_keyer )
527+
528+ def sorted_host_platforms (platform_map ):
529+ """Sort the keys in the platform map to give correct precedence.
530+
531+ See sorted_host_platform_names for explanation.
532+
455533 Args:
456534 platform_map: a mapping of platforms and their metadata.
457535
@@ -469,13 +547,12 @@ def sorted_host_platforms(platform_map):
469547 pref = 2
470548 return (pref , name )
471549
472- sorted_platform_keys = sorted (platform_map .keys (), key = platform_keyer )
473550 return {
474551 key : platform_map [key ]
475- for key in sorted_platform_keys
552+ for key in sorted_host_platform_names ( platform_map . keys ())
476553 }
477554
478- def _get_host_platform (* , rctx , logger , python_version , os_name , cpu_name , platforms ):
555+ def _get_host_impl_repo_suffix (* , rctx , logger , python_version , os_name , cpu_name , platforms ):
479556 """Gets the host platform.
480557
481558 Args:
@@ -495,6 +572,8 @@ def _get_host_platform(*, rctx, logger, python_version, os_name, cpu_name, platf
495572 platform_map [platform_name ] = struct (
496573 os_name = rctx .attr .os_names [key ],
497574 arch = rctx .attr .archs [key ],
575+ python_version = rctx .attr .python_versions .get (key ),
576+ impl_repo_suffix = rctx .attr .impl_repo_suffixes .get (key ),
498577 )
499578 else :
500579 platform_map = sorted_host_platforms (PLATFORMS )
@@ -504,11 +583,13 @@ def _get_host_platform(*, rctx, logger, python_version, os_name, cpu_name, platf
504583 meta = platform_map [platform ]
505584
506585 if meta .os_name == os_name and meta .arch == cpu_name :
507- candidates .append (platform )
586+ candidates .append (( platform , meta ) )
508587
509588 if len (candidates ) == 1 :
510- return candidates [0 ]
589+ platform_name , meta = candidates [0 ]
590+ return getattr (meta , "impl_repo_suffix" , platform_name )
511591
592+ # todo: have this handle multiple python versions
512593 if candidates :
513594 env_var = "RULES_PYTHON_REPO_TOOLCHAIN_{}_{}_{}" .format (
514595 python_version .replace ("." , "_" ),
@@ -522,12 +603,21 @@ def _get_host_platform(*, rctx, logger, python_version, os_name, cpu_name, platf
522603 candidates ,
523604 ))
524605 elif preference not in candidates :
606+ # todo: need to map names like 3_13_0_linux_x86_64 back to
607+ # the input values. Ah, er, wait
608+ # Is this working?
609+ # The return value is appended to this repo's name.
610+ # This repo's name is e.g. python_3_13.
611+ # the net result would be
612+ # python_3_10_3_13_0_linux_x86_64
613+ # which isn't a valid name
525614 return logger .fail ("Please choose a preferred interpreter out of the following platforms: {}" .format (candidates ))
526615 else :
527616 candidates = [preference ]
528617
529618 if candidates :
530- return candidates [0 ]
619+ platform_name , meta = candidates [0 ]
620+ return getattr (meta , "impl_repo_suffix" , platform_name )
531621
532622 return logger .fail ("Could not find a compatible 'host' python for '{os_name}', '{cpu_name}' from the loaded platforms: {platforms}" .format (
533623 os_name = os_name ,
0 commit comments