2024-11-06 22:48:21 +08:00
|
|
|
|
import sys
|
|
|
|
|
|
import os
|
|
|
|
|
|
import json
|
2025-03-08 15:18:18 +08:00
|
|
|
|
import zipfile
|
2025-06-26 02:55:45 +08:00
|
|
|
|
import argparse
|
2025-09-26 05:09:54 +08:00
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
from typing import Optional
|
2024-11-06 22:48:21 +08:00
|
|
|
|
|
2025-09-26 05:09:54 +08:00
|
|
|
|
# Switch to project root directory
|
|
|
|
|
|
os.chdir(Path(__file__).resolve().parent.parent)
|
2024-11-06 22:48:21 +08:00
|
|
|
|
|
2025-09-26 05:09:54 +08:00
|
|
|
|
################################################################################
|
|
|
|
|
|
# Common utility functions
|
|
|
|
|
|
################################################################################
|
|
|
|
|
|
|
|
|
|
|
|
def get_board_type_from_compile_commands() -> Optional[str]:
|
|
|
|
|
|
"""Parse the current compiled BOARD_TYPE from build/compile_commands.json"""
|
|
|
|
|
|
compile_file = Path("build/compile_commands.json")
|
|
|
|
|
|
if not compile_file.exists():
|
|
|
|
|
|
return None
|
|
|
|
|
|
with compile_file.open() as f:
|
2024-11-06 22:48:21 +08:00
|
|
|
|
data = json.load(f)
|
2025-09-26 05:09:54 +08:00
|
|
|
|
for item in data:
|
|
|
|
|
|
if not item["file"].endswith("main.cc"):
|
|
|
|
|
|
continue
|
|
|
|
|
|
cmd = item["command"]
|
|
|
|
|
|
if "-DBOARD_TYPE=\\\"" in cmd:
|
|
|
|
|
|
return cmd.split("-DBOARD_TYPE=\\\"")[1].split("\\\"")[0].strip()
|
2024-11-06 22:48:21 +08:00
|
|
|
|
return None
|
|
|
|
|
|
|
2025-09-26 05:09:54 +08:00
|
|
|
|
|
|
|
|
|
|
def get_project_version() -> Optional[str]:
|
|
|
|
|
|
"""Read set(PROJECT_VER "x.y.z") from root CMakeLists.txt"""
|
|
|
|
|
|
with Path("CMakeLists.txt").open() as f:
|
2024-11-06 22:48:21 +08:00
|
|
|
|
for line in f:
|
|
|
|
|
|
if line.startswith("set(PROJECT_VER"):
|
2025-09-26 05:09:54 +08:00
|
|
|
|
return line.split("\"")[1]
|
2024-11-06 22:48:21 +08:00
|
|
|
|
return None
|
|
|
|
|
|
|
2025-09-26 05:09:54 +08:00
|
|
|
|
|
|
|
|
|
|
def merge_bin() -> None:
|
2024-11-06 22:48:21 +08:00
|
|
|
|
if os.system("idf.py merge-bin") != 0:
|
2025-09-26 05:09:54 +08:00
|
|
|
|
print("merge-bin failed", file=sys.stderr)
|
2024-11-06 22:48:21 +08:00
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
2025-09-26 05:09:54 +08:00
|
|
|
|
|
|
|
|
|
|
def zip_bin(name: str, version: str) -> None:
|
|
|
|
|
|
"""Zip build/merged-binary.bin to releases/v{version}_{name}.zip"""
|
|
|
|
|
|
out_dir = Path("releases")
|
|
|
|
|
|
out_dir.mkdir(exist_ok=True)
|
|
|
|
|
|
output_path = out_dir / f"v{version}_{name}.zip"
|
|
|
|
|
|
|
|
|
|
|
|
if output_path.exists():
|
|
|
|
|
|
output_path.unlink()
|
|
|
|
|
|
|
|
|
|
|
|
with zipfile.ZipFile(output_path, "w", compression=zipfile.ZIP_DEFLATED) as zipf:
|
2025-03-08 15:18:18 +08:00
|
|
|
|
zipf.write("build/merged-binary.bin", arcname="merged-binary.bin")
|
2024-11-06 22:48:21 +08:00
|
|
|
|
print(f"zip bin to {output_path} done")
|
|
|
|
|
|
|
2025-09-26 05:09:54 +08:00
|
|
|
|
################################################################################
|
|
|
|
|
|
# board / variant related functions
|
|
|
|
|
|
################################################################################
|
|
|
|
|
|
|
|
|
|
|
|
_BOARDS_DIR = Path("main/boards")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _collect_variants(config_filename: str = "config.json") -> list[dict[str, str]]:
|
|
|
|
|
|
"""Traverse all boards under main/boards, collect variant information.
|
|
|
|
|
|
|
|
|
|
|
|
Return example:
|
|
|
|
|
|
[{"board": "bread-compact-ml307", "name": "bread-compact-ml307"}, ...]
|
|
|
|
|
|
"""
|
|
|
|
|
|
variants: list[dict[str, str]] = []
|
|
|
|
|
|
for board_path in _BOARDS_DIR.iterdir():
|
|
|
|
|
|
if not board_path.is_dir():
|
|
|
|
|
|
continue
|
|
|
|
|
|
if board_path.name == "common":
|
|
|
|
|
|
continue
|
|
|
|
|
|
cfg_path = board_path / config_filename
|
|
|
|
|
|
if not cfg_path.exists():
|
|
|
|
|
|
print(f"[WARN] {cfg_path} does not exist, skip", file=sys.stderr)
|
|
|
|
|
|
continue
|
|
|
|
|
|
try:
|
|
|
|
|
|
with cfg_path.open() as f:
|
|
|
|
|
|
cfg = json.load(f)
|
|
|
|
|
|
for build in cfg.get("builds", []):
|
|
|
|
|
|
variants.append({"board": board_path.name, "name": build["name"]})
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"[ERROR] 解析 {cfg_path} 失败: {e}", file=sys.stderr)
|
|
|
|
|
|
return variants
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _parse_board_config_map() -> dict[str, str]:
|
|
|
|
|
|
"""Build the mapping of CONFIG_BOARD_TYPE_xxx and board_type from main/CMakeLists.txt"""
|
|
|
|
|
|
cmake_file = Path("main/CMakeLists.txt")
|
|
|
|
|
|
mapping: dict[str, str] = {}
|
|
|
|
|
|
lines = cmake_file.read_text(encoding="utf-8").splitlines()
|
|
|
|
|
|
for idx, line in enumerate(lines):
|
|
|
|
|
|
if "if(CONFIG_BOARD_TYPE_" in line:
|
|
|
|
|
|
config_name = line.strip().split("if(")[1].split(")")[0]
|
|
|
|
|
|
if idx + 1 < len(lines):
|
|
|
|
|
|
next_line = lines[idx + 1].strip()
|
2025-02-18 00:52:31 +08:00
|
|
|
|
if next_line.startswith("set(BOARD_TYPE"):
|
|
|
|
|
|
board_type = next_line.split('"')[1]
|
2025-09-26 05:09:54 +08:00
|
|
|
|
mapping[config_name] = board_type
|
|
|
|
|
|
return mapping
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _find_board_config(board_type: str) -> Optional[str]:
|
|
|
|
|
|
"""Find the corresponding CONFIG_BOARD_TYPE_xxx for the given board_type"""
|
|
|
|
|
|
for config, b_type in _parse_board_config_map().items():
|
|
|
|
|
|
if b_type == board_type:
|
|
|
|
|
|
return config
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
################################################################################
|
|
|
|
|
|
# Check board_type in CMakeLists
|
|
|
|
|
|
################################################################################
|
|
|
|
|
|
|
|
|
|
|
|
def _board_type_exists(board_type: str) -> bool:
|
|
|
|
|
|
cmake_file = Path("main/CMakeLists.txt")
|
|
|
|
|
|
pattern = f'set(BOARD_TYPE "{board_type}")'
|
|
|
|
|
|
return pattern in cmake_file.read_text(encoding="utf-8")
|
|
|
|
|
|
|
|
|
|
|
|
################################################################################
|
|
|
|
|
|
# Compile implementation
|
|
|
|
|
|
################################################################################
|
|
|
|
|
|
|
|
|
|
|
|
def release(board_type: str, config_filename: str = "config.json", *, filter_name: Optional[str] = None) -> None:
|
|
|
|
|
|
"""Compile and package all/specified variants of the specified board_type
|
2025-02-18 00:52:31 +08:00
|
|
|
|
|
2025-09-26 05:09:54 +08:00
|
|
|
|
Args:
|
|
|
|
|
|
board_type: directory name under main/boards
|
|
|
|
|
|
config_filename: config.json name (default: config.json)
|
|
|
|
|
|
filter_name: if specified, only compile the build["name"] that matches
|
|
|
|
|
|
"""
|
|
|
|
|
|
cfg_path = _BOARDS_DIR / board_type / config_filename
|
|
|
|
|
|
if not cfg_path.exists():
|
|
|
|
|
|
print(f"[WARN] {cfg_path} 不存在,跳过 {board_type}")
|
2025-02-18 00:52:31 +08:00
|
|
|
|
return
|
|
|
|
|
|
|
2025-02-24 17:55:11 +08:00
|
|
|
|
project_version = get_project_version()
|
2025-09-26 05:09:54 +08:00
|
|
|
|
print(f"Project Version: {project_version} ({cfg_path})")
|
|
|
|
|
|
|
|
|
|
|
|
with cfg_path.open() as f:
|
|
|
|
|
|
cfg = json.load(f)
|
|
|
|
|
|
target = cfg["target"]
|
|
|
|
|
|
|
|
|
|
|
|
builds = cfg.get("builds", [])
|
|
|
|
|
|
if filter_name:
|
|
|
|
|
|
builds = [b for b in builds if b["name"] == filter_name]
|
|
|
|
|
|
if not builds:
|
|
|
|
|
|
print(f"[ERROR] 未在 {board_type} 的 {config_filename} 中找到变体 {filter_name}", file=sys.stderr)
|
|
|
|
|
|
sys.exit(1)
|
2025-02-24 17:55:11 +08:00
|
|
|
|
|
2025-02-18 00:52:31 +08:00
|
|
|
|
for build in builds:
|
|
|
|
|
|
name = build["name"]
|
2025-02-20 04:28:39 +08:00
|
|
|
|
if not name.startswith(board_type):
|
2025-09-26 05:09:54 +08:00
|
|
|
|
raise ValueError(f"build.name {name} 必须以 {board_type} 开头")
|
|
|
|
|
|
|
|
|
|
|
|
output_path = Path("releases") / f"v{project_version}_{name}.zip"
|
|
|
|
|
|
if output_path.exists():
|
|
|
|
|
|
print(f"跳过 {name} 因为 {output_path} 已存在")
|
2025-04-09 23:58:32 +08:00
|
|
|
|
continue
|
2025-02-20 04:28:39 +08:00
|
|
|
|
|
2025-09-26 05:09:54 +08:00
|
|
|
|
# Process sdkconfig_append
|
2025-09-26 08:52:43 +08:00
|
|
|
|
board_type_config = _find_board_config(board_type)
|
|
|
|
|
|
sdkconfig_append = [f"{board_type_config}=y"]
|
|
|
|
|
|
sdkconfig_append.extend(build.get("sdkconfig_append", []))
|
2025-09-26 05:09:54 +08:00
|
|
|
|
|
|
|
|
|
|
print("-" * 80)
|
2025-02-18 00:52:31 +08:00
|
|
|
|
print(f"name: {name}")
|
|
|
|
|
|
print(f"target: {target}")
|
2025-09-26 05:09:54 +08:00
|
|
|
|
for item in sdkconfig_append:
|
|
|
|
|
|
print(f"sdkconfig_append: {item}")
|
|
|
|
|
|
|
2025-02-18 00:52:31 +08:00
|
|
|
|
os.environ.pop("IDF_TARGET", None)
|
2025-09-26 05:09:54 +08:00
|
|
|
|
|
2025-02-18 00:52:31 +08:00
|
|
|
|
# Call set-target
|
|
|
|
|
|
if os.system(f"idf.py set-target {target}") != 0:
|
2025-09-26 05:09:54 +08:00
|
|
|
|
print("set-target failed", file=sys.stderr)
|
2025-02-18 00:52:31 +08:00
|
|
|
|
sys.exit(1)
|
2025-09-26 05:09:54 +08:00
|
|
|
|
|
2025-02-18 00:52:31 +08:00
|
|
|
|
# Append sdkconfig
|
2025-09-26 05:09:54 +08:00
|
|
|
|
with Path("sdkconfig").open("a") as f:
|
2025-02-18 00:52:31 +08:00
|
|
|
|
f.write("\n")
|
2025-09-10 01:02:23 +08:00
|
|
|
|
f.write("# Append by release.py\n")
|
2025-02-18 00:52:31 +08:00
|
|
|
|
for append in sdkconfig_append:
|
|
|
|
|
|
f.write(f"{append}\n")
|
2025-03-03 07:29:22 +08:00
|
|
|
|
# Build with macro BOARD_NAME defined to name
|
2025-10-14 10:44:45 +08:00
|
|
|
|
if os.system(f"idf.py -DBOARD_NAME={name} -DBOARD_TYPE={board_type} build") != 0:
|
2025-03-03 07:29:22 +08:00
|
|
|
|
print("build failed")
|
|
|
|
|
|
sys.exit(1)
|
2025-09-26 05:09:54 +08:00
|
|
|
|
|
|
|
|
|
|
# merge-bin
|
|
|
|
|
|
merge_bin()
|
|
|
|
|
|
|
|
|
|
|
|
# Zip
|
2025-02-18 00:52:31 +08:00
|
|
|
|
zip_bin(name, project_version)
|
2025-09-26 05:09:54 +08:00
|
|
|
|
|
|
|
|
|
|
################################################################################
|
|
|
|
|
|
# CLI entry
|
|
|
|
|
|
################################################################################
|
2025-02-18 00:52:31 +08:00
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2025-06-26 02:55:45 +08:00
|
|
|
|
parser = argparse.ArgumentParser()
|
|
|
|
|
|
parser.add_argument("board", nargs="?", default=None, help="板子类型或 all")
|
|
|
|
|
|
parser.add_argument("-c", "--config", default="config.json", help="指定 config 文件名,默认 config.json")
|
2025-09-26 05:09:54 +08:00
|
|
|
|
parser.add_argument("--list-boards", action="store_true", help="列出所有支持的 board 及变体列表")
|
2025-08-05 20:03:37 +08:00
|
|
|
|
parser.add_argument("--json", action="store_true", help="配合 --list-boards,JSON 格式输出")
|
2025-09-26 05:09:54 +08:00
|
|
|
|
parser.add_argument("--name", help="指定变体名称,仅编译匹配的变体")
|
|
|
|
|
|
|
2025-06-26 02:55:45 +08:00
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
2025-09-26 05:09:54 +08:00
|
|
|
|
# List mode
|
2025-08-05 20:03:37 +08:00
|
|
|
|
if args.list_boards:
|
2025-09-26 05:09:54 +08:00
|
|
|
|
variants = _collect_variants(config_filename=args.config)
|
2025-08-05 20:03:37 +08:00
|
|
|
|
if args.json:
|
2025-09-26 05:09:54 +08:00
|
|
|
|
print(json.dumps(variants))
|
2025-08-05 20:03:37 +08:00
|
|
|
|
else:
|
2025-09-26 05:09:54 +08:00
|
|
|
|
for v in variants:
|
|
|
|
|
|
print(f"{v['board']}: {v['name']}")
|
|
|
|
|
|
sys.exit(0)
|
|
|
|
|
|
|
|
|
|
|
|
# Current directory firmware packaging mode
|
|
|
|
|
|
if args.board is None:
|
|
|
|
|
|
merge_bin()
|
|
|
|
|
|
curr_board_type = get_board_type_from_compile_commands()
|
|
|
|
|
|
if curr_board_type is None:
|
|
|
|
|
|
print("未能从 compile_commands.json 解析 board_type", file=sys.stderr)
|
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
project_ver = get_project_version()
|
|
|
|
|
|
zip_bin(curr_board_type, project_ver)
|
2025-08-05 20:03:37 +08:00
|
|
|
|
sys.exit(0)
|
|
|
|
|
|
|
2025-09-26 05:09:54 +08:00
|
|
|
|
# Compile mode
|
|
|
|
|
|
board_type_input: str = args.board
|
|
|
|
|
|
name_filter: str | None = args.name
|
|
|
|
|
|
|
|
|
|
|
|
# Check board_type in CMakeLists
|
|
|
|
|
|
if board_type_input != "all" and not _board_type_exists(board_type_input):
|
|
|
|
|
|
print(f"[ERROR] main/CMakeLists.txt 中未找到 board_type {board_type_input}", file=sys.stderr)
|
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
variants_all = _collect_variants(config_filename=args.config)
|
|
|
|
|
|
|
|
|
|
|
|
# Filter board_type list
|
|
|
|
|
|
target_board_types: set[str]
|
|
|
|
|
|
if board_type_input == "all":
|
|
|
|
|
|
target_board_types = {v["board"] for v in variants_all}
|
2025-02-18 00:52:31 +08:00
|
|
|
|
else:
|
2025-09-26 05:09:54 +08:00
|
|
|
|
target_board_types = {board_type_input}
|
|
|
|
|
|
|
|
|
|
|
|
for bt in sorted(target_board_types):
|
|
|
|
|
|
if not _board_type_exists(bt):
|
|
|
|
|
|
print(f"[ERROR] main/CMakeLists.txt 中未找到 board_type {bt}", file=sys.stderr)
|
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
cfg_path = _BOARDS_DIR / bt / args.config
|
|
|
|
|
|
if bt == board_type_input and not cfg_path.exists():
|
|
|
|
|
|
print(f"开发板 {bt} 未定义 {args.config} 配置文件,跳过")
|
|
|
|
|
|
sys.exit(0)
|
|
|
|
|
|
release(bt, config_filename=args.config, filter_name=name_filter if bt == board_type_input else None)
|