Skip to content

Commit 8b3d47e

Browse files
committed
ci(release_checker): Script for check changes after last release for all components and BSPs
1 parent fb106df commit 8b3d47e

File tree

2 files changed

+289
-0
lines changed

2 files changed

+289
-0
lines changed

.github/ci/release_checker.py

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
# flake8: noqa
5+
6+
import os
7+
import yaml
8+
import subprocess
9+
from tabulate import tabulate
10+
import re
11+
from datetime import datetime
12+
import wcwidth
13+
from pathlib import Path
14+
import pytz
15+
16+
repo_path = Path(".")
17+
18+
target_dirs = [
19+
os.path.join(repo_path, "bsp"),
20+
os.path.join(repo_path, "components"),
21+
]
22+
23+
deprecated = [
24+
"esp-box",
25+
"esp-box-lite",
26+
"esp32_azure_iot_kit",
27+
"esp32_s2_kaluga_kit",
28+
"hts221",
29+
30+
]
31+
32+
priority_order = {
33+
"⛔ Yes": 0,
34+
"⚠️ MD ": 1,
35+
"✔️ No ": 2
36+
}
37+
38+
results = []
39+
40+
release_commits = {}
41+
component_paths = {}
42+
43+
44+
def run_git_command(args, cwd):
45+
result = subprocess.run(["git"] + args, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
46+
return result.stdout.strip()
47+
48+
49+
for base_dir in target_dirs:
50+
if os.path.exists(base_dir):
51+
for root, dirs, files in os.walk(base_dir):
52+
if "idf_component.yml" in files:
53+
yml_path = os.path.join(root, "idf_component.yml")
54+
component_name = os.path.basename(root)
55+
version = "N/A"
56+
release_date = "?"
57+
changes_since_version = "N/A"
58+
commit_count = "?"
59+
60+
if component_name in deprecated:
61+
continue
62+
63+
try:
64+
with open(yml_path, "r") as f:
65+
yml_data = yaml.safe_load(f)
66+
version = yml_data.get("version", "N/A")
67+
except Exception as e:
68+
print(f"Chyba: {e}")
69+
70+
if version != "N/A":
71+
try:
72+
rel_yml_path = os.path.relpath(yml_path, repo_path).replace("\\", "/")
73+
log_output = run_git_command(["log", "-p", "--", rel_yml_path], cwd=repo_path)
74+
75+
current_commit = None
76+
current_date = None
77+
old_version = None
78+
new_version = None
79+
commit_hash = None
80+
81+
lines = log_output.splitlines()
82+
for i, line in enumerate(lines):
83+
if line.startswith("commit "):
84+
current_commit = line.split()[1]
85+
old_version = None
86+
new_version = None
87+
elif line.startswith("Date:"):
88+
raw_date = line.replace("Date:", "").strip()
89+
try:
90+
dt = datetime.strptime(raw_date, "%a %b %d %H:%M:%S %Y %z")
91+
current_date = dt.strftime("%d.%m.%Y")
92+
except Exception as e:
93+
print(f"Chyba: {e}")
94+
current_date = raw_date
95+
elif line.startswith("-version:") and not line.startswith(" "):
96+
match = re.match(r"-version:\s*['\"]?([\w\.\-~]+)['\"]?", line)
97+
if match:
98+
old_version = match.group(1)
99+
elif line.startswith("+version:") and not line.startswith(" "):
100+
match = re.match(r"\+version:\s*['\"]?([\w\.\-~]+)['\"]?", line)
101+
if match:
102+
new_version = match.group(1)
103+
104+
if old_version and new_version and old_version != new_version:
105+
commit_hash = current_commit
106+
release_date = current_date
107+
break
108+
109+
if not commit_hash:
110+
first_commit = run_git_command(["log", "--diff-filter=A", "--format=%H %aD", "--", rel_yml_path], cwd=repo_path)
111+
if first_commit:
112+
parts = first_commit.split()
113+
commit_hash = parts[0]
114+
try:
115+
dt = datetime.strptime(" ".join(parts[1:]), "%a, %d %b %Y %H:%M:%S %z")
116+
release_date = dt.strftime("%d.%m.%Y")
117+
except Exception as e:
118+
print(f"Chyba: {e}")
119+
release_date = "?"
120+
121+
if commit_hash:
122+
rel_component_path = os.path.relpath(root, repo_path).replace("\\", "/")
123+
124+
# Save
125+
release_commits[component_name] = commit_hash
126+
component_paths[component_name] = rel_component_path
127+
128+
diff_output = run_git_command(["diff", "--name-only", f"{commit_hash}..HEAD", "--", rel_component_path], cwd=repo_path)
129+
130+
extensions = {}
131+
if diff_output:
132+
extensions = {os.path.splitext(path)[1] for path in diff_output.splitlines()}
133+
134+
if extensions == {'.md'}:
135+
changes_since_version = "⚠️ MD "
136+
elif extensions:
137+
changes_since_version = "⛔ Yes"
138+
else:
139+
changes_since_version = "✔️ No "
140+
141+
count_output = run_git_command(["rev-list", f"{commit_hash}..HEAD", "--count", rel_component_path], cwd=repo_path)
142+
commit_count = count_output if count_output.isdigit() else "?"
143+
144+
except Exception as e:
145+
print(f"Chyba: {e}")
146+
147+
if release_date != "?":
148+
extension_str = ", ".join(sorted(extensions)) if extensions else " "
149+
results.append([component_name, version, release_date, changes_since_version + f" ({commit_count})", extension_str])
150+
151+
152+
def show_diff_for_component(component_name):
153+
commit_hash = release_commits.get(component_name)
154+
rel_path = component_paths.get(component_name)
155+
156+
if not commit_hash or not rel_path:
157+
print("Commit path not found.")
158+
return
159+
160+
# Získání seznamu změněných souborů
161+
changed_files = run_git_command(["diff", "--name-only", f"{commit_hash}..HEAD", "--", rel_path], cwd=repo_path)
162+
changed_files = [f for f in changed_files.splitlines() if not f.endswith(".md")]
163+
164+
if not changed_files:
165+
print("No changes except *.md files.")
166+
return
167+
168+
print(f"Changes for component '{component_name}' from last release:\n")
169+
subprocess.run(["git", "diff", "--color=always", f"{commit_hash}..HEAD", "--"] + changed_files, cwd=repo_path)
170+
171+
172+
# Funkce pro výpočet vizuální šířky textu
173+
def visual_width(text):
174+
return sum(wcwidth.wcwidth(c) for c in text)
175+
176+
177+
# Funkce pro zarovnání textu na požadovanou vizuální šířku
178+
def pad_visual(text, target_width):
179+
current_width = visual_width(text)
180+
padding = max(0, target_width - current_width)
181+
return text + " " * padding
182+
183+
184+
def get_change_key(row):
185+
change = row[3].strip()
186+
extensions = row[4].split(", ")
187+
has_code_change = any(ext in ['.c', '.h'] for ext in extensions)
188+
189+
if change.startswith("⛔") and has_code_change:
190+
return 0
191+
elif change.startswith("⛔"):
192+
return 1
193+
elif change.startswith("⚠️"):
194+
return 2
195+
elif change.startswith("✔️"):
196+
return 3
197+
return 99
198+
199+
200+
# Seřaď results podle priority
201+
results.sort(key=get_change_key)
202+
203+
# Zarovnáme sloupec „Změny“ na vizuální šířku 8 znaků
204+
for row in results:
205+
row[3] = pad_visual(row[3], 8)
206+
207+
# Výpis jako Markdown tabulka
208+
headers = ["Component", "Version", "Released", "Changed", "Files"]
209+
210+
tz = pytz.timezone("Europe/Prague")
211+
last_updated = datetime.now(tz).strftime("%d.%m.%Y %H:%M:%S %Z")
212+
if os.getenv("CI") != "true":
213+
markdown_table = tabulate(results, headers=headers, tablefmt="github")
214+
print("# Component/BSP release version checker")
215+
print("This page show all components in BSP repository with its latest versions and how many changes was not released.")
216+
else:
217+
markdown_table = tabulate(results, headers=headers, tablefmt="html")
218+
print("<html><head>")
219+
print("<title>Component/BSP release version checker</title>")
220+
print(f"""<style>
221+
body {{ font-family: sans-serif; padding: 2em; }}
222+
table {{ border-collapse: collapse; width: 100%; }}
223+
th, td {{ border: 1px solid #ccc; padding: 0.5em; text-align: left; }}
224+
th {{ background-color: #f0f0f0; }}
225+
td:nth-child(4) {{ text-align: center; }}
226+
td:nth-child(5) {{ font-style: italic; color: #888; }}
227+
</style>""")
228+
print("</head><body>")
229+
print("<h1>Component/BSP release version checker</h1>")
230+
print(f"<p>Last updated: {last_updated}</p>")
231+
print("<p>This page show all components in BSP repository with its latest versions and how many changes was not released.</p>")
232+
print("</body></html>")
233+
234+
print(markdown_table)
235+
236+
if os.getenv("CI") != "true":
237+
while True:
238+
component_name = input("Input the component name for diff (or type 'exit' to quit): ")
239+
if component_name.lower() == 'exit':
240+
break
241+
show_diff_for_component(component_name)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
2+
name: Daily Release Check
3+
4+
5+
on:
6+
push:
7+
branches:
8+
- master
9+
workflow_dispatch: # can be run manually
10+
11+
12+
jobs:
13+
build-and-deploy:
14+
runs-on: ubuntu-latest
15+
16+
permissions:
17+
pages: write
18+
id-token: write
19+
20+
environment:
21+
name: github-pages
22+
url: ${{ steps.deployment.outputs.page_url }}
23+
24+
steps:
25+
- name: Checkout repo
26+
uses: actions/checkout@v4
27+
with:
28+
fetch-depth: 0 # all git history
29+
30+
- name: Set up Python
31+
uses: actions/setup-python@v5
32+
with:
33+
python-version: '3.11'
34+
35+
- name: Run release_checker.py
36+
run: |
37+
pip install requests pyyaml tabulate wcwidth pytz
38+
mkdir site
39+
python .github/ci/release_checker.py > site/release_checker.html
40+
41+
- name: Upload file to gh pages
42+
uses: actions/upload-pages-artifact@v3
43+
with:
44+
path: site
45+
46+
- name: Deploy to GitHub Pages
47+
id: deployment
48+
uses: actions/deploy-pages@v4

0 commit comments

Comments
 (0)