Publishing crates to crates.io
Some library crates in this workspace go to crates.io. Each published crate has its own version and its own release tag. A crate tag starts the Publish crate workflow (.github/workflows/publish-crate.yml), which publishes that one crate.
Which crates are published
| Crate | Current version | Depends on |
|---|---|---|
morphir-config | 0.0.1 | no workspace crates |
morphir-core | 0.2.0 | no workspace crates |
morphir-workspace | 0.2.0 | morphir-config |
morphir-extension-sdk | 0.2.0 | morphir-workspace |
morphir-projection | 0.1.0 | morphir-core |
The other crates in the workspace are not on crates.io. They keep the shared workspace version ([workspace.package] version in the root Cargo.toml).
A crate that goes to crates.io leaves the shared version. Its Cargo.toml sets version explicitly, for example version = "0.2.0".
Version rule
Published crates use 0.x.y versions until Morphir itself reaches 1.0.0.
- Increase
xfor a breaking change. Setyto 0. - Increase
yfor an additive change or a fix. - Prereleases are allowed, for example
0.2.0-alpha.1. - Do not publish
1.0.0or higher before Morphir 1.0.0.
Cargo treats 0.x as the compatibility range, so a change to x tells users that the change can break them.
Publish order
A published crate can only depend on other published crates. Every path dependency on a published crate also gives a version, anywhere in the workspace:
morphir-workspace = { path = "../morphir-workspace", version = "0.2.0" }
A workspace build uses the path. The packaged crate depends on that version from crates.io, so that version must be on crates.io before the crate that depends on it is published. When you change the version of a published crate, change the version of every path dependency on it too.
Publish the crates in tiers:
- Tier 0:
morphir-config, which is already on crates.io. - Tier 1:
morphir-coreandmorphir-workspace. - Tier 2:
morphir-extension-sdkandmorphir-projection.
Publish a tier only after all the crates of the tier before it are visible on crates.io. The crates of one tier do not depend on each other, so you can publish them in any order.
Packaging check in CI
The Package (published crates) job in the CI workflow packages all the published crates together:
cargo package -p morphir-config -p morphir-core -p morphir-workspace \
-p morphir-extension-sdk -p morphir-projection --locked
Cargo builds each package against the packages of the workspace crates it depends on, not against the workspace sources. This finds a crate that would not build from crates.io, before its dependencies are published. The job runs when one of these crates, Cargo.toml or Cargo.lock changes.
Some crates leave their tests out of the package with exclude = ["/tests/"], because the tests read shared fixtures from the workspace or need a path dev-dependency that the package drops.
How to publish a crate
- Set the new version in
crates/<crate>/Cargo.tomland runcargo checkso thatCargo.lockagrees. - Add the change to
CHANGELOG.md. -
Make sure that the dry run passes:
cargo publish --dry-run -p <crate> --lockedThe dry run gets the dependencies of the crate from crates.io. Before the tier below is published, use the packaging check above instead.
- Merge the change to
main. -
Tag the merge commit on
mainand push the tag:git tag -a crates/<crate>/v<version> -m "<crate> <version>" <commit> git push origin crates/<crate>/v<version>For example,
crates/morphir-config/v0.0.1.
To run the workflow again for a tag that already exists, start Publish crate from the Actions tab and give the tag name as input.
The v* and extension/*/v* tags start the extension release workflow. Crate tags do not match those patterns, so a crate tag starts only the crate publish workflow.
What the workflow checks
The resolve job runs .github/scripts/crate_release.py. It stops the workflow if:
- the tag is not
crates/<crate>/v<semver>, - there is no crate at
crates/<crate>, or its package name is different, - the crate sets
publish = false, - the version in the crate’s
Cargo.tomlis not the tag version, - the tag commit is not on
main.
The publish job checks out the tag commit, runs cargo publish --dry-run -p <crate> --locked and then cargo publish -p <crate> --locked.
Credentials
The workflow uses the CARGO_REGISTRY_TOKEN repository secret. crates.io needs a token for the first publish of a crate, because trusted publishing can only be set up for a crate that already exists.
Follow-up: after the first publish of a crate, set up trusted publishing for it on crates.io. Then change the workflow to get a short-lived token from rust-lang/crates-io-auth-action in a crates.io environment (with id-token: write), and stop using the long-lived token.
If a publish fails
crates.io does not let you replace a version that is already published.
- If the
resolvejob fails, nothing was published. Correct the cause. If the tag is wrong, delete it and tag again. - If the
publishjob fails before the upload, nothing was published. Fix the cause and run the workflow again for the same tag. - If a published version is bad, publish a new version with the fix. Use
cargo yank --version <version> <crate>to stop new projects from selecting the bad version.