Rewrite release preparation automation

This commit is contained in:
Pradyun Gedam 2019-11-03 00:36:57 +05:30
parent 3cb4bdd5e9
commit 9e3e82e081
No known key found for this signature in database
GPG Key ID: DA17C4B29CB32E4B
2 changed files with 134 additions and 89 deletions

View File

@ -4,13 +4,16 @@
# The following comment should be removed at some point in the future.
# mypy: disallow-untyped-defs=False
import io
import os
import shutil
import subprocess
import sys
import nox
sys.path.append(".")
from tools.automation import release # isort:skip # noqa
sys.path.pop()
nox.options.reuse_existing_virtualenvs = True
nox.options.sessions = ["lint"]
@ -27,29 +30,6 @@ AUTHORS_FILE = "AUTHORS.txt"
VERSION_FILE = "src/pip/__init__.py"
def get_author_list():
"""Get the list of authors from Git commits.
"""
# subprocess because session.run doesn't give us stdout
result = subprocess.run(
["git", "log", "--use-mailmap", "--format=%aN <%aE>"],
capture_output=True,
encoding="utf-8",
)
# Create a unique list.
authors = []
seen_authors = set()
for author in result.stdout.splitlines():
author = author.strip()
if author.lower() not in seen_authors:
seen_authors.add(author.lower())
authors.append(author)
# Sort our list of Authors by their case insensitive name
return sorted(authors, key=lambda x: x.lower())
def run_with_protected_pip(session, *arguments):
"""Do a session.run("pip", *arguments), using a "protected" pip.
@ -81,11 +61,6 @@ def should_update_common_wheels():
return need_to_repopulate
def update_version_file(new_version):
with open(VERSION_FILE, "w", encoding="utf-8") as f:
f.write('__version__ = "{}"\n'.format(new_version))
# -----------------------------------------------------------------------------
# Development Commands
# These are currently prototypes to evaluate whether we want to switch over
@ -174,70 +149,36 @@ def lint(session):
# -----------------------------------------------------------------------------
# Release Commands
# -----------------------------------------------------------------------------
@nox.session(python=False)
def generate_authors(session):
# Get our list of authors
session.log("Collecting author names")
authors = get_author_list()
@nox.session(name="prepare-release")
def prepare_release(session):
version = release.get_version_from_arguments(session.posargs)
if not version:
session.error("Usage: nox -s prepare-release -- YY.N[.P]")
# Write our authors to the AUTHORS file
session.log("Writing AUTHORS")
with io.open(AUTHORS_FILE, "w", encoding="utf-8") as fp:
fp.write(u"\n".join(authors))
fp.write(u"\n")
session.log("# Ensure nothing is staged")
if release.modified_files_in_git("--staged"):
session.error("There are files staged in git")
@nox.session
def generate_news(session):
session.log("Generating NEWS")
session.install("towncrier")
# You can pass 2 possible arguments: --draft, --yes
session.run("towncrier", *session.posargs)
@nox.session
def release(session):
assert len(session.posargs) == 1, "A version number is expected"
new_version = session.posargs[0]
parts = new_version.split('.')
# Expect YY.N or YY.N.P
assert 2 <= len(parts) <= 3, parts
# Only integers
parts = list(map(int, parts))
session.log("Generating commits for version {}".format(new_version))
session.log("Checking that nothing is staged")
# Non-zero exit code means that something is already staged
session.run("git", "diff", "--staged", "--exit-code", external=True)
session.log(f"Updating {AUTHORS_FILE}")
generate_authors(session)
if subprocess.run(["git", "diff", "--exit-code"]).returncode:
session.run("git", "add", AUTHORS_FILE, external=True)
session.run(
"git", "commit", "-m", f"Updating {AUTHORS_FILE}",
external=True,
session.log(f"# Updating {AUTHORS_FILE}")
release.generate_authors(AUTHORS_FILE)
if release.modified_files_in_git():
release.commit_file(
session, AUTHORS_FILE, message=f"Update {AUTHORS_FILE}",
)
else:
session.log(f"No update needed for {AUTHORS_FILE}")
session.log(f"# No changes to {AUTHORS_FILE}")
session.log("Generating NEWS")
session.install("towncrier")
session.run("towncrier", "--yes", "--version", new_version)
session.log("# Generating NEWS")
release.generate_news(session, version)
session.log("Updating version")
update_version_file(new_version)
session.run("git", "add", VERSION_FILE, external=True)
session.run("git", "commit", "-m", f"Release {new_version}", external=True)
session.log(f"# Bumping for release {version}")
release.update_version_file(version, VERSION_FILE)
release.commit_file(session, VERSION_FILE, message="Bump for release")
session.log("Tagging release")
session.run(
"git", "tag", "-m", f"Release {new_version}", new_version,
external=True,
)
session.log("# Tagging release")
release.create_git_tag(session, version, message=f"Release {version}")
next_dev_version = f"{parts[0]}.{parts[1] + 1}.dev0"
update_version_file(next_dev_version)
session.run("git", "add", VERSION_FILE, external=True)
session.run("git", "commit", "-m", "Back to development", external=True)
session.log("# Bumping for development")
next_dev_version = release.get_next_development_version(version)
release.update_version_file(next_dev_version, VERSION_FILE)
release.commit_file(session, VERSION_FILE, message="Bump for development")

View File

@ -0,0 +1,104 @@
"""Helpers for release automation.
These are written according to the order they are called in.
"""
import io
import subprocess
def get_version_from_arguments(arguments):
"""Checks the arguments passed to `nox -s release`.
If there is only 1 argument that looks like a pip version, returns that.
Otherwise, returns None.
"""
if len(arguments) != 1:
return None
version = arguments[0]
parts = version.split('.')
if not 2 <= len(parts) <= 3:
# Not of the form: YY.N or YY.N.P
return None
if not all(part.isdigit() for part in parts):
# Not all segments are integers.
return None
# All is good.
return version
def modified_files_in_git(*args):
return subprocess.run(
["git", "diff", "--no-patch", "--exit-code", *args],
capture_output=True,
).returncode
def get_author_list():
"""Get the list of authors from Git commits.
"""
# subprocess because session.run doesn't give us stdout
result = subprocess.run(
["git", "log", "--use-mailmap", "--format=%aN <%aE>"],
capture_output=True,
encoding="utf-8",
)
# Create a unique list.
authors = []
seen_authors = set()
for author in result.stdout.splitlines():
author = author.strip()
if author.lower() not in seen_authors:
seen_authors.add(author.lower())
authors.append(author)
# Sort our list of Authors by their case insensitive name
return sorted(authors, key=lambda x: x.lower())
def generate_authors(filename: str) -> None:
# Get our list of authors
authors = get_author_list()
# Write our authors to the AUTHORS file
with io.open(filename, "w", encoding="utf-8") as fp:
fp.write(u"\n".join(authors))
fp.write(u"\n")
def commit_file(session, filename, *, message):
session.run("git", "add", filename, external=True, silent=True)
session.run("git", "commit", "-m", message, external=True, silent=True)
def generate_news(session, version):
session.install("towncrier")
session.run("towncrier", "--yes", "--version", version, silent=True)
def update_version_file(new_version, filepath):
with open(filepath, "w", encoding="utf-8") as f:
f.write('__version__ = "{}"\n'.format(new_version))
def create_git_tag(session, tag_name, *, message):
session.run("git", "tag", "-m", message, tag_name, external=True, silent=True)
def get_next_development_version(version):
major, minor, *_ = map(int, version.split("."))
# We have at most 4 releases, starting with 0. Once we reach 3, we'd want
# to roll-over to the next year's release numbers.
if minor == 3:
major += 1
minor = 0
else:
minor += 1
return f"{major}.{minor}.dev0"