1818load ("@bazel_skylib//lib:selects.bzl" , "selects" )
1919load ("@bazel_skylib//rules:common_settings.bzl" , "BuildSettingInfo" )
2020load ("//python:versions.bzl" , "MINOR_MAPPING" , "TOOL_VERSIONS" )
21+ load (":semver.bzl" , "semver" )
2122
2223_PYTHON_VERSION_FLAG = str (Label ("//python/config_settings:python_version" ))
23-
24- def _ver_key (s ):
25- major , _ , s = s .partition ("." )
26- minor , _ , s = s .partition ("." )
27- micro , _ , s = s .partition ("." )
28- return (int (major ), int (minor ), int (micro ))
29-
30- def _flag_values (* , python_versions , minor_mapping ):
31- """Construct a map of python_version to a list of toolchain values.
32-
33- This mapping maps the concept of a config setting to a list of compatible toolchain versions.
34- For using this in the code, the VERSION_FLAG_VALUES should be used instead.
35-
36- Args:
37- python_versions: {type}`list[str]` X.Y.Z` python versions.
38- minor_mapping: {type}`dict[str, str]` `X.Y` to `X.Y.Z` mapping.
39-
40- Returns:
41- A `map[str, list[str]]`. Each key is a python_version flag value. Each value
42- is a list of the python_version flag values that should match when for the
43- `key`. For example:
44- ```
45- "3.8" -> ["3.8", "3.8.1", "3.8.2", ..., "3.8.19"] # All 3.8 versions
46- "3.8.2" -> ["3.8.2"] # Only 3.8.2
47- "3.8.19" -> ["3.8.19", "3.8"] # The latest version should also match 3.8 so
48- as when the `3.8` toolchain is used we just use the latest `3.8` toolchain.
49- this makes the `select("is_python_3.8.19")` work no matter how the user
50- specifies the latest python version to use.
51- ```
52- """
53- ret = {}
54-
55- for micro_version in sorted (python_versions , key = _ver_key ):
56- minor_version , _ , _ = micro_version .rpartition ("." )
57-
58- # This matches the raw flag value, e.g. --//python/config_settings:python_version=3.8
59- # It's private because matching the concept of e.g. "3.8" value is done
60- # using the `is_python_X.Y` config setting group, which is aware of the
61- # minor versions that could match instead.
62- ret .setdefault (minor_version , [minor_version ]).append (micro_version )
63-
64- # Ensure that is_python_3.9.8 is matched if python_version is set
65- # to 3.9 if minor_mapping points to 3.9.8
66- default_micro_version = minor_mapping [minor_version ]
67- ret [micro_version ] = [micro_version , minor_version ] if default_micro_version == micro_version else [micro_version ]
68-
69- return ret
70-
71- VERSION_FLAG_VALUES = _flag_values (python_versions = TOOL_VERSIONS .keys (), minor_mapping = MINOR_MAPPING )
72-
73- def is_python_config_setting (name , * , python_version , reuse_conditions = None , ** kwargs ):
74- """Create a config setting for matching 'python_version' configuration flag.
75-
76- This function is mainly intended for internal use within the `whl_library` and `pip_parse`
77- machinery.
78-
79- The matching of the 'python_version' flag depends on the value passed in
80- `python_version` and here is the example for `3.8` (but the same applies
81- to other python versions present in @//python:versions.bzl#TOOL_VERSIONS):
82- * "3.8" -> ["3.8", "3.8.1", "3.8.2", ..., "3.8.19"] # All 3.8 versions
83- * "3.8.2" -> ["3.8.2"] # Only 3.8.2
84- * "3.8.19" -> ["3.8.19", "3.8"] # The latest version should also match 3.8 so
85- as when the `3.8` toolchain is used we just use the latest `3.8` toolchain.
86- this makes the `select("is_python_3.8.19")` work no matter how the user
87- specifies the latest python version to use.
88-
89- Args:
90- name: name for the target that will be created to be used in select statements.
91- python_version: The python_version to be passed in the `flag_values` in the
92- `config_setting`. Depending on the version, the matching python version list
93- can be as described above.
94- reuse_conditions: A dict of version to version label for which we should
95- reuse config_setting targets instead of creating them from scratch. This
96- is useful when using is_python_config_setting multiple times in the
97- same package with the same `major.minor` python versions.
98- **kwargs: extra kwargs passed to the `config_setting`.
99- """
100- if python_version not in name :
101- fail ("The name '{}' must have the python version '{}' in it" .format (name , python_version ))
102-
103- if python_version not in VERSION_FLAG_VALUES :
104- fail ("The 'python_version' must be known to 'rules_python', choose from the values: {}" .format (VERSION_FLAG_VALUES .keys ()))
105-
106- python_versions = VERSION_FLAG_VALUES [python_version ]
107- extra_flag_values = kwargs .pop ("flag_values" , {})
108- if _PYTHON_VERSION_FLAG in extra_flag_values :
109- fail ("Cannot set '{}' in the flag values" .format (_PYTHON_VERSION_FLAG ))
110-
111- if len (python_versions ) == 1 :
112- native .config_setting (
113- name = name ,
114- flag_values = {
115- _PYTHON_VERSION_FLAG : python_version ,
116- } | extra_flag_values ,
117- ** kwargs
118- )
119- return
120-
121- reuse_conditions = reuse_conditions or {}
122- create_config_settings = {
123- "_{}" .format (name ).replace (python_version , version ): {_PYTHON_VERSION_FLAG : version }
124- for version in python_versions
125- if not reuse_conditions or version not in reuse_conditions
126- }
127- match_any = list (create_config_settings .keys ())
128- for version , condition in reuse_conditions .items ():
129- if len (VERSION_FLAG_VALUES [version ]) == 1 :
130- match_any .append (condition )
131- continue
132-
133- # Convert the name to an internal label that this function would create,
134- # so that we are hitting the config_setting and not the config_setting_group.
135- condition = Label (condition )
136- if hasattr (condition , "same_package_label" ):
137- condition = condition .same_package_label ("_" + condition .name )
138- else :
139- condition = condition .relative ("_" + condition .name )
140-
141- match_any .append (condition )
142-
143- for name_ , flag_values_ in create_config_settings .items ():
144- native .config_setting (
145- name = name_ ,
146- flag_values = flag_values_ | extra_flag_values ,
147- ** kwargs
148- )
149-
150- # An alias pointing to an underscore-prefixed config_setting_group
151- # is used because config_setting_group creates
152- # `is_{version}_N` targets, which are easily confused with the
153- # `is_{minor}.{micro}` (dot) targets.
154- selects .config_setting_group (
155- name = "_{}_group" .format (name ),
156- match_any = match_any ,
157- visibility = ["//visibility:private" ],
158- )
159- native .alias (
160- name = name ,
161- actual = "_{}_group" .format (name ),
162- visibility = kwargs .get ("visibility" , []),
163- )
24+ _PYTHON_MINOR_VERSION_FLAG = str (Label ("//python/config_settings:python_version_major_minor" ))
16425
16526def construct_config_settings (name = None ): # buildifier: disable=function-docstring
16627 """Create a 'python_version' config flag and construct all config settings used in rules_python.
@@ -181,23 +42,62 @@ def construct_config_settings(name = None): # buildifier: disable=function-docs
18142 visibility = ["//visibility:public" ],
18243 )
18344
45+ _python_major_minor (
46+ name = "python_version_major_minor" ,
47+ build_setting_default = "" ,
48+ visibility = ["//visibility:public" ],
49+ )
50+
18451 native .config_setting (
18552 name = "is_python_version_unset" ,
186- flag_values = {
187- Label ("//python/config_settings:python_version" ): "" ,
188- },
53+ flag_values = {_PYTHON_VERSION_FLAG : "" },
18954 visibility = ["//visibility:public" ],
19055 )
19156
192- for version , matching_versions in VERSION_FLAG_VALUES .items ():
193- is_python_config_setting (
194- name = "is_python_{}" .format (version ),
195- python_version = version ,
196- reuse_conditions = {
197- v : native .package_relative_label ("is_python_{}" .format (v ))
198- for v in matching_versions
199- if v != version
200- },
57+ # This matches the raw flag value, e.g. --//python/config_settings:python_version=3.8
58+ # It's private because matching the concept of e.g. "3.8" value is done
59+ # using the `is_python_X.Y` config setting group, which is aware of the
60+ # minor versions that could match instead.
61+ for minor in range (20 ):
62+ native .config_setting (
63+ name = "is_python_3.{}" .format (minor ),
64+ flag_values = {_PYTHON_MINOR_VERSION_FLAG : "3.{}" .format (minor )},
65+ visibility = ["//visibility:public" ],
66+ )
67+
68+ for version in TOOL_VERSIONS .keys ():
69+ minor_version , _ , _ = version .rpartition ("." )
70+ if MINOR_MAPPING [minor_version ] != version :
71+ native .config_setting (
72+ name = "is_python_{}" .format (version ),
73+ flag_values = {":python_version" : version },
74+ visibility = ["//visibility:public" ],
75+ )
76+ continue
77+
78+ # Also need to match the minor version when using
79+ name = "is_python_{}" .format (version )
80+ native .config_setting (
81+ name = "_" + name ,
82+ flag_values = {":python_version" : version },
83+ visibility = ["//visibility:public" ],
84+ )
85+
86+ # An alias pointing to an underscore-prefixed config_setting_group
87+ # is used because config_setting_group creates
88+ # `is_{version}_N` targets, which are easily confused with the
89+ # `is_{minor}.{micro}` (dot) targets.
90+ selects .config_setting_group (
91+ name = "_{}_group" .format (name ),
92+ match_any = [
93+ ":_is_python_{}" .format (version ),
94+ ":is_python_{}" .format (minor_version ),
95+ ],
96+ visibility = ["//visibility:private" ],
97+ )
98+ native .alias (
99+ name = name ,
100+ actual = "_{}_group" .format (name ),
201101 visibility = ["//visibility:public" ],
202102 )
203103
@@ -220,3 +120,23 @@ _python_version_flag = rule(
220120 build_setting = config .string (flag = True ),
221121 attrs = {},
222122)
123+
124+ def _python_major_minor_impl (ctx ):
125+ input = ctx .attr ._python_version_flag [config_common .FeatureFlagInfo ].value
126+ if input :
127+ version = semver (input )
128+ value = "{}.{}" .format (version .major , version .minor )
129+ else :
130+ value = ""
131+
132+ return [config_common .FeatureFlagInfo (value = value )]
133+
134+ _python_major_minor = rule (
135+ implementation = _python_major_minor_impl ,
136+ build_setting = config .string (flag = False ),
137+ attrs = {
138+ "_python_version_flag" : attr .label (
139+ default = _PYTHON_VERSION_FLAG ,
140+ ),
141+ },
142+ )
0 commit comments