Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add interpreter_version_info to py_runtime #1671

Merged
merged 4 commits into from
Jan 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ A brief description of the categories of changes:
method to make imports more ergonomic. Users should only need to import the
`Runfiles` object to locate runfiles.

* (toolchains) `PyRuntimeInfo` now includes a `interpreter_version_info` field
that contains the static version information for the given interpreter.
This can be set via `py_runtime` when registering an interpreter toolchain,
and will done automatically for the builtin interpreter versions registered via
`python_register_toolchains`.
Note that this only available on the Starlark implementation of the provider.

## [0.28.0] - 2024-01-07

[0.28.0]: https://github.com/bazelbuild/rules_python/releases/tag/0.28.0
Expand Down
34 changes: 33 additions & 1 deletion python/private/common/providers.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ def _PyRuntimeInfo_init(
coverage_files = None,
python_version,
stub_shebang = None,
bootstrap_template = None):
bootstrap_template = None,
interpreter_version_info = None):
if (interpreter_path and interpreter) or (not interpreter_path and not interpreter):
fail("exactly one of interpreter or interpreter_path must be specified")

Expand Down Expand Up @@ -82,13 +83,32 @@ def _PyRuntimeInfo_init(
if not stub_shebang:
stub_shebang = DEFAULT_STUB_SHEBANG

if interpreter_version_info:
if not ("major" in interpreter_version_info and "minor" in interpreter_version_info):
fail("interpreter_version_info must have at least two keys, 'major' and 'minor'")

_interpreter_version_info = dict(**interpreter_version_info)
interpreter_version_info = struct(
major = int(_interpreter_version_info.pop("major")),
minor = int(_interpreter_version_info.pop("minor")),
micro = int(_interpreter_version_info.pop("micro")) if "micro" in _interpreter_version_info else None,
releaselevel = str(_interpreter_version_info.pop("releaselevel")) if "releaselevel" in _interpreter_version_info else None,
serial = int(_interpreter_version_info.pop("serial")) if "serial" in _interpreter_version_info else None,
)

if len(_interpreter_version_info.keys()) > 0:
fail("unexpected keys {} in interpreter_version_info".format(
str(_interpreter_version_info.keys()),
))

return {
"bootstrap_template": bootstrap_template,
"coverage_files": coverage_files,
"coverage_tool": coverage_tool,
"files": files,
"interpreter": interpreter,
"interpreter_path": interpreter_path,
"interpreter_version_info": interpreter_version_info,
"python_version": python_version,
"stub_shebang": stub_shebang,
}
Expand Down Expand Up @@ -136,6 +156,18 @@ the same conventions as the standard CPython interpreter.
"filesystem path to the interpreter on the target platform. " +
"Otherwise, this is `None`."
),
"interpreter_version_info": (
mattem marked this conversation as resolved.
Show resolved Hide resolved
"Version information about the interpreter this runtime provides. " +
"It should match the format given by `sys.version_info`, however " +
"for simplicity, the micro, releaselevel, and serial values are " +
"optional." +
"A struct with the following fields:\n" +
" * major: int, the major version number\n" +
" * minor: int, the minor version number\n" +
" * micro: optional int, the micro version number\n" +
" * releaselevel: optional str, the release level\n" +
" * serial: optional int, the serial number of the release"
),
"python_version": (
"Indicates whether this runtime uses Python major version 2 or 3. " +
"Valid values are (only) `\"PY2\"` and " +
Expand Down
21 changes: 21 additions & 0 deletions python/private/common/py_runtime_rule.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ def _py_runtime_impl(ctx):

python_version = ctx.attr.python_version

interpreter_version_info = ctx.attr.interpreter_version_info

# TODO: Uncomment this after --incompatible_python_disable_py2 defaults to true
# if ctx.fragments.py.disable_py2 and python_version == "PY2":
# fail("Using Python 2 is not supported and disabled; see " +
Expand All @@ -93,10 +95,16 @@ def _py_runtime_impl(ctx):
python_version = python_version,
stub_shebang = ctx.attr.stub_shebang,
bootstrap_template = ctx.file.bootstrap_template,
interpreter_version_info = interpreter_version_info,
)
builtin_py_runtime_info_kwargs = dict(py_runtime_info_kwargs)

# Pop this property as it does not exist on BuiltinPyRuntimeInfo
builtin_py_runtime_info_kwargs.pop("interpreter_version_info")

if not IS_BAZEL_7_OR_HIGHER:
builtin_py_runtime_info_kwargs.pop("bootstrap_template")

return [
PyRuntimeInfo(**py_runtime_info_kwargs),
# Return the builtin provider for better compatibility.
Expand Down Expand Up @@ -232,6 +240,19 @@ not be set.
For a platform runtime, this is the absolute path of a Python interpreter on
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
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"
""",
mandatory = False,
),
"python_version": attr.string(
default = "PY3",
values = ["PY2", "PY3"],
Expand Down
11 changes: 10 additions & 1 deletion python/repositories.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ def _python_repository_impl(rctx):

platform = rctx.attr.platform
python_version = rctx.attr.python_version
python_short_version = python_version.rpartition(".")[0]
python_version_info = python_version.split(".")
python_short_version = "{0}.{1}".format(*python_version_info)
release_filename = rctx.attr.release_filename
urls = rctx.attr.urls or [rctx.attr.url]
auth = get_auth(rctx, urls)
Expand Down Expand Up @@ -335,6 +336,11 @@ py_runtime(
files = [":files"],
{coverage_attr}
interpreter = "{python_path}",
interpreter_version_info = {{
"major": "{interpreter_version_info_major}",
"minor": "{interpreter_version_info_minor}",
"micro": "{interpreter_version_info_micro}",
}},
python_version = "PY3",
)

Expand All @@ -356,6 +362,9 @@ py_cc_toolchain(
python_version = python_short_version,
python_version_nodot = python_short_version.replace(".", ""),
coverage_attr = coverage_attr_text,
interpreter_version_info_major = python_version_info[0],
interpreter_version_info_minor = python_version_info[1],
interpreter_version_info_micro = python_version_info[2],
)
rctx.delete("python")
rctx.symlink(python_bin, "python")
Expand Down
115 changes: 115 additions & 0 deletions tests/py_runtime/py_runtime_tests.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,121 @@ def _test_system_interpreter_must_be_absolute_impl(env, target):

_tests.append(_test_system_interpreter_must_be_absolute)

def _interpreter_version_info_test(name, interpreter_version_info, impl, expect_failure = True):
if config.enable_pystar:
py_runtime_kwargs = {
"interpreter_version_info": interpreter_version_info,
}
attr_values = {}
else:
py_runtime_kwargs = {}
attr_values = _SKIP_TEST

rt_util.helper_target(
py_runtime,
name = name + "_subject",
python_version = "PY3",
interpreter_path = "/py",
**py_runtime_kwargs
)
analysis_test(
name = name,
target = name + "_subject",
impl = impl,
expect_failure = expect_failure,
attr_values = attr_values,
)

def _test_interpreter_version_info_must_define_major_and_minor_only_major(name):
_interpreter_version_info_test(
name,
{
"major": "3",
},
lambda env, target: (
env.expect.that_target(target).failures().contains_predicate(
matching.str_matches("must have at least two keys, 'major' and 'minor'"),
)
),
)

_tests.append(_test_interpreter_version_info_must_define_major_and_minor_only_major)

def _test_interpreter_version_info_must_define_major_and_minor_only_minor(name):
_interpreter_version_info_test(
name,
{
"minor": "3",
},
lambda env, target: (
env.expect.that_target(target).failures().contains_predicate(
matching.str_matches("must have at least two keys, 'major' and 'minor'"),
)
),
)

_tests.append(_test_interpreter_version_info_must_define_major_and_minor_only_minor)

def _test_interpreter_version_info_no_extraneous_keys(name):
_interpreter_version_info_test(
name,
{
"major": "3",
"minor": "3",
"something": "foo",
},
lambda env, target: (
env.expect.that_target(target).failures().contains_predicate(
matching.str_matches("unexpected keys [\"something\"]"),
)
),
)

_tests.append(_test_interpreter_version_info_no_extraneous_keys)

def _test_interpreter_version_info_sets_values_to_none_if_not_given(name):
_interpreter_version_info_test(
name,
{
"major": "3",
"micro": "10",
"minor": "3",
},
lambda env, target: (
env.expect.that_target(target).provider(
PyRuntimeInfo,
factory = py_runtime_info_subject,
).interpreter_version_info().serial().equals(None)
),
expect_failure = False,
)

_tests.append(_test_interpreter_version_info_sets_values_to_none_if_not_given)

def _test_interpreter_version_info_parses_values_to_struct(name):
_interpreter_version_info_test(
name,
{
"major": "3",
"micro": "10",
"minor": "6",
"releaselevel": "alpha",
"serial": "1",
},
impl = _test_interpreter_version_info_parses_values_to_struct_impl,
expect_failure = False,
)

def _test_interpreter_version_info_parses_values_to_struct_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(6)
version_info.micro().equals(10)
version_info.releaselevel().equals("alpha")
version_info.serial().equals(1)

_tests.append(_test_interpreter_version_info_parses_values_to_struct)

def py_runtime_test_suite(name):
test_suite(
name = name,
Expand Down
14 changes: 14 additions & 0 deletions tests/py_runtime_info_subject.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ def py_runtime_info_subject(info, *, meta):
files = lambda *a, **k: _py_runtime_info_subject_files(self, *a, **k),
interpreter = lambda *a, **k: _py_runtime_info_subject_interpreter(self, *a, **k),
interpreter_path = lambda *a, **k: _py_runtime_info_subject_interpreter_path(self, *a, **k),
interpreter_version_info = lambda *a, **k: _py_runtime_info_subject_interpreter_version_info(self, *a, **k),
python_version = lambda *a, **k: _py_runtime_info_subject_python_version(self, *a, **k),
stub_shebang = lambda *a, **k: _py_runtime_info_subject_stub_shebang(self, *a, **k),
# go/keep-sorted end
Expand Down Expand Up @@ -100,3 +101,16 @@ def _py_runtime_info_subject_stub_shebang(self):
self.actual.stub_shebang,
meta = self.meta.derive("stub_shebang()"),
)

def _py_runtime_info_subject_interpreter_version_info(self):
return subjects.struct(
self.actual.interpreter_version_info,
attrs = dict(
major = subjects.int,
minor = subjects.int,
micro = subjects.int,
releaselevel = subjects.str,
serial = subjects.int,
),
meta = self.meta.derive("interpreter_version_info()"),
)
Loading