Skip to content

Commit

Permalink
Merge pull request #356 from FoamyGuy/automated_releaser
Browse files Browse the repository at this point in the history
adding automated releaser
  • Loading branch information
dhalbert committed Nov 28, 2023
2 parents b08b400 + 2502c31 commit a0bfd79
Show file tree
Hide file tree
Showing 6 changed files with 325 additions and 1 deletion.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ env.sh
.cp_org/*
.blinka/*
.vscode
.idea/*
39 changes: 39 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,45 @@ run the following command:
# the help argument to display usage.
python3 -m adabot.circuitpython_library_patches -h
Making Releases For CircuitPython Libraries
===========================================
Adabot includes a utility to check if a library needs a new release
and to help a human create the release with a CLI instead of the
web interface.

To use it:

1. Clone the adabot repo locally and open a terminal inside of it
2. Run ``pip install .`` in the root of Adabot repo to install it via pip
3. Clone the library repo locally
4. ``cd`` into the library repo
5. run ``python -m adabot.circuitpython_library_release``
6. Answer the prompts for new tag name and title.

This utility can be used in conjunction with ``git submodule foreach`` inside of the
CircuitPython Library Bundle.

These are the steps for that process:

1. Clone the adabot repo locally and open a terminal inside of it
2. If you want to use the same title for all libraries (i.e. due to a patch rollout)
then modify the ``RELEASE_TITLE`` dictionary value at the top
of ``adabot/circuitpython_library_release.py``
3. Run ``pip install .`` in the root of Adabot repo to install it via pip
4. Clone the Library Bundle repo and open a terminal inside of it
5. Run these commands to update all submodules

.. code-block:: shell
git submodule sync --quiet --recursive
git submodule update --init
6. Run ``git submodule foreach 'python -m adabot.circuitpython_library_release'``



Contributing
============

Expand Down
235 changes: 235 additions & 0 deletions adabot/circuitpython_library_release.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
# SPDX-FileCopyrightText: 2023 Tim Cocks for Adafruit Industries
#
# SPDX-License-Identifier: MIT

"""
Check if a new release needs to be made, and if so, make it.
"""
import subprocess
import logging
from datetime import datetime
import toml
from jinja2 import Template

# Empty RELEASE_TITLE will prompt to ask for a title for each release.
# Set a value here if you want to use the same string for the title of all releases
config = {"RELEASE_TITLE": ""}

release_date_format = "%Y-%m-%dT%H:%M:%SZ"
commit_date_format = "%a %b %d %H:%M:%S %Y"

VALID_MENU_CHOICES = ("1", "2", "3", "4", "")


def make_release(new_tag, logger, test_run=False):
"""
Make the release
"""
# pylint: disable=line-too-long

while config["RELEASE_TITLE"] == "":
config["RELEASE_TITLE"] = input("Enter a Release Title: ")

if not test_run:
make_release_result = subprocess.getoutput(
f"gh release create {new_tag} -F release_notes.md -t '{new_tag} - {config['RELEASE_TITLE']}'"
)

if logger is not None:
logger.info(make_release_result)
else:
print(make_release_result)
else:
print("would run: ")
print(
"gh release create {new_tag} -F release_notes.md -t '{new_tag} - {config['RELEASE_TITLE']}'"
)


def create_release_notes(pypi_name):
"""
render the release notes into a md file.
"""
# pylint: disable=line-too-long
RELEASE_NOTES_TEMPLATE = """To use in CircuitPython, simply install the [Adafruit CircuitPython Bundle](https://circuitpython.org/libraries).
To use in CPython, `pip3 install {{ pypi_name }}`.
Read the [docs](https://circuitpython.readthedocs.io/projects/{{ pypi_name }}/en/latest/) for info on how to use it."""

release_notes_template = Template(RELEASE_NOTES_TEMPLATE)

_rendered_template_text = release_notes_template.render(pypi_name=pypi_name)

with open("release_notes.md", "w") as f:
f.write(_rendered_template_text)


if __name__ == "__main__":
create_release_notes("testrepo")


def get_pypi_name():
"""
return the shorthand pypi project name
"""
data = toml.load("pyproject.toml")

return data["project"]["name"].replace("adafruit-circuitpython-", "")


def needs_new_release(logger):
"""
return true if there are commits newer than the latest release
"""
last_commit_time = subprocess.getoutput(
" TZ=UTC0 git log -1 --date=local --format='%cd'"
)
logger.info(f"last commit: {last_commit_time}")

last_commit_date_obj = datetime.strptime(last_commit_time, commit_date_format)

release_info = get_release_info()

logger.info(f"Latest release is: {release_info['current_tag']}")
logger.info(f"createdAt: {release_info['created_at']}")

release_date_obj = datetime.strptime(
release_info["created_at"], release_date_format
)
return release_date_obj < last_commit_date_obj


def bump_major(tag_symver):
"""
Returns a string with a new tag created by incrementing
the major version of the given semantic version tag.
"""
tag_parts = tag_symver.split(".")
tag_parts[0] = str(int(tag_parts[0]) + 1)
tag_parts[1] = "0"
tag_parts[2] = "0"
return ".".join(tag_parts)


def bump_minor(tag_symver):
"""
Returns a string with a new tag created by incrementing
the minor version of the given semantic version tag.
"""
tag_parts = tag_symver.split(".")
tag_parts[1] = str(int(tag_parts[1]) + 1)
tag_parts[2] = "0"
return ".".join(tag_parts)


def bump_patch(tag_symver):
"""
Returns a string with a new tag created by incrementing
the patch version of the given semantic version tag.
"""
tag_parts = tag_symver.split(".")
tag_parts[-1] = str(int(tag_parts[-1]) + 1)
return ".".join(tag_parts)


def get_release_info():
"""
return a dictionary of info about the latest release
"""
result = subprocess.getoutput("gh release list -L 1 | awk 2")
createdAt = result.split("\t")[-1]
tag = result.split("\t")[-2]
return {
"current_tag": tag,
"new_tag_patch": bump_patch(tag),
"new_tag_minor": bump_minor(tag),
"new_tag_major": bump_major(tag),
"created_at": createdAt,
}


def get_compare_url(tag_name):
"""
Get the URL to the GitHub compare page for the latest release compared
to current main.
"""
remote_url = subprocess.getoutput("git ls-remote --get-url origin")
if not remote_url.startswith("https"):
remote_url = subprocess.getoutput("git ls-remote --get-url adafruit")

if not remote_url.startswith("https"):
return "Sorry, Unknown Remotes"

compare_url = remote_url.replace(".git", f"/compare/{tag_name}...main")
return compare_url


def main_cli():
"""
Main CLI entry point
"""
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
handlers=[
logging.FileHandler("../../../automated_releaser.log"),
logging.StreamHandler(),
],
)

def menu_prompt(release_info):
"""
Prompt the user to ask which part of the symantic version should be
incremented, or if the library release should be skipped.
Returns the choice inputted by the user.
"""
print("This library needs a new release. Please select a choice:")
print(f"Changes: {get_compare_url(release_info['current_tag'])}")
print(
f"1. *default* Bump Patch, new tag would be: {release_info['new_tag_patch']}"
)
print(f"2. Bump Minor, new tag would be: {release_info['new_tag_minor']}")
print(f"3. Bump Major, new tag would be: {release_info['new_tag_major']}")
print("4. Skip releasing this library and go to next in the list")
return input("Choice, enter blank for default: ")

result = subprocess.getoutput("git checkout main")

result = subprocess.getoutput("pwd")
logging.info("Checking: %s", "/".join(result.split("/")[-3:]))

if needs_new_release(logging):
release_info = get_release_info()
choice = menu_prompt(release_info)
while choice not in VALID_MENU_CHOICES:
logging.info("Error: Invalid Selection '%s'", choice)
choice = menu_prompt(release_info)

if choice in ("1", ""):
logging.info(
"Making a new release with tag: %s", release_info["new_tag_patch"]
)
create_release_notes(get_pypi_name())
make_release(release_info["new_tag_patch"], logging)
elif choice == "2":
logging.info(
"Making a new release with tag: %s", release_info["new_tag_minor"]
)
create_release_notes(get_pypi_name())
make_release(release_info["new_tag_minor"], logging)
elif choice == "3":
logging.info(
"Making a new release with tag: %s", release_info["new_tag_major"]
)
create_release_notes(get_pypi_name())
make_release(release_info["new_tag_major"], logging)
elif choice == "4":
logging.info("Skipping release.")

else:
logging.info("No new commits since last release, skipping")


if __name__ == "__main__":
main_cli()
46 changes: 46 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# SPDX-FileCopyrightText: 2022 Alec Delaney for Adafruit Industries
#
# SPDX-License-Identifier: MIT

[build-system]
requires = [
"setuptools",
"wheel",
"setuptools-scm",
]

[project]
name = "adafruit-adabot"
description = "Adabot is our robot friend who helps Adafruit online "
version = "0.0.0+auto.0"
readme = "README.rst"
authors = [
{name = "Adafruit Industries", email = "circuitpython@adafruit.com"}
]
urls = {Homepage = "https://github.com/adafruit/adabot"}
keywords = [
"adafruit",
"micropython",
"circuitpython",
"automation",
]
license = {text = "MIT"}
classifiers = [
"Intended Audience :: Developers",
"Topic :: Software Development :: Libraries",
"Topic :: Software Development :: Embedded Systems",
"Topic :: System :: Hardware",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
]
dynamic = ["dependencies", "optional-dependencies"]

[project.scripts]
adabot-release = "adabot.circuitpython_library_release:main_cli"

[tool.setuptools]
packages = ["adabot"]

[tool.setuptools.dynamic]
dependencies = {file = ["requirements.txt"]}
optional-dependencies = {optional = {file = ["optional_requirements.txt"]}}
4 changes: 3 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ black==22.3.0
packaging==20.3
pylint==2.11.1
pytest
pyyaml==5.4.1
pyyaml>=5.4.1
redis==4.5.4
requests==2.31.0
sh==1.12.14
Expand All @@ -17,3 +17,5 @@ PyGithub==1.57
typing-extensions~=4.0
google-auth~=2.13
google-cloud-bigquery~=3.3
toml
jinja2
1 change: 1 addition & 0 deletions tools/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

# Adabot Tools and Scripts


Expand Down

0 comments on commit a0bfd79

Please sign in to comment.