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 17 import argparse 18 import collections 19 import difflib 20 import os 21 import subprocess 22 import sys 23 import tempfile 24 25 """Test vndk vtable dumper""" 26 27 NDK_VERSION = 'r11' 28 API_LEVEL = 'android-24' 29 30 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) 31 VNDK_VTABLE_DUMPER = 'vndk-vtable-dumper' 32 33 def 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 40 def 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 49 def 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 54 def 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 59 def 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 64 def 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 71 def 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 78 def run_vtable_dump(path, verbose=False): 79 """Run vndk vtable dumper""" 80 return run_output([VNDK_VTABLE_DUMPER, path], verbose) 81 82 83 class 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 130 def 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 173 class 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 223 def 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 266 if __name__ == '__main__': 267 sys.exit(main()) 268