From 64f91a7c394e187ab01cc1a686b6284579f19b1b Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Sun, 2 Nov 2025 01:16:00 +0900 Subject: [PATCH 01/12] exp: test what breaks if we drop 3.8 from minor versions --- python/versions.bzl | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/python/versions.bzl b/python/versions.bzl index 6de09ca72d..d3a857999a 100644 --- a/python/versions.bzl +++ b/python/versions.bzl @@ -54,17 +54,6 @@ DEFAULT_RELEASE_BASE_URL = "https://github.com/astral-sh/python-build-standalone # # buildifier: disable=unsorted-dict-items TOOL_VERSIONS = { - "3.8.20": { - "url": "20241002/cpython-{python_version}+20241002-{platform}-{build}.tar.gz", - "sha256": { - "aarch64-apple-darwin": "2ddfc04bdb3e240f30fb782fa1deec6323799d0e857e0b63fa299218658fd3d4", - "aarch64-unknown-linux-gnu": "9d8798f9e79e0fc0f36fcb95bfa28a1023407d51a8ea5944b4da711f1f75f1ed", - "x86_64-apple-darwin": "68d060cd373255d2ca5b8b3441363d5aa7cc45b0c11bbccf52b1717c2b5aa8bb", - "x86_64-pc-windows-msvc": "41b6709fec9c56419b7de1940d1f87fa62045aff81734480672dcb807eedc47e", - "x86_64-unknown-linux-gnu": "285e141c36f88b2e9357654c5f77d1f8fb29cc25132698fe35bb30d787f38e87", - }, - "strip_prefix": "python", - }, "3.9.10": { "url": "20220227/cpython-{python_version}+20220227-{platform}-{build}.tar.gz", "sha256": { @@ -1011,7 +1000,6 @@ TOOL_VERSIONS = { # buildifier: disable=unsorted-dict-items MINOR_MAPPING = { - "3.8": "3.8.20", "3.9": "3.9.24", "3.10": "3.10.19", "3.11": "3.11.14", From 9b4a71a5cb6e1982a7ab9acc7326f72e2744f1c6 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Sun, 2 Nov 2025 01:20:42 +0900 Subject: [PATCH 02/12] gazelle should not have 3.8 select --- gazelle/python/BUILD.bazel | 1 - 1 file changed, 1 deletion(-) diff --git a/gazelle/python/BUILD.bazel b/gazelle/python/BUILD.bazel index b988e493c7..ee2dce797b 100644 --- a/gazelle/python/BUILD.bazel +++ b/gazelle/python/BUILD.bazel @@ -54,7 +54,6 @@ copy_file( "@rules_python//python/config_settings:is_python_3.10": "@python_stdlib_list//:stdlib_list/lists/3.10.txt", "@rules_python//python/config_settings:is_python_3.11": "@python_stdlib_list//:stdlib_list/lists/3.11.txt", "@rules_python//python/config_settings:is_python_3.12": "@python_stdlib_list//:stdlib_list/lists/3.12.txt", - "@rules_python//python/config_settings:is_python_3.8": "@python_stdlib_list//:stdlib_list/lists/3.8.txt", "@rules_python//python/config_settings:is_python_3.9": "@python_stdlib_list//:stdlib_list/lists/3.9.txt", # This is the same behaviour as previously "//conditions:default": "@python_stdlib_list//:stdlib_list/lists/3.11.txt", From f14bea6d8f798718fb61c388dc55ff0dd55d6309 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Tue, 4 Nov 2025 23:57:01 +0900 Subject: [PATCH 03/12] add warnings for the python toolchains that have been dropped --- python/private/config_settings.bzl | 24 +++++++++++++++++++++++- python/private/full_version.bzl | 5 ++++- python/private/pypi/hub_builder.bzl | 27 ++++++++++++++++++++------- python/private/python.bzl | 5 +++++ 4 files changed, 52 insertions(+), 9 deletions(-) diff --git a/python/private/config_settings.bzl b/python/private/config_settings.bzl index 3089b9c6cf..8bb932f94a 100644 --- a/python/private/config_settings.bzl +++ b/python/private/config_settings.bzl @@ -35,7 +35,13 @@ If the value is missing, then the default value is being used, see documentation # access it, but it's not intended for general public usage. _NOT_ACTUALLY_PUBLIC = ["//visibility:public"] -def construct_config_settings(*, name, default_version, versions, minor_mapping, documented_flags): # buildifier: disable=function-docstring +def construct_config_settings( + *, + name, + default_version, + versions, + minor_mapping, + documented_flags): # buildifier: disable=function-docstring """Create a 'python_version' config flag and construct all config settings used in rules_python. This mainly includes the targets that are used in the toolchain and pip hub @@ -109,13 +115,29 @@ def construct_config_settings(*, name, default_version, versions, minor_mapping, # It's private because matching the concept of e.g. "3.8" value is done # using the `is_python_X.Y` config setting group, which is aware of the # minor versions that could match instead. + first_minor = None for minor in minor_mapping.keys(): + if first_minor == None: + first_minor = minor + native.config_setting( name = "is_python_{}".format(minor), flag_values = {_PYTHON_VERSION_MAJOR_MINOR_FLAG: minor}, visibility = ["//visibility:public"], ) + for minor in range(int(first_minor.partition(".")[-1]) + 1): + minor = "3.{}".format(minor) + if minor in minor_mapping: + break + + # TODO @aignas 2025-11-04: use env-marker-setting with the smallest minor_mapping version + native.alias( + name = "is_python_{}".format(minor), + actual = "@platforms//:incompatible", + visibility = ["//visibility:public"], + ) + _current_config( name = "current_config", build_setting_default = "", diff --git a/python/private/full_version.bzl b/python/private/full_version.bzl index 0292d6c77d..82ac12e11e 100644 --- a/python/private/full_version.bzl +++ b/python/private/full_version.bzl @@ -14,12 +14,13 @@ """A small helper to ensure that we are working with full versions.""" -def full_version(*, version, minor_mapping): +def full_version(*, version, minor_mapping, err = True): """Return a full version. Args: version: {type}`str` the version in `X.Y` or `X.Y.Z` format. minor_mapping: {type}`dict[str, str]` mapping between `X.Y` to `X.Y.Z` format. + err: {type}`bool` whether to fail on error or return `None` instead. Returns: a full version given the version string. If the string is already a @@ -31,6 +32,8 @@ def full_version(*, version, minor_mapping): parts = version.split(".") if len(parts) == 3: return version + elif not err: + return None elif len(parts) == 2: fail( "Unknown Python version '{}', available values are: {}".format( diff --git a/python/private/pypi/hub_builder.bzl b/python/private/pypi/hub_builder.bzl index 58d35f2681..4f09f8102a 100644 --- a/python/private/pypi/hub_builder.bzl +++ b/python/private/pypi/hub_builder.bzl @@ -114,9 +114,25 @@ def _pip_parse(self, module_ctx, pip_attr): version = python_version, )) - self._platforms[python_version] = _platforms( - python_version = python_version, + full_python_version = full_version( + version = python_version, minor_mapping = self._minor_mapping, + err = False, + ) + if not full_python_version: + self._logger.warn(lambda: ( + "Ignoring pip python version '{version}' for hub " + + "'{hub}' in module '{module}' because there is no registered " + + "toolchain for it." + ).format( + hub = self.name, + module = self.module_name, + version = python_version, + )) + return + + self._platforms[python_version] = _platforms( + python_version = full_python_version, config = self._config, ) _set_get_index_urls(self, pip_attr) @@ -280,13 +296,10 @@ def _detect_interpreter(self, pip_attr): path = pip_attr.python_interpreter, ) -def _platforms(*, python_version, minor_mapping, config): +def _platforms(*, python_version, config): platforms = {} python_version = version.parse( - full_version( - version = python_version, - minor_mapping = minor_mapping, - ), + python_version, strict = True, ) diff --git a/python/private/python.bzl b/python/private/python.bzl index a1fe80e0ce..25af132f97 100644 --- a/python/private/python.bzl +++ b/python/private/python.bzl @@ -268,7 +268,12 @@ def _python_impl(module_ctx): full_python_version = full_version( version = toolchain_info.python_version, minor_mapping = py.config.minor_mapping, + err = False, ) + if not full_python_version: + logger.warn(lambda: "The python version '{}' is unknown, please configure a toolchain to be downloaded".format(toolchain_info.python_version)) + continue + kwargs = { "python_version": full_python_version, "register_coverage_tool": toolchain_info.register_coverage_tool, From 27de73079cd69ed37cbdc0d92c5d2f7e7571d1ca Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Tue, 18 Nov 2025 17:07:43 +0900 Subject: [PATCH 04/12] lower warn to info --- python/private/pypi/hub_builder.bzl | 6 +++++- python/private/python.bzl | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/python/private/pypi/hub_builder.bzl b/python/private/pypi/hub_builder.bzl index 4f09f8102a..d0f18a195a 100644 --- a/python/private/pypi/hub_builder.bzl +++ b/python/private/pypi/hub_builder.bzl @@ -120,7 +120,11 @@ def _pip_parse(self, module_ctx, pip_attr): err = False, ) if not full_python_version: - self._logger.warn(lambda: ( + # NOTE @aignas 2025-11-18: If the python version is not present in our + # minor_mapping, then we will not register any packages and then the + # select in the hub repository will fail, which will prompt the user to + # configure the toolchain correctly and move forward. + self._logger.info(lambda: ( "Ignoring pip python version '{version}' for hub " + "'{hub}' in module '{module}' because there is no registered " + "toolchain for it." diff --git a/python/private/python.bzl b/python/private/python.bzl index 25af132f97..d5d06ef79c 100644 --- a/python/private/python.bzl +++ b/python/private/python.bzl @@ -271,7 +271,7 @@ def _python_impl(module_ctx): err = False, ) if not full_python_version: - logger.warn(lambda: "The python version '{}' is unknown, please configure a toolchain to be downloaded".format(toolchain_info.python_version)) + logger.info(lambda: "The python version '{}' is unknown, please configure a toolchain to be downloaded".format(toolchain_info.python_version)) continue kwargs = { From c07bfea26d263c1a7cc130139ab4c5e26acbe026 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Wed, 19 Nov 2025 11:35:48 +0900 Subject: [PATCH 05/12] tidy up the logic and comment for config settings --- python/private/config_settings.bzl | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/python/private/config_settings.bzl b/python/private/config_settings.bzl index 8bb932f94a..612c6be020 100644 --- a/python/private/config_settings.bzl +++ b/python/private/config_settings.bzl @@ -75,21 +75,21 @@ def construct_config_settings( ) _reverse_minor_mapping = {full: minor for minor, full in minor_mapping.items()} - for version in versions: - minor_version = _reverse_minor_mapping.get(version) + for ver in versions: + minor_version = _reverse_minor_mapping.get(ver) if not minor_version: native.config_setting( - name = "is_python_{}".format(version), - flag_values = {":python_version": version}, + name = "is_python_{}".format(ver), + flag_values = {":python_version": ver}, visibility = ["//visibility:public"], ) continue # Also need to match the minor version when using - name = "is_python_{}".format(version) + name = "is_python_{}".format(ver) native.config_setting( name = "_" + name, - flag_values = {":python_version": version}, + flag_values = {":python_version": ver}, visibility = ["//visibility:public"], ) @@ -100,7 +100,7 @@ def construct_config_settings( selects.config_setting_group( name = "_{}_group".format(name), match_any = [ - ":_is_python_{}".format(version), + ":_is_python_{}".format(ver), ":is_python_{}".format(minor_version), ], visibility = ["//visibility:private"], @@ -117,8 +117,9 @@ def construct_config_settings( # minor versions that could match instead. first_minor = None for minor in minor_mapping.keys(): - if first_minor == None: - first_minor = minor + ver = version.parse(minor) + if first_minor == None or version.is_lt(ver, first_minor): + first_minor = ver native.config_setting( name = "is_python_{}".format(minor), @@ -126,14 +127,11 @@ def construct_config_settings( visibility = ["//visibility:public"], ) - for minor in range(int(first_minor.partition(".")[-1]) + 1): - minor = "3.{}".format(minor) - if minor in minor_mapping: - break - - # TODO @aignas 2025-11-04: use env-marker-setting with the smallest minor_mapping version + # This is a compatibility layer to ensure that `select` statements don't break out right + # when the toolchains for EOL minor versions are no longer registered. + for minor in range(first_minor.release[-1]): native.alias( - name = "is_python_{}".format(minor), + name = "is_python_3.{}".format(minor), actual = "@platforms//:incompatible", visibility = ["//visibility:public"], ) From 1d8efe1b03d301ab2b52f3422d9b8f301f7c03cf Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Wed, 19 Nov 2025 11:36:17 +0900 Subject: [PATCH 06/12] revert the gazelle change --- gazelle/python/BUILD.bazel | 1 + 1 file changed, 1 insertion(+) diff --git a/gazelle/python/BUILD.bazel b/gazelle/python/BUILD.bazel index ee2dce797b..b988e493c7 100644 --- a/gazelle/python/BUILD.bazel +++ b/gazelle/python/BUILD.bazel @@ -54,6 +54,7 @@ copy_file( "@rules_python//python/config_settings:is_python_3.10": "@python_stdlib_list//:stdlib_list/lists/3.10.txt", "@rules_python//python/config_settings:is_python_3.11": "@python_stdlib_list//:stdlib_list/lists/3.11.txt", "@rules_python//python/config_settings:is_python_3.12": "@python_stdlib_list//:stdlib_list/lists/3.12.txt", + "@rules_python//python/config_settings:is_python_3.8": "@python_stdlib_list//:stdlib_list/lists/3.8.txt", "@rules_python//python/config_settings:is_python_3.9": "@python_stdlib_list//:stdlib_list/lists/3.9.txt", # This is the same behaviour as previously "//conditions:default": "@python_stdlib_list//:stdlib_list/lists/3.11.txt", From 2fabab1a1665b32ebad17294e4b890ef62fb59f9 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Wed, 19 Nov 2025 11:45:41 +0900 Subject: [PATCH 07/12] Fix wording --- python/private/python.bzl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/private/python.bzl b/python/private/python.bzl index d5d06ef79c..124aea7d63 100644 --- a/python/private/python.bzl +++ b/python/private/python.bzl @@ -271,7 +271,7 @@ def _python_impl(module_ctx): err = False, ) if not full_python_version: - logger.info(lambda: "The python version '{}' is unknown, please configure a toolchain to be downloaded".format(toolchain_info.python_version)) + logger.info(lambda: "The actual toolchain for python_version '{}' has not been registered, but was requested, please configure a toolchain to be actually downloaded and setup".format(toolchain_info.python_version)) continue kwargs = { From c0d1d6561f5e4bfdc0b9e2867e10e2af50392c41 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Wed, 19 Nov 2025 13:36:59 +0900 Subject: [PATCH 08/12] only add compatibility values for 3.8 and above --- python/private/config_settings.bzl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/python/private/config_settings.bzl b/python/private/config_settings.bzl index 612c6be020..91fbbba8cb 100644 --- a/python/private/config_settings.bzl +++ b/python/private/config_settings.bzl @@ -41,6 +41,7 @@ def construct_config_settings( default_version, versions, minor_mapping, + compat_lowest_version = "3.8", documented_flags): # buildifier: disable=function-docstring """Create a 'python_version' config flag and construct all config settings used in rules_python. @@ -52,6 +53,8 @@ def construct_config_settings( default_version: {type}`str` the default value for the `python_version` flag. versions: {type}`list[str]` A list of versions to build constraint settings for. minor_mapping: {type}`dict[str, str]` A mapping from `X.Y` to `X.Y.Z` python versions. + compat_lowest_version: {type}`str` The version that we should use as the lowest available + version for `is_python_3.X` flags. documented_flags: {type}`list[str]` The labels of the documented settings that affect build configuration. """ @@ -129,7 +132,8 @@ def construct_config_settings( # This is a compatibility layer to ensure that `select` statements don't break out right # when the toolchains for EOL minor versions are no longer registered. - for minor in range(first_minor.release[-1]): + compat_lowest_version = version.parse(compat_lowest_version) + for minor in range(compat_lowest_version.release[-1], first_minor.release[-1]): native.alias( name = "is_python_3.{}".format(minor), actual = "@platforms//:incompatible", From a8ed451677c5e97fc93da699985a16b246f8fc39 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Wed, 19 Nov 2025 16:06:03 +0900 Subject: [PATCH 09/12] add a test --- tests/python/python_tests.bzl | 71 +++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/tests/python/python_tests.bzl b/tests/python/python_tests.bzl index f2e87274f8..ff02cc859e 100644 --- a/tests/python/python_tests.bzl +++ b/tests/python/python_tests.bzl @@ -707,6 +707,77 @@ def _test_register_all_versions(env): _tests.append(_test_register_all_versions) +def _test_ignore_unsupported_versions(env): + py = parse_modules( + module_ctx = _mock_mctx( + _mod( + name = "my_module", + is_root = True, + toolchain = [ + _toolchain("3.11"), + _toolchain("3.12"), + _toolchain("3.13", is_default = True), + ], + single_version_override = [ + _single_version_override( + python_version = "3.13.0", + sha256 = { + "aarch64-unknown-linux-gnu": "deadbeef", + }, + urls = ["example.org"], + ), + ], + single_version_platform_override = [ + _single_version_platform_override( + sha256 = "deadb00f", + urls = ["something.org"], + platform = "aarch64-unknown-linux-gnu", + python_version = "3.13.99", + ), + ], + override = [ + _override( + base_url = "", + available_python_versions = ["3.12.4", "3.13.0", "3.13.1"], + minor_mapping = { + "3.12": "3.12.4", + "3.13": "3.13.1", + }, + ), + ], + ), + ), + logger = repo_utils.logger(verbosity_level = 0, name = "python"), + ) + + env.expect.that_str(py.default_python_version).equals("3.13") + env.expect.that_collection(py.config.default["tool_versions"].keys()).contains_exactly([ + "3.12.4", + "3.13.0", + "3.13.1", + ]) + env.expect.that_dict(py.config.minor_mapping).contains_exactly({ + # The mapping is calculated automatically + "3.12": "3.12.4", + "3.13": "3.13.1", + }) + env.expect.that_collection(py.toolchains).contains_exactly([ + struct( + name = name, + python_version = version, + register_coverage_tool = False, + ) + for name, version in { + # NOTE: that '3.11' wont be actually registered and present in the + # `tool_versions` above. + "python_3_11": "3.11", + "python_3_12": "3.12", + "python_3_13": "3.13", + }.items() + ]) + +_tests.append(_test_ignore_unsupported_versions) + def _test_add_patches(env): py = parse_modules( module_ctx = _mock_mctx( From 4e2cac0023278126e07b054ce1d7a66cb4d4d025 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Thu, 20 Nov 2025 11:19:54 +0900 Subject: [PATCH 10/12] comment: split lines --- python/private/python.bzl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/python/private/python.bzl b/python/private/python.bzl index 124aea7d63..2e55912458 100644 --- a/python/private/python.bzl +++ b/python/private/python.bzl @@ -271,7 +271,11 @@ def _python_impl(module_ctx): err = False, ) if not full_python_version: - logger.info(lambda: "The actual toolchain for python_version '{}' has not been registered, but was requested, please configure a toolchain to be actually downloaded and setup".format(toolchain_info.python_version)) + logger.info(lambda: ( + "The actual toolchain for python_version '{}' ".format(toolchain_info.python_version) + + "has not been registered, but was requested, please configure a toolchain " + + "to be actually downloaded and setup" + )) continue kwargs = { From a36758a9871e566a799db330b727b261a526f46e Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Thu, 20 Nov 2025 11:21:27 +0900 Subject: [PATCH 11/12] comment: rename to fail_on_err --- python/private/full_version.bzl | 6 +++--- python/private/pypi/hub_builder.bzl | 2 +- python/private/python.bzl | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/python/private/full_version.bzl b/python/private/full_version.bzl index 82ac12e11e..0be5b44daf 100644 --- a/python/private/full_version.bzl +++ b/python/private/full_version.bzl @@ -14,13 +14,13 @@ """A small helper to ensure that we are working with full versions.""" -def full_version(*, version, minor_mapping, err = True): +def full_version(*, version, minor_mapping, fail_on_err = True): """Return a full version. Args: version: {type}`str` the version in `X.Y` or `X.Y.Z` format. minor_mapping: {type}`dict[str, str]` mapping between `X.Y` to `X.Y.Z` format. - err: {type}`bool` whether to fail on error or return `None` instead. + fail_on_err: {type}`bool` whether to fail on error or return `None` instead. Returns: a full version given the version string. If the string is already a @@ -32,7 +32,7 @@ def full_version(*, version, minor_mapping, err = True): parts = version.split(".") if len(parts) == 3: return version - elif not err: + elif not fail_on_err: return None elif len(parts) == 2: fail( diff --git a/python/private/pypi/hub_builder.bzl b/python/private/pypi/hub_builder.bzl index 4abd141f27..bd6008128b 100644 --- a/python/private/pypi/hub_builder.bzl +++ b/python/private/pypi/hub_builder.bzl @@ -117,7 +117,7 @@ def _pip_parse(self, module_ctx, pip_attr): full_python_version = full_version( version = python_version, minor_mapping = self._minor_mapping, - err = False, + fail_on_err = False, ) if not full_python_version: # NOTE @aignas 2025-11-18: If the python version is not present in our diff --git a/python/private/python.bzl b/python/private/python.bzl index 2e55912458..cb14969ba0 100644 --- a/python/private/python.bzl +++ b/python/private/python.bzl @@ -268,7 +268,7 @@ def _python_impl(module_ctx): full_python_version = full_version( version = toolchain_info.python_version, minor_mapping = py.config.minor_mapping, - err = False, + fail_on_err = False, ) if not full_python_version: logger.info(lambda: ( From 8be613548d07a9036c3ad974390bca00574438e4 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Thu, 20 Nov 2025 11:24:41 +0900 Subject: [PATCH 12/12] make the style the same --- python/private/python.bzl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/python/private/python.bzl b/python/private/python.bzl index cb14969ba0..22f4753a62 100644 --- a/python/private/python.bzl +++ b/python/private/python.bzl @@ -272,9 +272,11 @@ def _python_impl(module_ctx): ) if not full_python_version: logger.info(lambda: ( - "The actual toolchain for python_version '{}' ".format(toolchain_info.python_version) + + "The actual toolchain for python_version '{version}' " + "has not been registered, but was requested, please configure a toolchain " + "to be actually downloaded and setup" + ).format( + version = toolchain_info.python_version, )) continue