8000 bzlmod Multiple Python Versions · Issue #1229 · bazel-contrib/rules_python · GitHub
[go: up one dir, main page]

Skip to content
bzlmod Multiple Python Versions #1229
Closed
@chrislovecnm

Description

@chrislovecnm

Currently, rules_python does not have bzlmod support to use multiple Python versions. The following example demonstrates the current "WORKSPACE" support: https://github.com/bazelbuild/rules_python/tree/main/examples/multi_python_versions

Design

Dependencies

In order to have support we need extensions that create the following data relationships:

First, we have 2 or more versions of Python; say Python 3.9, 3.10, and 3.11.
Second, each of those Python versions has a host-specific interpreter. You can see the interpreter extension used in the following code:

https://github.com/bazelbuild/rules_python/blob/16126d0ebfee074a3a8fe216b20cc19e1b3603c1/examples/bzlmod_build_file_generation/MODULE.bazel#L77

This extension creates a symlink to the host-specific interpreter so that pip can utilize the correct interpreter.

So we have

  • multiple Python versions
  • multiple Python host (OS and platform) specific Interpreter binaries
  • symlinks to each of those binaries

The above is a one to one to one relationship.

Lastly, we have requirements files. Currently ONLY one requirement lock file per Python version is supported. This does not match the functionality of using a single version of Python since we can define an OS-specific requirements lock file. See:

https://github.com/bazelbuild/rules_python/blob/16126d0ebfee074a3a8fe216b20cc19e1b3603c1/examples/bzlmod_build_file_generation/MODULE.bazel#L107

How "WORKSPACE" support works

Starting with the WORKSPACE file:

First, we call

https://github.com/bazelbuild/rules_python/blob/16126d0ebfee074a3a8fe216b20cc19e1b3603c1/examples/multi_python_versions/WORKSPACE#L8-L10

Which adds the bazel_skylib dependency.

Next, we call:

https://github.com/bazelbuild/rules_python/blob/16126d0ebfee074a3a8fe216b20cc19e1b3603c1/examples/multi_python_versions/WORKSPACE#L12-L14

Which adds various dependencies in the following data structure

https://github.com/bazelbuild/rules_python/blob/16126d0ebfee074a3a8fe216b20cc19e1b3603c1/python/pip_install/repositories.bzl#L22

So those two components are callable via an extension. Pretty trivial.

Next we call

https://github.com/bazelbuild/rules_python/blob/16126d0ebfee074a3a8fe216b20cc19e1b3603c1/examples/multi_python_versions/WORKSPACE#L18-L28

python_ register_mutiple_toolchains() does:

For each Python version, it calls the macro
https://github.com/bazelbuild/rules_python/blob/16126d0ebfee074a3a8fe216b20cc19e1b3603c1/python/repositories.bzl#L595

And it names the call with a specific name.

https://github.com/bazelbuild/rules_python/blob/16126d0ebfee074a3a8fe216b20cc19e1b3603c1/python/repositories.bzl#L451

Does three primary things. Again this is done for each version of Python that is passed into
python_ register_mutiple_toolchains.

First, it calls

https://github.com/bazelbuild/rules_python/blob/16126d0ebfee074a3a8fe216b20cc19e1b3603c1/python/repositories.bzl#L366-L376

Which creates components like py_runtime and py_pairs for each platform (Windows, OSX, Linux)

https://github.com/bazelbuild/rules_python/blob/16126d0ebfee074a3a8fe216b20cc19e1b3603c1/python/repositories.bzl#L325

Next, it uses

https://github.com/bazelbuild/rules_python/blob/16126d0ebfee074a3a8fe216b20cc19e1b3603c1/python/repositories.bzl#L366

Lastly, we have:

https://github.com/bazelbuild/rules_python/blob/16126d0ebfee074a3a8fe216b20cc19e1b3603c1/python/private/toolchains_repo.bzl#L163-L167

To create the py_binary and py_test targets for each Python version.

Next, the rule calls the same macro for the "default" Python version:

https://github.com/bazelbuild/rules_python/blob/16126d0ebfee074a3a8fe216b20cc19e1b3603c1/python/repositories.bzl#L601

Lastly, it calls:

https://github.com/bazelbuild/rules_python/blob/16126d0ebfee074a3a8fe216b20cc19e1b3603c1/python/repositories.bzl#LL608C5-L608C28

The above call is critical to how the pip repos are created.

https://github.com/bazelbuild/rules_python/blob/16126d0ebfee074a3a8fe216b20cc19e1b3603c1/python/private/toolchains_repo.bzl#L178

Is a repo rule that creates pip.bzl that contains:

https://github.com/bazelbuild/rules_python/blob/16126d0ebfee074a3a8fe216b20cc19e1b3603c1/python/private/toolchains_repo.bzl#L211

A load in the WORKSPACE file then uses this, which we cannot do in bzlmod.

Before we get into that, let's recap.

For each version of Python, multiple repositories are created that contain:

  • each Python version has multiple toolchains defined for each platform
  • py_runtime and py_test for each version

With bzlmod, we cannot have a load statement in the MODULES.bazel file. We must create a repo to map information passed to the next pip call. The important components are:

The host interpreter is set. We will need a repo containing a mapping of each host interpreter for each Python version.

The rule multi_pip_parse is dynamically created. This calls a private repository rule. Here is an example of the pip.bzl file:

# Generated by python/private/toolchains_repo.bzl

load("@rules_python//python:pip.bzl", "pip_parse", _multi_pip_parse = "multi_pip_parse")

def multi_pip_parse(name, requirements_lock, **kwargs):
    return _multi_pip_parse(
        name = name,
        python_versions = ["3.8", "3.9", "3.10", "3.11"],
        requirements_lock = requirements_lock,
        **kwargs
    )

Because we cannot do:

https://github.com/bazelbuild/rules_python/blob/16126d0ebfee074a3a8fe216b20cc19e1b3603c1/examples/multi_python_versions/WORKSPACE#L30

I do not believe that we can call the repository rule directly. The implementation between pip and bzlmod pip differs.
We will also need to map the interpreters, which is easily doable.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      0