#!/usr/bin/env python3 """ StubGen - C/H/Py 文件到 Python 存根文件生成器(工程化版本) 用法: python StubGen.py -c config.json python StubGen.py -i input.h -o output.py python StubGen.py -d ./kernel -o ./kernel/include 示例配置: { "InputDir": "./kernel", "OutputDir": "./kernel/include", "IncludePatterns": ["*.h", "*.c", "*.py"], "ExcludePatterns": ["*_test.py"], "PreserveStructure": true, "GenerateGuards": true, "TypeMappings": { "custom_type": "t.CCustom" } } """ import sys import os import re import json import ast import argparse import logging from pathlib import Path from typing import List, Dict, Optional, Set, Tuple from dataclasses import dataclass, field sys.path.insert(0, os.path.dirname(__file__)) from lib.core.stub_generator import CHeaderParser, PythonStubGenerator, CTypeMapper @dataclass class StubGenConfig: """存根生成器配置""" InputDir: Optional[str] = None OutputDir: str = "./stubs" InputFiles: List[str] = field(default_factory=list) IncludePatterns: List[str] = field(default_factory=lambda: ["*.h", "*.c", "*.py"]) ExcludePatterns: List[str] = field(default_factory=list) TypeMappings: Dict[str, str] = field(default_factory=dict) PreserveStructure: bool = True # 保持目录结构 GenerateGuards: bool = True # 生成宏守卫 verbose: bool = False DryRun: bool = False @classmethod def from_dict(cls, data: Dict) -> 'StubGenConfig': """从字典创建配置""" return cls( InputDir=data.get('InputDir'), OutputDir=data.get('OutputDir', './stubs'), InputFiles=data.get('InputFiles', []), IncludePatterns=data.get('IncludePatterns', ['*.h', '*.c', '*.py']), ExcludePatterns=data.get('ExcludePatterns', []), TypeMappings=data.get('TypeMappings', {}), PreserveStructure=data.get('PreserveStructure', True), GenerateGuards=data.get('GenerateGuards', True), verbose=data.get('verbose', False), DryRun=data.get('DryRun', False), ) @classmethod def from_file(cls, FilePath: str) -> 'StubGenConfig': """从 JSON 文件加载配置""" with open(FilePath, 'r', encoding='utf-8') as f: data = json.load(f) return cls.from_dict(data) class PythonToStubConverter: """将 Python 文件转换为存根格式""" # __include 别名映射表 {alias: ModuleName} IncludeAliasMap: Dict[str, str] = {} # 变量排除列表 - 匹配这些模式的变量不会被添加到存根文件 VarExcludeList: List[str] = [] # 宏排除列表 - 匹配这些模式的宏不会被添加到存根文件 MacroExcludeList: List[str] = [] @classmethod def SetIncludeAliasMap(cls, alias_map: Dict[str, str]): """设置 __include 别名映射表""" cls.IncludeAliasMap = alias_map @classmethod def SetVarExcludeList(cls, exclude_list: List[str]): """设置变量排除列表""" cls.VarExcludeList = exclude_list @classmethod def SetMacroExcludeList(cls, exclude_list: List[str]): """设置宏排除列表""" cls.MacroExcludeList = exclude_list @classmethod def _ShouldExcludeVar(cls, VarName: str) -> bool: """检查变量是否应该被排除""" import fnmatch for pattern in cls.VarExcludeList: if fnmatch.fnmatch(VarName, pattern): return True return False @classmethod def _ShouldExcludeMacro(cls, MacroName: str) -> bool: """检查宏是否应该被排除""" import fnmatch for pattern in cls.MacroExcludeList: if fnmatch.fnmatch(MacroName, pattern): return True return False @staticmethod def convert(PyContent: str, ModuleName: str) -> str: """将 Python 代码转换为存根格式""" lines = PyContent.split('\n') StubLines = [] # 添加文件头 StubLines.append('"""') StubLines.append(f'Auto-generated Python stub file from {ModuleName}.py') StubLines.append(f'Module: {ModuleName}') StubLines.append('"""') StubLines.append('') # 解析 Python 代码,检查是否已经导入了 t 和 c import ast HasImportT = False HasImportC = False try: tree = ast.parse(PyContent) for node in tree.body: if isinstance(node, ast.Import): for alias in node.names: if alias.name == 't': HasImportT = True elif alias.name == 'c': HasImportC = True except: pass # 添加默认导入(如果源代码中没有) if not HasImportT: StubLines.append('import t') if not HasImportC: StubLines.append('import c') if not HasImportT or not HasImportC: StubLines.append('') # 添加 c.CPragma("once") # StubLines.append('c.CPragma("once")') StubLines.append('') # 生成文件级别宏守卫名称 #FileGuardName = f'__{ModuleName.upper()}_DEFINE__' # 添加文件开头宏守卫 #StubLines.append(f'c.CIfndef({FileGuardName})') #StubLines.append(f'{FileGuardName}: t.CDefine') #StubLines.append('') # 解析 Python 代码,提取类型定义 try: tree = ast.parse(PyContent) # 第一遍扫描:收集 Postdefinition 变量 PostdefVars = {} # {ClassName: [(VarName, TypeStr, node), ...]} for node in tree.body: if isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name): TypeStr = PythonToStubConverter._GetTypeString(node.annotation) # 检查是否是 Postdefinition 类型 if 't.Postdefinition' in TypeStr: # 提取引用的类名 ClassName = PythonToStubConverter._ExtractPostdefClass(node.annotation) if ClassName: if ClassName not in PostdefVars: PostdefVars[ClassName] = [] PostdefVars[ClassName].append((node.target.id, TypeStr, node)) # 按原始顺序处理节点,保持代码的原始顺序 PrevType = None # 记录前一个节点的类型 for node in tree.body: CurrentType = None if isinstance(node, ast.ClassDef): CurrentType = 'class' # 如果前一个不是类,添加空行分隔 if PrevType is not None and PrevType != 'class': StubLines.append('') # 检查是否有对应的 Postdefinition 变量 ClassPostdefs = PostdefVars.get(node.name, []) ClassLines = PythonToStubConverter._ConvertClass( node, SourceLines=lines, PostdefVars=ClassPostdefs ) StubLines.extend(ClassLines) elif isinstance(node, ast.FunctionDef): CurrentType = 'function' # 如果前一个是类或者是第一个函数,添加空行分隔 if PrevType == 'class' or (PrevType is not None and PrevType != 'function'): StubLines.append('') FuncLines = PythonToStubConverter._ConvertFunction(node, SourceLines=lines) StubLines.extend(FuncLines) StubLines.append('') # 函数后空行 elif isinstance(node, ast.AnnAssign): CurrentType = 'variable' # 跳过 Postdefinition 变量(已经在类中处理) TypeStr = PythonToStubConverter._GetTypeString(node.annotation) if 't.Postdefinition' in TypeStr: continue # 检查是否带有 t.CStatic 标志,如果有则不排除 HasStatic = 't.CStatic' in TypeStr # 检查变量是否应该被排除 ShouldExclude = False if isinstance(node.target, ast.Name) and not HasStatic: VarName = node.target.id if PythonToStubConverter._ShouldExcludeVar(VarName): ShouldExclude = True if ShouldExclude: continue # 如果前一个不是变量,添加空行分隔 if PrevType is not None and PrevType != 'variable': StubLines.append('') VarLines = PythonToStubConverter._ConvertVariable(node, SourceLines=lines) # 移除多余的空行 VarLines = [line for line in VarLines if line.strip()] StubLines.extend(VarLines) elif isinstance(node, ast.Import): # 处理导入语句 CurrentType = 'import' ImportStr = PythonToStubConverter._GetImportString(node, SourceLines=lines) if ImportStr: if PrevType is not None and PrevType != 'import': StubLines.append('') StubLines.append(ImportStr) elif isinstance(node, ast.Assign): CurrentType = 'variable' # 处理模块级别的全局变量(无类型标注) if PrevType is not None and PrevType != 'variable': StubLines.append('') AssignLines = PythonToStubConverter._ConvertGlobalAssign(node) StubLines.extend(AssignLines) elif isinstance(node, ast.ImportFrom): # 处理 from ... import ... 语句 CurrentType = 'import' ImportStr = PythonToStubConverter._GetImportFromString(node, SourceLines=lines) if ImportStr: if PrevType is not None and PrevType != 'import': StubLines.append('') StubLines.append(ImportStr) elif isinstance(node, ast.Expr): # 处理模块级别的宏调用,如 c.CIf(...), c.CEndif() ExprStr = PythonToStubConverter._GetExprString(node.value) if ExprStr: CurrentType = 'macro' StubLines.append(ExprStr) StubLines.append('') # 宏后空行 elif isinstance(node, ast.If): # 处理模块级别的 if 宏条件,如 if c.CIf(FF_MULTI_PARTITION): IfStr = PythonToStubConverter._GetModuleIfMacroString(node) if IfStr: CurrentType = 'macro' StubLines.append(IfStr) StubLines.append('') # 宏后空行 if CurrentType: PrevType = CurrentType # 添加文件结尾宏守卫 #StubLines.append('') #StubLines.append('c.CEndif()') except SyntaxError as e: # 如果解析失败,添加注释说明 StubLines.append(f'# Warning: Failed to parse Python code: {e}') StubLines.append('# Original content:') StubLines.append('"""') StubLines.append(PyContent[:1000]) # 只显示前1000字符 StubLines.append('"""') return '\n'.join(StubLines) @staticmethod def _ConvertClass(node: ast.ClassDef, SourceLines: List[str] = None, PostdefVars: List[Tuple[str, str, ast.AnnAssign]] = None, indent: int = 0) -> List[str]: """转换类定义 Args: node: 类定义节点 SourceLines: 源代码行列表 PostdefVars: Postdefinition 变量列表 [(VarName, TypeStr, node), ...] indent: 缩进级别(用于嵌套类) """ lines = [] PostdefVars = PostdefVars or [] IndentStr = ' ' * indent # 处理类装饰器 for decorator in node.decorator_list: DecoratorStr = PythonToStubConverter._GetDecoratorString(decorator) if DecoratorStr: lines.append(f'{IndentStr}@{DecoratorStr}') # 检查是否是结构体、联合体或枚举 BaseNames = [base.attr if isinstance(base, ast.Attribute) else base.id for base in node.bases if isinstance(base, (ast.Name, ast.Attribute))] # 保留原始基类 if node.bases: BaseStrs = [] for base in node.bases: if isinstance(base, ast.Name): BaseStrs.append(base.id) elif isinstance(base, ast.Attribute): BaseStrs.append(f'{base.value.id}.{base.attr}') elif isinstance(base, ast.Subscript): # 处理泛型类型,如 memory_block_t[MAX_ORDER + 1] BaseStrs.append(PythonToStubConverter._GetTypeString(base)) ClassTypeParamStr = '' if hasattr(node, 'type_params') and node.type_params: param_names = [tp.name for tp in node.type_params] ClassTypeParamStr = f'[{", ".join(param_names)}]' if BaseStrs: lines.append(f'{IndentStr}class {node.name}{ClassTypeParamStr}({", ".join(BaseStrs)}):') else: lines.append(f'{IndentStr}class {node.name}{ClassTypeParamStr}:') else: ClassTypeParamStr = '' if hasattr(node, 'type_params') and node.type_params: param_names = [tp.name for tp in node.type_params] ClassTypeParamStr = f'[{", ".join(param_names)}]' lines.append(f'{IndentStr}class {node.name}{ClassTypeParamStr}:') # 处理类成员 HasMembers = False seen_member_names = set() for item in node.body: if isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name): seen_member_names.add(item.target.id) init_members = [] for item in node.body: if isinstance(item, ast.FunctionDef) and item.name == '__init__': for stmt in item.body: if (isinstance(stmt, ast.AnnAssign) and isinstance(stmt.target, ast.Attribute) and isinstance(stmt.target.value, ast.Name) and stmt.target.value.id == 'self'): init_members.append(stmt) untyped_self_members = [] for m_name in [m.target.attr for m in init_members if isinstance(m.target, ast.Attribute)]: seen_member_names.add(m_name) for item in node.body: if isinstance(item, ast.FunctionDef): for stmt in item.body: if (isinstance(stmt, ast.Assign) and len(stmt.targets) == 1 and isinstance(stmt.targets[0], ast.Attribute) and isinstance(stmt.targets[0].value, ast.Name) and stmt.targets[0].value.id == 'self'): attr_name = stmt.targets[0].attr if attr_name not in seen_member_names: seen_member_names.add(attr_name) untyped_self_members.append(stmt) for item in list(node.body) + init_members + untyped_self_members: if isinstance(item, ast.Assign): if (len(item.targets) == 1 and isinstance(item.targets[0], ast.Attribute) and isinstance(item.targets[0].value, ast.Name) and item.targets[0].value.id == 'self'): HasMembers = True MemberIndent = IndentStr + ' ' attr_name = item.targets[0].attr if attr_name in seen_member_names: continue InferredType = 'int' if item.value and isinstance(item.value, ast.Constant): if isinstance(item.value.value, float): InferredType = 'float' elif isinstance(item.value.value, bool): InferredType = 'bool' elif isinstance(item.value.value, str): InferredType = 'str' lines.append(f'{MemberIndent}{attr_name}: {InferredType}') else: HasMembers = True MemberIndent = IndentStr + ' ' if SourceLines and hasattr(item, 'lineno'): StartLine = item.lineno - 1 EndLine = item.EndLineno if hasattr(item, 'EndLineno') and item.EndLineno else StartLine + 1 for i in range(StartLine, min(EndLine, len(SourceLines))): lines.append(MemberIndent + SourceLines[i].lstrip()) elif isinstance(item, ast.AnnAssign): HasMembers = True TypeStr = PythonToStubConverter._GetTypeString(item.annotation) MemberIndent = IndentStr + ' ' # 检查是否是宏变量(类型包含 t.CDefine) if 't.CDefine' in TypeStr: # 检查宏变量是否应该被排除 if isinstance(item.target, ast.Name): MacroName = item.target.id if PythonToStubConverter._ShouldExcludeMacro(MacroName): continue # 跳过该宏 # 宏变量保持原样(包括赋值) if SourceLines and hasattr(item, 'lineno'): StartLine = item.lineno - 1 EndLine = item.EndLineno if hasattr(item, 'EndLineno') and item.EndLineno else StartLine + 1 for i in range(StartLine, min(EndLine, len(SourceLines))): lines.append(MemberIndent + SourceLines[i].lstrip()) else: lines.append(f'{MemberIndent}{item.target.id}: {TypeStr}') else: if isinstance(item.target, ast.Attribute): MemberName = item.target.attr else: MemberName = item.target.id FinalTypeStr = TypeStr if (isinstance(item.annotation, ast.Subscript) and isinstance(item.annotation.value, ast.Name) and item.annotation.value.id == 'list'): slice_node = item.annotation.slice has_size = False if isinstance(slice_node, ast.Tuple) and len(slice_node.elts) == 2: count_node = slice_node.elts[1] if isinstance(count_node, ast.Constant) and isinstance(count_node.value, int) and count_node.value > 0: has_size = True else: try: count_expr = ast.unparse(count_node) except Exception: count_expr = '' if count_expr and count_expr != 'None': has_size = True elem_type_str = PythonToStubConverter._GetTypeString(slice_node.elts[0]) FinalTypeStr = f'list[{elem_type_str}, {count_expr}]' if not has_size: elem_type_str = PythonToStubConverter._GetTypeString(slice_node if not isinstance(slice_node, ast.Tuple) else slice_node.elts[0]) init_len = 0 if item.value is not None: if isinstance(item.value, ast.List): init_len = len(item.value.elts) elif isinstance(item.value, ast.Constant) and isinstance(item.value.value, str): init_len = len(item.value.value) + 1 if init_len > 0: FinalTypeStr = f'list[{elem_type_str}, {init_len}]' else: FinalTypeStr = f'list[{elem_type_str}, None]' lines.append(f'{MemberIndent}{MemberName}: {FinalTypeStr}') elif isinstance(item, ast.FunctionDef): HasMembers = True FuncStub = PythonToStubConverter._ConvertFunction(item, indent=indent+1, SourceLines=SourceLines, class_name=node.name) lines.extend(FuncStub) elif isinstance(item, ast.Expr): # 处理宏调用,如 c.CIf(...), c.CEndif(), c.CElif(), c.CElse() ExprStr = PythonToStubConverter._GetExprString(item.value) if ExprStr: lines.append(f'{IndentStr} {ExprStr}') elif isinstance(item, ast.If): # 处理 if c.CIf(...): 形式的宏条件 IfStr = PythonToStubConverter._get_if_macro_string(item, indent=indent+1) if IfStr: lines.append(IfStr) elif isinstance(item, ast.ClassDef): # 处理嵌套类 HasMembers = True NestedClassLines = PythonToStubConverter._ConvertClass(item, SourceLines=SourceLines, PostdefVars=[], indent=indent+1) # 移除嵌套类的宏守卫(因为已经在父类的宏守卫内) FilteredLines = [] for line in NestedClassLines: # 跳过宏守卫相关行 if line.strip().startswith('c.CIfndef(') or line.strip().startswith('c.CEndif()'): continue if ': t.CDefine' in line and '__' in line: continue FilteredLines.append(line) lines.extend(FilteredLines) if not HasMembers: lines.append(f'{IndentStr} pass') # 添加 Postdefinition 变量 if PostdefVars: lines.append('') for VarName, TypeStr, _ in PostdefVars: lines.append(f'{VarName}: {TypeStr}') return lines @staticmethod def _ConvertFunction(node: ast.FunctionDef, indent: int = 0, SourceLines: List[str] = None, class_name: str = None) -> List[str]: """转换函数定义""" lines = [] IndentStr = ' ' * indent # 检查是否是宏函数(返回类型包含 t.CDefine) is_macro_func = False if node.returns: ReturnType = PythonToStubConverter._GetTypeString(node.returns) if 't.CDefine' in ReturnType: is_macro_func = True # 如果是宏函数,检查是否应该被排除 if is_macro_func: if PythonToStubConverter._ShouldExcludeMacro(node.name): return lines # 返回空列表,排除该宏 # 保持原样(包括代码块) if SourceLines: StartLine = node.lineno - 1 EndLine = node.EndLineno if hasattr(node, 'EndLineno') and node.EndLineno else StartLine + 1 for i in range(StartLine, min(EndLine, len(SourceLines))): lines.append(SourceLines[i]) if lines and lines[-1].strip() and not lines[-1].rstrip().endswith(':'): pass else: lines.append(f'{IndentStr} pass') return lines # 处理函数装饰器 for decorator in node.decorator_list: DecoratorStr = PythonToStubConverter._GetDecoratorString(decorator) if DecoratorStr: lines.append(f'{IndentStr}@{DecoratorStr}') # 构建参数列表 params = [] for arg_idx, arg in enumerate(node.args.args): ArgName = arg.arg if arg.annotation: TypeStr = PythonToStubConverter._GetTypeString(arg.annotation) params.append(f'{ArgName}: {TypeStr}') elif class_name and arg_idx == 0 and ArgName == 'self': params.append(f'self: {class_name}') else: params.append(ArgName) # 处理 *args if node.args.vararg: ArgName = node.args.vararg.arg if node.args.vararg.annotation: TypeStr = PythonToStubConverter._GetTypeString(node.args.vararg.annotation) params.append(f'*{ArgName}: {TypeStr}') else: params.append(f'*{ArgName}') # 处理 **kwargs if node.args.kwarg: ArgName = node.args.kwarg.arg if node.args.kwarg.annotation: TypeStr = PythonToStubConverter._GetTypeString(node.args.kwarg.annotation) params.append(f'**{ArgName}: {TypeStr}') else: params.append(f'**{ArgName}') ParamStr = ', '.join(params) # 返回类型 - 保持原始类型(不添加t.State) if node.returns: ReturnType = PythonToStubConverter._GetTypeString(node.returns) else: ReturnType = 't.CInt' TypeParamStr = '' if hasattr(node, 'type_params') and node.type_params: param_names = [tp.name for tp in node.type_params] TypeParamStr = f'[{", ".join(param_names)}]' lines.append(f'{IndentStr}def {node.name}{TypeParamStr}({ParamStr}) -> {ReturnType}: pass') return lines @staticmethod def _ConvertVariable(node: ast.AnnAssign, SourceLines: List[str] = None) -> List[str]: """转换变量定义""" lines = [] if isinstance(node.target, ast.Name): VarName = node.target.id TypeStr = PythonToStubConverter._GetTypeString(node.annotation) # 检查是否是宏变量(类型包含 t.CDefine) if 't.CDefine' in TypeStr: # 检查宏变量是否应该被排除 if PythonToStubConverter._ShouldExcludeMacro(VarName): return lines # 返回空列表,排除该宏 # 宏变量保持原样(包括赋值) if SourceLines and hasattr(node, 'lineno'): StartLine = node.lineno - 1 EndLine = node.EndLineno if hasattr(node, 'EndLineno') and node.EndLineno else StartLine + 1 for i in range(StartLine, min(EndLine, len(SourceLines))): lines.append(SourceLines[i]) else: lines.append(f'{VarName}: {TypeStr}') return lines # 非宏变量:不加 t.State # 含有 typedef 的变量不加 extern,普通变量需要加 extern has_typedef = 't.CTypedef' in TypeStr or 'CTypedef' in TypeStr if has_typedef and node.value is not None: ValueTypeStr = PythonToStubConverter._GetTypeString(node.value) lines.append(f'{VarName}: {TypeStr} = {ValueTypeStr}') else: if not has_typedef and 't.CExtern' not in TypeStr and 't.CStatic' not in TypeStr: TypeStr = f't.CExtern | {TypeStr}' lines.append(f'{VarName}: {TypeStr}') return lines @staticmethod def _ConvertGlobalAssign(node: ast.Assign) -> List[str]: """转换模块级别的全局变量(无类型标注)""" lines = [] if not node.targets or not isinstance(node.targets[0], ast.Name): return lines VarName = node.targets[0].id # 跳过私有变量(以下划线开头) if VarName.startswith('_'): return lines # 从值推断类型 inferred_type = PythonToStubConverter._InferTypeFromValue(node.value) if inferred_type: lines.append(f'{VarName}: {inferred_type}') else: lines.append(f'{VarName}: t.CInt') return lines @staticmethod def _InferTypeFromValue(value: ast.AST) -> str: """从值推断 C 类型""" if isinstance(value, ast.Constant): if isinstance(value.value, int): return 't.CInt' elif isinstance(value.value, float): return 't.CDouble' elif isinstance(value.value, str): return 't.CCharPtr' elif isinstance(value.value, bool): return 't.CInt' elif isinstance(value, ast.List): return 't.CVoidPtr' elif isinstance(value, ast.Dict): return 't.CVoidPtr' elif isinstance(value, ast.Name): return 't.CInt' elif isinstance(value, ast.BinOp): left_type = PythonToStubConverter._InferTypeFromValue(value.left) if left_type: return left_type return 't.CInt' elif isinstance(value, ast.Call): # 函数调用返回值,假设为指针类型 if isinstance(value.func, ast.Name): func_name = value.func.id if func_name == 'malloc': return 't.CVoidPtr' elif func_name in ('ctypes.cast', 'cast'): return 't.CVoidPtr' return 't.CVoidPtr' return None @staticmethod def _GetTypeString(annotation: ast.AST) -> str: """获取类型字符串""" if isinstance(annotation, ast.Name): return annotation.id elif isinstance(annotation, ast.Attribute): if isinstance(annotation.value, ast.Name): return f'{annotation.value.id}.{annotation.attr}' else: # 处理嵌套属性访问,如 t.attr.packed ParentStr = PythonToStubConverter._GetTypeString(annotation.value) return f'{ParentStr}.{annotation.attr}' elif isinstance(annotation, ast.Subscript): base = PythonToStubConverter._GetTypeString(annotation.value) if isinstance(annotation.slice, ast.Tuple): slice_strs = [PythonToStubConverter._GetTypeString(e) for e in annotation.slice.elts] SliceStr = ', '.join(slice_strs) else: SliceStr = PythonToStubConverter._GetTypeString(annotation.slice) return f'{base}[{SliceStr}]' elif isinstance(annotation, ast.BinOp): # 处理 BinOp,如 t.CExtern | fd_table.__set_default__(...) left = annotation.left right = annotation.right # 检查右侧是否是 __set_default__ 调用 if isinstance(right, ast.Call) and isinstance(right.func, ast.Attribute) and right.func.attr in ('__set_default__', '__set_default'): # 去除 __set_default__,保留左侧,右侧替换为函数名部分 LeftStr = PythonToStubConverter._GetTypeString(left) RightStr = PythonToStubConverter._GetTypeString(right.func.value) if isinstance(annotation.op, ast.BitOr): return f'{LeftStr} | {RightStr}' LeftStr = PythonToStubConverter._GetTypeString(left) RightStr = PythonToStubConverter._GetTypeString(right) if isinstance(annotation.op, ast.BitOr): return f'{LeftStr} | {RightStr}' elif isinstance(annotation.op, ast.Add): return f'{LeftStr} + {RightStr}' elif isinstance(annotation.op, ast.Sub): return f'{LeftStr} - {RightStr}' elif isinstance(annotation.op, ast.Mult): return f'{LeftStr} * {RightStr}' elif isinstance(annotation.op, ast.Div): return f'{LeftStr} / {RightStr}' elif isinstance(annotation.op, ast.Mod): return f'{LeftStr} % {RightStr}' elif isinstance(annotation, ast.UnaryOp): if isinstance(annotation.op, ast.Not): operand = PythonToStubConverter._GetTypeString(annotation.operand) return f'not {operand}' elif isinstance(annotation.op, ast.Invert): operand = PythonToStubConverter._GetTypeString(annotation.operand) return f'~{operand}' elif isinstance(annotation.op, ast.USub): operand = PythonToStubConverter._GetTypeString(annotation.operand) return f'-{operand}' elif isinstance(annotation, ast.Compare): # 处理比较操作,如 FF_MAX_SS != FF_MIN_SS left = PythonToStubConverter._GetTypeString(annotation.left) comparators = [PythonToStubConverter._GetTypeString(c) for c in annotation.comparators] ops = [] for op in annotation.ops: if isinstance(op, ast.Eq): ops.append('==') elif isinstance(op, ast.NotEq): ops.append('!=') elif isinstance(op, ast.Lt): ops.append('<') elif isinstance(op, ast.LtE): ops.append('<=') elif isinstance(op, ast.Gt): ops.append('>') elif isinstance(op, ast.GtE): ops.append('>=') else: ops.append('?') result = left for i, op in enumerate(ops): result += f' {op} {comparators[i]}' return result elif isinstance(annotation, ast.Constant): return repr(annotation.value) elif isinstance(annotation, ast.Index): return PythonToStubConverter._GetTypeString(annotation.value) elif isinstance(annotation, ast.List): # 处理列表,如 [MAX_FILE_DESCRIPTORS] elements = [PythonToStubConverter._GetTypeString(elem) for elem in annotation.elts] return f'[{", ".join(elements)}]' elif isinstance(annotation, ast.Call): # 处理函数调用形式的类型注解,如 t.Postdefinition(xxa) 或 callable(t.CVoid, app_id = t.CInt) # 如果是 __set_default__ 调用,只返回函数名部分 if isinstance(annotation.func, ast.Attribute) and annotation.func.attr in ('__set_default__', '__set_default'): return PythonToStubConverter._GetTypeString(annotation.func.value) FuncStr = PythonToStubConverter._GetTypeString(annotation.func) ArgsStr = ', '.join([PythonToStubConverter._GetTypeString(arg) for arg in annotation.args]) # 处理关键字参数 keywords_str = ', '.join([f'{kw.arg} = {PythonToStubConverter._GetTypeString(kw.value)}' for kw in annotation.keywords]) # 合并位置参数和关键字参数 if ArgsStr and keywords_str: return f'{FuncStr}({ArgsStr}, {keywords_str})' elif keywords_str: return f'{FuncStr}({keywords_str})' else: return f'{FuncStr}({ArgsStr})' return 't.CVoid' @staticmethod def _ExtractPostdefClass(annotation: ast.AST) -> Optional[str]: """从 Postdefinition 类型注解中提取类名 例如:t.Postdefinition(__buddy_system) -> '__buddy_system' t.Postdefinition(xxx) | t.CTypedef -> 'xxx' """ # 处理 BinOp (如: t.Postdefinition(xxx) | t.CTypedef) if isinstance(annotation, ast.BinOp): # 递归检查左操作数 left_result = PythonToStubConverter._ExtractPostdefClass(annotation.left) if left_result: return left_result # 递归检查右操作数 right_result = PythonToStubConverter._ExtractPostdefClass(annotation.right) if right_result: return right_result return None # 处理函数调用 (如: t.Postdefinition(__buddy_system)) if isinstance(annotation, ast.Call): FuncStr = PythonToStubConverter._GetTypeString(annotation.func) if FuncStr == 't.Postdefinition' and annotation.args: # 提取第一个参数 first_arg = annotation.args[0] if isinstance(first_arg, ast.Name): return first_arg.id elif isinstance(first_arg, ast.Attribute): return f'{first_arg.value.id}.{first_arg.attr}' return None @staticmethod def _GetImportString(node: ast.Import, SourceLines: List[str] = None) -> str: """获取导入语句字符串""" parts = [] IncludeAliasMap = PythonToStubConverter.IncludeAliasMap for alias in node.names: ModuleName = alias.name # 检查是否是 __include.xxx 格式的导入 if ModuleName.startswith('__include.'): # 提取别名(xxx 部分) SubModule = ModuleName[len('__include.'):] if SubModule in IncludeAliasMap: # 替换为实际模块路径 RealModule = IncludeAliasMap[SubModule] if alias.asname: parts.append(f'{RealModule} as {alias.asname}') else: parts.append(f'{RealModule} as {SubModule}') else: # 不在映射表中,保持原样 if alias.asname: parts.append(f'{ModuleName} as {alias.asname}') else: parts.append(ModuleName) else: if alias.asname: parts.append(f'{alias.name} as {alias.asname}') else: parts.append(alias.name) result = f'import {", ".join(parts)}' # 提取并保留注释 if SourceLines and hasattr(node, 'lineno'): LineIdx = node.lineno - 1 if 0 <= LineIdx < len(SourceLines): line = SourceLines[LineIdx] CommentMatch = re.search(r'#.*$', line) if CommentMatch: result += ' ' + CommentMatch.group(0) return result @staticmethod def _GetImportFromString(node: ast.ImportFrom, SourceLines: List[str] = None) -> str: """获取 from ... import ... 语句字符串""" module = node.module or '' parts = [] IncludeAliasMap = PythonToStubConverter.IncludeAliasMap # 处理相对导入 (from . import xxx 或 from ..package import xxx) if node.level > 0: if module: if node.level == 1: full_module = f'.{module}' else: full_module = '.' * node.level + module else: full_module = '.' * node.level for alias in node.names: if alias.asname: parts.append(f'{alias.name} as {alias.asname}') else: parts.append(alias.name) return f'from {full_module} import {", ".join(parts)}' # 提取注释(在生成结果前获取) comment = '' if SourceLines and hasattr(node, 'lineno'): LineIdx = node.lineno - 1 if 0 <= LineIdx < len(SourceLines): line = SourceLines[LineIdx] CommentMatch = re.search(r'#.*$', line) if CommentMatch: comment = ' ' + CommentMatch.group(0) # 检查是否是 from __include import xxx 格式 if module == '__include': for alias in node.names: name = alias.name if name in IncludeAliasMap: # 替换为实际模块路径 RealModule = IncludeAliasMap[name] if alias.asname: parts.append(f'import {RealModule} as {alias.asname}{comment}') else: parts.append(f'import {RealModule} as {name}{comment}') else: # 不在映射表中,保持原样 if alias.asname: parts.append(f'from {module} import {name} as {alias.asname}{comment}') else: parts.append(f'from {module} import {name}{comment}') return '\n'.join(parts) # 普通 from ... import ... 语句 for alias in node.names: if alias.asname: parts.append(f'{alias.name} as {alias.asname}') else: parts.append(alias.name) return f'from {module} import {", ".join(parts)}{comment}' @staticmethod def _GetDecoratorString(decorator: ast.AST) -> str: """获取装饰器字符串""" if isinstance(decorator, ast.Name): return decorator.id elif isinstance(decorator, ast.Attribute): if isinstance(decorator.value, ast.Name): return f'{decorator.value.id}.{decorator.attr}' return decorator.attr elif isinstance(decorator, ast.Call): # 处理带参数的装饰器,如 @c.Attribute(t.attr.packed) FuncStr = PythonToStubConverter._GetDecoratorString(decorator.func) ArgsStr = ', '.join([PythonToStubConverter._GetTypeString(arg) for arg in decorator.args]) return f'{FuncStr}({ArgsStr})' return '' @staticmethod def _GetExprString(expr: ast.AST) -> str: """获取表达式字符串(用于宏调用)""" if isinstance(expr, ast.Call): # 处理函数调用,如 c.CIf(...), c.CEndif(), c.CUndef(...), c.CPragma(...) FuncStr = PythonToStubConverter._GetDecoratorString(expr.func) if FuncStr and ('CIf' in FuncStr or 'CEndif' in FuncStr or 'CElif' in FuncStr or 'CElse' in FuncStr or 'CUndef' in FuncStr or 'CPragma' in FuncStr): ArgsStr = ', '.join([PythonToStubConverter._GetTypeString(arg) for arg in expr.args]) return f'{FuncStr}({ArgsStr})' if ArgsStr else f'{FuncStr}()' return '' @staticmethod def _get_if_macro_string(node: ast.If, indent: int = 1) -> str: """获取类内部的 if 宏字符串(如 if c.CIf(...):)""" IndentStr = ' ' * indent InnerIndent = ' ' * (indent + 1) # 检查条件是否是宏调用 if isinstance(node.test, ast.Call): FuncStr = PythonToStubConverter._GetDecoratorString(node.test.func) if FuncStr and 'CIf' in FuncStr: ArgsStr = ', '.join([PythonToStubConverter._GetTypeString(arg) for arg in node.test.args]) result = [f'{IndentStr}if {FuncStr}({ArgsStr}):' if ArgsStr else f'{IndentStr}if {FuncStr}():'] # 处理 if 体内的语句 for item in node.body: if isinstance(item, ast.AnnAssign): TypeStr = PythonToStubConverter._GetTypeString(item.annotation) result.append(f'{InnerIndent}{item.target.id}: {TypeStr}') elif isinstance(item, ast.Expr): ExprStr = PythonToStubConverter._GetExprString(item.value) if ExprStr: result.append(f'{InnerIndent}{ExprStr}') return '\n'.join(result) return '' @staticmethod def _GetModuleIfMacroString(node: ast.If) -> str: """获取模块级别的 if 宏字符串(如 if c.CIf(FF_MULTI_PARTITION):)""" return PythonToStubConverter._ProcessIfMacro(node, indent=0) @staticmethod def _ProcessIfMacro(node: ast.If, indent: int = 0) -> str: """处理 if 宏节点,支持 elif 和 else""" IndentStr = ' ' * indent # 检查条件是否是宏调用 if isinstance(node.test, ast.Call): FuncStr = PythonToStubConverter._GetDecoratorString(node.test.func) if FuncStr and 'CIf' in FuncStr: ArgsStr = ', '.join([PythonToStubConverter._GetTypeString(arg) for arg in node.test.args]) result = [f'{IndentStr}if {FuncStr}({ArgsStr}):' if ArgsStr else f'{IndentStr}if {FuncStr}():'] # 处理 if 体内的语句 for item in node.body: result.extend(PythonToStubConverter._ProcessMacroBodyItem(item, indent + 1)) # 处理 elif 和 else current = node while current.orelse: if len(current.orelse) == 1 and isinstance(current.orelse[0], ast.If): # elif 情况 elif_node = current.orelse[0] if isinstance(elif_node.test, ast.Call): ElifFuncStr = PythonToStubConverter._GetDecoratorString(elif_node.test.func) if ElifFuncStr and 'CIf' in ElifFuncStr: ElifArgsStr = ', '.join([PythonToStubConverter._GetTypeString(arg) for arg in elif_node.test.args]) result.append(f'{IndentStr}elif {ElifFuncStr}({ElifArgsStr}):' if ElifArgsStr else f'{IndentStr}elif {ElifFuncStr}():') for item in elif_node.body: result.extend(PythonToStubConverter._ProcessMacroBodyItem(item, indent + 1)) current = elif_node continue break else: # else 情况 result.append(f'{IndentStr}else:') for item in current.orelse: result.extend(PythonToStubConverter._ProcessMacroBodyItem(item, indent + 1)) break return '\n'.join(result) return '' @staticmethod def _ProcessMacroBodyItem(item: ast.AST, indent: int = 0) -> list: """处理宏体内的单个语句""" IndentStr = ' ' * indent result = [] if isinstance(item, ast.ClassDef): class_stub = PythonToStubConverter._ConvertClass(item) for line in class_stub: if line.strip(): result.append(f'{IndentStr}{line}') else: result.append(line) elif isinstance(item, ast.FunctionDef): FuncStub = PythonToStubConverter._ConvertFunction(item, indent=indent) result.extend(FuncStub) elif isinstance(item, ast.AnnAssign): TypeStr = PythonToStubConverter._GetTypeString(item.annotation) result.append(f'{IndentStr}{item.target.id}: {TypeStr}') elif isinstance(item, ast.Expr): ExprStr = PythonToStubConverter._GetExprString(item.value) if ExprStr: result.append(f'{IndentStr}{ExprStr}') elif isinstance(item, ast.If): # 处理嵌套的 if 宏 NestedIf = PythonToStubConverter._ProcessIfMacro(item, indent=indent) if NestedIf: result.append(NestedIf) return result class StubGen: """存根生成器主类""" def __init__(self, config: StubGenConfig): self.config = config self.logger = self._SetupLogger() self.GeneratedFiles: List[str] = [] self.FailedFiles: List[str] = [] # 应用自定义类型映射 if config.TypeMappings: CTypeMapper.BASIC_TYPE_MAP.update(config.TypeMappings) def _SetupLogger(self) -> logging.Logger: """设置日志""" logger = logging.getLogger('StubGen') logger.setLevel(logging.DEBUG if self.config.verbose else logging.INFO) if not logger.handlers: handler = logging.StreamHandler(sys.stdout) formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%H:%M:%S' ) handler.setFormatter(formatter) logger.addHandler(handler) return logger def FindInputFiles(self) -> List[Tuple[str, str]]: """查找输入文件,返回 (文件路径, 相对路径) 列表""" files = [] # 添加显式指定的文件 for FilePath in self.config.InputFiles: if os.path.exists(FilePath): RelPath = os.path.relpath(FilePath, self.config.InputDir or '.') files.append((FilePath, RelPath)) else: self.logger.warning(f"Input file not found: {FilePath}") # 从输入目录查找 if self.config.InputDir and os.path.exists(self.config.InputDir): InputPath = Path(self.config.InputDir) for pattern in self.config.IncludePatterns: for FilePath in InputPath.rglob(pattern): # 检查是否在排除列表中 excluded = False RelPath = os.path.relpath(str(FilePath), self.config.InputDir) for exclude_pattern in self.config.ExcludePatterns: if FilePath.match(exclude_pattern) or RelPath == exclude_pattern: excluded = True break if not excluded: files.append((str(FilePath), RelPath)) # 去重并保持顺序 seen = set() UniqueFiles = [] for FilePath, RelPath in files: key = FilePath if key not in seen: seen.add(key) UniqueFiles.append((FilePath, RelPath)) return UniqueFiles def _GenerateGuardName(self, FilePath: str) -> str: """生成宏守卫名称""" # 获取文件名(不含扩展名) BaseName = os.path.splitext(os.path.basename(FilePath))[0] # 转换为大写,替换特殊字符为下划线 guard = re.sub(r'[^a-zA-Z0-9_]', '_', BaseName).upper() guard = re.sub(r'_+', '_', guard) # 合并多个下划线 guard = guard.strip('_') return f'{guard}_DEFINE_H' def _GetOutputPath(self, RelPath: str) -> str: """获取输出文件路径""" # 更改扩展名为 .pyi (Python stub file) BaseName = os.path.splitext(RelPath)[0] OutputRelPath = BaseName + '.pyi' # 构建完整输出路径 if self.config.OutputDir: OutputPath = os.path.join(self.config.OutputDir, OutputRelPath) else: OutputPath = OutputRelPath return OutputPath def GenerateStub(self, InputFile: str, RelPath: str) -> bool: """生成单个存根文件""" try: self.logger.info(f"Processing: {InputFile}") # 确定输出文件路径 OutputFile = self._GetOutputPath(RelPath) # 确保输出目录存在 OutputDir = os.path.dirname(OutputFile) if OutputDir and not os.path.exists(OutputDir): if not self.config.DryRun: os.makedirs(OutputDir, exist_ok=True) self.logger.debug(f"Created directory: {OutputDir}") if self.config.DryRun: self.logger.info(f"[DRY RUN] Would generate: {OutputFile}") return True # 根据文件类型选择处理方式 ext = os.path.splitext(InputFile)[1].lower() if ext in ['.h', '.c']: # C 文件:使用 CHeaderParser content = self._GenerateFromC(InputFile, RelPath) elif ext == '.py': # Python 文件:使用 PythonToStubConverter content = self._GenerateFromPy(InputFile, RelPath) else: self.logger.warning(f"Unsupported file type: {ext}") return False # 写入文件 with open(OutputFile, 'w', encoding='utf-8') as f: f.write(content) self.GeneratedFiles.append(OutputFile) self.logger.info(f"Generated: {OutputFile}") return True except Exception as e: self.logger.error(f"Failed to process {InputFile}: {e}") import traceback traceback.print_exc() self.FailedFiles.append(InputFile) return False def _GenerateFromC(self, InputFile: str, RelPath: str) -> str: """从 C 文件生成存根""" parser = CHeaderParser() parser.parse_file(InputFile) generator = PythonStubGenerator(parser) ModuleName = os.path.splitext(os.path.basename(InputFile))[0] content = generator.generate(ModuleName) # 添加宏守卫(如果需要) if self.config.GenerateGuards: guard_name = self._GenerateGuardName(InputFile) guard_comment = f"\n# Guard: {guard_name}\n" content = content + guard_comment return content def _GenerateFromPy(self, InputFile: str, RelPath: str) -> str: """从 Python 文件生成存根""" with open(InputFile, 'r', encoding='utf-8') as f: PyContent = f.read() ModuleName = os.path.splitext(os.path.basename(InputFile))[0] content = PythonToStubConverter.convert(PyContent, ModuleName) # 添加宏守卫(如果需要) if self.config.GenerateGuards: guard_name = self._GenerateGuardName(InputFile) guard_comment = f"\n# Guard: {guard_name}\n" content = content + guard_comment return content def run(self) -> bool: """运行生成器""" self.logger.info("=" * 60) self.logger.info("StubGen - C/H/Py to Python Stub Generator") self.logger.info("=" * 60) # 查找输入文件 InputFiles = self.FindInputFiles() if not InputFiles: self.logger.warning("No input files found!") return False self.logger.info(f"Found {len(InputFiles)} input file(s)") for FilePath, RelPath in InputFiles: self.logger.debug(f" - {RelPath}") # 处理每个文件 SuccessCount = 0 for FilePath, RelPath in InputFiles: if self.GenerateStub(FilePath, RelPath): SuccessCount += 1 # 输出统计信息 self.logger.info("=" * 60) self.logger.info(f"Summary: {SuccessCount}/{len(InputFiles)} files generated successfully") if self.FailedFiles: self.logger.warning(f"Failed files ({len(self.FailedFiles)}):") for f in self.FailedFiles: self.logger.warning(f" - {f}") return SuccessCount == len(InputFiles) def main(): parser = argparse.ArgumentParser( description='StubGen - Generate Python stub files from C/H/Py sources', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=''' Examples: # 使用配置文件 %(prog)s -c config.json # 处理单个文件 %(prog)s -i input.h -o output.py # 批量处理目录(保持目录结构) %(prog)s -d ./kernel -o ./kernel/include # 干运行(不实际生成文件) %(prog)s -d ./kernel --dry-run # 不生成宏守卫 %(prog)s -d ./kernel --no-guards ''' ) # 输入选项 InputGroup = parser.add_mutually_exclusive_group(required=True) InputGroup.add_argument('-c', '--config', help='Configuration file (JSON)') InputGroup.add_argument('-i', '--input', help='Input file') InputGroup.add_argument('-d', '--directory', help='Input directory') # 输出选项 parser.add_argument('-o', '--output', help='Output directory') # 其他选项 parser.add_argument('--include', action='append', help='Include patterns (default: *.h, *.c, *.py)') parser.add_argument('--exclude', action='append', help='Exclude patterns') parser.add_argument('--no-guards', action='store_true', help='Do not generate guard macros') parser.add_argument('--no-structure', action='store_true', help='Do not preserve directory structure') parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output') parser.add_argument('--dry-run', action='store_true', help='Dry run (do not create files)') args = parser.parse_args() # 加载配置 if args.config: if not os.path.exists(args.config): print(f"Error: Config file not found: {args.config}") sys.exit(1) config = StubGenConfig.from_file(args.config) else: config = StubGenConfig() config.IncludePatterns = args.include or ['*.h', '*.c', '*.py'] config.ExcludePatterns = args.exclude or [] config.GenerateGuards = not args.no_guards config.PreserveStructure = not args.no_structure config.verbose = args.verbose config.DryRun = args.dry_run if args.input: config.InputFiles = [args.input] if args.output: config.OutputDir = os.path.dirname(args.output) or '.' elif args.directory: config.InputDir = args.directory if args.output: config.OutputDir = args.output # 创建生成器并运行 generator = StubGen(config) # 如果指定了单个输出文件,直接处理 if args.input and args.output and not os.path.isdir(args.output): RelPath = os.path.relpath(args.input, config.InputDir or '.') success = generator.GenerateStub(args.input, RelPath) else: success = generator.run() sys.exit(0 if success else 1) if __name__ == '__main__': main()