|
| 1 | +# Resolution of "virtual" dependencies |
| 2 | + |
| 3 | +rules_py allows external Python dependencies to be specified by name rather than as a label to an installed package, using a concept called "virtual" dependencies. |
| 4 | + |
| 5 | +Virtual dependencies allow the terminal rule (for example, a `py_binary` or `py_test`) to control the version of the package which is used to satisfy the dependency, by providing a mapping from the package name to the label of an installed package that provides it. |
| 6 | + |
| 7 | +This feature allows: |
| 8 | +- for individual projects within a monorepo to upgrade their dependencies independently of other projects within the same repository |
| 9 | +- overriding a single version of a dependency for a py_binary or py_test |
| 10 | +- to test against a range of different versions of dependencies for a single library |
| 11 | + |
| 12 | +Links to design docs are available on the original feature request: |
| 13 | +https://github.com/aspect-build/rules_py/issues/213 |
| 14 | + |
| 15 | +## Declaring a dependency as virtual |
| 16 | + |
| 17 | +Simply move an element from the `deps` attribute to `virtual_deps`. |
| 18 | + |
| 19 | +For example, instead of getting a specific version of Django from |
| 20 | +`deps = ["@pypi_django//:pkg"]` on a `py_library` target, |
| 21 | +provide the package name with `virtual_deps = ["django"]`. |
| 22 | + |
| 23 | +> Note that any `py_binary` or `py_test` transitively depending on this `py_library` must be loaded from `aspect_rules_py` rather than `rules_python`, as the latter does not have a feature of resolving the virtual dep. |
| 24 | +
|
| 25 | +## Resolving to a package installed by rules_python |
| 26 | + |
| 27 | +Typically, users write one or more `pip_parse` statements in `WORKSPACE` or `pip.parse` in `MODULE.bazel` to read requirements files, and install the referenced packages into an external repository. For example, from the [rules_python docs](https://rules-python.readthedocs.io/en/latest/pypi-dependencies.html#using-dependencies-from-pypi): |
| 28 | + |
| 29 | +``` |
| 30 | +pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip") |
| 31 | +pip.parse( |
| 32 | + hub_name = "my_deps", |
| 33 | + python_version = "3.11", |
| 34 | + requirements_lock = "//:requirements_lock_3_11.txt", |
| 35 | +) |
| 36 | +use_repo(pip, "my_deps") |
| 37 | +``` |
| 38 | + |
| 39 | +rules_python writes a `requirements.bzl` file which provides some symbols to work with the installed packages: |
| 40 | + |
| 41 | +``` |
| 42 | +load("@my_deps//:requirements.bzl", "all_whl_requirements_by_package", "requirement") |
| 43 | +``` |
| 44 | + |
| 45 | +These can be used to resolve a virtual dependency. Continuing the Django example above, a binary rule can specify which external repository to resolve to: |
| 46 | + |
| 47 | +``` |
| 48 | +load("@aspect_rules_py//py:defs.bzl", "resolutions") |
| 49 | +
|
| 50 | +py_binary( |
| 51 | + name = "manage", |
| 52 | + srcs = ["manage.py"], |
| 53 | + # Resolve django to the "standard" one from our requirements.txt |
| 54 | + resolutions = resolutions.from_requirements(all_whl_requirements_by_package, requirement), |
| 55 | +) |
| 56 | +``` |
| 57 | + |
| 58 | +## Resolving directly to a binary wheel |
| 59 | + |
| 60 | +It's possible to fetch a wheel file directly without using `pip` or any repository rules from `rules_python`, using the Bazel downloader. |
| 61 | + |
| 62 | +`MODULE.bazel`: |
| 63 | + |
| 64 | +``` |
| 65 | +http_file = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file") |
| 66 | +
|
| 67 | +http_file( |
| 68 | + name = "django_4_2_4", |
| 69 | + urls = ["https://files.pythonhosted.org/packages/7f/9e/fc6bab255ae10bc57fa2f65646eace3d5405fbb7f5678b90140052d1db0f/Django-4.2.4-py3-none-any.whl"], |
| 70 | + sha256 = "860ae6a138a238fc4f22c99b52f3ead982bb4b1aad8c0122bcd8c8a3a02e409d", |
| 71 | + downloaded_file_path = "Django-4.2.4-py3-none-any.whl", |
| 72 | +) |
| 73 | +``` |
| 74 | + |
| 75 | +Then in a `BUILD` file, extract it to a directory: |
| 76 | + |
| 77 | +``` |
| 78 | +load("@aspect_rules_py//py:defs.bzl", "py_binary", "py_unpacked_wheel") |
| 79 | +
|
| 80 | +# Extract the downloaded wheel to a directory |
| 81 | +py_unpacked_wheel( |
| 82 | + name = "django_4_2_4", |
| 83 | + src = "@django_4_2_4//file", |
| 84 | +) |
| 85 | +
|
| 86 | +py_binary( |
| 87 | + name = "manage.override_django", |
| 88 | + srcs = ["proj/manage.py"], |
| 89 | + resolutions = { |
| 90 | + # replace the resolution of django with that specific wheel |
| 91 | + "django": ":django_4_2_4", |
| 92 | + }, |
| 93 | + deps = [":proj"], |
| 94 | +) |
| 95 | +``` |
| 96 | + |
0 commit comments