Skip to content

Commit fce7354

Browse files
authored
refactor(bzlmod): generate fewer targets for pip_config_settings (#1974)
Remove the `python_version` from `flag_values` and reduces the number of targets we create by a lot because we no longer need to create all of the micro version combinations to also match when we want to match e.g. 3.10 on a particular platform by using a trick I've learned in #1968: ```starlark select({ "is_python_3.x": "my_config_setting" "//conditions:default": "is_python_3.x" }) ``` Then further optimization was done on how we create the aliases and config settings and optimizing the usage of `pip_whl` flag finally reduced the internal number targets. The number config settings targets in `//tests/private/pip_config_settings/...` where we have multiple python versions has changed: * `:is_*` - from **995** to **996** (the extra addition being is_python_default). * `:_.*is_*` - from **28272** to **2480** (just python version) and then to **496** (final). Work towards #260
1 parent 7f1d59e commit fce7354

File tree

6 files changed

+149
-150
lines changed

6 files changed

+149
-150
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ A brief description of the categories of changes:
2626

2727
### Changed
2828
* `protobuf`/`com_google_protobuf` dependency bumped to `v24.4`
29+
* (bzlmod): optimize the creation of config settings used in pip to
30+
reduce the total number of targets in the hub repo.
2931

3032
### Fixed
3133
* (bzlmod): Targets in `all_requirements` now use the same form as targets returned by the `requirement` macro.

python/config_settings/BUILD.bazel

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,33 @@ string_flag(
8888
visibility = ["//visibility:public"],
8989
)
9090

91+
config_setting(
92+
name = "is_pip_whl_auto",
93+
flag_values = {
94+
":pip_whl": UseWhlFlag.AUTO,
95+
},
96+
# NOTE: Only public because it is used in pip hub repos.
97+
visibility = ["//visibility:public"],
98+
)
99+
100+
config_setting(
101+
name = "is_pip_whl_no",
102+
flag_values = {
103+
":pip_whl": UseWhlFlag.NO,
104+
},
105+
# NOTE: Only public because it is used in pip hub repos.
106+
visibility = ["//visibility:public"],
107+
)
108+
109+
config_setting(
110+
name = "is_pip_whl_only",
111+
flag_values = {
112+
":pip_whl": UseWhlFlag.ONLY,
113+
},
114+
# NOTE: Only public because it is used in pip hub repos.
115+
visibility = ["//visibility:public"],
116+
)
117+
91118
string_flag(
92119
name = "pip_whl_osx_arch",
93120
build_setting_default = UniversalWhlFlag.ARCH,

python/private/config_settings.bzl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,14 @@ def construct_config_settings(name = None): # buildifier: disable=function-docs
181181
visibility = ["//visibility:public"],
182182
)
183183

184+
native.config_setting(
185+
name = "is_python_version_unset",
186+
flag_values = {
187+
Label("//python/config_settings:python_version"): "",
188+
},
189+
visibility = ["//visibility:public"],
190+
)
191+
184192
for version, matching_versions in VERSION_FLAG_VALUES.items():
185193
is_python_config_setting(
186194
name = "is_python_{}".format(version),

python/private/pip_config_settings.bzl

Lines changed: 107 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,10 @@ Note, that here the specialization of musl vs manylinux wheels is the same in
3939
order to ensure that the matching fails if the user requests for `musl` and we don't have it or vice versa.
4040
"""
4141

42-
load(":config_settings.bzl", "is_python_config_setting")
4342
load(
4443
":pip_flags.bzl",
4544
"INTERNAL_FLAGS",
4645
"UniversalWhlFlag",
47-
"UseWhlFlag",
4846
"WhlLibcFlag",
4947
)
5048

@@ -53,12 +51,14 @@ FLAGS = struct(
5351
f: str(Label("//python/config_settings:" + f))
5452
for f in [
5553
"python_version",
56-
"pip_whl",
5754
"pip_whl_glibc_version",
5855
"pip_whl_muslc_version",
5956
"pip_whl_osx_arch",
6057
"pip_whl_osx_version",
6158
"py_linux_libc",
59+
"is_pip_whl_no",
60+
"is_pip_whl_only",
61+
"is_pip_whl_auto",
6262
]
6363
}
6464
)
@@ -82,8 +82,7 @@ def pip_config_settings(
8282
target_platforms = [],
8383
name = None,
8484
visibility = None,
85-
alias_rule = None,
86-
config_setting_rule = None):
85+
native = native):
8786
"""Generate all of the pip config settings.
8887
8988
Args:
@@ -100,10 +99,9 @@ def pip_config_settings(
10099
constraint values for each condition.
101100
visibility (list[str], optional): The visibility to be passed to the
102101
exposed labels. All other labels will be private.
103-
alias_rule (rule): The alias rule to use for creating the
104-
objects. Can be overridden for unit tests reasons.
105-
config_setting_rule (rule): The config setting rule to use for creating the
106-
objects. Can be overridden for unit tests reasons.
102+
native (struct): The struct containing alias and config_setting rules
103+
to use for creating the objects. Can be overridden for unit tests
104+
reasons.
107105
"""
108106

109107
glibc_versions = [""] + glibc_versions
@@ -114,43 +112,25 @@ def pip_config_settings(
114112
for t in target_platforms
115113
]
116114

117-
alias_rule = alias_rule or native.alias
118-
119-
for version in python_versions:
120-
is_python = "is_python_{}".format(version)
121-
alias_rule(
115+
for python_version in [""] + python_versions:
116+
is_python = "is_python_{}".format(python_version or "version_unset")
117+
native.alias(
122118
name = is_python,
123119
actual = Label("//python/config_settings:" + is_python),
124120
visibility = visibility,
125121
)
126122

127-
for os, cpu in target_platforms:
128-
constraint_values = []
129-
suffix = ""
130-
if os:
131-
constraint_values.append("@platforms//os:" + os)
132-
suffix += "_" + os
133-
if cpu:
134-
constraint_values.append("@platforms//cpu:" + cpu)
135-
suffix += "_" + cpu
136-
137-
_sdist_config_setting(
138-
name = "sdist" + suffix,
139-
constraint_values = constraint_values,
140-
visibility = visibility,
141-
config_setting_rule = config_setting_rule,
142-
)
143-
for python_version in python_versions:
144-
_sdist_config_setting(
145-
name = "cp{}_sdist{}".format(python_version, suffix),
146-
python_version = python_version,
147-
constraint_values = constraint_values,
148-
visibility = visibility,
149-
config_setting_rule = config_setting_rule,
150-
)
151-
152-
for python_version in [""] + python_versions:
153-
_whl_config_settings(
123+
for os, cpu in target_platforms:
124+
constraint_values = []
125+
suffix = ""
126+
if os:
127+
constraint_values.append("@platforms//os:" + os)
128+
suffix += "_" + os
129+
if cpu:
130+
constraint_values.append("@platforms//cpu:" + cpu)
131+
suffix += "_" + cpu
132+
133+
_dist_config_settings(
154134
suffix = suffix,
155135
plat_flag_values = _plat_flag_values(
156136
os = os,
@@ -161,34 +141,45 @@ def pip_config_settings(
161141
),
162142
constraint_values = constraint_values,
163143
python_version = python_version,
144+
is_python = is_python,
164145
visibility = visibility,
165-
config_setting_rule = config_setting_rule,
146+
native = native,
166147
)
167148

168-
def _whl_config_settings(*, suffix, plat_flag_values, **kwargs):
169-
# With the following three we cover different per-version wheels
170-
python_version = kwargs.get("python_version")
171-
py = "cp{}_py".format(python_version) if python_version else "py"
172-
pycp = "cp{}_cp3x".format(python_version) if python_version else "cp3x"
173-
174-
flag_values = {}
175-
176-
for n, f in {
177-
"{}_none_any{}".format(py, suffix): None,
178-
"{}3_none_any{}".format(py, suffix): _flags.whl_py3,
179-
"{}3_abi3_any{}".format(py, suffix): _flags.whl_py3_abi3,
180-
"{}_none_any{}".format(pycp, suffix): _flags.whl_pycp3x,
181-
"{}_abi3_any{}".format(pycp, suffix): _flags.whl_pycp3x_abi3,
182-
"{}_cp_any{}".format(pycp, suffix): _flags.whl_pycp3x_abicp,
183-
}.items():
184-
if f and f in flag_values:
185-
fail("BUG")
186-
elif f:
149+
def _dist_config_settings(*, suffix, plat_flag_values, **kwargs):
150+
flag_values = {_flags.dist: ""}
151+
152+
# First create an sdist, we will be building upon the flag values, which
153+
# will ensure that each sdist config setting is the least specialized of
154+
# all. However, we need at least one flag value to cover the case where we
155+
# have `sdist` for any platform, hence we have a non-empty `flag_values`
156+
# here.
157+
_dist_config_setting(
158+
name = "sdist{}".format(suffix),
159+
flag_values = flag_values,
160+
is_pip_whl = FLAGS.is_pip_whl_no,
161+
**kwargs
162+
)
163+
164+
for name, f in [
165+
("py_none", _flags.whl_py2_py3),
166+
("py3_none", _flags.whl_py3),
167+
("py3_abi3", _flags.whl_py3_abi3),
168+
("cp3x_none", _flags.whl_pycp3x),
169+
("cp3x_abi3", _flags.whl_pycp3x_abi3),
170+
("cp3x_cp", _flags.whl_pycp3x_abicp),
171+
]:
172+
if f in flag_values:
173+
# This should never happen as all of the different whls should have
174+
# unique flag values.
175+
fail("BUG: the flag {} is attempted to be added twice to the list".format(f))
176+
else:
187177
flag_values[f] = ""
188178

189-
_whl_config_setting(
190-
name = n,
179+
_dist_config_setting(
180+
name = "{}_any{}".format(name, suffix),
191181
flag_values = flag_values,
182+
is_pip_whl = FLAGS.is_pip_whl_only,
192183
**kwargs
193184
)
194185

@@ -197,22 +188,25 @@ def _whl_config_settings(*, suffix, plat_flag_values, **kwargs):
197188
for (suffix, flag_values) in plat_flag_values:
198189
flag_values = flag_values | generic_flag_values
199190

200-
for n, f in {
201-
"{}_none_{}".format(py, suffix): _flags.whl_plat,
202-
"{}3_none_{}".format(py, suffix): _flags.whl_plat_py3,
203-
"{}3_abi3_{}".format(py, suffix): _flags.whl_plat_py3_abi3,
204-
"{}_none_{}".format(pycp, suffix): _flags.whl_plat_pycp3x,
205-
"{}_abi3_{}".format(pycp, suffix): _flags.whl_plat_pycp3x_abi3,
206-
"{}_cp_{}".format(pycp, suffix): _flags.whl_plat_pycp3x_abicp,
207-
}.items():
208-
if f and f in flag_values:
209-
fail("BUG")
210-
elif f:
191+
for name, f in [
192+
("py_none", _flags.whl_plat),
193+
("py3_none", _flags.whl_plat_py3),
194+
("py3_abi3", _flags.whl_plat_py3_abi3),
195+
("cp3x_none", _flags.whl_plat_pycp3x),
196+
("cp3x_abi3", _flags.whl_plat_pycp3x_abi3),
197+
("cp3x_cp", _flags.whl_plat_pycp3x_abicp),
198+
]:
199+
if f in flag_values:
200+
# This should never happen as all of the different whls should have
201+
# unique flag values.
202+
fail("BUG: the flag {} is attempted to be added twice to the list".format(f))
203+
else:
211204
flag_values[f] = ""
212205

213-
_whl_config_setting(
214-
name = n,
206+
_dist_config_setting(
207+
name = "{}_{}".format(name, suffix),
215208
flag_values = flag_values,
209+
is_pip_whl = FLAGS.is_pip_whl_only,
216210
**kwargs
217211
)
218212

@@ -282,85 +276,50 @@ def _plat_flag_values(os, cpu, osx_versions, glibc_versions, muslc_versions):
282276

283277
return ret
284278

285-
def _whl_config_setting(*, name, flag_values, visibility, config_setting_rule = None, **kwargs):
286-
config_setting_rule = config_setting_rule or _config_setting_or
287-
config_setting_rule(
288-
name = "is_" + name,
289-
flag_values = flag_values | {
290-
FLAGS.pip_whl: UseWhlFlag.ONLY,
291-
},
292-
default = flag_values | {
293-
_flags.whl_py2_py3: "",
294-
FLAGS.pip_whl: UseWhlFlag.AUTO,
295-
},
296-
visibility = visibility,
297-
**kwargs
298-
)
299-
300-
def _sdist_config_setting(*, name, visibility, config_setting_rule = None, **kwargs):
301-
config_setting_rule = config_setting_rule or _config_setting_or
302-
config_setting_rule(
303-
name = "is_" + name,
304-
flag_values = {FLAGS.pip_whl: UseWhlFlag.NO},
305-
default = {FLAGS.pip_whl: UseWhlFlag.AUTO},
306-
visibility = visibility,
307-
**kwargs
308-
)
279+
def _dist_config_setting(*, name, is_pip_whl, is_python, python_version, native = native, **kwargs):
280+
"""A macro to create a target that matches is_pip_whl_auto and one more value.
309281
310-
def _config_setting_or(*, name, flag_values, default, visibility, **kwargs):
311-
match_name = "_{}".format(name)
312-
default_name = "_{}_default".format(name)
282+
Args:
283+
name: The name of the public target.
284+
is_pip_whl: The config setting to match in addition to
285+
`is_pip_whl_auto` when evaluating the config setting.
286+
is_python: The python version config_setting to match.
287+
python_version: The python version name.
288+
native (struct): The struct containing alias and config_setting rules
289+
to use for creating the objects. Can be overridden for unit tests
290+
reasons.
291+
**kwargs: The kwargs passed to the config_setting rule. Visibility of
292+
the main alias target is also taken from the kwargs.
293+
"""
294+
_name = "_is_" + name
313295

296+
visibility = kwargs.get("visibility")
314297
native.alias(
315-
name = name,
298+
name = "is_cp{}_{}".format(python_version, name) if python_version else "is_{}".format(name),
316299
actual = select({
317-
"//conditions:default": default_name,
318-
match_name: match_name,
300+
# First match by the python version
301+
is_python: _name,
302+
"//conditions:default": is_python,
319303
}),
320304
visibility = visibility,
321305
)
322306

323-
_config_setting(
324-
name = match_name,
325-
flag_values = flag_values,
326-
visibility = visibility,
327-
**kwargs
328-
)
329-
_config_setting(
330-
name = default_name,
331-
flag_values = default,
307+
if python_version:
308+
# Reuse the config_setting targets that we use with the default
309+
# `python_version` setting.
310+
return
311+
312+
config_setting_name = _name + "_setting"
313+
native.config_setting(name = config_setting_name, **kwargs)
314+
315+
# Next match by the `pip_whl` flag value and then match by the flags that
316+
# are intrinsic to the distribution.
317+
native.alias(
318+
name = _name,
319+
actual = select({
320+
"//conditions:default": FLAGS.is_pip_whl_auto,
321+
FLAGS.is_pip_whl_auto: config_setting_name,
322+
is_pip_whl: config_setting_name,
323+
}),
332324
visibility = visibility,
333-
**kwargs
334325
)
335-
336-
def _config_setting(python_version = "", **kwargs):
337-
if python_version:
338-
# NOTE @aignas 2024-05-26: with this we are getting about 24k internal
339-
# config_setting targets in our unit tests. Whilst the number of the
340-
# external dependencies does not dictate this number, it does mean that
341-
# bazel will take longer to parse stuff. This would be especially
342-
# noticeable in repos, which use multiple hub repos within a single
343-
# workspace.
344-
#
345-
# A way to reduce the number of targets would be:
346-
# * put them to a central location and teach this code to just alias them,
347-
# maybe we should create a pip_config_settings repo within the pip
348-
# extension, which would collect config settings for all hub_repos.
349-
# * put them in rules_python - this has the drawback of exposing things like
350-
# is_cp3.10_linux and users may start depending upon the naming
351-
# convention and this API is very unstable.
352-
is_python_config_setting(
353-
python_version = python_version,
354-
**kwargs
355-
)
356-
else:
357-
# We need this to ensure that there are no ambiguous matches when python_version
358-
# is unset, which usually happens when we are not using the python version aware
359-
# rules.
360-
flag_values = kwargs.pop("flag_values", {}) | {
361-
FLAGS.python_version: "",
362-
}
363-
native.config_setting(
364-
flag_values = flag_values,
365-
**kwargs
366-
)

0 commit comments

Comments
 (0)