The version lives in one place: [workspace.package].version in the root
Cargo.toml. Member crates inherit it (version.workspace = true); internal
path deps omit a version (deny.toml sets allow-wildcard-paths = true, and
crates are publish = false). There is no separate VERSION file.
Hosted CI cannot build rMLX. GitHub's macOS runners are VMs without a usable Metal device, and the build links the Metal MLX libraries. CI (
.github/workflows/ci.yml) runs fmt + clippy + a best-effort release build only. All release artifacts are built locally on an Apple-Silicon machine withbrew install mlx-cpresent.
brew install mlx-c(pullsmlx) — provides thelibmlxc/libmlxdylibs the binary links.- A published tap repo
Pushkinist/homebrew-rmlxforbrew tap.
What the shipped artifacts resolve MLX to. The release tarball links
libmlx.dylib/libmlxc.dylibthrough the moving/opt/homebrew/opt/...symlinks, so neither carries the MLX it was built against — each runs against whatever the installing user has, whichdepends_on "mlx-c"resolves to the current release. Two consequences worth holding onto when reading a user report:
events.mlx_naxis read at run time from the metallib the user's machine actually loaded (crates/rmlx-mlx/src/nax.rs), so a row from a distributed binary is the user's answer, not the builder's. Nothing about the nax capability is baked into the artifact.- Run
make mlx-preflighton the release machine before building the release binary. It does not change what users get, but it keeps the release machine's own published prefill numbers honest.The formula deliberately does not pin an MLX version — that would force a downgrade on M1–M4 users for a benefit their hardware has no use for. The rationale is in
packaging/homebrew/rmlx.rb; do not "fix" it by adding a version constraint.
-
Bump
versioninCargo.toml[workspace.package]. -
Changelog: add a
## [<version>] - <date>section toCHANGELOG.md(Keep a Changelog format) and the matching[<version>]:link at the bottom. This is the durable, in-repo record and the source of the Release body.Do not version-bump
README.md. The README is intentionally decoupled from the release steps: its version badge (shields.io/github/v/release) tracks GitHub Releases automatically, and the Status line carries no version number.CHANGELOG.mdis the per-release doc edit; the README changes only when capabilities materially change (a new modality, architecture family, or endpoint) — never for a routine version bump. -
Gate:
make cigreen (fmt + clippy + test + deny + audit). -
Tag:
make tag→ creates annotatedv<version>fromCargo.toml. Push it:git push origin v<version>. -
Build the binary artifact:
make release-package→dist/rmlx-v<version>-aarch64-apple-darwin.tar.gz(+.sha256). -
GitHub Release (body = the CHANGELOG section for this version):
gh release create v<version> \ --title "rMLX <version>" \ --notes-file <(scripts/release/changelog_section.sh <version>) gh release upload v<version> \ dist/rmlx-v<version>-aarch64-apple-darwin.tar.gz \ dist/rmlx-v<version>-aarch64-apple-darwin.tar.gz.sha256
-
Sign the artifact (keyless cosign):
make release-sign→dist/rmlx-v<version>-aarch64-apple-darwin.tar.gz.cosign.bundle(needsbrew install cosign; opens a browser OIDC prompt). Upload it:gh release upload v<version> \ dist/rmlx-v<version>-aarch64-apple-darwin.tar.gz.cosign.bundle
The release binary is built locally (hosted CI has no Metal), so the
.sha256alone is self-attested. The cosign bundle binds the tarball to your authenticated identity + the public Rekor log — real provenance for the prebuilt binary. Consumer-side verification is in "Verify both install paths". -
No Homebrew bottle is published.
brew install rmlxbuilds from source, which is whatdepends_on "rust" => :buildin the formula has always described. The formula carries nobottle doblock, and adding one back needs the two problems below solved first — not just a fresh sha.Why the block was removed (0.4.1). A
bottle doblock names aroot_urlpinned to one release, but Homebrew derives the bottle filename from the formula's current version. So a block left behind after a version bump sendsbrew installlooking for a bottle that was never built, under the previous release's URL, and it 404s. The block shipped pinned tov0.3.0through bothv0.3.0andv0.4.0while the only bottle asset in existence wasrmlx-0.3.0.arm64_tahoe.bottle.tar.gz—brew inforeported(bottled)the whole time. Nothing in the release flow regenerated it, and nothing failed when it went stale.The deeper reason not to reinstate it casually. A bottle is a binary linked against the
mlx-cpresent on the build machine, butmlx-cis deliberately an unversioned dependency (see the rationale inpackaging/homebrew/rmlx.rb), andcrates/rmlx-mlx/mlx-pin.txtrecords that a mismatched mlx / mlx-c pair aborts at load with a dyldSymbol not found. A bottle poured onto a user whose mlx-c revision differs from the builder's can therefore fail at load, where a source build against that user's own mlx-c cannot. Building one on this machine also requiresbrew unpin mlx mlx-c, which upgrades the pinned pair the project's own measurements depend on.scripts/release/build_bottle.shandmake bottleare kept for a future bottle channel that solves the ABI coupling — an mlx-c version constraint, or a static link. They are not part of the release flow. Do not run them and paste the output into the formula without also arranging for the next release to rebuild or remove the block. -
Formula url + sha256. Note
make release-shaonly prints the sha of thev<version>GitHub source tarball; to patch theurl+sha256inpackaging/homebrew/rmlx.rbin place, run the script with--write:bash scripts/release/source_sha256.sh --write
GitHub generates the source archive on first access, so its sha256 can shift on the very first fetch right after a tag push. The
source_sha256.sh-written value is usually the correct stable one — but re-fetch the archive 2-3× (curl -fsSL .../archive/refs/tags/v<version>.tar.gz | shasum -a 256) and confirm the digest is stable before trusting it. Commit the url+sha change as its own formula PR;mainis ruleset-protected. Verify both lines read the new version before opening it —--writepatchesurlandsha256together, but a mismatch between them makesbrew installfail the checksum. -
Publish the tap:
make tap-sync(copies the formula intoPushkinist/homebrew-rmlxasFormula/rmlx.rband pushes).
Hosted CI runs only fmt + clippy + a best-effort build (no Metal, no tests), so
a green Dependabot check does not prove the bump builds with Metal or passes
the suite. Gate every bump locally with make ci (and a real-model smoke for
runtime-affecting deps — allocator, tokenizer) before merging.
A major bump that needs source migration is the trap: Dependabot only edits the manifest, so its branch stays RED until the migration commit is pushed to the remote PR branch:
gh pr checkout <PR> # work on the dependabot branch
# … migrate source, `make ci`, prove …
git push origin HEAD:dependabot/cargo/<branch> # REQUIRED — push the fixmain's ruleset requires the rustfmt and build + clippy checks with no
bypass (not even admin), so the PR cannot merge until the remote branch is
green. A migration that lives only in your local checkout will not unblock the
merge. Do not git branch -D the local branch before the remote has the
fix (the migration commit is otherwise only reachable via reflog).
Prebuilt binary (from the Release):
brew install mlx-c
gh release download v<version> -p '*aarch64-apple-darwin.tar.gz*'
shasum -a 256 -c rmlx-v<version>-aarch64-apple-darwin.tar.gz.sha256
tar xzf rmlx-v<version>-aarch64-apple-darwin.tar.gz
./rmlx-v<version>-aarch64-apple-darwin/rmlx --versionProvenance (cosign bundle, if the release ships one):
gh release download v<version> -p '*.cosign.bundle'
cosign verify-blob \
--bundle rmlx-v<version>-aarch64-apple-darwin.tar.gz.cosign.bundle \
--certificate-identity <maintainer-oidc-email> \
--certificate-oidc-issuer <issuer-url> \
rmlx-v<version>-aarch64-apple-darwin.tar.gz
# issuer: GitHub https://github.com/login/oauth · Google https://accounts.google.comHomebrew (build from source):
brew tap Pushkinist/rmlx
brew trust Pushkinist/rmlx # one-time: Homebrew refuses to load formulae from untrusted third-party taps
brew install rmlx
brew test rmlx
rmlx --versionRecent Homebrew versions block third-party taps until trusted — a fresh
brew install rmlxfails withRefusing to load formula … from untrusted tapuntilbrew trust Pushkinist/rmlxis run once.
Local formula check before publishing:
brew install --build-from-source ./packaging/homebrew/rmlx.rb
brew audit --strict --new rmlx| Path | Role |
|---|---|
CHANGELOG.md |
Durable release notes (Keep a Changelog); body source |
packaging/homebrew/rmlx.rb |
Canonical formula (source of truth) |
scripts/release/package_binary.sh |
Build + bundle the binary tarball |
scripts/release/build_bottle.sh |
Retired from the flow (step 8). Kept for a future bottle channel; not run at release time |
scripts/release/source_sha256.sh |
Compute / patch the formula source sha256 |
scripts/release/sync_tap.sh |
Push the formula to the tap repo |
scripts/release/changelog_section.sh |
Print one version's CHANGELOG section |