Install modes: pixi vs. submodules
Tins install either as pixi git source dependencies or as git submodules. Both are pinned by the registry to published commits; they differ in who does the building and where the source lives.
What pixi mode does better
- No submodule ceremony — no
--recurse-submodules, detached HEADs, or.gitmodulesnoise in your repo. - No
-Iflag bookkeeping — pixi builds each tin into a.mojopkgvia thepixi-build-mojobackend and imports just work. - FFI tins can ship their native shims as real conda artifacts instead of "run this script first" steps.
- It points where Modular's own tooling is heading, so migrating off mojoshelf later means editing a dependency list, not unwinding vendored submodules.
Why submodules still earn their keep
- Coverage — every tin supports submodule mode today; pixi mode requires the tin to be a pixi package, which the shelf is still adopting tin by tin.
- Stability — pixi-build is a preview feature and its manifest schema can still change; submodules are boring, stable git.
- Source in your tree — the dependency's code sits in your editor: greppable, LSP-navigable, patchable while debugging. With pixi dependencies you get a built package in a conda environment instead. This trade-off is permanent, not transitional.
Note — submodule mode means you do the build. Pixi mode
compiles tins for you; with submodules you point the Mojo compiler at each
tin's source yourself (-I shelf/<name>/src), and FFI tins
additionally need their native shim built and reachable at runtime. The build
is not a secret: every tin repo carries its own definition — the
[package] sections in its pixi.toml and, for shims,
a shim/ or ffi/ subpackage (CMake or a
rattler-build recipe). The shortcut: run pixi install inside the
tin's checkout and it builds everything, including the shim, into that
environment.
Which should I use?
Prefer pixi mode when every tin you need
supports it; fall back to submodules otherwise. The two modes coexist in one
project. A stable tin can also be graduated — shelf
graduate turns its pixi-build setup into a binary conda package
submission for the
modular-community channel. Submodule mode will be retired only when every published tin is
pixi-consumable and pixi-build has stabilized — and with a documented
migration path (shelf remove each tin, then
pixi shelf add).
The naming pattern: one tin, two namespaces
In pixi mode a tin's name becomes a conda package name in
the consumer's environment. A tin named after an existing conda package the
environment also needs (directly or transitively) makes the dependency
solver's job impossible — there cannot be two packages called
zlib in one environment, and the real one is required by half
the ecosystem.
The pattern:
- Tin name — must be unique in the conda namespace.
Before publishing, check
pixi search <name>against conda-forge and the Modular channels. For bindings to an existing library, the convention is an-mojosuffix:zlib-mojo,lancedb-mojo. - Mojo import name — independent of the tin name, set by
the backend's
[package.build.config.pkg]namefield, so imports stay natural:from zlib import inflate,from lancedb import …. - Native shims — FFI tins ship their C or Rust shims as
pixi subpackages (
pixi-build-cmake, or apixi-build-rattler-buildrecipe) that the tin run-depends on. The shim builds during install and lands in the environment'slib/, where the Mojo code dlopens it — consumers never run a build script.