Skip to content
Merged
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/*
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
55 changes: 55 additions & 0 deletions tools/automated_releaser/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Automated GitHub Release Maker

## Requirements

- git
- gh (github CLI)
- Python
- toml python library
- Jinja2 python library

To run the release automation:



## Steps to Run
1. Clone the library bundle and download all submodules. `clone_bundle.sh` can be used for this
2. Copy the following files / folders into the root of the bundle:
- `release_maker_scripts/`
- `copy_release_making_files.py`
- `list_submodules.py`
- `remove_submodules.py`
3. Set the `RELEASE_TITLE` config dictionary value inside of `release_maker_scripts/make_release.py` to something
appropriate for the new release. If you skip this step you'll be prompted for a title on each library needing a release.
4. From the root of `Adafruit_CircuitPython_Bundle` run:
```
git submodule foreach "python ../../../copy_release_making_files.py;python run_on_each.py"
```

The file `automated_releaser.log` will be created in the root of the bundle and contain log messages generated while the process runs.

## Notes

Scripts and instructions are written to assume Python3 is installed and accessible to all users with the `python`.

If you run `python` and do not see the Python 3 REPL then you need to:

1. Install Python3
2. If necessary, Make a symbolic link or shortcut that allows `python` to point to `python3`

Use a virtual environment if you are in a context where Python 2 exists so that inside your venv `python` will point to the venv rather than the systems instance of Py2.

## Steps to run on subset:

1. Clone the library bundle and download all submodules. `clone_bundle.sh` can be used for this
2. Copy the following files / folders into the root of the bundle:
- `release_maker_scripts/`
- `copy_release_making_files.py`
- `list_submodules.py`
- `remove_submodules.py`
3. Run `list_submodules.py` it will create `drivers.txt` and `helpers.txt`
4. Delete / Remove any libraries you want _**included**_ in the subset from these files. i.e. (if you want to run on only `acep7in` and `adxl34x` then delete those two lines from the drivers.txt file and leave all other lines). _**Ensure there are no blank lines in either txt file!**_
5. Run `remove_submodules.py` it will remove all library submodules that are present in `drivers.txt` and `helpers.txt` (any libraries not in those files will remain intact)
6. Run `git submodule foreach "python ../../../copy_release_making_files.py;python run_on_each.py"`

To run it against the rest of the full set (without duplicates). Repeat the above process, but remove all except the libraries completed from the txt files.
3 changes: 3 additions & 0 deletions tools/automated_releaser/README.md.license
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2023 Tim Cocks

SPDX-License-Identifier: MIT
11 changes: 11 additions & 0 deletions tools/automated_releaser/copy_release_making_files.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# SPDX-FileCopyrightText: 2023 Tim Cocks
#
# SPDX-License-Identifier: MIT

"""
Copy the scripts needed for releasing into the current directory.
Assumes library bundle struction and files pasted into position as instructed in README.md.
"""
import os

os.system("cp -r ../../../release_maker_scripts/* ./")
28 changes: 28 additions & 0 deletions tools/automated_releaser/list_submodules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# SPDX-FileCopyrightText: 2023 Tim Cocks
#
# SPDX-License-Identifier: MIT

"""
Create txt files listing all current submodules of the bundle repo.
"""
import os

drivers = os.listdir("libraries/drivers/")
helpers = os.listdir("libraries/helpers/")

drivers.sort()
helpers.sort()

with open("drivers.txt", "w") as f:
for i, submodule in enumerate(drivers):
if i < len(drivers) - 1:
f.write("{}\n".format(submodule))
else:
f.write("{}".format(submodule))

with open("helpers.txt", "w") as f:
for i, submodule in enumerate(helpers):
if i < len(helpers) - 1:
f.write("{}\n".format(submodule))
else:
f.write("{}".format(submodule))
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# SPDX-FileCopyrightText: 2023 Tim Cocks
#
# SPDX-License-Identifier: MIT

"""
compare the timestamp of the most recent commit
with the timestamp of the latest release.
"""

import subprocess
from datetime import datetime
from get_release_info import get_release_info

date_format = "%Y-%m-%dT%H:%M:%SZ"

other_date_format = "%a %b %d %H:%M:%S %Y"


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, other_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"], date_format)
return release_date_obj < last_commit_date_obj


if __name__ == "__main__":
import logging

logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
handlers=[
logging.FileHandler("../../../automated_releaser.log"),
logging.StreamHandler(),
],
)
if needs_new_release(logging):
print("There are new commits")
else:
print("Nope, nothing in the cannon.")
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# SPDX-FileCopyrightText: 2023 Tim Cocks
#
# SPDX-License-Identifier: MIT

"""
Create release_notes.md file populated with appropriate message for the
given library.
"""
from jinja2 import Template


def create_release_notes(pypi_name):
"""
render the release notes into a md file.
"""
with open("templates/release_notes_template.md", "r") as f:
release_notes_template = Template(f.read())

_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")
17 changes: 17 additions & 0 deletions tools/automated_releaser/release_maker_scripts/get_pypi_name.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# SPDX-FileCopyrightText: 2023 Tim Cocks
#
# SPDX-License-Identifier: MIT

"""
get the shorthand pypi name from the pyproject.toml file.
"""
import toml


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

return data["project"]["name"].replace("adafruit-circuitpython-", "")
61 changes: 61 additions & 0 deletions tools/automated_releaser/release_maker_scripts/get_release_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# SPDX-FileCopyrightText: 2023 Tim Cocks
#
# SPDX-License-Identifier: MIT

"""
Get infomation about the latest release.
- Current tag name
- New tag name assuming +1 to minor version
- timestamp for when the release was created
"""
import subprocess


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,
}
31 changes: 31 additions & 0 deletions tools/automated_releaser/release_maker_scripts/make_release.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# SPDX-FileCopyrightText: 2023 Tim Cocks
#
# SPDX-License-Identifier: MIT

"""
make a new release using the gh cli
"""
import subprocess

# 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": ""}


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

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

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(result)
else:
print(result)
71 changes: 71 additions & 0 deletions tools/automated_releaser/release_maker_scripts/run_on_each.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# SPDX-FileCopyrightText: 2023 Tim Cocks
#
# SPDX-License-Identifier: MIT

"""
Check if a new release needs to be made, and if so, make it.
"""
import subprocess
import logging
from compare_release_with_latest_commit import needs_new_release
from get_pypi_name import get_pypi_name
from create_release_notes import create_release_notes
from make_release import make_release
from get_release_info import get_release_info


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

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"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_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")
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
To use in CircuitPython, simply install the [Adafruit CircuitPython Bundle](https://circuitpython.org/libraries).

To use in CPython, `pip3 install adafruit-circuitpython-{{ pypi_name }}`.

Read the [docs](https://circuitpython.readthedocs.io/projects/{{ pypi_name }}/en/latest/) for info on how to use it.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2023 Tim Cocks

SPDX-License-Identifier: MIT
Loading