Skip to content

Commit afdbedd

Browse files
authored
fix(bzlmod): give precedence to the first seen versioned toolchain (bazel-contrib#1244)
This fixes an issue where the last submodule, instead of the first, was given precedence when creating versioned toolchains. To fix, when creating the map of versioned toolchains, if a version is already defined, then a subsequent usage is ignored. A warning is emitted when this occurs. This also fixes a similar problem that can occur to the root module. If the root module uses a particular version marked as the default, and is using the versioned rules, and a submodule also uses that same version, then the submodule's toolchain would be used. This happened because the root module's toolchain would be moved last, so its versioned rules would match the submodule's versioned toolchain. This also does some cleanup and refactoring to: * Compute the toolchains in one loop iteration * Give more informative error messages * Reject duplicate names within a module, even for the non-root module. * Reject duplicate versions within a module.
1 parent 4c365e7 commit afdbedd

File tree

1 file changed

+133
-70
lines changed

1 file changed

+133
-70
lines changed

python/extensions/python.bzl

Lines changed: 133 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -60,82 +60,97 @@ def _python_register_toolchains(toolchain_attr, version_constraint):
6060
)
6161

6262
def _python_impl(module_ctx):
63-
# Use to store all of the toolchains
63+
# The toolchain info structs to register, in the order to register them in.
6464
toolchains = []
6565

66-
# Used to check if toolchains already exist
67-
toolchain_names = []
68-
69-
# Used to store toolchains that are in sub modules.
70-
sub_toolchains_map = {}
66+
# We store the default toolchain separately to ensure it is the last
67+
# toolchain added to toolchains.
7168
default_toolchain = None
72-
python_versions = {}
69+
70+
# Map of toolchain name to registering module
71+
global_toolchain_names = {}
72+
73+
# Map of string Major.Minor to the toolchain name and module name
74+
global_toolchain_versions = {}
7375

7476
for mod in module_ctx.modules:
77+
module_toolchain_names = []
78+
module_toolchain_versions = []
79+
7580
for toolchain_attr in mod.tags.toolchain:
76-
# If we are in the root module we always register the toolchain.
77-
# We wait to register the default toolchain till the end.
81+
toolchain_name = toolchain_attr.name
82+
83+
# Duplicate names within a module indicate a misconfigured module.
84+
if toolchain_name in module_toolchain_names:
85+
_fail_duplicate_module_toolchain_name(mod.name, toolchain_name)
86+
module_toolchain_names.append(toolchain_name)
87+
88+
# Ignore name collisions in the global scope because there isn't
89+
# much else that can be done. Modules don't know and can't control
90+
# what other modules do, so the first in the dependency graph wins.
91+
if toolchain_name in global_toolchain_names:
92+
_warn_duplicate_global_toolchain_name(
93+
toolchain_name,
94+
first_module = global_toolchain_names[toolchain_name],
95+
second_module = mod.name,
96+
)
97+
continue
98+
global_toolchain_names[toolchain_name] = mod.name
99+
100+
# Duplicate versions within a module indicate a misconfigured module.
101+
toolchain_version = toolchain_attr.python_version
102+
if toolchain_version in module_toolchain_versions:
103+
_fail_duplicate_module_toolchain_version(toolchain_version, mod.name)
104+
module_toolchain_versions.append(toolchain_version)
105+
106+
# Ignore version collisions in the global scope because there isn't
107+
# much else that can be done. Modules don't know and can't control
108+
# what other modules do, so the first in the dependency graph wins.
109+
if toolchain_version in global_toolchain_versions:
110+
_warn_duplicate_global_toolchain_version(
111+
toolchain_version,
112+
first = global_toolchain_versions[toolchain_version],
113+
second_toolchain_name = toolchain_name,
114+
second_module_name = mod.name,
115+
)
116+
continue
117+
global_toolchain_versions[toolchain_version] = struct(
118+
toolchain_name = toolchain_name,
119+
module_name = mod.name,
120+
)
121+
122+
# Only the root module is allowed to set the default toolchain
123+
# to prevent submodules from clobbering each other.
124+
# A single toolchain in the root module is treated as the default
125+
# because it's unambigiuous.
78126
if mod.is_root:
79-
if toolchain_attr.name in toolchain_names:
80-
fail("""We found more than one toolchain that is named: {}.
81-
All toolchains must have an unique name.""".format(toolchain_attr.name))
82-
83-
toolchain_names.append(toolchain_attr.name)
84-
85-
# If we have the default version or we only have one toolchain
86-
# in the root module we set the toolchain as the default toolchain.
87-
if toolchain_attr.is_default or len(mod.tags.toolchain) == 1:
88-
# We have already found one default toolchain, and we can
89-
# only have one.
90-
if default_toolchain != None:
91-
fail("""We found more than one toolchain that is marked
92-
as the default version. Only set one toolchain with is_default set as
93-
True. The toolchain is named: {}""".format(toolchain_attr.name))
94-
95-
# We store the default toolchain to have it
96-
# as the last toolchain added to toolchains
97-
default_toolchain = _python_register_toolchains(
98-
toolchain_attr,
99-
version_constraint = False,
100-
)
101-
python_versions[toolchain_attr.python_version] = toolchain_attr.name
102-
continue
103-
104-
toolchains.append(
105-
_python_register_toolchains(
106-
toolchain_attr,
107-
version_constraint = True,
108-
),
127+
is_default = toolchain_attr.is_default or len(mod.tags.toolchain) == 1
128+
else:
129+
is_default = False
130+
131+
# We have already found one default toolchain, and we can only have
132+
# one.
133+
if is_default and default_toolchain != None:
134+
_fail_multiple_default_toolchains(
135+
first = default_toolchain.name,
136+
second = toolchain_name,
109137
)
110-
python_versions[toolchain_attr.python_version] = toolchain_attr.name
138+
139+
toolchain_info = _python_register_toolchains(
140+
toolchain_attr,
141+
version_constraint = not is_default,
142+
)
143+
144+
if is_default:
145+
default_toolchain = toolchain_info
111146
else:
112-
# We add the toolchain to a map, and we later create the
113-
# toolchain if the root module does not have a toolchain with
114-
# the same name. We have to loop through all of the modules to
115-
# ensure that we get a full list of the root toolchains.
116-
sub_toolchains_map[toolchain_attr.name] = toolchain_attr
147+
toolchains.append(toolchain_info)
117148

118-
# We did not find a default toolchain so we fail.
149+
# A default toolchain is required so that the non-version-specific rules
150+
# are able to match a toolchain.
119151
if default_toolchain == None:
120-
fail("""Unable to find a default toolchain in the root module.
121-
Please define a toolchain that has is_version set to True.""")
122-
123-
# Create the toolchains in the submodule(s).
124-
for name, toolchain_attr in sub_toolchains_map.items():
125-
# We cannot have a toolchain in a sub module that has the same name of
126-
# a toolchain in the root module. This will cause name clashing.
127-
if name in toolchain_names:
128-
_print_warn("""Not creating the toolchain from sub module, with the name {}. The root
129-
module has a toolchain of the same name.""".format(toolchain_attr.name))
130-
continue
131-
toolchain_names.append(name)
132-
toolchains.append(
133-
_python_register_toolchains(
134-
toolchain_attr,
135-
version_constraint = True,
136-
),
137-
)
138-
python_versions[toolchain_attr.python_version] = toolchain_attr.name
152+
fail("No default toolchain found: exactly one toolchain must have " +
153+
"is_default=True set")
139154

140155
# The last toolchain in the BUILD file is set as the default
141156
# toolchain. We need the default last.
@@ -161,9 +176,57 @@ Please define a toolchain that has is_version set to True.""")
161176
# and py_binary
162177
multi_toolchain_aliases(
163178
name = "python_aliases",
164-
python_versions = python_versions,
179+
python_versions = {
180+
version: entry.toolchain_name
181+
for version, entry in global_toolchain_versions.items()
182+
},
165183
)
166184

185+
def _fail_duplicate_module_toolchain_name(module_name, toolchain_name):
186+
fail(("Duplicate module toolchain name: module '{module}' attempted " +
187+
"to use the name '{toolchain}' multiple times in itself").format(
188+
toolchain = toolchain_name,
189+
module = module_name,
190+
))
191+
192+
def _warn_duplicate_global_toolchain_name(name, first_module, second_module):
193+
_print_warn((
194+
"Ignoring toolchain '{name}' from module '{second_module}': " +
195+
"Toolchain with the same name from module '{first_module}' has precedence"
196+
).format(
197+
name = name,
198+
first_module = first_module,
199+
second_module = second_module,
200+
))
201+
202+
def _fail_duplicate_module_toolchain_version(version, module):
203+
fail(("Duplicate module toolchain version: module '{module}' attempted " +
204+
"to use version '{version}' multiple times in itself").format(
205+
version = version,
206+
module = module,
207+
))
208+
209+
def _warn_duplicate_global_toolchain_version(version, first, second_toolchain_name, second_module_name):
210+
_print_warn((
211+
"Ignoring toolchain '{second_toolchain}' from module '{second_module}': " +
212+
"Toolchain '{first_toolchain}' from module '{first_module}' " +
213+
"already registered Python version {version} and has precedence"
214+
).format(
215+
first_toolchain = first.toolchain_name,
216+
first_module = first.module_name,
217+
second_module = second_module_name,
218+
second_toolchain = second_toolchain_name,
219+
version = version,
220+
))
221+
222+
def _fail_multiple_default_toolchains(first, second):
223+
fail(("Multiple default toolchains: only one toolchain " +
224+
"can have is_default=True. First default " +
225+
"was toolchain '{first}'. Second was '{second}'").format(
226+
first = first,
227+
second = second,
228+
))
229+
167230
python = module_extension(
168231
doc = """Bzlmod extension that is used to register Python toolchains.
169232
""",
@@ -184,15 +247,15 @@ Toolchains in Sub Modules
184247
It will create a toolchain that is in a sub module, if the toolchain
185248
of the same name does not exist in the root module. The extension stops name
186249
clashing between toolchains in the root module and toolchains in sub modules.
187-
You cannot configure more than one toolchain as the default toolchain.
250+
You cannot configure more than one toolchain as the default toolchain.
188251
189252
Toolchain set as the default version
190253
191-
This extension will not create a toolchain that exists in a sub module,
254+
This extension will not create a toolchain that exists in a sub module,
192255
if the sub module toolchain is marked as the default version. If you have
193256
more than one toolchain in your root module, you need to set one of the
194-
toolchains as the default version. If there is only one toolchain it
195-
is set as the default toolchain.
257+
toolchains as the default version. If there is only one toolchain it
258+
is set as the default toolchain.
196259
""",
197260
attrs = {
198261
"configure_coverage_tool": attr.bool(

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy