Skip to content

Commit

Permalink
Merge pull request #41 from danabens/release-script
Browse files Browse the repository at this point in the history
add release script and workflow
  • Loading branch information
l2yao authored Jan 31, 2020
2 parents ff4fedf + 9f695a2 commit a815544
Show file tree
Hide file tree
Showing 9 changed files with 275 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Build and Release
name: Build and Publish

on:
pull_request:
Expand Down
26 changes: 26 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Release

on:
schedule:
# every weekday at 9:00 AM
- cron: '0 9 * * 1-5'

jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Setup Python 3.7
uses: actions/setup-python@v1
with:
python-version: 3.7
- name: Install Dependencies
run: pip install tox
- name: Unit Tests
run: tox -e py37 -- tests/unit
env:
AWS_DEFAULT_REGION: us-west-2
- name: Create Release
run: tox -e release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
11 changes: 11 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,17 @@ Please remember to:

### Committing Your Change

Prefix your commit message with one of the following to indicate the version part incremented in the next release:

| Commit Message Prefix | Version Part Incremented
| --- | ---
| break, breaking | major
| feat, feature | minor
| depr, deprecation | minor
| change, fix | patch
| doc, documentation | patch
| default | patch

For the message use imperative style and keep things concise but informative. See [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/) for guidance.


Expand Down
67 changes: 67 additions & 0 deletions scripts/release.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""
Invoke development tasks.
"""
from subprocess import check_output
from version import next_version_from_current_version
from subject_parser import SubjectParser
from release_manager import ReleaseManager


def recent_changes_to_src(last_version):
stdout = check_output(["git", "log", "{}..HEAD".format(last_version), "--name-only", "--pretty=format: master"])
stdout = stdout.decode("utf-8")
lines = stdout.splitlines()
src_lines = filter(lambda l: l.startswith("src"), lines)
return src_lines


def get_changes(last_version):
stdout = check_output(["git", "log", "{}..HEAD".format(last_version), "--pretty=format:%s"])
stdout = stdout.decode("utf-8")
changes = list(map(lambda line: line.strip(), stdout.splitlines()))
print(f"{len(changes)} changes since last release {last_version}")
return changes


def get_next_version(last_version, increment_type):
# remove the 'v' prefix
last_version = last_version[1:]
return next_version_from_current_version(last_version, increment_type)


def get_version_increment_type(last_version):
stdout = check_output(["git", "log", "{}..HEAD".format(last_version), "--pretty=format:%s"])
stdout = stdout.decode("utf-8")
subjects = stdout.splitlines()
parsed_subjects = SubjectParser(subjects)
return parsed_subjects.increment_type()


def release():
"""Creates a github release."""

# get the last release tag
stdout = check_output(["git", "describe", "--abbrev=0", "--tags"])
stdout = stdout.decode("utf-8")
last_version = stdout.strip()

if not recent_changes_to_src(last_version):
print("Nothing to release.")
return

changes = get_changes(last_version)

increment_type = get_version_increment_type(last_version)

next_version = get_next_version(last_version, increment_type)

manager = ReleaseManager(str(next_version), changes)
manager.create_release()


def main():
release()


if __name__ == "__main__":
main()
26 changes: 26 additions & 0 deletions scripts/release_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from github import Github
import os
from pathlib import Path


class ReleaseManager:
def __init__(self, version, changes):
self._version = version
self._changes = changes
self._token = os.getenv("GITHUB_TOKEN")
self._repo = os.getenv("GITHUB_REPOSITORY")
if not self._token or not self._repo:
raise ValueError("Missing required environment variables.")

def create_release(self):
tag = "v" + self._version
name = f"Sagemaker Experiment SDK {tag}"
template_text = Path(__file__).parent.joinpath("release_template.rst").read_text(encoding="UTF-8")
change_list_content = "\n".join(list(map(lambda c: f"- {c}", self._changes)))
message = template_text.format(version=tag, changes=change_list_content)
g = Github(self._token)
repo = g.get_repo(self._repo)
# keep draft=True release script manually verified working
repo.create_git_release(tag=tag, name=name, message=message, draft=True, prerelease=False)
print(f"Created release {name}")
print(f"See it at https://github.com/{self._repo}/releases")
8 changes: 8 additions & 0 deletions scripts/release_template.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
SageMaker Experiments {version} release!

Changes:
{changes}

You can upgrade from PyPI via:

pip install -U sagemaker-experiments
69 changes: 69 additions & 0 deletions scripts/subject_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import logging
import re


logger = logging.getLogger(__name__)


class SubjectParser:
"""
Parses git commit subject lines to determine the type of change (breaking, feature, fix, etc...)
"""

# order must be aligned with associated increment_type (major...post)
_CHANGE_TYPES = ["breaking", "deprecation", "feature", "fix", "documentation", "infrastructure"]

_PARSE_SUBJECT_REGEX = re.compile(
r"""
(?:(?P<label>break(?:ing)?|feat(?:ure)?|depr(?:ecation)?|change|fix|doc(?:umentation)?)\s*:)?
""",
re.VERBOSE | re.IGNORECASE,
)

_CANONICAL_LABELS = {
"break": "breaking",
"feat": "feature",
"depr": "deprecation",
"change": "fix",
"doc": "documentation",
}

_CHANGE_TO_INCREMENT_TYPE_MAP = {
"breaking": "major",
"feature": "minor",
"deprecation": "minor",
"fix": "patch",
"change": "patch",
"documentation": "patch",
}

_DEFAULT_LABEL = "fix"

def __init__(self, subjects):
self._groups = {}
self._add_subjects(subjects)

def _add_subjects(self, subjects):
for subject in subjects:
self._parse_subject(subject)

def _parse_subject(self, subject):
label = None
match = SubjectParser._PARSE_SUBJECT_REGEX.search(subject)

if match:
label = match.group("label") or SubjectParser._DEFAULT_LABEL
label = SubjectParser._CANONICAL_LABELS.get(label, label)
else:
print(f"no match {subject}")
label = SubjectParser._DEFAULT_LABEL

if label in self._groups:
self._groups[label].append(subject)

def increment_type(self):
for change_type in SubjectParser._CHANGE_TYPES:
if change_type in self._groups:
return SubjectParser._CHANGE_TO_INCREMENT_TYPE_MAP[change_type]

return "patch"
56 changes: 56 additions & 0 deletions scripts/version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import re


# a subset of PEP 440
_VERSION_REGEX = re.compile(
r"""
^\s*
v?
(?P<major>\d+)
(?:\.(?P<minor>\d+))?
(?:\.(?P<patch>\d+))?
\s*$
""",
re.VERBOSE | re.IGNORECASE,
)


class Version:
"""
Represents a major.minor.patch version string
"""

def __init__(self, major, minor=0, patch=0):
self.major = major
self.minor = minor
self.patch = patch

self.tag = f"v{str(self)}"

def __str__(self):
parts = [str(x) for x in [self.major, self.minor, self.patch]]

return ".".join(parts).lower()

def increment(self, increment_type):
incr = None
if increment_type == "major":
incr = Version(self.major + 1)
elif increment_type == "minor":
incr = Version(self.major, self.minor + 1)
elif increment_type == "patch":
incr = Version(self.major, self.minor, self.patch + 1)

return incr


def parse(version):
match = _VERSION_REGEX.search(version)
if not match:
raise ValueError(f"invalid version: {version}")

return Version(int(match.group("major") or 0), int(match.group("minor") or 0), int(match.group("patch") or 0),)


def next_version_from_current_version(current_version, increment_type):
return parse(current_version).increment(increment_type)
12 changes: 11 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,14 @@ deps =
sphinx-rtd-theme
readthedocs-sphinx-ext
commands =
sphinx-build -T -W -b html -d _build/doctrees-readthedocs -D language=en . _build/html
sphinx-build -T -W -b html -d _build/doctrees-readthedocs -D language=en . _build/html

[testenv:release]
description = create a GitHub release, version number is derived from commit messages
basepython = python3
passenv =
GITHUB_*
deps =
PyGithub
pathlib
commands = python scripts/release.py {posargs}

0 comments on commit a815544

Please sign in to comment.