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