A Hatch build hook plugin that compiles Mojo sources during package builds.
Supports Python extension modules and standalone artifacts (shared libraries, executables, etc.).
uv add hatch-mojoAdd the hook to your pyproject.toml:
[build-system]
build-backend = "hatchling.build"
requires = ["hatchling", "hatch-mojo"]
[tool.hatch.build.targets.wheel.hooks.mojo]
targets = ["wheel"]
[[tool.hatch.build.targets.wheel.hooks.mojo.jobs]]
name = "core"
input = "src/mo/my_pkg/core.mojo"
emit = "python-extension"
module = "my_pkg._core"
include-dirs = ["src/mo"]Then build:
hatch build -t wheelAll configuration lives under [tool.hatch.build.targets.wheel.hooks.mojo] in pyproject.toml.
[tool.hatch.build.targets.wheel.hooks.mojo]
mojo-bin = "/opt/mojo/bin/mojo" # path to mojo binary (default: $HATCH_MOJO_BIN or "mojo")
parallel = true # compile jobs in parallel
fail-fast = true # stop on first failure (only with parallel)
clean-before-build = false # remove build-dir before compiling
clean-after-build = false # remove build-dir after compiling
skip-editable = true # skip Mojo compilation for editable installs
build-dir = "build/mojo" # working directory for compiled artifacts
targets = ["wheel"] # hatch build targets that trigger this hook
include = ["src/mo/**/*.mojo"] # git-style globs to include
exclude = ["**/experimental*.mojo"] # git-style globs to excludeProfiles define reusable sets of job options. Jobs reference them by name, and profile values are merged into the job config.
[tool.hatch.build.targets.wheel.hooks.mojo.profiles.default]
include-dirs = ["src/mo"]
flags = ["-I", "vendor/include"][[tool.hatch.build.targets.wheel.hooks.mojo.jobs]]
name = "core"
profiles = ["default"]
input = "src/mo/my_pkg/core.mojo"
emit = "python-extension"
module = "my_pkg._core"Each job compiles a single Mojo source into an output artifact.
Python extension:
[[tool.hatch.build.targets.wheel.hooks.mojo.jobs]]
name = "core"
input = "src/mo/my_pkg/core.mojo"
emit = "python-extension"
module = "my_pkg._core"
include-dirs = ["src/mo"]Non-Python artifact (requires an install mapping):
[[tool.hatch.build.targets.wheel.hooks.mojo.jobs]]
name = "cli"
input = "src/mo/my_pkg/cli.mojo"
emit = "executable"
install = { kind = "scripts", path = "my-cli" }| Option | Type | Description |
|---|---|---|
name |
string | Unique identifier for the job |
input |
string | Path to the Mojo source file (supports globs) |
emit |
string | python-extension, shared-lib, static-lib, object, executable |
output |
string | Explicit output path (overrides default) |
module |
string | Dotted import path (required for python-extension) |
install |
object | Install mapping for non-Python artifacts: { kind, path } |
profiles |
list | Profile names to inherit settings from |
include-dirs |
list | Additional include directories passed as -I |
defines |
list | Values passed as -D flags to the compiler |
flags |
list | Extra flags passed to mojo build |
env |
object | Environment variables set during compilation |
platforms |
list | Restrict job to specific sys.platform values |
arch |
list | Restrict job to specific platform.machine() values |
marker |
string | PEP 508 marker expression for conditional inclusion |
depends-on |
list | Job names that must complete before this one |
Supported install kinds: package, data, scripts, root, force-include
When building wheels that include Mojo python-extensions, set bundle-libs = true to copy the Mojo runtime libraries into the wheel:
[tool.hatch.build.targets.wheel.hooks.mojo]
bundle-libs = trueThis works on both Linux and macOS. On Linux, patchelf sets RPATH on each library. On macOS, install_name_tool rewrites dylib install names and inter-library references to use @rpath.
A NOTICE.mojo-runtime file is automatically included in the wheel alongside the bundled libraries, listing the bundled files and their license terms (Modular Community License). If the SDK directory contains a LICENSE file, it is also copied into the wheel as LICENSE.mojo-runtime.
When using cibuildwheel, keep the following in mind:
Linux (manylinux): The Mojo SDK requires manylinux_2_34 or newer due to GLIBCXX requirements. Standard auditwheel repair may reject the wheel — use a retag command instead:
[tool.cibuildwheel.linux]
repair-wheel-command = "python -m wheel tags --remove --platform-tag manylinux_2_34_x86_64 {wheel} && mv {wheel} {dest_dir}"macOS: With bundle-libs = true, macOS wheels work with standard delocate. No special repair command is needed:
[tool.cibuildwheel.macos]
repair-wheel-command = "delocate-wheel -w {dest_dir} {wheel}"macOS SIP and DYLD_LIBRARY_PATH: macOS System Integrity Protection (SIP) strips DYLD_LIBRARY_PATH from child processes. Setting it in CIBW_ENVIRONMENT will not propagate to the repair step. If you need it, pass it inline in the repair command:
[tool.cibuildwheel.macos]
repair-wheel-command = "DYLD_LIBRARY_PATH=/path/to/libs delocate-wheel -w {dest_dir} {wheel}"libstdc++ on manylinux: If the Mojo SDK links against a newer libstdc++ than the manylinux baseline provides, you may need to bundle it from conda-forge. This is a Mojo SDK limitation, not a hatch-mojo issue.
| Error | Fix |
|---|---|
mojo executable not found |
Set mojo-bin, HATCH_MOJO_BIN, or add mojo to PATH |
No build jobs resolved |
Check input paths, include/exclude globs, and targets |
unknown dependencies |
Ensure depends-on references valid job names |
| Wheel missing binaries | Set module for extension jobs or install for non-Python jobs |
MIT