xref: /aosp_15_r20/external/bazelbuild-rules_python/python/private/py_runtime_pair_rule.bzl (revision 60517a1edbc8ecf509223e9af94a7adec7d736b8)
1# Copyright 2019 The Bazel Authors. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#    http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Implementation of py_runtime_pair."""
16
17load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
18load("//python:py_runtime_info.bzl", "PyRuntimeInfo")
19load("//python/private:reexports.bzl", "BuiltinPyRuntimeInfo")
20load("//python/private:util.bzl", "IS_BAZEL_7_OR_HIGHER")
21
22def _py_runtime_pair_impl(ctx):
23    if ctx.attr.py2_runtime != None:
24        py2_runtime = _get_py_runtime_info(ctx.attr.py2_runtime)
25        if py2_runtime.python_version != "PY2":
26            fail("The Python runtime in the 'py2_runtime' attribute did not have " +
27                 "version 'PY2'")
28    else:
29        py2_runtime = None
30
31    if ctx.attr.py3_runtime != None:
32        py3_runtime = _get_py_runtime_info(ctx.attr.py3_runtime)
33        if py3_runtime.python_version != "PY3":
34            fail("The Python runtime in the 'py3_runtime' attribute did not have " +
35                 "version 'PY3'")
36    else:
37        py3_runtime = None
38
39    # TODO: Uncomment this after --incompatible_python_disable_py2 defaults to true
40    # if _is_py2_disabled(ctx) and py2_runtime != None:
41    #     fail("Using Python 2 is not supported and disabled; see " +
42    #          "https://github.com/bazelbuild/bazel/issues/15684")
43
44    extra_kwargs = {}
45    if ctx.attr._visible_for_testing[BuildSettingInfo].value:
46        extra_kwargs["toolchain_label"] = ctx.label
47
48    return [platform_common.ToolchainInfo(
49        py2_runtime = py2_runtime,
50        py3_runtime = py3_runtime,
51        **extra_kwargs
52    )]
53
54def _get_py_runtime_info(target):
55    # Prior to Bazel 7, the builtin PyRuntimeInfo object must be used because
56    # py_binary (implemented in Java) performs a type check on the provider
57    # value to verify it is an instance of the Java-implemented PyRuntimeInfo
58    # class.
59    if IS_BAZEL_7_OR_HIGHER and PyRuntimeInfo in target:
60        return target[PyRuntimeInfo]
61    else:
62        return target[BuiltinPyRuntimeInfo]
63
64# buildifier: disable=unused-variable
65def _is_py2_disabled(ctx):
66    # Because this file isn't bundled with Bazel, so we have to conditionally
67    # check for this flag.
68    # TODO: Remove this once all supported Balze versions have this flag.
69    if not hasattr(ctx.fragments.py, "disable_py"):
70        return False
71    return ctx.fragments.py.disable_py2
72
73py_runtime_pair = rule(
74    implementation = _py_runtime_pair_impl,
75    attrs = {
76        # The two runtimes are used by the py_binary at runtime, and so need to
77        # be built for the target platform.
78        "py2_runtime": attr.label(
79            providers = [[PyRuntimeInfo], [BuiltinPyRuntimeInfo]],
80            cfg = "target",
81            doc = """\
82The runtime to use for Python 2 targets. Must have `python_version` set to
83`PY2`.
84""",
85        ),
86        "py3_runtime": attr.label(
87            providers = [[PyRuntimeInfo], [BuiltinPyRuntimeInfo]],
88            cfg = "target",
89            doc = """\
90The runtime to use for Python 3 targets. Must have `python_version` set to
91`PY3`.
92""",
93        ),
94        "_visible_for_testing": attr.label(
95            default = "//python/private:visible_for_testing",
96        ),
97    },
98    fragments = ["py"],
99    doc = """\
100A toolchain rule for Python.
101
102This wraps up to two Python runtimes, one for Python 2 and one for Python 3.
103The rule consuming this toolchain will choose which runtime is appropriate.
104Either runtime may be omitted, in which case the resulting toolchain will be
105unusable for building Python code using that version.
106
107Usually the wrapped runtimes are declared using the `py_runtime` rule, but any
108rule returning a `PyRuntimeInfo` provider may be used.
109
110This rule returns a {obj}`ToolchainInfo` provider with fields:
111
112* `py2_runtime`: {type}`PyRuntimeInfo | None`, runtime information for a
113  Python 2 runtime.
114* `py3_runtime`: {type}`PyRuntimeInfo | None`. runtime information for a
115  Python 3 runtime.
116
117Example usage:
118
119```python
120# In your BUILD file...
121
122load("@rules_python//python:py_runtime.bzl", "py_runtime")
123load("@rules_python//python:py_runtime_pair.bzl", "py_runtime_pair")
124
125py_runtime(
126    name = "my_py2_runtime",
127    interpreter_path = "/system/python2",
128    python_version = "PY2",
129)
130
131py_runtime(
132    name = "my_py3_runtime",
133    interpreter_path = "/system/python3",
134    python_version = "PY3",
135)
136
137py_runtime_pair(
138    name = "my_py_runtime_pair",
139    py2_runtime = ":my_py2_runtime",
140    py3_runtime = ":my_py3_runtime",
141)
142
143toolchain(
144    name = "my_toolchain",
145    target_compatible_with = <...>,
146    toolchain = ":my_py_runtime_pair",
147    toolchain_type = "@rules_python//python:toolchain_type",
148)
149```
150
151```python
152# In your WORKSPACE...
153
154register_toolchains("//my_pkg:my_toolchain")
155```
156""",
157)
158