1616The {obj}`config_settings` macro is used to create the config setting targets
1717that can be used in the {obj}`pkg_aliases` macro for selecting the compatible
1818repositories.
19-
20- Bazel's selects work by selecting the most-specialized configuration setting
21- that matches the target platform, which is further described in [bazel documentation][docs].
22- We can leverage this fact to ensure that the most specialized matches are used
23- by default with the users being able to configure string_flag values to select
24- the less specialized ones.
25-
26- [docs]: https://bazel.build/docs/configurable-attributes
27-
28- The config settings in the order from the least specialized to the most
29- specialized is as follows:
30- * `:is_cp3<minor_version><suffix>`
31- * `:is_cp3<minor_version>_sdist<suffix>`
32- * `:is_cp3<minor_version>_py_none_any<suffix>`
33- * `:is_cp3<minor_version>_py3_none_any<suffix>`
34- * `:is_cp3<minor_version>_py3_abi3_any<suffix>`
35- * `:is_cp3<minor_version>_none_any<suffix>`
36- * `:is_cp3<minor_version>_any_any<suffix>`
37- * `:is_cp3<minor_version>_cp3<minor_version>_any<suffix>` and `:is_cp3<minor_version>_cp3<minor_version>t_any<suffix>`
38- * `:is_cp3<minor_version>_py_none_<platform_suffix>`
39- * `:is_cp3<minor_version>_py3_none_<platform_suffix>`
40- * `:is_cp3<minor_version>_py3_abi3_<platform_suffix>`
41- * `:is_cp3<minor_version>_none_<platform_suffix>`
42- * `:is_cp3<minor_version>_abi3_<platform_suffix>`
43- * `:is_cp3<minor_version>_cp3<minor_version>_<platform_suffix>` and `:is_cp3<minor_version>_cp3<minor_version>t_<platform_suffix>`
44-
45- Optionally instead of `<minor_version>` there sometimes may be `<minor_version>.<micro_version>` used in order to fully specify the versions
46-
47- The specialization of free-threaded vs non-free-threaded wheels is the same as
48- they are just variants of each other. The same goes for the specialization of
49- `musllinux` vs `manylinux`.
50-
51- The goal of this macro is to provide config settings that provide unambigous
52- matches if any pair of them is used together for any target configuration
53- setting. We achieve this by using dummy internal `flag_values` keys to force the
54- items further down the list to appear to be more specialized than the ones above.
55-
56- What is more, the names of the config settings are as similar to the platform wheel
57- specification as possible. How the wheel names map to the config setting names defined
58- in here is described in {obj}`pkg_aliases` documentation.
59-
60- :::{note}
61- Right now the specialization of adjacent config settings where one is with
62- `constraint_values` and one is without is ambiguous. I.e. `py_none_any` and
63- `sdist_linux_x86_64` have the same specialization from bazel point of view
64- because one has one `flag_value` entry and `constraint_values` and the
65- other has 2 flag_value entries. And unfortunately there is no way to disambiguate
66- it, because we are essentially in two dimensions here (`flag_values` and
67- `constraint_values`). Hence, when using the `config_settings` from here,
68- either have all of them with empty `suffix` or all of them with a non-empty
69- suffix.
70- :::
7119"""
7220
7321load ("@bazel_skylib//lib:selects.bzl" , "selects" )
74- load ("//python/private:flags.bzl" , "LibcFlag" )
75- load (":flags.bzl" , "INTERNAL_FLAGS" , "UniversalWhlFlag" )
76-
77- FLAGS = struct (
78- ** {
79- f : str (Label ("//python/config_settings:" + f ))
80- for f in [
81- "is_pip_whl_auto" ,
82- "is_pip_whl_no" ,
83- "is_pip_whl_only" ,
84- "_is_py_freethreaded_yes" ,
85- "_is_py_freethreaded_no" ,
86- "pip_whl_glibc_version" ,
87- "pip_whl_muslc_version" ,
88- "pip_whl_osx_arch" ,
89- "pip_whl_osx_version" ,
90- "py_linux_libc" ,
91- "python_version" ,
92- ]
93- }
94- )
95-
96- _DEFAULT = "//conditions:default"
97- _INCOMPATIBLE = "@platforms//:incompatible"
98-
99- # Here we create extra string flags that are just to work with the select
100- # selecting the most specialized match. We don't allow the user to change
101- # them.
102- _flags = struct (
103- ** {
104- f : str (Label ("//python/config_settings:_internal_pip_" + f ))
105- for f in INTERNAL_FLAGS
106- }
107- )
10822
10923def config_settings (
11024 * ,
11125 python_versions = [],
112- glibc_versions = [],
113- muslc_versions = [],
114- osx_versions = [],
11526 name = None ,
11627 platform_config_settings = {},
11728 ** kwargs ):
@@ -121,12 +32,6 @@ def config_settings(
12132 name (str): Currently unused.
12233 python_versions (list[str]): The list of python versions to configure
12334 config settings for.
124- glibc_versions (list[str]): The list of glibc version of the wheels to
125- configure config settings for.
126- muslc_versions (list[str]): The list of musl version of the wheels to
127- configure config settings for.
128- osx_versions (list[str]): The list of OSX OS versions to configure
129- config settings for.
13035 platform_config_settings: {type}`dict[str, list[str]]` the constraint
13136 values to use instead of the default ones. Key are platform names
13237 (a human-friendly platform string). Values are lists of
@@ -135,48 +40,22 @@ def config_settings(
13540 {obj}`native`.
13641 """
13742
138- glibc_versions = ["" ] + glibc_versions
139- muslc_versions = ["" ] + muslc_versions
140- osx_versions = ["" ] + osx_versions
14143 target_platforms = {
14244 "" : [],
143- # TODO @aignas 2025-06-15: allowing universal2 and platform specific wheels in one
144- # closure is making things maybe a little bit too complicated.
145- "osx_universal2" : ["@platforms//os:osx" ],
14645 } | platform_config_settings
14746
14847 for python_version in python_versions :
14948 for platform_name , config_settings in target_platforms .items ():
15049 suffix = "_{}" .format (platform_name ) if platform_name else ""
151- os , _ , cpu = platform_name .partition ("_" )
152-
153- # We parse the target settings and if there is a "platforms//os" or
154- # "platforms//cpu" value in here, we also add it into the constraint_values
155- #
156- # this is to ensure that we can still pass all of the unit tests for config
157- # setting specialization.
158- constraint_values = []
159- for setting in config_settings :
160- setting_label = Label (setting )
161- if setting_label .repo_name == "platforms" and setting_label .package in ["os" , "cpu" ]:
162- constraint_values .append (setting )
16350
16451 _dist_config_settings (
16552 suffix = suffix ,
166- plat_flag_values = _plat_flag_values (
167- os = os ,
168- cpu = cpu ,
169- osx_versions = osx_versions ,
170- glibc_versions = glibc_versions ,
171- muslc_versions = muslc_versions ,
172- ),
17353 config_settings = config_settings ,
174- constraint_values = constraint_values ,
17554 python_version = python_version ,
17655 ** kwargs
17756 )
17857
179- def _dist_config_settings (* , suffix , plat_flag_values , python_version , ** kwargs ):
58+ def _dist_config_settings (* , suffix , python_version , ** kwargs ):
18059 flag_values = {
18160 Label ("//python/config_settings:python_version_major_minor" ): python_version ,
18261 }
@@ -190,156 +69,11 @@ def _dist_config_settings(*, suffix, plat_flag_values, python_version, **kwargs)
19069 ** kwargs
19170 )
19271
193- flag_values [_flags .dist ] = ""
194-
195- # First create an sdist, we will be building upon the flag values, which
196- # will ensure that each sdist config setting is the least specialized of
197- # all. However, we need at least one flag value to cover the case where we
198- # have `sdist` for any platform, hence we have a non-empty `flag_values`
199- # here.
200- _dist_config_setting (
201- name = "{}_sdist{}" .format (prefix , suffix ),
202- flag_values = flag_values ,
203- compatible_with = (FLAGS .is_pip_whl_no , FLAGS .is_pip_whl_auto ),
204- ** kwargs
205- )
206-
207- used_flags = {}
208-
209- # NOTE @aignas 2024-12-01: the abi3 is not compatible with freethreaded
210- # builds as per PEP703 (https://peps.python.org/pep-0703/#backwards-compatibility)
211- #
212- # The discussion here also reinforces this notion:
213- # https://discuss.python.org/t/pep-703-making-the-global-interpreter-lock-optional-3-12-updates/26503/99
214-
215- for name , f , compatible_with in [
216- ("py_none" , _flags .whl , None ),
217- ("py3_none" , _flags .whl_py3 , None ),
218- ("py3_abi3" , _flags .whl_py3_abi3 , (FLAGS ._is_py_freethreaded_no ,)),
219- ("none" , _flags .whl_pycp3x , None ),
220- ("abi3" , _flags .whl_pycp3x_abi3 , (FLAGS ._is_py_freethreaded_no ,)),
221- # The below are not specializations of one another, they are variants
222- (cpv , _flags .whl_pycp3x_abicp , (FLAGS ._is_py_freethreaded_no ,)),
223- (cpv + "t" , _flags .whl_pycp3x_abicp , (FLAGS ._is_py_freethreaded_yes ,)),
224- ]:
225- if (f , compatible_with ) in used_flags :
226- # This should never happen as all of the different whls should have
227- # unique flag values
228- fail ("BUG: the flag {} is attempted to be added twice to the list" .format (f ))
229- else :
230- flag_values [f ] = "yes" if f == _flags .whl else ""
231- used_flags [(f , compatible_with )] = True
232-
233- _dist_config_setting (
234- name = "{}_{}_any{}" .format (prefix , name , suffix ),
235- flag_values = flag_values ,
236- compatible_with = compatible_with ,
237- ** kwargs
238- )
239-
240- generic_flag_values = flag_values
241- generic_used_flags = used_flags
242-
243- for (suffix , flag_values ) in plat_flag_values :
244- used_flags = {(f , None ): True for f in flag_values } | generic_used_flags
245- flag_values = flag_values | generic_flag_values
246-
247- for name , f , compatible_with in [
248- ("py_none" , _flags .whl_plat , None ),
249- ("py3_none" , _flags .whl_plat_py3 , None ),
250- ("py3_abi3" , _flags .whl_plat_py3_abi3 , (FLAGS ._is_py_freethreaded_no ,)),
251- ("none" , _flags .whl_plat_pycp3x , None ),
252- ("abi3" , _flags .whl_plat_pycp3x_abi3 , (FLAGS ._is_py_freethreaded_no ,)),
253- # The below are not specializations of one another, they are variants
254- (cpv , _flags .whl_plat_pycp3x_abicp , (FLAGS ._is_py_freethreaded_no ,)),
255- (cpv + "t" , _flags .whl_plat_pycp3x_abicp , (FLAGS ._is_py_freethreaded_yes ,)),
256- ]:
257- if (f , compatible_with ) in used_flags :
258- # This should never happen as all of the different whls should have
259- # unique flag values.
260- fail ("BUG: the flag {} is attempted to be added twice to the list" .format (f ))
261- else :
262- flag_values [f ] = ""
263- used_flags [(f , compatible_with )] = True
264-
265- _dist_config_setting (
266- name = "{}_{}_{}" .format (prefix , name , suffix ),
267- flag_values = flag_values ,
268- compatible_with = compatible_with ,
269- ** kwargs
270- )
271-
272- def _to_version_string (version , sep = "." ):
273- if not version :
274- return ""
275-
276- return "{}{}{}" .format (version [0 ], sep , version [1 ])
277-
278- def _plat_flag_values (os , cpu , osx_versions , glibc_versions , muslc_versions ):
279- ret = []
280- if os == "" :
281- return []
282- elif os == "windows" :
283- ret .append (("{}_{}" .format (os , cpu ), {}))
284- elif os == "osx" :
285- for osx_version in osx_versions :
286- flags = {
287- FLAGS .pip_whl_osx_version : _to_version_string (osx_version ),
288- }
289- if cpu != "universal2" :
290- flags [FLAGS .pip_whl_osx_arch ] = UniversalWhlFlag .ARCH
291-
292- if not osx_version :
293- suffix = "{}_{}" .format (os , cpu )
294- else :
295- suffix = "{}_{}_{}" .format (os , _to_version_string (osx_version , "_" ), cpu )
296-
297- ret .append ((suffix , flags ))
298-
299- elif os == "linux" :
300- for os_prefix , linux_libc in {
301- os : LibcFlag .GLIBC ,
302- "many" + os : LibcFlag .GLIBC ,
303- "musl" + os : LibcFlag .MUSL ,
304- }.items ():
305- if linux_libc == LibcFlag .GLIBC :
306- libc_versions = glibc_versions
307- libc_flag = FLAGS .pip_whl_glibc_version
308- elif linux_libc == LibcFlag .MUSL :
309- libc_versions = muslc_versions
310- libc_flag = FLAGS .pip_whl_muslc_version
311- else :
312- fail ("Unsupported libc type: {}" .format (linux_libc ))
313-
314- for libc_version in libc_versions :
315- if libc_version and os_prefix == os :
316- continue
317- elif libc_version :
318- suffix = "{}_{}_{}" .format (os_prefix , _to_version_string (libc_version , "_" ), cpu )
319- else :
320- suffix = "{}_{}" .format (os_prefix , cpu )
321-
322- ret .append ((
323- suffix ,
324- {
325- FLAGS .py_linux_libc : linux_libc ,
326- libc_flag : _to_version_string (libc_version ),
327- },
328- ))
329- else :
330- fail ("Unsupported os: {}" .format (os ))
331-
332- return ret
333-
334- def _dist_config_setting (* , name , compatible_with = None , selects = selects , native = native , config_settings = None , ** kwargs ):
72+ def _dist_config_setting (* , name , selects = selects , native = native , config_settings = None , ** kwargs ):
33573 """A macro to create a target for matching Python binary and source distributions.
33674
33775 Args:
33876 name: The name of the public target.
339- compatible_with: {type}`tuple[Label]` A collection of config settings that are
340- compatible with the given dist config setting. For example, if only
341- non-freethreaded python builds are allowed, add
342- FLAGS._is_py_freethreaded_no here.
34377 config_settings: {type}`list[str | Label]` the list of target settings that must
34478 be matched before we try to evaluate the config_setting that we may create in
34579 this function.
@@ -352,18 +86,6 @@ def _dist_config_setting(*, name, compatible_with = None, selects = selects, nat
35286 **kwargs: The kwargs passed to the config_setting rule. Visibility of
35387 the main alias target is also taken from the kwargs.
35488 """
355- if compatible_with :
356- dist_config_setting_name = "_" + name
357- native .alias (
358- name = name ,
359- actual = select (
360- {setting : dist_config_setting_name for setting in compatible_with } | {
361- _DEFAULT : _INCOMPATIBLE ,
362- },
363- ),
364- visibility = kwargs .get ("visibility" ),
365- )
366- name = dist_config_setting_name
36789
36890 # first define the config setting that has all of the constraint values
36991 _name = "_" + name
0 commit comments