|
|
@@ -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" |
|
|
|