Browse Source

Automate more of Wasmtime's release process (#3422)

* Automate more of Wasmtime's release process

This change revamps the release process for Wasmtime and intends to make
it nearly 100% automated for major release and hopefully still pretty
simple for patch releases. New workflows are introduced as part of
this commit:

* Once a month a PR is created with major version bumps
* Specifically hinted commit messages to the `main` branch will get
  tagged and pushed to the main repository.
* On tags we'll now not only build releases after running CI but
  additionally crates will be published to crates.io.

In conjunction with other changes this means that the release process
for a new major version of Wasmtime is simply merging a PR. Patch
releases will involve running some steps locally but most of the
nitty-gritty should be simply merging the PR that's generated.

* Use an anchor in a regex
pull/3475/head
Alex Crichton 3 years ago
committed by GitHub
parent
commit
807b528bfb
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 86
      .github/workflows/bump-version.yml
  2. 24
      .github/workflows/publish-to-cratesio.yml
  3. 49
      .github/workflows/push-tag.yml
  4. 89
      docs/contributing-release-process.md
  5. 56
      docs/stability-release.md
  6. 34
      scripts/publish.rs

86
.github/workflows/bump-version.yml

@ -0,0 +1,86 @@
# The purpose of this workflow is to, once a month, trigger Wasmtime's release
# process. All that actually happens here is that whenever this is triggered it
# will send a PR to the main repository with the version numbers automatically
# bumped. The next stage of the process is to simply merge the PR, and the
# `push-tag.yml` process takes over from there.
#
# Note that this creates a commit and a PR with a personal access token to
# ensure that the PR gets CI triggered on it. Additionally the commit message
# is specifically worded to get recognized by `push-tag.yml`.
name: "Bump version number"
on:
schedule:
# “At 00:00 on every day-of-month from 8 through 14 and on Monday.”
#
# https://crontab.guru/#0_0_8-14_*_1
- cron: '0 0 8-14 * 1'
jobs:
bump_version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
- run: rustup update stable && rustup default stable
- name: Bump versions locally
id: bump
run: |
rustc scripts/publish.rs
./publish bump
version=$(grep '^version =' Cargo.toml | head -n 1 | sed 's/.*"\(.*\)"/\1/')
echo "::set-output name=version::$version"
- name: Commit version changes
run: |
git config user.name 'Wasmtime Releases'
git config user.email 'wasmtime-releases@users.noreply.github.com'
git commit -a -F-<<EOF
Bump Wasmtime to $version
[automatically-tag-and-release-this-commit]
EOF
- name: Push to remote
run: |
git remote set-url origin https://git:${{ secrets.PERSONAL_ACCESS_TOKEN }}@github.com/${{ github.repository }}
git push origin main:ci/bump-version -f
- name: Make a PR
# Note that the syntax here is kinda funky, and the general gist is that
# I couldn't figure out a good way to have a multiline string-literal
# become a json-encoded string literal to send to GitHub. This
# represents my best attempt.
run: |
cat > pr-body <<-EOF
This is an automated pull request from CI which is intended to
notify maintainers that it's time to release Wasmtime version
${{ steps.bump.outputs.version }}. Version numbers have been bumped
in this PR automatically and the release process will automatically
enter the next stages once this PR is merged.
It's recommended that maintainers double-check that [RELEASES.md]
is up-to-date. If not please feel free to push to this PR any
modifications to the release notes. Additionally before merging it's
probably best to double-check the [release process] and make sure that
everything is ship-shape.
[RELEASES.md]: https://github.com/bytecodealliance/wasmtime/blob/main/RELEASES.md
[release process]: https://docs.wasmtime.dev/contributing-release-process.html
EOF
body=$(jq -sR < ./pr-body)
curl --include --request POST \
https://api.github.com/repos/${{ github.repository }}/pulls \
--header "Authorization: token ${{ secrets.PERSONAL_ACCESS_TOKEN }}" \
--data @- << EOF
{
"head": "ci/bump-version",
"base": "main",
"title": "Release Wasmtime ${{ steps.bump.outputs.version }}",
"body": $body,
"maintainer_can_modify": true
}
EOF

24
.github/workflows/publish-to-cratesio.yml

@ -0,0 +1,24 @@
# The purpose of this workflow is to publish the wasmtime workspace of crates
# whenever a wasmtime tag is created. This baiscally boils down to running
# `scripts/publish.rs` at the right time.
name: "Publish to crates.io"
on:
push:
tags:
- 'v*'
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
- run: rustup update stable && rustup default stable
- run: |
rustc scripts/publish.rs
./publish publish
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

49
.github/workflows/push-tag.yml

@ -0,0 +1,49 @@
# The purpose of this workflow is to watch for changes on the `main` branch of
# this repository and look for the term
# "automatically-tag-and-release-this-commit" within merged PRs/commits. Once
# that term is found the current version of `Cargo.toml`, the `wasmtime-cli`
# Cargo.toml, is created as a tag and the tag is pushed to the repo. Currently
# the tag is created through the GitHub API with an access token to ensure that
# CI is further triggered for the tag itself which performs the full release
# process.
name: "Push tagged release"
on:
push:
branches: [main]
jobs:
push_tag:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
fetch-depth: 0
- name: Test if tag is needed
run: |
git log ${{ github.event.before }}...${{ github.event.after }} | tee main.log
version=$(grep 'version =' Cargo.toml | head -n 1 | sed 's/.*"\(.*\)"/\1/')
echo "version: $version"
echo "::set-output name=version::$version"
echo "::set-output name=sha::$(git rev-parse HEAD)"
if grep -q "automatically-tag-and-release-this-commit" main.log; then
echo push-tag
echo "::set-output name=push_tag::yes"
else
echo no-push-tag
echo "::set-output name=push_tag::no"
fi
id: tag
- name: Push the tag
run: |
git_refs_url=$(jq .repository.git_refs_url $GITHUB_EVENT_PATH | tr -d '"' | sed 's/{\/sha}//g')
curl -iX POST $git_refs_url \
-H "Authorization: token ${{ secrets.PERSONAL_ACCESS_TOKEN }}" \
-d @- << EOF
{
"ref": "refs/tags/v${{ steps.tag.outputs.version }}",
"sha": "${{ steps.tag.outputs.sha }}"
}
EOF
if: steps.tag.outputs.push_tag == 'yes'

89
docs/contributing-release-process.md

@ -4,34 +4,65 @@ This is intended to serve as documentation for Wasmtime's release process. It's
largely an internal checklist for those of us performing a Wasmtime release, but
others might be curious in this as well!
To kick off the release process someone decides to do a release. Currently
there's not a schedule for releases or something similar. Once the decision is
made (there's also not really a body governing these decisions, it's more
whimsical currently, or on request from others) then the following steps need to
be executed to make the release:
1. Double-check that there are no open [rustsec advisory
issues][rustsec-issues] on the Wasmtime repository.
1. `git pull` - make sure you've got the latest changes
1. Run `rustc scripts/publish.rs`
1. Run `./publish bump`
* Review and commit the changes
* Note that this bumps all cranelift/wasmtime versions as a major version bump
at this time. See the `bump_version` function in `publish.rs` to tweak this.
1. Make sure `RELEASES.md` is up-to-date, and fill it out if it doesn't have an
entry yet for the current release.
1. Send this version update as a PR to the `wasmtime` repository, wait for a merge
1. After merging, tag the merge as `vA.B.C`
1. Push the tag to the repository
* This will trigger the release CI which will create all release artifacts and
publish them to GitHub releases.
1. Run `./publish publish`
* This will fail on some crates, but that's expected.
* Keep running this script until all crates are published. Note that crates.io
won't let you publish something twice so rerunning is only for crates which
need the index to be udpated and if it hasn't yet. It's recommended to wait
a bit between runs of the script.
And that's it, then you've done a Wasmtime release.
## Releasing a major version
Major versions of Wasmtime are relased once-a-month. Most of this is automatic
and **all that needs to be done is to merge the GitHub PR that CI will
generate** on the second Monday of each month.
Specifically what happens for a major version release is:
1. One day a month (configured via `.github/workflows/bump-version.yml`) a CI job
will run. This CI job will:
* Download the current `main` branch
* Run `./scripts/publish.rs` with the `bump` argument
* Commit the changes with a special marker in the commit message
* Push these changes to a branch
* Open a PR with this branch against `main`
1. A maintainer of Wasmtime signs off on the PR and merges it.
* Most likely someone will need to push updates to `RELEASES.md` beforehand.
* A maintainer should double-check there are [no open security issues][rustsec-issues].
1. The `.github/workflow/push-tag.yml` workflow is triggered on all commits to
`main`, including the one just created with a PR merge. This workflow will:
* Scan the git logs of pushed changes for the special marker added by
`bump-version.yml`.
* If found, tags the current `main` commit and pushes that to the main
repository.
1. Once a tag is created CI runs in full on the tag itself. CI for tags will
create a GitHub release with release artifacts and it will also publish
crates to crates.io. This is orchestrated by `.github/workflows/main.yml`.
If all goes well you won't have to read up much on this and after hitting the
Big Green Button for the automatically created PR everything will merrily carry
on its way.
[rustsec-issues]: https://github.com/bytecodealliance/wasmtime/issues?q=RUSTSEC+is%3Aissue+is%3Aopen+
## Releasing a patch release
Making a patch release is somewhat more manual than a major version. At this
time the process for making a patch release of `2.0.1` the process is:
1. All patch release development should be happening on a branch
`release-2.0.1`.
* Maintainers need to double-check that the `PUBLIC_CRATES` listed in
`scripts/publish.rs` do not have semver-API-breaking changes (in the
strictest sense). All security fixes must be done in such a way that the API
doesn't break between the patch version and the original version.
1. Locally check out `release-2.0.1` and make sure you're up-to-date.
1. Run `rustc scripts/publish.rs`
1. Run `./publish bump-patch`
1. Update `RELEASES.md`
1. Commit the changes. Include the marker
`[automatically-tag-and-release-this-commit]` in your commit message.
1. Make a PR against the `release-2.0.1` branch.
1. Merge the PR when CI is green
* Note that if historical branches may need updates to source code or CI to
pass itself since the CI likely hasn't been run in a month or so. When in
doubt don't be afraid to pin the Rust version in use to the rustc version
that was stable at the time of the branch's release.
From this point automated processes should take care of the rest of the steps,
basically resuming from step 3 above for major releases where `push-tag.yml`
will recognize the commit message and push an appropriate tag. This new tag will
then trigger full CI and building of release artifacts.

56
docs/stability-release.md

@ -1,3 +1,57 @@
# Release Process
... more coming soon
Wasmtime's release process was [originally designed in an RFC][rfc4] and this
page is intended to serve as documentation for the current process as-is today.
The high-level summary of Wasmtime's release process is:
* A new major version of Wasmtime will be made available once a month.
* Security bugs and correctness fixes will be backported to the latest two
releases of Wasmtime and issued as patch releases.
Once a month Wasmtime will issue a new major version. This will be issued with a
semver-major version update, such as 4.0.0 to 5.0.0. The precise schedule of
Wasmtime's release may fluctuate slightly depending on public holidays and
availability of release resources, but the general cadence will be once-a-month.
Each major release of Wasmtime reserves the right to break both behavior and API
backwards-compatibility. This is not expected to happen frequently, however, and
any breaking change will follow these criteria:
* Minor breaking changes, either behavior or with APIs, will be documented in
the `RELEASES.md` release notes. Minor changes will require some degree of
consensus but are not required to go through the entire RFC process.
* Major breaking changes, such as major refactorings to the API, will be
required to go through the [RFC process]. These changes are intended to be
broadly communicated to those interested and provides an opportunity to give
feedback about embeddings. Release notes will clearly indicate if any major
breaking changes through accepted RFCs are included in a release.
Patch releases of Wasmtime will only be issued for security and correctness
issues for on-by-default behavior in the previous releases. If Wasmtime is
currently at version 5.0.0 then 5.0.1 and 4.0.1 will be issued as patch releases
if a bug is found. Patch releases are guaranteed to maintain API and behavior
backwards-compatibility and are intended to be trivial for users to upgrade to.
## What's released?
At this time the release process of Wasmtime encompasses:
* The `wasmtime` Rust crate
* The C API of Wasmtime
* The `wasmtime` CLI tool through the `wasmtime-cli` Rust crate
Other projects maintained by the Bytecode Alliance will also likely be released,
with the same version numbers, with the main Wasmtime project soon after a
release is made, such as:
* [`wasmtime-dotnet`](https://github.com/bytecodealliance/wasmtime-dotnet)
* [`wasmtime-py`](https://github.com/bytecodealliance/wasmtime-py)
* [`wasmtime-go`](https://github.com/bytecodealliance/wasmtime-go)
* [`wasmtime-cpp`](https://github.com/bytecodealliance/wasmtime-cpp)
Note, though, that bugs and security issues in these projects do not at this
time warrant patch releases for Wasmtime.
[rfc4]: https://github.com/bytecodealliance/rfcs/blob/main/accepted/wasmtime-one-dot-oh.md
[RFC process]: https://github.com/bytecodealliance/rfcs

34
scripts/publish.rs

@ -110,7 +110,6 @@ struct Crate {
manifest: PathBuf,
name: String,
version: String,
next_version: String,
publish: bool,
}
@ -128,9 +127,9 @@ fn main() {
crates.sort_by_key(|krate| pos.get(&krate.name[..]));
match &env::args().nth(1).expect("must have one argument")[..] {
"bump" => {
name @ "bump" | name @ "bump-patch" => {
for krate in crates.iter() {
bump_version(&krate, &crates);
bump_version(&krate, &crates, name == "bump-patch");
}
// update the lock file
assert!(Command::new("cargo")
@ -226,11 +225,6 @@ fn read_crate(manifest: &Path) -> Crate {
}
let name = name.unwrap();
let version = version.unwrap();
let next_version = if CRATES_TO_PUBLISH.contains(&&name[..]) {
bump(&version)
} else {
version.clone()
};
if ["witx", "witx-cli", "wasi-crypto"].contains(&&name[..]) {
publish = false;
}
@ -238,13 +232,19 @@ fn read_crate(manifest: &Path) -> Crate {
manifest: manifest.to_path_buf(),
name,
version,
next_version,
publish,
}
}
fn bump_version(krate: &Crate, crates: &[Crate]) {
fn bump_version(krate: &Crate, crates: &[Crate], patch: bool) {
let contents = fs::read_to_string(&krate.manifest).unwrap();
let next_version = |krate: &Crate| -> String {
if CRATES_TO_PUBLISH.contains(&&krate.name[..]) {
bump(&krate.version, patch)
} else {
krate.version.clone()
}
};
let mut new_manifest = String::new();
let mut is_deps = false;
@ -254,9 +254,11 @@ fn bump_version(krate: &Crate, crates: &[Crate]) {
if CRATES_TO_PUBLISH.contains(&&krate.name[..]) {
println!(
"bump `{}` {} => {}",
krate.name, krate.version, krate.next_version
krate.name,
krate.version,
next_version(krate),
);
new_manifest.push_str(&line.replace(&krate.version, &krate.next_version));
new_manifest.push_str(&line.replace(&krate.version, &next_version(krate)));
rewritten = true;
}
}
@ -298,7 +300,7 @@ fn bump_version(krate: &Crate, crates: &[Crate]) {
}
}
rewritten = true;
new_manifest.push_str(&line.replace(&other.version, &other.next_version));
new_manifest.push_str(&line.replace(&other.version, &next_version(other)));
break;
}
if !rewritten {
@ -316,11 +318,15 @@ fn bump_version(krate: &Crate, crates: &[Crate]) {
/// repository since we're currently making major version bumps for all our
/// releases. This may end up getting tweaked as we stabilize crates and start
/// doing more minor/patch releases, but for now this should do the trick.
fn bump(version: &str) -> String {
fn bump(version: &str, patch_bump: bool) -> String {
let mut iter = version.split('.').map(|s| s.parse::<u32>().unwrap());
let major = iter.next().expect("major version");
let minor = iter.next().expect("minor version");
let patch = iter.next().expect("patch version");
if patch_bump {
return format!("{}.{}.{}", major, minor, patch + 1);
}
if major != 0 {
format!("{}.0.0", major + 1)
} else if minor != 0 {

Loading…
Cancel
Save