raydepsets
A dependency lock file management tool for Ray CI pipelines. It
maintains consistency relationships among lock files — ensuring that
when a dependency is updated, all related lock files are regenerated
together in the correct order. Built on top of
uv pip compile, it
generates reproducible, hash-verified lock files across multiple
Python versions, platforms, and CUDA variants.
Why
Ray's CI builds containers and test environments for many
combinations of Python version, platform (Linux x86_64, macOS ARM),
and GPU support (CPU, e.g. CUDA 12.8). Each combination needs a
locked, reproducible set of dependencies, and many of these lock
files have consistency relationships with each other — for example,
a test environment's lock file must be a strict superset of the base
image it runs on, and all CUDA variants for a given Python version
must agree on common package versions. A single uv pip compile
call can produce one lock file, but it has no awareness of these
cross-file constraints. When a dependency is updated, all downstream
lock files need to be regenerated in the right order to stay
consistent.
raydepsets solves this by:
- Modeling the relationships between lock files as a dependency graph, so that updating one file automatically propagates to all dependents
- Supporting four composable operations (compile, subset, expand, relax) to express how lock files derive from each other
- Defining dependency sets declaratively in YAML with template variables for matrix builds
- Automatically resolving execution order via topological sort
- Validating that committed lock files are up-to-date and mutually
consistent in CI (
--checkmode)
Directory Structure
ci/raydepsets/
├── raydepsets.py # Entry point
├── cli.py # CLI and DependencySetManager
├── workspace.py # Config parsing and data models
├── BUILD.bazel # Bazel build targets
├── configs/ # Production YAML configs
│ ├── rayimg.depsets.yaml
│ ├── rayllm.depsets.yaml
│ ├── data_test.depsets.yaml
│ ├── docs.depsets.yaml
│ └── ...
├── pre_hooks/ # Shell scripts run before compilation
│ ├── build-placeholder-wheel.sh
│ └── remove-compiled-headers.sh
└── tests/
├── test_cli.py
├── test_workspace.py
├── utils.py
└── test_data/
Usage
raydepsets is built and run via Bazel:
# Build all depsets in a config
bazelisk run //ci/raydepsets:raydepsets -- build ci/raydepsets/configs/rayimg.depsets.yaml
# Build a single named depset (and its dependencies)
bazelisk run //ci/raydepsets:raydepsets -- build ci/raydepsets/configs/rayimg.depsets.yaml --name ray_img_depset_313
# Build all configs at once
bazelisk run //ci/raydepsets:raydepsets -- build --all-configs
# Validate that lock files are up-to-date (used in CI)
bazelisk run //ci/raydepsets:raydepsets -- build ci/raydepsets/configs/rayimg.depsets.yaml --check
CLI Options
| Option | Description |
|---|---|
CONFIG_PATH |
Path to a .depsets.yaml config file (default: ci/raydepsets/configs/*.depsets.yaml) |
--workspace-dir |
Workspace root directory (default: $BUILD_WORKSPACE_DIRECTORY) |
--name |
Build only this depset and its dependencies |
--uv-cache-dir |
Cache directory for uv |
--check |
Validate lock files match what would be generated; exit non-zero on diff |
--all-configs |
Build depsets from all config files, not just the specified one |
Configuration Format
Config files use the .depsets.yaml extension and contain two top-level keys:
build_arg_sets (optional)
Defines template variable sets for matrix expansion. Each key maps to a dictionary of variable substitutions:
build_arg_sets:
py311:
PYTHON_VERSION: "3.11"
PYTHON_SHORT: "311"
py312:
PYTHON_VERSION: "3.12"
PYTHON_SHORT: "312"
Variables are referenced in depset fields using ${VARIABLE_NAME} syntax. When a depset lists multiple build_arg_sets, it is expanded into one depset per set.
depsets
A list of dependency set definitions. Each depset has these common fields:
| Field | Type | Description |
|---|---|---|
name |
string | Unique identifier (supports ${VAR} substitution) |
operation |
string | One of compile, subset, expand, relax |
output |
string | Output lock file path relative to workspace root |
build_arg_sets |
list | Which build arg sets to expand this depset with |
append_flags |
list | Additional flags passed to uv pip compile |
override_flags |
list | Flags that replace matching defaults |
pre_hooks |
list | Shell commands to run before this depset executes |
include_setuptools |
bool | Allow setuptools in output (default: false) |
Operation: compile
Runs uv pip compile to resolve and lock dependencies from requirements files.
- name: ray_img_depset_${PYTHON_SHORT}
operation: compile
requirements:
- python/deplocks/ray_img/ray_dev.in
constraints:
- /tmp/ray-deps/requirements_compiled_py${PYTHON_VERSION}.txt
output: python/deplocks/ray_img/ray_img_py${PYTHON_SHORT}.lock
append_flags:
- --python-version=${PYTHON_VERSION}
build_arg_sets:
- py310
- py311
- py312
- py313
Additional fields: requirements (input requirement files), constraints (version constraint files), packages (inline package specs passed via stdin).
Operation: subset
Extracts a subset of already-resolved dependencies from another depset's lock file. Validates that all requested requirements exist in the source.
- name: ray_base_deps_${PYTHON_SHORT}
operation: subset
source_depset: ray_base_extra_testdeps_${PYTHON_SHORT}
requirements:
- docker/base-deps/requirements.in
output: python/deplocks/base_deps/ray_base_deps_py${PYTHON_VERSION}.lock
Additional fields: source_depset (name of the depset to subset from).
Operation: expand
Combines multiple depsets into one, optionally adding new requirements. Recursively collects all transitive requirements from referenced depsets.
- name: compiled_ray_llm_test_depset_${PYTHON_VERSION}_${CUDA_CODE}
operation: expand
depsets:
- ray_base_test_depset_${PYTHON_VERSION}_${CUDA_CODE}
requirements:
- python/requirements/llm/llm-requirements.txt
- python/requirements/llm/llm-test-requirements.txt
constraints:
- python/deplocks/llm/ray_test_${PYTHON_VERSION}_${CUDA_CODE}.lock
output: python/deplocks/llm/rayllm_test_${PYTHON_VERSION}_${CUDA_CODE}.lock
Additional fields: depsets (list of depset names to combine), requirements (extra requirements to include), constraints (constraint files).
Operation: relax
Removes specified packages from another depset's lock file. Validates that all specified packages exist in the source before removing them.
- name: relaxed_depset_${PYTHON_SHORT}
operation: relax
source_depset: ray_img_depset_${PYTHON_SHORT}
packages:
- some-unwanted-package
- another-package
output: python/deplocks/ray_img/ray_img_relaxed_py${PYTHON_SHORT}.lock
Additional fields: source_depset (name of the depset to relax from), packages (list of package names to remove from the lock file).
Warning: This operation performs a simple removal of packages from the lock file and does not re-evaluate the dependency graph. Removing a package that is required by another package in the lock file may result in an inconsistent environment. Use with caution.
YAML Anchors
Configs support standard YAML anchors for DRY definitions:
.common_settings: &common_settings
append_flags:
- --python-version=3.11
- --unsafe-package ray
build_arg_sets:
- cpu
- cu128
depsets:
- name: my_depset
<<: *common_settings
operation: compile
requirements:
- requirements.txt
output: output.lock
Pre-Hooks
Pre-hooks are shell scripts that run before a depset is executed. They are useful for preparing the build environment (e.g., building placeholder wheels, stripping GPU index URLs from constraint files).
pre_hooks:
- ci/raydepsets/pre_hooks/build-placeholder-wheel.sh
- ci/raydepsets/pre_hooks/remove-compiled-headers.sh ${PYTHON_VERSION}
Pre-hooks support template variable substitution and are modeled as nodes in the dependency graph, so they execute in the correct order.
How It Works
- Config loading -- YAML configs are parsed into
Depsetdataclasses. Template variables frombuild_arg_setsare substituted, expanding one depset definition into N concrete depsets. - Graph construction -- A directed acyclic graph (NetworkX
DiGraph) is built from depset dependencies and pre-hooks. - Topological execution -- Depsets are executed in topological order so dependencies are resolved before dependents.
- Lock file generation -- Each depset calls
uv pip compilewith the appropriate flags, constraints, and requirements. - Validation (
--checkmode) -- Lock files are generated to a temp directory and compared against committed versions. Any diff causes a non-zero exit.
Default uv pip compile Flags
--no-header
--generate-hashes
--index-strategy unsafe-best-match
--no-strip-markers
--emit-index-url
--emit-find-links
--quiet
--unsafe-package setuptools (unless include_setuptools: true)