Skip to content

Commit 846dfd0

Browse files
authored
feat(toolchain): drop 3.8 and print info level messages about it (#3387)
Before this PR we had to have at least one 3.8 toolchain to not break things. With this we should be good to drop it. Any python_version 3.8 registrations will be dropped if there are no actual URLs configured, which means that 3.8 will not be selected. The same with pip.parse, we will just ignore it and won't add it to the hub. In order to ensure that `is_python_3.x` flags continue working, we just alias them to `@platforms//:incompatible`. No deprecation message is printed. Work towards #2704 Next step for anyone interested and who has more time than me these days: - [ ] Remove the 3.9 URLs and add them individually to our examples to show that one can do that. - [ ] Update the examples to no longer use 3.9, because it is a maintenance burden.
1 parent 1f6cc5c commit 846dfd0

File tree

6 files changed

+142
-28
lines changed

6 files changed

+142
-28
lines changed

python/private/config_settings.bzl

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,14 @@ If the value is missing, then the default value is being used, see documentation
3535
# access it, but it's not intended for general public usage.
3636
_NOT_ACTUALLY_PUBLIC = ["//visibility:public"]
3737

38-
def construct_config_settings(*, name, default_version, versions, minor_mapping, documented_flags): # buildifier: disable=function-docstring
38+
def construct_config_settings(
39+
*,
40+
name,
41+
default_version,
42+
versions,
43+
minor_mapping,
44+
compat_lowest_version = "3.8",
45+
documented_flags): # buildifier: disable=function-docstring
3946
"""Create a 'python_version' config flag and construct all config settings used in rules_python.
4047
4148
This mainly includes the targets that are used in the toolchain and pip hub
@@ -46,6 +53,8 @@ def construct_config_settings(*, name, default_version, versions, minor_mapping,
4653
default_version: {type}`str` the default value for the `python_version` flag.
4754
versions: {type}`list[str]` A list of versions to build constraint settings for.
4855
minor_mapping: {type}`dict[str, str]` A mapping from `X.Y` to `X.Y.Z` python versions.
56+
compat_lowest_version: {type}`str` The version that we should use as the lowest available
57+
version for `is_python_3.X` flags.
4958
documented_flags: {type}`list[str]` The labels of the documented settings
5059
that affect build configuration.
5160
"""
@@ -69,21 +78,21 @@ def construct_config_settings(*, name, default_version, versions, minor_mapping,
6978
)
7079

7180
_reverse_minor_mapping = {full: minor for minor, full in minor_mapping.items()}
72-
for version in versions:
73-
minor_version = _reverse_minor_mapping.get(version)
81+
for ver in versions:
82+
minor_version = _reverse_minor_mapping.get(ver)
7483
if not minor_version:
7584
native.config_setting(
76-
name = "is_python_{}".format(version),
77-
flag_values = {":python_version": version},
85+
name = "is_python_{}".format(ver),
86+
flag_values = {":python_version": ver},
7887
visibility = ["//visibility:public"],
7988
)
8089
continue
8190

8291
# Also need to match the minor version when using
83-
name = "is_python_{}".format(version)
92+
name = "is_python_{}".format(ver)
8493
native.config_setting(
8594
name = "_" + name,
86-
flag_values = {":python_version": version},
95+
flag_values = {":python_version": ver},
8796
visibility = ["//visibility:public"],
8897
)
8998

@@ -94,7 +103,7 @@ def construct_config_settings(*, name, default_version, versions, minor_mapping,
94103
selects.config_setting_group(
95104
name = "_{}_group".format(name),
96105
match_any = [
97-
":_is_python_{}".format(version),
106+
":_is_python_{}".format(ver),
98107
":is_python_{}".format(minor_version),
99108
],
100109
visibility = ["//visibility:private"],
@@ -109,13 +118,28 @@ def construct_config_settings(*, name, default_version, versions, minor_mapping,
109118
# It's private because matching the concept of e.g. "3.8" value is done
110119
# using the `is_python_X.Y` config setting group, which is aware of the
111120
# minor versions that could match instead.
121+
first_minor = None
112122
for minor in minor_mapping.keys():
123+
ver = version.parse(minor)
124+
if first_minor == None or version.is_lt(ver, first_minor):
125+
first_minor = ver
126+
113127
native.config_setting(
114128
name = "is_python_{}".format(minor),
115129
flag_values = {_PYTHON_VERSION_MAJOR_MINOR_FLAG: minor},
116130
visibility = ["//visibility:public"],
117131
)
118132

133+
# This is a compatibility layer to ensure that `select` statements don't break out right
134+
# when the toolchains for EOL minor versions are no longer registered.
135+
compat_lowest_version = version.parse(compat_lowest_version)
136+
for minor in range(compat_lowest_version.release[-1], first_minor.release[-1]):
137+
native.alias(
138+
name = "is_python_3.{}".format(minor),
139+
actual = "@platforms//:incompatible",
140+
visibility = ["//visibility:public"],
141+
)
142+
119143
_current_config(
120144
name = "current_config",
121145
build_setting_default = "",

python/private/full_version.bzl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@
1414

1515
"""A small helper to ensure that we are working with full versions."""
1616

17-
def full_version(*, version, minor_mapping):
17+
def full_version(*, version, minor_mapping, fail_on_err = True):
1818
"""Return a full version.
1919
2020
Args:
2121
version: {type}`str` the version in `X.Y` or `X.Y.Z` format.
2222
minor_mapping: {type}`dict[str, str]` mapping between `X.Y` to `X.Y.Z` format.
23+
fail_on_err: {type}`bool` whether to fail on error or return `None` instead.
2324
2425
Returns:
2526
a full version given the version string. If the string is already a
@@ -31,6 +32,8 @@ def full_version(*, version, minor_mapping):
3132
parts = version.split(".")
3233
if len(parts) == 3:
3334
return version
35+
elif not fail_on_err:
36+
return None
3437
elif len(parts) == 2:
3538
fail(
3639
"Unknown Python version '{}', available values are: {}".format(

python/private/pypi/hub_builder.bzl

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,29 @@ def _pip_parse(self, module_ctx, pip_attr):
114114
version = python_version,
115115
))
116116

117-
self._platforms[python_version] = _platforms(
118-
python_version = python_version,
117+
full_python_version = full_version(
118+
version = python_version,
119119
minor_mapping = self._minor_mapping,
120+
fail_on_err = False,
121+
)
122+
if not full_python_version:
123+
# NOTE @aignas 2025-11-18: If the python version is not present in our
124+
# minor_mapping, then we will not register any packages and then the
125+
# select in the hub repository will fail, which will prompt the user to
126+
# configure the toolchain correctly and move forward.
127+
self._logger.info(lambda: (
128+
"Ignoring pip python version '{version}' for hub " +
129+
"'{hub}' in module '{module}' because there is no registered " +
130+
"toolchain for it."
131+
).format(
132+
hub = self.name,
133+
module = self.module_name,
134+
version = python_version,
135+
))
136+
return
137+
138+
self._platforms[python_version] = _platforms(
139+
python_version = full_python_version,
120140
config = self._config,
121141
)
122142
_set_get_index_urls(self, pip_attr)
@@ -280,13 +300,10 @@ def _detect_interpreter(self, pip_attr):
280300
path = pip_attr.python_interpreter,
281301
)
282302

283-
def _platforms(*, python_version, minor_mapping, config):
303+
def _platforms(*, python_version, config):
284304
platforms = {}
285305
python_version = version.parse(
286-
full_version(
287-
version = python_version,
288-
minor_mapping = minor_mapping,
289-
),
306+
python_version,
290307
strict = True,
291308
)
292309

python/private/python.bzl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,18 @@ def _python_impl(module_ctx):
268268
full_python_version = full_version(
269269
version = toolchain_info.python_version,
270270
minor_mapping = py.config.minor_mapping,
271+
fail_on_err = False,
271272
)
273+
if not full_python_version:
274+
logger.info(lambda: (
275+
"The actual toolchain for python_version '{version}' " +
276+
"has not been registered, but was requested, please configure a toolchain " +
277+
"to be actually downloaded and setup"
278+
).format(
279+
version = toolchain_info.python_version,
280+
))
281+
continue
282+
272283
kwargs = {
273284
"python_version": full_python_version,
274285
"register_coverage_tool": toolchain_info.register_coverage_tool,

python/versions.bzl

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -54,17 +54,6 @@ DEFAULT_RELEASE_BASE_URL = "https://github.com/astral-sh/python-build-standalone
5454
#
5555
# buildifier: disable=unsorted-dict-items
5656
TOOL_VERSIONS = {
57-
"3.8.20": {
58-
"url": "20241002/cpython-{python_version}+20241002-{platform}-{build}.tar.gz",
59-
"sha256": {
60-
"aarch64-apple-darwin": "2ddfc04bdb3e240f30fb782fa1deec6323799d0e857e0b63fa299218658fd3d4",
61-
"aarch64-unknown-linux-gnu": "9d8798f9e79e0fc0f36fcb95bfa28a1023407d51a8ea5944b4da711f1f75f1ed",
62-
"x86_64-apple-darwin": "68d060cd373255d2ca5b8b3441363d5aa7cc45b0c11bbccf52b1717c2b5aa8bb",
63-
"x86_64-pc-windows-msvc": "41b6709fec9c56419b7de1940d1f87fa62045aff81734480672dcb807eedc47e",
64-
"x86_64-unknown-linux-gnu": "285e141c36f88b2e9357654c5f77d1f8fb29cc25132698fe35bb30d787f38e87",
65-
},
66-
"strip_prefix": "python",
67-
},
6857
"3.9.25": {
6958
"url": "20251031/cpython-{python_version}+20251031-{platform}-{build}.tar.gz",
7059
"sha256": {
@@ -872,7 +861,6 @@ TOOL_VERSIONS = {
872861

873862
# buildifier: disable=unsorted-dict-items
874863
MINOR_MAPPING = {
875-
"3.8": "3.8.20",
876864
"3.9": "3.9.25",
877865
"3.10": "3.10.19",
878866
"3.11": "3.11.14",

tests/python/python_tests.bzl

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,77 @@ def _test_register_all_versions(env):
707707

708708
_tests.append(_test_register_all_versions)
709709

710+
def _test_ignore_unsupported_versions(env):
711+
py = parse_modules(
712+
module_ctx = _mock_mctx(
713+
_mod(
714+
name = "my_module",
715+
is_root = True,
716+
toolchain = [
717+
_toolchain("3.11"),
718+
_toolchain("3.12"),
719+
_toolchain("3.13", is_default = True),
720+
],
721+
single_version_override = [
722+
_single_version_override(
723+
python_version = "3.13.0",
724+
sha256 = {
725+
"aarch64-unknown-linux-gnu": "deadbeef",
726+
},
727+
urls = ["example.org"],
728+
),
729+
],
730+
single_version_platform_override = [
731+
_single_version_platform_override(
732+
sha256 = "deadb00f",
733+
urls = ["something.org"],
734+
platform = "aarch64-unknown-linux-gnu",
735+
python_version = "3.13.99",
736+
),
737+
],
738+
override = [
739+
_override(
740+
base_url = "",
741+
available_python_versions = ["3.12.4", "3.13.0", "3.13.1"],
742+
minor_mapping = {
743+
"3.12": "3.12.4",
744+
"3.13": "3.13.1",
745+
},
746+
),
747+
],
748+
),
749+
),
750+
logger = repo_utils.logger(verbosity_level = 0, name = "python"),
751+
)
752+
753+
env.expect.that_str(py.default_python_version).equals("3.13")
754+
env.expect.that_collection(py.config.default["tool_versions"].keys()).contains_exactly([
755+
"3.12.4",
756+
"3.13.0",
757+
"3.13.1",
758+
])
759+
env.expect.that_dict(py.config.minor_mapping).contains_exactly({
760+
# The mapping is calculated automatically
761+
"3.12": "3.12.4",
762+
"3.13": "3.13.1",
763+
})
764+
env.expect.that_collection(py.toolchains).contains_exactly([
765+
struct(
766+
name = name,
767+
python_version = version,
768+
register_coverage_tool = False,
769+
)
770+
for name, version in {
771+
# NOTE: that '3.11' wont be actually registered and present in the
772+
# `tool_versions` above.
773+
"python_3_11": "3.11",
774+
"python_3_12": "3.12",
775+
"python_3_13": "3.13",
776+
}.items()
777+
])
778+
779+
_tests.append(_test_ignore_unsupported_versions)
780+
710781
def _test_add_patches(env):
711782
py = parse_modules(
712783
module_ctx = _mock_mctx(

0 commit comments

Comments
 (0)