fmt/support/release.py

210 lines
5.9 KiB
Python
Executable File

#!/usr/bin/env python3
"""Make a release.
Usage:
release.py [<branch>]
For the release command $FMT_TOKEN should contain a GitHub personal access token
obtained from https://github.com/settings/tokens.
"""
from __future__ import print_function
import datetime
import errno
import fileinput
import json
import os
import re
import shutil
import sys
import urllib.request
from subprocess import check_call
import docopt
class Git:
def __init__(self, dir):
self.dir = dir
def call(self, method, args, **kwargs):
return check_call(["git", method] + list(args), **kwargs)
def add(self, *args):
return self.call("add", args, cwd=self.dir)
def checkout(self, *args):
return self.call("checkout", args, cwd=self.dir)
def clean(self, *args):
return self.call("clean", args, cwd=self.dir)
def clone(self, *args):
return self.call("clone", list(args) + [self.dir])
def commit(self, *args):
return self.call("commit", args, cwd=self.dir)
def pull(self, *args):
return self.call("pull", args, cwd=self.dir)
def push(self, *args):
return self.call("push", args, cwd=self.dir)
def reset(self, *args):
return self.call("reset", args, cwd=self.dir)
def update(self, *args):
clone = not os.path.exists(self.dir)
if clone:
self.clone(*args)
return clone
def clean_checkout(repo, branch):
repo.clean("-f", "-d")
repo.reset("--hard")
repo.checkout(branch)
class Runner:
def __init__(self, cwd):
self.cwd = cwd
def __call__(self, *args, **kwargs):
kwargs["cwd"] = kwargs.get("cwd", self.cwd)
check_call(args, **kwargs)
def create_build_env():
"""Create a build environment."""
class Env:
pass
env = Env()
env.fmt_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
env.build_dir = "build"
env.fmt_repo = Git(os.path.join(env.build_dir, "fmt"))
return env
if __name__ == "__main__":
args = docopt.docopt(__doc__)
env = create_build_env()
fmt_repo = env.fmt_repo
branch = args.get("<branch>")
if branch is None:
branch = "master"
if not fmt_repo.update("-b", branch, "git@github.com:fmtlib/fmt"):
clean_checkout(fmt_repo, branch)
# Update the date in the changelog and extract the version and the first
# section content.
changelog = "ChangeLog.md"
changelog_path = os.path.join(fmt_repo.dir, changelog)
is_first_section = True
first_section = []
for i, line in enumerate(fileinput.input(changelog_path, inplace=True)):
if i == 0:
version = re.match(r"# (.*) - TBD", line).group(1)
line = "# {} - {}\n".format(version, datetime.date.today().isoformat())
elif not is_first_section:
pass
elif line.startswith("#"):
is_first_section = False
else:
first_section.append(line)
sys.stdout.write(line)
if first_section[0] == "\n":
first_section.pop(0)
ns_version = None
base_h_path = os.path.join(fmt_repo.dir, "include", "fmt", "base.h")
for line in fileinput.input(base_h_path):
m = re.match(r"\s*inline namespace v(.*) .*", line)
if m:
ns_version = m.group(1)
break
major_version = version.split(".")[0]
if not ns_version or ns_version != major_version:
raise Exception(f"Version mismatch {ns_version} != {major_version}")
# Workaround GitHub-flavored Markdown treating newlines as <br>.
changes = ""
code_block = False
stripped = False
for line in first_section:
if re.match(r"^\s*```", line):
code_block = not code_block
changes += line
stripped = False
continue
if code_block:
changes += line
continue
if line == "\n" or re.match(r"^\s*\|.*", line):
if stripped:
changes += "\n"
stripped = False
changes += line
continue
if stripped:
line = " " + line.lstrip()
changes += line.rstrip()
stripped = True
fmt_repo.checkout("-B", "release")
fmt_repo.add(changelog)
fmt_repo.commit("-m", "Update version")
# Build the docs and package.
run = Runner(fmt_repo.dir)
run("cmake", ".")
run("make", "doc", "package_source")
# Create a release on GitHub.
fmt_repo.push("origin", "release")
auth_headers = {"Authorization": "token " + os.getenv("FMT_TOKEN")}
req = urllib.request.Request(
"https://api.github.com/repos/fmtlib/fmt/releases",
data=json.dumps(
{
"tag_name": version,
"target_commitish": "release",
"body": changes,
"draft": True,
}
).encode("utf-8"),
headers=auth_headers,
method="POST",
)
with urllib.request.urlopen(req) as response:
if response.status != 201:
raise Exception(
f"Failed to create a release " + "{response.status} {response.reason}"
)
response_data = json.loads(response.read().decode("utf-8"))
id = response_data["id"]
# Upload the package.
uploads_url = "https://uploads.github.com/repos/fmtlib/fmt/releases"
package = "fmt-{}.zip".format(version)
req = urllib.request.Request(
f"{uploads_url}/{id}/assets?name={package}",
headers={"Content-Type": "application/zip"} | auth_headers,
data=open("build/fmt/" + package, "rb").read(),
method="POST",
)
with urllib.request.urlopen(req) as response:
if response.status != 201:
raise Exception(
f"Failed to upload an asset " "{response.status} {response.reason}"
)
short_version = ".".join(version.split(".")[:-1])
check_call(["./mkdocs", "deploy", short_version])