-
-
Notifications
You must be signed in to change notification settings - Fork 638
docs: add example for a complex multi-platform pypi configuration #3292
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
rickeylev
merged 6 commits into
bazel-contrib:main
from
rickeylev:docs.multi.pip.example
Sep 26, 2025
Merged
Changes from 1 commit
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
0b99b03
docs: add example for a complex multi-platform pypi configuration
rickeylev 0e97798
Apply suggestions from code review
rickeylev 8afab45
fix some typos
rickeylev d73869f
Merge branch 'docs.multi.pip.example' of https://github.com/rickeylev…
rickeylev b828729
add ref to howto guide in pypi docs
rickeylev ba2cebf
Merge branch 'main' of https://github.com/bazel-contrib/rules_python …
rickeylev File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,196 @@ | ||
| :::{default-domain} bzl | ||
| ::: | ||
|
|
||
| # How-to: Multi-Platform PyPI Dependencies | ||
|
|
||
| When developing applications that need to run on a wide variety of platforms, | ||
| managing PyPI dependencies can become complex. You might need different sets of | ||
| dependencies for different combinations of Python version, threading model, | ||
| operating system, CPU architecture, libc, and even hardware accelerators like | ||
| GPUs. | ||
|
|
||
| This guide demonstrates how to manage this complexity using `rules_python` with | ||
| bzlmod. If you prefer to learn by example, complete example code is provided at | ||
| the end. | ||
|
|
||
| In this how to guide, we configure for using 4 requirements files, each | ||
| for a different variation using Python 3.14 on Linux: | ||
|
|
||
| * Regular (non-freethreaded) Python | ||
| * Freethreaded Python | ||
| * Regular Python for CUDA 12.9 | ||
| * Freethreaded Python for ARM and Musl | ||
|
|
||
| ## Mapping requirements files to Bazel configuration settings | ||
|
|
||
| Unfortunately, a requirements file doesn't tell what it's compatible with, | ||
| so we have to manually specify the Bazel configuration settings for it. To do | ||
| that using rules_python, there are two steps: defining a platform, then | ||
| associating a requirements file with the platform. | ||
|
|
||
| ### Defining a platform | ||
|
|
||
| First, we define a "platform" using {obj}`pip.defaults`. This associates an | ||
| arbitrary name with a list of Bazel {obj}`config_setting` targets. Any name can | ||
| be used for a platform (it's name has no inherent semantic meaning), however, | ||
| the name should encode all the relevant dimensions that distinguish a | ||
| requirements file. For example, if a requirements file is specifically for the | ||
| combination of CUDA 12.9 and NumPY 2.0, then platform name should represent | ||
| that. | ||
|
|
||
| The convention is to follow the format of `{os}_{cpu}{threading}`, where: | ||
|
|
||
| Where: | ||
rickeylev marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| * `{os}` is the operating system (`linux`, `osx`, `windows`). | ||
| * `{cpu}` is the architecture (`x86_64`, `aarch64`). | ||
| * `{threading}` is `_freethreaded` for a freethreaded Python runtime, or an | ||
| empty string for the regular runtime. | ||
|
|
||
| Additional dimensions should be appended and separated with underscore (e.g. | ||
| `linux_x86_64_musl_cuda12.9_numpy2`). | ||
|
|
||
| The platform name should not include the Python version. That is handled by | ||
| `pip.parse.python_version` separately. | ||
|
|
||
| :::{note} | ||
| The term _platform_ here has nothing to do with Bazel's `platform()` rule. | ||
| ::: | ||
|
|
||
| #### Defining custom settings | ||
|
|
||
| Because {obj}`pip.parse.config_settings` is a list of arbitrary `config_setting` | ||
| targets, you can define your own flags or implement custom config matching | ||
| logic. This allows you to model settings that aren't inherently part of | ||
| rules_python. | ||
|
|
||
| This is typically done using [bazel_skylib flags], but any [Starlark | ||
rickeylev marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| defined build setting](https://bazel.build/extending/config) can be used. Just | ||
| remember to use `config_setting()` to match a particular value of the flag. | ||
|
|
||
| Here in our example, we define a custom flag for CUDA version. | ||
|
|
||
| #### Predefined and common build settings | ||
|
|
||
| rules_python has some predefined build settings you can use. Commonly used ones | ||
| are: | ||
|
|
||
| * {obj}`@rules_python//python/config_settings:py_linux_libc` | ||
| * {obj}`@rules_python//python/config_settings:py_freethreaded` | ||
|
|
||
| Additionally, [Bazel @platforms](https://github.com/bazelbuild/platforms) | ||
| contains commonly used settings for OS and CPU: | ||
|
|
||
| * `@platforms//os:windows` | ||
| * `@platforms//os:linux` | ||
| * `@platforms//os:osx` | ||
| * `@platforms//cpu:x86_64` | ||
| * `@platforms//cpu:aarch64` | ||
|
|
||
| Note that these are the raw flag names. In order to use them with `pip.default`, | ||
| you must use {obj}`config_setting()` to match a particular value for them. | ||
|
|
||
| ### Associating requirements to plaforms | ||
rickeylev marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| Next, we associate a requirements file with a platform using | ||
| {obj}`pip.parse.requirements_by_platform`. This is a dictionary arg where | ||
| keys are requirements files, and the value is a platform name. The platform | ||
| value can use trailing or leading `*` to match multiple platforms. It can also | ||
| specify multiple platform names using commas to separate them. | ||
|
|
||
| Note that Python version is _not_ part | ||
rickeylev marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| Under the hood, `pip.parse` merges all the requirements (for a `hub_name`) and | ||
| constructs `select()` expressions to route to the appropriate dependencies. | ||
|
|
||
| ### Using it in practice | ||
|
|
||
| Finally, to make use of what we've configured, perform a build and set | ||
| command line flags to the appropriate values. | ||
|
|
||
| ```shell | ||
| # Build for CUDA | ||
| bazel build --//:cuda_version=12.9 //:binary | ||
|
|
||
| # Build for ARM with musl | ||
| bazel build --@rules_python//python/config_settings:py_linux_libc=musl \ | ||
| --cpu=aarch64 //:binary | ||
|
|
||
| # Build for freethreaded | ||
| bazel build --@rule_python//python/config_settings:py_freethreaded=yes //:binary | ||
rickeylev marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ``` | ||
|
|
||
| Note that certain combinations of flags may result in an error or undefined | ||
| behavior. For example, trying to set both freethreaded and CUDA at the same | ||
| time would result in an error because no requirements file was registered | ||
| to match that combination. | ||
|
|
||
| ## Multiple Python version | ||
rickeylev marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| Having multiple Python versions is fully supported. Simply add a `pip.parse()` | ||
| call and set `python_version` appropriately. | ||
|
|
||
| ## Multiple hubs | ||
|
|
||
| Having multiple `pip.parse` calls with different `hub_name` values is fully | ||
| supported. Each hub only contains the requirements registered to it. | ||
|
|
||
| ## Complete Example | ||
|
|
||
| Here is a complete example that puts all the pieces together. | ||
|
|
||
| ```starlark | ||
| # File: BUILD.bazel | ||
| load("@bazel_skylib//rules:common_settings.bzl", "string_flag") | ||
|
|
||
| # A custom flag for controlling the CUDA version | ||
| string_flag( | ||
| name = "cuda_version", | ||
| build_setting_default = "none", | ||
| ) | ||
|
|
||
| config_setting( | ||
| name = "is_cuda_12_9", | ||
| flag_values = {":cuda_version": "12.9"}, | ||
| ) | ||
|
|
||
| # A config_setting that uses the built-in libc flag from rules_python | ||
| config_setting( | ||
| name = "is_musl", | ||
| flag_values = {"@rules_python//python/config_settings:py_linux_libc": "muslc"}, | ||
| ) | ||
|
|
||
| # File: MODULE.bazel | ||
| pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip") | ||
|
|
||
| # A custom platform for CUDA on glibc linux | ||
| pip.default( | ||
| platform = "linux_x86_64_cuda12.9", | ||
| os = "linux", | ||
| cpu = "x86_64", | ||
| config_settings = ["@//:is_cuda_12_9"], | ||
| ) | ||
|
|
||
| # A custom platform for musl on linux | ||
| pip.default( | ||
| platform = "linux_aarch64_musl", | ||
| os = "linux", | ||
| cpu = "aarch64", | ||
| config_settings = ["@//:is_musl"], | ||
| ) | ||
|
|
||
| pip.parse( | ||
| hub_name = "my_deps", | ||
| python_version = "3.14", | ||
| requirements_by_platform = { | ||
| # Map to default platform names | ||
| "//:py3.14-regular-linux-x86-glibc-cpu.txt": "linux_x86_64", | ||
| "//:py3.14-freethreaded-linux-x86-glibc-cpu.txt": "linux_x86_64_freethreaded", | ||
|
|
||
| # Map to our custom platform names | ||
| "//:py3.14-regular-linux-x86-glibc-cuda12.9.txt": "linux_x86_64_cuda12.9", | ||
| "//:py3.14-freethreaded-linux-arm-musl-cpu.txt": "linux_aarch64_musl", | ||
| }, | ||
| ) | ||
|
|
||
| use_repo(pip, "my_deps") | ||
| ``` | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.