• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2
3import gzip
4import os
5import subprocess
6import sys
7import tempfile
8import collections
9
10
11SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__))
12
13try:
14    AOSP_DIR = os.environ['ANDROID_BUILD_TOP']
15except KeyError:
16    print('error: ANDROID_BUILD_TOP environment variable is not set.',
17          file=sys.stderr)
18    sys.exit(1)
19
20BUILTIN_HEADERS_DIR = (
21    os.path.join(AOSP_DIR, 'bionic', 'libc', 'include'),
22    os.path.join(AOSP_DIR, 'external', 'libcxx', 'include'),
23    os.path.join(AOSP_DIR, 'prebuilts', 'clang-tools', 'linux-x86',
24                 'clang-headers'),
25)
26
27EXPORTED_HEADERS_DIR = (
28    os.path.join(AOSP_DIR, 'development', 'vndk', 'tools', 'header-checker',
29                 'tests'),
30)
31
32SO_EXT = '.so'
33SOURCE_ABI_DUMP_EXT_END = '.lsdump'
34SOURCE_ABI_DUMP_EXT = SO_EXT + SOURCE_ABI_DUMP_EXT_END
35COMPRESSED_SOURCE_ABI_DUMP_EXT = SOURCE_ABI_DUMP_EXT + '.gz'
36VENDOR_SUFFIX = '.vendor'
37
38DEFAULT_CPPFLAGS = ['-x', 'c++', '-std=c++11']
39DEFAULT_CFLAGS = ['-std=gnu99']
40DEFAULT_HEADER_FLAGS = ["-dump-function-declarations"]
41DEFAULT_FORMAT = 'ProtobufTextFormat'
42
43
44def get_reference_dump_dir(reference_dump_dir_stem,
45                           reference_dump_dir_insertion, lib_arch):
46    reference_dump_dir = os.path.join(reference_dump_dir_stem, lib_arch)
47    reference_dump_dir = os.path.join(reference_dump_dir,
48                                      reference_dump_dir_insertion)
49    return reference_dump_dir
50
51
52def copy_reference_dumps(lib_paths, reference_dir_stem,
53                         reference_dump_dir_insertion, lib_arch, compress):
54    reference_dump_dir = get_reference_dump_dir(reference_dir_stem,
55                                                reference_dump_dir_insertion,
56                                                lib_arch)
57    num_created = 0
58    for lib_path in lib_paths:
59        copy_reference_dump(lib_path, reference_dump_dir, compress)
60        num_created += 1
61    return num_created
62
63
64def copy_reference_dump(lib_path, reference_dump_dir, compress):
65    reference_dump_path = os.path.join(
66        reference_dump_dir, os.path.basename(lib_path))
67    if compress:
68        reference_dump_path += '.gz'
69    os.makedirs(os.path.dirname(reference_dump_path), exist_ok=True)
70    output_content = read_output_content(lib_path, AOSP_DIR)
71    if compress:
72        with gzip.open(reference_dump_path, 'wb') as f:
73            f.write(bytes(output_content, 'utf-8'))
74    else:
75        with open(reference_dump_path, 'wb') as f:
76            f.write(bytes(output_content, 'utf-8'))
77    print('Created abi dump at', reference_dump_path)
78    return reference_dump_path
79
80
81def read_output_content(output_path, replace_str):
82    with open(output_path, 'r') as f:
83        return f.read().replace(replace_str, '')
84
85
86def run_header_abi_dumper(input_path, cflags=tuple(),
87                          export_include_dirs=EXPORTED_HEADERS_DIR,
88                          flags=tuple()):
89    """Run header-abi-dumper to dump ABI from `input_path` and return the
90    output."""
91    with tempfile.TemporaryDirectory() as tmp:
92        output_path = os.path.join(tmp, os.path.basename(input_path)) + '.dump'
93        run_header_abi_dumper_on_file(input_path, output_path,
94                                      export_include_dirs, cflags, flags)
95        return read_output_content(output_path, AOSP_DIR)
96
97
98def run_header_abi_dumper_on_file(input_path, output_path,
99                                  export_include_dirs=tuple(), cflags=tuple(),
100                                  flags=tuple()):
101    """Run header-abi-dumper to dump ABI from `input_path` and the output is
102    written to `output_path`."""
103    input_ext = os.path.splitext(input_path)[1]
104    cmd = ['header-abi-dumper', '-o', output_path, input_path]
105    for dir in export_include_dirs:
106        cmd += ['-I', dir]
107    cmd += flags
108    if '-output-format' not in flags:
109        cmd += ['-output-format', DEFAULT_FORMAT]
110    if input_ext == ".h":
111        cmd += DEFAULT_HEADER_FLAGS
112    cmd += ['--']
113    cmd += cflags
114    if input_ext in ('.cpp', '.cc', '.h'):
115        cmd += DEFAULT_CPPFLAGS
116    else:
117        cmd += DEFAULT_CFLAGS
118
119    for dir in BUILTIN_HEADERS_DIR:
120        cmd += ['-isystem', dir]
121    # The export include dirs imply local include dirs.
122    for dir in export_include_dirs:
123        cmd += ['-I', dir]
124    subprocess.check_call(cmd)
125
126
127def run_header_abi_linker(output_path, inputs, version_script, api, arch,
128                          flags=tuple()):
129    """Link inputs, taking version_script into account"""
130    cmd = ['header-abi-linker', '-o', output_path, '-v', version_script,
131           '-api', api, '-arch', arch]
132    cmd += flags
133    if '-input-format' not in flags:
134        cmd += ['-input-format', DEFAULT_FORMAT]
135    if '-output-format' not in flags:
136        cmd += ['-output-format', DEFAULT_FORMAT]
137    cmd += inputs
138    subprocess.check_call(cmd)
139    return read_output_content(output_path, AOSP_DIR)
140
141
142def make_targets(product, variant, targets):
143    make_cmd = ['build/soong/soong_ui.bash', '--make-mode', '-j',
144                'TARGET_PRODUCT=' + product, 'TARGET_BUILD_VARIANT=' + variant]
145    make_cmd += targets
146    subprocess.check_call(make_cmd, cwd=AOSP_DIR)
147
148
149def make_tree(product, variant):
150    """Build all lsdump files."""
151    return make_targets(product, variant, ['findlsdumps'])
152
153
154def make_libraries(product, variant, targets, libs, llndk_mode):
155    """Build lsdump files for specific libs."""
156    lsdump_paths = read_lsdump_paths(product, variant, targets, build=True)
157    targets = []
158    for name in libs:
159        targets.extend(lsdump_paths[name].values())
160    make_targets(product, variant, targets)
161
162
163def get_lsdump_paths_file_path(product, variant):
164    """Get the path to lsdump_paths.txt."""
165    product_out = get_build_vars_for_product(
166        ['PRODUCT_OUT'], product, variant)[0]
167    return os.path.join(product_out, 'lsdump_paths.txt')
168
169
170def _is_sanitizer_variation(variation):
171    """Check whether the variation is introduced by a sanitizer."""
172    return variation in {'asan', 'hwasan', 'tsan', 'intOverflow', 'cfi', 'scs'}
173
174
175def _are_sanitizer_variations(variations):
176    """Check whether these variations are introduced by sanitizers."""
177    if isinstance(variations, str):
178        variations = [v for v in variations.split('_') if v]
179    return all(_is_sanitizer_variation(v) for v in variations)
180
181
182def _read_lsdump_paths(lsdump_paths_file_path, targets):
183    """Read lsdump path from lsdump_paths.txt for each libname and variant."""
184    lsdump_paths = collections.defaultdict(dict)
185    suffixes = collections.defaultdict(dict)
186
187    prefixes = []
188    prefixes.extend(get_module_variant_dir_name(
189        target.arch, target.arch_variant, target.cpu_variant, '_core_shared')
190        for target in targets)
191    prefixes.extend(get_module_variant_dir_name(
192        target.arch, target.arch_variant, target.cpu_variant, '_vendor_shared')
193        for target in targets)
194
195    with open(lsdump_paths_file_path, 'r') as lsdump_paths_file:
196        for line in lsdump_paths_file:
197            path = line.strip()
198            if not path:
199                continue
200            dirname, filename = os.path.split(path)
201            if not filename.endswith(SOURCE_ABI_DUMP_EXT):
202                continue
203            libname = filename[:-len(SOURCE_ABI_DUMP_EXT)]
204            if not libname:
205                continue
206            variant = os.path.basename(dirname)
207            if not variant:
208                continue
209            for prefix in prefixes:
210                if not variant.startswith(prefix):
211                    continue
212                new_suffix = variant[len(prefix):]
213                if not _are_sanitizer_variations(new_suffix):
214                    continue
215                old_suffix = suffixes[libname].get(prefix)
216                if not old_suffix or new_suffix > old_suffix:
217                    lsdump_paths[libname][prefix] = path
218                    suffixes[libname][prefix] = new_suffix
219    return lsdump_paths
220
221
222def read_lsdump_paths(product, variant, targets, build=True):
223    """Build lsdump_paths.txt and read the paths."""
224    lsdump_paths_file_path = get_lsdump_paths_file_path(product, variant)
225    if build:
226        make_targets(product, variant, [lsdump_paths_file_path])
227    lsdump_paths_file_abspath = os.path.join(AOSP_DIR, lsdump_paths_file_path)
228    return _read_lsdump_paths(lsdump_paths_file_abspath, targets)
229
230
231def get_module_variant_dir_name(arch, arch_variant, cpu_variant,
232                                variant_suffix):
233    """Create module variant directory name from the target architecture, the
234    target architecture variant, the target CPU variant, and a variant suffix
235    (e.g. `_core_shared`, `_vendor_shared`, etc)."""
236
237    if not arch_variant or arch_variant == arch:
238        arch_variant = ''
239    else:
240        arch_variant = '_' + arch_variant
241
242    if not cpu_variant or cpu_variant == 'generic':
243        cpu_variant = ''
244    else:
245        cpu_variant = '_' + cpu_variant
246
247    return 'android_' + arch + arch_variant + cpu_variant + variant_suffix
248
249
250def find_lib_lsdumps(module_variant_dir_name, lsdump_paths, libs):
251    """Find the lsdump corresponding to lib_name for the given module variant
252    if it exists."""
253    result = []
254    for lib_name, variations in lsdump_paths.items():
255        if libs and lib_name not in libs:
256            continue
257        for variation, path in variations.items():
258            if variation.startswith(module_variant_dir_name):
259                result.append(os.path.join(AOSP_DIR, path.strip()))
260    return result
261
262
263def run_abi_diff(old_test_dump_path, new_test_dump_path, arch, lib_name,
264                 flags=tuple()):
265    abi_diff_cmd = ['header-abi-diff', '-new', new_test_dump_path, '-old',
266                    old_test_dump_path, '-arch', arch, '-lib', lib_name]
267    with tempfile.TemporaryDirectory() as tmp:
268        output_name = os.path.join(tmp, lib_name) + '.abidiff'
269        abi_diff_cmd += ['-o', output_name]
270        abi_diff_cmd += flags
271        if '-input-format-old' not in flags:
272            abi_diff_cmd += ['-input-format-old', DEFAULT_FORMAT]
273        if '-input-format-new' not in flags:
274            abi_diff_cmd += ['-input-format-new', DEFAULT_FORMAT]
275        try:
276            subprocess.check_call(abi_diff_cmd)
277        except subprocess.CalledProcessError as err:
278            return err.returncode
279
280    return 0
281
282
283def get_build_vars_for_product(names, product=None, variant=None):
284    """ Get build system variable for the launched target."""
285
286    if product is None and 'ANDROID_PRODUCT_OUT' not in os.environ:
287        return None
288
289    env = os.environ.copy()
290    if product:
291        env['TARGET_PRODUCT'] = product
292    if variant:
293        env['TARGET_BUILD_VARIANT'] = variant
294    cmd = [
295        os.path.join('build', 'soong', 'soong_ui.bash'),
296        '--dumpvars-mode', '-vars', ' '.join(names),
297    ]
298
299    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
300                            stderr=subprocess.PIPE, cwd=AOSP_DIR, env=env)
301    out, err = proc.communicate()
302
303    if proc.returncode != 0:
304        print("error: %s" % err.decode('utf-8'), file=sys.stderr)
305        return None
306
307    build_vars = out.decode('utf-8').strip().splitlines()
308
309    build_vars_list = []
310    for build_var in build_vars:
311        value = build_var.partition('=')[2]
312        build_vars_list.append(value.replace('\'', ''))
313    return build_vars_list
314