Skip to content
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

feat: add release automation #233

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
48 changes: 48 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# This workflow is triggered when a PR is merged whose source branch's name starts with "release" and whose target branch is "main".
# It checks for the current package version (in pyproject.toml) and the latest tag on GitHub.
# If package version is more recent, it builds the project, uploads to PYPI, creates a Tag and makes a release on GitHub.
# Secrets PYPI_USERNAME and PYPI_PASSWORD are required!

name: Check and release
on:
pull_request:
branches: [main]
types: [closed]

jobs:
build:
defaults:
run:
shell: bash
working-directory: ./python

if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release')
runs-on: ubuntu-latest
steps:

- name: Checkout Repo
uses: actions/checkout@v3

- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.11'

- name: Install Poetry
run: |
export POETRY_HOME=/opt/poetry
python3 -m venv $POETRY_HOME
$POETRY_HOME/bin/pip install poetry==1.4.0
$POETRY_HOME/bin/poetry --version

- name: Run release script
env:
PYPI_USERNAME: __token__
PYPI_PASSWORD: ${{secrets.FETCHBOT_PYPI_TOKEN}}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ github.token }}
run: |
pip install tomli packaging poetry
git config --global user.email "[email protected]"
git config --global user.name "CI BOT"
python3 ./scripts/do_release.py
137 changes: 137 additions & 0 deletions scripts/do_release.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
"""Release automation script."""

import os
import subprocess
import sys
from pathlib import Path

import tomli
from packaging.version import Version


ROOT = Path(__file__).parent.parent


class EnvCredentials:
"""Credentials from env variables."""

@property
def pypi_username(self) -> str:
"""Get PYPI username."""
return os.environ.get("PYPI_USERNAME") or ""

@property
def pypi_password(self) -> str:
"""Get PYPI password."""
return os.environ.get("PYPI_PASSWORD") or ""


def get_the_latest_release_version() -> Version:
"""Get release version from gihtub tags."""
text = subprocess.check_output("git ls-remote --tags origin", shell=True, text=True)
tags = [i.split("\t")[1].strip() for i in text.splitlines()]
tags = [i for i in tags if i.startswith("refs/tags/v") and not i.endswith("^{}")]
versions = [i.replace("refs/tags/v", "") for i in tags]
return Version(versions[-1])


def get_current_version() -> Version:
"""Get current code version."""
text = (ROOT / "pyproject.toml").read_text()
version = tomli.loads(text)["tool"]["poetry"]["version"]
return Version(version)


def do_we_need_to_release() -> bool:
"""Check is code version is newer than on github."""
current_version = get_current_version()
released_version = get_the_latest_release_version()
return current_version > released_version


def make_tag(current_version: Version) -> None:
"""Make git tag."""
subprocess.check_call(
f"git tag v{current_version} -m 'Release {current_version}'", shell=True
)


def push_tag(current_version) -> None:
"""Push tag to github."""
subprocess.check_call(f"git push origin v{current_version}", shell=True)


def make_release(current_version: Version) -> None:
"""Make release on Github."""
subprocess.check_call(
f"""gh release create v{current_version} --title "v{current_version}"
--generate-notes --latest""",
shell=True,
)


def build_packages():
"""Build packages."""
subprocess.check_call("poetry build", shell=True)


class ReleaseTool:
"""Release helper tool."""

def __init__(self, credentials: EnvCredentials) -> None:
"""Init release tool instance."""
self._credentials = credentials

def upload_packages(self):
"""Upload packages to PYPI."""
result = subprocess.run(
f"poetry publish --skip-existing --username {self._credentials.pypi_username} "
f"--password {self._credentials.pypi_password} --verbose",
check=True,
shell=True,
stdout=sys.stdout,
stderr=sys.stderr,
)
if result.returncode != 0:
raise RuntimeError("Upload pacakges failed!")

def main(self):
"""Run release process."""
current_version = get_current_version()
latest_release_version = get_the_latest_release_version()

print("Current version:", current_version)
print("Latest release version:", latest_release_version)

if current_version > latest_release_version:
print("Current version is newer. Good to go.")
else:
print("Current version is not newer. Exiting.")
return

print("\nBuilding packages")
build_packages()
print("Packages built")

print("\nUpload packages")
self.upload_packages()
print("Packages uploaded")

print("\nMake tag")
make_tag(current_version)
print("Tag made")

print("\nPush tag")
push_tag(current_version)
print("Tag pushed")

print("\nMake release")
make_release(current_version)
print("Release made." "")

print("\nDONE")


if __name__ == "__main__":
creds = EnvCredentials()
ReleaseTool(creds).main()