mirror of
https://github.com/openharmony/interface_sdk_c.git
synced 2026-08-24 14:32:50 -04:00
8d3f796a9e
Signed-off-by: 李想 <lixiang380@huawei.com>
512 lines
19 KiB
Python
Executable File
512 lines
19 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
# Copyright (c) 2025 Huawei Device Co., Ltd.
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import argparse
|
|
import re
|
|
import sys
|
|
import os
|
|
import tempfile
|
|
from typing import List, Dict, Set, Optional, Any
|
|
import clang.cindex
|
|
from clang.cindex import Config, Index, CursorKind, TypeKind
|
|
|
|
|
|
class InterfaceExtractor:
|
|
"""接口提取类,封装所有相关功能"""
|
|
|
|
def __init__(self, libclang_path: str):
|
|
"""初始化Clang配置"""
|
|
self._configure_clang(libclang_path)
|
|
# 记录已经处理过的枚举名称、结构体和联合体,避免重复处理
|
|
self.processed_enums: Set[str] = set()
|
|
self.processed_structs: Set[str] = set()
|
|
self.processed_unions: Set[str] = set()
|
|
|
|
|
|
def extract_interfaces(self, filename: str) -> List[Dict]:
|
|
"""提取文件中的所有接口"""
|
|
abs_filename = os.path.abspath(filename)
|
|
|
|
# 编译参数
|
|
args = [
|
|
'-x', 'c++',
|
|
'-std=c++11', # 编译标准
|
|
'-D__OH_AVAILABILITY(x)=', # 忽略可用性属性
|
|
'-D__OH_VERSION(a,b)=', # 忽略版本宏
|
|
'-I./include', # 添加头文件搜索路径
|
|
]
|
|
|
|
index = None
|
|
tu = None
|
|
try:
|
|
# 创建并解析TU
|
|
index = Index.create()
|
|
tu = index.parse(abs_filename, args=args)
|
|
except Exception as e:
|
|
err_msg = f"Clang parsing {abs_filename} failed for: {e}"
|
|
raise ValueError(err_msg)
|
|
|
|
interfaces = []
|
|
self.processed_enums.clear() # 清空已处理枚举
|
|
self.processed_structs.clear() # 清空已处理结构体
|
|
self.processed_unions.clear() # 清空已处理联合体
|
|
|
|
def traverse_ast(cursor):
|
|
"""遍历AST的回调函数"""
|
|
if cursor.location.file and cursor.location.file.name == abs_filename:
|
|
try:
|
|
if cursor.kind == CursorKind.ENUM_DECL:
|
|
self._process_enum_decl(cursor, interfaces)
|
|
elif cursor.kind == CursorKind.UNION_DECL:
|
|
self._process_union_decl(cursor, interfaces)
|
|
elif cursor.kind == CursorKind.STRUCT_DECL: # 处理结构体声明
|
|
self._process_struct_decl(cursor, interfaces)
|
|
elif cursor.kind == CursorKind.TYPEDEF_DECL:
|
|
self._process_typedef_decl(cursor, interfaces)
|
|
else:
|
|
self._process_other_declarations(cursor, interfaces)
|
|
except Exception as e:
|
|
print(f"处理节点时出错 {cursor.kind.name}: {e}")
|
|
|
|
# 递归处理子节点
|
|
for child in cursor.get_children():
|
|
traverse_ast(child)
|
|
|
|
try:
|
|
traverse_ast(tu.cursor)
|
|
except Exception as e:
|
|
print(f"遍历AST时出错: {e}")
|
|
finally:
|
|
if tu is not None:
|
|
if hasattr(tu, 'dispose'):
|
|
tu.dispose()
|
|
else:
|
|
del tu
|
|
if index is not None:
|
|
if hasattr(index, 'dispose'):
|
|
index.dispose()
|
|
else:
|
|
del index
|
|
return interfaces
|
|
|
|
|
|
def _configure_clang(self, libclang_path: str) -> None:
|
|
"""配置Clang库路径"""
|
|
if not Config.loaded:
|
|
if os.path.exists(libclang_path):
|
|
Config.set_library_file(libclang_path)
|
|
else:
|
|
print(f"Clang库路径不存在: {libclang_path},使用系统默认路径")
|
|
|
|
|
|
def _extract_since_version(self, comment: str) -> Optional[str]:
|
|
"""提取since版本号"""
|
|
if not comment:
|
|
return None
|
|
matches = re.findall(r'@since\s+([^\s\n\r]+)', comment)
|
|
# 出现多段注释且@since有多个时,使用最后一个作为判断标准
|
|
return matches[-1] if matches else None
|
|
|
|
|
|
def _create_interface_info(self, cursor, node_type: str, interface_name: Optional[str] = None, parent_type: Optional[str] = None) -> Dict[str, Any]:
|
|
"""创建统一的接口信息字典"""
|
|
# 根据参数决定interface_name
|
|
if interface_name is None or interface_name == "":
|
|
interface_name = cursor.spelling
|
|
|
|
return {
|
|
'interface_name': interface_name,
|
|
'since_version': self._extract_since_version(cursor.raw_comment),
|
|
'node_type': node_type,
|
|
'cursor_kind': cursor.kind.name,
|
|
'location': f"{cursor.location.file.name}:{cursor.location.line}",
|
|
'comment': cursor.raw_comment.strip() if cursor.raw_comment else "",
|
|
'display_name': cursor.displayname,
|
|
'parent_type': parent_type, # 父节点类型
|
|
'line': f"(行号:{cursor.location.line})"
|
|
}
|
|
|
|
|
|
def _process_member(self, cursor, interfaces: List[Dict], parent_name: str, member_type: str) -> None:
|
|
"""统一的成员处理函数"""
|
|
if cursor.raw_comment and "@since" in cursor.raw_comment:
|
|
interface_info = self._create_interface_info(
|
|
cursor, f"{member_type}_member", cursor.spelling, parent_name
|
|
)
|
|
interfaces.append(interface_info)
|
|
|
|
|
|
def _process_composite_decl(self, cursor, interfaces: List[Dict], decl_type: str) -> None:
|
|
"""处理复合类型声明(枚举、结构体、联合体)"""
|
|
if not cursor.spelling:
|
|
return
|
|
|
|
type_name = cursor.spelling
|
|
# 检查是否已处理过
|
|
processed_sets = {
|
|
"enum": self.processed_enums,
|
|
"struct": self.processed_structs,
|
|
"union": self.processed_unions
|
|
}
|
|
|
|
if decl_type in processed_sets and type_name in processed_sets[decl_type]:
|
|
return
|
|
|
|
# 检查类型本身的@since注释
|
|
if cursor.raw_comment and "@since" in cursor.raw_comment:
|
|
interface_info = self._create_interface_info(cursor, decl_type, type_name)
|
|
interfaces.append(interface_info)
|
|
if decl_type not in processed_sets:
|
|
processed_sets[decl_type] = set()
|
|
processed_sets[decl_type].add(type_name)
|
|
|
|
# 处理成员
|
|
member_kind = CursorKind.ENUM_CONSTANT_DECL if decl_type == "enum" else CursorKind.FIELD_DECL
|
|
for child in cursor.get_children():
|
|
if child.kind == member_kind:
|
|
self._process_member(child, interfaces, type_name, decl_type)
|
|
|
|
|
|
def _process_enum_decl(self, cursor, interfaces: List[Dict]) -> None:
|
|
"""处理枚举类型声明"""
|
|
self._process_composite_decl(cursor, interfaces, "enum")
|
|
|
|
|
|
def _process_struct_decl(self, cursor, interfaces: List[Dict]) -> None:
|
|
"""处理结构体类型声明"""
|
|
self._process_composite_decl(cursor, interfaces, "struct")
|
|
|
|
|
|
def _process_union_decl(self, cursor, interfaces: List[Dict]) -> None:
|
|
"""处理联合体类型声明"""
|
|
self._process_composite_decl(cursor, interfaces, "union")
|
|
|
|
|
|
def _process_typedef_decl(self, cursor, interfaces: List[Dict]) -> None:
|
|
"""处理typedef声明"""
|
|
underlying_type = cursor.underlying_typedef_type
|
|
if not underlying_type:
|
|
return
|
|
|
|
# 处理 elaborated type (通常是枚举、结构体、联合体)
|
|
if underlying_type.kind == TypeKind.ELABORATED:
|
|
self._process_elaborated_typedef(cursor, interfaces)
|
|
# 处理函数指针
|
|
elif underlying_type.kind == TypeKind.POINTER:
|
|
self._process_function_pointer_typedef(cursor, interfaces)
|
|
|
|
|
|
def _process_elaborated_typedef(self, cursor, interfaces: List[Dict]) -> None:
|
|
"""处理 elaborated typedef (枚举、结构体、联合体)"""
|
|
underlying_decl = cursor.underlying_typedef_type.get_declaration()
|
|
|
|
# 根据底层声明类型分别处理
|
|
type_handlers = {
|
|
CursorKind.UNION_DECL: ("union", self.processed_unions),
|
|
CursorKind.STRUCT_DECL: ("struct", self.processed_structs),
|
|
CursorKind.ENUM_DECL: ("enum", self.processed_enums)
|
|
}
|
|
|
|
if underlying_decl.kind in type_handlers:
|
|
type_name, processed_set = type_handlers[underlying_decl.kind]
|
|
self._process_typedef_composite(cursor, underlying_decl, interfaces, type_name, processed_set)
|
|
|
|
|
|
def _process_typedef_composite(self, cursor, underlying_decl, interfaces: List[Dict],
|
|
type_name: str, processed_set: set) -> None:
|
|
"""统一处理typedef复合类型(枚举、结构体、联合体)"""
|
|
# 检查是否已处理
|
|
if cursor.spelling in processed_set:
|
|
return
|
|
|
|
# 处理typedef声明
|
|
if cursor.raw_comment and "@since" in cursor.raw_comment:
|
|
interface_info = self._create_interface_info(cursor, f"typedef_{type_name}")
|
|
interfaces.append(interface_info)
|
|
|
|
# 处理成员
|
|
member_kind = CursorKind.ENUM_CONSTANT_DECL if type_name == "enum" else CursorKind.FIELD_DECL
|
|
for child in underlying_decl.get_children():
|
|
if child.kind == member_kind:
|
|
self._process_member(child, interfaces, cursor.spelling, type_name)
|
|
|
|
|
|
def _process_enum_typedef(self, cursor, interfaces: List[Dict]) -> None:
|
|
"""处理枚举typedef(兼容旧版本调用)"""
|
|
underlying_decl = cursor.underlying_typedef_type.get_declaration() if cursor.underlying_typedef_type else None
|
|
if underlying_decl:
|
|
self._process_typedef_composite(cursor, underlying_decl, interfaces, "enum", self.processed_enums)
|
|
|
|
|
|
def _process_function_pointer_typedef(self, cursor, interfaces: List[Dict]) -> None:
|
|
"""处理函数指针typedef"""
|
|
if cursor.raw_comment and "@since" in cursor.raw_comment:
|
|
interface_info = self._create_interface_info(cursor, "typedef_function_pointer")
|
|
interfaces.append(interface_info)
|
|
|
|
|
|
def _process_other_declarations(self, cursor, interfaces: List[Dict]) -> None:
|
|
"""处理其他类型的声明(函数、变量、结构体等)"""
|
|
node_type_map = {
|
|
CursorKind.FUNCTION_DECL: "function",
|
|
CursorKind.VAR_DECL: "variable",
|
|
}
|
|
|
|
node_type = node_type_map.get(cursor.kind)
|
|
if node_type and cursor.raw_comment and "@since" in cursor.raw_comment:
|
|
interface_info = self._create_interface_info(cursor, node_type)
|
|
interfaces.append(interface_info)
|
|
|
|
|
|
def get_libclang_path() -> str:
|
|
"""获取libclang库路径"""
|
|
current_script_path = os.path.abspath(__file__)
|
|
great_grandparent_dir = os.path.dirname(
|
|
os.path.dirname(
|
|
os.path.dirname(
|
|
os.path.dirname(current_script_path)
|
|
)
|
|
)
|
|
)
|
|
|
|
lib_path = "prebuilts/clang/ohos/linux-x86_64/llvm/lib/libclang.so"
|
|
libclang_path = os.path.join(great_grandparent_dir, lib_path)
|
|
return libclang_path
|
|
|
|
|
|
def extract_include_part_str(path_str: str) -> str:
|
|
"""提取include及之后的部分"""
|
|
path_parts = path_str.split(os.sep)
|
|
try:
|
|
include_index = path_parts.index('include')
|
|
except ValueError:
|
|
return path_str
|
|
return os.sep.join(path_parts[include_index:])
|
|
|
|
|
|
def splice_check_results(interfaces: List[Dict], sdk_api_version: str, path: str,
|
|
violation_list: List[str] = None) -> None:
|
|
"""拼接检查结果"""
|
|
if not sdk_api_version.isdigit():
|
|
raise ValueError(f"--sdk-api-version must be digits!")
|
|
sdk_version_num = int(sdk_api_version)
|
|
|
|
for interface in interfaces:
|
|
since_version = interface.get('since_version')
|
|
if not since_version or not since_version.isdigit() or int(since_version) <= sdk_version_num:
|
|
continue
|
|
|
|
# 错误信息格式拼接
|
|
line_parts = [path]
|
|
|
|
# 处理结构体/联合体成员的特殊情况
|
|
if interface.get('node_type') and interface.get('parent_type'):
|
|
line_parts.extend([interface['parent_type'], interface['interface_name']])
|
|
else:
|
|
line_parts.append(interface['interface_name'])
|
|
violation_line = "{}{}\n".format("#".join(line_parts), interface['line'])
|
|
|
|
# 如果传入了violation_list,也追加到该列表中
|
|
if violation_list is not None:
|
|
violation_list.append(violation_line)
|
|
|
|
|
|
def find_since_define_pattern_with_line_numbers(file_path):
|
|
"""在文件中查找匹配指定正则表达式的模式,并返回包含行号的信息"""
|
|
# 正则表达式格式:包括@since注释块、可选的@version注释、#define结束标志
|
|
pattern = r'^\s*\* @since (\d+)(?:\s*\*\s*@version \d+(?:\.\d+)?)?\s*\*/\s*\n#define\s+(\w+)'
|
|
|
|
try:
|
|
with open(file_path, 'r', encoding='utf-8') as file:
|
|
content = file.read()
|
|
|
|
matches_info = []
|
|
for match in re.finditer(pattern, content, re.MULTILINE | re.DOTALL):
|
|
since_number = match.group(1)
|
|
macro_name = match.group(2)
|
|
|
|
# 计算行号:统计 match.start() 之前的换行符数量
|
|
line_number = content.count('\n', 0, match.start()) + 1
|
|
|
|
# 获取 #define 所在的具体行
|
|
match_text = match.group(0)
|
|
|
|
# 计算 #define 在匹配文本中的相对位置
|
|
define_pos_in_match = match_text.find('#define')
|
|
|
|
if define_pos_in_match != -1:
|
|
# 计算 #define 在完整内容中的绝对位置
|
|
define_abs_pos = match.start() + define_pos_in_match
|
|
# 重新计算行号,这次是基于 #define 的位置
|
|
define_line_number = content.count('\n', 0, define_abs_pos) + 1
|
|
|
|
matches_info.append({
|
|
'since_number': since_number,
|
|
'macro_name': macro_name,
|
|
'line_number': define_line_number,
|
|
})
|
|
return matches_info
|
|
except FileNotFoundError:
|
|
logging.error(f"文件 {file_path} 不存在")
|
|
return []
|
|
except Exception as e:
|
|
logging.error(f"读取文件 {file_path} 时出错: {e}")
|
|
return []
|
|
|
|
|
|
def replace_pragma_pack(file_paths):
|
|
"""查找并替换 #pragma pack(数字) 为空白行"""
|
|
# 读取输入文件内容
|
|
with open(file_paths, 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
|
|
# 正则表达式匹配 #pragma pack(数字) 模式
|
|
pattern = r'#pragma\s+pack\(\d+\)'
|
|
|
|
matches = re.findall(pattern, content, re.DOTALL)
|
|
if matches:
|
|
# 使用正则表达式替换匹配的行为空行
|
|
new_content = re.sub(pattern, '', content, flags=re.MULTILINE)
|
|
|
|
# 创建临时文件
|
|
temp_fd, temp_path = tempfile.mkstemp(suffix='.txt', prefix='temp_', text=True)
|
|
try:
|
|
# 写入临时文件
|
|
with os.fdopen(temp_fd, 'w', encoding='utf-8') as f:
|
|
f.write(new_content)
|
|
print(f"已创建临时文件: {temp_path}")
|
|
return temp_path
|
|
except Exception as e:
|
|
os.close(temp_fd)
|
|
if os.path.exists(temp_path):
|
|
os.unlink(temp_path)
|
|
raise e
|
|
|
|
else:
|
|
print(f"{file_paths} 文件未找到匹配")
|
|
return None
|
|
|
|
|
|
def check_files_for_pattern(file_paths: str, interfaces: List[Dict]):
|
|
"""检查多个文件中是否存在该模式,并记录行号"""
|
|
# 使用新的函数获取包含行号的匹配信息
|
|
matches_info = find_since_define_pattern_with_line_numbers(file_paths)
|
|
|
|
if matches_info:
|
|
for i, match_info in enumerate(matches_info, 1):
|
|
since_number = match_info['since_number']
|
|
macro_name = match_info['macro_name']
|
|
line_number = match_info['line_number']
|
|
|
|
interface_info = {
|
|
'interface_name': macro_name,
|
|
'since_version': since_number,
|
|
'node_type': "macro",
|
|
'cursor_kind': "",
|
|
'location': f"{file_paths}:{line_number}",
|
|
'comment': match_info.get('match_text', ''),
|
|
'display_name': "",
|
|
'parent_type': "",
|
|
'line': f"(行号:{str(line_number)})"
|
|
}
|
|
interfaces.append(interface_info)
|
|
else:
|
|
print(f"{file_paths} 文件未找到匹配")
|
|
|
|
|
|
def process_with_temp_file(extractor: InterfaceExtractor, temp_path: str):
|
|
"""使用临时文件处理接口信息"""
|
|
try:
|
|
# 从临时文件提取接口信息
|
|
return extractor.extract_interfaces(temp_path)
|
|
except FileNotFoundError:
|
|
err_msg = f"临时文件未找到: {temp_path}"
|
|
raise ValueError(err_msg)
|
|
except Exception as e:
|
|
err_msg = f"提取接口时发生错误: {e}"
|
|
raise ValueError(err_msg)
|
|
finally:
|
|
# 确保清理临时文件
|
|
try:
|
|
if os.path.exists(temp_path):
|
|
os.unlink(temp_path)
|
|
print(f"已清理临时文件: {temp_path}")
|
|
except Exception as cleanup_error:
|
|
print(f"清理临时文件时出错: {cleanup_error}")
|
|
|
|
|
|
def get_interfaces_info(path: str):
|
|
"""获取接口信息"""
|
|
# 初始化提取器
|
|
extractor = InterfaceExtractor(get_libclang_path())
|
|
|
|
temp_path = replace_pragma_pack(path)
|
|
|
|
# 如果使用临时文件,确保清理
|
|
if temp_path:
|
|
return process_with_temp_file(extractor, temp_path)
|
|
else:
|
|
# 直接处理原始文件
|
|
return extractor.extract_interfaces(path)
|
|
|
|
|
|
def main():
|
|
"""主函数"""
|
|
parser = argparse.ArgumentParser(description="提取带有@since注解的接口")
|
|
parser.add_argument('--input', required=True, help="头文件路径列表")
|
|
parser.add_argument('--sdk-api-version', required=True, help="当前构建API版本")
|
|
|
|
args = parser.parse_args()
|
|
|
|
if not os.path.exists(args.input):
|
|
print(f"header path list does not exist -> {args.input}")
|
|
exit(1)
|
|
|
|
# 从文件读取输入路径
|
|
try:
|
|
with open(args.input, 'r', encoding='utf-8') as f:
|
|
input_paths = [line.strip() for line in f if line.strip()]
|
|
except Exception as e:
|
|
print(f"错误: 无法读取输入文件 {args.input}: {e}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
# 处理每个输入路径
|
|
violation_lines = []
|
|
for i, path in enumerate(input_paths, 1):
|
|
# 检查输入文件
|
|
if not os.path.exists(path):
|
|
continue
|
|
|
|
interfaces = get_interfaces_info(path)
|
|
|
|
#查找是否有define
|
|
check_files_for_pattern(path, interfaces)
|
|
if not interfaces:
|
|
print("未找到带有@since注解的接口")
|
|
continue
|
|
|
|
# 处理结果
|
|
file_path = extract_include_part_str(path)
|
|
splice_check_results(interfaces, args.sdk_api_version, file_path, violation_lines)
|
|
|
|
if violation_lines:
|
|
check_result = "当前API最高版本号不得高于" + args.sdk_api_version + ",以下接口版本号不合规,请检查!\n"
|
|
check_result += "".join(violation_lines)
|
|
raise ValueError(check_result)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|