Description
Problem
Pip dependencies are implemented awkwardly, causing all sort of caveats.
For example:
-
Pip dependencies by default don't work with bzlmod. You have to vendor requirements.bzl.
-
But vendoring is awkward and requires minimum two
bazel
runs to work (update lockfile, then re-run repos and copy bzl). -
And it turns out requirements.bzl doesn't work with annotations anyway (Cannot use annotations with vendored requirements.bzl #821).
-
And you spend an hour figuring all that out because the implementation is so dang overcomplicated.
Probably more too, I just haven't gotten to them yet.
Solution
The core problem is doing stuff in repository rules. You should really do as little as possible in repository rules. Repository rules are for downloading things, and hacky non-hermetic one-offs. It's handicap, and jumping through those hoops hurts everything.
Rather, users should always vendor requirements.bzl.
WORKSPACE.bzl
load(":requirements.bzl", "PIP_PACKAGES")
load("@rules_python//python:pip.bzl", "pip_packages")
pip_packages(PIP_PACKAGES, annotations = {})
BUILD.bazel
# Executable target:
# 1. pip compile a requirements lockfile
# 2. generate the Starlark version
# 3. write the lockfile and bzl to BUILD_WORKSPACE_DIRECTORY
pip_resolve(
name = "pip",
requirements_bzl = "requirements.bzl",
requirements_in = "requirements.in",
requirements_lock = "requirements.txt",
)
Whenever requirements.in is updated, run bazel run :pip
to update files. Easy as that.
As a benefit, annotations are provided directly to pip_packages
without shenanigans. (And if there's something advanced not covered by annotations, uses can even consume or modify PIP_PACKAGES itself.)
Simple and robust.