• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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(":reexports.bzl", "BuiltinPyRuntimeInfo")
20load(":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) or BuiltinPyRuntimeInfo == None:
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
73_MaybeBuiltinPyRuntimeInfo = [[BuiltinPyRuntimeInfo]] if BuiltinPyRuntimeInfo != None else []
74
75py_runtime_pair = rule(
76    implementation = _py_runtime_pair_impl,
77    attrs = {
78        # The two runtimes are used by the py_binary at runtime, and so need to
79        # be built for the target platform.
80        "py2_runtime": attr.label(
81            providers = [[PyRuntimeInfo]] + _MaybeBuiltinPyRuntimeInfo,
82            cfg = "target",
83            doc = """\
84The runtime to use for Python 2 targets. Must have `python_version` set to
85`PY2`.
86""",
87        ),
88        "py3_runtime": attr.label(
89            providers = [[PyRuntimeInfo]] + _MaybeBuiltinPyRuntimeInfo,
90            cfg = "target",
91            doc = """\
92The runtime to use for Python 3 targets. Must have `python_version` set to
93`PY3`.
94""",
95        ),
96        "_visible_for_testing": attr.label(
97            default = "//python/private:visible_for_testing",
98        ),
99    },
100    fragments = ["py"],
101    doc = """\
102A toolchain rule for Python.
103
104This wraps up to two Python runtimes, one for Python 2 and one for Python 3.
105The rule consuming this toolchain will choose which runtime is appropriate.
106Either runtime may be omitted, in which case the resulting toolchain will be
107unusable for building Python code using that version.
108
109Usually the wrapped runtimes are declared using the `py_runtime` rule, but any
110rule returning a `PyRuntimeInfo` provider may be used.
111
112This rule returns a {obj}`ToolchainInfo` provider with fields:
113
114* `py2_runtime`: {type}`PyRuntimeInfo | None`, runtime information for a
115  Python 2 runtime.
116* `py3_runtime`: {type}`PyRuntimeInfo | None`. runtime information for a
117  Python 3 runtime.
118
119Example usage:
120
121```python
122# In your BUILD file...
123
124load("@rules_python//python:py_runtime.bzl", "py_runtime")
125load("@rules_python//python:py_runtime_pair.bzl", "py_runtime_pair")
126
127py_runtime(
128    name = "my_py2_runtime",
129    interpreter_path = "/system/python2",
130    python_version = "PY2",
131)
132
133py_runtime(
134    name = "my_py3_runtime",
135    interpreter_path = "/system/python3",
136    python_version = "PY3",
137)
138
139py_runtime_pair(
140    name = "my_py_runtime_pair",
141    py2_runtime = ":my_py2_runtime",
142    py3_runtime = ":my_py3_runtime",
143)
144
145toolchain(
146    name = "my_toolchain",
147    target_compatible_with = <...>,
148    toolchain = ":my_py_runtime_pair",
149    toolchain_type = "@rules_python//python:toolchain_type",
150)
151```
152
153```python
154# In your WORKSPACE...
155
156register_toolchains("//my_pkg:my_toolchain")
157```
158""",
159)
160