1616
1717load ("//python:repositories.bzl" , "python_register_toolchains" )
1818load ("//python/private:toolchains_repo.bzl" , "multi_toolchain_aliases" )
19+ load ("//python/private:util.bzl" , "IS_BAZEL_6_4_OR_HIGHER" )
1920load (":pythons_hub.bzl" , "hub_repo" )
2021
2122# This limit can be increased essentially arbitrarily, but doing so will cause a rebuild of all
@@ -43,14 +44,14 @@ def _left_pad_zero(index, length):
4344def _print_warn (msg ):
4445 print ("WARNING:" , msg )
4546
46- def _python_register_toolchains (name , toolchain_attr , module ):
47+ def _python_register_toolchains (name , toolchain_attr , module , ignore_root_user_error ):
4748 """Calls python_register_toolchains and returns a struct used to collect the toolchains.
4849 """
4950 python_register_toolchains (
5051 name = name ,
5152 python_version = toolchain_attr .python_version ,
5253 register_coverage_tool = toolchain_attr .configure_coverage_tool ,
53- ignore_root_user_error = toolchain_attr . ignore_root_user_error ,
54+ ignore_root_user_error = ignore_root_user_error ,
5455 )
5556 return struct (
5657 python_version = toolchain_attr .python_version ,
@@ -59,6 +60,13 @@ def _python_register_toolchains(name, toolchain_attr, module):
5960 )
6061
6162def _python_impl (module_ctx ):
63+ if module_ctx .os .environ .get ("RULES_PYTHON_BZLMOD_DEBUG" , "0" ) == "1" :
64+ debug_info = {
65+ "toolchains_registered" : [],
66+ }
67+ else :
68+ debug_info = None
69+
6270 # The toolchain_info structs to register, in the order to register them in.
6371 # NOTE: The last element is special: it is treated as the default toolchain,
6472 # so there is special handling to ensure the last entry is the correct one.
@@ -72,6 +80,13 @@ def _python_impl(module_ctx):
7280 # Map of string Major.Minor to the toolchain_info struct
7381 global_toolchain_versions = {}
7482
83+ ignore_root_user_error = None
84+
85+ # if the root module does not register any toolchain then the
86+ # ignore_root_user_error takes its default value: False
87+ if not module_ctx .modules [0 ].tags .toolchain :
88+ ignore_root_user_error = False
89+
7590 for mod in module_ctx .modules :
7691 module_toolchain_versions = []
7792
@@ -84,16 +99,27 @@ def _python_impl(module_ctx):
8499 _fail_duplicate_module_toolchain_version (toolchain_version , mod .name )
85100 module_toolchain_versions .append (toolchain_version )
86101
87- # Only the root module and rules_python are allowed to specify the default
88- # toolchain for a couple reasons:
89- # * It prevents submodules from specifying different defaults and only
90- # one of them winning.
91- # * rules_python needs to set a soft default in case the root module doesn't,
92- # e.g. if the root module doesn't use Python itself.
93- # * The root module is allowed to override the rules_python default.
94102 if mod .is_root :
103+ # Only the root module and rules_python are allowed to specify the default
104+ # toolchain for a couple reasons:
105+ # * It prevents submodules from specifying different defaults and only
106+ # one of them winning.
107+ # * rules_python needs to set a soft default in case the root module doesn't,
108+ # e.g. if the root module doesn't use Python itself.
109+ # * The root module is allowed to override the rules_python default.
110+
95111 # A single toolchain is treated as the default because it's unambiguous.
96112 is_default = toolchain_attr .is_default or len (mod .tags .toolchain ) == 1
113+
114+ # Also only the root module should be able to decide ignore_root_user_error.
115+ # Modules being depended upon don't know the final environment, so they aren't
116+ # in the right position to know or decide what the correct setting is.
117+
118+ # If an inconsistency in the ignore_root_user_error among multiple toolchains is detected, fail.
119+ if ignore_root_user_error != None and toolchain_attr .ignore_root_user_error != ignore_root_user_error :
120+ fail ("Toolchains in the root module must have consistent 'ignore_root_user_error' attributes" )
121+
122+ ignore_root_user_error = toolchain_attr .ignore_root_user_error
97123 elif mod .name == "rules_python" and not default_toolchain :
98124 # We don't do the len() check because we want the default that rules_python
99125 # sets to be clearly visible.
@@ -128,8 +154,14 @@ def _python_impl(module_ctx):
128154 toolchain_name ,
129155 toolchain_attr ,
130156 module = mod ,
157+ ignore_root_user_error = ignore_root_user_error ,
131158 )
132159 global_toolchain_versions [toolchain_version ] = toolchain_info
160+ if debug_info :
161+ debug_info ["toolchains_registered" ].append ({
162+ "ignore_root_user_error" : ignore_root_user_error ,
163+ "name" : toolchain_name ,
164+ })
133165
134166 if is_default :
135167 # This toolchain is setting the default, but the actual
@@ -192,6 +224,12 @@ def _python_impl(module_ctx):
192224 },
193225 )
194226
227+ if debug_info != None :
228+ _debug_repo (
229+ name = "rules_python_bzlmod_debug" ,
230+ debug_info = json .encode_indent (debug_info ),
231+ )
232+
195233def _fail_duplicate_module_toolchain_version (version , module ):
196234 fail (("Duplicate module toolchain version: module '{module}' attempted " +
197235 "to use version '{version}' multiple times in itself" ).format (
@@ -220,6 +258,14 @@ def _fail_multiple_default_toolchains(first, second):
220258 second = second ,
221259 ))
222260
261+ def _get_bazel_version_specific_kwargs ():
262+ kwargs = {}
263+
264+ if IS_BAZEL_6_4_OR_HIGHER :
265+ kwargs ["environ" ] = ["RULES_PYTHON_BZLMOD_DEBUG" ]
266+
267+ return kwargs
268+
223269python = module_extension (
224270 doc = """Bzlmod extension that is used to register Python toolchains.
225271""" ,
@@ -263,7 +309,16 @@ A toolchain's repository name uses the format `python_{major}_{minor}`, e.g.
263309 ),
264310 "ignore_root_user_error" : attr .bool (
265311 default = False ,
266- doc = "Whether the check for root should be ignored or not. This causes cache misses with .pyc files." ,
312+ doc = """\
313+ If False, the Python runtime installation will be made read only. This improves
314+ the ability for Bazel to cache it, but prevents the interpreter from creating
315+ pyc files for the standard library dynamically at runtime as they are loaded.
316+
317+ If True, the Python runtime installation is read-write. This allows the
318+ interpreter to create pyc files for the standard library, but, because they are
319+ created as needed, it adversely affects Bazel's ability to cache the runtime and
320+ can result in spurious build failures.
321+ """ ,
267322 mandatory = False ,
268323 ),
269324 "is_default" : attr .bool (
@@ -279,4 +334,23 @@ A toolchain's repository name uses the format `python_{major}_{minor}`, e.g.
279334 },
280335 ),
281336 },
337+ ** _get_bazel_version_specific_kwargs ()
338+ )
339+
340+ _DEBUG_BUILD_CONTENT = """
341+ package(
342+ default_visibility = ["//visibility:public"],
343+ )
344+ exports_files(["debug_info.json"])
345+ """
346+
347+ def _debug_repo_impl (repo_ctx ):
348+ repo_ctx .file ("BUILD.bazel" , _DEBUG_BUILD_CONTENT )
349+ repo_ctx .file ("debug_info.json" , repo_ctx .attr .debug_info )
350+
351+ _debug_repo = repository_rule (
352+ implementation = _debug_repo_impl ,
353+ attrs = {
354+ "debug_info" : attr .string (),
355+ },
282356)
0 commit comments