Skip to content

Commit

Permalink
feat: add interpreter_version_info to py_runtime
Browse files Browse the repository at this point in the history
  • Loading branch information
mattem committed Jan 8, 2024
1 parent ebd779e commit ef00f2b
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 2 deletions.
25 changes: 24 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,30 @@ 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 +154,11 @@ the same conventions as the standard CPython interpreter.
"filesystem path to the interpreter on the target platform. " +
"Otherwise, this is `None`."
),
"interpreter_version_info": (
"This is the version of the interpreter that this runtime provides. " +
"The struct can contain the five components of the version number: major, minor, micro, releaselevel, and serial. " +
"This should match the format given by `sys.version_info`, however for simplicity, the micro, releaselevel and serial values may be omitted."
),
"python_version": (
"Indicates whether this runtime uses Python major version 2 or 3. " +
"Valid values are (only) `\"PY2\"` and " +
Expand Down
16 changes: 16 additions & 0 deletions python/private/common/py_runtime_rule.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,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 @@ -75,10 +77,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 @@ -196,6 +204,14 @@ platform runtime this attribute must 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 = """
This is the version of the interpreter that this runtime provides.
The struct can contain the five components of the version number: major, minor, micro, releaselevel, and serial.
This should match the format given by `sys.version_info`, however for simplicity, the micro, releaselevel and serial values may be omitted.
""",
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 @@ -101,7 +101,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 @@ -334,6 +335,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 @@ -355,6 +361,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
114 changes: 114 additions & 0 deletions tests/py_runtime/py_runtime_tests.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,120 @@ 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",
"minor": "3",
"micro": "10",
},
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",
"minor": "6",
"micro": "10",
"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 @@ -37,6 +37,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 @@ -99,3 +100,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()"),
)

0 comments on commit ef00f2b

Please sign in to comment.