blob: fb512b23a40a4fcf12977a95b2951c63827b5ca9 [file] [log] [blame]
# 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",
},
)