Skip to content

Commit 45b88e7

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

File tree

3 files changed

+298
-2
lines changed

3 files changed

+298
-2
lines changed

.github/ci/release_checker.py

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
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+
changed_paths = diff_output.splitlines()
132+
if diff_output:
133+
extensions = {os.path.splitext(path)[1] for path in changed_paths}
134+
135+
if extensions == {'.md'}:
136+
changes_since_version = "⚠️ MD "
137+
elif changed_paths and all("test_apps/" in path for path in changed_paths):
138+
changes_since_version = "🧪 Test only"
139+
elif extensions:
140+
changes_since_version = "⛔ Yes"
141+
else:
142+
changes_since_version = "✔️ No "
143+
144+
count_output = run_git_command(["rev-list", f"{commit_hash}..HEAD", "--count", rel_component_path], cwd=repo_path)
145+
commit_count = count_output if count_output.isdigit() else "?"
146+
147+
except Exception as e:
148+
print(f"Chyba: {e}")
149+
150+
if release_date != "?":
151+
extension_str = ", ".join(sorted(extensions)) if extensions else " "
152+
results.append([component_name, version, release_date, changes_since_version + f" ({commit_count})", extension_str])
153+
154+
155+
def show_diff_for_component(component_name):
156+
commit_hash = release_commits.get(component_name)
157+
rel_path = component_paths.get(component_name)
158+
159+
if not commit_hash or not rel_path:
160+
print("Commit path not found.")
161+
return
162+
163+
# Získání seznamu změněných souborů
164+
changed_files = run_git_command(["diff", "--name-only", f"{commit_hash}..HEAD", "--", rel_path], cwd=repo_path)
165+
changed_files = [f for f in changed_files.splitlines() if not f.endswith(".md")]
166+
167+
if not changed_files:
168+
print("No changes except *.md files.")
169+
return
170+
171+
print(f"Changes for component '{component_name}' from last release:\n")
172+
subprocess.run(["git", "diff", "--color=always", f"{commit_hash}..HEAD", "--"] + changed_files, cwd=repo_path)
173+
174+
175+
# Funkce pro výpočet vizuální šířky textu
176+
def visual_width(text):
177+
return sum(wcwidth.wcwidth(c) for c in text)
178+
179+
180+
# Funkce pro zarovnání textu na požadovanou vizuální šířku
181+
def pad_visual(text, target_width):
182+
current_width = visual_width(text)
183+
padding = max(0, target_width - current_width)
184+
return text + " " * padding
185+
186+
187+
def get_change_key(row):
188+
change = row[3].strip()
189+
extensions = row[4].split(", ")
190+
has_code_change = any(ext in ['.c', '.h'] for ext in extensions)
191+
192+
if change.startswith("⛔") and has_code_change:
193+
return 0
194+
elif change.startswith("⛔"):
195+
return 1
196+
elif change.startswith("🧪"):
197+
return 2
198+
elif change.startswith("⚠️"):
199+
return 3
200+
elif change.startswith("✔️"):
201+
return 4
202+
return 99
203+
204+
205+
# Seřaď results podle priority
206+
results.sort(key=get_change_key)
207+
208+
# Zarovnáme sloupec „Změny“ na vizuální šířku 8 znaků
209+
for row in results:
210+
row[3] = pad_visual(row[3], 8)
211+
212+
# Výpis jako Markdown tabulka
213+
headers = ["Component", "Version", "Released", "Changed", "Files"]
214+
215+
tz = pytz.timezone("Europe/Prague")
216+
last_updated = datetime.now(tz).strftime("%d.%m.%Y %H:%M:%S %Z")
217+
if os.getenv("CI") != "true":
218+
markdown_table = tabulate(results, headers=headers, tablefmt="github")
219+
print("# Component/BSP release version checker")
220+
print("This page shows all components in the BSP repository with their latest versions and indicates whether any changes have not yet been released.")
221+
else:
222+
markdown_table = tabulate(results, headers=headers, tablefmt="html")
223+
deprecated_str = ", ".join(deprecated)
224+
print("<html><head>")
225+
print("<title>Component/BSP release version checker</title>")
226+
print(f"""<style>
227+
body {{ font-family: sans-serif; padding: 2em; }}
228+
table {{ border-collapse: collapse; width: 100%; }}
229+
th, td {{ border: 1px solid #ccc; padding: 0.5em; text-align: left; }}
230+
th {{ background-color: #f0f0f0; }}
231+
td:nth-child(4) {{ text-align: center; }}
232+
td:nth-child(5) {{ font-style: italic; color: #888; }}
233+
</style>""")
234+
print("</head><body>")
235+
print("<h1>Component/BSP release version checker</h1>")
236+
print(f"<p>Last updated: {last_updated}</p>")
237+
print("<p>This page shows all components in the BSP repository with their latest versions and indicates whether any changes have not yet been released.</p>")
238+
print(f"<p>Deprecated components: {deprecated_str}</p>")
239+
print("</body></html>")
240+
241+
print(markdown_table)
242+
243+
if os.getenv("CI") != "true":
244+
while True:
245+
component_name = input("Input the component name for diff (or type 'exit' to quit): ")
246+
if component_name.lower() == 'exit':
247+
break
248+
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

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33

44
# ESP-BSP: Espressif's Board Support Packages
55

6-
| [HOW TO USE](docu/how_to_use.md) | [BOARDS](#supported-boards) | [EXAMPLES](#examples) | [CONTRIBUTING](docu/CONTRIBUTING.md) | [LVGL port](components/esp_lvgl_port) | [LCD drivers](docu/LCD.md) |
7-
| :---------------------------------------: | :-------------------------: | :-------------------: | :--------------: | :-----------------------------------: | :------------------------: |
6+
| [HOW TO USE](docu/how_to_use.md) | [BOARDS](#supported-boards) | [EXAMPLES](#examples) | [CONTRIBUTING](docu/CONTRIBUTING.md) | [LVGL port](components/esp_lvgl_port) | [LCD drivers](docu/LCD.md) | [Releases](https://espressif.github.io/esp-bsp/release_checker.html) |
7+
| :---------------------------------------: | :-------------------------: | :-------------------: | :--------------: | :-----------------------------------: | :------------------------: | :------------------------: |
88

99
This repository provides **Board Support Packages (BSPs)** for various Espressif and M5Stack development boards. Written in **C**, each BSP offers a **unified and consistent API** to simplify the initialization and use of common onboard peripherals such as **displays, touch panels, audio codecs, SD cards, and selected sensors.** The goal is to streamline development and reduce hardware-specific boilerplate, enabling faster prototyping and cleaner application code.
1010

0 commit comments

Comments
 (0)