From 4c0b252ecf5d635c1aee2c4a5dc0f68b074352cd Mon Sep 17 00:00:00 2001 From: Liu Jinhong Date: Tue, 1 Sep 2026 17:29:50 +0800 Subject: [PATCH 1/2] ci: trigger dry-run upload validation --- .cursor/mcp.json | 2 +- .github/workflows/ci.yml | 20 +- GETTING_STARTED.md | 90 --------- README.md | 61 +++--- README_CN.md | 53 +++--- VENDOR_GUIDE.md | 123 ++++++++++++ VENDOR_GUIDE_CN.md | 77 ++++++++ ci/scripts/check_bmgr_catalog.py | 102 ++++++++++ ci/scripts/discover_matrix.py | 17 +- ci/scripts/pin_bmgr_version.py | 2 +- ci/scripts/test_check_bmgr_catalog.py | 61 ++++++ ci/scripts/test_discover_matrix.py | 47 +++++ ci/test_app/main/idf_component.yml | 4 +- docs/HOW_TO_GITHUB.md | 94 ---------- example_board/board_devices.yaml | 2 +- idf_component.yml | 9 +- image.png | Bin 0 -> 18502 bytes scripts/initialize_board_pack.py | 176 ++++++++++++++++++ scripts/test_initialize_board_pack.py | 127 +++++++++++++ scripts/test_update_supported_boards_table.py | 103 ++++++++++ scripts/update_supported_boards_table.py | 154 +++++++++++++++ 21 files changed, 1079 insertions(+), 245 deletions(-) delete mode 100644 GETTING_STARTED.md create mode 100644 VENDOR_GUIDE.md create mode 100644 VENDOR_GUIDE_CN.md create mode 100644 ci/scripts/check_bmgr_catalog.py create mode 100644 ci/scripts/test_check_bmgr_catalog.py create mode 100644 ci/scripts/test_discover_matrix.py delete mode 100644 docs/HOW_TO_GITHUB.md create mode 100644 image.png create mode 100644 scripts/initialize_board_pack.py create mode 100644 scripts/test_initialize_board_pack.py create mode 100644 scripts/test_update_supported_boards_table.py create mode 100644 scripts/update_supported_boards_table.py diff --git a/.cursor/mcp.json b/.cursor/mcp.json index be488ea..262ea08 100644 --- a/.cursor/mcp.json +++ b/.cursor/mcp.json @@ -2,7 +2,7 @@ "mcpServers": { "esp-pilot": { "type": "http", - "url": "https://mcp.esp-pilot.espressif.tools/mcp" + "url": "https://mcp.esp-pilot.espressif.com/mcp" } } } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd546b6..73048bf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,10 +34,24 @@ jobs: target: ${{ matrix.target }} path: ci/test_app command: | - pip install -q esp-bmgr-assist + if [ -n "${{ matrix.component_manager }}" ]; then + python -m pip install -q --upgrade "${{ matrix.component_manager }}" + fi + python -m pip install -q --upgrade esp-bmgr-assist + python -m pip show esp-bmgr-assist esp-bmgr-py idf-component-manager || true python ../scripts/pin_bmgr_version.py "${{ matrix.bmgr }}" - idf.py bmgr -b "${{ matrix.board }}" - idf.py build + idf.py bmgr -l + if python ../scripts/check_bmgr_catalog.py --chip "${{ matrix.target }}"; then + idf.py bmgr -b "${{ matrix.board }}" + idf.py build + else + status=$? + if [ "${status}" -eq 10 ]; then + echo "::notice title=Unsupported board/IDF combination::${{ matrix.board }} (${{ matrix.target }}) is not supported by Board Manager ${{ matrix.bmgr }} with this ESP-IDF version." + exit 0 + fi + exit "${status}" + fi upload: needs: test diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md deleted file mode 100644 index 4fa3580..0000000 --- a/GETTING_STARTED.md +++ /dev/null @@ -1,90 +0,0 @@ -# 厂家上手 - -用本模板创建自己的板组件仓库后,按下面 5 步完成第一次发布。GitHub Actions 已经写好,不必改 workflow。 - -## 开始前 - -- 用**个人 GitHub 账号**创建仓库。组件注册库的默认 namespace 就是这个 GitHub 用户名。先不要建在 GitHub Organization 下,否则用户名和 namespace 对不上,上传会失败。 -- 新仓库名必须包含 `boards`,只使用小写字母、数字、下划线,例如 `acme_boards`。ESP Board Manager 只自动扫描名称里带 `boards` 的组件。 -- 开发板目录名、`board_info.yaml` 里的 `board` 字段必须一致,同样只允许小写字母、数字、下划线,不能用中划线。 - -## 第 1 步:用模板建仓库 - -在 GitHub 上打开本模板仓库,点击 **Use this template** → **Create a new repository**,仓库名填 `yourcompany_boards`。 - -克隆到本地: - -```bash -git clone https://github.com/YOUR_GITHUB_USERNAME/yourcompany_boards.git -cd yourcompany_boards -``` - -## 第 2 步:注册组件注册库并保存 Token - -1. 用**同一个** GitHub 账号打开 [components.espressif.com](https://components.espressif.com) 并登录。 -2. 右上角用户名 → **Tokens** → **Create**,复制 Token。 -3. 打开 GitHub 仓库 **Settings** → **Secrets and variables** → **Actions** → **New repository secret**。 -4. Name 填 `IDF_COMPONENT_API_TOKEN`,Secret 填刚才的 Token。 - -不要把 Token 写进仓库文件,也不要发到聊天软件。 - -## 第 3 步:换成自己的开发板 - -1. 删除或改名 `example_board/`。 -2. 对照原理图编写 `board_info.yaml`、`board_peripherals.yaml`、`board_devices.yaml`。可用 Cursor,本仓库已预置 MCP:`https://mcp.esp-pilot.espressif.tools/mcp`。引脚和地址必须对照原理图确认。 -3. 也可使用网页工具 。 -4. 把 `LICENSE` 末尾的 `CHANGE_ME` 改成公司名,更新 `README.md` / `README_CN.md` 里的开发板列表。 -5. 需要更高版本的 Board Manager 能力时,只改根目录 `idf_component.yml` 里的: - - ```yaml - espressif/esp_board_manager: - version: ">=0.7.0" - ``` - - 把 `0.7.0` 抬到实际需要的下限。CI 会自动读这个下限,并再测当前最新 Board Manager。不要去改 `.github/workflows/ci.yml`。 - -本地先验证: - -```bash -pip install esp-bmgr-assist -cd /path/to/your_idf_project -# 在工程 main/idf_component.yml 里用 override_path 指向本仓库,然后: -idf.py bmgr -l -idf.py bmgr -b -idf.py build -``` - -## 第 4 步:合入 main - -把改动推到 `main`(或先开 Pull Request,绿了再合)。 - -CI 会对仓库里每一块开发板,在 ESP-IDF `v5.5.4` 和 `latest` 上,分别用 Board Manager 下限版本和最新版本执行 `idf.py bmgr -b` 和编译。任一路失败都不能合入,包括 `latest`。 - -新仓库第一次合入 `main` 时,当前 `version`(模板是 `0.7.0`,与 Board Manager 对齐)还不存在,会发第一次。之后同一 `version` 再合入,upload 会跳过,不会覆盖。这是故意的:允许先合入未发布的改动。 - -## 第 5 步:发布组件 - -确认要给应用使用时: - -1. 更新 `CHANGELOG.md`。 -2. 把根目录 `idf_component.yml` 的 `version` 从 `0.7.0` 改成新版本(例如 `0.7.1`)。 -3. 提交并推到 `main`。 - -上传 job 发现该版本尚未存在,就会发到: - -`https://components.espressif.com/components//<仓库名>` - -同一 `version` 不能覆盖。发错只能再发一个新版本号。 - -应用侧添加: - -```bash -idf.py add-dependency "YOUR_GITHUB_USERNAME/YOUR_REPO_NAME" -``` - -## 不要改 - -- `.github/workflows/ci.yml` -- `CMakeLists.txt`(保持 `idf_component_register()`) -- 不要把 `esp_board_manager` 源码拷进本仓库 -- 不要把原理图、大二进制放进要上传的组件包(`.github/` 和 `ci/` 已排除) diff --git a/README.md b/README.md index 2878912..61b3258 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,52 @@ -# ESP Board Manager 厂家板组件模板 +# YOUR_VENDOR_NAME ESP Board Manager Board Pack [中文](README_CN.md) -This repository is an ESP Board Manager **board pack**: YAML board definitions that applications can download from the [ESP Component Registry](https://components.espressif.com). +This component provides ESP Board Manager YAML definitions for YOUR_VENDOR_NAME +development boards. Applications can install it from the +[ESP Component Registry](https://components.espressif.com) and select a board. -## Use in an application +> Template note: Replace `YOUR_VENDOR_NAME` and update the following table with +> the supported boards before publishing. -1. Install the helper once in your ESP-IDF Python environment: +## Supported Boards - ```bash - pip install esp-bmgr-assist - ``` +Run `python scripts/update_supported_boards_table.py` to update the board and +chip columns. Maintainers fill in the device capability columns. -2. Add this board pack to the project (replace the namespace and name with your GitHub user name and this repository name): + +| Board | Chip | Audio | SD Card | LCD | LCD Touch | Camera | Buttons | LED Strip | Knob | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| `example_board` | ESP32-S3 | | | | | | | | | + - ```bash - idf.py add-dependency "YOUR_GITHUB_USERNAME/YOUR_REPO_NAME" - ``` +## Use This Pack in an Application - The pack already depends on `espressif/esp_board_manager` (`>=0.7.0`). You do not need to add Board Manager again unless you want a tighter pin. +Install the helper once in the active ESP-IDF Python environment: -3. List and select a board: +```bash +python -m pip install --upgrade esp-bmgr-assist +``` - ```bash - idf.py bmgr -l - idf.py bmgr -b - idf.py build - ``` +Add the released component, then select a board: -## Supported boards +```bash +idf.py add-dependency "YOUR_NAMESPACE/YOUR_COMPONENT_NAME" +idf.py bmgr -l +idf.py bmgr -b +idf.py build +``` -| Board | Chip | Notes | -|---|---|---| -| `example_board` | ESP32-S3 | Placeholder. Replace it with a real board before publishing. | +The board pack already declares `espressif/esp_board_manager`; applications do +not need to add it again unless they need a tighter version constraint. -## Create or publish this pack +## Vendor Maintenance Guide -Vendors starting from this GitHub template should follow [GETTING_STARTED.md](GETTING_STARTED.md). +For creating a repository from this template, migrating an existing BSP, +configuring CI, and publishing the component, see +[VENDOR_GUIDE.md](VENDOR_GUIDE.md). + +## License + +This component uses Apache-2.0. Preserve and comply with existing copyright +notices and licenses when migrating a BSP or adding third-party content. diff --git a/README_CN.md b/README_CN.md index 9499b45..48bd2e4 100644 --- a/README_CN.md +++ b/README_CN.md @@ -1,39 +1,44 @@ -# ESP Board Manager 厂家板组件模板 +# YOUR_VENDOR_NAME ESP Board Manager 板组件 [English](README.md) -本仓库是一份 ESP Board Manager **板组件**:用 YAML 描述开发板,应用可以从 [ESP 组件注册库](https://components.espressif.com) 下载后选板。 +本组件提供 YOUR_VENDOR_NAME 开发板的 ESP Board Manager YAML 定义,可从 [ESP 组件注册库](https://components.espressif.com) 下载并在应用工程中选择。 -## 在应用工程中使用 +> 模板提示:发布前替换 `YOUR_VENDOR_NAME`,并将下表更新为实际支持的开发板。 -1. 在已激活的 ESP-IDF Python 环境中安装一次辅助工具: +## 支持的开发板 - ```bash - pip install esp-bmgr-assist - ``` +运行 `python scripts/update_supported_boards_table.py` 可更新板名和芯片列;设备能力列由维护者填写。 -2. 将本板组件加入工程(把命名空间和组件名换成您的 GitHub 用户名和本仓库名): + +| 开发板名称 | 芯片 | 音频 | SD 卡 | LCD | LCD 触摸 | 摄像头 | 按键 | LED 灯带 | 旋钮 | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| `example_board` | ESP32-S3 | | | | | | | | | + - ```bash - idf.py add-dependency "YOUR_GITHUB_USERNAME/YOUR_REPO_NAME" - ``` +## 在应用工程中使用本组件 - 本组件已经依赖 `espressif/esp_board_manager`(`>=0.7.0`)。除非您要收紧版本,否则不必再单独添加 Board Manager。 +在已激活的 ESP-IDF Python 环境中安装一次辅助工具: -3. 列出并选择开发板: +```bash +python -m pip install --upgrade esp-bmgr-assist +``` - ```bash - idf.py bmgr -l - idf.py bmgr -b - idf.py build - ``` +添加已发布的组件后,选择开发板: -## 支持的开发板 +```bash +idf.py add-dependency "YOUR_NAMESPACE/YOUR_COMPONENT_NAME" +idf.py bmgr -l +idf.py bmgr -b +idf.py build +``` + +本板组件已声明 `espressif/esp_board_manager` 依赖。除非应用需要更严格的版本限制,否则不必再次添加。 + +## 厂家维护指南 -| 开发板 | 芯片 | 说明 | -|---|---|---| -| `example_board` | ESP32-S3 | 占位示例。正式发布前请替换为真实开发板。 | +从本模板创建仓库、迁移现有 BSP、配置 CI 和发布组件,请参见 [VENDOR_GUIDE_CN.md](VENDOR_GUIDE_CN.md)。 -## 创建或发布本组件 +## 许可证 -从本 GitHub 模板新建仓库的厂家,请按 [GETTING_STARTED.md](GETTING_STARTED.md) 操作。 +本组件采用 Apache-2.0。迁移 BSP 或引入第三方内容时,请保留并遵守其原有版权声明和许可证。 diff --git a/VENDOR_GUIDE.md b/VENDOR_GUIDE.md new file mode 100644 index 0000000..27324f6 --- /dev/null +++ b/VENDOR_GUIDE.md @@ -0,0 +1,123 @@ +# ESP Board Manager Vendor Board Pack Guide + +[中文](VENDOR_GUIDE_CN.md) + +Use this guide to create a vendor board-pack repository from this GitHub +template, adapt boards, and configure CI. Maintain the public component +description in [README.md](README.md). + +## 1. Create and Configure the Repository + +1. Open the [template repository](https://github.com/LiuCodee/boards-template) + on GitHub and select **Use this template** → **Create a new repository**. + + ![Create a repository from the GitHub template](image.png) + +2. Choose the personal GitHub account or company organization that will own the + repository, name it, and choose **Public**. +3. Clone the new repository: + + ```bash + git clone git@github.com:YOUR_NAMESPACE/REPOSITORY_NAME.git + cd REPOSITORY_NAME + ``` + +4. Run `python scripts/initialize_board_pack.py`. The script asks in Chinese + and English for the vendor name, namespace, component name, description, + and copyright holder. Press Enter to accept a default value; the script + updates the README files, `idf_component.yml`, `LICENSE`, and CI. +5. Sign in to [ESP Component Registry](https://components.espressif.com) with + your GitHub account and create an API token with the `write:components` + scope. +6. In your GitHub repository, open **Settings** → **Secrets and variables** → + **Actions** → **New repository secret**. Create the + `IDF_COMPONENT_API_TOKEN` secret and set it to the token value. + +## 2. Adapt and Publish the Board + +First prepare the board materials: schematics, hardware feature descriptions, +and board initialization code from existing projects, if available. + +Configure [ESP Pilot MCP](https://mcp.espressif.com/#esp-pilot-mcp) in your AI +tool, then provide those materials to the AI to help create or migrate board +configurations. If its introduction page is unavailable, give the AI assistant +this direct URL: `https://mcp.esp-pilot.espressif.com/mcp`. + +To create or adjust configurations manually, refer to the +[Create a Board Guide](https://docs.espressif.com/projects/esp-board-manager/en/latest/create-board/index.html) +and choose the appropriate method. + +After adding boards, run +`python scripts/update_supported_boards_table.py` to update the board tables in +both README files. The script does not modify existing table content; it only +appends the board name and chip for new boards at the end of the table. + +Submit board changes through a pull request. CI generates and builds every +supported board combination across the declared Board Manager lower bound, the +latest version, ESP-IDF `v5.5.4`, and `latest`. + +Before production publishing, check that: + +- `example_board/` has been removed and real boards have been added. +- Both README files list the vendor, component description, and supported boards. +- Every CI matrix entry has passed. + +When the checks pass, remove `--dry-run` from the upload command in +`.github/workflows/ci.yml`, then merge to `main` or push a `v*` tag to publish +the new version. + +## Notes + +### Component Namespace + +The first sign-in to ESP Component Registry with a GitHub account automatically +creates a default namespace matching that GitHub username. No separate request +is needed: + +```text +GitHub: user/demo_boards → Registry: user/demo_boards +``` + +To use a company name or another specified namespace, open the account menu in +ESP Component Registry and request it under **Permissions** → **Namespace +Requests**. After approval, create the API token with an account that has +permission to publish to that namespace. + +### Repository and Board Directories + +- Use a public repository name containing `boards`, with only lowercase letters, + numbers, and underscores, for example `acme_boards`. +- Board directories can be nested up to three levels below the repository root. + CI discovers and tests every board within this range: + + ```text + example_board/ + audio/esp32_s3_speaker/ + display/round/esp32_p4_screen/ + ``` + +- Each board directory name must match the `board` field in its + `board_info.yaml`. Directories at the fourth level or deeper are not + discovered. +- Retain the `esp_board_manager`, `board_manager`, and `boards` values in + `idf_component.yml`'s `tags` list so board-pack components can be discovered + automatically. You may add tags, but do not remove these three. + +### License + +This template uses Apache-2.0. For new content, replace `CHANGE_ME` in +`LICENSE` with the copyright holder, normally the legal company name. Preserve +and comply with existing notices and licenses when adding third-party content. + +### Release Version + +The component version must follow the [ESP-IDF Component Manager versioning +scheme](https://docs.espressif.com/projects/idf-component-manager/en/latest/reference/versioning.html). +The upload job in `.github/workflows/ci.yml` uses +`compote component upload --dry-run` by default to validate authentication and +packaging without creating a public registry version. `--allow-existing` +prevents the workflow from overwriting an existing version. + +The initialization script keeps `${{ github.repository_owner }}` in the upload +command by default. It replaces that argument with a fixed namespace only when +the entered namespace differs from the repository owner. diff --git a/VENDOR_GUIDE_CN.md b/VENDOR_GUIDE_CN.md new file mode 100644 index 0000000..ac40a68 --- /dev/null +++ b/VENDOR_GUIDE_CN.md @@ -0,0 +1,77 @@ +# ESP Board Manager 厂家板组件维护指南 + +[English](VENDOR_GUIDE.md) + +本指南用于从本 GitHub 模板创建厂家板组件仓库、适配板卡和配置 CI。发布后的组件说明请维护在 [README_CN.md](README_CN.md)。 + +## 1. 创建和配置仓库 + +1. 在 GitHub 打开[模板仓库](https://github.com/LiuCodee/boards-template),选择 **Use this template** → **Create a new repository**。 +![从 GitHub 模板创建仓库](image.png) +2. 选择个人 GitHub 账号或公司 Organization,填写仓库名,并选择 **Public**。 +3. 克隆新仓库: + + ```bash + git clone git@github.com:YOUR_NAMESPACE/REPOSITORY_NAME.git + cd REPOSITORY_NAME + ``` + +4. 运行 `python scripts/initialize_board_pack.py` 脚本,逐项输入厂商名称、namespace、组件名、组件描述和版权持有人,输入回车可采用默认值,运行结束自动更新 README、`idf_component.yml`、`LICENSE` 和 CI。 +5. 使用 GitHub 账号登录 [ESP 组件注册库](https://components.espressif.com),创建具有 `write:components` scope 的 API Token。 +6. 在 GitHub 仓库中打开 **Settings** → **Secrets and variables** → **Actions** → **New repository secret**,创建名为 `IDF_COMPONENT_API_TOKEN` 的 Secret 并填入刚才创建的 Token。 + +## 2. 适配板子并发布 + +先准备开发板资料:原理图、硬件功能说明,以及现有工程中的板级初始化代码(如有)。 + +建议优先在 AI 工具中配置 [ESP Pilot MCP](https://mcp.espressif.com/#esp-pilot-mcp),并将上述资料提供给 AI,辅助创建或迁移板卡配置。如果 MCP 介绍页无法访问,可直接将 `https://mcp.esp-pilot.espressif.com/mcp` 提供给 AI 助手进行配置。 + +如需手动调整或创建配置,请参考 [创建开发板指南](https://docs.espressif.com/projects/esp-board-manager/zh_CN/latest/create-board/index.html) 选择合适的方法。 + +添加开发板后,运行 `python scripts/update_supported_boards_table.py` 更新两份 README 的开发板表。脚本不会修改已有表格内容,只会将新开发板的板名和芯片追加到表格末尾。 + +通过 Pull Request 提交开发板改动。CI 会对每块开发板在声明的 Board Manager 最低支持版本、最新版本以及 ESP-IDF `v5.5.4`、`latest` 的组合中进行兼容性检查,并生成和编译所有受支持的组合。 + +正式发布前,请检查以下内容: + +- 删除 `example_board/`,并添加真实开发板。 +- 更新 `idf_component.yml` 、 `README.md` 和 `README_CN.md` 中的厂商名称、组件说明和开发板列表等信息。 +- 所有 CI 矩阵项通过。 + +确认无误后,从 `.github/workflows/ci.yml` 的 upload 命令中删除 `--dry-run`,再合入 `main` 或推送 `v*` tag 发布新版本。 + +## 注意事项 + +### 组件命名空间 + +首次使用 GitHub 账号登录 ESP 组件注册库时,系统会自动创建与 GitHub 用户名相同的默认 namespace,无需另行申请: + +```text +GitHub:user/demo_boards → 组件注册库:user/demo_boards +``` + +如果需要使用公司名称或其他指定 namespace,请在 ESP 组件注册库右上角账号菜单的 **Permissions** → **Namespace Requests** 中申请。申请通过后,使用具有该 namespace 发布权限的账号创建 Token。 + +### 仓库和开发板目录 + +- 仓库名必须包含 `boards`,并且只使用小写字母、数字和下划线,例如 `acme_boards`。 +- 开发板目录相对仓库根目录最多支持三层嵌套。CI 会扫描并测试这三层范围内的所有开发板: + + ```text + example_board/ + audio/esp32_s3_speaker/ + display/round/esp32_p4_screen/ + ``` + +- 每个开发板目录名必须与 `board_info.yaml` 中的 `board` 字段一致。第四层及更深层的目录不会被发现。 +- 保留 `idf_component.yml` 中的 `esp_board_manager`、`board_manager` 和 `boards` 三个 `tags` 值,以便后续自动发现板卡组件。可以新增标签,但不要删除这三个标签。 + +### 许可证 + +本模板采用 Apache-2.0。对新建内容,将 `LICENSE` 中的 `CHANGE_ME` 替换为版权持有人,通常为公司法定名称。引入第三方内容时,保留并遵守原有版权声明和许可证。 + +### 发布版本 + +组件版本号须遵循 [ESP-IDF Component Manager 版本规则](https://docs.espressif.com/projects/idf-component-manager/en/latest/reference/versioning.html)。`.github/workflows/ci.yml` 的 upload job 默认使用 `compote component upload --dry-run` 验证认证和打包,不会创建公开版本。`--allow-existing` 会阻止 workflow 覆盖已存在的版本。 + +初始化脚本默认保留 upload 命令中的 `${{ github.repository_owner }}`。仅当输入的 namespace 与仓库所有者不同时,脚本才会将该参数改为固定的自定义 namespace。 diff --git a/ci/scripts/check_bmgr_catalog.py b/ci/scripts/check_bmgr_catalog.py new file mode 100644 index 0000000..70050e5 --- /dev/null +++ b/ci/scripts/check_bmgr_catalog.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +"""Check whether a Board Manager catalog supports a chip for the active IDF.""" + +from __future__ import annotations + +import argparse +import os +import re +import sys +from pathlib import Path +from typing import Callable, Tuple + + +EXIT_UNSUPPORTED = 10 +IDF_VERSION_RE = re.compile(r"^\s*set\s*\(\s*IDF_VERSION_([A-Z]{5})\s+(\d+)") + + +def _normalize_chip(chip: str) -> str: + return chip.strip().lower().replace("-", "") + + +def _load_provider(bmgr_root: Path, idf_version: str): + catalog_dir = bmgr_root / "private_inc" / "soc_capability_catalog" + if not catalog_dir.is_dir(): + raise FileNotFoundError(f"missing Board Manager catalog: {catalog_dir}") + sys.path.insert(0, str(bmgr_root)) + from generators.utils.soc_capabilities import SocCapabilityProvider + + return SocCapabilityProvider.load_for_idf_version(catalog_dir, idf_version) + + +def check_compatibility( + bmgr_root: Path, + idf_version: str, + chip: str, + provider_loader: Callable[[Path, str], object] = _load_provider, +) -> Tuple[bool, str]: + """Return whether the selected BMGR catalog profile contains ``chip``.""" + provider = provider_loader(bmgr_root, idf_version) + profile = str(provider.selected_profile_id) + try: + provider.chip(_normalize_chip(chip)) + except KeyError: + return False, profile + return True, profile + + +def _idf_version(idf_path: Path) -> str: + version_file = idf_path / "tools" / "cmake" / "version.cmake" + parts = {} + with version_file.open(encoding="utf-8") as file: + for line in file: + match = IDF_VERSION_RE.match(line) + if match: + parts[match.group(1)] = match.group(2) + try: + return "{MAJOR}.{MINOR}.{PATCH}".format(**parts) + except KeyError as error: + raise RuntimeError(f"could not read ESP-IDF version from {version_file}") from error + + +def main() -> int: + parser = argparse.ArgumentParser( + description="Check active ESP-IDF chip support in the resolved BMGR catalog." + ) + parser.add_argument("--chip", required=True) + parser.add_argument( + "--bmgr-root", + type=Path, + default=Path("managed_components/espressif__esp_board_manager"), + ) + parser.add_argument("--idf-path", type=Path, default=os.environ.get("IDF_PATH")) + args = parser.parse_args() + + if args.idf_path is None: + print("IDF_PATH is not set", file=sys.stderr) + return 2 + try: + idf_version = _idf_version(args.idf_path) + supported, profile = check_compatibility( + args.bmgr_root, idf_version, args.chip + ) + except Exception as error: + print(f"failed to inspect Board Manager catalog: {error}", file=sys.stderr) + return 2 + + if supported: + print( + f"Board Manager catalog profile {profile} supports " + f"{_normalize_chip(args.chip)}." + ) + return 0 + + print( + f"Board Manager catalog profile {profile} does not support " + f"{_normalize_chip(args.chip)} for ESP-IDF {idf_version}." + ) + return EXIT_UNSUPPORTED + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/ci/scripts/discover_matrix.py b/ci/scripts/discover_matrix.py index b638375..e1d8090 100644 --- a/ci/scripts/discover_matrix.py +++ b/ci/scripts/discover_matrix.py @@ -15,10 +15,18 @@ REPO_ROOT = Path(__file__).resolve().parents[2] IDF_VERSIONS = ["v5.5.4", "latest"] +IDF_COMPONENT_MANAGER_SPECS = { + # idf-component-manager 2.4.8 in the v5.5.4 image mishandles BMGR's + # Kconfig-based dependency conditions. Keep the workaround in the IDF 5.x + # series; ESP-IDF latest provides its own compatible Component Manager. + "v5.5.4": "idf-component-manager>=2.5.0,<3.0.0", + "latest": "", +} BMGR_DEP_KEYS = ( "espressif/esp_board_manager", "esp_board_manager", ) +MAX_BOARD_DIRECTORY_DEPTH = 3 REGISTRY_VERSIONS_URL = ( "https://components.espressif.com/api/v1/components/espressif/esp_board_manager/versions" ) @@ -101,8 +109,12 @@ def _key(value: str): def _discover_boards() -> list[dict[str, str]]: boards = [] - for info_path in sorted(REPO_ROOT.glob("*/board_info.yaml")): - if info_path.parent.name.startswith("."): + for info_path in sorted(REPO_ROOT.rglob("board_info.yaml")): + board_dir = info_path.parent.relative_to(REPO_ROOT) + if ( + len(board_dir.parts) > MAX_BOARD_DIRECTORY_DEPTH + or any(part.startswith(".") for part in board_dir.parts) + ): continue with info_path.open(encoding="utf-8") as fh: info = yaml.safe_load(fh) or {} @@ -147,6 +159,7 @@ def main() -> int: "target": board["target"], "idf": idf, "bmgr": bmgr, + "component_manager": IDF_COMPONENT_MANAGER_SPECS[idf], } ) matrix = {"include": include} diff --git a/ci/scripts/pin_bmgr_version.py b/ci/scripts/pin_bmgr_version.py index 26dad56..0136c62 100644 --- a/ci/scripts/pin_bmgr_version.py +++ b/ci/scripts/pin_bmgr_version.py @@ -15,7 +15,7 @@ def main() -> int: print("usage: pin_bmgr_version.py ", file=sys.stderr) return 2 requested = sys.argv[1].strip() - version = "*" if requested == "*" else f"=={requested}" + version = '"*"' if requested == "*" else f'"=={requested}"' text = MANIFEST.read_text(encoding="utf-8") updated, count = re.subn( r"(espressif/esp_board_manager:[\s\S]*?version:\s*)([^\n]+)", diff --git a/ci/scripts/test_check_bmgr_catalog.py b/ci/scripts/test_check_bmgr_catalog.py new file mode 100644 index 0000000..0ea8d84 --- /dev/null +++ b/ci/scripts/test_check_bmgr_catalog.py @@ -0,0 +1,61 @@ +import importlib.util +import unittest +from pathlib import Path + + +SCRIPT_PATH = Path(__file__).with_name("check_bmgr_catalog.py") +SPEC = importlib.util.spec_from_file_location("check_bmgr_catalog", SCRIPT_PATH) +check_bmgr_catalog = importlib.util.module_from_spec(SPEC) +assert SPEC.loader is not None +SPEC.loader.exec_module(check_bmgr_catalog) + + +class FakeProvider: + selected_profile_id = "5.5" + + def __init__(self, supported_chips): + self.supported_chips = supported_chips + + def chip(self, chip): + if chip not in self.supported_chips: + raise KeyError(chip) + + +class CheckBmgrCatalogTest(unittest.TestCase): + def test_accepts_chip_present_in_selected_catalog(self): + provider = FakeProvider({"esp32s3"}) + + supported, profile = check_bmgr_catalog.check_compatibility( + Path("/unused"), + "5.5.4", + "ESP32-S3", + provider_loader=lambda _root, _version: provider, + ) + + self.assertTrue(supported) + self.assertEqual(profile, "5.5") + + def test_reports_chip_missing_from_selected_catalog_as_unsupported(self): + provider = FakeProvider({"esp32s3"}) + + supported, profile = check_bmgr_catalog.check_compatibility( + Path("/unused"), + "5.5.4", + "esp32s31", + provider_loader=lambda _root, _version: provider, + ) + + self.assertFalse(supported) + self.assertEqual(profile, "5.5") + + def test_does_not_classify_catalog_load_errors_as_unsupported(self): + def broken_loader(_root, _version): + raise RuntimeError("catalog is corrupt") + + with self.assertRaisesRegex(RuntimeError, "catalog is corrupt"): + check_bmgr_catalog.check_compatibility( + Path("/unused"), + "5.5.4", + "esp32s31", + provider_loader=broken_loader, + ) diff --git a/ci/scripts/test_discover_matrix.py b/ci/scripts/test_discover_matrix.py new file mode 100644 index 0000000..7ef5a1a --- /dev/null +++ b/ci/scripts/test_discover_matrix.py @@ -0,0 +1,47 @@ +import importlib.util +import tempfile +import unittest +from pathlib import Path + + +SCRIPT_PATH = Path(__file__).with_name("discover_matrix.py") +SPEC = importlib.util.spec_from_file_location("discover_matrix", SCRIPT_PATH) +discover_matrix = importlib.util.module_from_spec(SPEC) +assert SPEC.loader is not None +SPEC.loader.exec_module(discover_matrix) + + +class DiscoverBoardsTest(unittest.TestCase): + def setUp(self): + self.temp_dir = tempfile.TemporaryDirectory() + self.repo_root = Path(self.temp_dir.name) + self.original_repo_root = discover_matrix.REPO_ROOT + discover_matrix.REPO_ROOT = self.repo_root + + def tearDown(self): + discover_matrix.REPO_ROOT = self.original_repo_root + self.temp_dir.cleanup() + + def _add_board(self, relative_dir: str, name: str): + board_dir = self.repo_root / relative_dir + board_dir.mkdir(parents=True) + (board_dir / "board_info.yaml").write_text( + f"board: {name}\nchip: esp32s3\n", encoding="utf-8" + ) + + def test_discovers_board_directories_up_to_three_levels_deep(self): + self._add_board("root_board", "root_board") + self._add_board("audio/speaker_board", "speaker_board") + self._add_board("display/round/screen_board", "screen_board") + self._add_board("archived/old/unused/ignored_board", "ignored_board") + + boards = discover_matrix._discover_boards() + + self.assertEqual( + boards, + [ + {"board": "speaker_board", "target": "esp32s3"}, + {"board": "screen_board", "target": "esp32s3"}, + {"board": "root_board", "target": "esp32s3"}, + ], + ) diff --git a/ci/test_app/main/idf_component.yml b/ci/test_app/main/idf_component.yml index 8a1f8e8..c84cee3 100644 --- a/ci/test_app/main/idf_component.yml +++ b/ci/test_app/main/idf_component.yml @@ -2,6 +2,6 @@ dependencies: espressif/esp_board_manager: version: ">=0.7.0" require: public - local/vendor_boards: - override_path: ../.. + demo_boards: + override_path: ../../../ version: "*" diff --git a/docs/HOW_TO_GITHUB.md b/docs/HOW_TO_GITHUB.md deleted file mode 100644 index 9490b01..0000000 --- a/docs/HOW_TO_GITHUB.md +++ /dev/null @@ -1,94 +0,0 @@ -# 把本模板推到 GitHub(给乐鑫维护者) - -这份说明只给要**发布模板仓库**的人看。厂家请看仓库根目录的 `GETTING_STARTED.md`。 - -下面默认您还没有自己建过 GitHub 仓库。不需要先建空文件夹:模板文件已经在本地写好。 - -## 几个名字 - -| 名字 | 含义 | -|---|---| -| Git | 电脑上的版本记录工具。本目录执行 `git init` 之后,才有提交历史。 | -| GitHub | 存放 Git 仓库的网站。浏览器打开 github.com。 | -| 仓库(repository) | 一组文件加历史。本地一份,GitHub 上一份,用 `git push` 同步。 | -| Template repository | 一种特殊仓库。别人打开后可以点 **Use this template**,复制出属于自己的新仓库,而不必 Fork。 | - -厂家之后的操作是:打开这个模板 → Use this template → 得到 `某公司_boards`。您现在要做的,只是把本地这份文件变成 GitHub 上的模板仓库。 - -## 第 1 步:GitHub 账号 - -1. 打开 ,用公司邮箱注册,或使用已有账号登录。 -2. 建议最终把模板放在乐鑫组织下,例如 `espressif/esp-vendor-boards-template`。第一次练习也可以先建在您的个人账号下,确认流程后再转到组织。 - -## 第 2 步:本机已有文件 - -模板路径: - -```text -/home/liujinhong/esp/esp-vendor-boards-template -``` - -若该目录还不是 Git 仓库,在终端执行: - -```bash -cd /home/liujinhong/esp/esp-vendor-boards-template -git init -git add . -git status -``` - -此时还不要 `git commit`,除非您准备好提交说明。需要提交时可以说一声,由助手按仓库规范提交。 - -## 第 3 步:在 GitHub 上新建空仓库 - -1. 登录后打开 。 -2. Repository name 填 `esp-vendor-boards-template`(可改,建议带 `template` 以免和厂家的 `*_boards` 混淆)。 -3. Public。 -4. **不要**勾选 Add a README、Add .gitignore、Choose a license。本地已经有这些文件,GitHub 再生成会冲突。 -5. 点击 **Create repository**。 - -页面会给出一个空仓库地址,例如: - -```text -https://github.com/YOUR_USER/esp-vendor-boards-template.git -``` - -## 第 4 步:把本地文件推上去 - -把 `YOUR_USER` 换成第 1 步的 GitHub 用户名或组织名: - -```bash -cd /home/liujinhong/esp/esp-vendor-boards-template -git remote add origin https://github.com/YOUR_USER/esp-vendor-boards-template.git -git branch -M main -git push -u origin main -``` - -第一次 `git push` 会要求登录 GitHub。浏览器授权或 Personal Access Token 都可以。公司账号若开了 SSO,按提示授权。 - -推送成功后,刷新 GitHub 仓库页面,应能看到 `GETTING_STARTED.md`、`example_board/`、`.github/workflows/ci.yml`。 - -## 第 5 步:标记为 Template - -1. 打开该仓库的 **Settings**。 -2. 在 General 页顶部找到 **Template repository**,勾选。 -3. 保存后,仓库页的绿色 Code 按钮旁边会出现 **Use this template**。 - -发给厂家的就是这个仓库链接。厂家只点 Use this template,不必 Fork,也不必改 GitHub Actions 文件。 - -## 第 6 步:自己先走一遍厂家流程(建议) - -用另一个 GitHub 账号(或同一账号下另一个仓库名,例如 `demo_boards`)点 Use this template,按 `GETTING_STARTED.md` 配 Token、改一块板、合入 `main`。确认: - -- 第一次合入 `main`(模板自带 `version: 0.7.0`)后,组件出现在 `https://components.espressif.com/components/<用户名>/demo_boards`。 -- 之后没 bump `version` 时,upload 成功但注册库版本不变。 -- bump 成 `0.7.1` 后再推 `main`,注册库会出现新版本。 - -本模板声明 Board Manager `>=0.7.0`。若注册库上还没有 0.7.0,CI 下限格会失败,需要等 0.7 发布,或临时把下限改成当前已发布版本做联调。 - -## 常见卡住的地方 - -- **push 被拒**:本地还没有 commit。先 `git add` 和 `git commit`,再 `git push`。 -- **Use this template 按钮没有**:Settings 里未勾选 Template repository。 -- **厂家上传 401**:Secret 名称必须是 `IDF_COMPONENT_API_TOKEN`,Token 必须来自同一 GitHub 账号登录的组件注册库。 -- **厂家上传成功但 `idf.py bmgr -l` 看不到板**:仓库名不含 `boards`,或板目录超过扫描深度(板目录必须直接放在仓库根下)。 diff --git a/example_board/board_devices.yaml b/example_board/board_devices.yaml index aec257d..3b34c9a 100644 --- a/example_board/board_devices.yaml +++ b/example_board/board_devices.yaml @@ -6,4 +6,4 @@ devices: active_level: 1 default_level: 0 peripherals: - - name: gpio_power + - gpio_name: gpio_power diff --git a/idf_component.yml b/idf_component.yml index 9132998..cb18857 100644 --- a/idf_component.yml +++ b/idf_component.yml @@ -3,13 +3,13 @@ description: Board definitions for ESP Board Manager license: Apache-2.0 tags: - - board_manager - esp_board_manager + - board_manager - boards dependencies: espressif/esp_board_manager: - version: ">=0.7.0" + version: ">=0.7.2" require: public files: @@ -18,4 +18,7 @@ files: - ".github" - ".cursor" - "ci" - - "docs" + - "image.png" + - "scripts" + - "VENDOR_GUIDE.md" + - "VENDOR_GUIDE_CN.md" diff --git a/image.png b/image.png new file mode 100644 index 0000000000000000000000000000000000000000..50c21711fef7a2b4cbbe6931ca353d7e9d2f65ca GIT binary patch literal 18502 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 KT-G@yGywo-a>v*J literal 0 HcmV?d00001 diff --git a/scripts/initialize_board_pack.py b/scripts/initialize_board_pack.py new file mode 100644 index 0000000..5536486 --- /dev/null +++ b/scripts/initialize_board_pack.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python3 +"""Interactively initialize board-pack metadata and README files.""" + +from __future__ import annotations + +import re +import subprocess +import sys +from pathlib import Path +from typing import Callable, NamedTuple + +from update_supported_boards_table import update_readme + + +class BoardPackConfig(NamedTuple): + vendor_name: str + namespace: str + use_repository_owner: bool + component_name: str + description: str + copyright_holder: str + + +def _git_remote_owner(repo_root: Path) -> str | None: + result = subprocess.run( + ["git", "remote", "get-url", "origin"], + cwd=repo_root, + capture_output=True, + text=True, + check=False, + ) + if result.returncode: + return None + match = re.search(r"github\.com[:/]([^/]+)/", result.stdout.strip()) + return match.group(1) if match else None + + +def _prompt( + input_fn: Callable[[str], str], chinese: str, english: str, default: str +) -> str: + value = input_fn(f"{chinese} / {english} [{default}]: ").strip() + return value or default + + +def collect_config( + repo_root: Path, + input_fn: Callable[[str], str] = input, + default_namespace: str | None = None, +) -> BoardPackConfig: + vendor_name = _prompt(input_fn, "厂商名称", "Vendor name", "YOUR_VENDOR_NAME") + namespace_default = ( + default_namespace or _git_remote_owner(repo_root) or "YOUR_NAMESPACE" + ) + namespace_input = input_fn( + f"组件命名空间 / Component namespace [{namespace_default}]: " + ).strip() + namespace = namespace_input or namespace_default + use_repository_owner = not namespace_input or namespace == namespace_default + component_name = _prompt( + input_fn, "组件名", "Component name", repo_root.name + ) + description = _prompt( + input_fn, + "组件描述", + "Component description", + f"ESP Board Manager board definitions for {vendor_name} development boards", + ) + copyright_holder = _prompt( + input_fn, "版权持有人", "Copyright holder", vendor_name + ) + return BoardPackConfig( + vendor_name, + namespace, + use_repository_owner, + component_name, + description, + copyright_holder, + ) + + +def _replace_readme_tokens(readme_path: Path, config: BoardPackConfig) -> None: + content = readme_path.read_text(encoding="utf-8") + content = content.replace("YOUR_VENDOR_NAME", config.vendor_name) + content = content.replace("YOUR_NAMESPACE", config.namespace) + content = content.replace("YOUR_COMPONENT_NAME", config.component_name) + readme_path.write_text(content, encoding="utf-8") + + +def _update_manifest(repo_root: Path, config: BoardPackConfig) -> None: + manifest_path = repo_root / "idf_component.yml" + content = manifest_path.read_text(encoding="utf-8") + description = config.description.replace("\\", "\\\\").replace('"', '\\"') + updated, replacements = re.subn( + r"^description:\s*.*$", + f'description: "{description}"', + content, + count=1, + flags=re.MULTILINE, + ) + if replacements != 1: + raise SystemExit(f"missing description field in {manifest_path}") + manifest_path.write_text(updated, encoding="utf-8") + + +def _update_license(repo_root: Path, config: BoardPackConfig) -> None: + license_path = repo_root / "LICENSE" + content = license_path.read_text(encoding="utf-8") + updated, replacements = re.subn( + r"^(\s*Copyright\s+\d{4}\s+).*$", + lambda match: f"{match.group(1)}{config.copyright_holder}", + content, + count=1, + flags=re.MULTILINE, + ) + if replacements != 1: + raise SystemExit(f"missing copyright notice in {license_path}") + license_path.write_text(updated, encoding="utf-8") + + +def _update_workflow(repo_root: Path, config: BoardPackConfig) -> None: + workflow_path = repo_root / ".github" / "workflows" / "ci.yml" + content = workflow_path.read_text(encoding="utf-8") + workflow_namespace = ( + "${{ github.repository_owner }}" + if config.use_repository_owner + else config.namespace + ) + updated, namespace_replacements = re.subn( + r'^(\s*--namespace\s+)"[^"]*"(.*)$', + lambda match: f'{match.group(1)}"{workflow_namespace}"{match.group(2)}', + content, + count=1, + flags=re.MULTILINE, + ) + if namespace_replacements != 1: + raise SystemExit(f"missing upload --namespace argument in {workflow_path}") + component_name = ( + "${{ github.event.repository.name }}" + if config.component_name == repo_root.name + else config.component_name + ) + updated, name_replacements = re.subn( + r'^(\s*--name\s+)"[^"]*"(.*)$', + lambda match: f'{match.group(1)}"{component_name}"{match.group(2)}', + updated, + count=1, + flags=re.MULTILINE, + ) + if name_replacements != 1: + raise SystemExit(f"missing upload --name argument in {workflow_path}") + workflow_path.write_text(updated, encoding="utf-8") + + +def apply_config(repo_root: Path, config: BoardPackConfig) -> None: + _replace_readme_tokens(repo_root / "README.md", config) + _replace_readme_tokens(repo_root / "README_CN.md", config) + _update_manifest(repo_root, config) + _update_license(repo_root, config) + _update_workflow(repo_root, config) + update_readme(repo_root / "README.md", repo_root, "en") + update_readme(repo_root / "README_CN.md", repo_root, "cn") + + +def main() -> int: + repo_root = Path(__file__).resolve().parent.parent + config = collect_config(repo_root) + apply_config(repo_root, config) + print( + "已更新 README、组件清单、许可证和 CI。" + " / Updated the README files, manifest, license, and CI." + ) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/test_initialize_board_pack.py b/scripts/test_initialize_board_pack.py new file mode 100644 index 0000000..34adf02 --- /dev/null +++ b/scripts/test_initialize_board_pack.py @@ -0,0 +1,127 @@ +import importlib.util +import tempfile +import unittest +from pathlib import Path + + +SCRIPT_PATH = Path(__file__).with_name("initialize_board_pack.py") +SPEC = importlib.util.spec_from_file_location("initialize_board_pack", SCRIPT_PATH) +initialize_board_pack = importlib.util.module_from_spec(SPEC) +assert SPEC.loader is not None +SPEC.loader.exec_module(initialize_board_pack) + + +class InitializeBoardPackTest(unittest.TestCase): + def setUp(self): + self.temp_dir = tempfile.TemporaryDirectory() + self.repo_root = Path(self.temp_dir.name) / "demo_boards" + self.repo_root.mkdir() + board_dir = self.repo_root / "example_board" + board_dir.mkdir() + (board_dir / "board_info.yaml").write_text( + "board: example_board\nchip: esp32s3\n", encoding="utf-8" + ) + self._write_template_files() + + def tearDown(self): + self.temp_dir.cleanup() + + def _write_template_files(self): + for filename in ("README.md", "README_CN.md"): + (self.repo_root / filename).write_text( + "\n".join( + [ + "# YOUR_VENDOR_NAME", + "YOUR_VENDOR_NAME: YOUR_NAMESPACE/YOUR_COMPONENT_NAME", + "", + "", + "", + ] + ), + encoding="utf-8", + ) + (self.repo_root / "idf_component.yml").write_text( + 'version: "0.7.0"\ndescription: "Board definitions for ESP Board Manager"\n', + encoding="utf-8", + ) + (self.repo_root / "LICENSE").write_text( + "Copyright 2026 CHANGE_ME\n", encoding="utf-8" + ) + workflow = self.repo_root / ".github" / "workflows" + workflow.mkdir(parents=True) + (workflow / "ci.yml").write_text( + '\n'.join( + [ + '--namespace "${{ github.repository_owner }}"', + '--name "${{ github.event.repository.name }}"', + '', + ] + ), + encoding="utf-8", + ) + + def test_collects_bilingual_prompts_and_applies_entered_metadata(self): + answers = iter(["Acme", "", "acme_board_pack", "", ""]) + prompts = [] + + def input_fn(prompt): + prompts.append(prompt) + return next(answers) + + config = initialize_board_pack.collect_config( + self.repo_root, input_fn=input_fn, default_namespace="acme" + ) + initialize_board_pack.apply_config(self.repo_root, config) + + self.assertEqual(config.vendor_name, "Acme") + self.assertEqual(config.namespace, "acme") + self.assertEqual(config.component_name, "acme_board_pack") + self.assertEqual( + config.description, + "ESP Board Manager board definitions for Acme development boards", + ) + self.assertEqual(config.copyright_holder, "Acme") + self.assertIn("厂商名称 / Vendor name", prompts[0]) + self.assertIn("组件命名空间 / Component namespace", prompts[1]) + self.assertIn("组件名 / Component name", prompts[2]) + self.assertIn( + "Acme: acme/acme_board_pack", + (self.repo_root / "README.md").read_text(encoding="utf-8"), + ) + self.assertIn( + 'description: "ESP Board Manager board definitions for Acme development boards"', + (self.repo_root / "idf_component.yml").read_text(encoding="utf-8"), + ) + self.assertIn( + "Copyright 2026 Acme", + (self.repo_root / "LICENSE").read_text(encoding="utf-8"), + ) + self.assertIn( + '--name "acme_board_pack"', + (self.repo_root / ".github" / "workflows" / "ci.yml").read_text( + encoding="utf-8" + ), + ) + self.assertIn( + '--namespace "${{ github.repository_owner }}"', + (self.repo_root / ".github" / "workflows" / "ci.yml").read_text( + encoding="utf-8" + ), + ) + + def test_uses_a_fixed_namespace_when_it_differs_from_repository_owner(self): + answers = iter(["Acme", "vendor_namespace", "", "", ""]) + + config = initialize_board_pack.collect_config( + self.repo_root, + input_fn=lambda _prompt: next(answers), + default_namespace="acme", + ) + initialize_board_pack.apply_config(self.repo_root, config) + + self.assertIn( + '--namespace "vendor_namespace"', + (self.repo_root / ".github" / "workflows" / "ci.yml").read_text( + encoding="utf-8" + ), + ) diff --git a/scripts/test_update_supported_boards_table.py b/scripts/test_update_supported_boards_table.py new file mode 100644 index 0000000..455e6e2 --- /dev/null +++ b/scripts/test_update_supported_boards_table.py @@ -0,0 +1,103 @@ +import importlib.util +import tempfile +import unittest +from pathlib import Path + + +SCRIPT_PATH = Path(__file__).with_name("update_supported_boards_table.py") +SPEC = importlib.util.spec_from_file_location("update_supported_boards_table", SCRIPT_PATH) +update_supported_boards_table = importlib.util.module_from_spec(SPEC) +assert SPEC.loader is not None +SPEC.loader.exec_module(update_supported_boards_table) + + +class UpdateSupportedBoardsTableTest(unittest.TestCase): + def setUp(self): + self.temp_dir = tempfile.TemporaryDirectory() + self.repo_root = Path(self.temp_dir.name) + + def tearDown(self): + self.temp_dir.cleanup() + + def _add_board(self, relative_dir: str, board: str, chip: str): + board_dir = self.repo_root / relative_dir + board_dir.mkdir(parents=True) + (board_dir / "board_info.yaml").write_text( + f"board: {board}\nchip: {chip}\n", encoding="utf-8" + ) + + def test_updates_board_and_chip_columns_and_preserves_manual_details(self): + self._add_board("audio/speaker_board", "speaker_board", "esp32s3") + self._add_board("display/round/screen_board", "screen_board", "esp32p4") + readme = self.repo_root / "README.md" + readme.write_text( + "\n".join( + [ + "# Boards", + "", + "| Board | Chip | Audio | SD Card | LCD | LCD Touch | Camera | Buttons | LED Strip | Knob |", + "| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |", + "| `speaker_board` | ESP32-S3 | ES8311 | | | | | | | |", + "", + "", + ] + ), + encoding="utf-8", + ) + + update_supported_boards_table.update_readme(readme, self.repo_root, "en") + + self.assertEqual( + readme.read_text(encoding="utf-8"), + "\n".join( + [ + "# Boards", + "", + "| Board | Chip | Audio | SD Card | LCD | LCD Touch | Camera | Buttons | LED Strip | Knob |", + "| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |", + "| `speaker_board` | ESP32-S3 | ES8311 | | | | | | | |", + "| `screen_board` | ESP32-P4 | | | | | | | | |", + "", + "", + ] + ), + ) + + def test_preserves_existing_rows_and_appends_new_boards(self): + self._add_board("existing_board", "existing_board", "esp32s3") + self._add_board("1/new_board", "new_board", "esp32c6") + readme = self.repo_root / "README.md" + readme.write_text( + "\n".join( + [ + "# Boards", + "", + "| Board | Chip | Audio | SD Card | LCD | LCD Touch | Camera | Buttons | LED Strip | Knob |", + "| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |", + "| `existing_board` | ESP32 | ES8311 | | | | | | | |", + "| `manual_board` | ESP32-H2 | | | | | | | | |", + "", + "", + ] + ), + encoding="utf-8", + ) + + update_supported_boards_table.update_readme(readme, self.repo_root, "en") + + self.assertEqual( + readme.read_text(encoding="utf-8"), + "\n".join( + [ + "# Boards", + "", + "| Board | Chip | Audio | SD Card | LCD | LCD Touch | Camera | Buttons | LED Strip | Knob |", + "| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |", + "| `existing_board` | ESP32 | ES8311 | | | | | | | |", + "| `manual_board` | ESP32-H2 | | | | | | | | |", + "| `new_board` | ESP32-C6 | | | | | | | | |", + "", + "", + ] + ), + ) diff --git a/scripts/update_supported_boards_table.py b/scripts/update_supported_boards_table.py new file mode 100644 index 0000000..01a840a --- /dev/null +++ b/scripts/update_supported_boards_table.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +"""Update the supported-board tables in the repository README files.""" + +from __future__ import annotations + +import sys +from pathlib import Path + +import yaml + + +BEGIN_MARKER = "" +END_MARKER = "" +MAX_BOARD_DIRECTORY_DEPTH = 3 +TABLE_HEADERS = { + "cn": [ + "开发板名称", + "芯片", + "音频", + "SD 卡", + "LCD", + "LCD 触摸", + "摄像头", + "按键", + "LED 灯带", + "旋钮", + ], + "en": [ + "Board", + "Chip", + "Audio", + "SD Card", + "LCD", + "LCD Touch", + "Camera", + "Buttons", + "LED Strip", + "Knob", + ], +} + + +def _format_chip(chip: str) -> str: + normalized = chip.lower().replace("-", "") + if normalized == "esp32": + return "ESP32" + if normalized.startswith("esp32") and len(normalized) > len("esp32"): + return f"ESP32-{normalized[len('esp32'):].upper()}" + return chip.upper() + + +def _discover_boards(repo_root: Path) -> list[tuple[str, str]]: + boards = [] + for info_path in sorted(repo_root.rglob("board_info.yaml")): + board_dir = info_path.parent.relative_to(repo_root) + if ( + len(board_dir.parts) > MAX_BOARD_DIRECTORY_DEPTH + or any(part.startswith(".") for part in board_dir.parts) + ): + continue + with info_path.open(encoding="utf-8") as file: + info = yaml.safe_load(file) or {} + if not isinstance(info, dict): + raise SystemExit(f"invalid board definition: {info_path}") + board = str(info.get("board") or info_path.parent.name) + chip = str(info.get("chip") or "") + if not chip: + raise SystemExit(f"{info_path} is missing chip") + if board != info_path.parent.name: + raise SystemExit( + f"board name {board!r} must match directory {info_path.parent.name!r}" + ) + boards.append((board, _format_chip(chip))) + if not boards: + raise SystemExit("no board_info.yaml found; add at least one board directory") + return boards + + +def _table_cells(line: str) -> list[str]: + return [cell.strip() for cell in line.strip().strip("|").split("|")] + + +def _manual_details( + table: str, column_count: int +) -> tuple[dict[str, list[str]], list[str]]: + details = {} + board_order = [] + for line in table.splitlines(): + if not line.lstrip().startswith("|"): + continue + cells = _table_cells(line) + if len(cells) < 2 or all(set(cell) <= {"-", ":", " "} for cell in cells): + continue + board = cells[0].strip("`") + if not board or board.lower() in {"board", "开发板名称"}: + continue + details[board] = (cells[2:] + [""] * column_count)[:column_count] + board_order.append(board) + return details, board_order + + +def _render_table( + boards: list[tuple[str, str]], language: str, existing_details: dict[str, list[str]] +) -> str: + headers = TABLE_HEADERS[language] + lines = [ + "| " + " | ".join(headers) + " |", + "| " + " | ".join(["---"] * len(headers)) + " |", + ] + blank_details = [""] * (len(headers) - 2) + for board, chip in boards: + details = existing_details.get(board, blank_details) + row = "| " + " | ".join([f"`{board}`", chip, *details]) + " |" + while " | |" in row: + row = row.replace(" | |", " | |") + lines.append(row) + return "\n".join(lines) + + +def update_readme(readme_path: Path, repo_root: Path, language: str) -> None: + content = readme_path.read_text(encoding="utf-8") + start = content.find(BEGIN_MARKER) + end = content.find(END_MARKER, start + len(BEGIN_MARKER)) + if start == -1 or end == -1: + raise SystemExit( + f"{readme_path} must contain {BEGIN_MARKER} and {END_MARKER} markers" + ) + table_start = start + len(BEGIN_MARKER) + _, existing_order = _manual_details( + content[table_start:end], len(TABLE_HEADERS[language]) - 2 + ) + discovered_boards = _discover_boards(repo_root) + existing_names = set(existing_order) + new_boards = [ + board_entry + for board_entry in discovered_boards + if board_entry[0] not in existing_names + ] + if not new_boards: + return + new_rows = _render_table(new_boards, language, {}).splitlines()[2:] + updated = content[:end] + "\n".join(new_rows) + "\n" + content[end:] + readme_path.write_text(updated, encoding="utf-8") + + +def main() -> int: + repo_root = Path(__file__).resolve().parent.parent + update_readme(repo_root / "README.md", repo_root, "en") + update_readme(repo_root / "README_CN.md", repo_root, "cn") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) From 2e3cbc12096f47efd8255bf119a882b8776ac8e2 Mon Sep 17 00:00:00 2001 From: Liu Jinhong Date: Fri, 4 Sep 2026 15:15:39 +0800 Subject: [PATCH 2/2] test --- .github/workflows/ci.yml | 2 +- 1/2/example_board2/board_devices.yaml | 9 +++++++++ 1/2/example_board2/board_info.yaml | 5 +++++ 1/2/example_board2/board_peripherals.yaml | 8 ++++++++ 1/example_board1/board_devices.yaml | 9 +++++++++ 1/example_board1/board_info.yaml | 5 +++++ 1/example_board1/board_peripherals.yaml | 8 ++++++++ LICENSE | 2 +- README.md | 10 ++++++---- README_CN.md | 10 ++++++---- idf_component.yml | 2 +- 11 files changed, 59 insertions(+), 11 deletions(-) create mode 100644 1/2/example_board2/board_devices.yaml create mode 100644 1/2/example_board2/board_info.yaml create mode 100644 1/2/example_board2/board_peripherals.yaml create mode 100644 1/example_board1/board_devices.yaml create mode 100644 1/example_board1/board_info.yaml create mode 100644 1/example_board1/board_peripherals.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 73048bf..c4f7f28 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,7 +69,7 @@ jobs: fi python3 -m pip install -q idf-component-manager compote component upload \ - --namespace "${{ github.repository_owner }}" \ + --namespace "LiuCodee" \ --name "${{ github.event.repository.name }}" \ --allow-existing \ --dry-run diff --git a/1/2/example_board2/board_devices.yaml b/1/2/example_board2/board_devices.yaml new file mode 100644 index 0000000..3b34c9a --- /dev/null +++ b/1/2/example_board2/board_devices.yaml @@ -0,0 +1,9 @@ +devices: + - name: power_control + type: gpio_ctrl + version: default + config: + active_level: 1 + default_level: 0 + peripherals: + - gpio_name: gpio_power diff --git a/1/2/example_board2/board_info.yaml b/1/2/example_board2/board_info.yaml new file mode 100644 index 0000000..9891f4a --- /dev/null +++ b/1/2/example_board2/board_info.yaml @@ -0,0 +1,5 @@ +board: example_board2 +chip: esp32s31 +version: "1.0.0" +description: "Placeholder board. Replace this directory with a real board." +manufacturer: "CHANGE_ME" diff --git a/1/2/example_board2/board_peripherals.yaml b/1/2/example_board2/board_peripherals.yaml new file mode 100644 index 0000000..e86a3c3 --- /dev/null +++ b/1/2/example_board2/board_peripherals.yaml @@ -0,0 +1,8 @@ +peripherals: + - name: gpio_power + type: gpio + role: io + config: + pin: 4 + mode: GPIO_MODE_OUTPUT + default_level: 0 diff --git a/1/example_board1/board_devices.yaml b/1/example_board1/board_devices.yaml new file mode 100644 index 0000000..3b34c9a --- /dev/null +++ b/1/example_board1/board_devices.yaml @@ -0,0 +1,9 @@ +devices: + - name: power_control + type: gpio_ctrl + version: default + config: + active_level: 1 + default_level: 0 + peripherals: + - gpio_name: gpio_power diff --git a/1/example_board1/board_info.yaml b/1/example_board1/board_info.yaml new file mode 100644 index 0000000..7ce49c2 --- /dev/null +++ b/1/example_board1/board_info.yaml @@ -0,0 +1,5 @@ +board: example_board1 +chip: esp32 +version: "1.0.0" +description: "Placeholder board. Replace this directory with a real board." +manufacturer: "CHANGE_ME" diff --git a/1/example_board1/board_peripherals.yaml b/1/example_board1/board_peripherals.yaml new file mode 100644 index 0000000..e86a3c3 --- /dev/null +++ b/1/example_board1/board_peripherals.yaml @@ -0,0 +1,8 @@ +peripherals: + - name: gpio_power + type: gpio + role: io + config: + pin: 4 + mode: GPIO_MODE_OUTPUT + default_level: 0 diff --git a/LICENSE b/LICENSE index 86d22d8..a641b09 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2026 CHANGE_ME + Copyright 2026 My_boards Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 61b3258..bd75f11 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ -# YOUR_VENDOR_NAME ESP Board Manager Board Pack +# My_boards ESP Board Manager Board Pack [中文](README_CN.md) -This component provides ESP Board Manager YAML definitions for YOUR_VENDOR_NAME +This component provides ESP Board Manager YAML definitions for My_boards development boards. Applications can install it from the [ESP Component Registry](https://components.espressif.com) and select a board. -> Template note: Replace `YOUR_VENDOR_NAME` and update the following table with +> Template note: Replace `My_boards` and update the following table with > the supported boards before publishing. ## Supported Boards @@ -18,6 +18,8 @@ chip columns. Maintainers fill in the device capability columns. | Board | Chip | Audio | SD Card | LCD | LCD Touch | Camera | Buttons | LED Strip | Knob | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | `example_board` | ESP32-S3 | | | | | | | | | +| `example_board2` | ESP32-S31 | | | | | | | | | +| `example_board1` | ESP32 | | | | | | | | | ## Use This Pack in an Application @@ -31,7 +33,7 @@ python -m pip install --upgrade esp-bmgr-assist Add the released component, then select a board: ```bash -idf.py add-dependency "YOUR_NAMESPACE/YOUR_COMPONENT_NAME" +idf.py add-dependency "LiuCodee/demo_boards" idf.py bmgr -l idf.py bmgr -b idf.py build diff --git a/README_CN.md b/README_CN.md index 48bd2e4..cb90fcd 100644 --- a/README_CN.md +++ b/README_CN.md @@ -1,10 +1,10 @@ -# YOUR_VENDOR_NAME ESP Board Manager 板组件 +# My_boards ESP Board Manager 板组件 [English](README.md) -本组件提供 YOUR_VENDOR_NAME 开发板的 ESP Board Manager YAML 定义,可从 [ESP 组件注册库](https://components.espressif.com) 下载并在应用工程中选择。 +本组件提供 My_boards 开发板的 ESP Board Manager YAML 定义,可从 [ESP 组件注册库](https://components.espressif.com) 下载并在应用工程中选择。 -> 模板提示:发布前替换 `YOUR_VENDOR_NAME`,并将下表更新为实际支持的开发板。 +> 模板提示:发布前替换 `My_boards`,并将下表更新为实际支持的开发板。 ## 支持的开发板 @@ -14,6 +14,8 @@ | 开发板名称 | 芯片 | 音频 | SD 卡 | LCD | LCD 触摸 | 摄像头 | 按键 | LED 灯带 | 旋钮 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | `example_board` | ESP32-S3 | | | | | | | | | +| `example_board2` | ESP32-S31 | | | | | | | | | +| `example_board1` | ESP32 | | | | | | | | | ## 在应用工程中使用本组件 @@ -27,7 +29,7 @@ python -m pip install --upgrade esp-bmgr-assist 添加已发布的组件后,选择开发板: ```bash -idf.py add-dependency "YOUR_NAMESPACE/YOUR_COMPONENT_NAME" +idf.py add-dependency "LiuCodee/demo_boards" idf.py bmgr -l idf.py bmgr -b idf.py build diff --git a/idf_component.yml b/idf_component.yml index cb18857..5e5f09d 100644 --- a/idf_component.yml +++ b/idf_component.yml @@ -1,5 +1,5 @@ version: "0.7.0" -description: Board definitions for ESP Board Manager +description: "ESP Board Manager board definitions for My_boards development boards" license: Apache-2.0 tags: