1 #!/usr/bin/env python 2 3 from __future__ import print_function 4 5 import argparse 6 import collections 7 import os 8 import re 9 import subprocess 10 import sys 11 12 def detect_ndk_dir(): 13 ndk_dir = os.getenv('NDK') 14 if not ndk_dir: 15 error_msg = '''error: NDK toolchain is required for this test case. 16 error: 17 error: Steps: 18 error: 1. Download NDK from https://developer.android.com/ndk/downloads/ 19 error: 2. Unzip the archive (android-ndk-r15c-linux-x86_64.zip) 20 error: 3. Set environment variable NDK to the extracted directory 21 error: (export NDK=android-ndk-r15c) 22 error:''' 23 print(error_msg, file=sys.stderr) 24 raise ValueError('NDK toolchain not specified') 25 26 if not os.path.exists(ndk_dir): 27 raise ValueError('NDK toolchain not found') 28 29 return ndk_dir 30 31 def detect_api_level(ndk_dir): 32 try: 33 apis = [] 34 pattern = re.compile('android-(\\d+)') 35 for name in os.listdir(os.path.join(ndk_dir, 'platforms')): 36 match = pattern.match(name) 37 if match: 38 apis.append(int(match.group(1))) 39 if not apis: 40 raise ValueError('failed to find latest api') 41 return 'android-{}'.format(max(apis)) 42 except IOError: 43 raise ValueError('failed to find latest api') 44 45 def detect_host(): 46 if sys.platform.startswith('linux'): 47 return 'linux-x86_64' 48 if sys.platform.startswith('darwin'): 49 return 'darwin-x86_64' 50 raise NotImplementedError('unknown host platform') 51 52 def get_gcc_dir(ndk_dir, arch, host): 53 return os.path.join(ndk_dir, 'toolchains', arch, 'prebuilt', host) 54 55 def get_clang_dir(ndk_dir, host): 56 return os.path.join(ndk_dir, 'toolchains', 'llvm', 'prebuilt', host) 57 58 def get_platform_dir(ndk_dir, api, subdirs): 59 return os.path.join(ndk_dir, 'platforms', api, *subdirs) 60 61 class Target(object): 62 def __init__(self, name, triple, cflags, ldflags, gcc_toolchain_dir, 63 clang_dir, ndk_include, ndk_lib): 64 self.name = name 65 self.target_triple = triple 66 self.target_cflags = cflags 67 self.target_ldflags = ldflags 68 69 self.gcc_toolchain_dir = gcc_toolchain_dir 70 self.clang_dir = clang_dir 71 self.ndk_include = ndk_include 72 self.ndk_lib = ndk_lib 73 74 def check_paths(self): 75 def check_path(path): 76 if os.path.exists(path): 77 return True 78 print('error: File not found:', path, file=sys.stderr) 79 return False 80 81 ld_exeutable = os.path.join( 82 self.gcc_toolchain_dir, 'bin', self.target_triple + '-ld') 83 84 success = check_path(self.gcc_toolchain_dir) 85 success &= check_path(ld_exeutable) 86 success &= check_path(self.clang_dir) 87 success &= check_path(self.ndk_include) 88 success &= check_path(self.ndk_lib) 89 return success 90 91 def compile(self, obj_file, src_file, cflags): 92 clang = os.path.join(self.clang_dir, 'bin', 'clang') 93 94 cmd = [clang, '-o', obj_file, '-c', src_file] 95 cmd.extend(['-fPIE', '-fPIC']) 96 cmd.extend(['-gcc-toolchain', self.gcc_toolchain_dir]) 97 cmd.extend(['-target', self.target_triple]) 98 cmd.extend(['-isystem', self.ndk_include]) 99 cmd.extend(cflags) 100 cmd.extend(self.target_cflags) 101 subprocess.check_call(cmd) 102 103 def link(self, out_file, obj_files, ldflags): 104 if '-shared' in ldflags: 105 crtbegin = os.path.join(self.ndk_lib, 'crtbegin_so.o') 106 crtend = os.path.join(self.ndk_lib, 'crtend_so.o') 107 else: 108 crtbegin = os.path.join(self.ndk_lib, 'crtbegin_static.o') 109 crtend = os.path.join(self.ndk_lib, 'crtend_android.o') 110 111 clang = os.path.join(self.clang_dir, 'bin', 'clang') 112 113 cmd = [clang, '-o', out_file] 114 cmd.extend(['-fPIE', '-fPIC', '-Wl,--no-undefined', '-nostdlib']) 115 cmd.append('-L' + self.ndk_lib) 116 cmd.extend(['-gcc-toolchain', self.gcc_toolchain_dir]) 117 cmd.extend(['-target', self.target_triple]) 118 cmd.append(crtbegin) 119 cmd.extend(obj_files) 120 cmd.append(crtend) 121 cmd.extend(ldflags) 122 cmd.extend(self.target_ldflags) 123 if '-shared' not in ldflags: 124 cmd.append('-Wl,-pie') 125 subprocess.check_call(cmd) 126 127 def create_targets(ndk_dir=None, api=None, host=None): 128 if ndk_dir is None: 129 ndk_dir = detect_ndk_dir() 130 if api is None: 131 api = detect_api_level(ndk_dir) 132 if host is None: 133 host = detect_host() 134 135 targets = collections.OrderedDict() 136 137 targets['arm'] = Target( 138 'arm', 'arm-linux-androideabi', [],[], 139 get_gcc_dir(ndk_dir, 'arm-linux-androideabi-4.9', host), 140 get_clang_dir(ndk_dir, host), 141 get_platform_dir(ndk_dir, api, ['arch-arm', 'usr', 'include']), 142 get_platform_dir(ndk_dir, api, ['arch-arm', 'usr', 'lib'])) 143 144 targets['arm64'] = Target( 145 'arm64', 'aarch64-linux-android', [], [], 146 get_gcc_dir(ndk_dir, 'aarch64-linux-android-4.9', host), 147 get_clang_dir(ndk_dir, host), 148 get_platform_dir(ndk_dir, api, ['arch-arm64', 'usr', 'include']), 149 get_platform_dir(ndk_dir, api, ['arch-arm64', 'usr', 'lib'])) 150 151 targets['x86'] = Target( 152 'x86', 'i686-linux-android', ['-m32'], ['-m32'], 153 get_gcc_dir(ndk_dir, 'x86-4.9', host), 154 get_clang_dir(ndk_dir, host), 155 get_platform_dir(ndk_dir, api, ['arch-x86', 'usr', 'include']), 156 get_platform_dir(ndk_dir, api, ['arch-x86', 'usr', 'lib'])) 157 158 targets['x86_64'] = Target( 159 'x86_64', 'x86_64-linux-android', ['-m64'], ['-m64'], 160 get_gcc_dir(ndk_dir, 'x86_64-4.9', host), 161 get_clang_dir(ndk_dir, host), 162 get_platform_dir(ndk_dir, api, ['arch-x86_64', 'usr', 'include']), 163 get_platform_dir(ndk_dir, api, ['arch-x86_64', 'usr', 'lib64'])) 164 165 targets['mips'] = Target( 166 'mips', 'mipsel-linux-android', [], [], 167 get_gcc_dir(ndk_dir, 'mipsel-linux-android-4.9', host), 168 get_clang_dir(ndk_dir, host), 169 get_platform_dir(ndk_dir, api, ['arch-mips', 'usr', 'include']), 170 get_platform_dir(ndk_dir, api, ['arch-mips', 'usr', 'lib'])) 171 172 targets['mips64'] = Target( 173 'mips64', 'mips64el-linux-android', 174 ['-march=mips64el', '-mcpu=mips64r6'], 175 ['-march=mips64el', '-mcpu=mips64r6'], 176 get_gcc_dir(ndk_dir, 'mips64el-linux-android-4.9', host), 177 get_clang_dir(ndk_dir, host), 178 get_platform_dir(ndk_dir, api, ['arch-mips64', 'usr', 'include']), 179 get_platform_dir(ndk_dir, api, ['arch-mips64', 'usr', 'lib64'])) 180 181 return targets 182 183 def main(): 184 parser = argparse.ArgumentParser( 185 description='Dry-run NDK toolchain detection') 186 parser.add_argument('--ndk-dir') 187 parser.add_argument('--api-level') 188 parser.add_argument('--host') 189 args = parser.parse_args() 190 191 targets = create_targets(args.ndk_dir, args.api_level, args.host) 192 193 success = True 194 for name, target in targets.items(): 195 success &= target.check_paths() 196 if not success: 197 sys.exit(1) 198 199 print('succeed') 200 201 if __name__ == '__main__': 202 main() 203