Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/prefix-dev/pixi/llms.txt

Use this file to discover all available pages before exploring further.

Learn how to create build matrices that produce packages for different dependency versions, similar to parameterized builds or build configurations.
pixi-build is a preview feature and will change until stabilized. Keep this in mind when using it for your projects.

Why Use Build Variants?

Build variants solve version compatibility challenges:
  • Multiple Python versions - Support Python 3.11, 3.12, 3.13, etc.
  • Library versions - Build against different versions of dependencies
  • Testing - Verify compatibility across version ranges
  • Distribution - Provide packages for various configurations
In the conda ecosystem, this functionality is called “variants.” Other build systems might call this a build matrix, build configurations, or parameterized builds.

The Problem Variants Solve

Consider a C++ package built for Python 3.12:
[package.host-dependencies]
python = "3.12.*"
If a user tries to use this package with Python 3.11, Pixi reports a version conflict. Variants allow you to build multiple versions of the package, each compatible with different Python versions.

Creating Build Variants

1
Start with a Workspace
2
Begin with the workspace from the workspace tutorial:
3
workspace/
├── pixi.toml
├── pyproject.toml
├── src/
│   └── python_rich/
│       └── __init__.py
└── packages/
    └── cpp_math/
        ├── pixi.toml
        ├── CMakeLists.txt
        └── src/
            └── math.cpp
4
Relax Version Constraints
5
First, change the Python requirement in the C++ package to accept any version:
6
[package]
name = "cpp_math"
version = "0.1.0"

[package.host-dependencies]
cmake = ">=3.20, <3.27"
nanobind = ">=2.4.0, <2.5.0"
python = "*"  # (1)!
7
  • Changed from "3.12.*" to "*" to allow any Python version
  • 8
    Define Allowed Variants
    9
    Specify which Python versions to support:
    10
    [workspace]
    channels = ["https://prefix.dev/conda-forge"]
    platforms = ["osx-arm64", "osx-64", "linux-64", "win-64"]
    preview = ["pixi-build"]
    
    [workspace.build-variants]
    python = ["3.11.*", "3.12.*"]  # (1)!
    
    [dependencies]
    python_rich = { path = "." }
    
    11
  • Pixi will build packages for both Python 3.11 and 3.12
  • 12
    Without explicit environments, Pixi chooses a version based on the solver. To test both versions, create explicit environments.
    13
    Create Environments for Each Variant
    14
    Define environments that pin specific Python versions:
    15
    [feature.py311.dependencies]
    python = "3.11.*"
    
    [feature.py312.dependencies]
    python = "3.12.*"
    
    [environments]
    py311 = ["py311"]  # (1)!
    py312 = ["py312"]  # (2)!
    
    16
  • Environment using Python 3.11
  • Environment using Python 3.12
  • 17
    Verify Different Builds
    18
    Check that different packages were built:
    19
    pixi list --environment py311
    
    20
    Package            Version     Build               Size       Kind   Source
    python             3.11.11     h9e4cc4f_1_cpython  29.2 MiB   conda  python
    cpp_math           0.1.0       py311h43a39b2_0                conda  cpp_math
    python_rich        0.1.0       pyhbf21a9e_0                   conda  python_rich
    
    21
    pixi list --environment py312
    
    22
    Package            Version     Build               Size       Kind   Source
    python             3.12.8      h9e4cc4f_1_cpython  30.1 MiB   conda  python
    cpp_math           0.1.0       py312h2078e5b_0                conda  cpp_math
    python_rich        0.1.0       pyhbf21a9e_0                   conda  python_rich
    
    23
    Notice the different build strings:
    • cpp_math has different builds: py311h43a39b2_0 vs py312h2078e5b_0
    • python_rich has the same build: pyhbf21a9e_0 (noarch Python package)

    Complete Example

    Here’s the full configuration:
    pixi.toml
    [workspace]
    channels = ["https://prefix.dev/conda-forge"]
    platforms = ["osx-arm64", "osx-64", "linux-64", "win-64"]
    preview = ["pixi-build"]
    
    [workspace.build-variants]
    python = ["3.11.*", "3.12.*"]
    
    [dependencies]
    python_rich = { path = "." }
    
    [feature.py311.dependencies]
    python = "3.11.*"
    
    [feature.py312.dependencies]
    python = "3.12.*"
    
    [environments]
    py311 = ["py311"]
    py312 = ["py312"]
    
    [tasks]
    start = "rich-example-main"
    
    [package]
    name = "python_rich"
    version = "0.1.0"
    
    [package.build]
    backend = { name = "pixi-build-python", version = "0.4.*" }
    
    [package.build.config]
    noarch = false  # (1)!
    
    [package.host-dependencies]
    hatchling = "==1.26.3"
    python = "*"  # (2)!
    
    [package.run-dependencies]
    cpp_math = { path = "packages/cpp_math" }
    rich = ">=13.9.4,<14"
    
    1. Disable noarch to create variant-specific builds
    2. Allow any Python version - resolved by variants

    Multiple Variant Dimensions

    You can create variants for multiple dependencies:
    [workspace.build-variants]
    python = ["3.11.*", "3.12.*", "3.13.*"]
    nanobind = ["2.3.*", "2.4.*"]
    
    This creates a build matrix:
    • Python 3.11 + nanobind 2.3
    • Python 3.11 + nanobind 2.4
    • Python 3.12 + nanobind 2.3
    • Python 3.12 + nanobind 2.4
    • Python 3.13 + nanobind 2.3
    • Python 3.13 + nanobind 2.4
    Be careful with variant matrices - they grow exponentially! 3 Python versions × 2 nanobind versions × 4 platforms = 24 packages.

    Variant Files

    For complex configurations, use external variant files:
    pixi.toml
    [workspace.build-variants]
    files = ["variants.yaml"]
    
    variants.yaml
    python:
      - 3.11.*
      - 3.12.*
      - 3.13.*
    
    nanobind:
      - 2.4.*
      - 2.5.*
    
    zip_keys:
      - [python, nanobind]  # (1)!
    
    1. Create paired variants instead of a full matrix
    This creates only paired combinations:
    • Python 3.11 + nanobind 2.4
    • Python 3.12 + nanobind 2.5
    • Python 3.13 + nanobind 2.5
    See the manifest reference for details.

    Testing Across Variants

    Run Tests in All Environments

    pixi run --environment py311 test
    pixi run --environment py312 test
    

    Run Tasks in All Environments

    Use a loop (bash):
    for env in py311 py312 py313; do
      echo "Testing in $env"
      pixi run --environment $env test
    done
    

    Define Environment-Specific Tasks

    [feature.py311.tasks]
    test = "pytest tests/"
    
    [feature.py312.tasks]
    test = "pytest tests/ --strict-markers"
    
    [feature.py313.tasks]
    test = "pytest tests/ --strict-markers --new-feature"
    

    Understanding Build Strings

    Build strings indicate package variants:
    cpp_math-0.1.0-py311h43a39b2_0.conda
                    ^^^^^ ^^^^^^^^
                    |     |
                    |     Hash of dependencies
                    Python version
    
    Breakdown:
    • py311 - Python 3.11 variant
    • h43a39b2 - Hash of build configuration
    • _0 - Build number
    Noarch packages (pure Python) have the same build string across variants:
    python_rich-0.1.0-pyhbf21a9e_0.conda
                       ^^ ^^^^^^^^
                       |  |
                       |  Hash
                       Python (noarch)
    

    Platform vs Variant Matrix

    Platforms - Different OS/architectures:
    [workspace]
    platforms = ["linux-64", "osx-arm64", "win-64"]
    
    Variants - Different dependency versions:
    [workspace.build-variants]
    python = ["3.11.*", "3.12.*"]
    
    Combined: 3 platforms × 2 Python versions = 6 packages

    Common Variant Patterns

    [workspace.build-variants]
    python = ["3.11.*", "3.12.*", "3.13.*"]
    

    Advanced: Conditional Variants

    Use variant files for platform-specific variants:
    variants.yaml
    python:
      - 3.11.*
      - 3.12.*
    
    cuda:
      - 11.8  # linux-64 only
      - 12.0  # linux-64 only
    
    pin_run_as_build:
      python:
        min_pin: x.x
        max_pin: x.x
    

    Best Practices

    Begin with essential versions:
    # Start simple
    [workspace.build-variants]
    python = ["3.11.*", "3.12.*"]
    
    # Expand later
    python = ["3.10.*", "3.11.*", "3.12.*", "3.13.*"]
    
    Make packages flexible:
    [package.host-dependencies]
    python = "*"           # Any version
    nanobind = ">=2.4,<3"  # Range
    
    Automate variant testing:
    jobs:
      test:
        strategy:
          matrix:
            environment: [py311, py312, py313]
        steps:
          - run: pixi run --environment ${{ matrix.environment }} test
    
    Explain why variants exist:
    [workspace.build-variants]
    # Support Python 3.11+ for modern type hints
    # Support Python 3.12+ for better performance
    python = ["3.11.*", "3.12.*"]
    

    Next Steps

    Dependency Types

    Understand how variants affect dependencies

    Workspaces

    Use variants in multi-package workspaces

    Build Backends

    Backend-specific variant support

    Manifest Reference

    Complete variant configuration options

    Troubleshooting

    Check that:
    1. Package has noarch = false (or omit it)
    2. Dependency uses * or a range: python = "*"
    3. Variant is actually used in dependencies
    [package.build.config]
    noarch = false  # Required for variants
    
    [package.host-dependencies]
    python = "*"  # Must allow variant resolution
    
    Reduce variant dimensions:
    # Instead of this (6 builds)
    [workspace.build-variants]
    python = ["3.11.*", "3.12.*", "3.13.*"]
    nanobind = ["2.4.*", "2.5.*"]
    
    # Do this (3 builds)
    [workspace.build-variants]
    python = ["3.11.*", "3.12.*", "3.13.*"]
    
    Ensure environment pinning matches variants:
    [workspace.build-variants]
    python = ["3.11.*", "3.12.*"]  # Must match environment pins
    
    [feature.py311.dependencies]
    python = "3.11.*"  # Matches variant
    

    Build docs developers (and LLMs) love