Background & rationale#
This page explains the ecosystem context behind cx: why it exists, how the conda solver landscape has evolved, and prior art in single-binary conda distribution.
Why cx?#
conda is traditionally installed via miniconda or miniforge — large installers
that download and unpack hundreds of megabytes. The process is slow, requires
running a platform-specific installer, and leaves users with a heavyweight base
environment that includes the conda-libmamba-solver and its 27 exclusive
native dependencies (libsolv, libarchive, libcurl, spdlog, etc.).
cx takes a different approach: a single Rust binary (7-11 MB) that bootstraps a minimal conda installation in seconds using an embedded lockfile. No Python required to start; no installer framework; no shell profile modifications.
For air-gapped or restricted-network environments, cxz goes further: it
embeds all locked package archives directly into the binary (50-95 MB depending
on platform). One file, zero network access — cxz bootstrap installs conda
entirely from the embedded payload. See Self-contained binary (cxz) for details.
conda-rattler-solver#
The conda-rattler-solver project is the key enabler for cx’s solver strategy:
Dependencies: only
conda >=25.5.0+py-rattler >=0.21.0py-rattler is on PyPI with wheels for all major platforms (~28-31 MB, statically-compiled Rust bindings)
Uses resolvo, the fastest SAT solver in the conda ecosystem
Same solver used by pixi, under active development in the conda organization
Advantages over conda-libmamba-solver:
Pure wheel distribution — no libarchive, libsolv, CMake build issues
Single dependency (py-rattler) vs. complex C++ dependency chain
resolvo outperforms libsolv in benchmarks
Because conda on conda-forge hard-depends on conda-libmamba-solver, cx
uses a post-solve transitive dependency pruning algorithm to remove libmamba
and its 27 exclusive dependencies, reducing the install from 113 to 86
packages.
PyPI distribution (the uv pattern)#
cx is distributed on PyPI using the same technique as uv:
maturin with
bindings = "bin"compiles the Rust binary and packages it into a wheel’sscripts/directoryA tiny Python wrapper (
python/conda_express/) ships alongside withfind_cx_bin()and__main__.pyforpython -m conda_expressPre-built platform wheels (7-11 MB each) are uploaded to PyPI for every target
An sdist fallback builds from source if no wheel is available
Comparison: cx on PyPI vs. conda on PyPI#
Dimension |
cx on PyPI (maturin wheel) |
conda on PyPI (Python wheel) |
|---|---|---|
Upstream changes needed |
None |
pycosat optional, menuinst optional, solver plugin publishing |
What ships |
Single Rust binary (7-11 MB wheel) |
conda + all deps as Python wheels |
Solver |
rattler (compiled in) + conda-rattler-solver (from conda-forge) |
conda-rattler-solver (from PyPI, needs py-rattler) |
conda-forge packages |
Full access (bootstraps a real conda env) |
Limited to what’s on PyPI |
Install experience |
|
|
What blocks conda itself on PyPI#
The conda community has explored publishing conda directly to PyPI. The old conda 4.3.16 package is yanked — the name is available. The main blockers:
No solver on PyPI: conda-libmamba-solver depends on libmambapy (C++ bindings). Solved by conda-rattler-solver.
pycosat still a hard dependency: Needs to move to optional dependencies.
menuinst still a hard dependency: Already runtime-optional but still listed as required.
conda already has a pip-specific entry point at conda.cli.main_pip:main and
has commented out the libmamba dependency in its PyPI config.
conda’s dependencies (all on PyPI)#
Dependency |
Type |
PyPI Status |
|---|---|---|
archspec |
Pure Python |
Available |
boltons |
Pure Python |
Available |
charset-normalizer |
Pure Python |
Available |
conda-package-handling |
Pure Python |
Available |
distro |
Pure Python |
Available |
frozendict |
Pure Python |
Available |
packaging |
Pure Python |
Available |
platformdirs |
Pure Python |
Available |
pluggy |
Pure Python |
Available |
pycosat |
C extension |
Should be optional |
requests |
Pure Python |
Available |
ruamel.yaml |
C extension optional |
Available with wheels |
setuptools |
Pure Python |
Available |
tqdm |
Pure Python |
Available |
truststore |
Pure Python |
Available |
zstandard |
C extension |
Available with platform wheels |
Plugin ecosystem on PyPI#
Plugin |
PyPI status |
|---|---|
conda-pypi |
0.5.0 on PyPI and conda-forge |
conda-rattler-solver |
Not yet (pure Python + py-rattler) |
conda-spawn |
conda-forge only (pure Python) |
conda-self |
conda-forge only (pure Python) |
Removing the classic solver (upstream)#
Not required for cx, but valuable for conda’s long-term health.
Prior work by jaimergp#
Three PRs attempted to extract the classic (pycosat-based) solver:
PR #14131 (Aug 2024) — moved code into
conda/plugins/solvers/classic/as a previewPR #14167 (Aug 2024) — full extraction in favor of
conda-classic-solverrepoPR #14170 (Aug 2024) — most complete attempt (14 files modified). Marked stale Jan 2026.
Key blocker (jaimergp, Jan 2025): “We’ll need to publish
conda-classic-solver in both defaults and conda-forge before we can
undo this.”
What needs to happen#
Revive PR #14170 or create a new PR based on it
Publish conda-classic-solver to both
defaultsandconda-forgeMove pycosat from conda’s core deps to conda-classic-solver only
Make solver a required plugin (remove fallback to pycosat)
Prior art: uniconda#
uniconda was jaimergp’s earlier attempt at a single-binary conda, using PyOxidizer to build a static binary embedding Python and conda together.
Patches required (conda 22.11.1 era)#
conda/__init__.py—CONDA_PACKAGE_ROOT: changed to use__spec__.origininstead of__file__(PyOxidizer in-memory loading)conda/__init__.py—__version__: hardcoded (PyOxidizer brokeget_version(__file__))conda_package_handling—logging.getLogger(__file__)to__name__(in-memory issue)
Why cx doesn’t need these patches#
The rattler approach installs conda as a real conda package into a real
prefix — __file__, sys.prefix, all filesystem paths work normally.
cx doesn’t embed Python or conda into the binary; it bootstraps them into
a standard prefix on first run. The cxz variant embeds the compressed
package archives (not an unpacked Python), so the installed prefix is
still a normal conda environment — just sourced from the binary instead
of the network.