Skip to content

Commit 619fa0c

Browse files
authored
fix(bzlmod): allow modules to register the same toolchain as rules_python's default (#1642)
This fixes a bug where, if a module tries to register a non-default toolchain with the same version as rules_python's default toolchain, an error would occur. This happened because the earlier (non-default) toolchain caused the later (default) toolchain to be entirely skipped, and then no default toolchain would be seen. This most affects intermediary modules that need to register a toolchain, but can't specify a default one. To fix, just skip creating and registering the duplicate toolchain, but still check its default-ness to determine if it's the default toolchain. Fixes #1638
1 parent 4c2d7d9 commit 619fa0c

File tree

2 files changed

+60
-41
lines changed

2 files changed

+60
-41
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ A brief description of the categories of changes:
5252
specifying a local system interpreter.
5353
* (bzlmod pip.parse) Requirements files with duplicate entries for the same
5454
package (e.g. one for the package, one for an extra) now work.
55+
* (bzlmod python.toolchain) Submodules can now (re)register the Python version
56+
that rules_python has set as the default.
57+
([#1638](https://github.com/bazelbuild/rules_python/issues/1638))
5558
* (whl_library) Actually use the provided patches to patch the whl_library.
5659
On Windows the patching may result in files with CRLF line endings, as a result
5760
the RECORD file consistency requirement is lifted and now a warning is emitted

python/private/bzlmod/python.bzl

Lines changed: 57 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -43,31 +43,33 @@ def _left_pad_zero(index, length):
4343
def _print_warn(msg):
4444
print("WARNING:", msg)
4545

46-
def _python_register_toolchains(name, toolchain_attr, version_constraint):
46+
def _python_register_toolchains(name, toolchain_attr, module):
4747
"""Calls python_register_toolchains and returns a struct used to collect the toolchains.
4848
"""
4949
python_register_toolchains(
5050
name = name,
5151
python_version = toolchain_attr.python_version,
5252
register_coverage_tool = toolchain_attr.configure_coverage_tool,
5353
ignore_root_user_error = toolchain_attr.ignore_root_user_error,
54-
set_python_version_constraint = version_constraint,
5554
)
5655
return struct(
5756
python_version = toolchain_attr.python_version,
58-
set_python_version_constraint = str(version_constraint),
5957
name = name,
58+
module = struct(name = module.name, is_root = module.is_root),
6059
)
6160

6261
def _python_impl(module_ctx):
63-
# The toolchain info structs to register, in the order to register them in.
62+
# The toolchain_info structs to register, in the order to register them in.
63+
# NOTE: The last element is special: it is treated as the default toolchain,
64+
# so there is special handling to ensure the last entry is the correct one.
6465
toolchains = []
6566

6667
# We store the default toolchain separately to ensure it is the last
6768
# toolchain added to toolchains.
69+
# This is a toolchain_info struct.
6870
default_toolchain = None
6971

70-
# Map of string Major.Minor to the toolchain name and module name
72+
# Map of string Major.Minor to the toolchain_info struct
7173
global_toolchain_versions = {}
7274

7375
for mod in module_ctx.modules:
@@ -82,28 +84,6 @@ def _python_impl(module_ctx):
8284
_fail_duplicate_module_toolchain_version(toolchain_version, mod.name)
8385
module_toolchain_versions.append(toolchain_version)
8486

85-
# Ignore version collisions in the global scope because there isn't
86-
# much else that can be done. Modules don't know and can't control
87-
# what other modules do, so the first in the dependency graph wins.
88-
if toolchain_version in global_toolchain_versions:
89-
# If the python version is explicitly provided by the root
90-
# module, they should not be warned for choosing the same
91-
# version that rules_python provides as default.
92-
first = global_toolchain_versions[toolchain_version]
93-
if mod.name != "rules_python" or not first.is_root:
94-
_warn_duplicate_global_toolchain_version(
95-
toolchain_version,
96-
first = first,
97-
second_toolchain_name = toolchain_name,
98-
second_module_name = mod.name,
99-
)
100-
continue
101-
global_toolchain_versions[toolchain_version] = struct(
102-
toolchain_name = toolchain_name,
103-
module_name = mod.name,
104-
is_root = mod.is_root,
105-
)
106-
10787
# Only the root module and rules_python are allowed to specify the default
10888
# toolchain for a couple reasons:
10989
# * It prevents submodules from specifying different defaults and only
@@ -121,29 +101,60 @@ def _python_impl(module_ctx):
121101
else:
122102
is_default = False
123103

124-
# We have already found one default toolchain, and we can only have
125-
# one.
126104
if is_default and default_toolchain != None:
127105
_fail_multiple_default_toolchains(
128106
first = default_toolchain.name,
129107
second = toolchain_name,
130108
)
131109

132-
toolchain_info = _python_register_toolchains(
133-
toolchain_name,
134-
toolchain_attr,
135-
version_constraint = not is_default,
136-
)
110+
# Ignore version collisions in the global scope because there isn't
111+
# much else that can be done. Modules don't know and can't control
112+
# what other modules do, so the first in the dependency graph wins.
113+
if toolchain_version in global_toolchain_versions:
114+
# If the python version is explicitly provided by the root
115+
# module, they should not be warned for choosing the same
116+
# version that rules_python provides as default.
117+
first = global_toolchain_versions[toolchain_version]
118+
if mod.name != "rules_python" or not first.module.is_root:
119+
_warn_duplicate_global_toolchain_version(
120+
toolchain_version,
121+
first = first,
122+
second_toolchain_name = toolchain_name,
123+
second_module_name = mod.name,
124+
)
125+
toolchain_info = None
126+
else:
127+
toolchain_info = _python_register_toolchains(
128+
toolchain_name,
129+
toolchain_attr,
130+
module = mod,
131+
)
132+
global_toolchain_versions[toolchain_version] = toolchain_info
137133

138134
if is_default:
139-
default_toolchain = toolchain_info
140-
else:
135+
# This toolchain is setting the default, but the actual
136+
# registration was performed previously, by a different module.
137+
if toolchain_info == None:
138+
default_toolchain = global_toolchain_versions[toolchain_version]
139+
140+
# Remove it because later code will add it at the end to
141+
# ensure it is last in the list.
142+
toolchains.remove(default_toolchain)
143+
else:
144+
default_toolchain = toolchain_info
145+
elif toolchain_info:
141146
toolchains.append(toolchain_info)
142147

143148
# A default toolchain is required so that the non-version-specific rules
144149
# are able to match a toolchain.
145150
if default_toolchain == None:
146151
fail("No default Python toolchain configured. Is rules_python missing `is_default=True`?")
152+
elif default_toolchain.python_version not in global_toolchain_versions:
153+
fail('Default version "{python_version}" selected by module ' +
154+
'"{module_name}", but no toolchain with that version registered'.format(
155+
python_version = default_toolchain.python_version,
156+
module_name = default_toolchain.module.name,
157+
))
147158

148159
# The last toolchain in the BUILD file is set as the default
149160
# toolchain. We need the default last.
@@ -162,7 +173,12 @@ def _python_impl(module_ctx):
162173
for index, toolchain in enumerate(toolchains)
163174
],
164175
toolchain_python_versions = [t.python_version for t in toolchains],
165-
toolchain_set_python_version_constraints = [t.set_python_version_constraint for t in toolchains],
176+
# The last toolchain is the default; it can't have version constraints
177+
# Despite the implication of the arg name, the values are strs, not bools
178+
toolchain_set_python_version_constraints = [
179+
"True" if i != len(toolchains) - 1 else "False"
180+
for i in range(len(toolchains))
181+
],
166182
toolchain_user_repository_names = [t.name for t in toolchains],
167183
)
168184

@@ -171,8 +187,8 @@ def _python_impl(module_ctx):
171187
multi_toolchain_aliases(
172188
name = "python_versions",
173189
python_versions = {
174-
version: entry.toolchain_name
175-
for version, entry in global_toolchain_versions.items()
190+
version: toolchain.name
191+
for version, toolchain in global_toolchain_versions.items()
176192
},
177193
)
178194

@@ -189,8 +205,8 @@ def _warn_duplicate_global_toolchain_version(version, first, second_toolchain_na
189205
"Toolchain '{first_toolchain}' from module '{first_module}' " +
190206
"already registered Python version {version} and has precedence"
191207
).format(
192-
first_toolchain = first.toolchain_name,
193-
first_module = first.module_name,
208+
first_toolchain = first.name,
209+
first_module = first.module.name,
194210
second_module = second_module_name,
195211
second_toolchain = second_toolchain_name,
196212
version = version,

0 commit comments

Comments
 (0)