# Using Environment Constraints in `pyproject.toml` for Cross-Platform PyTorch Builds

> Streamline cross-platform PyTorch builds using environment constraints in pyproject.toml. Leverage optional dependencies and source selectors for platform-specific wheels with UV.

- Repository: [Th3Unknovvn/uv-install-torch](https://github.com/baonguyen6742/uv-install-torch)
- Tags: tutorial
- Published: 2026-02-26

---

**Use UV's optional dependencies, conflict rules, and source selectors in [`pyproject.toml`](https://github.com/baonguyen6742/uv-install-torch/blob/main/pyproject.toml) to declare mutually exclusive CPU and CUDA PyTorch extras that resolve to platform-specific wheels without editing lock files.**

The `baonguyen6742/uv-install-torch` repository demonstrates a production-ready pattern for managing PyTorch's CPU-only and CUDA-enabled variants within a single Python project. By leveraging environment constraints and explicit package indexes in [`pyproject.toml`](https://github.com/baonguyen6742/uv-install-torch/blob/main/pyproject.toml), you can ensure that `uv sync --extra cpu` installs portable wheels on any operating system, while `uv sync --extra cu124` resolves to CUDA 12.4 builds only on compatible Linux systems.

## How UV Manages Cross-Platform PyTorch Dependencies

PyTorch distributes separate wheels for CPU and CUDA variants through different repository indexes. UV (the Astral package manager) handles this complexity through three coordinated mechanisms defined in [`pyproject.toml`](https://github.com/baonguyen6742/uv-install-torch/blob/main/pyproject.toml):

### 1. Optional Dependencies (Extras)

Define mutually exclusive extras under `[project.optional-dependencies]` at lines 24-28. Each extra lists identical package versions but resolves to different wheels based on source configuration:

```toml
[project.optional-dependencies]
cpu = ["torch==2.4.1", "torchvision==0.19.1", "torchaudio==2.4.1"]
cu124 = ["torch==2.4.1", "torchvision==0.19.1", "torchaudio==2.4.1"]

```

### 2. Conflict Rules

Prevent simultaneous activation of incompatible extras using the `conflicts` matrix in `[tool.uv]` at lines 30-33:

```toml
[tool.uv]
conflicts = [[{ extra = "cpu" }, { extra = "cu124" }]]

```

This declaration ensures UV rejects `uv sync --extra cpu --extra cu124` with a clear resolution error, protecting against undefined runtime behavior.

### 3. Source Selectors and Explicit Indexes

Map each extra to its corresponding PyTorch index in `[tool.uv.sources]` and `[[tool.uv.index]]` at lines 37-62:

```toml
[[tool.uv.index]]
name = "pytorch-cpu"
url = "https://download.pytorch.org/whl/cpu"
explicit = true

[[tool.uv.index]]
name = "pytorch-cu124"
url = "https://download.pytorch.org/whl/cu124"
explicit = true

[tool.uv.sources]
torch = [
  { index = "pytorch-cpu", extra = "cpu" },
  { index = "pytorch-cu124", extra = "cu124" },
]

```

The `explicit = true` flag ensures these indexes are only used for packages that explicitly reference them, preventing pollution of your dependency resolution with unrelated CUDA packages.

## Implementing Platform-Specific Environment Constraints

While the extras system handles wheel selection, you can add optional platform guards to restrict entire installation paths to specific operating systems. In [`pyproject.toml`](https://github.com/baonguyen6742/uv-install-torch/blob/main/pyproject.toml) at line 35, the commented `environments` entry demonstrates this:

```toml
[tool.uv]

# Uncomment to restrict CUDA builds to Linux only

# environments = ["sys_platform == 'linux' and os_name == 'posix'"]

```

When uncommented, this constraint prevents the `cu124` extra from resolving on Windows or macOS, enforcing the requirement that CUDA builds only install on Linux systems where NVIDIA drivers are typically available.

## Complete Configuration Example

The following [`pyproject.toml`](https://github.com/baonguyen6742/uv-install-torch/blob/main/pyproject.toml) from `baonguyen6742/uv-install-torch` implements the full cross-platform build system:

```toml
[project]
name = "uv-install-torch"
requires-python = ">=3.10"

[project.optional-dependencies]
cpu = ["torch==2.4.1", "torchvision==0.19.1", "torchaudio==2.4.1"]
cu124 = ["torch==2.4.1", "torchvision==0.19.1", "torchaudio==2.4.1"]

[tool.uv]
conflicts = [[{ extra = "cpu" }, { extra = "cu124" }]]

[tool.uv.sources]
torch = [
  { index = "pytorch-cpu", extra = "cpu" },
  { index = "pytorch-cu124", extra = "cu124" },
]
torchvision = [
  { index = "pytorch-cpu", extra = "cpu" },
  { index = "pytorch-cu124", extra = "cu124" },
]
torchaudio = [
  { index = "pytorch-cpu", extra = "cpu" },
  { index = "pytorch-cu124", extra = "cu124" },
]

[[tool.uv.index]]
name = "pytorch-cpu"
url = "https://download.pytorch.org/whl/cpu"
explicit = true

[[tool.uv.index]]
name = "pytorch-cu124"
url = "https://download.pytorch.org/whl/cu124"
explicit = true

```

## Installing and Verifying Your Build

Select your target platform during installation:

```bash

# CPU-only: works on Linux, macOS, Windows

uv sync --extra cpu

# CUDA 12.4: requires Linux and compatible NVIDIA driver

uv sync --extra cu124

```

Verify the installation using the [`main.py`](https://github.com/baonguyen6742/uv-install-torch/blob/main/main.py) script in the repository:

```python
import torch

print("torch version:", torch.__version__)
print("CUDA available:", torch.cuda.is_available())
if torch.cuda.is_available():
    print("GPU name:", torch.cuda.get_device_name(0))
    print("CUDA version:", torch.version.cuda)

```

Running `uv run main.py` after installing the `cu124` extra outputs:

```

torch version: 2.4.1+cu124
CUDA available: True
GPU name: NVIDIA GeForce RTX 3060

```

## Extending to New CUDA Versions

To add support for CUDA 12.5, extend the configuration with new indexes and sources without removing existing ones:

```toml
[project.optional-dependencies]
cu125 = ["torch==2.5.0", "torchvision==0.20.0", "torchaudio==2.5.0"]

[[tool.uv.index]]
name = "pytorch-cu125"
url = "https://download.pytorch.org/whl/cu125"
explicit = true

[tool.uv.sources]
torch = [
  { index = "pytorch-cpu", extra = "cpu" },
  { index = "pytorch-cu124", extra = "cu124" },
  { index = "pytorch-cu125", extra = "cu125" },
]

# Repeat for torchvision and torchaudio arrays

[tool.uv]
conflicts = [[{ extra = "cpu" }, { extra = "cu124" }, { extra = "cu125" }]]

```

Update the conflict matrix to include all mutually exclusive variants, ensuring only one PyTorch backend installs at a time.

## Summary

- **Define extras** in `[project.optional-dependencies]` to declare CPU and CUDA package sets without duplicating version constraints.
- **Use conflict rules** in `[tool.uv]` to prevent installing CPU and CUDA wheels simultaneously, avoiding runtime conflicts.
- **Configure explicit indexes** in `[[tool.uv.index]]` and map extras to them via `[tool.uv.sources]` to ensure UV pulls wheels from the correct PyTorch repository.
- **Add environment constraints** via `[tool.uv].environments` to restrict CUDA extras to Linux systems where drivers are available.
- **Reference the implementation** in `baonguyen6742/uv-install-torch` for the complete [`pyproject.toml`](https://github.com/baonguyen6742/uv-install-torch/blob/main/pyproject.toml) structure and the [`main.py`](https://github.com/baonguyen6742/uv-install-torch/blob/main/main.py) validation script.

## Frequently Asked Questions

### How do I prevent UV from installing CUDA wheels on macOS?

Add an environment constraint to the `[tool.uv]` section: `environments = ["sys_platform == 'linux' and os_name == 'posix'"]`. This restricts dependency resolution to Linux systems, preventing UV from attempting to install Linux-only CUDA wheels on macOS or Windows.

### Can I install multiple CUDA versions simultaneously?

No. The `conflicts` matrix explicitly prevents activating multiple extras at once. PyTorch's CUDA and CPU wheels contain overlapping file paths that would cause installation conflicts. You must choose one backend per virtual environment, though you can maintain separate `.venv` directories for different targets.

### What happens if I forget to specify `--extra` during `uv sync`?

UV installs only the core dependencies defined in `[project.dependencies]`, omitting PyTorch entirely. You must explicitly pass `--extra cpu` or `--extra cu124` to include the optional PyTorch wheels, ensuring intentional selection of the appropriate backend for your platform.

### Where does UV store the index URLs for PyTorch wheels?

Index definitions reside in `[[tool.uv.index]]` table entries within [`pyproject.toml`](https://github.com/baonguyen6742/uv-install-torch/blob/main/pyproject.toml). The `baonguyen6742/uv-install-torch` repository defines these at lines 55-62, setting `explicit = true` to prevent UV from searching these indexes for unrelated packages, which speeds up resolution and prevents version conflicts.