diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e2f9bb1fb..c4c99206b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,10 @@ A brief description of the categories of changes: * (gazelle): Update error messages when unable to resolve a dependency to be more human-friendly. * (flags) The {obj}`--python_version` flag now also returns {obj}`config_common.FeatureFlagInfo`. +* (toolchains) When {obj}`py_runtime.interpreter_version_info` isn't specified, + the {obj}`--python_version` flag will determine the value. This allows + specifying the build-time Python version for the + {obj}`runtime_env_toolchains`. * (toolchains) {obj}`py_cc_toolchain.libs` and {obj}`PyCcToolchainInfo.libs` is optional. This is to support situations where only the Python headers are available. diff --git a/python/private/common/py_runtime_rule.bzl b/python/private/common/py_runtime_rule.bzl index e0b5fb2313..dd40f76a00 100644 --- a/python/private/common/py_runtime_rule.bzl +++ b/python/private/common/py_runtime_rule.bzl @@ -15,6 +15,7 @@ load("@bazel_skylib//lib:dicts.bzl", "dicts") load("@bazel_skylib//lib:paths.bzl", "paths") +load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo") load("//python/private:reexports.bzl", "BuiltinPyRuntimeInfo") load("//python/private:util.bzl", "IS_BAZEL_7_OR_HIGHER") load(":attributes.bzl", "NATIVE_RULES_ALLOWLIST_ATTRS") @@ -80,6 +81,10 @@ def _py_runtime_impl(ctx): python_version = ctx.attr.python_version interpreter_version_info = ctx.attr.interpreter_version_info + if not interpreter_version_info: + python_version_flag = ctx.attr._python_version_flag[BuildSettingInfo].value + if python_version_flag: + interpreter_version_info = _interpreter_version_info_from_version_str(python_version_flag) # TODO: Uncomment this after --incompatible_python_disable_py2 defaults to true # if ctx.fragments.py.disable_py2 and python_version == "PY2": @@ -133,13 +138,6 @@ def _py_runtime_impl(ctx): ), ] -def _is_singleton_depset(files): - # Bazel 6 doesn't have this helper to optimize detecting singleton depsets. - if _py_builtins: - return _py_builtins.is_singleton_depset(files) - else: - return len(files.to_list()) == 1 - # Bind to the name "py_runtime" to preserve the kind/rule_class it shows up # as elsewhere. py_runtime = rule( @@ -260,15 +258,22 @@ the target platform. For an in-build runtime this attribute must not be set. """), "interpreter_version_info": attr.string_dict( doc = """ -Version information about the interpreter this runtime provides. The -supported keys match the names for `sys.version_info`. While the input +Version information about the interpreter this runtime provides. + +If not specified, uses {obj}`--python_version` + +The supported keys match the names for `sys.version_info`. While the input values are strings, most are converted to ints. The supported keys are: * major: int, the major version number * minor: int, the minor version number * micro: optional int, the micro version number * releaselevel: optional str, the release level - * serial: optional int, the serial number of the release" - """, + * serial: optional int, the serial number of the release + +:::{versionchanged} 0.36.0 +{obj}`--python_version` determines the default value. +::: +""", mandatory = False, ), "pyc_tag": attr.string( @@ -327,5 +332,25 @@ The {obj}`PyRuntimeInfo.zip_main_template` field. ::: """, ), + "_python_version_flag": attr.label( + default = "//python/config_settings:python_version", + ), }), ) + +def _is_singleton_depset(files): + # Bazel 6 doesn't have this helper to optimize detecting singleton depsets. + if _py_builtins: + return _py_builtins.is_singleton_depset(files) + else: + return len(files.to_list()) == 1 + +def _interpreter_version_info_from_version_str(version_str): + parts = version_str.split(".") + version_info = {} + for key in ("major", "minor", "micro"): + if not parts: + break + version_info[key] = parts.pop(0) + + return version_info diff --git a/tests/py_runtime/py_runtime_tests.bzl b/tests/py_runtime/py_runtime_tests.bzl index 596cace4fc..d5a6076153 100644 --- a/tests/py_runtime/py_runtime_tests.bzl +++ b/tests/py_runtime/py_runtime_tests.bzl @@ -22,6 +22,7 @@ load("//python:py_runtime.bzl", "py_runtime") load("//python:py_runtime_info.bzl", "PyRuntimeInfo") load("//tests/base_rules:util.bzl", br_util = "util") load("//tests/support:py_runtime_info_subject.bzl", "py_runtime_info_subject") +load("//tests/support:support.bzl", "PYTHON_VERSION") _tests = [] @@ -528,6 +529,34 @@ def _test_interpreter_version_info_parses_values_to_struct_impl(env, target): _tests.append(_test_interpreter_version_info_parses_values_to_struct) +def _test_version_info_from_flag(name): + if not config.enable_pystar: + rt_util.skip_test(name) + return + py_runtime( + name = name + "_subject", + interpreter_version_info = None, + interpreter_path = "/bogus", + ) + analysis_test( + name = name, + target = name + "_subject", + impl = _test_version_info_from_flag_impl, + config_settings = { + PYTHON_VERSION: "3.12", + }, + ) + +def _test_version_info_from_flag_impl(env, target): + version_info = env.expect.that_target(target).provider(PyRuntimeInfo, factory = py_runtime_info_subject).interpreter_version_info() + version_info.major().equals(3) + version_info.minor().equals(12) + version_info.micro().equals(None) + version_info.releaselevel().equals(None) + version_info.serial().equals(None) + +_tests.append(_test_version_info_from_flag) + def py_runtime_test_suite(name): test_suite( name = name,