Open-source project
axodotdev/cargo-dist avatar
axodotdev/cargo-dist

cargo-dist is now dist: tag, push, and let generated CI ship it

📦 shippable application packaging

2,126 stars153 forksRustApache-2.0

At a glance

What is it?
The project renamed itself from cargo-dist to dist and kept the crates.io name. What it does is take a pushed git tag and turn it into a five-stage pipeline: plan the release, build binaries and installers, publish to package managers, host the artifacts, announce the notes. The pipeline file is generated by `dist init`, which is both the strength and the constraint.
Who is it for?
Adopt dist if you publish binaries to more than one platform and would rather not hand-maintain a release workflow, and read the generated release.yml as a code review because you will not be editing it by hand. Skip it if your release needs a custom step the generator does not emit, or if you cannot accept a schema format frozen for compatibility, since dist's own artifacts are always built by the previous version of itself.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 2, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Tag a version, get a release

The pitch is four commands. With dist set up, this is the entire release process:

sh
git commit -am "release: 0.2.0"
git tag "v0.2.0"
git push
git push --tags

What comes out the other end is a GitHub Release with binaries attached. If you also use oranda, the same tag produces a documentation website.

There is no install command in this README, which is itself a sign of how little manual involvement is expected. The badges point at the crates.io and docs.rs listings for `cargo-dist`, and the book's Install page is linked from the documentation. Note the naming state: the binary is called `dist`, the project says it was formerly known as cargo-dist, and the published crate is still `cargo-dist`. Two names for one tool is a small thing that will cost you one search.

The architecture underneath is a five-stage pipeline, and the project's own headings are the clearest summary of it: plan, build, host, publish, announce. The split that matters for adoption is between building and distributing. Building covers planning the release and producing binaries and installers; distributing covers hosting artifacts, publishing packages and announcing. The README says the build side works on its own if you only want tarballs and installers, and that everything comes together once you turn on distribution. So the adoption decision is really about whether you want the whole pipeline or just the packaging.

dist init generates release.yml, so your pipeline is a generated file

The design decision that makes dist different from a build script is stated plainly: it generates its own CI scripts. Enabling GitHub CI with `dist init` produces a `release.yml` that implements the whole pipeline, and the generated file is checked into your repository like any other source.

Read what the plan stage does, because it carries the multi-workspace design. It waits for a git tag, and the tag formats it accepts are `v1.0.0`, `my-app-v1.0.0` and `my-app/1.0.0`. Given one of those, it selects which apps in your workspace to announce releases for. In a single-binary repository that is trivial; in a workspace with several publishable crates it is the mechanism that decides whether one tag releases one app or five, and the tag syntax is how you express that.

The plan stage also generates a machine-readable manifest containing changelogs and build plans, which is the handoff between stages: the build machines do not need to know what you intended, they read the plan. The build stage then spins up a machine for each platform you support and produces binaries, tarballs and installers. Publish uploads to package managers. Host and announce create or edit the GitHub Release, upload the artifacts, and pull the relevant release notes out of your RELEASES or CHANGELOG.

The consequence to plan for is that you now review a generated file instead of writing one. Every change to your release process is a change to your dist configuration plus a regeneration, and a hand edit to `release.yml` will be overwritten the next time somebody runs `dist init`. That is a defensible trade for consistency and a poor one if your release needs a bespoke step that the generator does not emit.

The installer is a tool somebody else maintains for you

The README is unusually candid about where its effort goes. Its list of build capabilities is short: pick good build flags for shippable binaries, make tarballs and installers for the results, and generate machine-readable manifests so other tools can understand them. Then it notes that the phrase about making installers is doing a lot of heavy lifting, and that each installer could be, and sometimes is, an entire standalone tool with its own documentation and ecosystem.

That is the honest framing. The user-facing artefact of a dist release is not the tarball, it is the install script that a stranger runs with curl piped into a shell. Those scripts carry shell compatibility, privilege prompts, checksum verification, upgrade paths and platform detection, and they have to keep working for years after the binary version that generated them is gone. Anyone adopting this is inheriting maintenance of an installer they never wrote.

The dependency list explains how they are produced. minijinja is a templating engine, which is what you expect for scripts that have to vary per platform and per target triple. cargo-wix sits in the third-party dependencies for Windows installer generation, and goblin with mach_object are binary-format libraries, which is what you need to inspect Mach-O executables on macOS. comfy-table and dialoguer are there for readable terminal output and interactive prompts.

The documentation splits along the same lines: separate pages for archives, for the installer catalogue, for the manifest schema and for the CI providers. The manifest schema page is the one to bookmark, because it is the contract between dist and anything that consumes its output.

Three crates: the tool, the schema and the project abstraction

The workspace has three members, and the split tells you where the boundaries are:

toml
members = [
    "axoproject",
    "cargo-dist",
    "cargo-dist-schema",
]
resolver = "2"
exclude = ["axoproject/tests/projects/"]

`cargo-dist` is the tool you run. `cargo-dist-schema` is the manifest format and the thing other tools are expected to read. `axoproject` is the abstraction underneath, and its features are the most telling line in the manifest:

toml
axoproject = { version = "=0.33.0", path = "axoproject", default-features = false, features = ["cargo-projects", "generic-projects", "npm-projects"] }

Three feature flags, three kinds of input. `cargo-projects` is Cargo workspaces, `generic-projects` is projects that are not Cargo at all, and `npm-projects` is npm packages. That is the evidence behind the project's description, shippable application packaging: the tag-driven pipeline is not Rust-only, and the naming is a leftover from when it was.

The exclusion line is worth noticing too. `axoproject/tests/projects/` is kept out of the workspace because it holds fixture projects for the test suite, and keeping them out stops the workspace from trying to build them as real members. That directory is also a useful artefact for anyone writing tests against dist: the fixtures are the sample inputs the tool is exercised on.

Other first-party crates show the shape of the work. axotag parses and compares version tags, which is what makes the `v1.0.0` and `my-app/1.0.0` forms equivalent. axoupdater carries the self-update logic with a GitHub Releases feature enabled. axoasset handles manifests in JSON, TOML, YAML with compression and remote fetching. axoprocess runs the external commands, and axocli wraps the command line.

Self-hosting dist, and why the schema format cannot break

dist releases itself, and the README explains the consequences in more detail than most projects explain anything. Because the release pipeline is generated by dist and dist ships its own releases through it, dist's published artifacts are always built by a previous version of itself.

The stated consequence is a constraint on the project itself: if that were not true, a breaking change to the cargo-dist-schema format would be survivable. So do not make one. The design response is that many things in the schema are intentionally optional, to enable forward and backward compatibility between versions that will always be one release apart in a chain like this.

There is a manual step after every release too, and it is the bootstrap update. You install the version of dist you just released, then regenerate the configuration against it and commit the result:

sh
dist init --yes

The commit message the README suggests is along the lines of updating the bootstrap dist version. Mechanically, the rest of dist's own release work is delegated to cargo-release, which handles the version bumps, headings and tags. The one editorial rule is that the Unreleased heading in CHANGELOG.md is reserved: dist and cargo-release are wired to understand that name and will update it themselves, so it should not be renamed.

The version pinning is visible in the manifest too, and it is exact rather than caret. Intra-workspace dependencies use `=0.33.0` forms with a comment noting that you need to bump those versions when cutting releases. So a release is not one version bump; it is several, and the project says so in a comment rather than letting you discover it.

Snapshot tests, and a test that writes to the working tree

The test strategy is snapshot testing through cargo-insta, chosen so that output and interface changes show up as reviewable diffs rather than as assertion failures. The workflow for a failing snapshot is the standard one:

sh
cargo install cargo-insta
sh
cargo insta review

`cargo insta accept` applies all changes without the review step, and brand-new snapshots have to be added to git afterwards. The reasoning given is that this catches regressions and makes UI and output changes easier to review, which for a tool whose main output is a CI file and a set of manifests is the right choice.

Then there is the footnote that shows how this project talks about its own rough edges. The `emit` test in cargo-dist-schema, when it succeeds, commits results back to `cargo-dist-schema/cargo-dist-schema.json` as a side effect. The README calls this a janky hack that exists to keep the file stored and up to date, notes that the file is not used for anything yet, and says the author wants it to exist because it seems useful and important. The stated plan is to host it properly and have dist's outputs link to it through a `$schema` field.

That is worth reading as a status report rather than a footnote. The JSON schema is published and generated, but nothing consumes it yet, so today a third-party tool cannot validate a dist manifest by pointing at a `$schema`. It is coming, not present.

A generated workflow against a hand-written one

The real alternative is the one most teams already have: a release workflow you wrote yourself, in YAML, in your repository, which you can edit. The comparison is not about capability, since a hand-written workflow can do anything dist does and more. It is about who owns the file.

A hand-written workflow is source. You review it, you refactor it, you add a step for the one platform dist has no story for, and your change survives the next release. Its costs are the ones you already pay: matrix definitions to maintain, installer scripts to write, a changelog-to-release-notes mapping to get right, and a step that silently stops working when an upstream action changes its interface.

The generated workflow is a build output. It is consistent, because everyone with the same config gets the same pipeline, and it is correct on the day it is written without anybody maintaining a matrix. Its cost is that a custom requirement has to go through the tool rather than around it, and that the file in your repository cannot be the place where you solve a one-off problem. That trade is worth making when you have several platforms and one person maintaining the release, and worth avoiding when your releases are unusual enough that a bespoke workflow is the honest answer.

There is a third, cheaper option that the README points at: use only the build half. If you want the tarballs and the installers and would rather keep your existing upload step, dist can be used without distribution at all.

MIT OR Apache-2.0, Rust 1.74, and a four-month gap

The licence is dual, MIT OR Apache-2.0, with both files present at the top level as LICENSE-MIT and LICENSE-APACHE. That choice is the usual one for Rust projects with corporate contributors: MIT for permissive simplicity, Apache-2.0 for the patent grant. Read the files for the exact terms; this review does not interpret them.

The version is 0.33.0, the edition is 2021, and the manifest declares `rust-version = "1.74"` as the floor. That floor is worth checking before you wire dist into a workspace, because a tool that runs inside your release pipeline is a dependency of your build, and its minimum compiler version is a hard constraint on your contributors' toolchains. The workspace uses resolver 2.

The release history shows how the project paces itself. v0.32.0 shipped on 2026-05-22 and v0.33.0 on 2026-09-11, so nearly four months between minor versions, with a 0.33.0 prerelease cut the same day as the final. A prerelease on release day tells you pre-releases come off main rather than from a long-lived branch, which is a useful thing to know before you depend on one.

The last push was on 2026-09-25 and the repository is not archived. The development tooling around it is worth noting because it explains the maintenance cost: a Justfile for tasks, a devenv.nix for a Nix-based development environment, a pinned rust-toolchain.toml, a typos.toml, a SECURITY.md, and the dist book itself living in the tree under `book/`. The docs are versioned with the code, which means documentation fixes ship as releases rather than as website edits.

Editorial conclusion

Adopt dist if you publish binaries to more than one platform and would rather not hand-maintain a release workflow, and read the generated release.yml as a code review because you will not be editing it by hand. Skip it if your release needs a custom step the generator does not emit, or if you cannot accept a schema format frozen for compatibility, since dist's own artifacts are always built by the previous version of itself. Check the `rust-version` of 1.74 in Cargo.toml before wiring it into a workspace, and leave the Unreleased heading in CHANGELOG.md to the tools.

Frequently asked questions

Is cargo-dist the same tool as dist?

Yes, it was renamed. The README titles the project `dist` and notes it was formerly known as cargo-dist, while the published crate on crates.io and the docs.rs target are still `cargo-dist`. The binary you run is called `dist`, so a search for cargo-dist lands you in the right place.

How do I release with cargo-dist once it is configured?

Commit, tag, push and push the tags: `git commit -am "release: 0.2.0"`, `git tag "v0.2.0"`, `git push` and `git push --tags`. The generated CI pipeline sees the tag and runs plan, build, publish, host and announce, ending in a GitHub Release with the artifacts attached.

Can I use cargo-dist for Cargo workspaces with several publishable crates?

Yes. The plan stage reads the tag format to decide which apps in the workspace to release, accepting v1.0.0, my-app-v1.0.0 and my-app/1.0.0. Underneath, the axoproject crate carries cargo-projects, generic-projects and npm-projects features, so the pipeline also covers non-Cargo projects and npm packages.

Can I use cargo-dist only to build binaries and installers?

Yes. The README splits the tool into building, which plans the release and produces binaries, tarballs and installers, and distributing, which hosts artifacts, publishes packages and announces releases. The build side can be used on its own if you already have an upload or hosting step you trust.

What does dist generate for the build, and is the schema usable?

It generates a machine-readable manifest with changelogs and build plans during the plan stage, and a release.yml implementing the whole pipeline when you run dist init. The README notes that the schema JSON is generated and stored but not used for anything yet, with plans to host it and have outputs link to it through a $schema field.

What licence and Rust version does cargo-dist require?

The licence is MIT OR Apache-2.0, with both LICENSE-MIT and LICENSE-APACHE in the repository root. The current version is 0.33.0, the edition is 2021, and Cargo.toml declares rust-version 1.74, which is the minimum compiler version to check before adding it to a workspace.

Official sources

  1. axodotdev/cargo-dist on GitHub
  2. License: Apache-2.0
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/axodotdev-cargo-dist.svg)](https://hysenlabs.com/projects/axodotdev-cargo-dist)