diff --git a/ohos/ndk/BUILD.gn b/ohos/ndk/BUILD.gn index 16d6d6dc84eaefb3d976b8391faefea9c6163129..7ddf4012e4a7dd6a71bbc2a6130b05e0e77dc4a2 100755 --- a/ohos/ndk/BUILD.gn +++ b/ohos/ndk/BUILD.gn @@ -47,7 +47,7 @@ if (!use_current_sdk) { write_file(package_info_file, package_info, "json") action_with_pydeps("_collect_ndk_syscap") { - deps = all_ndk_targets_list + deps = [ ":parse_ndk_headers" ] script = "//build/ohos/ndk/collect_ndk_syscap.py" depfile = "$target_gen_dir/$target_name.d" _ndk_syscap_desc_file = @@ -71,6 +71,16 @@ if (!use_current_sdk) { } } + action_with_pydeps("parse_ndk_headers") { + deps = all_ndk_targets_list + script = "//build/ohos/ndk/parse_ndk_headers.py" + outputs = [ ndk_headers_out_dir_parsed ] + args = [ + "--input", + rebase_path("$ndk_headers_out_dir", root_build_dir), + ] + } + if (!defined(ext_ndk_config_file) || ext_ndk_config_file == "") { action_with_pydeps("_clean_ndk_ani") { deps = [ ":_collect_ndk_syscap" ] diff --git a/ohos/ndk/ndk.gni b/ohos/ndk/ndk.gni index dd3e316b880b80cdae4ab4294e1b4b05cae8a7d5..423b89842031b64a58a65a58d926a871acf847b6 100755 --- a/ohos/ndk/ndk.gni +++ b/ohos/ndk/ndk.gni @@ -39,6 +39,8 @@ if (use_current_sdk) { } ndk_headers_out_dir = "$ndk_os_irrelevant_out_dir/sysroot/usr/include" +ndk_headers_out_dir_parsed = + "$ndk_os_irrelevant_out_dir/sysroot/usr/include_parsed" ndk_libraries_out_dir = "$ndk_os_irrelevant_out_dir/sysroot/usr/lib" ndk_docs_out_dir = "$ndk_os_irrelevant_out_dir/docs" diff --git a/ohos/ndk/parse_ndk_headers.py b/ohos/ndk/parse_ndk_headers.py new file mode 100755 index 0000000000000000000000000000000000000000..d497746d8d5858f782a62e0435d765b03f4b80f5 --- /dev/null +++ b/ohos/ndk/parse_ndk_headers.py @@ -0,0 +1,155 @@ +#!/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 os +import re +import sys +from typing import Optional + + +class HeaderProcessor: + MAX_LINE_LENGTH = 120 # 最大行长度限制 + + def __init__(self): + # 头文件保护宏 + self.guard_pattern = re.compile(r'^#define\s+([A-Za-z0-9_]+_H(_)?)\s*$', re.MULTILINE) + # api接口匹配 + self.interface_pattern = re.compile( + r'^(?!typedef\s)([\w\s*]+?)\bOH_[A-Za-z_]\w*\s*\([^)]*\)\s*;', + re.MULTILINE | re.DOTALL + ) + # 字段匹配,匹配最近的/** ... */注释块 extern开头、分号结尾;排除extern "C",不含括号 + self.field_pattern = re.compile( + r'(/\*\*((?!/\*\*).)*?@since\s+(\d+)[\s\S]*?\*/)\s*' + r'(extern\s+(?!\"C\"\s*\{)[^(){}\[\]]*?;)', + re.DOTALL + ) + # 解析since标签 + self.since_pattern = re.compile(r'@since\s+(\d+)') + # 解析deprecated since标签 + self.deprecated_pattern = re.compile(r'@deprecated since\s+(\d+)') + + + def process_file(self, file_path: str) -> None: + try: + with open(file_path, 'r+', encoding='utf-8') as f: + content = f.read() + modified = self._process_content(content) + if modified != content: + f.seek(0) + f.write(modified) + f.truncate() + print(f"modify success: {file_path}") + else: + print(f"No modifications are needed: {file_path}") + except Exception as e: + print(f"Processing failed. {file_path}: {str(e)}") + + + def _process_content(self, content: str) -> str: + # 引入头文件version.h + content = self._add_version_include(content) + # 转换变量 + content = self._process_fields(content) + # 转换接口 + content = self._process_interfaces(content) + return content + + + def _add_version_include(self, content: str) -> str: + def replacer(match): + return f"{match.group(0)}\n#include \"info/application_target_sdk_version.h\"" + return self.guard_pattern.sub(replacer, content, count=1) + + + def _process_interfaces(self, content: str) -> str: + def process_match(match): + decl = match.group(0).rstrip(';') + comment_start = content.rfind('/**', 0, match.start()) + since, deprecated = ('0', '0') + if comment_start != -1: + comment = content[comment_start:match.start()] + since = self._extract_tag(comment, self.since_pattern) or '0' + deprecated = self._extract_tag(comment, self.deprecated_pattern) or '0' + macro = f'__OH_AVAILABILITY(__OH_VERSION({since},{deprecated}));' + return self._format_declaration(decl, macro) + return self.interface_pattern.sub(process_match, content) + + + def _process_fields(self, content: str) -> str: + def process_match(match: re.Match) -> str: + decl = match.group(0).rstrip(';') + # 匹配到的注释块 + comment = match.group(1) + since, deprecated = ('0', '0') + if comment: + since = self._extract_tag(comment, self.since_pattern) or '0' + deprecated = self._extract_tag(comment, self.deprecated_pattern) or '0' + macro = f'__OH_AVAILABILITY(__OH_VERSION({since},{deprecated}));' + return self._format_declaration(decl, macro) + return self.field_pattern.sub(process_match, content) + + + def _format_declaration(self, decl: str, macro: str) -> str: + # 获取最后一行缩进(用于跨行声明) + last_line = decl.split('\n')[-1] + indent = ' ' * (len(last_line) - len(last_line.lstrip())) + combined = f"{decl.rstrip()} {macro}" + if len(combined) <= self.MAX_LINE_LENGTH: + return combined + # 跨行对齐处理 + return f"{decl.rstrip()}\n{indent}{macro}" + + + def _extract_tag(self, text: str, pattern: re.Pattern) -> Optional[str]: + match = pattern.search(text) + return match.group(1) if match else None + + +def add_test_to_files(directory, recursive=True): + # 检查目录是否存在 + if not os.path.isdir(directory): + return False + # 遍历目录 + for root, dirs, files in os.walk(directory): + if not recursive and root != directory: + continue + processor = HeaderProcessor() + for filename in files: + filepath = os.path.join(root, filename) + relative_path = os.path.relpath(filepath, directory) + try: + processor.process_file(filepath) + except PermissionError: + print("error: not have permission to modify the file") + except UnicodeDecodeError: + print("error: File encoding is not supported") + except Exception as e: + print("An error occurred while processing the file") + return True + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--input', required=True) + args = parser.parse_args() + if not os.path.exists(args.input): + exit(1) + add_test_to_files(args.input) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/ohos/ndk/parse_ndk_headers.pydeps b/ohos/ndk/parse_ndk_headers.pydeps new file mode 100644 index 0000000000000000000000000000000000000000..32247a59ca62d8f592b1d58cf878c92a984a48e2 --- /dev/null +++ b/ohos/ndk/parse_ndk_headers.pydeps @@ -0,0 +1,3 @@ +# Generated by running: +# build/print_python_deps.py --root build/ohos/ndk --output build/ohos/ndk/parse_ndk_headers.pydeps build/ohos/ndk/parse_ndk_headers.py +parse_ndk_headers.py