Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ADR-0019: Prebuilt mnem binaries via cargo-dist

  • Status: Accepted
  • Date: 2026-09-19
  • Issue: #173

Context

cargo install mnem-git is the only way to get the mnem CLI without building from source, and it needs a Rust toolchain. #165 solved the equivalent problem for the Python SDK: cross-platform wheels, built and published by a GitHub Actions workflow. This ADR does the same for the Rust binary: a prebuilt archive and a one-line installer script per platform, attached to the GitHub Release.

Two things about this needed a decision before writing any workflow, because both are hard to walk back cheaply: whether to stand up a Homebrew tap (a second, permanent GitHub repository under the same account), and whether to change the release process this project has used since v0.0.2 (tag by hand, then create the GitHub Release by hand). Both were put to the maintainer directly rather than decided here.

Decision

Tool: cargo-dist (crates.io cargo-dist, binary dist, v0.32.0 pinned in dist-workspace.toml). It builds the archives, generates the shell and PowerShell installer scripts, and knows how to attach everything to a GitHub Release. Hand-rolling the same thing (as release-pypi.yml does for wheels) would mean maintaining installer scripts by hand for no reason; this is exactly cargo-dist's job.

Targets: x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu, x86_64-apple-darwin, aarch64-apple-darwin, x86_64-pc-windows-msvc. The same five platforms #165 covers for the Python wheel, so the CLI and the SDK have the same reach.

No Homebrew tap for now. The maintainer's call: ship the raw archives and the shell/PowerShell installers only. A tap is a second repository (Nabzx/homebrew-tap) to maintain forever after; add it once there is real usage to justify it. cargo-dist supports adding one later (the tap setting) without redoing anything else.

The release process stays manual, cargo-dist included. The maintainer's call, matching how release-pypi.yml already behaves. Note for anyone reading ADR-0007 alongside this: ADR-0007 sketches a release workflow that also cuts the GitHub Release from the tag push; what #68 actually built keeps that step manual (the workflow triggers on release: published, an event only the manual step produces). This ADR follows the practice that shipped, not ADR-0007's original sketch of it.

  • dispatch-releases = true: the workflow triggers on workflow_dispatch (gh workflow run release.yml -f tag=vX.Y.Z, or the Actions tab), never on a tag push by itself.
  • create-release = false: cargo-dist does not create the GitHub Release. It expects one to already exist for the tag, as a draft, and uploads the binaries to that draft, undrafting it once everything is attached.

That second point changes the manual step slightly: today the maintainer creates the GitHub Release and publishes it in one action. Under this ADR, the release is created as a draft first; running the dist workflow (after the tag is pushed) is what makes it visible, once the binaries are actually attached. Nothing about the release goes live before there is something to download, which is arguably the better property, not a worse one, and the maintainer still decides both when to draft it and when to run the workflow.

Two release workflows, not one. dist generate writes to .github/workflows/release.yml unconditionally; that name was already taken by #165's PyPI workflow. Renamed the PyPI one to release-pypi.yml (and its display name to release (pypi)) rather than fight cargo-dist's own regeneration model by hand-merging jobs into a file it expects to own outright. release.yml (cargo-dist's) is regenerated by editing dist-workspace.toml and running dist generate again; it is not meant to be hand-edited.

Consequences

  • cargo install mnem-git remains the primary path for anyone with a Rust toolchain; the new installers are for everyone else. Neither changes what the crate publishes to crates.io (#165's sibling ADR did not apply here, so there is no ADR change there).
  • A [profile.dist] in the root Cargo.toml (added by dist init, inherits = "release", lto = "thin") is the profile the release binaries build with; it does not affect cargo build --release.
  • dist-workspace.toml is the source of truth; .github/workflows/release.yml is generated output, checked in for review but not meant to be edited by hand. A future dist version bump or a target change means editing the config and re-running dist generate, the same discipline release-pypi.yml does not need because it has no equivalent generator.
  • PyPI trusted publishing (#165) is scoped to a workflow filename; the rename to release-pypi.yml means a trusted publisher must be added under that name, not release.yml, when #68's remaining PyPI packages are configured.
  • No format_version change, no object-model change, no Rust/Python boundary change. This is packaging and CI, not the substrate.

Alternatives considered

Hand-roll the binary builds and installer, matching how release-pypi.yml was hand-written for Python. Rejected: an installer script is easy to get subtly wrong (platform detection, checksum verification, $PATH handling) and cargo-dist's is maintained by people who do this for a living. Reinventing it buys nothing.

Let cargo-dist create the GitHub Release (create-release = true, its default). Rejected per the maintainer: it would mean a tag push (or a workflow_dispatch) starts producing a public release page without the explicit "publish" action this project has used since v0.0.2.

A Homebrew tap now. Rejected for now, not permanently: cheap to add later, and a second repository to maintain is a real, ongoing cost with no users yet to justify it.

Merge cargo-dist's jobs into the existing release.yml by hand. Rejected: dist generate regenerates that file from dist-workspace.toml on every config change or dist version bump; hand-merged jobs would be silently destroyed the next time it runs, or fought with forever.