# 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(":builders.bzl", "builders") load(":reexports.bzl", "BuiltinPyInfo") 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 that are considered required and downstream binaries (or equivalent) **must** include in their outputs to have a functioning program. Normally, these are the `.py` files in the appearing in the target's `srcs` and the `srcs` of the target's transitive `deps`, **however**, precompile settings may cause `.py` files to be omitted. In particular, pyc-only builds may result in this depset being **empty**. ::::{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], )