17
17
18
18
load ("@bazel_skylib//lib:selects.bzl" , "selects" )
19
19
load ("@bazel_skylib//rules:common_settings.bzl" , "BuildSettingInfo" )
20
- load ("//python:versions .bzl" , "MINOR_MAPPING" , "TOOL_VERSIONS " )
20
+ load (":semver .bzl" , "semver " )
21
21
22
- _PYTHON_VERSION_FLAG = str (Label ("//python/config_settings:python_version" ))
22
+ _PYTHON_VERSION_FLAG = Label ("//python/config_settings:python_version" )
23
+ _PYTHON_VERSION_MAJOR_MINOR_FLAG = Label ("//python/config_settings:_python_version_major_minor" )
23
24
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
- )
164
-
165
- def construct_config_settings (name = None ): # buildifier: disable=function-docstring
25
+ def construct_config_settings (* , name , versions , minor_mapping ): # buildifier: disable=function-docstring
166
26
"""Create a 'python_version' config flag and construct all config settings used in rules_python.
167
27
168
28
This mainly includes the targets that are used in the toolchain and pip hub
169
29
repositories that only match on the 'python_version' flag values.
170
30
171
31
Args:
172
- name(str): A dummy name value that is no-op for now.
32
+ name: {type}`str` A dummy name value that is no-op for now.
33
+ versions: {type}`list[str]` A list of versions to build constraint settings for.
34
+ minor_mapping: {type}`dict[str, str]` A mapping from `X.Y` to `X.Y.Z` python versions.
173
35
"""
36
+ _ = name # @unused
174
37
_python_version_flag (
175
- name = "python_version" ,
38
+ name = _PYTHON_VERSION_FLAG . name ,
176
39
# TODO: The default here should somehow match the MODULE config. Until
177
40
# then, use the empty string to indicate an unknown version. This
178
41
# also prevents version-unaware targets from inadvertently matching
179
42
# a select condition when they shouldn't.
180
43
build_setting_default = "" ,
181
- values = ["" ] + VERSION_FLAG_VALUES .keys (),
44
+ visibility = ["//visibility:public" ],
45
+ )
46
+
47
+ _python_version_major_minor_flag (
48
+ name = _PYTHON_VERSION_MAJOR_MINOR_FLAG .name ,
49
+ build_setting_default = "" ,
182
50
visibility = ["//visibility:public" ],
183
51
)
184
52
185
53
native .config_setting (
186
54
name = "is_python_version_unset" ,
187
- flag_values = {
188
- Label ("//python/config_settings:python_version" ): "" ,
189
- },
55
+ flag_values = {_PYTHON_VERSION_FLAG : "" },
190
56
visibility = ["//visibility:public" ],
191
57
)
192
58
193
- for version , matching_versions in VERSION_FLAG_VALUES .items ():
194
- is_python_config_setting (
195
- name = "is_python_{}" .format (version ),
196
- python_version = version ,
197
- reuse_conditions = {
198
- v : native .package_relative_label ("is_python_{}" .format (v ))
199
- for v in matching_versions
200
- if v != version
201
- },
59
+ _reverse_minor_mapping = {full : minor for minor , full in minor_mapping .items ()}
60
+ for version in versions :
61
+ minor_version = _reverse_minor_mapping .get (version )
62
+ if not minor_version :
63
+ native .config_setting (
64
+ name = "is_python_{}" .format (version ),
65
+ flag_values = {":python_version" : version },
66
+ visibility = ["//visibility:public" ],
67
+ )
68
+ continue
69
+
70
+ # Also need to match the minor version when using
71
+ name = "is_python_{}" .format (version )
72
+ native .config_setting (
73
+ name = "_" + name ,
74
+ flag_values = {":python_version" : version },
75
+ visibility = ["//visibility:public" ],
76
+ )
77
+
78
+ # An alias pointing to an underscore-prefixed config_setting_group
79
+ # is used because config_setting_group creates
80
+ # `is_{version}_N` targets, which are easily confused with the
81
+ # `is_{minor}.{micro}` (dot) targets.
82
+ selects .config_setting_group (
83
+ name = "_{}_group" .format (name ),
84
+ match_any = [
85
+ ":_is_python_{}" .format (version ),
86
+ ":is_python_{}" .format (minor_version ),
87
+ ],
88
+ visibility = ["//visibility:private" ],
89
+ )
90
+ native .alias (
91
+ name = name ,
92
+ actual = "_{}_group" .format (name ),
93
+ visibility = ["//visibility:public" ],
94
+ )
95
+
96
+ # This matches the raw flag value, e.g. --//python/config_settings:python_version=3.8
97
+ # It's private because matching the concept of e.g. "3.8" value is done
98
+ # using the `is_python_X.Y` config setting group, which is aware of the
99
+ # minor versions that could match instead.
100
+ for minor in minor_mapping .keys ():
101
+ native .config_setting (
102
+ name = "is_python_{}" .format (minor ),
103
+ flag_values = {_PYTHON_VERSION_MAJOR_MINOR_FLAG : minor },
202
104
visibility = ["//visibility:public" ],
203
105
)
204
106
205
107
def _python_version_flag_impl (ctx ):
206
108
value = ctx .build_setting_value
207
- if value not in ctx .attr .values :
208
- fail ((
209
- "Invalid --python_version value: {actual}\n Allowed values {allowed}"
210
- ).format (
211
- actual = value ,
212
- allowed = ", " .join (sorted (ctx .attr .values )),
213
- ))
214
-
215
109
return [
216
110
# BuildSettingInfo is the original provider returned, so continue to
217
111
# return it for compatibility
@@ -227,9 +121,25 @@ def _python_version_flag_impl(ctx):
227
121
_python_version_flag = rule (
228
122
implementation = _python_version_flag_impl ,
229
123
build_setting = config .string (flag = True ),
124
+ attrs = {},
125
+ )
126
+
127
+ def _python_version_major_minor_flag_impl (ctx ):
128
+ input = ctx .attr ._python_version_flag [config_common .FeatureFlagInfo ].value
129
+ if input :
130
+ version = semver (input )
131
+ value = "{}.{}" .format (version .major , version .minor )
132
+ else :
133
+ value = ""
134
+
135
+ return [config_common .FeatureFlagInfo (value = value )]
136
+
137
+ _python_version_major_minor_flag = rule (
138
+ implementation = _python_version_major_minor_flag_impl ,
139
+ build_setting = config .string (flag = False ),
230
140
attrs = {
231
- "values " : attr .string_list (
232
- doc = "Allowed values." ,
141
+ "_python_version_flag " : attr .label (
142
+ default = _PYTHON_VERSION_FLAG ,
233
143
),
234
144
},
235
145
)
0 commit comments