16
16
17
17
load ("//python:repositories.bzl" , "python_register_toolchains" )
18
18
load ("//python/private:toolchains_repo.bzl" , "multi_toolchain_aliases" )
19
+ load ("//python/private:util.bzl" , "IS_BAZEL_6_4_OR_HIGHER" )
19
20
load (":pythons_hub.bzl" , "hub_repo" )
20
21
21
22
# 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):
43
44
def _print_warn (msg ):
44
45
print ("WARNING:" , msg )
45
46
46
- def _python_register_toolchains (name , toolchain_attr , module ):
47
+ def _python_register_toolchains (name , toolchain_attr , module , ignore_root_user_error ):
47
48
"""Calls python_register_toolchains and returns a struct used to collect the toolchains.
48
49
"""
49
50
python_register_toolchains (
50
51
name = name ,
51
52
python_version = toolchain_attr .python_version ,
52
53
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 ,
54
55
)
55
56
return struct (
56
57
python_version = toolchain_attr .python_version ,
@@ -59,6 +60,13 @@ def _python_register_toolchains(name, toolchain_attr, module):
59
60
)
60
61
61
62
def _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
+
62
70
# The toolchain_info structs to register, in the order to register them in.
63
71
# NOTE: The last element is special: it is treated as the default toolchain,
64
72
# so there is special handling to ensure the last entry is the correct one.
@@ -72,6 +80,13 @@ def _python_impl(module_ctx):
72
80
# Map of string Major.Minor to the toolchain_info struct
73
81
global_toolchain_versions = {}
74
82
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
+
75
90
for mod in module_ctx .modules :
76
91
module_toolchain_versions = []
77
92
@@ -84,16 +99,27 @@ def _python_impl(module_ctx):
84
99
_fail_duplicate_module_toolchain_version (toolchain_version , mod .name )
85
100
module_toolchain_versions .append (toolchain_version )
86
101
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.
94
102
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
+
95
111
# A single toolchain is treated as the default because it's unambiguous.
96
112
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
97
123
elif mod .name == "rules_python" and not default_toolchain :
98
124
# We don't do the len() check because we want the default that rules_python
99
125
# sets to be clearly visible.
@@ -128,8 +154,14 @@ def _python_impl(module_ctx):
128
154
toolchain_name ,
129
155
toolchain_attr ,
130
156
module = mod ,
157
+ ignore_root_user_error = ignore_root_user_error ,
131
158
)
132
159
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
+ })
133
165
134
166
if is_default :
135
167
# This toolchain is setting the default, but the actual
@@ -192,6 +224,12 @@ def _python_impl(module_ctx):
192
224
},
193
225
)
194
226
227
+ if debug_info != None :
228
+ _debug_repo (
229
+ name = "rules_python_bzlmod_debug" ,
230
+ debug_info = json .encode_indent (debug_info ),
231
+ )
232
+
195
233
def _fail_duplicate_module_toolchain_version (version , module ):
196
234
fail (("Duplicate module toolchain version: module '{module}' attempted " +
197
235
"to use version '{version}' multiple times in itself" ).format (
@@ -220,6 +258,14 @@ def _fail_multiple_default_toolchains(first, second):
220
258
second = second ,
221
259
))
222
260
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
+
223
269
python = module_extension (
224
270
doc = """Bzlmod extension that is used to register Python toolchains.
225
271
""" ,
@@ -263,7 +309,16 @@ A toolchain's repository name uses the format `python_{major}_{minor}`, e.g.
263
309
),
264
310
"ignore_root_user_error" : attr .bool (
265
311
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
+ """ ,
267
322
mandatory = False ,
268
323
),
269
324
"is_default" : attr .bool (
@@ -279,4 +334,23 @@ A toolchain's repository name uses the format `python_{major}_{minor}`, e.g.
279
334
},
280
335
),
281
336
},
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
+ },
282
356
)
0 commit comments