Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: refactor(toolchain): create a hub repo for the coverage tool #2601

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ Unreleased changes template.
env var.
* (pypi) Downgraded versions of packages: `pip` from `24.3.2` to `24.0.0` and
`packaging` from `24.2` to `24.0`.
* (toolchain) `coverage_tool` now accepts a special value of the `//python:none`
that acts as a label that means "no coverage tool".
* (toolchain) The coverage deps are now registered as part of `py_repositories`.

{#v0-0-0-fixed}
### Fixed
Expand Down
27 changes: 27 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,33 @@ use_repo(
"pypi__build",
"pypi__click",
"pypi__colorama",
"pypi__coverage",
"pypi__coverage_cp310_aarch64_apple_darwin",
"pypi__coverage_cp310_aarch64_unknown_linux_gnu",
"pypi__coverage_cp310_x86_64_apple_darwin",
"pypi__coverage_cp310_x86_64_unknown_linux_gnu",
"pypi__coverage_cp311_aarch64_apple_darwin",
"pypi__coverage_cp311_aarch64_unknown_linux_gnu",
"pypi__coverage_cp311_x86_64_apple_darwin",
"pypi__coverage_cp311_x86_64_unknown_linux_gnu",
"pypi__coverage_cp312_aarch64_apple_darwin",
"pypi__coverage_cp312_aarch64_unknown_linux_gnu",
"pypi__coverage_cp312_x86_64_apple_darwin",
"pypi__coverage_cp312_x86_64_unknown_linux_gnu",
"pypi__coverage_cp313_aarch64_apple_darwin",
"pypi__coverage_cp313_aarch64_apple_darwin_freethreaded",
"pypi__coverage_cp313_aarch64_unknown_linux_gnu",
"pypi__coverage_cp313_aarch64_unknown_linux_gnu_freethreaded",
"pypi__coverage_cp313_x86_64_unknown_linux_gnu",
"pypi__coverage_cp313_x86_64_unknown_linux_gnu_freethreaded",
"pypi__coverage_cp38_aarch64_apple_darwin",
"pypi__coverage_cp38_aarch64_unknown_linux_gnu",
"pypi__coverage_cp38_x86_64_apple_darwin",
"pypi__coverage_cp38_x86_64_unknown_linux_gnu",
"pypi__coverage_cp39_aarch64_apple_darwin",
"pypi__coverage_cp39_aarch64_unknown_linux_gnu",
"pypi__coverage_cp39_x86_64_apple_darwin",
"pypi__coverage_cp39_x86_64_unknown_linux_gnu",
"pypi__importlib_metadata",
"pypi__installer",
"pypi__more_itertools",
Expand Down
9 changes: 8 additions & 1 deletion python/private/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ filegroup(
visibility = ["//python:__pkg__"],
)

alias(
name = "coverage",
actual = "@pypi__coverage//coverage",
visibility = ["//visibility:public"],
)

bzl_library(
name = "attributes_bzl",
srcs = ["attributes.bzl"],
Expand Down Expand Up @@ -134,7 +140,8 @@ bzl_library(
srcs = ["coverage_deps.bzl"],
deps = [
":bazel_tools_bzl",
":version_label_bzl",
"//python/private/pypi:hub_repository_bzl",
"//python/private/pypi:whl_config_setting_bzl",
],
)

Expand Down
87 changes: 51 additions & 36 deletions python/private/coverage_deps.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
load("//python/private:version_label.bzl", "version_label")
load("//python/private/pypi:hub_repository.bzl", "hub_repository", "whl_config_settings_to_json")
load("//python/private/pypi:whl_config_setting.bzl", "whl_config_setting")

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

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

def coverage_dep(name, python_version, platform, visibility):
"""Register a single coverage dependency based on the python version and platform.
def coverage_deps(name):
"""Register all coverage dependencies.

Args:
name: The name of the registered repository.
python_version: The full python version.
platform: The platform, which can be found in //python:versions.bzl PLATFORMS dict.
visibility: The visibility of the coverage tool.
This exposes them through a hub repository.

Returns:
The label of the coverage tool if the platform is supported, otherwise - None.
Args:
name: The name of the hub repository.
"""
if "windows" in platform:
# NOTE @aignas 2023-01-19: currently we do not support windows as the
# upstream coverage wrapper is written in shell. Do not log any warning
# for now as it is not actionable.
return None

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

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

maybe(
http_archive,
name = name,
build_file_content = """
whl_map[spoke_name] = [config_setting]

# NOTE @aignas 2025-02-04: under `bzlmod` the `maybe` function is noop.
# This is only kept for `WORKSPACE` compatibility.
maybe(
http_archive,
name = spoke_name,
build_file_content = """
filegroup(
name = "coverage",
name = "pkg",
srcs = ["coverage/__main__.py"],
data = glob(["coverage/*.py", "coverage/**/*.py", "coverage/*.so"]),
visibility = {visibility},
visibility = ["{visibility}"],
)
""".format(
visibility = visibility,
),
patch_args = ["-p1"],
patches = [_coverage_patch],
sha256 = sha256,
type = "zip",
urls = [url],
)
""".format(
visibility = "@{}//:__subpackages__".format(name),
),
patch_args = ["-p1"],
patches = [_coverage_patch],
sha256 = sha256,
type = "zip",
urls = [url],
)

return "@{name}//:coverage".format(name = name)
maybe(
hub_repository,
name = name,
repo_name = name,
whl_map = {
"coverage": whl_config_settings_to_json(whl_map),
},
add_requirements_bzl = False,
extra_hub_aliases = {},
packages = [],
groups = {},
)
7 changes: 4 additions & 3 deletions python/private/hermetic_runtime_repo_setup.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ load(":py_exec_tools_toolchain.bzl", "py_exec_tools_toolchain")
load(":semver.bzl", "semver")

_IS_FREETHREADED = Label("//python/config_settings:is_py_freethreaded")
_NONE = Label("//python:none")

def define_hermetic_runtime_toolchain_impl(
*,
Expand All @@ -49,7 +50,7 @@ def define_hermetic_runtime_toolchain_impl(
format.
python_bin: {type}`str` The path to the Python binary within the
repository.
coverage_tool: {type}`str` optional target to the coverage tool to
coverage_tool: {type}`Label` optional target to the coverage tool to
use.
"""
_ = name # @unused
Expand Down Expand Up @@ -204,8 +205,8 @@ def define_hermetic_runtime_toolchain_impl(
},
coverage_tool = select({
# Convert empty string to None
":coverage_enabled": coverage_tool or None,
"//conditions:default": None,
":coverage_enabled": coverage_tool,
"//conditions:default": _NONE,
}),
python_version = "PY3",
implementation_name = "cpython",
Expand Down
4 changes: 4 additions & 0 deletions python/private/py_repositories.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,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:versions.bzl", "MINOR_MAPPING", "TOOL_VERSIONS")
load("//python/private/pypi:deps.bzl", "pypi_deps")
load(":coverage_deps.bzl", "coverage_deps")
load(":internal_config_repo.bzl", "internal_config_repo")
load(":pythons_hub.bzl", "hub_repo")

Expand All @@ -34,6 +35,9 @@ def py_repositories():
internal_config_repo,
name = "rules_python_internal",
)
coverage_deps(
name = "pypi__coverage",
)
maybe(
hub_repo,
name = "pythons_hub",
Expand Down
12 changes: 10 additions & 2 deletions python/private/py_runtime_rule.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ load(":util.bzl", "IS_BAZEL_7_OR_HIGHER")

_py_builtins = py_internal

# We need to use the resolved alias value to the sentinel
_NONE = Label("//python/private:sentinel")

def _py_runtime_impl(ctx):
interpreter_path = ctx.attr.interpreter_path or None # Convert empty string to None
interpreter = ctx.attr.interpreter
Expand Down Expand Up @@ -61,15 +64,15 @@ def _py_runtime_impl(ctx):
else:
fail("interpreter must be an executable target or must produce exactly one file.")

if ctx.attr.coverage_tool:
if ctx.attr.coverage_tool and ctx.attr.coverage_tool.label != _NONE:
coverage_di = ctx.attr.coverage_tool[DefaultInfo]

if _is_singleton_depset(coverage_di.files):
coverage_tool = coverage_di.files.to_list()[0]
elif coverage_di.files_to_run and coverage_di.files_to_run.executable:
coverage_tool = coverage_di.files_to_run.executable
else:
fail("coverage_tool must be an executable target or must produce exactly one file.")
fail("coverage_tool must be an executable target or must produce exactly one file, got: {}".format(ctx.attr.coverage_tool.label))

coverage_files = depset(transitive = [
coverage_di.files,
Expand Down Expand Up @@ -234,6 +237,11 @@ The entry point for the tool must be loadable by a Python interpreter (e.g. a
`.py` or `.pyc` file). It must accept the command line arguments
of [`coverage.py`](https://coverage.readthedocs.io), at least including
the `run` and `lcov` subcommands.

:::{versionchanged} VERSION_NEXT_PATCH
If set to {obj}`//python:none`, then it will be treated the same as if the attribute
is unset.
:::
""",
),
"files": attr.label_list(
Expand Down
3 changes: 3 additions & 0 deletions python/private/pypi/deps.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

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

_RULE_DEPS = [
# START: maintained by 'bazel run //tools/private/update_deps:update_pip_deps'
Expand Down Expand Up @@ -145,3 +146,5 @@ def pypi_deps():
type = "zip",
build_file_content = _GENERIC_WHEEL,
)

coverage_deps(name = "pypi__coverage")
2 changes: 1 addition & 1 deletion python/private/pypi/extension.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def _create_whl_repos(
used during the `repository_rule` and must be always compatible with the host.

Returns a {type}`struct` with the following attributes:
whl_map: {type}`dict[str, list[struct]]` the output is keyed by the
whl_map: {type}`dict[str, dict[struct, str]]` the output is keyed by the
normalized package name and the values are the instances of the
{bzl:obj}`whl_config_setting` return values.
exposed_packages: {type}`dict[str, Any]` this is just a way to
Expand Down
11 changes: 10 additions & 1 deletion python/private/pypi/hub_repository.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ exports_files(["requirements.bzl"])
"""

def _impl(rctx):
bzl_packages = rctx.attr.packages or rctx.attr.whl_map.keys()
aliases = render_multiplatform_pkg_aliases(
aliases = {
key: _whl_config_settings_from_json(values)
Expand All @@ -38,6 +37,12 @@ def _impl(rctx):
for path, contents in aliases.items():
rctx.file(path, contents)

if not rctx.attr.add_requirements_bzl:
rctx.file("BUILD.bazel", "")
return

bzl_packages = rctx.attr.packages or rctx.attr.whl_map.keys()

# NOTE: we are using the canonical name with the double '@' in order to
# always uniquely identify a repository, as the labels are being passed as
# a string and the resolution of the label happens at the call-site of the
Expand All @@ -63,6 +68,10 @@ def _impl(rctx):

hub_repository = repository_rule(
attrs = {
"add_requirements_bzl": attr.bool(
mandatory = False,
default = True,
),
"extra_hub_aliases": attr.string_list_dict(
doc = "Extra aliases to make for specific wheels in the hub repo.",
mandatory = True,
Expand Down
20 changes: 13 additions & 7 deletions python/private/pypi/pkg_aliases.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ def pkg_aliases(
actual,
group_name = None,
extra_aliases = None,
target_names = None,
**kwargs):
"""Create aliases for an actual package.

Expand All @@ -135,6 +136,9 @@ def pkg_aliases(
the aliases to point to mapping to repositories. The keys are passed
to bazel skylib's `selects.with_or`, so they can be tuples as well.
group_name: {type}`str` The group name that the pkg belongs to.
target_names: {type}`dict[str, str]` the dictionary of aliases that will be
made by default. Defaults to `pkg`, `whl`, `data` and `dist_info` labels.
This will be altered slightly if the {attr}`group_name` is set.
extra_aliases: {type}`list[str]` The extra aliases to be created.
**kwargs: extra kwargs to pass to {bzl:obj}`get_filename_config_settings`.
"""
Expand All @@ -146,12 +150,14 @@ def pkg_aliases(
actual = ":" + PY_LIBRARY_PUBLIC_LABEL,
)

target_names = {
PY_LIBRARY_PUBLIC_LABEL: PY_LIBRARY_IMPL_LABEL if group_name else PY_LIBRARY_PUBLIC_LABEL,
WHEEL_FILE_PUBLIC_LABEL: WHEEL_FILE_IMPL_LABEL if group_name else WHEEL_FILE_PUBLIC_LABEL,
DATA_LABEL: DATA_LABEL,
DIST_INFO_LABEL: DIST_INFO_LABEL,
} | {
if target_names == None:
target_names = {
PY_LIBRARY_PUBLIC_LABEL: PY_LIBRARY_IMPL_LABEL if group_name else PY_LIBRARY_PUBLIC_LABEL,
WHEEL_FILE_PUBLIC_LABEL: WHEEL_FILE_IMPL_LABEL if group_name else WHEEL_FILE_PUBLIC_LABEL,
DATA_LABEL: DATA_LABEL,
DIST_INFO_LABEL: DIST_INFO_LABEL,
}
target_names = target_names | {
x: x
for x in extra_aliases or []
}
Expand Down Expand Up @@ -190,7 +196,7 @@ def pkg_aliases(
v: "@{repo}//:{target_name}".format(
repo = repo,
target_name = name,
) if repo != _INCOMPATIBLE else repo
) if repo not in [_INCOMPATIBLE, str(_LABEL_NONE)] else repo
for v, repo in actual.items()
},
)
Expand Down
Loading