diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..bd07a8b --- /dev/null +++ b/.github/workflows/release.yml @@ -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 "ci-bot@fetch.ai" + git config --global user.name "CI BOT" + python3 ./scripts/do_release.py \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index bab7035..8cd1ddc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "fetchai-babble" -version = "0.3.0" +version = "0.4.0" description = "A simple python library for interacting with the Fetch.ai messaging service (called Memorandum)" authors = ["Fetch.AI Limited"] license = "Apache 2.0" diff --git a/scripts/do_release.py b/scripts/do_release.py new file mode 100755 index 0000000..1f3340f --- /dev/null +++ b/scripts/do_release.py @@ -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""", + 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()