diff --git a/python/private/config_settings.bzl b/python/private/config_settings.bzl index 91fbbba8cb..fc5eb5a041 100644 --- a/python/private/config_settings.bzl +++ b/python/private/config_settings.bzl @@ -184,6 +184,13 @@ def construct_config_settings( def _python_version_flag_impl(ctx): value = ctx.build_setting_value + if value: + # FIXME @aignas 2025-12-27: is this the right thing to do here? Should we do this only for + # the `FeatureFlagInfo`? + # + # ensure that the values here are normalized + value = version.parse(value).string + return [ # BuildSettingInfo is the original provider returned, so continue to # return it for compatibility @@ -203,9 +210,9 @@ _python_version_flag = rule( ) def _python_version_major_minor_flag_impl(ctx): - input = _flag_value(ctx.attr._python_version_flag) - if input: - ver = version.parse(input) + value = _flag_value(ctx.attr._python_version_flag) + if value: + ver = version.parse(value) value = "{}.{}".format(ver.release[0], ver.release[1]) else: value = "" diff --git a/python/private/pypi/config_settings.bzl b/python/private/pypi/config_settings.bzl index dcb6779d5b..d21bf1e8a5 100644 --- a/python/private/pypi/config_settings.bzl +++ b/python/private/pypi/config_settings.bzl @@ -72,7 +72,7 @@ def config_settings( _dist_config_setting( name = prefix + suffix, flag_values = { - Label("//python/config_settings:python_version_major_minor"): python_version, + Label("//python/config_settings:python_version"): python_version, }, config_settings = config_settings, constraint_values = constraint_values, diff --git a/python/private/pypi/hub_builder.bzl b/python/private/pypi/hub_builder.bzl index 700f22e2c0..6a2f4da466 100644 --- a/python/private/pypi/hub_builder.bzl +++ b/python/private/pypi/hub_builder.bzl @@ -15,9 +15,9 @@ load(":requirements_files_by_platform.bzl", "requirements_files_by_platform") load(":whl_config_setting.bzl", "whl_config_setting") load(":whl_repo_name.bzl", "pypi_repo_name", "whl_repo_name") -def _major_minor_version(version_str): +def _normalize_version(version_str): ver = version.parse(version_str) - return "{}.{}".format(ver.release[0], ver.release[1]) + return ver.string def hub_builder( *, @@ -288,7 +288,8 @@ def _detect_interpreter(self, pip_attr): python_interpreter_target = pip_attr.python_interpreter_target if python_interpreter_target == None and not pip_attr.python_interpreter: python_name = "python_{}_host".format( - pip_attr.python_version.replace(".", "_"), + # normalize the version when getting the available interpreter + version.parse(pip_attr.python_version).string.replace(".", "_"), ) if python_name not in self._available_interpreters: fail(( @@ -478,7 +479,8 @@ def _create_whl_repos( netrc = self._config.netrc or pip_attr.netrc, use_downloader = _use_downloader(self, pip_attr.python_version, whl.name), auth_patterns = self._config.auth_patterns or pip_attr.auth_patterns, - python_version = _major_minor_version(pip_attr.python_version), + # TODO @aignas 2025-12-27: normalize version tests + python_version = _normalize_version(pip_attr.python_version), is_multiple_versions = whl.is_multiple_versions, interpreter = interpreter, enable_pipstar = enable_pipstar, diff --git a/python/private/pypi/render_pkg_aliases.bzl b/python/private/pypi/render_pkg_aliases.bzl index 0a1c328491..f314fc1486 100644 --- a/python/private/pypi/render_pkg_aliases.bzl +++ b/python/private/pypi/render_pkg_aliases.bzl @@ -144,12 +144,14 @@ def _major_minor(python_version): minor, _, _ = tail.partition(".") return "{}.{}".format(major, minor) -def _major_minor_versions(python_versions): +def _with_major_minor_versions(python_versions): if not python_versions: return [] # Use a dict as a simple set - return sorted({_major_minor(v): None for v in python_versions}) + ret = {_major_minor(v): None for v in python_versions} + ret.update({v: None for v in python_versions}) + return sorted(ret) def render_multiplatform_pkg_aliases(*, aliases, platform_config_settings = {}, **kwargs): """Render the multi-platform pkg aliases. @@ -178,7 +180,7 @@ def render_multiplatform_pkg_aliases(*, aliases, platform_config_settings = {}, **kwargs ) contents["_config/BUILD.bazel"] = _render_config_settings( - python_versions = _major_minor_versions(flag_versions.get("python_versions", [])), + python_versions = _with_major_minor_versions(flag_versions.get("python_versions", [])), platform_config_settings = platform_config_settings, visibility = ["//:__subpackages__"], ) diff --git a/python/private/version_label.bzl b/python/private/version_label.bzl index 1bca92cfd8..09130fc100 100644 --- a/python/private/version_label.bzl +++ b/python/private/version_label.bzl @@ -14,13 +14,18 @@ "" +load(":version.bzl", v = "version") + def version_label(version, *, sep = ""): - """A version fragment derived from python minor version + """A version fragment derived from python minor version. + + This replaces dots with the provided separator. Examples: version_label("3.9") == "39" - version_label("3.9.12", sep="_") == "3_9" + version_label("3.9.12", sep="_") == "3_9_12" version_label("3.11") == "311" + version_label("3.11.12") == "31112" Args: version: Python version. @@ -30,7 +35,5 @@ def version_label(version, *, sep = ""): Returns: The fragment of the version. """ - major, _, version = version.partition(".") - minor, _, _ = version.partition(".") - - return major + sep + minor + parsed = v.parse(version) + return parsed.string.replace(".", sep) diff --git a/tests/pypi/hub_builder/hub_builder_tests.bzl b/tests/pypi/hub_builder/hub_builder_tests.bzl index 42c65ae8f7..2bd91beb3c 100644 --- a/tests/pypi/hub_builder/hub_builder_tests.bzl +++ b/tests/pypi/hub_builder/hub_builder_tests.bzl @@ -17,6 +17,8 @@ load("@rules_testing//lib:test_suite.bzl", "test_suite") load("@rules_testing//lib:truth.bzl", "subjects") load("//python/private:repo_utils.bzl", "REPO_DEBUG_ENV_VAR", "REPO_VERBOSITY_ENV_VAR", "repo_utils") # buildifier: disable=bzl-visibility +load("//python/private:version.bzl", _version = "version") # buildifier: disable=bzl-visibility +load("//python/private:version_label.bzl", "version_label") # buildifier: disable=bzl-visibility load("//python/private/pypi:hub_builder.bzl", _hub_builder = "hub_builder") # buildifier: disable=bzl-visibility load("//python/private/pypi:parse_simpleapi_html.bzl", "parse_simpleapi_html") # buildifier: disable=bzl-visibility load("//python/private/pypi:platform.bzl", _plat = "platform") # buildifier: disable=bzl-visibility @@ -114,40 +116,58 @@ def hub_builder( return self def _test_simple(env): - builder = hub_builder(env) - builder.pip_parse( - _mock_mctx( - os_name = "osx", - arch_name = "aarch64", - ), - _parse( - hub_name = "pypi", - python_version = "3.15", - requirements_lock = "requirements.txt", - ), - ) - pypi = builder.build() + for python_version, _ in { + "3.15": "315", + "3.15.0alpha2": "3150a2", + "3.15.0rc2": "3150rc2", + "3.15.2": "3152", + }.items(): + # All of the versions should be normalized + normalized_python_version = _version.parse(python_version).string - pypi.exposed_packages().contains_exactly(["simple"]) - pypi.group_map().contains_exactly({}) - pypi.whl_map().contains_exactly({ - "simple": { - "pypi_315_simple": [ - whl_config_setting( - version = "3.15", - ), - ], - }, - }) - pypi.whl_libraries().contains_exactly({ - "pypi_315_simple": { - "config_load": "@pypi//:config.bzl", - "dep_template": "@pypi//{name}:{target}", - "python_interpreter_target": "unit_test_interpreter_target", - "requirement": "simple==0.0.1 --hash=sha256:deadbeef --hash=sha256:deadbaaf", - }, - }) - pypi.extra_aliases().contains_exactly({}) + builder = hub_builder( + env, + available_interpreters = { + # We are using this instead of `version_label` to keep backwards compatibility + "python_{}_host".format(normalized_python_version.replace(".", "_")): "unit_test_interpreter_target", + }, + ) + builder.pip_parse( + _mock_mctx( + os_name = "osx", + arch_name = "aarch64", + ), + _parse( + hub_name = "pypi", + # the input version can be whatever + python_version = python_version, + requirements_lock = "requirements.txt", + ), + ) + pypi = builder.build() + + pypi.exposed_packages().contains_exactly(["simple"]) + pypi.group_map().contains_exactly({}) + pypi.whl_map().contains_exactly({ + "simple": { + # this should normalize the version inside + "pypi_{}_simple".format(version_label(python_version)): [ + whl_config_setting( + version = normalized_python_version, + ), + ], + }, + }) + pypi.whl_libraries().contains_exactly({ + # this should normalize the version inside + "pypi_{}_simple".format(version_label(python_version)): { + "config_load": "@pypi//:config.bzl", + "dep_template": "@pypi//{name}:{target}", + "python_interpreter_target": "unit_test_interpreter_target", + "requirement": "simple==0.0.1 --hash=sha256:deadbeef --hash=sha256:deadbaaf", + }, + }) + pypi.extra_aliases().contains_exactly({}) _tests.append(_test_simple) diff --git a/tests/version_label/version_label_test.bzl b/tests/version_label/version_label_test.bzl index b4ed6f9270..a278ed1953 100644 --- a/tests/version_label/version_label_test.bzl +++ b/tests/version_label/version_label_test.bzl @@ -27,7 +27,7 @@ _tests.append(_test_version_label_from_major_minor_version) def _test_version_label_from_major_minor_patch_version(env): actual = version_label("3.9.3") - env.expect.that_str(actual).equals("39") + env.expect.that_str(actual).equals("393") _tests.append(_test_version_label_from_major_minor_patch_version) @@ -39,7 +39,10 @@ _tests.append(_test_version_label_from_major_minor_version_custom_sep) def _test_version_label_from_complex_version(env): actual = version_label("3.9.3-rc.0") - env.expect.that_str(actual).equals("39") + env.expect.that_str(actual).equals("393rc0") + + actual = version_label("3.9.3-alpha1") + env.expect.that_str(actual).equals("393a1") _tests.append(_test_version_label_from_complex_version)