diff --git a/python/BUILD.bazel b/python/BUILD.bazel index b7a2172df5..53fb812af6 100644 --- a/python/BUILD.bazel +++ b/python/BUILD.bazel @@ -167,8 +167,8 @@ bzl_library( name = "py_info_bzl", srcs = ["py_info.bzl"], deps = [ + "//python/private:py_info_bzl", "//python/private:reexports_bzl", - "//python/private/common:providers_bzl", "@rules_python_internal//:rules_python_config_bzl", ], ) diff --git a/python/private/BUILD.bazel b/python/private/BUILD.bazel index bfe3764296..8479f67b49 100644 --- a/python/private/BUILD.bazel +++ b/python/private/BUILD.bazel @@ -266,6 +266,15 @@ bzl_library( srcs = ["py_executable_info.bzl"], ) +bzl_library( + name = "py_info_bzl", + srcs = ["py_info.bzl"], + deps = [ + ":reexports_bzl", + ":util_bzl", + ], +) + bzl_library( name = "py_interpreter_program_bzl", srcs = ["py_interpreter_program.bzl"], diff --git a/python/private/common/BUILD.bazel b/python/private/common/BUILD.bazel index 805c00226d..6fef8e87af 100644 --- a/python/private/common/BUILD.bazel +++ b/python/private/common/BUILD.bazel @@ -29,11 +29,11 @@ bzl_library( srcs = ["attributes.bzl"], deps = [ ":common_bzl", - ":providers_bzl", ":py_internal_bzl", ":semantics_bzl", "//python/private:enum_bzl", "//python/private:flags_bzl", + "//python/private:py_info_bzl", "//python/private:reexports_bzl", "//python/private:rules_cc_srcs_bzl", "@bazel_skylib//rules:common_settings", @@ -68,6 +68,7 @@ bzl_library( ":providers_bzl", ":py_internal_bzl", ":semantics_bzl", + "//python/private:py_info_bzl", "//python/private:reexports_bzl", "//python/private:rules_cc_srcs_bzl", ], @@ -133,6 +134,7 @@ bzl_library( ":py_internal_bzl", "//python/private:flags_bzl", "//python/private:py_executable_info_bzl", + "//python/private:py_info_bzl", "//python/private:rules_cc_srcs_bzl", "//python/private:toolchain_types_bzl", "@bazel_skylib//lib:dicts", diff --git a/python/private/common/attributes.bzl b/python/private/common/attributes.bzl index 90a53328a7..5e81f4698e 100644 --- a/python/private/common/attributes.bzl +++ b/python/private/common/attributes.bzl @@ -17,9 +17,9 @@ load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo") load("@rules_cc//cc:defs.bzl", "CcInfo") load("//python/private:enum.bzl", "enum") load("//python/private:flags.bzl", "PrecompileFlag", "PrecompileSourceRetentionFlag") +load("//python/private:py_info.bzl", "PyInfo") load("//python/private:reexports.bzl", "BuiltinPyInfo") load(":common.bzl", "union_attrs") -load(":providers.bzl", "PyInfo") load(":py_internal.bzl", "py_internal") load( ":semantics.bzl", diff --git a/python/private/common/common.bzl b/python/private/common/common.bzl index 5559ccd195..bbda712d2d 100644 --- a/python/private/common/common.bzl +++ b/python/private/common/common.bzl @@ -13,9 +13,9 @@ # limitations under the License. """Various things common to Bazel and Google rule implementations.""" +load("//python/private:py_info.bzl", "PyInfo") load("//python/private:reexports.bzl", "BuiltinPyInfo") load(":cc_helper.bzl", "cc_helper") -load(":providers.bzl", "PyInfo") load(":py_internal.bzl", "py_internal") load( ":semantics.bzl", diff --git a/python/private/common/providers.bzl b/python/private/common/providers.bzl index eb8b910a2e..b704ce0298 100644 --- a/python/private/common/providers.bzl +++ b/python/private/common/providers.bzl @@ -14,7 +14,7 @@ """Providers for Python rules.""" load("@rules_cc//cc:defs.bzl", "CcInfo") -load("//python/private:util.bzl", "IS_BAZEL_6_OR_HIGHER") +load("//python/private:util.bzl", "define_bazel_6_provider") DEFAULT_STUB_SHEBANG = "#!/usr/bin/env python3" @@ -22,18 +22,6 @@ DEFAULT_BOOTSTRAP_TEMPLATE = Label("//python/private:bootstrap_template") _PYTHON_VERSION_VALUES = ["PY2", "PY3"] -# Helper to make the provider definitions not crash under Bazel 5.4: -# Bazel 5.4 doesn't support the `init` arg of `provider()`, so we have to -# not pass that when using Bazel 5.4. But, not passing the `init` arg -# changes the return value from a two-tuple to a single value, which then -# breaks Bazel 6+ code. -# This isn't actually used under Bazel 5.4, so just stub out the values -# to get past the loading phase. -def _define_provider(doc, fields, **kwargs): - if not IS_BAZEL_6_OR_HIGHER: - return provider("Stub, not used", fields = []), None - return provider(doc = doc, fields = fields, **kwargs) - def _optional_int(value): return int(value) if value != None else None @@ -133,9 +121,7 @@ def _PyRuntimeInfo_init( "zip_main_template": zip_main_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 = _define_provider( +PyRuntimeInfo, _unused_raw_py_runtime_info_ctor = define_bazel_6_provider( doc = """Contains information about a Python runtime, as returned by the `py_runtime` rule. @@ -314,102 +300,13 @@ The following substitutions are made during template expansion: }, ) -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, - direct_pyc_files = depset(), - transitive_pyc_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) - 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_pyc_files": transitive_pyc_files, - "transitive_sources": transitive_sources, - "uses_shared_libraries": uses_shared_libraries, - } - -PyInfo, _unused_raw_py_info_ctor = _define_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. -""", - "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_pyc_files": """ -:type: depset[File] - -Direct and transitive precompiled Python files that are provided by the target. -""", - "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`. -""", - "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. -""", - }, -) - 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 = _define_provider( +PyCcLinkParamsProvider, _unused_raw_py_cc_link_params_provider_ctor = define_bazel_6_provider( doc = ("Python-wrapper to forward {obj}`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"), diff --git a/python/private/common/py_executable.bzl b/python/private/common/py_executable.bzl index 80418acfcc..37ca313e0b 100644 --- a/python/private/common/py_executable.bzl +++ b/python/private/common/py_executable.bzl @@ -19,6 +19,7 @@ load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo") load("@rules_cc//cc:defs.bzl", "cc_common") load("//python/private:flags.bzl", "PrecompileAddToRunfilesFlag") load("//python/private:py_executable_info.bzl", "PyExecutableInfo") +load("//python/private:py_info.bzl", "PyInfo") load("//python/private:reexports.bzl", "BuiltinPyRuntimeInfo") load( "//python/private:toolchain_types.bzl", @@ -52,7 +53,6 @@ load( load( ":providers.bzl", "PyCcLinkParamsProvider", - "PyInfo", "PyRuntimeInfo", ) load(":py_internal.bzl", "py_internal") diff --git a/python/private/py_info.bzl b/python/private/py_info.bzl new file mode 100644 index 0000000000..7945775a25 --- /dev/null +++ b/python/private/py_info.bzl @@ -0,0 +1,115 @@ +# 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(":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()): + _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) + + 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_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_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`. +""", + "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. +""", + }, +) diff --git a/python/private/util.bzl b/python/private/util.bzl index 16b8ff8f55..3c32adc607 100644 --- a/python/private/util.bzl +++ b/python/private/util.bzl @@ -84,6 +84,19 @@ def add_tag(attrs, tag): else: attrs["tags"] = [tag] +# Helper to make the provider definitions not crash under Bazel 5.4: +# Bazel 5.4 doesn't support the `init` arg of `provider()`, so we have to +# not pass that when using Bazel 5.4. But, not passing the `init` arg +# changes the return value from a two-tuple to a single value, which then +# breaks Bazel 6+ code. +# This isn't actually used under Bazel 5.4, so just stub out the values +# to get past the loading phase. +def define_bazel_6_provider(doc, fields, **kwargs): + """Define a provider, or a stub for pre-Bazel 7.""" + if not IS_BAZEL_6_OR_HIGHER: + return provider("Stub, not used", fields = []), None + return provider(doc = doc, fields = fields, **kwargs) + IS_BAZEL_7_OR_HIGHER = hasattr(native, "starlark_doc_extract") # Bazel 5.4 has a bug where every access of testing.ExecutionInfo is a diff --git a/python/py_info.bzl b/python/py_info.bzl index 0af35ac320..52a66a87c2 100644 --- a/python/py_info.bzl +++ b/python/py_info.bzl @@ -15,7 +15,7 @@ """Public entry point for PyInfo.""" load("@rules_python_internal//:rules_python_config.bzl", "config") +load("//python/private:py_info.bzl", _starlark_PyInfo = "PyInfo") load("//python/private:reexports.bzl", "BuiltinPyInfo") -load("//python/private/common:providers.bzl", _starlark_PyInfo = "PyInfo") PyInfo = _starlark_PyInfo if config.enable_pystar else BuiltinPyInfo