247 lines
8.3 KiB
Python
247 lines
8.3 KiB
Python
import os
|
|
import re
|
|
|
|
# The directories to scan for Kconfig files
|
|
SUBSYSTEM_DIRS = ["04_hal", "05_executors", "05_service", "06_behaviors", "tests"]
|
|
TEMPLATE_SCAN_DIRS = SUBSYSTEM_DIRS + ["sweekar_demos"]
|
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
OUTPUT_FILE = os.path.join(BASE_DIR, "Kconfig.modules")
|
|
|
|
SOURCE_EXTENSIONS = (".c", ".cc", ".cpp")
|
|
HEADER_EXTENSIONS = (".h", ".hpp")
|
|
SKIP_DIR_NAMES = {"build", "__pycache__"}
|
|
|
|
|
|
def iter_child_dirs(parent_dir):
|
|
if not os.path.exists(parent_dir):
|
|
return []
|
|
return sorted(
|
|
[
|
|
name
|
|
for name in os.listdir(parent_dir)
|
|
if os.path.isdir(os.path.join(parent_dir, name)) and not name.startswith(".")
|
|
]
|
|
)
|
|
|
|
|
|
def compute_default_config_name(subsystem_dir, module_name):
|
|
if subsystem_dir == "tests":
|
|
prefix = "TEST"
|
|
else:
|
|
prefix = subsystem_dir.split("_")[1].upper()
|
|
if prefix == "EXECUTORS":
|
|
prefix = "EXEC"
|
|
elif prefix == "BEHAVIORS":
|
|
prefix = "BEHAV"
|
|
elif prefix == "SERVICE":
|
|
prefix = "SVC"
|
|
return f"CONFIG_ENABLE_{prefix}_{module_name.upper()}"
|
|
|
|
|
|
def contains_module_files(module_dir):
|
|
for _, _, files in os.walk(module_dir):
|
|
for filename in files:
|
|
if filename.endswith(SOURCE_EXTENSIONS) or filename.endswith(HEADER_EXTENSIONS):
|
|
return True
|
|
return False
|
|
|
|
|
|
def collect_existing_module_cmake_files():
|
|
module_files = []
|
|
for root, _, files in os.walk(BASE_DIR):
|
|
if "module.cmake" in files:
|
|
module_files.append(os.path.join(root, "module.cmake"))
|
|
return sorted(module_files)
|
|
|
|
|
|
def collect_module_files(module_dir):
|
|
srcs = []
|
|
include_dirs = {"."}
|
|
|
|
for root, dirs, files in os.walk(module_dir):
|
|
dirs[:] = [
|
|
d for d in dirs
|
|
if d not in SKIP_DIR_NAMES and not d.startswith(".")
|
|
]
|
|
|
|
rel_root = os.path.relpath(root, module_dir).replace(os.sep, "/")
|
|
rel_root = "." if rel_root == "." else rel_root
|
|
|
|
has_header = False
|
|
for filename in files:
|
|
if filename.endswith(SOURCE_EXTENSIONS):
|
|
rel_path = os.path.normpath(os.path.join(rel_root, filename)).replace(os.sep, "/")
|
|
if rel_path.startswith("./"):
|
|
rel_path = rel_path[2:]
|
|
srcs.append(rel_path)
|
|
elif filename.endswith(HEADER_EXTENSIONS):
|
|
has_header = True
|
|
|
|
if has_header:
|
|
include_dirs.add(rel_root)
|
|
|
|
srcs = sorted(set(srcs))
|
|
include_dirs = sorted(set(include_dirs))
|
|
return srcs, include_dirs
|
|
|
|
|
|
def is_already_aggregated(rel_module_dir, existing_module_files):
|
|
rel_module_prefix = f'"{rel_module_dir}/'
|
|
for module_file in existing_module_files:
|
|
try:
|
|
with open(module_file, "r", encoding="utf-8") as f:
|
|
content = f.read()
|
|
except OSError:
|
|
continue
|
|
|
|
if rel_module_prefix in content:
|
|
return True
|
|
return False
|
|
|
|
|
|
def render_module_template(subsystem_dir, module_name, module_dir):
|
|
srcs, include_dirs = collect_module_files(module_dir)
|
|
rel_module_dir = os.path.relpath(module_dir, BASE_DIR).replace(os.sep, "/")
|
|
|
|
lines = [
|
|
f"# main/{subsystem_dir}/{module_name}/module.cmake",
|
|
"# 自动生成的模块脚手架,可直接手工修改。",
|
|
"# 如果后续添加了新文件,可以手工在此处添加,或者删除本文件让脚本重新生成。",
|
|
"",
|
|
]
|
|
|
|
if subsystem_dir != "sweekar_demos":
|
|
config_name = compute_default_config_name(subsystem_dir, module_name)
|
|
lines.extend(
|
|
[
|
|
f"if(NOT {config_name} AND NOT EARLY_EXPANSION)",
|
|
f' message(STATUS " [Skip] {module_name} module disabled by menuconfig.")',
|
|
" return()",
|
|
"endif()",
|
|
"",
|
|
]
|
|
)
|
|
|
|
lines.append("list(APPEND MODULE_SRCS")
|
|
for src in srcs:
|
|
lines.append(f' "{rel_module_dir}/{src}"')
|
|
lines.append(")")
|
|
lines.append("")
|
|
|
|
lines.append("list(APPEND MODULE_INCLUDE_DIRS")
|
|
for inc in include_dirs:
|
|
if inc == ".":
|
|
lines.append(f' "{rel_module_dir}"')
|
|
else:
|
|
lines.append(f' "{rel_module_dir}/{inc}"')
|
|
lines.append(")")
|
|
lines.append("")
|
|
|
|
lines.extend(
|
|
[
|
|
"# 需要公开依赖时,在这里手工补充。例如:",
|
|
"# list(APPEND MODULE_REQUIRES",
|
|
"# esp_timer",
|
|
"# driver",
|
|
"# )",
|
|
"",
|
|
"# 需要私有依赖时,在这里手工补充。例如:",
|
|
"# list(APPEND MODULE_PRIV_REQUIRES",
|
|
"# nvs_flash",
|
|
"# )",
|
|
"",
|
|
]
|
|
)
|
|
return "\n".join(lines)
|
|
|
|
|
|
def ensure_module_templates():
|
|
created_files = []
|
|
existing_module_files = collect_existing_module_cmake_files()
|
|
|
|
for subsystem_dir in TEMPLATE_SCAN_DIRS:
|
|
dir_path = os.path.join(BASE_DIR, subsystem_dir)
|
|
if not os.path.exists(dir_path):
|
|
continue
|
|
|
|
for module_name in iter_child_dirs(dir_path):
|
|
if subsystem_dir == "sweekar_demos" and not module_name.startswith("sweekar_demo"):
|
|
continue
|
|
|
|
module_dir = os.path.join(dir_path, module_name)
|
|
module_cmake = os.path.join(module_dir, "module.cmake")
|
|
if os.path.exists(module_cmake):
|
|
continue
|
|
|
|
if not contains_module_files(module_dir):
|
|
continue
|
|
|
|
rel_module_dir = os.path.relpath(module_dir, BASE_DIR).replace(os.sep, "/")
|
|
if subsystem_dir != "sweekar_demos" and is_already_aggregated(rel_module_dir, existing_module_files):
|
|
continue
|
|
|
|
template_content = render_module_template(subsystem_dir, module_name, module_dir)
|
|
with open(module_cmake, "w", encoding="utf-8") as f:
|
|
f.write(template_content)
|
|
created_files.append(module_cmake)
|
|
|
|
return created_files
|
|
|
|
def generate_kconfig_modules():
|
|
created_templates = ensure_module_templates()
|
|
kconfig_content = 'menu "Sweekar Subsystems Configuration"\n\n'
|
|
|
|
for d in SUBSYSTEM_DIRS:
|
|
dir_path = os.path.join(BASE_DIR, d)
|
|
if not os.path.exists(dir_path):
|
|
continue
|
|
|
|
# Create a pretty menu name from the directory name
|
|
# e.g., "05_executors" -> "05 Executors Modules"
|
|
parts = d.split('_')
|
|
menu_name = parts[0] + " " + " ".join([p.capitalize() for p in parts[1:]]) + " Modules"
|
|
|
|
kconfig_content += f' menu "{menu_name}"\n'
|
|
|
|
# Sort subdirectories to ensure consistent ordering
|
|
subdirs = iter_child_dirs(dir_path)
|
|
|
|
for module_name in subdirs:
|
|
module_dir = os.path.join(dir_path, module_name)
|
|
kconfig_file = os.path.join(module_dir, "Kconfig")
|
|
|
|
if os.path.exists(kconfig_file):
|
|
# If the module has its own Kconfig, just source it
|
|
# Make sure the source path is relative to the project root or uses proper env vars
|
|
# Since Kconfig.projbuild is processed relative to project root usually
|
|
rel_path = os.path.join("main", d, module_name, "Kconfig")
|
|
kconfig_content += f' source "{rel_path}"\n'
|
|
else:
|
|
# If no Kconfig exists, generate a default toggle for it
|
|
config_name = compute_default_config_name(d, module_name)
|
|
pretty_name = " ".join([word.capitalize() for word in module_name.split('_')])
|
|
|
|
# Default to 'n' for tests, 'y' for everything else
|
|
default_val = 'n' if d == 'tests' else 'y'
|
|
|
|
kconfig_content += f''' config {config_name.replace("CONFIG_", "")}
|
|
bool "Enable {pretty_name}"
|
|
default {default_val}
|
|
help
|
|
Enable the {module_name} module in {d}.
|
|
'''
|
|
kconfig_content += ' endmenu\n\n'
|
|
|
|
kconfig_content += 'endmenu\n'
|
|
|
|
with open(OUTPUT_FILE, 'w') as f:
|
|
f.write(kconfig_content)
|
|
|
|
print(f"Generated {OUTPUT_FILE} successfully.")
|
|
if created_templates:
|
|
print("Generated module.cmake templates:")
|
|
for path in created_templates:
|
|
print(f" - {path}")
|
|
|
|
if __name__ == "__main__":
|
|
generate_kconfig_modules() |