@@ -85,46 +85,7 @@ def parse_modules(*, module_ctx, logger, _fail = fail):
8585
8686 config = _get_toolchain_config (modules = module_ctx .modules , _fail = _fail )
8787
88- default_python_version = None
89- for mod in module_ctx .modules :
90- defaults_attr_structs = _create_defaults_attr_structs (mod = mod )
91- default_python_version_env = None
92- default_python_version_file = None
93-
94- # Only the root module and rules_python are allowed to specify the default
95- # toolchain for a couple reasons:
96- # * It prevents submodules from specifying different defaults and only
97- # one of them winning.
98- # * rules_python needs to set a soft default in case the root module doesn't,
99- # e.g. if the root module doesn't use Python itself.
100- # * The root module is allowed to override the rules_python default.
101- if mod .is_root or (mod .name == "rules_python" and not default_python_version ):
102- for defaults_attr in defaults_attr_structs :
103- default_python_version = _one_or_the_same (
104- default_python_version ,
105- defaults_attr .python_version ,
106- onerror = _fail_multiple_defaults_python_version ,
107- )
108- default_python_version_env = _one_or_the_same (
109- default_python_version_env ,
110- defaults_attr .python_version_env ,
111- onerror = _fail_multiple_defaults_python_version_env ,
112- )
113- default_python_version_file = _one_or_the_same (
114- default_python_version_file ,
115- defaults_attr .python_version_file ,
116- onerror = _fail_multiple_defaults_python_version_file ,
117- )
118- if default_python_version_file :
119- default_python_version = _one_or_the_same (
120- default_python_version ,
121- module_ctx .read (default_python_version_file , watch = "yes" ).strip (),
122- )
123- if default_python_version_env :
124- default_python_version = module_ctx .getenv (
125- default_python_version_env ,
126- default_python_version ,
127- )
88+ default_python_version = _compute_default_python_version (module_ctx )
12889
12990 seen_versions = {}
13091 for mod in module_ctx .modules :
@@ -152,13 +113,7 @@ def parse_modules(*, module_ctx, logger, _fail = fail):
152113 # * rules_python needs to set a soft default in case the root module doesn't,
153114 # e.g. if the root module doesn't use Python itself.
154115 # * The root module is allowed to override the rules_python default.
155- if default_python_version :
156- is_default = default_python_version == toolchain_version
157- if toolchain_attr .is_default and not is_default :
158- fail ("The 'is_default' attribute doesn't work if you set " +
159- "the default Python version with the `defaults` tag." )
160- else :
161- is_default = toolchain_attr .is_default
116+ is_default = default_python_version == toolchain_version
162117
163118 # Also only the root module should be able to decide ignore_root_user_error.
164119 # Modules being depended upon don't know the final environment, so they aren't
@@ -169,15 +124,15 @@ def parse_modules(*, module_ctx, logger, _fail = fail):
169124 fail ("Toolchains in the root module must have consistent 'ignore_root_user_error' attributes" )
170125
171126 ignore_root_user_error = toolchain_attr .ignore_root_user_error
172- elif mod .name == "rules_python" and not default_toolchain and not default_python_version :
173- # We don't do the len() check because we want the default that rules_python
174- # sets to be clearly visible.
175- is_default = toolchain_attr . is_default
127+ elif mod .name == "rules_python" and not default_toolchain :
128+ # This branch handles when the root module doesn't declare a
129+ # Python toolchain
130+ is_default = default_python_version == toolchain_version
176131 else :
177132 is_default = False
178133
179134 if is_default and default_toolchain != None :
180- _fail_multiple_default_toolchains (
135+ _fail_multiple_default_toolchains_chosen (
181136 first = default_toolchain .name ,
182137 second = toolchain_name ,
183138 )
@@ -577,14 +532,24 @@ def _fail_multiple_defaults_python_version_env(first, second):
577532 second = second ,
578533 ))
579534
580- def _fail_multiple_default_toolchains (first , second ):
535+ def _fail_multiple_default_toolchains_chosen (first , second ):
581536 fail (("Multiple default toolchains: only one toolchain " +
582- "can have is_default=True . First default " +
537+ "can be chosen as a default . First default " +
583538 "was toolchain '{first}'. Second was '{second}'" ).format (
584539 first = first ,
585540 second = second ,
586541 ))
587542
543+ def _fail_multiple_default_toolchains_in_module (mod , toolchain_attrs ):
544+ fail (("Multiple default toolchains: only one toolchain " +
545+ "can have is_default=True.\n " +
546+ "Module '{module}' contains {count} toolchains with " +
547+ "is_default=True: {versions}" ).format (
548+ module = mod .name ,
549+ count = len (toolchain_attrs ),
550+ versions = ", " .join (sorted ([v .python_version for v in toolchain_attrs ])),
551+ ))
552+
588553def _validate_version (version_str , * , _fail = fail ):
589554 v = version .parse (version_str , strict = True , _fail = _fail )
590555 if v == None :
@@ -880,6 +845,72 @@ def _get_toolchain_config(*, modules, _fail = fail):
880845 register_all_versions = register_all_versions ,
881846 )
882847
848+ def _compute_default_python_version (mctx ):
849+ default_python_version = None
850+ for mod in mctx .modules :
851+ # Only the root module and rules_python are allowed to specify the default
852+ # toolchain for a couple reasons:
853+ # * It prevents submodules from specifying different defaults and only
854+ # one of them winning.
855+ # * rules_python needs to set a soft default in case the root module doesn't,
856+ # e.g. if the root module doesn't use Python itself.
857+ # * The root module is allowed to override the rules_python default.
858+ if not (mod .is_root or mod .name == "rules_python" ):
859+ continue
860+
861+ defaults_attr_structs = _create_defaults_attr_structs (mod = mod )
862+ default_python_version_env = None
863+ default_python_version_file = None
864+
865+ for defaults_attr in defaults_attr_structs :
866+ default_python_version = _one_or_the_same (
867+ default_python_version ,
868+ defaults_attr .python_version ,
869+ onerror = _fail_multiple_defaults_python_version ,
870+ )
871+ default_python_version_env = _one_or_the_same (
872+ default_python_version_env ,
873+ defaults_attr .python_version_env ,
874+ onerror = _fail_multiple_defaults_python_version_env ,
875+ )
876+ default_python_version_file = _one_or_the_same (
877+ default_python_version_file ,
878+ defaults_attr .python_version_file ,
879+ onerror = _fail_multiple_defaults_python_version_file ,
880+ )
881+ if default_python_version_file :
882+ default_python_version = _one_or_the_same (
883+ default_python_version ,
884+ mctx .read (default_python_version_file , watch = "yes" ).strip (),
885+ )
886+ if default_python_version_env :
887+ default_python_version = mctx .getenv (
888+ default_python_version_env ,
889+ default_python_version ,
890+ )
891+
892+ if default_python_version :
893+ break
894+
895+ # Otherwise, look at legacy python.toolchain() calls for a default
896+ toolchain_attrs = mod .tags .toolchain
897+
898+ # Convenience: if one python.toolchain() call exists, treat it as
899+ # the default.
900+ if len (toolchain_attrs ) == 1 :
901+ default_python_version = toolchain_attrs [0 ].python_version
902+ else :
903+ sets_default = [v for v in toolchain_attrs if v .is_default ]
904+ if len (sets_default ) == 1 :
905+ default_python_version = sets_default [0 ].python_version
906+ elif len (sets_default ) > 1 :
907+ _fail_multiple_default_toolchains_in_module (mod , toolchain_attrs )
908+
909+ if default_python_version :
910+ break
911+
912+ return default_python_version
913+
883914def _create_defaults_attr_structs (* , mod ):
884915 arg_structs = []
885916
@@ -915,7 +946,11 @@ def _create_toolchain_attr_structs(*, mod, config, seen_versions):
915946
916947 return arg_structs
917948
918- def _create_toolchain_attrs_struct (* , tag = None , python_version = None , toolchain_tag_count = None ):
949+ def _create_toolchain_attrs_struct (
950+ * ,
951+ tag = None ,
952+ python_version = None ,
953+ toolchain_tag_count = None ):
919954 if tag and python_version :
920955 fail ("Only one of tag and python version can be specified" )
921956 if tag :
0 commit comments