diff --git a/.github/ci/release_checker.py b/.github/ci/release_checker.py new file mode 100644 index 000000000..53138bdd1 --- /dev/null +++ b/.github/ci/release_checker.py @@ -0,0 +1,248 @@ +# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +# flake8: noqa + +import os +import yaml +import subprocess +from tabulate import tabulate +import re +from datetime import datetime +import wcwidth +from pathlib import Path +import pytz + +repo_path = Path(".") + +target_dirs = [ + os.path.join(repo_path, "bsp"), + os.path.join(repo_path, "components"), +] + +deprecated = [ + "esp-box", + "esp-box-lite", + "esp32_azure_iot_kit", + "esp32_s2_kaluga_kit", + "hts221", + +] + +priority_order = { + "⛔ Yes": 0, + "⚠️ MD ": 1, + "✔️ No ": 2 +} + +results = [] + +release_commits = {} +component_paths = {} + + +def run_git_command(args, cwd): + result = subprocess.run(["git"] + args, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + return result.stdout.strip() + + +for base_dir in target_dirs: + if os.path.exists(base_dir): + for root, dirs, files in os.walk(base_dir): + if "idf_component.yml" in files: + yml_path = os.path.join(root, "idf_component.yml") + component_name = os.path.basename(root) + version = "N/A" + release_date = "?" + changes_since_version = "N/A" + commit_count = "?" + + if component_name in deprecated: + continue + + try: + with open(yml_path, "r") as f: + yml_data = yaml.safe_load(f) + version = yml_data.get("version", "N/A") + except Exception as e: + print(f"Chyba: {e}") + + if version != "N/A": + try: + rel_yml_path = os.path.relpath(yml_path, repo_path).replace("\\", "/") + log_output = run_git_command(["log", "-p", "--", rel_yml_path], cwd=repo_path) + + current_commit = None + current_date = None + old_version = None + new_version = None + commit_hash = None + + lines = log_output.splitlines() + for i, line in enumerate(lines): + if line.startswith("commit "): + current_commit = line.split()[1] + old_version = None + new_version = None + elif line.startswith("Date:"): + raw_date = line.replace("Date:", "").strip() + try: + dt = datetime.strptime(raw_date, "%a %b %d %H:%M:%S %Y %z") + current_date = dt.strftime("%d.%m.%Y") + except Exception as e: + print(f"Chyba: {e}") + current_date = raw_date + elif line.startswith("-version:") and not line.startswith(" "): + match = re.match(r"-version:\s*['\"]?([\w\.\-~]+)['\"]?", line) + if match: + old_version = match.group(1) + elif line.startswith("+version:") and not line.startswith(" "): + match = re.match(r"\+version:\s*['\"]?([\w\.\-~]+)['\"]?", line) + if match: + new_version = match.group(1) + + if old_version and new_version and old_version != new_version: + commit_hash = current_commit + release_date = current_date + break + + if not commit_hash: + first_commit = run_git_command(["log", "--diff-filter=A", "--format=%H %aD", "--", rel_yml_path], cwd=repo_path) + if first_commit: + parts = first_commit.split() + commit_hash = parts[0] + try: + dt = datetime.strptime(" ".join(parts[1:]), "%a, %d %b %Y %H:%M:%S %z") + release_date = dt.strftime("%d.%m.%Y") + except Exception as e: + print(f"Chyba: {e}") + release_date = "?" + + if commit_hash: + rel_component_path = os.path.relpath(root, repo_path).replace("\\", "/") + + # Save + release_commits[component_name] = commit_hash + component_paths[component_name] = rel_component_path + + diff_output = run_git_command(["diff", "--name-only", f"{commit_hash}..HEAD", "--", rel_component_path], cwd=repo_path) + + extensions = {} + changed_paths = diff_output.splitlines() + if diff_output: + extensions = {os.path.splitext(path)[1] for path in changed_paths} + + if extensions == {'.md'}: + changes_since_version = "⚠️ MarkDown " + elif changed_paths and all("test_apps/" in path for path in changed_paths): + changes_since_version = "🧪 Test only" + elif extensions: + changes_since_version = "⛔ Yes" + else: + changes_since_version = "✔️ No " + + # count_output = run_git_command(["rev-list", f"{commit_hash}..HEAD", "--count", rel_component_path], cwd=repo_path) + commit_count = len(changed_paths) if changed_paths else "?" + + except Exception as e: + print(f"Chyba: {e}") + + if release_date != "?": + extension_str = ", ".join(sorted(extensions)) if extensions else " " + results.append([component_name, version, release_date, changes_since_version + f" ({commit_count})", extension_str]) + + +def show_diff_for_component(component_name): + commit_hash = release_commits.get(component_name) + rel_path = component_paths.get(component_name) + + if not commit_hash or not rel_path: + print("Commit path not found.") + return + + # List of changed files + changed_files = run_git_command(["diff", "--name-only", f"{commit_hash}..HEAD", "--", rel_path], cwd=repo_path) + changed_files = [f for f in changed_files.splitlines() if not f.endswith(".md")] + + if not changed_files: + print("No changes except *.md files.") + return + + print(f"Changes for component '{component_name}' from last release (except *.md files):\n") + subprocess.run(["git", "diff", "--color=always", f"{commit_hash}..HEAD", "--"] + changed_files, cwd=repo_path) + + +# Calculate width +def visual_width(text): + return sum(wcwidth.wcwidth(c) for c in text) + + +# Text align +def pad_visual(text, target_width): + current_width = visual_width(text) + padding = max(0, target_width - current_width) + return text + " " * padding + + +def get_change_key(row): + change = row[3].strip() + extensions = row[4].split(", ") + has_code_change = any(ext in ['.c', '.h'] for ext in extensions) + + if change.startswith("⛔") and has_code_change: + return 0 + elif change.startswith("⛔"): + return 1 + elif change.startswith("🧪"): + return 2 + elif change.startswith("⚠️"): + return 3 + elif change.startswith("✔️"): + return 4 + return 99 + + +# Sort by priority +results.sort(key=get_change_key) + +# Column align +for row in results: + row[3] = pad_visual(row[3], 8) + +# Table header +headers = ["Component", "Version", "Released", "Changed", "Files"] + +tz = pytz.timezone("Europe/Prague") +last_updated = datetime.now(tz).strftime("%d.%m.%Y %H:%M:%S %Z") +if os.getenv("CI") != "true": + markdown_table = tabulate(results, headers=headers, tablefmt="github") + print("# Component/BSP release version checker") + print("This page shows all components in the BSP repository with their latest versions and indicates whether any changes have not yet been released.") +else: + markdown_table = tabulate(results, headers=headers, tablefmt="html") + deprecated_str = ", ".join(deprecated) + print("
") + print("Last updated: {last_updated}
") + print("This page shows all components in the BSP repository with their latest versions and indicates whether any changes have not yet been released.
") + print(f"Deprecated components: {deprecated_str}
") + print("") + +print(markdown_table) + +if os.getenv("CI") != "true": + while True: + component_name = input("Input the component name for diff (or type 'exit' to quit): ") + if component_name.lower() == 'exit': + break + show_diff_for_component(component_name) diff --git a/.github/workflows/build-examples-gh-pages-on-push.yml b/.github/workflows/build-examples-launchpad.yml similarity index 88% rename from .github/workflows/build-examples-gh-pages-on-push.yml rename to .github/workflows/build-examples-launchpad.yml index 004684821..071e2cdbd 100644 --- a/.github/workflows/build-examples-gh-pages-on-push.yml +++ b/.github/workflows/build-examples-launchpad.yml @@ -1,4 +1,4 @@ -name: "ESP-IDF build examples to github pages (push)" +name: Build examples for Launchpad on: push: @@ -61,8 +61,6 @@ jobs: - name: Upload built files to gh pages uses: actions/upload-pages-artifact@v3 with: - path: binaries/ + name: gh-pages-launchpad + path: binaries/ - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 diff --git a/.github/workflows/deploy_gh_pages.yml b/.github/workflows/deploy_gh_pages.yml new file mode 100644 index 000000000..8e9c5c12d --- /dev/null +++ b/.github/workflows/deploy_gh_pages.yml @@ -0,0 +1,35 @@ +name: Deploy GitHub Pages + +on: + workflow_run: + workflows: + - Build examples for Launchpad + - Release Checker + types: + - completed + +jobs: + merge-and-deploy: + runs-on: ubuntu-latest + steps: + - name: Download site from launchpad examples + uses: actions/download-artifact@v3 + with: + name: gh-pages-launchpad + path: ./combined-site + + - name: Download site from release checker + uses: actions/download-artifact@v3 + with: + name: gh-pages-release-checker + path: ./combined-site + + - name: Upload combined site + uses: actions/upload-pages-artifact@v3 + with: + path: ./combined-site + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 + diff --git a/.github/workflows/release_checker.yml b/.github/workflows/release_checker.yml new file mode 100644 index 000000000..a3ede5ec1 --- /dev/null +++ b/.github/workflows/release_checker.yml @@ -0,0 +1,43 @@ +name: Release Checker + +on: + push: + branches: + - master + workflow_dispatch: # can be run manually + + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + + permissions: + pages: write + id-token: write + + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + with: + fetch-depth: 0 # all git history + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Run release_checker.py + run: | + pip install requests pyyaml tabulate wcwidth pytz + mkdir site + python .github/ci/release_checker.py > site/release_checker.html + + - name: Upload file to gh pages + uses: actions/upload-pages-artifact@v3 + with: + name: gh-pages-release-checker + path: site diff --git a/README.md b/README.md index a2f353cf7..8ea950d8f 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@ # ESP-BSP: Espressif's Board Support Packages -| [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) | -| :---------------------------------------: | :-------------------------: | :-------------------: | :--------------: | :-----------------------------------: | :------------------------: | +| [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) | +| :---------------------------------------: | :-------------------------: | :-------------------: | :--------------: | :-----------------------------------: | :------------------------: | :------------------------: | 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.