Skip to content

add release script and workflow #41

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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}