diff --git a/docs/dev/release.md b/docs/dev/release.md new file mode 100644 index 00000000000..025ca5e6ac8 --- /dev/null +++ b/docs/dev/release.md @@ -0,0 +1,85 @@ +# Release Process + +The release process of wasmer is mostly automated, including generating the CHANGELOG, +tagging the release and starting the `build.yaml` action + +In the `/scripts` folder, you will see three files: + +- `update-version.py`: iterates through all `Cargo.toml` files and bumps the version number +according to `PREVIOUS_VERSION` and `NEXT_VERSION` +- `publish.py`: resolves the dependency order and publishes all crates to crates.io +- `make-release.py`: creates a new pull request from the current master branch, generates the +CHANGELOG, waits until all checks have passed and the release PR is merged, then starts the +GitHub action to trigger the actual release on GitHub. + +In theory, all you need to do to create a new release is to look that master is green, then +run `python3 scripts/make-release.py 3.2.0` - in practice the script can sometimes lock up or +break due to unexpected delays for example it takes a couple of seconds between a pull request +being merged and master being updated. Therefore it's best to run each step individually and +make sure that every step finishes properly. + +```sh +# required before starting +gh login + +# Script will create a release PR (release-3.2.0) and loop until the +# release PR is merged into master (after checks are green) +python3 scripts/make-release.py 3.2.0 + +# After the release PR is merged, the build.yml workflow should be +# triggered automatically - if it isn't, trigger it manually +git checkout master +git tag v3.2.0 && git push origin v3.2.0 +gh workflow run build.yml --ref v3.2.0 --field release=v3.2.0 + +# After the release is done on GitHub, run the script again +# to update the release notes +python3 scripts/make-release.py 3.2.0 + +# Once the release on GitHub is properly done and verified that all +# artifacts exist, checkout the tag and run the publishing to crates.io +git checkout v3.2.0 +python3 scripts/publish.py publish +``` + +After the GitHub release (first command), the crates need to be +published to crates.io - the order is important because if anything +goes wrong in the first command or a release needs to be amended +because of last-minute fixes, we can still revert the GitHub release, +but publishing on crates.io is final because we can't yank crates +(this has caused a lot of version-renumbering issues in the past). + +## Issues to watch out for + +There are a couple of problems with the scripts that you should watch out for: + +- On the release pull request, the CHANGELOG might be generated incorrectly or with wrong line endings +- If the script fails, there should be an audible message (implemented using the `say` command), so that you + can leave the script running in the background and get notified if anything goes wrong. +- The script might not trigger the `build.yml` action, in some cases it has to be run manually +- Publishing to crates.io might fail because of new crates that have to be published manually. + - It is important to adjust the `SETTINGS` in the `publish.py` script if some crates need default-features + to be enabled when publishing + - crates that were never published before need to usually be published for the first time + by `cd lib/crate && cargo publish` +- After publishing new crates, check that the crate ownership is set to `github:wasmerio:wasmer-core`. +- The CHANGELOG is generated from the pull request titles since the last release. Sometimes these titles need + to be fixed up to make any sense for a reader +- The release notes should just highlight the most important changes for a release, not dump everything. +- The following files should be released (TODO: more consistent naming schema): + - wasmer-darwin-amd64.tar.gz + - wasmer-darwin-arm64.tar.gz + - wasmer-linux-aarch64.tar.gz + - wasmer-linux-amd64.tar.gz + - wasmer-linux-musl-amd64.tar.gz + - wasmer-windows-amd64.tar.gz + - wasmer-windows-gnu64.tar.gz + - wasmer-windows.exe + +## Videos + +- [Creating the release pull request](https://www.youtube.com/watch?v=RMPTT-rnykA) +- [Triggering the build.yml action manually](https://www.youtube.com/watch?v=7mF0nlfpQfA) + - Note that the version should always be tagged with a "v" tag + - The commit to tag for the version should be the merge commit of the release PR +- [Publishing to crates.io](https://www.youtube.com/watch?v=uLdxIr6YwuY) diff --git a/scripts/make-release.py b/scripts/make-release.py index b5e730e7156..b5d4cb143ee 100644 --- a/scripts/make-release.py +++ b/scripts/make-release.py @@ -12,6 +12,7 @@ RELEASE_VERSION="" DATE = datetime.date.today().strftime("%d/%m/%Y") SIGNOFF_REVIEWER = "syrusakbary" +TAG = "master" if len(sys.argv) > 1: RELEASE_VERSION = sys.argv[1] @@ -19,6 +20,10 @@ print("no release version as first argument") sys.exit(1) + +if len(sys.argv) > 2: + TAG = sys.argv[2] + RELEASE_VERSION_WITH_V = RELEASE_VERSION if not(RELEASE_VERSION.startswith("v")): @@ -35,7 +40,7 @@ sys.exit(1) def get_file_string(file): - file_handle = open(file, 'r') + file_handle = open(file, 'r', newline='') file_string = file_handle.read() file_handle.close() return file_string @@ -59,7 +64,7 @@ def make_release(version): temp_dir = tempfile.TemporaryDirectory() print(temp_dir.name) - if os.system("git clone https://github.com/wasmerio/wasmer --branch master --depth 1 " + temp_dir.name) != 0: + if os.system("git clone https://github.com/wasmerio/wasmer --branch " + TAG + " --depth 1 " + temp_dir.name) != 0: raise Exception("could not clone github repo") # generate changelog @@ -170,7 +175,7 @@ def make_release(version): print(line.rstrip()) raise Exception("could not run git checkout -b release-" + RELEASE_VERSION) - replace(temp_dir.name + "/CHANGELOG.md", "## **Unreleased**", "\n".join(changelog)) + replace(temp_dir.name + "/CHANGELOG.md", "## **Unreleased**", "\r\n".join(changelog)) proc = subprocess.Popen(['git','commit', "-am", "Update CHANGELOG"], stdout = subprocess.PIPE, cwd = temp_dir.name) proc.wait() @@ -231,75 +236,25 @@ def make_release(version): proc = subprocess.Popen(['gh','pr', "checks", pr_number], stdout = subprocess.PIPE, cwd = temp_dir.name) proc.wait() - bors_failed = False all_checks_have_passed = True - if proc.stderr is not None: - for line in proc.stderr: - if "no checks reported" in line: - all_checks_have_passed = False - - if all_checks_have_passed: - for line in proc.stdout: - line = line.decode("utf-8").rstrip() - print("---- " + line) - if "no checks reported" in line: - all_checks_have_passed = False - if line.startswith("*"): - all_checks_have_passed = False - if "pending" in line and not("bors" in line): - all_checks_have_passed = False - if line.startswith("X"): - raise Exception("check failed") - if "fail" in line and "bors" in line: - bors_failed = True - if "pending" in line and "bors" in line: - bors_failed = True - if "fail" in line and not("bors" in line): - raise Exception("check failed") + print("Waiting for checks to pass... PR " + pr_number + " https://github.com/wasmerio/wasmer/pull/" + pr_number) + print("") + + for line in proc.stdout: + line = line.decode("utf-8").rstrip() + print(" " + line) + if "no checks reported" in line: + all_checks_have_passed = False + if line.startswith("*") or "pending" in line: + all_checks_have_passed = False + if line.startswith("X") or "fail" in line: + raise Exception("check failed") if all_checks_have_passed: - if proc.returncode != 0 and not(bors_failed): - raise Exception("failed to list checks with: gh pr checks " + pr_number) break else: - print("Waiting for checks to pass... PR " + pr_number + " https://github.com/wasmerio/wasmer/pull/" + pr_number) - time.sleep(30) - - if not(already_released): - # PR created, checks have passed, run python script and publish to crates.io - proc = subprocess.Popen(['gh','pr', "comment", pr_number, "--body", "[bot] Checks have passed. Publishing to crates.io..."], stdout = subprocess.PIPE, cwd = temp_dir.name) - proc.wait() - - proc = subprocess.Popen(['python3',temp_dir.name + "/scripts/publish.py", "publish"], stdout = subprocess.PIPE, cwd = temp_dir.name) - while True: - line = proc.stdout.readline() - line = line.decode("utf-8").rstrip() - print(line.rstrip()) - if not line: break - - proc.wait() - - if proc.returncode != 0: - log = ["[bot] Failed to publish to crates.io"] - log.append("") - log.append("```") - for line in proc.stdout: - line = line.decode("utf-8").rstrip() - log.append("stdout: " + line) - log.append("```") - log.append("```") - if proc.stderr is not None: - for line in proc.stderr: - line = line.decode("utf-8").rstrip() - log.append("stderr: " + line) - log.append("```") - proc = subprocess.Popen(['gh','pr', "comment", pr_number, "--body", "\r\n".join(log)], stdout = subprocess.PIPE, cwd = temp_dir.name) - proc.wait() - raise Exception("Failed to publish to crates.io: " + "\r\n".join(log)) - else: - proc = subprocess.Popen(['gh','pr', "comment", pr_number, "--body", "[bot] Successfully published wasmer version " + RELEASE_VERSION + " to crates.io"], stdout = subprocess.PIPE, cwd = temp_dir.name) - proc.wait() + time.sleep(5) last_commit = "" proc = subprocess.Popen(['git','log'], stdout = subprocess.PIPE, cwd = temp_dir.name) @@ -324,7 +279,7 @@ def make_release(version): raise Exception("could not commit checkout master " + RELEASE_VERSION_WITH_V) if not(already_released): - proc = subprocess.Popen(['gh','pr', "comment", pr_number, "--body", "bors r+"], stdout = subprocess.PIPE, cwd = temp_dir.name) + proc = subprocess.Popen(['gh','pr', "merge", "--auto", pr_number, "--merge", "--delete-branch"], stdout = subprocess.PIPE, cwd = temp_dir.name) proc.wait() # wait for bors to merge PR