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
This commit is contained in:
86
.github/workflows/bump-version.yml
vendored
Normal file
86
.github/workflows/bump-version.yml
vendored
Normal file
@@ -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
vendored
Normal file
24
.github/workflows/publish-to-cratesio.yml
vendored
Normal file
@@ -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
vendored
Normal file
49
.github/workflows/push-tag.yml
vendored
Normal file
@@ -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'
|
||||
@@ -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:
|
||||
## Releasing a major version
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
And that's it, then you've done a Wasmtime release.
|
||||
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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user