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