import json
import re
import logging
import sys
from pathlib import Path
from shutil import copy2
from datetime import datetime
from argparse import ArgumentParser
# -------------------------------
# 🔧 新增:日志文件配置
# -------------------------------
# 正确获取项目根目录
PROJECT_ROOT = Path(__file__).parent.parent.resolve()
LOG_DIR = PROJECT_ROOT / "output" / "log"
# ✅ 关键:必须加上 parents=True 才能自动创建 output/
LOG_DIR.mkdir(parents=True, exist_ok=True)
# 生成带时间戳的日志文件名
LOG_FILE = LOG_DIR / f"range_sync_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
class CLMRangeSynchronizer:
def __init__(self, manifest_path="output/generated_ranges_manifest.json",
c_file_path="input/wlc_clm_data_6726b0.c", dry_run=False):
self.manifest_path = Path(manifest_path)
self.c_file_path = Path(c_file_path)
self.dry_run = dry_run # 是否为预览模式
if not self.manifest_path.exists():
raise FileNotFoundError(f"找不到 manifest 文件: {self.manifest_path}")
if not self.c_file_path.exists():
raise FileNotFoundError(f"找不到 C 源文件: {self.c_file_path}")
self.used_ranges = []
self.array_macros = {}
self.struct_entries = {}
self.start_marker = "// === START CHANNEL RANGES"
self.end_marker = "// === END CHANNEL RANGES"
# ✅ 在实例化时获取 logger(避免全局变量)
self.logger = logging.getLogger(__name__)
def load_manifest(self):
"""加载并解析 generated_ranges_manifest.json"""
with open(self.manifest_path, 'r', encoding='utf-8') as f:
data = json.load(f)
if "used_ranges" not in data:
raise KeyError("❌ manifest 文件缺少 'used_ranges' 字段")
valid_ranges = []
for item in data["used_ranges"]:
if isinstance(item, str) and re.match(r'^RANGE_[\w\d_]+_\d+_\d+$', item):
valid_ranges.append(item)
else:
self.logger.warning(f"跳过无效项: {item}")
self.used_ranges = sorted(set(valid_ranges))
self.logger.info(f"已加载 {len(self.used_ranges)} 个有效 RANGE 宏")
def parse_c_arrays(self):
"""解析 C 文件中的 channel_ranges_xxx[] 数组"""
content = self.c_file_path.read_text(encoding='utf-8')
start_idx = content.find(self.start_marker)
end_idx = content.find(self.end_marker)
if start_idx == -1 or end_idx == -1:
raise ValueError("未找到 CHANNEL RANGES 注释锚点")
block = content[start_idx:end_idx]
pattern = re.compile(
r'static\s+const\s+struct\s+clm_channel_range\s+(channel_ranges_[\w\d]+)\s*\[\s*\]\s*=\s*\{([^}]*)\};',
re.DOTALL
)
for array_name, body in pattern.findall(block):
macros = [m.strip() for m in re.findall(r'RANGE_[\w\d_]+', body)]
entries = []
for low, high in re.findall(r'\{\s*(\d+)\s*,\s*(\d+)\s*\}', body):
entries.append({"low": int(low), "high": int(high)})
self.array_macros[array_name] = macros
self.struct_entries[array_name] = entries
self.logger.debug(f"解析数组 {array_name}: {len(macros)} 个宏")
def get_array_name_for_range(self, range_macro):
"""根据 RANGE 宏推断应属于哪个数组"""
match = re.match(r'RANGE_([0-9]+[A-Za-z])_([0-9]+)M_', range_macro, re.IGNORECASE)
if not match:
self.logger.warning(f"无法推断数组名: {range_macro}")
return None
band = match.group(1).lower() # '2g'
bw = match.group(2) # '20'
return f"channel_ranges_{band}_{bw}m"
def extract_channels_from_macro(self, macro):
"""
从宏字符串中提取信道范围。
Args:
macro (str): 包含信道范围的宏字符串,格式应为 "_数字_数字" 形式。
Returns:
tuple: 包含两个整数的元组,分别表示信道的低值和高值。如果宏格式错误,则返回 (None, None)。
"""
match = re.search(r'_(\d+)_(\d+)$', macro)
if match:
low = int(match.group(1))
high = int(match.group(2))
return low, high
self.logger.warning(f"宏格式错误,无法提取信道: {macro}")
return None, None
def validate_and_repair(self):
"""确保每个 used_range 都在正确的数组中"""
modified = False
changes = []
for range_macro in self.used_ranges:
array_name = self.get_array_name_for_range(range_macro)
if not array_name:
self.logger.warning(f"无法识别数组类型,跳过: {range_macro}")
continue
if array_name not in self.array_macros:
self.logger.info(f"创建新数组: {array_name}")
self.array_macros[array_name] = []
self.struct_entries[array_name] = []
if range_macro not in self.array_macros[array_name]:
low, high = self.extract_channels_from_macro(range_macro)
if low is not None and high is not None:
self.array_macros[array_name].append(range_macro)
self.struct_entries[array_name].append({"low": low, "high": high})
changes.append(f"添加: {range_macro} → {{{low}, {high}}} 到 {array_name}")
self.logger.info(f"将添加: {range_macro} → {{{low}, {high}}} 到 {array_name}")
modified = True
else:
self.logger.warning(f"无法解析信道范围: {range_macro}")
if modified and not self.dry_run:
self._write_back_in_block()
self.logger.info("C 文件已更新")
elif modified and self.dry_run:
self.logger.info("DRY-RUN MODE: 有变更但不会写入文件")
else:
self.logger.info("所有 RANGE 已存在,无需修改")
return modified
def _infer_array_from_enum(self, enum_decl):
"""
从枚举声明中推断数组名称。
Args:
enum_decl (str): 枚举声明字符串。
Returns:
Optional[str]: 如果匹配到枚举声明,则返回推断出的数组名称;否则返回None。
"""
match = re.search(r'enum\s+range_([a-z\d_]+)', enum_decl)
if match:
return f"channel_ranges_{match.group(1)}"
return None
def _format_array_body(self, macros, structs, indent=" "):
"""
将宏定义和结构体信息格式化为字符串。
Args:
macros (list): 宏定义列表。
structs (list): 结构体信息列表,每个元素是一个包含'low'和'high'键的字典。
indent (str, optional): 缩进字符串。默认为" "。
Returns:
str: 格式化后的字符串。
"""
lines = []
for i in range(len(macros)):
line = f"{indent}{{ {structs[i]['low']}, {structs[i]['high']} }}, /* {macros[i]} */"
lines.append(line)
return "\n".join(lines)
def _write_back_in_block(self):
"""
安全地一次性更新 C 文件中的数组和枚举定义。
如果是 dry_run 模式,则不进行任何文件 I/O 操作(包括备份和写入)。
"""
if self.dry_run:
self.logger.info("DRY-RUN: 跳过写入文件(包括备份)")
return
try:
content = self.c_file_path.read_text(encoding='utf-8')
start_idx = content.find(self.start_marker)
end_idx = content.find(self.end_marker) + len(self.end_marker)
if start_idx == -1 or end_idx == -1:
raise ValueError("未找到 CHANNEL RANGES 标记块")
header = content[:start_idx]
footer = content[end_idx:]
block = content[start_idx:end_idx]
new_block = block
# === 更新数组定义 ===
array_pattern = re.compile(
r'(static\s+const\s+struct\s+clm_channel_range\s+(channel_ranges_[\w\d]+)\s*\[\s*\]\s*=\s*\{)([^}]*)\};',
re.DOTALL
)
for match in array_pattern.finditer(block):
array_name = match.group(2)
if array_name not in self.array_macros:
continue
macros = self.array_macros[array_name]
structs = self.struct_entries[array_name]
formatted_body = self._format_array_body(macros, structs)
old_decl = match.group(0)
new_decl = f"{match.group(1)}\n{formatted_body}\n}};"
new_block = new_block.replace(old_decl, new_decl)
# === 更新 enum 定义 ===
enum_pattern = re.compile(r'(enum\s+range_[\w\d_]+\s*\{)([^}]*)\}(;)', re.DOTALL)
final_block = new_block
for match in enum_pattern.finditer(new_block):
enum_body = match.group(2).strip()
existing_macros = dict(re.findall(r'(RANGE_[\w\d_]+)\s*=\s*(\d+)', enum_body))
array_name = self._infer_array_from_enum(match.group(0))
if not array_name or array_name not in self.array_macros:
continue
expected_macros = self.array_macros[array_name]
current_ids = [int(v) for v in existing_macros.values()]
next_id = max(current_ids) + 1 if current_ids else 0
missing_macros = [m for m in expected_macros if m not in existing_macros]
if not missing_macros:
continue
insert_lines = [f" {macro} = {next_id + i}," for i, macro in enumerate(missing_macros)]
to_insert = "\n" + "\n".join(insert_lines)
if enum_body.endswith(","):
to_insert = "," + to_insert
final_block = final_block[:match.end(2)] + to_insert + final_block[match.end(2):]
# === 执行 I/O:仅在此处进行文件操作 ===
backup_path = self.c_file_path.with_suffix('.c.bak')
copy2(self.c_file_path, backup_path)
self.logger.info(f"已备份 → {backup_path}")
self.c_file_path.write_text(header + final_block + footer, encoding='utf-8')
self.logger.info(f"已保存: {self.c_file_path}")
except Exception as e:
self.logger.error(f"写回文件失败: {e}")
raise
def run(self):
self.logger.info("开始同步 CLM RANGE 定义...")
try:
self.load_manifest()
self.parse_c_arrays()
was_modified = self.validate_and_repair()
# 🔍 根据 dry_run 和是否需要修改,输出不同信息
if was_modified:
if self.dry_run:
self.logger.info("🟡 预览模式:检测到变更,但不会写入文件")
else:
self.logger.info("✅ 同步完成:已成功更新 C 文件")
else:
self.logger.info("🟢 所有 RANGE 已存在,无需修改")
return was_modified
except Exception as e:
self.logger.error(f"❌ 同步失败: {e}")
raise
def main():
# ✅ 配置 logging:同时输出到 控制台 和 文件
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(name)s: %(message)s',
handlers=[
logging.FileHandler(LOG_FILE, encoding='utf-8'), # 写入日志文件
logging.StreamHandler(sys.stdout) # 输出到屏幕
],
force=True # 允许重复运行(如 PyCharm 中多次 Run)
)
logger = logging.getLogger(__name__)
# === 固定配置 ===
manifest_path = "../output/generated_ranges_manifest.json"
c_file_path = "../input/wlc_clm_data_6726b0.c"
dry_run = False
log_level = "INFO"
# 设置运行时日志级别
logging.getLogger().setLevel(log_level)
print(f"📌 开始同步 RANGE 定义...")
print(f"📊 manifest 文件: {manifest_path}")
print(f"📄 C 源文件: {c_file_path}")
if dry_run:
print("🟡 启用 dry-run 模式:仅预览变更,不修改文件")
try:
sync = CLMRangeSynchronizer(
manifest_path=manifest_path,
c_file_path=c_file_path,
dry_run=dry_run
)
sync.run()
print("✅ 同步完成!")
print(f"📄 详细日志已保存至: {LOG_FILE}") # 👈 提示用户日志位置
except FileNotFoundError as e:
logger.error(f"文件未找到: {e}")
print("❌ 请检查文件路径是否正确。")
sys.exit(1)
except PermissionError as e:
logger.error(f"权限错误: {e}")
print("❌ 无法读取或写入文件,请检查权限。")
sys.exit(1)
except Exception as e:
logger.error(f"程序异常退出: {e}", exc_info=True)
sys.exit(1)
if __name__ == '__main__':
main()
我发现执行完之后,// === START CHANNEL RANGES ===
/** 2.4G 20M channel ranges used in locales and restricted sets */
static const struct clm_channel_range channel_ranges_2g_20m[] = {
{ 1, 1}, { 1, 2}, { 1, 11}, { 1, 13}, { 1, 14}, { 2, 2}, { 2, 7}, { 2, 10},
{ 3, 3}, { 3, 9}, { 3, 10}, { 4, 4}, { 4, 8}, { 5, 7}, { 8, 8}, { 9, 9},
{ 10, 10}, { 11, 11}, { 12, 12}, { 12, 13}, { 13, 13}, { 14, 14},
};
/** Indices for 2.4G 20M channel ranges */
enum range_2g_20m {
RANGE_2G_20M_1_1 = 0, RANGE_2G_20M_1_2 = 1, RANGE_2G_20M_1_11 = 2, RANGE_2G_20M_1_13 = 3,
RANGE_2G_20M_1_14 = 4, RANGE_2G_20M_2_2 = 5, RANGE_2G_20M_2_7 = 6, RANGE_2G_20M_2_10 = 7,
RANGE_2G_20M_3_3 = 8, RANGE_2G_20M_3_9 = 9, RANGE_2G_20M_3_10 = 10, RANGE_2G_20M_4_4 = 11,
RANGE_2G_20M_4_8 = 12, RANGE_2G_20M_5_7 = 13, RANGE_2G_20M_8_8 = 14, RANGE_2G_20M_9_9 = 15,
RANGE_2G_20M_10_10 = 16, RANGE_2G_20M_11_11 = 17, RANGE_2G_20M_12_12 = 18, RANGE_2G_20M_12_13 = 19,
RANGE_2G_20M_13_13 = 20, RANGE_2G_20M_14_14 = 21,
};
/** 2.4G 40M channel ranges used in locales and restricted sets */
static const struct clm_channel_range channel_ranges_2g_40m[] = {
{ 3, 3}, { 3, 11}, { 4, 4}, { 4, 5}, { 5, 5}, { 5, 6}, { 5, 7}, { 6, 6},
{ 6, 7}, { 7, 7}, { 7, 8}, { 8, 8}, { 9, 9}, { 10, 10}, { 11, 11},
};
/** Indices for 2.4G 40M channel ranges */
enum range_2g_40m {
RANGE_2G_40M_3_3 = 0, RANGE_2G_40M_3_11 = 1, RANGE_2G_40M_4_4 = 2, RANGE_2G_40M_4_5 = 3,
RANGE_2G_40M_5_5 = 4, RANGE_2G_40M_5_6 = 5, RANGE_2G_40M_5_7 = 6, RANGE_2G_40M_6_6 = 7,
RANGE_2G_40M_6_7 = 8, RANGE_2G_40M_7_7 = 9, RANGE_2G_40M_7_8 = 10, RANGE_2G_40M_8_8 = 11,
RANGE_2G_40M_9_9 = 12, RANGE_2G_40M_10_10 = 13, RANGE_2G_40M_11_11 = 14,
,
RANGE_2G_40M_4_8 = 15,};只插入了一个RANGE_2G_40M_4_8 = 15,并且还没有在channel_ranges_2g_40m[]里面增加{15 15},但是看日志写的C:\Users\admin\PyCharmMiscProject\.venv\Scripts\python.exe F:\issue\channel\range_sync.py
📌 开始同步 RANGE 定义...
📊 manifest 文件: ../output/generated_ranges_manifest.json
📄 C 源文件: ../input/wlc_clm_data_6726b0.c
2025-10-15 20:32:52,502 [INFO] __main__: 开始同步 CLM RANGE 定义...
2025-10-15 20:32:52,503 [INFO] __main__: 已加载 7 个有效 RANGE 宏
2025-10-15 20:32:52,508 [INFO] __main__: 创建新数组: channel_ranges_2g_20m
2025-10-15 20:32:52,508 [INFO] __main__: 将添加: RANGE_2G_20M_11_11 → {11, 11} 到 channel_ranges_2g_20m
2025-10-15 20:32:52,508 [INFO] __main__: 将添加: RANGE_2G_20M_1_1 → {1, 1} 到 channel_ranges_2g_20m
2025-10-15 20:32:52,508 [INFO] __main__: 将添加: RANGE_2G_20M_1_11 → {1, 11} 到 channel_ranges_2g_20m
2025-10-15 20:32:52,508 [INFO] __main__: 将添加: RANGE_2G_20M_2_10 → {2, 10} 到 channel_ranges_2g_20m
2025-10-15 20:32:52,508 [INFO] __main__: 创建新数组: channel_ranges_2g_40m
2025-10-15 20:32:52,510 [INFO] __main__: 将添加: RANGE_2G_40M_3_3 → {3, 3} 到 channel_ranges_2g_40m
2025-10-15 20:32:52,510 [INFO] __main__: 将添加: RANGE_2G_40M_4_8 → {4, 8} 到 channel_ranges_2g_40m
2025-10-15 20:32:52,510 [INFO] __main__: 将添加: RANGE_2G_40M_9_9 → {9, 9} 到 channel_ranges_2g_40m
2025-10-15 20:32:52,518 [INFO] __main__: 已备份 → ..\input\wlc_clm_data_6726b0.c.bak
2025-10-15 20:32:52,523 [INFO] __main__: 已保存: ..\input\wlc_clm_data_6726b0.c
2025-10-15 20:32:52,523 [INFO] __main__: C 文件已更新
2025-10-15 20:32:52,523 [INFO] __main__: ✅ 同步完成:已成功更新 C 文件
✅ 同步完成!
📄 详细日志已保存至: F:\issue\output\log\range_sync_20251015_203252.log
进程已结束,退出代码为 0
我很蒙
最新发布