Skip to content

feat(toolchain): allow users easily override coverage #3902

Open

Description

@aignas

This ticket I am going to repurpose for implementing some sort of method to allow users override coverage library used.

Things that we could do:

  • Get rid of the py script calling pip -> vendor the bzl file to load from. This is somewhat hard to maintain and when new toolchain version get added (or removed) the file may get stale. Goal state is - We have bzlmod API to set the versions and we have a diff_test to actually do the vendoring of the .bzl file. We should leverage uv.lock files to actually do the configuration, they can be either user or rules_python supplied.
  • Make the coverage package more similar to regular pip packages. Currently it is just an http_archive, but in reality we could have potentially something that is more similar to how it is configured. Potentials:
    • Create a coverage_collector rule that would not require us patching the coverage wheel.
    • Change the coverage collector bootstrap to not require patching.
    • Just include the py_library into the venv and let the rest work out naturally.
  • Have a some sort of hub for the coverage tool that is created by pip.parse(hub="coverage") where we use_repo("coverage") and then have in @rules_python an alias for the coverage tool. The python.toolchain then has python.default(coverage="@rules_python//python/private:coverage_alias"). The main idea is that this configuration could accept a regular target. Maybe we can depend on extracted_whl_srcs.

Out of scope:

  • Make pip.dep somehow provide package version, markers, etc to download a particular package. This is similar to feat(pypi): add pip.dep to declare abstract pypi dependencies #3850, but the idea is:
    • We (for now only rules_python) can define a default fallback venv to the unified_hub_repo alias, so that if the user does not select anything, it just works.
    • We use @pypi//coverage and it just works, if the user has a different coverage package version in their venv, it gets used instead.
    • The logic for such deps may be that this is for particular tools, like twine, pip, pip-tools, coverage. The interesting thing is that those tools could be each in its own venv if we are using them in exec configuration, i.e. as a tool runner. This suggests that we may need to rething this approach of using @pypi//<dep> and instead have something like @pypi//<dep> or @pypi_tool//<dep> where we should have something similar to the Add optional dependencies to the universal_hub #3923. For now we shall allow users define a different version.

TODO

Note

This list is a working plan and will be updated as the PRs land and the direction gets clearer.

PRs that can be reviewed independently:

Stacked PRs (merge in order, each contains the previous ones):

Follow-ups:

  • Drop coverage.patch once Bazel 7 is no longer supported: the rules_python bootstrap imports coverage in-process, so the sys.path patch of coverage/__main__.py only matters if the entry point is executed as a script (e.g. by the Bazel 7 native rules). The feat(toolchain): allow using a py_library such as @pypi//coverage as the coverage_tool #4433 test shows that the unpatched PyPI wheel works.
  • Decide whether the bundled coverage wheels should be deprecated in favour of the user supplied ones.

Done:

Related:

Activity

  1. changed the title [-]Idea - Leverage pip.dep for the coverage tooling[/-] [+]feat(toolchain): allow users easily override coverage[/+] on Nov 7, 2026
  2. self-assigned this
    on Nov 8, 2026
  3. aignas commented on Nov 9, 2026

    @aignas
    CollaboratorAuthor

    An update on the approach that is being taken. It is split into a stack of PRs, in the order they should be merged:

    1. feat(toolchain): allow using a py_library such as @pypi//coverage as the coverage_tool #4433 – py_runtime.coverage_tool accepts a py_library, such as @pypi//coverage.
      • Similarly to how rules_go builds nogo, the coverage_tool attribute transitions to a configuration where a private bootstrap flag is set and collect_code_coverage is disabled, and the py_runtime macro drops the coverage_tool dependency there. This breaks the dependency cycle where the py_library resolves the toolchain that depends on it.
      • The bootstrap never executes the coverage entry point, it only imports coverage, so for targets providing PyInfo the import paths are used (the new PyRuntimeInfo.coverage_imports). No patching of the wheel or searching for coverage/__main__.py is needed.
    2. feat(pypi): support target_platforms = ["*"] in pip.parse #4455 – pip.parse(target_platforms = ["*"]) to use all of the configured pip.default platforms.
    3. feat(pypi): define default pip platforms for all toolchain platforms #4456 – default pip.default platforms for all of the platforms of the hermetic toolchains, with a test that each toolchain platform variant has a matching pip platform.
    4. feat(toolchain): use a uv.lock based coverage hub for the bzlmod toolchains #4415 – bzlmod: the coverage dependency lives in python/private/coverage/pyproject.toml with a uv.lock, which locks the latest coverage supporting each Python version from 3.9 onwards. rules_python defines a rules_python_coverage pip hub from it (one pip.parse per Python version, target_platforms = ["*"]), and python.toolchain(configure_coverage_tool = True) uses @rules_python_coverage//coverage. The docs recommend passing a py_library as the coverage tool and the other kinds of targets will be deprecated in the next version.
    5. feat(toolchain): use the uv.lock based coverage hub for the WORKSPACE toolchains #4457 – WORKSPACE: the same wheels are used without any changes to users' WORKSPACE files. A bzlmod-only repository rule, which uses the same wheel selection as pip.parse, generates a checked-in workspace_deps.bzl, and a diff_test keeps it in sync with the uv.lock. python_register_toolchains(register_coverage_tool = True) creates the wheel repositories and a @rules_python_coverage hub from it, with a coverage tool per Python version (e.g. @rules_python_coverage//coverage:3.13), because WORKSPACE builds do not set the Python version flag by default. This replaces the hand-maintained coverage_deps.bzl and its update script.

    We have abandoned the more gradual transition that #4415 originally implemented, which kept the bundled http_archive wheels and only improved how the coverage wheel is selected for each toolchain platform (and was the first step towards selecting it from a uv.lock file). Using a regular pip hub is much better: the coverage tool is a normal py_library, the wheel selection, platform handling and lock file updates are shared with pip.parse instead of being maintained separately, and users can swap in their own hub.

    Users who want a different coverage version can point the toolchain at their own hub, e.g. python.single_version_platform_override(coverage_tool = "@pypi//coverage", ...) or py_runtime(coverage_tool = "@pypi//coverage"). A dedicated python.default(coverage = ...) style API is not part of this stack, and the coverage tool is still not configured for the Windows toolchains.

    Agent-generated comment, posted automatically on behalf of @aignas.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions