Victor Chang | 2b8ba1e | 2020-08-19 18:14:25 +0100 | [diff] [blame] | 1 | #!/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 | |
| 20 | This script parses all the header files specified by the ICU module names. For |
| 21 | each function in the allowlist, it generates the NDK headers, and shim functions |
| 22 | to shim.cpp, which in turn calls the real implementation at runtime. |
| 23 | The tool relies on libclang to parse header files. |
| 24 | |
| 25 | Reference to ICU4C stable C APIs: |
| 26 | http://icu-project.org/apiref/icu4c/files.html |
| 27 | """ |
| 28 | from __future__ import absolute_import |
| 29 | from __future__ import print_function |
| 30 | |
| 31 | import logging |
| 32 | import os |
| 33 | import re |
| 34 | import shutil |
| 35 | import subprocess |
| 36 | |
| 37 | from 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 |
| 48 | SYMBOL_SUFFIX = '' |
| 49 | |
| 50 | SECRET_PROCESSING_TOKEN = "@@@SECRET@@@" |
| 51 | |
| 52 | DOC_BLOCK_COMMENT = r"\/\*\*(?:\*(?!\/)|[^*])*\*\/[ ]*\n" |
| 53 | TILL_CLOSE_PARENTHESIS = r"[^)^;]*\)" |
| 54 | STABLE_MACRO = r"(?:U_STABLE|U_CAPI)" |
| 55 | STABLE_FUNCTION_DECLARATION = r"^(" + DOC_BLOCK_COMMENT + STABLE_MACRO \ |
| 56 | + TILL_CLOSE_PARENTHESIS + ");$" |
| 57 | NONSTABLE_FUNCTION_DECLARATION = r"^(" + DOC_BLOCK_COMMENT + r"(U_INTERNAL|U_DEPRECATED|U_DRAFT)" \ |
| 58 | + TILL_CLOSE_PARENTHESIS + ");$" |
| 59 | |
| 60 | REGEX_STABLE_FUNCTION_DECLARATION = re.compile(STABLE_FUNCTION_DECLARATION, re.MULTILINE) |
| 61 | REGEX_NONSTABLE_FUNCTION_DECLARATION = re.compile(NONSTABLE_FUNCTION_DECLARATION, re.MULTILINE) |
| 62 | |
| 63 | def 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 Chang | e06440b | 2020-11-20 20:28:54 +0000 | [diff] [blame] | 67 | + r"\("+ TILL_CLOSE_PARENTHESIS +");$" |
Victor Chang | 2b8ba1e | 2020-08-19 18:14:25 +0100 | [diff] [blame] | 68 | |
| 69 | def 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 Chang | 7d787d7 | 2020-09-23 14:18:50 +0100 | [diff] [blame] | 72 | return r"#if !defined(__ANDROID__) || __ANDROID_API__ >= {0}\n\n" \ |
Victor Chang | 2b8ba1e | 2020-08-19 18:14:25 +0100 | [diff] [blame] | 73 | r"\1 __INTRODUCED_IN({0});\n\n" \ |
Victor Chang | 7d787d7 | 2020-09-23 14:18:50 +0100 | [diff] [blame] | 74 | r"#endif // !defined(__ANDROID__) || __ANDROID_API__ >= {0}".format(api_level) |
Victor Chang | 2b8ba1e | 2020-08-19 18:14:25 +0100 | [diff] [blame] | 75 | |
| 76 | def 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 Chang | e06440b | 2020-11-20 20:28:54 +0000 | [diff] [blame] | 83 | secret_allowlist_decl_regex = re.compile('^' + SECRET_PROCESSING_TOKEN |
| 84 | + allowlist_regex_string, re.MULTILINE) |
Victor Chang | 2b8ba1e | 2020-08-19 18:14:25 +0100 | [diff] [blame] | 85 | 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 Chang | e06440b | 2020-11-20 20:28:54 +0000 | [diff] [blame] | 97 | modified = secret_allowlist_decl_regex.sub( |
Victor Chang | 2b8ba1e | 2020-08-19 18:14:25 +0100 | [diff] [blame] | 98 | get_replacement_adding_api_level_macro(31), modified) |
| 99 | |
| 100 | with open(dst_path, "w") as out: |
| 101 | out.write(modified) |
Victor Chang | e06440b | 2020-11-20 20:28:54 +0000 | [diff] [blame] | 102 | def 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 Chang | 2b8ba1e | 2020-08-19 18:14:25 +0100 | [diff] [blame] | 122 | |
| 123 | def 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 Chang | 7322950 | 2020-09-17 13:39:19 +0100 | [diff] [blame] | 143 | def 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 | |
| 173 | def 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 | |
| 178 | def 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 Chang | 0db762b | 2020-10-27 17:24:05 +0000 | [diff] [blame] | 193 | IGNORED_INCLUDE_DEPENDENCY = { |
Victor Chang | f1a8c98 | 2020-11-20 18:16:37 +0000 | [diff] [blame^] | 194 | "ubrk.h": ["parseerr.h", ], |
Victor Chang | e06440b | 2020-11-20 20:28:54 +0000 | [diff] [blame] | 195 | "ulocdata.h": ["ures.h", "uset.h", ], |
Victor Chang | f1a8c98 | 2020-11-20 18:16:37 +0000 | [diff] [blame^] | 196 | "unorm2.h": ["uset.h", ], |
Victor Chang | e06440b | 2020-11-20 20:28:54 +0000 | [diff] [blame] | 197 | "ustring.h": ["uiter.h", ], |
Victor Chang | 0db762b | 2020-10-27 17:24:05 +0000 | [diff] [blame] | 198 | } |
Victor Chang | 7322950 | 2020-09-17 13:39:19 +0100 | [diff] [blame] | 199 | |
Victor Chang | 2b8ba1e | 2020-08-19 18:14:25 +0100 | [diff] [blame] | 200 | def 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 Chang | 0db762b | 2020-10-27 17:24:05 +0000 | [diff] [blame] | 208 | parser.set_ignored_include_dependency(IGNORED_INCLUDE_DEPENDENCY) |
Victor Chang | 2b8ba1e | 2020-08-19 18:14:25 +0100 | [diff] [blame] | 209 | |
| 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 Chang | e06440b | 2020-11-20 20:28:54 +0000 | [diff] [blame] | 238 | # 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 Chang | 2b8ba1e | 2020-08-19 18:14:25 +0100 | [diff] [blame] | 241 | |
| 242 | copy_header_only_files() |
| 243 | |
Victor Chang | 7322950 | 2020-09-17 13:39:19 +0100 | [diff] [blame] | 244 | generate_cts_headers(allowlisted_apis) |
| 245 | |
Victor Chang | a4d48f5 | 2020-09-23 17:25:17 +0100 | [diff] [blame] | 246 | # 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 Chang | 2b8ba1e | 2020-08-19 18:14:25 +0100 | [diff] [blame] | 250 | if __name__ == '__main__': |
| 251 | main() |