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