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:Creating Build Variants
Begin with the workspace from the workspace tutorial:
workspace/
├── pixi.toml
├── pyproject.toml
├── src/
│ └── python_rich/
│ └── __init__.py
└── packages/
└── cpp_math/
├── pixi.toml
├── CMakeLists.txt
└── src/
└── math.cpp
[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)!
[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 = "." }
Without explicit environments, Pixi chooses a version based on the solver. To test both versions, create explicit environments.
[feature.py311.dependencies]
python = "3.11.*"
[feature.py312.dependencies]
python = "3.12.*"
[environments]
py311 = ["py311"] # (1)!
py312 = ["py312"] # (2)!
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
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
Complete Example
Here’s the full configuration:pixi.toml
- Disable noarch to create variant-specific builds
- Allow any Python version - resolved by variants
Multiple Variant Dimensions
You can create variants for multiple dependencies:- 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
Variant Files
For complex configurations, use external variant files:pixi.toml
variants.yaml
- Create paired variants instead of a full matrix
- Python 3.11 + nanobind 2.4
- Python 3.12 + nanobind 2.5
- Python 3.13 + nanobind 2.5
Testing Across Variants
Run Tests in All Environments
Run Tasks in All Environments
Use a loop (bash):Define Environment-Specific Tasks
Understanding Build Strings
Build strings indicate package variants:py311- Python 3.11 varianth43a39b2- Hash of build configuration_0- Build number
Noarch packages (pure Python) have the same build string across variants:
Platform vs Variant Matrix
Platforms - Different OS/architectures:Common Variant Patterns
Advanced: Conditional Variants
Use variant files for platform-specific variants:variants.yaml
Best Practices
Start with minimal variants
Start with minimal variants
Begin with essential versions:
Use version ranges in packages
Use version ranges in packages
Make packages flexible:
Test with CI/CD
Test with CI/CD
Automate variant testing:
Document required variants
Document required variants
Explain why variants exist:
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
Variants not creating different builds
Variants not creating different builds
Check that:
- Package has
noarch = false(or omit it) - Dependency uses
*or a range:python = "*" - Variant is actually used in dependencies
Too many builds
Too many builds
Reduce variant dimensions:
Environment resolution fails
Environment resolution fails
Ensure environment pinning matches variants: