Transition uv to the correct platform (#122)

Currently, uv is selected for the exec platform. If it were used as part
of a build action, this would be correct, but as it is invoked by an
emitted shell script it is not. This causes it to fail when running
builds on one platform, but targeting another. This is the case, for
example, when running builds in a remote execution cluster with Linux
workers, but compiling for a Mac. This patch transitions uv to the
proper platform, allowing it to work in this situation.
3 files changed
tree: 28a27fb7cb207e5cb01cbc63a1a7c0d1b5d436d9
  1. .bcr/
  2. .github/
  3. examples/
  4. uv/
  5. .bazelignore
  6. .bazelrc
  7. .bazelversion
  8. .gitattributes
  9. .gitignore
  10. BUILD.bazel
  11. LICENSE
  12. MODULE.bazel
  13. readme.md
  14. WORKSPACE.bazel
readme.md

rules_uv

Bazel rules to enable use of uv to compile pip requirements and generate virtual envs.

Usage

Installing with bzlmod, add to MODULE.bazel (adjust version as appropriate):

bazel_dep(name = "rules_uv", version = "<version>")

Note: rules_uv requires a Python toolchain to be available. One can be obtained by having rules_python installed using:

bazel_dep(name = "rules_python", version = "<rules_python version>")

pip_compile

Create a requirements.in or pyproject.toml -> requirements.txt compilation target and diff test:

load("@rules_uv//uv:pip.bzl", "pip_compile")

pip_compile(
    name = "generate_requirements_txt",
    requirements_in = "//:requirements.in", # default
    requirements_txt = "//:requirements.txt", # default
)

Ensure both requirements.in and requirements.txt exist (the latter must exist but may be empty).

Run the compilation step with bazel run //:generate_requirements_txt.

This will automatically register a diff test with name [name]_test.

Additionally, you can specify the following optional args:

  • python_platform: the uv pip compile compatible --python-platform value to pass to uv
  • args: override the default arguments passed to uv (--generate-hashes, --emit-index-url and --no-strip-extras)
  • data: pass additional files to be present when generating and testing requirements txt files (see also examples/multiple-inputs)
  • tags: tags to apply to the test target
  • target_compatible_with: restrict targets to running on the specified Bazel platform

create_venv

Create a virtual environment creation target:

load("@rules_uv//uv:venv.bzl", "create_venv")

create_venv(
    name = "create_venv",
    requirements_txt = "//:requirements.txt", # default
)

Create a virtual environment with default path venv by running bazel run //:create_venv. The generated script accepts a single, optional argument to define the virtual environment path.

The created venv will use the default Python 3 runtime defined in rules_python.

Multi-platform setup

uv supports generating platform-specific requirements files, and rules_uv exposes this configuration, and a multi-platform setup might look like this:

load("@rules_multirun//:defs.bzl", "multirun")
load("@rules_uv//uv:pip.bzl", "pip_compile")
load("@rules_uv//uv:venv.bzl", "create_venv")

pip_compile(
    name = "generate_requirements_linux_txt",
    python_platform = "x86_64-unknown-linux-gnu",
    requirements_txt = "requirements_linux.txt",
)

pip_compile(
    name = "generate_requirements_macos_txt",
    python_platform = "aarch64-apple-darwin",
    requirements_txt = "requirements_macos.txt",
)

multirun(
    name = "generate_requirements_lock",
    commands = [
        ":generate_requirements_linux_txt",
        ":generate_requirements_macos_txt",
    ],
    # Running in a single threaded mode allows consecutive `uv` invocations to benefit
    # from the `uv` cache from the first run.
    jobs = 1,
)

create_venv(
    name = "create_venv",
    requirements_txt = select({
        "@platforms//os:linux": ":requirements_linux.txt",
        "@platforms//os:osx": ":requirements_macos.txt",
    }),
)

This makes use of the excellent rules_multirun.

To match up with rules_python, a bzlmod config will look something like:

pip.parse(
    hub_name = "pip",
    python_version = "3.11",
    requirements_darwin = "//:requirements_macos.txt",
    requirements_linux = "//:requirements_linux.txt",
)