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

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

  1. On crates.io, create an API token with the publish-new and publish-update scopes. Pick no expiry (or a long one), so it is set once.
  2. Store it as the repository secret CARGO_REGISTRY_TOKEN:
    gh secret set CARGO_REGISTRY_TOKEN -R Himasnhu-AT/ashlar
    
    (paste the token when asked). The token never needs to be on a developer machine.
  3. 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:

GuardWhy
the tag equals v + the workspace versionno mismatched or mistyped tags
the tagged commit is on mainnothing 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 sectionevery release is documented
that version of ashlar-rs is not on crates.io yetno 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

  1. Make sure main is green on CI and everything for the release is merged.
  2. Run cargo xtask release x.y.z (add --date YYYY-MM-DD to date it other than today, UTC). It bumps version in [workspace.package] and every internal crate’s version = "…" in [workspace.dependencies] together, renames “Unreleased” in CHANGELOG.md to ## [x.y.z] - YYYY-MM-DD under a fresh empty “Unreleased”, updates the compare links at the bottom, and runs cargo check to update Cargo.lock. It refuses when the versions disagree, the section already exists or “Unreleased” is empty, and it never commits, tags or publishes.
  3. Read the diff; bump any version shown in docs snippets (for example docs/trace/integration.md).
  4. Check that everything packages: cargo publish --workspace --locked --dry-run.
  5. Commit to main and push. Wait for CI to pass.
  6. Tag and push the tag:
    git tag -a v0.2.0 -m "ashlar 0.2.0"
    git push origin v0.2.0
    
  7. Watch the release workflow. 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.