Skip to content

Commit

Permalink
feat: default py_runtime version info to --python_version (#2198)
Browse files Browse the repository at this point in the history
This changes `py_runtime` to get its interpreter version from the
`--python_version` flag if
it wasn't explicitly specified. This is useful in two contexts:

For the runtime env toolchains, a local toolchain, or platform
interpreter (basically any
py_runtime without a known version), it allows getting some Python
version into the
analysis phase, which allows e.g. precompiling.

For environments using embedded Python, it allows defining fewer (e.g.
1) `py_runtime`
target instead of one for every Python version. This is because
`py_runtime` serves a minor
role in such builds.
  • Loading branch information
rickeylev committed Sep 9, 2024
1 parent c804a13 commit fcd1d5e
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 11 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
47 changes: 36 additions & 11 deletions python/private/common/py_runtime_rule.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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":
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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
29 changes: 29 additions & 0 deletions tests/py_runtime/py_runtime_tests.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []

Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit fcd1d5e

Please sign in to comment.