| # Copyright 2022 The Bazel Authors. All rights reserved. | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| """Providers for Python rules.""" | |
| load(":common/python/semantics.bzl", "TOOLS_REPO") | |
| _CcInfo = _builtins.toplevel.CcInfo | |
| # NOTE: This is copied to PyRuntimeInfo.java | |
| DEFAULT_STUB_SHEBANG = "#!/usr/bin/env python3" | |
| # NOTE: This is copied to PyRuntimeInfo.java | |
| DEFAULT_BOOTSTRAP_TEMPLATE = "@" + TOOLS_REPO + "//tools/python:python_bootstrap_template.txt" | |
| _PYTHON_VERSION_VALUES = ["PY2", "PY3"] | |
| def _PyRuntimeInfo_init( | |
| *, | |
| interpreter_path = None, | |
| interpreter = None, | |
| files = None, | |
| coverage_tool = None, | |
| coverage_files = None, | |
| python_version, | |
| stub_shebang = None, | |
| bootstrap_template = None): | |
| if (interpreter_path and interpreter) or (not interpreter_path and not interpreter): | |
| fail("exactly one of interpreter or interpreter_path must be specified") | |
| if interpreter_path and files != None: | |
| fail("cannot specify 'files' if 'interpreter_path' is given") | |
| if (coverage_tool and not coverage_files) or (not coverage_tool and coverage_files): | |
| fail( | |
| "coverage_tool and coverage_files must both be set or neither must be set, " + | |
| "got coverage_tool={}, coverage_files={}".format( | |
| coverage_tool, | |
| coverage_files, | |
| ), | |
| ) | |
| if python_version not in _PYTHON_VERSION_VALUES: | |
| fail("invalid python_version: '{}'; must be one of {}".format( | |
| python_version, | |
| _PYTHON_VERSION_VALUES, | |
| )) | |
| if files != None and type(files) != type(depset()): | |
| fail("invalid files: got value of type {}, want depset".format(type(files))) | |
| if interpreter: | |
| if files == None: | |
| files = depset() | |
| else: | |
| files = None | |
| if coverage_files == None: | |
| coverage_files = depset() | |
| if not stub_shebang: | |
| stub_shebang = DEFAULT_STUB_SHEBANG | |
| return { | |
| "interpreter_path": interpreter_path, | |
| "interpreter": interpreter, | |
| "files": files, | |
| "coverage_tool": coverage_tool, | |
| "coverage_files": coverage_files, | |
| "python_version": python_version, | |
| "stub_shebang": stub_shebang, | |
| "bootstrap_template": bootstrap_template, | |
| } | |
| # TODO(#15897): Rename this to PyRuntimeInfo when we're ready to replace the Java | |
| # implemented provider with the Starlark one. | |
| PyRuntimeInfo, _unused_raw_py_runtime_info_ctor = provider( | |
| doc = """Contains information about a Python runtime, as returned by the `py_runtime` | |
| rule. | |
| A Python runtime describes either a *platform runtime* or an *in-build runtime*. | |
| A platform runtime accesses a system-installed interpreter at a known path, | |
| whereas an in-build runtime points to a `File` that acts as the interpreter. In | |
| both cases, an "interpreter" is really any executable binary or wrapper script | |
| that is capable of running a Python script passed on the command line, following | |
| the same conventions as the standard CPython interpreter. | |
| """, | |
| init = _PyRuntimeInfo_init, | |
| fields = { | |
| "interpreter_path": ( | |
| "If this is a platform runtime, this field is the absolute " + | |
| "filesystem path to the interpreter on the target platform. " + | |
| "Otherwise, this is `None`." | |
| ), | |
| "interpreter": ( | |
| "If this is an in-build runtime, this field is a `File` representing " + | |
| "the interpreter. Otherwise, this is `None`. Note that an in-build " + | |
| "runtime can use either a prebuilt, checked-in interpreter or an " + | |
| "interpreter built from source." | |
| ), | |
| "files": ( | |
| "If this is an in-build runtime, this field is a `depset` of `File`s" + | |
| "that need to be added to the runfiles of an executable target that " + | |
| "uses this runtime (in particular, files needed by `interpreter`). " + | |
| "The value of `interpreter` need not be included in this field. If " + | |
| "this is a platform runtime then this field is `None`." | |
| ), | |
| "coverage_tool": ( | |
| "If set, this field is a `File` representing tool used for collecting code coverage information from python tests. Otherwise, this is `None`." | |
| ), | |
| "coverage_files": ( | |
| "The files required at runtime for using `coverage_tool`. " + | |
| "Will be `None` if no `coverage_tool` was provided." | |
| ), | |
| "python_version": ( | |
| "Indicates whether this runtime uses Python major version 2 or 3. " + | |
| "Valid values are (only) `\"PY2\"` and " + | |
| "`\"PY3\"`." | |
| ), | |
| "stub_shebang": ( | |
| "\"Shebang\" expression prepended to the bootstrapping Python stub " + | |
| "script used when executing `py_binary` targets. Does not " + | |
| "apply to Windows." | |
| ), | |
| "bootstrap_template": ( | |
| "See py_runtime_rule.bzl%py_runtime.bootstrap_template for docs." | |
| ), | |
| }, | |
| ) | |
| def _check_arg_type(name, required_type, value): | |
| value_type = type(value) | |
| if value_type != required_type: | |
| fail("parameter '{}' got value of type '{}', want '{}'".format( | |
| name, | |
| value_type, | |
| required_type, | |
| )) | |
| def _PyInfo_init( | |
| *, | |
| transitive_sources, | |
| uses_shared_libraries = False, | |
| imports = depset(), | |
| has_py2_only_sources = False, | |
| has_py3_only_sources = False): | |
| _check_arg_type("transitive_sources", "depset", transitive_sources) | |
| # Verify it's postorder compatible, but retain is original ordering. | |
| depset(transitive = [transitive_sources], order = "postorder") | |
| _check_arg_type("uses_shared_libraries", "bool", uses_shared_libraries) | |
| _check_arg_type("imports", "depset", imports) | |
| _check_arg_type("has_py2_only_sources", "bool", has_py2_only_sources) | |
| _check_arg_type("has_py3_only_sources", "bool", has_py3_only_sources) | |
| return { | |
| "transitive_sources": transitive_sources, | |
| "imports": imports, | |
| "uses_shared_libraries": uses_shared_libraries, | |
| "has_py2_only_sources": has_py2_only_sources, | |
| "has_py3_only_sources": has_py2_only_sources, | |
| } | |
| PyInfo, _unused_raw_py_info_ctor = provider( | |
| "Encapsulates information provided by the Python rules.", | |
| init = _PyInfo_init, | |
| fields = { | |
| "transitive_sources": """\ | |
| A (`postorder`-compatible) depset of `.py` files appearing in the target's | |
| `srcs` and the `srcs` of the target's transitive `deps`. | |
| """, | |
| "uses_shared_libraries": """ | |
| Whether any of this target's transitive `deps` has a shared library file (such | |
| as a `.so` file). | |
| This field is currently unused in Bazel and may go away in the future. | |
| """, | |
| "imports": """\ | |
| A depset of import path strings to be added to the `PYTHONPATH` of executable | |
| Python targets. These are accumulated from the transitive `deps`. | |
| The order of the depset is not guaranteed and may be changed in the future. It | |
| is recommended to use `default` order (the default). | |
| """, | |
| "has_py2_only_sources": "Whether any of this target's transitive sources requires a Python 2 runtime.", | |
| "has_py3_only_sources": "Whether any of this target's transitive sources requires a Python 3 runtime.", | |
| }, | |
| ) | |
| def _PyCcLinkParamsProvider_init(cc_info): | |
| return { | |
| "cc_info": _CcInfo(linking_context = cc_info.linking_context), | |
| } | |
| # buildifier: disable=name-conventions | |
| PyCcLinkParamsProvider, _unused_raw_py_cc_link_params_provider_ctor = provider( | |
| doc = ("Python-wrapper to forward CcInfo.linking_context. This is to " + | |
| "allow Python targets to propagate C++ linking information, but " + | |
| "without the Python target appearing to be a valid C++ rule dependency"), | |
| init = _PyCcLinkParamsProvider_init, | |
| fields = { | |
| "cc_info": "A CcInfo instance; it has only linking_context set", | |
| }, | |
| ) |