• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
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
17import argparse
18import collections
19import difflib
20import os
21import subprocess
22import sys
23import tempfile
24
25"""Test vndk vtable dumper"""
26
27NDK_VERSION = 'r11'
28API_LEVEL = 'android-24'
29
30SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
31VNDK_VTABLE_DUMPER = 'vndk-vtable-dumper'
32
33def get_dirnames(path, n):
34    """Get directory, n directories before path"""
35    for i in range(n):
36        path = os.path.dirname(path)
37    return path
38
39
40def get_prebuilts_host():
41    """Get the host dir for prebuilts"""
42    if sys.platform.startswith('linux'):
43        return 'linux-x86'
44    if sys.platform.startswith('darwin'):
45        return 'darwin-x86'
46    raise NotImplementedError('unknown platform')
47
48
49def get_prebuilts_gcc(android_build_top, arch, gcc_version):
50    """Get the path to gcc for the current platform"""
51    return os.path.join(android_build_top, 'prebuilts', 'gcc',
52                        get_prebuilts_host(), arch, gcc_version)
53
54def get_prebuilts_clang(android_build_top):
55    """Get the path to prebuilt gcc for the current platform"""
56    return os.path.join(android_build_top, 'prebuilts', 'clang', 'host',
57                        get_prebuilts_host(), 'clang-stable')
58
59def get_prebuilts_ndk(android_build_top, subdirs):
60    """Get the path to prebuilt ndk  for the current platform and API level"""
61    return os.path.join(android_build_top, 'prebuilts', 'ndk', NDK_VERSION,
62                        'platforms', API_LEVEL, *subdirs)
63
64def run_cmd(cmd, verbose=False):
65    """Run the command given and print the command if verbose is True"""
66    if verbose:
67        print('RUN:', ' '.join(cmd), file=sys.stderr)
68    subprocess.check_call(cmd)
69
70
71def run_output(cmd, verbose=False):
72    """Run the command given and print output of the command"""
73    if verbose:
74        print('RUN:', ' '.join(cmd), file=sys.stderr)
75    return subprocess.check_output(cmd, universal_newlines=True)
76
77
78def run_vtable_dump(path, verbose=False):
79    """Run vndk vtable dumper"""
80    return run_output([VNDK_VTABLE_DUMPER, path], verbose)
81
82
83class Target(object):
84    """Class representing a target: for eg: x86, arm64 etc"""
85    def __init__(self, name, triple, cflags, ldflags, gcc_toolchain_dir,
86                 clang_dir, ndk_include, ndk_lib):
87        """Parameterized Constructor"""
88        self.name = name
89        self.target_triple = triple
90        self.target_cflags = cflags
91        self.target_ldflags = ldflags
92
93        self.gcc_toolchain_dir = gcc_toolchain_dir
94        self.clang_dir = clang_dir
95        self.ndk_include = ndk_include
96        self.ndk_lib = ndk_lib
97
98    def compile(self, obj_file, src_file, cflags, verbose=False):
99        """Compiles the given source files and produces a .o at obj_file"""
100        clangpp = os.path.join(self.clang_dir, 'bin', 'clang++')
101
102        cmd = [clangpp, '-o', obj_file, '-c', src_file]
103        cmd.extend(['-fPIE', '-fPIC', '-fno-rtti', '-std=c++11'])
104        cmd.extend(['-gcc-toolchain', self.gcc_toolchain_dir])
105        cmd.extend(['-target', self.target_triple])
106        cmd.extend(['-isystem', self.ndk_include])
107        cmd.extend(cflags)
108        cmd.extend(self.target_cflags)
109        run_cmd(cmd, verbose)
110
111    def link(self, out_file, obj_files, ldflags, verbose=False):
112        """Link the given obj files to form a shared library"""
113        crtbegin = os.path.join(self.ndk_lib, 'crtbegin_so.o')
114        crtend = os.path.join(self.ndk_lib, 'crtend_so.o')
115        clangpp = os.path.join(self.clang_dir, 'bin', 'clang++')
116
117        cmd = [clangpp, '-o', out_file]
118        cmd.extend(['-fPIE', '-fPIC', '-fno-rtti', '-Wl,--no-undefined', '-nostdlib'])
119        cmd.append('-L' + self.ndk_lib)
120        cmd.extend(['-gcc-toolchain', self.gcc_toolchain_dir])
121        cmd.extend(['-target', self.target_triple])
122        cmd.append(crtbegin)
123        cmd.extend(obj_files)
124        cmd.append(crtend)
125        cmd.extend(ldflags)
126        cmd.extend(self.target_ldflags)
127        run_cmd(cmd, verbose)
128
129
130def create_targets(top):
131    """Create multiple targets objects, one for each architecture supported"""
132    return [
133        Target('arm', 'arm-linux-androideabi', [],[],
134               get_prebuilts_gcc(top, 'arm', 'arm-linux-androideabi-4.9'),
135               get_prebuilts_clang(top),
136               get_prebuilts_ndk(top, ['arch-arm', 'usr', 'include']),
137               get_prebuilts_ndk(top, ['arch-arm', 'usr', 'lib'])),
138
139        Target('arm64', 'aarch64-linux-android', [], [],
140               get_prebuilts_gcc(top, 'aarch64', 'aarch64-linux-android-4.9'),
141               get_prebuilts_clang(top),
142               get_prebuilts_ndk(top, ['arch-arm64', 'usr', 'include']),
143               get_prebuilts_ndk(top, ['arch-arm64', 'usr', 'lib'])),
144
145        Target('mips', 'mipsel-linux-android', [], [],
146               get_prebuilts_gcc(top, 'mips', 'mips64el-linux-android-4.9'),
147               get_prebuilts_clang(top),
148               get_prebuilts_ndk(top, ['arch-mips', 'usr', 'include']),
149               get_prebuilts_ndk(top, ['arch-mips', 'usr', 'lib'])),
150
151        Target('mips64', 'mips64el-linux-android',
152               ['-march=mips64el', '-mcpu=mips64r6'],
153               ['-march=mips64el', '-mcpu=mips64r6'],
154               get_prebuilts_gcc(top, 'mips', 'mips64el-linux-android-4.9'),
155               get_prebuilts_clang(top),
156               get_prebuilts_ndk(top, ['arch-mips64', 'usr', 'include']),
157               get_prebuilts_ndk(top, ['arch-mips64', 'usr', 'lib64'])),
158
159        Target('x86', 'x86_64-linux-android', ['-m32'], ['-m32'],
160               get_prebuilts_gcc(top, 'x86', 'x86_64-linux-android-4.9'),
161               get_prebuilts_clang(top),
162               get_prebuilts_ndk(top, ['arch-x86', 'usr', 'include']),
163               get_prebuilts_ndk(top, ['arch-x86', 'usr', 'lib'])),
164
165        Target('x86_64', 'x86_64-linux-android', ['-m64'], ['-m64'],
166               get_prebuilts_gcc(top, 'x86', 'x86_64-linux-android-4.9'),
167               get_prebuilts_clang(top),
168               get_prebuilts_ndk(top, ['arch-x86_64', 'usr', 'include']),
169               get_prebuilts_ndk(top, ['arch-x86_64', 'usr', 'lib64'])),
170    ]
171
172
173class TestRunner(object):
174    """Class to run the test"""
175    def __init__(self, expected_dir, test_dir, verbose):
176        """Parameterized constructor"""
177        self.expected_dir = expected_dir
178        self.test_dir = test_dir
179        self.verbose = verbose
180        self.num_errors = 0
181
182    def check_output(self, expected_file_path, actual):
183        """Compare the output of the test run and the expected output"""
184        actual = actual.splitlines(True)
185        with open(expected_file_path, 'r') as f:
186            expected = f.readlines()
187        if actual == expected:
188            return
189        for line in difflib.context_diff(expected, actual,
190                                         fromfile=expected_file_path,
191                                         tofile='actual'):
192            sys.stderr.write(line)
193        self.num_errors += 1
194
195    def run_test_for_target(self, target):
196        """Run the test for a specific target"""
197        print('Testing target', target.name, '...', file=sys.stderr)
198
199        expected_dir = os.path.join(self.expected_dir, target.name)
200
201        # Create test directory for this target.
202        test_dir = os.path.join(self.test_dir, target.name)
203        os.makedirs(test_dir, exist_ok=True)
204
205        # Compile and test "libtest.so".
206        src_file = os.path.join(SCRIPT_DIR, 'test1.cpp')
207        obj_file = os.path.join(test_dir, 'test.o')
208        target.compile(obj_file, src_file, [], self.verbose)
209
210        out_file = os.path.join(test_dir, 'libtest.so')
211        target.link(out_file, [obj_file],
212                    ['-shared', '-lc', '-lgcc', '-lstdc++'],
213                    self.verbose)
214        self.check_output(os.path.join(expected_dir, 'libtest.so.txt'),
215                          run_vtable_dump(out_file, self.verbose))
216
217    def run_test(self, targets):
218        """Run test fo all targets"""
219        for target in targets:
220            self.run_test_for_target(target)
221
222
223def main():
224    """ Set up and run test"""
225    # Parse command line arguments.
226    parser = argparse.ArgumentParser()
227    parser.add_argument('--verbose', '-v', action='store_true')
228    parser.add_argument('--android-build-top', help='path to android build top')
229    parser.add_argument('--test-dir',
230                        help='directory for temporary files')
231    parser.add_argument('--expected-dir', help='directory with expected output')
232    args = parser.parse_args()
233
234    # Find ${ANDROID_BUILD_TOP}.
235    if args.android_build_top:
236        android_build_top = args.android_build_top
237    else:
238        android_build_top = get_dirnames(SCRIPT_DIR, 5)
239
240    # Find expected output directory.
241    if args.expected_dir:
242        expected_dir = args.expected_dir
243    else:
244        expected_dir = os.path.join(SCRIPT_DIR, 'expected')
245
246    # Load compilation targets.
247    targets = create_targets(android_build_top)
248
249    # Run tests.
250    if args.test_dir:
251        os.makedirs(args.test_dir, exist_ok=True)
252        runner = TestRunner(expected_dir, args.test_dir, args.verbose)
253        runner.run_test(targets)
254    else:
255        with tempfile.TemporaryDirectory() as test_dir:
256            runner = TestRunner(expected_dir, test_dir, args.verbose)
257            runner.run_test(targets)
258
259    if runner.num_errors:
260        print('FAILED:', runner.num_errors, 'test(s) failed', file=sys.stderr)
261    else:
262        print('SUCCESS', file=sys.stderr)
263
264    return 1 if runner.num_errors else 0
265
266if __name__ == '__main__':
267    sys.exit(main())
268