Skip to content

Commit

Permalink
Merge pull request #3539 from wasmerio/release-process
Browse files Browse the repository at this point in the history
Document release process and update release script
  • Loading branch information
fschutt authored Feb 21, 2023
2 parents c9e658e + afe2e09 commit ec206dc
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 67 deletions.
85 changes: 85 additions & 0 deletions docs/dev/release.md
Original file line number Diff line number Diff line change
@@ -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)
89 changes: 22 additions & 67 deletions scripts/make-release.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,18 @@
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]
else:
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")):
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down

0 comments on commit ec206dc

Please sign in to comment.