blob: 7f54d7e02ee7fb6f13a050ef18792cb07476ea61 [file] [log] [blame]
Victor Chang2b8ba1e2020-08-19 18:14:25 +01001#!/usr/bin/env python
2#
3# Copyright (C) 2018 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17"""Generate ICU stable C API wrapper source.
18
19
20This script parses all the header files specified by the ICU module names. For
21each function in the allowlist, it generates the NDK headers, and shim functions
22to shim.cpp, which in turn calls the real implementation at runtime.
23The tool relies on libclang to parse header files.
24
25Reference to ICU4C stable C APIs:
26http://icu-project.org/apiref/icu4c/files.html
27"""
28from __future__ import absolute_import
29from __future__ import print_function
30
31import logging
32import os
33import re
34import shutil
35import subprocess
36
37from genutil import (
38 android_path,
39 generate_shim,
40 generate_symbol_txt,
41 get_allowlisted_apis,
42 AllowlistedDeclarationFilter,
43 DeclaredFunctionsParser,
44 StableDeclarationFilter,
45)
46
47# No suffix for ndk shim
48SYMBOL_SUFFIX = ''
49
50SECRET_PROCESSING_TOKEN = "@@@SECRET@@@"
51
52DOC_BLOCK_COMMENT = r"\/\*\*(?:\*(?!\/)|[^*])*\*\/[ ]*\n"
53TILL_CLOSE_PARENTHESIS = r"[^)^;]*\)"
54STABLE_MACRO = r"(?:U_STABLE|U_CAPI)"
55STABLE_FUNCTION_DECLARATION = r"^(" + DOC_BLOCK_COMMENT + STABLE_MACRO \
56 + TILL_CLOSE_PARENTHESIS + ");$"
57NONSTABLE_FUNCTION_DECLARATION = r"^(" + DOC_BLOCK_COMMENT + r"(U_INTERNAL|U_DEPRECATED|U_DRAFT)" \
58 + TILL_CLOSE_PARENTHESIS + ");$"
59
60REGEX_STABLE_FUNCTION_DECLARATION = re.compile(STABLE_FUNCTION_DECLARATION, re.MULTILINE)
61REGEX_NONSTABLE_FUNCTION_DECLARATION = re.compile(NONSTABLE_FUNCTION_DECLARATION, re.MULTILINE)
62
63def get_allowlisted_regex_string(decl_names):
64 """Return a regex in string to capture the C function declarations in the decl_names list"""
65 tag = "|".join(decl_names)
66 return r"(" + DOC_BLOCK_COMMENT + STABLE_MACRO + r"[^(]*(?=" + tag + r")(" + tag + ")" \
Victor Change06440b2020-11-20 20:28:54 +000067 + r"\("+ TILL_CLOSE_PARENTHESIS +");$"
Victor Chang2b8ba1e2020-08-19 18:14:25 +010068
69def get_replacement_adding_api_level_macro(api_level):
70 """Return the replacement string adding the NDK C macro
71 guarding C function declaration by the api_level"""
Victor Chang7d787d72020-09-23 14:18:50 +010072 return r"#if !defined(__ANDROID__) || __ANDROID_API__ >= {0}\n\n" \
Victor Chang2b8ba1e2020-08-19 18:14:25 +010073 r"\1 __INTRODUCED_IN({0});\n\n" \
Victor Chang7d787d72020-09-23 14:18:50 +010074 r"#endif // !defined(__ANDROID__) || __ANDROID_API__ >= {0}".format(api_level)
Victor Chang2b8ba1e2020-08-19 18:14:25 +010075
76def modify_func_declarations(src_path, dst_path, decl_names):
77 """Process the source file,
78 remove the C function declarations not in the decl_names,
79 add guard the functions listed in decl_names by the API level,
80 and output to the dst_path """
81 allowlist_regex_string = get_allowlisted_regex_string(decl_names)
82 allowlist_decl_regex = re.compile('^' + allowlist_regex_string, re.MULTILINE)
Victor Change06440b2020-11-20 20:28:54 +000083 secret_allowlist_decl_regex = re.compile('^' + SECRET_PROCESSING_TOKEN
84 + allowlist_regex_string, re.MULTILINE)
Victor Chang2b8ba1e2020-08-19 18:14:25 +010085 with open(src_path, "r") as file:
86 src = file.read()
87
88 # Remove all non-stable function declarations
89 modified = REGEX_NONSTABLE_FUNCTION_DECLARATION.sub('', src)
90
91 # Insert intermediate token to all functions in the allowlist
92 if decl_names:
93 modified = allowlist_decl_regex.sub(SECRET_PROCESSING_TOKEN + r"\1;", modified)
94 # Remove all other stable declarations not in the allowlist
95 modified = REGEX_STABLE_FUNCTION_DECLARATION.sub('', modified)
96 # Insert C macro and annotation to indicate the API level to each functions in the allowlist
Victor Change06440b2020-11-20 20:28:54 +000097 modified = secret_allowlist_decl_regex.sub(
Victor Chang2b8ba1e2020-08-19 18:14:25 +010098 get_replacement_adding_api_level_macro(31), modified)
99
100 with open(dst_path, "w") as out:
101 out.write(modified)
Victor Change06440b2020-11-20 20:28:54 +0000102def remove_ignored_includes(file_path, include_list):
103 """
104 Remove the included header, i.e. #include lines, listed in include_list from the file_path
105 header.
106 """
107
108 # Do nothing if the list is empty
109 if not include_list:
110 return
111
112 tag = "|".join(include_list)
113
114 with open(file_path, "r") as file:
115 content = file.read()
116
117 regex = re.compile(r"^#include \"unicode\/(" + tag + ")\"\n", re.MULTILINE)
118 content = regex.sub('', content)
119
120 with open(file_path, "w") as out:
121 out.write(content)
Victor Chang2b8ba1e2020-08-19 18:14:25 +0100122
123def copy_header_only_files():
124 """Copy required header only files"""
125 base_src_path = android_path('external/icu/icu4c/source/')
126 base_dest_path = android_path('external/icu/libicu/ndk_headers/unicode/')
127 with open(android_path('external/icu/tools/icu4c_srcgen/libicu_required_header_only_files.txt'),
128 'r') as in_file:
129 header_only_files = [
130 base_src_path + line.strip() for line in in_file.readlines() if not line.startswith('#')
131 ]
132
133 for src_path in header_only_files:
134 dest_path = base_dest_path + os.path.basename(src_path)
135 cmd = ['sed',
136 "s/U_SHOW_CPLUSPLUS_API/LIBICU_U_SHOW_CPLUSPLUS_API/g",
137 src_path
138 ]
139
140 with open(dest_path, "w") as destfile:
141 subprocess.check_call(cmd, stdout=destfile)
142
Victor Chang73229502020-09-17 13:39:19 +0100143def copy_cts_headers():
144 """Copy headers from common/ and i18n/ to cts_headers/ for compiling cintltst as CTS."""
145 dst_folder = android_path('external/icu/libicu/cts_headers')
146 if os.path.exists(dst_folder):
147 shutil.rmtree(dst_folder)
148 os.mkdir(dst_folder)
149 os.mkdir(os.path.join(dst_folder, 'unicode'))
150
151 shutil.copyfile(android_path('external/icu/android_icu4c/include/uconfig_local.h'),
152 android_path('external/icu/libicu/cts_headers/uconfig_local.h'))
153
154 header_subfolders = (
155 'common',
156 'common/unicode',
157 'i18n',
158 'i18n/unicode',
159 )
160 for subfolder in header_subfolders:
161 path = android_path('external/icu/icu4c/source', subfolder)
162 files = [os.path.join(path, f) for f in os.listdir(path) if f.endswith('.h')]
163
164 for src_path in files:
165 base_header_name = os.path.basename(src_path)
166 dst_path = dst_folder
167 if subfolder.endswith('unicode'):
168 dst_path = os.path.join(dst_path, 'unicode')
169 dst_path = os.path.join(dst_path, base_header_name)
170
171 shutil.copyfile(src_path, dst_path)
172
173def get_rename_macro_regex(decl_names):
174 """Return a regex in string to capture the C macro defining the name in the decl_names list"""
175 tag = "|".join(decl_names)
176 return re.compile(r"^(#define (?:" + tag + r") .*)$", re.MULTILINE)
177
178def generate_cts_headers(decl_names):
179 """Generate headers for compiling cintltst as CTS."""
180 copy_cts_headers()
181
182 # Disable all C macro renaming the NDK functions in order to test the functions in the CTS
183 urename_path = android_path('external/icu/libicu/cts_headers/unicode/urename.h')
184 with open(urename_path, "r") as file:
185 src = file.read()
186
187 regex = get_rename_macro_regex(decl_names)
188 modified = regex.sub(r"// \1", src)
189
190 with open(urename_path, "w") as out:
191 out.write(modified)
192
Victor Chang0db762b2020-10-27 17:24:05 +0000193IGNORED_INCLUDE_DEPENDENCY = {
Victor Changf1a8c982020-11-20 18:16:37 +0000194 "ubrk.h": ["parseerr.h", ],
Victor Change06440b2020-11-20 20:28:54 +0000195 "ulocdata.h": ["ures.h", "uset.h", ],
Victor Changf1a8c982020-11-20 18:16:37 +0000196 "unorm2.h": ["uset.h", ],
Victor Change06440b2020-11-20 20:28:54 +0000197 "ustring.h": ["uiter.h", ],
Victor Chang0db762b2020-10-27 17:24:05 +0000198}
Victor Chang73229502020-09-17 13:39:19 +0100199
Victor Chang2b8ba1e2020-08-19 18:14:25 +0100200def main():
201 """Parse the ICU4C headers and generate the shim libicu."""
202 logging.basicConfig(level=logging.DEBUG)
203
204 allowlisted_apis = get_allowlisted_apis('libicu_export.txt')
205 decl_filters = [StableDeclarationFilter()]
206 decl_filters.append(AllowlistedDeclarationFilter(allowlisted_apis))
207 parser = DeclaredFunctionsParser(decl_filters, [])
Victor Chang0db762b2020-10-27 17:24:05 +0000208 parser.set_ignored_include_dependency(IGNORED_INCLUDE_DEPENDENCY)
Victor Chang2b8ba1e2020-08-19 18:14:25 +0100209
210 parser.parse()
211
212 includes = parser.header_includes
213 functions = parser.declared_functions
214 header_to_function_names = parser.header_to_function_names
215
216 # The shim has the allowlisted functions only
217 functions = [f for f in functions if f.name in allowlisted_apis]
218
219 headers_folder = android_path('external/icu/libicu/ndk_headers/unicode')
220 if os.path.exists(headers_folder):
221 shutil.rmtree(headers_folder)
222 os.mkdir(headers_folder)
223
224 with open(android_path('external/icu/libicu/src/shim.cpp'),
225 'w') as out_file:
226 out_file.write(generate_shim(functions, includes, SYMBOL_SUFFIX, 'libicu_shim.cpp.j2')
227 .encode('utf8'))
228
229 with open(android_path('external/icu/libicu/libicu.map.txt'), 'w') as out_file:
230 out_file.write(generate_symbol_txt(functions, [], 'libicu.map.txt.j2')
231 .encode('utf8'))
232
233 # Process the C headers and put them into the ndk folder.
234 for src_path in parser.header_paths_to_copy:
235 basename = os.path.basename(src_path)
236 dst_path = os.path.join(headers_folder, basename)
237 modify_func_declarations(src_path, dst_path, header_to_function_names[basename])
Victor Change06440b2020-11-20 20:28:54 +0000238 # Remove #include lines from the header files.
239 if basename in IGNORED_INCLUDE_DEPENDENCY:
240 remove_ignored_includes(dst_path, IGNORED_INCLUDE_DEPENDENCY[basename])
Victor Chang2b8ba1e2020-08-19 18:14:25 +0100241
242 copy_header_only_files()
243
Victor Chang73229502020-09-17 13:39:19 +0100244 generate_cts_headers(allowlisted_apis)
245
Victor Changa4d48f52020-09-23 17:25:17 +0100246 # Apply documentation patches by the following shell script
247 subprocess.check_call(
248 [android_path('external/icu/tools/icu4c_srcgen/doc_patches/apply_patches.sh')])
249
Victor Chang2b8ba1e2020-08-19 18:14:25 +0100250if __name__ == '__main__':
251 main()