diff --git a/.bazelignore b/.bazelignore index 135f709824..025277a60e 100644 --- a/.bazelignore +++ b/.bazelignore @@ -6,6 +6,10 @@ bazel-rules_python bazel-bin bazel-out bazel-testlogs +# Prevent the convenience symlinks within the examples from being +# treated as directories with valid BUILD files for the main repo. examples/bzlmod/bazel-bzlmod +examples/bzlmod/other_module/bazel-other_module examples/bzlmod_build_file_generation/bazel-bzlmod_build_file_generation +examples/pip_parse/bazel-pip_parse examples/py_proto_library/bazel-py_proto_library diff --git a/CHANGELOG.md b/CHANGELOG.md index 1385adeee0..de26db6eae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,11 @@ A brief description of the categories of changes: * (multi-version) The `distribs` attribute is no longer propagated. This attribute has been long deprecated by Bazel and shouldn't be used. +* Calling `//python:repositories.bzl#py_repositories()` is required. It has + always been documented as necessary, but it was possible to omit it in certain + cases. An error about `@rules_python_internal` means the `py_repositories()` + call is missing in `WORKSPACE`. + ### Added diff --git a/MODULE.bazel b/MODULE.bazel index aaa5c86912..ab7b597518 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -15,6 +15,7 @@ internal_deps = use_extension("@rules_python//python/extensions/private:internal internal_deps.install() use_repo( internal_deps, + "rules_python_internal", # START: maintained by 'bazel run //tools/private:update_pip_deps' "pypi__build", "pypi__click", diff --git a/examples/build_file_generation/WORKSPACE b/examples/build_file_generation/WORKSPACE index fa11380dde..03085d86b5 100644 --- a/examples/build_file_generation/WORKSPACE +++ b/examples/build_file_generation/WORKSPACE @@ -71,8 +71,11 @@ local_repository( path = "../../gazelle", ) -# Next we load the toolchain from rules_python. -load("@rules_python//python:repositories.bzl", "python_register_toolchains") +# Next we load the setup and toolchain from rules_python. +load("@rules_python//python:repositories.bzl", "py_repositories", "python_register_toolchains") + +# Perform general setup +py_repositories() # We now register a hermetic Python interpreter rather than relying on a system-installed interpreter. # This toolchain will allow bazel to download a specific python version, and use that version diff --git a/gazelle/WORKSPACE b/gazelle/WORKSPACE index b9ba91d9f8..fe7ac3ec53 100644 --- a/gazelle/WORKSPACE +++ b/gazelle/WORKSPACE @@ -34,7 +34,9 @@ local_repository( path = "..", ) -load("@rules_python//python:repositories.bzl", "python_register_toolchains") +load("@rules_python//python:repositories.bzl", "py_repositories", "python_register_toolchains") + +py_repositories() python_register_toolchains( name = "python39", diff --git a/internal_setup.bzl b/internal_setup.bzl index c3a7ad452d..0c9d6c48a6 100644 --- a/internal_setup.bzl +++ b/internal_setup.bzl @@ -20,10 +20,13 @@ load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps") load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains") load("//:version.bzl", "SUPPORTED_BAZEL_VERSIONS") load("//python/pip_install:repositories.bzl", "pip_install_dependencies") +load("//python/private:internal_config_repo.bzl", "internal_config_repo") # buildifier: disable=bzl-visibility def rules_python_internal_setup(): """Setup for rules_python tests and tools.""" + internal_config_repo(name = "rules_python_internal") + # Because we don't use the pip_install rule, we have to call this to fetch its deps pip_install_dependencies() diff --git a/python/BUILD.bazel b/python/BUILD.bazel index aa8c8bf3e4..3e0919c4df 100644 --- a/python/BUILD.bazel +++ b/python/BUILD.bazel @@ -84,7 +84,11 @@ bzl_library( bzl_library( name = "py_binary_bzl", srcs = ["py_binary.bzl"], - deps = ["//python/private:util_bzl"], + deps = [ + "//python/private:util_bzl", + "//python/private/common:py_binary_macro_bazel_bzl", + "@rules_python_internal//:rules_python_config_bzl", + ], ) bzl_library( @@ -101,19 +105,31 @@ bzl_library( bzl_library( name = "py_info_bzl", srcs = ["py_info.bzl"], - deps = ["//python/private:reexports_bzl"], + deps = [ + "//python/private:reexports_bzl", + "//python/private/common:providers_bzl", + "@rules_python_internal//:rules_python_config_bzl", + ], ) bzl_library( name = "py_library_bzl", srcs = ["py_library.bzl"], - deps = ["//python/private:util_bzl"], + deps = [ + "//python/private:util_bzl", + "//python/private/common:py_library_macro_bazel_bzl", + "@rules_python_internal//:rules_python_config_bzl", + ], ) bzl_library( name = "py_runtime_bzl", srcs = ["py_runtime.bzl"], - deps = ["//python/private:util_bzl"], + deps = [ + "//python/private:util_bzl", + "//python/private/common:py_runtime_macro_bzl", + "@rules_python_internal//:rules_python_config_bzl", + ], ) bzl_library( @@ -125,13 +141,21 @@ bzl_library( bzl_library( name = "py_runtime_info_bzl", srcs = ["py_runtime_info.bzl"], - deps = ["//python/private:reexports_bzl"], + deps = [ + "//python/private:reexports_bzl", + "//python/private/common:providers_bzl", + "@rules_python_internal//:rules_python_config_bzl", + ], ) bzl_library( name = "py_test_bzl", srcs = ["py_test.bzl"], - deps = ["//python/private:util_bzl"], + deps = [ + "//python/private:util_bzl", + "//python/private/common:py_test_macro_bazel_bzl", + "@rules_python_internal//:rules_python_config_bzl", + ], ) # NOTE: Remember to add bzl_library targets to //tests:bzl_libraries diff --git a/python/extensions/private/internal_deps.bzl b/python/extensions/private/internal_deps.bzl index 8a98b82827..aadf2cc997 100644 --- a/python/extensions/private/internal_deps.bzl +++ b/python/extensions/private/internal_deps.bzl @@ -9,9 +9,11 @@ "Python toolchain module extension for internal rule use" load("//python/pip_install:repositories.bzl", "pip_install_dependencies") +load("//python/private:internal_config_repo.bzl", "internal_config_repo") # buildifier: disable=unused-variable def _internal_deps_impl(module_ctx): + internal_config_repo(name = "rules_python_internal") pip_install_dependencies() internal_deps = module_extension( diff --git a/python/private/BUILD.bazel b/python/private/BUILD.bazel index 48c3f8c73b..5dd7b35f1a 100644 --- a/python/private/BUILD.bazel +++ b/python/private/BUILD.bazel @@ -22,7 +22,11 @@ licenses(["notice"]) filegroup( name = "distribution", - srcs = glob(["**"]) + ["//python/private/proto:distribution"], + srcs = glob(["**"]) + [ + "//python/private/common:distribution", + "//python/private/proto:distribution", + "//tools/build_defs/python/private:distribution", + ], visibility = ["//python:__pkg__"], ) diff --git a/python/private/common/BUILD.bazel b/python/private/common/BUILD.bazel index aa21042e25..f20e682e26 100644 --- a/python/private/common/BUILD.bazel +++ b/python/private/common/BUILD.bazel @@ -11,3 +11,194 @@ # 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. + +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") + +package( + default_visibility = ["//python:__subpackages__"], +) + +bzl_library( + name = "attributes_bazel_bzl", + srcs = ["attributes_bazel.bzl"], +) + +bzl_library( + name = "attributes_bzl", + srcs = ["attributes.bzl"], + deps = [ + ":common_bzl", + ":providers_bzl", + ":py_internal_bzl", + ":semantics_bzl", + ], +) + +bzl_library( + name = "cc_helper_bzl", + srcs = ["cc_helper.bzl"], + deps = [":py_internal_bzl"], +) + +bzl_library( + name = "common_bazel_bzl", + srcs = ["common_bazel.bzl"], + deps = [ + ":common_bzl", + ":providers_bzl", + ":py_internal_bzl", + "@bazel_skylib//lib:paths", + ], +) + +bzl_library( + name = "common_bzl", + srcs = ["common.bzl"], + deps = [ + ":cc_helper_bzl", + ":providers_bzl", + ":py_internal_bzl", + ":semantics_bzl", + ], +) + +filegroup( + name = "distribution", + srcs = glob(["**"]), +) + +bzl_library( + name = "providers_bzl", + srcs = ["providers.bzl"], + deps = [ + ":semantics_bzl", + "@rules_python_internal//:rules_python_config_bzl", + ], +) + +bzl_library( + name = "py_binary_macro_bazel_bzl", + srcs = ["py_binary_macro_bazel.bzl"], + deps = [ + ":common_bzl", + ":py_binary_rule_bazel_bzl", + ], +) + +bzl_library( + name = "py_binary_rule_bazel_bzl", + srcs = ["py_binary_rule_bazel.bzl"], + deps = [ + ":attributes_bzl", + ":py_executable_bazel_bzl", + ":semantics_bzl", + "@bazel_skylib//lib:dicts", + ], +) + +bzl_library( + name = "py_executable_bazel_bzl", + srcs = ["py_executable_bazel.bzl"], + deps = [ + ":attributes_bazel_bzl", + ":common_bazel_bzl", + ":common_bzl", + ":providers_bzl", + ":py_executable_bzl", + ":py_internal_bzl", + ":semantics_bzl", + ], +) + +bzl_library( + name = "py_executable_bzl", + srcs = ["py_executable.bzl"], + deps = [ + ":attributes_bzl", + ":cc_helper_bzl", + ":common_bzl", + ":providers_bzl", + ":py_internal_bzl", + "@bazel_skylib//lib:dicts", + ], +) + +bzl_library( + name = "py_internal_bzl", + srcs = ["py_internal.bzl"], + deps = ["@rules_python_internal//:py_internal_bzl"], +) + +bzl_library( + name = "py_library_bzl", + srcs = ["py_library.bzl"], + deps = [ + ":attributes_bzl", + ":common_bzl", + ":providers_bzl", + ":py_internal_bzl", + "@bazel_skylib//lib:dicts", + ], +) + +bzl_library( + name = "py_library_macro_bazel_bzl", + srcs = ["py_library_macro_bazel.bzl"], + deps = [":py_library_rule_bazel_bzl"], +) + +bzl_library( + name = "py_library_rule_bazel_bzl", + srcs = ["py_library_rule_bazel.bzl"], + deps = [ + ":attributes_bazel_bzl", + ":common_bazel_bzl", + ":common_bzl", + ":py_library_bzl", + ], +) + +bzl_library( + name = "py_runtime_macro_bzl", + srcs = ["py_runtime_macro.bzl"], + deps = [":py_runtime_rule_bzl"], +) + +bzl_library( + name = "py_runtime_rule_bzl", + srcs = ["py_runtime_rule.bzl"], + deps = [ + ":attributes_bzl", + ":common_bzl", + ":providers_bzl", + ":py_internal_bzl", + "@bazel_skylib//lib:dicts", + "@bazel_skylib//lib:paths", + ], +) + +bzl_library( + name = "py_test_macro_bazel_bzl", + srcs = ["py_test_macro_bazel.bzl"], + deps = [ + ":common_bazel_bzl", + ":py_test_rule_bazel_bzl", + ], +) + +bzl_library( + name = "py_test_rule_bazel_bzl", + srcs = ["py_test_rule_bazel.bzl"], + deps = [ + ":attributes_bzl", + ":common_bzl", + ":py_executable_bazel_bzl", + ":semantics_bzl", + "@bazel_skylib//lib:dicts", + ], +) + +bzl_library( + name = "semantics_bzl", + srcs = ["semantics.bzl"], +) diff --git a/python/private/common/attributes.bzl b/python/private/common/attributes.bzl index ea43ceafb1..6e184c0c8f 100644 --- a/python/private/common/attributes.bzl +++ b/python/private/common/attributes.bzl @@ -26,7 +26,7 @@ load( # TODO: Load CcInfo from rules_cc _CcInfo = CcInfo -_PackageSpecificationInfo = py_internal.PackageSpecificationInfo +_PackageSpecificationInfo = getattr(py_internal, "PackageSpecificationInfo", None) _STAMP_VALUES = [-1, 0, 1] @@ -85,15 +85,28 @@ DATA_ATTRS = { ), } -NATIVE_RULES_ALLOWLIST_ATTRS = { - "_native_rules_allowlist": attr.label( +def _create_native_rules_allowlist_attrs(): + if py_internal: + # The fragment and name are validated when configuration_field is called default = configuration_field( fragment = "py", name = "native_rules_allowlist", + ) + + # A None provider isn't allowed + providers = [_PackageSpecificationInfo] + else: + default = None + providers = [] + + return { + "_native_rules_allowlist": attr.label( + default = default, + providers = providers, ), - providers = [_PackageSpecificationInfo], - ), -} + } + +NATIVE_RULES_ALLOWLIST_ATTRS = _create_native_rules_allowlist_attrs() # Attributes common to all rules. COMMON_ATTRS = union_attrs( diff --git a/python/private/common/cc_helper.bzl b/python/private/common/cc_helper.bzl index cef1ab169d..552b42eae8 100644 --- a/python/private/common/cc_helper.bzl +++ b/python/private/common/cc_helper.bzl @@ -20,4 +20,4 @@ These may change at any time and are closely coupled to the rule implementation. load(":py_internal.bzl", "py_internal") -cc_helper = py_internal.cc_helper +cc_helper = getattr(py_internal, "cc_helper", None) diff --git a/python/private/common/common.bzl b/python/private/common/common.bzl index 8522d80606..bffbf6f0cf 100644 --- a/python/private/common/common.bzl +++ b/python/private/common/common.bzl @@ -27,7 +27,7 @@ _testing = testing _platform_common = platform_common _coverage_common = coverage_common _py_builtins = py_internal -PackageSpecificationInfo = py_internal.PackageSpecificationInfo +PackageSpecificationInfo = getattr(py_internal, "PackageSpecificationInfo", None) TOOLCHAIN_TYPE = "@" + TOOLS_REPO + "//tools/python:toolchain_type" diff --git a/python/private/common/providers.bzl b/python/private/common/providers.bzl index 237a3e4d20..8a5089d976 100644 --- a/python/private/common/providers.bzl +++ b/python/private/common/providers.bzl @@ -13,6 +13,7 @@ # limitations under the License. """Providers for Python rules.""" +load("@rules_python_internal//:rules_python_config.bzl", "config") load(":semantics.bzl", "TOOLS_REPO") # TODO: load CcInfo from rules_cc @@ -23,6 +24,18 @@ DEFAULT_STUB_SHEBANG = "#!/usr/bin/env python3" DEFAULT_BOOTSTRAP_TEMPLATE = "@" + TOOLS_REPO + "//tools/python:python_bootstrap_template.txt" _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 config.enable_pystar: + return provider("Stub, not used", fields = []), None + return provider(doc = doc, fields = fields, **kwargs) + def _PyRuntimeInfo_init( *, interpreter_path = None, @@ -82,7 +95,7 @@ def _PyRuntimeInfo_init( # 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( +PyRuntimeInfo, _unused_raw_py_runtime_info_ctor = _define_provider( doc = """Contains information about a Python runtime, as returned by the `py_runtime` rule. @@ -169,8 +182,8 @@ def _PyInfo_init( "uses_shared_libraries": uses_shared_libraries, } -PyInfo, _unused_raw_py_info_ctor = provider( - "Encapsulates information provided by the Python rules.", +PyInfo, _unused_raw_py_info_ctor = _define_provider( + doc = "Encapsulates information provided by the Python rules.", init = _PyInfo_init, fields = { "has_py2_only_sources": "Whether any of this target's transitive sources requires a Python 2 runtime.", @@ -200,7 +213,7 @@ def _PyCcLinkParamsProvider_init(cc_info): } # buildifier: disable=name-conventions -PyCcLinkParamsProvider, _unused_raw_py_cc_link_params_provider_ctor = provider( +PyCcLinkParamsProvider, _unused_raw_py_cc_link_params_provider_ctor = _define_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"), diff --git a/python/private/common/py_binary_rule_bazel.bzl b/python/private/common/py_binary_rule_bazel.bzl index 6c324d8bc5..491d9050da 100644 --- a/python/private/common/py_binary_rule_bazel.bzl +++ b/python/private/common/py_binary_rule_bazel.bzl @@ -13,6 +13,7 @@ # limitations under the License. """Rule implementation of py_binary for Bazel.""" +load("@bazel_skylib//lib:dicts.bzl", "dicts") load(":attributes.bzl", "AGNOSTIC_BINARY_ATTRS") load( ":py_executable_bazel.bzl", @@ -43,6 +44,6 @@ def _py_binary_impl(ctx): py_binary = create_executable_rule( implementation = _py_binary_impl, - attrs = AGNOSTIC_BINARY_ATTRS | _PY_TEST_ATTRS, + attrs = dicts.add(AGNOSTIC_BINARY_ATTRS, _PY_TEST_ATTRS), executable = True, ) diff --git a/python/private/common/py_executable.bzl b/python/private/common/py_executable.bzl index 7a50a75c11..98a29bedde 100644 --- a/python/private/common/py_executable.bzl +++ b/python/private/common/py_executable.bzl @@ -13,6 +13,7 @@ # limitations under the License. """Common functionality between test/binary executables.""" +load("@bazel_skylib//lib:dicts.bzl", "dicts") load( ":attributes.bzl", "AGNOSTIC_EXECUTABLE_ATTRS", @@ -452,7 +453,7 @@ def _write_build_data(ctx, central_uncachable_version_file, extra_write_build_da ctx.actions.run( executable = ctx.executable._build_data_gen, - env = { + env = dicts.add({ # NOTE: ctx.info_file is undocumented; see # https://github.com/bazelbuild/bazel/issues/9363 "INFO_FILE": ctx.info_file.path, @@ -460,7 +461,7 @@ def _write_build_data(ctx, central_uncachable_version_file, extra_write_build_da "PLATFORM": cc_helper.find_cpp_toolchain(ctx).toolchain_id, "TARGET": str(ctx.label), "VERSION_FILE": version_file.path, - } | extra_write_build_data_env, + }, extra_write_build_data_env), inputs = depset( direct = direct_inputs, ), @@ -808,8 +809,8 @@ def create_base_executable_rule(*, attrs, fragments = [], **kwargs): fragments = fragments + ["py"] return rule( # TODO: add ability to remove attrs, i.e. for imports attr - attrs = EXECUTABLE_ATTRS | attrs, - toolchains = [TOOLCHAIN_TYPE] + cc_helper.use_cpp_toolchain(), + attrs = dicts.add(EXECUTABLE_ATTRS, attrs), + toolchains = [TOOLCHAIN_TYPE] + (cc_helper.use_cpp_toolchain() if cc_helper else []), fragments = fragments, **kwargs ) diff --git a/python/private/common/py_executable_bazel.bzl b/python/private/common/py_executable_bazel.bzl index a145d421a6..6c50b75b71 100644 --- a/python/private/common/py_executable_bazel.bzl +++ b/python/private/common/py_executable_bazel.bzl @@ -13,6 +13,7 @@ # limitations under the License. """Implementation for Bazel Python executable.""" +load("@bazel_skylib//lib:dicts.bzl", "dicts") load("@bazel_skylib//lib:paths.bzl", "paths") load(":attributes_bazel.bzl", "IMPORTS_ATTRS") load( @@ -62,10 +63,13 @@ the `srcs` of Python targets as required. executable = True, ), "_py_interpreter": attr.label( + # The configuration_field args are validated when called; + # we use the precense of py_internal to indicate this Bazel + # build has that fragment and name. default = configuration_field( fragment = "bazel_py", name = "python_top", - ), + ) if py_internal else None, ), # TODO: This appears to be vestigial. It's only added because # GraphlessQueryTest.testLabelsOperator relies on it to test for @@ -88,7 +92,7 @@ the `srcs` of Python targets as required. def create_executable_rule(*, attrs, **kwargs): return create_base_executable_rule( - attrs = BAZEL_EXECUTABLE_ATTRS | attrs, + attrs = dicts.add(BAZEL_EXECUTABLE_ATTRS, attrs), fragments = ["py", "bazel_py"], **kwargs ) diff --git a/python/private/common/py_internal.bzl b/python/private/common/py_internal.bzl index c17bbf0522..429637253f 100644 --- a/python/private/common/py_internal.bzl +++ b/python/private/common/py_internal.bzl @@ -18,7 +18,9 @@ Re-exports the restricted-use py_internal helper under its original name. These may change at any time and are closely coupled to the rule implementation. """ -# buildifier: disable=bzl-visibility -load("//tools/build_defs/python/private:py_internal_renamed.bzl", "py_internal_renamed") +# The py_internal global is only available in Bazel 7+, so loading of it +# must go through a repo rule with Bazel version detection logic. +load("@rules_python_internal//:py_internal.bzl", "py_internal_impl") -py_internal = py_internal_renamed +# NOTE: This is None prior to Bazel 7, as set by @rules_python_internal +py_internal = py_internal_impl diff --git a/python/private/common/py_library.bzl b/python/private/common/py_library.bzl index ca71e72443..8d09c51092 100644 --- a/python/private/common/py_library.bzl +++ b/python/private/common/py_library.bzl @@ -13,6 +13,7 @@ # limitations under the License. """Implementation of py_library rule.""" +load("@bazel_skylib//lib:dicts.bzl", "dicts") load( ":attributes.bzl", "COMMON_ATTRS", @@ -92,7 +93,7 @@ def create_py_library_rule(*, attrs = {}, **kwargs): A rule object """ return rule( - attrs = LIBRARY_ATTRS | attrs, + attrs = dicts.add(LIBRARY_ATTRS, attrs), # TODO(b/253818097): fragments=py is only necessary so that # RequiredConfigFragmentsTest passes fragments = ["py"], diff --git a/python/private/common/py_runtime_rule.bzl b/python/private/common/py_runtime_rule.bzl index 4bffb876c9..39434042ea 100644 --- a/python/private/common/py_runtime_rule.bzl +++ b/python/private/common/py_runtime_rule.bzl @@ -13,6 +13,7 @@ # limitations under the License. """Implementation of py_runtime rule.""" +load("@bazel_skylib//lib:dicts.bzl", "dicts") load("@bazel_skylib//lib:paths.bzl", "paths") load(":attributes.bzl", "NATIVE_RULES_ALLOWLIST_ATTRS") load(":common.bzl", "check_native_allowed") @@ -128,7 +129,7 @@ py_runtime( ``` """, fragments = ["py"], - attrs = NATIVE_RULES_ALLOWLIST_ATTRS | { + attrs = dicts.add(NATIVE_RULES_ALLOWLIST_ATTRS, { "bootstrap_template": attr.label( allow_single_file = True, default = DEFAULT_BOOTSTRAP_TEMPLATE, @@ -211,5 +212,5 @@ motivation. Does not apply to Windows. """, ), - }, + }), ) diff --git a/python/private/common/py_test_rule_bazel.bzl b/python/private/common/py_test_rule_bazel.bzl index de1aa4581c..348935edee 100644 --- a/python/private/common/py_test_rule_bazel.bzl +++ b/python/private/common/py_test_rule_bazel.bzl @@ -13,6 +13,7 @@ # limitations under the License. """Rule implementation of py_test for Bazel.""" +load("@bazel_skylib//lib:dicts.bzl", "dicts") load(":attributes.bzl", "AGNOSTIC_TEST_ATTRS") load(":common.bzl", "maybe_add_test_execution_info") load( @@ -50,6 +51,6 @@ def _py_test_impl(ctx): py_test = create_executable_rule( implementation = _py_test_impl, - attrs = AGNOSTIC_TEST_ATTRS | _BAZEL_PY_TEST_ATTRS, + attrs = dicts.add(AGNOSTIC_TEST_ATTRS, _BAZEL_PY_TEST_ATTRS), test = True, ) diff --git a/python/private/internal_config_repo.bzl b/python/private/internal_config_repo.bzl new file mode 100644 index 0000000000..cfc7616de9 --- /dev/null +++ b/python/private/internal_config_repo.bzl @@ -0,0 +1,99 @@ +# Copyright 2023 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. +"""Repository to generate configuration settings info from the environment. + +This handles settings that can't be encoded as regular build configuration flags, +such as globals available to Bazel versions, or propagating user environment +settings for rules to later use. +""" + +load("//python/private:bzlmod_enabled.bzl", "BZLMOD_ENABLED") + +_ENABLE_PYSTAR_ENVVAR_NAME = "RULES_PYTHON_ENABLE_PYSTAR" +_ENABLE_PYSTAR_DEFAULT = "0" + +_CONFIG_TEMPLATE = """\ +config = struct( + enable_pystar = {enable_pystar}, +) +""" + +# The py_internal symbol is only accessible from within @rules_python, so we have to +# load it from there and re-export it so that rules_python can later load it. +_PY_INTERNAL_SHIM = """\ +load("@rules_python//tools/build_defs/python/private:py_internal_renamed.bzl", "py_internal_renamed") +py_internal_impl = py_internal_renamed +""" + +ROOT_BUILD_TEMPLATE = """\ +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") + +package( + default_visibility = [ + "{visibility}", + ] +) + +bzl_library( + name = "rules_python_config_bzl", + srcs = ["rules_python_config.bzl"] +) + +bzl_library( + name = "py_internal_bzl", + srcs = ["py_internal.bzl"], + deps = [{py_internal_dep}], +) +""" + +def _internal_config_repo_impl(rctx): + enable_pystar = _bool_from_environ(rctx, _ENABLE_PYSTAR_ENVVAR_NAME, _ENABLE_PYSTAR_DEFAULT) + rctx.file("rules_python_config.bzl", _CONFIG_TEMPLATE.format( + enable_pystar = enable_pystar, + )) + + if enable_pystar or ( + # Bazel 7+ (dev and later) has native.starlark_doc_extract, and thus the py_internal global + hasattr(native, "starlark_doc_extract") and + # The logic to allow the symbol doesn't work properly under bzlmod, + # even if the symbol is otherwise functional. + not BZLMOD_ENABLED + ): + shim_content = _PY_INTERNAL_SHIM + py_internal_dep = '"@rules_python//tools/build_defs/python/private:py_internal_renamed_bzl"' + else: + shim_content = "py_internal_impl = None\n" + py_internal_dep = "" + + # Bazel 5 doesn't support repository visibility, so just use public + # as a stand-in + if native.bazel_version.startswith("5."): + visibility = "//visibility:public" + else: + visibility = "@rules_python//:__subpackages__" + + rctx.file("BUILD", ROOT_BUILD_TEMPLATE.format( + py_internal_dep = py_internal_dep, + visibility = visibility, + )) + rctx.file("py_internal.bzl", shim_content) + return None + +internal_config_repo = repository_rule( + implementation = _internal_config_repo_impl, + environ = [_ENABLE_PYSTAR_ENVVAR_NAME], +) + +def _bool_from_environ(rctx, key, default): + return bool(int(rctx.os.environ.get(key, default))) diff --git a/python/py_binary.bzl b/python/py_binary.bzl index 6b6f7e0f8a..6dcb4ad40c 100644 --- a/python/py_binary.bzl +++ b/python/py_binary.bzl @@ -14,7 +14,12 @@ """Public entry point for py_binary.""" +load("@rules_python_internal//:rules_python_config.bzl", "config") load("//python/private:util.bzl", "add_migration_tag") +load("//python/private/common:py_binary_macro_bazel.bzl", _starlark_py_binary = "py_binary") + +# buildifier: disable=native-python +_py_binary_impl = _starlark_py_binary if config.enable_pystar else native.py_binary def py_binary(**attrs): """See the Bazel core [py_binary](https://docs.bazel.build/versions/master/be/python.html#py_binary) documentation. @@ -27,5 +32,4 @@ def py_binary(**attrs): if attrs.get("srcs_version") in ("PY2", "PY2ONLY"): fail("Python 2 is no longer supported: https://github.com/bazelbuild/rules_python/issues/886") - # buildifier: disable=native-python - native.py_binary(**add_migration_tag(attrs)) + _py_binary_impl(**add_migration_tag(attrs)) diff --git a/python/py_info.bzl b/python/py_info.bzl index 2c3997dee2..cbf145d07d 100644 --- a/python/py_info.bzl +++ b/python/py_info.bzl @@ -14,6 +14,8 @@ """Public entry point for PyInfo.""" +load("@rules_python_internal//:rules_python_config.bzl", "config") load("//python/private:reexports.bzl", "internal_PyInfo") +load("//python/private/common:providers.bzl", _starlark_PyInfo = "PyInfo") -PyInfo = internal_PyInfo +PyInfo = _starlark_PyInfo if config.enable_pystar else internal_PyInfo diff --git a/python/py_library.bzl b/python/py_library.bzl index d54cbb2958..ef4c3c3969 100644 --- a/python/py_library.bzl +++ b/python/py_library.bzl @@ -14,7 +14,12 @@ """Public entry point for py_library.""" +load("@rules_python_internal//:rules_python_config.bzl", "config") load("//python/private:util.bzl", "add_migration_tag") +load("//python/private/common:py_library_macro_bazel.bzl", _starlark_py_library = "py_library") + +# buildifier: disable=native-python +_py_library_impl = _starlark_py_library if config.enable_pystar else native.py_library def py_library(**attrs): """See the Bazel core [py_library](https://docs.bazel.build/versions/master/be/python.html#py_library) documentation. @@ -25,5 +30,4 @@ def py_library(**attrs): if attrs.get("srcs_version") in ("PY2", "PY2ONLY"): fail("Python 2 is no longer supported: https://github.com/bazelbuild/rules_python/issues/886") - # buildifier: disable=native-python - native.py_library(**add_migration_tag(attrs)) + _py_library_impl(**add_migration_tag(attrs)) diff --git a/python/py_runtime.bzl b/python/py_runtime.bzl index b70f9d4ec4..ac8b090c94 100644 --- a/python/py_runtime.bzl +++ b/python/py_runtime.bzl @@ -14,7 +14,12 @@ """Public entry point for py_runtime.""" +load("@rules_python_internal//:rules_python_config.bzl", "config") load("//python/private:util.bzl", "add_migration_tag") +load("//python/private/common:py_runtime_macro.bzl", _starlark_py_runtime = "py_runtime") + +# buildifier: disable=native-python +_py_runtime_impl = _starlark_py_runtime if config.enable_pystar else native.py_runtime def py_runtime(**attrs): """See the Bazel core [py_runtime](https://docs.bazel.build/versions/master/be/python.html#py_runtime) documentation. @@ -25,5 +30,4 @@ def py_runtime(**attrs): if attrs.get("python_version") == "PY2": fail("Python 2 is no longer supported: see https://github.com/bazelbuild/rules_python/issues/886") - # buildifier: disable=native-python - native.py_runtime(**add_migration_tag(attrs)) + _py_runtime_impl(**add_migration_tag(attrs)) diff --git a/python/py_runtime_info.bzl b/python/py_runtime_info.bzl index 15598ee903..699b31d6df 100644 --- a/python/py_runtime_info.bzl +++ b/python/py_runtime_info.bzl @@ -14,6 +14,8 @@ """Public entry point for PyRuntimeInfo.""" +load("@rules_python_internal//:rules_python_config.bzl", "config") load("//python/private:reexports.bzl", "internal_PyRuntimeInfo") +load("//python/private/common:providers.bzl", _starlark_PyRuntimeInfo = "PyRuntimeInfo") -PyRuntimeInfo = internal_PyRuntimeInfo +PyRuntimeInfo = _starlark_PyRuntimeInfo if config.enable_pystar else internal_PyRuntimeInfo diff --git a/python/py_test.bzl b/python/py_test.bzl index 09580c01c4..ad9bdc06ad 100644 --- a/python/py_test.bzl +++ b/python/py_test.bzl @@ -14,7 +14,12 @@ """Public entry point for py_test.""" +load("@rules_python_internal//:rules_python_config.bzl", "config") load("//python/private:util.bzl", "add_migration_tag") +load("//python/private/common:py_test_macro_bazel.bzl", _starlark_py_test = "py_test") + +# buildifier: disable=native-python +_py_test_impl = _starlark_py_test if config.enable_pystar else native.py_test def py_test(**attrs): """See the Bazel core [py_test](https://docs.bazel.build/versions/master/be/python.html#py_test) documentation. @@ -28,4 +33,4 @@ def py_test(**attrs): fail("Python 2 is no longer supported: https://github.com/bazelbuild/rules_python/issues/886") # buildifier: disable=native-python - native.py_test(**add_migration_tag(attrs)) + _py_test_impl(**add_migration_tag(attrs)) diff --git a/python/repositories.bzl b/python/repositories.bzl index fbe23bc2e3..2c0b421695 100644 --- a/python/repositories.bzl +++ b/python/repositories.bzl @@ -21,6 +21,7 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", _http_archive = "http_archi load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") load("//python/private:bzlmod_enabled.bzl", "BZLMOD_ENABLED") load("//python/private:coverage_deps.bzl", "coverage_dep") +load("//python/private:internal_config_repo.bzl", "internal_config_repo") load( "//python/private:toolchains_repo.bzl", "multi_toolchain_aliases", @@ -46,6 +47,10 @@ def py_repositories(): This function should be loaded and called in the user's WORKSPACE. With bzlmod enabled, this function is not needed since MODULE.bazel handles transitive deps. """ + maybe( + internal_config_repo, + name = "rules_python_internal", + ) http_archive( name = "bazel_skylib", sha256 = "74d544d96f4a5bb630d465ca8bbcfe231e3594e5aae57e1edbf17a6eb3ca2506", diff --git a/python/tests/toolchains/workspace_template/WORKSPACE.tmpl b/python/tests/toolchains/workspace_template/WORKSPACE.tmpl index 973e020c1e..3335f4b063 100644 --- a/python/tests/toolchains/workspace_template/WORKSPACE.tmpl +++ b/python/tests/toolchains/workspace_template/WORKSPACE.tmpl @@ -19,7 +19,9 @@ local_repository( path = "", ) -load("@rules_python//python:repositories.bzl", "python_register_toolchains") +load("@rules_python//python:repositories.bzl", "python_register_toolchains", "py_repositories") + +py_repositories() python_register_toolchains( name = "python", diff --git a/tests/ignore_root_user_error/WORKSPACE b/tests/ignore_root_user_error/WORKSPACE index e0528e4047..d1249feab2 100644 --- a/tests/ignore_root_user_error/WORKSPACE +++ b/tests/ignore_root_user_error/WORKSPACE @@ -3,7 +3,9 @@ local_repository( path = "../..", ) -load("@rules_python//python:repositories.bzl", "python_register_toolchains") +load("@rules_python//python:repositories.bzl", "py_repositories", "python_register_toolchains") + +py_repositories() python_register_toolchains( name = "python39", diff --git a/tools/build_defs/python/private/BUILD.bazel b/tools/build_defs/python/private/BUILD.bazel index aa21042e25..0a7f308f02 100644 --- a/tools/build_defs/python/private/BUILD.bazel +++ b/tools/build_defs/python/private/BUILD.bazel @@ -11,3 +11,17 @@ # 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. + +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") + +filegroup( + name = "distribution", + srcs = glob(["**"]), + visibility = ["//python:__subpackages__"], +) + +bzl_library( + name = "py_internal_renamed_bzl", + srcs = ["py_internal_renamed.bzl"], + visibility = ["@rules_python_internal//:__subpackages__"], +) diff --git a/tools/build_defs/python/private/py_internal_renamed.bzl b/tools/build_defs/python/private/py_internal_renamed.bzl index abab31c45e..a12fc2d14e 100644 --- a/tools/build_defs/python/private/py_internal_renamed.bzl +++ b/tools/build_defs/python/private/py_internal_renamed.bzl @@ -13,6 +13,10 @@ # limitations under the License. """PYTHON RULE IMPLEMENTATION ONLY: Do not use outside of the rule implementations and their tests. +NOTE: This file is only loaded by @rules_python_internal//:py_internal.bzl. This +is because the `py_internal` global symbol is only present in Bazel 7+, so +a repo rule has to conditionally load this depending on the Bazel version. + Re-exports the restricted-use py_internal helper under another name. This is necessary because `py_internal = py_internal` results in an error (trying to bind a local symbol to itself before its defined). @@ -22,4 +26,5 @@ the internal helpers only rules_python is allowed to use. These may change at any time and are closely coupled to the rule implementation. """ + py_internal_renamed = py_internal