From 917ce817249ebd25a78a824c5aa893a73e837d99 Mon Sep 17 00:00:00 2001 From: wenyu Date: Tue, 24 Dec 2024 10:13:34 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81IDL=20SA=20gni=E7=BC=96?= =?UTF-8?q?=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wenyu --- .../idl_tool/build_sa_files_info.py | 475 ++++++++++++++++++ config/components/idl_tool/idl.gni | 107 ++-- config/components/idl_tool/idl.py | 32 +- core/gn/ohos_exec_script_allowlist.gni | 1 + 4 files changed, 539 insertions(+), 76 deletions(-) create mode 100755 config/components/idl_tool/build_sa_files_info.py diff --git a/config/components/idl_tool/build_sa_files_info.py b/config/components/idl_tool/build_sa_files_info.py new file mode 100755 index 0000000000..23bcef6572 --- /dev/null +++ b/config/components/idl_tool/build_sa_files_info.py @@ -0,0 +1,475 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright (c) 2024 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 json +import os +import sys +from enum import Enum +from os.path import realpath, basename, relpath, join, dirname, exists + + +class TokenType(Enum): + UNKNOWN = 0 + COMMENT = 1 + PACKAGE = 2 + IMPORT = 3 + INTERFACE = 4 + CALLBACK = 5 + ID = 6 + END_OF_FILE = 7 + + +class Token: + def __init__(self, file_name, token_type, value): + self.token_type = token_type + self.value = value + self.row = 1 + self.col = 1 + self.file_name = file_name + + def clean(self): + self.token_type = TokenType.UNKNOWN + self.value = "" + self.row = 1 + self.col = 1 + + def info(self): + return "{}:{}:{}".format(self.file_name, self.row, self.col) + + +class Char: + def __init__(self, is_eof, char): + self.is_eof = is_eof + self.char = char + + +class Lexer: + _key_words = { + "package": TokenType.PACKAGE, + "import": TokenType.IMPORT, + "interface": TokenType.INTERFACE, + "callback": TokenType.CALLBACK, + } + + def __init__(self, idl_file_path): + self.have_peek = False + with open(idl_file_path, 'r') as idl_file: + file_info = idl_file.read() + self.data = file_info + self.data_len = len(self.data) + self.read_index = 0 + self.cur_token = Token(basename(idl_file_path), TokenType.UNKNOWN, "") + self.cur_row = 1 + self.cur_col = 1 + + def peek_char(self, peek_count=0): + index = self.read_index + peek_count + if index >= self.data_len: + return Char(True, '0') + return Char(False, self.data[index]) + + def next_char(self): + return self.peek_char(1) + + def get_char(self): + if self.read_index >= self.data_len: + return Char(True, '0') + read_index = self.read_index + self.read_index += 1 + if self.data[read_index] == '\n': + self.cur_row += 1 + self.cur_col = 1 + else: + self.cur_col += 1 + return Char(False, self.data[read_index]) + + def peek_token(self): + if not self.have_peek: + self.read_token() + self.have_peek = True + return self.cur_token + + def get_token(self): + if not self.have_peek: + self.read_token() + self.have_peek = False + return self.cur_token + + def read_token(self): + self.cur_token.clean() + while not self.peek_char().is_eof: + c = self.peek_char() + if c.char.isspace(): + self.get_char() + continue + self.cur_token.row = self.cur_row + self.cur_token.col = self.cur_col + if c.char.isalpha() or c.char == '_' or (c.char == '.' and self.next_char().char == '.'): + self.read_id() + return + if c.char == '/': + self.read_comment() + return + self.cur_token.value = c.char + self.cur_token.token_type = TokenType.UNKNOWN + self.get_char() + return + self.cur_token.token_type = TokenType.END_OF_FILE + + def read_id(self): + token_value = [self.get_char().char] + while not self.peek_char().is_eof: + c = self.peek_char() + if c.char.isalpha() or c.char.isdigit() or c.char == '_' or c.char == '.' or c.char == '/': + token_value.append(c.char) + self.get_char() + continue + break + + key = "".join(token_value) + if key in self._key_words.keys(): + self.cur_token.token_type = self._key_words[key] + else: + self.cur_token.token_type = TokenType.ID + self.cur_token.value = key + + def read_comment(self): + token_value = [self.get_char().char] + c = self.peek_char() + if not c.is_eof: + if c.char == '/': + self.read_line_comment(token_value) + return + elif c.char == '*': + self.read_block_comment(token_value) + return + self.cur_token.token_type = TokenType.UNKNOWN + self.cur_token.value = "".join(token_value) + + def read_line_comment(self, token_value): + token_value.append(self.get_char().char) + while not self.peek_char().is_eof: + c = self.get_char() + if c.char == '\n': + break + token_value.append(c.char) + self.cur_token.token_type = TokenType.COMMENT + self.cur_token.value = "".join(token_value) + + def read_block_comment(self, token_value): + token_value.append(self.get_char().char) + while not self.peek_char().is_eof: + c = self.get_char() + token_value.append(c.char) + if c.char == '*' and self.peek_char().char == '/': + token_value.append(self.get_char().char) + break + + value = "".join(token_value) + if value.endswith("*/"): + self.cur_token.token_type = TokenType.COMMENT + else: + self.cur_token.token_type = TokenType.UNKNOWN + self.cur_token.value = value + + +# module info of all idl +class BuildInfo: + include_dirs = set() + out_dir = "" + sources = [] + proxy_sources = [] + stub_sources = [] + + @staticmethod + def json_info(): + include_dirs_ret = sorted(list(BuildInfo.include_dirs)) + BuildInfo.sources.sort() + BuildInfo.proxy_sources.sort() + BuildInfo.stub_sources.sort() + + result = { + "include_dirs": include_dirs_ret, + "out_dir": BuildInfo.out_dir, + "sources": BuildInfo.sources, + "proxy_sources": BuildInfo.proxy_sources, + "stub_sources": BuildInfo.stub_sources, + } + return json.dumps(result, indent=4, separators=(',', ':')) + + +class Option: + language = "cpp" + root_path = "" # absolute root path of this project + gen_dir = "" # absolute path of the directory where the file is generated + main_file_path = "" # absolute path of the interface file + main_file_dir = "" # absolute path of interface file directory + idl_sources = [] + + @staticmethod + def load(opt_args): + Option.language = opt_args.language + Option.root_path = opt_args.root_path + + if opt_args.out == "": + raise Exception("the gen_dir '{}' is empty, please check input".format(opt_args.out)) + else: + Option.gen_dir = realpath(opt_args.out) + + if len(opt_args.file) == 0: + raise Exception("the idl sources is empty, please check input") + else: + Option.idl_sources = opt_args.file + Option.main_file_path = realpath(opt_args.file[0]) + Option.main_file_dir = realpath(dirname(opt_args.file[0])) + + +class IdlType(Enum): + INTERFACE = 1 + CALLBACK = 2 + TYPES = 3 + + +# file detail of idl file +class IdlDetail: + def __init__(self, path): + self.idl_type = IdlType.TYPES + self.imports = [] # imported IDL file + self.file_path = realpath(path) # absolute file path + self.file_name = basename(path) # file name + self.file_dir = realpath(dirname(path)) # absolute path of the directory where the file is located + self.name = self.file_name.split('.')[0] # file name without suffix + + def full_name(self): + return self.file_path + + +class IdlParser: + @staticmethod + def get_sources(all_idl_details: dict[str, IdlDetail], generator: "CodeGen"): + BuildInfo.include_dirs.add(CodeGen.convert_to_out_dir(Option.gen_dir)) + for idl_detail in all_idl_details.values(): + sources, proxy_sources, sub_sources = generator.gen_code(idl_detail) + BuildInfo.sources.extend(sources) + BuildInfo.proxy_sources.extend(proxy_sources) + BuildInfo.stub_sources.extend(sub_sources) + + @staticmethod + def parse_callback(lex, idl_detail: IdlDetail): + lex.get_token() + idl_detail.idl_type = IdlType.CALLBACK + + @staticmethod + def parse_interface(lex, idl_detail: IdlDetail): + lex.get_token() + if lex.peek_token().token_type != TokenType.ID: + token = lex.peek_token() + raise Exception("{}: expected interface name before '{}'".format(token.info(), token.value)) + + lex.get_token() + if idl_detail.idl_type != IdlType.CALLBACK: + idl_detail.idl_type = IdlType.INTERFACE + + def parse_import(self, lex, all_idl_details: dict[str, IdlDetail], idl_detail: IdlDetail): + lex.get_token() + token = lex.peek_token() + if token.token_type != TokenType.ID: + raise Exception("{}: expected package name before '{}'".format(token.info(), token.value)) + + import_name = lex.get_token().value + idl_file = realpath(join(idl_detail.file_dir, import_name + ".idl")) + if not exists(idl_file): + raise Exception("{}: import file '{}' not found".format(token.info(), idl_file)) + + # Recursive parsing the imported idl files + self.parse_one(all_idl_details, idl_file) + + def parse(self): + all_idl_details: dict[str, IdlDetail] = {} + for idl_file in Option.idl_sources: + self.parse_one(all_idl_details, idl_file) + self.get_build_info(all_idl_details) + + def parse_one(self, all_idl_details: dict[str, IdlDetail], file_path): + idl_detail = IdlDetail(file_path) + if idl_detail.full_name() in all_idl_details: + return + + lex = Lexer(file_path) + while lex.peek_token().token_type != TokenType.END_OF_FILE: + cur_token_type = lex.peek_token().token_type + if cur_token_type == TokenType.IMPORT: + self.parse_import(lex, all_idl_details, idl_detail) + elif cur_token_type == TokenType.CALLBACK: + self.parse_callback(lex, idl_detail) + elif cur_token_type == TokenType.INTERFACE: + self.parse_interface(lex, idl_detail) + else: + lex.get_token() + + all_idl_details[idl_detail.full_name()] = idl_detail + + def get_build_info(self, all_idl_details: dict[str, IdlDetail]): + generator = CodeGenFactory.create_code_generate() + if generator is None: + return + + BuildInfo.out_dir = CodeGen.convert_to_out_dir(Option.gen_dir) + self.get_sources(all_idl_details, generator) + + +# generate code file info +class CodeGen: + @staticmethod + def str_to_snake_case(s): + under_line = '_' + result = [] + name_len = len(s) + for index in range(name_len): + cur_char = s[index] + if cur_char.isupper(): + if index > 1: + result.append(under_line) + result.append(cur_char.lower()) + else: + result.append(cur_char) + return "".join(result) + + @staticmethod + def get_proxy_name(name): + temp_name = CodeGen.str_to_snake_case(name) + return "{}_proxy".format(temp_name) + + @staticmethod + def get_stub_name(name): + temp_name = CodeGen.str_to_snake_case(name) + return "{}_stub".format(temp_name) + + @staticmethod + def get_file_names(idl_detail: IdlDetail): + interface_name = "" + proxy_name = "" + stub_name = "" + types_name = "" + + if idl_detail.idl_type == IdlType.TYPES: + types_name = CodeGen.str_to_snake_case(idl_detail.name) + return interface_name, proxy_name, stub_name, types_name + + base_name = idl_detail.name[1:] if idl_detail.name.startswith("I") else idl_detail.name + interface_name = CodeGen.str_to_snake_case(idl_detail.name) + proxy_name = CodeGen.get_proxy_name(base_name) + stub_name = CodeGen.get_stub_name(base_name) + return interface_name, proxy_name, stub_name, types_name + + @staticmethod + def header_file(file_dir, name): + return join(file_dir, "{}.h".format(name)) + + @staticmethod + def cpp_source_file(file_dir, name): + return join(file_dir, "{}.cpp".format(name)) + + @staticmethod + def convert_to_out_dir(path): + # convert the path to a relative path based on the root path of this project, + # this relative path starts with two separators + return os.sep + os.sep + relpath(path, Option.root_path) + + def gen_code(self, idl_detail: IdlDetail): + return [], [], [] + + +# generate ipc cpp code file information +class IpcCppCodeGen(CodeGen): + def gen_code(self, idl_detail: IdlDetail): + # relative path of the current file to the interface file + relative_path = relpath(idl_detail.file_dir, Option.main_file_dir) + out_dir = self.convert_to_out_dir(realpath(join(Option.gen_dir, relative_path))) + sources = [] + proxy_sources = [] + stub_sources = [] + interface_name, proxy_name, stub_name, types_name = self.get_file_names(idl_detail) + iface_header_file = self.header_file(out_dir, interface_name) + proxy_header_file = self.header_file(out_dir, proxy_name) + proxy_source_file = self.cpp_source_file(out_dir, proxy_name) + stub_header_file = self.header_file(out_dir, stub_name) + stub_source_file = self.cpp_source_file(out_dir, stub_name) + types_header_file = self.header_file(out_dir, types_name) + types_source_file = self.cpp_source_file(out_dir, types_name) + + if idl_detail.idl_type == IdlType.INTERFACE: + sources.extend([ + iface_header_file, proxy_header_file, proxy_source_file, + stub_header_file, stub_source_file + ]) + proxy_sources.append(proxy_source_file) + stub_sources.append(stub_source_file) + elif idl_detail.idl_type == IdlType.CALLBACK: + sources.extend([ + iface_header_file, proxy_header_file, proxy_source_file, + stub_header_file, stub_source_file + ]) + proxy_sources.append(stub_source_file) + stub_sources.append(proxy_source_file) + elif idl_detail.idl_type == IdlType.TYPES: + sources.extend([types_header_file, types_source_file]) + proxy_sources.append(types_source_file) + stub_sources.append(types_source_file) + + return sources, proxy_sources, stub_sources + + +class CodeGenFactory: + action_config = { + "c": None, + "cpp": IpcCppCodeGen() + } + + @staticmethod + def create_code_generate() -> CodeGen: + generator = CodeGenFactory.action_config.get(Option.language) + if generator is None: + raise Exception("The '{}' language is not support".format(Option.language)) + return generator + + +def check_python_version(): + if sys.version_info < (3, 0): + raise Exception("Please run with python version >= 3.0") + + +if __name__ == "__main__": + check_python_version() + + # load options + option_parser = argparse.ArgumentParser(description="Tools for generating compilation information of idl files") + option_parser.add_argument("-l", "--language", choices=["c", "cpp"], default="cpp", + help="language of generating code") + option_parser.add_argument("-o", "--out", required=True, help="directory of generate file") + option_parser.add_argument("-f", "--file", required=True, action="append", help="the idl files") + option_parser.add_argument("-r", "--root-path", required=True, + help="absolute root path of this project") + Option.load(option_parser.parse_args()) + + # parse idl files + idl_parser = IdlParser() + idl_parser.parse() + + # output generating files as JSON + sys.stdout.write(BuildInfo.json_info()) + sys.stdout.flush() diff --git a/config/components/idl_tool/idl.gni b/config/components/idl_tool/idl.gni index 9c875af0a4..9b23943d8e 100644 --- a/config/components/idl_tool/idl.gni +++ b/config/components/idl_tool/idl.gni @@ -1,4 +1,4 @@ -# Copyright (c) 2023 Huawei Device Co., Ltd. +# Copyright (c) 2023-2024 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 @@ -52,20 +52,18 @@ if (ohos_indep_compiler_enable) { } template("idl_gen_interface") { + not_needed(invoker, [ "dst_file" ]) + # idl sources - idl_list = [] src_idl_fullpath = [] if (defined(invoker.sources)) { - idl_list += invoker.sources - not_needed(invoker, - [ - "src_idl", - "dst_file", - ]) + not_needed(invoker, [ "src_idl" ]) + + # sources support multiple idl files + src_idl_fullpath += invoker.sources } else { - assert(defined(invoker.src_idl), "src-idl is required!") - not_needed(invoker, [ "dst_file" ]) - idl_list += [ get_path_info(invoker.src_idl, "file") ] + # src_idl support single idl file + assert(defined(invoker.src_idl), "src_idl is required") src_idl_fullpath += [ invoker.src_idl ] } @@ -78,45 +76,28 @@ template("idl_gen_interface") { language = invoker.language } - # idl name transform - str_upper = "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z" - str_lower = "a b c d e f g h i j k l m n o p q r s t u v w x y z" - str_upper_list = string_split(str_upper, " ") - str_lower_list = string_split(str_lower, " ") - store = [] - dst_file_list = [] - print("idl config idl_list: ", idl_list) - foreach(idl_name, idl_list) { - i = 0 - if (defined(invoker.sources)) { - src_idl_fullpath += [ rebase_path(idl_name) ] - } - name = string_replace(idl_name, ".idl", "") - foreach(s, str_upper_list) { - name = string_replace(name, s, "_" + str_lower_list[i]) - i = i + 1 - } - - # first letter - name_split = [] - name_split = string_split(name, "_i_") - if (name_split[0] == "") { - name = string_replace(name, "_i_", "", 1) - } - name_split = [] - name_split = string_split(name, "_") - if (name_split[0] == "") { - name = string_replace(name, "_", "", 1) - } - dst_file_list += [ name ] - store += [ - "${target_gen_dir}/" + name + "_proxy.cpp", - "${target_gen_dir}/" + name + "_stub.cpp", - ] + # get build info + build_info_args = [ + "-l", + language, + "-o", + rebase_path(target_gen_dir), + "-r", + rebase_path("//"), + ] + foreach(idl_file, src_idl_fullpath) { + build_info_args += [ "-f" ] + build_info_args += [ rebase_path(idl_file) ] } - arg_src_idl = string_join(",", src_idl_fullpath) - arg_dst_file = string_join(",", dst_file_list) - print("idl config store: ", store, dst_file_list) + build_info = + exec_script("//build/config/components/idl_tool/build_sa_files_info.py", + build_info_args, + "json") + assert(defined(build_info.include_dirs), "missing build_info.include_dirs") + assert(defined(build_info.out_dir), "missing build_info.out_dir") + assert(defined(build_info.sources), "missing build_info.sources") + assert(defined(build_info.proxy_sources), "missing build_info.proxy_sources") + assert(defined(build_info.stub_sources), "missing build_info.stub_sources") action("$target_name") { inputs = src_idl_fullpath @@ -124,13 +105,13 @@ template("idl_gen_interface") { script = "//build/config/components/idl_tool/idl.py" args = [ "--src-idl", - arg_src_idl, + string_join(",", src_idl_fullpath), "--dst-path", - rebase_path("${target_gen_dir}"), + rebase_path(target_gen_dir), "--idl-tool-path", - rebase_path("${idl_build_path}"), + rebase_path(idl_build_path), "--dst-file", - arg_dst_file, + string_join(",", rebase_path(build_info.sources)), "--language", language, ] @@ -152,27 +133,31 @@ template("idl_gen_interface") { invoker.hitrace, ] } - outputs = store + outputs = build_info.sources } - # multip cpp, build as so + # build so if ((language == "c" || language == "cpp") && defined(invoker.sources)) { idl_headers_config = target_name + "_idl_headers_config" config("$idl_headers_config") { - include_dirs = [ "${target_gen_dir}" ] + include_dirs = [ target_gen_dir ] if (defined(invoker.sub_include)) { include_dirs += invoker.sub_include } + include_dirs += build_info.include_dirs } + lib_client = "lib" + target_name + "_proxy" + lib_server = "lib" + target_name + "_stub" action_target_name = ":" + target_name + + # build client so ohos_shared_library(lib_client) { sources = [] - output_values = get_target_outputs(action_target_name) - sources += filter_include(output_values, [ "*_proxy.cpp" ]) if (defined(invoker.sources_cpp)) { sources += invoker.sources_cpp } + sources += build_info.proxy_sources if (defined(invoker.configs)) { configs = invoker.configs } @@ -220,14 +205,14 @@ template("idl_gen_interface") { } } } - lib_server = "lib" + target_name + "_stub" + + # build server so ohos_shared_library(lib_server) { sources = [] - output_values = get_target_outputs(action_target_name) - sources += filter_include(output_values, [ "*_stub.cpp" ]) if (defined(invoker.sources_cpp)) { sources += invoker.sources_cpp } + sources += build_info.stub_sources if (defined(invoker.configs)) { configs = invoker.configs } diff --git a/config/components/idl_tool/idl.py b/config/components/idl_tool/idl.py index 35ef2a2ae0..4d192f6feb 100755 --- a/config/components/idl_tool/idl.py +++ b/config/components/idl_tool/idl.py @@ -17,6 +17,7 @@ import os import subprocess import argparse +import sys def parse_args(): @@ -33,10 +34,12 @@ def parse_args(): return arguments -def run_command(cmd, execution_path, input_arguments): - print(" ".join(cmd) + " | execution_path: " + execution_path) +def run_command(cmd, execution_path): + print("cmd:", " ".join(cmd)) + print("execution_path:", execution_path) proc = subprocess.Popen(cmd, cwd=execution_path, stdout=subprocess.PIPE) proc.wait() + return proc.returncode def idl_gen_interface(input_arguments): @@ -53,13 +56,9 @@ def idl_gen_interface(input_arguments): print("idl_gen_interface run os.remove start") dst_file_list = input_arguments.dst_file.split(',') for dst_file in dst_file_list: - i_dst_file = 'i{0}'.format(dst_file) - for file_name in os.listdir(input_arguments.dst_path): - if ((file_name.startswith(dst_file) or file_name.startswith(i_dst_file)) and - (file_name.endswith('.cpp') or file_name.endswith('.h'))): - file_path = os.path.join(input_arguments.dst_path, file_name) - os.remove(file_path) - print("idl_gen_interface run os.remove", i_dst_file) + if os.path.exists(dst_file): + os.remove(dst_file) + print("idl_gen_interface run os.remove", dst_file) gen_language = "-gen-cpp" if input_arguments.language == "rust": @@ -67,17 +66,20 @@ def idl_gen_interface(input_arguments): elif input_arguments.language == "ts": gen_language = "-gen-ts" - src_idls = input_arguments.src_idl.split(",") - for src_idl in src_idls: - cmd = [os.path.join("./", name, "idl"), - gen_language, "-d", input_arguments.dst_path, "-c", src_idl] + src_idl_list = input_arguments.src_idl.split(",") + for src_idl in src_idl_list: + cmd = [os.path.join("./", name, "idl"), gen_language, "-d", input_arguments.dst_path, "-c", src_idl] if input_arguments.log_domainid: cmd += ['-log-domainid', input_arguments.log_domainid] if input_arguments.log_tag: cmd += ['-log-tag', input_arguments.log_tag] if input_arguments.hitrace: cmd += ['-t', input_arguments.hitrace] - run_command(cmd, path, input_arguments) + ret = run_command(cmd, path) + if ret != 0: + return ret + return 0 + if __name__ == '__main__': - idl_gen_interface(parse_args()) \ No newline at end of file + sys.exit(idl_gen_interface(parse_args())) diff --git a/core/gn/ohos_exec_script_allowlist.gni b/core/gn/ohos_exec_script_allowlist.gni index 6e9be5b3ae..3000533749 100644 --- a/core/gn/ohos_exec_script_allowlist.gni +++ b/core/gn/ohos_exec_script_allowlist.gni @@ -30,6 +30,7 @@ ohos_exec_script_config = { "//base/web/webview/ohos_nweb/BUILD.gn", "//build/config/BUILDCONFIG.gn", "//build/config/components/hdi/hdi.gni", + "//build/config/components/idl_tool/idl.gni", "//build/config/components/hc_gen/hc_gen.gni", "//build/config/linux/pkg_config.gni", "//build/config/mac/mac_sdk.gni", -- Gitee