From 807b528bfb1e7c0d3b38bb35f51a9853c379c39a Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 26 Oct 2021 10:25:40 -0500 Subject: [PATCH] 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 --- .github/workflows/bump-version.yml | 86 ++++++++++++++++++++++ .github/workflows/publish-to-cratesio.yml | 24 ++++++ .github/workflows/push-tag.yml | 49 +++++++++++++ docs/contributing-release-process.md | 89 +++++++++++++++-------- docs/stability-release.md | 56 +++++++++++++- scripts/publish.rs | 34 +++++---- 6 files changed, 294 insertions(+), 44 deletions(-) create mode 100644 .github/workflows/bump-version.yml create mode 100644 .github/workflows/publish-to-cratesio.yml create mode 100644 .github/workflows/push-tag.yml diff --git a/.github/workflows/bump-version.yml b/.github/workflows/bump-version.yml new file mode 100644 index 0000000000..1d490201bc --- /dev/null +++ b/.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-< 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 diff --git a/.github/workflows/publish-to-cratesio.yml b/.github/workflows/publish-to-cratesio.yml new file mode 100644 index 0000000000..68bb056a0f --- /dev/null +++ b/.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 }} diff --git a/.github/workflows/push-tag.yml b/.github/workflows/push-tag.yml new file mode 100644 index 0000000000..09b5dbff0b --- /dev/null +++ b/.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' diff --git a/docs/contributing-release-process.md b/docs/contributing-release-process.md index 89b428e5f1..5c972e4718 100644 --- a/docs/contributing-release-process.md +++ b/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. diff --git a/docs/stability-release.md b/docs/stability-release.md index 0bc16d92a3..183f8204bc 100644 --- a/docs/stability-release.md +++ b/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 diff --git a/scripts/publish.rs b/scripts/publish.rs index be55bb0bba..848d319915 100644 --- a/scripts/publish.rs +++ b/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::().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 {