Skip to content

Commit a2394ab

Browse files
dizzy57rickeylev
andauthored
feat(config_settings): allow matching minor version of python_version flag (#1555)
Currently a user has to specify a full (x.y.z) version of Python when setting the `//python/config_settings:python_version` flag. When they upgrade `rules_python` or change `MINOR_MAPPING` in some other way, user has to update the flag's value to keep it in sync with `MINOR_MAPPING`. This adds micro-version agnostic config settings to allow matching the minor version. For example e.g. `//python/config_settings:is_python_3.8` will match any of `3.8.1, 3.8.2, ...' (or whatever other versions are listed in `TOOL_VERSIONS`) --------- Co-authored-by: Richard Levasseur <[email protected]>
1 parent bd65eed commit a2394ab

File tree

5 files changed

+159
-11
lines changed

5 files changed

+159
-11
lines changed

CHANGELOG.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ A brief description of the categories of changes:
6060
`python_register_toolchains`.
6161
Note that this only available on the Starlark implementation of the provider.
6262

63+
* (config_settings) Added `//python/config_settings:is_python_X.Y` config
64+
settings to match on minor Python version. These settings match any `X.Y`
65+
version instead of just an exact `X.Y.Z` version.
66+
6367
## [0.28.0] - 2024-01-07
6468

6569
[0.28.0]: https://github.com/bazelbuild/rules_python/releases/tag/0.28.0
@@ -234,8 +238,6 @@ Breaking changes:
234238
* (utils) Added a `pip_utils` struct with a `normalize_name` function to allow users
235239
to find out how `rules_python` would normalize a PyPI distribution name.
236240

237-
[0.27.0]: https://github.com/bazelbuild/rules_python/releases/tag/0.27.0
238-
239241
## [0.26.0] - 2023-10-06
240242

241243
### Changed

python/config_settings/BUILD.bazel

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,7 @@ filegroup(
1010
visibility = ["//python:__pkg__"],
1111
)
1212

13-
construct_config_settings(python_versions = TOOL_VERSIONS.keys())
13+
construct_config_settings(
14+
name = "construct_config_settings",
15+
python_versions = TOOL_VERSIONS.keys(),
16+
)

python/config_settings/config_settings.bzl

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,69 @@
1515
"""This module is used to construct the config settings in the BUILD file in this same package.
1616
"""
1717

18+
load("@bazel_skylib//lib:selects.bzl", "selects")
1819
load("@bazel_skylib//rules:common_settings.bzl", "string_flag")
1920

20-
# buildifier: disable=unnamed-macro
21-
def construct_config_settings(python_versions):
21+
def construct_config_settings(name, python_versions):
2222
"""Constructs a set of configs for all Python versions.
2323
2424
Args:
25-
python_versions: The Python versions supported by rules_python.
25+
name: str, unused; only specified to satisfy buildifier lint checks
26+
and allow programatic modification of the target.
27+
python_versions: list of all (x.y.z) Python versions supported by rules_python.
2628
"""
29+
30+
# Maps e.g. "3.8" -> ["3.8.1", "3.8.2", etc]
31+
minor_to_micro_versions = {}
32+
allowed_flag_values = []
33+
for micro_version in python_versions:
34+
minor, _, _ = micro_version.rpartition(".")
35+
minor_to_micro_versions.setdefault(minor, []).append(micro_version)
36+
allowed_flag_values.append(micro_version)
37+
2738
string_flag(
2839
name = "python_version",
40+
# TODO: The default here should somehow match the MODULE config
2941
build_setting_default = python_versions[0],
30-
values = python_versions,
42+
values = sorted(allowed_flag_values),
3143
visibility = ["//visibility:public"],
3244
)
3345

34-
for python_version in python_versions:
35-
python_version_constraint_setting = "is_python_" + python_version
46+
for minor_version, micro_versions in minor_to_micro_versions.items():
47+
# This matches the raw flag value, e.g. --//python/config_settings:python_version=3.8
48+
# It's private because matching the concept of e.g. "3.8" value is done
49+
# using the `is_python_X.Y` config setting group, which is aware of the
50+
# minor versions that could match instead.
51+
equals_minor_version_name = "_python_version_flag_equals_" + minor_version
3652
native.config_setting(
37-
name = python_version_constraint_setting,
38-
flag_values = {":python_version": python_version},
53+
name = equals_minor_version_name,
54+
flag_values = {":python_version": minor_version},
55+
)
56+
57+
matches_minor_version_names = [equals_minor_version_name]
58+
59+
for micro_version in micro_versions:
60+
is_micro_version_name = "is_python_" + micro_version
61+
native.config_setting(
62+
name = is_micro_version_name,
63+
flag_values = {":python_version": micro_version},
64+
visibility = ["//visibility:public"],
65+
)
66+
matches_minor_version_names.append(is_micro_version_name)
67+
68+
# This is prefixed with an underscore to prevent confusion due to how
69+
# config_setting_group is implemented and how our micro-version targets
70+
# are named. config_setting_group will generate targets like
71+
# "is_python_3.10_1" (where the `_N` suffix is len(match_any).
72+
# Meanwhile, the micro-version tarets are named "is_python_3.10.1" --
73+
# just a single dot vs underscore character difference.
74+
selects.config_setting_group(
75+
name = "_is_python_" + minor_version,
76+
match_any = matches_minor_version_names,
77+
)
78+
79+
native.alias(
80+
name = "is_python_" + minor_version,
81+
actual = "_is_python_" + minor_version,
3982
visibility = ["//visibility:public"],
4083
)

tests/config_settings/BUILD.bazel

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Copyright 2022 The Bazel Authors. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
load(":construct_config_settings_tests.bzl", "construct_config_settings_test_suite")
16+
17+
construct_config_settings_test_suite(
18+
name = "construct_config_settings_tests",
19+
)
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Copyright 2024 The Bazel Authors. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
"""Tests for construction of Python version matching config settings."""
15+
16+
load("@rules_testing//lib:analysis_test.bzl", "analysis_test")
17+
load("@rules_testing//lib:test_suite.bzl", "test_suite")
18+
load("@rules_testing//lib:truth.bzl", "subjects")
19+
load("@rules_testing//lib:util.bzl", rt_util = "util")
20+
21+
_tests = []
22+
23+
def _subject_impl(ctx):
24+
_ = ctx # @unused
25+
return [DefaultInfo()]
26+
27+
_subject = rule(
28+
implementation = _subject_impl,
29+
attrs = {
30+
"match_micro": attr.string(),
31+
"match_minor": attr.string(),
32+
"no_match": attr.string(),
33+
},
34+
)
35+
36+
def _test_minor_version_matching(name):
37+
rt_util.helper_target(
38+
_subject,
39+
name = name + "_subject",
40+
match_minor = select({
41+
"//python/config_settings:is_python_3.11": "matched-3.11",
42+
"//conditions:default": "matched-default",
43+
}),
44+
match_micro = select({
45+
"//python/config_settings:is_python_3.11": "matched-3.11",
46+
"//conditions:default": "matched-default",
47+
}),
48+
no_match = select({
49+
"//python/config_settings:is_python_3.12": "matched-3.12",
50+
"//conditions:default": "matched-default",
51+
}),
52+
)
53+
54+
analysis_test(
55+
name = name,
56+
target = name + "_subject",
57+
impl = _test_minor_version_matching_impl,
58+
config_settings = {
59+
str(Label("//python/config_settings:python_version")): "3.11.1",
60+
},
61+
)
62+
63+
def _test_minor_version_matching_impl(env, target):
64+
target = env.expect.that_target(target)
65+
target.attr("match_minor", factory = subjects.str).equals(
66+
"matched-3.11",
67+
)
68+
target.attr("match_micro", factory = subjects.str).equals(
69+
"matched-3.11",
70+
)
71+
target.attr("no_match", factory = subjects.str).equals(
72+
"matched-default",
73+
)
74+
75+
_tests.append(_test_minor_version_matching)
76+
77+
def construct_config_settings_test_suite(name):
78+
test_suite(
79+
name = name,
80+
tests = _tests,
81+
)

0 commit comments

Comments
 (0)