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