1#!/usr/bin/env python
2#
3# Copyright (C) 2017 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
18import argparse
19import os
20import subprocess
21import sys
22
23import extract_lsdump
24
25
26def _ExecuteCommand(cmd, **kwargs):
27    """Executes a command and returns stdout.
28
29    Args:
30        cmd: A list of strings, the command to execute.
31        **kwargs: The arguments passed to subprocess.Popen.
32
33    Returns:
34        A string, the stdout.
35    """
36    proc = subprocess.Popen(
37        cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs)
38    stdout, stderr = proc.communicate()
39    if proc.returncode:
40        sys.exit("Command failed: %s\nstdout=%s\nstderr=%s" % (
41                 cmd, stdout, stderr))
42    if stderr:
43        print("Warning: cmd=%s\nstdout=%s\nstderr=%s" % (cmd, stdout, stderr))
44    return stdout.strip()
45
46
47def GetBuildVariables(build_top_dir, abs_path, vars):
48    """Gets values of variables from build config.
49
50    Args:
51        build_top_dir: The path to root directory of Android source.
52        abs_path: A boolean, whether to convert the values to absolute paths.
53        vars: A list of strings, the names of the variables.
54
55    Returns:
56        A list of strings which are the values of the variables.
57    """
58    cmd = ["build/soong/soong_ui.bash", "--dumpvars-mode",
59           ("--abs-vars" if abs_path else "--vars"), " ".join(vars)]
60    stdout = _ExecuteCommand(cmd, cwd=build_top_dir)
61    print(stdout)
62    return [line.split("=", 1)[1].strip("'") for line in stdout.splitlines()]
63
64
65def _LoadLibraryNamesFromTxt(vndk_lib_list_file):
66    """Loads VNDK and VNDK-SP library names from a VNDK library list.
67
68    Args:
69        vndk_lib_list_file: A file object of
70                            build/make/target/product/vndk/current.txt
71
72    Returns:
73        A list of strings, the VNDK and VNDK-SP library names with vndk/vndk-sp
74        directory prefixes.
75    """
76    tags = (
77        ("VNDK-core: ", len("VNDK-core: "), False),
78        ("VNDK-SP: ", len("VNDK-SP: "), False),
79        ("VNDK-private: ", len("VNDK-private: "), True),
80        ("VNDK-SP-private: ", len("VNDK-SP-private: "), True),
81    )
82    lib_names = set()
83    lib_names_exclude = set()
84    for line in vndk_lib_list_file:
85        for tag, tag_len, is_exclude in tags:
86            if line.startswith(tag):
87                lib_name = line[tag_len:].strip()
88                if is_exclude:
89                    lib_names_exclude.add(lib_name)
90                else:
91                    lib_names.add(lib_name)
92    return sorted(lib_names - lib_names_exclude)
93
94
95def _LoadLibraryNames(file_names):
96    """Loads library names from files.
97
98    Each element in the input list can be a .so file or a .txt file. The
99    returned list consists of:
100    - The .so file names in the input list.
101    - The libraries tagged with VNDK-core or VNDK-SP in the .txt file.
102
103    Args:
104        file_names: A list of strings, the library or text file names.
105
106    Returns:
107        A list of strings, the library names (probably with vndk/vndk-sp
108        directory prefixes).
109    """
110    lib_names = []
111    for file_name in file_names:
112        if file_name.endswith(".so"):
113            lib_names.append(file_name)
114        else:
115            with open(file_name, "r") as txt_file:
116                lib_names.extend(_LoadLibraryNamesFromTxt(txt_file))
117    return lib_names
118
119
120def DumpAbi(output_dir, lib_names, lsdump_path):
121    """Generates ABI dumps from library lsdumps.
122
123    Args:
124        output_dir: The output directory of dump files.
125        lib_names: The names of the libraries to dump.
126        lsdump_path: The path to the directory containing lsdumps.
127
128    Returns:
129        A list of strings, the libraries whose ABI dump fails to be created.
130    """
131    missing_dumps = []
132    for lib_name in lib_names:
133        dump_path = os.path.join(output_dir, lib_name + '.abi.dump')
134        lib_lsdump_path = os.path.join(lsdump_path, lib_name + '.lsdump')
135        if os.path.isfile(lib_lsdump_path + '.gz'):
136            lib_lsdump_path += '.gz'
137
138        print(lib_lsdump_path)
139        try:
140            extract_lsdump.ParseLsdumpFile(lib_lsdump_path, dump_path)
141        except extract_lsdump.LsdumpError as e:
142            missing_dumps.append(lib_name)
143            print(e)
144        else:
145            print('Output: ' + dump_path)
146        print('')
147    return missing_dumps
148
149
150def _GetTargetArchDir(target_arch, target_arch_variant):
151    if target_arch == target_arch_variant:
152        return target_arch
153    return '{}_{}'.format(target_arch, target_arch_variant)
154
155
156def _GetAbiBitnessFromArch(target_arch):
157    arch_bitness = {
158        'arm': '32',
159        'arm64': '64',
160        'x86': '32',
161        'x86_64': '64',
162    }
163    return arch_bitness[target_arch]
164
165
166def main():
167    # Parse arguments
168    description = (
169        'Generates VTS VNDK ABI test abidumps from lsdump. '
170        'Option values are read from build variables if no value is given. '
171        'If none of the options are specified, then abidumps for target second '
172        'arch are also generated.'
173    )
174    arg_parser = argparse.ArgumentParser(description=description)
175    arg_parser.add_argument("file", nargs="*",
176                            help="the libraries to dump. Each file can be "
177                                 ".so or .txt. The text file can be found at "
178                                 "build/make/target/product/vndk/current.txt.")
179    arg_parser.add_argument("--output", "-o", action="store",
180                            help="output directory for ABI reference dump. "
181                                 "Default value is PLATFORM_VNDK_VERSION.")
182    arg_parser.add_argument('--platform-vndk-version',
183                            help='platform VNDK version. '
184                                 'Default value is PLATFORM_VNDK_VERSION.')
185    arg_parser.add_argument('--binder-bitness',
186                            choices=['32', '64'],
187                            help='bitness of binder interface. '
188                                 'Default value is 32 if BINDER32BIT is set '
189                                 'else is 64.')
190    arg_parser.add_argument('--target-main-arch',
191                            choices=['arm', 'arm64', 'x86', 'x86_64'],
192                            help='main CPU arch of the device. '
193                                 'Default value is TARGET_ARCH.')
194    arg_parser.add_argument('--target-arch',
195                            choices=['arm', 'arm64', 'x86', 'x86_64'],
196                            help='CPU arch of the libraries to dump. '
197                                 'Default value is TARGET_ARCH.')
198    arg_parser.add_argument('--target-arch-variant',
199                            help='CPU arch variant of the libraries to dump. '
200                                 'Default value is TARGET_ARCH_VARIANT.')
201
202    args = arg_parser.parse_args()
203
204    build_top_dir = os.getenv("ANDROID_BUILD_TOP")
205    if not build_top_dir:
206        sys.exit("env var ANDROID_BUILD_TOP is not set")
207
208    # If some options are not specified, read build variables as default values.
209    if not all([args.platform_vndk_version,
210                args.binder_bitness,
211                args.target_main_arch,
212                args.target_arch,
213                args.target_arch_variant]):
214        [platform_vndk_version,
215         binder_32_bit,
216         target_arch,
217         target_arch_variant,
218         target_2nd_arch,
219         target_2nd_arch_variant] = GetBuildVariables(
220            build_top_dir,
221            False,
222            ['PLATFORM_VNDK_VERSION',
223             'BINDER32BIT',
224             'TARGET_ARCH',
225             'TARGET_ARCH_VARIANT',
226             'TARGET_2ND_ARCH',
227             'TARGET_2ND_ARCH_VARIANT']
228        )
229        target_main_arch = target_arch
230        binder_bitness = '32' if binder_32_bit else '64'
231
232    if args.platform_vndk_version:
233        platform_vndk_version = args.platform_vndk_version
234
235    if args.binder_bitness:
236        binder_bitness = args.binder_bitness
237
238    if args.target_main_arch:
239        target_main_arch = args.target_main_arch
240
241    if args.target_arch:
242        target_arch = args.target_arch
243
244    if args.target_arch_variant:
245        target_arch_variant = args.target_arch_variant
246
247    dump_targets = [(platform_vndk_version,
248                     binder_bitness,
249                     target_main_arch,
250                     target_arch,
251                     target_arch_variant)]
252
253    # If all options are not specified, then also create dump for 2nd arch.
254    if not any([args.platform_vndk_version,
255                args.binder_bitness,
256                args.target_main_arch,
257                args.target_arch,
258                args.target_arch_variant]):
259        dump_targets.append((platform_vndk_version,
260                             binder_bitness,
261                             target_main_arch,
262                             target_2nd_arch,
263                             target_2nd_arch_variant))
264
265    for target_tuple in dump_targets:
266        (platform_vndk_version,
267         binder_bitness,
268         target_main_arch,
269         target_arch,
270         target_arch_variant) = target_tuple
271
272        # Determine abi_bitness from target architecture
273        abi_bitness = _GetAbiBitnessFromArch(target_arch)
274
275        # Generate ABI dump from lsdump in TOP/prebuilts/abi-dumps
276        lsdump_path = os.path.join(
277            build_top_dir,
278            'prebuilts',
279            'abi-dumps',
280            'vndk',
281            platform_vndk_version,
282            binder_bitness,
283            _GetTargetArchDir(target_arch, target_arch_variant),
284            'source-based')
285        if not os.path.exists(lsdump_path):
286            print('Warning: lsdump path does not exist: ' + lsdump_path)
287            print('No abidump created.')
288            continue
289
290        output_dir = os.path.join(
291            args.output if args.output else platform_vndk_version,
292            'binder' + binder_bitness,
293            target_main_arch,
294            'lib64' if abi_bitness == '64' else 'lib')
295        print("OUTPUT_DIR=" + output_dir)
296
297        lib_names = _LoadLibraryNames(args.file)
298
299        missing_dumps = DumpAbi(output_dir, lib_names, lsdump_path)
300
301        if missing_dumps:
302            print('Warning: Fails to create ABI dumps for libraries:')
303            for lib_name in missing_dumps:
304                print(lib_name)
305
306
307if __name__ == "__main__":
308    main()
309