Skip to content

Commit a0bfd79

Browse files
authored
Merge pull request #356 from FoamyGuy/automated_releaser
adding automated releaser
2 parents b08b400 + 2502c31 commit a0bfd79

File tree

6 files changed

+325
-1
lines changed

6 files changed

+325
-1
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ env.sh
1515
.cp_org/*
1616
.blinka/*
1717
.vscode
18+
.idea/*

README.rst

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,45 @@ run the following command:
160160
# the help argument to display usage.
161161
python3 -m adabot.circuitpython_library_patches -h
162162
163+
164+
Making Releases For CircuitPython Libraries
165+
===========================================
166+
Adabot includes a utility to check if a library needs a new release
167+
and to help a human create the release with a CLI instead of the
168+
web interface.
169+
170+
To use it:
171+
172+
1. Clone the adabot repo locally and open a terminal inside of it
173+
2. Run ``pip install .`` in the root of Adabot repo to install it via pip
174+
3. Clone the library repo locally
175+
4. ``cd`` into the library repo
176+
5. run ``python -m adabot.circuitpython_library_release``
177+
6. Answer the prompts for new tag name and title.
178+
179+
This utility can be used in conjunction with ``git submodule foreach`` inside of the
180+
CircuitPython Library Bundle.
181+
182+
These are the steps for that process:
183+
184+
1. Clone the adabot repo locally and open a terminal inside of it
185+
2. If you want to use the same title for all libraries (i.e. due to a patch rollout)
186+
then modify the ``RELEASE_TITLE`` dictionary value at the top
187+
of ``adabot/circuitpython_library_release.py``
188+
3. Run ``pip install .`` in the root of Adabot repo to install it via pip
189+
4. Clone the Library Bundle repo and open a terminal inside of it
190+
5. Run these commands to update all submodules
191+
192+
.. code-block:: shell
193+
194+
git submodule sync --quiet --recursive
195+
git submodule update --init
196+
197+
198+
6. Run ``git submodule foreach 'python -m adabot.circuitpython_library_release'``
199+
200+
201+
163202
Contributing
164203
============
165204

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
# SPDX-FileCopyrightText: 2023 Tim Cocks for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
"""
6+
Check if a new release needs to be made, and if so, make it.
7+
"""
8+
import subprocess
9+
import logging
10+
from datetime import datetime
11+
import toml
12+
from jinja2 import Template
13+
14+
# Empty RELEASE_TITLE will prompt to ask for a title for each release.
15+
# Set a value here if you want to use the same string for the title of all releases
16+
config = {"RELEASE_TITLE": ""}
17+
18+
release_date_format = "%Y-%m-%dT%H:%M:%SZ"
19+
commit_date_format = "%a %b %d %H:%M:%S %Y"
20+
21+
VALID_MENU_CHOICES = ("1", "2", "3", "4", "")
22+
23+
24+
def make_release(new_tag, logger, test_run=False):
25+
"""
26+
Make the release
27+
"""
28+
# pylint: disable=line-too-long
29+
30+
while config["RELEASE_TITLE"] == "":
31+
config["RELEASE_TITLE"] = input("Enter a Release Title: ")
32+
33+
if not test_run:
34+
make_release_result = subprocess.getoutput(
35+
f"gh release create {new_tag} -F release_notes.md -t '{new_tag} - {config['RELEASE_TITLE']}'"
36+
)
37+
38+
if logger is not None:
39+
logger.info(make_release_result)
40+
else:
41+
print(make_release_result)
42+
else:
43+
print("would run: ")
44+
print(
45+
"gh release create {new_tag} -F release_notes.md -t '{new_tag} - {config['RELEASE_TITLE']}'"
46+
)
47+
48+
49+
def create_release_notes(pypi_name):
50+
"""
51+
render the release notes into a md file.
52+
"""
53+
# pylint: disable=line-too-long
54+
RELEASE_NOTES_TEMPLATE = """To use in CircuitPython, simply install the [Adafruit CircuitPython Bundle](https://circuitpython.org/libraries).
55+
56+
To use in CPython, `pip3 install {{ pypi_name }}`.
57+
58+
Read the [docs](https://circuitpython.readthedocs.io/projects/{{ pypi_name }}/en/latest/) for info on how to use it."""
59+
60+
release_notes_template = Template(RELEASE_NOTES_TEMPLATE)
61+
62+
_rendered_template_text = release_notes_template.render(pypi_name=pypi_name)
63+
64+
with open("release_notes.md", "w") as f:
65+
f.write(_rendered_template_text)
66+
67+
68+
if __name__ == "__main__":
69+
create_release_notes("testrepo")
70+
71+
72+
def get_pypi_name():
73+
"""
74+
return the shorthand pypi project name
75+
"""
76+
data = toml.load("pyproject.toml")
77+
78+
return data["project"]["name"].replace("adafruit-circuitpython-", "")
79+
80+
81+
def needs_new_release(logger):
82+
"""
83+
return true if there are commits newer than the latest release
84+
"""
85+
last_commit_time = subprocess.getoutput(
86+
" TZ=UTC0 git log -1 --date=local --format='%cd'"
87+
)
88+
logger.info(f"last commit: {last_commit_time}")
89+
90+
last_commit_date_obj = datetime.strptime(last_commit_time, commit_date_format)
91+
92+
release_info = get_release_info()
93+
94+
logger.info(f"Latest release is: {release_info['current_tag']}")
95+
logger.info(f"createdAt: {release_info['created_at']}")
96+
97+
release_date_obj = datetime.strptime(
98+
release_info["created_at"], release_date_format
99+
)
100+
return release_date_obj < last_commit_date_obj
101+
102+
103+
def bump_major(tag_symver):
104+
"""
105+
Returns a string with a new tag created by incrementing
106+
the major version of the given semantic version tag.
107+
"""
108+
tag_parts = tag_symver.split(".")
109+
tag_parts[0] = str(int(tag_parts[0]) + 1)
110+
tag_parts[1] = "0"
111+
tag_parts[2] = "0"
112+
return ".".join(tag_parts)
113+
114+
115+
def bump_minor(tag_symver):
116+
"""
117+
Returns a string with a new tag created by incrementing
118+
the minor version of the given semantic version tag.
119+
"""
120+
tag_parts = tag_symver.split(".")
121+
tag_parts[1] = str(int(tag_parts[1]) + 1)
122+
tag_parts[2] = "0"
123+
return ".".join(tag_parts)
124+
125+
126+
def bump_patch(tag_symver):
127+
"""
128+
Returns a string with a new tag created by incrementing
129+
the patch version of the given semantic version tag.
130+
"""
131+
tag_parts = tag_symver.split(".")
132+
tag_parts[-1] = str(int(tag_parts[-1]) + 1)
133+
return ".".join(tag_parts)
134+
135+
136+
def get_release_info():
137+
"""
138+
return a dictionary of info about the latest release
139+
"""
140+
result = subprocess.getoutput("gh release list -L 1 | awk 2")
141+
createdAt = result.split("\t")[-1]
142+
tag = result.split("\t")[-2]
143+
return {
144+
"current_tag": tag,
145+
"new_tag_patch": bump_patch(tag),
146+
"new_tag_minor": bump_minor(tag),
147+
"new_tag_major": bump_major(tag),
148+
"created_at": createdAt,
149+
}
150+
151+
152+
def get_compare_url(tag_name):
153+
"""
154+
Get the URL to the GitHub compare page for the latest release compared
155+
to current main.
156+
"""
157+
remote_url = subprocess.getoutput("git ls-remote --get-url origin")
158+
if not remote_url.startswith("https"):
159+
remote_url = subprocess.getoutput("git ls-remote --get-url adafruit")
160+
161+
if not remote_url.startswith("https"):
162+
return "Sorry, Unknown Remotes"
163+
164+
compare_url = remote_url.replace(".git", f"/compare/{tag_name}...main")
165+
return compare_url
166+
167+
168+
def main_cli():
169+
"""
170+
Main CLI entry point
171+
"""
172+
logging.basicConfig(
173+
level=logging.INFO,
174+
format="%(asctime)s [%(levelname)s] %(message)s",
175+
handlers=[
176+
logging.FileHandler("../../../automated_releaser.log"),
177+
logging.StreamHandler(),
178+
],
179+
)
180+
181+
def menu_prompt(release_info):
182+
"""
183+
Prompt the user to ask which part of the symantic version should be
184+
incremented, or if the library release should be skipped.
185+
Returns the choice inputted by the user.
186+
"""
187+
print("This library needs a new release. Please select a choice:")
188+
print(f"Changes: {get_compare_url(release_info['current_tag'])}")
189+
print(
190+
f"1. *default* Bump Patch, new tag would be: {release_info['new_tag_patch']}"
191+
)
192+
print(f"2. Bump Minor, new tag would be: {release_info['new_tag_minor']}")
193+
print(f"3. Bump Major, new tag would be: {release_info['new_tag_major']}")
194+
print("4. Skip releasing this library and go to next in the list")
195+
return input("Choice, enter blank for default: ")
196+
197+
result = subprocess.getoutput("git checkout main")
198+
199+
result = subprocess.getoutput("pwd")
200+
logging.info("Checking: %s", "/".join(result.split("/")[-3:]))
201+
202+
if needs_new_release(logging):
203+
release_info = get_release_info()
204+
choice = menu_prompt(release_info)
205+
while choice not in VALID_MENU_CHOICES:
206+
logging.info("Error: Invalid Selection '%s'", choice)
207+
choice = menu_prompt(release_info)
208+
209+
if choice in ("1", ""):
210+
logging.info(
211+
"Making a new release with tag: %s", release_info["new_tag_patch"]
212+
)
213+
create_release_notes(get_pypi_name())
214+
make_release(release_info["new_tag_patch"], logging)
215+
elif choice == "2":
216+
logging.info(
217+
"Making a new release with tag: %s", release_info["new_tag_minor"]
218+
)
219+
create_release_notes(get_pypi_name())
220+
make_release(release_info["new_tag_minor"], logging)
221+
elif choice == "3":
222+
logging.info(
223+
"Making a new release with tag: %s", release_info["new_tag_major"]
224+
)
225+
create_release_notes(get_pypi_name())
226+
make_release(release_info["new_tag_major"], logging)
227+
elif choice == "4":
228+
logging.info("Skipping release.")
229+
230+
else:
231+
logging.info("No new commits since last release, skipping")
232+
233+
234+
if __name__ == "__main__":
235+
main_cli()

pyproject.toml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# SPDX-FileCopyrightText: 2022 Alec Delaney for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
[build-system]
6+
requires = [
7+
"setuptools",
8+
"wheel",
9+
"setuptools-scm",
10+
]
11+
12+
[project]
13+
name = "adafruit-adabot"
14+
description = "Adabot is our robot friend who helps Adafruit online "
15+
version = "0.0.0+auto.0"
16+
readme = "README.rst"
17+
authors = [
18+
{name = "Adafruit Industries", email = "circuitpython@adafruit.com"}
19+
]
20+
urls = {Homepage = "https://github.com/adafruit/adabot"}
21+
keywords = [
22+
"adafruit",
23+
"micropython",
24+
"circuitpython",
25+
"automation",
26+
]
27+
license = {text = "MIT"}
28+
classifiers = [
29+
"Intended Audience :: Developers",
30+
"Topic :: Software Development :: Libraries",
31+
"Topic :: Software Development :: Embedded Systems",
32+
"Topic :: System :: Hardware",
33+
"License :: OSI Approved :: MIT License",
34+
"Programming Language :: Python :: 3",
35+
]
36+
dynamic = ["dependencies", "optional-dependencies"]
37+
38+
[project.scripts]
39+
adabot-release = "adabot.circuitpython_library_release:main_cli"
40+
41+
[tool.setuptools]
42+
packages = ["adabot"]
43+
44+
[tool.setuptools.dynamic]
45+
dependencies = {file = ["requirements.txt"]}
46+
optional-dependencies = {optional = {file = ["optional_requirements.txt"]}}

requirements.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ black==22.3.0
66
packaging==20.3
77
pylint==2.11.1
88
pytest
9-
pyyaml==5.4.1
9+
pyyaml>=5.4.1
1010
redis==4.5.4
1111
requests==2.31.0
1212
sh==1.12.14
@@ -17,3 +17,5 @@ PyGithub==1.57
1717
typing-extensions~=4.0
1818
google-auth~=2.13
1919
google-cloud-bigquery~=3.3
20+
toml
21+
jinja2

tools/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
# Adabot Tools and Scripts
23

34

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy