Releasing
Every crate in crates/ is published to crates.io with the same version (the workspace
version in the root Cargo.toml). The umbrella crate is published as ashlar-rs, because
the ashlar name on crates.io belongs to an unrelated project; its library name is still ashlar,
so apps write use ashlar::prelude::*. Examples and xtask are publish = false.
One-time setup
- On crates.io, create an API token with the
publish-newandpublish-updatescopes. Pick no expiry (or a long one), so it is set once. - Store it as the repository secret
CARGO_REGISTRY_TOKEN:
(paste the token when asked). The token never needs to be on a developer machine.gh secret set CARGO_REGISTRY_TOKEN -R Himasnhu-AT/ashlar - Optional, later: switch to crates.io trusted publishing and delete the token.
What triggers a release
Only pushing a tag of the form vMAJOR.MINOR.PATCH (for example v0.2.0). Nothing else
publishes: pushes to main, pull requests and Dependabot never do, and running the release
workflow by hand (Actions → release → Run workflow) is always a dry run.
Before anything is uploaded, the workflow refuses to go on unless:
| Guard | Why |
|---|---|
the tag equals v + the workspace version | no mismatched or mistyped tags |
the tagged commit is on main | nothing from an unmerged branch |
| the full CI gate passes on that exact commit (all three OSes, MSRV) | no broken or half-done state |
CHANGELOG.md has a ## [x.y.z] - YYYY-MM-DD section | every release is documented |
that version of ashlar-rs is not on crates.io yet | no accidental re-release |
Then it runs cargo publish --workspace --locked (every crate in dependency order), skipping the
crates that already have this version on crates.io, and creates the GitHub release with that
version’s changelog section as the text. crates.io lets an account create only a few new crates at
once and then about one every 10 minutes, so a release that adds new crates (the first one adds
all of them) waits and tries again until every crate is there; that can take a few hours. Any
other publish error stops the job.
Cutting a release
- Make sure
mainis green on CI and everything for the release is merged. - Run
cargo xtask release x.y.z(add--date YYYY-MM-DDto date it other than today, UTC). It bumpsversionin[workspace.package]and every internal crate’sversion = "…"in[workspace.dependencies]together, renames “Unreleased” inCHANGELOG.mdto## [x.y.z] - YYYY-MM-DDunder a fresh empty “Unreleased”, updates the compare links at the bottom, and runscargo checkto updateCargo.lock. It refuses when the versions disagree, the section already exists or “Unreleased” is empty, and it never commits, tags or publishes. - Read the diff; bump any version shown in docs snippets (for example
docs/trace/integration.md). - Check that everything packages:
cargo publish --workspace --locked --dry-run. - Commit to
mainand push. Wait for CI to pass. - Tag and push the tag:
git tag -a v0.2.0 -m "ashlar 0.2.0" git push origin v0.2.0 - Watch the
releaseworkflow. If a guard fails, nothing was published: fix the cause, delete the tag (git push origin :refs/tags/v0.2.0,git tag -d v0.2.0) and tag again.
If publishing stops halfway (a network error, a timeout), crates.io keeps the crates that made it.
Fix the cause and run the failed publish job again from the workflow run: it publishes only what
is missing. If the fix changes the workflow itself, a re-run still uses the old one; push the fix
to main and move the tag to that commit (git tag -fa vX.Y.Z -m "ashlar X.Y.Z",
git push -f origin vX.Y.Z). That is safe only while the crates’ own files are unchanged, since
the published crates came from the old commit.
Versioning
ashlar follows Semantic Versioning. Before 1.0, a minor version (0.x) may
break the API; patch versions never do. The items marked CONTRACT in ashlar-command change only
additively.