Skip to content

Commit b1d20d7

Browse files
committed
refactor(toolchain): create a hub repo for the coverage tool
This PR refactors how the coverage tool is handled in the toolchain registration. 1. Instead of creating the repo during the macro evaluation of `python_register_toolchains`, we are creating the coverage hub repo `pypi__coverage` separately in a macro that mimics the `pip.parse` extension. This is done as part of `py_repositories` call that is used by `WORKSPACE` and `bzlmod` users. 2. We accept `@rules_python//python:none` label as the sentinel value of no coverage tool so that the user can define a select statement that when no coverage tool is defined a special value is passed. 3. We reuse the `bzlmod` `hub_repository` for the coverage hub repo.
1 parent 33cb431 commit b1d20d7

14 files changed

+164
-87
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ Unreleased changes template.
5959
env var.
6060
* (pypi) Downgraded versions of packages: `pip` from `24.3.2` to `24.0.0` and
6161
`packaging` from `24.2` to `24.0`.
62+
* (toolchain) `coverage_tool` now accepts a special value of the `//python:none`
63+
that acts as a label that means "no coverage tool".
64+
* (toolchain) The coverage deps are now registered as part of `py_repositories`.
6265

6366
{#v0-0-0-fixed}
6467
### Fixed

MODULE.bazel

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,33 @@ use_repo(
1919
"pypi__build",
2020
"pypi__click",
2121
"pypi__colorama",
22+
"pypi__coverage",
23+
"pypi__coverage_cp310_aarch64_apple_darwin",
24+
"pypi__coverage_cp310_aarch64_unknown_linux_gnu",
25+
"pypi__coverage_cp310_x86_64_apple_darwin",
26+
"pypi__coverage_cp310_x86_64_unknown_linux_gnu",
27+
"pypi__coverage_cp311_aarch64_apple_darwin",
28+
"pypi__coverage_cp311_aarch64_unknown_linux_gnu",
29+
"pypi__coverage_cp311_x86_64_apple_darwin",
30+
"pypi__coverage_cp311_x86_64_unknown_linux_gnu",
31+
"pypi__coverage_cp312_aarch64_apple_darwin",
32+
"pypi__coverage_cp312_aarch64_unknown_linux_gnu",
33+
"pypi__coverage_cp312_x86_64_apple_darwin",
34+
"pypi__coverage_cp312_x86_64_unknown_linux_gnu",
35+
"pypi__coverage_cp313_aarch64_apple_darwin",
36+
"pypi__coverage_cp313_aarch64_apple_darwin_freethreaded",
37+
"pypi__coverage_cp313_aarch64_unknown_linux_gnu",
38+
"pypi__coverage_cp313_aarch64_unknown_linux_gnu_freethreaded",
39+
"pypi__coverage_cp313_x86_64_unknown_linux_gnu",
40+
"pypi__coverage_cp313_x86_64_unknown_linux_gnu_freethreaded",
41+
"pypi__coverage_cp38_aarch64_apple_darwin",
42+
"pypi__coverage_cp38_aarch64_unknown_linux_gnu",
43+
"pypi__coverage_cp38_x86_64_apple_darwin",
44+
"pypi__coverage_cp38_x86_64_unknown_linux_gnu",
45+
"pypi__coverage_cp39_aarch64_apple_darwin",
46+
"pypi__coverage_cp39_aarch64_unknown_linux_gnu",
47+
"pypi__coverage_cp39_x86_64_apple_darwin",
48+
"pypi__coverage_cp39_x86_64_unknown_linux_gnu",
2249
"pypi__importlib_metadata",
2350
"pypi__installer",
2451
"pypi__more_itertools",

python/private/BUILD.bazel

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ filegroup(
5252
visibility = ["//python:__pkg__"],
5353
)
5454

55+
alias(
56+
name = "coverage",
57+
actual = "@pypi__coverage//coverage",
58+
visibility = ["//visibility:public"],
59+
)
60+
5561
bzl_library(
5662
name = "attributes_bzl",
5763
srcs = ["attributes.bzl"],
@@ -134,7 +140,8 @@ bzl_library(
134140
srcs = ["coverage_deps.bzl"],
135141
deps = [
136142
":bazel_tools_bzl",
137-
":version_label_bzl",
143+
"//python/private/pypi:hub_repository_bzl",
144+
"//python/private/pypi:whl_config_setting_bzl",
138145
],
139146
)
140147

python/private/coverage_deps.bzl

Lines changed: 51 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717

1818
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
1919
load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
20-
load("//python/private:version_label.bzl", "version_label")
20+
load("//python/private/pypi:hub_repository.bzl", "hub_repository", "whl_config_settings_to_json")
21+
load("//python/private/pypi:whl_config_setting.bzl", "whl_config_setting")
2122

2223
# START: maintained by 'bazel run //tools/private/update_deps:update_coverage_deps <version>'
2324
_coverage_deps = {
@@ -142,49 +143,63 @@ _coverage_deps = {
142143

143144
_coverage_patch = Label("//python/private:coverage.patch")
144145

145-
def coverage_dep(name, python_version, platform, visibility):
146-
"""Register a single coverage dependency based on the python version and platform.
146+
def coverage_deps(name):
147+
"""Register all coverage dependencies.
147148
148-
Args:
149-
name: The name of the registered repository.
150-
python_version: The full python version.
151-
platform: The platform, which can be found in //python:versions.bzl PLATFORMS dict.
152-
visibility: The visibility of the coverage tool.
149+
This exposes them through a hub repository.
153150
154-
Returns:
155-
The label of the coverage tool if the platform is supported, otherwise - None.
151+
Args:
152+
name: The name of the hub repository.
156153
"""
157-
if "windows" in platform:
158-
# NOTE @aignas 2023-01-19: currently we do not support windows as the
159-
# upstream coverage wrapper is written in shell. Do not log any warning
160-
# for now as it is not actionable.
161-
return None
162154

163-
abi = "cp" + version_label(python_version)
164-
url, sha256 = _coverage_deps.get(abi, {}).get(platform, (None, ""))
155+
whl_map = {
156+
str(Label("//python:none")): [
157+
whl_config_setting(config_setting = "//conditions:default"),
158+
],
159+
}
160+
for abi, values in _coverage_deps.items():
161+
for platform, (url, sha256) in values.items():
162+
spoke_name = "{}_{}_{}".format(name, abi, platform).replace("-", "_")
163+
_, _, filename = url.rpartition("/")
165164

166-
if url == None:
167-
# Some wheels are not present for some builds, so let's silently ignore those.
168-
return None
165+
config_setting = whl_config_setting(
166+
version = "3.{}".format(abi[3:]),
167+
filename = filename,
168+
)
169169

170-
maybe(
171-
http_archive,
172-
name = name,
173-
build_file_content = """
170+
whl_map[spoke_name] = [config_setting]
171+
172+
# NOTE @aignas 2025-02-04: under `bzlmod` the `maybe` function is noop.
173+
# This is only kept for `WORKSPACE` compatibility.
174+
maybe(
175+
http_archive,
176+
name = spoke_name,
177+
build_file_content = """
174178
filegroup(
175-
name = "coverage",
179+
name = "pkg",
176180
srcs = ["coverage/__main__.py"],
177181
data = glob(["coverage/*.py", "coverage/**/*.py", "coverage/*.so"]),
178-
visibility = {visibility},
182+
visibility = ["{visibility}"],
179183
)
180-
""".format(
181-
visibility = visibility,
182-
),
183-
patch_args = ["-p1"],
184-
patches = [_coverage_patch],
185-
sha256 = sha256,
186-
type = "zip",
187-
urls = [url],
188-
)
184+
""".format(
185+
visibility = "@{}//:__subpackages__".format(name),
186+
),
187+
patch_args = ["-p1"],
188+
patches = [_coverage_patch],
189+
sha256 = sha256,
190+
type = "zip",
191+
urls = [url],
192+
)
189193

190-
return "@{name}//:coverage".format(name = name)
194+
maybe(
195+
hub_repository,
196+
name = name,
197+
repo_name = name,
198+
whl_map = {
199+
"coverage": whl_config_settings_to_json(whl_map),
200+
},
201+
add_requirements_bzl = False,
202+
extra_hub_aliases = {},
203+
packages = [],
204+
groups = {},
205+
)

python/private/hermetic_runtime_repo_setup.bzl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ load(":py_exec_tools_toolchain.bzl", "py_exec_tools_toolchain")
2323
load(":semver.bzl", "semver")
2424

2525
_IS_FREETHREADED = Label("//python/config_settings:is_py_freethreaded")
26+
_NONE = Label("//python:none")
2627

2728
def define_hermetic_runtime_toolchain_impl(
2829
*,
@@ -49,7 +50,7 @@ def define_hermetic_runtime_toolchain_impl(
4950
format.
5051
python_bin: {type}`str` The path to the Python binary within the
5152
repository.
52-
coverage_tool: {type}`str` optional target to the coverage tool to
53+
coverage_tool: {type}`Label` optional target to the coverage tool to
5354
use.
5455
"""
5556
_ = name # @unused
@@ -204,8 +205,8 @@ def define_hermetic_runtime_toolchain_impl(
204205
},
205206
coverage_tool = select({
206207
# Convert empty string to None
207-
":coverage_enabled": coverage_tool or None,
208-
"//conditions:default": None,
208+
":coverage_enabled": coverage_tool,
209+
"//conditions:default": _NONE,
209210
}),
210211
python_version = "PY3",
211212
implementation_name = "cpython",

python/private/py_repositories.bzl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", _http_archive = "http_archi
1818
load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
1919
load("//python:versions.bzl", "MINOR_MAPPING", "TOOL_VERSIONS")
2020
load("//python/private/pypi:deps.bzl", "pypi_deps")
21+
load(":coverage_deps.bzl", "coverage_deps")
2122
load(":internal_config_repo.bzl", "internal_config_repo")
2223
load(":pythons_hub.bzl", "hub_repo")
2324

@@ -34,6 +35,9 @@ def py_repositories():
3435
internal_config_repo,
3536
name = "rules_python_internal",
3637
)
38+
coverage_deps(
39+
name = "pypi__coverage",
40+
)
3741
maybe(
3842
hub_repo,
3943
name = "pythons_hub",

python/private/py_runtime_rule.bzl

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ load(":util.bzl", "IS_BAZEL_7_OR_HIGHER")
2525

2626
_py_builtins = py_internal
2727

28+
# We need to use the resolved alias value to the sentinel
29+
_NONE = Label("//python/private:sentinel")
30+
2831
def _py_runtime_impl(ctx):
2932
interpreter_path = ctx.attr.interpreter_path or None # Convert empty string to None
3033
interpreter = ctx.attr.interpreter
@@ -61,15 +64,15 @@ def _py_runtime_impl(ctx):
6164
else:
6265
fail("interpreter must be an executable target or must produce exactly one file.")
6366

64-
if ctx.attr.coverage_tool:
67+
if ctx.attr.coverage_tool and ctx.attr.coverage_tool.label != _NONE:
6568
coverage_di = ctx.attr.coverage_tool[DefaultInfo]
6669

6770
if _is_singleton_depset(coverage_di.files):
6871
coverage_tool = coverage_di.files.to_list()[0]
6972
elif coverage_di.files_to_run and coverage_di.files_to_run.executable:
7073
coverage_tool = coverage_di.files_to_run.executable
7174
else:
72-
fail("coverage_tool must be an executable target or must produce exactly one file.")
75+
fail("coverage_tool must be an executable target or must produce exactly one file, got: {}".format(ctx.attr.coverage_tool.label))
7376

7477
coverage_files = depset(transitive = [
7578
coverage_di.files,
@@ -234,6 +237,11 @@ The entry point for the tool must be loadable by a Python interpreter (e.g. a
234237
`.py` or `.pyc` file). It must accept the command line arguments
235238
of [`coverage.py`](https://coverage.readthedocs.io), at least including
236239
the `run` and `lcov` subcommands.
240+
241+
:::{versionchanged} VERSION_NEXT_PATCH
242+
If set to {obj}`//python:none`, then it will be treated the same as if the attribute
243+
is unset.
244+
:::
237245
""",
238246
),
239247
"files": attr.label_list(

python/private/pypi/deps.bzl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
1818
load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
19+
load("//python/private:coverage_deps.bzl", "coverage_deps")
1920

2021
_RULE_DEPS = [
2122
# START: maintained by 'bazel run //tools/private/update_deps:update_pip_deps'
@@ -145,3 +146,5 @@ def pypi_deps():
145146
type = "zip",
146147
build_file_content = _GENERIC_WHEEL,
147148
)
149+
150+
coverage_deps(name = "pypi__coverage")

python/private/pypi/extension.bzl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def _create_whl_repos(
8484
used during the `repository_rule` and must be always compatible with the host.
8585
8686
Returns a {type}`struct` with the following attributes:
87-
whl_map: {type}`dict[str, list[struct]]` the output is keyed by the
87+
whl_map: {type}`dict[str, dict[struct, str]]` the output is keyed by the
8888
normalized package name and the values are the instances of the
8989
{bzl:obj}`whl_config_setting` return values.
9090
exposed_packages: {type}`dict[str, Any]` this is just a way to

python/private/pypi/hub_repository.bzl

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ exports_files(["requirements.bzl"])
2626
"""
2727

2828
def _impl(rctx):
29-
bzl_packages = rctx.attr.packages or rctx.attr.whl_map.keys()
3029
aliases = render_multiplatform_pkg_aliases(
3130
aliases = {
3231
key: _whl_config_settings_from_json(values)
@@ -38,6 +37,12 @@ def _impl(rctx):
3837
for path, contents in aliases.items():
3938
rctx.file(path, contents)
4039

40+
if not rctx.attr.add_requirements_bzl:
41+
rctx.file("BUILD.bazel", "")
42+
return
43+
44+
bzl_packages = rctx.attr.packages or rctx.attr.whl_map.keys()
45+
4146
# NOTE: we are using the canonical name with the double '@' in order to
4247
# always uniquely identify a repository, as the labels are being passed as
4348
# a string and the resolution of the label happens at the call-site of the
@@ -63,6 +68,10 @@ def _impl(rctx):
6368

6469
hub_repository = repository_rule(
6570
attrs = {
71+
"add_requirements_bzl": attr.bool(
72+
mandatory = False,
73+
default = True,
74+
),
6675
"extra_hub_aliases": attr.string_list_dict(
6776
doc = "Extra aliases to make for specific wheels in the hub repo.",
6877
mandatory = True,

0 commit comments

Comments
 (0)