|
@@ -18,10 +18,12 @@
|
|
|
"""
|
|
|
|
|
|
import glob
|
|
|
+import json
|
|
|
import os
|
|
|
import re
|
|
|
import subprocess
|
|
|
import sys
|
|
|
+import time
|
|
|
import urllib.request
|
|
|
from os import path
|
|
|
from tempfile import TemporaryDirectory
|
|
@@ -71,18 +73,21 @@ def cli() -> None:
|
|
|
|
|
|
./scripts-dev/release.py tag
|
|
|
|
|
|
- # ... wait for assets to build ...
|
|
|
+ # wait for assets to build, either manually or with:
|
|
|
+ ./scripts-dev/release.py wait-for-actions
|
|
|
|
|
|
./scripts-dev/release.py publish
|
|
|
|
|
|
./scripts-dev/release.py upload
|
|
|
|
|
|
- # Optional: generate some nice links for the announcement
|
|
|
-
|
|
|
./scripts-dev/release.py merge-back
|
|
|
|
|
|
+ # Optional: generate some nice links for the announcement
|
|
|
./scripts-dev/release.py announce
|
|
|
|
|
|
+ Alternatively, `./scripts-dev/release.py full` will do all the above
|
|
|
+ as well as guiding you through the manual steps.
|
|
|
+
|
|
|
If the env var GH_TOKEN (or GITHUB_TOKEN) is set, or passed into the
|
|
|
`tag`/`publish` command, then a new draft release will be created/published.
|
|
|
"""
|
|
@@ -90,6 +95,10 @@ def cli() -> None:
|
|
|
|
|
|
@cli.command()
|
|
|
def prepare() -> None:
|
|
|
+ _prepare()
|
|
|
+
|
|
|
+
|
|
|
+def _prepare() -> None:
|
|
|
"""Do the initial stages of creating a release, including creating release
|
|
|
branch, updating changelog and pushing to GitHub.
|
|
|
"""
|
|
@@ -284,6 +293,10 @@ def prepare() -> None:
|
|
|
@cli.command()
|
|
|
@click.option("--gh-token", envvar=["GH_TOKEN", "GITHUB_TOKEN"])
|
|
|
def tag(gh_token: Optional[str]) -> None:
|
|
|
+ _tag(gh_token)
|
|
|
+
|
|
|
+
|
|
|
+def _tag(gh_token: Optional[str]) -> None:
|
|
|
"""Tags the release and generates a draft GitHub release"""
|
|
|
|
|
|
# Make sure we're in a git repo.
|
|
@@ -374,6 +387,10 @@ def tag(gh_token: Optional[str]) -> None:
|
|
|
@cli.command()
|
|
|
@click.option("--gh-token", envvar=["GH_TOKEN", "GITHUB_TOKEN"], required=True)
|
|
|
def publish(gh_token: str) -> None:
|
|
|
+ _publish(gh_token)
|
|
|
+
|
|
|
+
|
|
|
+def _publish(gh_token: str) -> None:
|
|
|
"""Publish release on GitHub."""
|
|
|
|
|
|
# Make sure we're in a git repo.
|
|
@@ -411,6 +428,10 @@ def publish(gh_token: str) -> None:
|
|
|
|
|
|
@cli.command()
|
|
|
def upload() -> None:
|
|
|
+ _upload()
|
|
|
+
|
|
|
+
|
|
|
+def _upload() -> None:
|
|
|
"""Upload release to pypi."""
|
|
|
|
|
|
current_version = get_package_version()
|
|
@@ -479,8 +500,75 @@ def _merge_into(repo: Repo, source: str, target: str) -> None:
|
|
|
repo.remote().push()
|
|
|
|
|
|
|
|
|
+@cli.command()
|
|
|
+@click.option("--gh-token", envvar=["GH_TOKEN", "GITHUB_TOKEN"], required=False)
|
|
|
+def wait_for_actions(gh_token: Optional[str]) -> None:
|
|
|
+ _wait_for_actions(gh_token)
|
|
|
+
|
|
|
+
|
|
|
+def _wait_for_actions(gh_token: Optional[str]) -> None:
|
|
|
+ # Find out the version and tag name.
|
|
|
+ current_version = get_package_version()
|
|
|
+ tag_name = f"v{current_version}"
|
|
|
+
|
|
|
+ # Authentication is optional on this endpoint,
|
|
|
+ # but use a token if we have one to reduce the chance of being rate-limited.
|
|
|
+ url = f"https://api.github.com/repos/matrix-org/synapse/actions/runs?branch={tag_name}"
|
|
|
+ headers = {"Accept": "application/vnd.github+json"}
|
|
|
+ if gh_token is not None:
|
|
|
+ headers["authorization"] = f"token {gh_token}"
|
|
|
+ req = urllib.request.Request(url, headers=headers)
|
|
|
+
|
|
|
+ time.sleep(10 * 60)
|
|
|
+ while True:
|
|
|
+ time.sleep(5 * 60)
|
|
|
+ response = urllib.request.urlopen(req)
|
|
|
+ resp = json.loads(response.read())
|
|
|
+
|
|
|
+ if len(resp["workflow_runs"]) == 0:
|
|
|
+ continue
|
|
|
+
|
|
|
+ if all(
|
|
|
+ workflow["status"] != "in_progress" for workflow in resp["workflow_runs"]
|
|
|
+ ):
|
|
|
+ success = (
|
|
|
+ workflow["status"] == "completed" for workflow in resp["workflow_runs"]
|
|
|
+ )
|
|
|
+ if success:
|
|
|
+ _notify("Workflows successful. You can now continue the release.")
|
|
|
+ else:
|
|
|
+ _notify("Workflows failed.")
|
|
|
+ click.confirm("Continue anyway?", abort=True)
|
|
|
+
|
|
|
+ break
|
|
|
+
|
|
|
+
|
|
|
+def _notify(message: str) -> None:
|
|
|
+ # Send a bell character. Most terminals will play a sound or show a notification
|
|
|
+ # for this.
|
|
|
+ click.echo(f"\a{message}")
|
|
|
+
|
|
|
+ # Try and run notify-send, but don't raise an Exception if this fails
|
|
|
+ # (This is best-effort)
|
|
|
+ # TODO Support other platforms?
|
|
|
+ subprocess.run(
|
|
|
+ [
|
|
|
+ "notify-send",
|
|
|
+ "--app-name",
|
|
|
+ "Synapse Release Script",
|
|
|
+ "--expire-time",
|
|
|
+ "3600000",
|
|
|
+ message,
|
|
|
+ ]
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
@cli.command()
|
|
|
def merge_back() -> None:
|
|
|
+ _merge_back()
|
|
|
+
|
|
|
+
|
|
|
+def _merge_back() -> None:
|
|
|
"""Merge the release branch back into the appropriate branches.
|
|
|
All branches will be automatically pulled from the remote and the results
|
|
|
will be pushed to the remote."""
|
|
@@ -519,6 +607,10 @@ def merge_back() -> None:
|
|
|
|
|
|
@cli.command()
|
|
|
def announce() -> None:
|
|
|
+ _announce()
|
|
|
+
|
|
|
+
|
|
|
+def _announce() -> None:
|
|
|
"""Generate markdown to announce the release."""
|
|
|
|
|
|
current_version = get_package_version()
|
|
@@ -548,10 +640,56 @@ Announce the release in
|
|
|
- #homeowners:matrix.org (Synapse Announcements), bumping the version in the topic
|
|
|
- #synapse:matrix.org (Synapse Admins), bumping the version in the topic
|
|
|
- #synapse-dev:matrix.org
|
|
|
-- #synapse-package-maintainers:matrix.org"""
|
|
|
+- #synapse-package-maintainers:matrix.org
|
|
|
+
|
|
|
+Ask the designated people to do the blog and tweets."""
|
|
|
)
|
|
|
|
|
|
|
|
|
+@cli.command()
|
|
|
+@click.option("--gh-token", envvar=["GH_TOKEN", "GITHUB_TOKEN"], required=True)
|
|
|
+def full(gh_token: str) -> None:
|
|
|
+ click.echo("1. If this is a security release, read the security wiki page.")
|
|
|
+ click.echo("2. Check for any release blockers before proceeding.")
|
|
|
+ click.echo(" https://github.com/matrix-org/synapse/labels/X-Release-Blocker")
|
|
|
+
|
|
|
+ click.confirm("Ready?", abort=True)
|
|
|
+
|
|
|
+ click.echo("\n*** prepare ***")
|
|
|
+ _prepare()
|
|
|
+
|
|
|
+ click.echo("Deploy to matrix.org and ensure that it hasn't fallen over.")
|
|
|
+ click.echo("Remember to silence the alerts to prevent alert spam.")
|
|
|
+ click.confirm("Deployed?", abort=True)
|
|
|
+
|
|
|
+ click.echo("\n*** tag ***")
|
|
|
+ _tag(gh_token)
|
|
|
+
|
|
|
+ click.echo("\n*** wait for actions ***")
|
|
|
+ _wait_for_actions(gh_token)
|
|
|
+
|
|
|
+ click.echo("\n*** publish ***")
|
|
|
+ _publish(gh_token)
|
|
|
+
|
|
|
+ click.echo("\n*** upload ***")
|
|
|
+ _upload()
|
|
|
+
|
|
|
+ click.echo("\n*** merge back ***")
|
|
|
+ _merge_back()
|
|
|
+
|
|
|
+ click.echo("\nUpdate the Debian repository")
|
|
|
+ click.confirm("Started updating Debian repository?", abort=True)
|
|
|
+
|
|
|
+ click.echo("\nWait for all release methods to be ready.")
|
|
|
+ # Docker should be ready because it was done by the workflows earlier
|
|
|
+ # PyPI should be ready because we just ran upload().
|
|
|
+ # TODO Automatically poll until the Debs have made it to packages.matrix.org
|
|
|
+ click.confirm("Debs ready?", abort=True)
|
|
|
+
|
|
|
+ click.echo("\n*** announce ***")
|
|
|
+ _announce()
|
|
|
+
|
|
|
+
|
|
|
def get_package_version() -> version.Version:
|
|
|
version_string = subprocess.check_output(["poetry", "version", "--short"]).decode(
|
|
|
"utf-8"
|