xref: /aosp_15_r20/external/icu/tools/icu4c_srcgen/genutil.py (revision 0e209d3975ff4a8c132096b14b0e9364a753506e)
1*0e209d39SAndroid Build Coastguard Worker# Copyright (C) 2018 The Android Open Source Project
2*0e209d39SAndroid Build Coastguard Worker#
3*0e209d39SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
4*0e209d39SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
5*0e209d39SAndroid Build Coastguard Worker# You may obtain a copy of the License at
6*0e209d39SAndroid Build Coastguard Worker#
7*0e209d39SAndroid Build Coastguard Worker#            http://www.apache.org/licenses/LICENSE-2.0
8*0e209d39SAndroid Build Coastguard Worker#
9*0e209d39SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
10*0e209d39SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
11*0e209d39SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*0e209d39SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
13*0e209d39SAndroid Build Coastguard Worker# limitations under the License.
14*0e209d39SAndroid Build Coastguard Worker#
15*0e209d39SAndroid Build Coastguard Worker
16*0e209d39SAndroid Build Coastguard Worker"""Utility for ICU4C code generation"""
17*0e209d39SAndroid Build Coastguard Worker
18*0e209d39SAndroid Build Coastguard Workerfrom __future__ import absolute_import
19*0e209d39SAndroid Build Coastguard Workerfrom __future__ import division
20*0e209d39SAndroid Build Coastguard Workerfrom __future__ import print_function
21*0e209d39SAndroid Build Coastguard Worker
22*0e209d39SAndroid Build Coastguard Workerimport logging
23*0e209d39SAndroid Build Coastguard Workerimport os
24*0e209d39SAndroid Build Coastguard Workerimport site
25*0e209d39SAndroid Build Coastguard Workerimport sys
26*0e209d39SAndroid Build Coastguard Workerimport textwrap
27*0e209d39SAndroid Build Coastguard Workerfrom collections import deque
28*0e209d39SAndroid Build Coastguard Worker
29*0e209d39SAndroid Build Coastguard Workerimport jinja2
30*0e209d39SAndroid Build Coastguard Worker
31*0e209d39SAndroid Build Coastguard WorkerTHIS_DIR = os.path.dirname(os.path.realpath(__file__))
32*0e209d39SAndroid Build Coastguard WorkerANDROID_TOP = os.path.realpath(os.path.join(THIS_DIR, '../../../..'))
33*0e209d39SAndroid Build Coastguard Worker
34*0e209d39SAndroid Build Coastguard WorkerJINJA_ENV = jinja2.Environment(loader=jinja2.FileSystemLoader(
35*0e209d39SAndroid Build Coastguard Worker    os.path.join(THIS_DIR, 'jinja_templates')))
36*0e209d39SAndroid Build Coastguard WorkerJINJA_ENV.trim_blocks = True
37*0e209d39SAndroid Build Coastguard WorkerJINJA_ENV.lstrip_blocks = True
38*0e209d39SAndroid Build Coastguard Worker
39*0e209d39SAndroid Build Coastguard Workerdef generate_shim(functions, includes, suffix, template_file):
40*0e209d39SAndroid Build Coastguard Worker    """Generates the library source file from the given functions."""
41*0e209d39SAndroid Build Coastguard Worker    data = {
42*0e209d39SAndroid Build Coastguard Worker        'functions': functions,
43*0e209d39SAndroid Build Coastguard Worker        'icu_headers': includes,
44*0e209d39SAndroid Build Coastguard Worker        'suffix': suffix,
45*0e209d39SAndroid Build Coastguard Worker    }
46*0e209d39SAndroid Build Coastguard Worker    return JINJA_ENV.get_template(template_file).render(data)
47*0e209d39SAndroid Build Coastguard Worker
48*0e209d39SAndroid Build Coastguard Workerdef generate_symbol_txt(shim_functions, extra_function_names, template_file):
49*0e209d39SAndroid Build Coastguard Worker    """Generates the symbol txt file from the given functions."""
50*0e209d39SAndroid Build Coastguard Worker    data = {
51*0e209d39SAndroid Build Coastguard Worker        # Each shim_function is given a suffix.
52*0e209d39SAndroid Build Coastguard Worker        'shim_functions' : shim_functions,
53*0e209d39SAndroid Build Coastguard Worker        # Each extra function name is included as given.
54*0e209d39SAndroid Build Coastguard Worker        'extra_function_names': extra_function_names,
55*0e209d39SAndroid Build Coastguard Worker    }
56*0e209d39SAndroid Build Coastguard Worker    return JINJA_ENV.get_template(template_file).render(data)
57*0e209d39SAndroid Build Coastguard Worker
58*0e209d39SAndroid Build Coastguard Workerdef get_jinja_env():
59*0e209d39SAndroid Build Coastguard Worker    """Return a jinja2 environment"""
60*0e209d39SAndroid Build Coastguard Worker    return JINJA_ENV
61*0e209d39SAndroid Build Coastguard Worker
62*0e209d39SAndroid Build Coastguard Workerdef get_allowlisted_apis(allowlist_file):
63*0e209d39SAndroid Build Coastguard Worker    """Return all allowlisted API in allowlist_file"""
64*0e209d39SAndroid Build Coastguard Worker    allowlisted_apis = set()
65*0e209d39SAndroid Build Coastguard Worker    with open(os.path.join(THIS_DIR, allowlist_file), 'r') as file:
66*0e209d39SAndroid Build Coastguard Worker        for line in file:
67*0e209d39SAndroid Build Coastguard Worker            line = line.strip()
68*0e209d39SAndroid Build Coastguard Worker            if line and not line.startswith("#"):
69*0e209d39SAndroid Build Coastguard Worker                allowlisted_apis.add(line)
70*0e209d39SAndroid Build Coastguard Worker    return allowlisted_apis
71*0e209d39SAndroid Build Coastguard Worker
72*0e209d39SAndroid Build Coastguard Workerdef android_path(*args):
73*0e209d39SAndroid Build Coastguard Worker    """Returns the absolute path to a directory within the Android tree."""
74*0e209d39SAndroid Build Coastguard Worker    return os.path.join(ANDROID_TOP, *args)
75*0e209d39SAndroid Build Coastguard Worker
76*0e209d39SAndroid Build Coastguard Worker
77*0e209d39SAndroid Build Coastguard Workerdef get_clang_path():
78*0e209d39SAndroid Build Coastguard Worker    """Find the latest clang version and return the full path"""
79*0e209d39SAndroid Build Coastguard Worker    base_path = android_path('prebuilts/clang/host/linux-x86/')
80*0e209d39SAndroid Build Coastguard Worker    files = [f for f in os.listdir(base_path) if f.startswith('clang-r')]
81*0e209d39SAndroid Build Coastguard Worker    # TODO: Don't use sort() because it assumes the same number of digits in the version name
82*0e209d39SAndroid Build Coastguard Worker    files.sort(reverse=True)
83*0e209d39SAndroid Build Coastguard Worker    selected = files[0]
84*0e209d39SAndroid Build Coastguard Worker    print("Using clang version %s" % selected)
85*0e209d39SAndroid Build Coastguard Worker    path = os.path.join(base_path, selected)
86*0e209d39SAndroid Build Coastguard Worker    return path
87*0e209d39SAndroid Build Coastguard Worker
88*0e209d39SAndroid Build Coastguard Worker
89*0e209d39SAndroid Build Coastguard Workerdef get_clang_lib_path(clang_path):
90*0e209d39SAndroid Build Coastguard Worker    """Return the libclang.so path"""
91*0e209d39SAndroid Build Coastguard Worker    base_path = os.path.join(clang_path, 'lib')
92*0e209d39SAndroid Build Coastguard Worker    files = [f for f in os.listdir(base_path) if f.startswith('libclang.so')]
93*0e209d39SAndroid Build Coastguard Worker    return os.path.join(base_path, files[0])
94*0e209d39SAndroid Build Coastguard Worker
95*0e209d39SAndroid Build Coastguard Worker
96*0e209d39SAndroid Build Coastguard Workerdef get_clang_header_dir(clang_path):
97*0e209d39SAndroid Build Coastguard Worker    """Return the path to clang header directory"""
98*0e209d39SAndroid Build Coastguard Worker    base_path = os.path.join(clang_path, 'lib/clang/')
99*0e209d39SAndroid Build Coastguard Worker    files = os.listdir(base_path)
100*0e209d39SAndroid Build Coastguard Worker    return os.path.join(base_path, files[0], 'include/')
101*0e209d39SAndroid Build Coastguard Worker
102*0e209d39SAndroid Build Coastguard Worker
103*0e209d39SAndroid Build Coastguard WorkerCLANG_PATH = get_clang_path()
104*0e209d39SAndroid Build Coastguard WorkerCLANG_LIB_PATH = get_clang_lib_path(CLANG_PATH)
105*0e209d39SAndroid Build Coastguard WorkerCLANG_HEADER_PATH = get_clang_header_dir(CLANG_PATH)
106*0e209d39SAndroid Build Coastguard Worker
107*0e209d39SAndroid Build Coastguard Workersite.addsitedir(os.path.join(CLANG_PATH, 'lib/python3/site-packages/'))
108*0e209d39SAndroid Build Coastguard Workerimport clang.cindex  # pylint: disable=import-error,wrong-import-position
109*0e209d39SAndroid Build Coastguard Worker
110*0e209d39SAndroid Build Coastguard Worker
111*0e209d39SAndroid Build Coastguard Workerclass Function:
112*0e209d39SAndroid Build Coastguard Worker    """A visible function found in an ICU header."""
113*0e209d39SAndroid Build Coastguard Worker
114*0e209d39SAndroid Build Coastguard Worker    def __init__(self, name, result_type, params, is_variadic, module):
115*0e209d39SAndroid Build Coastguard Worker        self.name = name
116*0e209d39SAndroid Build Coastguard Worker        self.result_type = result_type
117*0e209d39SAndroid Build Coastguard Worker        self.params = params
118*0e209d39SAndroid Build Coastguard Worker        self.is_variadic = is_variadic
119*0e209d39SAndroid Build Coastguard Worker        self.va_list_insert_position = -1
120*0e209d39SAndroid Build Coastguard Worker
121*0e209d39SAndroid Build Coastguard Worker        # callee will be used in dlsym and may be identical to others for
122*0e209d39SAndroid Build Coastguard Worker        # functions with variable argument lists.
123*0e209d39SAndroid Build Coastguard Worker        self.callee = self.name
124*0e209d39SAndroid Build Coastguard Worker        if self.is_variadic:
125*0e209d39SAndroid Build Coastguard Worker            self.last_param = self.params[-1][1]
126*0e209d39SAndroid Build Coastguard Worker        self.handle = 'handle_' + module
127*0e209d39SAndroid Build Coastguard Worker        self.return_void = self.result_type == 'void'
128*0e209d39SAndroid Build Coastguard Worker
129*0e209d39SAndroid Build Coastguard Worker    @property
130*0e209d39SAndroid Build Coastguard Worker    def param_str(self):
131*0e209d39SAndroid Build Coastguard Worker        """Returns a string usable as a parameter list in a function decl."""
132*0e209d39SAndroid Build Coastguard Worker        params = []
133*0e209d39SAndroid Build Coastguard Worker        for param_type, param_name in self.params:
134*0e209d39SAndroid Build Coastguard Worker            if '[' in param_type:
135*0e209d39SAndroid Build Coastguard Worker                # `int foo[42]` will be a param_type of `int [42]` and a
136*0e209d39SAndroid Build Coastguard Worker                # param_name of `foo`. We need to put these back in the right
137*0e209d39SAndroid Build Coastguard Worker                # order.
138*0e209d39SAndroid Build Coastguard Worker                param_name += param_type[param_type.find('['):]
139*0e209d39SAndroid Build Coastguard Worker                param_type = param_type[:param_type.find('[')]
140*0e209d39SAndroid Build Coastguard Worker            params.append('{} {}'.format(param_type, param_name))
141*0e209d39SAndroid Build Coastguard Worker        if self.is_variadic:
142*0e209d39SAndroid Build Coastguard Worker            params.append('...')
143*0e209d39SAndroid Build Coastguard Worker        return ', '.join(params)
144*0e209d39SAndroid Build Coastguard Worker
145*0e209d39SAndroid Build Coastguard Worker    @property
146*0e209d39SAndroid Build Coastguard Worker    def arg_str(self):
147*0e209d39SAndroid Build Coastguard Worker        """Returns a string usable as an argument list in a function call."""
148*0e209d39SAndroid Build Coastguard Worker        args = []
149*0e209d39SAndroid Build Coastguard Worker        for _, param_name in self.params:
150*0e209d39SAndroid Build Coastguard Worker            args.append(param_name)
151*0e209d39SAndroid Build Coastguard Worker        if self.is_variadic:
152*0e209d39SAndroid Build Coastguard Worker            if self.va_list_insert_position >= 0:
153*0e209d39SAndroid Build Coastguard Worker                args.insert(self.va_list_insert_position, 'args')
154*0e209d39SAndroid Build Coastguard Worker            else:
155*0e209d39SAndroid Build Coastguard Worker                raise ValueError(textwrap.dedent("""\
156*0e209d39SAndroid Build Coastguard Worker                {}({}) is variadic, but has no valid \
157*0e209d39SAndroid Build Coastguard Worker                inserted position""".format(
158*0e209d39SAndroid Build Coastguard Worker                    self.name,
159*0e209d39SAndroid Build Coastguard Worker                    self.param_str)))
160*0e209d39SAndroid Build Coastguard Worker        return ', '.join(args)
161*0e209d39SAndroid Build Coastguard Worker
162*0e209d39SAndroid Build Coastguard Worker    def set_variadic_callee(self, callee, inserted_position):
163*0e209d39SAndroid Build Coastguard Worker        """Set variadic callee with callee name and inserted position"""
164*0e209d39SAndroid Build Coastguard Worker        if self.is_variadic:
165*0e209d39SAndroid Build Coastguard Worker            self.callee = callee
166*0e209d39SAndroid Build Coastguard Worker            self.va_list_insert_position = inserted_position
167*0e209d39SAndroid Build Coastguard Worker
168*0e209d39SAndroid Build Coastguard Worker
169*0e209d39SAndroid Build Coastguard Workerdef logger():
170*0e209d39SAndroid Build Coastguard Worker    """Returns the module level logger."""
171*0e209d39SAndroid Build Coastguard Worker    return logging.getLogger(__name__)
172*0e209d39SAndroid Build Coastguard Worker
173*0e209d39SAndroid Build Coastguard Worker
174*0e209d39SAndroid Build Coastguard Workerclass DeclaredFunctionsParser:
175*0e209d39SAndroid Build Coastguard Worker    """Parser to get declared functions from ICU4C headers. """
176*0e209d39SAndroid Build Coastguard Worker
177*0e209d39SAndroid Build Coastguard Worker    def __init__(self, decl_filters, allowlisted_decl_filter):
178*0e209d39SAndroid Build Coastguard Worker        """
179*0e209d39SAndroid Build Coastguard Worker        Args:
180*0e209d39SAndroid Build Coastguard Worker            decl_filters: A list of filters for declared functions.
181*0e209d39SAndroid Build Coastguard Worker            allowlisted_decl_filter: A list of allowlisting filters for declared functions.
182*0e209d39SAndroid Build Coastguard Worker            If the function is allowlisted here, the function will not filtered by the filter added
183*0e209d39SAndroid Build Coastguard Worker            in decl_filters
184*0e209d39SAndroid Build Coastguard Worker        """
185*0e209d39SAndroid Build Coastguard Worker        self.decl_filters = decl_filters
186*0e209d39SAndroid Build Coastguard Worker        self.allowlisted_decl_filters = allowlisted_decl_filter
187*0e209d39SAndroid Build Coastguard Worker        self.va_functions_mapping = {}
188*0e209d39SAndroid Build Coastguard Worker        self.ignored_include_dependency = {}
189*0e209d39SAndroid Build Coastguard Worker
190*0e209d39SAndroid Build Coastguard Worker        # properties to store the parsing result
191*0e209d39SAndroid Build Coastguard Worker        self.all_headers = []
192*0e209d39SAndroid Build Coastguard Worker        self.all_header_paths_to_copy = set()
193*0e209d39SAndroid Build Coastguard Worker        self.all_declared_functions = []
194*0e209d39SAndroid Build Coastguard Worker        self.seen_functions = set()
195*0e209d39SAndroid Build Coastguard Worker        self.all_header_to_function_names = {}
196*0e209d39SAndroid Build Coastguard Worker
197*0e209d39SAndroid Build Coastguard Worker        # Configures libclang to load in our environment
198*0e209d39SAndroid Build Coastguard Worker        # Set up LD_LIBRARY_PATH to include libclang.so, libLLVM.so, etc.  Note
199*0e209d39SAndroid Build Coastguard Worker        # that setting LD_LIBRARY_PATH with os.putenv() sometimes doesn't help.
200*0e209d39SAndroid Build Coastguard Worker        clang.cindex.Config.set_library_file(CLANG_LIB_PATH)
201*0e209d39SAndroid Build Coastguard Worker
202*0e209d39SAndroid Build Coastguard Worker    def set_va_functions_mapping(self, mapping):
203*0e209d39SAndroid Build Coastguard Worker        """Set mapping from a variable argument function to an implementation.
204*0e209d39SAndroid Build Coastguard Worker
205*0e209d39SAndroid Build Coastguard Worker        Functions w/ variable argument lists (...) need special care to call
206*0e209d39SAndroid Build Coastguard Worker        their corresponding v- versions that accept a va_list argument. Note that
207*0e209d39SAndroid Build Coastguard Worker        although '...' will always appear as the last parameter, its v- version
208*0e209d39SAndroid Build Coastguard Worker        may put the va_list arg in a different place. Hence we provide an index
209*0e209d39SAndroid Build Coastguard Worker        to indicate the position.
210*0e209d39SAndroid Build Coastguard Worker        e.g. 'umsg_format': ('umsg_vformat', 3) means in the wrapper function of
211*0e209d39SAndroid Build Coastguard Worker        'umsg_format', it will call 'umsg_vformat' instead, with the va_list arg
212*0e209d39SAndroid Build Coastguard Worker        inserted as the 3rd argument."""
213*0e209d39SAndroid Build Coastguard Worker        self.va_functions_mapping = mapping
214*0e209d39SAndroid Build Coastguard Worker
215*0e209d39SAndroid Build Coastguard Worker    def set_ignored_include_dependency(self, mapping):
216*0e209d39SAndroid Build Coastguard Worker        """
217*0e209d39SAndroid Build Coastguard Worker        A sample mapping is { "ulocdata.h" : [ "uloc.h", "ures.h" ] }.
218*0e209d39SAndroid Build Coastguard Worker        The include dependencies will explicitly be ignored when producing header_paths_to_copy.
219*0e209d39SAndroid Build Coastguard Worker        """
220*0e209d39SAndroid Build Coastguard Worker        self.ignored_include_dependency = mapping
221*0e209d39SAndroid Build Coastguard Worker
222*0e209d39SAndroid Build Coastguard Worker    @property
223*0e209d39SAndroid Build Coastguard Worker    def header_includes(self):
224*0e209d39SAndroid Build Coastguard Worker        """Return all headers declaring the functions returned in get_all_declared_functions.
225*0e209d39SAndroid Build Coastguard Worker
226*0e209d39SAndroid Build Coastguard Worker        If all functions in the header are filtered, the header is not included in here."""
227*0e209d39SAndroid Build Coastguard Worker        return [DeclaredFunctionsParser.short_header_path(header) for header in self.all_headers]
228*0e209d39SAndroid Build Coastguard Worker
229*0e209d39SAndroid Build Coastguard Worker    @property
230*0e209d39SAndroid Build Coastguard Worker    def header_paths_to_copy(self):
231*0e209d39SAndroid Build Coastguard Worker        """Return all headers needed to be copied"""
232*0e209d39SAndroid Build Coastguard Worker        return self.all_header_paths_to_copy
233*0e209d39SAndroid Build Coastguard Worker
234*0e209d39SAndroid Build Coastguard Worker    @property
235*0e209d39SAndroid Build Coastguard Worker    def declared_functions(self):
236*0e209d39SAndroid Build Coastguard Worker        """Return all declared functions after filtering"""
237*0e209d39SAndroid Build Coastguard Worker        return self.all_declared_functions
238*0e209d39SAndroid Build Coastguard Worker
239*0e209d39SAndroid Build Coastguard Worker    @property
240*0e209d39SAndroid Build Coastguard Worker    def header_to_function_names(self):
241*0e209d39SAndroid Build Coastguard Worker        """Return the mapping from the header file name to a list of function names in the file"""
242*0e209d39SAndroid Build Coastguard Worker        return self.all_header_to_function_names
243*0e209d39SAndroid Build Coastguard Worker
244*0e209d39SAndroid Build Coastguard Worker    @staticmethod
245*0e209d39SAndroid Build Coastguard Worker    def get_cflags():
246*0e209d39SAndroid Build Coastguard Worker        """Returns the cflags that should be used for parsing."""
247*0e209d39SAndroid Build Coastguard Worker        clang_flags = [
248*0e209d39SAndroid Build Coastguard Worker            '-x',
249*0e209d39SAndroid Build Coastguard Worker            'c',
250*0e209d39SAndroid Build Coastguard Worker            '-std=c99',
251*0e209d39SAndroid Build Coastguard Worker            '-DU_DISABLE_RENAMING=1',
252*0e209d39SAndroid Build Coastguard Worker            '-DU_SHOW_CPLUSPLUS_API=0',
253*0e209d39SAndroid Build Coastguard Worker            '-DU_HIDE_DRAFT_API',
254*0e209d39SAndroid Build Coastguard Worker            '-DU_HIDE_DEPRECATED_API',
255*0e209d39SAndroid Build Coastguard Worker            '-DU_HIDE_INTERNAL_API',
256*0e209d39SAndroid Build Coastguard Worker            '-DANDROID_LINK_SHARED_ICU4C',
257*0e209d39SAndroid Build Coastguard Worker        ]
258*0e209d39SAndroid Build Coastguard Worker
259*0e209d39SAndroid Build Coastguard Worker        include_dirs = [
260*0e209d39SAndroid Build Coastguard Worker            CLANG_HEADER_PATH,
261*0e209d39SAndroid Build Coastguard Worker            android_path('bionic/libc/include'),
262*0e209d39SAndroid Build Coastguard Worker            android_path('external/icu/android_icu4c/include'),
263*0e209d39SAndroid Build Coastguard Worker            android_path('external/icu/icu4c/source/common'),
264*0e209d39SAndroid Build Coastguard Worker            android_path('external/icu/icu4c/source/i18n'),
265*0e209d39SAndroid Build Coastguard Worker        ]
266*0e209d39SAndroid Build Coastguard Worker
267*0e209d39SAndroid Build Coastguard Worker        for include_dir in include_dirs:
268*0e209d39SAndroid Build Coastguard Worker            clang_flags.append('-I' + include_dir)
269*0e209d39SAndroid Build Coastguard Worker        return clang_flags
270*0e209d39SAndroid Build Coastguard Worker
271*0e209d39SAndroid Build Coastguard Worker    @staticmethod
272*0e209d39SAndroid Build Coastguard Worker    def get_all_cpp_headers():
273*0e209d39SAndroid Build Coastguard Worker        """Return all C++ header names in icu4c/source/test/hdrtst/cxxfiles.txt"""
274*0e209d39SAndroid Build Coastguard Worker        cpp_headers = []
275*0e209d39SAndroid Build Coastguard Worker        with open(android_path('external/icu/tools/icu4c_srcgen/cxxfiles.txt'), 'r') as file:
276*0e209d39SAndroid Build Coastguard Worker            for line in file:
277*0e209d39SAndroid Build Coastguard Worker                line = line.strip()
278*0e209d39SAndroid Build Coastguard Worker                if not line.startswith("#"):
279*0e209d39SAndroid Build Coastguard Worker                    cpp_headers.append(line)
280*0e209d39SAndroid Build Coastguard Worker        return cpp_headers
281*0e209d39SAndroid Build Coastguard Worker
282*0e209d39SAndroid Build Coastguard Worker    def parse(self):
283*0e209d39SAndroid Build Coastguard Worker        """Parse the headers and collect the declared functions after filtering
284*0e209d39SAndroid Build Coastguard Worker        and the headers containing the functions."""
285*0e209d39SAndroid Build Coastguard Worker        index = clang.cindex.Index.create()
286*0e209d39SAndroid Build Coastguard Worker
287*0e209d39SAndroid Build Coastguard Worker        icu_modules = (
288*0e209d39SAndroid Build Coastguard Worker            'common',
289*0e209d39SAndroid Build Coastguard Worker            'i18n',
290*0e209d39SAndroid Build Coastguard Worker        )
291*0e209d39SAndroid Build Coastguard Worker        header_dependencies = {}
292*0e209d39SAndroid Build Coastguard Worker        for module in icu_modules:
293*0e209d39SAndroid Build Coastguard Worker            path = android_path(android_path('external/icu/icu4c/source', module, 'unicode'))
294*0e209d39SAndroid Build Coastguard Worker            files = [os.path.join(path, f)
295*0e209d39SAndroid Build Coastguard Worker                     for f in os.listdir(path) if f.endswith('.h')]
296*0e209d39SAndroid Build Coastguard Worker
297*0e209d39SAndroid Build Coastguard Worker            for file_path in files:
298*0e209d39SAndroid Build Coastguard Worker                base_header_name = os.path.basename(file_path)
299*0e209d39SAndroid Build Coastguard Worker                # Ignore C++ headers.
300*0e209d39SAndroid Build Coastguard Worker                if base_header_name in DeclaredFunctionsParser.get_all_cpp_headers():
301*0e209d39SAndroid Build Coastguard Worker                    continue
302*0e209d39SAndroid Build Coastguard Worker
303*0e209d39SAndroid Build Coastguard Worker                tunit = index.parse(file_path, DeclaredFunctionsParser.get_cflags())
304*0e209d39SAndroid Build Coastguard Worker                DeclaredFunctionsParser.handle_diagnostics(tunit)
305*0e209d39SAndroid Build Coastguard Worker                header_dependencies[file_path] = [file_inclusion.include.name for file_inclusion
306*0e209d39SAndroid Build Coastguard Worker                                                  in tunit.get_includes()]
307*0e209d39SAndroid Build Coastguard Worker                visible_functions = self.get_visible_functions(
308*0e209d39SAndroid Build Coastguard Worker                    tunit.cursor, module, file_path)
309*0e209d39SAndroid Build Coastguard Worker                self.all_header_to_function_names[base_header_name] = \
310*0e209d39SAndroid Build Coastguard Worker                    [f.name for f in visible_functions]
311*0e209d39SAndroid Build Coastguard Worker                for function in visible_functions:
312*0e209d39SAndroid Build Coastguard Worker                    self.seen_functions.add(function.name)
313*0e209d39SAndroid Build Coastguard Worker                    self.all_declared_functions.append(function)
314*0e209d39SAndroid Build Coastguard Worker                if visible_functions:
315*0e209d39SAndroid Build Coastguard Worker                    self.all_headers.append(file_path)
316*0e209d39SAndroid Build Coastguard Worker
317*0e209d39SAndroid Build Coastguard Worker        # Sort to produce an deterministic output
318*0e209d39SAndroid Build Coastguard Worker        self.all_declared_functions = sorted(self.all_declared_functions, key=lambda f: f.name)
319*0e209d39SAndroid Build Coastguard Worker        self.all_headers = sorted(self.all_headers)
320*0e209d39SAndroid Build Coastguard Worker
321*0e209d39SAndroid Build Coastguard Worker        # Build the headers required for using your restricted API set, and put the set into
322*0e209d39SAndroid Build Coastguard Worker        # all_header_files_to_copy.
323*0e209d39SAndroid Build Coastguard Worker        # header_dependencies is a map from icu4c header file path to a list of included headers.
324*0e209d39SAndroid Build Coastguard Worker        # The key must be a ICU4C header, but the value could contain non-ICU4C headers, e.g.
325*0e209d39SAndroid Build Coastguard Worker        # {
326*0e209d39SAndroid Build Coastguard Worker        #   ".../icu4c/source/common/unicode/utype.h": [
327*0e209d39SAndroid Build Coastguard Worker        #      ".../icu4c/source/common/unicode/uversion.h",
328*0e209d39SAndroid Build Coastguard Worker        #      ".../bionic/libc/include/ctype.h",
329*0e209d39SAndroid Build Coastguard Worker        #    ],
330*0e209d39SAndroid Build Coastguard Worker        #    ...
331*0e209d39SAndroid Build Coastguard Worker        # }
332*0e209d39SAndroid Build Coastguard Worker        file_queue = deque()
333*0e209d39SAndroid Build Coastguard Worker        file_processed = set()
334*0e209d39SAndroid Build Coastguard Worker        for header in self.all_headers:
335*0e209d39SAndroid Build Coastguard Worker            file_queue.appendleft(header)
336*0e209d39SAndroid Build Coastguard Worker            self.all_header_paths_to_copy.add(header)
337*0e209d39SAndroid Build Coastguard Worker        while file_queue:
338*0e209d39SAndroid Build Coastguard Worker            file = file_queue.pop()
339*0e209d39SAndroid Build Coastguard Worker            file_basename = os.path.basename(file)
340*0e209d39SAndroid Build Coastguard Worker            if file in file_processed:
341*0e209d39SAndroid Build Coastguard Worker                continue
342*0e209d39SAndroid Build Coastguard Worker            file_processed.add(file)
343*0e209d39SAndroid Build Coastguard Worker            for header in header_dependencies[file]:
344*0e209d39SAndroid Build Coastguard Worker                header_basename = os.path.basename(header)
345*0e209d39SAndroid Build Coastguard Worker                # Skip this header if this dependency is explicitly ignored
346*0e209d39SAndroid Build Coastguard Worker                if file_basename in self.ignored_include_dependency and \
347*0e209d39SAndroid Build Coastguard Worker                    header_basename in self.ignored_include_dependency[file_basename]:
348*0e209d39SAndroid Build Coastguard Worker                    continue
349*0e209d39SAndroid Build Coastguard Worker                if header in header_dependencies:  # Do not care non-icu4c headers
350*0e209d39SAndroid Build Coastguard Worker                    self.all_header_paths_to_copy.add(header)
351*0e209d39SAndroid Build Coastguard Worker                    file_queue.appendleft(header)
352*0e209d39SAndroid Build Coastguard Worker
353*0e209d39SAndroid Build Coastguard Worker    @staticmethod
354*0e209d39SAndroid Build Coastguard Worker    def handle_diagnostics(tunit):
355*0e209d39SAndroid Build Coastguard Worker        """Prints compiler diagnostics to stdout. Exits if errors occurred."""
356*0e209d39SAndroid Build Coastguard Worker        errors = 0
357*0e209d39SAndroid Build Coastguard Worker        for diag in tunit.diagnostics:
358*0e209d39SAndroid Build Coastguard Worker            if diag.severity == clang.cindex.Diagnostic.Fatal:
359*0e209d39SAndroid Build Coastguard Worker                level = logging.CRITICAL
360*0e209d39SAndroid Build Coastguard Worker                errors += 1
361*0e209d39SAndroid Build Coastguard Worker            elif diag.severity == clang.cindex.Diagnostic.Error:
362*0e209d39SAndroid Build Coastguard Worker                level = logging.ERROR
363*0e209d39SAndroid Build Coastguard Worker                errors += 1
364*0e209d39SAndroid Build Coastguard Worker            elif diag.severity == clang.cindex.Diagnostic.Warning:
365*0e209d39SAndroid Build Coastguard Worker                level = logging.WARNING
366*0e209d39SAndroid Build Coastguard Worker            elif diag.severity == clang.cindex.Diagnostic.Note:
367*0e209d39SAndroid Build Coastguard Worker                level = logging.INFO
368*0e209d39SAndroid Build Coastguard Worker            logger().log(
369*0e209d39SAndroid Build Coastguard Worker                level, '%s:%s:%s %s', diag.location.file, diag.location.line,
370*0e209d39SAndroid Build Coastguard Worker                diag.location.column, diag.spelling)
371*0e209d39SAndroid Build Coastguard Worker        if errors:
372*0e209d39SAndroid Build Coastguard Worker            sys.exit('Errors occurred during parsing. Exiting.')
373*0e209d39SAndroid Build Coastguard Worker
374*0e209d39SAndroid Build Coastguard Worker    def get_visible_functions(self, cursor, module, file_name):
375*0e209d39SAndroid Build Coastguard Worker        """Returns a list of all visible functions in a header file."""
376*0e209d39SAndroid Build Coastguard Worker        functions = []
377*0e209d39SAndroid Build Coastguard Worker        for child in cursor.get_children():
378*0e209d39SAndroid Build Coastguard Worker            if self.should_process_decl(child, file_name):
379*0e209d39SAndroid Build Coastguard Worker                functions.append(self.from_cursor(child, module))
380*0e209d39SAndroid Build Coastguard Worker        return functions
381*0e209d39SAndroid Build Coastguard Worker
382*0e209d39SAndroid Build Coastguard Worker    def should_process_decl(self, decl, file_name):
383*0e209d39SAndroid Build Coastguard Worker        """Returns True if this function needs to be processed."""
384*0e209d39SAndroid Build Coastguard Worker        if decl.kind != clang.cindex.CursorKind.FUNCTION_DECL:
385*0e209d39SAndroid Build Coastguard Worker            return False
386*0e209d39SAndroid Build Coastguard Worker        if decl.location.file.name != file_name:
387*0e209d39SAndroid Build Coastguard Worker            return False
388*0e209d39SAndroid Build Coastguard Worker        if decl.spelling in self.seen_functions:
389*0e209d39SAndroid Build Coastguard Worker            return False
390*0e209d39SAndroid Build Coastguard Worker        if not DeclaredFunctionsParser.is_function_visible(decl):
391*0e209d39SAndroid Build Coastguard Worker            return False
392*0e209d39SAndroid Build Coastguard Worker        for allowlisted_decl_filter in self.allowlisted_decl_filters:
393*0e209d39SAndroid Build Coastguard Worker            if allowlisted_decl_filter(decl):
394*0e209d39SAndroid Build Coastguard Worker                return True
395*0e209d39SAndroid Build Coastguard Worker        for decl_filter in self.decl_filters:
396*0e209d39SAndroid Build Coastguard Worker            if not decl_filter(decl):
397*0e209d39SAndroid Build Coastguard Worker                return False
398*0e209d39SAndroid Build Coastguard Worker        return True
399*0e209d39SAndroid Build Coastguard Worker
400*0e209d39SAndroid Build Coastguard Worker    @staticmethod
401*0e209d39SAndroid Build Coastguard Worker    def is_function_visible(decl):
402*0e209d39SAndroid Build Coastguard Worker        """Returns True if the function has default visibility."""
403*0e209d39SAndroid Build Coastguard Worker        visible = False
404*0e209d39SAndroid Build Coastguard Worker        vis_attrs = DeclaredFunctionsParser.get_children_by_kind(
405*0e209d39SAndroid Build Coastguard Worker            decl, clang.cindex.CursorKind.VISIBILITY_ATTR)
406*0e209d39SAndroid Build Coastguard Worker        for child in vis_attrs:
407*0e209d39SAndroid Build Coastguard Worker            visible = child.spelling == 'default'
408*0e209d39SAndroid Build Coastguard Worker        return visible
409*0e209d39SAndroid Build Coastguard Worker
410*0e209d39SAndroid Build Coastguard Worker    @staticmethod
411*0e209d39SAndroid Build Coastguard Worker    def get_children_by_kind(cursor, kind):
412*0e209d39SAndroid Build Coastguard Worker        """Returns a generator of cursor's children of a specific kind."""
413*0e209d39SAndroid Build Coastguard Worker        for child in cursor.get_children():
414*0e209d39SAndroid Build Coastguard Worker            if child.kind == kind:
415*0e209d39SAndroid Build Coastguard Worker                yield child
416*0e209d39SAndroid Build Coastguard Worker
417*0e209d39SAndroid Build Coastguard Worker    @staticmethod
418*0e209d39SAndroid Build Coastguard Worker    def short_header_path(name):
419*0e209d39SAndroid Build Coastguard Worker        """Trim the given file name to 'unicode/xyz.h'."""
420*0e209d39SAndroid Build Coastguard Worker        return name[name.rfind('unicode/'):]
421*0e209d39SAndroid Build Coastguard Worker
422*0e209d39SAndroid Build Coastguard Worker    def from_cursor(self, cursor, module):
423*0e209d39SAndroid Build Coastguard Worker        """Creates a Function object from the decl at the cursor."""
424*0e209d39SAndroid Build Coastguard Worker        if cursor.type.kind != clang.cindex.TypeKind.FUNCTIONPROTO:
425*0e209d39SAndroid Build Coastguard Worker            raise ValueError(textwrap.dedent("""\
426*0e209d39SAndroid Build Coastguard Worker                {}'s type kind is {}, expected TypeKind.FUNCTIONPROTO.
427*0e209d39SAndroid Build Coastguard Worker                {} Line {} Column {}""".format(
428*0e209d39SAndroid Build Coastguard Worker                    cursor.spelling,
429*0e209d39SAndroid Build Coastguard Worker                    cursor.type.kind,
430*0e209d39SAndroid Build Coastguard Worker                    cursor.location.file,
431*0e209d39SAndroid Build Coastguard Worker                    cursor.location.line,
432*0e209d39SAndroid Build Coastguard Worker                    cursor.location.column)))
433*0e209d39SAndroid Build Coastguard Worker
434*0e209d39SAndroid Build Coastguard Worker        name = cursor.spelling
435*0e209d39SAndroid Build Coastguard Worker        result_type = cursor.result_type.spelling
436*0e209d39SAndroid Build Coastguard Worker        is_variadic = cursor.type.is_function_variadic()
437*0e209d39SAndroid Build Coastguard Worker        params = []
438*0e209d39SAndroid Build Coastguard Worker        for arg in cursor.get_arguments():
439*0e209d39SAndroid Build Coastguard Worker            params.append((arg.type.spelling, arg.spelling))
440*0e209d39SAndroid Build Coastguard Worker        function = Function(name, result_type, params, is_variadic, module)
441*0e209d39SAndroid Build Coastguard Worker        # For variadic function, set the callee and va_list position
442*0e209d39SAndroid Build Coastguard Worker        if function.is_variadic and function.name in self.va_functions_mapping:
443*0e209d39SAndroid Build Coastguard Worker            va_func = self.va_functions_mapping[function.name]
444*0e209d39SAndroid Build Coastguard Worker            function.set_variadic_callee(va_func[0], va_func[1])
445*0e209d39SAndroid Build Coastguard Worker        return function
446*0e209d39SAndroid Build Coastguard Worker
447*0e209d39SAndroid Build Coastguard Worker
448*0e209d39SAndroid Build Coastguard Workerclass StableDeclarationFilter:
449*0e209d39SAndroid Build Coastguard Worker    """Return true if it's @stable API"""
450*0e209d39SAndroid Build Coastguard Worker    def __call__(self, decl):
451*0e209d39SAndroid Build Coastguard Worker        """Returns True if the given decl has a doxygen stable tag."""
452*0e209d39SAndroid Build Coastguard Worker        if not decl.raw_comment:
453*0e209d39SAndroid Build Coastguard Worker            return False
454*0e209d39SAndroid Build Coastguard Worker        if '@stable' in decl.raw_comment:
455*0e209d39SAndroid Build Coastguard Worker            return True
456*0e209d39SAndroid Build Coastguard Worker        return False
457*0e209d39SAndroid Build Coastguard Worker
458*0e209d39SAndroid Build Coastguard Worker
459*0e209d39SAndroid Build Coastguard Workerclass AllowlistedDeclarationFilter:
460*0e209d39SAndroid Build Coastguard Worker    """A filter for allowlisting function declarations."""
461*0e209d39SAndroid Build Coastguard Worker    def __init__(self, allowlisted_function_names):
462*0e209d39SAndroid Build Coastguard Worker        self.allowlisted_function_names = allowlisted_function_names
463*0e209d39SAndroid Build Coastguard Worker
464*0e209d39SAndroid Build Coastguard Worker    def __call__(self, decl):
465*0e209d39SAndroid Build Coastguard Worker        """Returns True if the given decl is allowlisted"""
466*0e209d39SAndroid Build Coastguard Worker        return decl.spelling in self.allowlisted_function_names
467*0e209d39SAndroid Build Coastguard Worker
468*0e209d39SAndroid Build Coastguard Worker
469*0e209d39SAndroid Build Coastguard Workerclass BlocklistedlistedDeclarationFilter:
470*0e209d39SAndroid Build Coastguard Worker    """A filter for blocklisting function declarations."""
471*0e209d39SAndroid Build Coastguard Worker    def __init__(self, blocklisted_function_names):
472*0e209d39SAndroid Build Coastguard Worker        self.blocklisted_function_names = blocklisted_function_names
473*0e209d39SAndroid Build Coastguard Worker
474*0e209d39SAndroid Build Coastguard Worker    def __call__(self, decl):
475*0e209d39SAndroid Build Coastguard Worker        """Returns True if the given decl is nor blocklisted"""
476*0e209d39SAndroid Build Coastguard Worker        return decl.spelling not in self.blocklisted_function_names
477*0e209d39SAndroid Build Coastguard Worker
478*0e209d39SAndroid Build Coastguard Worker
479*0e209d39SAndroid Build Coastguard Worker# Functions w/ variable argument lists (...) need special care to call
480*0e209d39SAndroid Build Coastguard Worker# their corresponding v- versions that accept a va_list argument. Note that
481*0e209d39SAndroid Build Coastguard Worker# although '...' will always appear as the last parameter, its v- version
482*0e209d39SAndroid Build Coastguard Worker# may put the va_list arg in a different place. Hence we provide an index
483*0e209d39SAndroid Build Coastguard Worker# to indicate the position.
484*0e209d39SAndroid Build Coastguard Worker#
485*0e209d39SAndroid Build Coastguard Worker# e.g. 'umsg_format': ('umsg_vformat', 3) means in the wrapper function of
486*0e209d39SAndroid Build Coastguard Worker# 'umsg_format', it will call 'umsg_vformat' instead, with the va_list arg
487*0e209d39SAndroid Build Coastguard Worker# inserted as the 3rd argument.
488*0e209d39SAndroid Build Coastguard Worker
489*0e209d39SAndroid Build Coastguard Worker# We need to insert the va_list (named args) at the position
490*0e209d39SAndroid Build Coastguard Worker# indicated by the KNOWN_VA_FUNCTIONS map.
491*0e209d39SAndroid Build Coastguard WorkerKNOWN_VA_FUNCTIONS = {
492*0e209d39SAndroid Build Coastguard Worker    'u_formatMessage': ('u_vformatMessage', 5),
493*0e209d39SAndroid Build Coastguard Worker    'u_parseMessage': ('u_vparseMessage', 5),
494*0e209d39SAndroid Build Coastguard Worker    'u_formatMessageWithError': ('u_vformatMessageWithError', 6),
495*0e209d39SAndroid Build Coastguard Worker    'u_parseMessageWithError': ('u_vparseMessageWithError', 5),
496*0e209d39SAndroid Build Coastguard Worker    'umsg_format': ('umsg_vformat', 3),
497*0e209d39SAndroid Build Coastguard Worker    'umsg_parse': ('umsg_vparse', 4),
498*0e209d39SAndroid Build Coastguard Worker    'utrace_format': ('utrace_vformat', 4),
499*0e209d39SAndroid Build Coastguard Worker}
500*0e209d39SAndroid Build Coastguard Worker
501*0e209d39SAndroid Build Coastguard Worker# The following functions are not @stable
502*0e209d39SAndroid Build Coastguard WorkerALLOWLISTED_FUNCTION_NAMES = (
503*0e209d39SAndroid Build Coastguard Worker    # Not intended to be called directly, but are used by @stable macros.
504*0e209d39SAndroid Build Coastguard Worker    'utf8_nextCharSafeBody',
505*0e209d39SAndroid Build Coastguard Worker    'utf8_appendCharSafeBody',
506*0e209d39SAndroid Build Coastguard Worker    'utf8_prevCharSafeBody',
507*0e209d39SAndroid Build Coastguard Worker    'utf8_back1SafeBody',
508*0e209d39SAndroid Build Coastguard Worker)
509