Skip to content

Commit 88b528f

Browse files
feat: Add start of uv vs. Pixi
Co-authored-by: Henry Schreiner <HenrySchreinerIII@gmail.com>
1 parent c2cd988 commit 88b528f

1 file changed

Lines changed: 152 additions & 1 deletion

File tree

Lines changed: 152 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,152 @@
1-
# uv vs. Pixi: a showdown
1+
# uv vs. Pixi: A showdown
2+
3+
As mentioned in [Setting up for development](https://scikit-build.org/SIMPLE-Py/setup/), two tools were mentioned as being important to have installed: `uv` and Pixi.
4+
What tool should you use and when?
5+
The answers mainly come down to what are your goals and what are your constraints.
6+
To start with though, let's compare the tools feature sets for their high-level interfaces and note their similarity in design.
7+
A more complete list is [given on the Pixi documentation website](https://pixi.prefix.dev/latest/switching_from/uv/#quick-look-at-the-differences).
8+
9+
| Feature | uv | Pixi |
10+
| ------------------------------------------------- | ---------------------------------------------------------- | -------------------------------------------------------- |
11+
| Installs Python packages | ✅ | ✅ (via uv's Rust libraries) |
12+
| Installs conda packages | ❌ | ✅ |
13+
| Installs Python | ✅ | ✅ |
14+
| Environment format | Python virtual environments (`.venv`) | conda environments (`.pixi/envs`) |
15+
| Activate environments in the shell | ✅ (`. .venv/bin/activate`) | ✅ (`pixi shell`) |
16+
| Supports lockfiles | ✅ (`uv.lock`, TOML) | ✅ (`pixi.lock`, YAML) |
17+
| Automatically produce lockfiles | ✅ (`uv run`, `uv sync`) | ✅ (all operations that touch the workspace or lockfile) |
18+
| Resolve environment to a lockfile without install during development | ❌ | ❌[^1] |
19+
| Low-level commands | ✅ (`uv pip install`) | ❌ |
20+
| Fully disable lockfile operations | ❌ (partial: `--frozen`, `--no-sync`, or drop to `uv pip`) | ❌ (partial: `--frozen`, `--locked`) |
21+
| Declarative dependency specification | ✅ (`uv add`) | ✅ (`pixi add`) |
22+
| Globally install tools in isolated envs | ✅ (`uv tool install`) | ✅ (`pixi global`) |
23+
| Run tools in temporary envs | ✅ (`uvx`) | ✅ (`pixi exec`) |
24+
| Task runner system | ❌ | ✅ |
25+
| Native multi-environment support | ❌ (delegated to `nox` or `tox`) | ✅ (arbitrary environments) |
26+
| Build packages | ✅ (sdists and wheels via `uv build`) | ✅ (conda packages via Pixi Build) |
27+
| Publish packages | ✅ (to PyPI via `uv publish`) | ✅ (to conda channels via `pixi upload`) |
28+
| Fast? | Fastest! | Fast! |
29+
30+
While both tools support lockfiles, they mean different things by it.
31+
uv's `uv.lock` lockfiles are a single **universal** resolution recorded with environment markers, that is valid on all platforms.
32+
Pixi's `pixi.lock` lockfiles record a **separate solve for each platform** that the workspace declares.
33+
Neither format is the standardized lockfile format ([PEP 751](https://peps.python.org/pep-0751/)'s `pylock.toml`), though `uv` can export one with `uv export --format pylock.toml`.
34+
35+
## When to use what tool
36+
37+
Importantly `uv` and Pixi are tools for software environment resolution, installation, and management.
38+
They do not preclude use of any of the tools that we use for **building** Python packages (e.g. `hatchling`, `scikit-build-core`, `setuptools`).[^2]
39+
So the choice comes down much more to what sort of project you're working on and what kind of dependencies it requires for building, running, and testing.
40+
41+
### uv
42+
43+
If **everything your project needs, including at build time, is a Python package** then `uv` is a reasonable default.
44+
This covers more than the pure-Python case: build tools like CMake and Ninja are distributed as wheels, so a compiled-extension project using `scikit-build-core` can still be developed with `uv` as long as a suitable compiler exists on the system.
45+
It uses "traditional" Python virtual environments, with development dependencies organized into [PEP 735](https://peps.python.org/pep-0735/) dependency groups, so the standard mechanics of virtual environment workflows are retained.
46+
As `uv` also only operates on and with Python packages it also can focus on the Python ecosystem and optimizations that can be made for Python packages and Python package index design that are not generalizable.
47+
It is also blazingly fast and as of 2026 still is the fastest Python dependency manager that exists.
48+
49+
### Pixi
50+
51+
If you are working with Python projects that **depend on things that do not exist as Python packages**, like a compiler stack or large non-Python dependencies (e.g. your project is Python bindings to a large C++ framework like Geant4), then Pixi's strengths of being a multi-platform software environment manager with access to conda packages start to stand out more.
52+
With Pixi and Pixi Build you have the ability to ensure consistency of compiler stacks used across time.
53+
Pixi also supports **multiple named environments in one workspace**, composed from "features", so a test matrix across Python versions, or an optional CUDA environment, can coexist side by side without an external tool like `nox` or `tox`.
54+
One cost is that because `pixi.lock` records a separate solve for every platform the workspace declares, lock files are large and resolution takes longer with each platform added.
55+
Another cost appears during development: resolving a PyPI **source** dependency with dynamic metadata — which usually includes the package under development itself, present as an editable install — requires running its build backend with the environment's Python interpreter, so the conda environment must be installed just to produce the lock file.[^1]
56+
57+
### Developing a package for distribution
58+
59+
The tools also differ in how the development environment relates to the package you eventually ship.
60+
61+
- **Dependency metadata:** `uv add` writes to `[project.dependencies]` in `pyproject.toml` (the same metadata that ends up in your published sdist and wheel) so the development environment and the package's declared dependencies can not drift apart.
62+
Pixi's conda dependencies live in `[tool.pixi.dependencies]`, which does not propagate into package metadata, so a package distributed on PyPI needs its runtime dependencies specified twice: once as conda packages for development and once under `[project]` for distribution.
63+
- **Editable installs:** `uv sync` installs the project itself in editable mode by default.
64+
Pixi requires declaring this explicitly:
65+
66+
```toml
67+
[tool.pixi.pypi-dependencies]
68+
your-package = { path = ".", editable = true }
69+
```
70+
71+
- **Build artifacts:** `uv build` produces sdists and wheels and `uv publish` uploads them to PyPI, while Pixi Build produces **conda packages** for conda channels.
72+
73+
### Using them together
74+
75+
The choice is not exclusive.
76+
Pixi already uses uv's resolver internally for its PyPI dependencies, and some projects ship both manifests so that contributors can use whichever tool fits their setup.
77+
In continuous integration both tools have first-party GitHub Actions with built-in caching keyed on the lockfile.
78+
79+
- [`astral-sh/setup-uv`](https://github.com/astral-sh/setup-uv)
80+
- [`prefix-dev/setup-pixi`](https://github.com/prefix-dev/setup-pixi)
81+
82+
## Pixi Exercises
83+
84+
::::{exercise} Basic Pixi environment
85+
:label: basic-pixi-env
86+
87+
Use Pixi to create a Pixi workspace.
88+
Install the `cowsay` package from PyPI and then run `cowsay`.
89+
Remove the workspace when done.
90+
91+
:::{solution} basic-pixi-env
92+
:class: dropdown
93+
94+
```bash
95+
pixi init cowsay-example
96+
cd cowsay-example
97+
pixi add python
98+
pixi add --pypi cowsay
99+
pixi run cowsay -t SIMPLE-Py
100+
cd ..
101+
rm -rf ./cowsay-example
102+
```
103+
104+
:::
105+
106+
::::
107+
108+
::::{exercise} Tool install
109+
:label: pixi-tool-install
110+
111+
Use `pixi global` to install `pixi-browse`.
112+
Run it.
113+
Remove the tool when done.
114+
115+
:::{solution} pixi-tool-install
116+
:class: dropdown
117+
118+
```bash
119+
pixi global install pixi-browse
120+
pixi browse -m boost-histogram
121+
pixi global uninstall pixi-browse
122+
```
123+
124+
:::
125+
126+
::::
127+
128+
::::{exercise} Development environment
129+
:label: pixi-dev-env
130+
131+
The most downloaded Python package in the world is probably `packaging` (not counting AWS).
132+
Download the `https://github.com/pypa/packaging` repository from GitHub, and run the test suite with `pytest` using Pixi.
133+
134+
:::{solution} pixi-dev-env
135+
:class: dropdown
136+
137+
Assuming that you have nothing installed on a new machine beyond Pixi:
138+
139+
```bash
140+
pixi global install git
141+
git clone https://github.com/pypa/packaging
142+
cd packaging
143+
pixi init --format pyproject
144+
pixi run --environment test pytest
145+
```
146+
147+
:::
148+
149+
::::
150+
151+
[^1]: PyPI dependencies resolve without installation (`pixi lock --no-install`) only when their metadata can be read statically: wheels, or source dependencies with fully static `pyproject.toml` metadata. With dynamic metadata (e.g. `dynamic = ["version"]`, common for packages built with `setuptools_scm`, `hatch-vcs`, or `scikit-build-core`) `pixi lock` must install the conda environment to run the build backend, and `pixi lock --no-install` fails with `installation of conda environment is required to solve PyPI source dependencies`.
152+
[^2]: Though `uv` does have its own [PEP 517](https://peps.python.org/pep-0517/) compliant build backend, `uv_build`, for pure-Python projects.

0 commit comments

Comments
 (0)