blob: 6c2c3c64994191faaecce6ca78f7604360adb936 [file] [log] [blame]
# Copyright 2024 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.
"""Implementation of PyInfo provider and PyInfo-specific utilities."""
load("@rules_python_internal//:rules_python_config.bzl", "config")
load("//python/private:reexports.bzl", "BuiltinPyInfo")
load(":builders.bzl", "builders")
load(":util.bzl", "define_bazel_6_provider")
def _check_arg_type(name, required_type, value):
"""Check that a value is of an expected type."""
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,
direct_pyc_files = depset(),
transitive_pyc_files = depset(),
transitive_implicit_pyc_files = depset(),
transitive_implicit_pyc_source_files = depset()):
_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)
_check_arg_type("direct_pyc_files", "depset", direct_pyc_files)
_check_arg_type("transitive_pyc_files", "depset", transitive_pyc_files)
_check_arg_type("transitive_implicit_pyc_files", "depset", transitive_pyc_files)
_check_arg_type("transitive_implicit_pyc_source_files", "depset", transitive_pyc_files)
return {
"direct_pyc_files": direct_pyc_files,
"has_py2_only_sources": has_py2_only_sources,
"has_py3_only_sources": has_py2_only_sources,
"imports": imports,
"transitive_implicit_pyc_files": transitive_implicit_pyc_files,
"transitive_implicit_pyc_source_files": transitive_implicit_pyc_source_files,
"transitive_pyc_files": transitive_pyc_files,
"transitive_sources": transitive_sources,
"uses_shared_libraries": uses_shared_libraries,
}
PyInfo, _unused_raw_py_info_ctor = define_bazel_6_provider(
doc = "Encapsulates information provided by the Python rules.",
init = _PyInfo_init,
fields = {
"direct_pyc_files": """
:type: depset[File]
Precompiled Python files that are considered directly provided
by the target and **must be included**.
These files usually come from, e.g., a library setting {attr}`precompile=enabled`
to forcibly enable precompiling for itself. Downstream binaries are expected
to always include these files, as the originating target expects them to exist.
""",
"has_py2_only_sources": """
:type: bool
Whether any of this target's transitive sources requires a Python 2 runtime.
""",
"has_py3_only_sources": """
:type: bool
Whether any of this target's transitive sources requires a Python 3 runtime.
""",
"imports": """\
:type: depset[str]
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).
""",
"transitive_implicit_pyc_files": """
:type: depset[File]
Automatically generated pyc files that downstream binaries (or equivalent)
can choose to include in their output. If not included, then
{obj}`transitive_implicit_pyc_source_files` should be included instead.
::::{versionadded} 0.37.0
::::
""",
"transitive_implicit_pyc_source_files": """
:type: depset[File]
Source `.py` files for {obj}`transitive_implicit_pyc_files` that downstream
binaries (or equivalent) can choose to include in their output. If not included,
then {obj}`transitive_implicit_pyc_files` should be included instead.
::::{versionadded} 0.37.0
::::
""",
"transitive_pyc_files": """
:type: depset[File]
The transitive set of precompiled files that must be included.
These files usually come from, e.g., a library setting {attr}`precompile=enabled`
to forcibly enable precompiling for itself. Downstream binaries are expected
to always include these files, as the originating target expects them to exist.
""",
"transitive_sources": """\
:type: depset[File]
A (`postorder`-compatible) depset of `.py` files appearing in the target's
`srcs` and the `srcs` of the target's transitive `deps`.
These are `.py` source files that are considered required and downstream
binaries (or equivalent) must include in their outputs.
::::{versionchanged} 0.37.0
The files are considered necessary for downstream binaries to function;
previously they were considerd informational and largely unused.
::::
""",
"uses_shared_libraries": """
:type: bool
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.
""",
},
)
# The "effective" PyInfo is what the canonical //python:py_info.bzl%PyInfo symbol refers to
_EffectivePyInfo = PyInfo if (config.enable_pystar or BuiltinPyInfo == None) else BuiltinPyInfo
def PyInfoBuilder():
# buildifier: disable=uninitialized
self = struct(
_has_py2_only_sources = [False],
_has_py3_only_sources = [False],
_uses_shared_libraries = [False],
build = lambda *a, **k: _PyInfoBuilder_build(self, *a, **k),
build_builtin_py_info = lambda *a, **k: _PyInfoBuilder_build_builtin_py_info(self, *a, **k),
direct_pyc_files = builders.DepsetBuilder(),
get_has_py2_only_sources = lambda *a, **k: _PyInfoBuilder_get_has_py2_only_sources(self, *a, **k),
get_has_py3_only_sources = lambda *a, **k: _PyInfoBuilder_get_has_py3_only_sources(self, *a, **k),
get_uses_shared_libraries = lambda *a, **k: _PyInfoBuilder_get_uses_shared_libraries(self, *a, **k),
imports = builders.DepsetBuilder(),
merge = lambda *a, **k: _PyInfoBuilder_merge(self, *a, **k),
merge_all = lambda *a, **k: _PyInfoBuilder_merge_all(self, *a, **k),
merge_has_py2_only_sources = lambda *a, **k: _PyInfoBuilder_merge_has_py2_only_sources(self, *a, **k),
merge_has_py3_only_sources = lambda *a, **k: _PyInfoBuilder_merge_has_py3_only_sources(self, *a, **k),
merge_target = lambda *a, **k: _PyInfoBuilder_merge_target(self, *a, **k),
merge_targets = lambda *a, **k: _PyInfoBuilder_merge_targets(self, *a, **k),
merge_uses_shared_libraries = lambda *a, **k: _PyInfoBuilder_merge_uses_shared_libraries(self, *a, **k),
set_has_py2_only_sources = lambda *a, **k: _PyInfoBuilder_set_has_py2_only_sources(self, *a, **k),
set_has_py3_only_sources = lambda *a, **k: _PyInfoBuilder_set_has_py3_only_sources(self, *a, **k),
set_uses_shared_libraries = lambda *a, **k: _PyInfoBuilder_set_uses_shared_libraries(self, *a, **k),
transitive_implicit_pyc_files = builders.DepsetBuilder(),
transitive_implicit_pyc_source_files = builders.DepsetBuilder(),
transitive_pyc_files = builders.DepsetBuilder(),
transitive_sources = builders.DepsetBuilder(),
)
return self
def _PyInfoBuilder_get_has_py3_only_sources(self):
return self._has_py3_only_sources[0]
def _PyInfoBuilder_get_has_py2_only_sources(self):
return self._has_py2_only_sources[0]
def _PyInfoBuilder_set_has_py2_only_sources(self, value):
self._has_py2_only_sources[0] = value
return self
def _PyInfoBuilder_set_has_py3_only_sources(self, value):
self._has_py3_only_sources[0] = value
return self
def _PyInfoBuilder_merge_has_py2_only_sources(self, value):
self._has_py2_only_sources[0] = self._has_py2_only_sources[0] or value
return self
def _PyInfoBuilder_merge_has_py3_only_sources(self, value):
self._has_py3_only_sources[0] = self._has_py3_only_sources[0] or value
return self
def _PyInfoBuilder_merge_uses_shared_libraries(self, value):
self._uses_shared_libraries[0] = self._uses_shared_libraries[0] or value
return self
def _PyInfoBuilder_get_uses_shared_libraries(self):
return self._uses_shared_libraries[0]
def _PyInfoBuilder_set_uses_shared_libraries(self, value):
self._uses_shared_libraries[0] = value
return self
def _PyInfoBuilder_merge(self, *infos, direct = []):
return self.merge_all(list(infos), direct = direct)
def _PyInfoBuilder_merge_all(self, transitive, *, direct = []):
for info in direct:
# BuiltinPyInfo doesn't have this field
if hasattr(info, "direct_pyc_files"):
self.direct_pyc_files.add(info.direct_pyc_files)
for info in direct + transitive:
self.imports.add(info.imports)
self.merge_has_py2_only_sources(info.has_py2_only_sources)
self.merge_has_py3_only_sources(info.has_py3_only_sources)
self.merge_uses_shared_libraries(info.uses_shared_libraries)
self.transitive_sources.add(info.transitive_sources)
# BuiltinPyInfo doesn't have these fields
if hasattr(info, "transitive_pyc_files"):
self.transitive_implicit_pyc_files.add(info.transitive_implicit_pyc_files)
self.transitive_implicit_pyc_source_files.add(info.transitive_implicit_pyc_source_files)
self.transitive_pyc_files.add(info.transitive_pyc_files)
return self
def _PyInfoBuilder_merge_target(self, target):
if PyInfo in target:
self.merge(target[PyInfo])
elif BuiltinPyInfo != None and BuiltinPyInfo in target:
self.merge(target[BuiltinPyInfo])
return self
def _PyInfoBuilder_merge_targets(self, targets):
for t in targets:
self.merge_target(t)
return self
def _PyInfoBuilder_build(self):
if config.enable_pystar:
kwargs = dict(
direct_pyc_files = self.direct_pyc_files.build(),
transitive_pyc_files = self.transitive_pyc_files.build(),
transitive_implicit_pyc_files = self.transitive_implicit_pyc_files.build(),
transitive_implicit_pyc_source_files = self.transitive_implicit_pyc_source_files.build(),
)
else:
kwargs = {}
return _EffectivePyInfo(
has_py2_only_sources = self._has_py2_only_sources[0],
has_py3_only_sources = self._has_py3_only_sources[0],
imports = self.imports.build(),
transitive_sources = self.transitive_sources.build(),
uses_shared_libraries = self._uses_shared_libraries[0],
**kwargs
)
def _PyInfoBuilder_build_builtin_py_info(self):
if BuiltinPyInfo == None:
return None
return BuiltinPyInfo(
has_py2_only_sources = self._has_py2_only_sources[0],
has_py3_only_sources = self._has_py3_only_sources[0],
imports = self.imports.build(),
transitive_sources = self.transitive_sources.build(),
uses_shared_libraries = self._uses_shared_libraries[0],
)