Packaging: conda vs. wheel
In pixi mode a tin is a source dependency: pixi fetches the tin's repo at its registry-pinned commit and builds it into a conda package in your environment. The conda machinery underneath does real work for us:
What conda does for tins
- Builds from source, automatically. The
pixi-build-mojobackend compiles the tin's Mojo package (.mojopkg) on install; C and Rust FFI shims build as sibling packages (cmake or rattler-build recipes) the tin run-depends on. - Import resolution for free. Packages land in
$CONDA_PREFIX/lib/mojo, where the Mojo compiler finds them — no-Iflags in your build commands. - A native dependency graph. Shims link real libraries (libz, OpenSSL, …) from conda-forge; conda resolves and installs them into the same environment and relocates rpaths so everything loads at runtime.
- Toolchain coherence. Compiled Mojo packages are tied to a
compiler version; the solver enforces one
mojo-compileracross every tin in the environment. - Transitive builds. A tin that imports another tin
(
docx→zlib-mojo) declares it as a git-pinned host dependency; the dependency is built into the build environment first.
The same machinery distributes the shelf CLI itself: a conda
package on the static channel at /channel, installed with
pixi global install.
One repo, more than one tin
A library whose optional half pulls in heavy dependencies can ship that half
as a separate tin from the same repository, so the dependencies stay
optional at install time rather than only at compile time. Put a second
pixi.toml and shelf.toml in a subdirectory, with its
sources beneath it — a subdirectory manifest's build context is the
subdirectory, not the repo root — and depend on the root package by relative
path:
# full/pixi.toml
[package]
name = "parquet-full-mojo"
[package.build.config.pkg]
path = "src/parquet_full" # i.e. full/src/parquet_full
name = "parquet_full"
[package.host-dependencies]
parquet-mojo = { path = ".." } # the root package, same commit
Publish it by running shelf publish from that directory.
The registry records where the manifest sits, and consumers get it
automatically:
pixi shelf add parquet-full-mojo
# writes: { git = "…/parquet.mojo", rev = "…", subdirectory = "full" }
Two rules make this work, and breaking either ships a tin nobody can install:
Publish every tin in the repo at the same commit.
path = ".." resolves to
git+<repo>?rev=<the commit the subdirectory tin was published
at>. If the registry pins the root at a different commit, a consumer
ends up with two source records for one package and the solve fails with
encountered duplicate records for <root>-…_source.conda.
Do not list an in-repo path dependency in
shelf.toml's tins. That field is for tins
the registry must resolve; the root package is already reached by
path = "..", and naming it as well pins it a second time — the
same collision from the other side. A tin's own FFI shim is not listed either,
for the same reason.
Both tins then move together, so there is no re-pinning between them. The usual rule still applies in the other direction: a path dependency that climbs past the repository root resolves in nobody's checkout and is refused at publish.
A pypi/wheel backend?
Wheels are a plausible additional distribution for pure-Mojo,
Python-facing tins: PyPI hosting is universal, uv is fast, plain
venvs could consume tins, and pixi speaks PyPI natively. What's missing today:
a PEP 517 build backend for Mojo (the wheel-world equivalent of
pixi-build-mojo), a convention for finding .mojopkgs in
site-packages, and per-wheel vendoring of native shim libraries —
wheels have no shared native dependency graph, so each shim must bundle its
dylibs. Conda stays the primary substrate; a wheel experiment would start with
a Python-interop tin like pontoneer.