Plan: Publish workspace crates on release¶
Problem¶
The current release.yml tags, builds
binaries, creates a GitHub Release, and updates Homebrew. It does not
publish any of the four workspace crates to crates.io. The three
sub-crates (shakenfist-spice-protocol, shakenfist-spice-compression,
shakenfist-spice-usbredir) are currently parked on crates.io at
0.0.0 as name reservations, and ryll's path dependencies on them
carry no version = qualifier, so cargo publish -p ryll would fail
today regardless of workflow changes.
Goals¶
- Publish all four crates to crates.io on every tag.
- Keep all four crates on a single, shared version number, bumped together with each release tag.
- Make cutting a release a one-command operation that a human confirms before anything irreversible happens.
- Fail fast if the release tag and the workspace version disagree.
Design¶
Version unification via [workspace.package]¶
The workspace root Cargo.toml gains
version = "0.1.3" in [workspace.package]. All four members switch
from their current per-crate version = "..." lines to
version.workspace = true. Bumping the workspace version in one
place now updates all four crates.
Ryll's path dependencies on the three sub-crates gain explicit
version = "0.1.3" qualifiers so cargo publish -p ryll succeeds
against crates.io's rules about published crates declaring versions
on their dependencies.
Sub-crate descriptions are cleaned up to drop the "Reserved crate
name; the first published release will follow as 0.1.0" language.
The first real publish will be 0.1.4, matching the ryll release,
rather than 0.1.0. There is no behavioural reason to renumber
ryll and no value in having the sub-crates at different versions
from ryll.
Release-cutting workflow¶
The cut is split into two phases so the version bump goes through
the same PR review gate as every other change, rather than landing
directly on develop.
Phase 1 — tools/propose-release.sh,
invoked as make propose-release 0.1.4. The script:
- Parses and validates the version argument as
X.Y.Z. - Verifies git: on
develop, working tree clean, in sync withorigin/develop. - Verifies neither the tag
v0.1.4nor the branchrelease-0.1.4already exists locally or onorigin. - Verifies crates.io does not already have
0.1.4for any of the four crates (HTTP GET to the crates.io API — 404 means free). - Creates and switches to
release-0.1.4. - Runs
pre-commit run --all-files. - Runs
cargo release version 0.1.4 --workspace --execute --no-confirmto bump[workspace.package].versionand the path-depversion =qualifiers in one pass. - Runs
cargo test --workspaceas a final gate. - Shows
git diff --stat, then prompts "Commit and push release-0.1.4? [y/N]". - On
y: commitsRelease 0.1.4.and pushesrelease-0.1.4toorigin. Onn: switches back todevelopand deletes the release branch — nothing reachesorigin. - Prints the PR-creation URL so the operator can open the PR manually. The script never creates PRs itself (per the repository convention that PR creation stays with the operator).
Phase 2 — tools/tag-release.sh,
invoked as make tag-release 0.1.4 after the phase-1 PR has been
merged. The script:
- Fetches
origin/developand the latest tags. - Verifies no
v0.1.4tag exists locally or onorigin. - Reads
[workspace.package].versionfromorigin/developand checks it is0.1.4; bails otherwise (means the PR has not merged yet or a different version landed). - Shows the target SHA and subject line, spells out that pushing the tag will publish all four crates to crates.io irreversibly, and prompts "Create and push tag v0.1.4? [y/N]".
- On
y: creates an annotated tagv0.1.4at the fetchedorigin/developSHA and pushes it (this is what triggers release.yml). - Runs
gh run watchon the triggered workflow, thengh release view v0.1.4 --webonce it completes.
cargo-release is installed on the host (not inside Docker). It is
a pure Rust CLI and does not interact with the project's toolchain;
running it on the host is simpler than the alternative because the
scripts also need git, gh, and SSH key access. Hosts running older
rustc (e.g. Debian's cargo 1.85) should pin
cargo-release@0.25.18, which is the last release that supports
1.85.
Release workflow (release.yml)¶
Three changes to release.yml:
check-versionreads the version from root[workspace.package]instead ofryll/Cargo.toml. Because all four crates now inherit, a single check covers the entire workspace.- New
publish-cratesjob runs afterbuildsucceeds. UsesCARGO_REGISTRY_TOKEN. Publishes in dependency order: shakenfist-spice-protocolshakenfist-spice-compressionshakenfist-spice-usbredirryllThe three sub-crates are independent of each other and could publish in any order or in parallel, but serial keeps the logs simple. Ryll must be last because it depends on all three.github-releaseis unchanged (generate_release_notes: truealready produces the autogenerated changelog that previously came from the web UI — nogh release createstep is needed).
Required secret¶
CARGO_REGISTRY_TOKEN must exist as a repo secret before the first
release after this lands. Generated at
https://crates.io/settings/tokens with "publish new crates" and
"publish updates" scopes. A human has to do this once — not
something the workflow or this PR can automate.
Out of scope¶
- Deriving version from the git tag.
cargo publishreadsCargo.tomlat publish time; version-from-tag tricks either require rewriting Cargo.toml in CI (breakscargo install --gitby making the repo source disagree with the published crate) or use third-party build-info tools that don't integrate withcargo publish. Thecheck-versionstep catches mismatches early enough that this isn't a real problem. - Automating bumps in response to conventional commits. The
human picks the version.
cargo releasesupports--bump patch/--bump minor, but the script requires an explicit version to keep the intent auditable. - Major version discipline for 0.x crates. Under semver,
0.y.z→0.(y+1).0is a breaking change. The user is aware and will apply this convention manually when bumping.
Rollout¶
The changes in this branch land as a single commit. The first tag
after merge (v0.1.4) will be the first real release of the
sub-crates — they go from 0.0.0 placeholder to 0.1.4 alongside
ryll.