1#!/usr/bin/env python3
2
3import argparse
4import os
5import time
6
7from utils import (
8    AOSP_DIR, SOURCE_ABI_DUMP_EXT_END, SO_EXT, BuildTarget, Arch,
9    copy_reference_dump, find_lib_lsdumps, get_build_vars,
10    make_libraries, make_targets, read_lsdump_paths)
11
12
13PRODUCTS_DEFAULT = ['aosp_arm', 'aosp_arm64', 'aosp_x86', 'aosp_x86_64']
14
15PREBUILTS_ABI_DUMPS_DIR = os.path.join(AOSP_DIR, 'prebuilts', 'abi-dumps')
16PREBUILTS_ABI_DUMPS_SUBDIRS = ('ndk', 'platform', 'vndk')
17KNOWN_TAGS = {'APEX', 'LLNDK', 'NDK', 'PLATFORM', 'VENDOR', 'PRODUCT'}
18NON_AOSP_TAGS = {'VENDOR', 'PRODUCT'}
19
20SOONG_DIR = os.path.join(AOSP_DIR, 'out', 'soong', '.intermediates')
21
22
23class GetRefDumpDirStem:
24    def __init__(self, ref_dump_dir):
25        self.ref_dump_dir = ref_dump_dir
26
27    def __call__(self, subdir, arch_str):
28        return os.path.join(self.ref_dump_dir, arch_str)
29
30
31class GetVersionedRefDumpDirStem:
32    def __init__(self, board_api_level, chosen_platform_version,
33                 binder_bitness):
34        self.board_api_level = board_api_level
35        self.chosen_platform_version = chosen_platform_version
36        self.binder_bitness = binder_bitness
37
38    def __call__(self, subdir, arch_str):
39        if subdir not in PREBUILTS_ABI_DUMPS_SUBDIRS:
40            raise ValueError(f'"{subdir}" is not a valid dump directory under '
41                             f'{PREBUILTS_ABI_DUMPS_DIR}.')
42        version_stem = (self.board_api_level if subdir == 'vndk' else
43                        self.chosen_platform_version)
44        return os.path.join(PREBUILTS_ABI_DUMPS_DIR, subdir, version_stem,
45                            self.binder_bitness, arch_str)
46
47
48class LsdumpFilter:
49    def __init__(self, include_names, include_tags, exclude_tags):
50        self.include_names = include_names
51        self.include_tags = include_tags
52        self.exclude_tags = exclude_tags
53
54    def __call__(self, tag, lib_name):
55        """Determine whether to dump the library.
56
57        lib_name does not contain '.so'.
58        """
59        if self.include_names and lib_name not in self.include_names:
60            return False
61        if self.include_tags and tag not in self.include_tags:
62            return False
63        if tag in self.exclude_tags:
64            return False
65        return True
66
67
68def tag_to_dir_name(tag):
69    if tag in NON_AOSP_TAGS:
70        return ''
71    if tag == 'NDK':
72        return 'ndk'
73    if tag in ('APEX', 'PLATFORM'):
74        return 'platform'
75    if tag == 'LLNDK':
76        return 'vndk'
77    raise ValueError(tag + ' is not a known tag.')
78
79
80def find_and_copy_lib_lsdumps(get_ref_dump_dir_stem, arch, libs,
81                              lsdump_paths):
82    arch_lsdump_paths = find_lib_lsdumps(lsdump_paths, libs, arch)
83    num_created = 0
84    for tag, path in arch_lsdump_paths:
85        ref_dump_dir_stem = get_ref_dump_dir_stem(tag_to_dir_name(tag),
86                                                  arch.get_arch_str())
87        copy_reference_dump(
88            path, os.path.join(ref_dump_dir_stem, 'source-based'))
89        num_created += 1
90    return num_created
91
92
93def create_source_abi_reference_dumps(args, get_ref_dump_dir_stem,
94                                      lsdump_paths, arches):
95    num_libs_copied = 0
96    for arch in arches:
97        assert arch.primary_arch != ''
98        print(f'Creating dumps for arch: {arch.arch}, '
99              f'primary arch: {arch.primary_arch}')
100
101        num_libs_copied += find_and_copy_lib_lsdumps(
102            get_ref_dump_dir_stem, arch, args.libs, lsdump_paths)
103    return num_libs_copied
104
105
106def create_source_abi_reference_dumps_for_all_products(args):
107    """Create reference ABI dumps for all specified products."""
108    num_processed = 0
109
110    for product in args.products:
111        build_target = BuildTarget(product, args.release, args.build_variant)
112        (
113            release_board_api_level, binder_32_bit,
114            platform_version_codename, platform_sdk_version,
115        ) = get_build_vars(
116            ['RELEASE_BOARD_API_LEVEL', 'BINDER32BIT',
117             'PLATFORM_VERSION_CODENAME', 'PLATFORM_SDK_VERSION'],
118            build_target
119        )
120        if binder_32_bit == 'true':
121            binder_bitness = '32'
122        else:
123            binder_bitness = '64'
124
125        # chosen_platform_version is expected to be the finalized
126        # PLATFORM_SDK_VERSION if the codename is REL.
127        chosen_platform_version = (platform_sdk_version
128                                   if platform_version_codename == 'REL'
129                                   else 'current')
130
131        arches = [arch for arch in
132                  (Arch(True, build_target), Arch(False, build_target))
133                  if arch.arch]
134
135        if args.ref_dump_dir:
136            get_ref_dump_dir_stem = GetRefDumpDirStem(args.ref_dump_dir)
137            exclude_tags = set()
138        else:
139            get_ref_dump_dir_stem = GetVersionedRefDumpDirStem(
140                release_board_api_level, chosen_platform_version,
141                binder_bitness)
142            exclude_tags = NON_AOSP_TAGS
143
144        lsdump_filter = LsdumpFilter(args.libs, args.include_tags,
145                                     exclude_tags)
146
147        try:
148            if not args.no_make_lib:
149                print('making libs for', '-'.join(filter(None, build_target)))
150                if args.libs:
151                    make_libraries(build_target, arches, args.libs,
152                                   lsdump_filter)
153                elif args.include_tags:
154                    make_targets(
155                        build_target,
156                        ['findlsdumps_' + tag for tag in args.include_tags])
157                else:
158                    make_targets(build_target, ['findlsdumps'])
159
160            lsdump_paths = read_lsdump_paths(build_target, arches,
161                                             lsdump_filter, build=False)
162
163            num_processed += create_source_abi_reference_dumps(
164                args, get_ref_dump_dir_stem, lsdump_paths, arches)
165        except KeyError as e:
166            if args.libs or not args.ref_dump_dir:
167                raise RuntimeError('Please check the lib name, --lib-variant '
168                                   'and -ref-dump-dir if you are updating '
169                                   'reference dumps for product or vendor '
170                                   'libraries.') from e
171            raise
172
173    return num_processed
174
175
176def _parse_args():
177    """Parse the command line arguments."""
178
179    parser = argparse.ArgumentParser()
180    parser.add_argument('--no-make-lib', action='store_true',
181                        help='skip building dumps while creating references')
182    parser.add_argument('--lib', '-libs', action='append',
183                        dest='libs', metavar='LIB',
184                        help='libs to create references for')
185    parser.add_argument('--product', '-products', action='append',
186                        dest='products', metavar='PRODUCT',
187                        help='products to create references for')
188    parser.add_argument('--release', '-release',
189                        help='release configuration to create references for. '
190                             'e.g., trunk_staging, next.')
191    parser.add_argument('--build-variant', default='userdebug',
192                        help='build variant to create references for')
193    parser.add_argument('--lib-variant', action='append', dest='include_tags',
194                        default=[], choices=KNOWN_TAGS,
195                        help='library variant to create references for.')
196    parser.add_argument('--ref-dump-dir', '-ref-dump-dir',
197                        help='directory to copy reference abi dumps into')
198    args = parser.parse_args()
199
200    if args.libs:
201        if any(lib_name.endswith(SOURCE_ABI_DUMP_EXT_END) or
202               lib_name.endswith(SO_EXT) for lib_name in args.libs):
203            parser.error('--lib should be followed by a base name without '
204                         'file extension.')
205
206    if NON_AOSP_TAGS.intersection(args.include_tags) and not args.libs:
207        parser.error('--lib must be given if --lib-variant is any of ' +
208                     str(NON_AOSP_TAGS))
209
210    if args.ref_dump_dir and not args.libs:
211        parser.error('--lib must be given if --ref-dump-dir is given.')
212
213    if args.ref_dump_dir and len(args.include_tags) != 1:
214        print('WARNING: Exactly one --lib-variant should be specified if '
215              '--ref-dump-dir is given.')
216
217    if args.products is None:
218        # If `args.products` is unspecified, generate reference ABI dumps for
219        # all products.
220        args.products = PRODUCTS_DEFAULT
221
222    return args
223
224
225def main():
226    args = _parse_args()
227
228    # Clear SKIP_ABI_CHECKS as it forbids ABI dumps from being built.
229    os.environ.pop('SKIP_ABI_CHECKS', None)
230
231    if os.environ.get('KEEP_VNDK') == 'true':
232        raise RuntimeError('KEEP_VNDK is not supported. Please undefine it.')
233
234    start = time.time()
235    num_processed = create_source_abi_reference_dumps_for_all_products(args)
236    end = time.time()
237
238    print()
239    print('msg: Processed', num_processed, 'libraries in ', (end - start) / 60,
240          ' minutes')
241
242
243if __name__ == '__main__':
244    main()
245