feat(bzlmod)!: Move each bzlmod extension into its own file (#1226)
This commit refactors the files that contain the bzlmod
extensions.
- All extensions are moved under the new extensions folder
- Private extensions are moved under extensions/private
- All extension files are renamed to remove the _extension suffix
- pip and internal_deps extensions are moved to their own file
This commit organizes the extensions better and also follows the
best practice of having a single extension per file. Having each
extension in its own file allows them to use some additional features
while helping avoid backwards incompatible changes.
## BREAKING CHANGES
This splits `//python:extensions.bzl`, which previously held the
`python`
and `pip` extensions, into separate files (`python.bzl` and `pip.bzl`,
respectively). Unfortunately, moving the location of the extensions is a
breaking change due to how bzlmod extension identity works (see
https://bazel.build/external/extension#extension_identity). Fortunately,
by moving to one extension per file, we shouldn't have to ever do this
again.
Users must update the file path in their `use_repo()` statements as
follows:
* `use_extension("@rules_python//python:extensions.bzl", "python")` ->
`use_extension("@rules_python//python/extensions:python.bzl", "python")`
* `use_extension("@rules_python//python:extensions.bzl", "pip")` ->
`use_extension("@rules_python//python/extensions:pip.bzl", "pip")`
The following `sed` commands should approximate the necessary changes:
```
sed 'sXuse_extension("@rules_python//python:extensions.bzl", "python")Xuse_extension("@rules_python//python/extensions:python.bzl", "python")X'`
sed 'sXuse_extension("@rules_python//python:extensions.bzl", "pip")Xuse_extension("@rules_python//python/extensions:pip.bzl", "pip")X'`
```
See `examples/bzlmod_build_file_generation/MODULE.bazel` for an example
of the new paths.
diff --git a/MODULE.bazel b/MODULE.bazel
index e490beb..ddd946c 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -11,7 +11,7 @@
bazel_dep(name = "rules_proto", version = "5.3.0-21.7")
bazel_dep(name = "protobuf", version = "21.7", repo_name = "com_google_protobuf")
-internal_deps = use_extension("@rules_python//python:extensions.bzl", "internal_deps")
+internal_deps = use_extension("@rules_python//python/extensions/private:internal_deps.bzl", "internal_deps")
internal_deps.install()
use_repo(
internal_deps,
@@ -47,5 +47,5 @@
"pypi__coverage_cp39_x86_64-unknown-linux-gnu",
)
-python = use_extension("@rules_python//python:extensions.bzl", "python")
+python = use_extension("@rules_python//python/extensions:python.bzl", "python")
use_repo(python, "pythons_hub")
diff --git a/examples/bzlmod/MODULE.bazel b/examples/bzlmod/MODULE.bazel
index ce91228..61d7967 100644
--- a/examples/bzlmod/MODULE.bazel
+++ b/examples/bzlmod/MODULE.bazel
@@ -10,7 +10,7 @@
path = "../..",
)
-python = use_extension("@rules_python//python:extensions.bzl", "python")
+python = use_extension("@rules_python//python/extensions:python.bzl", "python")
python.toolchain(
name = "python3_9",
configure_coverage_tool = True,
@@ -23,7 +23,7 @@
"@python3_9_toolchains//:all",
)
-pip = use_extension("@rules_python//python:extensions.bzl", "pip")
+pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip")
pip.parse(
name = "pip",
requirements_lock = "//:requirements_lock.txt",
diff --git a/examples/bzlmod_build_file_generation/MODULE.bazel b/examples/bzlmod_build_file_generation/MODULE.bazel
index d59fbb3..179fe1b 100644
--- a/examples/bzlmod_build_file_generation/MODULE.bazel
+++ b/examples/bzlmod_build_file_generation/MODULE.bazel
@@ -42,7 +42,7 @@
# The following stanze returns a proxy object representing a module extension;
# its methods can be invoked to create module extension tags.
-python = use_extension("@rules_python//python:extensions.bzl", "python")
+python = use_extension("@rules_python//python/extensions:python.bzl", "python")
# This name is passed into python.toolchain and it's use_repo statement.
# We also use the same name for python.host_python_interpreter.
@@ -74,7 +74,7 @@
# The interpreter extension discovers the platform specific Python binary.
# It creates a symlink to the binary, and we pass the label to the following
# pip.parse call.
-interpreter = use_extension("@rules_python//python:interpreter_extension.bzl", "interpreter")
+interpreter = use_extension("@rules_python//python/extensions:interpreter.bzl", "interpreter")
interpreter.install(
name = "interpreter_python3",
python_name = PYTHON_NAME,
@@ -88,7 +88,7 @@
# You can instead check this `requirements.bzl` file into your repo.
# Because this project has different requirements for windows vs other
# operating systems, we have requirements for each.
-pip = use_extension("@rules_python//python:extensions.bzl", "pip")
+pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip")
pip.parse(
name = "pip",
# When using gazelle you must use set the following flag
diff --git a/examples/py_proto_library/MODULE.bazel b/examples/py_proto_library/MODULE.bazel
index 5ce0924..6fb1a05 100644
--- a/examples/py_proto_library/MODULE.bazel
+++ b/examples/py_proto_library/MODULE.bazel
@@ -12,7 +12,7 @@
path = "../..",
)
-python = use_extension("@rules_python//python:extensions.bzl", "python")
+python = use_extension("@rules_python//python/extensions:python.bzl", "python")
python.toolchain(
name = "python3_9",
configure_coverage_tool = True,
diff --git a/python/extensions.bzl b/python/extensions.bzl
deleted file mode 100644
index ce11069..0000000
--- a/python/extensions.bzl
+++ /dev/null
@@ -1,141 +0,0 @@
-# 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.
-
-"Module extensions for use with bzlmod"
-
-load("@rules_python//python:repositories.bzl", "python_register_toolchains")
-load("@rules_python//python/pip_install:pip_repository.bzl", "locked_requirements_label", "pip_repository_attrs", "pip_repository_bzlmod", "use_isolated", "whl_library")
-load("@rules_python//python/pip_install:repositories.bzl", "pip_install_dependencies")
-load("@rules_python//python/pip_install:requirements_parser.bzl", parse_requirements = "parse")
-load("@rules_python//python/private:coverage_deps.bzl", "install_coverage_deps")
-load("@rules_python//python/private:interpreter_hub.bzl", "hub_repo")
-
-def _python_impl(module_ctx):
- toolchains = []
- for mod in module_ctx.modules:
- for toolchain_attr in mod.tags.toolchain:
- python_register_toolchains(
- name = toolchain_attr.name,
- python_version = toolchain_attr.python_version,
- bzlmod = True,
- # Toolchain registration in bzlmod is done in MODULE file
- register_toolchains = False,
- register_coverage_tool = toolchain_attr.configure_coverage_tool,
- ignore_root_user_error = toolchain_attr.ignore_root_user_error,
- )
-
- # We collect all of the toolchain names to create
- # the INTERPRETER_LABELS map. This is used
- # by interpreter_extensions.bzl
- toolchains.append(toolchain_attr.name)
-
- hub_repo(
- name = "pythons_hub",
- toolchains = toolchains,
- )
-
-python = module_extension(
- implementation = _python_impl,
- tag_classes = {
- "toolchain": tag_class(
- attrs = {
- "configure_coverage_tool": attr.bool(
- mandatory = False,
- doc = "Whether or not to configure the default coverage tool for the toolchains.",
- ),
- "ignore_root_user_error": attr.bool(
- default = False,
- doc = "Whether the check for root should be ignored or not. This causes cache misses with .pyc files.",
- mandatory = False,
- ),
- "name": attr.string(mandatory = True),
- "python_version": attr.string(mandatory = True),
- },
- ),
- },
-)
-
-# buildifier: disable=unused-variable
-def _internal_deps_impl(module_ctx):
- pip_install_dependencies()
- install_coverage_deps()
-
-internal_deps = module_extension(
- implementation = _internal_deps_impl,
- tag_classes = {
- "install": tag_class(attrs = dict()),
- },
-)
-
-def _pip_impl(module_ctx):
- for mod in module_ctx.modules:
- for attr in mod.tags.parse:
- requrements_lock = locked_requirements_label(module_ctx, attr)
-
- # Parse the requirements file directly in starlark to get the information
- # needed for the whl_libary declarations below. This is needed to contain
- # the pip_repository logic to a single module extension.
- requirements_lock_content = module_ctx.read(requrements_lock)
- parse_result = parse_requirements(requirements_lock_content)
- requirements = parse_result.requirements
- extra_pip_args = attr.extra_pip_args + parse_result.options
-
- # Create the repository where users load the `requirement` macro. Under bzlmod
- # this does not create the install_deps() macro.
- pip_repository_bzlmod(
- name = attr.name,
- requirements_lock = attr.requirements_lock,
- incompatible_generate_aliases = attr.incompatible_generate_aliases,
- )
-
- for name, requirement_line in requirements:
- whl_library(
- name = "%s_%s" % (attr.name, _sanitize_name(name)),
- requirement = requirement_line,
- repo = attr.name,
- repo_prefix = attr.name + "_",
- annotation = attr.annotations.get(name),
- python_interpreter = attr.python_interpreter,
- python_interpreter_target = attr.python_interpreter_target,
- quiet = attr.quiet,
- timeout = attr.timeout,
- isolated = use_isolated(module_ctx, attr),
- extra_pip_args = extra_pip_args,
- download_only = attr.download_only,
- pip_data_exclude = attr.pip_data_exclude,
- enable_implicit_namespace_pkgs = attr.enable_implicit_namespace_pkgs,
- environment = attr.environment,
- )
-
-# Keep in sync with python/pip_install/tools/bazel.py
-def _sanitize_name(name):
- return name.replace("-", "_").replace(".", "_").lower()
-
-def _pip_parse_ext_attrs():
- attrs = dict({
- "name": attr.string(mandatory = True),
- }, **pip_repository_attrs)
-
- # Like the pip_repository rule, we end up setting this manually so
- # don't allow users to override it.
- attrs.pop("repo_prefix")
-
- return attrs
-
-pip = module_extension(
- implementation = _pip_impl,
- tag_classes = {
- "parse": tag_class(attrs = _pip_parse_ext_attrs()),
- },
-)
diff --git a/python/extensions/BUILD.bazel b/python/extensions/BUILD.bazel
new file mode 100644
index 0000000..7f6873d
--- /dev/null
+++ b/python/extensions/BUILD.bazel
@@ -0,0 +1,23 @@
+# Copyright 2017 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.
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])
+
+filegroup(
+ name = "distribution",
+ srcs = glob(["**"]),
+ visibility = ["//extensions:__pkg__"],
+)
diff --git a/python/interpreter_extension.bzl b/python/extensions/interpreter.bzl
similarity index 100%
rename from python/interpreter_extension.bzl
rename to python/extensions/interpreter.bzl
diff --git a/python/extensions/pip.bzl b/python/extensions/pip.bzl
new file mode 100644
index 0000000..2ec2bbf
--- /dev/null
+++ b/python/extensions/pip.bzl
@@ -0,0 +1,84 @@
+# 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.
+
+"pip module extension for use with bzlmod"
+
+load("@rules_python//python/pip_install:pip_repository.bzl", "locked_requirements_label", "pip_repository_attrs", "pip_repository_bzlmod", "use_isolated", "whl_library")
+load("@rules_python//python/pip_install:requirements_parser.bzl", parse_requirements = "parse")
+
+def _pip_impl(module_ctx):
+ for mod in module_ctx.modules:
+ for attr in mod.tags.parse:
+ requrements_lock = locked_requirements_label(module_ctx, attr)
+
+ # Parse the requirements file directly in starlark to get the information
+ # needed for the whl_libary declarations below. This is needed to contain
+ # the pip_repository logic to a single module extension.
+ requirements_lock_content = module_ctx.read(requrements_lock)
+ parse_result = parse_requirements(requirements_lock_content)
+ requirements = parse_result.requirements
+ extra_pip_args = attr.extra_pip_args + parse_result.options
+
+ # Create the repository where users load the `requirement` macro. Under bzlmod
+ # this does not create the install_deps() macro.
+ pip_repository_bzlmod(
+ name = attr.name,
+ requirements_lock = attr.requirements_lock,
+ incompatible_generate_aliases = attr.incompatible_generate_aliases,
+ )
+
+ for name, requirement_line in requirements:
+ whl_library(
+ name = "%s_%s" % (attr.name, _sanitize_name(name)),
+ requirement = requirement_line,
+ repo = attr.name,
+ repo_prefix = attr.name + "_",
+ annotation = attr.annotations.get(name),
+ python_interpreter = attr.python_interpreter,
+ python_interpreter_target = attr.python_interpreter_target,
+ quiet = attr.quiet,
+ timeout = attr.timeout,
+ isolated = use_isolated(module_ctx, attr),
+ extra_pip_args = extra_pip_args,
+ download_only = attr.download_only,
+ pip_data_exclude = attr.pip_data_exclude,
+ enable_implicit_namespace_pkgs = attr.enable_implicit_namespace_pkgs,
+ environment = attr.environment,
+ )
+
+# Keep in sync with python/pip_install/tools/bazel.py
+def _sanitize_name(name):
+ return name.replace("-", "_").replace(".", "_").lower()
+
+def _pip_parse_ext_attrs():
+ attrs = dict({
+ "name": attr.string(mandatory = True),
+ }, **pip_repository_attrs)
+
+ # Like the pip_repository rule, we end up setting this manually so
+ # don't allow users to override it.
+ attrs.pop("repo_prefix")
+
+ return attrs
+
+pip = module_extension(
+ doc = """\
+This extension is used to create a pip respository and create the various wheel libaries if
+provided in a requirements file.
+""",
+ implementation = _pip_impl,
+ tag_classes = {
+ "parse": tag_class(attrs = _pip_parse_ext_attrs()),
+ },
+)
diff --git a/python/extensions/private/BUILD.bazel b/python/extensions/private/BUILD.bazel
new file mode 100644
index 0000000..f367b71
--- /dev/null
+++ b/python/extensions/private/BUILD.bazel
@@ -0,0 +1,23 @@
+# 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.
+
+package(default_visibility = ["//visibility:private"])
+
+licenses(["notice"])
+
+filegroup(
+ name = "distribution",
+ srcs = glob(["**"]),
+ visibility = ["//python/extensions/private:__pkg__"],
+)
diff --git a/python/extensions/private/internal_deps.bzl b/python/extensions/private/internal_deps.bzl
new file mode 100644
index 0000000..dfa3e26
--- /dev/null
+++ b/python/extensions/private/internal_deps.bzl
@@ -0,0 +1,25 @@
+# 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.
+
+"Python toolchain module extension for internal rule use"
+
+load("@rules_python//python/pip_install:repositories.bzl", "pip_install_dependencies")
+load("@rules_python//python/private:coverage_deps.bzl", "install_coverage_deps")
+
+# buildifier: disable=unused-variable
+def _internal_deps_impl(module_ctx):
+ pip_install_dependencies()
+ install_coverage_deps()
+
+internal_deps = module_extension(
+ doc = "This extension to register internal rules_python dependecies.",
+ implementation = _internal_deps_impl,
+ tag_classes = {
+ "install": tag_class(attrs = dict()),
+ },
+)
diff --git a/python/private/interpreter_hub.bzl b/python/extensions/private/interpreter_hub.bzl
similarity index 100%
rename from python/private/interpreter_hub.bzl
rename to python/extensions/private/interpreter_hub.bzl
diff --git a/python/extensions/python.bzl b/python/extensions/python.bzl
new file mode 100644
index 0000000..9a3d9ed
--- /dev/null
+++ b/python/extensions/python.bzl
@@ -0,0 +1,64 @@
+# 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.
+
+"Python toolchain module extensions for use with bzlmod"
+
+load("@rules_python//python:repositories.bzl", "python_register_toolchains")
+load("@rules_python//python/extensions/private:interpreter_hub.bzl", "hub_repo")
+
+def _python_impl(module_ctx):
+ toolchains = []
+ for mod in module_ctx.modules:
+ for toolchain_attr in mod.tags.toolchain:
+ python_register_toolchains(
+ name = toolchain_attr.name,
+ python_version = toolchain_attr.python_version,
+ bzlmod = True,
+ # Toolchain registration in bzlmod is done in MODULE file
+ register_toolchains = False,
+ register_coverage_tool = toolchain_attr.configure_coverage_tool,
+ ignore_root_user_error = toolchain_attr.ignore_root_user_error,
+ )
+
+ # We collect all of the toolchain names to create
+ # the INTERPRETER_LABELS map. This is used
+ # by interpreter_extensions.bzl
+ toolchains.append(toolchain_attr.name)
+
+ hub_repo(
+ name = "pythons_hub",
+ toolchains = toolchains,
+ )
+
+python = module_extension(
+ doc = "Bzlmod extension that is used to register a Python toolchain.",
+ implementation = _python_impl,
+ tag_classes = {
+ "toolchain": tag_class(
+ attrs = {
+ "configure_coverage_tool": attr.bool(
+ mandatory = False,
+ doc = "Whether or not to configure the default coverage tool for the toolchains.",
+ ),
+ "ignore_root_user_error": attr.bool(
+ default = False,
+ doc = "Whether the check for root should be ignored or not. This causes cache misses with .pyc files.",
+ mandatory = False,
+ ),
+ "name": attr.string(mandatory = True),
+ "python_version": attr.string(mandatory = True),
+ },
+ ),
+ },
+)